diff --git a/.cargo/config b/.cargo/config.toml
similarity index 73%
rename from .cargo/config
rename to .cargo/config.toml
index 544ab69523..4aa93e2080 100644
--- a/.cargo/config
+++ b/.cargo/config.toml
@@ -6,14 +6,21 @@ test-gen-llvm-wasm = "test -p roc_gen_wasm -p test_gen --no-default-features --f
[target.wasm32-unknown-unknown]
# Rust compiler flags for minimum-sized .wasm binary in the web REPL
-# opt-level=s Optimizations should focus more on size than speed
-# lto=fat Spend extra effort on link-time optimization across crates
-rustflags = ["-Copt-level=s", "-Clto=fat"]
+# opt-level=s Optimizations should focus more on size than speed
+# lto=fat Spend extra effort on link-time optimization across crates
+# embed-bitcode=yes Turn back on lto since it is no longer default
+rustflags = ["-Copt-level=s", "-Clto=fat", "-Cembed-bitcode=yes"]
[target.'cfg(not(target = "wasm32-unknown-unknown"))']
# Sets the avx, avx2, sse2 and sse4.2 target-features correctly based on your CPU.
rustflags = ["-Ctarget-cpu=native"]
+# TODO: there is probably a more proper solution to this.
+# We are pulling in roc_alloc and friends due to using roc_std.
+# They ared defined in roc_glue, but windows linking breaks before we get there.
+[target.'cfg(target_os = "windows")']
+rustflags = ["-Clink-args=/FORCE:UNRESOLVED"]
+
[env]
# Gives us the path of the workspace root for use in cargo tests without having
# to compute it per-package.
diff --git a/.github/workflows/basic_cli_build_release.yml b/.github/workflows/basic_cli_build_release.yml
index b9243dceed..df0e1966c4 100644
--- a/.github/workflows/basic_cli_build_release.yml
+++ b/.github/workflows/basic_cli_build_release.yml
@@ -1,4 +1,5 @@
-on: [workflow_dispatch]
+on:
+ workflow_dispatch:
# this cancels workflows currently in progress if you start a new one
concurrency:
@@ -6,48 +7,59 @@ concurrency:
cancel-in-progress: true
jobs:
-
- build-linux-x86_64-files:
+ fetch-releases:
runs-on: [ubuntu-20.04]
steps:
- uses: actions/checkout@v3
- # note: moving this step to a bash script will not work, the GITHUB_TOKEN is not passed properly
- name: Fetch releases data and save to file. Authorization is used to prevent rate limiting.
+ env:
+ AUTH_HEADER: 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}'
run: |
- curl --request GET \
- --url https://api.github.com/repos/roc-lang/roc/releases \
- --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
- --header 'content-type: application/json' \
- --output roc_releases.json
+ ./ci/get_releases_json.sh
- # does a build with the surgical linker and also with the legacy linker
- - run: ./ci/build_basic_cli.sh linux_x86_64 "--linker legacy"
+ - run: curl -OL $(./ci/get_latest_release_url.sh linuxTESTING_x86_64)
+ - run: curl -OL $(./ci/get_latest_release_url.sh macosTESTING_x86_64)
+ - run: curl -OL $(./ci/get_latest_release_url.sh macosTESTING_apple_silicon)
- - name: Save .rh1, .rm2 and .o file
+ - name: Save roc_nightly archives
+ uses: actions/upload-artifact@v3
+ with:
+ path: roc_nightly-*
+
+ build-linux-x86_64-files:
+ runs-on: [ubuntu-20.04]
+ needs: [fetch-releases]
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Download the previously uploaded roc_nightly archives
+ uses: actions/download-artifact@v3
+
+ - name: build basic-cli with surgical linker and also with legacy linker
+ env:
+ CARGO_BUILD_TARGET: x86_64-unknown-linux-musl
+ run: ./ci/build_basic_cli.sh linuxTESTING_x86_64 "--linker legacy"
+
+ - name: Save .rh, .rm and .o file
uses: actions/upload-artifact@v3
with:
name: linux-x86_64-files
path: |
- basic-cli/src/metadata_linux-x86_64.rm2
- basic-cli/src/linux-x86_64.rh1
+ basic-cli/src/metadata_linux-x86_64.rm
+ basic-cli/src/linux-x86_64.rh
basic-cli/src/linux-x86_64.o
build-macos-x86_64-files:
runs-on: [macos-11] # I expect the generated files to work on macOS 12
+ needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- # note: moving this step to a bash script will not work, the GITHUB_TOKEN is not passed properly
- - name: Fetch releases data and save to file. Authorization is used to prevent rate limiting due to shared IP of github's macos ci servers.
- run: |
- curl --request GET \
- --url https://api.github.com/repos/roc-lang/roc/releases \
- --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
- --header 'content-type: application/json' \
- --output roc_releases.json
+ - name: Download the previously uploaded roc_nightly archives
+ uses: actions/download-artifact@v3
- - run: ./ci/build_basic_cli.sh macos_x86_64
+ - run: ./ci/build_basic_cli.sh macosTESTING_x86_64
- name: Save .o files
uses: actions/upload-artifact@v3
@@ -59,13 +71,14 @@ jobs:
build-macos-apple-silicon-files:
name: build apple silicon .o file
runs-on: [self-hosted, macOS, ARM64]
+ needs: [fetch-releases]
steps:
- uses: actions/checkout@v3
- - name: fetch releases data and save to file
- run: curl https://api.github.com/repos/roc-lang/roc/releases > roc_releases.json
+ - name: Download the previously uploaded roc_nightly archives
+ uses: actions/download-artifact@v3
- - run: ./ci/build_basic_cli.sh silicon
+ - run: ./ci/build_basic_cli.sh macosTESTING_apple_silicon
- name: Save macos-arm64.o file
uses: actions/upload-artifact@v3
@@ -84,43 +97,36 @@ jobs:
- name: remove all folders except the ci folder
run: ls | grep -v ci | xargs rm -rf
- # note: moving this step to a bash script will not work, the GITHUB_TOKEN is not passed properly
- - name: Fetch releases data and save to file. Authorization is used to prevent rate limiting.
- run: |
- curl --request GET \
- --url https://api.github.com/repos/roc-lang/roc/releases \
- --header 'authorization: Bearer ${{ secrets.GITHUB_TOKEN }}' \
- --header 'content-type: application/json' \
- --output roc_releases.json
+ - name: Download the previously uploaded files
+ uses: actions/download-artifact@v3
- - run: echo "ROC_RELEASE_URL=$(./ci/get_latest_release_url.sh linux_x86_64)" >> $GITHUB_ENV
-
- - name: Get the archive from the url.
- run: curl -OL ${{ env.ROC_RELEASE_URL }}
+ - name: mv roc nightly and simplify name
+ run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz
- name: decompress the tar
- run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
+ run: tar -xzvf roc_nightly.tar.gz
- name: delete tar
- run: ls | grep -v "roc_nightly.*tar\.gz" | xargs rm -rf
+ run: rm roc_nightly.tar.gz
- name: rename nightly folder
run: mv roc_nightly* roc_nightly
- run: git clone https://github.com/roc-lang/basic-cli.git
- - name: Download the previously uploaded files
- uses: actions/download-artifact@v3
-
- run: cp macos-apple-silicon-files/* ./basic-cli/src
- run: cp linux-x86_64-files/* ./basic-cli/src
- run: cp macos-x86_64-files/* ./basic-cli/src
- - run: ./roc_nightly/roc build --bundle=.tar.br ./basic-cli/src/main.roc
+ # change to tar.gz for fast test build
+ - run: |
+ echo "ARCHIVE_FORMAT='.tar.br'" >> "$GITHUB_ENV"
- - run: echo "TAR_FILENAME=$(ls -d basic-cli/src/* | grep '.tar.br')" >> $GITHUB_ENV
+ - run: ./roc_nightly/roc build --bundle=${{ env.ARCHIVE_FORMAT }} ./basic-cli/src/main.roc
+
+ - run: echo "TAR_FILENAME=$(ls -d basic-cli/src/* | grep ${{ env.ARCHIVE_FORMAT }})" >> $GITHUB_ENV
- name: Upload platform archive
uses: actions/upload-artifact@v3
@@ -129,3 +135,43 @@ jobs:
path: |
${{ env.TAR_FILENAME }}
+ test-release-ubuntu:
+ needs: [create-brotli-archive]
+ runs-on: [ubuntu-20.04]
+ steps:
+
+ - name: Download the previously uploaded files
+ uses: actions/download-artifact@v3
+
+ - name: mv roc nightly and simplify name
+ run: mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "linux") ./roc_nightly.tar.gz
+
+ - name: decompress the tar
+ run: tar -xzvf roc_nightly.tar.gz
+
+ - name: delete tar
+ run: rm roc_nightly.tar.gz
+
+ - name: rename nightly folder
+ run: mv roc_nightly* roc_nightly
+
+ - run: cd basic-cli-platform && ls | grep "tar" | xargs tar -xzf
+
+ - name: prep testing http-get.roc
+ run: |
+ mv roc_nightly basic-cli-platform/.
+ cd basic-cli-platform
+ mkdir examples
+ cd examples
+ curl -OL https://raw.githubusercontent.com/roc-lang/basic-cli/main/examples/http-get.roc
+ sed -i 's/pf:\ \"[^"]*/pf:\ \"\.\.\/main.roc/g' http-get.roc
+ cd ..
+ curl -OL https://raw.githubusercontent.com/roc-lang/basic-cli/main/ci/expect_scripts/http-get.exp
+
+ - run: sudo apt install -y expect
+
+ - name: execute test
+ run: |
+ cd basic-cli-platform
+ expect http-get.exp
+
diff --git a/.github/workflows/macos_x86_64.yml b/.github/workflows/macos_x86_64.yml
index 286c69bea4..8d112a2a53 100644
--- a/.github/workflows/macos_x86_64.yml
+++ b/.github/workflows/macos_x86_64.yml
@@ -25,9 +25,5 @@ jobs:
# swift tests are skipped because of "Could not find or use auto-linked library 'swiftCompatibilityConcurrency'" on macos-11 x86_64 CI machine
# this issue may be caused by using older versions of XCode
- # TODO build basic-cli for macos 11
- #- name: test examples/helloWorld.roc separately because it is ignored by default
- # run: cargo test --locked --release -p roc_cli cli_run::hello_world -- --ignored && sccache --show-stats
-
- name: test launching the editor
run: cargo test --release --locked editor_launch_test::launch -- --ignored # `--ignored` to run this test that is ignored for "normal" runs
diff --git a/.github/workflows/nightly_linux_x86_64.yml b/.github/workflows/nightly_linux_x86_64.yml
index cf464875ad..6e506b8c82 100644
--- a/.github/workflows/nightly_linux_x86_64.yml
+++ b/.github/workflows/nightly_linux_x86_64.yml
@@ -16,9 +16,9 @@ jobs:
- name: create version.txt
run: ./ci/write_version.sh
- - name: build release
- run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked
- # target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
+ - name: build release with lto
+ run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --features "editor" --bin roc
+ # target-cpu=x86-64 -> For maximal compatibility for all CPU's. This was also faster in our tests: https://roc.zulipchat.com/#narrow/stream/231635-compiler-development/topic/.2Ecargo.2Fconfig.2Etoml/near/325726299
- name: get commit SHA
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
@@ -41,6 +41,10 @@ jobs:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-linux_x86_64-$DATE-$SHA" >> $GITHUB_ENV
+
+ # this makes the roc binary a lot smaller
+ - name: strip debug info
+ run: strip ./target/release-with-lto/roc
- name: Make nightly release tar archive
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
diff --git a/.github/workflows/nightly_macos_apple_silicon.yml b/.github/workflows/nightly_macos_apple_silicon.yml
index 7bc89abef5..3c0e70fd23 100644
--- a/.github/workflows/nightly_macos_apple_silicon.yml
+++ b/.github/workflows/nightly_macos_apple_silicon.yml
@@ -36,7 +36,11 @@ jobs:
run: ./ci/write_version.sh
- name: build nightly release
- run: cargo build --locked --release
+ run: cargo build --locked --profile=release-with-lto --features "editor" --bin roc
+
+ # this makes the roc binary a lot smaller
+ - name: strip debug info
+ run: strip ./target/release-with-lto/roc
- name: package release
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
diff --git a/.github/workflows/nightly_macos_x86_64.yml b/.github/workflows/nightly_macos_x86_64.yml
index 7b4f0e2eb0..43e5ce868f 100644
--- a/.github/workflows/nightly_macos_x86_64.yml
+++ b/.github/workflows/nightly_macos_x86_64.yml
@@ -22,7 +22,7 @@ jobs:
# this issue may be caused by using older versions of XCode
- name: build release
- run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked
+ run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --profile=release-with-lto --locked --features "editor" --bin roc
# target-cpu=x86-64 -> For maximal compatibility for all CPU's. Note that this setting will likely make the compiler slower.
- name: get commit SHA
@@ -36,6 +36,10 @@ jobs:
DATE: ${{ env.DATE }}
SHA: ${{ env.SHA }}
run: echo "RELEASE_FOLDER_NAME=roc_nightly-macos_x86_64-$DATE-$SHA" >> $GITHUB_ENV
+
+ # this makes the roc binary a lot smaller
+ - name: strip debug info
+ run: strip ./target/release-with-lto/roc
- name: package release
run: ./ci/package_release.sh ${{ env.RELEASE_FOLDER_NAME }}
diff --git a/.github/workflows/nix_macos_x86_64.yml b/.github/workflows/nix_macos_x86_64.yml
index 755ebb608c..03fb59e743 100644
--- a/.github/workflows/nix_macos_x86_64.yml
+++ b/.github/workflows/nix_macos_x86_64.yml
@@ -19,13 +19,7 @@ jobs:
with:
clean: "true"
- - uses: cachix/install-nix-action@v15
-
- # to cache nix packages
- - uses: cachix/cachix-action@v10
- with:
- name: enigmaticsunrise
- authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}'
+ - uses: cachix/install-nix-action@v20
- name: execute cli_run tests only, the full tests take too long but are run nightly
run: nix develop -c cargo test --locked --release -p roc_cli
diff --git a/.github/workflows/test_nightly_macos_apple_silicon.yml b/.github/workflows/test_nightly_macos_apple_silicon.yml
index f423acf746..a09549e471 100644
--- a/.github/workflows/test_nightly_macos_apple_silicon.yml
+++ b/.github/workflows/test_nightly_macos_apple_silicon.yml
@@ -1,7 +1,5 @@
on:
workflow_dispatch:
- schedule:
- - cron: '0 13 * * *'
name: Test latest nightly release for macOS Apple Silicon
@@ -35,16 +33,16 @@ jobs:
run: mv roc_nightly* roc_nightly
- name: test roc hello world
- run: ./roc_nightly/roc examples/helloWorld.roc
+ run: cd roc_nightly && ./roc examples/helloWorld.roc
- name: test platform switching rust
- run: ./roc_nightly/roc examples/platform-switching/rocLovesRust.roc
+ run: cd roc_nightly && ./roc examples/platform-switching/rocLovesRust.roc
- name: test platform switching zig
- run: ./roc_nightly/roc examples/platform-switching/rocLovesZig.roc
+ run: cd roc_nightly && ./roc examples/platform-switching/rocLovesZig.roc
- name: test platform switching c
- run: ./roc_nightly/roc examples/platform-switching/rocLovesC.roc
+ run: cd roc_nightly && ./roc examples/platform-switching/rocLovesC.roc
diff --git a/.github/workflows/test_nightly_many_os.yml b/.github/workflows/test_nightly_many_os.yml
index 57170bb7cb..e9b6f536ad 100644
--- a/.github/workflows/test_nightly_many_os.yml
+++ b/.github/workflows/test_nightly_many_os.yml
@@ -1,7 +1,5 @@
on:
workflow_dispatch:
- schedule:
- - cron: '0 13 * * *'
name: Test latest nightly release for macOS, ubu 20.04, ubu 22.04 x86_64
@@ -52,10 +50,10 @@ jobs:
run: mv roc_nightly* roc_nightly
- name: test roc hello world
- run: ./roc_nightly/roc examples/helloWorld.roc
+ run: cd roc_nightly && ./roc examples/helloWorld.roc
- name: test platform switching rust
- run: ./roc_nightly/roc examples/platform-switching/rocLovesRust.roc
+ run: cd roc_nightly && ./roc examples/platform-switching/rocLovesRust.roc
- name: get OS to use for zig download
if: startsWith(matrix.os, 'ubuntu')
@@ -73,10 +71,10 @@ jobs:
run: zig version
- name: test platform switching zig
- run: ./roc_nightly/roc examples/platform-switching/rocLovesZig.roc
+ run: cd roc_nightly && ./roc examples/platform-switching/rocLovesZig.roc
- name: test platform switching c
- run: ./roc_nightly/roc examples/platform-switching/rocLovesC.roc
+ run: cd roc_nightly && ./roc examples/platform-switching/rocLovesC.roc
diff --git a/.github/workflows/ubuntu_x86_64.yml b/.github/workflows/ubuntu_x86_64.yml
index df756ddd52..fd7f81ddf0 100644
--- a/.github/workflows/ubuntu_x86_64.yml
+++ b/.github/workflows/ubuntu_x86_64.yml
@@ -36,12 +36,6 @@ jobs:
- name: regular rust tests
run: cargo test --locked --release && sccache --show-stats
- - name: test hellow_world separately because it is ignored by default, the use of url platforms causes issues within nix
- run: cargo test --locked --release -p roc_cli cli_run::hello_world -- --ignored && sccache --show-stats
-
- - name: test parse_letter_counts separately because it is ignored by default, the use of url platforms causes issues within nix
- run: cargo test --locked --release -p roc_cli cli_run::parse_letter_counts -- --ignored && sccache --show-stats
-
- name: check that the platform`s produced dylib is loadable
run: cd examples/platform-switching/rust-platform && LD_LIBRARY_PATH=. cargo test --release --locked
diff --git a/.github/workflows/windows_release_build.yml b/.github/workflows/windows_release_build.yml
new file mode 100644
index 0000000000..b095c1205b
--- /dev/null
+++ b/.github/workflows/windows_release_build.yml
@@ -0,0 +1,44 @@
+on: [pull_request]
+
+name: windows - release build
+
+concurrency:
+ group: ${{ github.workflow }}-${{ github.ref }}
+ cancel-in-progress: true
+
+env:
+ RUST_BACKTRACE: 1
+
+jobs:
+ windows-release-build:
+ name: windows-release-build
+ runs-on: windows-2022
+ env:
+ LLVM_SYS_130_PREFIX: C:\LLVM-13.0.1-win64
+
+ timeout-minutes: 150
+ steps:
+ - uses: actions/checkout@v2
+
+ - run: Add-Content -Path "$env:GITHUB_ENV" -Value "GITHUB_RUNNER_CPU=$((Get-CimInstance Win32_Processor).Name)"
+
+ - name: download and install zig
+ run: |
+ curl.exe --output "C:\zig-windows-x86_64-0.9.1.zip" --url https://ziglang.org/download/0.9.1/zig-windows-x86_64-0.9.1.zip
+ cd C:\
+ 7z x zig-windows-x86_64-0.9.1.zip
+ Add-Content $env:GITHUB_PATH "C:\zig-windows-x86_64-0.9.1\"
+
+ - name: zig version
+ run: zig version
+
+ - name: install rust nightly 1.65
+ run: rustup install nightly-2022-09-17
+
+ - name: set up llvm 13
+ run: |
+ curl.exe -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z
+ 7z x LLVM-13.0.1-win64.7z -oC:\LLVM-13.0.1-win64
+
+ - name: cargo build release. Twice for zig lld-link error.
+ run: cargo build --locked --release || cargo build --locked --release
\ No newline at end of file
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows_tests.yml
similarity index 87%
rename from .github/workflows/windows.yml
rename to .github/workflows/windows_tests.yml
index d45f7a3eb6..3388162109 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows_tests.yml
@@ -1,6 +1,6 @@
on: [pull_request]
-name: Test windows build
+name: windows - subset of tests
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -10,8 +10,8 @@ env:
RUST_BACKTRACE: 1
jobs:
- windows-cargo-build:
- name: windows-cargo-build
+ windows-test-subset:
+ name: windows-test-subset
runs-on: windows-2022
env:
LLVM_SYS_130_PREFIX: C:\LLVM-13.0.1-win64
@@ -41,7 +41,7 @@ jobs:
- name: set up llvm 13
run: |
- curl.exe -L -O https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z
+ curl.exe -L -O -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z
7z x LLVM-13.0.1-win64.7z -oC:\LLVM-13.0.1-win64
- name: Build tests --release without running. Twice for zig lld-link error.
diff --git a/.gitignore b/.gitignore
index d65db52827..62c229150a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,10 @@ zig-cache
.envrc
*.rs.bk
*.o
+*.so
*.obj
*.dll
+*.dylib
*.lib
*.def
*.tmp
@@ -22,11 +24,10 @@ zig-cache
vgcore.*
# roc cache files
-*.rh1
-*.rm1
+*.rh*
+*.rm*
preprocessedhost
metadata
-roc-cheaty-lib.so
#editors
.idea/
@@ -72,4 +73,11 @@ roc_linux_x86_64.tar.gz
result
# tutorial
-www/src/roc-tutorial
\ No newline at end of file
+www/src/roc-tutorial
+
+# Only keep Cargo.lock dependencies for the main compiler.
+# Examples and test only crates should be fine to be unlocked.
+# This remove unneccessary lock file versioning.
+# It also ensures the compiler can always pull in 1 version of things and doesn't get restricted by sub lockfiles.
+/**/Cargo.lock
+!/Cargo.lock
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 81662fe873..8614af2132 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -50,18 +50,32 @@ This command will generate the documentation in the [`generated-docs`](generated
- You can find good first issues [here][good-first-issues]. Once you have gained some experience you can take a look at the [intermediate issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22intermediate+issue%22).
- [Fork](https://github.com/roc-lang/roc/fork) the repo so that you can apply your changes first on your own copy of the roc repo.
- It's a good idea to open a draft 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. Click the button "ready for review" when it's ready.
+
+### Commit signing
+
- All your commits need to be signed [to prevent impersonation](https://dev.to/martiliones/how-i-got-linus-torvalds-in-my-contributors-on-github-3k4g):
- - If you don't have signing set up on your device and you only want to change a single file, it will be easier to use [github's edit button](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files). This will sign your commit automatically.
- - For multi-file or complex changes you will want to set up signing on your device:
- 1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.
- 2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
- 3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
- 4. Make git sign your commits automatically:
+- If you don't have signing set up on your device and you only want to change a single file, it will be easier to use [github's edit button](https://docs.github.com/en/repositories/working-with-files/managing-files/editing-files). This will sign your commit automatically.
+- For multi-file or complex changes you will want to set up signing on your device:
+ 1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.
+ 2. [Make a key to sign your commits.](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key)
+ 3. [Configure git to use your key.](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)
+ 4. Make git sign your commits automatically:
```sh
git config --global commit.gpgsign true
```
+#### Commit signing on NixOS
+
+On NixOS pinentry can cause problems, the following setup works well for those with a KDE desktop. From `/etc/nixos/configuration.nix`:
+```
+programs.gnupg.agent = {
+ enable = true;
+ pinentryFlavor = "qt";
+ enableSSHSupport = true;
+ };
+```
+
### Forgot to sign commits?
You can view your commits on github, those without the "Verified" badge still need to be signed.
diff --git a/Cargo.lock b/Cargo.lock
index 6e46a850ec..21f33166df 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "ab_glyph"
-version = "0.2.15"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24606928a235e73cdef55a0c909719cadd72fce573e5713d58cb2952d8f5794c"
+checksum = "fe21446ad43aa56417a767f3e2f3d7c4ca522904de1dd640529a76e9c5c3b33c"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser",
@@ -14,15 +14,15 @@ dependencies = [
[[package]]
name = "ab_glyph_rasterizer"
-version = "0.1.5"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a13739d7177fbd22bb0ed28badfff9f372f8bef46c863db4e1c6248f6b223b6e"
+checksum = "c71b1793ee61086797f5c80b6efa2b8ffa6d5dd703f118545808a7f2e27f7046"
[[package]]
name = "addr2line"
-version = "0.17.0"
+version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
+checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
"gimli",
]
@@ -45,14 +45,34 @@ dependencies = [
]
[[package]]
-name = "aho-corasick"
-version = "0.7.18"
+name = "ahash"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
+checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f"
+dependencies = [
+ "cfg-if 1.0.0",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "aho-corasick"
+version = "0.7.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
+[[package]]
+name = "aligned"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80a21b9440a626c7fc8573a9e3d3a06b75c7c97754c2949bc7857b90353ca655"
+dependencies = [
+ "as-slice",
+]
+
[[package]]
name = "alloc-no-stdlib"
version = "2.0.4"
@@ -68,28 +88,6 @@ dependencies = [
"alloc-no-stdlib",
]
-[[package]]
-name = "alsa"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5915f52fe2cf65e83924d037b6c5290b7cee097c6b5c8700746e6168a343fd6b"
-dependencies = [
- "alsa-sys",
- "bitflags",
- "libc",
- "nix 0.23.1",
-]
-
-[[package]]
-name = "alsa-sys"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
-dependencies = [
- "libc",
- "pkg-config",
-]
-
[[package]]
name = "approx"
version = "0.4.0"
@@ -133,6 +131,15 @@ version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+[[package]]
+name = "as-slice"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516"
+dependencies = [
+ "stable_deref_trait",
+]
+
[[package]]
name = "ash"
version = "0.34.0+1.2.203"
@@ -148,7 +155,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.1.19",
"libc",
"winapi",
]
@@ -161,9 +168,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
-version = "0.3.66"
+version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
+checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
"addr2line",
"cc",
@@ -180,13 +187,19 @@ version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
+[[package]]
+name = "base64"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
+
[[package]]
name = "base64-url"
version = "1.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67a99c239d0c7e77c85dddfa9cebce48704b3c49550fcd3b84dd637e4484899f"
dependencies = [
- "base64",
+ "base64 0.13.1",
]
[[package]]
@@ -198,30 +211,11 @@ dependencies = [
"serde",
]
-[[package]]
-name = "bindgen"
-version = "0.59.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8"
-dependencies = [
- "bitflags",
- "cexpr",
- "clang-sys",
- "lazy_static",
- "lazycell",
- "peeking_take_while",
- "proc-macro2",
- "quote",
- "regex",
- "rustc-hash",
- "shlex",
-]
-
[[package]]
name = "bit-set"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
+checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
@@ -247,28 +241,16 @@ dependencies = [
"typenum",
]
-[[package]]
-name = "bitvec"
-version = "0.22.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527"
-dependencies = [
- "funty 1.2.0",
- "radium 0.6.2",
- "tap",
- "wyz 0.4.0",
-]
-
[[package]]
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
- "funty 2.0.0",
- "radium 0.7.0",
+ "funty",
+ "radium",
"tap",
- "wyz 0.5.0",
+ "wyz",
]
[[package]]
@@ -293,9 +275,9 @@ checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a"
[[package]]
name = "block-buffer"
-version = "0.10.2"
+version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
+checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e"
dependencies = [
"generic-array",
]
@@ -313,46 +295,34 @@ dependencies = [
[[package]]
name = "brotli-decompressor"
-version = "2.3.2"
+version = "2.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
+checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744"
dependencies = [
"alloc-no-stdlib",
"alloc-stdlib",
]
-[[package]]
-name = "bstr"
-version = "0.2.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
-dependencies = [
- "lazy_static",
- "memchr",
- "regex-automata",
- "serde",
-]
-
[[package]]
name = "bumpalo"
-version = "3.11.1"
+version = "3.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba"
+checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
[[package]]
name = "bytemuck"
-version = "1.12.1"
+version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
+checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea"
dependencies = [
"bytemuck_derive",
]
[[package]]
name = "bytemuck_derive"
-version = "1.2.1"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9"
+checksum = "1aca418a974d83d40a0c1f0c5cba6ff4bc28d8df099109ca459a2118d40b6322"
dependencies = [
"proc-macro2",
"quote",
@@ -367,9 +337,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
-version = "1.1.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "calloop"
@@ -412,27 +382,9 @@ dependencies = [
[[package]]
name = "cc"
-version = "1.0.73"
+version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
-dependencies = [
- "jobserver",
-]
-
-[[package]]
-name = "cesu8"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c"
-
-[[package]]
-name = "cexpr"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766"
-dependencies = [
- "nom",
-]
+checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
@@ -462,17 +414,6 @@ dependencies = [
"num-traits",
]
-[[package]]
-name = "clang-sys"
-version = "1.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b"
-dependencies = [
- "glob",
- "libc",
- "libloading",
-]
-
[[package]]
name = "clap"
version = "2.34.0"
@@ -486,32 +427,17 @@ dependencies = [
[[package]]
name = "clap"
-version = "3.2.20"
+version = "3.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
+checksum = "71655c45cb9845d3270c9d6df84ebe72b4dad3c2ba3f7023ad47c144e4e473a5"
dependencies = [
"atty",
"bitflags",
- "clap_derive",
"clap_lex",
"indexmap",
- "once_cell",
"strsim",
"termcolor",
- "textwrap 0.15.0",
-]
-
-[[package]]
-name = "clap_derive"
-version = "3.2.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
-dependencies = [
- "heck",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
+ "textwrap 0.16.0",
]
[[package]]
@@ -523,12 +449,6 @@ dependencies = [
"os_str_bytes",
]
-[[package]]
-name = "claxon"
-version = "0.4.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4bfbf56724aa9eca8afa4fcfadeb479e722935bb2a0900c2d37e0cc477af0688"
-
[[package]]
name = "cli_utils"
version = "0.0.1"
@@ -537,13 +457,12 @@ dependencies = [
"criterion",
"rlimit",
"roc_collections",
+ "roc_command_utils",
"roc_load",
"roc_module",
"roc_reporting",
- "roc_utils",
"serde",
"serde-xml-rs",
- "strip-ansi-escapes",
"tempfile",
]
@@ -559,9 +478,9 @@ dependencies = [
[[package]]
name = "clipboard-win"
-version = "4.4.1"
+version = "4.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f3e1238132dc01f081e1cbb9dace14e5ef4c3a51ee244bd982275fb514605db"
+checksum = "7191c27c2357d9b7ef96baac1773290d4ca63b24205b82a3fd8a0637afcf0362"
dependencies = [
"error-code",
"str-buf",
@@ -570,9 +489,9 @@ dependencies = [
[[package]]
name = "cocoa"
-version = "0.24.0"
+version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f63902e9223530efb4e26ccd0cf55ec30d592d3b42e21a28defc42a9586e832"
+checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a"
dependencies = [
"bitflags",
"block",
@@ -620,20 +539,10 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "combine"
-version = "4.6.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a604e93b79d1808327a6fca85a6f2d69de66461e7620f5a4cbf5fb4d1d7c948"
-dependencies = [
- "bytes",
- "memchr",
-]
-
[[package]]
name = "confy"
-version = "0.5.0"
-source = "git+https://github.com/rust-cli/confy#fd069f062aa3373c846f0d8c6e3b5e2a5cd0096b"
+version = "0.5.1"
+source = "git+https://github.com/rust-cli/confy#700337e5a3fbc12f4f49fc9b7ce449b1b71040ee"
dependencies = [
"directories",
"serde",
@@ -643,15 +552,14 @@ dependencies = [
[[package]]
name = "console"
-version = "0.15.1"
+version = "0.15.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
+checksum = "c3d79fbe8970a77e3e34151cc13d3b3e248aa0faaecb9f6091fa07ebefe5ad60"
dependencies = [
"encode_unicode 0.3.6",
+ "lazy_static",
"libc",
- "once_cell",
- "terminal_size",
- "winapi",
+ "windows-sys 0.42.0",
]
[[package]]
@@ -666,18 +574,18 @@ dependencies = [
[[package]]
name = "const_format"
-version = "0.2.26"
+version = "0.2.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "939dc9e2eb9077e0679d2ce32de1ded8531779360b003b4a972a7a39ec263495"
+checksum = "7309d9b4d3d2c0641e018d449232f2e28f1b22933c137f157d3dbc14228b8c0e"
dependencies = [
"const_format_proc_macros",
]
[[package]]
name = "const_format_proc_macros"
-version = "0.2.22"
+version = "0.2.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef196d5d972878a48da7decb7686eded338b4858fbabeed513d63a7c98b2b82d"
+checksum = "d897f47bf7270cf70d370f8f98c1abb6d2d4cf60a6845d30e05bfb90c6568650"
dependencies = [
"proc-macro2",
"quote",
@@ -698,9 +606,9 @@ checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
[[package]]
name = "copypasta"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7216b5c1e9ad3867252505995b02d01c6fa7e6db0d8abd42634352ef377777e"
+checksum = "133fc8675ee3a4ec9aa513584deda9aa0faeda3586b87f7f0f2ba082c66fb172"
dependencies = [
"clipboard-win 3.1.1",
"objc",
@@ -792,55 +700,11 @@ dependencies = [
"objc",
]
-[[package]]
-name = "coreaudio-rs"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11894b20ebfe1ff903cbdc52259693389eea03b94918a2def2c30c3bf227ad88"
-dependencies = [
- "bitflags",
- "coreaudio-sys",
-]
-
-[[package]]
-name = "coreaudio-sys"
-version = "0.2.10"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3dff444d80630d7073077d38d40b4501fd518bd2b922c2a55edcc8b0f7be57e6"
-dependencies = [
- "bindgen",
-]
-
-[[package]]
-name = "cpal"
-version = "0.13.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "74117836a5124f3629e4b474eed03e479abaf98988b4bb317e29f08cfe0e4116"
-dependencies = [
- "alsa",
- "core-foundation-sys 0.8.3",
- "coreaudio-rs",
- "jni",
- "js-sys",
- "lazy_static",
- "libc",
- "mach",
- "ndk 0.6.0",
- "ndk-glue 0.6.2",
- "nix 0.23.1",
- "oboe",
- "parking_lot 0.11.2",
- "stdweb",
- "thiserror",
- "web-sys",
- "winapi",
-]
-
[[package]]
name = "cpufeatures"
-version = "0.2.2"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
+checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320"
dependencies = [
"libc",
]
@@ -904,9 +768,9 @@ dependencies = [
[[package]]
name = "crossbeam-channel"
-version = "0.5.5"
+version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c"
+checksum = "cf2b3e8478797446514c91ef04bafcb59faba183e621ad488df88983cc14128c"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
@@ -914,9 +778,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
+checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
@@ -925,23 +789,22 @@ dependencies = [
[[package]]
name = "crossbeam-epoch"
-version = "0.9.9"
+version = "0.9.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d"
+checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695"
dependencies = [
"autocfg",
"cfg-if 1.0.0",
"crossbeam-utils",
- "memoffset",
- "once_cell",
+ "memoffset 0.8.0",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
-version = "0.3.5"
+version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
+checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils",
@@ -949,19 +812,18 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.10"
+version = "0.8.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83"
+checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b"
dependencies = [
"cfg-if 1.0.0",
- "once_cell",
]
[[package]]
name = "crypto-common"
-version = "0.1.5"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2ccfd8c0ee4cce11e45b3fd6f9d5e69e0cc62912aa6a0cb1bf4617b0eba5a12f"
+checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
@@ -969,13 +831,12 @@ dependencies = [
[[package]]
name = "csv"
-version = "1.1.6"
+version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1"
+checksum = "0b015497079b9a9d69c02ad25de6c0a6edef051ea6360a327d0bd05802ef64ad"
dependencies = [
- "bstr",
"csv-core",
- "itoa 0.4.8",
+ "itoa",
"ryu",
"serde",
]
@@ -991,9 +852,9 @@ dependencies = [
[[package]]
name = "ctor"
-version = "0.1.22"
+version = "0.1.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
+checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096"
dependencies = [
"quote",
"syn",
@@ -1005,6 +866,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35"
+[[package]]
+name = "cvt"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34ac344c7efccb80cd25bc61b2170aec26f2f693fd40e765a539a1243db48c71"
+dependencies = [
+ "cfg-if 0.1.10",
+]
+
[[package]]
name = "d3d12"
version = "0.4.1"
@@ -1053,14 +923,15 @@ dependencies = [
[[package]]
name = "dashmap"
-version = "5.3.4"
+version = "5.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f"
+checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc"
dependencies = [
"cfg-if 1.0.0",
"hashbrown 0.12.3",
"lock_api",
- "parking_lot_core 0.9.3",
+ "once_cell",
+ "parking_lot_core 0.9.7",
]
[[package]]
@@ -1071,9 +942,9 @@ checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "digest"
-version = "0.10.3"
+version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
+checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f"
dependencies = [
"block-buffer",
"crypto-common",
@@ -1082,9 +953,9 @@ dependencies = [
[[package]]
name = "dircpy"
-version = "0.3.13"
+version = "0.3.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73ff6269b47c0c5220a0ff5eb140424340276ec89a10e58cbd4cf366de52dfa9"
+checksum = "10b6622b9d0dc20c70e74ff24c56493278d7d9299ac8729deb923703616e5a7e"
dependencies = [
"jwalk",
"log",
@@ -1093,11 +964,10 @@ dependencies = [
[[package]]
name = "directories"
-version = "2.0.2"
+version = "4.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c"
+checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210"
dependencies = [
- "cfg-if 0.1.10",
"dirs-sys",
]
@@ -1174,9 +1044,9 @@ checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c"
[[package]]
name = "either"
-version = "1.8.0"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
+checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "encode_unicode"
@@ -1192,9 +1062,9 @@ checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
-version = "0.8.31"
+version = "0.8.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
+checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394"
dependencies = [
"cfg-if 1.0.0",
]
@@ -1226,6 +1096,17 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "errno"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0"
+dependencies = [
+ "errno-dragonfly",
+ "libc",
+ "windows-sys 0.45.0",
+]
+
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
@@ -1246,36 +1127,27 @@ dependencies = [
"str-buf",
]
-[[package]]
-name = "fastrand"
-version = "1.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
-dependencies = [
- "instant",
-]
-
[[package]]
name = "fd-lock"
-version = "3.0.6"
+version = "3.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e11dcc7e4d79a8c89b9ab4c6f5c30b1fc4a83c420792da3542fd31179ed5f517"
+checksum = "8ef1a30ae415c3a691a4f41afddc2dbcd6d70baf338368d85ebc1e8ed92cedb9"
dependencies = [
"cfg-if 1.0.0",
"rustix",
- "windows-sys 0.36.1",
+ "windows-sys 0.45.0",
]
[[package]]
name = "filetime"
-version = "0.2.18"
+version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b9663d381d07ae25dc88dbdf27df458faa83a9b25336bcac83d5e452b5fc9d3"
+checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
- "windows-sys 0.42.0",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -1289,9 +1161,9 @@ dependencies = [
[[package]]
name = "flate2"
-version = "1.0.24"
+version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841"
dependencies = [
"crc32fast",
"miniz_oxide",
@@ -1320,25 +1192,33 @@ checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "form_urlencoded"
-version = "1.0.1"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
+checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8"
dependencies = [
- "matches",
"percent-encoding",
]
[[package]]
-name = "fs_extra"
-version = "1.2.0"
+name = "fs_at"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
+checksum = "ab60b097d8208fe02d24ae954c3248a9436f96edefa8f4b9fcb0f26d60d003a9"
+dependencies = [
+ "aligned",
+ "cfg-if 1.0.0",
+ "cvt",
+ "libc",
+ "nix 0.26.2",
+ "smart-default",
+ "windows-sys 0.45.0",
+]
[[package]]
-name = "funty"
-version = "1.2.0"
+name = "fs_extra"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e"
+checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]]
name = "funty"
@@ -1348,9 +1228,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "futures"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
+checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84"
dependencies = [
"futures-channel",
"futures-core",
@@ -1363,9 +1243,9 @@ dependencies = [
[[package]]
name = "futures-channel"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
+checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5"
dependencies = [
"futures-core",
"futures-sink",
@@ -1373,15 +1253,15 @@ dependencies = [
[[package]]
name = "futures-core"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
+checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608"
[[package]]
name = "futures-executor"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
+checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e"
dependencies = [
"futures-core",
"futures-task",
@@ -1390,15 +1270,15 @@ dependencies = [
[[package]]
name = "futures-io"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
+checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531"
[[package]]
name = "futures-macro"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
+checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70"
dependencies = [
"proc-macro2",
"quote",
@@ -1407,21 +1287,21 @@ dependencies = [
[[package]]
name = "futures-sink"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
+checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364"
[[package]]
name = "futures-task"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
+checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366"
[[package]]
name = "futures-util"
-version = "0.3.24"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
+checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1"
dependencies = [
"futures-channel",
"futures-core",
@@ -1446,36 +1326,42 @@ dependencies = [
[[package]]
name = "generic-array"
-version = "0.14.5"
+version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803"
+checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9"
dependencies = [
"typenum",
"version_check",
]
[[package]]
-name = "getrandom"
-version = "0.2.7"
+name = "gethostname"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if 1.0.0",
+ "js-sys",
"libc",
"wasi",
+ "wasm-bindgen",
]
[[package]]
name = "gimli"
-version = "0.26.1"
+version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
-
-[[package]]
-name = "glob"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
[[package]]
name = "glow"
@@ -1491,13 +1377,12 @@ dependencies = [
[[package]]
name = "glyph_brush"
-version = "0.7.5"
+version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac02497410cdb5062cc056a33f2e1e19ff69fbf26a4be9a02bf29d6e17ea105b"
+checksum = "4edefd123f28a0b1d41ec4a489c2b43020b369180800977801611084f342978d"
dependencies = [
"glyph_brush_draw_cache",
"glyph_brush_layout",
- "log",
"ordered-float",
"rustc-hash",
"twox-hash",
@@ -1549,13 +1434,13 @@ dependencies = [
[[package]]
name = "gpu-descriptor"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a538f217be4d405ff4719a283ca68323cc2384003eca5baaa87501e821c81dda"
+checksum = "0b0c02e1ba0bdb14e965058ca34e09c020f8e507a760df1121728e0aef68d57a"
dependencies = [
"bitflags",
"gpu-descriptor-types",
- "hashbrown 0.11.2",
+ "hashbrown 0.12.3",
]
[[package]]
@@ -1569,9 +1454,9 @@ dependencies = [
[[package]]
name = "h2"
-version = "0.3.15"
+version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4"
+checksum = "5be7b54589b581f624f566bf5d8eb2bab1db736c51528720b6bd36b96b55924d"
dependencies = [
"bytes",
"fnv",
@@ -1592,30 +1477,30 @@ version = "1.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
-[[package]]
-name = "hashbrown"
-version = "0.11.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
-dependencies = [
- "ahash",
-]
-
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
- "ahash",
+ "ahash 0.7.6",
+]
+
+[[package]]
+name = "hashbrown"
+version = "0.13.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
+dependencies = [
+ "ahash 0.8.3",
"bumpalo",
]
[[package]]
name = "heck"
-version = "0.4.0"
+version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
+checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
@@ -1626,6 +1511,15 @@ dependencies = [
"libc",
]
+[[package]]
+name = "hermit-abi"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
+dependencies = [
+ "libc",
+]
+
[[package]]
name = "hexf-parse"
version = "0.2.1"
@@ -1633,20 +1527,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
-name = "hound"
-version = "3.4.0"
+name = "html-escape"
+version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a164bb2ceaeff4f42542bdb847c41517c78a60f5649671b2a07312b6e117549"
+checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476"
+dependencies = [
+ "utf8-width",
+]
[[package]]
name = "http"
-version = "0.2.8"
+version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399"
+checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [
"bytes",
"fnv",
- "itoa 1.0.2",
+ "itoa",
]
[[package]]
@@ -1674,9 +1571,9 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
-version = "0.14.23"
+version = "0.14.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c"
+checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
dependencies = [
"bytes",
"futures-channel",
@@ -1687,7 +1584,7 @@ dependencies = [
"http-body",
"httparse",
"httpdate",
- "itoa 1.0.2",
+ "itoa",
"pin-project-lite",
"socket2",
"tokio",
@@ -1698,9 +1595,9 @@ dependencies = [
[[package]]
name = "hyper-rustls"
-version = "0.23.1"
+version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59df7c4e19c950e6e0e868dcc0a300b09a9b88e9ec55bd879ca819087a77355d"
+checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c"
dependencies = [
"http",
"hyper",
@@ -1711,12 +1608,11 @@ dependencies = [
[[package]]
name = "iced-x86"
-version = "1.17.0"
+version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "158f5204401d08f91d19176112146d75e99b3cf745092e268fa7be33e09adcec"
+checksum = "1dd04b950d75b3498320253b17fb92745b2cc79ead8814aede2f7c1bab858bec"
dependencies = [
"lazy_static",
- "static_assertions",
]
[[package]]
@@ -1727,11 +1623,10 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
-version = "0.2.3"
+version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
+checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6"
dependencies = [
- "matches",
"unicode-bidi",
"unicode-normalization",
]
@@ -1766,9 +1661,9 @@ dependencies = [
[[package]]
name = "indexmap"
-version = "1.9.1"
+version = "1.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399"
dependencies = [
"autocfg",
"hashbrown 0.12.3",
@@ -1776,14 +1671,14 @@ dependencies = [
[[package]]
name = "indoc"
-version = "1.0.7"
+version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3"
+checksum = "bfa799dd5ed20a7e349f3b4639aa80d74549c81716d9ec4f994c9b5815598306"
[[package]]
name = "inkwell"
version = "0.1.0"
-source = "git+https://github.com/roc-lang/inkwell?branch=master#9b63d543eaf996aa91fdeb20a2bc8b8558775648"
+source = "git+https://github.com/roc-lang/inkwell?branch=inkwell-llvm-15#95a3a13c99289539f5cdd91193a4def38711117d"
dependencies = [
"either",
"inkwell_internals",
@@ -1795,8 +1690,8 @@ dependencies = [
[[package]]
name = "inkwell_internals"
-version = "0.5.0"
-source = "git+https://github.com/roc-lang/inkwell?branch=master#9b63d543eaf996aa91fdeb20a2bc8b8558775648"
+version = "0.7.0"
+source = "git+https://github.com/roc-lang/inkwell?branch=inkwell-llvm-15#95a3a13c99289539f5cdd91193a4def38711117d"
dependencies = [
"proc-macro2",
"quote",
@@ -1805,19 +1700,19 @@ dependencies = [
[[package]]
name = "inplace_it"
-version = "0.3.3"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca"
+checksum = "e567468c50f3d4bc7397702e09b380139f9b9288b4e909b070571007f8b5bf78"
[[package]]
name = "insta"
-version = "1.20.0"
+version = "1.28.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58a931b01c76064c5be919faa2ef0dc570e9a889dcd1e5fef08a8ca6eb4d6c0b"
+checksum = "fea5b3894afe466b4bcf0388630fc15e11938a6074af0cd637c825ba2ec8a099"
dependencies = [
"console",
+ "lazy_static",
"linked-hash-map",
- "once_cell",
"similar",
"yaml-rust",
]
@@ -1836,15 +1731,19 @@ dependencies = [
[[package]]
name = "io-lifetimes"
-version = "0.7.2"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "24c3f4eff5495aee4c0399d7b6a0dc2b6e81be84242ffbfcf253ebacccc1d0cb"
+checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
+dependencies = [
+ "libc",
+ "windows-sys 0.45.0",
+]
[[package]]
name = "ipnet"
-version = "2.5.1"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f88c5561171189e69df9d98bcf18fd5f9558300f7ea7b801eb8a0fd748bd8745"
+checksum = "30e22bd8629359895450b59ea7a776c850561b96a3b1d31321c1949d9e6c9146"
[[package]]
name = "itertools"
@@ -1866,29 +1765,9 @@ dependencies = [
[[package]]
name = "itoa"
-version = "0.4.8"
+version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4"
-
-[[package]]
-name = "itoa"
-version = "1.0.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
-
-[[package]]
-name = "jni"
-version = "0.19.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec"
-dependencies = [
- "cesu8",
- "combine",
- "jni-sys",
- "log",
- "thiserror",
- "walkdir",
-]
+checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]]
name = "jni-sys"
@@ -1896,29 +1775,20 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
-[[package]]
-name = "jobserver"
-version = "0.1.24"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "js-sys"
-version = "0.3.60"
+version = "0.3.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
+checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730"
dependencies = [
"wasm-bindgen",
]
[[package]]
name = "jwalk"
-version = "0.6.0"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "172752e853a067cbce46427de8470ddf308af7fd8ceaf9b682ef31a5021b6bb9"
+checksum = "5dbcda57db8b6dc067e589628b7348639014e793d9e8137d8cf215e8b133a0bd"
dependencies = [
"crossbeam",
"rayon",
@@ -1946,46 +1816,36 @@ version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-[[package]]
-name = "lazycell"
-version = "1.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55"
-
-[[package]]
-name = "lewton"
-version = "0.10.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "777b48df9aaab155475a83a7df3070395ea1ac6902f5cd062b8f2b028075c030"
-dependencies = [
- "byteorder",
- "ogg",
- "tinyvec",
-]
-
[[package]]
name = "libc"
-version = "0.2.135"
+version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68783febc7782c6c5cb401fbda4de5a9898be1762314da0bb2c10ced61f18b0c"
+checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "libloading"
-version = "0.7.3"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd"
+checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f"
dependencies = [
"cfg-if 1.0.0",
"winapi",
]
[[package]]
-name = "libmimalloc-sys"
-version = "0.1.25"
+name = "libm"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11ca136052550448f55df7898c6dbe651c6b574fe38a0d9ea687a9f8088a2e2c"
+checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb"
+
+[[package]]
+name = "libmimalloc-sys"
+version = "0.1.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd8c7cbf8b89019683667e347572e6d55a7df7ea36b0c4ce69961b0cde67b174"
dependencies = [
"cc",
+ "libc",
]
[[package]]
@@ -1996,15 +1856,15 @@ checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "linux-raw-sys"
-version = "0.0.46"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d"
+checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "llvm-sys"
-version = "130.0.4"
+version = "130.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bdb6ea20e8a348f6db0b43a7f009fa7d981d22edf4cbe2e0c7b2247dbb25be61"
+checksum = "b54ec4a457c4b55ffb2bd56ed44841161ae933fd4fe3dc379748fcd4193661d4"
dependencies = [
"cc",
"lazy_static",
@@ -2015,9 +1875,9 @@ dependencies = [
[[package]]
name = "lock_api"
-version = "0.4.7"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53"
+checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
@@ -2032,15 +1892,6 @@ dependencies = [
"cfg-if 1.0.0",
]
-[[package]]
-name = "mach"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "mach_object"
version = "0.1.17"
@@ -2081,12 +1932,6 @@ dependencies = [
"regex-automata",
]
-[[package]]
-name = "matches"
-version = "0.1.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
-
[[package]]
name = "memchr"
version = "2.5.0"
@@ -2104,9 +1949,9 @@ dependencies = [
[[package]]
name = "memmap2"
-version = "0.5.7"
+version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
+checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327"
dependencies = [
"libc",
]
@@ -2120,6 +1965,15 @@ dependencies = [
"autocfg",
]
+[[package]]
+name = "memoffset"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1"
+dependencies = [
+ "autocfg",
+]
+
[[package]]
name = "metal"
version = "0.23.1"
@@ -2136,9 +1990,9 @@ dependencies = [
[[package]]
name = "mimalloc"
-version = "0.1.29"
+version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f64ad83c969af2e732e907564deb0d0ed393cec4af80776f77dd77a1a427698"
+checksum = "9dcb174b18635f7561a0c6c9fc2ce57218ac7523cf72c50af80e2d79ab8f3ba1"
dependencies = [
"libmimalloc-sys",
]
@@ -2155,52 +2009,33 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
-[[package]]
-name = "minimp3"
-version = "0.5.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "985438f75febf74c392071a975a29641b420dd84431135a6e6db721de4b74372"
-dependencies = [
- "minimp3-sys",
- "slice-deque",
- "thiserror",
-]
-
-[[package]]
-name = "minimp3-sys"
-version = "0.3.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e21c73734c69dc95696c9ed8926a2b393171d98b3f5f5935686a26a487ab9b90"
-dependencies = [
- "cc",
-]
-
[[package]]
name = "miniz_oxide"
-version = "0.5.3"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "mio"
-version = "0.8.4"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf"
+checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
- "windows-sys 0.36.1",
+ "windows-sys 0.45.0",
]
[[package]]
name = "morphic_lib"
version = "0.1.0"
dependencies = [
- "sha2",
+ "blake3",
+ "roc_collections",
"smallvec",
"thiserror",
"typed-arena",
@@ -2232,20 +2067,7 @@ checksum = "96d868f654c72e75f8687572699cdabe755f03effbb62542768e995d5b8d699d"
dependencies = [
"bitflags",
"jni-sys",
- "ndk-sys 0.2.2",
- "num_enum",
- "thiserror",
-]
-
-[[package]]
-name = "ndk"
-version = "0.6.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4"
-dependencies = [
- "bitflags",
- "jni-sys",
- "ndk-sys 0.3.0",
+ "ndk-sys",
"num_enum",
"thiserror",
]
@@ -2265,25 +2087,10 @@ dependencies = [
"lazy_static",
"libc",
"log",
- "ndk 0.5.0",
+ "ndk",
"ndk-context",
"ndk-macro",
- "ndk-sys 0.2.2",
-]
-
-[[package]]
-name = "ndk-glue"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d0c4a7b83860226e6b4183edac21851f05d5a51756e97a1144b7f5a6b63e65f"
-dependencies = [
- "lazy_static",
- "libc",
- "log",
- "ndk 0.6.0",
- "ndk-context",
- "ndk-macro",
- "ndk-sys 0.3.0",
+ "ndk-sys",
]
[[package]]
@@ -2305,15 +2112,6 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1bcdd74c20ad5d95aacd60ef9ba40fdf77f767051040541df557b7a9b2a2121"
-[[package]]
-name = "ndk-sys"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97"
-dependencies = [
- "jni-sys",
-]
-
[[package]]
name = "nibble_vec"
version = "0.1.0"
@@ -2333,38 +2131,51 @@ dependencies = [
"cc",
"cfg-if 1.0.0",
"libc",
- "memoffset",
+ "memoffset 0.6.5",
]
[[package]]
name = "nix"
-version = "0.23.1"
+version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9f866317acbd3a240710c63f065ffb1e4fd466259045ccb504130b7f668f35c6"
+checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [
"bitflags",
"cc",
"cfg-if 1.0.0",
"libc",
- "memoffset",
+ "memoffset 0.6.5",
]
[[package]]
name = "nix"
-version = "0.24.1"
+version = "0.24.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9"
+checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
"libc",
+ "memoffset 0.6.5",
+]
+
+[[package]]
+name = "nix"
+version = "0.26.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a"
+dependencies = [
+ "bitflags",
+ "cfg-if 1.0.0",
+ "libc",
+ "static_assertions",
]
[[package]]
name = "nom"
-version = "7.1.1"
+version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a8903e5a29a317527874d0402f867152a3d21c908bb0b933e416c65e301d4c36"
+checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
@@ -2372,9 +2183,18 @@ dependencies = [
[[package]]
name = "nonempty"
-version = "0.8.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09f1f8e5676e1a1f2ee8b21f38238e1243c827531c9435624c7bfb305102cee4"
+checksum = "aeaf4ad7403de93e699c191202f017118df734d3850b01e13a3a8b2e6953d3c9"
+
+[[package]]
+name = "normpath"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "972dec05f98e7c787ede35d7a9ea4735eb7788c299287352757b3def6cc1f7b5"
+dependencies = [
+ "windows-sys 0.45.0",
+]
[[package]]
name = "nu-ansi-term"
@@ -2386,17 +2206,6 @@ dependencies = [
"winapi",
]
-[[package]]
-name = "num-derive"
-version = "0.3.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
-]
-
[[package]]
name = "num-traits"
version = "0.2.15"
@@ -2404,32 +2213,33 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
+ "libm",
]
[[package]]
name = "num_cpus"
-version = "1.13.1"
+version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
+checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.2.6",
"libc",
]
[[package]]
name = "num_enum"
-version = "0.5.7"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf5395665662ef45796a4ff5486c5d41d29e0c09640af4c5f17fd94ee2c119c9"
+checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9"
dependencies = [
"num_enum_derive",
]
[[package]]
name = "num_enum_derive"
-version = "0.5.7"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b0498641e53dd6ac1a4f22547548caa6864cc4933784319cd1775271c5a46ce"
+checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799"
dependencies = [
"proc-macro-crate",
"proc-macro2",
@@ -2437,15 +2247,6 @@ dependencies = [
"syn",
]
-[[package]]
-name = "num_threads"
-version = "0.1.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44"
-dependencies = [
- "libc",
-]
-
[[package]]
name = "objc"
version = "0.2.7"
@@ -2487,54 +2288,22 @@ dependencies = [
[[package]]
name = "object"
-version = "0.29.0"
+version = "0.30.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
+checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
dependencies = [
"crc32fast",
"flate2",
- "hashbrown 0.12.3",
+ "hashbrown 0.13.2",
"indexmap",
"memchr",
]
-[[package]]
-name = "oboe"
-version = "0.4.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "27f63c358b4fa0fbcfefd7c8be5cfc39c08ce2389f5325687e7762a48d30a5c1"
-dependencies = [
- "jni",
- "ndk 0.6.0",
- "ndk-context",
- "num-derive",
- "num-traits",
- "oboe-sys",
-]
-
-[[package]]
-name = "oboe-sys"
-version = "0.4.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3370abb7372ed744232c12954d920d1a40f1c4686de9e79e800021ef492294bd"
-dependencies = [
- "cc",
-]
-
-[[package]]
-name = "ogg"
-version = "0.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6951b4e8bf21c8193da321bcce9c9dd2e13c858fe078bf9054a288b419ae5d6e"
-dependencies = [
- "byteorder",
-]
-
[[package]]
name = "once_cell"
-version = "1.15.0"
+version = "1.17.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e82dad04139b71a90c080c8463fe0dc7902db5192d939bd0950f074d014339e1"
+checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
[[package]]
name = "oorandom"
@@ -2544,18 +2313,18 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
[[package]]
name = "ordered-float"
-version = "3.0.0"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "96bcbab4bfea7a59c2c0fe47211a1ac4e3e96bea6eb446d704f310bc5c732ae2"
+checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf"
dependencies = [
"num-traits",
]
[[package]]
name = "os_str_bytes"
-version = "6.1.0"
+version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa"
+checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "output_vt100"
@@ -2574,29 +2343,29 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "owned_ttf_parser"
-version = "0.15.0"
+version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb1e509cfe7a12db2a90bfa057dfcdbc55a347f5da677c506b53dd099cfec9d"
+checksum = "e25e9fb15717794fae58ab55c26e044103aad13186fbb625893f9a3bbcc24228"
dependencies = [
"ttf-parser",
]
[[package]]
name = "packed_struct"
-version = "0.10.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1"
+checksum = "36b29691432cc9eff8b282278473b63df73bea49bc3ec5e67f31a3ae9c3ec190"
dependencies = [
- "bitvec 0.22.3",
+ "bitvec",
"packed_struct_codegen",
"serde",
]
[[package]]
name = "packed_struct_codegen"
-version = "0.10.0"
+version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "56e3692b867ec1d48ccb441e951637a2cc3130d0912c0059e48319e1c83e44bc"
+checksum = "9cd6706dfe50d53e0f6aa09e12c034c44faacd23e966ae5a209e8bdb8f179f98"
dependencies = [
"proc-macro2",
"quote",
@@ -2605,9 +2374,9 @@ dependencies = [
[[package]]
name = "page_size"
-version = "0.4.2"
+version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eebde548fbbf1ea81a99b128872779c437752fb99f217c45245e1a61dcd9edcd"
+checksum = "1b7663cbd190cfd818d08efa8497f6cd383076688c49a391ef7c0d03cd12b561"
dependencies = [
"libc",
"winapi",
@@ -2645,7 +2414,7 @@ checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
- "parking_lot_core 0.8.5",
+ "parking_lot_core 0.8.6",
]
[[package]]
@@ -2655,14 +2424,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
- "parking_lot_core 0.9.3",
+ "parking_lot_core 0.9.7",
]
[[package]]
name = "parking_lot_core"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
+checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if 1.0.0",
"instant",
@@ -2674,23 +2443,17 @@ dependencies = [
[[package]]
name = "parking_lot_core"
-version = "0.9.3"
+version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929"
+checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if 1.0.0",
"libc",
"redox_syscall",
"smallvec",
- "windows-sys 0.36.1",
+ "windows-sys 0.45.0",
]
-[[package]]
-name = "peeking_take_while"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099"
-
[[package]]
name = "peg"
version = "0.8.1"
@@ -2720,15 +2483,15 @@ checksum = "9fa00462b37ead6d11a82c9d568b26682d78e0477dc02d1966c013af80969739"
[[package]]
name = "percent-encoding"
-version = "2.1.0"
+version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
+checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pest"
-version = "2.3.1"
+version = "2.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048"
+checksum = "8cbd939b234e95d72bc393d51788aec68aeeb5d51e748ca08ff3aad58cb722f7"
dependencies = [
"thiserror",
"ucd-trie",
@@ -2736,9 +2499,9 @@ dependencies = [
[[package]]
name = "pest_derive"
-version = "2.3.1"
+version = "2.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "502b62a6d0245378b04ffe0a7fb4f4419a4815fce813bd8a0ec89a56e07d67b1"
+checksum = "a81186863f3d0a27340815be8f2078dd8050b14cd71913db9fbda795e5f707d7"
dependencies = [
"pest",
"pest_generator",
@@ -2746,9 +2509,9 @@ dependencies = [
[[package]]
name = "pest_generator"
-version = "2.3.1"
+version = "2.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "451e629bf49b750254da26132f1a5a9d11fd8a95a3df51d15c4abd1ba154cb6c"
+checksum = "75a1ef20bf3193c15ac345acb32e26b3dc3223aff4d77ae4fc5359567683796b"
dependencies = [
"pest",
"pest_meta",
@@ -2759,13 +2522,13 @@ dependencies = [
[[package]]
name = "pest_meta"
-version = "2.3.1"
+version = "2.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bcec162c71c45e269dfc3fc2916eaeb97feab22993a21bcce4721d08cd7801a6"
+checksum = "5e3b284b1f13a20dc5ebc90aff59a51b8d7137c221131b52a7260c08cbc1cc80"
dependencies = [
"once_cell",
"pest",
- "sha1",
+ "sha2",
]
[[package]]
@@ -2824,9 +2587,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
-version = "0.3.25"
+version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae"
+checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160"
[[package]]
name = "plotters"
@@ -2848,18 +2611,18 @@ checksum = "193228616381fecdc1224c62e96946dfbc73ff4384fba576e052ff8c1bea8142"
[[package]]
name = "plotters-svg"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e0918736323d1baff32ee0eade54984f6f201ad7e97d5cfb5d6ab4a358529615"
+checksum = "f9a81d2759aae1dae668f783c308bc5c8ebd191ff4184aaa1b37f65a6ae5a56f"
dependencies = [
"plotters-backend",
]
[[package]]
name = "ppv-lite86"
-version = "0.2.16"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "pretty_assertions"
@@ -2875,58 +2638,34 @@ dependencies = [
[[package]]
name = "proc-macro-crate"
-version = "1.1.3"
+version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a"
+checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919"
dependencies = [
- "thiserror",
- "toml",
-]
-
-[[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",
+ "once_cell",
+ "toml_edit",
]
[[package]]
name = "proc-macro2"
-version = "1.0.40"
+version = "1.0.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7"
+checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6"
dependencies = [
"unicode-ident",
]
[[package]]
name = "profiling"
-version = "1.0.6"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2f61dcf0b917cd75d4521d7343d1ffff3d1583054133c9b5cbea3375c703c40d"
+checksum = "74605f360ce573babfe43964cbe520294dcb081afbf8c108fc6e23036b4da2df"
[[package]]
name = "proptest"
-version = "1.0.0"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5"
+checksum = "29f1b898011ce9595050a68e60f90bad083ff2987a695a42357134c8381fba70"
dependencies = [
"bit-set",
"bitflags",
@@ -2940,6 +2679,7 @@ dependencies = [
"regex-syntax",
"rusty-fork",
"tempfile",
+ "unarray",
]
[[package]]
@@ -2965,15 +2705,6 @@ version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
-[[package]]
-name = "quick-xml"
-version = "0.22.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8533f14c8382aaad0d592c812ac3b826162128b65662331e1127b45c3d18536b"
-dependencies = [
- "memchr",
-]
-
[[package]]
name = "quickcheck"
version = "1.0.3"
@@ -2998,19 +2729,13 @@ dependencies = [
[[package]]
name = "quote"
-version = "1.0.20"
+version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804"
+checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
-[[package]]
-name = "radium"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
-
[[package]]
name = "radium"
version = "0.7.0"
@@ -3050,9 +2775,9 @@ dependencies = [
[[package]]
name = "rand_core"
-version = "0.6.3"
+version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
@@ -3077,9 +2802,9 @@ dependencies = [
[[package]]
name = "range-alloc"
-version = "0.1.2"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63e935c45e09cc6dcf00d2f0b2d630a58f4095320223d47fc68918722f0538b6"
+checksum = "9c8a99fddc9f0ba0a85884b8d14e3592853e787d581ca1816c91349b10e4eeab"
[[package]]
name = "raw-window-handle"
@@ -3092,21 +2817,19 @@ dependencies = [
[[package]]
name = "rayon"
-version = "1.5.3"
+version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
+checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b"
dependencies = [
- "autocfg",
- "crossbeam-deque",
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
-version = "1.9.3"
+version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
+checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
@@ -3116,9 +2839,9 @@ dependencies = [
[[package]]
name = "redox_syscall"
-version = "0.2.13"
+version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42"
+checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
@@ -3136,9 +2859,9 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.6.0"
+version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
+checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
@@ -3156,9 +2879,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.27"
+version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
+checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "remove_dir_all"
@@ -3171,15 +2894,18 @@ dependencies = [
[[package]]
name = "remove_dir_all"
-version = "0.7.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "882f368737489ea543bc5c340e6f3d34a28c39980bd9a979e47322b26f60ac40"
+checksum = "7174320e07c29945955cedd70b865995b286847111c8308d349a1f3a9e3af555"
dependencies = [
+ "aligned",
+ "cfg-if 1.0.0",
+ "cvt",
+ "fs_at",
+ "lazy_static",
"libc",
- "log",
- "num_cpus",
- "rayon",
- "winapi",
+ "normpath",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -3204,11 +2930,11 @@ dependencies = [
[[package]]
name = "reqwest"
-version = "0.11.13"
+version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68cc60575865c7831548863cc02356512e3f1dc2f3f82cb837d7fc4cc8f3c97c"
+checksum = "21eed90ec8570952d53b772ecf8f206aa1ec9a3d76b2521c56c42973f2d91ee9"
dependencies = [
- "base64",
+ "base64 0.21.0",
"bytes",
"encoding_rs",
"futures-core",
@@ -3258,9 +2984,9 @@ dependencies = [
[[package]]
name = "rlimit"
-version = "0.6.2"
+version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc0bf25554376fd362f54332b8410a625c71f15445bca32ffdfdf4ec9ac91726"
+checksum = "f8a29d87a652dc4d43c586328706bb5cdff211f3f39a530f240b53f7221dab8e"
dependencies = [
"libc",
]
@@ -3273,6 +2999,7 @@ dependencies = [
"morphic_lib",
"roc_collections",
"roc_debug_flags",
+ "roc_error_macros",
"roc_module",
"roc_mono",
]
@@ -3306,25 +3033,46 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "roc_bitcode"
+version = "0.0.1"
+dependencies = [
+ "dunce",
+ "roc_command_utils",
+ "tempfile",
+]
+
+[[package]]
+name = "roc_bitcode_bc"
+version = "0.0.1"
+dependencies = [
+ "dunce",
+ "roc_command_utils",
+ "tempfile",
+]
+
[[package]]
name = "roc_build"
version = "0.0.1"
dependencies = [
"bumpalo",
- "const_format",
+ "indoc",
"inkwell",
"libloading",
- "roc_builtins",
+ "roc_bitcode",
"roc_can",
"roc_collections",
+ "roc_command_utils",
"roc_constrain",
"roc_error_macros",
"roc_gen_dev",
"roc_gen_llvm",
"roc_gen_wasm",
+ "roc_linker",
"roc_load",
"roc_module",
"roc_mono",
+ "roc_packaging",
"roc_parse",
"roc_problem",
"roc_region",
@@ -3334,7 +3082,6 @@ dependencies = [
"roc_target",
"roc_types",
"roc_unify",
- "roc_utils",
"serde_json",
"target-lexicon",
"tempfile",
@@ -3345,12 +3092,10 @@ dependencies = [
name = "roc_builtins"
version = "0.0.1"
dependencies = [
- "dunce",
"roc_collections",
"roc_module",
"roc_region",
"roc_target",
- "roc_utils",
"tempfile",
]
@@ -3358,7 +3103,7 @@ dependencies = [
name = "roc_can"
version = "0.0.1"
dependencies = [
- "bitvec 1.0.1",
+ "bitvec",
"bumpalo",
"indoc",
"pretty_assertions",
@@ -3380,23 +3125,23 @@ name = "roc_cli"
version = "0.0.1"
dependencies = [
"bumpalo",
- "clap 3.2.20",
+ "clap 3.2.23",
"cli_utils",
"const_format",
"criterion",
- "errno",
+ "errno 0.3.0",
"indoc",
"inkwell",
"libc",
"libloading",
"mimalloc",
- "once_cell",
"parking_lot 0.12.1",
"pretty_assertions",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
+ "roc_command_utils",
"roc_docs",
"roc_editor",
"roc_error_macros",
@@ -3416,7 +3161,6 @@ dependencies = [
"roc_target",
"roc_test_utils",
"roc_tracing",
- "roc_utils",
"roc_wasm_interp",
"serial_test",
"signal-hook",
@@ -3433,8 +3177,8 @@ dependencies = [
"bumpalo",
"palette",
"roc_ast",
+ "roc_error_utils",
"roc_module",
- "roc_utils",
"serde",
"snafu",
]
@@ -3443,15 +3187,19 @@ dependencies = [
name = "roc_collections"
version = "0.0.1"
dependencies = [
- "bitvec 1.0.1",
+ "bitvec",
"bumpalo",
"fnv",
- "hashbrown 0.12.3",
+ "hashbrown 0.13.2",
"im",
"im-rc",
"wyhash",
]
+[[package]]
+name = "roc_command_utils"
+version = "0.0.1"
+
[[package]]
name = "roc_constrain"
version = "0.0.1"
@@ -3490,7 +3238,6 @@ dependencies = [
name = "roc_derive_key"
version = "0.0.1"
dependencies = [
- "roc_can",
"roc_collections",
"roc_error_macros",
"roc_module",
@@ -3527,7 +3274,7 @@ dependencies = [
name = "roc_docs_cli"
version = "0.0.1"
dependencies = [
- "clap 3.2.20",
+ "clap 3.2.23",
"libc",
"roc_docs",
]
@@ -3559,6 +3306,7 @@ dependencies = [
"roc_can",
"roc_code_markup",
"roc_collections",
+ "roc_command_utils",
"roc_load",
"roc_module",
"roc_packaging",
@@ -3569,8 +3317,6 @@ dependencies = [
"roc_solve",
"roc_types",
"roc_unify",
- "roc_utils",
- "rodio",
"serde",
"snafu",
"tempfile",
@@ -3586,6 +3332,13 @@ dependencies = [
name = "roc_error_macros"
version = "0.0.1"
+[[package]]
+name = "roc_error_utils"
+version = "0.0.1"
+dependencies = [
+ "snafu",
+]
+
[[package]]
name = "roc_exhaustive"
version = "0.0.1"
@@ -3641,6 +3394,7 @@ dependencies = [
"inkwell",
"morphic_lib",
"roc_alias_analysis",
+ "roc_bitcode_bc",
"roc_builtins",
"roc_collections",
"roc_debug_flags",
@@ -3657,7 +3411,7 @@ dependencies = [
name = "roc_gen_wasm"
version = "0.0.1"
dependencies = [
- "bitvec 1.0.1",
+ "bitvec",
"bumpalo",
"roc_builtins",
"roc_collections",
@@ -3673,18 +3427,23 @@ dependencies = [
name = "roc_glue"
version = "0.0.1"
dependencies = [
+ "backtrace",
"bumpalo",
- "clap 3.2.20",
"cli_utils",
"dircpy",
"fnv",
"indexmap",
"indoc",
+ "libc",
+ "libloading",
"pretty_assertions",
+ "roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_error_macros",
+ "roc_gen_llvm",
+ "roc_linker",
"roc_load",
"roc_module",
"roc_mono",
@@ -3692,7 +3451,6 @@ dependencies = [
"roc_reporting",
"roc_std",
"roc_target",
- "roc_test_utils",
"roc_tracing",
"roc_types",
"strum",
@@ -3705,8 +3463,9 @@ dependencies = [
name = "roc_highlight"
version = "0.0.1"
dependencies = [
- "peg",
- "roc_code_markup",
+ "html-escape",
+ "roc_parse",
+ "roc_region",
]
[[package]]
@@ -3738,16 +3497,18 @@ dependencies = [
"indoc",
"libc",
"mach_object",
- "memmap2 0.5.7",
+ "memmap2 0.5.10",
"object",
- "roc_build",
"roc_collections",
"roc_error_macros",
"roc_load",
+ "roc_module",
"roc_mono",
"roc_packaging",
"roc_reporting",
+ "roc_target",
"serde",
+ "serial_test",
"target-lexicon",
"tempfile",
]
@@ -3822,9 +3583,9 @@ dependencies = [
name = "roc_mono"
version = "0.0.1"
dependencies = [
- "bitvec 1.0.1",
+ "bitvec",
"bumpalo",
- "hashbrown 0.12.3",
+ "hashbrown 0.13.2",
"parking_lot 0.12.1",
"roc_builtins",
"roc_can",
@@ -3856,8 +3617,6 @@ dependencies = [
"bumpalo",
"flate2",
"fs_extra",
- "indoc",
- "pretty_assertions",
"reqwest",
"roc_error_macros",
"roc_parse",
@@ -3881,7 +3640,6 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_region",
- "roc_test_utils",
]
[[package]]
@@ -3983,7 +3741,6 @@ dependencies = [
"strip-ansi-escapes",
"target-lexicon",
"tempfile",
- "test_gen",
]
[[package]]
@@ -3993,7 +3750,9 @@ dependencies = [
"bumpalo",
"console_error_panic_hook",
"futures",
+ "getrandom",
"js-sys",
+ "roc_bitcode",
"roc_builtins",
"roc_collections",
"roc_gen_wasm",
@@ -4003,7 +3762,6 @@ dependencies = [
"roc_reporting",
"roc_target",
"roc_types",
- "roc_utils",
"tempfile",
"wasi_libc_sys",
"wasm-bindgen",
@@ -4018,6 +3776,7 @@ dependencies = [
"distance",
"indoc",
"insta",
+ "itertools 0.10.5",
"pretty_assertions",
"roc_builtins",
"roc_can",
@@ -4101,6 +3860,12 @@ name = "roc_std"
version = "0.0.1"
dependencies = [
"arrayvec 0.7.2",
+ "libc",
+ "pretty_assertions",
+ "quickcheck",
+ "quickcheck_macros",
+ "serde",
+ "serde_json",
"static_assertions",
]
@@ -4118,7 +3883,7 @@ name = "roc_test_utils"
version = "0.0.1"
dependencies = [
"pretty_assertions",
- "remove_dir_all 0.7.0",
+ "remove_dir_all 0.8.1",
]
[[package]]
@@ -4150,30 +3915,21 @@ name = "roc_unify"
version = "0.0.1"
dependencies = [
"bitflags",
- "roc_can",
"roc_collections",
"roc_debug_flags",
"roc_error_macros",
"roc_module",
- "roc_solve_problem",
"roc_tracing",
"roc_types",
]
[[package]]
-name = "roc_utils"
+name = "roc_wasm_interp"
version = "0.0.1"
dependencies = [
- "snafu",
-]
-
-[[package]]
-name = "roc_wasm_interp"
-version = "0.1.0"
-dependencies = [
- "bitvec 1.0.1",
+ "bitvec",
"bumpalo",
- "clap 3.2.20",
+ "clap 3.2.23",
"rand",
"roc_wasm_module",
]
@@ -4182,24 +3938,11 @@ dependencies = [
name = "roc_wasm_module"
version = "0.0.1"
dependencies = [
- "bitvec 1.0.1",
+ "bitvec",
"bumpalo",
"roc_error_macros",
]
-[[package]]
-name = "rodio"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ec0939e9f626e6c6f1989adb6226a039c855ca483053f0ee7c98b90e41cf731e"
-dependencies = [
- "claxon",
- "cpal",
- "hound",
- "lewton",
- "minimp3",
-]
-
[[package]]
name = "rustc-demangle"
version = "0.1.21"
@@ -4218,28 +3961,28 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
dependencies = [
- "semver 1.0.12",
+ "semver 1.0.16",
]
[[package]]
name = "rustix"
-version = "0.35.7"
+version = "0.36.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d51cc38aa10f6bbb377ed28197aa052aa4e2b762c22be9d3153d01822587e787"
+checksum = "fd5c6ff11fecd55b40746d1995a02f2eb375bf8c00d192d521ee09f42bef37bc"
dependencies = [
"bitflags",
- "errno",
+ "errno 0.2.8",
"io-lifetimes",
"libc",
"linux-raw-sys",
- "windows-sys 0.36.1",
+ "windows-sys 0.45.0",
]
[[package]]
name = "rustls"
-version = "0.20.7"
+version = "0.20.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c"
+checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f"
dependencies = [
"log",
"ring",
@@ -4249,18 +3992,18 @@ dependencies = [
[[package]]
name = "rustls-pemfile"
-version = "1.0.1"
+version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55"
+checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b"
dependencies = [
- "base64",
+ "base64 0.21.0",
]
[[package]]
name = "rustversion"
-version = "1.0.7"
+version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
+checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06"
[[package]]
name = "rusty-fork"
@@ -4281,13 +4024,13 @@ source = "git+https://github.com/roc-lang/rustyline?rev=e74333c#e74333c0d618896b
dependencies = [
"bitflags",
"cfg-if 1.0.0",
- "clipboard-win 4.4.1",
+ "clipboard-win 4.5.0",
"dirs-next",
"fd-lock",
"libc",
"log",
"memchr",
- "nix 0.23.1",
+ "nix 0.23.2",
"radix_trie",
"scopeguard",
"smallvec",
@@ -4308,9 +4051,9 @@ dependencies = [
[[package]]
name = "ryu"
-version = "1.0.10"
+version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
+checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041"
[[package]]
name = "same-file"
@@ -4323,9 +4066,9 @@ dependencies = [
[[package]]
name = "scoped-tls"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2"
+checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294"
[[package]]
name = "scopeguard"
@@ -4354,9 +4097,9 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.12"
+version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1"
+checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a"
[[package]]
name = "semver-parser"
@@ -4369,18 +4112,18 @@ dependencies = [
[[package]]
name = "serde"
-version = "1.0.144"
+version = "1.0.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
+checksum = "3a382c72b4ba118526e187430bb4963cd6d55051ebf13d9b25574d379cc98d20"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde-xml-rs"
-version = "0.5.1"
+version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65162e9059be2f6a3421ebbb4fef3e74b7d9e7c60c50a0e292c6239f19f1edfa"
+checksum = "fb3aa78ecda1ebc9ec9847d5d3aba7d618823446a049ba2491940506da6e2782"
dependencies = [
"log",
"serde",
@@ -4400,9 +4143,9 @@ dependencies = [
[[package]]
name = "serde_derive"
-version = "1.0.144"
+version = "1.0.153"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
+checksum = "1ef476a5790f0f6decbc66726b6e5d63680ed518283e64c7df415989d880954f"
dependencies = [
"proc-macro2",
"quote",
@@ -4411,11 +4154,11 @@ dependencies = [
[[package]]
name = "serde_json"
-version = "1.0.85"
+version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
+checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea"
dependencies = [
- "itoa 1.0.2",
+ "itoa",
"ryu",
"serde",
]
@@ -4427,16 +4170,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
dependencies = [
"form_urlencoded",
- "itoa 1.0.2",
+ "itoa",
"ryu",
"serde",
]
[[package]]
name = "serde_yaml"
-version = "0.8.25"
+version = "0.8.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1ec0091e1f5aa338283ce049bd9dfefd55e1f168ac233e85c1ffe0038fb48cbe"
+checksum = "578a7433b776b56a35785ed5ce9a7e777ac0598aac5a6dd1b4b18a307c7fc71b"
dependencies = [
"indexmap",
"ryu",
@@ -4446,9 +4189,9 @@ dependencies = [
[[package]]
name = "serial_test"
-version = "0.9.0"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92761393ee4dc3ff8f4af487bd58f4307c9329bbedea02cac0089ad9c411e153"
+checksum = "538c30747ae860d6fb88330addbbd3e0ddbe46d662d032855596d8a8ca260611"
dependencies = [
"dashmap",
"futures",
@@ -4460,32 +4203,20 @@ dependencies = [
[[package]]
name = "serial_test_derive"
-version = "0.9.0"
+version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b6f5d1c3087fb119617cff2966fe3808a80e5eb59a8c1601d5994d66f4346a5"
+checksum = "079a83df15f85d89a68d64ae1238f142f172b1fa915d0d76b26a7cba1b659a69"
dependencies = [
- "proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
-[[package]]
-name = "sha1"
-version = "0.10.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "006769ba83e921b3085caa8334186b00cf92b4cb1a6cf4632fbccc8eff5c7549"
-dependencies = [
- "cfg-if 1.0.0",
- "cpufeatures",
- "digest",
-]
-
[[package]]
name = "sha2"
-version = "0.10.2"
+version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
+checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0"
dependencies = [
"cfg-if 1.0.0",
"cpufeatures",
@@ -4501,17 +4232,11 @@ dependencies = [
"lazy_static",
]
-[[package]]
-name = "shlex"
-version = "1.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3"
-
[[package]]
name = "signal-hook"
-version = "0.3.14"
+version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
+checksum = "732768f1176d21d09e076c23a93123d40bba92d50c4058da34d45c8de8e682b9"
dependencies = [
"libc",
"signal-hook-registry",
@@ -4519,18 +4244,18 @@ dependencies = [
[[package]]
name = "signal-hook-registry"
-version = "1.4.0"
+version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
+checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "similar"
-version = "2.1.0"
+version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
+checksum = "420acb44afdae038210c99e69aae24109f32f15500aa708e81d46c9f29d55fcf"
[[package]]
name = "siphasher"
@@ -4550,19 +4275,11 @@ dependencies = [
[[package]]
name = "slab"
-version = "0.4.6"
+version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32"
-
-[[package]]
-name = "slice-deque"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "31ef6ee280cdefba6d2d0b4b78a84a1c1a3f3a4cec98c2d4231c8bc225de0f25"
+checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d"
dependencies = [
- "libc",
- "mach",
- "winapi",
+ "autocfg",
]
[[package]]
@@ -4576,9 +4293,20 @@ dependencies = [
[[package]]
name = "smallvec"
-version = "1.9.0"
+version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fd0db749597d91ff862fd1d55ea87f7855a744a8425a64695b6fca237d1dad1"
+checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
+
+[[package]]
+name = "smart-default"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "133659a15339456eeeb07572eb02a91c91e9815e9cbc89566944d2c8d3efdbf6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
[[package]]
name = "smithay-client-toolkit"
@@ -4609,8 +4337,8 @@ dependencies = [
"dlib",
"lazy_static",
"log",
- "memmap2 0.5.7",
- "nix 0.24.1",
+ "memmap2 0.5.10",
+ "nix 0.24.3",
"pkg-config",
"wayland-client",
"wayland-cursor",
@@ -4629,9 +4357,9 @@ dependencies = [
[[package]]
name = "snafu"
-version = "0.7.1"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
+checksum = "cb0656e7e3ffb70f6c39b3c2a86332bb74aa3c679da781642590f3c1118c5045"
dependencies = [
"backtrace",
"doc-comment",
@@ -4640,9 +4368,9 @@ dependencies = [
[[package]]
name = "snafu-derive"
-version = "0.7.1"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
+checksum = "475b3bbe5245c26f2d8a6f62d67c1f30eb9fffeccee721c45d162c3ebbdf81b2"
dependencies = [
"heck",
"proc-macro2",
@@ -4652,9 +4380,9 @@ dependencies = [
[[package]]
name = "socket2"
-version = "0.4.7"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd"
+checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
@@ -4676,18 +4404,18 @@ dependencies = [
"num-traits",
]
+[[package]]
+name = "stable_deref_trait"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
+
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
-[[package]]
-name = "stdweb"
-version = "0.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef5430c8e36b713e13b48a9f709cc21e046723fe44ce34587b73a830203b533e"
-
[[package]]
name = "str-buf"
version = "1.0.6"
@@ -4739,9 +4467,9 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
[[package]]
name = "syn"
-version = "1.0.99"
+version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
+checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
@@ -4767,19 +4495,19 @@ dependencies = [
[[package]]
name = "target-lexicon"
-version = "0.12.4"
+version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1"
+checksum = "8ae9980cab1db3fceee2f6c6f643d5d8de2997c58ee8d25fb0cc8a9e9e7348e5"
[[package]]
name = "tempfile"
-version = "3.3.0"
+version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
+checksum = "dac1c663cfc93810f88aed9b8941d48cabf856a1b111c29a40439018d870eb22"
dependencies = [
"cfg-if 1.0.0",
- "fastrand",
"libc",
+ "rand",
"redox_syscall",
"remove_dir_all 0.5.3",
"winapi",
@@ -4787,31 +4515,19 @@ dependencies = [
[[package]]
name = "termcolor"
-version = "1.1.3"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
-[[package]]
-name = "terminal_size"
-version = "0.1.17"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
-dependencies = [
- "libc",
- "winapi",
-]
-
[[package]]
name = "test_derive"
version = "0.0.1"
dependencies = [
"bumpalo",
- "indoc",
"insta",
- "pretty_assertions",
"roc_builtins",
"roc_can",
"roc_collections",
@@ -4841,10 +4557,12 @@ dependencies = [
"lazy_static",
"libc",
"libloading",
+ "roc_bitcode",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
+ "roc_command_utils",
"roc_constrain",
"roc_debug_flags",
"roc_error_macros",
@@ -4864,7 +4582,6 @@ dependencies = [
"roc_target",
"roc_types",
"roc_unify",
- "roc_utils",
"roc_wasm_interp",
"roc_wasm_module",
"target-lexicon",
@@ -4927,24 +4644,24 @@ dependencies = [
[[package]]
name = "textwrap"
-version = "0.15.0"
+version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
+checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d"
[[package]]
name = "thiserror"
-version = "1.0.31"
+version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a"
+checksum = "a5ab016db510546d856297882807df8da66a16fb8c4101cb8b30054b0d5b2d9c"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.31"
+version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a"
+checksum = "5420d42e90af0c38c3290abcca25b9b3bdf379fc9f55c528f53a269d9c9a267e"
dependencies = [
"proc-macro2",
"quote",
@@ -4953,10 +4670,11 @@ dependencies = [
[[package]]
name = "thread_local"
-version = "1.1.4"
+version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
+ "cfg-if 1.0.0",
"once_cell",
]
@@ -4971,21 +4689,30 @@ dependencies = [
[[package]]
name = "time"
-version = "0.3.11"
+version = "0.3.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217"
+checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
dependencies = [
- "itoa 1.0.2",
- "libc",
- "num_threads",
+ "itoa",
+ "serde",
+ "time-core",
"time-macros",
]
[[package]]
-name = "time-macros"
-version = "0.2.4"
+name = "time-core"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
+checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
+
+[[package]]
+name = "time-macros"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
+dependencies = [
+ "time-core",
+]
[[package]]
name = "tinytemplate"
@@ -5008,15 +4735,15 @@ dependencies = [
[[package]]
name = "tinyvec_macros"
-version = "0.1.0"
+version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
+checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
-version = "1.24.1"
+version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
+checksum = "03201d01c3c27a29c8a5cee5b55a93ddae1ccf6f08f65365c2c918f8c1b76f64"
dependencies = [
"autocfg",
"bytes",
@@ -5026,7 +4753,7 @@ dependencies = [
"num_cpus",
"pin-project-lite",
"socket2",
- "windows-sys 0.42.0",
+ "windows-sys 0.45.0",
]
[[package]]
@@ -5042,9 +4769,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.7.4"
+version = "0.7.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740"
+checksum = "5427d89453009325de0d8f342c9490009f76e999cb7672d77e46267448f7e6b2"
dependencies = [
"bytes",
"futures-core",
@@ -5056,13 +4783,30 @@ dependencies = [
[[package]]
name = "toml"
-version = "0.5.9"
+version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7"
+checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234"
dependencies = [
"serde",
]
+[[package]]
+name = "toml_datetime"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622"
+
+[[package]]
+name = "toml_edit"
+version = "0.19.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
[[package]]
name = "tower-service"
version = "0.3.2"
@@ -5071,9 +4815,9 @@ checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52"
[[package]]
name = "tracing"
-version = "0.1.36"
+version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2fce9567bd60a67d08a16488756721ba392f24f29006402881e43b19aac64307"
+checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite",
@@ -5094,9 +4838,9 @@ dependencies = [
[[package]]
name = "tracing-attributes"
-version = "0.1.22"
+version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11c75893af559bc8e10716548bdef5cb2b983f8e637db9d0e15126b61b484ee2"
+checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
@@ -5144,15 +4888,15 @@ dependencies = [
[[package]]
name = "try-lock"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed"
[[package]]
name = "ttf-parser"
-version = "0.15.2"
+version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
+checksum = "0609f771ad9c6155384897e1df4d948e692667cc0588548b68eb44d052b27633"
[[package]]
name = "twox-hash"
@@ -5167,21 +4911,27 @@ dependencies = [
[[package]]
name = "typed-arena"
-version = "2.0.1"
+version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
+checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
[[package]]
name = "typenum"
-version = "1.15.0"
+version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
+checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "ucd-trie"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
+
+[[package]]
+name = "unarray"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89570599c4fe5585de2b388aab47e99f7fa4e9238a1399f707a02e356058141c"
+checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94"
[[package]]
name = "unicase"
@@ -5194,15 +4944,15 @@ dependencies = [
[[package]]
name = "unicode-bidi"
-version = "0.3.8"
+version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992"
+checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58"
[[package]]
name = "unicode-ident"
-version = "1.0.1"
+version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c"
+checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "unicode-normalization"
@@ -5215,21 +4965,21 @@ dependencies = [
[[package]]
name = "unicode-segmentation"
-version = "1.10.0"
+version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0fdbf052a0783de01e944a6ce7a8cb939e295b1e7be835a1112c3b9a7f047a5a"
+checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36"
[[package]]
name = "unicode-width"
-version = "0.1.9"
+version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
+checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b"
[[package]]
name = "unicode-xid"
-version = "0.2.3"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04"
+checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c"
[[package]]
name = "untrusted"
@@ -5239,15 +4989,21 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
-version = "2.3.0"
+version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22fe195a4f217c25b25cb5058ced57059824a678474874038dc88d211bf508d3"
+checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643"
dependencies = [
"form_urlencoded",
"idna",
"percent-encoding",
]
+[[package]]
+name = "utf8-width"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5190c9442dcdaf0ddd50f37420417d219ae5261bbf5db120d0f9bab996c9cba1"
+
[[package]]
name = "utf8parse"
version = "0.2.0"
@@ -5256,22 +5012,21 @@ checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372"
[[package]]
name = "uuid"
-version = "1.1.2"
+version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
+checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
dependencies = [
"getrandom",
]
[[package]]
name = "valgrind"
-version = "0.1.0"
+version = "0.0.1"
dependencies = [
"bumpalo",
"cli_utils",
"indoc",
"roc_build",
- "roc_cli",
"roc_linker",
"roc_load",
"roc_mono",
@@ -5370,14 +5125,14 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
name = "wasi_libc_sys"
version = "0.0.1"
dependencies = [
- "roc_utils",
+ "roc_command_utils",
]
[[package]]
name = "wasm-bindgen"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
+checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@@ -5385,9 +5140,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
+checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9"
dependencies = [
"bumpalo",
"log",
@@ -5400,9 +5155,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
-version = "0.4.33"
+version = "0.4.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d"
+checksum = "f219e0d211ba40266969f6dbdd90636da12f75bee4fc9d6c23d1260dadb51454"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@@ -5412,9 +5167,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
+checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -5422,9 +5177,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
+checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6"
dependencies = [
"proc-macro2",
"quote",
@@ -5435,20 +5190,20 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.83"
+version = "0.2.84"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
+checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d"
[[package]]
name = "wayland-client"
-version = "0.29.4"
+version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "91223460e73257f697d9e23d401279123d36039a3f7a449e983f123292d4458f"
+checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715"
dependencies = [
"bitflags",
"downcast-rs",
"libc",
- "nix 0.22.3",
+ "nix 0.24.3",
"scoped-tls",
"wayland-commons",
"wayland-scanner",
@@ -5457,11 +5212,11 @@ dependencies = [
[[package]]
name = "wayland-commons"
-version = "0.29.4"
+version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94f6e5e340d7c13490eca867898c4cec5af56c27a5ffe5c80c6fc4708e22d33e"
+checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902"
dependencies = [
- "nix 0.22.3",
+ "nix 0.24.3",
"once_cell",
"smallvec",
"wayland-sys",
@@ -5469,20 +5224,20 @@ dependencies = [
[[package]]
name = "wayland-cursor"
-version = "0.29.4"
+version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c52758f13d5e7861fc83d942d3d99bf270c83269575e52ac29e5b73cb956a6bd"
+checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661"
dependencies = [
- "nix 0.22.3",
+ "nix 0.24.3",
"wayland-client",
"xcursor",
]
[[package]]
name = "wayland-protocols"
-version = "0.29.4"
+version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "60147ae23303402e41fe034f74fb2c35ad0780ee88a1c40ac09a3be1e7465741"
+checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6"
dependencies = [
"bitflags",
"wayland-client",
@@ -5492,9 +5247,9 @@ dependencies = [
[[package]]
name = "wayland-scanner"
-version = "0.29.4"
+version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39a1ed3143f7a143187156a2ab52742e89dac33245ba505c17224df48939f9e0"
+checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53"
dependencies = [
"proc-macro2",
"quote",
@@ -5503,9 +5258,9 @@ dependencies = [
[[package]]
name = "wayland-sys"
-version = "0.29.4"
+version = "0.29.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9341df79a8975679188e37dab3889bfa57c44ac2cb6da166f519a81cbe452d4"
+checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4"
dependencies = [
"dlib",
"lazy_static",
@@ -5534,9 +5289,9 @@ dependencies = [
[[package]]
name = "webpki-roots"
-version = "0.22.5"
+version = "0.22.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "368bfe657969fb01238bb756d351dcade285e0f6fcbd36dcb23359a5169975be"
+checksum = "b6c71e40d7d2c34a5106301fb632274ca37242cd0c9d3e64dbece371a40a2d87"
dependencies = [
"webpki",
]
@@ -5669,25 +5424,21 @@ dependencies = [
"winapi",
]
+[[package]]
+name = "winapi-wsapoll"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44c17110f57155602a80dca10be03852116403c9ff3cd25b079d666f2aa3df6e"
+dependencies = [
+ "winapi",
+]
+
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-[[package]]
-name = "windows-sys"
-version = "0.36.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
-dependencies = [
- "windows_aarch64_msvc 0.36.1",
- "windows_i686_gnu 0.36.1",
- "windows_i686_msvc 0.36.1",
- "windows_x86_64_gnu 0.36.1",
- "windows_x86_64_msvc 0.36.1",
-]
-
[[package]]
name = "windows-sys"
version = "0.42.0"
@@ -5695,85 +5446,79 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
- "windows_aarch64_msvc 0.42.0",
- "windows_i686_gnu 0.42.0",
- "windows_i686_msvc 0.42.0",
- "windows_x86_64_gnu 0.42.0",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
"windows_x86_64_gnullvm",
- "windows_x86_64_msvc 0.42.0",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows-sys"
+version = "0.45.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.42.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.42.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e"
+checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.36.1"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
-
-[[package]]
-name = "windows_aarch64_msvc"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4"
+checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
-version = "0.36.1"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
-
-[[package]]
-name = "windows_i686_gnu"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7"
+checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
-version = "0.36.1"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
-
-[[package]]
-name = "windows_i686_msvc"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246"
+checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.36.1"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
-
-[[package]]
-name = "windows_x86_64_gnu"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed"
+checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.42.0"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028"
+checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.36.1"
+version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
-
-[[package]]
-name = "windows_x86_64_msvc"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
+checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
[[package]]
name = "winit"
@@ -5792,9 +5537,9 @@ dependencies = [
"libc",
"log",
"mio",
- "ndk 0.5.0",
- "ndk-glue 0.5.2",
- "ndk-sys 0.2.2",
+ "ndk",
+ "ndk-glue",
+ "ndk-sys",
"objc",
"parking_lot 0.11.2",
"percent-encoding",
@@ -5808,6 +5553,15 @@ dependencies = [
"x11-dl",
]
+[[package]]
+name = "winnow"
+version = "0.3.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee7b2c67f962bf5042bfd8b6a916178df33a26eec343ae064cb8e069f638fa6f"
+dependencies = [
+ "memchr",
+]
+
[[package]]
name = "winreg"
version = "0.10.1"
@@ -5828,42 +5582,55 @@ dependencies = [
[[package]]
name = "wyz"
-version = "0.4.0"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188"
-dependencies = [
- "tap",
-]
-
-[[package]]
-name = "wyz"
-version = "0.5.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
+checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
dependencies = [
"tap",
]
[[package]]
name = "x11-clipboard"
-version = "0.6.1"
+version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a7468a5768fea473e6c8c0d4b60d6d7001a64acceaac267207ca0281e1337e8"
+checksum = "0827f86aa910c4e73329a4f619deabe88ebb4b042370bf023c2d5d8b4eb54695"
dependencies = [
- "xcb",
+ "x11rb",
]
[[package]]
name = "x11-dl"
-version = "2.19.1"
+version = "2.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ea26926b4ce81a6f5d9d0f3a0bc401e5a37c6ae14a1bfaa8ff6099ca80038c59"
+checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f"
dependencies = [
- "lazy_static",
"libc",
+ "once_cell",
"pkg-config",
]
+[[package]]
+name = "x11rb"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "592b4883219f345e712b3209c62654ebda0bb50887f330cbd018d0f654bfd507"
+dependencies = [
+ "gethostname",
+ "nix 0.24.3",
+ "winapi",
+ "winapi-wsapoll",
+ "x11rb-protocol",
+]
+
+[[package]]
+name = "x11rb-protocol"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56b245751c0ac9db0e006dc812031482784e434630205a93c73cfefcaabeac67"
+dependencies = [
+ "nix 0.24.3",
+]
+
[[package]]
name = "xattr"
version = "0.2.3"
@@ -5873,17 +5640,6 @@ dependencies = [
"libc",
]
-[[package]]
-name = "xcb"
-version = "1.1.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b127bf5bfe9dbb39118d6567e3773d4bbc795411a8e1ef7b7e056bccac0011a9"
-dependencies = [
- "bitflags",
- "libc",
- "quick-xml",
-]
-
[[package]]
name = "xcursor"
version = "0.3.4"
diff --git a/Cargo.toml b/Cargo.toml
index 2f3631b2b2..e405349946 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,44 +1,40 @@
[workspace]
members = [
- "crates/compiler/*",
- "crates/vendor/*",
- "crates/glue",
- "crates/editor",
- "crates/ast",
- "crates/cli",
- "crates/code_markup",
- "crates/highlight",
- "crates/error_macros",
- "crates/reporting",
- "crates/packaging",
- "crates/repl_cli",
- "crates/repl_eval",
- "crates/repl_test",
- "crates/repl_wasm",
- "crates/repl_expect",
- "crates/test_utils",
- "crates/valgrind",
- "crates/tracing",
- "crates/utils",
- "crates/docs",
- "crates/docs_cli",
- "crates/linker",
- "crates/wasi-libc-sys",
- "crates/wasm_module",
- "crates/wasm_interp",
+ "crates/compiler/*",
+ "crates/vendor/*",
+ "crates/glue",
+ "crates/editor",
+ "crates/ast",
+ "crates/cli",
+ "crates/cli_utils",
+ "crates/code_markup",
+ "crates/highlight",
+ "crates/error_macros",
+ "crates/reporting",
+ "crates/packaging",
+ "crates/repl_cli",
+ "crates/repl_eval",
+ "crates/repl_test",
+ "crates/repl_wasm",
+ "crates/repl_expect",
+ "crates/roc_std",
+ "crates/test_utils",
+ "crates/valgrind",
+ "crates/tracing",
+ "crates/utils/*",
+ "crates/docs",
+ "crates/docs_cli",
+ "crates/linker",
+ "crates/wasi-libc-sys",
+ "crates/wasm_module",
+ "crates/wasm_interp",
]
+
exclude = [
- "ci/benchmarks/bench-runner",
- # Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
- "crates/cli_testing_examples",
- "examples",
- # Ignore building these normally. They are only imported by tests.
- # The tests will still correctly build them.
- "crates/cli_utils",
- "crates/compiler/test_mono_macros",
- "crates/compiler/str",
- # `cargo build` would cause roc_std to be built with default features which errors on windows
- "crates/roc_std",
+ "ci/benchmarks/bench-runner",
+ # Examples sometimes have Rust hosts in their platforms. The compiler should ignore those.
+ "crates/cli_testing_examples",
+ "examples",
]
# Needed to be able to run `cargo run -p roc_cli --no-default-features` -
# see www/build.sh for more.
@@ -47,6 +43,13 @@ exclude = [
# workspace, and without `resolver = "2"` here, you can't use `-p` like this.
resolver = "2"
+[workspace.package]
+authors = ["The Roc Contributors"]
+edition = "2021"
+license = "UPL-1.0"
+repository = "https://github.com/roc-lang/roc"
+version = "0.0.1"
+
[workspace.dependencies]
# NOTE: roc-lang/inkwell is a fork of TheDan64/inkwell which does not change anything.
#
@@ -65,72 +68,121 @@ resolver = "2"
# commit of TheDan64/inkwell, push a new tag which points to the latest commit,
# change the tag value in this Cargo.toml to point to that tag, and `cargo update`.
# This way, GitHub Actions works and nobody's builds get broken.
-inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "master", features = [ "llvm13-0" ] }
+inkwell = { git = "https://github.com/roc-lang/inkwell", branch = "inkwell-llvm-15", features = ["llvm13-0"] }
-arrayvec = "0.7.2"
+arrayvec = "0.7.2" # update roc_std/Cargo.toml on change
+backtrace = "0.3.67"
+base64-url = "1.4.13"
bincode = "1.3.3"
+bitflags = "1.3.2"
bitvec = "1.0.1"
-bumpalo = { version = "3.11.1", features = ["collections"] }
-capstone = "0.11.0"
-clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
-const_format = { version = "0.2.23", features = ["const_generics"] }
-criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]}
+blake3 = "1.3.3"
+brotli = "3.3.4" # used for decompressing tarballs over HTTPS, if the server supports brotli
+bumpalo = { version = "3.12.0", features = ["collections"] }
+bytemuck = { version = "1.13.1", features = ["derive"] }
+capstone = { version = "0.11.0", default-features = false }
+cgmath = "0.18.0"
+clap = { version = "3.2.23", default-features = false, features = ["std", "color", "suggestions"] }
+colored = "2.0.0"
+confy = { git = 'https://github.com/rust-cli/confy', features = ["yaml_conf"], default-features = false }
+console_error_panic_hook = "0.1.7"
+const_format = { version = "0.2.30", features = ["const_generics"] }
+copypasta = "0.8.2"
+criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"] }
+criterion-perf-events = { git = "https://github.com/Anton-4/criterion-perf-events" }
crossbeam = "0.8.2"
+dircpy = "0.3.14"
distance = "0.4.0"
encode_unicode = "1.0.0"
-errno = "0.2.8"
+errno = "0.3.0"
+flate2 = "1.0.25"
fnv = "1.0.7"
-fs_extra = "1.2.0"
-hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
-iced-x86 = { version = "1.15.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
-im = "15.0.0"
-im-rc = "15.0.0"
-indoc = "1.0.7"
-insta = "1.20.0"
+fs_extra = "1.3.0"
+futures = "0.3.26"
+glyph_brush = "0.7.7"
+hashbrown = { version = "0.13.2", features = ["bumpalo"] }
+iced-x86 = { version = "1.18.0", default-features = false, features = ["std", "decoder", "op_code_info", "instr_info"] }
+im = "15.1.0"
+im-rc = "15.1.0"
+indexmap = "1.9.2"
+indoc = "1.0.9"
+insta = "1.28.0"
+js-sys = "0.3.61"
lazy_static = "1.4.0"
-libc = "0.2.135"
-libloading = "0.7.1"
+libc = "0.2.139" # update roc_std/Cargo.toml on change
+libfuzzer-sys = "0.4"
+libloading = "0.7.4"
+log = "0.4.17"
mach_object = "0.1"
maplit = "1.0.2"
-memmap2 = "0.5.7"
-mimalloc = { version = "0.1.26", default-features = false }
-packed_struct = "0.10.0"
-page_size = "0.4.2"
+memmap2 = "0.5.10"
+mimalloc = { version = "0.1.34", default-features = false }
+nonempty = "0.8.1"
+object = { version = "0.30.3", features = ["read", "write"] }
+packed_struct = "0.10.1"
+page_size = "0.5.0"
+palette = "0.6.1"
parking_lot = "0.12"
peg = "0.8.1"
-pretty_assertions = "1.3.0"
-quickcheck = "1.0.3"
-quickcheck_macros = "1.0.0"
-regex = "1.5.5"
-rustyline = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
-rustyline-derive = {git = "https://github.com/roc-lang/rustyline", rev = "e74333c"}
-serde = { version = "1.0.144", features = ["derive"] }
-signal-hook = "0.3.14"
-snafu = { version = "0.7.1", features = ["backtraces"] }
-static_assertions = "1.1.0"
+perfcnt = "0.8.0"
+pest = "2.5.6"
+pest_derive = "2.5.6"
+pretty_assertions = "1.3.0" # update roc_std/Cargo.toml on change
+proc-macro2 = "1.0.51"
+proptest = "1.1.0"
+pulldown-cmark = { version = "0.9.2", default-features = false }
+quickcheck = "1.0.3" # update roc_std/Cargo.toml on change
+quickcheck_macros = "1.0.0" # update roc_std/Cargo.toml on change
+quote = "1.0.23"
+rand = "0.8.5"
+regex = "1.7.1"
+remove_dir_all = "0.8.1"
+reqwest = { version = "0.11.14", default-features = false, features = ["blocking", "rustls-tls"] } # default-features=false removes libopenssl as a dependency on Linux, which might not be available!
+rlimit = "0.9.1"
+rustyline = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
+rustyline-derive = { git = "https://github.com/roc-lang/rustyline", rev = "e74333c" }
+serde = { version = "1.0.153", features = ["derive"] } # update roc_std/Cargo.toml on change
+serde-xml-rs = "0.6.0"
+serde_json = "1.0.94" # update roc_std/Cargo.toml on change
+serial_test = "1.0.0"
+signal-hook = "0.3.15"
+snafu = { version = "0.7.4", features = ["backtraces"] }
+static_assertions = "1.1.0" # update roc_std/Cargo.toml on change
strip-ansi-escapes = "0.1.1"
strum = { version = "0.24.1", features = ["derive"] }
-target-lexicon = "0.12.3"
-tempfile = "3.2.0"
-unicode-segmentation = "1.10.0"
+strum_macros = "0.24.3"
+syn = { version = "1.0.109", features = ["full", "extra-traits"] }
+tar = "0.4.38"
+target-lexicon = "0.12.6"
+tempfile = "=3.2.0"
+threadpool = "1.8.1"
+tracing = { version = "0.1.37", features = ["release_max_level_off"] }
+tracing-appender = "0.2.2"
+tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
+unicode-segmentation = "1.10.1"
+uuid = { version = "1.3.0", features = ["v4"] }
walkdir = "2.3.2"
+wasm-bindgen = "0.2.84"
+wasm-bindgen-futures = "0.4.34"
+wgpu = "0.12.0"
+wgpu_glyph = "0.16.0"
+winapi = { version = "0.3.9", features = ["memoryapi"] }
+winit = "0.26.1"
wyhash = "0.5.0"
-# TODO: Deal with the update of object to 0.27.
-# It looks like it breaks linking the generated objects.
-# Probably just need to specify an extra field that used to be implicit or something.
-object = { version = "0.29.0", features = ["read", "write"] }
-
# Optimizations based on https://deterministic.space/high-performance-rust.html
[profile.release]
-lto = "thin"
codegen-units = 1
# debug = true # enable when profiling
[profile.bench]
-lto = "thin"
codegen-units = 1
+lto = "thin"
[profile.release-with-debug]
-inherits = "release"
debug = true
+inherits = "release"
+
+[profile.release-with-lto]
+lto = "thin" # TODO: We could consider full here since this is only used for packaged release on github.
+inherits = "release"
diff --git a/FAQ.md b/FAQ.md
index 02e21ca116..ff7fcfa6cd 100644
--- a/FAQ.md
+++ b/FAQ.md
@@ -276,7 +276,7 @@ So why does Roc have the specific syntax changes it does? Here are some brief ex
- No `<|` operator. In Elm, I almost exclusively found myself wanting to use this in conjunction with anonymous functions (e.g. `foo <| \bar -> ...`) or conditionals (e.g. `foo <| if bar then ...`). In Roc you can do both of these without the `<|`. That means the main remaining use for `<|` is to reduce parentheses, but I tend to think `|>` is better at that (or else the parens are fine), so after the other syntactic changes, I considered `<|` an unnecessary stylistic alternative to `|>` or parens.
- The `|>` operator passes the expression before the `|>` as the _first_ argument to the function after the `|>` instead of as the last argument. See the section on currying for details on why this works this way.
- `:` instead of `type alias` - I like to avoid reserved keywords for terms that are desirable in userspace, so that people don't have to name things `typ` because `type` is a reserved keyword, or `clazz` because `class` is reserved. (I couldn't think of satisfactory alternatives for `as`, `when`, `is`, or `if` other than different reserved keywords. I could see an argument for `then`—and maybe even `is`—being replaced with a `->` or `=>` or something, but I don't anticipate missing either of those words much in userspace. `then` is used in JavaScript promises, but I think there are several better names for that function.)
-- No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.)
+- No underscores in variable names - I've seen Elm beginners reflexively use `snake_case` over `camelCase` and then need to un-learn the habit after the compiler accepted it. I'd rather have the compiler give feedback that this isn't the way to do it in Roc, and suggest a camelCase alternative. I've also seen underscores used for lazy naming, e.g. `foo` and then `foo_`. If lazy naming is the goal, `foo2` is just as concise as `foo_`, but `foo3` is more concise than `foo__`. So in a way, removing `_` is a forcing function for improved laziness. (Of course, more descriptive naming would be even better.) Acronyms also use camelCase despite being capitalized in English, eg. `xmlHttpRequest` for a variable and `XmlHttpRequest` for a type. Each word starts with a capital letter, so if acronyms are only capitals it's harder to see where the words start. eg. `XMLHTTPRequest` is less clear than `XmlHttpRequest`, unless you already know the acronyms.
- Trailing commas - I've seen people walk away (in some cases physically!) from Elm as soon as they saw the leading commas in collection literals. While I think they've made a mistake by not pushing past this aesthetic preference to give the language a chance, I also would prefer not put them in a position to make such a mistake in the first place. Secondarily, while I'm personally fine with either style, between the two I prefer the look of trailing commas.
- The `!` unary prefix operator. I didn't want to have a `Basics` module (more on that in a moment), and without `Basics`, this would either need to be called fully-qualified (`Bool.not`) or else a module import of `Bool.{ not }` would be necessary. Both seemed less nice than supporting the `!` prefix that's common to so many widely-used languages, especially when we already have a unary prefix operator of `-` for negation (e.g. `-x`).
- `!=` for the inequality operator (instead of Elm's `/=`) - this one pairs more naturally with the `!` prefix operator and is also very common in other languages.
diff --git a/README.md b/README.md
index 0ce48d4b8c..3d122ce452 100644
--- a/README.md
+++ b/README.md
@@ -8,16 +8,28 @@ Roc is not ready for a 0.1 release yet, but we do have:
- [frequently asked questions](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [Zulip chat](https://roc.zulipchat.com) for help, questions and discussions
-If you'd like to get involved in contributing to the language, the Zulip chat is also the best place to get help with [good first issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
+If you'd like to contribute, check out [good first issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). Don't hesitate to ask for help on our [Zulip chat](https://roc.zulipchat.com), we're friendly!
## Sponsors
-We are very grateful to our sponsors [NoRedInk](https://www.noredink.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en).
+We are very grateful for our corporate sponsors [Vendr](https://www.vendr.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en).
-[
](https://www.noredink.com/)
+[
](https://www.vendr.com)
-[
](https://www.rwx.com)
+[
](https://www.rwx.com)
[
](https://tweedegolf.nl/en)
-If you or your employer would like to sponsor Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)!
+If you would like your company to become a corporate sponsor of Roc's development, please [DM Richard Feldman on Zulip](https://roc.zulipchat.com/#narrow/pm-with/281383-user281383)!
+
+We'd also like to express our gratitude to each and every one of our fantastic [GitHub sponsors](https://github.com/sponsors/roc-lang/)! A special thanks to those sponsoring $25/month or more:
+
+* [Christopher Dolan](https://github.com/cdolan)
+* [Nick Gravgaard](https://github.com/nickgravgaard)
+* [Aaron White](https://github.com/aaronwhite)
+* [Zeljko Nesic](https://github.com/popara)
+* [Shritesh Bhattarai](https://github.com/shritesh)
+* [Richard Feldman](https://github.com/rtfeldman)
+* [Ayaz Hafiz](https://github.com/ayazhafiz)
+
+Thank you all so much for helping Roc progress!
diff --git a/ci/benchmarks/bench-runner/Cargo.lock b/ci/benchmarks/bench-runner/Cargo.lock
deleted file mode 100644
index 465b503f3f..0000000000
--- a/ci/benchmarks/bench-runner/Cargo.lock
+++ /dev/null
@@ -1,422 +0,0 @@
-# This file is automatically @generated by Cargo.
-# It is not intended for manual editing.
-version = 3
-
-[[package]]
-name = "aho-corasick"
-version = "0.7.18"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-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.0.1"
-dependencies = [
- "clap",
- "data-encoding",
- "is_executable",
- "regex",
- "ring",
-]
-
-[[package]]
-name = "bitflags"
-version = "1.2.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-
-[[package]]
-name = "bumpalo"
-version = "3.12.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535"
-
-[[package]]
-name = "cc"
-version = "1.0.69"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
-
-[[package]]
-name = "cfg-if"
-version = "1.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
-
-[[package]]
-name = "clap"
-version = "3.1.15"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "85a35a599b11c089a7f49105658d089b8f2cf0882993c17daf6de15285c2c35d"
-dependencies = [
- "atty",
- "bitflags",
- "clap_derive",
- "clap_lex",
- "indexmap",
- "lazy_static",
- "strsim",
- "termcolor",
- "textwrap",
-]
-
-[[package]]
-name = "clap_derive"
-version = "3.1.7"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a3aab4734e083b809aaf5794e14e756d1c798d2c69c7f7de7a09a2f5214993c1"
-dependencies = [
- "heck",
- "proc-macro-error",
- "proc-macro2",
- "quote",
- "syn",
-]
-
-[[package]]
-name = "clap_lex"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
-dependencies = [
- "os_str_bytes",
-]
-
-[[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.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
-
-[[package]]
-name = "hermit-abi"
-version = "0.1.19"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
-dependencies = [
- "libc",
-]
-
-[[package]]
-name = "indexmap"
-version = "1.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5"
-dependencies = [
- "autocfg",
- "hashbrown",
-]
-
-[[package]]
-name = "is_executable"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fa9acdc6d67b75e626ad644734e8bc6df893d9cd2a834129065d3dd6158ea9c8"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "js-sys"
-version = "0.3.51"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
-dependencies = [
- "wasm-bindgen",
-]
-
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-
-[[package]]
-name = "libc"
-version = "0.2.98"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
-
-[[package]]
-name = "log"
-version = "0.4.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "memchr"
-version = "2.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b16bd47d9e329435e309c58469fe0791c2d0d1ba96ec0954152a5ae2b04387dc"
-
-[[package]]
-name = "once_cell"
-version = "1.8.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
-
-[[package]]
-name = "os_str_bytes"
-version = "6.0.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
-
-[[package]]
-name = "proc-macro-error"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
-dependencies = [
- "proc-macro-error-attr",
- "proc-macro2",
- "quote",
- "syn",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro-error-attr"
-version = "1.0.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
-dependencies = [
- "proc-macro2",
- "quote",
- "version_check",
-]
-
-[[package]]
-name = "proc-macro2"
-version = "1.0.28"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
-dependencies = [
- "unicode-xid",
-]
-
-[[package]]
-name = "quote"
-version = "1.0.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
-dependencies = [
- "proc-macro2",
-]
-
-[[package]]
-name = "regex"
-version = "1.5.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
-dependencies = [
- "aho-corasick",
- "memchr",
- "regex-syntax",
-]
-
-[[package]]
-name = "regex-syntax"
-version = "0.6.25"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
-
-[[package]]
-name = "ring"
-version = "0.16.20"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
-dependencies = [
- "cc",
- "libc",
- "once_cell",
- "spin",
- "untrusted",
- "web-sys",
- "winapi",
-]
-
-[[package]]
-name = "spin"
-version = "0.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-
-[[package]]
-name = "strsim"
-version = "0.10.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
-
-[[package]]
-name = "syn"
-version = "1.0.74"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
-dependencies = [
- "proc-macro2",
- "quote",
- "unicode-xid",
-]
-
-[[package]]
-name = "termcolor"
-version = "1.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
-dependencies = [
- "winapi-util",
-]
-
-[[package]]
-name = "textwrap"
-version = "0.15.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
-
-[[package]]
-name = "unicode-xid"
-version = "0.2.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
-
-[[package]]
-name = "untrusted"
-version = "0.7.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
-
-[[package]]
-name = "version_check"
-version = "0.9.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
-
-[[package]]
-name = "wasm-bindgen"
-version = "0.2.74"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
-dependencies = [
- "cfg-if",
- "wasm-bindgen-macro",
-]
-
-[[package]]
-name = "wasm-bindgen-backend"
-version = "0.2.74"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
-dependencies = [
- "bumpalo",
- "lazy_static",
- "log",
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-macro"
-version = "0.2.74"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
-dependencies = [
- "quote",
- "wasm-bindgen-macro-support",
-]
-
-[[package]]
-name = "wasm-bindgen-macro-support"
-version = "0.2.74"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
-dependencies = [
- "proc-macro2",
- "quote",
- "syn",
- "wasm-bindgen-backend",
- "wasm-bindgen-shared",
-]
-
-[[package]]
-name = "wasm-bindgen-shared"
-version = "0.2.74"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
-
-[[package]]
-name = "web-sys"
-version = "0.3.51"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
-dependencies = [
- "js-sys",
- "wasm-bindgen",
-]
-
-[[package]]
-name = "winapi"
-version = "0.3.9"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
-dependencies = [
- "winapi-i686-pc-windows-gnu",
- "winapi-x86_64-pc-windows-gnu",
-]
-
-[[package]]
-name = "winapi-i686-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-
-[[package]]
-name = "winapi-util"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
-dependencies = [
- "winapi",
-]
-
-[[package]]
-name = "winapi-x86_64-pc-windows-gnu"
-version = "0.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/ci/benchmarks/bench-runner/Cargo.toml b/ci/benchmarks/bench-runner/Cargo.toml
index 0534892f3c..477b6672ae 100644
--- a/ci/benchmarks/bench-runner/Cargo.toml
+++ b/ci/benchmarks/bench-runner/Cargo.toml
@@ -7,7 +7,7 @@ edition = "2021"
[dependencies]
clap = { version = "3.1.15", features = ["derive"] }
-regex = "1.5.5"
+data-encoding = "2.3.2"
is_executable = "1.0.1"
+regex = "1.5.5"
ring = "0.16.20"
-data-encoding = "2.3.2"
\ No newline at end of file
diff --git a/ci/build_basic_cli.sh b/ci/build_basic_cli.sh
index f472270ba3..5c78ce1df2 100755
--- a/ci/build_basic_cli.sh
+++ b/ci/build_basic_cli.sh
@@ -5,17 +5,24 @@ set -euxo pipefail
git clone https://github.com/roc-lang/basic-cli.git
-# Get the url of the latest release. We're not using the latest main source code for easier reproducibility.
-RELEASE_URL=$(./ci/get_latest_release_url.sh $1)
+cd basic-cli
+git checkout new-release
+cd ..
-# get the archive from the url
-curl -OL $RELEASE_URL
+if [ "$(uname -m)" == "x86_64" ] && [ "$(uname -s)" == "Linux" ]; then
+ sudo apt-get install musl-tools
+ cd basic-cli/src # we cd to install the target for the right rust version
+ rustup target add x86_64-unknown-linux-musl
+ cd ../..
+fi
+
+mv $(ls -d artifact/* | grep "roc_nightly.*tar\.gz" | grep "$1") ./roc_nightly.tar.gz
# decompress the tar
-ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
+tar -xzvf roc_nightly.tar.gz
# delete tar
-ls | grep -v "roc_nightly.*tar\.gz" | xargs rm -rf
+rm roc_nightly.tar.gz
# simplify dir name
mv roc_nightly* roc_nightly
@@ -23,14 +30,14 @@ mv roc_nightly* roc_nightly
cd roc_nightly
# build the basic cli platform
-./roc build ../basic-cli/examples/file.roc
+./roc build ../basic-cli/examples/countdown.roc
# We need this extra variable so we can safely check if $2 is empty later
EXTRA_ARGS=${2:-}
# In some rare cases it's nice to be able to use the legacy linker, so we produce the .o file to be able to do that
if [ -n "${EXTRA_ARGS}" ];
- then ./roc build $EXTRA_ARGS ../basic-cli/examples/file.roc
+ then ./roc build $EXTRA_ARGS ../basic-cli/examples/countdown.roc
fi
cd ..
diff --git a/ci/get_releases_json.sh b/ci/get_releases_json.sh
new file mode 100755
index 0000000000..560b38138f
--- /dev/null
+++ b/ci/get_releases_json.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+
+# retrieves roc_releases.json, expects AUTH_HEADER to be set
+
+# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
+set -euxo pipefail
+
+curl --request GET \
+ --url https://api.github.com/repos/roc-lang/roc/releases \
+ --header '$AUTH_HEADER' \
+ --header 'content-type: application/json' \
+ --output roc_releases.json
\ No newline at end of file
diff --git a/ci/package_release.sh b/ci/package_release.sh
index ea4f9c1495..e99680f3e5 100755
--- a/ci/package_release.sh
+++ b/ci/package_release.sh
@@ -3,7 +3,7 @@
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
-cp target/release/roc ./roc # to be able to delete "target" later
+cp target/release-with-lto/roc ./roc # to be able to delete "target" later
# delete unnecessary files and folders
git clean -fdx --exclude roc
diff --git a/crates/README.md b/crates/README.md
index 209992db82..db2dc86d80 100644
--- a/crates/README.md
+++ b/crates/README.md
@@ -60,7 +60,6 @@ The compiler includes the following sub-crates;
- `roc_serialize` provides helpers for serializing and deserializing to/from bytes.
- `roc_solve` The entry point of Roc's [type inference](https://en.wikipedia.org/wiki/Type_inference) system. Implements type inference and specialization of abilities.
- `roc_solve_problem` provides types to describe problems that can occur during solving.
-- `roc_str` provides `Roc` styled collection [reference counting](https://en.wikipedia.org/wiki/Reference_counting). See [README.md](./compiler/str/README.md) for more information.
- `test_derive` Tests Roc's auto-derivers.
- `test_gen` contains all of Roc's [code generation](https://en.wikipedia.org/wiki/Code_generation_(compiler)) tests. See [README.md](./compiler/test_gen/README.md) for more information.
- `test_mono` Tests Roc's generation of the mono intermediate representation.
diff --git a/crates/ast/Cargo.toml b/crates/ast/Cargo.toml
index a42b2c14ae..801af48e14 100644
--- a/crates/ast/Cargo.toml
+++ b/crates/ast/Cargo.toml
@@ -1,39 +1,39 @@
[package]
name = "roc_ast"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-roc_builtins = { path = "../compiler/builtins"}
+roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
roc_collections = { path = "../compiler/collections" }
-roc_region = { path = "../compiler/region" }
+roc_error_macros = { path = "../error_macros" }
+roc_load = { path = "../compiler/load" }
roc_module = { path = "../compiler/module" }
+roc_packaging = { path = "../packaging" }
roc_parse = { path = "../compiler/parse" }
roc_problem = { path = "../compiler/problem" }
-roc_types = { path = "../compiler/types" }
-roc_unify = { path = "../compiler/unify"}
-roc_solve = { path = "../compiler/solve"}
-roc_load = { path = "../compiler/load" }
-roc_target = { path = "../compiler/roc_target" }
-roc_error_macros = { path = "../error_macros" }
-roc_packaging = { path = "../packaging" }
+roc_region = { path = "../compiler/region" }
roc_reporting = { path = "../reporting" }
+roc_solve = { path = "../compiler/solve" }
+roc_target = { path = "../compiler/roc_target" }
+roc_types = { path = "../compiler/types" }
+roc_unify = { path = "../compiler/unify" }
ven_graph = { path = "../vendor/pathfinding" }
arrayvec.workspace = true
bumpalo.workspace = true
+libc.workspace = true
page_size.workspace = true
snafu.workspace = true
-libc.workspace = true
[dev-dependencies]
indoc.workspace = true
[target.'cfg(windows)'.dependencies]
-winapi = { version = "0.3.9", features = ["memoryapi"]}
-
+winapi.workspace = true
diff --git a/crates/ast/src/constrain.rs b/crates/ast/src/constrain.rs
index c6ea36e795..eb2d8080df 100644
--- a/crates/ast/src/constrain.rs
+++ b/crates/ast/src/constrain.rs
@@ -9,7 +9,7 @@ use roc_module::{
use roc_region::all::Region;
use roc_types::{
subs::Variable,
- types::{self, AnnotationSource, PReason, PatternCategory},
+ types::{self, AnnotationSource, IndexOrField, PReason, PatternCategory},
types::{Category, Reason},
};
@@ -375,7 +375,7 @@ pub fn constrain_expr<'a>(
env.pool.add(ext_type),
);
- let category = Category::RecordAccessor(field.as_str(env.pool).into());
+ let category = Category::Accessor(IndexOrField::Field(field.as_str(env.pool).into()));
let record_expected = Expected::NoExpectation(record_type.shallow_clone());
let record_con = Eq(
diff --git a/crates/ast/src/lang/core/expr/expr_to_expr2.rs b/crates/ast/src/lang/core/expr/expr_to_expr2.rs
index 9a551a4109..cdbc12a054 100644
--- a/crates/ast/src/lang/core/expr/expr_to_expr2.rs
+++ b/crates/ast/src/lang/core/expr/expr_to_expr2.rs
@@ -6,6 +6,7 @@ use roc_can::num::{
use roc_can::operator::desugar_expr;
use roc_collections::all::MutSet;
use roc_module::symbol::Symbol;
+use roc_parse::ident::Accessor;
use roc_parse::{ast::Expr, pattern::PatternType};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Loc, Region};
@@ -295,7 +296,7 @@ pub fn expr_to_expr2<'a>(
)
}
- RecordAccessorFunction(field) => (
+ AccessorFunction(Accessor::RecordField(field)) => (
Expr2::Accessor {
function_var: env.var_store.fresh(),
record_var: env.var_store.fresh(),
diff --git a/crates/ast/src/lang/core/types.rs b/crates/ast/src/lang/core/types.rs
index 7ef0b52e51..75ace432c8 100644
--- a/crates/ast/src/lang/core/types.rs
+++ b/crates/ast/src/lang/core/types.rs
@@ -405,7 +405,7 @@ pub fn to_type2<'a>(
Type2::Variable(var)
}
- Tuple { fields: _, ext: _ } => {
+ Tuple { elems: _, ext: _ } => {
todo!("tuple type");
}
Record { fields, ext, .. } => {
diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml
index ddc7c62344..6a4b2d9654 100644
--- a/crates/cli/Cargo.toml
+++ b/crates/cli/Cargo.toml
@@ -1,87 +1,81 @@
[package]
name = "roc_cli"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-repository = "https://github.com/roc-lang/roc"
-edition = "2021"
description = "The Roc binary that brings together all functionality in the Roc toolset."
default-run = "roc"
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
[[bin]]
+bench = false
name = "roc"
path = "src/main.rs"
test = false
-bench = false
[features]
-default = ["target-aarch64", "target-x86_64", "target-wasm32", "editor"]
+default = ["target-aarch64", "target-x86_64", "target-wasm32"]
-wasm32-cli-run = ["target-wasm32", "run-wasm32"]
i386-cli-run = ["target-x86"]
+wasm32-cli-run = ["target-wasm32", "run-wasm32"]
editor = ["roc_editor"]
run-wasm32 = ["roc_wasm_interp"]
# Compiling for a different target than the current machine can cause linker errors.
-target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
+target-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
+target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
target-x86_64 = ["roc_build/target-x86_64", "roc_repl_cli/target-x86_64"]
-target-wasm32 = ["roc_build/target-wasm32", "roc_repl_cli/target-wasm32"]
-target-all = [
- "target-aarch64",
- "target-arm",
- "target-x86",
- "target-x86_64",
- "target-wasm32"
-]
+target-all = ["target-aarch64", "target-arm", "target-x86", "target-x86_64", "target-wasm32"]
sanitizers = ["roc_build/sanitizers"]
[dependencies]
-roc_collections = { path = "../compiler/collections" }
+roc_build = { path = "../compiler/build" }
+roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
+roc_collections = { path = "../compiler/collections" }
roc_docs = { path = "../docs" }
+roc_editor = { path = "../editor", optional = true }
+roc_error_macros = { path = "../error_macros" }
+roc_fmt = { path = "../compiler/fmt" }
+roc_gen_llvm = { path = "../compiler/gen_llvm" }
roc_glue = { path = "../glue" }
+roc_linker = { path = "../linker" }
+roc_load = { path = "../compiler/load" }
+roc_module = { path = "../compiler/module" }
+roc_mono = { path = "../compiler/mono" }
+roc_packaging = { path = "../packaging" }
roc_parse = { path = "../compiler/parse" }
roc_region = { path = "../compiler/region" }
-roc_module = { path = "../compiler/module" }
-roc_builtins = { path = "../compiler/builtins" }
-roc_mono = { path = "../compiler/mono" }
-roc_load = { path = "../compiler/load" }
-roc_build = { path = "../compiler/build" }
-roc_fmt = { path = "../compiler/fmt" }
-roc_target = { path = "../compiler/roc_target" }
-roc_packaging = { path = "../packaging" }
-roc_reporting = { path = "../reporting" }
-roc_error_macros = { path = "../error_macros" }
-roc_editor = { path = "../editor", optional = true }
-roc_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true }
+roc_reporting = { path = "../reporting" }
+roc_target = { path = "../compiler/roc_target" }
roc_tracing = { path = "../tracing" }
-roc_gen_llvm = {path = "../compiler/gen_llvm"}
roc_wasm_interp = { path = "../wasm_interp", optional = true }
ven_pretty = { path = "../vendor/pretty" }
-indoc.workspace = true
+bumpalo.workspace = true
clap.workspace = true
const_format.workspace = true
-mimalloc.workspace = true
-bumpalo.workspace = true
-libc.workspace = true
errno.workspace = true
+indoc.workspace = true
+inkwell.workspace = true
+libc.workspace = true
+libloading.workspace = true
+mimalloc.workspace = true
+signal-hook.workspace = true
+strum.workspace = true
target-lexicon.workspace = true
tempfile.workspace = true
-strum.workspace = true
-libloading.workspace = true
-signal-hook.workspace = true
-
-inkwell.workspace = true
# for now, uses unix/libc functions that windows does not support
[target.'cfg(not(windows))'.dependencies]
@@ -89,15 +83,15 @@ roc_repl_expect = { path = "../repl_expect" }
[dev-dependencies]
-pretty_assertions = "1.3.0"
-roc_test_utils = { path = "../test_utils" }
-roc_utils = { path = "../utils" }
-indoc = "1.0.7"
-serial_test = "0.9.0"
-criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" }
-once_cell = "1.15.0"
-parking_lot = "0.12"
+roc_test_utils = { path = "../test_utils" }
+roc_command_utils = { path = "../utils/command" }
+
+criterion.workspace = true
+indoc.workspace = true
+parking_lot.workspace = true
+pretty_assertions.workspace = true
+serial_test.workspace = true
[[bench]]
name = "time_bench"
diff --git a/crates/cli/src/build.rs b/crates/cli/src/build.rs
deleted file mode 100644
index 3b1a673eb0..0000000000
--- a/crates/cli/src/build.rs
+++ /dev/null
@@ -1,628 +0,0 @@
-use bumpalo::Bump;
-use roc_build::{
- link::{
- legacy_host_filename, link, preprocess_host_wasm32, preprocessed_host_filename,
- rebuild_host, LinkType, LinkingStrategy,
- },
- program::{self, CodeGenBackend, CodeGenOptions},
-};
-use roc_builtins::bitcode;
-use roc_load::{
- EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
- LoadingProblem, Threading,
-};
-use roc_mono::ir::OptLevel;
-use roc_packaging::cache::RocCacheDir;
-use roc_reporting::{
- cli::Problems,
- report::{RenderTarget, DEFAULT_PALETTE},
-};
-use roc_target::TargetInfo;
-use std::{
- path::Path,
- time::{Duration, Instant},
-};
-use std::{path::PathBuf, thread::JoinHandle};
-use target_lexicon::Triple;
-
-fn report_timing(buf: &mut String, label: &str, duration: Duration) {
- use std::fmt::Write;
-
- writeln!(
- buf,
- " {:9.3} ms {}",
- duration.as_secs_f64() * 1000.0,
- label,
- )
- .unwrap()
-}
-
-pub struct BuiltFile<'a> {
- pub binary_path: PathBuf,
- pub problems: Problems,
- pub total_time: Duration,
- pub expect_metadata: ExpectMetadata<'a>,
-}
-
-pub enum BuildOrdering {
- /// Run up through typechecking first; continue building iff that is successful.
- BuildIfChecks,
- /// Always build the Roc binary, even if there are type errors.
- AlwaysBuild,
-}
-
-#[derive(Debug)]
-#[allow(clippy::large_enum_variant)]
-pub enum BuildFileError<'a> {
- LoadingProblem(LoadingProblem<'a>),
- ErrorModule {
- module: LoadedModule,
- total_time: Duration,
- },
-}
-
-impl<'a> BuildFileError<'a> {
- fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self {
- match error {
- LoadMonomorphizedError::LoadingProblem(problem) => {
- BuildFileError::LoadingProblem(problem)
- }
- LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule {
- module,
- total_time: compilation_start.elapsed(),
- },
- }
- }
-}
-
-pub fn standard_load_config(
- target: &Triple,
- order: BuildOrdering,
- threading: Threading,
-) -> LoadConfig {
- let target_info = TargetInfo::from(target);
-
- let exec_mode = match order {
- BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
- BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
- };
-
- LoadConfig {
- target_info,
- render: RenderTarget::ColorTerminal,
- palette: DEFAULT_PALETTE,
- threading,
- exec_mode,
- }
-}
-
-#[allow(clippy::too_many_arguments)]
-pub fn build_file<'a>(
- arena: &'a Bump,
- target: &Triple,
- app_module_path: PathBuf,
- code_gen_options: CodeGenOptions,
- emit_timings: bool,
- link_type: LinkType,
- linking_strategy: LinkingStrategy,
- prebuilt_requested: bool,
- wasm_dev_stack_bytes: Option,
- roc_cache_dir: RocCacheDir<'_>,
- load_config: LoadConfig,
-) -> Result, BuildFileError<'a>> {
- let compilation_start = Instant::now();
-
- // Step 1: compile the app and generate the .o file
- let loaded =
- roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config)
- .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
-
- build_loaded_file(
- arena,
- target,
- app_module_path,
- code_gen_options,
- emit_timings,
- link_type,
- linking_strategy,
- prebuilt_requested,
- wasm_dev_stack_bytes,
- loaded,
- compilation_start,
- )
-}
-
-#[allow(clippy::too_many_arguments)]
-fn build_loaded_file<'a>(
- arena: &'a Bump,
- target: &Triple,
- app_module_path: PathBuf,
- code_gen_options: CodeGenOptions,
- emit_timings: bool,
- link_type: LinkType,
- linking_strategy: LinkingStrategy,
- prebuilt_requested: bool,
- wasm_dev_stack_bytes: Option,
- loaded: roc_load::MonomorphizedModule<'a>,
- compilation_start: Instant,
-) -> Result, BuildFileError<'a>> {
- let operating_system = roc_target::OperatingSystem::from(target.operating_system);
-
- let platform_main_roc = match &loaded.entry_point {
- EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
- _ => unreachable!(),
- };
-
- // the preprocessed host is stored beside the platform's main.roc
- let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
- if let roc_target::OperatingSystem::Wasi = operating_system {
- // when compiling a wasm application, we implicitly assume here that the host is in zig
- // and has a file called "host.zig"
- platform_main_roc.with_file_name("host.zig")
- } else {
- platform_main_roc.with_file_name(legacy_host_filename(target).unwrap())
- }
- } else {
- platform_main_roc.with_file_name(preprocessed_host_filename(target).unwrap())
- };
-
- // For example, if we're loading the platform from a URL, it's automatically prebuilt
- // even if the --prebuilt-platform=true CLI flag wasn't set.
- let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
-
- let cwd = app_module_path.parent().unwrap();
- let mut output_exe_path = cwd.join(&*loaded.output_path);
-
- if let Some(extension) = operating_system.executable_file_ext() {
- output_exe_path.set_extension(extension);
- }
-
- // We don't need to spawn a rebuild thread when using a prebuilt host.
- let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
- None
- } else if is_platform_prebuilt {
- if !preprocessed_host_path.exists() {
- invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
-
- std::process::exit(1);
- }
-
- if linking_strategy == LinkingStrategy::Surgical {
- // Copy preprocessed host to executable location.
- // The surgical linker will modify that copy in-place.
- std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
- }
-
- None
- } else {
- // TODO this should probably be moved before load_and_monomorphize.
- // To do this we will need to preprocess files just for their exported symbols.
- // Also, we should no longer need to do this once we have platforms on
- // a package repository, as we can then get prebuilt platforms from there.
-
- let exposed_values = loaded
- .exposed_to_host
- .values
- .keys()
- .map(|x| x.as_str(&loaded.interns).to_string())
- .collect();
-
- let exposed_closure_types = loaded
- .exposed_to_host
- .closure_types
- .iter()
- .map(|x| {
- format!(
- "{}_{}",
- x.module_string(&loaded.interns),
- x.as_str(&loaded.interns)
- )
- })
- .collect();
-
- let join_handle = spawn_rebuild_thread(
- code_gen_options.opt_level,
- linking_strategy,
- platform_main_roc.clone(),
- preprocessed_host_path.clone(),
- output_exe_path.clone(),
- target,
- exposed_values,
- exposed_closure_types,
- );
-
- Some(join_handle)
- };
-
- let buf = &mut String::with_capacity(1024);
-
- let mut it = loaded.timings.iter().peekable();
- while let Some((module_id, module_timing)) = it.next() {
- let module_name = loaded.interns.module_name(*module_id);
-
- buf.push_str(" ");
-
- if module_name.is_empty() {
- // the App module
- buf.push_str("Application Module");
- } else {
- buf.push_str(module_name);
- }
-
- buf.push('\n');
-
- use std::fmt::Write;
- write!(buf, "{}", module_timing).unwrap();
-
- if it.peek().is_some() {
- buf.push('\n');
- }
- }
-
- // This only needs to be mutable for report_problems. This can't be done
- // inside a nested scope without causing a borrow error!
- let mut loaded = loaded;
- let problems = program::report_problems_monomorphized(&mut loaded);
- let loaded = loaded;
-
- enum HostRebuildTiming {
- BeforeApp(u128),
- ConcurrentWithApp(JoinHandle),
- }
-
- let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
- if linking_strategy == LinkingStrategy::Additive {
- let rebuild_duration = rebuild_thread
- .join()
- .expect("Failed to (re)build platform.");
-
- if emit_timings && !is_platform_prebuilt {
- println!(
- "Finished rebuilding the platform in {} ms\n",
- rebuild_duration
- );
- }
-
- Some(HostRebuildTiming::BeforeApp(rebuild_duration))
- } else {
- Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
- }
- } else {
- None
- };
-
- let (roc_app_bytes, code_gen_timing, expect_metadata) = program::gen_from_mono_module(
- arena,
- loaded,
- &app_module_path,
- target,
- code_gen_options,
- &preprocessed_host_path,
- wasm_dev_stack_bytes,
- );
-
- buf.push('\n');
- buf.push_str(" ");
- buf.push_str("Code Generation");
- buf.push('\n');
-
- report_timing(
- buf,
- "Generate Assembly from Mono IR",
- code_gen_timing.code_gen,
- );
-
- let compilation_end = compilation_start.elapsed();
- let size = roc_app_bytes.len();
-
- if emit_timings {
- println!(
- "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
- buf
- );
-
- println!(
- "Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
- compilation_end.as_millis(),
- size,
- );
- }
-
- if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
- let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
-
- if emit_timings && !is_platform_prebuilt {
- println!(
- "Finished rebuilding the platform in {} ms\n",
- rebuild_duration
- );
- }
- }
-
- // Step 2: link the prebuilt platform and compiled app
- let link_start = Instant::now();
-
- match (linking_strategy, link_type) {
- (LinkingStrategy::Surgical, _) => {
- roc_linker::link_preprocessed_host(
- target,
- &platform_main_roc,
- &roc_app_bytes,
- &output_exe_path,
- );
- }
- (LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
- // Just copy the object file to the output folder.
- output_exe_path.set_extension(operating_system.object_file_ext());
- std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap();
- }
- (LinkingStrategy::Legacy, _) => {
- let app_o_file = tempfile::Builder::new()
- .prefix("roc_app")
- .suffix(&format!(".{}", operating_system.object_file_ext()))
- .tempfile()
- .map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
- let app_o_file = app_o_file.path();
-
- std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
-
- let builtins_host_tempfile =
- bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
-
- let mut inputs = vec![app_o_file.to_str().unwrap()];
-
- if !matches!(link_type, LinkType::Dylib | LinkType::None) {
- // the host has been compiled into a .o or .obj file
- inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
- }
-
- if matches!(code_gen_options.backend, program::CodeGenBackend::Assembly) {
- inputs.push(builtins_host_tempfile.path().to_str().unwrap());
- }
-
- let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
- .map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
-
- let exit_status = child
- .wait()
- .map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
-
- // Extend the lifetime of the tempfile so it doesn't get dropped
- // (and thus deleted) before the child process is done using it!
- let _ = builtins_host_tempfile;
-
- if !exit_status.success() {
- todo!(
- "gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
- exit_status.code()
- );
- }
- }
- }
-
- let linking_time = link_start.elapsed();
-
- if emit_timings {
- println!("Finished linking in {} ms\n", linking_time.as_millis());
- }
-
- let total_time = compilation_start.elapsed();
-
- Ok(BuiltFile {
- binary_path: output_exe_path,
- problems,
- total_time,
- expect_metadata,
- })
-}
-
-fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
- let prefix = match prebuilt_requested {
- true => "Because I was run with --prebuilt-platform=true, ",
- false => "",
- };
-
- eprintln!(
- indoc::indoc!(
- r#"
- {}I was expecting this file to exist:
-
- {}
-
- However, it was not there!
-
- If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false
- "#
- ),
- prefix,
- preprocessed_host_path.to_string_lossy(),
- );
-}
-
-#[allow(clippy::too_many_arguments)]
-fn spawn_rebuild_thread(
- opt_level: OptLevel,
- linking_strategy: LinkingStrategy,
- platform_main_roc: PathBuf,
- preprocessed_host_path: PathBuf,
- output_exe_path: PathBuf,
- target: &Triple,
- exported_symbols: Vec,
- exported_closure_types: Vec,
-) -> std::thread::JoinHandle {
- let thread_local_target = target.clone();
- std::thread::spawn(move || {
- // Printing to stderr because we want stdout to contain only the output of the roc program.
- // We are aware of the trade-offs.
- // `cargo run` follows the same approach
- eprintln!("🔨 Rebuilding platform...");
-
- let rebuild_host_start = Instant::now();
-
- match linking_strategy {
- LinkingStrategy::Additive => {
- let host_dest = rebuild_host(
- opt_level,
- &thread_local_target,
- platform_main_roc.as_path(),
- None,
- );
-
- preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
- }
- LinkingStrategy::Surgical => {
- roc_linker::build_and_preprocess_host(
- opt_level,
- &thread_local_target,
- platform_main_roc.as_path(),
- preprocessed_host_path.as_path(),
- exported_symbols,
- exported_closure_types,
- );
-
- // Copy preprocessed host to executable location.
- // The surgical linker will modify that copy in-place.
- std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
- }
- LinkingStrategy::Legacy => {
- rebuild_host(
- opt_level,
- &thread_local_target,
- platform_main_roc.as_path(),
- None,
- );
- }
- }
-
- rebuild_host_start.elapsed().as_millis()
- })
-}
-
-#[allow(clippy::too_many_arguments)]
-pub fn check_file<'a>(
- arena: &'a Bump,
- roc_file_path: PathBuf,
- emit_timings: bool,
- roc_cache_dir: RocCacheDir<'_>,
- threading: Threading,
-) -> Result<(Problems, Duration), LoadingProblem<'a>> {
- let compilation_start = Instant::now();
-
- // only used for generating errors. We don't do code generation, so hardcoding should be fine
- // we need monomorphization for when exhaustiveness checking
- let target_info = TargetInfo::default_x86_64();
-
- // Step 1: compile the app and generate the .o file
-
- let load_config = LoadConfig {
- target_info,
- // TODO: expose this from CLI?
- render: RenderTarget::ColorTerminal,
- palette: DEFAULT_PALETTE,
- threading,
- exec_mode: ExecutionMode::Check,
- };
- let mut loaded =
- roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?;
-
- let buf = &mut String::with_capacity(1024);
-
- let mut it = loaded.timings.iter().peekable();
- while let Some((module_id, module_timing)) = it.next() {
- let module_name = loaded.interns.module_name(*module_id);
-
- buf.push_str(" ");
-
- if module_name.is_empty() {
- // the App module
- buf.push_str("Application Module");
- } else {
- buf.push_str(module_name);
- }
-
- buf.push('\n');
-
- report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
- report_timing(buf, "Parse header", module_timing.parse_header);
- report_timing(buf, "Parse body", module_timing.parse_body);
- report_timing(buf, "Canonicalize", module_timing.canonicalize);
- report_timing(buf, "Constrain", module_timing.constrain);
- report_timing(buf, "Solve", module_timing.solve);
- report_timing(buf, "Other", module_timing.other());
- buf.push('\n');
- report_timing(buf, "Total", module_timing.total());
-
- if it.peek().is_some() {
- buf.push('\n');
- }
- }
-
- let compilation_end = compilation_start.elapsed();
-
- if emit_timings {
- println!(
- "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
- buf
- );
-
- println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
- }
-
- Ok((
- program::report_problems_typechecked(&mut loaded),
- compilation_end,
- ))
-}
-
-pub fn build_str_test<'a>(
- arena: &'a Bump,
- app_module_path: &Path,
- app_module_source: &'a str,
- assume_prebuild: bool,
-) -> Result, BuildFileError<'a>> {
- let triple = target_lexicon::Triple::host();
-
- let code_gen_options = CodeGenOptions {
- backend: CodeGenBackend::Llvm,
- opt_level: OptLevel::Normal,
- emit_debug_info: false,
- };
-
- let emit_timings = false;
- let link_type = LinkType::Executable;
- let linking_strategy = LinkingStrategy::Surgical;
- let wasm_dev_stack_bytes = None;
-
- let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
- let build_ordering = BuildOrdering::AlwaysBuild;
- let threading = Threading::AtMost(2);
-
- let load_config = standard_load_config(&triple, build_ordering, threading);
-
- let compilation_start = std::time::Instant::now();
-
- // Step 1: compile the app and generate the .o file
- let loaded = roc_load::load_and_monomorphize_from_str(
- arena,
- PathBuf::from("valgrind_test.roc"),
- app_module_source,
- app_module_path.to_path_buf(),
- roc_cache_dir,
- load_config,
- )
- .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
-
- build_loaded_file(
- arena,
- &triple,
- app_module_path.to_path_buf(),
- code_gen_options,
- emit_timings,
- link_type,
- linking_strategy,
- assume_prebuild,
- wasm_dev_stack_bytes,
- loaded,
- compilation_start,
- )
-}
diff --git a/crates/cli/src/lib.rs b/crates/cli/src/lib.rs
index 6d48900d05..0627edb390 100644
--- a/crates/cli/src/lib.rs
+++ b/crates/cli/src/lib.rs
@@ -3,13 +3,16 @@
#[macro_use]
extern crate const_format;
-use build::BuiltFile;
use bumpalo::Bump;
use clap::{Arg, ArgMatches, Command, ValueSource};
use roc_build::link::{LinkType, LinkingStrategy};
-use roc_build::program::{CodeGenBackend, CodeGenOptions};
+use roc_build::program::{
+ handle_error_module, handle_loading_problem, standard_load_config, BuildFileError,
+ BuildOrdering, BuiltFile, CodeGenBackend, CodeGenOptions, DEFAULT_ROC_FILENAME,
+};
use roc_error_macros::{internal_error, user_error};
-use roc_load::{ExpectMetadata, LoadingProblem, Threading};
+use roc_gen_llvm::llvm::build::LlvmBackendMode;
+use roc_load::{ExpectMetadata, Threading};
use roc_mono::ir::OptLevel;
use roc_packaging::cache::RocCacheDir;
use roc_packaging::tarball::Compression;
@@ -29,14 +32,9 @@ use target_lexicon::{
#[cfg(not(target_os = "linux"))]
use tempfile::TempDir;
-pub mod build;
mod format;
pub use format::format;
-use crate::build::{standard_load_config, BuildFileError, BuildOrdering};
-
-const DEFAULT_ROC_FILENAME: &str = "main.roc";
-
pub const CMD_BUILD: &str = "build";
pub const CMD_RUN: &str = "run";
pub const CMD_DEV: &str = "dev";
@@ -66,7 +64,8 @@ pub const FLAG_CHECK: &str = "check";
pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb";
pub const ROC_FILE: &str = "ROC_FILE";
pub const ROC_DIR: &str = "ROC_DIR";
-pub const GLUE_FILE: &str = "GLUE_FILE";
+pub const GLUE_DIR: &str = "GLUE_DIR";
+pub const GLUE_SPEC: &str = "GLUE_SPEC";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
@@ -281,17 +280,24 @@ pub fn build_app<'a>() -> Command<'a> {
.subcommand(Command::new(CMD_GLUE)
.about("Generate glue code between a platform's Roc API and its host language")
.arg(
- Arg::new(ROC_FILE)
- .help("The .roc file for the platform module")
+ Arg::new(GLUE_SPEC)
+ .help("The specification for how to translate Roc types into output files.")
.allow_invalid_utf8(true)
.required(true)
)
.arg(
- Arg::new(GLUE_FILE)
- .help("The filename for the generated glue code\n(Currently, this must be a .rs file because only Rust glue generation is supported so far.)")
+ Arg::new(GLUE_DIR)
+ .help("The directory for the generated glue code.\nNote: The implementation can write to any file in this directory.")
.allow_invalid_utf8(true)
.required(true)
)
+ .arg(
+ Arg::new(ROC_FILE)
+ .help("The .roc file whose exposed types should be translated.")
+ .allow_invalid_utf8(true)
+ .required(false)
+ .default_value(DEFAULT_ROC_FILENAME)
+ )
)
.subcommand(Command::new(CMD_GEN_STUB_LIB)
.about("Generate a stubbed shared library that can be used for linking a platform binary.\nThe stubbed library has prototypes, but no function bodies.\n\nNote: This command will be removed in favor of just using `roc build` once all platforms support the surgical linker")
@@ -332,6 +338,7 @@ pub fn build_app<'a>() -> Command<'a> {
Arg::new(DIRECTORY_OR_FILES)
.multiple_values(true)
.required(false)
+ .allow_invalid_utf8(true)
.help("(optional) The directory or files to open on launch"),
),
)
@@ -360,7 +367,6 @@ pub fn test(_matches: &ArgMatches, _triple: Triple) -> io::Result {
#[cfg(not(windows))]
pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result {
use roc_build::program::report_problems_monomorphized;
- use roc_gen_llvm::llvm::build::LlvmBackendMode;
use roc_load::{ExecutionMode, LoadConfig, LoadMonomorphizedError};
use roc_packaging::cache;
use roc_target::TargetInfo;
@@ -520,7 +526,7 @@ pub fn build(
roc_cache_dir: RocCacheDir<'_>,
link_type: LinkType,
) -> io::Result {
- use build::build_file;
+ use roc_build::program::build_file;
use BuildConfig::*;
let filename = matches.value_of_os(ROC_FILE).unwrap();
@@ -594,15 +600,6 @@ pub fn build(
// so we don't want to spend time freeing these values
let arena = ManuallyDrop::new(Bump::new());
- let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
- CodeGenBackend::Wasm
- } else {
- match matches.is_present(FLAG_DEV) {
- true => CodeGenBackend::Assembly,
- false => CodeGenBackend::Llvm,
- }
- };
-
let opt_level = if let BuildConfig::BuildAndRunIfNoErrors = config {
OptLevel::Development
} else {
@@ -618,6 +615,25 @@ pub fn build(
}
}
};
+
+ let code_gen_backend = if matches!(triple.architecture, Architecture::Wasm32) {
+ CodeGenBackend::Wasm
+ } else {
+ match matches.is_present(FLAG_DEV) {
+ true => CodeGenBackend::Assembly,
+ false => {
+ let backend_mode = match opt_level {
+ OptLevel::Development => LlvmBackendMode::BinaryDev,
+ OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => {
+ LlvmBackendMode::Binary
+ }
+ };
+
+ CodeGenBackend::Llvm(backend_mode)
+ }
+ }
+ };
+
let emit_debug_info = matches.is_present(FLAG_DEBUG);
let emit_timings = matches.is_present(FLAG_TIME);
@@ -760,48 +776,6 @@ pub fn build(
}
}
-fn handle_error_module(
- mut module: roc_load::LoadedModule,
- total_time: std::time::Duration,
- filename: &OsStr,
- print_run_anyway_hint: bool,
-) -> io::Result {
- debug_assert!(module.total_problems() > 0);
-
- let problems = roc_build::program::report_problems_typechecked(&mut module);
-
- problems.print_to_stdout(total_time);
-
- if print_run_anyway_hint {
- // If you're running "main.roc" then you can just do `roc run`
- // to re-run the program.
- print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
-
- if filename != DEFAULT_ROC_FILENAME {
- print!(" {}", &filename.to_string_lossy());
- }
-
- println!("\x1B[39m");
- }
-
- Ok(problems.exit_code())
-}
-
-fn handle_loading_problem(problem: LoadingProblem) -> io::Result {
- match problem {
- LoadingProblem::FormattedReport(report) => {
- print!("{}", report);
- Ok(1)
- }
- _ => {
- // TODO: tighten up the types here, we should always end up with a
- // formatted report from load.
- print!("Failed with error: {:?}", problem);
- Ok(1)
- }
- }
-}
-
fn roc_run<'a, I: IntoIterator- >(
arena: &Bump,
opt_level: OptLevel,
@@ -1323,40 +1297,3 @@ impl std::str::FromStr for Target {
}
}
}
-
-// These functions don't end up in the final Roc binary but Windows linker needs a definition inside the crate.
-// On Windows, there seems to be less dead-code-elimination than on Linux or MacOS, or maybe it's done later.
-#[cfg(windows)]
-#[allow(unused_imports)]
-use windows_roc_platform_functions::*;
-
-#[cfg(windows)]
-mod windows_roc_platform_functions {
- use core::ffi::c_void;
-
- /// # Safety
- /// The Roc application needs this.
- #[no_mangle]
- pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
- libc::malloc(size)
- }
-
- /// # Safety
- /// The Roc application needs this.
- #[no_mangle]
- pub unsafe fn roc_realloc(
- c_ptr: *mut c_void,
- new_size: usize,
- _old_size: usize,
- _alignment: u32,
- ) -> *mut c_void {
- libc::realloc(c_ptr, new_size)
- }
-
- /// # Safety
- /// The Roc application needs this.
- #[no_mangle]
- pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
- libc::free(c_ptr)
- }
-}
diff --git a/crates/cli/src/main.rs b/crates/cli/src/main.rs
index debaa4f7f4..35db201dfb 100644
--- a/crates/cli/src/main.rs
+++ b/crates/cli/src/main.rs
@@ -1,11 +1,11 @@
//! The `roc` binary that brings together all functionality in the Roc toolset.
use roc_build::link::LinkType;
-use roc_cli::build::check_file;
+use roc_build::program::check_file;
use roc_cli::{
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV,
CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GEN_STUB_LIB, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST,
CMD_VERSION, DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME,
- GLUE_FILE, ROC_FILE,
+ GLUE_DIR, GLUE_SPEC, ROC_FILE,
};
use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
@@ -88,12 +88,13 @@ fn main() -> io::Result<()> {
}
Some((CMD_GLUE, matches)) => {
let input_path = Path::new(matches.value_of_os(ROC_FILE).unwrap());
- let output_path = Path::new(matches.value_of_os(GLUE_FILE).unwrap());
+ let output_path = Path::new(matches.value_of_os(GLUE_DIR).unwrap());
+ let spec_path = Path::new(matches.value_of_os(GLUE_SPEC).unwrap());
- if Some("rs") == output_path.extension().and_then(OsStr::to_str) {
- roc_glue::generate(input_path, output_path)
+ if !output_path.exists() || output_path.is_dir() {
+ roc_glue::generate(input_path, output_path, spec_path)
} else {
- eprintln!("Currently, `roc glue` only supports generating Rust glue files (with the .rs extension). In the future, the plan is to decouple `roc glue` from any particular output format, by having it accept a second .roc file which gets executed as a plugin to generate glue code for any desired language. However, this has not yet been implemented, and for now only .rs is supported.");
+ eprintln!("`roc glue` must be given a directory to output into, because the glue might generate multiple files.");
Ok(1)
}
diff --git a/crates/cli/tests/cli_run.rs b/crates/cli/tests/cli_run.rs
index 9b39c38d7d..5aa6021c45 100644
--- a/crates/cli/tests/cli_run.rs
+++ b/crates/cli/tests/cli_run.rs
@@ -474,7 +474,8 @@ mod cli_run {
}
#[test]
- #[ignore = "Prebuilt platforms cause problems with nix and NixOS. This is run explicitly tested on CI (.github/workflows/ubuntu_x86_64.yml)"]
+ #[serial(basic_cli_url)]
+ #[cfg_attr(windows, ignore)]
fn hello_world() {
test_roc_app_slim(
"examples",
@@ -533,6 +534,7 @@ mod cli_run {
}
#[test]
+ #[serial(zig_platform)]
#[cfg_attr(windows, ignore)]
fn platform_switching_zig() {
test_roc_app_slim(
@@ -675,6 +677,8 @@ mod cli_run {
)
}
+ #[cfg_attr(windows, ignore)] // flaky error; issue #5024
+ #[serial(breakout)]
#[test]
fn breakout() {
test_roc_app_slim(
@@ -686,6 +690,18 @@ mod cli_run {
)
}
+ #[test]
+ #[serial(breakout)]
+ fn breakout_hello_gui() {
+ test_roc_app_slim(
+ "examples/gui/breakout",
+ "hello-gui.roc",
+ "hello-gui",
+ "",
+ UseValgrind::No,
+ )
+ }
+
#[test]
#[cfg_attr(windows, ignore)]
fn quicksort() {
@@ -790,10 +806,10 @@ mod cli_run {
"False.roc",
"false",
&[],
- &[Arg::ExamplePath("examples/hello.false")],
+ &[Arg::ExamplePath("examples/sqrt.false")],
&[],
- &("Hello, World!".to_string() + LINE_ENDING),
- UseValgrind::No,
+ "1414",
+ UseValgrind::Yes,
TestCliCommands::Many,
)
}
@@ -819,7 +835,7 @@ mod cli_run {
&[],
&[Arg::ExamplePath("input"), Arg::ExamplePath("output")],
&[],
- "Processed 3 files with 3 successes and 0 errors\n",
+ "Processed 4 files with 3 successes and 0 errors\n",
UseValgrind::No,
TestCliCommands::Run,
)
@@ -849,6 +865,8 @@ mod cli_run {
}
#[test]
+ #[serial(parser_package)]
+ #[serial(zig_platform)]
#[cfg_attr(windows, ignore)]
fn parse_movies_csv() {
test_roc_app_slim(
@@ -861,7 +879,9 @@ mod cli_run {
}
#[test]
- #[ignore = "Prebuilt platforms cause problems with nix and NixOS. This is run explicitly tested on CI (.github/workflows/ubuntu_x86_64.yml)"]
+ #[serial(parser_package)]
+ #[serial(basic_cli_url)]
+ #[cfg_attr(windows, ignore)]
fn parse_letter_counts() {
test_roc_app_slim(
"examples/parser/examples",
diff --git a/crates/cli/tests/editor.rs b/crates/cli/tests/editor.rs
index 054e48065d..f6ec81e579 100644
--- a/crates/cli/tests/editor.rs
+++ b/crates/cli/tests/editor.rs
@@ -8,25 +8,40 @@ mod editor_launch_test {
thread,
};
- use cli_utils::helpers::build_roc_bin_cached;
+ use cli_utils::helpers::build_roc_bin;
use roc_cli::CMD_EDIT;
- use roc_utils::root_dir;
+ use roc_command_utils::root_dir;
use std::io::Read;
- // ignored because we don't want to bring up the editor window during regular tests, only on specific CI machines
- #[ignore]
+ #[ignore = "We don't want to bring up the editor window during regular tests, only on specific CI machines."]
#[test]
- fn launch() {
+ fn launch_test() {
+ launch(None);
+
+ // with a file arg
+ launch(Some("roc-projects/new-roc-project-1/main.roc"));
+
+ // with a folder arg
+ launch(Some("roc-projects/new-roc-project-1"));
+ }
+
+ fn launch(arg_path_str_opt: Option<&str>) {
let root_dir = root_dir();
// The editor expects to be run from the root of the repo, so it can find the cli-platform to init a new project folder.
env::set_current_dir(&root_dir)
.unwrap_or_else(|_| panic!("Failed to set current dir to {:?}", root_dir));
- let roc_binary_path = build_roc_bin_cached();
+ let roc_binary_path = build_roc_bin(&["--features", "editor"]);
+
+ let mut cmd_args = vec![CMD_EDIT];
+
+ if let Some(arg_path_str) = arg_path_str_opt {
+ cmd_args.push(arg_path_str)
+ }
let mut roc_process = Command::new(roc_binary_path)
- .arg(CMD_EDIT)
+ .args(cmd_args)
.stdout(Stdio::piped())
.spawn()
.expect("Failed to start editor from cli.");
diff --git a/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig b/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig
index bb797f2bf3..857772ff27 100644
--- a/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig
+++ b/crates/cli/tests/fixtures/multi-dep-str/platform/host.zig
@@ -115,7 +115,7 @@ pub export fn main() i32 {
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
- callresult.deinit();
+ callresult.decref();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
diff --git a/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig b/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig
index 95f4ab1d89..7030d8276b 100644
--- a/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig
+++ b/crates/cli/tests/fixtures/multi-dep-thunk/platform/host.zig
@@ -114,7 +114,7 @@ pub export fn main() i32 {
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
- callresult.deinit();
+ callresult.decref();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
diff --git a/crates/cli/tests/fixtures/packages/platform/host.zig b/crates/cli/tests/fixtures/packages/platform/host.zig
index bb797f2bf3..857772ff27 100644
--- a/crates/cli/tests/fixtures/packages/platform/host.zig
+++ b/crates/cli/tests/fixtures/packages/platform/host.zig
@@ -115,7 +115,7 @@ pub export fn main() i32 {
// stdout the result
stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
- callresult.deinit();
+ callresult.decref();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
diff --git a/crates/cli_testing_examples/benchmarks/platform/host.zig b/crates/cli_testing_examples/benchmarks/platform/host.zig
index e072951995..63dd9ef9e3 100644
--- a/crates/cli_testing_examples/benchmarks/platform/host.zig
+++ b/crates/cli_testing_examples/benchmarks/platform/host.zig
@@ -25,10 +25,10 @@ const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed_generic([*]u8) void;
-extern fn roc__mainForHost_size() i64;
-extern fn roc__mainForHost_1__Fx_caller(*const u8, [*]u8, [*]u8) void;
-extern fn roc__mainForHost_1__Fx_size() i64;
-extern fn roc__mainForHost_1__Fx_result_size() i64;
+extern fn roc__mainForHost_1_exposed_size() i64;
+extern fn roc__mainForHost_0_caller(*const u8, [*]u8, [*]u8) void;
+extern fn roc__mainForHost_0_size() i64;
+extern fn roc__mainForHost_0_result_size() i64;
const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) anyopaque;
@@ -123,7 +123,7 @@ pub fn main() !u8 {
const stderr = std.io.getStdErr().writer();
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
- const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
+ const size = std.math.max(@intCast(usize, roc__mainForHost_1_exposed_size()), 8);
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
var output = @ptrCast([*]u8, raw_output);
@@ -155,7 +155,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator;
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
- const size = std.math.max(roc__mainForHost_1__Fx_result_size(), 8);
+ const size = std.math.max(roc__mainForHost_0_result_size(), 8);
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output);
@@ -165,7 +165,7 @@ fn call_the_closure(closure_data_pointer: [*]u8) void {
const flags: u8 = 0;
- roc__mainForHost_1__Fx_caller(&flags, closure_data_pointer, output);
+ roc__mainForHost_0_caller(&flags, closure_data_pointer, output);
// The closure returns result, nothing interesting to do with it
return;
diff --git a/crates/cli_testing_examples/expects/zig-platform/host.zig b/crates/cli_testing_examples/expects/zig-platform/host.zig
index 0d54bc79a3..7e80941376 100644
--- a/crates/cli_testing_examples/expects/zig-platform/host.zig
+++ b/crates/cli_testing_examples/expects/zig-platform/host.zig
@@ -134,7 +134,7 @@ pub fn main() u8 {
// stdout the result
stdout.print("{s}", .{callresult.asSlice()}) catch unreachable;
- callresult.deinit();
+ callresult.decref();
stderr.print("runtime: {d:.3}ms\n", .{seconds * 1000}) catch unreachable;
diff --git a/crates/cli_utils/Cargo.toml b/crates/cli_utils/Cargo.toml
index 3ac3952ece..2530866df5 100644
--- a/crates/cli_utils/Cargo.toml
+++ b/crates/cli_utils/Cargo.toml
@@ -1,27 +1,25 @@
[package]
name = "cli_utils"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-repository = "https://github.com/roc-lang/roc"
-edition = "2021"
description = "Provides shared code for cli tests and benchmarks."
-# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
[dependencies]
roc_collections = { path = "../compiler/collections" }
-roc_reporting = { path = "../reporting" }
roc_load = { path = "../compiler/load" }
roc_module = { path = "../compiler/module" }
-roc_utils = { path = "../utils" }
+roc_reporting = { path = "../reporting" }
+roc_command_utils = { path = "../utils/command" }
-bumpalo = { version = "3.8.0", features = ["collections"] }
-criterion = { git = "https://github.com/Anton-4/criterion.rs"}
-serde = { version = "1.0.130", features = ["derive"] }
-serde-xml-rs = "0.5.1"
-strip-ansi-escapes = "0.1.1"
-tempfile = "3.2.0"
+bumpalo.workspace = true
+criterion.workspace = true
+serde-xml-rs.workspace = true
+serde.workspace = true
+tempfile.workspace = true
[target.'cfg(unix)'.dependencies]
-rlimit = "0.6.2"
+rlimit.workspace = true
diff --git a/crates/cli_utils/src/helpers.rs b/crates/cli_utils/src/helpers.rs
index a48ba3423e..e6a99a58b5 100644
--- a/crates/cli_utils/src/helpers.rs
+++ b/crates/cli_utils/src/helpers.rs
@@ -4,9 +4,7 @@ extern crate roc_load;
extern crate roc_module;
extern crate tempfile;
-use roc_utils::cargo;
-use roc_utils::pretty_command_string;
-use roc_utils::root_dir;
+use roc_command_utils::{cargo, pretty_command_string, root_dir};
use serde::Deserialize;
use serde_xml_rs::from_str;
use std::env;
@@ -16,6 +14,7 @@ use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::{Command, ExitStatus, Stdio};
+use std::sync::Mutex;
use tempfile::NamedTempFile;
#[derive(Debug)]
@@ -41,49 +40,67 @@ pub fn build_roc_bin_cached() -> PathBuf {
let roc_binary_path = path_to_roc_binary();
if !roc_binary_path.exists() {
- // Remove the /target/release/roc part
- let root_project_dir = roc_binary_path
- .parent()
- .unwrap()
- .parent()
- .unwrap()
- .parent()
- .unwrap();
-
- // cargo build --bin roc
- // (with --release iff the test is being built with --release)
- let args = if cfg!(debug_assertions) {
- vec!["build", "--bin", "roc"]
- } else {
- vec!["build", "--release", "--bin", "roc"]
- };
-
- let mut cargo_cmd = cargo();
-
- cargo_cmd.current_dir(root_project_dir).args(&args);
-
- let cargo_cmd_str = format!("{:?}", cargo_cmd);
-
- let cargo_output = cargo_cmd.output().unwrap();
-
- if !cargo_output.status.success() {
- panic!(
- "The following cargo command failed:\n\n {}\n\n stdout was:\n\n {}\n\n stderr was:\n\n {}\n",
- cargo_cmd_str,
- String::from_utf8(cargo_output.stdout).unwrap(),
- String::from_utf8(cargo_output.stderr).unwrap()
- );
- }
+ build_roc_bin(&[]);
}
roc_binary_path
}
+pub fn build_roc_bin(extra_args: &[&str]) -> PathBuf {
+ let roc_binary_path = path_to_roc_binary();
+
+ // Remove the /target/release/roc part
+ let root_project_dir = roc_binary_path
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap()
+ .parent()
+ .unwrap();
+
+ // cargo build --bin roc
+ // (with --release iff the test is being built with --release)
+ let mut args = if cfg!(debug_assertions) {
+ vec!["build", "--bin", "roc"]
+ } else {
+ vec!["build", "--release", "--bin", "roc"]
+ };
+
+ args.extend(extra_args);
+
+ let mut cargo_cmd = cargo();
+
+ cargo_cmd.current_dir(root_project_dir).args(&args);
+
+ let cargo_cmd_str = format!("{:?}", cargo_cmd);
+
+ let cargo_output = cargo_cmd.output().unwrap();
+
+ if !cargo_output.status.success() {
+ panic!(
+ "The following cargo command failed:\n\n {}\n\n stdout was:\n\n {}\n\n stderr was:\n\n {}\n",
+ cargo_cmd_str,
+ String::from_utf8(cargo_output.stdout).unwrap(),
+ String::from_utf8(cargo_output.stderr).unwrap()
+ );
+ }
+
+ roc_binary_path
+}
+
+// Since glue is always compiling the same plugin, it can not be run in parallel.
+// That would lead to a race condition in writing the output shared library.
+// Thus, all calls to glue in a test are made sequential.
+// TODO: In the future, look into compiling the shared libary once and then caching it.
+static GLUE_LOCK: Mutex<()> = Mutex::new(());
+
pub fn run_glue(args: I) -> Out
where
I: IntoIterator
- ,
S: AsRef,
{
+ let _guard = GLUE_LOCK.lock().unwrap();
+
run_roc_with_stdin(&path_to_roc_binary(), args, &[])
}
@@ -387,7 +404,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf {
// Descend into examples/{dir_name}
path.push("crates");
path.push("cli_testing_examples");
- path.extend(dir_name.split("/")); // Make slashes cross-target
+ path.extend(dir_name.split('/')); // Make slashes cross-target
path
}
@@ -396,7 +413,7 @@ pub fn cli_testing_dir(dir_name: &str) -> PathBuf {
pub fn dir_path_from_root(dir_name: &str) -> PathBuf {
let mut path = root_dir();
- path.extend(dir_name.split("/")); // Make slashes cross-target
+ path.extend(dir_name.split('/')); // Make slashes cross-target
path
}
@@ -419,7 +436,7 @@ pub fn fixtures_dir(dir_name: &str) -> PathBuf {
path.push("cli");
path.push("tests");
path.push("fixtures");
- path.extend(dir_name.split("/")); // Make slashes cross-target
+ path.extend(dir_name.split('/')); // Make slashes cross-target
path
}
diff --git a/crates/code_markup/Cargo.toml b/crates/code_markup/Cargo.toml
index d8d63f7a4a..4a784234b1 100644
--- a/crates/code_markup/Cargo.toml
+++ b/crates/code_markup/Cargo.toml
@@ -1,16 +1,19 @@
[package]
name = "roc_code_markup"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Our own markup language for Roc code. Used by the editor and the docs."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
roc_ast = { path = "../ast" }
roc_module = { path = "../compiler/module" }
-roc_utils = { path = "../utils" }
-serde = { version = "1.0.144", features = ["derive"] }
-palette = "0.6.1"
-snafu = { version = "0.7.1", features = ["backtraces"] }
-bumpalo = { version = "3.11.1", features = ["collections"] }
+roc_error_utils = { path = "../utils/error" }
+
+palette.workspace = true
+
+bumpalo.workspace = true
+serde.workspace = true
+snafu.workspace = true
diff --git a/crates/code_markup/src/markup/nodes.rs b/crates/code_markup/src/markup/nodes.rs
index 9f1551d1e4..4ff045dea4 100644
--- a/crates/code_markup/src/markup/nodes.rs
+++ b/crates/code_markup/src/markup/nodes.rs
@@ -16,7 +16,7 @@ use roc_ast::{
lang::{core::ast::ASTNodeId, env::Env},
mem_pool::pool_str::PoolStr,
};
-use roc_utils::{index_of, slice_get};
+use roc_error_utils::{index_of, slice_get};
use std::fmt;
use std::fmt::Write;
diff --git a/crates/code_markup/src/markup_error.rs b/crates/code_markup/src/markup_error.rs
index 89ac7c45d7..5e888652d2 100644
--- a/crates/code_markup/src/markup_error.rs
+++ b/crates/code_markup/src/markup_error.rs
@@ -1,4 +1,4 @@
-use roc_utils::util_error::UtilError;
+use roc_error_utils::UtilError;
use snafu::{Backtrace, NoneError, ResultExt, Snafu};
use crate::slow_pool::MarkNodeId;
diff --git a/crates/compiler/alias_analysis/Cargo.toml b/crates/compiler/alias_analysis/Cargo.toml
index 3b2abd0000..3d6fd4987b 100644
--- a/crates/compiler/alias_analysis/Cargo.toml
+++ b/crates/compiler/alias_analysis/Cargo.toml
@@ -1,14 +1,17 @@
[package]
-authors = ["The Roc Contributors"]
-edition = "2021"
-license = "UPL-1.0"
name = "roc_alias_analysis"
-version = "0.0.1"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
[dependencies]
-morphic_lib = {path = "../../vendor/morphic_lib"}
-roc_collections = {path = "../collections"}
-roc_module = {path = "../module"}
-roc_mono = {path = "../mono"}
-roc_debug_flags = {path = "../debug_flags"}
+morphic_lib = { path = "../../vendor/morphic_lib" }
+roc_collections = { path = "../collections" }
+roc_debug_flags = { path = "../debug_flags" }
+roc_error_macros = { path = "../../error_macros" }
+roc_module = { path = "../module" }
+roc_mono = { path = "../mono" }
+
bumpalo.workspace = true
diff --git a/crates/compiler/alias_analysis/src/lib.rs b/crates/compiler/alias_analysis/src/lib.rs
index 0b2eae34d0..eab45fc8b6 100644
--- a/crates/compiler/alias_analysis/src/lib.rs
+++ b/crates/compiler/alias_analysis/src/lib.rs
@@ -9,6 +9,7 @@ use morphic_lib::{
TypeDefBuilder, TypeId, TypeName, UpdateModeVar, ValueId,
};
use roc_collections::all::{MutMap, MutSet};
+use roc_error_macros::internal_error;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
@@ -187,20 +188,28 @@ where
let func_name = FuncName(&bytes);
if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts {
- for (_, (symbol, top_level, layout)) in aliases {
- match layout {
+ for (_, hels) in aliases {
+ match hels.raw_function_layout {
RawFunctionLayout::Function(_, _, _) => {
- let it = top_level.arguments.iter().copied();
- let bytes =
- func_name_bytes_help(*symbol, it, Niche::NONE, top_level.result);
+ let it = hels.proc_layout.arguments.iter().copied();
+ let bytes = func_name_bytes_help(
+ hels.symbol,
+ it,
+ Niche::NONE,
+ hels.proc_layout.result,
+ );
- host_exposed_functions.push((bytes, top_level.arguments));
+ host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
- let bytes =
- func_name_bytes_help(*symbol, [], Niche::NONE, top_level.result);
+ let bytes = func_name_bytes_help(
+ hels.symbol,
+ [],
+ Niche::NONE,
+ hels.proc_layout.result,
+ );
- host_exposed_functions.push((bytes, top_level.arguments));
+ host_exposed_functions.push((bytes, hels.proc_layout.arguments));
}
}
}
@@ -365,13 +374,7 @@ fn build_entry_point<'a>(
let block = builder.add_block();
// to the modelling language, the arguments appear out of thin air
- let argument_type = build_tuple_type(
- env,
- &mut builder,
- interner,
- layout.arguments,
- &WhenRecursive::Unreachable,
- )?;
+ let argument_type = build_tuple_type(env, &mut builder, interner, layout.arguments)?;
// does not make any assumptions about the input
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
@@ -401,13 +404,7 @@ fn build_entry_point<'a>(
let block = builder.add_block();
let struct_layout = interner.insert(Layout::struct_no_name_order(layouts));
- let type_id = layout_spec(
- env,
- &mut builder,
- interner,
- struct_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let type_id = layout_spec(env, &mut builder, interner, struct_layout)?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
@@ -466,20 +463,8 @@ fn proc_spec<'a>(
let args_struct_layout = interner.insert(Layout::struct_no_name_order(
argument_layouts.into_bump_slice(),
));
- let arg_type_id = layout_spec(
- &mut env,
- &mut builder,
- interner,
- args_struct_layout,
- &WhenRecursive::Unreachable,
- )?;
- let ret_type_id = layout_spec(
- &mut env,
- &mut builder,
- interner,
- proc.ret_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let arg_type_id = layout_spec(&mut env, &mut builder, interner, args_struct_layout)?;
+ let ret_type_id = layout_spec(&mut env, &mut builder, interner, proc.ret_layout)?;
let spec = builder.build(arg_type_id, ret_type_id, root)?;
@@ -617,17 +602,10 @@ fn stmt_spec<'a>(
let mut type_ids = Vec::new();
for p in parameters.iter() {
- type_ids.push(layout_spec(
- env,
- builder,
- interner,
- p.layout,
- &WhenRecursive::Unreachable,
- )?);
+ type_ids.push(layout_spec(env, builder, interner, p.layout)?);
}
- let ret_type_id =
- layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
+ let ret_type_id = layout_spec(env, builder, interner, layout)?;
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
@@ -668,8 +646,7 @@ fn stmt_spec<'a>(
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
}
Jump(id, symbols) => {
- let ret_type_id =
- layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
+ let ret_type_id = layout_spec(env, builder, interner, layout)?;
let argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id];
@@ -678,8 +655,7 @@ fn stmt_spec<'a>(
Crash(msg, _) => {
// Model this as a foreign call rather than TERMINATE because
// we want ownership of the message.
- let result_type =
- layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
+ let result_type = layout_spec(env, builder, interner, layout)?;
builder.add_unknown_with(block, &[env.symbols[msg]], result_type)
}
@@ -708,23 +684,16 @@ fn build_tuple_value(
builder.add_make_tuple(block, &value_ids)
}
-#[derive(Clone, Debug, PartialEq)]
-enum WhenRecursive<'a> {
- Unreachable,
- Loop(UnionLayout<'a>),
-}
-
fn build_recursive_tuple_type<'a>(
env: &mut Env<'a>,
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layouts: &[InLayout<'a>],
- when_recursive: &WhenRecursive,
) -> Result {
let mut field_types = Vec::new();
for field in layouts.iter() {
- let type_id = layout_spec_help(env, builder, interner, *field, when_recursive)?;
+ let type_id = layout_spec_help(env, builder, interner, *field)?;
field_types.push(type_id);
}
@@ -736,12 +705,11 @@ fn build_tuple_type<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layouts: &[InLayout<'a>],
- when_recursive: &WhenRecursive,
) -> Result {
let mut field_types = Vec::new();
for field in layouts.iter() {
- field_types.push(layout_spec(env, builder, interner, *field, when_recursive)?);
+ field_types.push(layout_spec(env, builder, interner, *field)?);
}
builder.add_tuple_type(&field_types)
@@ -811,13 +779,7 @@ fn call_spec<'a>(
.map(|symbol| env.symbols[symbol])
.collect();
- let result_type = layout_spec(
- env,
- builder,
- interner,
- *ret_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let result_type = layout_spec(env, builder, interner, *ret_layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@@ -888,23 +850,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
- let output_element_type = layout_spec(
- env,
- builder,
- interner,
- *return_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
- let state_type = layout_spec(
- env,
- builder,
- interner,
- state_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@@ -931,13 +881,7 @@ fn call_spec<'a>(
let arg0_layout = argument_layouts[0];
let state_layout = interner.insert(Layout::Builtin(Builtin::List(arg0_layout)));
- let state_type = layout_spec(
- env,
- builder,
- interner,
- state_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
@@ -961,23 +905,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
- let output_element_type = layout_spec(
- env,
- builder,
- interner,
- *return_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
- let state_type = layout_spec(
- env,
- builder,
- interner,
- state_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@@ -1007,23 +939,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
- let output_element_type = layout_spec(
- env,
- builder,
- interner,
- *return_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
- let state_type = layout_spec(
- env,
- builder,
- interner,
- state_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@@ -1059,23 +979,11 @@ fn call_spec<'a>(
list_append(builder, block, update_mode_var, state, new_element)
};
- let output_element_type = layout_spec(
- env,
- builder,
- interner,
- *return_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let output_element_type = layout_spec(env, builder, interner, *return_layout)?;
let state_layout =
interner.insert(Layout::Builtin(Builtin::List(*return_layout)));
- let state_type = layout_spec(
- env,
- builder,
- interner,
- state_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let state_type = layout_spec(env, builder, interner, state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
@@ -1130,7 +1038,7 @@ fn lowlevel_spec<'a>(
) -> Result {
use LowLevel::*;
- let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
+ let type_id = layout_spec(env, builder, interner, layout)?;
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
@@ -1238,13 +1146,7 @@ fn lowlevel_spec<'a>(
match interner.get(layout) {
Layout::Builtin(Builtin::List(element_layout)) => {
- let type_id = layout_spec(
- env,
- builder,
- interner,
- element_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let type_id = layout_spec(env, builder, interner, element_layout)?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
@@ -1255,6 +1157,11 @@ fn lowlevel_spec<'a>(
list_clone(builder, block, update_mode_var, list)
}
+ ListReleaseExcessCapacity => {
+ let list = env.symbols[&arguments[0]];
+
+ list_clone(builder, block, update_mode_var, list)
+ }
ListAppendUnsafe => {
let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[1]];
@@ -1287,8 +1194,7 @@ fn lowlevel_spec<'a>(
// TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
- let result_type =
- layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
+ let result_type = layout_spec(env, builder, interner, layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
@@ -1299,12 +1205,9 @@ fn recursive_tag_variant<'a>(
env: &mut Env<'a>,
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
- union_layout: &UnionLayout,
fields: &[InLayout<'a>],
) -> Result {
- let when_recursive = WhenRecursive::Loop(*union_layout);
-
- build_recursive_tuple_type(env, builder, interner, fields, &when_recursive)
+ build_recursive_tuple_type(env, builder, interner, fields)
}
fn recursive_variant_types<'a>(
@@ -1325,23 +1228,11 @@ fn recursive_variant_types<'a>(
result = Vec::with_capacity(tags.len());
for tag in tags.iter() {
- result.push(recursive_tag_variant(
- env,
- builder,
- interner,
- union_layout,
- tag,
- )?);
+ result.push(recursive_tag_variant(env, builder, interner, tag)?);
}
}
NonNullableUnwrapped(fields) => {
- result = vec![recursive_tag_variant(
- env,
- builder,
- interner,
- union_layout,
- fields,
- )?];
+ result = vec![recursive_tag_variant(env, builder, interner, fields)?];
}
NullableWrapped {
nullable_id,
@@ -1352,39 +1243,21 @@ fn recursive_variant_types<'a>(
let cutoff = *nullable_id as usize;
for tag in tags[..cutoff].iter() {
- result.push(recursive_tag_variant(
- env,
- builder,
- interner,
- union_layout,
- tag,
- )?);
+ result.push(recursive_tag_variant(env, builder, interner, tag)?);
}
- result.push(recursive_tag_variant(
- env,
- builder,
- interner,
- union_layout,
- &[],
- )?);
+ result.push(recursive_tag_variant(env, builder, interner, &[])?);
for tag in tags[cutoff..].iter() {
- result.push(recursive_tag_variant(
- env,
- builder,
- interner,
- union_layout,
- tag,
- )?);
+ result.push(recursive_tag_variant(env, builder, interner, tag)?);
}
}
NullableUnwrapped {
nullable_id,
other_fields: fields,
} => {
- let unit = recursive_tag_variant(env, builder, interner, union_layout, &[])?;
- let other_type = recursive_tag_variant(env, builder, interner, union_layout, fields)?;
+ let unit = recursive_tag_variant(env, builder, interner, &[])?;
+ let other_type = recursive_tag_variant(env, builder, interner, fields)?;
if *nullable_id {
// nullable_id == 1
@@ -1432,13 +1305,7 @@ fn expr_spec<'a>(
let value_id = match tag_layout {
UnionLayout::NonRecursive(tags) => {
- let variant_types = non_recursive_variant_types(
- env,
- builder,
- interner,
- tags,
- &WhenRecursive::Unreachable,
- )?;
+ let variant_types = non_recursive_variant_types(env, builder, interner, tags)?;
let value_id = build_tuple_value(builder, env, block, arguments)?;
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
}
@@ -1543,13 +1410,7 @@ fn expr_spec<'a>(
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Array { elem_layout, elems } => {
- let type_id = layout_spec(
- env,
- builder,
- interner,
- *elem_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let type_id = layout_spec(env, builder, interner, *elem_layout)?;
let list = new_list(builder, block, type_id)?;
@@ -1576,13 +1437,7 @@ fn expr_spec<'a>(
EmptyArray => match interner.get(layout) {
Layout::Builtin(Builtin::List(element_layout)) => {
- let type_id = layout_spec(
- env,
- builder,
- interner,
- element_layout,
- &WhenRecursive::Unreachable,
- )?;
+ let type_id = layout_spec(env, builder, interner, element_layout)?;
new_list(builder, block, type_id)
}
_ => unreachable!("empty array does not have a list layout"),
@@ -1615,7 +1470,7 @@ fn expr_spec<'a>(
with_new_heap_cell(builder, block, union_data)
}
RuntimeErrorFunction(_) => {
- let type_id = layout_spec(env, builder, interner, layout, &WhenRecursive::Unreachable)?;
+ let type_id = layout_spec(env, builder, interner, layout)?;
builder.add_terminate(block, type_id)
}
@@ -1647,9 +1502,8 @@ fn layout_spec<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
- when_recursive: &WhenRecursive,
) -> Result {
- layout_spec_help(env, builder, interner, layout, when_recursive)
+ layout_spec_help(env, builder, interner, layout)
}
fn non_recursive_variant_types<'a>(
@@ -1657,19 +1511,11 @@ fn non_recursive_variant_types<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
tags: &[&[InLayout<'a>]],
- // If there is a recursive pointer latent within this layout, coming from a containing layout.
- when_recursive: &WhenRecursive,
) -> Result> {
let mut result = Vec::with_capacity(tags.len());
for tag in tags.iter() {
- result.push(build_tuple_type(
- env,
- builder,
- interner,
- tag,
- when_recursive,
- )?);
+ result.push(build_tuple_type(env, builder, interner, tag)?);
}
Ok(result)
@@ -1680,22 +1526,17 @@ fn layout_spec_help<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
layout: InLayout<'a>,
- when_recursive: &WhenRecursive,
) -> Result {
use Layout::*;
match interner.get(layout) {
- Builtin(builtin) => builtin_spec(env, builder, interner, &builtin, when_recursive),
+ Builtin(builtin) => builtin_spec(env, builder, interner, &builtin),
Struct { field_layouts, .. } => {
- build_recursive_tuple_type(env, builder, interner, field_layouts, when_recursive)
+ build_recursive_tuple_type(env, builder, interner, field_layouts)
+ }
+ LambdaSet(lambda_set) => {
+ layout_spec_help(env, builder, interner, lambda_set.runtime_representation())
}
- LambdaSet(lambda_set) => layout_spec_help(
- env,
- builder,
- interner,
- lambda_set.runtime_representation(),
- when_recursive,
- ),
Union(union_layout) => {
match union_layout {
UnionLayout::NonRecursive(&[]) => {
@@ -1705,8 +1546,7 @@ fn layout_spec_help<'a>(
builder.add_tuple_type(&[])
}
UnionLayout::NonRecursive(tags) => {
- let variant_types =
- non_recursive_variant_types(env, builder, interner, tags, when_recursive)?;
+ let variant_types = non_recursive_variant_types(env, builder, interner, tags)?;
builder.add_union_type(&variant_types)
}
UnionLayout::Recursive(_)
@@ -1724,29 +1564,21 @@ fn layout_spec_help<'a>(
}
Boxed(inner_layout) => {
- let inner_type =
- layout_spec_help(env, builder, interner, inner_layout, when_recursive)?;
+ let inner_type = layout_spec_help(env, builder, interner, inner_layout)?;
let cell_type = builder.add_heap_cell_type();
builder.add_tuple_type(&[cell_type, inner_type])
}
// TODO(recursive-layouts): update once we have recursive pointer loops
- 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);
+ RecursivePointer(union_layout) => match interner.get(union_layout) {
+ Layout::Union(union_layout) => {
+ assert!(!matches!(union_layout, UnionLayout::NonRecursive(..)));
+ 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))
- }
- },
+ Ok(builder.add_named_type(MOD_APP, type_name))
+ }
+ _ => internal_error!("somehow, a non-recursive layout is under a recursive pointer"),
},
}
}
@@ -1756,7 +1588,6 @@ fn builtin_spec<'a>(
builder: &mut impl TypeContext,
interner: &STLayoutInterner<'a>,
builtin: &Builtin<'a>,
- when_recursive: &WhenRecursive,
) -> Result {
use Builtin::*;
@@ -1765,8 +1596,7 @@ fn builtin_spec<'a>(
Decimal | Float(_) => builder.add_tuple_type(&[]),
Str => str_type(builder),
List(element_layout) => {
- let element_type =
- layout_spec_help(env, builder, interner, *element_layout, when_recursive)?;
+ let element_type = layout_spec_help(env, builder, interner, *element_layout)?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?;
diff --git a/crates/compiler/arena_pool/Cargo.toml b/crates/compiler/arena_pool/Cargo.toml
index 1afb7e4eb3..ab35ee4d37 100644
--- a/crates/compiler/arena_pool/Cargo.toml
+++ b/crates/compiler/arena_pool/Cargo.toml
@@ -1,11 +1,12 @@
[package]
name = "arena-pool"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-repository = "https://github.com/roc-lang/roc"
-edition = "2021"
description = "An implementation of an arena allocator designed for the compiler's workloads."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+repository.workspace = true
+version.workspace = true
+
[dependencies]
roc_error_macros = { path = "../../error_macros" }
diff --git a/crates/compiler/build/Cargo.toml b/crates/compiler/build/Cargo.toml
index ac04bd5cef..2dae039b57 100644
--- a/crates/compiler/build/Cargo.toml
+++ b/crates/compiler/build/Cargo.toml
@@ -1,52 +1,55 @@
[package]
name = "roc_build"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Responsible for coordinating building and linking of a Roc app with its host."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-roc_collections = { path = "../collections" }
+roc_bitcode = { path = "../builtins/bitcode" }
roc_can = { path = "../can" }
-roc_parse = { path = "../parse" }
-roc_region = { path = "../region" }
-roc_module = { path = "../module" }
-roc_problem = { path = "../problem" }
-roc_types = { path = "../types" }
-roc_builtins = { path = "../builtins" }
+roc_collections = { path = "../collections" }
roc_constrain = { path = "../constrain" }
-roc_unify = { path = "../unify" }
-roc_solve_problem = { path = "../solve_problem" }
-roc_mono = { path = "../mono" }
-roc_load = { path = "../load" }
-roc_target = { path = "../roc_target" }
+roc_error_macros = { path = "../../error_macros" }
+roc_gen_dev = { path = "../gen_dev", default-features = false }
roc_gen_llvm = { path = "../gen_llvm" }
roc_gen_wasm = { path = "../gen_wasm" }
-roc_gen_dev = { path = "../gen_dev", default-features = false }
+roc_linker = { path = "../../linker" }
+roc_load = { path = "../load" }
+roc_module = { path = "../module" }
+roc_mono = { path = "../mono" }
+roc_packaging = { path = "../../packaging" }
+roc_parse = { path = "../parse" }
+roc_problem = { path = "../problem" }
+roc_region = { path = "../region" }
roc_reporting = { path = "../../reporting" }
-roc_error_macros = { path = "../../error_macros" }
+roc_solve_problem = { path = "../solve_problem" }
roc_std = { path = "../../roc_std" }
-roc_utils = { path = "../../utils" }
+roc_target = { path = "../roc_target" }
+roc_types = { path = "../types" }
+roc_unify = { path = "../unify" }
+roc_command_utils = { path = "../../utils/command" }
wasi_libc_sys = { path = "../../wasi-libc-sys" }
-const_format.workspace = true
bumpalo.workspace = true
-libloading.workspace = true
-tempfile.workspace = true
-target-lexicon.workspace = true
+indoc.workspace = true
inkwell.workspace = true
+libloading.workspace = true
+target-lexicon.workspace = true
+tempfile.workspace = true
[target.'cfg(target_os = "macos")'.dependencies]
-serde_json = "1.0.85"
+serde_json.workspace = true
[features]
-target-arm = []
target-aarch64 = ["roc_gen_dev/target-aarch64"]
+target-arm = []
+target-wasm32 = []
target-x86 = []
target-x86_64 = ["roc_gen_dev/target-x86_64"]
-target-wasm32 = []
# This is used to enable fuzzing and sanitizers.
# Example use is describe here: https://github.com/bhansconnect/roc-fuzz
diff --git a/crates/compiler/build/src/link.rs b/crates/compiler/build/src/link.rs
index cf9c11231c..6798dc65d6 100644
--- a/crates/compiler/build/src/link.rs
+++ b/crates/compiler/build/src/link.rs
@@ -1,26 +1,18 @@
use crate::target::{arch_str, target_zig_str};
-use const_format::concatcp;
use libloading::{Error, Library};
-use roc_builtins::bitcode;
+use roc_command_utils::{cargo, clang, get_lib_path, rustup, zig};
use roc_error_macros::internal_error;
use roc_mono::ir::OptLevel;
-use roc_utils::{cargo, clang, zig};
-use roc_utils::{get_lib_path, rustup};
use std::collections::HashMap;
-use std::env;
+use std::fs::DirEntry;
use std::io;
use std::path::{Path, PathBuf};
use std::process::{self, Child, Command};
+use std::{env, fs};
use target_lexicon::{Architecture, OperatingSystem, Triple};
use wasi_libc_sys::{WASI_COMPILER_RT_PATH, WASI_LIBC_PATH};
-#[derive(Debug, Copy, Clone, PartialEq, Eq)]
-pub enum LinkType {
- // These numbers correspond to the --lib and --no-link flags
- Executable = 0,
- Dylib = 1,
- None = 2,
-}
+pub use roc_linker::LinkType;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum LinkingStrategy {
@@ -60,134 +52,15 @@ pub fn link(
}
}
-const PRECOMPILED_HOST_EXT: &str = "rh1"; // Short for "roc host version 1" (so we can change format in the future)
-
-const WASM_TARGET_STR: &str = "wasm32";
-const LINUX_X86_64_TARGET_STR: &str = "linux-x86_64";
-const LINUX_ARM64_TARGET_STR: &str = "linux-arm64";
-const MACOS_ARM64_TARGET_STR: &str = "macos-arm64";
-const MACOS_X86_64_TARGET_STR: &str = "macos-x86_64";
-const WINDOWS_X86_64_TARGET_STR: &str = "windows-x86_64";
-const WINDOWS_X86_32_TARGET_STR: &str = "windows-x86_32";
-const WIDNOWS_ARM64_TARGET_STR: &str = "windows-arm64";
-
-pub const fn preprocessed_host_filename(target: &Triple) -> Option<&'static str> {
- // Don't try to split this match off in a different function, it will not work with concatcp
- match target {
- Triple {
- architecture: Architecture::Wasm32,
- ..
- } => Some(concatcp!(WASM_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
- Triple {
- operating_system: OperatingSystem::Linux,
- architecture: Architecture::X86_64,
- ..
- } => Some(concatcp!(
- LINUX_X86_64_TARGET_STR,
- '.',
- PRECOMPILED_HOST_EXT
- )),
- Triple {
- operating_system: OperatingSystem::Linux,
- architecture: Architecture::Aarch64(_),
- ..
- } => Some(concatcp!(LINUX_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
- Triple {
- operating_system: OperatingSystem::Darwin,
- architecture: Architecture::Aarch64(_),
- ..
- } => Some(concatcp!(MACOS_ARM64_TARGET_STR, '.', PRECOMPILED_HOST_EXT)),
- Triple {
- operating_system: OperatingSystem::Darwin,
- architecture: Architecture::X86_64,
- ..
- } => Some(concatcp!(
- MACOS_X86_64_TARGET_STR,
- '.',
- PRECOMPILED_HOST_EXT
- )),
- Triple {
- operating_system: OperatingSystem::Windows,
- architecture: Architecture::X86_64,
- ..
- } => Some(concatcp!(
- WINDOWS_X86_64_TARGET_STR,
- '.',
- PRECOMPILED_HOST_EXT
- )),
- Triple {
- operating_system: OperatingSystem::Windows,
- architecture: Architecture::X86_32(_),
- ..
- } => Some(concatcp!(
- WINDOWS_X86_32_TARGET_STR,
- '.',
- PRECOMPILED_HOST_EXT
- )),
- Triple {
- operating_system: OperatingSystem::Windows,
- architecture: Architecture::Aarch64(_),
- ..
- } => Some(concatcp!(
- WIDNOWS_ARM64_TARGET_STR,
- '.',
- PRECOMPILED_HOST_EXT
- )),
- _ => None,
- }
-}
-
-pub fn get_target_triple_str(target: &Triple) -> Option<&'static str> {
- match target {
- Triple {
- architecture: Architecture::Wasm32,
- ..
- } => Some(WASM_TARGET_STR),
- Triple {
- operating_system: OperatingSystem::Linux,
- architecture: Architecture::X86_64,
- ..
- } => Some(LINUX_X86_64_TARGET_STR),
- Triple {
- operating_system: OperatingSystem::Linux,
- architecture: Architecture::Aarch64(_),
- ..
- } => Some(LINUX_ARM64_TARGET_STR),
- Triple {
- operating_system: OperatingSystem::Darwin,
- architecture: Architecture::Aarch64(_),
- ..
- } => Some(MACOS_ARM64_TARGET_STR),
- Triple {
- operating_system: OperatingSystem::Darwin,
- architecture: Architecture::X86_64,
- ..
- } => Some(MACOS_X86_64_TARGET_STR),
- Triple {
- operating_system: OperatingSystem::Windows,
- architecture: Architecture::X86_64,
- ..
- } => Some(WINDOWS_X86_64_TARGET_STR),
- Triple {
- operating_system: OperatingSystem::Windows,
- architecture: Architecture::X86_32(_),
- ..
- } => Some(WINDOWS_X86_32_TARGET_STR),
- Triple {
- operating_system: OperatingSystem::Windows,
- architecture: Architecture::Aarch64(_),
- ..
- } => Some(WIDNOWS_ARM64_TARGET_STR),
- _ => None,
- }
-}
-
/// Same format as the precompiled host filename, except with a file extension like ".o" or ".obj"
pub fn legacy_host_filename(target: &Triple) -> Option {
let os = roc_target::OperatingSystem::from(target.operating_system);
let ext = os.object_file_ext();
- Some(preprocessed_host_filename(target)?.replace(PRECOMPILED_HOST_EXT, ext))
+ Some(
+ roc_linker::preprocessed_host_filename(target)?
+ .replace(roc_linker::PRECOMPILED_HOST_EXT, ext),
+ )
}
fn find_zig_str_path() -> PathBuf {
@@ -681,7 +554,7 @@ pub fn rebuild_host(
let env_cpath = env::var("CPATH").unwrap_or_else(|_| "".to_string());
let builtins_host_tempfile =
- bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
+ roc_bitcode::host_tempfile().expect("failed to write host builtins object to tempfile");
if zig_host_src.exists() {
// Compile host.zig
@@ -752,14 +625,6 @@ pub fn rebuild_host(
// Compile and link Cargo.toml, if it exists
let cargo_dir = platform_main_roc.parent().unwrap();
- let cargo_out_dir = cargo_dir.join("target").join(
- if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
- "release"
- } else {
- "debug"
- },
- );
-
let mut cargo_cmd = if cfg!(windows) {
// on windows, we need the nightly toolchain so we can use `-Z export-executable-symbols`
// using `+nightly` only works when running cargo through rustup
@@ -793,11 +658,20 @@ pub fn rebuild_host(
run_build_command(cargo_cmd, source_file, 0);
+ let cargo_out_dir = find_used_target_sub_folder(opt_level, cargo_dir.join("target"));
+
if shared_lib_path.is_some() {
// For surgical linking, just copy the dynamically linked rust app.
let mut exe_path = cargo_out_dir.join("host");
exe_path.set_extension(executable_extension);
- std::fs::copy(&exe_path, &host_dest).unwrap();
+ if let Err(e) = std::fs::copy(&exe_path, &host_dest) {
+ panic!(
+ "unable to copy {} => {}: {:?}\n\nIs the file used by another invocation of roc?",
+ exe_path.display(),
+ host_dest.display(),
+ e,
+ );
+ }
} else {
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
@@ -943,6 +817,63 @@ pub fn rebuild_host(
host_dest
}
+// there can be multiple release folders, one in target and one in target/x86_64-unknown-linux-musl,
+// we want the one that was most recently used
+fn find_used_target_sub_folder(opt_level: OptLevel, target_folder: PathBuf) -> PathBuf {
+ let out_folder_name = if matches!(opt_level, OptLevel::Optimize | OptLevel::Size) {
+ "release"
+ } else {
+ "debug"
+ };
+
+ let matching_folders = find_in_folder_or_subfolders(&target_folder, out_folder_name);
+ let mut matching_folders_iter = matching_folders.iter();
+
+ let mut out_folder = match matching_folders_iter.next() {
+ Some(dir_entry) => dir_entry,
+ None => panic!("I could not find a folder named {} in {:?}. This may be because the `cargo build` for the platform went wrong.", out_folder_name, target_folder)
+ };
+
+ let mut out_folder_last_change = out_folder.metadata().unwrap().modified().unwrap();
+
+ for dir_entry in matching_folders_iter {
+ let last_modified = dir_entry.metadata().unwrap().modified().unwrap();
+
+ if last_modified > out_folder_last_change {
+ out_folder_last_change = last_modified;
+ out_folder = dir_entry;
+ }
+ }
+
+ out_folder.path().canonicalize().unwrap()
+}
+
+fn find_in_folder_or_subfolders(path: &PathBuf, folder_to_find: &str) -> Vec {
+ let mut matching_dirs = vec![];
+
+ if let Ok(entries) = fs::read_dir(path) {
+ for entry in entries.flatten() {
+ if entry.file_type().unwrap().is_dir() {
+ let dir_name = entry
+ .file_name()
+ .into_string()
+ .unwrap_or_else(|_| "".to_string());
+
+ if dir_name == folder_to_find {
+ matching_dirs.push(entry)
+ } else {
+ let matched_in_sub_dir =
+ find_in_folder_or_subfolders(&entry.path(), folder_to_find);
+
+ matching_dirs.extend(matched_in_sub_dir);
+ }
+ }
+ }
+ }
+
+ matching_dirs
+}
+
fn get_target_str(target: &Triple) -> &str {
if target.operating_system == OperatingSystem::Windows
&& target.environment == target_lexicon::Environment::Gnu
@@ -1502,8 +1433,8 @@ pub fn preprocess_host_wasm32(host_input_path: &Path, preprocessed_host_path: &P
(but seems to be an unofficial API)
*/
- let builtins_host_tempfile =
- bitcode::host_wasm_tempfile().expect("failed to write host builtins object to tempfile");
+ let builtins_host_tempfile = roc_bitcode::host_wasm_tempfile()
+ .expect("failed to write host builtins object to tempfile");
let mut zig_cmd = zig();
let args = &[
diff --git a/crates/compiler/build/src/program.rs b/crates/compiler/build/src/program.rs
index 2a3bbcecbc..cee336736f 100644
--- a/crates/compiler/build/src/program.rs
+++ b/crates/compiler/build/src/program.rs
@@ -1,17 +1,36 @@
+use crate::link::{
+ legacy_host_filename, link, preprocess_host_wasm32, rebuild_host, LinkType, LinkingStrategy,
+};
+use bumpalo::Bump;
use inkwell::memory_buffer::MemoryBuffer;
use roc_error_macros::internal_error;
use roc_gen_llvm::llvm::build::{module_from_builtins, LlvmBackendMode};
use roc_gen_llvm::llvm::externs::add_default_roc_externs;
-use roc_load::{EntryPoint, ExpectMetadata, LoadedModule, MonomorphizedModule};
+use roc_load::{
+ EntryPoint, ExecutionMode, ExpectMetadata, LoadConfig, LoadMonomorphizedError, LoadedModule,
+ LoadingProblem, MonomorphizedModule, Threading,
+};
use roc_mono::ir::{OptLevel, SingleEntryPoint};
-use roc_reporting::cli::{report_problems, Problems};
+use roc_packaging::cache::RocCacheDir;
+use roc_reporting::{
+ cli::{report_problems, Problems},
+ report::{RenderTarget, DEFAULT_PALETTE},
+};
+use roc_target::TargetInfo;
+use std::ffi::OsStr;
use std::ops::Deref;
-use std::path::{Path, PathBuf};
-use std::time::{Duration, Instant};
+use std::{
+ path::{Path, PathBuf},
+ thread::JoinHandle,
+ time::{Duration, Instant},
+};
+use target_lexicon::Triple;
#[cfg(feature = "target-wasm32")]
use roc_collections::all::MutSet;
+pub const DEFAULT_ROC_FILENAME: &str = "main.roc";
+
#[derive(Debug, Clone, Copy, Default)]
pub struct CodeGenTiming {
pub code_gen: Duration,
@@ -56,7 +75,7 @@ impl Deref for CodeObject {
#[derive(Debug, Clone, Copy)]
pub enum CodeGenBackend {
Assembly,
- Llvm,
+ Llvm(LlvmBackendMode),
Wasm,
}
@@ -79,6 +98,10 @@ pub fn gen_from_mono_module<'a>(
preprocessed_host_path: &Path,
wasm_dev_stack_bytes: Option,
) -> GenFromMono<'a> {
+ let path = roc_file_path;
+ let debug = code_gen_options.emit_debug_info;
+ let opt = code_gen_options.opt_level;
+
match code_gen_options.backend {
CodeGenBackend::Assembly => gen_from_mono_module_dev(
arena,
@@ -87,12 +110,18 @@ pub fn gen_from_mono_module<'a>(
preprocessed_host_path,
wasm_dev_stack_bytes,
),
- CodeGenBackend::Llvm => {
- gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
+ CodeGenBackend::Llvm(backend_mode) => {
+ gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug)
}
CodeGenBackend::Wasm => {
// emit wasm via the llvm backend
- gen_from_mono_module_llvm(arena, loaded, roc_file_path, target, code_gen_options)
+
+ let backend_mode = match code_gen_options.opt_level {
+ OptLevel::Development => LlvmBackendMode::BinaryDev,
+ OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
+ };
+
+ gen_from_mono_module_llvm(arena, loaded, path, target, opt, backend_mode, debug)
}
}
}
@@ -105,7 +134,9 @@ fn gen_from_mono_module_llvm<'a>(
mut loaded: MonomorphizedModule<'a>,
roc_file_path: &Path,
target: &target_lexicon::Triple,
- code_gen_options: CodeGenOptions,
+ opt_level: OptLevel,
+ backend_mode: LlvmBackendMode,
+ emit_debug_info: bool,
) -> GenFromMono<'a> {
use crate::target::{self, convert_opt_level};
use inkwell::attributes::{Attribute, AttributeLoc};
@@ -155,12 +186,6 @@ fn gen_from_mono_module_llvm<'a>(
}
}
- let CodeGenOptions {
- backend: _,
- opt_level,
- emit_debug_info,
- } = code_gen_options;
-
let builder = context.create_builder();
let (dibuilder, compile_unit) = roc_gen_llvm::llvm::build::Env::new_debug_info(module);
let (mpm, _fpm) = roc_gen_llvm::llvm::build::construct_optimization_passes(module, opt_level);
@@ -175,12 +200,14 @@ fn gen_from_mono_module_llvm<'a>(
interns: loaded.interns,
module,
target_info,
- mode: match opt_level {
- OptLevel::Development => LlvmBackendMode::BinaryDev,
- OptLevel::Normal | OptLevel::Size | OptLevel::Optimize => LlvmBackendMode::Binary,
- },
+ mode: backend_mode,
- exposed_to_host: loaded.exposed_to_host.values.keys().copied().collect(),
+ exposed_to_host: loaded
+ .exposed_to_host
+ .top_level_values
+ .keys()
+ .copied()
+ .collect(),
};
// does not add any externs for this mode (we have a host) but cleans up some functions around
@@ -208,6 +235,7 @@ fn gen_from_mono_module_llvm<'a>(
loaded.procedures,
entry_point,
Some(&app_ll_file),
+ &loaded.glue_layouts,
);
env.dibuilder.finalize();
@@ -484,7 +512,7 @@ fn gen_from_mono_module_dev_wasm32<'a>(
let exposed_to_host = loaded
.exposed_to_host
- .values
+ .top_level_values
.keys()
.copied()
.collect::>();
@@ -555,7 +583,7 @@ fn gen_from_mono_module_dev_assembly<'a>(
let env = roc_gen_dev::Env {
arena,
module_id,
- exposed_to_host: exposed_to_host.values.keys().copied().collect(),
+ exposed_to_host: exposed_to_host.top_level_values.keys().copied().collect(),
lazy_literals,
generate_allocators,
};
@@ -579,3 +607,675 @@ fn gen_from_mono_module_dev_assembly<'a>(
},
)
}
+
+fn report_timing(buf: &mut String, label: &str, duration: Duration) {
+ use std::fmt::Write;
+
+ writeln!(
+ buf,
+ " {:9.3} ms {}",
+ duration.as_secs_f64() * 1000.0,
+ label,
+ )
+ .unwrap()
+}
+
+pub struct BuiltFile<'a> {
+ pub binary_path: PathBuf,
+ pub problems: Problems,
+ pub total_time: Duration,
+ pub expect_metadata: ExpectMetadata<'a>,
+}
+
+pub enum BuildOrdering {
+ /// Run up through typechecking first; continue building iff that is successful.
+ BuildIfChecks,
+ /// Always build the Roc binary, even if there are type errors.
+ AlwaysBuild,
+}
+
+#[derive(Debug)]
+#[allow(clippy::large_enum_variant)]
+pub enum BuildFileError<'a> {
+ LoadingProblem(LoadingProblem<'a>),
+ ErrorModule {
+ module: LoadedModule,
+ total_time: Duration,
+ },
+}
+
+impl<'a> BuildFileError<'a> {
+ fn from_mono_error(error: LoadMonomorphizedError<'a>, compilation_start: Instant) -> Self {
+ match error {
+ LoadMonomorphizedError::LoadingProblem(problem) => {
+ BuildFileError::LoadingProblem(problem)
+ }
+ LoadMonomorphizedError::ErrorModule(module) => BuildFileError::ErrorModule {
+ module,
+ total_time: compilation_start.elapsed(),
+ },
+ }
+ }
+}
+
+pub fn handle_error_module(
+ mut module: roc_load::LoadedModule,
+ total_time: std::time::Duration,
+ filename: &OsStr,
+ print_run_anyway_hint: bool,
+) -> std::io::Result {
+ debug_assert!(module.total_problems() > 0);
+
+ let problems = report_problems_typechecked(&mut module);
+
+ problems.print_to_stdout(total_time);
+
+ if print_run_anyway_hint {
+ // If you're running "main.roc" then you can just do `roc run`
+ // to re-run the program.
+ print!(".\n\nYou can run the program anyway with \x1B[32mroc run");
+
+ if filename != DEFAULT_ROC_FILENAME {
+ print!(" {}", &filename.to_string_lossy());
+ }
+
+ println!("\x1B[39m");
+ }
+
+ Ok(problems.exit_code())
+}
+
+pub fn handle_loading_problem(problem: LoadingProblem) -> std::io::Result {
+ match problem {
+ LoadingProblem::FormattedReport(report) => {
+ print!("{}", report);
+ Ok(1)
+ }
+ _ => {
+ // TODO: tighten up the types here, we should always end up with a
+ // formatted report from load.
+ print!("Failed with error: {:?}", problem);
+ Ok(1)
+ }
+ }
+}
+
+pub fn standard_load_config(
+ target: &Triple,
+ order: BuildOrdering,
+ threading: Threading,
+) -> LoadConfig {
+ let target_info = TargetInfo::from(target);
+
+ let exec_mode = match order {
+ BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
+ BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
+ };
+
+ LoadConfig {
+ target_info,
+ render: RenderTarget::ColorTerminal,
+ palette: DEFAULT_PALETTE,
+ threading,
+ exec_mode,
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+pub fn build_file<'a>(
+ arena: &'a Bump,
+ target: &Triple,
+ app_module_path: PathBuf,
+ code_gen_options: CodeGenOptions,
+ emit_timings: bool,
+ link_type: LinkType,
+ linking_strategy: LinkingStrategy,
+ prebuilt_requested: bool,
+ wasm_dev_stack_bytes: Option,
+ roc_cache_dir: RocCacheDir<'_>,
+ load_config: LoadConfig,
+) -> Result, BuildFileError<'a>> {
+ let compilation_start = Instant::now();
+
+ // Step 1: compile the app and generate the .o file
+ let loaded =
+ roc_load::load_and_monomorphize(arena, app_module_path.clone(), roc_cache_dir, load_config)
+ .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
+
+ build_loaded_file(
+ arena,
+ target,
+ app_module_path,
+ code_gen_options,
+ emit_timings,
+ link_type,
+ linking_strategy,
+ prebuilt_requested,
+ wasm_dev_stack_bytes,
+ loaded,
+ compilation_start,
+ )
+}
+
+#[allow(clippy::too_many_arguments)]
+fn build_loaded_file<'a>(
+ arena: &'a Bump,
+ target: &Triple,
+ app_module_path: PathBuf,
+ code_gen_options: CodeGenOptions,
+ emit_timings: bool,
+ link_type: LinkType,
+ linking_strategy: LinkingStrategy,
+ prebuilt_requested: bool,
+ wasm_dev_stack_bytes: Option,
+ loaded: roc_load::MonomorphizedModule<'a>,
+ compilation_start: Instant,
+) -> Result, BuildFileError<'a>> {
+ let operating_system = roc_target::OperatingSystem::from(target.operating_system);
+
+ let platform_main_roc = match &loaded.entry_point {
+ EntryPoint::Executable { platform_path, .. } => platform_path.to_path_buf(),
+ _ => unreachable!(),
+ };
+
+ // the preprocessed host is stored beside the platform's main.roc
+ let preprocessed_host_path = if linking_strategy == LinkingStrategy::Legacy {
+ if let roc_target::OperatingSystem::Wasi = operating_system {
+ // when compiling a wasm application, we implicitly assume here that the host is in zig
+ // and has a file called "host.zig"
+ platform_main_roc.with_file_name("host.zig")
+ } else {
+ platform_main_roc.with_file_name(legacy_host_filename(target).unwrap())
+ }
+ } else {
+ platform_main_roc.with_file_name(roc_linker::preprocessed_host_filename(target).unwrap())
+ };
+
+ // For example, if we're loading the platform from a URL, it's automatically prebuilt
+ // even if the --prebuilt-platform=true CLI flag wasn't set.
+ let is_platform_prebuilt = prebuilt_requested || loaded.uses_prebuilt_platform;
+
+ let cwd = app_module_path.parent().unwrap();
+ let mut output_exe_path = cwd.join(&*loaded.output_path);
+
+ if let Some(extension) = operating_system.executable_file_ext() {
+ output_exe_path.set_extension(extension);
+ }
+
+ // We don't need to spawn a rebuild thread when using a prebuilt host.
+ let rebuild_thread = if matches!(link_type, LinkType::Dylib | LinkType::None) {
+ None
+ } else if is_platform_prebuilt {
+ if !preprocessed_host_path.exists() {
+ invalid_prebuilt_platform(prebuilt_requested, preprocessed_host_path);
+
+ std::process::exit(1);
+ }
+
+ if linking_strategy == LinkingStrategy::Surgical {
+ // Copy preprocessed host to executable location.
+ // The surgical linker will modify that copy in-place.
+ std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
+ }
+
+ None
+ } else {
+ // TODO this should probably be moved before load_and_monomorphize.
+ // To do this we will need to preprocess files just for their exported symbols.
+ // Also, we should no longer need to do this once we have platforms on
+ // a package repository, as we can then get prebuilt platforms from there.
+
+ let dll_stub_symbols = roc_linker::ExposedSymbols::from_exposed_to_host(
+ &loaded.interns,
+ &loaded.exposed_to_host,
+ );
+
+ let join_handle = spawn_rebuild_thread(
+ code_gen_options.opt_level,
+ linking_strategy,
+ platform_main_roc.clone(),
+ preprocessed_host_path.clone(),
+ output_exe_path.clone(),
+ target,
+ dll_stub_symbols,
+ );
+
+ Some(join_handle)
+ };
+
+ let buf = &mut String::with_capacity(1024);
+
+ let mut it = loaded.timings.iter().peekable();
+ while let Some((module_id, module_timing)) = it.next() {
+ let module_name = loaded.interns.module_name(*module_id);
+
+ buf.push_str(" ");
+
+ if module_name.is_empty() {
+ // the App module
+ buf.push_str("Application Module");
+ } else {
+ buf.push_str(module_name);
+ }
+
+ buf.push('\n');
+
+ use std::fmt::Write;
+ write!(buf, "{}", module_timing).unwrap();
+
+ if it.peek().is_some() {
+ buf.push('\n');
+ }
+ }
+
+ // This only needs to be mutable for report_problems. This can't be done
+ // inside a nested scope without causing a borrow error!
+ let mut loaded = loaded;
+ let problems = report_problems_monomorphized(&mut loaded);
+ let loaded = loaded;
+
+ enum HostRebuildTiming {
+ BeforeApp(u128),
+ ConcurrentWithApp(JoinHandle),
+ }
+
+ let opt_rebuild_timing = if let Some(rebuild_thread) = rebuild_thread {
+ if linking_strategy == LinkingStrategy::Additive {
+ let rebuild_duration = rebuild_thread
+ .join()
+ .expect("Failed to (re)build platform.");
+
+ if emit_timings && !is_platform_prebuilt {
+ println!(
+ "Finished rebuilding the platform in {} ms\n",
+ rebuild_duration
+ );
+ }
+
+ Some(HostRebuildTiming::BeforeApp(rebuild_duration))
+ } else {
+ Some(HostRebuildTiming::ConcurrentWithApp(rebuild_thread))
+ }
+ } else {
+ None
+ };
+
+ let (roc_app_bytes, code_gen_timing, expect_metadata) = gen_from_mono_module(
+ arena,
+ loaded,
+ &app_module_path,
+ target,
+ code_gen_options,
+ &preprocessed_host_path,
+ wasm_dev_stack_bytes,
+ );
+
+ buf.push('\n');
+ buf.push_str(" ");
+ buf.push_str("Code Generation");
+ buf.push('\n');
+
+ report_timing(
+ buf,
+ "Generate Assembly from Mono IR",
+ code_gen_timing.code_gen,
+ );
+
+ let compilation_end = compilation_start.elapsed();
+ let size = roc_app_bytes.len();
+
+ if emit_timings {
+ println!(
+ "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
+ buf
+ );
+
+ println!(
+ "Finished compilation and code gen in {} ms\n\nProduced a app.o file of size {:?}\n",
+ compilation_end.as_millis(),
+ size,
+ );
+ }
+
+ if let Some(HostRebuildTiming::ConcurrentWithApp(thread)) = opt_rebuild_timing {
+ let rebuild_duration = thread.join().expect("Failed to (re)build platform.");
+
+ if emit_timings && !is_platform_prebuilt {
+ println!(
+ "Finished rebuilding the platform in {} ms\n",
+ rebuild_duration
+ );
+ }
+ }
+
+ // Step 2: link the prebuilt platform and compiled app
+ let link_start = Instant::now();
+
+ match (linking_strategy, link_type) {
+ (LinkingStrategy::Surgical, _) => {
+ roc_linker::link_preprocessed_host(
+ target,
+ &platform_main_roc,
+ &roc_app_bytes,
+ &output_exe_path,
+ );
+ }
+ (LinkingStrategy::Additive, _) | (LinkingStrategy::Legacy, LinkType::None) => {
+ // Just copy the object file to the output folder.
+ output_exe_path.set_extension(operating_system.object_file_ext());
+ std::fs::write(&output_exe_path, &*roc_app_bytes).unwrap();
+ }
+ (LinkingStrategy::Legacy, _) => {
+ let app_o_file = tempfile::Builder::new()
+ .prefix("roc_app")
+ .suffix(&format!(".{}", operating_system.object_file_ext()))
+ .tempfile()
+ .map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
+ let app_o_file = app_o_file.path();
+
+ std::fs::write(app_o_file, &*roc_app_bytes).unwrap();
+
+ let builtins_host_tempfile = roc_bitcode::host_tempfile()
+ .expect("failed to write host builtins object to tempfile");
+
+ let mut inputs = vec![app_o_file.to_str().unwrap()];
+
+ if !matches!(link_type, LinkType::Dylib | LinkType::None) {
+ // the host has been compiled into a .o or .obj file
+ inputs.push(preprocessed_host_path.as_path().to_str().unwrap());
+ }
+
+ if matches!(code_gen_options.backend, CodeGenBackend::Assembly) {
+ inputs.push(builtins_host_tempfile.path().to_str().unwrap());
+ }
+
+ let (mut child, _) = link(target, output_exe_path.clone(), &inputs, link_type)
+ .map_err(|_| todo!("gracefully handle `ld` failing to spawn."))?;
+
+ let exit_status = child
+ .wait()
+ .map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
+
+ // Extend the lifetime of the tempfile so it doesn't get dropped
+ // (and thus deleted) before the child process is done using it!
+ let _ = builtins_host_tempfile;
+
+ if !exit_status.success() {
+ todo!(
+ "gracefully handle `ld` (or `zig` in the case of wasm with --optimize) returning exit code {:?}",
+ exit_status.code()
+ );
+ }
+ }
+ }
+
+ let linking_time = link_start.elapsed();
+
+ if emit_timings {
+ println!("Finished linking in {} ms\n", linking_time.as_millis());
+ }
+
+ let total_time = compilation_start.elapsed();
+
+ Ok(BuiltFile {
+ binary_path: output_exe_path,
+ problems,
+ total_time,
+ expect_metadata,
+ })
+}
+
+fn invalid_prebuilt_platform(prebuilt_requested: bool, preprocessed_host_path: PathBuf) {
+ let prefix = match prebuilt_requested {
+ true => "Because I was run with --prebuilt-platform=true, ",
+ false => "",
+ };
+
+ let preprocessed_host_path_str = preprocessed_host_path.to_string_lossy();
+ let extra_err_msg = if preprocessed_host_path_str.ends_with(".rh") {
+ "\n\n\tNote: If the platform does have an .rh1 file but no .rh file, it's because it's been built with an older version of roc. Contact the author to release a new build of the platform using a roc release newer than March 21 2023.\n"
+ } else {
+ ""
+ };
+
+ eprintln!(
+ indoc::indoc!(
+ r#"
+ {}I was expecting this file to exist:
+
+ {}
+
+ However, it was not there!{}
+
+ If you have the platform's source code locally, you may be able to generate it by re-running this command with --prebuilt-platform=false
+ "#
+ ),
+ prefix,
+ preprocessed_host_path.to_string_lossy(),
+ extra_err_msg
+ );
+}
+
+#[allow(clippy::too_many_arguments)]
+fn spawn_rebuild_thread(
+ opt_level: OptLevel,
+ linking_strategy: LinkingStrategy,
+ platform_main_roc: PathBuf,
+ preprocessed_host_path: PathBuf,
+ output_exe_path: PathBuf,
+ target: &Triple,
+ dll_stub_symbols: Vec,
+) -> std::thread::JoinHandle {
+ let thread_local_target = target.clone();
+ std::thread::spawn(move || {
+ // Printing to stderr because we want stdout to contain only the output of the roc program.
+ // We are aware of the trade-offs.
+ // `cargo run` follows the same approach
+ eprintln!("🔨 Rebuilding platform...");
+
+ let rebuild_host_start = Instant::now();
+
+ match linking_strategy {
+ LinkingStrategy::Additive => {
+ let host_dest = rebuild_host(
+ opt_level,
+ &thread_local_target,
+ platform_main_roc.as_path(),
+ None,
+ );
+
+ preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
+ }
+ LinkingStrategy::Surgical => {
+ build_and_preprocess_host_lowlevel(
+ opt_level,
+ &thread_local_target,
+ platform_main_roc.as_path(),
+ preprocessed_host_path.as_path(),
+ &dll_stub_symbols,
+ );
+
+ // Copy preprocessed host to executable location.
+ // The surgical linker will modify that copy in-place.
+ std::fs::copy(&preprocessed_host_path, output_exe_path.as_path()).unwrap();
+ }
+ LinkingStrategy::Legacy => {
+ rebuild_host(
+ opt_level,
+ &thread_local_target,
+ platform_main_roc.as_path(),
+ None,
+ );
+ }
+ }
+
+ rebuild_host_start.elapsed().as_millis()
+ })
+}
+
+pub fn build_and_preprocess_host(
+ opt_level: OptLevel,
+ target: &Triple,
+ platform_main_roc: &Path,
+ preprocessed_host_path: &Path,
+ exposed_symbols: roc_linker::ExposedSymbols,
+) {
+ let stub_dll_symbols = exposed_symbols.stub_dll_symbols();
+
+ build_and_preprocess_host_lowlevel(
+ opt_level,
+ target,
+ platform_main_roc,
+ preprocessed_host_path,
+ &stub_dll_symbols,
+ )
+}
+
+fn build_and_preprocess_host_lowlevel(
+ opt_level: OptLevel,
+ target: &Triple,
+ platform_main_roc: &Path,
+ preprocessed_host_path: &Path,
+ stub_dll_symbols: &[String],
+) {
+ let stub_lib =
+ roc_linker::generate_stub_lib_from_loaded(target, platform_main_roc, stub_dll_symbols);
+
+ debug_assert!(stub_lib.exists());
+
+ rebuild_host(opt_level, target, platform_main_roc, Some(&stub_lib));
+
+ roc_linker::preprocess_host(
+ target,
+ platform_main_roc,
+ preprocessed_host_path,
+ &stub_lib,
+ stub_dll_symbols,
+ )
+}
+
+#[allow(clippy::too_many_arguments)]
+pub fn check_file<'a>(
+ arena: &'a Bump,
+ roc_file_path: PathBuf,
+ emit_timings: bool,
+ roc_cache_dir: RocCacheDir<'_>,
+ threading: Threading,
+) -> Result<(Problems, Duration), LoadingProblem<'a>> {
+ let compilation_start = Instant::now();
+
+ // only used for generating errors. We don't do code generation, so hardcoding should be fine
+ // we need monomorphization for when exhaustiveness checking
+ let target_info = TargetInfo::default_x86_64();
+
+ // Step 1: compile the app and generate the .o file
+
+ let load_config = LoadConfig {
+ target_info,
+ // TODO: expose this from CLI?
+ render: RenderTarget::ColorTerminal,
+ palette: DEFAULT_PALETTE,
+ threading,
+ exec_mode: ExecutionMode::Check,
+ };
+ let mut loaded =
+ roc_load::load_and_typecheck(arena, roc_file_path, roc_cache_dir, load_config)?;
+
+ let buf = &mut String::with_capacity(1024);
+
+ let mut it = loaded.timings.iter().peekable();
+ while let Some((module_id, module_timing)) = it.next() {
+ let module_name = loaded.interns.module_name(*module_id);
+
+ buf.push_str(" ");
+
+ if module_name.is_empty() {
+ // the App module
+ buf.push_str("Application Module");
+ } else {
+ buf.push_str(module_name);
+ }
+
+ buf.push('\n');
+
+ report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
+ report_timing(buf, "Parse header", module_timing.parse_header);
+ report_timing(buf, "Parse body", module_timing.parse_body);
+ report_timing(buf, "Canonicalize", module_timing.canonicalize);
+ report_timing(buf, "Constrain", module_timing.constrain);
+ report_timing(buf, "Solve", module_timing.solve);
+ report_timing(buf, "Other", module_timing.other());
+ buf.push('\n');
+ report_timing(buf, "Total", module_timing.total());
+
+ if it.peek().is_some() {
+ buf.push('\n');
+ }
+ }
+
+ let compilation_end = compilation_start.elapsed();
+
+ if emit_timings {
+ println!(
+ "\n\nCompilation finished!\n\nHere's how long each module took to compile:\n\n{}",
+ buf
+ );
+
+ println!("Finished checking in {} ms\n", compilation_end.as_millis(),);
+ }
+
+ Ok((report_problems_typechecked(&mut loaded), compilation_end))
+}
+
+pub fn build_str_test<'a>(
+ arena: &'a Bump,
+ app_module_path: &Path,
+ app_module_source: &'a str,
+ assume_prebuild: bool,
+) -> Result, BuildFileError<'a>> {
+ let triple = target_lexicon::Triple::host();
+
+ let code_gen_options = CodeGenOptions {
+ backend: CodeGenBackend::Llvm(LlvmBackendMode::Binary),
+ opt_level: OptLevel::Normal,
+ emit_debug_info: false,
+ };
+
+ let emit_timings = false;
+ let link_type = LinkType::Executable;
+ let linking_strategy = LinkingStrategy::Surgical;
+ let wasm_dev_stack_bytes = None;
+
+ let roc_cache_dir = roc_packaging::cache::RocCacheDir::Disallowed;
+ let build_ordering = BuildOrdering::AlwaysBuild;
+ let threading = Threading::AtMost(2);
+
+ let load_config = standard_load_config(&triple, build_ordering, threading);
+
+ let compilation_start = std::time::Instant::now();
+
+ // Step 1: compile the app and generate the .o file
+ let loaded = roc_load::load_and_monomorphize_from_str(
+ arena,
+ PathBuf::from("valgrind_test.roc"),
+ app_module_source,
+ app_module_path.to_path_buf(),
+ roc_cache_dir,
+ load_config,
+ )
+ .map_err(|e| BuildFileError::from_mono_error(e, compilation_start))?;
+
+ build_loaded_file(
+ arena,
+ &triple,
+ app_module_path.to_path_buf(),
+ code_gen_options,
+ emit_timings,
+ link_type,
+ linking_strategy,
+ assume_prebuild,
+ wasm_dev_stack_bytes,
+ loaded,
+ compilation_start,
+ )
+}
diff --git a/crates/compiler/builtins/Cargo.toml b/crates/compiler/builtins/Cargo.toml
index ca3a5b38f8..852a085a9f 100644
--- a/crates/compiler/builtins/Cargo.toml
+++ b/crates/compiler/builtins/Cargo.toml
@@ -1,23 +1,17 @@
[package]
name = "roc_builtins"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Provides the Roc functions and modules that are implicitly imported into every module."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
roc_collections = { path = "../collections" }
-roc_region = { path = "../region" }
roc_module = { path = "../module" }
+roc_region = { path = "../region" }
roc_target = { path = "../roc_target" }
-roc_utils = { path = "../../utils" }
+
tempfile.workspace = true
-[build-dependencies]
-# dunce can be removed once ziglang/zig#5109 is fixed
-dunce = "1.0.3"
-roc_utils = { path = "../../utils" }
-
-[target.'cfg(target_os = "macos")'.build-dependencies]
-tempfile.workspace = true
diff --git a/crates/compiler/builtins/bitcode/Cargo.toml b/crates/compiler/builtins/bitcode/Cargo.toml
new file mode 100644
index 0000000000..46ef041619
--- /dev/null
+++ b/crates/compiler/builtins/bitcode/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "roc_bitcode"
+description = "Compiles the zig bitcode to `.o` for builtins"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
+[dependencies]
+tempfile.workspace = true
+
+[build-dependencies]
+roc_command_utils = { path = "../../../utils/command" }
+# dunce can be removed once ziglang/zig#5109 is fixed
+dunce = "1.0.3"
+
+[target.'cfg(target_os = "macos")'.build-dependencies]
+tempfile.workspace = true
diff --git a/crates/compiler/builtins/bitcode/bc/Cargo.toml b/crates/compiler/builtins/bitcode/bc/Cargo.toml
new file mode 100644
index 0000000000..d6b8113443
--- /dev/null
+++ b/crates/compiler/builtins/bitcode/bc/Cargo.toml
@@ -0,0 +1,16 @@
+[package]
+name = "roc_bitcode_bc"
+description = "Compiles the zig bitcode to `.bc` for llvm"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
+[build-dependencies]
+roc_command_utils = { path = "../../../../utils/command" }
+# dunce can be removed once ziglang/zig#5109 is fixed
+dunce = "1.0.3"
+
+[target.'cfg(target_os = "macos")'.build-dependencies]
+tempfile.workspace = true
diff --git a/crates/compiler/builtins/bitcode/bc/build.rs b/crates/compiler/builtins/bitcode/bc/build.rs
new file mode 100644
index 0000000000..bda982ccd6
--- /dev/null
+++ b/crates/compiler/builtins/bitcode/bc/build.rs
@@ -0,0 +1,154 @@
+use roc_command_utils::{pretty_command_string, zig};
+use std::fs;
+use std::io;
+use std::path::Path;
+use std::str;
+use std::{env, path::PathBuf, process::Command};
+
+#[cfg(target_os = "macos")]
+use tempfile::tempdir;
+
+/// To debug the zig code with debug prints, we need to disable the wasm code gen
+const DEBUG: bool = false;
+
+fn main() {
+ println!("cargo:rerun-if-changed=build.rs");
+
+ // "." is relative to where "build.rs" is
+ // dunce can be removed once ziglang/zig#5109 is fixed
+ let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap().join("..");
+
+ // workaround for github.com/ziglang/zig/issues/9711
+ #[cfg(target_os = "macos")]
+ let zig_cache_dir = tempdir().expect("Failed to create temp directory for zig cache");
+ #[cfg(target_os = "macos")]
+ std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
+
+ // LLVM .bc FILES
+
+ generate_bc_file(&bitcode_path, "ir", "builtins-host");
+
+ if !DEBUG {
+ generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
+ }
+
+ generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
+ generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
+ generate_bc_file(
+ &bitcode_path,
+ "ir-windows-x86_64",
+ "builtins-windows-x86_64",
+ );
+
+ get_zig_files(bitcode_path.as_path(), &|path| {
+ let path: &Path = path;
+ println!(
+ "cargo:rerun-if-changed={}",
+ path.to_str().expect("Failed to convert path to str")
+ );
+ })
+ .unwrap();
+
+ #[cfg(target_os = "macos")]
+ zig_cache_dir
+ .close()
+ .expect("Failed to delete temp dir zig_cache_dir.");
+}
+
+fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
+ let mut ll_path = bitcode_path.join(file_name);
+ ll_path.set_extension("ll");
+ let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
+
+ println!("Compiling host ir to: {}", dest_ir_host);
+
+ let mut bc_path = bitcode_path.join(file_name);
+ bc_path.set_extension("bc");
+ let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
+ println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
+
+ // workaround for github.com/ziglang/zig/issues/9711
+ #[cfg(target_os = "macos")]
+ let _ = fs::remove_dir_all("./zig-cache");
+
+ let mut zig_cmd = zig();
+
+ zig_cmd
+ .current_dir(bitcode_path)
+ .args(["build", zig_object, "-Drelease=true"]);
+
+ run_command(zig_cmd, 0);
+}
+
+pub fn get_lib_dir() -> PathBuf {
+ // Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
+ // So we just need to add "/bitcode" to that.
+ let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
+
+ // create dir if it does not exist
+ fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
+
+ dir
+}
+
+fn run_command(mut command: Command, flaky_fail_counter: usize) {
+ let command_str = pretty_command_string(&command);
+ let command_str = command_str.to_string_lossy();
+
+ let output_result = command.output();
+
+ match output_result {
+ Ok(output) => match output.status.success() {
+ true => (),
+ false => {
+ let error_str = match str::from_utf8(&output.stderr) {
+ Ok(stderr) => stderr.to_string(),
+ Err(_) => format!("Failed to run \"{}\"", command_str),
+ };
+
+ // Flaky test errors that only occur sometimes on MacOS ci server.
+ if error_str.contains("FileNotFound")
+ || error_str.contains("unable to save cached ZIR code")
+ || error_str.contains("LLVM failed to emit asm")
+ {
+ if flaky_fail_counter == 10 {
+ panic!("{} failed 10 times in a row. The following error is unlikely to be a flaky error: {}", command_str, error_str);
+ } else {
+ run_command(command, flaky_fail_counter + 1)
+ }
+ } else if error_str
+ .contains("lld-link: error: failed to write the output file: Permission denied")
+ {
+ panic!("{} failed with:\n\n {}\n\nWorkaround:\n\n Re-run the cargo command that triggered this build.\n\n", command_str, error_str);
+ } else {
+ panic!("{} failed with:\n\n {}\n", command_str, error_str);
+ }
+ }
+ },
+ Err(reason) => panic!("{} failed: {}", command_str, reason),
+ }
+}
+
+fn get_zig_files(dir: &Path, cb: &dyn Fn(&Path)) -> io::Result<()> {
+ if dir.is_dir() {
+ for entry in fs::read_dir(dir)? {
+ let entry = entry?;
+ let path_buf = entry.path();
+ if path_buf.is_dir() {
+ if !path_buf.ends_with("zig-cache") {
+ get_zig_files(&path_buf, cb).unwrap();
+ }
+ } else {
+ let path = path_buf.as_path();
+
+ match path.extension() {
+ Some(osstr) if osstr == "zig" => {
+ cb(path);
+ }
+ _ => {}
+ }
+ }
+ }
+ }
+ Ok(())
+}
diff --git a/crates/compiler/builtins/bitcode/bc/src/lib.rs b/crates/compiler/builtins/bitcode/bc/src/lib.rs
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/crates/compiler/builtins/bitcode/bc/src/lib.rs
@@ -0,0 +1 @@
+
diff --git a/crates/compiler/builtins/build.rs b/crates/compiler/builtins/bitcode/build.rs
similarity index 80%
rename from crates/compiler/builtins/build.rs
rename to crates/compiler/builtins/bitcode/build.rs
index 0e10b07627..40ed60021c 100644
--- a/crates/compiler/builtins/build.rs
+++ b/crates/compiler/builtins/bitcode/build.rs
@@ -1,11 +1,9 @@
-use roc_utils::zig;
-use std::env;
+use roc_command_utils::{pretty_command_string, zig};
use std::fs;
use std::io;
use std::path::Path;
-use std::path::PathBuf;
-use std::process::Command;
use std::str;
+use std::{env, path::PathBuf, process::Command};
#[cfg(target_os = "macos")]
use tempfile::tempdir;
@@ -18,8 +16,7 @@ fn main() {
// "." is relative to where "build.rs" is
// dunce can be removed once ziglang/zig#5109 is fixed
- let build_script_dir_path = dunce::canonicalize(Path::new(".")).unwrap();
- let bitcode_path = build_script_dir_path.join("bitcode");
+ let bitcode_path = dunce::canonicalize(Path::new(".")).unwrap();
// workaround for github.com/ziglang/zig/issues/9711
#[cfg(target_os = "macos")]
@@ -27,22 +24,6 @@ fn main() {
#[cfg(target_os = "macos")]
std::env::set_var("ZIG_GLOBAL_CACHE_DIR", zig_cache_dir.path().as_os_str());
- // LLVM .bc FILES
-
- generate_bc_file(&bitcode_path, "ir", "builtins-host");
-
- if !DEBUG {
- generate_bc_file(&bitcode_path, "ir-wasm32", "builtins-wasm32");
- }
-
- generate_bc_file(&bitcode_path, "ir-i386", "builtins-i386");
- generate_bc_file(&bitcode_path, "ir-x86_64", "builtins-x86_64");
- generate_bc_file(
- &bitcode_path,
- "ir-windows-x86_64",
- "builtins-windows-x86_64",
- );
-
// OBJECT FILES
#[cfg(windows)]
const BUILTINS_HOST_FILE: &str = "builtins-host.obj";
@@ -107,38 +88,13 @@ fn generate_object_file(bitcode_path: &Path, zig_object: &str, object_file_name:
}
}
-fn generate_bc_file(bitcode_path: &Path, zig_object: &str, file_name: &str) {
- let mut ll_path = bitcode_path.join(file_name);
- ll_path.set_extension("ll");
- let dest_ir_host = ll_path.to_str().expect("Invalid dest ir path");
-
- println!("Compiling host ir to: {}", dest_ir_host);
-
- let mut bc_path = bitcode_path.join(file_name);
- bc_path.set_extension("bc");
- let dest_bc_64bit = bc_path.to_str().expect("Invalid dest bc path");
- println!("Compiling 64-bit bitcode to: {}", dest_bc_64bit);
-
- // workaround for github.com/ziglang/zig/issues/9711
- #[cfg(target_os = "macos")]
- let _ = fs::remove_dir_all("./bitcode/zig-cache");
-
- let mut zig_cmd = zig();
-
- zig_cmd
- .current_dir(bitcode_path)
- .args(["build", zig_object, "-Drelease=true"]);
-
- run_command(zig_cmd, 0);
-}
-
pub fn get_lib_dir() -> PathBuf {
// Currently we have the OUT_DIR variable which points to `/target/debug/build/roc_builtins-*/out/`.
// So we just need to add "/bitcode" to that.
- let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap()).join("bitcode");
+ let dir = PathBuf::from(env::var_os("OUT_DIR").unwrap());
// create dir if it does not exist
- fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/bitcode dir.");
+ fs::create_dir_all(&dir).expect("Failed to make $OUT_DIR/ dir.");
dir
}
@@ -192,7 +148,7 @@ fn cp_unless_zig_cache(src_dir: &Path, target_dir: &Path) -> io::Result<()> {
}
fn run_command(mut command: Command, flaky_fail_counter: usize) {
- let command_str = roc_utils::pretty_command_string(&command);
+ let command_str = pretty_command_string(&command);
let command_str = command_str.to_string_lossy();
let output_result = command.output();
diff --git a/crates/compiler/builtins/bitcode/src/dec.zig b/crates/compiler/builtins/bitcode/src/dec.zig
index 3910d98a2d..a3f0171133 100644
--- a/crates/compiler/builtins/bitcode/src/dec.zig
+++ b/crates/compiler/builtins/bitcode/src/dec.zig
@@ -921,8 +921,8 @@ test "toStr: 123.1111111" {
test "toStr: 123.1111111111111 (big str)" {
var dec: RocDec = .{ .num = 123111111111111000000 };
var res_roc_str = dec.toStr();
- errdefer res_roc_str.deinit();
- defer res_roc_str.deinit();
+ errdefer res_roc_str.decref();
+ defer res_roc_str.decref();
const res_slice: []const u8 = "123.111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@@ -931,8 +931,8 @@ test "toStr: 123.1111111111111 (big str)" {
test "toStr: 123.111111111111444444 (max number of decimal places)" {
var dec: RocDec = .{ .num = 123111111111111444444 };
var res_roc_str = dec.toStr();
- errdefer res_roc_str.deinit();
- defer res_roc_str.deinit();
+ errdefer res_roc_str.decref();
+ defer res_roc_str.decref();
const res_slice: []const u8 = "123.111111111111444444"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@@ -941,8 +941,8 @@ test "toStr: 123.111111111111444444 (max number of decimal places)" {
test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
var dec: RocDec = .{ .num = 12345678912345678912111111111111111111 };
var res_roc_str = dec.toStr();
- errdefer res_roc_str.deinit();
- defer res_roc_str.deinit();
+ errdefer res_roc_str.decref();
+ defer res_roc_str.decref();
const res_slice: []const u8 = "12345678912345678912.111111111111111111"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@@ -951,8 +951,8 @@ test "toStr: 12345678912345678912.111111111111111111 (max number of digits)" {
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();
+ errdefer res_roc_str.decref();
+ defer res_roc_str.decref();
const res_slice: []const u8 = "170141183460469231731.687303715884105727"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@@ -961,8 +961,8 @@ test "toStr: std.math.maxInt" {
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();
+ errdefer res_roc_str.decref();
+ defer res_roc_str.decref();
const res_slice: []const u8 = "-170141183460469231731.687303715884105728"[0..];
try expectEqualSlices(u8, res_slice, res_roc_str.asSlice());
@@ -1047,8 +1047,8 @@ test "div: 10 / 3" {
var denom: RocDec = RocDec.fromU64(3);
var roc_str = RocStr.init("3.333333333333333333", 20);
- errdefer roc_str.deinit();
- defer roc_str.deinit();
+ errdefer roc_str.decref();
+ defer roc_str.decref();
var res: RocDec = RocDec.fromStr(roc_str).?;
diff --git a/crates/compiler/builtins/bitcode/src/lib.rs b/crates/compiler/builtins/bitcode/src/lib.rs
new file mode 100644
index 0000000000..58efea062e
--- /dev/null
+++ b/crates/compiler/builtins/bitcode/src/lib.rs
@@ -0,0 +1,65 @@
+use tempfile::NamedTempFile;
+
+const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-wasm32.o"));
+// TODO: in the future, we should use Zig's cross-compilation to generate and store these
+// for all targets, so that we can do cross-compilation!
+#[cfg(unix)]
+const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/builtins-host.o"));
+#[cfg(windows)]
+const HOST_WINDOWS: &[u8] =
+ include_bytes!(concat!(env!("OUT_DIR"), "/builtins-windows-x86_64.obj"));
+
+pub fn host_wasm_tempfile() -> std::io::Result {
+ let tempfile = tempfile::Builder::new()
+ .prefix("host_bitcode")
+ .suffix(".wasm")
+ .rand_bytes(8)
+ .tempfile()?;
+
+ std::fs::write(tempfile.path(), HOST_WASM)?;
+
+ Ok(tempfile)
+}
+
+#[cfg(unix)]
+fn host_unix_tempfile() -> std::io::Result {
+ let tempfile = tempfile::Builder::new()
+ .prefix("host_bitcode")
+ .suffix(".o")
+ .rand_bytes(8)
+ .tempfile()?;
+
+ std::fs::write(tempfile.path(), HOST_UNIX)?;
+
+ Ok(tempfile)
+}
+
+#[cfg(windows)]
+fn host_windows_tempfile() -> std::io::Result {
+ let tempfile = tempfile::Builder::new()
+ .prefix("host_bitcode")
+ .suffix(".obj")
+ .rand_bytes(8)
+ .tempfile()?;
+
+ std::fs::write(tempfile.path(), HOST_WINDOWS)?;
+
+ Ok(tempfile)
+}
+
+pub fn host_tempfile() -> std::io::Result {
+ #[cfg(unix)]
+ {
+ host_unix_tempfile()
+ }
+
+ #[cfg(windows)]
+ {
+ host_windows_tempfile()
+ }
+
+ #[cfg(not(any(windows, unix)))]
+ {
+ unreachable!()
+ }
+}
diff --git a/crates/compiler/builtins/bitcode/src/list.zig b/crates/compiler/builtins/bitcode/src/list.zig
index 66d5fe9147..71968aa082 100644
--- a/crates/compiler/builtins/bitcode/src/list.zig
+++ b/crates/compiler/builtins/bitcode/src/list.zig
@@ -15,21 +15,45 @@ const IncN = fn (?[*]u8, usize) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
const HasTagId = fn (u16, ?[*]u8) callconv(.C) extern struct { matched: bool, data: ?[*]u8 };
+const SEAMLESS_SLICE_BIT: usize =
+ @bitCast(usize, @as(isize, std.math.minInt(isize)));
+
pub const RocList = extern struct {
bytes: ?[*]u8,
length: usize,
- capacity: usize,
+ // This technically points to directly after the refcount.
+ // This is an optimization that enables use one code path for regular lists and slices for geting the refcount ptr.
+ capacity_or_ref_ptr: usize,
pub inline fn len(self: RocList) usize {
return self.length;
}
+ pub fn getCapacity(self: RocList) usize {
+ const list_capacity = self.capacity_or_ref_ptr;
+ const slice_capacity = self.length;
+ const slice_mask = self.seamlessSliceMask();
+ const capacity = (list_capacity & ~slice_mask) | (slice_capacity & slice_mask);
+ return capacity;
+ }
+
+ pub fn isSeamlessSlice(self: RocList) bool {
+ return @bitCast(isize, self.capacity_or_ref_ptr) < 0;
+ }
+
+ // This returns all ones if the list is a seamless slice.
+ // Otherwise, it returns all zeros.
+ // This is done without branching for optimization purposes.
+ pub fn seamlessSliceMask(self: RocList) usize {
+ return @bitCast(usize, @bitCast(isize, self.capacity_or_ref_ptr) >> (@bitSizeOf(isize) - 1));
+ }
+
pub fn isEmpty(self: RocList) bool {
return self.len() == 0;
}
pub fn empty() RocList {
- return RocList{ .bytes = null, .length = 0, .capacity = 0 };
+ return RocList{ .bytes = null, .length = 0, .capacity_or_ref_ptr = 0 };
}
pub fn eql(self: RocList, other: RocList) bool {
@@ -75,8 +99,21 @@ pub const RocList = extern struct {
return list;
}
- pub fn deinit(self: RocList, comptime T: type) void {
- utils.decref(self.bytes, self.capacity, @alignOf(T));
+ // returns a pointer to just after the refcount.
+ // It is just after the refcount as an optimization for other shared code paths.
+ // For regular list, it just returns their bytes pointer.
+ // For seamless slices, it returns the pointer stored in capacity_or_ref_ptr.
+ pub fn getRefcountPtr(self: RocList) ?[*]u8 {
+ const list_ref_ptr = @ptrToInt(self.bytes);
+ const slice_ref_ptr = self.capacity_or_ref_ptr << 1;
+ const slice_mask = self.seamlessSliceMask();
+ const ref_ptr = (list_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask);
+ return @intToPtr(?[*]u8, ref_ptr);
+ }
+
+ pub fn decref(self: RocList, alignment: u32) void {
+ // We use the raw capacity to ensure we always decrement the refcount of seamless slices.
+ utils.decref(self.getRefcountPtr(), self.capacity_or_ref_ptr, alignment);
}
pub fn elements(self: RocList, comptime T: type) ?[*]T {
@@ -88,7 +125,7 @@ pub const RocList = extern struct {
}
fn refcountMachine(self: RocList) usize {
- if (self.capacity == 0) {
+ if (self.getCapacity() == 0 and !self.isSeamlessSlice()) {
// the zero-capacity is Clone, copying it will not leak memory
return utils.REFCOUNT_ONE;
}
@@ -110,12 +147,15 @@ pub const RocList = extern struct {
}
pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList {
- if (self.isEmpty()) {
+ if (self.isUnique()) {
return self;
}
- if (self.isUnique()) {
- return self;
+ if (self.isEmpty()) {
+ // Empty is not necessarily unique on it's own.
+ // The list could have capacity and be shared.
+ self.decref(alignment);
+ return RocList.empty();
}
// unfortunately, we have to clone
@@ -127,9 +167,8 @@ pub const RocList = extern struct {
const number_of_bytes = self.len() * element_width;
@memcpy(new_bytes, old_bytes, number_of_bytes);
- // NOTE we fuse an increment of all keys/values with a decrement of the input dict
- const data_bytes = self.len() * element_width;
- utils.decref(self.bytes, data_bytes, alignment);
+ // NOTE we fuse an increment of all keys/values with a decrement of the input list.
+ self.decref(alignment);
return new_list;
}
@@ -148,7 +187,24 @@ pub const RocList = extern struct {
return RocList{
.bytes = utils.allocateWithRefcount(data_bytes, alignment),
.length = length,
- .capacity = capacity,
+ .capacity_or_ref_ptr = capacity,
+ };
+ }
+
+ pub fn allocateExact(
+ alignment: u32,
+ length: usize,
+ element_width: usize,
+ ) RocList {
+ if (length == 0) {
+ return empty();
+ }
+
+ const data_bytes = length * element_width;
+ return RocList{
+ .bytes = utils.allocateWithRefcount(data_bytes, alignment),
+ .length = length,
+ .capacity_or_ref_ptr = length,
};
}
@@ -159,13 +215,14 @@ pub const RocList = extern struct {
element_width: usize,
) RocList {
if (self.bytes) |source_ptr| {
- if (self.isUnique()) {
- if (self.capacity >= new_length) {
- return RocList{ .bytes = self.bytes, .length = new_length, .capacity = self.capacity };
+ if (self.isUnique() and !self.isSeamlessSlice()) {
+ const capacity = self.capacity_or_ref_ptr;
+ if (capacity >= new_length) {
+ return RocList{ .bytes = self.bytes, .length = new_length, .capacity_or_ref_ptr = capacity };
} else {
- const new_capacity = utils.calculateCapacity(self.capacity, new_length, element_width);
- const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_capacity, element_width);
- return RocList{ .bytes = new_source, .length = new_length, .capacity = new_capacity };
+ const new_capacity = utils.calculateCapacity(capacity, new_length, element_width);
+ const new_source = utils.unsafeReallocate(source_ptr, alignment, capacity, new_capacity, element_width);
+ return RocList{ .bytes = new_source, .length = new_length, .capacity_or_ref_ptr = new_capacity };
}
}
return self.reallocateFresh(alignment, new_length, element_width);
@@ -193,7 +250,7 @@ pub const RocList = extern struct {
@memset(dest_ptr + old_length * element_width, 0, delta_length * element_width);
}
- utils.decref(self.bytes, old_length * element_width, alignment);
+ self.decref(alignment);
return result;
}
@@ -428,7 +485,7 @@ pub fn listReserve(
update_mode: UpdateMode,
) callconv(.C) RocList {
const old_length = list.len();
- if ((update_mode == .InPlace or list.isUnique()) and list.capacity >= list.len() + spare) {
+ if ((update_mode == .InPlace or list.isUnique()) and list.getCapacity() >= list.len() + spare) {
return list;
} else {
var output = list.reallocate(alignment, old_length + spare, element_width);
@@ -437,6 +494,31 @@ pub fn listReserve(
}
}
+pub fn listReleaseExcessCapacity(
+ list: RocList,
+ alignment: u32,
+ element_width: usize,
+ update_mode: UpdateMode,
+) callconv(.C) RocList {
+ const old_length = list.len();
+ // We use the direct list.capacity_or_ref_ptr to make sure both that there is no extra capacity and that it isn't a seamless slice.
+ if ((update_mode == .InPlace or list.isUnique()) and list.capacity_or_ref_ptr == old_length) {
+ return list;
+ } else if (old_length == 0) {
+ list.decref(alignment);
+ return RocList.empty();
+ } else {
+ var output = RocList.allocateExact(alignment, old_length, element_width);
+ if (list.bytes) |source_ptr| {
+ const dest_ptr = output.bytes orelse unreachable;
+
+ @memcpy(dest_ptr, source_ptr, old_length * element_width);
+ }
+ list.decref(alignment);
+ return output;
+ }
+}
+
pub fn listAppendUnsafe(
list: RocList,
element: Opaque,
@@ -462,10 +544,12 @@ fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usi
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);
+ // TODO: properly wire in update mode.
+ var with_capacity = listReserve(list, alignment, 1, element_width, .Immutable);
+ with_capacity.length += 1;
// can't use one memcpy here because source and target overlap
- if (output.bytes) |target| {
+ if (with_capacity.bytes) |target| {
var i: usize = old_length;
while (i > 0) {
@@ -481,7 +565,7 @@ pub fn listPrepend(list: RocList, alignment: u32, element: Opaque, element_width
}
}
- return output;
+ return with_capacity;
}
pub fn listSwap(
@@ -522,19 +606,20 @@ pub fn listSublist(
) callconv(.C) RocList {
const size = list.len();
if (len == 0 or start >= size) {
- if (list.isUnique()) {
- // Decrement the reference counts of all elements.
- if (list.bytes) |source_ptr| {
- var i: usize = 0;
- while (i < size) : (i += 1) {
- const element = source_ptr + i * element_width;
- dec(element);
- }
- var output = list;
- output.length = 0;
- return output;
+ // Decrement the reference counts of all elements.
+ if (list.bytes) |source_ptr| {
+ var i: usize = 0;
+ while (i < size) : (i += 1) {
+ const element = source_ptr + i * element_width;
+ dec(element);
}
}
+ if (list.isUnique()) {
+ var output = list;
+ output.length = 0;
+ return output;
+ }
+ list.decref(alignment);
return RocList.empty();
}
@@ -557,26 +642,20 @@ pub fn listSublist(
dec(element);
}
- if (list.isUnique()) {
+ if (start == 0 and list.isUnique()) {
var output = list;
output.length = keep_len;
- if (start == 0) {
- return output;
- } else {
- // We want memmove due to aliasing. Zig does not expose it directly.
- // Instead use copy which can write to aliases as long as the dest is before the source.
- mem.copy(u8, source_ptr[0 .. keep_len * element_width], source_ptr[start * element_width .. (start + keep_len) * element_width]);
- return output;
- }
- } else {
- const output = RocList.allocate(alignment, keep_len, element_width);
- const target_ptr = output.bytes orelse unreachable;
-
- @memcpy(target_ptr, source_ptr + start * element_width, keep_len * element_width);
-
- utils.decref(list.bytes, size * element_width, alignment);
-
return output;
+ } else {
+ const list_ref_ptr = (@ptrToInt(source_ptr) >> 1) | SEAMLESS_SLICE_BIT;
+ const slice_ref_ptr = list.capacity_or_ref_ptr;
+ const slice_mask = list.seamlessSliceMask();
+ const ref_ptr = (list_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask);
+ return RocList{
+ .bytes = source_ptr + start * element_width,
+ .length = keep_len,
+ .capacity_or_ref_ptr = ref_ptr,
+ };
}
}
@@ -590,9 +669,17 @@ pub fn listDropAt(
drop_index: usize,
dec: Dec,
) callconv(.C) RocList {
- if (list.bytes) |source_ptr| {
- const size = list.len();
+ const size = list.len();
+ // If droping the first or last element, return a seamless slice.
+ // For simplicity, do this by calling listSublist.
+ // In the future, we can test if it is faster to manually inline the important parts here.
+ if (drop_index == 0) {
+ return listSublist(list, alignment, element_width, 1, size -| 1, dec);
+ } else if (drop_index == size -| 1) {
+ return listSublist(list, alignment, element_width, 0, size -| 1, dec);
+ }
+ if (list.bytes) |source_ptr| {
if (drop_index >= size) {
return list;
}
@@ -607,7 +694,7 @@ pub fn listDropAt(
// because we rely on the pointer field being null if the list is empty
// which also requires duplicating the utils.decref call to spend the RC token
if (size < 2) {
- utils.decref(list.bytes, size * element_width, alignment);
+ list.decref(alignment);
return RocList.empty();
}
@@ -637,7 +724,7 @@ pub fn listDropAt(
const tail_size = (size - drop_index - 1) * element_width;
@memcpy(tail_target, tail_source, tail_size);
- utils.decref(list.bytes, size * element_width, alignment);
+ list.decref(alignment);
return output;
} else {
@@ -747,11 +834,13 @@ fn swapElements(source_ptr: [*]u8, element_width: usize, index_1: usize, index_2
pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_width: usize) callconv(.C) RocList {
// NOTE we always use list_a! because it is owned, we must consume it, and it may have unused capacity
if (list_b.isEmpty()) {
- if (list_a.capacity == 0) {
+ if (list_a.getCapacity() == 0) {
+ // a could be a seamless slice, so we still need to decref.
+ list_a.decref(alignment);
return list_b;
} else {
// we must consume this list. Even though it has no elements, it could still have capacity
- list_b.deinit(usize);
+ list_b.decref(alignment);
return list_a;
}
@@ -766,7 +855,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
@memcpy(source_a + list_a.len() * element_width, source_b, list_b.len() * element_width);
// decrement list b.
- utils.decref(source_b, list_b.len(), alignment);
+ list_b.decref(alignment);
return resized_list_a;
} else if (list_b.isUnique()) {
@@ -787,7 +876,7 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
@memcpy(source_b, source_a, byte_count_a);
// decrement list a.
- utils.decref(source_a, list_a.len(), alignment);
+ list_a.decref(alignment);
return resized_list_b;
}
@@ -804,8 +893,8 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
@memcpy(target + list_a.len() * element_width, source_b, list_b.len() * element_width);
// decrement list a and b.
- utils.decref(source_a, list_a.len(), alignment);
- utils.decref(source_b, list_b.len(), alignment);
+ list_a.decref(alignment);
+ list_b.decref(alignment);
return output;
}
@@ -868,20 +957,32 @@ pub fn listIsUnique(
return list.isEmpty() or list.isUnique();
}
+pub fn listCapacity(
+ list: RocList,
+) callconv(.C) usize {
+ return list.getCapacity();
+}
+
+pub fn listRefcountPtr(
+ list: RocList,
+) callconv(.C) ?[*]u8 {
+ return list.getRefcountPtr();
+}
+
test "listConcat: non-unique with unique overlapping" {
var nonUnique = RocList.fromSlice(u8, ([_]u8{1})[0..]);
var bytes: [*]u8 = @ptrCast([*]u8, nonUnique.bytes);
const ptr_width = @sizeOf(usize);
const refcount_ptr = @ptrCast([*]isize, @alignCast(ptr_width, bytes) - ptr_width);
utils.increfC(&refcount_ptr[0], 1);
- defer nonUnique.deinit(u8); // listConcat will dec the other refcount
+ defer nonUnique.decref(@sizeOf(u8)); // listConcat will dec the other refcount
var unique = RocList.fromSlice(u8, ([_]u8{ 2, 3, 4 })[0..]);
- defer unique.deinit(u8);
+ defer unique.decref(@sizeOf(u8));
var concatted = listConcat(nonUnique, unique, 1, 1);
var wanted = RocList.fromSlice(u8, ([_]u8{ 1, 2, 3, 4 })[0..]);
- defer wanted.deinit(u8);
+ defer wanted.decref(@sizeOf(u8));
try expect(concatted.eql(wanted));
}
diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig
index 56a403a8c8..6f0b59468d 100644
--- a/crates/compiler/builtins/bitcode/src/main.zig
+++ b/crates/compiler/builtins/bitcode/src/main.zig
@@ -54,6 +54,9 @@ comptime {
exportListFn(list.listReplaceInPlace, "replace_in_place");
exportListFn(list.listSwap, "swap");
exportListFn(list.listIsUnique, "is_unique");
+ exportListFn(list.listCapacity, "capacity");
+ exportListFn(list.listRefcountPtr, "refcount_ptr");
+ exportListFn(list.listReleaseExcessCapacity, "release_excess_capacity");
}
// Num Module
@@ -67,6 +70,8 @@ const NUMBERS = INTEGERS ++ FLOATS;
comptime {
exportNumFn(num.bytesToU16C, "bytes_to_u16");
exportNumFn(num.bytesToU32C, "bytes_to_u32");
+ exportNumFn(num.bytesToU64C, "bytes_to_u64");
+ exportNumFn(num.bytesToU128C, "bytes_to_u128");
inline for (INTEGERS) |T, i| {
num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int.");
@@ -86,6 +91,10 @@ comptime {
num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow.");
num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic.");
num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated.");
+
+ num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits.");
+ num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits.");
+ num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits.");
}
inline for (INTEGERS) |FROM| {
@@ -138,7 +147,6 @@ comptime {
exportStrFn(str.getScalarUnsafe, "get_scalar_unsafe");
exportStrFn(str.appendScalar, "append_scalar");
exportStrFn(str.strToUtf8C, "to_utf8");
- exportStrFn(str.fromUtf8C, "from_utf8");
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim");
@@ -147,6 +155,8 @@ comptime {
exportStrFn(str.strCloneTo, "clone_to");
exportStrFn(str.withCapacity, "with_capacity");
exportStrFn(str.strGraphemes, "graphemes");
+ exportStrFn(str.strRefcountPtr, "refcount_ptr");
+ exportStrFn(str.strReleaseExcessCapacity, "release_excess_capacity");
inline for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");
diff --git a/crates/compiler/builtins/bitcode/src/num.zig b/crates/compiler/builtins/bitcode/src/num.zig
index cb8c2498f2..dbbf87c1cf 100644
--- a/crates/compiler/builtins/bitcode/src/num.zig
+++ b/crates/compiler/builtins/bitcode/src/num.zig
@@ -236,6 +236,24 @@ fn bytesToU32(arg: RocList, position: usize) u32 {
return @bitCast(u32, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3] });
}
+pub fn bytesToU64C(arg: RocList, position: usize) callconv(.C) u64 {
+ return @call(.{ .modifier = always_inline }, bytesToU64, .{ arg, position });
+}
+
+fn bytesToU64(arg: RocList, position: usize) u64 {
+ const bytes = @ptrCast([*]const u8, arg.bytes);
+ return @bitCast(u64, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7] });
+}
+
+pub fn bytesToU128C(arg: RocList, position: usize) callconv(.C) u128 {
+ return @call(.{ .modifier = always_inline }, bytesToU128, .{ arg, position });
+}
+
+fn bytesToU128(arg: RocList, position: usize) u128 {
+ const bytes = @ptrCast([*]const u8, arg.bytes);
+ return @bitCast(u128, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] });
+}
+
fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) {
switch (@typeInfo(T)) {
.Int => {
@@ -460,3 +478,30 @@ pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []con
}.func;
@export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
}
+
+pub fn exportCountLeadingZeroBits(comptime T: type, comptime name: []const u8) void {
+ comptime var f = struct {
+ fn func(self: T) callconv(.C) usize {
+ return @as(usize, @clz(T, self));
+ }
+ }.func;
+ @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
+}
+
+pub fn exportCountTrailingZeroBits(comptime T: type, comptime name: []const u8) void {
+ comptime var f = struct {
+ fn func(self: T) callconv(.C) usize {
+ return @as(usize, @ctz(T, self));
+ }
+ }.func;
+ @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
+}
+
+pub fn exportCountOneBits(comptime T: type, comptime name: []const u8) void {
+ comptime var f = struct {
+ fn func(self: T) callconv(.C) usize {
+ return @as(usize, @popCount(T, self));
+ }
+ }.func;
+ @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong });
+}
diff --git a/crates/compiler/builtins/bitcode/src/str.zig b/crates/compiler/builtins/bitcode/src/str.zig
index 9c72af30c8..ea57a934e1 100644
--- a/crates/compiler/builtins/bitcode/src/str.zig
+++ b/crates/compiler/builtins/bitcode/src/str.zig
@@ -18,6 +18,7 @@ const InPlace = enum(u8) {
const MASK_ISIZE: isize = std.math.minInt(isize);
const MASK: usize = @bitCast(usize, MASK_ISIZE);
+const SEAMLESS_SLICE_BIT: usize = MASK;
const SMALL_STR_MAX_LENGTH = SMALL_STRING_SIZE - 1;
const SMALL_STRING_SIZE = @sizeOf(RocStr);
@@ -57,6 +58,37 @@ pub const RocStr = extern struct {
return result;
}
+ // This requires that the list is non-null.
+ // It also requires that start and count define a slice that does not go outside the bounds of the list.
+ pub fn fromSubListUnsafe(list: RocList, start: usize, count: usize, update_mode: UpdateMode) RocStr {
+ const start_byte = @ptrCast([*]u8, list.bytes) + start;
+ if (list.isSeamlessSlice()) {
+ return RocStr{
+ .str_bytes = start_byte,
+ .str_len = count | SEAMLESS_SLICE_BIT,
+ .str_capacity = list.capacity_or_ref_ptr & (~SEAMLESS_SLICE_BIT),
+ };
+ } else if (start == 0 and (update_mode == .InPlace or list.isUnique())) {
+ // Rare case, we can take over the original list.
+ return RocStr{
+ .str_bytes = start_byte,
+ .str_len = count,
+ .str_capacity = list.capacity_or_ref_ptr, // This is guaranteed to be a proper capacity.
+ };
+ } else {
+ // Create seamless slice pointing to the list.
+ return RocStr{
+ .str_bytes = start_byte,
+ .str_len = count | SEAMLESS_SLICE_BIT,
+ .str_capacity = @ptrToInt(list.bytes) >> 1,
+ };
+ }
+ }
+
+ pub fn isSeamlessSlice(self: RocStr) bool {
+ return !self.isSmallStr() and @bitCast(isize, self.str_len) < 0;
+ }
+
pub fn fromSlice(slice: []const u8) RocStr {
return RocStr.init(slice.ptr, slice.len);
}
@@ -89,13 +121,55 @@ pub const RocStr = extern struct {
}
}
- pub fn deinit(self: RocStr) void {
- self.decref();
+ // allocate space for a (big or small) RocStr, but put nothing in it yet.
+ // Will have the exact same capacity as length if it is not a small string.
+ pub fn allocateExact(length: usize) RocStr {
+ const result_is_big = length >= SMALL_STRING_SIZE;
+
+ if (result_is_big) {
+ return RocStr.allocateBig(length, length);
+ } else {
+ var string = RocStr.empty();
+
+ string.asU8ptrMut()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
+
+ return string;
+ }
}
- fn decref(self: RocStr) void {
+ // This returns all ones if the list is a seamless slice.
+ // Otherwise, it returns all zeros.
+ // This is done without branching for optimization purposes.
+ pub fn seamlessSliceMask(self: RocStr) usize {
+ return @bitCast(usize, @bitCast(isize, self.str_len) >> (@bitSizeOf(isize) - 1));
+ }
+
+ // returns a pointer to just after the refcount.
+ // It is just after the refcount as an optimization for other shared code paths.
+ // For regular list, it just returns their bytes pointer.
+ // For seamless slices, it returns the pointer stored in capacity_or_ref_ptr.
+ // This does not return a valid value if the input is a small string.
+ pub fn getRefcountPtr(self: RocStr) ?[*]u8 {
+ const str_ref_ptr = @ptrToInt(self.str_bytes);
+ const slice_ref_ptr = self.str_capacity << 1;
+ const slice_mask = self.seamlessSliceMask();
+ const ref_ptr = (str_ref_ptr & ~slice_mask) | (slice_ref_ptr & slice_mask);
+ return @intToPtr(?[*]u8, ref_ptr);
+ }
+
+ pub fn incref(self: RocStr, n: usize) void {
if (!self.isSmallStr()) {
- utils.decref(self.str_bytes, self.str_capacity, RocStr.alignment);
+ const ref_ptr = self.getRefcountPtr();
+ if (ref_ptr != null) {
+ const isizes: [*]isize = @ptrCast([*]isize, @alignCast(@alignOf(isize), ref_ptr));
+ utils.increfC(@ptrCast(*isize, isizes - 1), @intCast(isize, n));
+ }
+ }
+ }
+
+ pub fn decref(self: RocStr) void {
+ if (!self.isSmallStr()) {
+ utils.decref(self.getRefcountPtr(), self.str_capacity, RocStr.alignment);
}
}
@@ -116,20 +190,11 @@ pub const RocStr = extern struct {
// Now we have to look at the string contents
const self_bytes = self.asU8ptr();
const other_bytes = other.asU8ptr();
-
- // It's faster to compare pointer-sized words rather than bytes, as far as possible
- // The bytes are always pointer-size aligned due to the refcount
- const self_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), self_bytes));
- const other_words = @ptrCast([*]const usize, @alignCast(@alignOf(usize), other_bytes));
- var w: usize = 0;
- while (w < self_len / @sizeOf(usize)) : (w += 1) {
- if (self_words[w] != other_words[w]) {
- return false;
- }
- }
-
- // Compare the leftover bytes
- var b = w * @sizeOf(usize);
+ // TODO: we can make an optimization like memcmp does in glibc.
+ // We can check the min shared alignment 1, 2, 4, or 8.
+ // Then do a copy at that alignment before falling back on one byte at a time.
+ // Currently we have to be unaligned because slices can be at any alignment.
+ var b: usize = 0;
while (b < self_len) : (b += 1) {
if (self_bytes[b] != other_bytes[b]) {
return false;
@@ -162,7 +227,7 @@ pub const RocStr = extern struct {
const element_width = 1;
const old_capacity = self.getCapacity();
- if (self.isSmallStr() or !self.isUnique()) {
+ if (self.isSmallStr() or self.isSeamlessSlice() or !self.isUnique()) {
return self.reallocateFresh(new_length);
}
@@ -228,7 +293,7 @@ pub const RocStr = extern struct {
if (self.isSmallStr()) {
return self.asArray()[@sizeOf(RocStr) - 1] ^ 0b1000_0000;
} else {
- return self.str_len;
+ return self.str_len & (~SEAMLESS_SLICE_BIT);
}
}
@@ -236,13 +301,15 @@ pub const RocStr = extern struct {
if (self.isSmallStr()) {
self.asU8ptrMut()[@sizeOf(RocStr) - 1] = @intCast(u8, length) | 0b1000_0000;
} else {
- self.str_len = length;
+ self.str_len = length | (SEAMLESS_SLICE_BIT & self.str_len);
}
}
pub fn getCapacity(self: RocStr) usize {
if (self.isSmallStr()) {
return SMALL_STR_MAX_LENGTH;
+ } else if (self.isSeamlessSlice()) {
+ return self.str_len & (~SEAMLESS_SLICE_BIT);
} else {
return self.str_capacity;
}
@@ -289,6 +356,9 @@ pub const RocStr = extern struct {
// then the next byte is off the end of the struct;
// in that case, we are also not null-terminated!
return length != 0 and length != longest_small_str;
+ } else if (self.isSeamlessSlice()) {
+ // Seamless slices can not use the character past the end even if it is null.
+ return false;
} else {
// This is a big string, and it's not empty, so we can safely
// dereference the pointer.
@@ -324,7 +394,7 @@ pub const RocStr = extern struct {
}
fn refcountMachine(self: RocStr) usize {
- if (self.getCapacity() == 0 or self.isSmallStr()) {
+ if ((self.getCapacity() == 0 and !self.isSeamlessSlice()) or self.isSmallStr()) {
return utils.REFCOUNT_ONE;
}
@@ -388,8 +458,8 @@ pub const RocStr = extern struct {
try expect(roc_str1.eq(roc_str2));
- roc_str1.deinit();
- roc_str2.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
}
test "RocStr.eq: small, not equal, different length" {
@@ -404,8 +474,8 @@ pub const RocStr = extern struct {
var roc_str2 = RocStr.init(str2_ptr, str2_len);
defer {
- roc_str1.deinit();
- roc_str2.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
}
try expect(!roc_str1.eq(roc_str2));
@@ -423,8 +493,8 @@ pub const RocStr = extern struct {
var roc_str2 = RocStr.init(str2_ptr, str2_len);
defer {
- roc_str1.deinit();
- roc_str2.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
}
try expect(!roc_str1.eq(roc_str2));
@@ -436,8 +506,8 @@ pub const RocStr = extern struct {
const roc_str2 = RocStr.init(content, content.len);
defer {
- roc_str1.deinit();
- roc_str2.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
}
try expect(roc_str1.eq(roc_str2));
@@ -450,8 +520,8 @@ pub const RocStr = extern struct {
const roc_str2 = RocStr.init(content2, content2.len);
defer {
- roc_str1.deinit();
- roc_str2.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
}
try expect(!roc_str1.eq(roc_str2));
@@ -464,8 +534,8 @@ pub const RocStr = extern struct {
const roc_str2 = RocStr.init(content2, content2.len);
defer {
- roc_str1.deinit();
- roc_str2.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
}
try expect(!roc_str1.eq(roc_str2));
@@ -484,8 +554,8 @@ pub const RocStr = extern struct {
roc_str2.str_bytes.?[31] = '-';
defer {
- roc_str1.deinit();
- roc_str2.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
}
try expect(roc_str1.eq(roc_str2));
@@ -619,95 +689,95 @@ inline fn writeNextScalar(non_empty_string: RocStr, src_index: usize, dest: [*]u
test "strToScalars: empty string" {
const str = RocStr.fromSlice("");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected = RocList.empty();
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One ASCII char" {
const str = RocStr.fromSlice("R");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{82};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple ASCII chars" {
const str = RocStr.fromSlice("Roc!");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{ 82, 111, 99, 33 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 2-byte UTF-8 character" {
const str = RocStr.fromSlice("é");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{233};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 2-byte UTF-8 characters" {
const str = RocStr.fromSlice("Cäfés");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{ 67, 228, 102, 233, 115 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: One 3-byte UTF-8 character" {
const str = RocStr.fromSlice("鹏");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{40527};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
test "strToScalars: Multiple 3-byte UTF-8 characters" {
const str = RocStr.fromSlice("鹏很有趣");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{ 40527, 24456, 26377, 36259 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
@@ -715,14 +785,14 @@ test "strToScalars: Multiple 3-byte UTF-8 characters" {
test "strToScalars: One 4-byte UTF-8 character" {
// from https://design215.com/toolbox/utf8-4byte-characters.php
const str = RocStr.fromSlice("𒀀");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{73728};
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
@@ -730,14 +800,14 @@ test "strToScalars: One 4-byte UTF-8 character" {
test "strToScalars: Multiple 4-byte UTF-8 characters" {
// from https://design215.com/toolbox/utf8-4byte-characters.php
const str = RocStr.fromSlice("𒀀𒀁");
- defer RocStr.deinit(str);
+ defer RocStr.decref(str);
const expected_array = [_]u32{ 73728, 73729 };
const expected = RocList.fromSlice(u32, expected_array[0..expected_array.len]);
- defer RocList.deinit(expected, u32);
+ defer expected.decref(@sizeOf(u32));
const actual = strToScalars(str);
- defer RocList.deinit(actual, u32);
+ defer actual.decref(@sizeOf(u32));
try expect(RocList.eql(actual, expected));
}
@@ -801,6 +871,27 @@ pub fn strSplit(string: RocStr, delimiter: RocStr) callconv(.C) RocList {
return list;
}
+fn initFromSmallStr(slice_bytes: [*]u8, len: usize, _: usize) RocStr {
+ return RocStr.init(slice_bytes, len);
+}
+
+// The ref_ptr must already be shifted to be ready for storing in a seamless slice.
+fn initFromBigStr(slice_bytes: [*]u8, len: usize, ref_ptr: usize) RocStr {
+ // Here we can make seamless slices instead of copying to a new small str.
+ return RocStr{
+ .str_bytes = slice_bytes,
+ .str_len = len | SEAMLESS_SLICE_BIT,
+ .str_capacity = ref_ptr,
+ };
+}
+
+// TODO: relpace this with @qualCast or @constCast in future version of zig
+fn constCast(ptr: [*]const u8) [*]u8 {
+ var result: [*]u8 = undefined;
+ @memcpy(@ptrCast([*]u8, &result), @ptrCast([*]const u8, &ptr), @sizeOf([*]u8));
+ return result;
+}
+
fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
var ret_array_index: usize = 0;
var slice_start_index: usize = 0;
@@ -808,6 +899,11 @@ fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
const str_bytes = string.asU8ptr();
const str_len = string.len();
+ const ref_ptr = @ptrToInt(string.getRefcountPtr()) >> 1;
+ const init_fn = if (string.isSmallStr())
+ initFromSmallStr
+ else
+ initFromBigStr;
const delimiter_bytes_ptrs = delimiter.asU8ptr();
const delimiter_len = delimiter.len();
@@ -839,7 +935,7 @@ fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
if (matches_delimiter) {
const segment_len: usize = str_index - slice_start_index;
- array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, segment_len);
+ array[ret_array_index] = init_fn(constCast(str_bytes) + slice_start_index, segment_len, ref_ptr);
slice_start_index = str_index + delimiter_len;
ret_array_index += 1;
str_index += delimiter_len;
@@ -849,7 +945,12 @@ fn strSplitHelp(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
}
}
- array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, str_len - slice_start_index);
+ array[ret_array_index] = init_fn(constCast(str_bytes) + slice_start_index, str_len - slice_start_index, ref_ptr);
+
+ if (!string.isSmallStr()) {
+ // Correct refcount for all of the splits made.
+ string.incref(ret_array_index + 1);
+ }
}
test "strSplitHelp: empty delimiter" {
@@ -871,15 +972,15 @@ test "strSplitHelp: empty delimiter" {
defer {
for (array) |roc_str| {
- roc_str.deinit();
+ roc_str.decref();
}
for (expected) |roc_str| {
- roc_str.deinit();
+ roc_str.decref();
}
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
try expectEqual(array.len, expected.len);
@@ -905,15 +1006,15 @@ test "strSplitHelp: no delimiter" {
defer {
for (array) |roc_str| {
- roc_str.deinit();
+ roc_str.decref();
}
for (expected) |roc_str| {
- roc_str.deinit();
+ roc_str.decref();
}
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
try expectEqual(array.len, expected.len);
@@ -944,15 +1045,15 @@ test "strSplitHelp: empty start" {
defer {
for (array) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
for (expected) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
try expectEqual(array.len, expected.len);
@@ -986,15 +1087,15 @@ test "strSplitHelp: empty end" {
defer {
for (array) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
for (expected) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
try expectEqual(array.len, expected.len);
@@ -1020,14 +1121,14 @@ test "strSplitHelp: string equals delimiter" {
defer {
for (array) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
for (expected) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
- str_delimiter.deinit();
+ str_delimiter.decref();
}
try expectEqual(array.len, expected.len);
@@ -1060,15 +1161,15 @@ test "strSplitHelp: delimiter on sides" {
defer {
for (array) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
for (expected) |rocStr| {
- rocStr.deinit();
+ rocStr.decref();
}
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
try expectEqual(array.len, expected.len);
@@ -1101,15 +1202,15 @@ test "strSplitHelp: three pieces" {
defer {
for (array) |roc_str| {
- roc_str.deinit();
+ roc_str.decref();
}
for (expected_array) |roc_str| {
- roc_str.deinit();
+ roc_str.decref();
}
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
try expectEqual(expected_array.len, array.len);
@@ -1118,6 +1219,58 @@ test "strSplitHelp: three pieces" {
try expect(array[2].eq(expected_array[2]));
}
+test "strSplitHelp: overlapping delimiter 1" {
+ // Str.split "aaa" "aa" == ["", "a"]
+ const str_arr = "aaa";
+ const str = RocStr.init(str_arr, str_arr.len);
+
+ const delimiter_arr = "aa";
+ const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
+
+ var array: [2]RocStr = undefined;
+ const array_ptr: [*]RocStr = &array;
+
+ strSplitHelp(array_ptr, str, delimiter);
+
+ var expected = [2]RocStr{
+ RocStr.empty(),
+ RocStr.init("a", 1),
+ };
+
+ // strings are all small so we ignore freeing the memory
+
+ try expectEqual(array.len, expected.len);
+ try expect(array[0].eq(expected[0]));
+ try expect(array[1].eq(expected[1]));
+}
+
+test "strSplitHelp: overlapping delimiter 2" {
+ // Str.split "aaa" "aa" == ["", "a"]
+ const str_arr = "aaaa";
+ const str = RocStr.init(str_arr, str_arr.len);
+
+ const delimiter_arr = "aa";
+ const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
+
+ var array: [3]RocStr = undefined;
+ const array_ptr: [*]RocStr = &array;
+
+ strSplitHelp(array_ptr, str, delimiter);
+
+ var expected = [3]RocStr{
+ RocStr.empty(),
+ RocStr.empty(),
+ RocStr.empty(),
+ };
+
+ // strings are all small so we ignore freeing the memory
+
+ try expectEqual(array.len, expected.len);
+ try expect(array[0].eq(expected[0]));
+ try expect(array[1].eq(expected[1]));
+ try expect(array[2].eq(expected[2]));
+}
+
// This is used for `Str.split : Str, Str -> Array Str
// It is used to count how many segments the input `_str`
// needs to be broken into, so that we can allocate a array
@@ -1154,9 +1307,10 @@ pub fn countSegments(string: RocStr, delimiter: RocStr) callconv(.C) usize {
if (matches_delimiter) {
count += 1;
+ str_index += delimiter_len;
+ } else {
+ str_index += 1;
}
-
- str_index += 1;
}
}
@@ -1173,8 +1327,8 @@ test "countSegments: long delimiter" {
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer {
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
const segments_count = countSegments(str, delimiter);
@@ -1191,8 +1345,8 @@ test "countSegments: delimiter at start" {
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer {
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
const segments_count = countSegments(str, delimiter);
@@ -1210,8 +1364,8 @@ test "countSegments: delimiter interspered" {
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer {
- str.deinit();
- delimiter.deinit();
+ str.decref();
+ delimiter.decref();
}
const segments_count = countSegments(str, delimiter);
@@ -1226,7 +1380,7 @@ test "countSegments: string equals delimiter" {
const str_delimiter = RocStr.init(str_delimiter_arr, str_delimiter_arr.len);
defer {
- str_delimiter.deinit();
+ str_delimiter.decref();
}
const segments_count = countSegments(str_delimiter, str_delimiter);
@@ -1234,6 +1388,20 @@ test "countSegments: string equals delimiter" {
try expectEqual(segments_count, 2);
}
+test "countSegments: overlapping delimiter 1" {
+ // Str.split "aaa" "aa" == ["", "a"]
+ const segments_count = countSegments(RocStr.init("aaa", 3), RocStr.init("aa", 2));
+
+ try expectEqual(segments_count, 2);
+}
+
+test "countSegments: overlapping delimiter 2" {
+ // Str.split "aaa" "aa" == ["", "a"]
+ const segments_count = countSegments(RocStr.init("aaaa", 4), RocStr.init("aa", 2));
+
+ try expectEqual(segments_count, 3);
+}
+
// Str.countGraphemeClusters
pub fn countGraphemeClusters(string: RocStr) callconv(.C) usize {
if (string.isEmpty()) {
@@ -1277,6 +1445,12 @@ pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList {
var index: usize = 0;
var last_codepoint_len: u8 = 0;
+ const ref_ptr = @ptrToInt(roc_str.getRefcountPtr()) >> 1;
+ const init_fn = if (roc_str.isSmallStr())
+ initFromSmallStr
+ else
+ initFromBigStr;
+
var result = RocList.allocate(@alignOf(RocStr), countGraphemeClusters(roc_str), @sizeOf(RocStr));
const graphemes = result.elements(RocStr) orelse return result;
var slice = roc_str.asSlice();
@@ -1287,7 +1461,7 @@ pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList {
if (opt_last_codepoint) |last_codepoint| {
var did_break = grapheme.isGraphemeBreak(last_codepoint, cur_codepoint, &break_state);
if (did_break) {
- graphemes[index] = RocStr.fromSlice(slice[0..last_codepoint_len]);
+ graphemes[index] = init_fn(constCast(slice.ptr), last_codepoint_len, ref_ptr);
slice = slice[last_codepoint_len..];
index += 1;
break_state = null;
@@ -1298,19 +1472,24 @@ pub fn strGraphemes(roc_str: RocStr) callconv(.C) RocList {
opt_last_codepoint = cur_codepoint;
}
// Append last grapheme
- graphemes[index] = RocStr.fromSlice(slice);
+ graphemes[index] = init_fn(constCast(slice.ptr), slice.len, ref_ptr);
+
+ if (!roc_str.isSmallStr()) {
+ // Correct refcount for all of the splits made.
+ roc_str.incref(index + 1);
+ }
return result;
}
// these test both countGraphemeClusters() and strGraphemes()
fn graphemesTest(input: []const u8, expected: []const []const u8) !void {
const rocstr = RocStr.fromSlice(input);
- defer rocstr.deinit();
+ defer rocstr.decref();
const count = countGraphemeClusters(rocstr);
try expectEqual(expected.len, count);
const graphemes = strGraphemes(rocstr);
- defer graphemes.deinit(u8);
+ defer graphemes.decref(@sizeOf(u8));
if (input.len == 0) return; // empty string
const elems = graphemes.elements(RocStr) orelse unreachable;
for (expected) |g, i| {
@@ -1361,10 +1540,10 @@ pub fn getUnsafe(string: RocStr, index: usize) callconv(.C) u8 {
test "substringUnsafe: start" {
const str = RocStr.fromSlice("abcdef");
- defer str.deinit();
+ defer str.decref();
const expected = RocStr.fromSlice("abc");
- defer expected.deinit();
+ defer expected.decref();
const actual = substringUnsafe(str, 0, 3);
@@ -1373,10 +1552,10 @@ test "substringUnsafe: start" {
test "substringUnsafe: middle" {
const str = RocStr.fromSlice("abcdef");
- defer str.deinit();
+ defer str.decref();
const expected = RocStr.fromSlice("bcd");
- defer expected.deinit();
+ defer expected.decref();
const actual = substringUnsafe(str, 1, 3);
@@ -1385,10 +1564,10 @@ test "substringUnsafe: middle" {
test "substringUnsafe: end" {
const str = RocStr.fromSlice("a string so long it is heap-allocated");
- defer str.deinit();
+ defer str.decref();
const expected = RocStr.fromSlice("heap-allocated");
- defer expected.deinit();
+ defer expected.decref();
const actual = substringUnsafe(str, 23, 37 - 23);
@@ -1480,15 +1659,15 @@ test "startsWith: foo starts with fo" {
test "startsWith: 123456789123456789 starts with 123456789123456789" {
const str = RocStr.fromSlice("123456789123456789");
- defer str.deinit();
+ defer str.decref();
try expect(startsWith(str, str));
}
test "startsWith: 12345678912345678910 starts with 123456789123456789" {
const str = RocStr.fromSlice("12345678912345678910");
- defer str.deinit();
+ defer str.decref();
const prefix = RocStr.fromSlice("123456789123456789");
- defer prefix.deinit();
+ defer prefix.decref();
try expect(startsWith(str, prefix));
}
@@ -1519,23 +1698,23 @@ pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
test "endsWith: foo ends with oo" {
const foo = RocStr.init("foo", 3);
const oo = RocStr.init("oo", 2);
- defer foo.deinit();
- defer oo.deinit();
+ defer foo.decref();
+ defer oo.decref();
try expect(endsWith(foo, oo));
}
test "endsWith: 123456789123456789 ends with 123456789123456789" {
const str = RocStr.init("123456789123456789", 18);
- defer str.deinit();
+ defer str.decref();
try expect(endsWith(str, str));
}
test "endsWith: 12345678912345678910 ends with 345678912345678910" {
const str = RocStr.init("12345678912345678910", 20);
const suffix = RocStr.init("345678912345678910", 18);
- defer str.deinit();
- defer suffix.deinit();
+ defer str.decref();
+ defer suffix.decref();
try expect(endsWith(str, suffix));
}
@@ -1543,8 +1722,8 @@ test "endsWith: 12345678912345678910 ends with 345678912345678910" {
test "endsWith: hello world ends with world" {
const str = RocStr.init("hello world", 11);
const suffix = RocStr.init("world", 5);
- defer str.deinit();
- defer suffix.deinit();
+ defer str.decref();
+ defer suffix.decref();
try expect(endsWith(str, suffix));
}
@@ -1587,14 +1766,14 @@ test "RocStr.concat: small concat small" {
var roc_str3 = RocStr.init(str3_ptr, str3_len);
defer {
- roc_str1.deinit();
- roc_str2.deinit();
- roc_str3.deinit();
+ roc_str1.decref();
+ roc_str2.decref();
+ roc_str3.decref();
}
const result = strConcat(roc_str1, roc_str2);
- defer result.deinit();
+ defer result.decref();
try expect(roc_str3.eq(result));
}
@@ -1602,7 +1781,7 @@ test "RocStr.concat: small concat small" {
pub const RocListStr = extern struct {
list_elements: ?[*]RocStr,
list_length: usize,
- list_capacity: usize,
+ list_capacity_or_ref_ptr: usize,
};
// Str.joinWith
@@ -1610,7 +1789,7 @@ pub fn strJoinWithC(list: RocList, separator: RocStr) callconv(.C) RocStr {
const roc_list_str = RocListStr{
.list_elements = @ptrCast(?[*]RocStr, @alignCast(@alignOf(usize), list.bytes)),
.list_length = list.length,
- .list_capacity = list.capacity,
+ .list_capacity_or_ref_ptr = list.capacity_or_ref_ptr,
};
return @call(.{ .modifier = always_inline }, strJoinWith, .{ roc_list_str, separator });
@@ -1672,19 +1851,19 @@ test "RocStr.joinWith: result is big" {
var elements: [3]RocStr = .{ roc_elem, roc_elem, roc_elem };
const list = RocListStr{
.list_length = 3,
- .list_capacity = 3,
+ .list_capacity_or_ref_ptr = 3,
.list_elements = @ptrCast([*]RocStr, &elements),
};
defer {
- roc_sep.deinit();
- roc_elem.deinit();
- roc_result.deinit();
+ roc_sep.decref();
+ roc_elem.decref();
+ roc_result.decref();
}
const result = strJoinWith(list, roc_sep);
- defer result.deinit();
+ defer result.decref();
try expect(roc_result.eq(result));
}
@@ -1703,9 +1882,10 @@ inline fn strToBytes(arg: RocStr) RocList {
@memcpy(ptr, arg.asU8ptr(), length);
- return RocList{ .length = length, .bytes = ptr, .capacity = length };
+ return RocList{ .length = length, .bytes = ptr, .capacity_or_ref_ptr = length };
} else {
- return RocList{ .length = length, .bytes = arg.str_bytes, .capacity = arg.str_capacity };
+ const is_seamless_slice = arg.str_len & SEAMLESS_SLICE_BIT;
+ return RocList{ .length = length, .bytes = arg.str_bytes, .capacity_or_ref_ptr = arg.str_capacity | is_seamless_slice };
}
}
@@ -1721,61 +1901,6 @@ const CountAndStart = extern struct {
start: usize,
};
-pub fn fromUtf8C(output: *FromUtf8Result, arg: RocList, update_mode: UpdateMode) callconv(.C) void {
- output.* = fromUtf8(arg, update_mode);
-}
-
-inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result {
- const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length];
-
- if (unicode.utf8ValidateSlice(bytes)) {
- // the output will be correct. Now we need to take ownership of the input
- if (arg.len() <= SMALL_STR_MAX_LENGTH) {
- // turn the bytes into a small string
- const string = RocStr.init(@ptrCast([*]u8, arg.bytes), arg.len());
-
- // then decrement the input list
- const data_bytes = arg.len();
- utils.decref(arg.bytes, data_bytes, RocStr.alignment);
-
- return FromUtf8Result{
- .is_ok = true,
- .string = string,
- .byte_index = 0,
- .problem_code = Utf8ByteProblem.InvalidStartByte,
- };
- } else {
- const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode);
-
- const string = RocStr{
- .str_bytes = byte_list.bytes,
- .str_len = byte_list.length,
- .str_capacity = byte_list.capacity,
- };
-
- return FromUtf8Result{
- .is_ok = true,
- .string = string,
- .byte_index = 0,
- .problem_code = Utf8ByteProblem.InvalidStartByte,
- };
- }
- } else {
- const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length);
-
- // consume the input list
- const data_bytes = arg.len();
- utils.decref(arg.bytes, data_bytes, RocStr.alignment);
-
- return FromUtf8Result{
- .is_ok = false,
- .string = RocStr.empty(),
- .byte_index = temp.index,
- .problem_code = temp.problem,
- };
- }
-}
-
pub fn fromUtf8RangeC(
output: *FromUtf8Result,
list: RocList,
@@ -1787,45 +1912,31 @@ pub fn fromUtf8RangeC(
}
pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: UpdateMode) FromUtf8Result {
- const bytes = @ptrCast([*]const u8, arg.bytes)[start..count];
+ if (arg.len() == 0 or count == 0) {
+ arg.decref(RocStr.alignment);
+ return FromUtf8Result{
+ .is_ok = true,
+ .string = RocStr.empty(),
+ .byte_index = 0,
+ .problem_code = Utf8ByteProblem.InvalidStartByte,
+ };
+ }
+ const bytes = @ptrCast([*]const u8, arg.bytes)[start .. start + count];
- if (unicode.utf8ValidateSlice(bytes)) {
- // the output will be correct. Now we need to clone the input
-
- if (count == arg.len() and count > SMALL_STR_MAX_LENGTH) {
- const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode);
-
- const string = RocStr{
- .str_bytes = byte_list.bytes,
- .str_len = byte_list.length,
- .str_capacity = byte_list.capacity,
- };
-
- return FromUtf8Result{
- .is_ok = true,
- .string = string,
- .byte_index = 0,
- .problem_code = Utf8ByteProblem.InvalidStartByte,
- };
- } else {
- // turn the bytes into a small string
- const string = RocStr.init(@ptrCast([*]const u8, bytes), count);
-
- // decref the list
- utils.decref(arg.bytes, arg.len(), 1);
-
- return FromUtf8Result{
- .is_ok = true,
- .string = string,
- .byte_index = 0,
- .problem_code = Utf8ByteProblem.InvalidStartByte,
- };
- }
+ if (isValidUnicode(bytes)) {
+ // Make a seamless slice of the input.
+ const string = RocStr.fromSubListUnsafe(arg, start, count, update_mode);
+ return FromUtf8Result{
+ .is_ok = true,
+ .string = string,
+ .byte_index = 0,
+ .problem_code = Utf8ByteProblem.InvalidStartByte,
+ };
} else {
const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length);
// decref the list
- utils.decref(arg.bytes, arg.len(), 1);
+ arg.decref(RocStr.alignment);
return FromUtf8Result{
.is_ok = false,
@@ -1858,9 +1969,48 @@ fn errorToProblem(bytes: [*]u8, length: usize) struct { index: usize, problem: U
unreachable;
}
-pub fn isValidUnicode(ptr: [*]u8, len: usize) callconv(.C) bool {
- const bytes: []u8 = ptr[0..len];
- return @call(.{ .modifier = always_inline }, unicode.utf8ValidateSlice, .{bytes});
+pub fn isValidUnicode(buf: []const u8) bool {
+ const size = @sizeOf(u64);
+ // TODO: we should test changing the step on other platforms.
+ // The general tradeoff is making extremely large strings potentially much faster
+ // at the cost of small strings being slightly slower.
+ const step = size;
+ var i: usize = 0;
+ while (i + step < buf.len) {
+ var bytes: u64 = undefined;
+ @memcpy(@ptrCast([*]u8, &bytes), @ptrCast([*]const u8, buf) + i, size);
+ const unicode_bytes = bytes & 0x8080_8080_8080_8080;
+ if (unicode_bytes == 0) {
+ i += step;
+ continue;
+ }
+
+ while (buf[i] < 0b1000_0000) : (i += 1) {}
+
+ while (buf[i] >= 0b1000_0000) {
+ // This forces prefetching, otherwise the loop can run at about half speed.
+ if (i + 4 >= buf.len) break;
+ var small_buf: [4]u8 = undefined;
+ @memcpy(&small_buf, @ptrCast([*]const u8, buf) + i, 4);
+ // TODO: Should we always inline these function calls below?
+ if (std.unicode.utf8ByteSequenceLength(small_buf[0])) |cp_len| {
+ if (std.meta.isError(std.unicode.utf8Decode(small_buf[0..cp_len]))) {
+ return false;
+ }
+ i += cp_len;
+ } else |_| {
+ return false;
+ }
+ }
+ }
+
+ if (i == buf.len) return true;
+ while (buf[i] < 0b1000_0000) {
+ i += 1;
+ if (i == buf.len) return true;
+ }
+
+ return @call(.{ .modifier = always_inline }, unicode.utf8ValidateSlice, .{buf[i..]});
}
const Utf8DecodeError = error{
@@ -1897,11 +2047,11 @@ pub const Utf8ByteProblem = enum(u8) {
};
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
- return fromUtf8(RocList{ .bytes = bytes, .length = length, .capacity = length }, .Immutable);
+ return fromUtf8Range(RocList{ .bytes = bytes, .length = length, .capacity_or_ref_ptr = length }, 0, length, .Immutable);
}
fn validateUtf8BytesX(str: RocList) FromUtf8Result {
- return fromUtf8(str, .Immutable);
+ return fromUtf8Range(str, 0, str.len(), .Immutable);
}
fn expectOk(result: FromUtf8Result) !void {
@@ -1930,7 +2080,9 @@ test "validateUtf8Bytes: ascii" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
- try expectOk(validateUtf8BytesX(list));
+ const str_result = validateUtf8BytesX(list);
+ defer str_result.string.decref();
+ try expectOk(str_result);
}
test "validateUtf8Bytes: unicode œ" {
@@ -1938,7 +2090,9 @@ test "validateUtf8Bytes: unicode œ" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
- try expectOk(validateUtf8BytesX(list));
+ const str_result = validateUtf8BytesX(list);
+ defer str_result.string.decref();
+ try expectOk(str_result);
}
test "validateUtf8Bytes: unicode ∆" {
@@ -1946,7 +2100,9 @@ test "validateUtf8Bytes: unicode ∆" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
- try expectOk(validateUtf8BytesX(list));
+ const str_result = validateUtf8BytesX(list);
+ defer str_result.string.decref();
+ try expectOk(str_result);
}
test "validateUtf8Bytes: emoji" {
@@ -1954,7 +2110,9 @@ test "validateUtf8Bytes: emoji" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
- try expectOk(validateUtf8BytesX(list));
+ const str_result = validateUtf8BytesX(list);
+ defer str_result.string.decref();
+ try expectOk(str_result);
}
test "validateUtf8Bytes: unicode ∆ in middle of array" {
@@ -1962,7 +2120,9 @@ test "validateUtf8Bytes: unicode ∆ in middle of array" {
const ptr: [*]const u8 = @ptrCast([*]const u8, raw);
const list = sliceHelp(ptr, raw.len);
- try expectOk(validateUtf8BytesX(list));
+ const str_result = validateUtf8BytesX(list);
+ defer str_result.string.decref();
+ try expectOk(str_result);
}
fn expectErr(list: RocList, index: usize, err: Utf8DecodeError, problem: Utf8ByteProblem) !void {
@@ -2089,134 +2249,149 @@ test "isWhitespace" {
try expect(!isWhitespace('x'));
}
-pub fn strTrim(string: RocStr) callconv(.C) RocStr {
- if (string.str_bytes) |bytes_ptr| {
- const leading_bytes = countLeadingWhitespaceBytes(string);
- const original_len = string.len();
+pub fn strTrim(input_string: RocStr) callconv(.C) RocStr {
+ var string = input_string;
- if (original_len == leading_bytes) {
- string.deinit();
- return RocStr.empty();
- }
-
- const trailing_bytes = countTrailingWhitespaceBytes(string);
- const new_len = original_len - leading_bytes - trailing_bytes;
-
- const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne();
- if (small_or_shared) {
- // consume the input string; this will not free the
- // bytes because the string is small or shared
- const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len);
-
- string.decref();
-
- return result;
- } else {
- // nonempty, large, and unique: shift everything over in-place if necessary.
- // Note: must use memmove over memcpy, because the bytes definitely overlap!
- if (leading_bytes > 0) {
- // Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on:
- // https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115
- // Copyright Andrew Kelley, MIT licensed.
- const src = bytes_ptr + leading_bytes;
- var index: usize = 0;
-
- while (index != new_len) : (index += 1) {
- bytes_ptr[index] = src[index];
- }
- }
-
- var new_string = string;
- new_string.str_len = new_len;
-
- return new_string;
- }
+ if (string.isEmpty()) {
+ string.decref();
+ return RocStr.empty();
}
- return RocStr.empty();
-}
+ const bytes_ptr = string.asU8ptrMut();
-pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr {
- if (string.str_bytes) |bytes_ptr| {
- const leading_bytes = countLeadingWhitespaceBytes(string);
- const original_len = string.len();
+ const leading_bytes = countLeadingWhitespaceBytes(string);
+ const original_len = string.len();
- if (original_len == leading_bytes) {
- string.deinit();
- return RocStr.empty();
- }
-
- const new_len = original_len - leading_bytes;
-
- if (string.isSmallStr() or !string.isRefcountOne()) {
- // if the trimmed string fits in a small string,
- // make the result a small string and decref the original string
- const result = RocStr.init(string.asU8ptr() + leading_bytes, new_len);
-
- string.decref();
-
- return result;
- } else {
- // nonempty, large, and unique: shift everything over in-place if necessary.
- // Note: must use memmove over memcpy, because the bytes definitely overlap!
- if (leading_bytes > 0) {
- // Zig doesn't seem to have `memmove` in the stdlib anymore; this is based on:
- // https://github.com/ziglang/zig/blob/52ba2c3a43a88a4db30cff47f2f3eff8c3d5be19/lib/std/special/c.zig#L115
- // Copyright Andrew Kelley, MIT licensed.
- const src = bytes_ptr + leading_bytes;
- var index: usize = 0;
-
- while (index != new_len) : (index += 1) {
- bytes_ptr[index] = src[index];
- }
- }
-
- var new_string = string;
- new_string.str_len = new_len;
-
- return new_string;
- }
+ if (original_len == leading_bytes) {
+ string.decref();
+ return RocStr.empty();
}
- return RocStr.empty();
-}
-
-pub fn strTrimRight(string: RocStr) callconv(.C) RocStr {
- if (string.str_bytes) |bytes_ptr| {
- const trailing_bytes = countTrailingWhitespaceBytes(string);
- const original_len = string.len();
-
- if (original_len == trailing_bytes) {
- string.deinit();
- return RocStr.empty();
- }
-
- const new_len = original_len - trailing_bytes;
-
- if (string.isSmallStr() or !string.isRefcountOne()) {
- const result = RocStr.init(string.asU8ptr(), new_len);
-
- string.decref();
-
- return result;
- }
-
- // nonempty, large, and unique:
-
- var i: usize = 0;
- while (i < new_len) : (i += 1) {
- const dest = bytes_ptr + i;
- const source = dest;
- @memcpy(dest, source, 1);
- }
+ const trailing_bytes = countTrailingWhitespaceBytes(string);
+ const new_len = original_len - leading_bytes - trailing_bytes;
+ if (string.isSmallStr()) {
+ // Just create another small string of the correct bytes.
+ // No need to decref because it is a small string.
+ return RocStr.init(string.asU8ptr() + leading_bytes, new_len);
+ } else if (leading_bytes == 0 and string.isUnique()) {
+ // Big and unique with no leading bytes to remove.
+ // Just take ownership and shrink the length.
var new_string = string;
new_string.str_len = new_len;
return new_string;
+ } else if (string.isSeamlessSlice()) {
+ // Already a seamless slice, just update the range.
+ return RocStr{
+ .str_bytes = bytes_ptr + leading_bytes,
+ .str_len = new_len | SEAMLESS_SLICE_BIT,
+ .str_capacity = string.str_capacity,
+ };
+ } else {
+ // Not unique or removing leading bytes, just make a slice.
+ return RocStr{
+ .str_bytes = bytes_ptr + leading_bytes,
+ .str_len = new_len | SEAMLESS_SLICE_BIT,
+ .str_capacity = @ptrToInt(bytes_ptr) >> 1,
+ };
+ }
+}
+
+pub fn strTrimLeft(input_string: RocStr) callconv(.C) RocStr {
+ var string = input_string;
+
+ if (string.isEmpty()) {
+ string.decref();
+ return RocStr.empty();
}
- return RocStr.empty();
+ const bytes_ptr = string.asU8ptrMut();
+
+ const leading_bytes = countLeadingWhitespaceBytes(string);
+ const original_len = string.len();
+
+ if (original_len == leading_bytes) {
+ string.decref();
+ return RocStr.empty();
+ }
+
+ const new_len = original_len - leading_bytes;
+
+ if (string.isSmallStr()) {
+ // Just create another small string of the correct bytes.
+ // No need to decref because it is a small string.
+ return RocStr.init(string.asU8ptr() + leading_bytes, new_len);
+ } else if (leading_bytes == 0 and string.isUnique()) {
+ // Big and unique with no leading bytes to remove.
+ // Just take ownership and shrink the length.
+ var new_string = string;
+ new_string.str_len = new_len;
+
+ return new_string;
+ } else if (string.isSeamlessSlice()) {
+ // Already a seamless slice, just update the range.
+ return RocStr{
+ .str_bytes = bytes_ptr + leading_bytes,
+ .str_len = new_len | SEAMLESS_SLICE_BIT,
+ .str_capacity = string.str_capacity,
+ };
+ } else {
+ // Not unique or removing leading bytes, just make a slice.
+ return RocStr{
+ .str_bytes = bytes_ptr + leading_bytes,
+ .str_len = new_len | SEAMLESS_SLICE_BIT,
+ .str_capacity = @ptrToInt(bytes_ptr) >> 1,
+ };
+ }
+}
+
+pub fn strTrimRight(input_string: RocStr) callconv(.C) RocStr {
+ var string = input_string;
+
+ if (string.isEmpty()) {
+ string.decref();
+ return RocStr.empty();
+ }
+
+ const bytes_ptr = string.asU8ptrMut();
+
+ const trailing_bytes = countTrailingWhitespaceBytes(string);
+ const original_len = string.len();
+
+ if (original_len == trailing_bytes) {
+ string.decref();
+ return RocStr.empty();
+ }
+
+ const new_len = original_len - trailing_bytes;
+
+ if (string.isSmallStr()) {
+ // Just create another small string of the correct bytes.
+ // No need to decref because it is a small string.
+ return RocStr.init(string.asU8ptr(), new_len);
+ } else if (string.isUnique()) {
+ // Big and unique with no leading bytes to remove.
+ // Just take ownership and shrink the length.
+ var new_string = string;
+ new_string.str_len = new_len;
+
+ return new_string;
+ } else if (string.isSeamlessSlice()) {
+ // Already a seamless slice, just update the range.
+ return RocStr{
+ .str_bytes = bytes_ptr,
+ .str_len = new_len | SEAMLESS_SLICE_BIT,
+ .str_capacity = string.str_capacity,
+ };
+ } else {
+ // Not unique, just make a slice.
+ return RocStr{
+ .str_bytes = bytes_ptr,
+ .str_len = new_len | SEAMLESS_SLICE_BIT,
+ .str_capacity = @ptrToInt(bytes_ptr) >> 1,
+ };
+ }
}
fn countLeadingWhitespaceBytes(string: RocStr) usize {
@@ -2318,12 +2493,31 @@ test "strTrim: empty" {
try expect(trimmedEmpty.eq(RocStr.empty()));
}
+test "strTrim: null byte" {
+ const bytes = [_]u8{0};
+ const original = RocStr.init(&bytes, 1);
+
+ try expectEqual(@as(usize, 1), original.len());
+ try expectEqual(@as(usize, SMALL_STR_MAX_LENGTH), original.getCapacity());
+
+ const original_with_capacity = reserve(original, 40);
+ defer original_with_capacity.decref();
+
+ try expectEqual(@as(usize, 1), original_with_capacity.len());
+ try expectEqual(@as(usize, 64), original_with_capacity.getCapacity());
+
+ const trimmed = strTrim(original.clone());
+ defer trimmed.decref();
+
+ try expect(original.eq(trimmed));
+}
+
test "strTrim: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
const trimmed = strTrim(original);
+ defer trimmed.decref();
try expect(trimmed.eq(RocStr.empty()));
}
@@ -2331,22 +2525,22 @@ test "strTrim: blank" {
test "strTrim: large to large" {
const original_bytes = " hello even more giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = "hello even more giant world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(!expected.isSmallStr());
const trimmed = strTrim(original);
+ defer trimmed.decref();
try expect(trimmed.eq(expected));
}
-test "strTrim: large to small" {
+test "strTrim: large to small sized slice" {
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
@@ -2354,26 +2548,28 @@ test "strTrim: large to small" {
const expected_bytes = "hello";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(expected.isSmallStr());
+ try expect(original.isUnique());
const trimmed = strTrim(original);
+ defer trimmed.decref();
try expect(trimmed.eq(expected));
- try expect(trimmed.isSmallStr());
+ try expect(!trimmed.isSmallStr());
}
test "strTrim: small to small" {
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
+ defer original.decref();
try expect(original.isSmallStr());
const expected_bytes = "hello";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(expected.isSmallStr());
@@ -2391,7 +2587,7 @@ test "strTrimLeft: empty" {
test "strTrimLeft: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
+ defer original.decref();
const trimmed = strTrimLeft(original);
@@ -2401,13 +2597,13 @@ test "strTrimLeft: blank" {
test "strTrimLeft: large to large" {
const original_bytes = " hello even more giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
+ defer original.decref();
try expect(!original.isSmallStr());
const expected_bytes = "hello even more giant world ";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(!expected.isSmallStr());
@@ -2425,12 +2621,12 @@ test "strTrimLeft: large to small" {
const expected_bytes = "hello ";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(expected.isSmallStr());
const trimmed = strTrimLeft(original);
- defer trimmed.deinit();
+ defer trimmed.decref();
try expect(trimmed.eq(expected));
try expect(!trimmed.isSmallStr());
@@ -2439,13 +2635,13 @@ test "strTrimLeft: large to small" {
test "strTrimLeft: small to small" {
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
+ defer original.decref();
try expect(original.isSmallStr());
const expected_bytes = "hello ";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(expected.isSmallStr());
@@ -2463,7 +2659,7 @@ test "strTrimRight: empty" {
test "strTrimRight: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
+ defer original.decref();
const trimmed = strTrimRight(original);
@@ -2473,13 +2669,13 @@ test "strTrimRight: blank" {
test "strTrimRight: large to large" {
const original_bytes = " hello even more giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
+ defer original.decref();
try expect(!original.isSmallStr());
const expected_bytes = " hello even more giant world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(!expected.isSmallStr());
@@ -2497,12 +2693,12 @@ test "strTrimRight: large to small" {
const expected_bytes = " hello";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
- defer trimmed.deinit();
+ defer trimmed.decref();
try expect(trimmed.eq(expected));
try expect(!trimmed.isSmallStr());
@@ -2511,13 +2707,13 @@ test "strTrimRight: large to small" {
test "strTrimRight: small to small" {
const original_bytes = " hello ";
const original = RocStr.init(original_bytes, original_bytes.len);
- defer original.deinit();
+ defer original.decref();
try expect(original.isSmallStr());
const expected_bytes = " hello";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(expected.isSmallStr());
@@ -2551,7 +2747,7 @@ test "ReverseUtf8View: empty" {
test "capacity: small string" {
const data_bytes = "foobar";
var data = RocStr.init(data_bytes, data_bytes.len);
- defer data.deinit();
+ defer data.decref();
try expectEqual(data.getCapacity(), SMALL_STR_MAX_LENGTH);
}
@@ -2559,7 +2755,7 @@ test "capacity: small string" {
test "capacity: big string" {
const data_bytes = "a string so large that it must be heap-allocated";
var data = RocStr.init(data_bytes, data_bytes.len);
- defer data.deinit();
+ defer data.decref();
try expect(data.getCapacity() >= data_bytes.len);
}
@@ -2583,11 +2779,11 @@ test "appendScalar: small A" {
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, A[0]);
- defer actual.deinit();
+ defer actual.decref();
const expected_bytes = "helloA";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(actual.eq(expected));
}
@@ -2597,11 +2793,11 @@ test "appendScalar: small 😀" {
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, 0x1F600);
- defer actual.deinit();
+ defer actual.decref();
const expected_bytes = "hello😀";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(actual.eq(expected));
}
@@ -2613,11 +2809,11 @@ test "appendScalar: big A" {
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, A[0]);
- defer actual.deinit();
+ defer actual.decref();
const expected_bytes = "a string so large that it must be heap-allocatedA";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(actual.eq(expected));
}
@@ -2627,11 +2823,11 @@ test "appendScalar: big 😀" {
var data = RocStr.init(data_bytes, data_bytes.len);
const actual = appendScalar(data, 0x1F600);
- defer actual.deinit();
+ defer actual.decref();
const expected_bytes = "a string so large that it must be heap-allocated😀";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
- defer expected.deinit();
+ defer expected.decref();
try expect(actual.eq(expected));
}
@@ -2705,3 +2901,34 @@ pub fn strCloneTo(
return extra_offset + slice.len;
}
}
+
+pub fn strRefcountPtr(
+ string: RocStr,
+) callconv(.C) ?[*]u8 {
+ return string.getRefcountPtr();
+}
+
+pub fn strReleaseExcessCapacity(
+ string: RocStr,
+) callconv(.C) RocStr {
+ const old_length = string.len();
+ // We use the direct list.capacity_or_ref_ptr to make sure both that there is no extra capacity and that it isn't a seamless slice.
+ if (string.isSmallStr()) {
+ // SmallStr has no excess capacity.
+ return string;
+ } else if (string.isUnique() and !string.isSeamlessSlice() and string.getCapacity() == old_length) {
+ return string;
+ } else if (old_length == 0) {
+ string.decref();
+ return RocStr.empty();
+ } else {
+ var output = RocStr.allocateExact(old_length);
+ const source_ptr = string.asU8ptr();
+ const dest_ptr = output.asU8ptrMut();
+
+ @memcpy(dest_ptr, source_ptr, old_length);
+ string.decref();
+
+ return output;
+ }
+}
diff --git a/crates/compiler/builtins/bitcode/tests/bitcode_test.rs b/crates/compiler/builtins/bitcode/tests/bitcode_test.rs
deleted file mode 100644
index 515e2d309b..0000000000
--- a/crates/compiler/builtins/bitcode/tests/bitcode_test.rs
+++ /dev/null
@@ -1,64 +0,0 @@
-#[macro_use]
-extern crate pretty_assertions;
-
-#[cfg(test)]
-mod bitcode {
- use roc_builtins_bitcode::{count_segments_, str_split_};
-
- #[test]
- fn count_segments() {
- assert_eq!(
- count_segments_((&"hello there").as_bytes(), (&"hello").as_bytes()),
- 2
- );
- assert_eq!(
- count_segments_((&"a\nb\nc").as_bytes(), (&"\n").as_bytes()),
- 3
- );
- assert_eq!(
- count_segments_((&"str").as_bytes(), (&"delimiter").as_bytes()),
- 1
- );
- }
-
- #[test]
- fn str_split() {
- fn splits_to(string: &str, delimiter: &str, expectation: &[&[u8]]) {
- assert_eq!(
- str_split_(
- &mut [(&"").as_bytes()].repeat(expectation.len()),
- &string.as_bytes(),
- &delimiter.as_bytes()
- ),
- expectation
- );
- }
-
- splits_to(
- "a!b!c",
- "!",
- &[(&"a").as_bytes(), (&"b").as_bytes(), (&"c").as_bytes()],
- );
-
- splits_to(
- "a!?b!?c!?",
- "!?",
- &[
- (&"a").as_bytes(),
- (&"b").as_bytes(),
- (&"c").as_bytes(),
- (&"").as_bytes(),
- ],
- );
-
- splits_to("abc", "!", &[(&"abc").as_bytes()]);
-
- splits_to(
- "tttttghittttt",
- "ttttt",
- &[(&"").as_bytes(), (&"ghi").as_bytes(), (&"").as_bytes()],
- );
-
- splits_to("def", "!!!!!!", &[(&"def").as_bytes()]);
- }
-}
diff --git a/crates/compiler/builtins/roc/Bool.roc b/crates/compiler/builtins/roc/Bool.roc
index bb95805069..d16e8b2494 100644
--- a/crates/compiler/builtins/roc/Bool.roc
+++ b/crates/compiler/builtins/roc/Bool.roc
@@ -32,6 +32,8 @@ Eq has
## cannot derive `isEq` for types that contain functions.
isEq : a, a -> Bool | a has Eq
+## Represents the boolean true and false using an opaque type.
+## `Bool` implements the `Eq` ability.
Bool := [True, False] has [Eq { isEq: boolIsEq }]
boolIsEq = \@Bool b1, @Bool b2 -> structuralEq b1 b2
@@ -49,23 +51,27 @@ false = @Bool False
## gate. The infix operator `&&` can also be used as shorthand for
## `Bool.and`.
##
-## expect (Bool.and Bool.true Bool.true) == Bool.true
-## expect (Bool.true && Bool.true) == Bool.true
-## expect (Bool.false && Bool.true) == Bool.false
-## expect (Bool.true && Bool.false) == Bool.false
-## expect (Bool.false && Bool.false) == Bool.false
+## ```
+## expect (Bool.and Bool.true Bool.true) == Bool.true
+## expect (Bool.true && Bool.true) == Bool.true
+## expect (Bool.false && Bool.true) == Bool.false
+## expect (Bool.true && Bool.false) == Bool.false
+## expect (Bool.false && Bool.false) == Bool.false
+## ```
##
-## **Performance Note** that in Roc the `&&` and `||` work the same way as any
+## ## Performance Details
+##
+## In Roc the `&&` and `||` work the same way as any
## other function. However, in some languages `&&` and `||` are special-cased.
## In these languages the compiler will skip evaluating the expression after the
## first operator under certain circumstances. For example an expression like
## `enablePets && likesDogs user` would compile to.
-##
-## if enablePets then
-## likesDogs user
-## else
-## Bool.false
-##
+## ```
+## if enablePets then
+## likesDogs user
+## else
+## Bool.false
+## ```
## Roc does not do this because conditionals like `if` and `when` have a
## performance cost. Calling a function can sometimes be faster across the board
## than doing an `if` to decide whether to skip calling it.
@@ -74,14 +80,17 @@ and : Bool, Bool -> Bool
## Returns `Bool.true` when either input is a `Bool.true`. This is equivalent to
## the logic [OR](https://en.wikipedia.org/wiki/Logical_disjunction) gate.
## The infix operator `||` can also be used as shorthand for `Bool.or`.
+## ```
+## expect (Bool.or Bool.false Bool.true) == Bool.true
+## expect (Bool.true || Bool.true) == Bool.true
+## expect (Bool.false || Bool.true) == Bool.true
+## expect (Bool.true || Bool.false) == Bool.true
+## expect (Bool.false || Bool.false) == Bool.false
+## ```
##
-## expect (Bool.or Bool.false Bool.true) == Bool.true
-## expect (Bool.true || Bool.true) == Bool.true
-## expect (Bool.false || Bool.true) == Bool.true
-## expect (Bool.true || Bool.false) == Bool.true
-## expect (Bool.false || Bool.false) == Bool.false
+## ## Performance Details
##
-## **Performance Note** that in Roc the `&&` and `||` work the same way as any
+## In Roc the `&&` and `||` work the same way as any
## other functions. However, in some languages `&&` and `||` are special-cased.
## Refer to the note in `Bool.and` for more detail.
or : Bool, Bool -> Bool
@@ -89,9 +98,10 @@ or : Bool, Bool -> Bool
## Returns `Bool.false` when given `Bool.true`, and vice versa. This is
## equivalent to the logic [NOT](https://en.wikipedia.org/wiki/Negation)
## gate. The operator `!` can also be used as shorthand for `Bool.not`.
-##
-## expect (Bool.not Bool.false) == Bool.true
-## expect (!Bool.false) == Bool.true
+## ```
+## expect (Bool.not Bool.false) == Bool.true
+## expect (!Bool.false) == Bool.true
+## ```
not : Bool -> Bool
## This will call the function `Bool.isEq` on the inputs, and then `Bool.not`
@@ -101,10 +111,11 @@ not : Bool -> Bool
##
## **Note** that `isNotEq` does not accept arguments whose types contain
## functions.
-##
-## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
-## expect (Bool.false != Bool.false) == Bool.false
-## expect "Apples" != "Oranges"
+## ```
+## expect (Bool.isNotEq Bool.false Bool.true) == Bool.true
+## expect (Bool.false != Bool.false) == Bool.false
+## expect "Apples" != "Oranges"
+## ```
isNotEq : a, a -> Bool | a has Eq
isNotEq = \a, b -> structuralNotEq a b
diff --git a/crates/compiler/builtins/roc/Box.roc b/crates/compiler/builtins/roc/Box.roc
index f4e0158e85..5bebae160a 100644
--- a/crates/compiler/builtins/roc/Box.roc
+++ b/crates/compiler/builtins/roc/Box.roc
@@ -6,13 +6,15 @@ interface Box
## the value from the stack to the heap. This may provide a performance
## optimization for advanced use cases with large values. A platform may require
## that some values are boxed.
-##
-## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
+## ```
+## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
+## ```
box : a -> Box a
## Returns a boxed value.
-##
-## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
+## ```
+## expect Box.unbox (Box.box "Stack Faster") == "Stack Faster"
+## ```
unbox : Box a -> a
# # we'd need reset/reuse for box for this to be efficient
diff --git a/crates/compiler/builtins/roc/Decode.roc b/crates/compiler/builtins/roc/Decode.roc
index 95407be0d0..a6bfef53b7 100644
--- a/crates/compiler/builtins/roc/Decode.roc
+++ b/crates/compiler/builtins/roc/Decode.roc
@@ -23,6 +23,7 @@ interface Decode
string,
list,
record,
+ tuple,
custom,
decodeWith,
fromBytesPartial,
@@ -43,6 +44,7 @@ interface Decode
I32,
I64,
I128,
+ Nat,
F32,
F64,
Dec,
@@ -76,8 +78,24 @@ DecoderFormatting has
bool : Decoder Bool fmt | fmt has DecoderFormatting
string : Decoder Str fmt | fmt has DecoderFormatting
list : Decoder elem fmt -> Decoder (List elem) fmt | fmt has DecoderFormatting
+
+ ## `record state stepField finalizer` decodes a record field-by-field.
+ ##
+ ## `stepField` returns a decoder for the given field in the record, or
+ ## `Skip` if the field is not a part of the decoded record.
+ ##
+ ## `finalizer` should produce the record value from the decoded `state`.
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting
+ ## `tuple state stepElem finalizer` decodes a tuple element-by-element.
+ ##
+ ## `stepElem` returns a decoder for the nth index in the tuple, or
+ ## `TooLong` if the index is larger than the expected size of the tuple. The
+ ## index passed to `stepElem` is 0-indexed.
+ ##
+ ## `finalizer` should produce the tuple value from the decoded `state`.
+ tuple : state, (state, Nat -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting
+
custom : (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
custom = \decode -> @Decoder decode
diff --git a/crates/compiler/builtins/roc/Dict.roc b/crates/compiler/builtins/roc/Dict.roc
index dddf993397..0db8f601ca 100644
--- a/crates/compiler/builtins/roc/Dict.roc
+++ b/crates/compiler/builtins/roc/Dict.roc
@@ -34,7 +34,7 @@ interface Dict
## A [dictionary](https://en.wikipedia.org/wiki/Associative_array) that lets you
## associate keys with values.
##
-## ### Inserting
+## ## Inserting
##
## The most basic way to use a dictionary is to start with an empty one and
## then:
@@ -45,16 +45,16 @@ interface Dict
##
## Here's an example of a dictionary which uses a city's name as the key, and
## its population as the associated value.
-##
-## populationByCity =
-## Dict.empty {}
-## |> Dict.insert "London" 8_961_989
-## |> Dict.insert "Philadelphia" 1_603_797
-## |> Dict.insert "Shanghai" 24_870_895
-## |> Dict.insert "Delhi" 16_787_941
-## |> Dict.insert "Amsterdam" 872_680
-##
-## ### Accessing keys or values
+## ```
+## populationByCity =
+## Dict.empty {}
+## |> Dict.insert "London" 8_961_989
+## |> Dict.insert "Philadelphia" 1_603_797
+## |> Dict.insert "Shanghai" 24_870_895
+## |> Dict.insert "Delhi" 16_787_941
+## |> Dict.insert "Amsterdam" 872_680
+## ```
+## ## Accessing keys or values
##
## We can use [Dict.keys] and [Dict.values] functions to get only the keys or
## only the values.
@@ -63,16 +63,16 @@ interface Dict
## order. This will be true if all you ever do is [Dict.insert] and [Dict.get] operations
## on the dictionary, but [Dict.remove] operations can change this order.
##
-## ### Removing
+## ## Removing
##
## We can remove an element from the dictionary, like so:
-##
-## populationByCity
-## |> Dict.remove "Philadelphia"
-## |> Dict.keys
-## ==
-## ["London", "Amsterdam", "Shanghai", "Delhi"]
-##
+## ```
+## populationByCity
+## |> Dict.remove "Philadelphia"
+## |> Dict.keys
+## ==
+## ["London", "Amsterdam", "Shanghai", "Delhi"]
+## ```
## Notice that the order has changed. Philadelphia was not only removed from the
## list, but Amsterdam - the last entry we inserted - has been moved into the
## spot where Philadelphia was previously. This is exactly what [Dict.remove]
@@ -100,6 +100,9 @@ Dict k v := {
} | k has Hash & Eq
## Return an empty dictionary.
+## ```
+## emptyDict = Dict.empty {}
+## ```
empty : {} -> Dict k v | k has Hash & Eq
empty = \{} ->
@Dict {
@@ -110,6 +113,13 @@ empty = \{} ->
}
## Returns the max number of elements the dictionary can hold before requiring a rehash.
+## ```
+## foodDict =
+## Dict.empty {}
+## |> Dict.insert "apple" "fruit"
+##
+## capacityOfDict = Dict.capacity foodDict
+## ```
capacity : Dict k v -> Nat | k has Hash & Eq
capacity = \@Dict { dataIndices } ->
cap = List.len dataIndices
@@ -125,41 +135,55 @@ withCapacity = \_ ->
empty {}
## Returns a dictionary containing the key and value provided as input.
-##
-## expect
-## Dict.single "A" "B"
-## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
+## ```
+## expect
+## Dict.single "A" "B"
+## |> Bool.isEq (Dict.insert (Dict.empty {}) "A" "B")
+## ```
single : k, v -> Dict k v | k has Hash & Eq
single = \k, v ->
insert (empty {}) k v
## Returns dictionary with the keys and values specified by the input [List].
-##
-## expect
-## Dict.single 1 "One"
-## |> Dict.insert 2 "Two"
-## |> Dict.insert 3 "Three"
-## |> Dict.insert 4 "Four"
-## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"])
+## ```
+## expect
+## Dict.single 1 "One"
+## |> Dict.insert 2 "Two"
+## |> Dict.insert 3 "Three"
+## |> Dict.insert 4 "Four"
+## |> Bool.isEq (Dict.fromList [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"])
+## ```
fromList : List (T k v) -> Dict k v | k has Hash & Eq
fromList = \data ->
# TODO: make this efficient. Should just set data and then set all indicies in the hashmap.
List.walk data (empty {}) (\dict, T k v -> insert dict k v)
## Returns the number of values in the dictionary.
-##
-## expect
-## Dict.empty {}
-## |> Dict.insert "One" "A Song"
-## |> Dict.insert "Two" "Candy Canes"
-## |> Dict.insert "Three" "Boughs of Holly"
-## |> Dict.len
-## |> Bool.isEq 3
+## ```
+## expect
+## Dict.empty {}
+## |> Dict.insert "One" "A Song"
+## |> Dict.insert "Two" "Candy Canes"
+## |> Dict.insert "Three" "Boughs of Holly"
+## |> Dict.len
+## |> Bool.isEq 3
+## ```
len : Dict k v -> Nat | k has Hash & Eq
len = \@Dict { size } ->
size
## Clears all elements from a dictionary keeping around the allocation if it isn't huge.
+## ```
+## songs =
+## Dict.empty {}
+## |> Dict.insert "One" "A Song"
+## |> Dict.insert "Two" "Candy Canes"
+## |> Dict.insert "Three" "Boughs of Holly"
+##
+## clearSongs = Dict.clear songs
+##
+## expect Dict.len clearSongs == 0
+## ```
clear : Dict k v -> Dict k v | k has Hash & Eq
clear = \@Dict { metadata, dataIndices, data } ->
cap = List.len dataIndices
@@ -180,13 +204,14 @@ clear = \@Dict { metadata, dataIndices, data } ->
## Iterate through the keys and values in the dictionary and call the provided
## function with signature `state, k, v -> state` for each value, with an
## initial `state` value provided for the first call.
-##
-## expect
-## Dict.empty {}
-## |> Dict.insert "Apples" 12
-## |> Dict.insert "Orange" 24
-## |> Dict.walk 0 (\count, _, qty -> count + qty)
-## |> Bool.isEq 36
+## ```
+## expect
+## Dict.empty {}
+## |> Dict.insert "Apples" 12
+## |> Dict.insert "Orange" 24
+## |> Dict.walk 0 (\count, _, qty -> count + qty)
+## |> Bool.isEq 36
+## ```
walk : Dict k v, state, (state, k, v -> state) -> state | k has Hash & Eq
walk = \@Dict { data }, initialState, transform ->
List.walk data initialState (\state, T k v -> transform state k v)
@@ -202,20 +227,38 @@ walk = \@Dict { data }, initialState, transform ->
##
## As such, it is typically better for performance to use this over [Dict.walk]
## if returning `Break` earlier than the last element is expected to be common.
+## ```
+## people =
+## Dict.empty {}
+## |> Dict.insert "Alice" 17
+## |> Dict.insert "Bob" 18
+## |> Dict.insert "Charlie" 19
+##
+## isAdult = \_, _, age ->
+## if age >= 18 then
+## Break Bool.true
+## else
+## Continue Bool.false
+##
+## someoneIsAnAdult = Dict.walkUntil people Bool.false isAdult
+##
+## expect someoneIsAnAdult == Bool.true
+## ```
walkUntil : Dict k v, state, (state, k, v -> [Continue state, Break state]) -> state | k has Hash & Eq
walkUntil = \@Dict { data }, initialState, transform ->
List.walkUntil data initialState (\state, T k v -> transform state k v)
## Get the value for a given key. If there is a value for the specified key it
## will return [Ok value], otherwise return [Err KeyNotFound].
+## ```
+## dictionary =
+## Dict.empty {}
+## |> Dict.insert 1 "Apple"
+## |> Dict.insert 2 "Orange"
##
-## dictionary =
-## Dict.empty {}
-## |> Dict.insert 1 "Apple"
-## |> Dict.insert 2 "Orange"
-##
-## expect Dict.get dictionary 1 == Ok "Apple"
-## expect Dict.get dictionary 2000 == Err KeyNotFound
+## expect Dict.get dictionary 1 == Ok "Apple"
+## expect Dict.get dictionary 2000 == Err KeyNotFound
+## ```
get : Dict k v, k -> Result v [KeyNotFound] | k has Hash & Eq
get = \@Dict { metadata, dataIndices, data }, key ->
hashKey =
@@ -237,12 +280,13 @@ get = \@Dict { metadata, dataIndices, data }, key ->
Err KeyNotFound
## Check if the dictionary has a value for a specified key.
-##
-## expect
-## Dict.empty {}
-## |> Dict.insert 1234 "5678"
-## |> Dict.contains 1234
-## |> Bool.isEq Bool.true
+## ```
+## expect
+## Dict.empty {}
+## |> Dict.insert 1234 "5678"
+## |> Dict.contains 1234
+## |> Bool.isEq Bool.true
+## ```
contains : Dict k v, k -> Bool | k has Hash & Eq
contains = \@Dict { metadata, dataIndices, data }, key ->
hashKey =
@@ -261,12 +305,13 @@ contains = \@Dict { metadata, dataIndices, data }, key ->
Bool.false
## Insert a value into the dictionary at a specified key.
-##
-## expect
-## Dict.empty {}
-## |> Dict.insert "Apples" 12
-## |> Dict.get "Apples"
-## |> Bool.isEq (Ok 12)
+## ```
+## expect
+## Dict.empty {}
+## |> Dict.insert "Apples" 12
+## |> Dict.get "Apples"
+## |> Bool.isEq (Ok 12)
+## ```
insert : Dict k v, k, v -> Dict k v | k has Hash & Eq
insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
hashKey =
@@ -305,13 +350,14 @@ insert = \@Dict { metadata, dataIndices, data, size }, key, value ->
insertNotFoundHelper rehashedDict key value h1Key h2Key
## Remove a value from the dictionary for a specified key.
-##
-## expect
-## Dict.empty {}
-## |> Dict.insert "Some" "Value"
-## |> Dict.remove "Some"
-## |> Dict.len
-## |> Bool.isEq 0
+## ```
+## expect
+## Dict.empty {}
+## |> Dict.insert "Some" "Value"
+## |> Dict.remove "Some"
+## |> Dict.len
+## |> Bool.isEq 0
+## ```
remove : Dict k v, k -> Dict k v | k has Hash & Eq
remove = \@Dict { metadata, dataIndices, data, size }, key ->
# TODO: change this from swap remove to tombstone and test is performance is still good.
@@ -345,16 +391,17 @@ remove = \@Dict { metadata, dataIndices, data, size }, key ->
## performance optimisation for the use case of providing a default when a value
## is missing. This is more efficient than doing both a `Dict.get` and then a
## `Dict.insert` call, and supports being piped.
+## ```
+## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
+## alterValue = \possibleValue ->
+## when possibleValue is
+## Missing -> Present Bool.false
+## Present value -> if value then Missing else Present Bool.true
##
-## alterValue : [Present Bool, Missing] -> [Present Bool, Missing]
-## alterValue = \possibleValue ->
-## when possibleValue is
-## Missing -> Present Bool.false
-## Present value -> if value then Missing else Present Bool.true
-##
-## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
-## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
-## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
+## expect Dict.update (Dict.empty {}) "a" alterValue == Dict.single "a" Bool.false
+## expect Dict.update (Dict.single "a" Bool.false) "a" alterValue == Dict.single "a" Bool.true
+## expect Dict.update (Dict.single "a" Bool.true) "a" alterValue == Dict.empty {}
+## ```
update : Dict k v, k, ([Present v, Missing] -> [Present v, Missing]) -> Dict k v | k has Hash & Eq
update = \dict, key, alter ->
# TODO: look into optimizing by merging substeps and reducing lookups.
@@ -369,42 +416,45 @@ update = \dict, key, alter ->
## Returns the keys and values of a dictionary as a [List].
## This requires allocating a temporary list, prefer using [Dict.toList] or [Dict.walk] instead.
-##
-## expect
-## Dict.single 1 "One"
-## |> Dict.insert 2 "Two"
-## |> Dict.insert 3 "Three"
-## |> Dict.insert 4 "Four"
-## |> Dict.toList
-## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]
+## ```
+## expect
+## Dict.single 1 "One"
+## |> Dict.insert 2 "Two"
+## |> Dict.insert 3 "Three"
+## |> Dict.insert 4 "Four"
+## |> Dict.toList
+## |> Bool.isEq [T 1 "One", T 2 "Two", T 3 "Three", T 4 "Four"]
+## ```
toList : Dict k v -> List (T k v) | k has Hash & Eq
toList = \@Dict { data } ->
data
## Returns the keys of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
-##
-## expect
-## Dict.single 1 "One"
-## |> Dict.insert 2 "Two"
-## |> Dict.insert 3 "Three"
-## |> Dict.insert 4 "Four"
-## |> Dict.keys
-## |> Bool.isEq [1,2,3,4]
+## ```
+## expect
+## Dict.single 1 "One"
+## |> Dict.insert 2 "Two"
+## |> Dict.insert 3 "Three"
+## |> Dict.insert 4 "Four"
+## |> Dict.keys
+## |> Bool.isEq [1,2,3,4]
+## ```
keys : Dict k v -> List k | k has Hash & Eq
keys = \@Dict { data } ->
List.map data (\T k _ -> k)
## Returns the values of a dictionary as a [List].
## This requires allocating a temporary [List], prefer using [Dict.toList] or [Dict.walk] instead.
-##
-## expect
-## Dict.single 1 "One"
-## |> Dict.insert 2 "Two"
-## |> Dict.insert 3 "Three"
-## |> Dict.insert 4 "Four"
-## |> Dict.values
-## |> Bool.isEq ["One","Two","Three","Four"]
+## ```
+## expect
+## Dict.single 1 "One"
+## |> Dict.insert 2 "Two"
+## |> Dict.insert 3 "Three"
+## |> Dict.insert 4 "Four"
+## |> Dict.values
+## |> Bool.isEq ["One","Two","Three","Four"]
+## ```
values : Dict k v -> List v | k has Hash & Eq
values = \@Dict { data } ->
List.map data (\T _ v -> v)
@@ -414,24 +464,25 @@ values = \@Dict { data } ->
## both dictionaries will be combined. Note that where there are pairs
## with the same key, the value contained in the second input will be
## retained, and the value in the first input will be removed.
+## ```
+## first =
+## Dict.single 1 "Not Me"
+## |> Dict.insert 2 "And Me"
##
-## first =
-## Dict.single 1 "Not Me"
-## |> Dict.insert 2 "And Me"
+## second =
+## Dict.single 1 "Keep Me"
+## |> Dict.insert 3 "Me Too"
+## |> Dict.insert 4 "And Also Me"
##
-## second =
-## Dict.single 1 "Keep Me"
-## |> Dict.insert 3 "Me Too"
-## |> Dict.insert 4 "And Also Me"
+## expected =
+## Dict.single 1 "Keep Me"
+## |> Dict.insert 2 "And Me"
+## |> Dict.insert 3 "Me Too"
+## |> Dict.insert 4 "And Also Me"
##
-## expected =
-## Dict.single 1 "Keep Me"
-## |> Dict.insert 2 "And Me"
-## |> Dict.insert 3 "Me Too"
-## |> Dict.insert 4 "And Also Me"
-##
-## expect
-## Dict.insertAll first second == expected
+## expect
+## Dict.insertAll first second == expected
+## ```
insertAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
insertAll = \xs, ys ->
walk ys xs insert
@@ -441,18 +492,19 @@ insertAll = \xs, ys ->
## that are in both dictionaries. Note that where there are pairs with
## the same key, the value contained in the first input will be retained,
## and the value in the second input will be removed.
+## ```
+## first =
+## Dict.single 1 "Keep Me"
+## |> Dict.insert 2 "And Me"
##
-## first =
-## Dict.single 1 "Keep Me"
-## |> Dict.insert 2 "And Me"
+## second =
+## Dict.single 1 "Keep Me"
+## |> Dict.insert 2 "And Me"
+## |> Dict.insert 3 "But Not Me"
+## |> Dict.insert 4 "Or Me"
##
-## second =
-## Dict.single 1 "Keep Me"
-## |> Dict.insert 2 "And Me"
-## |> Dict.insert 3 "But Not Me"
-## |> Dict.insert 4 "Or Me"
-##
-## expect Dict.keepShared first second == first
+## expect Dict.keepShared first second == first
+## ```
keepShared : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
keepShared = \xs, ys ->
walk
@@ -469,21 +521,22 @@ keepShared = \xs, ys ->
## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
## of the values. This means that we will be left with only those pairs that
## are in the first dictionary and whose keys are not in the second.
+## ```
+## first =
+## Dict.single 1 "Keep Me"
+## |> Dict.insert 2 "And Me"
+## |> Dict.insert 3 "Remove Me"
##
-## first =
-## Dict.single 1 "Keep Me"
-## |> Dict.insert 2 "And Me"
-## |> Dict.insert 3 "Remove Me"
+## second =
+## Dict.single 3 "Remove Me"
+## |> Dict.insert 4 "I do nothing..."
##
-## second =
-## Dict.single 3 "Remove Me"
-## |> Dict.insert 4 "I do nothing..."
+## expected =
+## Dict.single 1 "Keep Me"
+## |> Dict.insert 2 "And Me"
##
-## expected =
-## Dict.single 1 "Keep Me"
-## |> Dict.insert 2 "And Me"
-##
-## expect Dict.removeAll first second == expected
+## expect Dict.removeAll first second == expected
+## ```
removeAll : Dict k v, Dict k v -> Dict k v | k has Hash & Eq
removeAll = \xs, ys ->
walk ys xs (\state, k, _ -> remove state k)
@@ -851,7 +904,7 @@ LowLevelHasher := { originalSeed : U64, state : U64 } has [
# TODO hide behind an InternalList.roc module
listGetUnsafe : List a, Nat -> a
-createLowLevelHasher : { seed ?U64 } -> LowLevelHasher
+createLowLevelHasher : { seed ? U64 } -> LowLevelHasher
createLowLevelHasher = \{ seed ? 0x526F_6352_616E_643F } ->
@LowLevelHasher { originalSeed: seed, state: seed }
@@ -1220,3 +1273,25 @@ expect
|> complete
hash1 != hash2
+
+expect
+ empty {}
+ |> len
+ |> Bool.isEq 0
+
+expect
+ empty {}
+ |> insert "One" "A Song"
+ |> insert "Two" "Candy Canes"
+ |> insert "Three" "Boughs of Holly"
+ |> clear
+ |> len
+ |> Bool.isEq 0
+
+expect
+ Dict.empty {}
+ |> Dict.insert "Alice" 17
+ |> Dict.insert "Bob" 18
+ |> Dict.insert "Charlie" 19
+ |> Dict.walkUntil Bool.false (\_, _, age -> if age >= 18 then Break Bool.true else Continue Bool.false)
+ |> Bool.isEq Bool.true
diff --git a/crates/compiler/builtins/roc/Encode.roc b/crates/compiler/builtins/roc/Encode.roc
index ec0d10ef0a..2ac900e3e7 100644
--- a/crates/compiler/builtins/roc/Encode.roc
+++ b/crates/compiler/builtins/roc/Encode.roc
@@ -22,6 +22,7 @@ interface Encode
list,
record,
tag,
+ tuple,
custom,
appendWith,
append,
@@ -69,6 +70,7 @@ EncoderFormatting has
string : Str -> Encoder fmt | fmt has EncoderFormatting
list : List elem, (elem -> Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
record : List { key : Str, value : Encoder fmt } -> Encoder fmt | fmt has EncoderFormatting
+ tuple : List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
tag : Str, List (Encoder fmt) -> Encoder fmt | fmt has EncoderFormatting
custom : (List U8, fmt -> List U8) -> Encoder fmt | fmt has EncoderFormatting
diff --git a/crates/compiler/builtins/roc/Hash.roc b/crates/compiler/builtins/roc/Hash.roc
index 4e5845becb..caed5814c8 100644
--- a/crates/compiler/builtins/roc/Hash.roc
+++ b/crates/compiler/builtins/roc/Hash.roc
@@ -9,6 +9,7 @@ interface Hash
addU32,
addU64,
addU128,
+ hashBool,
hashI8,
hashI16,
hashI32,
@@ -20,7 +21,7 @@ interface Hash
hashList,
hashUnordered,
] imports [
- Bool.{ isEq },
+ Bool.{ Bool, isEq },
List,
Str,
Num.{ U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, Nat },
@@ -70,6 +71,12 @@ hashList = \hasher, lst ->
List.walk lst hasher \accumHasher, elem ->
hash accumHasher elem
+## Adds a single [Bool] to a hasher.
+hashBool : a, Bool -> a | a has Hasher
+hashBool = \hasher, b ->
+ asU8 = if b then 1 else 0
+ addU8 hasher asU8
+
## Adds a single I8 to a hasher.
hashI8 : a, I8 -> a | a has Hasher
hashI8 = \hasher, n -> addU8 hasher (Num.toU8 n)
diff --git a/crates/compiler/builtins/roc/Json.roc b/crates/compiler/builtins/roc/Json.roc
index aa01c37c1c..09f7df875c 100644
--- a/crates/compiler/builtins/roc/Json.roc
+++ b/crates/compiler/builtins/roc/Json.roc
@@ -1,3 +1,40 @@
+## JSON is a data format that is easy for humans to read and write. It is
+## commonly used to exhange data between two systems such as a server and a
+## client (e.g. web browser).
+##
+## This module implements functionality to serialise and de-serialise Roc types
+## to and from JSON data. Using the `Encode` and `Decode` builtins this process
+## can be achieved without the need to write custom encoder and decoder functions
+## to parse UTF-8 strings.
+##
+## Here is a basic example which shows how to parse a JSON record into a Roc
+## type named `Language` which includes a `name` field. The JSON string is
+## decoded and then the field is encoded back into a UTF-8 string.
+##
+## ```
+## Language : {
+## name : Str,
+## }
+##
+## jsonStr = Str.toUtf8 "{\"name\":\"Röc Lang\"}"
+##
+## result : Result Language _
+## result =
+## jsonStr
+## |> Decode.fromBytes fromUtf8 # returns `Ok {name : "Röc Lang"}`
+##
+## name =
+## decodedValue <- Result.map result
+##
+## Encode.toBytes decodedValue.name toUtf8
+##
+## expect name == Ok (Str.toUtf8 "\"Röc Lang\"")
+## ```
+##
+## **Note:** This module is likely to be moved out of the builtins in future.
+## It is currently located here to facilitate development of the Abilities
+## language feature and testing. You are welcome to use this module, just note
+## that it will be moved into a package in a future update.
interface Json
exposes [
Json,
@@ -7,6 +44,7 @@ interface Json
imports [
List,
Str,
+ Result.{ Result },
Encode,
Encode.{
Encoder,
@@ -37,6 +75,8 @@ interface Json
Result,
]
+## An opaque type with the `EncoderFormatting` and
+## `DecoderFormatting` abilities.
Json := {} has [
EncoderFormatting {
u8: encodeU8,
@@ -56,6 +96,7 @@ Json := {} has [
string: encodeString,
list: encodeList,
record: encodeRecord,
+ tuple: encodeTuple,
tag: encodeTag,
},
DecoderFormatting {
@@ -76,11 +117,14 @@ Json := {} has [
string: decodeString,
list: decodeList,
record: decodeRecord,
+ tuple: decodeTuple,
},
]
+## Returns a JSON `Decoder`
toUtf8 = @Json {}
+## Returns a JSON `Encoder`
fromUtf8 = @Json {}
numToBytes = \n ->
@@ -165,6 +209,25 @@ encodeRecord = \fields ->
List.append bytesWithRecord (Num.toU8 '}')
+encodeTuple = \elems ->
+ Encode.custom \bytes, @Json {} ->
+ writeTuple = \{ buffer, elemsLeft }, elemEncoder ->
+ bufferWithElem =
+ appendWith buffer elemEncoder (@Json {})
+
+ bufferWithSuffix =
+ if elemsLeft > 1 then
+ List.append bufferWithElem (Num.toU8 ',')
+ else
+ bufferWithElem
+
+ { buffer: bufferWithSuffix, elemsLeft: elemsLeft - 1 }
+
+ bytesHead = List.append bytes (Num.toU8 '[')
+ { buffer: bytesWithRecord } = List.walk elems { buffer: bytesHead, elemsLeft: List.len elems } writeTuple
+
+ List.append bytesWithRecord (Num.toU8 ']')
+
encodeTag = \name, payload ->
Encode.custom \bytes, @Json {} ->
# Idea: encode `A v1 v2` as `{"A": [v1, v2]}`
@@ -191,16 +254,42 @@ encodeTag = \name, payload ->
List.append bytesWithPayload (Num.toU8 ']')
|> List.append (Num.toU8 '}')
+isEscapeSequence : U8, U8 -> Bool
+isEscapeSequence = \a, b ->
+ when P a b is
+ P '\\' 'b' -> Bool.true # Backspace
+ P '\\' 'f' -> Bool.true # Form feed
+ P '\\' 'n' -> Bool.true # Newline
+ P '\\' 'r' -> Bool.true # Carriage return
+ P '\\' 't' -> Bool.true # Tab
+ P '\\' '"' -> Bool.true # Double quote
+ P '\\' '\\' -> Bool.true # Backslash
+ _ -> Bool.false
+
takeWhile = \list, predicate ->
helper = \{ taken, rest } ->
- when List.first rest is
- Ok elem ->
- if predicate elem then
- helper { taken: List.append taken elem, rest: List.split rest 1 |> .others }
+ when rest is
+ [a, b, ..] ->
+ if isEscapeSequence a b then
+ helper {
+ taken: taken |> List.append a |> List.append b,
+ rest: List.drop rest 2,
+ }
+ else if predicate a then
+ helper {
+ taken: List.append taken a,
+ rest: List.dropFirst rest,
+ }
else
{ taken, rest }
- Err _ -> { taken, rest }
+ [a, ..] if predicate a ->
+ helper {
+ taken: List.append taken a,
+ rest: List.dropFirst rest,
+ }
+
+ _ -> { taken, rest }
helper { taken: [], rest: list }
@@ -341,7 +430,6 @@ jsonString = \bytes ->
if
before == ['"']
then
- # TODO: handle escape sequences
{ taken: strSequence, rest } = takeWhile afterStartingQuote \n -> n != '"'
when Str.fromUtf8 strSequence is
@@ -358,42 +446,38 @@ decodeString = Decode.custom \bytes, @Json {} ->
jsonString bytes
decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
+
decodeElems = \chunk, accum ->
when Decode.decodeWith chunk decodeElem (@Json {}) is
{ result, rest } ->
when result is
- Ok val ->
- # TODO: handle spaces before ','
- { before: afterElem, others } = List.split rest 1
-
- if
- afterElem == [',']
- then
- decodeElems others (List.append accum val)
- else
- Done (List.append accum val) rest
-
Err e -> Errored e rest
+ Ok val ->
+ restWithoutWhitespace = eatWhitespace rest
+ when restWithoutWhitespace is
+ [',', ..] -> decodeElems (eatWhitespace (List.dropFirst restWithoutWhitespace)) (List.append accum val)
+ _ -> Done (List.append accum val) restWithoutWhitespace
- { before, others: afterStartingBrace } = List.split bytes 1
+ when bytes is
+ ['[', ']'] -> { result: Ok [], rest: List.drop bytes 2 }
+ ['[', ..] ->
+ bytesWithoutWhitespace = eatWhitespace (List.dropFirst bytes)
+ when bytesWithoutWhitespace is
+ [']', ..] ->
+ { result: Ok [], rest: List.dropFirst bytesWithoutWhitespace }
- if
- before == ['[']
- then
- # TODO: empty lists
- when decodeElems afterStartingBrace [] is
- Errored e rest -> { result: Err e, rest }
- Done vals rest ->
- { before: maybeEndingBrace, others: afterEndingBrace } = List.split rest 1
+ _ ->
+ when decodeElems bytesWithoutWhitespace [] is
+ Errored e rest ->
+ { result: Err e, rest }
- if
- maybeEndingBrace == [']']
- then
- { result: Ok vals, rest: afterEndingBrace }
- else
- { result: Err TooShort, rest }
- else
- { result: Err TooShort, rest: bytes }
+ Done vals rest ->
+ when rest is
+ [']', ..] -> { result: Ok vals, rest: List.dropFirst rest }
+ _ -> { result: Err TooShort, rest }
+
+ _ ->
+ { result: Err TooShort, rest: bytes }
parseExactChar : List U8, U8 -> DecodeResult {}
parseExactChar = \bytes, char ->
@@ -414,6 +498,12 @@ openBrace = \bytes -> parseExactChar bytes '{'
closingBrace : List U8 -> DecodeResult {}
closingBrace = \bytes -> parseExactChar bytes '}'
+openBracket : List U8 -> DecodeResult {}
+openBracket = \bytes -> parseExactChar bytes '['
+
+closingBracket : List U8 -> DecodeResult {}
+closingBracket = \bytes -> parseExactChar bytes ']'
+
recordKey : List U8 -> DecodeResult Str
recordKey = \bytes -> jsonString bytes
@@ -463,3 +553,92 @@ decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Jso
when finalizer endStateResult is
Ok val -> { result: Ok val, rest: afterRecordBytes }
Err e -> { result: Err e, rest: afterRecordBytes }
+
+decodeTuple = \initialState, stepElem, finalizer -> Decode.custom \initialBytes, @Json {} ->
+ # NB: the stepper function must be passed explicitly until #2894 is resolved.
+ decodeElems = \stepper, state, index, bytes ->
+ { val: newState, rest: beforeCommaOrBreak } <- tryDecode
+ (
+ when stepper state index is
+ TooLong ->
+ { rest: beforeCommaOrBreak } <- bytes |> anything |> tryDecode
+ { result: Ok state, rest: beforeCommaOrBreak }
+
+ Next decoder ->
+ Decode.decodeWith bytes decoder (@Json {})
+ )
+
+ { result: commaResult, rest: nextBytes } = comma beforeCommaOrBreak
+
+ when commaResult is
+ Ok {} -> decodeElems stepElem newState (index + 1) nextBytes
+ Err _ -> { result: Ok newState, rest: nextBytes }
+
+ { rest: afterBracketBytes } <- initialBytes |> openBracket |> tryDecode
+
+ { val: endStateResult, rest: beforeClosingBracketBytes } <- decodeElems stepElem initialState 0 afterBracketBytes |> tryDecode
+
+ { rest: afterTupleBytes } <- beforeClosingBracketBytes |> closingBracket |> tryDecode
+
+ when finalizer endStateResult is
+ Ok val -> { result: Ok val, rest: afterTupleBytes }
+ Err e -> { result: Err e, rest: afterTupleBytes }
+
+# Helper to eat leading Json whitespace characters
+eatWhitespace = \input ->
+ when input is
+ [' ', ..] -> eatWhitespace (List.dropFirst input)
+ ['\n', ..] -> eatWhitespace (List.dropFirst input)
+ ['\r', ..] -> eatWhitespace (List.dropFirst input)
+ ['\t', ..] -> eatWhitespace (List.dropFirst input)
+ _ -> input
+
+# Test eating Json whitespace
+expect
+ input = Str.toUtf8 " \n\r\tabc"
+ actual = eatWhitespace input
+ expected = Str.toUtf8 "abc"
+
+ actual == expected
+
+# Test json string decoding with escapes
+expect
+ input = Str.toUtf8 "\"a\r\nbc\\\"xz\""
+ expected = Ok "a\r\nbc\\\"xz"
+ actual = Decode.fromBytes input fromUtf8
+
+ actual == expected
+
+# Test json string encoding with escapes
+expect
+ input = "a\r\nbc\\\"xz"
+ expected = Str.toUtf8 "\"a\r\nbc\\\"xz\""
+ actual = Encode.toBytes input toUtf8
+
+ actual == expected
+
+# Test json array decode empty list
+expect
+ input = Str.toUtf8 "[ ]"
+ actual : Result (List U8) _
+ actual = Decode.fromBytes input fromUtf8
+ expected = Ok []
+
+ actual == expected
+
+# Test json array decoding into integers
+expect
+ input = Str.toUtf8 "[ 1,\n2,\t3]"
+ actual : Result (List U8) _
+ actual = Decode.fromBytes input fromUtf8
+ expected = Ok [1, 2, 3]
+
+ actual == expected
+
+# Test json array decoding into strings ignoring whitespace around values
+expect
+ input = Str.toUtf8 "[\r\"one\" ,\t\"two\"\n,\n\"3\"\t]"
+ actual = Decode.fromBytes input fromUtf8
+ expected = Ok ["one", "two", "3"]
+
+ actual == expected
diff --git a/crates/compiler/builtins/roc/List.roc b/crates/compiler/builtins/roc/List.roc
index 0090457e7b..a33206a398 100644
--- a/crates/compiler/builtins/roc/List.roc
+++ b/crates/compiler/builtins/roc/List.roc
@@ -62,6 +62,7 @@ interface List
sortAsc,
sortDesc,
reserve,
+ releaseExcessCapacity,
walkBackwardsUntil,
countIf,
]
@@ -73,11 +74,11 @@ interface List
## Types
## A sequential list of values.
-##
-## >>> [1, 2, 3] # a list of numbers
-## >>> ["a", "b", "c"] # a list of strings
-## >>> [[1.1], [], [2.2, 3.3]] # a list of lists of numbers
-##
+## ```
+## [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 maximum size of a [List] is limited by the amount of heap memory available
## to the current process. If there is not enough memory available, attempting to
## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html)
@@ -105,11 +106,11 @@ interface List
## will be immediately freed.
##
## Let's look at an example.
+## ```
+## ratings = [5, 4, 3]
##
-## ratings = [5, 4, 3]
-##
-## { foo: ratings, bar: ratings }
-##
+## { foo: ratings, bar: ratings }
+## ```
## The first line binds the name `ratings` to the list `[5, 4, 3]`. The list
## begins with a refcount of 1, because so far only `ratings` is referencing it.
##
@@ -118,14 +119,14 @@ interface List
## refcount getting incremented from 1 to 3.
##
## Let's turn this example into a function.
+## ```
+## getRatings = \first ->
+## ratings = [first, 4, 3]
##
-## getRatings = \first ->
-## ratings = [first, 4, 3]
-##
-## { foo: ratings, bar: ratings }
-##
-## getRatings 5
+## { foo: ratings, bar: ratings }
##
+## getRatings 5
+## ```
## At the end of the `getRatings` function, when the record gets returned,
## the original `ratings =` binding has gone out of scope and is no longer
## accessible. (Trying to reference `ratings` outside the scope of the
@@ -140,23 +141,23 @@ interface List
## list, and that list has a refcount of 2.
##
## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`:
+## ```
+## getRatings = \first ->
+## ratings = [first, 4, 3]
##
-## getRatings = \first ->
-## ratings = [first, 4, 3]
-##
-## { foo: ratings, bar: ratings }
-##
-## (getRatings 5).bar
+## { foo: ratings, bar: ratings }
##
+## (getRatings 5).bar
+## ```
## Now, when this expression returns, only the `bar` field of the record will
## be returned. This will mean that the `foo` field becomes inaccessible, causing
## the list's refcount to get decremented from 2 to 1. At this point, the list is back
## where it started: there is only 1 reference to it.
##
## Finally let's suppose the final line were changed to this:
-##
-## 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
## 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
@@ -167,26 +168,26 @@ interface List
## and then with a list of lists, to see how they differ.
##
## Here's the example using a list of numbers.
+## ```
+## nums = [1, 2, 3, 4, 5, 6, 7]
##
-## nums = [1, 2, 3, 4, 5, 6, 7]
-##
-## first = List.first nums
-## last = List.last nums
-##
-## first
+## first = List.first nums
+## last = List.last nums
##
+## 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:
+## ```
+## lists = [[1], [2, 3], [], [4, 5, 6, 7]]
##
-## lists = [[1], [2, 3], [], [4, 5, 6, 7]]
+## first = List.first lists
+## last = List.last lists
##
-## first = List.first lists
-## last = List.last lists
-##
-## first
-##
-## TODO explain how in the former example, when we go to free `nums` at the end,
+## first
+## ```
+## **TODO** explain how in the former example, when we go to free `nums` at the end,
## we can free it immediately because there are no other refcounts. However,
## in the case of `lists`, we have to iterate through the list and decrement
## the refcounts of each of its contained lists - because they, too, have
@@ -206,10 +207,11 @@ interface List
## * 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!
## Check if the list is empty.
+## ```
+## List.isEmpty [1, 2, 3]
##
-## >>> List.isEmpty [1, 2, 3]
-##
-## >>> List.isEmpty []
+## List.isEmpty []
+## ```
isEmpty : List a -> Bool
isEmpty = \list ->
List.len list == 0
@@ -237,9 +239,9 @@ replace = \list, index, newValue ->
{ list, value: newValue }
## Replaces the element at the given index with a replacement.
-##
-## >>> List.set ["a", "b", "c"] 1 "B"
-##
+## ```
+## List.set ["a", "b", "c"] 1 "B"
+## ```
## If the given index is outside the bounds of the list, returns the original
## list unmodified.
##
@@ -249,11 +251,12 @@ set = \list, index, value ->
(List.replace list index value).list
## Add a single element to the end of a list.
+## ```
+## List.append [1, 2, 3] 4
##
-## >>> List.append [1, 2, 3] 4
-##
-## >>> [0, 1, 2]
-## >>> |> List.append 3
+## [0, 1, 2]
+## |> List.append 3
+## ```
append : List a, a -> List a
append = \list, element ->
list
@@ -268,11 +271,12 @@ append = \list, element ->
appendUnsafe : List a, a -> List a
## Add a single element to the beginning of a list.
+## ```
+## List.prepend [1, 2, 3] 0
##
-## >>> List.prepend [1, 2, 3] 0
-##
-## >>> [2, 3, 4]
-## >>> |> List.prepend 1
+## [2, 3, 4]
+## |> List.prepend 1
+## ```
prepend : List a, a -> List a
## Returns the length of the list - the number of elements it contains.
@@ -288,12 +292,17 @@ withCapacity : Nat -> List a
## Enlarge the list for at least capacity additional elements
reserve : List a, Nat -> List a
+## Shrink the memory footprint of a list such that it's capacity and length are equal.
+## Note: This will also convert seamless slices to regular lists.
+releaseExcessCapacity : List a -> List a
+
## Put two lists together.
+## ```
+## List.concat [1, 2, 3] [4, 5]
##
-## >>> List.concat [1, 2, 3] [4, 5]
-##
-## >>> [0, 1, 2]
-## >>> |> List.concat [3, 4]
+## [0, 1, 2]
+## |> List.concat [3, 4]
+## ```
concat : List a, List a -> List a
## Returns the last element in the list, or `ListWasEmpty` if it was empty.
@@ -306,17 +315,15 @@ last = \list ->
## A list with a single element in it.
##
## This is useful in pipelines, like so:
-##
-## websites =
-## Str.concat domain ".com"
-## |> List.single
-##
+## ```
+## websites =
+## Str.concat domain ".com"
+## |> List.single
+## ```
single : a -> List a
single = \x -> [x]
## Returns a list with the given length, where every element is the given value.
-##
-##
repeat : a, Nat -> List a
repeat = \value, count ->
repeatHelp value count (List.withCapacity count)
@@ -329,8 +336,9 @@ repeatHelp = \value, count, accum ->
accum
## Returns the list with its elements reversed.
-##
-## >>> List.reverse [1, 2, 3]
+## ```
+## List.reverse [1, 2, 3]
+## ```
reverse : List a -> List a
reverse = \list ->
reverseHelp list 0 (Num.subSaturated (List.len list) 1)
@@ -342,12 +350,11 @@ reverseHelp = \list, left, right ->
list
## Join the given lists together into one list.
-##
-## >>> List.join [[1, 2, 3], [4, 5], [], [6, 7]]
-##
-## >>> List.join [[], []]
-##
-## >>> List.join []
+## ```
+## List.join [[1, 2, 3], [4, 5], [], [6, 7]]
+## List.join [[], []]
+## List.join []
+## ```
join : List (List a) -> List a
join = \lists ->
totalLength =
@@ -366,10 +373,10 @@ contains = \list, needle ->
## which updates the `state`. It returns the final `state` at the end.
##
## You can use it in a pipeline:
-##
-## [2, 4, 8]
-## |> List.walk 0 Num.add
-##
+## ```
+## [2, 4, 8]
+## |> List.walk 0 Num.add
+## ```
## This returns 14 because:
## * `state` starts at 0
## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`.
@@ -385,10 +392,10 @@ contains = \list, needle ->
## 6 | 8 | 14
##
## The following returns -6:
-##
-## [1, 2, 3]
-## |> List.walk 0 Num.sub
-##
+## ```
+## [1, 2, 3]
+## |> List.walk 0 Num.sub
+## ```
## Note that in other languages, `walk` is sometimes called `reduce`,
## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state
@@ -494,9 +501,9 @@ all = \list, predicate ->
## Run the given function on each element of a list, and return all the
## elements for which the function returned `Bool.true`.
-##
-## >>> List.keepIf [1, 2, 3, 4] (\num -> num > 2)
-##
+## ```
+## List.keepIf [1, 2, 3, 4] (\num -> num > 2)
+## ```
## ## Performance Details
##
## [List.keepIf] always returns a list that takes up exactly the same amount
@@ -531,9 +538,9 @@ keepIfHelp = \list, predicate, kept, index, length ->
## Run the given function on each element of a list, and return all the
## elements for which the function returned `Bool.false`.
-##
-## >>> List.dropIf [1, 2, 3, 4] (\num -> num > 2)
-##
+## ```
+## List.dropIf [1, 2, 3, 4] (\num -> num > 2)
+## ```
## ## Performance Details
##
## `List.dropIf` has the same performance characteristics as [List.keepIf].
@@ -556,12 +563,13 @@ countIf = \list, predicate ->
## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
+## ```
+## List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last
##
-## >>> List.keepOks [["a", "b"], [], [], ["c", "d", "e"]] List.last
+## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
##
-## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
-## >>>
-## >>> List.keepOks ["", "a", "bc", "", "d", "ef", ""]
+## List.keepOks ["", "a", "bc", "", "d", "ef", ""]
+## ```
keepOks : List before, (before -> Result after *) -> List after
keepOks = \list, toResult ->
walker = \accum, element ->
@@ -573,12 +581,13 @@ keepOks = \list, toResult ->
## This works like [List.map], except only the transformed values that are
## wrapped in `Err` are kept. Any that are wrapped in `Ok` are dropped.
+## ```
+## List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last
##
-## >>> List.keepErrs [["a", "b"], [], [], ["c", "d", "e"]] List.last
+## fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
##
-## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str)
-## >>>
-## >>> List.keepErrs ["", "a", "bc", "", "d", "ef", ""]
+## List.keepErrs ["", "a", "bc", "", "d", "ef", ""]
+## ```
keepErrs : List before, (before -> Result * after) -> List after
keepErrs = \list, toResult ->
walker = \accum, element ->
@@ -590,10 +599,11 @@ keepErrs = \list, toResult ->
## Convert each element in the list to something new, by calling a conversion
## function on each of them. Then return a new list of the converted values.
+## ```
+## List.map [1, 2, 3] (\num -> num + 1)
##
-## > List.map [1, 2, 3] (\num -> num + 1)
-##
-## > List.map ["", "a", "bc"] Str.isEmpty
+## List.map ["", "a", "bc"] Str.isEmpty
+## ```
map : List a, (a -> b) -> List b
## Run a transformation function on the first element of each list,
@@ -602,8 +612,9 @@ map : List a, (a -> b) -> List b
##
## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`:
-##
-## >>> zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
+## ```
+## zipped = List.map2 ["a", "b", "c"] [1, 2, 3] Pair
+## ```
map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list,
@@ -640,92 +651,110 @@ mapWithIndexHelp = \src, dest, func, index, length ->
## Returns a list of all the integers between `start` and `end`.
##
## To include the `start` and `end` integers themselves, use `At` like so:
-##
-## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5]
-##
+## ```
+## List.range { start: At 2, end: At 5 } # returns [2, 3, 4, 5]
+## ```
## To exclude them, use `After` and `Before`, like so:
-##
-## List.range { start: After 2, end: Before 5 } # returns [3, 4]
-##
+## ```
+## List.range { start: After 2, end: Before 5 } # returns [3, 4]
+## ```
## You can have the list end at a certain length rather than a certain integer:
-##
-## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9]
-##
+## ```
+## List.range { start: At 6, end: Length 4 } # returns [6, 7, 8, 9]
+## ```
## If `step` is specified, each integer increases by that much. (`step: 1` is the default.)
-##
-## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6]
-##
+## ```
+## List.range { start: After 0, end: Before 9, step: 3 } # returns [3, 6]
+## ```
+## List.range will also generate a reversed list if step is negative or end comes before start:
+## ```
+## List.range { start: At 5, end: At 2 } # returns [5, 4, 3, 2]
+## ```
## All of these options are compatible with the others. For example, you can use `At` or `After`
## with `start` regardless of what `end` and `step` are set to.
range : _
range = \{ start, end, step ? 0 } ->
- { incByStep, stepIsPositive } =
+ { calcNext, stepIsPositive } =
if step == 0 then
when T start end is
T (At x) (At y) | T (At x) (Before y) | T (After x) (At y) | T (After x) (Before y) ->
if x < y then
{
- incByStep: \i -> i + 1,
+ calcNext: \i -> Num.addChecked i 1,
stepIsPositive: Bool.true,
}
else
{
- incByStep: \i -> i - 1,
+ calcNext: \i -> Num.subChecked i 1,
stepIsPositive: Bool.false,
}
T (At _) (Length _) | T (After _) (Length _) ->
{
- incByStep: \i -> i + 1,
+ calcNext: \i -> Num.addChecked i 1,
stepIsPositive: Bool.true,
}
else
{
- incByStep: \i -> i + step,
+ calcNext: \i -> Num.addChecked i step,
stepIsPositive: step > 0,
}
inclusiveStart =
when start is
- At x -> x
- After x -> incByStep x
+ At x -> Ok x
+ After x -> calcNext x
when end is
At at ->
- isComplete =
+ isValid =
if stepIsPositive then
- \i -> i > at
+ \i -> i <= at
else
- \i -> i < at
+ \i -> i >= at
# TODO: switch to List.withCapacity
- rangeHelp [] inclusiveStart incByStep isComplete
+ rangeHelp [] inclusiveStart calcNext isValid
Before before ->
- isComplete =
+ isValid =
if stepIsPositive then
- \i -> i >= before
+ \i -> i < before
else
- \i -> i <= before
+ \i -> i > before
# TODO: switch to List.withCapacity
- rangeHelp [] inclusiveStart incByStep isComplete
+ rangeHelp [] inclusiveStart calcNext isValid
Length l ->
- rangeLengthHelp (List.withCapacity l) inclusiveStart l incByStep
+ rangeLengthHelp (List.withCapacity l) inclusiveStart l calcNext
-rangeHelp = \accum, i, incByStep, isComplete ->
- if isComplete i then
- accum
- else
- # TODO: change this to List.appendUnsafe once capacity is set correctly
- rangeHelp (List.append accum i) (incByStep i) incByStep isComplete
+rangeHelp = \accum, i, calcNext, isValid ->
+ when i is
+ Ok val ->
+ if isValid val then
+ # TODO: change this to List.appendUnsafe once capacity is set correctly
+ rangeHelp (List.append accum val) (calcNext val) calcNext isValid
+ else
+ accum
-rangeLengthHelp = \accum, i, remaining, incByStep ->
+ Err _ ->
+ # We went past the end of the numeric range and there is no next.
+ # return the generated list.
+ accum
+
+rangeLengthHelp = \accum, i, remaining, calcNext ->
if remaining == 0 then
accum
else
- rangeLengthHelp (List.appendUnsafe accum i) (incByStep i) (remaining - 1) incByStep
+ when i is
+ Ok val ->
+ rangeLengthHelp (List.appendUnsafe accum val) (calcNext val) (remaining - 1) calcNext
+
+ Err _ ->
+ # We went past the end of the numeric range and there is no next.
+ # The list is not the correct length yet, so we must crash.
+ crash "List.range: failed to generate enough elements to fill the range before overflowing the numeric type"
expect
List.range { start: At 0, end: At 4 } == [0, 1, 2, 3, 4]
@@ -754,6 +783,18 @@ expect
expect
List.range { start: At 4, end: Length 5, step: -3 } == [4, 1, -2, -5, -8]
+expect
+ List.range { start: After 250u8, end: At 255 } == [251, 252, 253, 254, 255]
+
+expect
+ List.range { start: After 250u8, end: At 255, step: 10 } == []
+
+expect
+ List.range { start: After 250u8, end: At 245, step: 10 } == []
+
+expect
+ List.range { start: At 4, end: At 0 } == [4, 3, 2, 1, 0]
+
## Sort with a custom comparison function
sortWith : List a, (a, a -> [LT, EQ, GT]) -> List a
@@ -795,14 +836,14 @@ dropLast = \list ->
List.dropAt list (Num.subSaturated (List.len list) 1)
## Returns the given number of elements from the beginning of the list.
-##
-## >>> List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4
-##
+## ```
+## List.takeFirst [1, 2, 3, 4, 5, 6, 7, 8] 4
+## ```
## If there are fewer elements in the list than the requested number,
## returns the entire list.
-##
-## >>> List.takeFirst [1, 2] 5
-##
+## ```
+## List.takeFirst [1, 2] 5
+## ```
## To *remove* elements from the beginning of the list, use `List.takeLast`.
##
## To remove elements from both the beginning and end of the list,
@@ -810,28 +851,19 @@ dropLast = \list ->
##
## To split the list into two lists, use `List.split`.
##
-## ## Performance Details
-##
-## When given a Unique list, this runs extremely fast. It sets the list's length
-## to the given length value, and frees the leftover elements. This runs very
-## slightly faster than `List.takeLast`.
-##
-## In fact, `List.takeFirst list 1` runs faster than `List.first list` when given
-## a Unique list, because [List.first] returns the first element as well -
-## which introduces a conditional bounds check as well as a memory load.
takeFirst : List elem, Nat -> List elem
takeFirst = \list, outputLength ->
List.sublist list { start: 0, len: outputLength }
## Returns the given number of elements from the end of the list.
-##
-## >>> List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4
-##
+## ```
+## List.takeLast [1, 2, 3, 4, 5, 6, 7, 8] 4
+## ```
## If there are fewer elements in the list than the requested number,
## returns the entire list.
-##
-## >>> List.takeLast [1, 2] 5
-##
+## ```
+## List.takeLast [1, 2] 5
+## ```
## To *remove* elements from the end of the list, use `List.takeFirst`.
##
## To remove elements from both the beginning and end of the list,
@@ -839,16 +871,6 @@ takeFirst = \list, outputLength ->
##
## To split the list into two lists, use `List.split`.
##
-## ## Performance Details
-##
-## When given a Unique list, this runs extremely fast. It moves the list's
-## pointer to the index at the given length value, updates its length,
-## and frees the leftover elements. This runs very nearly as fast as
-## `List.takeFirst` on a Unique list.
-##
-## In fact, `List.takeLast list 1` runs faster than `List.first list` when given
-## a Unique list, because [List.first] returns the first element as well -
-## which introduces a conditional bounds check as well as a memory load.
takeLast : List elem, Nat -> List elem
takeLast = \list, outputLength ->
List.sublist list { start: Num.subSaturated (List.len list) outputLength, len: outputLength }
@@ -971,13 +993,13 @@ findLastIndex = \list, matches ->
## including a total of `len` elements.
##
## If `start` is outside the bounds of the given list, returns the empty list.
-##
-## >>> List.sublist [1, 2, 3] { start: 4, len: 0 }
-##
+## ```
+## List.sublist [1, 2, 3] { start: 4, len: 0 }
+## ```
## If more elements are requested than exist in the list, returns as many as it can.
-##
-## >>> List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 }
-##
+## ```
+## List.sublist [1, 2, 3, 4, 5] { start: 2, len: 10 }
+## ```
## > 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.
##
@@ -993,7 +1015,9 @@ sublist = \list, config ->
sublistLowlevel : List elem, Nat, Nat -> List elem
## Intersperses `sep` between the elements of `list`
-## >>> List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3]
+## ```
+## List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3]
+## ```
intersperse : List elem, elem -> List elem
intersperse = \list, sep ->
capacity = 2 * List.len list
@@ -1051,8 +1075,9 @@ split = \elements, userSplitIndex ->
## Returns the elements before the first occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
-##
-## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Baz] }
+## ```
+## List.splitFirst [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo], after: [Bar, Z, Baz] }
+## ```
splitFirst : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitFirst = \list, delimiter ->
when List.findFirstIndex list (\elem -> elem == delimiter) is
@@ -1066,8 +1091,9 @@ splitFirst = \list, delimiter ->
## Returns the elements before the last occurrence of a delimiter, as well as the
## remaining elements after that occurrence. If the delimiter is not found, returns `Err`.
-##
-## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Bar], after: [Baz] }
+## ```
+## List.splitLast [Foo, Z, Bar, Z, Baz] Z == Ok { before: [Foo, Z, Bar], after: [Baz] }
+## ```
splitLast : List elem, elem -> Result { before : List elem, after : List elem } [NotFound] | elem has Eq
splitLast = \list, delimiter ->
when List.findLastIndex list (\elem -> elem == delimiter) is
diff --git a/crates/compiler/builtins/roc/Num.roc b/crates/compiler/builtins/roc/Num.roc
index eef9cf4741..cc616af3d6 100644
--- a/crates/compiler/builtins/roc/Num.roc
+++ b/crates/compiler/builtins/roc/Num.roc
@@ -68,6 +68,9 @@ interface Num
compare,
pow,
powInt,
+ countLeadingZeroBits,
+ countTrailingZeroBits,
+ countOneBits,
addWrap,
addChecked,
addSaturated,
@@ -86,6 +89,8 @@ interface Num
intCast,
bytesToU16,
bytesToU32,
+ bytesToU64,
+ bytesToU128,
divCeil,
divCeilChecked,
divTrunc,
@@ -151,9 +156,9 @@ interface Num
## Represents a number that could be either an [Int] or a [Frac].
##
## This is useful for functions that can work on either, for example [Num.add], whose type is:
-##
-## add : Num a, Num a -> Num a
-##
+## ```
+## add : Num a, Num a -> Num a
+## ```
## The number 1.5 technically has the type `Num (Fraction *)`, so when you pass
## two of them to [Num.add], the answer you get is `3.0 : Num (Fraction *)`.
##
@@ -191,9 +196,9 @@ interface Num
##
## If this default of [I64] is not big enough for your purposes,
## you can add an `i128` to the end of the number literal, like so:
-##
-## >>> Num.toStr 5_000_000_000i128
-##
+## ```
+## Num.toStr 5_000_000_000i128
+## ```
## This `i128` suffix specifies that you want this number literal to be
## an [I128] instead of a `Num *`. All the other numeric types have
## suffixes just like `i128`; here are some other examples:
@@ -234,7 +239,7 @@ Num range := range
##
## This pattern continues up to [U128] and [I128].
##
-## ## Performance notes
+## ## Performance Details
##
## In general, using smaller numeric sizes means your program will use less memory.
## However, if a mathematical operation results in an answer that is too big
@@ -259,15 +264,11 @@ Num range := range
##
## All number literals without decimal points are compatible with [Int] values.
##
-## >>> 1
-##
-## >>> 0
-##
## You can optionally put underscores in your [Int] literals.
## They have no effect on the number's value, but can make large numbers easier to read.
-##
-## >>> 1_000_000
-##
+## ```
+## 1_000_000
+## ```
## Integers come in two flavors: *signed* and *unsigned*.
##
## * *Unsigned* integers can never be negative. The lowest value they can hold is zero.
@@ -342,16 +343,16 @@ Int range : Num (Integer range)
##
## If you don't specify a type, Roc will default to using [Dec] because it's
## the least error-prone overall. For example, suppose you write this:
-##
-## wasItPrecise = 0.1 + 0.2 == 0.3
-##
+## ```
+## wasItPrecise = 0.1 + 0.2 == 0.3
+## ```
## The value of `wasItPrecise` here will be `Bool.true`, because Roc uses [Dec]
## by default when there are no types specified.
##
## In contrast, suppose we use `f32` or `f64` for one of these numbers:
-##
-## wasItPrecise = 0.1f64 + 0.2 == 0.3
-##
+## ```
+## wasItPrecise = 0.1f64 + 0.2 == 0.3
+## ```
## Here, `wasItPrecise` will be `Bool.false` because the entire calculation will have
## been done in a base-2 floating point calculation, which causes noticeable
## precision loss in this case.
@@ -380,7 +381,7 @@ Int range : Num (Integer range)
## Whenever a function in this module could return one of these values, that
## possibility is noted in the function's documentation.
##
-## ## Performance Notes
+## ## Performance Details
##
## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32]
## for addition and subtraction. For example, [F32] and [F64] do addition using
@@ -480,7 +481,7 @@ F32 : Num (FloatingPoint Binary32)
## details, below), and decimal precision loss isn't as big a concern when
## dealing with screen coordinates as it is when dealing with currency.
##
-## ## Performance
+## ## Performance Details
##
## [Dec] typically takes slightly less time than [F64] to perform addition and
## subtraction, but 10-20 times longer to perform multiplication and division.
@@ -492,15 +493,14 @@ Dec : Num (FloatingPoint Decimal)
##
## This is the same as calling `Num.format {}` - so for more details on
## exact formatting, see `Num.format`.
-##
-## >>> Num.toStr 42
-##
+## ```
+## Num.toStr 42
+## ```
## Only [Frac] values will include a decimal point, and they will always include one.
-##
-## >>> Num.toStr 4.2
-##
-## >>> Num.toStr 4.0
-##
+## ```
+## Num.toStr 4.2
+## Num.toStr 4.0
+## ```
## When this function is given a non-[finite](Num.isFinite)
## [F64] or [F32] value, the returned string will be `"NaN"`, `"∞"`, or `"-∞"`.
##
@@ -510,6 +510,8 @@ intCast : Int a -> Int b
bytesToU16Lowlevel : List U8, Nat -> U16
bytesToU32Lowlevel : List U8, Nat -> U32
+bytesToU64Lowlevel : List U8, Nat -> U64
+bytesToU128Lowlevel : List U8, Nat -> U128
bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds]
bytesToU16 = \bytes, index ->
@@ -531,6 +533,26 @@ bytesToU32 = \bytes, index ->
else
Err OutOfBounds
+bytesToU64 : List U8, Nat -> Result U64 [OutOfBounds]
+bytesToU64 = \bytes, index ->
+ # we need at least 7 more bytes
+ offset = 7
+
+ if index + offset < List.len bytes then
+ Ok (bytesToU64Lowlevel bytes index)
+ else
+ Err OutOfBounds
+
+bytesToU128 : List U8, Nat -> Result U128 [OutOfBounds]
+bytesToU128 = \bytes, index ->
+ # we need at least 15 more bytes
+ offset = 15
+
+ if index + offset < List.len bytes then
+ Ok (bytesToU128Lowlevel bytes index)
+ else
+ Err OutOfBounds
+
compare : Num a, Num a -> [LT, EQ, GT]
## Returns `Bool.true` if the first number is less than the second.
@@ -539,9 +561,10 @@ compare : Num a, Num a -> [LT, EQ, GT]
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
-##
-## >>> 5
-## >>> |> Num.isLt 6
+## ```
+## 5
+## |> Num.isLt 6
+## ```
isLt : Num a, Num a -> Bool
## Returns `Bool.true` if the first number is greater than the second.
@@ -550,9 +573,10 @@ isLt : Num a, Num a -> Bool
##
## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
-##
-## >>> 6
-## >>> |> Num.isGt 5
+## ```
+## 6
+## |> Num.isGt 5
+## ```
isGt : Num a, Num a -> Bool
## Returns `Bool.true` if the first number is less than or equal to the second.
@@ -601,15 +625,15 @@ toFrac : Num * -> Frac *
## * For a positive number, returns the same number.
## * For a negative number, returns the same number except positive.
## * For zero, returns zero.
+## ```
+## Num.abs 4
##
-## >>> Num.abs 4
+## Num.abs -2.5
##
-## >>> Num.abs -2.5
-##
-## >>> Num.abs 0
-##
-## >>> Num.abs 0.0
+## Num.abs 0
##
+## Num.abs 0.0
+## ```
## This is safe to use with any [Frac], 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 [Num.minI64] or [Num.minI32]) will cause overflow.
@@ -620,15 +644,15 @@ toFrac : Num * -> Frac *
abs : Num a -> Num a
## Return a negative number when given a positive one, and vice versa.
+## ```
+## Num.neg 5
##
-## >>> Num.neg 5
+## Num.neg -2.5
##
-## >>> Num.neg -2.5
-##
-## >>> Num.neg 0
-##
-## >>> Num.neg 0.0
+## Num.neg 0
##
+## Num.neg 0.0
+## ```
## This is safe to use with any [Frac], 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 [Num.minI64] or [Num.minI32]) will cause overflow.
@@ -645,16 +669,16 @@ neg : Num a -> Num a
## (To add an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
## `a + b` is shorthand for `Num.add a b`.
+## ```
+## 5 + 7
##
-## >>> 5 + 7
-##
-## >>> Num.add 5 7
-##
+## Num.add 5 7
+## ```
## `Num.add` can be convenient in pipelines.
-##
-## >>> Frac.pi
-## >>> |> Num.add 1.0
-##
+## ```
+## Frac.pi
+## |> Num.add 1.0
+## ```
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
@@ -666,16 +690,16 @@ add : Num a, Num a -> Num a
## (To subtract an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
## `a - b` is shorthand for `Num.sub a b`.
+## ```
+## 7 - 5
##
-## >>> 7 - 5
-##
-## >>> Num.sub 7 5
-##
+## Num.sub 7 5
+## ```
## `Num.sub` can be convenient in pipelines.
-##
-## >>> Frac.pi
-## >>> |> Num.sub 2.0
-##
+## ```
+## Frac.pi
+## |> Num.sub 2.0
+## ```
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
@@ -687,16 +711,18 @@ sub : Num a, Num a -> Num a
## (To multiply an [Int] and a [Frac], first convert one so that they both have the same type. There are functions in this module that can convert both [Int] to [Frac] and the other way around.)
##
## `a * b` is shorthand for `Num.mul a b`.
+## ```
+## 5 * 7
##
-## >>> 5 * 7
-##
-## >>> Num.mul 5 7
+## Num.mul 5 7
+## ```
##
## `Num.mul` can be convenient in pipelines.
##
-## >>> Frac.pi
-## >>> |> Num.mul 2.0
-##
+## ```
+## Frac.pi
+## |> Num.mul 2.0
+## ```
## If the answer to this operation can't fit in the return value (e.g. an
## [I8] answer that's higher than 127 or lower than -128), the result is an
## *overflow*. For [F64] and [F32], overflow results in an answer of either
@@ -731,14 +757,15 @@ atan : Frac a -> Frac a
## > this standard, deviating from these rules has a significant performance
## > cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
## > access to hardware-accelerated performance, Roc follows these rules exactly.
+## ```
+## Num.sqrt 4.0
##
-## >>> Num.sqrt 4.0
+## Num.sqrt 1.5
##
-## >>> Num.sqrt 1.5
+## Num.sqrt 0.0
##
-## >>> Num.sqrt 0.0
-##
-## >>> Num.sqrt -4.0f64
+## Num.sqrt -4.0f64
+## ```
sqrt : Frac a -> Frac a
sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative]
@@ -748,6 +775,7 @@ sqrtChecked = \x ->
else
Ok (Num.sqrt x)
+## Natural logarithm
log : Frac a -> Frac a
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]
@@ -778,15 +806,16 @@ logChecked = \x ->
##
## To divide an [Int] and a [Frac], first convert the [Int] to a [Frac] using
## one of the functions in this module like #toDec.
+## ```
+## 5.0 / 7.0
##
-## >>> 5.0 / 7.0
-##
-## >>> Num.div 5 7
-##
+## Num.div 5 7
+## ```
## `Num.div` can be convenient in pipelines.
-##
-## >>> Num.pi
-## >>> |> Num.div 2.0
+## ```
+## Num.pi
+## |> Num.div 2.0
+## ```
div : Frac a, Frac a -> Frac a
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]
@@ -812,15 +841,15 @@ divCeilChecked = \a, b ->
## Division by zero is undefined in mathematics. As such, you should make
## sure never to pass zero as the denomaintor to this function! If you do,
## it will crash.
+## ```
+## 5 // 7
##
-## >>> 5 // 7
+## Num.divTrunc 5 7
##
-## >>> Num.divTrunc 5 7
-##
-## >>> 8 // -3
-##
-## >>> Num.divTrunc 8 -3
+## 8 // -3
##
+## Num.divTrunc 8 -3
+## ```
divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]
@@ -833,14 +862,15 @@ divTruncChecked = \a, b ->
## Obtain the remainder (truncating modulo) from the division of two integers.
##
## `a % b` is shorthand for `Num.rem a b`.
+## ```
+## 5 % 7
##
-## >>> 5 % 7
+## Num.rem 5 7
##
-## >>> Num.rem 5 7
+## -8 % -3
##
-## >>> -8 % -3
-##
-## >>> Num.rem -8 -3
+## Num.rem -8 -3
+## ```
rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [DivByZero]
@@ -860,24 +890,24 @@ bitwiseOr : Int a, Int a -> Int a
##
## The least significant bits always become 0. This means that shifting left is
## like multiplying by factors of two for unsigned integers.
+## ```
+## shiftLeftBy 0b0000_0011 2 == 0b0000_1100
##
-## >>> shiftLeftBy 0b0000_0011 2 == 0b0000_1100
-##
-## >>> 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
-##
+## 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
+## ```
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
shiftLeftBy : Int a, U8 -> Int a
## Bitwise arithmetic shift of a number by another
##
## The most significant bits are copied from the current.
+## ```
+## shiftRightBy 0b0000_0011 2 == 0b0000_1100
##
-## >>> shiftRightBy 0b0000_0011 2 == 0b0000_1100
-##
-## >>> 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
-##
-## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
+## 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
##
+## 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
+## ```
## In some languages `shiftRightBy` is implemented as a binary operator `>>>`.
shiftRightBy : Int a, U8 -> Int a
@@ -885,13 +915,13 @@ shiftRightBy : Int a, U8 -> Int a
##
## The most significant bits always become 0. This means that shifting left is
## like dividing by factors of two for unsigned integers.
+## ```
+## shiftRightBy 0b0010_1000 2 == 0b0000_1010
##
-## >>> shiftRightBy 0b0010_1000 2 == 0b0000_1010
-##
-## >>> 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
-##
-## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
+## 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
##
+## 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
+## ```
## In some languages `shiftRightBy` is implemented as a binary operator `>>`.
shiftRightZfBy : Int a, U8 -> Int a
@@ -912,21 +942,60 @@ pow : Frac a, Frac a -> Frac a
##
## For a [Frac] alternative to this function, which supports negative exponents,
## see #Num.exp.
+## ```
+## Num.exp 5 0
##
-## >>> Num.exp 5 0
+## Num.exp 5 1
##
-## >>> Num.exp 5 1
+## Num.exp 5 2
##
-## >>> Num.exp 5 2
-##
-## >>> Num.exp 5 6
-##
-## ## Performance Notes
+## Num.exp 5 6
+## ```
+## ## Performance Details
##
## Be careful! It is very easy for this function to produce an answer
## so large it causes an overflow.
powInt : Int a, Int a -> Int a
+## Counts the number of most-significant (leading in a big-Endian sense) zeroes in an integer.
+##
+## ```
+## Num.countLeadingZeroBits 0b0001_1100u8
+##
+## 3
+##
+## Num.countLeadingZeroBits 0b0000_0000u8
+##
+## 8
+## ```
+countLeadingZeroBits : Int a -> Nat
+
+## Counts the number of least-significant (trailing in a big-Endian sense) zeroes in an integer.
+##
+## ```
+## Num.countTrailingZeroBits 0b0001_1100u8
+##
+## 2
+##
+## Num.countTrailingZeroBits 0b0000_0000u8
+##
+## 8
+## ```
+countTrailingZeroBits : Int a -> Nat
+
+## Counts the number of set bits in an integer.
+##
+## ```
+## Num.countOneBits 0b0001_1100u8
+##
+## 3
+##
+## Num.countOneBits 0b0000_0000u8
+##
+## 0
+## ```
+countOneBits : Int a -> Nat
+
addWrap : Int range, Int range -> Int range
## Add two numbers, clamping on the maximum representable number rather than
@@ -1261,164 +1330,3 @@ toU128Checked : Int * -> Result U128 [OutOfBounds]
toNatChecked : Int * -> Result Nat [OutOfBounds]
toF32Checked : Num * -> Result F32 [OutOfBounds]
toF64Checked : Num * -> Result F64 [OutOfBounds]
-
-# Special Floating-Point operations
-## When given a [F64] or [F32] value, returns `Bool.false` if that value is
-## [*NaN*](Num.isNaN), ∞ or -∞, and `Bool.true` otherwise.
-##
-## Always returns `Bool.true` when given a [Dec].
-##
-## This is the opposite of #isInfinite, except when given [*NaN*](Num.isNaN). Both
-## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN).
-# isFinite : Frac * -> Bool
-## When given a [F64] or [F32] value, returns `Bool.true` if that value is either
-## ∞ or -∞, and `Bool.false` otherwise.
-##
-## Always returns `Bool.false` when given a [Dec].
-##
-## This is the opposite of #isFinite, except when given [*NaN*](Num.isNaN). Both
-## #isFinite and #isInfinite return `Bool.false` for [*NaN*](Num.isNaN).
-# isInfinite : Frac * -> Bool
-## When given a [F64] or [F32] value, returns `Bool.true` if that value is
-## *NaN* ([not a number](https://en.wikipedia.org/wiki/NaN)), and `Bool.false` otherwise.
-##
-## Always returns `Bool.false` when given a [Dec].
-##
-## >>> Num.isNaN 12.3
-##
-## >>> Num.isNaN (Num.pow -1 0.5)
-##
-## *NaN* is unusual from other numberic values in that:
-## * *NaN* is not equal to any other number, even itself. [Bool.isEq] always returns `Bool.false` if either argument is *NaN*.
-## * *NaN* has no ordering, so [isLt], [isLte], [isGt], and [isGte] always return `Bool.false` if either argument is *NaN*.
-##
-## These rules come from the [IEEE-754](https://en.wikipedia.org/wiki/IEEE_754)
-## floating point standard. Because almost all modern processors are built to
-## this standard, deviating from these rules has a significant performance
-## cost! Since the most common reason to choose [F64] or [F32] over [Dec] is
-## access to hardware-accelerated performance, Roc follows these rules exactly.
-##
-## Note that you should never put a *NaN* into a [Set], or use it as the key in
-## a [Dict]. The result is entries that can never be removed from those
-## collections! See the documentation for [Set.insert] and [Dict.insert] for details.
-# isNaN : Frac * -> Bool
-## Returns the higher of two numbers.
-##
-## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
-## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
-# max : Num a, Num a -> Num a
-## Returns the lower of two numbers.
-##
-## If either argument is [*NaN*](Num.isNaN), returns `Bool.false` no matter what. (*NaN*
-## is [defined to be unordered](https://en.wikipedia.org/wiki/NaN#Comparison_with_NaN).)
-# min : Num a, Num a -> Num a
-# Branchless implementation that works for all numeric types:
-#
-# let is_lt = arg1 < arg2;
-# let is_eq = arg1 == arg2;
-# return (is_lt as i8 - is_eq as i8) + 1;
-#
-# 1, 1 -> (0 - 1) + 1 == 0 # Eq
-# 5, 1 -> (0 - 0) + 1 == 1 # Gt
-# 1, 5 -> (1 - 0) + 1 == 2 # Lt
-## Returns `Lt` if the first number is less than the second, `Gt` if
-## the first is greater than the second, and `Eq` if they're equal.
-##
-## Although this can be passed to `List.sort`, you'll get better performance
-## by using `List.sortAsc` or `List.sortDesc` instead.
-# compare : Num a, Num a -> [Lt, Eq, Gt]
-## [Endianness](https://en.wikipedia.org/wiki/Endianness)
-# Endi : [Big, Little, Native]
-## The `Endi` argument does not matter for [U8] and [I8], since they have
-## only one byte.
-# toBytes : Num *, Endi -> List U8
-## when Num.parseBytes bytes Big is
-## Ok { val: f64, rest } -> ...
-## Err (ExpectedNum (Frac Binary64)) -> ...
-# parseBytes : List U8, Endi -> Result { val : Num a, rest : List U8 } [ExpectedNum a]*
-## when Num.fromBytes bytes Big is
-## Ok f64 -> ...
-## Err (ExpectedNum (Frac Binary64)) -> ...
-# fromBytes : List U8, Endi -> Result (Num a) [ExpectedNum a]*
-# Bit shifts
-## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) left.
-##
-## `a << b` is shorthand for `Num.shl a b`.
-# shl : Int a, Int a -> Int a
-## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) left.
-##
-## This is called `shlWrap` because any bits shifted
-## off the beginning of the number will be wrapped around to
-## the end. (In contrast, #shl replaces discarded bits with zeroes.)
-# shlWrap : Int a, Int a -> Int a
-## [Logical bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Logical_shift) right.
-##
-## `a >> b` is shorthand for `Num.shr a b`.
-# shr : Int a, Int a -> Int a
-## [Arithmetic bit shift](https://en.wikipedia.org/wiki/Bitwise_operation#Arithmetic_shift) right.
-##
-## This is called `shrWrap` because any bits shifted
-## off the end of the number will be wrapped around to
-## the beginning. (In contrast, #shr replaces discarded bits with zeroes.)
-# shrWrap : Int a, Int a -> Int a
-# ## Convert a number into a [Str], formatted with the given options.
-# ##
-# ## Default options:
-# ## * `base: Decimal`
-# ## * `notation: Standard`
-# ## * `decimalMark: HideForIntegers "."`
-# ## * `decimalDigits: { min: 0, max: All }`
-# ## * `minIntDigits: 1`
-# ## * `wholeSep: { mark: ",", places: 3 }`
-# ##
-# ## ## Options
-# ##
-# ##
-# ## ### decimalMark
-# ##
-# ## * `AlwaysShow` always shows the decimal mark, no matter what.
-# ## * `HideForIntegers` hides the decimal mark if all the numbers after the decimal mark are 0.
-# ##
-# ## The [Str] included in either of these represents the mark itself.
-# ##
-# ## ### `decimalDigits
-# ##
-# ## With 0 decimal digits, the decimal mark will still be rendered if
-# ## `decimalMark` is set to `AlwaysShow`.
-# ##
-# ## If `max` is less than `min`, then first the number will be truncated to `max`
-# ## digits, and then zeroes will be added afterwards until it reaches `min` digits.
-# ##
-# ## >>> Num.format 1.23 { decPlaces: 0, decPointVis: AlwaysShow }
-# ##
-# ## ### minIntDigits
-# ##
-# ## If the integer portion of number is fewer than this many digits, zeroes will
-# ## be added in front of it until there are at least `minWholeDigits` digits.
-# ##
-# ## If this is set to zero, then numbers less than 1 will begin with `"."`
-# ## rather than `"0."`.
-# ##
-# ## ### wholeSep
-# ##
-# ## Examples:
-# ##
-# ## In some countries (e.g. USA and UK), a comma is used to separate thousands:
-# ## >>> Num.format 1_000_000 { pf: Decimal, wholeSep: { mark: ",", places: 3 } }
-# ##
-# ## Sometimes when rendering bits, it's nice to group them into groups of 4:
-# ## >>> Num.format 1_000_000 { pf: Binary, wholeSep: { mark: " ", places: 4 } }
-# ##
-# ## It's also common to render hexadecimal in groups of 2:
-# ## >>> Num.format 1_000_000 { pf: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
-# format :
-# Num *,
-# {
-# base ? [Decimal, Hexadecimal, Octal, Binary],
-# notation ? [Standard, Scientific],
-# decimalMark ? [AlwaysShow Str, HideForIntegers],
-# decimalDigits ? { min : U16, max : [All, Trunc U16, Round U16, Floor U16, Ceil U16] },
-# minWholeDigits ? U16,
-# wholeSep ? { mark : Str, places : U64 }
-# }
-# -> Str
diff --git a/crates/compiler/builtins/roc/Result.roc b/crates/compiler/builtins/roc/Result.roc
index 967c901567..596a3e78da 100644
--- a/crates/compiler/builtins/roc/Result.roc
+++ b/crates/compiler/builtins/roc/Result.roc
@@ -7,8 +7,9 @@ interface Result
Result ok err : [Ok ok, Err err]
## Return `Bool.true` if the result indicates a success, else return `Bool.false`
-##
-## >>> Result.isOk (Ok 5)
+## ```
+## Result.isOk (Ok 5)
+## ```
isOk : Result ok err -> Bool
isOk = \result ->
when result is
@@ -16,8 +17,9 @@ isOk = \result ->
Err _ -> Bool.false
## Return `Bool.true` if the result indicates a failure, else return `Bool.false`
-##
-## >>> Result.isErr (Err "uh oh")
+## ```
+## Result.isErr (Err "uh oh")
+## ```
isErr : Result ok err -> Bool
isErr = \result ->
when result is
@@ -26,10 +28,10 @@ isErr = \result ->
## 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
+## ```
+## Result.withDefault (Ok 7) 42
+## Result.withDefault (Err "uh oh") 42
+## ```
withDefault : Result ok err, ok -> ok
withDefault = \result, default ->
when result is
@@ -37,16 +39,15 @@ withDefault = \result, default ->
Err _ -> default
## 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.
+## 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
+## ```
##
-## (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`.
+## Functions like `map` are common in Roc; see for example [List.map],
+## `Set.map`, and `Dict.map`.
map : Result a err, (a -> b) -> Result b err
map = \result, transform ->
when result is
@@ -54,13 +55,12 @@ map = \result, transform ->
Err e -> Err e
## 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
+## 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 a, (a -> b) -> Result ok b
mapErr = \result, transform ->
when result is
@@ -68,13 +68,12 @@ mapErr = \result, transform ->
Err e -> Err (transform e)
## 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 `onErr` to transform an `Err`.)
-##
-## >>> Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
-##
-## >>> Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
+## function on the value the `Ok` holds. Then return that new result. If the
+## result is `Err`, this has no effect. Use `onErr` to transform an `Err`.
+## ```
+## Result.try (Ok -1) \num -> if num < 0 then Err "negative!" else Ok -num
+## Result.try (Err "yipes!") \num -> if num < 0 then Err "negative!" else Ok -num
+## ```
try : Result a err, (a -> Result b err) -> Result b err
try = \result, transform ->
when result is
@@ -82,13 +81,12 @@ try = \result, transform ->
Err e -> Err e
## If the result is `Err`, transform the entire result by running a conversion
-## function on the value the `Err` holds. Then return that new result.
-##
-## (If the result is `Ok`, this has no effect. Use `try` to transform an `Ok`.)
-##
-## >>> Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum
-##
-## >>> Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
+## function on the value the `Err` holds. Then return that new result. If the
+## result is `Ok`, this has no effect. Use `try` to transform an `Ok`.
+## ```
+## Result.onErr (Ok 10) \errorNum -> Str.toNat errorNum
+## Result.onErr (Err "42") \errorNum -> Str.toNat errorNum
+## ```
onErr : Result a err, (err -> Result a otherErr) -> Result a otherErr
onErr = \result, transform ->
when result is
diff --git a/crates/compiler/builtins/roc/Set.roc b/crates/compiler/builtins/roc/Set.roc
index c8ead19fcc..ba6a09069b 100644
--- a/crates/compiler/builtins/roc/Set.roc
+++ b/crates/compiler/builtins/roc/Set.roc
@@ -4,6 +4,7 @@ interface Set
empty,
single,
walk,
+ walkUntil,
insert,
len,
remove,
@@ -25,6 +26,8 @@ interface Set
# We should have this line above the next has.
# It causes the formatter to fail currently.
# | k has Hash & Eq
+## Provides a [set](https://en.wikipedia.org/wiki/Set_(abstract_data_type))
+## type which stores a collection of unique values, without any ordering
Set k := Dict.Dict k {}
has [
Eq {
@@ -43,14 +46,39 @@ isEq = \xs, ys ->
else
Break Bool.false
-## Creates a new empty set.
+## Creates a new empty `Set`.
+## ```
+## emptySet = Set.empty {}
+## countValues = Set.len emptySet
+##
+## expect countValues == 0
+## ```
empty : {} -> Set k | k has Hash & Eq
empty = \{} -> @Set (Dict.empty {})
+## Creates a new `Set` with a single value.
+## ```
+## singleItemSet = Set.single "Apple"
+## countValues = Set.len singleItemSet
+##
+## expect countValues == 1
+## ```
single : k -> Set k | k has Hash & Eq
single = \key ->
Dict.single key {} |> @Set
+## Insert a value into a `Set`.
+## ```
+## fewItemSet =
+## Set.empty {}
+## |> Set.insert "Apple"
+## |> Set.insert "Pear"
+## |> Set.insert "Banana"
+##
+## countValues = Set.len fewItemSet
+##
+## expect countValues == 3
+## ```
insert : Set k, k -> Set k | k has Hash & Eq
insert = \@Set dict, key ->
Dict.insert dict key {} |> @Set
@@ -72,6 +100,18 @@ expect
expected == actual
+## Counts the number of values in a given `Set`.
+## ```
+## fewItemSet =
+## Set.empty {}
+## |> Set.insert "Apple"
+## |> Set.insert "Pear"
+## |> Set.insert "Banana"
+##
+## countValues = Set.len fewItemSet
+##
+## expect countValues == 3
+## ```
len : Set k -> Nat | k has Hash & Eq
len = \@Set dict ->
Dict.len dict
@@ -88,41 +128,151 @@ expect
actual == 3
-## Drops the given element from the set.
+## Removes the value from the given `Set`.
+## ```
+## numbers =
+## Set.empty {}
+## |> Set.insert 10
+## |> Set.insert 20
+## |> Set.remove 10
+##
+## has10 = Set.contains numbers 10
+## has20 = Set.contains numbers 20
+##
+## expect has10 == Bool.false
+## expect has20 == Bool.true
+## ```
remove : Set k, k -> Set k | k has Hash & Eq
remove = \@Set dict, key ->
Dict.remove dict key |> @Set
+## Test if a value is in the `Set`.
+## ```
+## Fruit : [Apple, Pear, Banana]
+##
+## fruit : Set Fruit
+## fruit =
+## Set.single Apple
+## |> Set.insert Pear
+##
+## hasApple = Set.contains fruit Apple
+## hasBanana = Set.contains fruit Banana
+##
+## expect hasApple == Bool.true
+## expect hasBanana == Bool.false
+## ```
contains : Set k, k -> Bool | k has Hash & Eq
contains = \@Set dict, key ->
Dict.contains dict key
+## Retrieve the values in a `Set` as a `List`.
+## ```
+## numbers : Set U64
+## numbers = Set.fromList [1,2,3,4,5]
+##
+## values = [1,2,3,4,5]
+##
+## expect Set.toList numbers == values
+## ```
toList : Set k -> List k | k has Hash & Eq
toList = \@Set dict ->
Dict.keys dict
+## Create a `Set` from a `List` of values.
+## ```
+## values =
+## Set.empty {}
+## |> Set.insert Banana
+## |> Set.insert Apple
+## |> Set.insert Pear
+##
+## expect Set.fromList [Pear, Apple, Banana] == values
+## ```
fromList : List k -> Set k | k has Hash & Eq
fromList = \list ->
initial = @Set (Dict.withCapacity (List.len list))
List.walk list initial insert
+## Combine two `Set` collection by keeping the
+## [union](https://en.wikipedia.org/wiki/Union_(set_theory))
+## of all the values pairs. This means that all of the values in both `Set`s
+## will be combined.
+## ```
+## set1 = Set.single Left
+## set2 = Set.single Right
+##
+## expect Set.union set1 set2 == Set.fromList [Left, Right]
+## ```
union : Set k, Set k -> Set k | k has Hash & Eq
union = \@Set dict1, @Set dict2 ->
Dict.insertAll dict1 dict2 |> @Set
+## Combine two `Set`s by keeping the [intersection](https://en.wikipedia.org/wiki/Intersection_(set_theory))
+## of all the values pairs. This means that we keep only those values that are
+## in both `Set`s.
+## ```
+## set1 = Set.fromList [Left, Other]
+## set2 = Set.fromList [Left, Right]
+##
+## expect Set.intersection set1 set2 == Set.single Left
+## ```
intersection : Set k, Set k -> Set k | k has Hash & Eq
intersection = \@Set dict1, @Set dict2 ->
Dict.keepShared dict1 dict2 |> @Set
+## Remove the values in the first `Set` that are also in the second `Set`
+## using the [set difference](https://en.wikipedia.org/wiki/Complement_(set_theory)#Relative_complement)
+## of the values. This means that we will be left with only those values that
+## are in the first and not in the second.
+## ```
+## first = Set.fromList [Left, Right, Up, Down]
+## second = Set.fromList [Left, Right]
+##
+## expect Set.difference first second == Set.fromList [Up, Down]
+## ```
difference : Set k, Set k -> Set k | k has Hash & Eq
difference = \@Set dict1, @Set dict2 ->
Dict.removeAll dict1 dict2 |> @Set
+## Iterate through the values of a given `Set` and build a value.
+## ```
+## values = Set.fromList ["March", "April", "May"]
+##
+## startsWithLetterM = \month ->
+## when Str.toUtf8 month is
+## ['M', ..] -> Bool.true
+## _ -> Bool.false
+##
+## reduce = \state, k ->
+## if startsWithLetterM k then
+## state + 1
+## else
+## state
+##
+## result = Set.walk values 0 reduce
+##
+## expect result == 2
+## ```
walk : Set k, state, (state, k -> state) -> state | k has Hash & Eq
walk = \@Set dict, state, step ->
Dict.walk dict state (\s, k, _ -> step s k)
+## Iterate through the values of a given `Set` and build a value, can stop
+## iterating part way through the collection.
+## ```
+## numbers = Set.fromList [1,2,3,4,5,6,42,7,8,9,10]
+##
+## find42 = \state, k ->
+## if k == 42 then
+## Break FoundTheAnswer
+## else
+## Continue state
+##
+## result = Set.walkUntil numbers NotFound find42
+##
+## expect result == FoundTheAnswer
+## ```
walkUntil : Set k, state, (state, k -> [Continue state, Break state]) -> state | k has Hash & Eq
walkUntil = \@Set dict, state, step ->
Dict.walkUntil dict state (\s, k, _ -> step s k)
diff --git a/crates/compiler/builtins/roc/Str.roc b/crates/compiler/builtins/roc/Str.roc
index 7d5456e307..4744dc245f 100644
--- a/crates/compiler/builtins/roc/Str.roc
+++ b/crates/compiler/builtins/roc/Str.roc
@@ -1,14 +1,13 @@
-## Working with Unicode strings in Roc.
##
-## ### Unicode
+## ## Working with Unicode strings in Roc
##
## Unicode can represent text values which span multiple languages, symbols, and emoji.
## Here are some valid Roc strings:
-##
+## ```
## "Roc!"
## "鹏"
## "🕊"
-##
+## ```
## Every Unicode string is a sequence of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster).
## An extended grapheme cluster represents what a person reading a string might
## call a "character" - like "A" or "ö" or "👩👩👦👦".
@@ -17,11 +16,11 @@
## 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:
-##
-## Str.countGraphemes "Roc!"
-## Str.countGraphemes "折り紙"
-## Str.countGraphemes "🕊"
-##
+## ```
+## Str.countGraphemes "Roc!"
+## Str.countGraphemes "折り紙"
+## Str.countGraphemes "🕊"
+## ```
## > The `countGraphemes` function walks through the entire string to get its answer,
## > so if you want to check whether a string is empty, you'll get much better performance
## > by calling `Str.isEmpty myStr` instead of `Str.countGraphemes myStr == 0`.
@@ -31,23 +30,23 @@
## If you put a `\` in a Roc string literal, it begins an *escape sequence*.
## An escape sequence is a convenient way to insert certain strings into other strings.
## For example, suppose you write this Roc string:
-##
-## "I took the one less traveled by,\nAnd that has made all the difference."
-##
+## ```
+## "I took the one less traveled by,\nAnd that has made all the difference."
+## ```
## The `"\n"` in the middle will insert a line break into this string. There are
## other ways of getting a line break in there, but `"\n"` is the most common.
##
-## Another way you could insert a newlines is by writing `\u{0x0A}` instead of `\n`.
+## Another way you could insert a newlines is by writing `\u(0A)` instead of `\n`.
## That would result in the same string, because the `\u` escape sequence inserts
## [Unicode code points](https://unicode.org/glossary/#code_point) directly into
## the string. The Unicode code point 10 is a newline, and 10 is `0A` in hexadecimal.
-## `0x0A` is a Roc hexadecimal literal, and `\u` escape sequences are always
-## followed by a hexadecimal literal inside `{` and `}` like this.
+## `\u` escape sequences are always followed by a hexadecimal number inside `(` and `)`
+## like this.
##
-## As another example, `"R\u{0x6F}c"` is the same string as `"Roc"`, because
-## `"\u{0x6F}"` corresponds to the Unicode code point for lowercase `o`. If you
+## As another example, `"R\u(6F)c"` is the same string as `"Roc"`, because
+## `"\u(6F)"` corresponds to the Unicode code point for lowercase `o`. If you
## want to [spice things up a bit](https://en.wikipedia.org/wiki/Metal_umlaut),
-## you can write `"R\u{0xF6}c"` as an alternative way to get the string `"Röc"\.
+## you can write `"R\u(F6)c"` as an alternative way to get the string `"Röc"\.
##
## Roc strings also support these escape sequences:
##
@@ -58,12 +57,11 @@
## * `\v` - [vertical tab](https://en.wikipedia.org/wiki/Tab_key#Tab_characters)
##
## You can also use escape sequences to insert named strings into other strings, like so:
-##
-## name = "Lee"
-## city = "Roctown"
-##
-## greeting = "Hello there, \(name)! Welcome to \(city)."
-##
+## ```
+## name = "Lee"
+## city = "Roctown"
+## greeting = "Hello there, \(name)! Welcome to \(city)."
+## ```
## Here, `greeting` will become the string `"Hello there, Lee! Welcome to Roctown."`.
## This is known as [string interpolation](https://en.wikipedia.org/wiki/String_interpolation),
## and you can use it as many times as you like inside a string. The name
@@ -111,6 +109,7 @@ interface Str
splitLast,
walkUtf8WithIndex,
reserve,
+ releaseExcessCapacity,
appendScalar,
walkScalars,
walkScalarsUntil,
@@ -125,7 +124,6 @@ interface Str
Num.{ Nat, Num, U8, U16, U32, U64, U128, I8, I16, I32, I64, I128, F32, F64, Dec },
]
-## Test
Utf8ByteProblem : [
InvalidStartByte,
UnexpectedEndOfSequence,
@@ -138,16 +136,18 @@ Utf8ByteProblem : [
Utf8Problem : { byteIndex : Nat, problem : Utf8ByteProblem }
## Returns [Bool.true] if the string is empty, and [Bool.false] otherwise.
-##
-## expect Str.isEmpty "hi!" == Bool.false
-## expect Str.isEmpty "" == Bool.true
+## ```
+## expect Str.isEmpty "hi!" == Bool.false
+## expect Str.isEmpty "" == Bool.true
+## ```
isEmpty : Str -> Bool
## Concatenates two strings together.
-##
-## expect Str.concat "ab" "cd" == "abcd"
-## expect Str.concat "hello" "" == "hello"
-## expect Str.concat "" "" == ""
+## ```
+## expect Str.concat "ab" "cd" == "abcd"
+## expect Str.concat "hello" "" == "hello"
+## expect Str.concat "" "" == ""
+## ```
concat : Str, Str -> Str
## Returns a string of the specified capacity without any content.
@@ -155,9 +155,10 @@ withCapacity : Nat -> Str
## Combines a [List] of strings into a single string, with a separator
## string in between each.
-##
-## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
-## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
+## ```
+## expect Str.joinWith ["one", "two", "three"] ", " == "one, two, three"
+## expect Str.joinWith ["1", "2", "3", "4"] "." == "1.2.3.4"
+## ```
joinWith : List Str, Str -> Str
## Split a string around a separator.
@@ -165,20 +166,22 @@ joinWith : List Str, Str -> Str
## Passing `""` for the separator is not useful;
## it returns the original string wrapped in a [List]. To split a string
## into its individual [graphemes](https://stackoverflow.com/a/27331885/4200103), use `Str.graphemes`
-##
-## expect Str.split "1,2,3" "," == ["1","2","3"]
-## expect Str.split "1,2,3" "" == ["1,2,3"]
+## ```
+## expect Str.split "1,2,3" "," == ["1","2","3"]
+## expect Str.split "1,2,3" "" == ["1,2,3"]
+## ```
split : Str, Str -> List Str
## Repeats a string the given number of times.
-##
-## expect Str.repeat "z" 3 == "zzz"
-## expect Str.repeat "na" 8 == "nananananananana"
-##
+## ```
+## expect Str.repeat "z" 3 == "zzz"
+## expect Str.repeat "na" 8 == "nananananananana"
+## ```
## Returns `""` when given `""` for the string or `0` for the count.
-##
-## expect Str.repeat "" 10 == ""
-## expect Str.repeat "anything" 0 == ""
+## ```
+## expect Str.repeat "" 10 == ""
+## expect Str.repeat "anything" 0 == ""
+## ```
repeat : Str, Nat -> Str
## Counts the number of [extended grapheme clusters](http://www.unicode.org/glossary/#extended_grapheme_cluster)
@@ -186,11 +189,11 @@ repeat : Str, Nat -> Str
##
## Note that the number of extended grapheme clusters can be different from the number
## of visual glyphs rendered! Consider the following examples:
-##
-## expect Str.countGraphemes "Roc" == 3
-## expect Str.countGraphemes "👩👩👦👦" == 4
-## expect Str.countGraphemes "🕊" == 1
-##
+## ```
+## expect Str.countGraphemes "Roc" == 3
+## expect Str.countGraphemes "👩👩👦👦" == 4
+## expect Str.countGraphemes "🕊" == 1
+## ```
## Note that "👩👩👦👦" takes up 4 graphemes (even though visually it appears as a single
## glyph) because under the hood it's represented using an emoji modifier sequence.
## In contrast, "🕊" only takes up 1 grapheme because under the hood it's represented
@@ -205,12 +208,15 @@ graphemes : Str -> List Str
##
## If the given string is empty, or if the given [U32] is not a valid
## code point, returns [Bool.false].
+## ```
+## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
+## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
+## expect !Str.startsWithScalar "" 40527
+## ```
##
-## expect Str.startsWithScalar "鹏 means 'roc'" 40527 # "鹏" is Unicode scalar 40527
-## expect !Str.startsWithScalar "9" 9 # the Unicode scalar for "9" is 57, not 9
-## expect !Str.startsWithScalar "" 40527
+## ## Performance Details
##
-## **Performance Note:** This runs slightly faster than [Str.startsWith], so
+## This runs slightly faster than [Str.startsWith], so
## if you want to check whether a string begins with something that's representable
## in a single code point, you can use (for example) `Str.startsWithScalar '鹏'`
## instead of `Str.startsWith "鹏"`. ('鹏' evaluates to the [U32] value `40527`.)
@@ -225,36 +231,39 @@ startsWithScalar : Str, U32 -> Bool
##
## (Roc strings contain only scalar values, not [surrogate code points](https://unicode.org/glossary/#surrogate_code_point),
## so this is equivalent to returning a list of the string's [code points](https://unicode.org/glossary/#code_point).)
-##
-## expect Str.toScalars "Roc" == [82, 111, 99]
-## expect Str.toScalars "鹏" == [40527]
-## expect Str.toScalars "சி" == [2970, 3007]
-## expect Str.toScalars "🐦" == [128038]
-## expect Str.toScalars "👩👩👦👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
-## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
-## expect Str.toScalars "" == []
+## ```
+## expect Str.toScalars "Roc" == [82, 111, 99]
+## expect Str.toScalars "鹏" == [40527]
+## expect Str.toScalars "சி" == [2970, 3007]
+## expect Str.toScalars "🐦" == [128038]
+## expect Str.toScalars "👩👩👦👦" == [128105, 8205, 128105, 8205, 128102, 8205, 128102]
+## expect Str.toScalars "I ♥ Roc" == [73, 32, 9829, 32, 82, 111, 99]
+## expect Str.toScalars "" == []
+## ```
toScalars : Str -> List U32
## Returns a [List] of the string's [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit).
## (To split the string into a [List] of smaller [Str] values instead of [U8] values,
## see [Str.split].)
-##
-## expect Str.toUtf8 "Roc" == [82, 111, 99]
-## expect Str.toUtf8 "鹏" == [233, 185, 143]
-## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
-## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
+## ```
+## expect Str.toUtf8 "Roc" == [82, 111, 99]
+## expect Str.toUtf8 "鹏" == [233, 185, 143]
+## expect Str.toUtf8 "சி" == [224, 174, 154, 224, 174, 191]
+## expect Str.toUtf8 "🐦" == [240, 159, 144, 166]
+## ```
toUtf8 : Str -> List U8
## Converts a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit) to a string.
##
## Returns `Err` if the given bytes are invalid UTF-8, and returns `Ok ""` when given `[]`.
-##
-## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
-## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
-## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
-## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
-## expect Str.fromUtf8 [] == Ok ""
-## expect Str.fromUtf8 [255] |> Result.isErr
+## ```
+## expect Str.fromUtf8 [82, 111, 99] == Ok "Roc"
+## expect Str.fromUtf8 [233, 185, 143] == Ok "鹏"
+## expect Str.fromUtf8 [224, 174, 154, 224, 174, 191] == Ok "சி"
+## expect Str.fromUtf8 [240, 159, 144, 166] == Ok "🐦"
+## expect Str.fromUtf8 [] == Ok ""
+## expect Str.fromUtf8 [255] |> Result.isErr
+## ```
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]
fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
@@ -266,8 +275,9 @@ fromUtf8 = \bytes ->
## Encode part of a [List] of [U8] UTF-8 [code units](https://unicode.org/glossary/#code_unit)
## into a [Str]
-##
-## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
+## ```
+## expect Str.fromUtf8Range [72, 105, 80, 103] { start : 0, count : 2 } == Ok "Hi"
+## ```
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]
fromUtf8Range = \bytes, config ->
if config.start + config.count <= List.len bytes then
@@ -290,57 +300,65 @@ FromUtf8Result : {
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
## Check if the given [Str] starts with a value.
-##
-## expect Str.startsWith "ABC" "A" == Bool.true
-## expect Str.startsWith "ABC" "X" == Bool.false
+## ```
+## expect Str.startsWith "ABC" "A" == Bool.true
+## expect Str.startsWith "ABC" "X" == Bool.false
+## ```
startsWith : Str, Str -> Bool
## Check if the given [Str] ends with a value.
-##
-## expect Str.endsWith "ABC" "C" == Bool.true
-## expect Str.endsWith "ABC" "X" == Bool.false
+## ```
+## expect Str.endsWith "ABC" "C" == Bool.true
+## expect Str.endsWith "ABC" "X" == Bool.false
+## ```
endsWith : Str, Str -> Bool
## Return the [Str] with all whitespace removed from both the beginning
## as well as the end.
-##
-## expect Str.trim " Hello \n\n" == "Hello"
+## ```
+## expect Str.trim " Hello \n\n" == "Hello"
+## ```
trim : Str -> Str
## Return the [Str] with all whitespace removed from the beginning.
-##
-## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
+## ```
+## expect Str.trimLeft " Hello \n\n" == "Hello \n\n"
+## ```
trimLeft : Str -> Str
## Return the [Str] with all whitespace removed from the end.
-##
-## expect Str.trimRight " Hello \n\n" == " Hello"
+## ```
+## expect Str.trimRight " Hello \n\n" == " Hello"
+## ```
trimRight : Str -> Str
## Encode a [Str] to a [Dec]. A [Dec] value is a 128-bit decimal
## [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic).
-##
-## expect Str.toDec "10" == Ok 10dec
-## expect Str.toDec "-0.25" == Ok -0.25dec
-## expect Str.toDec "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toDec "10" == Ok 10dec
+## expect Str.toDec "-0.25" == Ok -0.25dec
+## expect Str.toDec "not a number" == Err InvalidNumStr
+## ```
toDec : Str -> Result Dec [InvalidNumStr]
toDec = \string -> strToNumHelp string
## Encode a [Str] to a [F64]. A [F64] value is a 64-bit
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
## specified with a `f64` suffix.
-##
-## expect Str.toF64 "0.10" == Ok 0.10f64
-## expect Str.toF64 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toF64 "0.10" == Ok 0.10f64
+## expect Str.toF64 "not a number" == Err InvalidNumStr
+## ```
toF64 : Str -> Result F64 [InvalidNumStr]
toF64 = \string -> strToNumHelp string
## Encode a [Str] to a [F32].A [F32] value is a 32-bit
## [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) and can be
## specified with a `f32` suffix.
-##
-## expect Str.toF32 "0.10" == Ok 0.10f32
-## expect Str.toF32 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toF32 "0.10" == Ok 0.10f32
+## expect Str.toF32 "not a number" == Err InvalidNumStr
+## ```
toF32 : Str -> Result F32 [InvalidNumStr]
toF32 = \string -> strToNumHelp string
@@ -356,20 +374,22 @@ toF32 = \string -> strToNumHelp string
## Calling `Str.toNat "9_000_000_000"` on a 64-bit system will return
## 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 smaller than `Num.maxU64`.
-##
-## expect Str.toNat "9_000_000_000" == Ok 9000000000
-## expect Str.toNat "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toNat "9_000_000_000" == Ok 9000000000
+## expect Str.toNat "not a number" == Err InvalidNumStr
+## ```
toNat : Str -> Result Nat [InvalidNumStr]
toNat = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U128] integer. A [U128] value can hold numbers
## from `0` to `340_282_366_920_938_463_463_374_607_431_768_211_455` (over
## 340 undecillion). It can be specified with a u128 suffix.
-##
-## expect Str.toU128 "1500" == Ok 1500u128
-## expect Str.toU128 "0.1" == Err InvalidNumStr
-## expect Str.toU128 "-1" == Err InvalidNumStr
-## expect Str.toU128 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toU128 "1500" == Ok 1500u128
+## expect Str.toU128 "0.1" == Err InvalidNumStr
+## expect Str.toU128 "-1" == Err InvalidNumStr
+## expect Str.toU128 "not a number" == Err InvalidNumStr
+## ```
toU128 : Str -> Result U128 [InvalidNumStr]
toU128 = \string -> strToNumHelp string
@@ -377,96 +397,105 @@ toU128 = \string -> strToNumHelp string
## from `-170_141_183_460_469_231_731_687_303_715_884_105_728` to
## `170_141_183_460_469_231_731_687_303_715_884_105_727`. It can be specified
## with a i128 suffix.
-##
-## expect Str.toI128 "1500" == Ok 1500i128
-## expect Str.toI128 "-1" == Ok -1i128
-## expect Str.toI128 "0.1" == Err InvalidNumStr
-## expect Str.toI128 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toI128 "1500" == Ok 1500i128
+## expect Str.toI128 "-1" == Ok -1i128
+## expect Str.toI128 "0.1" == Err InvalidNumStr
+## expect Str.toI128 "not a number" == Err InvalidNumStr
+## ```
toI128 : Str -> Result I128 [InvalidNumStr]
toI128 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U64] integer. A [U64] value can hold numbers
## from `0` to `18_446_744_073_709_551_615` (over 18 quintillion). It
## can be specified with a u64 suffix.
-##
-## expect Str.toU64 "1500" == Ok 1500u64
-## expect Str.toU64 "0.1" == Err InvalidNumStr
-## expect Str.toU64 "-1" == Err InvalidNumStr
-## expect Str.toU64 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toU64 "1500" == Ok 1500u64
+## expect Str.toU64 "0.1" == Err InvalidNumStr
+## expect Str.toU64 "-1" == Err InvalidNumStr
+## expect Str.toU64 "not a number" == Err InvalidNumStr
+## ```
toU64 : Str -> Result U64 [InvalidNumStr]
toU64 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I64] integer. A [I64] value can hold numbers
## from `-9_223_372_036_854_775_808` to `9_223_372_036_854_775_807`. It can be
## specified with a i64 suffix.
-##
-## expect Str.toI64 "1500" == Ok 1500i64
-## expect Str.toI64 "-1" == Ok -1i64
-## expect Str.toI64 "0.1" == Err InvalidNumStr
-## expect Str.toI64 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toI64 "1500" == Ok 1500i64
+## expect Str.toI64 "-1" == Ok -1i64
+## expect Str.toI64 "0.1" == Err InvalidNumStr
+## expect Str.toI64 "not a number" == Err InvalidNumStr
+## ```
toI64 : Str -> Result I64 [InvalidNumStr]
toI64 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U32] integer. A [U32] value can hold numbers
## from `0` to `4_294_967_295` (over 4 billion). It can be specified with
## a u32 suffix.
-##
-## expect Str.toU32 "1500" == Ok 1500u32
-## expect Str.toU32 "0.1" == Err InvalidNumStr
-## expect Str.toU32 "-1" == Err InvalidNumStr
-## expect Str.toU32 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toU32 "1500" == Ok 1500u32
+## expect Str.toU32 "0.1" == Err InvalidNumStr
+## expect Str.toU32 "-1" == Err InvalidNumStr
+## expect Str.toU32 "not a number" == Err InvalidNumStr
+## ```
toU32 : Str -> Result U32 [InvalidNumStr]
toU32 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I32] integer. A [I32] value can hold numbers
## from `-2_147_483_648` to `2_147_483_647`. It can be
## specified with a i32 suffix.
-##
-## expect Str.toI32 "1500" == Ok 1500i32
-## expect Str.toI32 "-1" == Ok -1i32
-## expect Str.toI32 "0.1" == Err InvalidNumStr
-## expect Str.toI32 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toI32 "1500" == Ok 1500i32
+## expect Str.toI32 "-1" == Ok -1i32
+## expect Str.toI32 "0.1" == Err InvalidNumStr
+## expect Str.toI32 "not a number" == Err InvalidNumStr
+## ```
toI32 : Str -> Result I32 [InvalidNumStr]
toI32 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U16] integer. A [U16] value can hold numbers
## from `0` to `65_535`. It can be specified with a u16 suffix.
-##
-## expect Str.toU16 "1500" == Ok 1500u16
-## expect Str.toU16 "0.1" == Err InvalidNumStr
-## expect Str.toU16 "-1" == Err InvalidNumStr
-## expect Str.toU16 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toU16 "1500" == Ok 1500u16
+## expect Str.toU16 "0.1" == Err InvalidNumStr
+## expect Str.toU16 "-1" == Err InvalidNumStr
+## expect Str.toU16 "not a number" == Err InvalidNumStr
+## ```
toU16 : Str -> Result U16 [InvalidNumStr]
toU16 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I16] integer. A [I16] value can hold numbers
## from `-32_768` to `32_767`. It can be
## specified with a i16 suffix.
-##
-## expect Str.toI16 "1500" == Ok 1500i16
-## expect Str.toI16 "-1" == Ok -1i16
-## expect Str.toI16 "0.1" == Err InvalidNumStr
-## expect Str.toI16 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toI16 "1500" == Ok 1500i16
+## expect Str.toI16 "-1" == Ok -1i16
+## expect Str.toI16 "0.1" == Err InvalidNumStr
+## expect Str.toI16 "not a number" == Err InvalidNumStr
+## ```
toI16 : Str -> Result I16 [InvalidNumStr]
toI16 = \string -> strToNumHelp string
## Encode a [Str] to an unsigned [U8] integer. A [U8] value can hold numbers
## from `0` to `255`. It can be specified with a u8 suffix.
-##
-## expect Str.toU8 "250" == Ok 250u8
-## expect Str.toU8 "-0.1" == Err InvalidNumStr
-## expect Str.toU8 "not a number" == Err InvalidNumStr
-## expect Str.toU8 "1500" == Err InvalidNumStr
+## ```
+## expect Str.toU8 "250" == Ok 250u8
+## expect Str.toU8 "-0.1" == Err InvalidNumStr
+## expect Str.toU8 "not a number" == Err InvalidNumStr
+## expect Str.toU8 "1500" == Err InvalidNumStr
+## ```
toU8 : Str -> Result U8 [InvalidNumStr]
toU8 = \string -> strToNumHelp string
## Encode a [Str] to a signed [I8] integer. A [I8] value can hold numbers
## from `-128` to `127`. It can be
## specified with a i8 suffix.
-##
-## expect Str.toI8 "-15" == Ok -15i8
-## expect Str.toI8 "150.00" == Err InvalidNumStr
-## expect Str.toI8 "not a number" == Err InvalidNumStr
+## ```
+## expect Str.toI8 "-15" == Ok -15i8
+## expect Str.toI8 "150.00" == Err InvalidNumStr
+## expect Str.toI8 "not a number" == Err InvalidNumStr
+## ```
toI8 : Str -> Result I8 [InvalidNumStr]
toI8 = \string -> strToNumHelp string
@@ -474,8 +503,9 @@ toI8 = \string -> strToNumHelp string
getUnsafe : Str, Nat -> U8
## Gives the number of bytes in a [Str] value.
-##
-## expect Str.countUtf8Bytes "Hello World" == 11
+## ```
+## expect Str.countUtf8Bytes "Hello World" == 11
+## ```
countUtf8Bytes : Str -> Nat
## string slice that does not do bounds checking or utf-8 verification
@@ -483,9 +513,10 @@ substringUnsafe : Str, Nat, Nat -> Str
## Returns the given [Str] with each occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
-##
-## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
-## expect Str.replaceEach "not here" "/" "_" == Err NotFound
+## ```
+## expect Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
+## expect Str.replaceEach "not here" "/" "_" == Err NotFound
+## ```
replaceEach : Str, Str, Str -> Result Str [NotFound]
replaceEach = \haystack, needle, flower ->
when splitFirst haystack needle is
@@ -515,9 +546,10 @@ expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
## Returns the given [Str] with the first occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
-##
-## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
-## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
+## ```
+## expect Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
+## expect Str.replaceFirst "no slashes here" "/" "_" == Err NotFound
+## ```
replaceFirst : Str, Str, Str -> Result Str [NotFound]
replaceFirst = \haystack, needle, flower ->
when splitFirst haystack needle is
@@ -530,9 +562,10 @@ expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
## Returns the given [Str] with the last occurrence of a substring replaced.
## Returns [Err NotFound] if the substring is not found.
-##
-## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
-## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
+## ```
+## expect Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
+## expect Str.replaceLast "no slashes here" "/" "_" == Err NotFound
+## ```
replaceLast : Str, Str, Str -> Result Str [NotFound]
replaceLast = \haystack, needle, flower ->
when splitLast haystack needle is
@@ -546,9 +579,10 @@ expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
## Returns the given [Str] before the first occurrence of a [delimiter](https://www.computerhope.com/jargon/d/delimite.htm), as well
## as the rest of the string after that occurrence.
## Returns [ Err NotFound] if the delimiter is not found.
-##
-## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
-## expect Str.splitFirst "no slashes here" "/" == Err NotFound
+## ```
+## expect Str.splitFirst "foo/bar/baz" "/" == Ok { before: "foo", after: "bar/baz" }
+## expect Str.splitFirst "no slashes here" "/" == Err NotFound
+## ```
splitFirst : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitFirst = \haystack, needle ->
when firstMatch haystack needle is
@@ -599,9 +633,10 @@ firstMatchHelp = \haystack, needle, index, lastPossible ->
## Returns the given [Str] before the last occurrence of a delimiter, as well as
## the rest of the string after that occurrence.
## Returns [Err NotFound] if the delimiter is not found.
-##
-## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
-## expect Str.splitLast "no slashes here" "/" == Err NotFound
+## ```
+## expect Str.splitLast "foo/bar/baz" "/" == Ok { before: "foo/bar", after: "baz" }
+## expect Str.splitLast "no slashes here" "/" == Err NotFound
+## ```
splitLast : Str, Str -> Result { before : Str, after : Str } [NotFound]
splitLast = \haystack, needle ->
when lastMatch haystack needle is
@@ -690,10 +725,11 @@ matchesAtHelp = \state ->
## Walks over the `UTF-8` bytes of the given [Str] and calls a function to update
## state for each byte. The index for that byte in the string is provided
## to the update function.
-##
-## f : List U8, U8, Nat -> List U8
-## f = \state, byte, _ -> List.append state byte
-## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
+## ```
+## f : List U8, U8, Nat -> List U8
+## f = \state, byte, _ -> List.append state byte
+## expect Str.walkUtf8WithIndex "ABC" [] f == [65, 66, 67]
+## ```
walkUtf8WithIndex : Str, state, (state, U8, Nat -> state) -> state
walkUtf8WithIndex = \string, state, step ->
walkUtf8WithIndexHelp string state step 0 (Str.countUtf8Bytes string)
@@ -711,14 +747,19 @@ walkUtf8WithIndexHelp = \string, state, step, index, length ->
## Enlarge a string for at least the given number additional bytes.
reserve : Str, Nat -> Str
+## Shrink the memory footprint of a str such that it's capacity and length are equal.
+## Note: This will also convert seamless slices to regular lists.
+releaseExcessCapacity : Str -> Str
+
## is UB when the scalar is invalid
appendScalarUnsafe : Str, U32 -> Str
## Append a [U32] scalar to the given string. If the given scalar is not a valid
## unicode value, it returns [Err InvalidScalar].
-##
-## expect Str.appendScalar "H" 105 == Ok "Hi"
-## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
+## ```
+## expect Str.appendScalar "H" 105 == Ok "Hi"
+## expect Str.appendScalar "😢" 0xabcdef == Err InvalidScalar
+## ```
appendScalar : Str, U32 -> Result Str [InvalidScalar]
appendScalar = \string, scalar ->
if isValidScalar scalar then
@@ -734,10 +775,11 @@ getScalarUnsafe : Str, Nat -> { scalar : U32, bytesParsed : Nat }
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
-##
-## f : List U32, U32 -> List U32
-## f = \state, scalar -> List.append state scalar
-## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
+## ```
+## f : List U32, U32 -> List U32
+## f = \state, scalar -> List.append state scalar
+## expect Str.walkScalars "ABC" [] f == [65, 66, 67]
+## ```
walkScalars : Str, state, (state, U32 -> state) -> state
walkScalars = \string, init, step ->
walkScalarsHelp string init step 0 (Str.countUtf8Bytes string)
@@ -754,16 +796,17 @@ walkScalarsHelp = \string, state, step, index, length ->
## Walks over the unicode [U32] values for the given [Str] and calls a function
## to update state for each.
-##
-## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
-## f = \state, scalar ->
-## check = 66
-## if scalar == check then
-## Break [check]
-## else
-## Continue (List.append state scalar)
-## expect Str.walkScalarsUntil "ABC" [] f == [66]
-## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
+## ```
+## f : List U32, U32 -> [Break (List U32), Continue (List U32)]
+## f = \state, scalar ->
+## check = 66
+## if scalar == check then
+## Break [check]
+## else
+## Continue (List.append state scalar)
+## expect Str.walkScalarsUntil "ABC" [] f == [66]
+## expect Str.walkScalarsUntil "AxC" [] f == [65, 120, 67]
+## ```
walkScalarsUntil : Str, state, (state, U32 -> [Break state, Continue state]) -> state
walkScalarsUntil = \string, init, step ->
walkScalarsUntilHelp string init step 0 (Str.countUtf8Bytes string)
@@ -795,7 +838,8 @@ strToNumHelp = \string ->
Err InvalidNumStr
## Adds a prefix to the given [Str].
-##
-## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
+## ```
+## expect Str.withPrefix "Awesome" "Roc" == "RocAwesome"
+## ```
withPrefix : Str, Str -> Str
withPrefix = \str, prefix -> Str.concat prefix str
diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs
index 99b19dfddd..d96496eae9 100644
--- a/crates/compiler/builtins/src/bitcode.rs
+++ b/crates/compiler/builtins/src/bitcode.rs
@@ -1,73 +1,6 @@
use roc_module::symbol::Symbol;
use roc_target::TargetInfo;
use std::ops::Index;
-use tempfile::NamedTempFile;
-
-pub const HOST_WASM: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-wasm32.o"));
-// TODO: in the future, we should use Zig's cross-compilation to generate and store these
-// for all targets, so that we can do cross-compilation!
-#[cfg(unix)]
-pub const HOST_UNIX: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/bitcode/builtins-host.o"));
-#[cfg(windows)]
-pub const HOST_WINDOWS: &[u8] = include_bytes!(concat!(
- env!("OUT_DIR"),
- "/bitcode/builtins-windows-x86_64.obj"
-));
-
-pub fn host_wasm_tempfile() -> std::io::Result {
- let tempfile = tempfile::Builder::new()
- .prefix("host_bitcode")
- .suffix(".wasm")
- .rand_bytes(8)
- .tempfile()?;
-
- std::fs::write(tempfile.path(), HOST_WASM)?;
-
- Ok(tempfile)
-}
-
-#[cfg(unix)]
-fn host_unix_tempfile() -> std::io::Result {
- let tempfile = tempfile::Builder::new()
- .prefix("host_bitcode")
- .suffix(".o")
- .rand_bytes(8)
- .tempfile()?;
-
- std::fs::write(tempfile.path(), HOST_UNIX)?;
-
- Ok(tempfile)
-}
-
-#[cfg(windows)]
-fn host_windows_tempfile() -> std::io::Result {
- let tempfile = tempfile::Builder::new()
- .prefix("host_bitcode")
- .suffix(".obj")
- .rand_bytes(8)
- .tempfile()?;
-
- std::fs::write(tempfile.path(), HOST_WINDOWS)?;
-
- Ok(tempfile)
-}
-
-pub fn host_tempfile() -> std::io::Result {
- #[cfg(unix)]
- {
- host_unix_tempfile()
- }
-
- #[cfg(windows)]
- {
- host_windows_tempfile()
- }
-
- #[cfg(not(any(windows, unix)))]
- {
- unreachable!()
- }
-}
#[derive(Debug, Default, Copy, Clone)]
pub struct IntrinsicName {
@@ -356,8 +289,16 @@ pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.
pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName =
float_intrinsic!("roc_builtins.num.mul_with_overflow");
+pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName =
+ int_intrinsic!("roc_builtins.num.count_leading_zero_bits");
+pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName =
+ int_intrinsic!("roc_builtins.num.count_trailing_zero_bits");
+pub const NUM_COUNT_ONE_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_one_bits");
+
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_BYTES_TO_U64: &str = "roc_builtins.num.bytes_to_u64";
+pub const NUM_BYTES_TO_U128: &str = "roc_builtins.num.bytes_to_u128";
pub const STR_INIT: &str = "roc_builtins.str.init";
pub const STR_COUNT_SEGMENTS: &str = "roc_builtins.str.count_segments";
@@ -392,6 +333,8 @@ pub const STR_GET_SCALAR_UNSAFE: &str = "roc_builtins.str.get_scalar_unsafe";
pub const STR_CLONE_TO: &str = "roc_builtins.str.clone_to";
pub const STR_WITH_CAPACITY: &str = "roc_builtins.str.with_capacity";
pub const STR_GRAPHEMES: &str = "roc_builtins.str.graphemes";
+pub const STR_REFCOUNT_PTR: &str = "roc_builtins.str.refcount_ptr";
+pub const STR_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.str.release_excess_capacity";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
@@ -409,6 +352,9 @@ pub const LIST_IS_UNIQUE: &str = "roc_builtins.list.is_unique";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_APPEND_UNSAFE: &str = "roc_builtins.list.append_unsafe";
pub const LIST_RESERVE: &str = "roc_builtins.list.reserve";
+pub const LIST_CAPACITY: &str = "roc_builtins.list.capacity";
+pub const LIST_REFCOUNT_PTR: &str = "roc_builtins.list.refcount_ptr";
+pub const LIST_RELEASE_EXCESS_CAPACITY: &str = "roc_builtins.list.release_excess_capacity";
pub const DEC_FROM_STR: &str = "roc_builtins.dec.from_str";
pub const DEC_TO_STR: &str = "roc_builtins.dec.to_str";
diff --git a/crates/compiler/can/Cargo.toml b/crates/compiler/can/Cargo.toml
index 16efe76ab7..986c5fd732 100644
--- a/crates/compiler/can/Cargo.toml
+++ b/crates/compiler/can/Cargo.toml
@@ -1,28 +1,29 @@
[package]
name = "roc_can"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Canonicalize a roc abstract syntax tree, resolving symbols, re-ordering definitions, and preparing a module for type inference."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
roc_exhaustive = { path = "../exhaustive" }
-roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
-roc_types = { path = "../types" }
+roc_region = { path = "../region" }
roc_serialize = { path = "../serialize" }
-
-bumpalo.workspace = true
-static_assertions.workspace = true
-bitvec.workspace = true
+roc_types = { path = "../types" }
ven_pretty = { path = "../../vendor/pretty" }
+bitvec.workspace = true
+bumpalo.workspace = true
+static_assertions.workspace = true
+
[dev-dependencies]
-pretty_assertions.workspace = true
indoc.workspace = true
+pretty_assertions.workspace = true
diff --git a/crates/compiler/can/src/annotation.rs b/crates/compiler/can/src/annotation.rs
index fbfd6fc3cc..e028b750b8 100644
--- a/crates/compiler/can/src/annotation.rs
+++ b/crates/compiler/can/src/annotation.rs
@@ -448,7 +448,7 @@ pub fn find_type_def_symbols(
As(actual, _, _) => {
stack.push(&actual.value);
}
- Tuple { fields: _, ext: _ } => {
+ Tuple { elems: _, ext: _ } => {
todo!("find_type_def_symbols: Tuple");
}
Record { fields, ext } => {
@@ -872,8 +872,41 @@ fn can_annotation_help(
}
}
- Tuple { fields: _, ext: _ } => {
- todo!("tuple");
+ Tuple { elems, ext } => {
+ let (ext_type, is_implicit_openness) = can_extension_type(
+ env,
+ pol,
+ scope,
+ var_store,
+ introduced_variables,
+ local_aliases,
+ references,
+ ext,
+ roc_problem::can::ExtensionTypeKind::Record,
+ );
+
+ debug_assert!(
+ matches!(is_implicit_openness, ExtImplicitOpenness::No),
+ "tuples should never be implicitly inferred open"
+ );
+
+ debug_assert!(!elems.is_empty()); // We don't allow empty tuples
+
+ let elem_types = can_assigned_tuple_elems(
+ env,
+ pol,
+ &elems.items,
+ scope,
+ var_store,
+ introduced_variables,
+ local_aliases,
+ references,
+ );
+
+ Type::Tuple(
+ elem_types,
+ TypeExtension::from_type(ext_type, is_implicit_openness),
+ )
}
Record { fields, ext } => {
let (ext_type, is_implicit_openness) = can_extension_type(
@@ -1440,6 +1473,39 @@ fn can_assigned_fields<'a>(
field_types
}
+// TODO trim down these arguments!
+#[allow(clippy::too_many_arguments)]
+fn can_assigned_tuple_elems<'a>(
+ env: &mut Env,
+ pol: CanPolarity,
+ elems: &&[Loc>],
+ scope: &mut Scope,
+ var_store: &mut VarStore,
+ introduced_variables: &mut IntroducedVariables,
+ local_aliases: &mut VecMap,
+ references: &mut VecSet,
+) -> VecMap {
+ let mut elem_types = VecMap::with_capacity(elems.len());
+
+ for (index, loc_elem) in elems.iter().enumerate() {
+ let elem_type = can_annotation_help(
+ env,
+ pol,
+ &loc_elem.value,
+ loc_elem.region,
+ scope,
+ var_store,
+ introduced_variables,
+ local_aliases,
+ references,
+ );
+
+ elem_types.insert(index, elem_type);
+ }
+
+ elem_types
+}
+
// TODO trim down these arguments!
#[allow(clippy::too_many_arguments)]
fn can_tags<'a>(
diff --git a/crates/compiler/can/src/builtins.rs b/crates/compiler/can/src/builtins.rs
index 4e5b9f95b2..aafbff85a7 100644
--- a/crates/compiler/can/src/builtins.rs
+++ b/crates/compiler/can/src/builtins.rs
@@ -126,6 +126,7 @@ map_symbol_to_lowlevel_and_arity! {
StrGetCapacity; STR_CAPACITY; 1,
StrWithCapacity; STR_WITH_CAPACITY; 1,
StrGraphemes; STR_GRAPHEMES; 1,
+ StrReleaseExcessCapacity; STR_RELEASE_EXCESS_CAPACITY; 1,
ListLen; LIST_LEN; 1,
ListWithCapacity; LIST_WITH_CAPACITY; 1,
@@ -145,6 +146,7 @@ map_symbol_to_lowlevel_and_arity! {
ListDropAt; LIST_DROP_AT; 2,
ListSwap; LIST_SWAP; 3,
ListGetCapacity; LIST_CAPACITY; 1,
+ ListReleaseExcessCapacity; LIST_RELEASE_EXCESS_CAPACITY; 1,
ListGetUnsafe; DICT_LIST_GET_UNSAFE; 2,
@@ -187,6 +189,8 @@ map_symbol_to_lowlevel_and_arity! {
NumAsin; NUM_ASIN; 1,
NumBytesToU16; NUM_BYTES_TO_U16_LOWLEVEL; 2,
NumBytesToU32; NUM_BYTES_TO_U32_LOWLEVEL; 2,
+ NumBytesToU64; NUM_BYTES_TO_U64_LOWLEVEL; 2,
+ NumBytesToU128; NUM_BYTES_TO_U128_LOWLEVEL; 2,
NumBitwiseAnd; NUM_BITWISE_AND; 2,
NumBitwiseXor; NUM_BITWISE_XOR; 2,
NumBitwiseOr; NUM_BITWISE_OR; 2,
@@ -194,6 +198,9 @@ map_symbol_to_lowlevel_and_arity! {
NumShiftRightBy; NUM_SHIFT_RIGHT; 2,
NumShiftRightZfBy; NUM_SHIFT_RIGHT_ZERO_FILL; 2,
NumToStr; NUM_TO_STR; 1,
+ NumCountLeadingZeroBits; NUM_COUNT_LEADING_ZERO_BITS; 1,
+ NumCountTrailingZeroBits; NUM_COUNT_TRAILING_ZERO_BITS; 1,
+ NumCountOneBits; NUM_COUNT_ONE_BITS; 1,
Eq; BOOL_STRUCTURAL_EQ; 2,
NotEq; BOOL_STRUCTURAL_NOT_EQ; 2,
diff --git a/crates/compiler/can/src/copy.rs b/crates/compiler/can/src/copy.rs
index 307c435425..7da4816a45 100644
--- a/crates/compiler/can/src/copy.rs
+++ b/crates/compiler/can/src/copy.rs
@@ -1,10 +1,9 @@
use crate::{
def::Def,
expr::{
- ClosureData, Expr, Field, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData,
- WhenBranchPattern,
+ ClosureData, Expr, Field, OpaqueWrapFunctionData, StructAccessorData, WhenBranchPattern,
},
- pattern::{DestructType, ListPatterns, Pattern, RecordDestruct},
+ pattern::{DestructType, ListPatterns, Pattern, RecordDestruct, TupleDestruct},
};
use roc_module::{
ident::{Lowercase, TagName},
@@ -513,7 +512,7 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr
field: field.clone(),
},
- RecordAccessor(RecordAccessorData {
+ RecordAccessor(StructAccessorData {
name,
function_var,
record_var,
@@ -521,7 +520,7 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr
ext_var,
field_var,
field,
- }) => RecordAccessor(RecordAccessorData {
+ }) => RecordAccessor(StructAccessorData {
name: *name,
function_var: sub!(*function_var),
record_var: sub!(*record_var),
@@ -545,24 +544,6 @@ fn deep_copy_expr_help(env: &mut C, copied: &mut Vec, expr
index: *index,
},
- TupleAccessor(TupleAccessorData {
- name,
- function_var,
- tuple_var: record_var,
- closure_var,
- ext_var,
- elem_var: field_var,
- index,
- }) => TupleAccessor(TupleAccessorData {
- name: *name,
- function_var: sub!(*function_var),
- tuple_var: sub!(*record_var),
- closure_var: sub!(*closure_var),
- ext_var: sub!(*ext_var),
- elem_var: sub!(*field_var),
- index: *index,
- }),
-
RecordUpdate {
record_var,
ext_var,
@@ -794,6 +775,30 @@ fn deep_copy_pattern_help(
})
.collect(),
},
+ TupleDestructure {
+ whole_var,
+ ext_var,
+ destructs,
+ } => TupleDestructure {
+ whole_var: sub!(*whole_var),
+ ext_var: sub!(*ext_var),
+ destructs: destructs
+ .iter()
+ .map(|lrd| {
+ lrd.map(
+ |TupleDestruct {
+ destruct_index: index,
+ var,
+ typ: (tyvar, pat),
+ }: &crate::pattern::TupleDestruct| TupleDestruct {
+ destruct_index: *index,
+ var: sub!(*var),
+ typ: (sub!(*tyvar), pat.map(|p| go_help!(p))),
+ },
+ )
+ })
+ .collect(),
+ },
List {
list_var,
elem_var,
diff --git a/crates/compiler/can/src/debug/pretty_print.rs b/crates/compiler/can/src/debug/pretty_print.rs
index 0f8de5070d..9450e71dfa 100644
--- a/crates/compiler/can/src/debug/pretty_print.rs
+++ b/crates/compiler/can/src/debug/pretty_print.rs
@@ -5,7 +5,7 @@ use crate::expr::Expr::{self, *};
use crate::expr::{
ClosureData, DeclarationTag, Declarations, FunctionDef, OpaqueWrapFunctionData, WhenBranch,
};
-use crate::pattern::{Pattern, RecordDestruct};
+use crate::pattern::{Pattern, RecordDestruct, TupleDestruct};
use roc_module::symbol::{Interns, ModuleId, Symbol};
@@ -35,7 +35,10 @@ pub fn pretty_print_declarations(c: &Ctx, declarations: &Declarations) -> String
DeclarationTag::Expectation => todo!(),
DeclarationTag::ExpectationFx => todo!(),
DeclarationTag::Destructure(_) => todo!(),
- DeclarationTag::MutualRecursion { .. } => todo!(),
+ DeclarationTag::MutualRecursion { .. } => {
+ // the defs will be printed next
+ continue;
+ }
};
defs.push(def);
@@ -123,9 +126,10 @@ fn toplevel_function<'a>(
.append(f.line())
.append(f.text("\\"))
.append(f.intersperse(args, f.text(", ")))
- .append(f.text("->"))
+ .append(f.text(" ->"))
+ .group()
.append(f.line())
- .append(expr(c, EPrec::Free, f, body))
+ .append(expr(c, EPrec::Free, f, body).group())
.nest(2)
.group()
}
@@ -330,12 +334,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
} => expr(c, AppArg, f, &loc_expr.value)
.append(f.text(format!(".{}", field.as_str())))
.group(),
- TupleAccess { .. } => todo!(),
+ TupleAccess {
+ loc_expr, index, ..
+ } => expr(c, AppArg, f, &loc_expr.value)
+ .append(f.text(format!(".{index}")))
+ .group(),
OpaqueWrapFunction(OpaqueWrapFunctionData { opaque_name, .. }) => {
f.text(format!("@{}", opaque_name.as_str(c.interns)))
}
RecordAccessor(_) => todo!(),
- TupleAccessor(_) => todo!(),
RecordUpdate {
symbol, updates, ..
} => f
@@ -386,7 +393,15 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
),
Crash { .. } => todo!(),
ZeroArgumentTag { .. } => todo!(),
- OpaqueRef { .. } => todo!(),
+ OpaqueRef { name, argument, .. } => maybe_paren!(
+ Free,
+ p,
+ || true,
+ pp_sym(c, f, *name)
+ .append(f.space())
+ .append(expr(c, AppArg, f, &argument.1.value))
+ .group()
+ ),
Dbg { .. } => todo!(),
Expect { .. } => todo!(),
ExpectFx { .. } => todo!(),
@@ -505,6 +520,19 @@ fn pattern<'a>(
)
.append(f.text("}"))
.group(),
+ TupleDestructure { destructs, .. } => f
+ .text("(")
+ .append(
+ f.intersperse(
+ destructs
+ .iter()
+ .map(|l| &l.value)
+ .map(|TupleDestruct { typ: (_, p), .. }| pattern(c, Free, f, &p.value)),
+ f.text(", "),
+ ),
+ )
+ .append(f.text(")"))
+ .group(),
List { .. } => todo!(),
NumLiteral(_, n, _, _) | IntLiteral(_, _, n, _, _) | FloatLiteral(_, _, n, _, _) => {
f.text(&**n)
diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs
index 5e4e95e3bc..2910e2498a 100644
--- a/crates/compiler/can/src/def.rs
+++ b/crates/compiler/can/src/def.rs
@@ -15,7 +15,7 @@ use crate::expr::AnnotatedMark;
use crate::expr::ClosureData;
use crate::expr::Declarations;
use crate::expr::Expr::{self, *};
-use crate::expr::RecordAccessorData;
+use crate::expr::StructAccessorData;
use crate::expr::{canonicalize_expr, Output, Recursive};
use crate::pattern::{canonicalize_def_header_pattern, BindingsFromPattern, Pattern};
use crate::procedure::References;
@@ -36,6 +36,7 @@ use roc_parse::ast::AssignedField;
use roc_parse::ast::Defs;
use roc_parse::ast::ExtractSpaces;
use roc_parse::ast::TypeHeader;
+use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType;
use roc_problem::can::ShadowKind;
use roc_problem::can::{CycleEntry, Problem, RuntimeError};
@@ -45,6 +46,7 @@ use roc_types::subs::{VarStore, Variable};
use roc_types::types::AliasCommon;
use roc_types::types::AliasKind;
use roc_types::types::AliasVar;
+use roc_types::types::IndexOrField;
use roc_types::types::LambdaSet;
use roc_types::types::MemberImpl;
use roc_types::types::OptAbleType;
@@ -1995,6 +1997,16 @@ fn pattern_to_vars_by_symbol(
vars_by_symbol.insert(*opaque, expr_var);
}
+ TupleDestructure { destructs, .. } => {
+ for destruct in destructs {
+ pattern_to_vars_by_symbol(
+ vars_by_symbol,
+ &destruct.value.typ.1.value,
+ destruct.value.typ.0,
+ );
+ }
+ }
+
RecordDestructure { destructs, .. } => {
for destruct in destructs {
vars_by_symbol.insert(destruct.value.symbol, destruct.value.var);
@@ -2316,19 +2328,23 @@ fn canonicalize_pending_body<'a>(
ident: defined_symbol,
..
},
- ast::Expr::RecordAccessorFunction(field),
+ ast::Expr::AccessorFunction(field),
) => {
+ let field = match field {
+ Accessor::RecordField(field) => IndexOrField::Field((*field).into()),
+ Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()),
+ };
let (loc_can_expr, can_output) = (
Loc::at(
loc_expr.region,
- RecordAccessor(RecordAccessorData {
+ RecordAccessor(StructAccessorData {
name: *defined_symbol,
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
field_var: var_store.fresh(),
- field: (*field).into(),
+ field,
}),
),
Output::default(),
diff --git a/crates/compiler/can/src/exhaustive.rs b/crates/compiler/can/src/exhaustive.rs
index b810d554a7..1960ee2aab 100644
--- a/crates/compiler/can/src/exhaustive.rs
+++ b/crates/compiler/can/src/exhaustive.rs
@@ -10,9 +10,10 @@ use roc_module::ident::{Lowercase, TagIdIntType, TagName};
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{
- Content, FlatType, GetSubsSlice, RedundantMark, Subs, SubsFmtContent, Variable,
+ Content, FlatType, GetSubsSlice, RedundantMark, SortedTagsIterator, Subs, SubsFmtContent,
+ Variable,
};
-use roc_types::types::AliasKind;
+use roc_types::types::{gather_tags_unsorted_iter, AliasKind};
pub use roc_exhaustive::Context as ExhaustiveContext;
@@ -80,6 +81,8 @@ enum IndexCtor<'a> {
Opaque,
/// Index a record type. The arguments are the types of the record fields.
Record(&'a [Lowercase]),
+ /// Index a tuple type.
+ Tuple,
/// Index a guard constructor. The arguments are a faux guard pattern, and then the real
/// pattern being guarded. E.g. `A B if g` becomes Guard { [True, (A B)] }.
Guard,
@@ -112,6 +115,7 @@ impl<'a> IndexCtor<'a> {
}
RenderAs::Opaque => Self::Opaque,
RenderAs::Record(fields) => Self::Record(fields),
+ RenderAs::Tuple => Self::Tuple,
RenderAs::Guard => Self::Guard,
}
}
@@ -145,9 +149,7 @@ fn index_var(
var = *structure;
}
Content::Structure(structure) => match structure {
- FlatType::Func(_, _, _) | FlatType::FunctionOrTagUnion(_, _, _) => {
- return Err(TypeError)
- }
+ FlatType::Func(_, _, _) => return Err(TypeError),
FlatType::Apply(Symbol::LIST_LIST, args) => {
match (subs.get_subs_slice(*args), ctor) {
([elem_var], IndexCtor::List) => {
@@ -208,6 +210,19 @@ fn index_var(
let vars = opt_vars.expect("constructor must be known in the indexable type if we are exhautiveness checking");
return Ok(vars);
}
+ FlatType::FunctionOrTagUnion(tags, _, _) => {
+ let tag_ctor = match ctor {
+ IndexCtor::Tag(name) => name,
+ _ => {
+ internal_error!("constructor in a tag union must be tag")
+ }
+ };
+
+ let tags = subs.get_subs_slice(*tags);
+ debug_assert!(tags.contains(tag_ctor), "constructor must be known in the indexable type if we are exhautiveness checking");
+
+ return Ok(vec![]);
+ }
FlatType::EmptyRecord => {
debug_assert!(matches!(ctor, IndexCtor::Record(..)));
// If there are optional record fields we don't unify them, but we need to
@@ -354,6 +369,30 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
SP::KnownCtor(union, tag_id, patterns)
}
+ TupleDestructure { destructs, .. } => {
+ let tag_id = TagId(0);
+ let mut patterns = std::vec::Vec::with_capacity(destructs.len());
+
+ for Loc {
+ value: destruct,
+ region: _,
+ } in destructs
+ {
+ patterns.push(sketch_pattern(&destruct.typ.1.value));
+ }
+
+ let union = Union {
+ render_as: RenderAs::Tuple,
+ alternatives: vec![Ctor {
+ name: CtorName::Tag(TagName("#Record".into())),
+ tag_id,
+ arity: destructs.len(),
+ }],
+ };
+
+ SP::KnownCtor(union, tag_id, patterns)
+ }
+
List {
patterns,
list_var: _,
@@ -616,64 +655,80 @@ fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union,
use {Content::*, FlatType::*};
- match dealias_tag(subs, content) {
+ let (sorted_tags, ext) = match dealias_tag(subs, content) {
Structure(TagUnion(tags, ext) | RecursiveTagUnion(_, tags, ext)) => {
let (sorted_tags, ext) = tags.sorted_iterator_and_ext(subs, *ext);
-
- let mut num_tags = sorted_tags.len();
-
- // DEVIATION: model openness by attaching a #Open constructor, that can never
- // be matched unless there's an `Anything` pattern.
- let opt_openness_tag = match subs.get_content_without_compacting(ext.var()) {
- FlexVar(_) | RigidVar(_) => {
- let openness_tag = TagName(NONEXHAUSIVE_CTOR.into());
- num_tags += 1;
- Some((openness_tag, &[] as _))
- }
- Structure(EmptyTagUnion) => None,
- // Anything else is erroneous and we ignore
- _ => None,
- };
-
- // High tag ID if we're out-of-bounds.
- let mut my_tag_id = TagId(num_tags as TagIdIntType);
-
- let mut alternatives = Vec::with_capacity(num_tags);
- let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter());
-
- let mut index = 0;
- for (tag, args) in alternatives_iter {
- let is_inhabited = args.iter().all(|v| subs.is_inhabited(*v));
- if !is_inhabited {
- // This constructor is not material; we don't need to match over it!
- continue;
- }
-
- let tag_id = TagId(index as TagIdIntType);
- index += 1;
-
- if this_tag == &tag {
- my_tag_id = tag_id;
- }
- alternatives.push(Ctor {
- name: CtorName::Tag(tag),
- tag_id,
- arity: args.len(),
+ (sorted_tags, ext)
+ }
+ Structure(FunctionOrTagUnion(tags, _, ext)) => {
+ let (ext_tags, ext) = gather_tags_unsorted_iter(subs, Default::default(), *ext)
+ .unwrap_or_else(|_| {
+ internal_error!("Content is not a tag union: {:?}", subs.dbg(whole_var))
});
+ let mut all_tags: Vec<(TagName, &[Variable])> = Vec::with_capacity(tags.len());
+ for tag in subs.get_subs_slice(*tags) {
+ all_tags.push((tag.clone(), &[]));
}
-
- let union = Union {
- alternatives,
- render_as: RenderAs::Tag,
- };
-
- (union, my_tag_id)
+ for (tag, vars) in ext_tags {
+ debug_assert!(vars.is_empty());
+ all_tags.push((tag.clone(), &[]));
+ }
+ (Box::new(all_tags.into_iter()) as SortedTagsIterator, ext)
}
_ => internal_error!(
"Content is not a tag union: {:?}",
SubsFmtContent(content, subs)
),
+ };
+
+ let mut num_tags = sorted_tags.len();
+
+ // DEVIATION: model openness by attaching a #Open constructor, that can never
+ // be matched unless there's an `Anything` pattern.
+ let opt_openness_tag = match subs.get_content_without_compacting(ext.var()) {
+ FlexVar(_) | RigidVar(_) => {
+ let openness_tag = TagName(NONEXHAUSIVE_CTOR.into());
+ num_tags += 1;
+ Some((openness_tag, &[] as _))
+ }
+ Structure(EmptyTagUnion) => None,
+ // Anything else is erroneous and we ignore
+ _ => None,
+ };
+
+ // High tag ID if we're out-of-bounds.
+ let mut my_tag_id = TagId(num_tags as TagIdIntType);
+
+ let mut alternatives = Vec::with_capacity(num_tags);
+ let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter());
+
+ let mut index = 0;
+ for (tag, args) in alternatives_iter {
+ let is_inhabited = args.iter().all(|v| subs.is_inhabited(*v));
+ if !is_inhabited {
+ // This constructor is not material; we don't need to match over it!
+ continue;
+ }
+
+ let tag_id = TagId(index as TagIdIntType);
+ index += 1;
+
+ if this_tag == &tag {
+ my_tag_id = tag_id;
+ }
+ alternatives.push(Ctor {
+ name: CtorName::Tag(tag),
+ tag_id,
+ arity: args.len(),
+ });
}
+
+ let union = Union {
+ alternatives,
+ render_as: RenderAs::Tag,
+ };
+
+ (union, my_tag_id)
}
pub fn dealias_tag<'a>(subs: &'a Subs, content: &'a Content) -> &'a Content {
diff --git a/crates/compiler/can/src/expr.rs b/crates/compiler/can/src/expr.rs
index 1a8ceb303b..888d0222b6 100644
--- a/crates/compiler/can/src/expr.rs
+++ b/crates/compiler/can/src/expr.rs
@@ -19,12 +19,13 @@ use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, Defs, StrLiteral};
+use roc_parse::ident::Accessor;
use roc_parse::pattern::PatternType::*;
use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError};
use roc_region::all::{Loc, Region};
use roc_types::num::SingleQuoteBound;
use roc_types::subs::{ExhaustiveMark, IllegalCycleMark, RedundantMark, VarStore, Variable};
-use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
+use roc_types::types::{Alias, Category, IndexOrField, LambdaSet, OptAbleVar, Type};
use std::fmt::{Debug, Display};
use std::{char, u32};
@@ -186,8 +187,8 @@ pub enum Expr {
field: Lowercase,
},
- /// field accessor as a function, e.g. (.foo) expr
- RecordAccessor(RecordAccessorData),
+ /// tuple or field accessor as a function, e.g. (.foo) expr or (.1) expr
+ RecordAccessor(StructAccessorData),
TupleAccess {
tuple_var: Variable,
@@ -197,9 +198,6 @@ pub enum Expr {
index: usize,
},
- /// tuple accessor as a function, e.g. (.1) expr
- TupleAccessor(TupleAccessorData),
-
RecordUpdate {
record_var: Variable,
ext_var: Variable,
@@ -315,9 +313,8 @@ impl Expr {
Self::Record { .. } => Category::Record,
Self::EmptyRecord => Category::Record,
Self::RecordAccess { field, .. } => Category::RecordAccess(field.clone()),
- Self::RecordAccessor(data) => Category::RecordAccessor(data.field.clone()),
+ Self::RecordAccessor(data) => Category::Accessor(data.field.clone()),
Self::TupleAccess { index, .. } => Category::TupleAccess(*index),
- Self::TupleAccessor(data) => Category::TupleAccessor(data.index),
Self::RecordUpdate { .. } => Category::Record,
Self::Tag {
name, arguments, ..
@@ -383,43 +380,30 @@ pub struct ClosureData {
pub loc_body: Box>,
}
-/// A tuple accessor like `.2`, which is equivalent to `\x -> x.2`
-/// TupleAccessors are desugared to closures; they need to have a name
+/// A record or tuple accessor like `.foo` or `.0`, which is equivalent to `\r -> r.foo`
+/// Struct accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set.
///
/// We distinguish them from closures so we can have better error messages
/// during constraint generation.
#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct TupleAccessorData {
- pub name: Symbol,
- pub function_var: Variable,
- pub tuple_var: Variable,
- pub closure_var: Variable,
- pub ext_var: Variable,
- pub elem_var: Variable,
- pub index: usize,
-}
-
-/// A record accessor like `.foo`, which is equivalent to `\r -> r.foo`
-/// RecordAccessors are desugared to closures; they need to have a name
-/// so the closure can have a correct lambda set.
-///
-/// We distinguish them from closures so we can have better error messages
-/// during constraint generation.
-#[derive(Clone, Debug, PartialEq, Eq)]
-pub struct RecordAccessorData {
+pub struct StructAccessorData {
pub name: Symbol,
pub function_var: Variable,
pub record_var: Variable,
pub closure_var: Variable,
pub ext_var: Variable,
pub field_var: Variable,
- pub field: Lowercase,
+
+ /// Note that the `field` field is an `IndexOrField` in order to represent both
+ /// record and tuple accessors. This is different from `TupleAccess` and
+ /// `RecordAccess` (and RecordFields/TupleElems), which share less of their implementation.
+ pub field: IndexOrField,
}
-impl RecordAccessorData {
+impl StructAccessorData {
pub fn to_closure_data(self, record_symbol: Symbol) -> ClosureData {
- let RecordAccessorData {
+ let StructAccessorData {
name,
function_var,
record_var,
@@ -436,12 +420,21 @@ impl RecordAccessorData {
// into
//
// (\r -> r.foo)
- let body = Expr::RecordAccess {
- record_var,
- ext_var,
- field_var,
- loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
- field,
+ let body = match field {
+ IndexOrField::Index(index) => Expr::TupleAccess {
+ tuple_var: record_var,
+ ext_var,
+ elem_var: field_var,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
+ index,
+ },
+ IndexOrField::Field(field) => Expr::RecordAccess {
+ record_var,
+ ext_var,
+ field_var,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(record_symbol, record_var))),
+ field,
+ },
};
let loc_body = Loc::at_zero(body);
@@ -1080,15 +1073,18 @@ pub fn canonicalize_expr<'a>(
output,
)
}
- ast::Expr::RecordAccessorFunction(field) => (
- RecordAccessor(RecordAccessorData {
+ ast::Expr::AccessorFunction(field) => (
+ RecordAccessor(StructAccessorData {
name: scope.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
field_var: var_store.fresh(),
- field: (*field).into(),
+ field: match field {
+ Accessor::RecordField(field) => IndexOrField::Field((*field).into()),
+ Accessor::TupleIndex(index) => IndexOrField::Index(index.parse().unwrap()),
+ },
}),
Output::default(),
),
@@ -1106,18 +1102,6 @@ pub fn canonicalize_expr<'a>(
output,
)
}
- ast::Expr::TupleAccessorFunction(index) => (
- TupleAccessor(TupleAccessorData {
- name: scope.gen_unique_symbol(),
- function_var: var_store.fresh(),
- tuple_var: var_store.fresh(),
- ext_var: var_store.fresh(),
- closure_var: var_store.fresh(),
- elem_var: var_store.fresh(),
- index: index.parse().unwrap(),
- }),
- Output::default(),
- ),
ast::Expr::Tag(tag) => {
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
@@ -1874,7 +1858,6 @@ pub fn inline_calls(var_store: &mut VarStore, expr: Expr) -> Expr {
| other @ RuntimeError(_)
| other @ EmptyRecord
| other @ RecordAccessor { .. }
- | other @ TupleAccessor { .. }
| other @ RecordUpdate { .. }
| other @ Var(..)
| other @ AbilityMember(..)
@@ -3004,7 +2987,6 @@ pub(crate) fn get_lookup_symbols(expr: &Expr) -> Vec {
| Expr::Str(_)
| Expr::ZeroArgumentTag { .. }
| Expr::RecordAccessor(_)
- | Expr::TupleAccessor(_)
| Expr::SingleQuote(..)
| Expr::EmptyRecord
| Expr::TypedHole(_)
diff --git a/crates/compiler/can/src/module.rs b/crates/compiler/can/src/module.rs
index 07958d457e..05073411c6 100644
--- a/crates/compiler/can/src/module.rs
+++ b/crates/compiler/can/src/module.rs
@@ -837,21 +837,10 @@ fn fix_values_captured_in_closure_defs(
no_capture_symbols: &mut VecSet,
closure_captures: &mut VecMap>,
) {
- // recursive defs cannot capture each other
- for def in defs.iter() {
- no_capture_symbols.extend(
- crate::traverse::symbols_introduced_from_pattern(&def.loc_pattern).map(|ls| ls.value),
- );
- }
-
- for def in defs.iter_mut() {
- fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
- }
-
// Mutually recursive functions should both capture the union of all their capture sets
//
// Really unfortunate we make a lot of clones here, can this be done more efficiently?
- let mut total_capture_set = Vec::default();
+ let mut total_capture_set = VecMap::default();
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
@@ -860,8 +849,16 @@ fn fix_values_captured_in_closure_defs(
total_capture_set.extend(captured_symbols.iter().copied());
}
}
+ for def in defs.iter() {
+ for symbol in
+ crate::traverse::symbols_introduced_from_pattern(&def.loc_pattern).map(|ls| ls.value)
+ {
+ total_capture_set.remove(&symbol);
+ }
+ }
+
+ let mut total_capture_set: Vec<_> = total_capture_set.into_iter().collect();
total_capture_set.sort_by_key(|(sym, _)| *sym);
- total_capture_set.dedup_by_key(|(sym, _)| *sym);
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
@@ -870,6 +867,10 @@ fn fix_values_captured_in_closure_defs(
*captured_symbols = total_capture_set.clone();
}
}
+
+ for def in defs.iter_mut() {
+ fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
+ }
}
fn fix_values_captured_in_closure_pattern(
@@ -918,6 +919,15 @@ fn fix_values_captured_in_closure_pattern(
}
}
}
+ TupleDestructure { destructs, .. } => {
+ for loc_destruct in destructs.iter_mut() {
+ fix_values_captured_in_closure_pattern(
+ &mut loc_destruct.value.typ.1.value,
+ no_capture_symbols,
+ closure_captures,
+ )
+ }
+ }
List { patterns, .. } => {
for loc_pat in patterns.patterns.iter_mut() {
fix_values_captured_in_closure_pattern(
@@ -1023,9 +1033,9 @@ fn fix_values_captured_in_closure_expr(
captured_symbols.retain(|(s, _)| s != name);
let original_captures_len = captured_symbols.len();
- let mut num_visited = 0;
let mut i = 0;
- while num_visited < original_captures_len {
+ let mut added_captures = false;
+ while i < original_captures_len {
// If we've captured a capturing closure, replace the captured closure symbol with
// the symbols of its captures. That way, we can construct the closure with the
// captures it needs inside our body.
@@ -1039,19 +1049,21 @@ fn fix_values_captured_in_closure_expr(
let (captured_symbol, _) = captured_symbols[i];
if let Some(captures) = closure_captures.get(&captured_symbol) {
debug_assert!(!captures.is_empty());
- captured_symbols.swap_remove(i);
captured_symbols.extend(captures);
+ captured_symbols.swap_remove(i);
// Jump two, because the next element is now one of the newly-added captures,
// which we don't need to check.
i += 2;
+
+ added_captures = true;
} else {
i += 1;
}
- num_visited += 1;
}
- if captured_symbols.len() > original_captures_len {
+ if added_captures {
// Re-sort, since we've added new captures.
captured_symbols.sort_by_key(|(sym, _)| *sym);
+ captured_symbols.dedup_by_key(|(sym, _)| *sym);
}
if captured_symbols.is_empty() {
@@ -1087,8 +1099,7 @@ fn fix_values_captured_in_closure_expr(
| TypedHole { .. }
| RuntimeError(_)
| ZeroArgumentTag { .. }
- | RecordAccessor { .. }
- | TupleAccessor { .. } => {}
+ | RecordAccessor { .. } => {}
List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() {
diff --git a/crates/compiler/can/src/operator.rs b/crates/compiler/can/src/operator.rs
index bbcc84f89c..013a87d6d8 100644
--- a/crates/compiler/can/src/operator.rs
+++ b/crates/compiler/can/src/operator.rs
@@ -130,8 +130,7 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Loc>) -> &'a Loc
| NonBase10Int { .. }
| Str(_)
| SingleQuote(_)
- | RecordAccessorFunction(_)
- | TupleAccessorFunction(_)
+ | AccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)
diff --git a/crates/compiler/can/src/pattern.rs b/crates/compiler/can/src/pattern.rs
index 9d6a366cf9..fbbee0ab0e 100644
--- a/crates/compiler/can/src/pattern.rs
+++ b/crates/compiler/can/src/pattern.rs
@@ -58,6 +58,11 @@ pub enum Pattern {
ext_var: Variable,
destructs: Vec>,
},
+ TupleDestructure {
+ whole_var: Variable,
+ ext_var: Variable,
+ destructs: Vec>,
+ },
List {
list_var: Variable,
elem_var: Variable,
@@ -100,6 +105,7 @@ impl Pattern {
AppliedTag { whole_var, .. } => Some(*whole_var),
UnwrappedOpaque { whole_var, .. } => Some(*whole_var),
RecordDestructure { whole_var, .. } => Some(*whole_var),
+ TupleDestructure { whole_var, .. } => Some(*whole_var),
List {
list_var: whole_var,
..
@@ -130,7 +136,21 @@ impl Pattern {
| UnsupportedPattern(..)
| MalformedPattern(..)
| AbilityMemberSpecialization { .. } => true,
- RecordDestructure { destructs, .. } => destructs.is_empty(),
+
+ RecordDestructure { destructs, .. } => {
+ // If all destructs are surely exhaustive, then this is surely exhaustive.
+ destructs.iter().all(|d| match &d.value.typ {
+ DestructType::Required | DestructType::Optional(_, _) => false,
+ DestructType::Guard(_, pat) => pat.value.surely_exhaustive(),
+ })
+ }
+ TupleDestructure { destructs, .. } => {
+ // If all destructs are surely exhaustive, then this is surely exhaustive.
+ destructs
+ .iter()
+ .all(|d| d.value.typ.1.value.surely_exhaustive())
+ }
+
As(pattern, _identifier) => pattern.value.surely_exhaustive(),
List { patterns, .. } => patterns.surely_exhaustive(),
AppliedTag { .. }
@@ -160,6 +180,7 @@ impl Pattern {
UnwrappedOpaque { opaque, .. } => C::Opaque(*opaque),
RecordDestructure { destructs, .. } if destructs.is_empty() => C::EmptyRecord,
RecordDestructure { .. } => C::Record,
+ TupleDestructure { .. } => C::Tuple,
List { .. } => C::List,
NumLiteral(..) => C::Num,
IntLiteral(..) => C::Int,
@@ -215,6 +236,13 @@ pub struct RecordDestruct {
pub typ: DestructType,
}
+#[derive(Clone, Debug)]
+pub struct TupleDestruct {
+ pub var: Variable,
+ pub destruct_index: usize,
+ pub typ: (Variable, Loc),
+}
+
#[derive(Clone, Debug)]
pub enum DestructType {
Required,
@@ -554,8 +582,38 @@ pub fn canonicalize_pattern<'a>(
)
}
- Tuple(_patterns) => {
- todo!("canonicalize_pattern: Tuple")
+ Tuple(patterns) => {
+ let ext_var = var_store.fresh();
+ let whole_var = var_store.fresh();
+ let mut destructs = Vec::with_capacity(patterns.len());
+
+ for (i, loc_pattern) in patterns.iter().enumerate() {
+ let can_guard = canonicalize_pattern(
+ env,
+ var_store,
+ scope,
+ output,
+ pattern_type,
+ &loc_pattern.value,
+ loc_pattern.region,
+ permit_shadows,
+ );
+
+ destructs.push(Loc {
+ region: loc_pattern.region,
+ value: TupleDestruct {
+ destruct_index: i,
+ var: var_store.fresh(),
+ typ: (var_store.fresh(), can_guard),
+ },
+ });
+ }
+
+ Pattern::TupleDestructure {
+ whole_var,
+ ext_var,
+ destructs,
+ }
}
RecordDestructure(patterns) => {
@@ -861,7 +919,8 @@ pub enum BindingsFromPattern<'a> {
pub enum BindingsFromPatternWork<'a> {
Pattern(&'a Loc),
- Destruct(&'a Loc),
+ RecordDestruct(&'a Loc),
+ TupleDestruct(&'a Loc),
}
impl<'a> BindingsFromPattern<'a> {
@@ -911,8 +970,12 @@ impl<'a> BindingsFromPattern<'a> {
let (_, loc_arg) = &**argument;
stack.push(Pattern(loc_arg));
}
+ TupleDestructure { destructs, .. } => {
+ let it = destructs.iter().rev().map(TupleDestruct);
+ stack.extend(it);
+ }
RecordDestructure { destructs, .. } => {
- let it = destructs.iter().rev().map(Destruct);
+ let it = destructs.iter().rev().map(RecordDestruct);
stack.extend(it);
}
NumLiteral(..)
@@ -930,7 +993,7 @@ impl<'a> BindingsFromPattern<'a> {
}
}
}
- BindingsFromPatternWork::Destruct(loc_destruct) => {
+ BindingsFromPatternWork::RecordDestruct(loc_destruct) => {
match &loc_destruct.value.typ {
DestructType::Required | DestructType::Optional(_, _) => {
return Some((loc_destruct.value.symbol, loc_destruct.region));
@@ -941,6 +1004,10 @@ impl<'a> BindingsFromPattern<'a> {
}
}
}
+ BindingsFromPatternWork::TupleDestruct(loc_destruct) => {
+ let inner = &loc_destruct.value.typ.1;
+ stack.push(BindingsFromPatternWork::Pattern(inner))
+ }
}
}
diff --git a/crates/compiler/can/src/traverse.rs b/crates/compiler/can/src/traverse.rs
index ceaa46fd6a..c8b31ec715 100644
--- a/crates/compiler/can/src/traverse.rs
+++ b/crates/compiler/can/src/traverse.rs
@@ -9,9 +9,9 @@ use crate::{
def::{Annotation, Declaration, Def},
expr::{
self, AnnotatedMark, ClosureData, Declarations, Expr, Field, OpaqueWrapFunctionData,
- RecordAccessorData, TupleAccessorData,
+ StructAccessorData,
},
- pattern::{DestructType, Pattern, RecordDestruct},
+ pattern::{DestructType, Pattern, RecordDestruct, TupleDestruct},
};
macro_rules! visit_list {
@@ -242,7 +242,7 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) {
record_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *field_var),
- Expr::RecordAccessor(RecordAccessorData { .. }) => { /* terminal */ }
+ Expr::RecordAccessor(StructAccessorData { .. }) => { /* terminal */ }
Expr::TupleAccess {
elem_var,
loc_expr,
@@ -250,7 +250,6 @@ pub fn walk_expr(visitor: &mut V, expr: &Expr, var: Variable) {
tuple_var: _,
ext_var: _,
} => visitor.visit_expr(&loc_expr.value, loc_expr.region, *elem_var),
- Expr::TupleAccessor(TupleAccessorData { .. }) => { /* terminal */ }
Expr::OpaqueWrapFunction(OpaqueWrapFunctionData { .. }) => { /* terminal */ }
Expr::RecordUpdate {
record_var: _,
@@ -483,6 +482,16 @@ pub trait Visitor: Sized {
walk_record_destruct(self, destruct);
}
}
+
+ fn visit_tuple_destruct(&mut self, destruct: &TupleDestruct, region: Region) {
+ if self.should_visit(region) {
+ self.visit_pattern(
+ &destruct.typ.1.value,
+ destruct.typ.1.region,
+ Some(destruct.typ.0),
+ )
+ }
+ }
}
pub fn walk_pattern(visitor: &mut V, pattern: &Pattern) {
@@ -503,6 +512,9 @@ pub fn walk_pattern(visitor: &mut V, pattern: &Pattern) {
RecordDestructure { destructs, .. } => destructs
.iter()
.for_each(|d| visitor.visit_record_destruct(&d.value, d.region)),
+ TupleDestructure { destructs, .. } => destructs
+ .iter()
+ .for_each(|d| visitor.visit_tuple_destruct(&d.value, d.region)),
List {
patterns, elem_var, ..
} => patterns
diff --git a/crates/compiler/collections/Cargo.toml b/crates/compiler/collections/Cargo.toml
index 241bb44c04..5b2d493461 100644
--- a/crates/compiler/collections/Cargo.toml
+++ b/crates/compiler/collections/Cargo.toml
@@ -1,16 +1,17 @@
[package]
name = "roc_collections"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Domain-specific collections created for the needs of the compiler."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-fnv.workspace = true
-im.workspace = true
-im-rc.workspace = true
-wyhash.workspace = true
-bumpalo.workspace = true
-hashbrown.workspace = true
bitvec.workspace = true
+bumpalo.workspace = true
+fnv.workspace = true
+hashbrown.workspace = true
+im-rc.workspace = true
+im.workspace = true
+wyhash.workspace = true
diff --git a/crates/compiler/constrain/Cargo.toml b/crates/compiler/constrain/Cargo.toml
index a830ac3f37..01d9414867 100644
--- a/crates/compiler/constrain/Cargo.toml
+++ b/crates/compiler/constrain/Cargo.toml
@@ -1,18 +1,20 @@
[package]
name = "roc_constrain"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Responsible for building the set of constraints that are used during type inference of a program, and for gathering context needed for pleasant error messages when a type error occurs."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
+roc_can = { path = "../can" }
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
-roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
+roc_region = { path = "../region" }
roc_types = { path = "../types" }
-roc_can = { path = "../can" }
-arrayvec = "0.7.2"
+
+arrayvec.workspace = true
diff --git a/crates/compiler/constrain/src/expr.rs b/crates/compiler/constrain/src/expr.rs
index 659c202e65..96baf89b22 100644
--- a/crates/compiler/constrain/src/expr.rs
+++ b/crates/compiler/constrain/src/expr.rs
@@ -17,7 +17,7 @@ use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{
AnnotatedMark, ClosureData, DeclarationTag, Declarations, DestructureDef, ExpectLookup, Field,
- FunctionDef, OpaqueWrapFunctionData, RecordAccessorData, TupleAccessorData, WhenBranch,
+ FunctionDef, OpaqueWrapFunctionData, StructAccessorData, WhenBranch,
};
use roc_can::pattern::Pattern;
use roc_can::traverse::symbols_introduced_from_pattern;
@@ -30,7 +30,7 @@ use roc_region::all::{Loc, Region};
use roc_types::subs::{IllegalCycleMark, Variable};
use roc_types::types::Type::{self, *};
use roc_types::types::{
- AliasKind, AnnotationSource, Category, OptAbleType, PReason, Reason, RecordField,
+ AliasKind, AnnotationSource, Category, IndexOrField, OptAbleType, PReason, Reason, RecordField,
TypeExtension, TypeTag, Types,
};
@@ -1162,7 +1162,7 @@ pub fn constrain_expr(
[constraint, eq, record_con],
)
}
- RecordAccessor(RecordAccessorData {
+ RecordAccessor(StructAccessorData {
name: closure_name,
function_var,
field,
@@ -1176,19 +1176,32 @@ pub fn constrain_expr(
let field_var = *field_var;
let field_type = Variable(field_var);
- let mut field_types = SendMap::default();
- let label = field.clone();
- field_types.insert(label, RecordField::Demanded(field_type.clone()));
- let record_type = Type::Record(
- field_types,
- TypeExtension::from_non_annotation_type(ext_type),
- );
+ let record_type = match field {
+ IndexOrField::Field(field) => {
+ let mut field_types = SendMap::default();
+ let label = field.clone();
+ field_types.insert(label, RecordField::Demanded(field_type.clone()));
+ Type::Record(
+ field_types,
+ TypeExtension::from_non_annotation_type(ext_type),
+ )
+ }
+ IndexOrField::Index(index) => {
+ let mut field_types = VecMap::with_capacity(1);
+ field_types.insert(*index, field_type.clone());
+ Type::Tuple(
+ field_types,
+ TypeExtension::from_non_annotation_type(ext_type),
+ )
+ }
+ };
+
let record_type_index = {
let typ = types.from_old_type(&record_type);
constraints.push_type(types, typ)
};
- let category = Category::RecordAccessor(field.clone());
+ let category = Category::Accessor(field.clone());
let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
let record_con =
@@ -1288,88 +1301,6 @@ pub fn constrain_expr(
let eq = constraints.equal_types_var(elem_var, expected, category, region);
constraints.exists_many([*tuple_var, elem_var, ext_var], [constraint, eq, tuple_con])
}
- TupleAccessor(TupleAccessorData {
- name: closure_name,
- function_var,
- tuple_var,
- closure_var,
- ext_var,
- elem_var,
- index,
- }) => {
- let ext_var = *ext_var;
- let ext_type = Variable(ext_var);
- let elem_var = *elem_var;
- let elem_type = Variable(elem_var);
-
- let mut elem_types = VecMap::with_capacity(1);
- elem_types.insert(*index, elem_type.clone());
-
- let record_type = Type::Tuple(
- elem_types,
- TypeExtension::from_non_annotation_type(ext_type),
- );
- let record_type_index = {
- let typ = types.from_old_type(&record_type);
- constraints.push_type(types, typ)
- };
-
- let category = Category::TupleAccessor(*index);
-
- let record_expected = constraints.push_expected_type(NoExpectation(record_type_index));
- let record_con =
- constraints.equal_types_var(*tuple_var, record_expected, category.clone(), region);
-
- let expected_lambda_set = {
- let lambda_set_ty = {
- let typ = types.from_old_type(&Type::ClosureTag {
- name: *closure_name,
- captures: vec![],
- ambient_function: *function_var,
- });
- constraints.push_type(types, typ)
- };
- constraints.push_expected_type(NoExpectation(lambda_set_ty))
- };
-
- let closure_type = Type::Variable(*closure_var);
-
- let function_type_index = {
- let typ = types.from_old_type(&Type::Function(
- vec![record_type],
- Box::new(closure_type),
- Box::new(elem_type),
- ));
- constraints.push_type(types, typ)
- };
-
- let cons = [
- constraints.equal_types_var(
- *closure_var,
- expected_lambda_set,
- category.clone(),
- region,
- ),
- constraints.equal_types(function_type_index, expected, category.clone(), region),
- {
- let store_fn_var_index = constraints.push_variable(*function_var);
- let store_fn_var_expected =
- constraints.push_expected_type(NoExpectation(store_fn_var_index));
- constraints.equal_types(
- function_type_index,
- store_fn_var_expected,
- category,
- region,
- )
- },
- record_con,
- ];
-
- constraints.exists_many(
- [*tuple_var, *function_var, *closure_var, elem_var, ext_var],
- cons,
- )
- }
LetRec(defs, loc_ret, cycle_mark) => {
let body_con = constrain_expr(
types,
@@ -4001,10 +3932,6 @@ fn is_generalizable_expr(mut expr: &Expr) -> bool {
// RecordAccessor functions `.field` are equivalent to closures `\r -> r.field`, no need to weaken them.
return true;
}
- TupleAccessor(_) => {
- // TupleAccessor functions `.0` are equivalent to closures `\r -> r.0`, no need to weaken them.
- return true;
- }
OpaqueWrapFunction(_) => {
// Opaque wrapper functions `@Q` are equivalent to closures `\x -> @Q x`, no need to weaken them.
return true;
diff --git a/crates/compiler/constrain/src/pattern.rs b/crates/compiler/constrain/src/pattern.rs
index d67c0c95a4..f05c218125 100644
--- a/crates/compiler/constrain/src/pattern.rs
+++ b/crates/compiler/constrain/src/pattern.rs
@@ -3,7 +3,7 @@ use crate::expr::{constrain_expr, Env};
use roc_can::constraint::{Constraint, Constraints, PExpectedTypeIndex, TypeOrVar};
use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
-use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct};
+use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct, TupleDestruct};
use roc_collections::all::{HumanIndex, SendMap};
use roc_collections::VecMap;
use roc_module::ident::Lowercase;
@@ -125,6 +125,10 @@ fn headers_from_annotation_help(
_ => false,
},
+ TupleDestructure { destructs: _, .. } => {
+ todo!();
+ }
+
List { patterns, .. } => {
if let Some((_, Some(rest))) = patterns.opt_rest {
let annotation_index = {
@@ -465,6 +469,96 @@ pub fn constrain_pattern(
));
}
+ TupleDestructure {
+ whole_var,
+ ext_var,
+ destructs,
+ } => {
+ state.vars.push(*whole_var);
+ state.vars.push(*ext_var);
+ let ext_type = Type::Variable(*ext_var);
+
+ let mut elem_types: VecMap = VecMap::default();
+
+ for Loc {
+ value:
+ TupleDestruct {
+ destruct_index: index,
+ var,
+ typ,
+ },
+ ..
+ } in destructs.iter()
+ {
+ let pat_type = Type::Variable(*var);
+ let pat_type_index = constraints.push_variable(*var);
+ let expected =
+ constraints.push_pat_expected_type(PExpected::NoExpectation(pat_type_index));
+
+ let (guard_var, loc_guard) = typ;
+ let elem_type = {
+ let guard_type = constraints.push_variable(*guard_var);
+ let expected_pat = constraints.push_pat_expected_type(PExpected::ForReason(
+ PReason::PatternGuard,
+ pat_type_index,
+ loc_guard.region,
+ ));
+
+ state.constraints.push(constraints.pattern_presence(
+ guard_type,
+ expected_pat,
+ PatternCategory::PatternGuard,
+ region,
+ ));
+ state.vars.push(*guard_var);
+
+ constrain_pattern(
+ types,
+ constraints,
+ env,
+ &loc_guard.value,
+ loc_guard.region,
+ expected,
+ state,
+ );
+
+ pat_type
+ };
+
+ elem_types.insert(*index, elem_type);
+
+ state.vars.push(*var);
+ }
+
+ let tuple_type = {
+ let typ = types.from_old_type(&Type::Tuple(
+ elem_types,
+ TypeExtension::from_non_annotation_type(ext_type),
+ ));
+ constraints.push_type(types, typ)
+ };
+
+ let whole_var_index = constraints.push_variable(*whole_var);
+ let expected_record =
+ constraints.push_expected_type(Expected::NoExpectation(tuple_type));
+ let whole_con = constraints.equal_types(
+ whole_var_index,
+ expected_record,
+ Category::Storage(std::file!(), std::line!()),
+ region,
+ );
+
+ let record_con = constraints.pattern_presence(
+ whole_var_index,
+ expected,
+ PatternCategory::Record,
+ region,
+ );
+
+ state.constraints.push(whole_con);
+ state.constraints.push(record_con);
+ }
+
RecordDestructure {
whole_var,
ext_var,
diff --git a/crates/compiler/debug_flags/Cargo.toml b/crates/compiler/debug_flags/Cargo.toml
index 9e299039a4..b3b9b4bc15 100644
--- a/crates/compiler/debug_flags/Cargo.toml
+++ b/crates/compiler/debug_flags/Cargo.toml
@@ -1,9 +1,10 @@
[package]
name = "roc_debug_flags"
-version = "0.0.1"
-edition = "2021"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
description = "Environment variables that can be toggled to aid debugging of the compiler itself."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
diff --git a/crates/compiler/derive/Cargo.toml b/crates/compiler/derive/Cargo.toml
index 05d4dbdbbb..9aba2f51c7 100644
--- a/crates/compiler/derive/Cargo.toml
+++ b/crates/compiler/derive/Cargo.toml
@@ -1,25 +1,27 @@
[package]
name = "roc_derive"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Provides auto-derivers for builtin abilities like `Hash` and `Decode`."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-roc_collections = { path = "../collections" }
-roc_error_macros = { path = "../../error_macros" }
-roc_derive_key = { path = "../derive_key" }
-roc_region = { path = "../region" }
-roc_module = { path = "../module" }
-roc_types = { path = "../types" }
roc_can = { path = "../can" }
+roc_collections = { path = "../collections" }
+roc_derive_key = { path = "../derive_key" }
+roc_error_macros = { path = "../../error_macros" }
+roc_module = { path = "../module" }
+roc_region = { path = "../region" }
+roc_types = { path = "../types" }
roc_unify = { path = "../unify" }
+
bumpalo.workspace = true
[features]
-default = []
debug-derived-symbols = ["roc_module/debug-symbols"]
+default = []
# Enables open extension variables for constructed records and tag unions.
# This is not necessary for code generation, but may be necessary if you are
# constraining and solving generated derived bodies.
diff --git a/crates/compiler/derive/src/decoding.rs b/crates/compiler/derive/src/decoding.rs
index 77bc98c1ad..2ba0b8cff8 100644
--- a/crates/compiler/derive/src/decoding.rs
+++ b/crates/compiler/derive/src/decoding.rs
@@ -1,33 +1,32 @@
//! Derivers for the `Decoding` ability.
-use roc_can::expr::{
- AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch, WhenBranchPattern,
-};
+use roc_can::expr::{AnnotatedMark, ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern;
-use roc_collections::SendMap;
-use roc_derive_key::decoding::FlatDecodableKey;
-use roc_error_macros::internal_error;
-use roc_module::called_via::CalledVia;
-use roc_module::ident::Lowercase;
-use roc_module::symbol::Symbol;
-use roc_region::all::{Loc, Region};
-use roc_types::subs::{
- Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
- RedundantMark, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable,
-};
-use roc_types::types::{AliasKind, RecordField};
-use crate::util::{Env, ExtensionKind};
+use roc_derive_key::decoding::FlatDecodableKey;
+use roc_module::called_via::CalledVia;
+use roc_module::symbol::Symbol;
+use roc_region::all::Loc;
+use roc_types::subs::{
+ Content, FlatType, LambdaSet, OptVariable, SubsSlice, UnionLambdas, Variable,
+};
+
+use crate::util::Env;
use crate::{synth_var, DerivedBody};
+mod list;
+mod record;
+mod tuple;
+
pub(crate) fn derive_decoder(
env: &mut Env<'_>,
key: FlatDecodableKey,
def_symbol: Symbol,
) -> DerivedBody {
let (body, body_type) = match key {
- FlatDecodableKey::List() => decoder_list(env, def_symbol),
- FlatDecodableKey::Record(fields) => decoder_record(env, def_symbol, fields),
+ FlatDecodableKey::List() => list::decoder(env, def_symbol),
+ FlatDecodableKey::Record(fields) => record::decoder(env, def_symbol, fields),
+ FlatDecodableKey::Tuple(arity) => tuple::decoder(env, def_symbol, arity),
};
let specialization_lambda_sets =
@@ -40,1062 +39,9 @@ pub(crate) fn derive_decoder(
}
}
-// Implements decoding of a record. For example, for
-//
-// {first: a, second: b}
-//
-// we'd like to generate an impl like
-//
-// decoder : Decoder {first: a, second: b} fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting
-// decoder =
-// initialState : {f0: Result a [NoField], f1: Result b [NoField]}
-// initialState = {f0: Err NoField, f1: Err NoField}
-//
-// stepField = \state, field ->
-// when field is
-// "first" ->
-// Keep (Decode.custom \bytes, fmt ->
-// when Decode.decodeWith bytes Decode.decoder fmt is
-// {result, rest} ->
-// {result: Result.map result \val -> {state & f0: Ok val}, rest})
-// "second" ->
-// Keep (Decode.custom \bytes, fmt ->
-// when Decode.decodeWith bytes Decode.decoder fmt is
-// {result, rest} ->
-// {result: Result.map result \val -> {state & f1: Ok val}, rest})
-// _ -> Skip
-//
-// finalizer = \{f0, f1} ->
-// when f0 is
-// Ok first ->
-// when f1 is
-// Ok second -> Ok {first, second}
-// Err NoField -> Err TooShort
-// Err NoField -> Err TooShort
-//
-// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.record initialState stepField finalizer) fmt
-fn decoder_record(env: &mut Env, _def_symbol: Symbol, fields: Vec) -> (Expr, Variable) {
- // The decoded type of each field in the record, e.g. {first: a, second: b}.
- let mut field_vars = Vec::with_capacity(fields.len());
- // The type of each field in the decoding state, e.g. {first: Result a [NoField], second: Result b [NoField]}
- let mut result_field_vars = Vec::with_capacity(fields.len());
-
- // initialState = ...
- let (initial_state_var, initial_state) =
- decoder_record_initial_state(env, &fields, &mut field_vars, &mut result_field_vars);
-
- // finalizer = ...
- let (finalizer, finalizer_var, decode_err_var) = decoder_record_finalizer(
- env,
- initial_state_var,
- &fields,
- &field_vars,
- &result_field_vars,
- );
-
- // stepField = ...
- let (step_field, step_var) = decoder_record_step_field(
- env,
- fields,
- &field_vars,
- &result_field_vars,
- initial_state_var,
- decode_err_var,
- );
-
- // Build up the type of `Decode.record` we expect
- let record_decoder_var = env.subs.fresh_unnamed_flex_var();
- let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var();
- let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_RECORD);
- let this_decode_record_var = {
- let flat_type = FlatType::Func(
- SubsSlice::insert_into_subs(env.subs, [initial_state_var, step_var, finalizer_var]),
- decode_record_lambda_set,
- record_decoder_var,
- );
-
- synth_var(env.subs, Content::Structure(flat_type))
- };
-
- env.unify(decode_record_var, this_decode_record_var);
-
- // Decode.record initialState stepField finalizer
- let call_decode_record = Expr::Call(
- Box::new((
- this_decode_record_var,
- Loc::at_zero(Expr::AbilityMember(
- Symbol::DECODE_RECORD,
- None,
- this_decode_record_var,
- )),
- decode_record_lambda_set,
- record_decoder_var,
- )),
- vec![
- (initial_state_var, Loc::at_zero(initial_state)),
- (step_var, Loc::at_zero(step_field)),
- (finalizer_var, Loc::at_zero(finalizer)),
- ],
- CalledVia::Space,
- );
-
- let (call_decode_custom, decode_custom_ret_var) = {
- let bytes_sym = env.new_symbol("bytes");
- let fmt_sym = env.new_symbol("fmt");
- let fmt_var = env.subs.fresh_unnamed_flex_var();
-
- let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with(
- env,
- bytes_sym,
- (fmt_sym, fmt_var),
- vec![],
- (call_decode_record, record_decoder_var),
- );
-
- (decode_custom, decode_custom_var)
- };
-
- (call_decode_custom, decode_custom_ret_var)
-}
-
-// Example:
-// stepField = \state, field ->
-// when field is
-// "first" ->
-// Keep (Decode.custom \bytes, fmt ->
-// # Uses a single-branch `when` because `let` is more expensive to monomorphize
-// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
-// when Decode.decodeWith bytes Decode.decoder fmt is
-// rec ->
-// {
-// rest: rec.rest,
-// result: when rec.result is
-// Ok val -> Ok {state & first: Ok val},
-// Err err -> Err err
-// })
-//
-// "second" ->
-// Keep (Decode.custom \bytes, fmt ->
-// when Decode.decodeWith bytes Decode.decoder fmt is
-// rec ->
-// {
-// rest: rec.rest,
-// result: when rec.result is
-// Ok val -> Ok {state & second: Ok val},
-// Err err -> Err err
-// })
-//
-// _ -> Skip
-fn decoder_record_step_field(
- env: &mut Env,
- fields: Vec,
- field_vars: &[Variable],
- result_field_vars: &[Variable],
- state_record_var: Variable,
- decode_err_var: Variable,
-) -> (Expr, Variable) {
- let state_arg_symbol = env.new_symbol("stateRecord");
- let field_arg_symbol = env.new_symbol("field");
-
- // +1 because of the default branch.
- let mut branches = Vec::with_capacity(fields.len() + 1);
- let keep_payload_var = env.subs.fresh_unnamed_flex_var();
- let keep_or_skip_var = {
- let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]);
- let flat_type = FlatType::TagUnion(
- UnionTags::insert_slices_into_subs(
- env.subs,
- [
- ("Keep".into(), keep_payload_subs_slice),
- ("Skip".into(), Default::default()),
- ],
- ),
- TagExt::Any(Variable::EMPTY_TAG_UNION),
- );
-
- synth_var(env.subs, Content::Structure(flat_type))
- };
-
- for ((field_name, &field_var), &result_field_var) in fields
- .into_iter()
- .zip(field_vars.iter())
- .zip(result_field_vars.iter())
- {
- // Example:
- // "first" ->
- // Keep (Decode.custom \bytes, fmt ->
- // # Uses a single-branch `when` because `let` is more expensive to monomorphize
- // # due to checks for polymorphic expressions, and `rec` would be polymorphic.
- // when Decode.decodeWith bytes Decode.decoder fmt is
- // rec ->
- // {
- // rest: rec.rest,
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- // }
- // )
-
- let this_custom_callback_var;
- let custom_callback_ret_var;
- let custom_callback = {
- // \bytes, fmt ->
- // when Decode.decodeWith bytes Decode.decoder fmt is
- // rec ->
- // {
- // rest: rec.rest,
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- // }
- let bytes_arg_symbol = env.new_symbol("bytes");
- let fmt_arg_symbol = env.new_symbol("fmt");
- let bytes_arg_var = env.subs.fresh_unnamed_flex_var();
- let fmt_arg_var = env.subs.fresh_unnamed_flex_var();
-
- // rec.result : [Ok field_var, Err DecodeError]
- let rec_dot_result = {
- let tag_union = FlatType::TagUnion(
- UnionTags::for_result(env.subs, field_var, decode_err_var),
- TagExt::Any(Variable::EMPTY_TAG_UNION),
- );
-
- synth_var(env.subs, Content::Structure(tag_union))
- };
-
- // rec : { rest: List U8, result: (typeof rec.result) }
- let rec_var = {
- let fields = RecordFields::insert_into_subs(
- env.subs,
- [
- ("rest".into(), RecordField::Required(Variable::LIST_U8)),
- ("result".into(), RecordField::Required(rec_dot_result)),
- ],
- );
- let record = FlatType::Record(fields, Variable::EMPTY_RECORD);
-
- synth_var(env.subs, Content::Structure(record))
- };
-
- // `Decode.decoder` for the field's value
- let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
- let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
- let lambda_set_var = env.subs.fresh_unnamed_flex_var();
- let this_decode_with_var = {
- let subs_slice = SubsSlice::insert_into_subs(
- env.subs,
- [bytes_arg_var, decoder_var, fmt_arg_var],
- );
- let this_decode_with_var = synth_var(
- env.subs,
- Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)),
- );
-
- env.unify(decode_with_var, this_decode_with_var);
-
- this_decode_with_var
- };
-
- // The result of decoding this field's value - either the updated state, or a decoding error.
- let when_expr_var = {
- let flat_type = FlatType::TagUnion(
- UnionTags::for_result(env.subs, state_record_var, decode_err_var),
- TagExt::Any(Variable::EMPTY_TAG_UNION),
- );
-
- synth_var(env.subs, Content::Structure(flat_type))
- };
-
- // What our decoder passed to `Decode.custom` returns - the result of decoding the
- // field's value, and the remaining bytes.
- custom_callback_ret_var = {
- let rest_field = RecordField::Required(Variable::LIST_U8);
- let result_field = RecordField::Required(when_expr_var);
- let flat_type = FlatType::Record(
- RecordFields::insert_into_subs(
- env.subs,
- [("rest".into(), rest_field), ("result".into(), result_field)],
- ),
- Variable::EMPTY_RECORD,
- );
-
- synth_var(env.subs, Content::Structure(flat_type))
- };
-
- let custom_callback_body = {
- let rec_symbol = env.new_symbol("rec");
-
- // # Uses a single-branch `when` because `let` is more expensive to monomorphize
- // # due to checks for polymorphic expressions, and `rec` would be polymorphic.
- // when Decode.decodeWith bytes Decode.decoder fmt is
- // rec ->
- // {
- // rest: rec.rest,
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- // }
- let branch_body = {
- let result_val = {
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- let ok_val_symbol = env.new_symbol("val");
- let err_val_symbol = env.new_symbol("err");
- let ok_branch_expr = {
- // Ok {state & first: Ok val},
- let mut updates = SendMap::default();
-
- updates.insert(
- field_name.clone(),
- Field {
- var: result_field_var,
- region: Region::zero(),
- loc_expr: Box::new(Loc::at_zero(Expr::Tag {
- tag_union_var: result_field_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: "Ok".into(),
- arguments: vec![(
- field_var,
- Loc::at_zero(Expr::Var(ok_val_symbol, field_var)),
- )],
- })),
- },
- );
-
- let updated_record = Expr::RecordUpdate {
- record_var: state_record_var,
- ext_var: env.new_ext_var(ExtensionKind::Record),
- symbol: state_arg_symbol,
- updates,
- };
-
- Expr::Tag {
- tag_union_var: when_expr_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: "Ok".into(),
- arguments: vec![(state_record_var, Loc::at_zero(updated_record))],
- }
- };
-
- let branches = vec![
- // Ok val -> Ok {state & first: Ok val},
- WhenBranch {
- patterns: vec![WhenBranchPattern {
- pattern: Loc::at_zero(Pattern::AppliedTag {
- whole_var: rec_dot_result,
- ext_var: Variable::EMPTY_TAG_UNION,
- tag_name: "Ok".into(),
- arguments: vec![(
- field_var,
- Loc::at_zero(Pattern::Identifier(ok_val_symbol)),
- )],
- }),
- degenerate: false,
- }],
- value: Loc::at_zero(ok_branch_expr),
- guard: None,
- redundant: RedundantMark::known_non_redundant(),
- },
- // Err err -> Err err
- WhenBranch {
- patterns: vec![WhenBranchPattern {
- pattern: Loc::at_zero(Pattern::AppliedTag {
- whole_var: rec_dot_result,
- ext_var: Variable::EMPTY_TAG_UNION,
- tag_name: "Err".into(),
- arguments: vec![(
- decode_err_var,
- Loc::at_zero(Pattern::Identifier(err_val_symbol)),
- )],
- }),
- degenerate: false,
- }],
- value: Loc::at_zero(Expr::Tag {
- tag_union_var: when_expr_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: "Err".into(),
- arguments: vec![(
- decode_err_var,
- Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)),
- )],
- }),
- guard: None,
- redundant: RedundantMark::known_non_redundant(),
- },
- ];
-
- // when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- Expr::When {
- loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess {
- record_var: rec_var,
- ext_var: env.new_ext_var(ExtensionKind::Record),
- field_var: rec_dot_result,
- loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
- field: "result".into(),
- })),
- cond_var: rec_dot_result,
- expr_var: when_expr_var,
- region: Region::zero(),
- branches,
- branches_cond_var: rec_dot_result,
- exhaustive: ExhaustiveMark::known_exhaustive(),
- }
- };
-
- // {
- // rest: rec.rest,
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- // }
- let mut fields_map = SendMap::default();
-
- fields_map.insert(
- "rest".into(),
- Field {
- var: Variable::LIST_U8,
- region: Region::zero(),
- loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess {
- record_var: rec_var,
- ext_var: env.new_ext_var(ExtensionKind::Record),
- field_var: Variable::LIST_U8,
- loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
- field: "rest".into(),
- })),
- },
- );
-
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- fields_map.insert(
- "result".into(),
- Field {
- var: when_expr_var,
- region: Region::zero(),
- loc_expr: Box::new(Loc::at_zero(result_val)),
- },
- );
-
- Expr::Record {
- record_var: custom_callback_ret_var,
- fields: fields_map,
- }
- };
-
- let branch = WhenBranch {
- patterns: vec![WhenBranchPattern {
- pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)),
- degenerate: false,
- }],
- value: Loc::at_zero(branch_body),
- guard: None,
- redundant: RedundantMark::known_non_redundant(),
- };
-
- let condition_expr = Expr::Call(
- Box::new((
- this_decode_with_var,
- Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)),
- lambda_set_var,
- rec_var,
- )),
- vec![
- (
- Variable::LIST_U8,
- Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)),
- ),
- (
- decoder_var,
- Loc::at_zero(Expr::AbilityMember(
- Symbol::DECODE_DECODER,
- None,
- decoder_var,
- )),
- ),
- (
- fmt_arg_var,
- Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)),
- ),
- ],
- CalledVia::Space,
- );
-
- // when Decode.decodeWith bytes Decode.decoder fmt is
- Expr::When {
- loc_cond: Box::new(Loc::at_zero(condition_expr)),
- cond_var: rec_var,
- expr_var: custom_callback_ret_var,
- region: Region::zero(),
- branches: vec![branch],
- branches_cond_var: rec_var,
- exhaustive: ExhaustiveMark::known_exhaustive(),
- }
- };
-
- let custom_closure_symbol = env.new_symbol("customCallback");
- this_custom_callback_var = env.subs.fresh_unnamed_flex_var();
- let custom_callback_lambda_set_var = {
- let content = Content::LambdaSet(LambdaSet {
- solved: UnionLambdas::insert_into_subs(
- env.subs,
- [(custom_closure_symbol, [state_record_var])],
- ),
- recursion_var: OptVariable::NONE,
- unspecialized: Default::default(),
- ambient_function: this_custom_callback_var,
- });
- let custom_callback_lambda_set_var = synth_var(env.subs, content);
- let subs_slice =
- SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]);
-
- env.subs.set_content(
- this_custom_callback_var,
- Content::Structure(FlatType::Func(
- subs_slice,
- custom_callback_lambda_set_var,
- custom_callback_ret_var,
- )),
- );
-
- custom_callback_lambda_set_var
- };
-
- // \bytes, fmt -> …
- Expr::Closure(ClosureData {
- function_type: this_custom_callback_var,
- closure_type: custom_callback_lambda_set_var,
- return_type: custom_callback_ret_var,
- name: custom_closure_symbol,
- captured_symbols: vec![(state_arg_symbol, state_record_var)],
- recursive: Recursive::NotRecursive,
- arguments: vec![
- (
- bytes_arg_var,
- AnnotatedMark::known_exhaustive(),
- Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)),
- ),
- (
- fmt_arg_var,
- AnnotatedMark::known_exhaustive(),
- Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)),
- ),
- ],
- loc_body: Box::new(Loc::at_zero(custom_callback_body)),
- })
- };
-
- let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
- let decode_custom = {
- let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
- let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var();
- let this_decode_custom_var = {
- let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]);
- let flat_type =
- FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var);
-
- synth_var(env.subs, Content::Structure(flat_type))
- };
-
- env.unify(decode_custom_var, this_decode_custom_var);
-
- // Decode.custom \bytes, fmt -> …
- Expr::Call(
- Box::new((
- this_decode_custom_var,
- Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)),
- decode_custom_closure_var,
- decode_custom_ret_var,
- )),
- vec![(this_custom_callback_var, Loc::at_zero(custom_callback))],
- CalledVia::Space,
- )
- };
-
- env.unify(keep_payload_var, decode_custom_ret_var);
-
- let keep = {
- // Keep (Decode.custom \bytes, fmt ->
- // when Decode.decodeWith bytes Decode.decoder fmt is
- // rec ->
- // {
- // rest: rec.rest,
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- // }
- // )
- Expr::Tag {
- tag_union_var: keep_or_skip_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: "Keep".into(),
- arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))],
- }
- };
-
- let branch = {
- // "first" ->
- // Keep (Decode.custom \bytes, fmt ->
- // when Decode.decodeWith bytes Decode.decoder fmt is
- // rec ->
- // {
- // rest: rec.rest,
- // result: when rec.result is
- // Ok val -> Ok {state & first: Ok val},
- // Err err -> Err err
- // }
- // )
- WhenBranch {
- patterns: vec![WhenBranchPattern {
- pattern: Loc::at_zero(Pattern::StrLiteral(field_name.into())),
- degenerate: false,
- }],
- value: Loc::at_zero(keep),
- guard: None,
- redundant: RedundantMark::known_non_redundant(),
- }
- };
-
- branches.push(branch);
- }
-
- // Example: `_ -> Skip`
- let default_branch = WhenBranch {
- patterns: vec![WhenBranchPattern {
- pattern: Loc::at_zero(Pattern::Underscore),
- degenerate: false,
- }],
- value: Loc::at_zero(Expr::Tag {
- tag_union_var: keep_or_skip_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: "Skip".into(),
- arguments: Vec::new(),
- }),
- guard: None,
- redundant: RedundantMark::known_non_redundant(),
- };
-
- branches.push(default_branch);
-
- // when field is
- let body = Expr::When {
- loc_cond: Box::new(Loc::at_zero(Expr::Var(field_arg_symbol, Variable::STR))),
- cond_var: Variable::STR,
- expr_var: keep_or_skip_var,
- region: Region::zero(),
- branches,
- branches_cond_var: Variable::STR,
- exhaustive: ExhaustiveMark::known_exhaustive(),
- };
-
- let step_field_closure = env.new_symbol("stepField");
- let function_type = env.subs.fresh_unnamed_flex_var();
- let closure_type = {
- let lambda_set = LambdaSet {
- solved: UnionLambdas::tag_without_arguments(env.subs, step_field_closure),
- recursion_var: OptVariable::NONE,
- unspecialized: Default::default(),
- ambient_function: function_type,
- };
-
- synth_var(env.subs, Content::LambdaSet(lambda_set))
- };
-
- {
- let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::STR]);
-
- env.subs.set_content(
- function_type,
- Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)),
- )
- };
-
- let expr = Expr::Closure(ClosureData {
- function_type,
- closure_type,
- return_type: keep_or_skip_var,
- name: step_field_closure,
- captured_symbols: Vec::new(),
- recursive: Recursive::NotRecursive,
- arguments: vec![
- (
- state_record_var,
- AnnotatedMark::known_exhaustive(),
- Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
- ),
- (
- Variable::STR,
- AnnotatedMark::known_exhaustive(),
- Loc::at_zero(Pattern::Identifier(field_arg_symbol)),
- ),
- ],
- loc_body: Box::new(Loc::at_zero(body)),
- });
-
- (expr, function_type)
-}
-
-// Example:
-// finalizer = \rec ->
-// when rec.first is
-// Ok first ->
-// when rec.second is
-// Ok second -> Ok {first, second}
-// Err NoField -> Err TooShort
-// Err NoField -> Err TooShort
-fn decoder_record_finalizer(
- env: &mut Env,
- state_record_var: Variable,
- fields: &[Lowercase],
- field_vars: &[Variable],
- result_field_vars: &[Variable],
-) -> (Expr, Variable, Variable) {
- let state_arg_symbol = env.new_symbol("stateRecord");
- let mut fields_map = SendMap::default();
- let mut pattern_symbols = Vec::with_capacity(fields.len());
- let decode_err_var = {
- let flat_type = FlatType::TagUnion(
- UnionTags::tag_without_arguments(env.subs, "TooShort".into()),
- TagExt::Any(Variable::EMPTY_TAG_UNION),
- );
-
- synth_var(env.subs, Content::Structure(flat_type))
- };
-
- for (field_name, &field_var) in fields.iter().zip(field_vars.iter()) {
- let symbol = env.new_symbol(field_name.as_str());
-
- pattern_symbols.push(symbol);
-
- let field_expr = Expr::Var(symbol, field_var);
- let field = Field {
- var: field_var,
- region: Region::zero(),
- loc_expr: Box::new(Loc::at_zero(field_expr)),
- };
-
- fields_map.insert(field_name.clone(), field);
- }
-
- // The bottom of the happy path - return the decoded record {first: a, second: b} wrapped with
- // "Ok".
- let return_type_var;
- let mut body = {
- let subs = &mut env.subs;
- let record_field_iter = fields
- .iter()
- .zip(field_vars.iter())
- .map(|(field_name, &field_var)| (field_name.clone(), RecordField::Required(field_var)));
- let flat_type = FlatType::Record(
- RecordFields::insert_into_subs(subs, record_field_iter),
- Variable::EMPTY_RECORD,
- );
- let done_record_var = synth_var(subs, Content::Structure(flat_type));
- let done_record = Expr::Record {
- record_var: done_record_var,
- fields: fields_map,
- };
-
- return_type_var = {
- let flat_type = FlatType::TagUnion(
- UnionTags::for_result(subs, done_record_var, decode_err_var),
- TagExt::Any(Variable::EMPTY_TAG_UNION),
- );
-
- synth_var(subs, Content::Structure(flat_type))
- };
-
- Expr::Tag {
- tag_union_var: return_type_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: "Ok".into(),
- arguments: vec![(done_record_var, Loc::at_zero(done_record))],
- }
- };
-
- // Unwrap each result in the decoded state
- //
- // when rec.first is
- // Ok first -> ...happy path...
- // Err NoField -> Err TooShort
- for (((symbol, field_name), &field_var), &result_field_var) in pattern_symbols
- .iter()
- .rev()
- .zip(fields.iter().rev())
- .zip(field_vars.iter().rev())
- .zip(result_field_vars.iter().rev())
- {
- // when rec.first is
- let cond_expr = Expr::RecordAccess {
- record_var: state_record_var,
- ext_var: env.new_ext_var(ExtensionKind::Record),
- field_var: result_field_var,
- loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))),
- field: field_name.clone(),
- };
-
- // Example: `Ok x -> expr`
- let ok_branch = WhenBranch {
- patterns: vec![WhenBranchPattern {
- pattern: Loc::at_zero(Pattern::AppliedTag {
- whole_var: result_field_var,
- ext_var: Variable::EMPTY_TAG_UNION,
- tag_name: "Ok".into(),
- arguments: vec![(field_var, Loc::at_zero(Pattern::Identifier(*symbol)))],
- }),
- degenerate: false,
- }],
- value: Loc::at_zero(body),
- guard: None,
- redundant: RedundantMark::known_non_redundant(),
- };
-
- // Example: `_ -> Err TooShort`
- let err_branch = WhenBranch {
- patterns: vec![WhenBranchPattern {
- pattern: Loc::at_zero(Pattern::Underscore),
- degenerate: false,
- }],
- value: Loc::at_zero(Expr::Tag {
- tag_union_var: return_type_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: "Err".into(),
- arguments: vec![(
- decode_err_var,
- Loc::at_zero(Expr::Tag {
- tag_union_var: decode_err_var,
- ext_var: Variable::EMPTY_TAG_UNION,
- name: "TooShort".into(),
- arguments: Vec::new(),
- }),
- )],
- }),
- guard: None,
- redundant: RedundantMark::known_non_redundant(),
- };
-
- body = Expr::When {
- loc_cond: Box::new(Loc::at_zero(cond_expr)),
- cond_var: result_field_var,
- expr_var: return_type_var,
- region: Region::zero(),
- branches: vec![ok_branch, err_branch],
- branches_cond_var: result_field_var,
- exhaustive: ExhaustiveMark::known_exhaustive(),
- };
- }
-
- let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later.
- let function_symbol = env.new_symbol("finalizer");
- let lambda_set = LambdaSet {
- solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol),
- recursion_var: OptVariable::NONE,
- unspecialized: Default::default(),
- ambient_function: function_var,
- };
- let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set));
- let flat_type = FlatType::Func(
- SubsSlice::insert_into_subs(env.subs, [state_record_var]),
- closure_type,
- return_type_var,
- );
-
- // Fix up function_var so it's not Content::Error anymore
- env.subs
- .set_content(function_var, Content::Structure(flat_type));
-
- let finalizer = Expr::Closure(ClosureData {
- function_type: function_var,
- closure_type,
- return_type: return_type_var,
- name: function_symbol,
- captured_symbols: Vec::new(),
- recursive: Recursive::NotRecursive,
- arguments: vec![(
- state_record_var,
- AnnotatedMark::known_exhaustive(),
- Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
- )],
- loc_body: Box::new(Loc::at_zero(body)),
- });
-
- (finalizer, function_var, decode_err_var)
-}
-
-// Example:
-// initialState : {first: Result a [NoField], second: Result b [NoField]}
-// initialState = {first: Err NoField, second: Err NoField}
-fn decoder_record_initial_state(
- env: &mut Env<'_>,
- field_names: &[Lowercase],
- field_vars: &mut Vec,
- result_field_vars: &mut Vec,
-) -> (Variable, Expr) {
- let mut initial_state_fields = SendMap::default();
-
- for field_name in field_names {
- let subs = &mut env.subs;
- let field_var = subs.fresh_unnamed_flex_var();
-
- field_vars.push(field_var);
-
- let no_field_label = "NoField";
- let union_tags = UnionTags::tag_without_arguments(subs, no_field_label.into());
- let no_field_var = synth_var(
- subs,
- Content::Structure(FlatType::TagUnion(
- union_tags,
- TagExt::Any(Variable::EMPTY_TAG_UNION),
- )),
- );
- let no_field = Expr::Tag {
- tag_union_var: no_field_var,
- ext_var: Variable::EMPTY_TAG_UNION,
- name: no_field_label.into(),
- arguments: Vec::new(),
- };
- let err_label = "Err";
- let union_tags = UnionTags::for_result(subs, field_var, no_field_var);
- let result_var = synth_var(
- subs,
- Content::Structure(FlatType::TagUnion(
- union_tags,
- TagExt::Any(Variable::EMPTY_TAG_UNION),
- )),
- );
- let field_expr = Expr::Tag {
- tag_union_var: result_var,
- ext_var: env.new_ext_var(ExtensionKind::TagUnion),
- name: err_label.into(),
- arguments: vec![(no_field_var, Loc::at_zero(no_field))],
- };
- result_field_vars.push(result_var);
- let field = Field {
- var: result_var,
- region: Region::zero(),
- loc_expr: Box::new(Loc::at_zero(field_expr)),
- };
-
- initial_state_fields.insert(field_name.clone(), field);
- }
-
- let subs = &mut env.subs;
- let record_field_iter = field_names
- .iter()
- .zip(result_field_vars.iter())
- .map(|(field_name, &var)| (field_name.clone(), RecordField::Required(var)));
- let flat_type = FlatType::Record(
- RecordFields::insert_into_subs(subs, record_field_iter),
- Variable::EMPTY_RECORD,
- );
-
- let state_record_var = synth_var(subs, Content::Structure(flat_type));
-
- (
- state_record_var,
- Expr::Record {
- record_var: state_record_var,
- fields: initial_state_fields,
- },
- )
-}
-
-fn decoder_list(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) {
- // Build
- //
- // def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting
- // def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
- //
- // TODO try to reduce to `Decode.list Decode.decoder`
-
- use Expr::*;
-
- // Decode.list Decode.decoder : Decoder (List elem) fmt
- let (decode_list_call, this_decode_list_ret_var) = {
- // List elem
- let elem_var = env.subs.fresh_unnamed_flex_var();
-
- // Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting
- let (elem_decoder, elem_decoder_var) = {
- // build `Decode.decoder : Decoder elem fmt` type
- // Decoder val fmt | val has Decoding, fmt has EncoderFormatting
- let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
-
- // set val ~ elem
- let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) {
- Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque)
- if vars.type_variables_len == 2 =>
- {
- env.subs.get_subs_slice(vars.type_variables())[0]
- }
- _ => internal_error!("Decode.decode not an opaque type"),
- };
-
- env.unify(val_var, elem_var);
-
- (
- AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var),
- elem_decoder_var,
- )
- };
-
- // Build `Decode.list Decode.decoder` type
- // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
- let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST);
-
- // Decoder elem fmt -a-> b
- let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]);
- let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var();
- let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var();
- let this_decode_list_fn_var = synth_var(
- env.subs,
- Content::Structure(FlatType::Func(
- elem_decoder_var_slice,
- this_decode_list_clos_var,
- this_decode_list_ret_var,
- )),
- );
-
- // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
- // ~ Decoder elem fmt -a -> b
- env.unify(decode_list_fn_var, this_decode_list_fn_var);
-
- let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var);
- let decode_list_fn = Box::new((
- decode_list_fn_var,
- Loc::at_zero(decode_list_member),
- this_decode_list_clos_var,
- this_decode_list_ret_var,
- ));
-
- let decode_list_call = Call(
- decode_list_fn,
- vec![(elem_decoder_var, Loc::at_zero(elem_decoder))],
- CalledVia::Space,
- );
-
- (decode_list_call, this_decode_list_ret_var)
- };
-
- let bytes_sym = env.new_symbol("bytes");
- let fmt_sym = env.new_symbol("fmt");
- let fmt_var = env.subs.fresh_unnamed_flex_var();
- let captures = vec![];
-
- wrap_in_decode_custom_decode_with(
- env,
- bytes_sym,
- (fmt_sym, fmt_var),
- captures,
- (decode_list_call, this_decode_list_ret_var),
- )
-}
-
// Wraps `myDecoder` in `Decode.custom \bytes, fmt -> Decode.decodeWith bytes myDecoder fmt`.
-// I think most can be removed when https://github.com/roc-lang/roc/issues/3724 is resolved.
+//
+// Needed to work around the Higher-Region Restriction. See https://github.com/roc-lang/roc/issues/3724.
fn wrap_in_decode_custom_decode_with(
env: &mut Env,
bytes: Symbol,
diff --git a/crates/compiler/derive/src/decoding/list.rs b/crates/compiler/derive/src/decoding/list.rs
new file mode 100644
index 0000000000..53c89fd1bd
--- /dev/null
+++ b/crates/compiler/derive/src/decoding/list.rs
@@ -0,0 +1,104 @@
+use roc_can::expr::Expr;
+
+use roc_error_macros::internal_error;
+use roc_module::called_via::CalledVia;
+
+use roc_module::symbol::Symbol;
+use roc_region::all::Loc;
+use roc_types::subs::{Content, FlatType, GetSubsSlice, SubsSlice, Variable};
+use roc_types::types::AliasKind;
+
+use crate::decoding::wrap_in_decode_custom_decode_with;
+use crate::synth_var;
+use crate::util::Env;
+
+pub(crate) fn decoder(env: &mut Env<'_>, _def_symbol: Symbol) -> (Expr, Variable) {
+ // Build
+ //
+ // def_symbol : Decoder (List elem) fmt | elem has Decoding, fmt has DecoderFormatting
+ // def_symbol = Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.list Decode.decoder) fmt
+ //
+ // NB: reduction to `Decode.list Decode.decoder` is not possible to the HRR.
+
+ use Expr::*;
+
+ // Decode.list Decode.decoder : Decoder (List elem) fmt
+ let (decode_list_call, this_decode_list_ret_var) = {
+ // List elem
+ let elem_var = env.subs.fresh_unnamed_flex_var();
+
+ // Decode.decoder : Decoder elem fmt | elem has Decoding, fmt has EncoderFormatting
+ let (elem_decoder, elem_decoder_var) = {
+ // build `Decode.decoder : Decoder elem fmt` type
+ // Decoder val fmt | val has Decoding, fmt has EncoderFormatting
+ let elem_decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
+
+ // set val ~ elem
+ let val_var = match env.subs.get_content_without_compacting(elem_decoder_var) {
+ Content::Alias(Symbol::DECODE_DECODER_OPAQUE, vars, _, AliasKind::Opaque)
+ if vars.type_variables_len == 2 =>
+ {
+ env.subs.get_subs_slice(vars.type_variables())[0]
+ }
+ _ => internal_error!("Decode.decode not an opaque type"),
+ };
+
+ env.unify(val_var, elem_var);
+
+ (
+ AbilityMember(Symbol::DECODE_DECODER, None, elem_decoder_var),
+ elem_decoder_var,
+ )
+ };
+
+ // Build `Decode.list Decode.decoder` type
+ // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
+ let decode_list_fn_var = env.import_builtin_symbol_var(Symbol::DECODE_LIST);
+
+ // Decoder elem fmt -a-> b
+ let elem_decoder_var_slice = SubsSlice::insert_into_subs(env.subs, [elem_decoder_var]);
+ let this_decode_list_clos_var = env.subs.fresh_unnamed_flex_var();
+ let this_decode_list_ret_var = env.subs.fresh_unnamed_flex_var();
+ let this_decode_list_fn_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Func(
+ elem_decoder_var_slice,
+ this_decode_list_clos_var,
+ this_decode_list_ret_var,
+ )),
+ );
+
+ // Decoder val fmt -[uls]-> Decoder (List val) fmt | fmt has DecoderFormatting
+ // ~ Decoder elem fmt -a -> b
+ env.unify(decode_list_fn_var, this_decode_list_fn_var);
+
+ let decode_list_member = AbilityMember(Symbol::DECODE_LIST, None, this_decode_list_fn_var);
+ let decode_list_fn = Box::new((
+ decode_list_fn_var,
+ Loc::at_zero(decode_list_member),
+ this_decode_list_clos_var,
+ this_decode_list_ret_var,
+ ));
+
+ let decode_list_call = Call(
+ decode_list_fn,
+ vec![(elem_decoder_var, Loc::at_zero(elem_decoder))],
+ CalledVia::Space,
+ );
+
+ (decode_list_call, this_decode_list_ret_var)
+ };
+
+ let bytes_sym = env.new_symbol("bytes");
+ let fmt_sym = env.new_symbol("fmt");
+ let fmt_var = env.subs.fresh_unnamed_flex_var();
+ let captures = vec![];
+
+ wrap_in_decode_custom_decode_with(
+ env,
+ bytes_sym,
+ (fmt_sym, fmt_var),
+ captures,
+ (decode_list_call, this_decode_list_ret_var),
+ )
+}
diff --git a/crates/compiler/derive/src/decoding/record.rs b/crates/compiler/derive/src/decoding/record.rs
new file mode 100644
index 0000000000..d62ffe2f90
--- /dev/null
+++ b/crates/compiler/derive/src/decoding/record.rs
@@ -0,0 +1,990 @@
+use roc_can::expr::{
+ AnnotatedMark, ClosureData, Expr, Field, Recursive, WhenBranch, WhenBranchPattern,
+};
+use roc_can::pattern::Pattern;
+use roc_collections::SendMap;
+use roc_module::called_via::CalledVia;
+use roc_module::ident::Lowercase;
+use roc_module::symbol::Symbol;
+use roc_region::all::{Loc, Region};
+use roc_types::subs::{
+ Content, ExhaustiveMark, FlatType, LambdaSet, OptVariable, RecordFields, RedundantMark,
+ SubsSlice, TagExt, UnionLambdas, UnionTags, Variable,
+};
+use roc_types::types::RecordField;
+
+use crate::synth_var;
+use crate::util::{Env, ExtensionKind};
+
+use super::wrap_in_decode_custom_decode_with;
+
+/// Implements decoding of a record. For example, for
+///
+/// ```text
+/// {first: a, second: b}
+/// ```
+///
+/// we'd like to generate an impl like
+///
+/// ```roc
+/// decoder : Decoder {first: a, second: b} fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting
+/// decoder =
+/// initialState : {f0: Result a [NoField], f1: Result b [NoField]}
+/// initialState = {f0: Err NoField, f1: Err NoField}
+///
+/// stepField = \state, field ->
+/// when field is
+/// "first" ->
+/// Keep (Decode.custom \bytes, fmt ->
+/// when Decode.decodeWith bytes Decode.decoder fmt is
+/// {result, rest} ->
+/// {result: Result.map result \val -> {state & f0: Ok val}, rest})
+/// "second" ->
+/// Keep (Decode.custom \bytes, fmt ->
+/// when Decode.decodeWith bytes Decode.decoder fmt is
+/// {result, rest} ->
+/// {result: Result.map result \val -> {state & f1: Ok val}, rest})
+/// _ -> Skip
+///
+/// finalizer = \{f0, f1} ->
+/// when f0 is
+/// Ok first ->
+/// when f1 is
+/// Ok second -> Ok {first, second}
+/// Err NoField -> Err TooShort
+/// Err NoField -> Err TooShort
+///
+/// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.record initialState stepField finalizer) fmt
+/// ```
+pub(crate) fn decoder(
+ env: &mut Env,
+ _def_symbol: Symbol,
+ fields: Vec,
+) -> (Expr, Variable) {
+ // The decoded type of each field in the record, e.g. {first: a, second: b}.
+ let mut field_vars = Vec::with_capacity(fields.len());
+ // The type of each field in the decoding state, e.g. {first: Result a [NoField], second: Result b [NoField]}
+ let mut result_field_vars = Vec::with_capacity(fields.len());
+
+ // initialState = ...
+ let (initial_state_var, initial_state) =
+ initial_state(env, &fields, &mut field_vars, &mut result_field_vars);
+
+ // finalizer = ...
+ let (finalizer, finalizer_var, decode_err_var) = finalizer(
+ env,
+ initial_state_var,
+ &fields,
+ &field_vars,
+ &result_field_vars,
+ );
+
+ // stepField = ...
+ let (step_field, step_var) = step_field(
+ env,
+ fields,
+ &field_vars,
+ &result_field_vars,
+ initial_state_var,
+ decode_err_var,
+ );
+
+ // Build up the type of `Decode.record` we expect
+ let record_decoder_var = env.subs.fresh_unnamed_flex_var();
+ let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var();
+ let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_RECORD);
+ let this_decode_record_var = {
+ let flat_type = FlatType::Func(
+ SubsSlice::insert_into_subs(env.subs, [initial_state_var, step_var, finalizer_var]),
+ decode_record_lambda_set,
+ record_decoder_var,
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ env.unify(decode_record_var, this_decode_record_var);
+
+ // Decode.record initialState stepField finalizer
+ let call_decode_record = Expr::Call(
+ Box::new((
+ this_decode_record_var,
+ Loc::at_zero(Expr::AbilityMember(
+ Symbol::DECODE_RECORD,
+ None,
+ this_decode_record_var,
+ )),
+ decode_record_lambda_set,
+ record_decoder_var,
+ )),
+ vec![
+ (initial_state_var, Loc::at_zero(initial_state)),
+ (step_var, Loc::at_zero(step_field)),
+ (finalizer_var, Loc::at_zero(finalizer)),
+ ],
+ CalledVia::Space,
+ );
+
+ let (call_decode_custom, decode_custom_ret_var) = {
+ let bytes_sym = env.new_symbol("bytes");
+ let fmt_sym = env.new_symbol("fmt");
+ let fmt_var = env.subs.fresh_unnamed_flex_var();
+
+ let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with(
+ env,
+ bytes_sym,
+ (fmt_sym, fmt_var),
+ vec![],
+ (call_decode_record, record_decoder_var),
+ );
+
+ (decode_custom, decode_custom_var)
+ };
+
+ (call_decode_custom, decode_custom_ret_var)
+}
+
+// Example:
+// stepField = \state, field ->
+// when field is
+// "first" ->
+// Keep (Decode.custom \bytes, fmt ->
+// # Uses a single-branch `when` because `let` is more expensive to monomorphize
+// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
+// when Decode.decodeWith bytes Decode.decoder fmt is
+// rec ->
+// {
+// rest: rec.rest,
+// result: when rec.result is
+// Ok val -> Ok {state & first: Ok val},
+// Err err -> Err err
+// })
+//
+// "second" ->
+// Keep (Decode.custom \bytes, fmt ->
+// when Decode.decodeWith bytes Decode.decoder fmt is
+// rec ->
+// {
+// rest: rec.rest,
+// result: when rec.result is
+// Ok val -> Ok {state & second: Ok val},
+// Err err -> Err err
+// })
+//
+// _ -> Skip
+fn step_field(
+ env: &mut Env,
+ fields: Vec,
+ field_vars: &[Variable],
+ result_field_vars: &[Variable],
+ state_record_var: Variable,
+ decode_err_var: Variable,
+) -> (Expr, Variable) {
+ let state_arg_symbol = env.new_symbol("stateRecord");
+ let field_arg_symbol = env.new_symbol("field");
+
+ // +1 because of the default branch.
+ let mut branches = Vec::with_capacity(fields.len() + 1);
+ let keep_payload_var = env.subs.fresh_unnamed_flex_var();
+ let keep_or_skip_var = {
+ let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]);
+ let flat_type = FlatType::TagUnion(
+ UnionTags::insert_slices_into_subs(
+ env.subs,
+ [
+ ("Keep".into(), keep_payload_subs_slice),
+ ("Skip".into(), Default::default()),
+ ],
+ ),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ for ((field_name, &field_var), &result_field_var) in fields
+ .into_iter()
+ .zip(field_vars.iter())
+ .zip(result_field_vars.iter())
+ {
+ // Example:
+ // "first" ->
+ // Keep (Decode.custom \bytes, fmt ->
+ // # Uses a single-branch `when` because `let` is more expensive to monomorphize
+ // # due to checks for polymorphic expressions, and `rec` would be polymorphic.
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ // }
+ // )
+
+ let this_custom_callback_var;
+ let custom_callback_ret_var;
+ let custom_callback = {
+ // \bytes, fmt ->
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ // }
+ let bytes_arg_symbol = env.new_symbol("bytes");
+ let fmt_arg_symbol = env.new_symbol("fmt");
+ let bytes_arg_var = env.subs.fresh_unnamed_flex_var();
+ let fmt_arg_var = env.subs.fresh_unnamed_flex_var();
+
+ // rec.result : [Ok field_var, Err DecodeError]
+ let rec_dot_result = {
+ let tag_union = FlatType::TagUnion(
+ UnionTags::for_result(env.subs, field_var, decode_err_var),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(tag_union))
+ };
+
+ // rec : { rest: List U8, result: (typeof rec.result) }
+ let rec_var = {
+ let fields = RecordFields::insert_into_subs(
+ env.subs,
+ [
+ ("rest".into(), RecordField::Required(Variable::LIST_U8)),
+ ("result".into(), RecordField::Required(rec_dot_result)),
+ ],
+ );
+ let record = FlatType::Record(fields, Variable::EMPTY_RECORD);
+
+ synth_var(env.subs, Content::Structure(record))
+ };
+
+ // `Decode.decoder` for the field's value
+ let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
+ let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
+ let lambda_set_var = env.subs.fresh_unnamed_flex_var();
+ let this_decode_with_var = {
+ let subs_slice = SubsSlice::insert_into_subs(
+ env.subs,
+ [bytes_arg_var, decoder_var, fmt_arg_var],
+ );
+ let this_decode_with_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)),
+ );
+
+ env.unify(decode_with_var, this_decode_with_var);
+
+ this_decode_with_var
+ };
+
+ // The result of decoding this field's value - either the updated state, or a decoding error.
+ let when_expr_var = {
+ let flat_type = FlatType::TagUnion(
+ UnionTags::for_result(env.subs, state_record_var, decode_err_var),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ // What our decoder passed to `Decode.custom` returns - the result of decoding the
+ // field's value, and the remaining bytes.
+ custom_callback_ret_var = {
+ let rest_field = RecordField::Required(Variable::LIST_U8);
+ let result_field = RecordField::Required(when_expr_var);
+ let flat_type = FlatType::Record(
+ RecordFields::insert_into_subs(
+ env.subs,
+ [("rest".into(), rest_field), ("result".into(), result_field)],
+ ),
+ Variable::EMPTY_RECORD,
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ let custom_callback_body = {
+ let rec_symbol = env.new_symbol("rec");
+
+ // # Uses a single-branch `when` because `let` is more expensive to monomorphize
+ // # due to checks for polymorphic expressions, and `rec` would be polymorphic.
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ // }
+ let branch_body = {
+ let result_val = {
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ let ok_val_symbol = env.new_symbol("val");
+ let err_val_symbol = env.new_symbol("err");
+ let ok_branch_expr = {
+ // Ok {state & first: Ok val},
+ let mut updates = SendMap::default();
+
+ updates.insert(
+ field_name.clone(),
+ Field {
+ var: result_field_var,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(Expr::Tag {
+ tag_union_var: result_field_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Ok".into(),
+ arguments: vec![(
+ field_var,
+ Loc::at_zero(Expr::Var(ok_val_symbol, field_var)),
+ )],
+ })),
+ },
+ );
+
+ let updated_record = Expr::RecordUpdate {
+ record_var: state_record_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ symbol: state_arg_symbol,
+ updates,
+ };
+
+ Expr::Tag {
+ tag_union_var: when_expr_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Ok".into(),
+ arguments: vec![(state_record_var, Loc::at_zero(updated_record))],
+ }
+ };
+
+ let branches = vec![
+ // Ok val -> Ok {state & first: Ok val},
+ WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::AppliedTag {
+ whole_var: rec_dot_result,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ tag_name: "Ok".into(),
+ arguments: vec![(
+ field_var,
+ Loc::at_zero(Pattern::Identifier(ok_val_symbol)),
+ )],
+ }),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(ok_branch_expr),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ },
+ // Err err -> Err err
+ WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::AppliedTag {
+ whole_var: rec_dot_result,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ tag_name: "Err".into(),
+ arguments: vec![(
+ decode_err_var,
+ Loc::at_zero(Pattern::Identifier(err_val_symbol)),
+ )],
+ }),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(Expr::Tag {
+ tag_union_var: when_expr_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Err".into(),
+ arguments: vec![(
+ decode_err_var,
+ Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)),
+ )],
+ }),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ },
+ ];
+
+ // when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ Expr::When {
+ loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess {
+ record_var: rec_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ field_var: rec_dot_result,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
+ field: "result".into(),
+ })),
+ cond_var: rec_dot_result,
+ expr_var: when_expr_var,
+ region: Region::zero(),
+ branches,
+ branches_cond_var: rec_dot_result,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ }
+ };
+
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ // }
+ let mut fields_map = SendMap::default();
+
+ fields_map.insert(
+ "rest".into(),
+ Field {
+ var: Variable::LIST_U8,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess {
+ record_var: rec_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ field_var: Variable::LIST_U8,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
+ field: "rest".into(),
+ })),
+ },
+ );
+
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ fields_map.insert(
+ "result".into(),
+ Field {
+ var: when_expr_var,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(result_val)),
+ },
+ );
+
+ Expr::Record {
+ record_var: custom_callback_ret_var,
+ fields: fields_map,
+ }
+ };
+
+ let branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(branch_body),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ let condition_expr = Expr::Call(
+ Box::new((
+ this_decode_with_var,
+ Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)),
+ lambda_set_var,
+ rec_var,
+ )),
+ vec![
+ (
+ Variable::LIST_U8,
+ Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)),
+ ),
+ (
+ decoder_var,
+ Loc::at_zero(Expr::AbilityMember(
+ Symbol::DECODE_DECODER,
+ None,
+ decoder_var,
+ )),
+ ),
+ (
+ fmt_arg_var,
+ Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)),
+ ),
+ ],
+ CalledVia::Space,
+ );
+
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ Expr::When {
+ loc_cond: Box::new(Loc::at_zero(condition_expr)),
+ cond_var: rec_var,
+ expr_var: custom_callback_ret_var,
+ region: Region::zero(),
+ branches: vec![branch],
+ branches_cond_var: rec_var,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ }
+ };
+
+ let custom_closure_symbol = env.new_symbol("customCallback");
+ this_custom_callback_var = env.subs.fresh_unnamed_flex_var();
+ let custom_callback_lambda_set_var = {
+ let content = Content::LambdaSet(LambdaSet {
+ solved: UnionLambdas::insert_into_subs(
+ env.subs,
+ [(custom_closure_symbol, [state_record_var])],
+ ),
+ recursion_var: OptVariable::NONE,
+ unspecialized: Default::default(),
+ ambient_function: this_custom_callback_var,
+ });
+ let custom_callback_lambda_set_var = synth_var(env.subs, content);
+ let subs_slice =
+ SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]);
+
+ env.subs.set_content(
+ this_custom_callback_var,
+ Content::Structure(FlatType::Func(
+ subs_slice,
+ custom_callback_lambda_set_var,
+ custom_callback_ret_var,
+ )),
+ );
+
+ custom_callback_lambda_set_var
+ };
+
+ // \bytes, fmt -> …
+ Expr::Closure(ClosureData {
+ function_type: this_custom_callback_var,
+ closure_type: custom_callback_lambda_set_var,
+ return_type: custom_callback_ret_var,
+ name: custom_closure_symbol,
+ captured_symbols: vec![(state_arg_symbol, state_record_var)],
+ recursive: Recursive::NotRecursive,
+ arguments: vec![
+ (
+ bytes_arg_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)),
+ ),
+ (
+ fmt_arg_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)),
+ ),
+ ],
+ loc_body: Box::new(Loc::at_zero(custom_callback_body)),
+ })
+ };
+
+ let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
+ let decode_custom = {
+ let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
+ let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var();
+ let this_decode_custom_var = {
+ let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]);
+ let flat_type =
+ FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var);
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ env.unify(decode_custom_var, this_decode_custom_var);
+
+ // Decode.custom \bytes, fmt -> …
+ Expr::Call(
+ Box::new((
+ this_decode_custom_var,
+ Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)),
+ decode_custom_closure_var,
+ decode_custom_ret_var,
+ )),
+ vec![(this_custom_callback_var, Loc::at_zero(custom_callback))],
+ CalledVia::Space,
+ )
+ };
+
+ env.unify(keep_payload_var, decode_custom_ret_var);
+
+ let keep = {
+ // Keep (Decode.custom \bytes, fmt ->
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ // }
+ // )
+ Expr::Tag {
+ tag_union_var: keep_or_skip_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Keep".into(),
+ arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))],
+ }
+ };
+
+ let branch = {
+ // "first" ->
+ // Keep (Decode.custom \bytes, fmt ->
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & first: Ok val},
+ // Err err -> Err err
+ // }
+ // )
+ WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::StrLiteral(field_name.into())),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(keep),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ }
+ };
+
+ branches.push(branch);
+ }
+
+ // Example: `_ -> Skip`
+ let default_branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::Underscore),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(Expr::Tag {
+ tag_union_var: keep_or_skip_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Skip".into(),
+ arguments: Vec::new(),
+ }),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ branches.push(default_branch);
+
+ // when field is
+ let body = Expr::When {
+ loc_cond: Box::new(Loc::at_zero(Expr::Var(field_arg_symbol, Variable::STR))),
+ cond_var: Variable::STR,
+ expr_var: keep_or_skip_var,
+ region: Region::zero(),
+ branches,
+ branches_cond_var: Variable::STR,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ };
+
+ let step_field_closure = env.new_symbol("stepField");
+ let function_type = env.subs.fresh_unnamed_flex_var();
+ let closure_type = {
+ let lambda_set = LambdaSet {
+ solved: UnionLambdas::tag_without_arguments(env.subs, step_field_closure),
+ recursion_var: OptVariable::NONE,
+ unspecialized: Default::default(),
+ ambient_function: function_type,
+ };
+
+ synth_var(env.subs, Content::LambdaSet(lambda_set))
+ };
+
+ {
+ let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::STR]);
+
+ env.subs.set_content(
+ function_type,
+ Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)),
+ )
+ };
+
+ let expr = Expr::Closure(ClosureData {
+ function_type,
+ closure_type,
+ return_type: keep_or_skip_var,
+ name: step_field_closure,
+ captured_symbols: Vec::new(),
+ recursive: Recursive::NotRecursive,
+ arguments: vec![
+ (
+ state_record_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
+ ),
+ (
+ Variable::STR,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(field_arg_symbol)),
+ ),
+ ],
+ loc_body: Box::new(Loc::at_zero(body)),
+ });
+
+ (expr, function_type)
+}
+
+// Example:
+// finalizer = \rec ->
+// when rec.first is
+// Ok first ->
+// when rec.second is
+// Ok second -> Ok {first, second}
+// Err NoField -> Err TooShort
+// Err NoField -> Err TooShort
+fn finalizer(
+ env: &mut Env,
+ state_record_var: Variable,
+ fields: &[Lowercase],
+ field_vars: &[Variable],
+ result_field_vars: &[Variable],
+) -> (Expr, Variable, Variable) {
+ let state_arg_symbol = env.new_symbol("stateRecord");
+ let mut fields_map = SendMap::default();
+ let mut pattern_symbols = Vec::with_capacity(fields.len());
+ let decode_err_var = {
+ let flat_type = FlatType::TagUnion(
+ UnionTags::tag_without_arguments(env.subs, "TooShort".into()),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ for (field_name, &field_var) in fields.iter().zip(field_vars.iter()) {
+ let symbol = env.new_symbol(field_name.as_str());
+
+ pattern_symbols.push(symbol);
+
+ let field_expr = Expr::Var(symbol, field_var);
+ let field = Field {
+ var: field_var,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(field_expr)),
+ };
+
+ fields_map.insert(field_name.clone(), field);
+ }
+
+ // The bottom of the happy path - return the decoded record {first: a, second: b} wrapped with
+ // "Ok".
+ let return_type_var;
+ let mut body = {
+ let subs = &mut env.subs;
+ let record_field_iter = fields
+ .iter()
+ .zip(field_vars.iter())
+ .map(|(field_name, &field_var)| (field_name.clone(), RecordField::Required(field_var)));
+ let flat_type = FlatType::Record(
+ RecordFields::insert_into_subs(subs, record_field_iter),
+ Variable::EMPTY_RECORD,
+ );
+ let done_record_var = synth_var(subs, Content::Structure(flat_type));
+ let done_record = Expr::Record {
+ record_var: done_record_var,
+ fields: fields_map,
+ };
+
+ return_type_var = {
+ let flat_type = FlatType::TagUnion(
+ UnionTags::for_result(subs, done_record_var, decode_err_var),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(subs, Content::Structure(flat_type))
+ };
+
+ Expr::Tag {
+ tag_union_var: return_type_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Ok".into(),
+ arguments: vec![(done_record_var, Loc::at_zero(done_record))],
+ }
+ };
+
+ // Unwrap each result in the decoded state
+ //
+ // when rec.first is
+ // Ok first -> ...happy path...
+ // Err NoField -> Err TooShort
+ for (((symbol, field_name), &field_var), &result_field_var) in pattern_symbols
+ .iter()
+ .rev()
+ .zip(fields.iter().rev())
+ .zip(field_vars.iter().rev())
+ .zip(result_field_vars.iter().rev())
+ {
+ // when rec.first is
+ let cond_expr = Expr::RecordAccess {
+ record_var: state_record_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ field_var: result_field_var,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))),
+ field: field_name.clone(),
+ };
+
+ // Example: `Ok x -> expr`
+ let ok_branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::AppliedTag {
+ whole_var: result_field_var,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ tag_name: "Ok".into(),
+ arguments: vec![(field_var, Loc::at_zero(Pattern::Identifier(*symbol)))],
+ }),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(body),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ // Example: `_ -> Err TooShort`
+ let err_branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::Underscore),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(Expr::Tag {
+ tag_union_var: return_type_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Err".into(),
+ arguments: vec![(
+ decode_err_var,
+ Loc::at_zero(Expr::Tag {
+ tag_union_var: decode_err_var,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ name: "TooShort".into(),
+ arguments: Vec::new(),
+ }),
+ )],
+ }),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ body = Expr::When {
+ loc_cond: Box::new(Loc::at_zero(cond_expr)),
+ cond_var: result_field_var,
+ expr_var: return_type_var,
+ region: Region::zero(),
+ branches: vec![ok_branch, err_branch],
+ branches_cond_var: result_field_var,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ };
+ }
+
+ let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later.
+ let function_symbol = env.new_symbol("finalizer");
+ let lambda_set = LambdaSet {
+ solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol),
+ recursion_var: OptVariable::NONE,
+ unspecialized: Default::default(),
+ ambient_function: function_var,
+ };
+ let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set));
+ let flat_type = FlatType::Func(
+ SubsSlice::insert_into_subs(env.subs, [state_record_var]),
+ closure_type,
+ return_type_var,
+ );
+
+ // Fix up function_var so it's not Content::Error anymore
+ env.subs
+ .set_content(function_var, Content::Structure(flat_type));
+
+ let finalizer = Expr::Closure(ClosureData {
+ function_type: function_var,
+ closure_type,
+ return_type: return_type_var,
+ name: function_symbol,
+ captured_symbols: Vec::new(),
+ recursive: Recursive::NotRecursive,
+ arguments: vec![(
+ state_record_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
+ )],
+ loc_body: Box::new(Loc::at_zero(body)),
+ });
+
+ (finalizer, function_var, decode_err_var)
+}
+
+// Example:
+// initialState : {first: Result a [NoField], second: Result b [NoField]}
+// initialState = {first: Err NoField, second: Err NoField}
+fn initial_state(
+ env: &mut Env<'_>,
+ field_names: &[Lowercase],
+ field_vars: &mut Vec,
+ result_field_vars: &mut Vec,
+) -> (Variable, Expr) {
+ let mut initial_state_fields = SendMap::default();
+
+ for field_name in field_names {
+ let subs = &mut env.subs;
+ let field_var = subs.fresh_unnamed_flex_var();
+
+ field_vars.push(field_var);
+
+ let no_field_label = "NoField";
+ let union_tags = UnionTags::tag_without_arguments(subs, no_field_label.into());
+ let no_field_var = synth_var(
+ subs,
+ Content::Structure(FlatType::TagUnion(
+ union_tags,
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ )),
+ );
+ let no_field = Expr::Tag {
+ tag_union_var: no_field_var,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ name: no_field_label.into(),
+ arguments: Vec::new(),
+ };
+ let err_label = "Err";
+ let union_tags = UnionTags::for_result(subs, field_var, no_field_var);
+ let result_var = synth_var(
+ subs,
+ Content::Structure(FlatType::TagUnion(
+ union_tags,
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ )),
+ );
+ let field_expr = Expr::Tag {
+ tag_union_var: result_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: err_label.into(),
+ arguments: vec![(no_field_var, Loc::at_zero(no_field))],
+ };
+ result_field_vars.push(result_var);
+ let field = Field {
+ var: result_var,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(field_expr)),
+ };
+
+ initial_state_fields.insert(field_name.clone(), field);
+ }
+
+ let subs = &mut env.subs;
+ let record_field_iter = field_names
+ .iter()
+ .zip(result_field_vars.iter())
+ .map(|(field_name, &var)| (field_name.clone(), RecordField::Required(var)));
+ let flat_type = FlatType::Record(
+ RecordFields::insert_into_subs(subs, record_field_iter),
+ Variable::EMPTY_RECORD,
+ );
+
+ let state_record_var = synth_var(subs, Content::Structure(flat_type));
+
+ (
+ state_record_var,
+ Expr::Record {
+ record_var: state_record_var,
+ fields: initial_state_fields,
+ },
+ )
+}
diff --git a/crates/compiler/derive/src/decoding/tuple.rs b/crates/compiler/derive/src/decoding/tuple.rs
new file mode 100644
index 0000000000..b6ce201691
--- /dev/null
+++ b/crates/compiler/derive/src/decoding/tuple.rs
@@ -0,0 +1,994 @@
+use roc_can::expr::{
+ AnnotatedMark, ClosureData, Expr, Field, IntValue, Recursive, WhenBranch, WhenBranchPattern,
+};
+use roc_can::num::{IntBound, IntLitWidth};
+use roc_can::pattern::Pattern;
+use roc_collections::SendMap;
+use roc_module::called_via::CalledVia;
+use roc_module::ident::Lowercase;
+use roc_module::symbol::Symbol;
+use roc_region::all::{Loc, Region};
+use roc_types::subs::{
+ Content, ExhaustiveMark, FlatType, LambdaSet, OptVariable, RecordFields, RedundantMark,
+ SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags, Variable,
+};
+use roc_types::types::RecordField;
+
+use crate::synth_var;
+use crate::util::{Env, ExtensionKind};
+
+use super::wrap_in_decode_custom_decode_with;
+
+/// Implements decoding of a tuple. For example, for
+///
+/// ```text
+/// (a, b)
+/// ```
+///
+/// we'd like to generate an impl like
+///
+/// ```roc
+/// decoder : Decoder (a, b) fmt | a has Decoding, b has Decoding, fmt has DecoderFormatting
+/// decoder =
+/// initialState : {e0: Result a [NoElem], e1: Result b [NoElem]}
+/// initialState = {e0: Err NoElem, e1: Err NoElem}
+///
+/// stepElem = \state, index ->
+/// when index is
+/// 0 ->
+/// Next (Decode.custom \bytes, fmt ->
+/// when Decode.decodeWith bytes Decode.decoder fmt is
+/// {result, rest} ->
+/// {result: Result.map result \val -> {state & e0: Ok val}, rest})
+/// 1 ->
+/// Next (Decode.custom \bytes, fmt ->
+/// when Decode.decodeWith bytes Decode.decoder fmt is
+/// {result, rest} ->
+/// {result: Result.map result \val -> {state & e1: Ok val}, rest})
+/// _ -> TooLong
+///
+/// finalizer = \st ->
+/// when st.e0 is
+/// Ok e0 ->
+/// when st.e1 is
+/// Ok e1 -> Ok (e0, e1)
+/// Err NoElem -> Err TooShort
+/// Err NoElem -> Err TooShort
+///
+/// Decode.custom \bytes, fmt -> Decode.decodeWith bytes (Decode.tuple initialState stepElem finalizer) fmt
+/// ```
+pub(crate) fn decoder(env: &mut Env, _def_symbol: Symbol, arity: u32) -> (Expr, Variable) {
+ // The decoded type of each index in the tuple, e.g. (a, b).
+ let mut index_vars = Vec::with_capacity(arity as _);
+ // The type of each index in the decoding state, e.g. {e0: Result a [NoElem], e1: Result b [NoElem]}
+ let mut state_fields = Vec::with_capacity(arity as _);
+ let mut state_field_vars = Vec::with_capacity(arity as _);
+
+ // initialState = ...
+ let (state_var, initial_state) = initial_state(
+ env,
+ arity,
+ &mut index_vars,
+ &mut state_fields,
+ &mut state_field_vars,
+ );
+
+ // finalizer = ...
+ let (finalizer, finalizer_var, decode_err_var) = finalizer(
+ env,
+ &index_vars,
+ state_var,
+ &state_fields,
+ &state_field_vars,
+ );
+
+ // stepElem = ...
+ let (step_elem, step_var) = step_elem(
+ env,
+ &index_vars,
+ state_var,
+ &state_fields,
+ &state_field_vars,
+ decode_err_var,
+ );
+
+ // Build up the type of `Decode.tuple` we expect
+ let tuple_decoder_var = env.subs.fresh_unnamed_flex_var();
+ let decode_record_lambda_set = env.subs.fresh_unnamed_flex_var();
+ let decode_record_var = env.import_builtin_symbol_var(Symbol::DECODE_TUPLE);
+ let this_decode_record_var = {
+ let flat_type = FlatType::Func(
+ SubsSlice::insert_into_subs(env.subs, [state_var, step_var, finalizer_var]),
+ decode_record_lambda_set,
+ tuple_decoder_var,
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ env.unify(decode_record_var, this_decode_record_var);
+
+ // Decode.tuple initialState stepElem finalizer
+ let call_decode_record = Expr::Call(
+ Box::new((
+ this_decode_record_var,
+ Loc::at_zero(Expr::AbilityMember(
+ Symbol::DECODE_TUPLE,
+ None,
+ this_decode_record_var,
+ )),
+ decode_record_lambda_set,
+ tuple_decoder_var,
+ )),
+ vec![
+ (state_var, Loc::at_zero(initial_state)),
+ (step_var, Loc::at_zero(step_elem)),
+ (finalizer_var, Loc::at_zero(finalizer)),
+ ],
+ CalledVia::Space,
+ );
+
+ let (call_decode_custom, decode_custom_ret_var) = {
+ let bytes_sym = env.new_symbol("bytes");
+ let fmt_sym = env.new_symbol("fmt");
+ let fmt_var = env.subs.fresh_unnamed_flex_var();
+
+ let (decode_custom, decode_custom_var) = wrap_in_decode_custom_decode_with(
+ env,
+ bytes_sym,
+ (fmt_sym, fmt_var),
+ vec![],
+ (call_decode_record, tuple_decoder_var),
+ );
+
+ (decode_custom, decode_custom_var)
+ };
+
+ (call_decode_custom, decode_custom_ret_var)
+}
+
+// Example:
+// stepElem = \state, index ->
+// when index is
+// 0 ->
+// Next (Decode.custom \bytes, fmt ->
+// # Uses a single-branch `when` because `let` is more expensive to monomorphize
+// # due to checks for polymorphic expressions, and `rec` would be polymorphic.
+// when Decode.decodeWith bytes Decode.decoder fmt is
+// rec ->
+// {
+// rest: rec.rest,
+// result: when rec.result is
+// Ok val -> Ok {state & e0: Ok val},
+// Err err -> Err err
+// })
+//
+// "e1" ->
+// Next (Decode.custom \bytes, fmt ->
+// when Decode.decodeWith bytes Decode.decoder fmt is
+// rec ->
+// {
+// rest: rec.rest,
+// result: when rec.result is
+// Ok val -> Ok {state & e1: Ok val},
+// Err err -> Err err
+// })
+//
+// _ -> TooLong
+fn step_elem(
+ env: &mut Env,
+ index_vars: &[Variable],
+ state_record_var: Variable,
+ state_fields: &[Lowercase],
+ state_field_vars: &[Variable],
+ decode_err_var: Variable,
+) -> (Expr, Variable) {
+ let state_arg_symbol = env.new_symbol("stateRecord");
+ let index_arg_symbol = env.new_symbol("index");
+
+ // +1 because of the default branch.
+ let mut branches = Vec::with_capacity(index_vars.len() + 1);
+ let keep_payload_var = env.subs.fresh_unnamed_flex_var();
+ let keep_or_skip_var = {
+ let keep_payload_subs_slice = SubsSlice::insert_into_subs(env.subs, [keep_payload_var]);
+ let flat_type = FlatType::TagUnion(
+ UnionTags::insert_slices_into_subs(
+ env.subs,
+ [
+ ("Next".into(), keep_payload_subs_slice),
+ ("TooLong".into(), Default::default()),
+ ],
+ ),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ for (((index, state_field), &index_var), &result_index_var) in state_fields
+ .iter()
+ .enumerate()
+ .zip(index_vars)
+ .zip(state_field_vars)
+ {
+ // Example:
+ // 0 ->
+ // Next (Decode.custom \bytes, fmt ->
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ // }
+ // )
+
+ let this_custom_callback_var;
+ let custom_callback_ret_var;
+ let custom_callback = {
+ // \bytes, fmt ->
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ // }
+ let bytes_arg_symbol = env.new_symbol("bytes");
+ let fmt_arg_symbol = env.new_symbol("fmt");
+ let bytes_arg_var = env.subs.fresh_unnamed_flex_var();
+ let fmt_arg_var = env.subs.fresh_unnamed_flex_var();
+
+ // rec.result : [Ok index_var, Err DecodeError]
+ let rec_dot_result = {
+ let tag_union = FlatType::TagUnion(
+ UnionTags::for_result(env.subs, index_var, decode_err_var),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(tag_union))
+ };
+
+ // rec : { rest: List U8, result: (typeof rec.result) }
+ let rec_var = {
+ let indexs = RecordFields::insert_into_subs(
+ env.subs,
+ [
+ ("rest".into(), RecordField::Required(Variable::LIST_U8)),
+ ("result".into(), RecordField::Required(rec_dot_result)),
+ ],
+ );
+ let record = FlatType::Record(indexs, Variable::EMPTY_RECORD);
+
+ synth_var(env.subs, Content::Structure(record))
+ };
+
+ // `Decode.decoder` for the index's value
+ let decoder_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODER);
+ let decode_with_var = env.import_builtin_symbol_var(Symbol::DECODE_DECODE_WITH);
+ let lambda_set_var = env.subs.fresh_unnamed_flex_var();
+ let this_decode_with_var = {
+ let subs_slice = SubsSlice::insert_into_subs(
+ env.subs,
+ [bytes_arg_var, decoder_var, fmt_arg_var],
+ );
+ let this_decode_with_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Func(subs_slice, lambda_set_var, rec_var)),
+ );
+
+ env.unify(decode_with_var, this_decode_with_var);
+
+ this_decode_with_var
+ };
+
+ // The result of decoding this index's value - either the updated state, or a decoding error.
+ let when_expr_var = {
+ let flat_type = FlatType::TagUnion(
+ UnionTags::for_result(env.subs, state_record_var, decode_err_var),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ // What our decoder passed to `Decode.custom` returns - the result of decoding the
+ // index's value, and the remaining bytes.
+ custom_callback_ret_var = {
+ let rest_index = RecordField::Required(Variable::LIST_U8);
+ let result_index = RecordField::Required(when_expr_var);
+ let flat_type = FlatType::Record(
+ RecordFields::insert_into_subs(
+ env.subs,
+ [("rest".into(), rest_index), ("result".into(), result_index)],
+ ),
+ Variable::EMPTY_RECORD,
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ let custom_callback_body = {
+ let rec_symbol = env.new_symbol("rec");
+
+ // # Uses a single-branch `when` because `let` is more expensive to monomorphize
+ // # due to checks for polymorphic expressions, and `rec` would be polymorphic.
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ // }
+ let branch_body = {
+ let result_val = {
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ let ok_val_symbol = env.new_symbol("val");
+ let err_val_symbol = env.new_symbol("err");
+ let ok_branch_expr = {
+ // Ok {state & e0: Ok val},
+ let mut updates = SendMap::default();
+
+ updates.insert(
+ state_field.clone(),
+ Field {
+ var: result_index_var,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(Expr::Tag {
+ tag_union_var: result_index_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Ok".into(),
+ arguments: vec![(
+ index_var,
+ Loc::at_zero(Expr::Var(ok_val_symbol, index_var)),
+ )],
+ })),
+ },
+ );
+
+ let updated_record = Expr::RecordUpdate {
+ record_var: state_record_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ symbol: state_arg_symbol,
+ updates,
+ };
+
+ Expr::Tag {
+ tag_union_var: when_expr_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Ok".into(),
+ arguments: vec![(state_record_var, Loc::at_zero(updated_record))],
+ }
+ };
+
+ let branches = vec![
+ // Ok val -> Ok {state & e0: Ok val},
+ WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::AppliedTag {
+ whole_var: rec_dot_result,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ tag_name: "Ok".into(),
+ arguments: vec![(
+ index_var,
+ Loc::at_zero(Pattern::Identifier(ok_val_symbol)),
+ )],
+ }),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(ok_branch_expr),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ },
+ // Err err -> Err err
+ WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::AppliedTag {
+ whole_var: rec_dot_result,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ tag_name: "Err".into(),
+ arguments: vec![(
+ decode_err_var,
+ Loc::at_zero(Pattern::Identifier(err_val_symbol)),
+ )],
+ }),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(Expr::Tag {
+ tag_union_var: when_expr_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Err".into(),
+ arguments: vec![(
+ decode_err_var,
+ Loc::at_zero(Expr::Var(err_val_symbol, decode_err_var)),
+ )],
+ }),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ },
+ ];
+
+ // when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ Expr::When {
+ loc_cond: Box::new(Loc::at_zero(Expr::RecordAccess {
+ record_var: rec_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ field_var: rec_dot_result,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
+ field: "result".into(),
+ })),
+ cond_var: rec_dot_result,
+ expr_var: when_expr_var,
+ region: Region::zero(),
+ branches,
+ branches_cond_var: rec_dot_result,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ }
+ };
+
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ // }
+ let mut fields_map = SendMap::default();
+
+ fields_map.insert(
+ "rest".into(),
+ Field {
+ var: Variable::LIST_U8,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(Expr::RecordAccess {
+ record_var: rec_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ field_var: Variable::LIST_U8,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(rec_symbol, rec_var))),
+ field: "rest".into(),
+ })),
+ },
+ );
+
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ fields_map.insert(
+ "result".into(),
+ Field {
+ var: when_expr_var,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(result_val)),
+ },
+ );
+
+ Expr::Record {
+ record_var: custom_callback_ret_var,
+ fields: fields_map,
+ }
+ };
+
+ let branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::Identifier(rec_symbol)),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(branch_body),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ let condition_expr = Expr::Call(
+ Box::new((
+ this_decode_with_var,
+ Loc::at_zero(Expr::Var(Symbol::DECODE_DECODE_WITH, this_decode_with_var)),
+ lambda_set_var,
+ rec_var,
+ )),
+ vec![
+ (
+ Variable::LIST_U8,
+ Loc::at_zero(Expr::Var(bytes_arg_symbol, Variable::LIST_U8)),
+ ),
+ (
+ decoder_var,
+ Loc::at_zero(Expr::AbilityMember(
+ Symbol::DECODE_DECODER,
+ None,
+ decoder_var,
+ )),
+ ),
+ (
+ fmt_arg_var,
+ Loc::at_zero(Expr::Var(fmt_arg_symbol, fmt_arg_var)),
+ ),
+ ],
+ CalledVia::Space,
+ );
+
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ Expr::When {
+ loc_cond: Box::new(Loc::at_zero(condition_expr)),
+ cond_var: rec_var,
+ expr_var: custom_callback_ret_var,
+ region: Region::zero(),
+ branches: vec![branch],
+ branches_cond_var: rec_var,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ }
+ };
+
+ let custom_closure_symbol = env.new_symbol("customCallback");
+ this_custom_callback_var = env.subs.fresh_unnamed_flex_var();
+ let custom_callback_lambda_set_var = {
+ let content = Content::LambdaSet(LambdaSet {
+ solved: UnionLambdas::insert_into_subs(
+ env.subs,
+ [(custom_closure_symbol, [state_record_var])],
+ ),
+ recursion_var: OptVariable::NONE,
+ unspecialized: Default::default(),
+ ambient_function: this_custom_callback_var,
+ });
+ let custom_callback_lambda_set_var = synth_var(env.subs, content);
+ let subs_slice =
+ SubsSlice::insert_into_subs(env.subs, [bytes_arg_var, fmt_arg_var]);
+
+ env.subs.set_content(
+ this_custom_callback_var,
+ Content::Structure(FlatType::Func(
+ subs_slice,
+ custom_callback_lambda_set_var,
+ custom_callback_ret_var,
+ )),
+ );
+
+ custom_callback_lambda_set_var
+ };
+
+ // \bytes, fmt -> …
+ Expr::Closure(ClosureData {
+ function_type: this_custom_callback_var,
+ closure_type: custom_callback_lambda_set_var,
+ return_type: custom_callback_ret_var,
+ name: custom_closure_symbol,
+ captured_symbols: vec![(state_arg_symbol, state_record_var)],
+ recursive: Recursive::NotRecursive,
+ arguments: vec![
+ (
+ bytes_arg_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(bytes_arg_symbol)),
+ ),
+ (
+ fmt_arg_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(fmt_arg_symbol)),
+ ),
+ ],
+ loc_body: Box::new(Loc::at_zero(custom_callback_body)),
+ })
+ };
+
+ let decode_custom_ret_var = env.subs.fresh_unnamed_flex_var();
+ let decode_custom = {
+ let decode_custom_var = env.import_builtin_symbol_var(Symbol::DECODE_CUSTOM);
+ let decode_custom_closure_var = env.subs.fresh_unnamed_flex_var();
+ let this_decode_custom_var = {
+ let subs_slice = SubsSlice::insert_into_subs(env.subs, [this_custom_callback_var]);
+ let flat_type =
+ FlatType::Func(subs_slice, decode_custom_closure_var, decode_custom_ret_var);
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ env.unify(decode_custom_var, this_decode_custom_var);
+
+ // Decode.custom \bytes, fmt -> …
+ Expr::Call(
+ Box::new((
+ this_decode_custom_var,
+ Loc::at_zero(Expr::Var(Symbol::DECODE_CUSTOM, this_decode_custom_var)),
+ decode_custom_closure_var,
+ decode_custom_ret_var,
+ )),
+ vec![(this_custom_callback_var, Loc::at_zero(custom_callback))],
+ CalledVia::Space,
+ )
+ };
+
+ env.unify(keep_payload_var, decode_custom_ret_var);
+
+ let keep = {
+ // Next (Decode.custom \bytes, fmt ->
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ // }
+ // )
+ Expr::Tag {
+ tag_union_var: keep_or_skip_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Next".into(),
+ arguments: vec![(decode_custom_ret_var, Loc::at_zero(decode_custom))],
+ }
+ };
+
+ let branch = {
+ // 0 ->
+ // Next (Decode.custom \bytes, fmt ->
+ // when Decode.decodeWith bytes Decode.decoder fmt is
+ // rec ->
+ // {
+ // rest: rec.rest,
+ // result: when rec.result is
+ // Ok val -> Ok {state & e0: Ok val},
+ // Err err -> Err err
+ // }
+ // )
+ WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::IntLiteral(
+ Variable::NAT,
+ Variable::NATURAL,
+ index.to_string().into_boxed_str(),
+ IntValue::I128((index as i128).to_ne_bytes()),
+ IntBound::Exact(IntLitWidth::Nat),
+ )),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(keep),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ }
+ };
+
+ branches.push(branch);
+ }
+
+ // Example: `_ -> TooLong`
+ let default_branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::Underscore),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(Expr::Tag {
+ tag_union_var: keep_or_skip_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "TooLong".into(),
+ arguments: Vec::new(),
+ }),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ branches.push(default_branch);
+
+ // when index is
+ let body = Expr::When {
+ loc_cond: Box::new(Loc::at_zero(Expr::Var(index_arg_symbol, Variable::NAT))),
+ cond_var: Variable::NAT,
+ expr_var: keep_or_skip_var,
+ region: Region::zero(),
+ branches,
+ branches_cond_var: Variable::NAT,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ };
+
+ let step_elem_closure = env.new_symbol("stepElem");
+ let function_type = env.subs.fresh_unnamed_flex_var();
+ let closure_type = {
+ let lambda_set = LambdaSet {
+ solved: UnionLambdas::tag_without_arguments(env.subs, step_elem_closure),
+ recursion_var: OptVariable::NONE,
+ unspecialized: Default::default(),
+ ambient_function: function_type,
+ };
+
+ synth_var(env.subs, Content::LambdaSet(lambda_set))
+ };
+
+ {
+ let args_slice = SubsSlice::insert_into_subs(env.subs, [state_record_var, Variable::NAT]);
+
+ env.subs.set_content(
+ function_type,
+ Content::Structure(FlatType::Func(args_slice, closure_type, keep_or_skip_var)),
+ )
+ };
+
+ let expr = Expr::Closure(ClosureData {
+ function_type,
+ closure_type,
+ return_type: keep_or_skip_var,
+ name: step_elem_closure,
+ captured_symbols: Vec::new(),
+ recursive: Recursive::NotRecursive,
+ arguments: vec![
+ (
+ state_record_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
+ ),
+ (
+ Variable::NAT,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(index_arg_symbol)),
+ ),
+ ],
+ loc_body: Box::new(Loc::at_zero(body)),
+ });
+
+ (expr, function_type)
+}
+
+// Example:
+// finalizer = \rec ->
+// when rec.e0 is
+// Ok e0 ->
+// when rec.e1 is
+// Ok e1 -> Ok (e0, e1)
+// Err NoElem -> Err TooShort
+// Err NoElem -> Err TooShort
+fn finalizer(
+ env: &mut Env,
+ index_vars: &[Variable],
+ state_record_var: Variable,
+ state_fields: &[Lowercase],
+ state_field_vars: &[Variable],
+) -> (Expr, Variable, Variable) {
+ let state_arg_symbol = env.new_symbol("stateRecord");
+ let mut tuple_elems = Vec::with_capacity(index_vars.len());
+ let mut pattern_symbols = Vec::with_capacity(index_vars.len());
+ let decode_err_var = {
+ let flat_type = FlatType::TagUnion(
+ UnionTags::tag_without_arguments(env.subs, "TooShort".into()),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(env.subs, Content::Structure(flat_type))
+ };
+
+ for (i, &index_var) in index_vars.iter().enumerate() {
+ let symbol = env.new_symbol(i);
+
+ pattern_symbols.push(symbol);
+
+ let index_expr = Expr::Var(symbol, index_var);
+
+ tuple_elems.push((index_var, Box::new(Loc::at_zero(index_expr))));
+ }
+
+ // The bottom of the happy path - return the decoded tuple (a, b) wrapped with
+ // "Ok".
+ let return_type_var;
+ let mut body = {
+ let subs = &mut env.subs;
+ let tuple_indices_iter = index_vars.iter().copied().enumerate();
+ let flat_type = FlatType::Tuple(
+ TupleElems::insert_into_subs(subs, tuple_indices_iter),
+ Variable::EMPTY_TUPLE,
+ );
+ let done_tuple_var = synth_var(subs, Content::Structure(flat_type));
+ let done_record = Expr::Tuple {
+ tuple_var: done_tuple_var,
+ elems: tuple_elems,
+ };
+
+ return_type_var = {
+ let flat_type = FlatType::TagUnion(
+ UnionTags::for_result(subs, done_tuple_var, decode_err_var),
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ );
+
+ synth_var(subs, Content::Structure(flat_type))
+ };
+
+ Expr::Tag {
+ tag_union_var: return_type_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Ok".into(),
+ arguments: vec![(done_tuple_var, Loc::at_zero(done_record))],
+ }
+ };
+
+ // Unwrap each result in the decoded state
+ //
+ // when rec.e0 is
+ // Ok e0 -> ...happy path...
+ // Err NoElem -> Err TooShort
+ for (((symbol, field), &index_var), &result_index_var) in pattern_symbols
+ .iter()
+ .zip(state_fields)
+ .zip(index_vars)
+ .zip(state_field_vars)
+ .rev()
+ {
+ // when rec.e0 is
+ let cond_expr = Expr::RecordAccess {
+ record_var: state_record_var,
+ ext_var: env.new_ext_var(ExtensionKind::Record),
+ field_var: result_index_var,
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(state_arg_symbol, state_record_var))),
+ field: field.clone(),
+ };
+
+ // Example: `Ok x -> expr`
+ let ok_branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::AppliedTag {
+ whole_var: result_index_var,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ tag_name: "Ok".into(),
+ arguments: vec![(index_var, Loc::at_zero(Pattern::Identifier(*symbol)))],
+ }),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(body),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ // Example: `_ -> Err TooShort`
+ let err_branch = WhenBranch {
+ patterns: vec![WhenBranchPattern {
+ pattern: Loc::at_zero(Pattern::Underscore),
+ degenerate: false,
+ }],
+ value: Loc::at_zero(Expr::Tag {
+ tag_union_var: return_type_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: "Err".into(),
+ arguments: vec![(
+ decode_err_var,
+ Loc::at_zero(Expr::Tag {
+ tag_union_var: decode_err_var,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ name: "TooShort".into(),
+ arguments: Vec::new(),
+ }),
+ )],
+ }),
+ guard: None,
+ redundant: RedundantMark::known_non_redundant(),
+ };
+
+ body = Expr::When {
+ loc_cond: Box::new(Loc::at_zero(cond_expr)),
+ cond_var: result_index_var,
+ expr_var: return_type_var,
+ region: Region::zero(),
+ branches: vec![ok_branch, err_branch],
+ branches_cond_var: result_index_var,
+ exhaustive: ExhaustiveMark::known_exhaustive(),
+ };
+ }
+
+ let function_var = synth_var(env.subs, Content::Error); // We'll fix this up in subs later.
+ let function_symbol = env.new_symbol("finalizer");
+ let lambda_set = LambdaSet {
+ solved: UnionLambdas::tag_without_arguments(env.subs, function_symbol),
+ recursion_var: OptVariable::NONE,
+ unspecialized: Default::default(),
+ ambient_function: function_var,
+ };
+ let closure_type = synth_var(env.subs, Content::LambdaSet(lambda_set));
+ let flat_type = FlatType::Func(
+ SubsSlice::insert_into_subs(env.subs, [state_record_var]),
+ closure_type,
+ return_type_var,
+ );
+
+ // Fix up function_var so it's not Content::Error anymore
+ env.subs
+ .set_content(function_var, Content::Structure(flat_type));
+
+ let finalizer = Expr::Closure(ClosureData {
+ function_type: function_var,
+ closure_type,
+ return_type: return_type_var,
+ name: function_symbol,
+ captured_symbols: Vec::new(),
+ recursive: Recursive::NotRecursive,
+ arguments: vec![(
+ state_record_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(state_arg_symbol)),
+ )],
+ loc_body: Box::new(Loc::at_zero(body)),
+ });
+
+ (finalizer, function_var, decode_err_var)
+}
+
+// Example:
+// initialState : {e0: Result a [NoElem], e1: Result b [NoElem]}
+// initialState = {e0: Err NoElem, e1: Err NoElem}
+fn initial_state(
+ env: &mut Env<'_>,
+ arity: u32,
+ index_vars: &mut Vec,
+ state_fields: &mut Vec,
+ state_field_vars: &mut Vec,
+) -> (Variable, Expr) {
+ let mut initial_state_fields = SendMap::default();
+
+ for i in 0..arity {
+ let subs = &mut env.subs;
+ let index_var = subs.fresh_unnamed_flex_var();
+
+ index_vars.push(index_var);
+
+ let state_field = Lowercase::from(format!("e{i}"));
+ state_fields.push(state_field.clone());
+
+ let no_index_label = "NoElem";
+ let union_tags = UnionTags::tag_without_arguments(subs, no_index_label.into());
+ let no_index_var = synth_var(
+ subs,
+ Content::Structure(FlatType::TagUnion(
+ union_tags,
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ )),
+ );
+ let no_index = Expr::Tag {
+ tag_union_var: no_index_var,
+ ext_var: Variable::EMPTY_TAG_UNION,
+ name: no_index_label.into(),
+ arguments: Vec::new(),
+ };
+ let err_label = "Err";
+ let union_tags = UnionTags::for_result(subs, index_var, no_index_var);
+ let result_var = synth_var(
+ subs,
+ Content::Structure(FlatType::TagUnion(
+ union_tags,
+ TagExt::Any(Variable::EMPTY_TAG_UNION),
+ )),
+ );
+ let index_expr = Expr::Tag {
+ tag_union_var: result_var,
+ ext_var: env.new_ext_var(ExtensionKind::TagUnion),
+ name: err_label.into(),
+ arguments: vec![(no_index_var, Loc::at_zero(no_index))],
+ };
+ state_field_vars.push(result_var);
+ let index = Field {
+ var: result_var,
+ region: Region::zero(),
+ loc_expr: Box::new(Loc::at_zero(index_expr)),
+ };
+
+ initial_state_fields.insert(state_field, index);
+ }
+
+ let subs = &mut env.subs;
+ let record_index_iter = state_fields
+ .iter()
+ .zip(state_field_vars.iter())
+ .map(|(index_name, &var)| (index_name.clone(), RecordField::Required(var)));
+ let flat_type = FlatType::Record(
+ RecordFields::insert_into_subs(subs, record_index_iter),
+ Variable::EMPTY_RECORD,
+ );
+
+ let state_record_var = synth_var(subs, Content::Structure(flat_type));
+
+ (
+ state_record_var,
+ Expr::Record {
+ record_var: state_record_var,
+ fields: initial_state_fields,
+ },
+ )
+}
diff --git a/crates/compiler/derive/src/encoding.rs b/crates/compiler/derive/src/encoding.rs
index f51bf545e3..183d489db1 100644
--- a/crates/compiler/derive/src/encoding.rs
+++ b/crates/compiler/derive/src/encoding.rs
@@ -14,7 +14,8 @@ use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::{
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
- RedundantMark, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
+ RedundantMark, SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags, Variable,
+ VariableSubsSlice,
};
use roc_types::types::RecordField;
@@ -50,6 +51,21 @@ pub(crate) fn derive_to_encoder(
to_encoder_record(env, record_var, fields, def_symbol)
}
+ FlatEncodableKey::Tuple(arity) => {
+ // Generalized tuple var so we can reuse this impl between many tuples:
+ // if arity = n, this is (t1, ..., tn) for fresh t1, ..., tn.
+ let flex_elems = (0..arity)
+ .into_iter()
+ .map(|idx| (idx as usize, env.subs.fresh_unnamed_flex_var()))
+ .collect::>();
+ let elems = TupleElems::insert_into_subs(env.subs, flex_elems);
+ let tuple_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Tuple(elems, Variable::EMPTY_TUPLE)),
+ );
+
+ to_encoder_tuple(env, tuple_var, elems, def_symbol)
+ }
FlatEncodableKey::TagUnion(tags) => {
// Generalized tag union var so we can reuse this impl between many unions:
// if tags = [ A arity=2, B arity=1 ], this is [ A t1 t2, B t3 ] for fresh t1, t2, t3
@@ -490,6 +506,189 @@ fn to_encoder_record(
(clos, fn_var)
}
+fn to_encoder_tuple(
+ env: &mut Env<'_>,
+ tuple_var: Variable,
+ elems: TupleElems,
+ fn_name: Symbol,
+) -> (Expr, Variable) {
+ // Suppose tup = (t1, t2). Build
+ //
+ // \tup -> Encode.tuple [
+ // Encode.toEncoder tup.0,
+ // Encode.toEncoder tup.1,
+ // ]
+
+ let tup_sym = env.new_symbol("tup");
+ let whole_encoder_in_list_var = env.subs.fresh_unnamed_flex_var(); // type of the encoder in the list
+
+ use Expr::*;
+
+ let elem_encoders_list = elems
+ .iter_all()
+ .map(|(elem_index, elem_var_index)| {
+ let index = env.subs[elem_index];
+ let elem_var = env.subs[elem_var_index];
+ let elem_var_slice = VariableSubsSlice::new(elem_var_index.index, 1);
+
+ // tup.0
+ let tuple_access = TupleAccess {
+ tuple_var,
+ ext_var: env.subs.fresh_unnamed_flex_var(),
+ elem_var,
+ loc_expr: Box::new(Loc::at_zero(Var(
+ tup_sym,
+ env.subs.fresh_unnamed_flex_var(),
+ ))),
+ index,
+ };
+
+ // build `toEncoder tup.0` type
+ // val -[uls]-> Encoder fmt | fmt has EncoderFormatting
+ let to_encoder_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TO_ENCODER);
+
+ // (typeof tup.0) -[clos]-> t1
+ let to_encoder_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
+ let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1
+ let this_to_encoder_fn_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Func(
+ elem_var_slice,
+ to_encoder_clos_var,
+ encoder_var,
+ )),
+ );
+
+ // val -[uls]-> Encoder fmt | fmt has EncoderFormatting
+ // ~ (typeof tup.0) -[clos]-> t1
+ env.unify(to_encoder_fn_var, this_to_encoder_fn_var);
+
+ // toEncoder : (typeof tup.0) -[clos]-> Encoder fmt | fmt has EncoderFormatting
+ let to_encoder_var = AbilityMember(Symbol::ENCODE_TO_ENCODER, None, to_encoder_fn_var);
+ let to_encoder_fn = Box::new((
+ to_encoder_fn_var,
+ Loc::at_zero(to_encoder_var),
+ to_encoder_clos_var,
+ encoder_var,
+ ));
+
+ // toEncoder tup.0
+ let to_encoder_call = Call(
+ to_encoder_fn,
+ vec![(elem_var, Loc::at_zero(tuple_access))],
+ CalledVia::Space,
+ );
+
+ // NOTE: must be done to unify the lambda sets under `encoder_var`
+ env.unify(encoder_var, whole_encoder_in_list_var);
+
+ Loc::at_zero(to_encoder_call)
+ })
+ .collect::>();
+
+ // typeof [ toEncoder tup.0, toEncoder tup.1 ]
+ let whole_encoder_in_list_var_slice =
+ VariableSubsSlice::insert_into_subs(env.subs, once(whole_encoder_in_list_var));
+ let elem_encoders_list_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Apply(
+ Symbol::LIST_LIST,
+ whole_encoder_in_list_var_slice,
+ )),
+ );
+
+ // [ toEncoder tup.0, toEncoder tup.1 ]
+ let elem_encoders_list = List {
+ elem_var: whole_encoder_in_list_var,
+ loc_elems: elem_encoders_list,
+ };
+
+ // build `Encode.tuple [ toEncoder tup.0, toEncoder tup.1 ]` type
+ // List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
+ let encode_tuple_fn_var = env.import_builtin_symbol_var(Symbol::ENCODE_TUPLE);
+
+ // elem_encoders_list_var -[clos]-> t1
+ let elem_encoders_list_var_slice =
+ VariableSubsSlice::insert_into_subs(env.subs, once(elem_encoders_list_var));
+ let encode_tuple_clos_var = env.subs.fresh_unnamed_flex_var(); // clos
+ let encoder_var = env.subs.fresh_unnamed_flex_var(); // t1
+ let this_encode_tuple_fn_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Func(
+ elem_encoders_list_var_slice,
+ encode_tuple_clos_var,
+ encoder_var,
+ )),
+ );
+
+ // List (Encoder fmt) -[uls]-> Encoder fmt | fmt has EncoderFormatting
+ // ~ elem_encoders_list_var -[clos]-> t1
+ env.unify(encode_tuple_fn_var, this_encode_tuple_fn_var);
+
+ // Encode.tuple : elem_encoders_list_var -[clos]-> Encoder fmt | fmt has EncoderFormatting
+ let encode_tuple_var = AbilityMember(Symbol::ENCODE_TUPLE, None, encode_tuple_fn_var);
+ let encode_tuple_fn = Box::new((
+ encode_tuple_fn_var,
+ Loc::at_zero(encode_tuple_var),
+ encode_tuple_clos_var,
+ encoder_var,
+ ));
+
+ // Encode.tuple [ { key: .., value: .. }, .. ]
+ let encode_tuple_call = Call(
+ encode_tuple_fn,
+ vec![(elem_encoders_list_var, Loc::at_zero(elem_encoders_list))],
+ CalledVia::Space,
+ );
+
+ // Encode.custom \bytes, fmt -> Encode.appendWith bytes (Encode.tuple_var ..) fmt
+ let (body, this_encoder_var) =
+ wrap_in_encode_custom(env, encode_tuple_call, encoder_var, tup_sym, tuple_var);
+
+ // Create fn_var for ambient capture; we fix it up below.
+ let fn_var = synth_var(env.subs, Content::Error);
+
+ // -[fn_name]->
+ let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![])));
+ let fn_clos_var = synth_var(
+ env.subs,
+ Content::LambdaSet(LambdaSet {
+ solved: fn_name_labels,
+ recursion_var: OptVariable::NONE,
+ unspecialized: SubsSlice::default(),
+ ambient_function: fn_var,
+ }),
+ );
+ // typeof tup -[fn_name]-> (typeof Encode.tuple [ .. ] = Encoder fmt)
+ let tuple_var_slice = SubsSlice::insert_into_subs(env.subs, once(tuple_var));
+ env.subs.set_content(
+ fn_var,
+ Content::Structure(FlatType::Func(
+ tuple_var_slice,
+ fn_clos_var,
+ this_encoder_var,
+ )),
+ );
+
+ // \tup -[fn_name]-> Encode.tuple [ { key: .., value: .. }, .. ]
+ let clos = Closure(ClosureData {
+ function_type: fn_var,
+ closure_type: fn_clos_var,
+ return_type: this_encoder_var,
+ name: fn_name,
+ captured_symbols: vec![],
+ recursive: Recursive::NotRecursive,
+ arguments: vec![(
+ tuple_var,
+ AnnotatedMark::known_exhaustive(),
+ Loc::at_zero(Pattern::Identifier(tup_sym)),
+ )],
+ loc_body: Box::new(Loc::at_zero(body)),
+ });
+
+ (clos, fn_var)
+}
+
fn to_encoder_tag_union(
env: &mut Env<'_>,
tag_union_var: Variable,
diff --git a/crates/compiler/derive/src/hash.rs b/crates/compiler/derive/src/hash.rs
index 6a786dfb66..1f597f2dda 100644
--- a/crates/compiler/derive/src/hash.rs
+++ b/crates/compiler/derive/src/hash.rs
@@ -19,8 +19,8 @@ use roc_types::{
num::int_lit_width_to_variable,
subs::{
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
- RedundantMark, Subs, SubsIndex, SubsSlice, TagExt, UnionLambdas, UnionTags, Variable,
- VariableSubsSlice,
+ RedundantMark, Subs, SubsIndex, SubsSlice, TagExt, TupleElems, UnionLambdas, UnionTags,
+ Variable, VariableSubsSlice,
},
types::RecordField,
};
@@ -30,6 +30,7 @@ use crate::{synth_var, util::Env, DerivedBody};
pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody {
let (body_type, body) = match key {
FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields),
+ FlatHashKey::Tuple(arity) => hash_tuple(env, def_symbol, arity),
FlatHashKey::TagUnion(tags) => {
if tags.len() == 1 {
hash_newtype_tag_union(env, def_symbol, tags.into_iter().next().unwrap())
@@ -122,6 +123,76 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec) -> (V
)
}
+fn hash_tuple(env: &mut Env<'_>, fn_name: Symbol, arity: u32) -> (Variable, Expr) {
+ // Suppose tup = (v1, ..., vn).
+ // Build a generalized type t_tup = (t1, ..., tn), with fresh t1, ..., tn,
+ // so that we can re-use the derived impl for many tuples of the same arity.
+ let (tuple_var, tuple_elems) = {
+ // TODO: avoid an allocation here by pre-allocating the indices and variables `TupleElems`
+ // will be instantiated with.
+ let flex_elems: Vec<_> = (0..arity)
+ .into_iter()
+ .map(|i| (i as usize, env.subs.fresh_unnamed_flex_var()))
+ .collect();
+ let elems = TupleElems::insert_into_subs(env.subs, flex_elems);
+ let tuple_var = synth_var(
+ env.subs,
+ Content::Structure(FlatType::Tuple(elems, Variable::EMPTY_TUPLE)),
+ );
+
+ (tuple_var, elems)
+ };
+
+ // Now, a hasher for this tuple is
+ //
+ // hash_tup : hasher, (t1, ..., tn) -> hasher | hasher has Hasher
+ // hash_tup = \hasher, tup ->
+ // Hash.hash (
+ // Hash.hash
+ // ...
+ // (Hash.hash hasher tup.0)
+ // ...
+ // tup.n1)
+ // tup.n
+ //
+ // So, just a build a fold travelling up the elements.
+ let tup_sym = env.new_symbol("tup");
+
+ let hasher_sym = env.new_symbol("hasher");
+ let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Subs::AB_HASHER));
+
+ let (body_var, body) = tuple_elems.iter_all().fold(
+ (hasher_var, Expr::Var(hasher_sym, hasher_var)),
+ |total_hasher, (elem_idx, elem_var)| {
+ let index = env.subs[elem_idx];
+ let elem_var = env.subs[elem_var];
+
+ let elem_access = Expr::TupleAccess {
+ tuple_var,
+ elem_var,
+ ext_var: env.subs.fresh_unnamed_flex_var(),
+ loc_expr: Box::new(Loc::at_zero(Expr::Var(
+ tup_sym,
+ env.subs.fresh_unnamed_flex_var(),
+ ))),
+ index,
+ };
+
+ call_hash_hash(env, total_hasher, (elem_var, elem_access))
+ },
+ );
+
+ // Finally, build the closure
+ // \hasher, rcd -> body
+ build_outer_derived_closure(
+ env,
+ fn_name,
+ (hasher_var, hasher_sym),
+ (tuple_var, Pattern::Identifier(tup_sym)),
+ (body_var, body),
+ )
+}
+
/// Build a `hash` implementation for a non-singleton tag union.
fn hash_tag_union(
env: &mut Env<'_>,
diff --git a/crates/compiler/derive/src/util.rs b/crates/compiler/derive/src/util.rs
index 7dab0d1606..2319dd2e12 100644
--- a/crates/compiler/derive/src/util.rs
+++ b/crates/compiler/derive/src/util.rs
@@ -18,19 +18,20 @@ pub(crate) struct Env<'a> {
}
impl Env<'_> {
- pub fn new_symbol(&mut self, name_hint: &str) -> Symbol {
+ pub fn new_symbol(&mut self, name_hint: impl std::string::ToString) -> Symbol {
if cfg!(any(
debug_assertions,
test,
feature = "debug-derived-symbols"
)) {
let mut i = 0;
+ let hint = name_hint.to_string();
let debug_name = loop {
i += 1;
let name = if i == 1 {
- name_hint.to_owned()
+ hint.clone()
} else {
- format!("{}{}", name_hint, i)
+ format!("{}{}", hint, i)
};
if self.derived_ident_ids.get_id(&name).is_none() {
break name;
diff --git a/crates/compiler/derive_key/Cargo.toml b/crates/compiler/derive_key/Cargo.toml
index a69689fd32..a8f9a5a826 100644
--- a/crates/compiler/derive_key/Cargo.toml
+++ b/crates/compiler/derive_key/Cargo.toml
@@ -1,14 +1,14 @@
[package]
name = "roc_derive_key"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
[dependencies]
roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
-roc_region = { path = "../region" }
roc_module = { path = "../module" }
+roc_region = { path = "../region" }
roc_types = { path = "../types" }
-roc_can = { path = "../can" }
diff --git a/crates/compiler/derive_key/src/decoding.rs b/crates/compiler/derive_key/src/decoding.rs
index ec0d732363..dc52745f85 100644
--- a/crates/compiler/derive_key/src/decoding.rs
+++ b/crates/compiler/derive_key/src/decoding.rs
@@ -2,7 +2,7 @@ use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable};
use crate::{
- util::{check_derivable_ext_var, debug_name_record},
+ util::{check_derivable_ext_var, debug_name_record, debug_name_tuple},
DeriveError,
};
@@ -18,6 +18,7 @@ pub enum FlatDecodableKey {
// Unfortunate that we must allocate here, c'est la vie
Record(Vec),
+ Tuple(u32),
}
impl FlatDecodableKey {
@@ -25,6 +26,7 @@ impl FlatDecodableKey {
match self {
FlatDecodableKey::List() => "list".to_string(),
FlatDecodableKey::Record(fields) => debug_name_record(fields),
+ FlatDecodableKey::Tuple(arity) => debug_name_tuple(*arity),
}
}
}
@@ -61,8 +63,14 @@ impl FlatDecodable {
Ok(Key(FlatDecodableKey::Record(field_names)))
}
- FlatType::Tuple(_elems, _ext) => {
- todo!()
+ FlatType::Tuple(elems, ext) => {
+ let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
+
+ check_derivable_ext_var(subs, ext, |ext| {
+ matches!(ext, Content::Structure(FlatType::EmptyTuple))
+ })?;
+
+ Ok(Key(FlatDecodableKey::Tuple(elems_iter.count() as _)))
}
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
Err(Underivable) // yet
@@ -78,27 +86,18 @@ impl FlatDecodable {
//
FlatType::Func(..) => Err(Underivable),
},
- Content::Alias(sym, _, real_var, _) => match sym {
- Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::DECODE_U8)),
- Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::DECODE_U16)),
- Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::DECODE_U32)),
- Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::DECODE_U64)),
- Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::DECODE_U128)),
- Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::DECODE_I8)),
- Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::DECODE_I16)),
- Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::DECODE_I32)),
- Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::DECODE_I64)),
- Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::DECODE_I128)),
- Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::DECODE_DEC)),
- Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::DECODE_F32)),
- Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::DECODE_F64)),
+ Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) {
+ Some(lambda) => lambda,
// NB: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.
- _ => Self::from_var(subs, real_var),
+ None => Self::from_var(subs, real_var),
},
- Content::RangedNumber(_) => Err(Underivable),
+ Content::RangedNumber(range) => {
+ Self::from_var(subs, range.default_compilation_variable())
+ }
+ //
+ Content::RecursionVar { structure, .. } => Self::from_var(subs, structure),
//
- Content::RecursionVar { .. } => Err(Underivable),
Content::Error => Err(Underivable),
Content::FlexVar(_)
| Content::RigidVar(_)
@@ -107,4 +106,30 @@ impl FlatDecodable {
Content::LambdaSet(_) => Err(Underivable),
}
}
+
+ pub(crate) fn from_builtin_symbol(symbol: Symbol) -> Result {
+ from_builtin_symbol(symbol).unwrap_or(Err(DeriveError::Underivable))
+ }
+}
+
+const fn from_builtin_symbol(symbol: Symbol) -> Option> {
+ use FlatDecodable::*;
+ match symbol {
+ Symbol::BOOL_BOOL => Some(Ok(Immediate(Symbol::DECODE_BOOL))),
+ Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(Ok(Immediate(Symbol::DECODE_U8))),
+ Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(Ok(Immediate(Symbol::DECODE_U16))),
+ Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(Ok(Immediate(Symbol::DECODE_U32))),
+ Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(Ok(Immediate(Symbol::DECODE_U64))),
+ Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(Ok(Immediate(Symbol::DECODE_U128))),
+ Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(Ok(Immediate(Symbol::DECODE_I8))),
+ Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(Ok(Immediate(Symbol::DECODE_I16))),
+ Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(Ok(Immediate(Symbol::DECODE_I32))),
+ Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(Ok(Immediate(Symbol::DECODE_I64))),
+ Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(Ok(Immediate(Symbol::DECODE_I128))),
+ Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::DECODE_DEC))),
+ Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::DECODE_F32))),
+ Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::DECODE_F64))),
+ Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)),
+ _ => None,
+ }
}
diff --git a/crates/compiler/derive_key/src/encoding.rs b/crates/compiler/derive_key/src/encoding.rs
index 521cc27744..ec6140cbe1 100644
--- a/crates/compiler/derive_key/src/encoding.rs
+++ b/crates/compiler/derive_key/src/encoding.rs
@@ -5,7 +5,7 @@ use roc_module::{
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
use crate::{
- util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
+ util::{check_derivable_ext_var, debug_name_record, debug_name_tag, debug_name_tuple},
DeriveError,
};
@@ -22,6 +22,7 @@ pub enum FlatEncodableKey {
Dict(/* takes two variables */),
// Unfortunate that we must allocate here, c'est la vie
Record(Vec),
+ Tuple(u32),
TagUnion(Vec<(TagName, u16)>),
}
@@ -32,6 +33,7 @@ impl FlatEncodableKey {
FlatEncodableKey::Set() => "set".to_string(),
FlatEncodableKey::Dict() => "dict".to_string(),
FlatEncodableKey::Record(fields) => debug_name_record(fields),
+ FlatEncodableKey::Tuple(arity) => debug_name_tuple(*arity),
FlatEncodableKey::TagUnion(tags) => debug_name_tag(tags),
}
}
@@ -66,8 +68,14 @@ impl FlatEncodable {
Ok(Key(FlatEncodableKey::Record(field_names)))
}
- FlatType::Tuple(_elems, _ext) => {
- todo!()
+ FlatType::Tuple(elems, ext) => {
+ let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
+
+ check_derivable_ext_var(subs, ext, |ext| {
+ matches!(ext, Content::Structure(FlatType::EmptyTuple))
+ })?;
+
+ Ok(Key(FlatEncodableKey::Tuple(elems_iter.count() as _)))
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
// The recursion var doesn't matter, because the derived implementation will only
@@ -112,27 +120,18 @@ impl FlatEncodable {
//
FlatType::Func(..) => Err(Underivable),
},
- Content::Alias(sym, _, real_var, _) => match sym {
- Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Ok(Immediate(Symbol::ENCODE_U8)),
- Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Ok(Immediate(Symbol::ENCODE_U16)),
- Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Ok(Immediate(Symbol::ENCODE_U32)),
- Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Ok(Immediate(Symbol::ENCODE_U64)),
- Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Ok(Immediate(Symbol::ENCODE_U128)),
- Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Ok(Immediate(Symbol::ENCODE_I8)),
- Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Ok(Immediate(Symbol::ENCODE_I16)),
- Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Ok(Immediate(Symbol::ENCODE_I32)),
- Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Ok(Immediate(Symbol::ENCODE_I64)),
- Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Ok(Immediate(Symbol::ENCODE_I128)),
- Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Ok(Immediate(Symbol::ENCODE_DEC)),
- Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Ok(Immediate(Symbol::ENCODE_F32)),
- Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Ok(Immediate(Symbol::ENCODE_F64)),
+ Content::Alias(sym, _, real_var, _) => match from_builtin_symbol(sym) {
+ Some(lambda) => lambda,
// TODO: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.
_ => Self::from_var(subs, real_var),
},
- Content::RangedNumber(_) => Err(Underivable),
+ Content::RangedNumber(range) => {
+ Self::from_var(subs, range.default_compilation_variable())
+ }
+ //
+ Content::RecursionVar { structure, .. } => Self::from_var(subs, structure),
//
- Content::RecursionVar { .. } => Err(Underivable),
Content::Error => Err(Underivable),
Content::FlexVar(_)
| Content::RigidVar(_)
@@ -141,4 +140,30 @@ impl FlatEncodable {
Content::LambdaSet(_) => Err(Underivable),
}
}
+
+ pub(crate) fn from_builtin_symbol(symbol: Symbol) -> Result {
+ from_builtin_symbol(symbol).unwrap_or(Err(DeriveError::Underivable))
+ }
+}
+
+const fn from_builtin_symbol(symbol: Symbol) -> Option> {
+ use FlatEncodable::*;
+ match symbol {
+ Symbol::BOOL_BOOL => Some(Ok(Immediate(Symbol::ENCODE_BOOL))),
+ Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => Some(Ok(Immediate(Symbol::ENCODE_U8))),
+ Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => Some(Ok(Immediate(Symbol::ENCODE_U16))),
+ Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => Some(Ok(Immediate(Symbol::ENCODE_U32))),
+ Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => Some(Ok(Immediate(Symbol::ENCODE_U64))),
+ Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => Some(Ok(Immediate(Symbol::ENCODE_U128))),
+ Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => Some(Ok(Immediate(Symbol::ENCODE_I8))),
+ Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => Some(Ok(Immediate(Symbol::ENCODE_I16))),
+ Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => Some(Ok(Immediate(Symbol::ENCODE_I32))),
+ Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => Some(Ok(Immediate(Symbol::ENCODE_I64))),
+ Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => Some(Ok(Immediate(Symbol::ENCODE_I128))),
+ Symbol::NUM_DEC | Symbol::NUM_DECIMAL => Some(Ok(Immediate(Symbol::ENCODE_DEC))),
+ Symbol::NUM_F32 | Symbol::NUM_BINARY32 => Some(Ok(Immediate(Symbol::ENCODE_F32))),
+ Symbol::NUM_F64 | Symbol::NUM_BINARY64 => Some(Ok(Immediate(Symbol::ENCODE_F64))),
+ Symbol::NUM_NAT | Symbol::NUM_NATURAL => Some(Err(DeriveError::Underivable)),
+ _ => None,
+ }
}
diff --git a/crates/compiler/derive_key/src/hash.rs b/crates/compiler/derive_key/src/hash.rs
index 00e765974b..6ca876988c 100644
--- a/crates/compiler/derive_key/src/hash.rs
+++ b/crates/compiler/derive_key/src/hash.rs
@@ -5,7 +5,7 @@ use roc_module::{
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
use crate::{
- util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
+ util::{check_derivable_ext_var, debug_name_record, debug_name_tag, debug_name_tuple},
DeriveError,
};
@@ -21,6 +21,7 @@ pub enum FlatHash {
pub enum FlatHashKey {
// Unfortunate that we must allocate here, c'est la vie
Record(Vec),
+ Tuple(u32),
TagUnion(Vec<(TagName, u16)>),
}
@@ -28,6 +29,7 @@ impl FlatHashKey {
pub(crate) fn debug_name(&self) -> String {
match self {
FlatHashKey::Record(fields) => debug_name_record(fields),
+ FlatHashKey::Tuple(arity) => debug_name_tuple(*arity),
FlatHashKey::TagUnion(tags) => debug_name_tag(tags),
}
}
@@ -65,8 +67,14 @@ impl FlatHash {
Ok(Key(FlatHashKey::Record(field_names)))
}
- FlatType::Tuple(_elems, _ext) => {
- todo!();
+ FlatType::Tuple(elems, ext) => {
+ let (elems_iter, ext) = elems.sorted_iterator_and_ext(subs, ext);
+
+ check_derivable_ext_var(subs, ext, |ext| {
+ matches!(ext, Content::Structure(FlatType::EmptyTuple))
+ })?;
+
+ Ok(Key(FlatHashKey::Tuple(elems_iter.count() as _)))
}
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
// The recursion var doesn't matter, because the derived implementation will only
@@ -109,7 +117,7 @@ impl FlatHash {
//
FlatType::Func(..) => Err(Underivable),
},
- Content::Alias(sym, _, real_var, _) => match num_symbol_to_hash_lambda(sym) {
+ Content::Alias(sym, _, real_var, _) => match builtin_symbol_to_hash_lambda(sym) {
Some(lambda) => Ok(lambda),
// NB: I believe it is okay to unwrap opaques here because derivers are only used
// by the backend, and the backend treats opaques like structural aliases.
@@ -129,7 +137,7 @@ impl FlatHash {
// during monomorphization, at which point we always choose a default layout
// for ranged numbers, without concern for reification to a ground type.
let chosen_width = range.default_compilation_width();
- let lambda = num_symbol_to_hash_lambda(chosen_width.symbol()).unwrap();
+ let lambda = builtin_symbol_to_hash_lambda(chosen_width.symbol()).unwrap();
Ok(lambda)
}
//
@@ -143,11 +151,16 @@ impl FlatHash {
Content::LambdaSet(_) => Err(Underivable),
}
}
+
+ pub fn from_builtin_symbol(symbol: Symbol) -> Result {
+ builtin_symbol_to_hash_lambda(symbol).ok_or(DeriveError::Underivable)
+ }
}
-const fn num_symbol_to_hash_lambda(symbol: Symbol) -> Option {
+const fn builtin_symbol_to_hash_lambda(symbol: Symbol) -> Option {
use FlatHash::*;
match symbol {
+ Symbol::BOOL_BOOL => Some(SingleLambdaSetImmediate(Symbol::HASH_HASH_BOOL)),
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
Some(SingleLambdaSetImmediate(Symbol::HASH_ADD_U8))
}
diff --git a/crates/compiler/derive_key/src/lib.rs b/crates/compiler/derive_key/src/lib.rs
index c9b6e3731f..8d122c2661 100644
--- a/crates/compiler/derive_key/src/lib.rs
+++ b/crates/compiler/derive_key/src/lib.rs
@@ -52,7 +52,7 @@ impl DeriveKey {
}
}
-#[derive(Hash, PartialEq, Eq, Debug)]
+#[derive(Hash, Clone, PartialEq, Eq, Debug)]
pub enum Derived {
/// If a derived implementation name is well-known ahead-of-time, we can inline the symbol
/// directly rather than associating a key for an implementation to be made later on.
@@ -123,4 +123,34 @@ impl Derived {
}
}
}
+
+ pub fn builtin_with_builtin_symbol(
+ builtin: DeriveBuiltin,
+ symbol: Symbol,
+ ) -> Result {
+ match builtin {
+ DeriveBuiltin::ToEncoder => match encoding::FlatEncodable::from_builtin_symbol(symbol)?
+ {
+ FlatEncodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
+ FlatEncodable::Key(repr) => Ok(Derived::Key(DeriveKey::ToEncoder(repr))),
+ },
+ DeriveBuiltin::Decoder => match decoding::FlatDecodable::from_builtin_symbol(symbol)? {
+ FlatDecodable::Immediate(imm) => Ok(Derived::Immediate(imm)),
+ FlatDecodable::Key(repr) => Ok(Derived::Key(DeriveKey::Decoder(repr))),
+ },
+ DeriveBuiltin::Hash => match hash::FlatHash::from_builtin_symbol(symbol)? {
+ FlatHash::SingleLambdaSetImmediate(imm) => {
+ Ok(Derived::SingleLambdaSetImmediate(imm))
+ }
+ FlatHash::Key(repr) => Ok(Derived::Key(DeriveKey::Hash(repr))),
+ },
+ DeriveBuiltin::IsEq => {
+ // If obligation checking passes, we always lower derived implementations of `isEq`
+ // to the `Eq` low-level, to be fulfilled by the backends.
+ Ok(Derived::SingleLambdaSetImmediate(
+ Symbol::BOOL_STRUCTURAL_EQ,
+ ))
+ }
+ }
+ }
}
diff --git a/crates/compiler/derive_key/src/util.rs b/crates/compiler/derive_key/src/util.rs
index a834dcb2d0..8fbab82957 100644
--- a/crates/compiler/derive_key/src/util.rs
+++ b/crates/compiler/derive_key/src/util.rs
@@ -43,6 +43,10 @@ pub(crate) fn debug_name_record(fields: &[Lowercase]) -> String {
str
}
+pub(crate) fn debug_name_tuple(arity: u32) -> String {
+ format!("(arity:{arity})")
+}
+
pub(crate) fn debug_name_tag(tags: &[(TagName, u16)]) -> String {
let mut str = String::from('[');
tags.iter().enumerate().for_each(|(i, (tag, arity))| {
diff --git a/crates/compiler/exhaustive/Cargo.toml b/crates/compiler/exhaustive/Cargo.toml
index 045e477d50..17d96fcd40 100644
--- a/crates/compiler/exhaustive/Cargo.toml
+++ b/crates/compiler/exhaustive/Cargo.toml
@@ -1,14 +1,15 @@
[package]
name = "roc_exhaustive"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Provides exhaustiveness checking for Roc."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
roc_collections = { path = "../collections" }
-roc_region = { path = "../region" }
-roc_module = { path = "../module" }
roc_error_macros = { path = "../../error_macros" }
+roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
+roc_region = { path = "../region" }
diff --git a/crates/compiler/exhaustive/src/lib.rs b/crates/compiler/exhaustive/src/lib.rs
index 561564f7fd..2b71f3e6cf 100644
--- a/crates/compiler/exhaustive/src/lib.rs
+++ b/crates/compiler/exhaustive/src/lib.rs
@@ -38,6 +38,7 @@ pub enum RenderAs {
Tag,
Opaque,
Record(Vec),
+ Tuple,
Guard,
}
diff --git a/crates/compiler/fmt/Cargo.toml b/crates/compiler/fmt/Cargo.toml
index 4d0afff245..d72ce03678 100644
--- a/crates/compiler/fmt/Cargo.toml
+++ b/crates/compiler/fmt/Cargo.toml
@@ -1,14 +1,16 @@
[package]
name = "roc_fmt"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "The roc code formatter."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
roc_collections = { path = "../collections" }
-roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_parse = { path = "../parse" }
-bumpalo.workspace = true
+roc_region = { path = "../region" }
+
+bumpalo.workspace = true
diff --git a/crates/compiler/fmt/src/annotation.rs b/crates/compiler/fmt/src/annotation.rs
index 73ed08a605..1091b81458 100644
--- a/crates/compiler/fmt/src/annotation.rs
+++ b/crates/compiler/fmt/src/annotation.rs
@@ -177,7 +177,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
annot.is_multiline() || has_clauses.iter().any(|has| has.is_multiline())
}
- Tuple { fields, ext } => {
+ Tuple { elems: fields, ext } => {
match ext {
Some(ann) if ann.value.is_multiline() => return true,
_ => {}
@@ -343,7 +343,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
}
}
- Tuple { fields, ext } => {
+ Tuple { elems: fields, ext } => {
fmt_collection(buf, indent, Braces::Round, *fields, newlines);
if let Some(loc_ext_ann) = *ext {
@@ -509,6 +509,7 @@ fn format_assigned_field_help<'a, 'buf, T>(
buf.spaces(separator_spaces);
buf.push('?');
+ buf.spaces(1);
ann.value.format(buf, indent);
}
LabelOnly(name) => {
diff --git a/crates/compiler/fmt/src/collection.rs b/crates/compiler/fmt/src/collection.rs
index 643928fcb1..3d241dafca 100644
--- a/crates/compiler/fmt/src/collection.rs
+++ b/crates/compiler/fmt/src/collection.rs
@@ -38,7 +38,7 @@ pub fn fmt_collection<'a, 'buf, T: ExtractSpaces<'a> + Formattable>(
let braces_indent = indent;
let item_indent = braces_indent + INDENT;
if newline == Newlines::Yes {
- buf.newline();
+ buf.ensure_ends_with_newline();
}
buf.indent(braces_indent);
buf.push(start);
diff --git a/crates/compiler/fmt/src/def.rs b/crates/compiler/fmt/src/def.rs
index 930f8e9351..d45e37b733 100644
--- a/crates/compiler/fmt/src/def.rs
+++ b/crates/compiler/fmt/src/def.rs
@@ -61,7 +61,7 @@ impl<'a> Formattable for TypeDef<'a> {
&self,
buf: &mut Buf<'buf>,
_parens: Parens,
- _newlines: Newlines,
+ newlines: Newlines,
indent: u16,
) {
use roc_parse::ast::TypeDef::*;
@@ -76,8 +76,19 @@ impl<'a> Formattable for TypeDef<'a> {
for var in *vars {
buf.spaces(1);
+
+ let need_parens = matches!(var.value, Pattern::Apply(..));
+
+ if need_parens {
+ buf.push_str("(");
+ }
+
fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
buf.indent(indent);
+
+ if need_parens {
+ buf.push_str(")");
+ }
}
buf.push_str(" :");
@@ -86,22 +97,10 @@ impl<'a> Formattable for TypeDef<'a> {
ann.format(buf, indent)
}
Opaque {
- header: TypeHeader { name, vars },
+ header,
typ: ann,
derived: has_abilities,
} => {
- buf.indent(indent);
- buf.push_str(name.value);
-
- for var in *vars {
- buf.spaces(1);
- fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
- buf.indent(indent);
- }
-
- buf.push_str(" :=");
- buf.spaces(1);
-
let ann_is_where_clause =
matches!(ann.extract_spaces().item, TypeAnnotation::Where(..));
@@ -115,7 +114,7 @@ impl<'a> Formattable for TypeDef<'a> {
let make_multiline = ann.is_multiline() || has_abilities_multiline;
- ann.format(buf, indent);
+ fmt_general_def(header, buf, indent, ":=", &ann.value, newlines);
if let Some(has_abilities) = has_abilities {
buf.spaces(1);
@@ -167,6 +166,29 @@ impl<'a> Formattable for TypeDef<'a> {
}
}
+impl<'a> Formattable for TypeHeader<'a> {
+ fn is_multiline(&self) -> bool {
+ self.vars.iter().any(|v| v.is_multiline())
+ }
+
+ fn format_with_options<'buf>(
+ &self,
+ buf: &mut Buf<'buf>,
+ _parens: Parens,
+ _newlines: Newlines,
+ indent: u16,
+ ) {
+ buf.indent(indent);
+ buf.push_str(self.name.value);
+
+ for var in self.vars.iter() {
+ buf.spaces(1);
+ fmt_pattern(buf, &var.value, indent, Parens::NotNeeded);
+ buf.indent(indent);
+ }
+ }
+}
+
impl<'a> Formattable for ValueDef<'a> {
fn is_multiline(&self) -> bool {
use roc_parse::ast::ValueDef::*;
@@ -193,63 +215,14 @@ impl<'a> Formattable for ValueDef<'a> {
use roc_parse::ast::ValueDef::*;
match self {
Annotation(loc_pattern, loc_annotation) => {
- loc_pattern.format(buf, indent);
- buf.indent(indent);
-
- if loc_annotation.is_multiline() {
- buf.push_str(" :");
- buf.spaces(1);
-
- let should_outdent = match loc_annotation.value {
- TypeAnnotation::SpaceBefore(sub_def, spaces) => match sub_def {
- TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => {
- let is_only_newlines = spaces.iter().all(|s| s.is_newline());
- is_only_newlines && sub_def.is_multiline()
- }
- _ => false,
- },
- TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => true,
- _ => false,
- };
-
- if should_outdent {
- match loc_annotation.value {
- TypeAnnotation::SpaceBefore(sub_def, _) => {
- sub_def.format_with_options(
- buf,
- Parens::NotNeeded,
- Newlines::No,
- indent,
- );
- }
- _ => {
- loc_annotation.format_with_options(
- buf,
- Parens::NotNeeded,
- Newlines::No,
- indent,
- );
- }
- }
- } else {
- loc_annotation.format_with_options(
- buf,
- Parens::NotNeeded,
- newlines,
- indent + INDENT,
- );
- }
- } else {
- buf.spaces(1);
- buf.push(':');
- buf.spaces(1);
- loc_annotation.format_with_options(
- buf,
- Parens::NotNeeded,
- Newlines::No,
- indent,
- );
- }
+ fmt_general_def(
+ loc_pattern,
+ buf,
+ indent,
+ ":",
+ &loc_annotation.value,
+ newlines,
+ );
}
Body(loc_pattern, loc_expr) => {
fmt_body(buf, &loc_pattern.value, &loc_expr.value, indent);
@@ -266,34 +239,7 @@ impl<'a> Formattable for ValueDef<'a> {
body_pattern,
body_expr,
} => {
- let is_type_multiline = ann_type.is_multiline();
- let is_type_function = matches!(
- ann_type.value,
- TypeAnnotation::Function(..)
- | TypeAnnotation::SpaceBefore(TypeAnnotation::Function(..), ..)
- | TypeAnnotation::SpaceAfter(TypeAnnotation::Function(..), ..)
- );
-
- let next_indent = if is_type_multiline {
- indent + INDENT
- } else {
- indent
- };
-
- ann_pattern.format(buf, indent);
- buf.push_str(" :");
-
- if is_type_multiline && is_type_function {
- ann_type.format_with_options(
- buf,
- Parens::NotNeeded,
- Newlines::Yes,
- next_indent,
- );
- } else {
- buf.spaces(1);
- ann_type.format(buf, indent);
- }
+ fmt_general_def(ann_pattern, buf, indent, ":", &ann_type.value, newlines);
if let Some(comment_str) = comment {
buf.push_str(" #");
@@ -308,6 +254,66 @@ impl<'a> Formattable for ValueDef<'a> {
}
}
+fn fmt_general_def(
+ lhs: L,
+ buf: &mut Buf,
+ indent: u16,
+ sep: &str,
+ rhs: &TypeAnnotation,
+ newlines: Newlines,
+) {
+ lhs.format(buf, indent);
+ buf.indent(indent);
+
+ if rhs.is_multiline() {
+ buf.spaces(1);
+ buf.push_str(sep);
+ buf.spaces(1);
+
+ let should_outdent = should_outdent(rhs);
+
+ if should_outdent {
+ match rhs {
+ TypeAnnotation::SpaceBefore(sub_def, _) => {
+ sub_def.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
+ }
+ _ => {
+ rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
+ }
+ }
+ } else {
+ rhs.format_with_options(buf, Parens::NotNeeded, newlines, indent + INDENT);
+ }
+ } else {
+ buf.spaces(1);
+ buf.push_str(sep);
+ buf.spaces(1);
+ rhs.format_with_options(buf, Parens::NotNeeded, Newlines::No, indent);
+ }
+}
+
+fn should_outdent(mut rhs: &TypeAnnotation) -> bool {
+ loop {
+ match rhs {
+ TypeAnnotation::SpaceBefore(sub_def, spaces) => {
+ let is_only_newlines = spaces.iter().all(|s| s.is_newline());
+ if !is_only_newlines || !sub_def.is_multiline() {
+ return false;
+ }
+ rhs = sub_def;
+ }
+ TypeAnnotation::Where(ann, _clauses) => {
+ if !ann.is_multiline() {
+ return false;
+ }
+ rhs = &ann.value;
+ }
+ TypeAnnotation::Record { .. } | TypeAnnotation::TagUnion { .. } => return true,
+ _ => return false,
+ }
+ }
+}
+
fn fmt_dbg_in_def<'a, 'buf>(
buf: &mut Buf<'buf>,
condition: &'a Loc>,
diff --git a/crates/compiler/fmt/src/expr.rs b/crates/compiler/fmt/src/expr.rs
index 1fc7a90032..8e716e1772 100644
--- a/crates/compiler/fmt/src/expr.rs
+++ b/crates/compiler/fmt/src/expr.rs
@@ -1,4 +1,4 @@
-use crate::annotation::{except_last, Formattable, Newlines, Parens};
+use crate::annotation::{except_last, is_collection_multiline, Formattable, Newlines, Parens};
use crate::collection::{fmt_collection, Braces};
use crate::def::fmt_defs;
use crate::pattern::fmt_pattern;
@@ -12,6 +12,7 @@ use roc_parse::ast::{
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch,
};
use roc_parse::ast::{StrLiteral, StrSegment};
+use roc_parse::ident::Accessor;
use roc_region::all::Loc;
impl<'a> Formattable for Expr<'a> {
@@ -35,9 +36,8 @@ impl<'a> Formattable for Expr<'a> {
| NonBase10Int { .. }
| SingleQuote(_)
| RecordAccess(_, _)
- | RecordAccessorFunction(_)
+ | AccessorFunction(_)
| TupleAccess(_, _)
- | TupleAccessorFunction(_)
| Var { .. }
| Underscore { .. }
| MalformedIdent(_, _)
@@ -49,27 +49,9 @@ impl<'a> Formattable for Expr<'a> {
// These expressions always have newlines
Defs(_, _) | When(_, _) => true,
- List(items) => items.iter().any(|loc_expr| loc_expr.is_multiline()),
+ List(items) => is_collection_multiline(items),
- Str(literal) => {
- use roc_parse::ast::StrLiteral::*;
-
- match literal {
- PlainLine(string) => {
- // When a PlainLine contains '\n' or '"', format as a block string
- string.contains('"') || string.contains('\n')
- }
- Line(_) => {
- // If this had any newlines, it'd have parsed as Block.
- false
- }
- Block(_) => {
- // Block strings are always formatted on multiple lines,
- // even if the string is only a single line.
- true
- }
- }
- }
+ Str(literal) => is_str_multiline(literal),
Apply(loc_expr, args, _) => {
loc_expr.is_multiline() || args.iter().any(|loc_arg| loc_arg.is_multiline())
}
@@ -114,9 +96,9 @@ impl<'a> Formattable for Expr<'a> {
.any(|loc_pattern| loc_pattern.is_multiline())
}
- Record(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
- Tuple(fields) => fields.iter().any(|loc_field| loc_field.is_multiline()),
- RecordUpdate { fields, .. } => fields.iter().any(|loc_field| loc_field.is_multiline()),
+ Record(fields) => is_collection_multiline(fields),
+ Tuple(fields) => is_collection_multiline(fields),
+ RecordUpdate { fields, .. } => is_collection_multiline(fields),
}
}
@@ -271,8 +253,21 @@ impl<'a> Formattable for Expr<'a> {
indent
};
+ let expr_needs_parens =
+ matches!(loc_expr.value.extract_spaces().item, Expr::Closure(..))
+ && !loc_args.is_empty();
+
+ if expr_needs_parens {
+ buf.push('(');
+ }
+
loc_expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
+ if expr_needs_parens {
+ buf.indent(indent);
+ buf.push(')');
+ }
+
for loc_arg in loc_args.iter() {
if should_reflow_outdentable {
buf.spaces(1);
@@ -432,23 +427,45 @@ impl<'a> Formattable for Expr<'a> {
}
}
- sub_expr.format_with_options(buf, Parens::InApply, newlines, indent);
+ let needs_newline = match &sub_expr.value {
+ SpaceBefore(..) => true,
+ Str(text) => is_str_multiline(text),
+ _ => false,
+ };
+ let needs_parens =
+ needs_newline && matches!(unary_op.value, called_via::UnaryOp::Negate);
+
+ if needs_parens {
+ // Unary negation can't be followed by whitespace (which is what a newline is) - so
+ // we need to wrap the negated value in parens.
+ buf.push('(');
+ }
+
+ let inner_indent = if needs_parens {
+ indent + INDENT
+ } else {
+ indent
+ };
+
+ sub_expr.format_with_options(buf, Parens::InApply, newlines, inner_indent);
+
+ if needs_parens {
+ buf.push(')');
+ }
}
- RecordAccessorFunction(key) => {
+ AccessorFunction(key) => {
buf.indent(indent);
buf.push('.');
- buf.push_str(key);
+ match key {
+ Accessor::RecordField(key) => buf.push_str(key),
+ Accessor::TupleIndex(key) => buf.push_str(key),
+ }
}
RecordAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
buf.push_str(key);
}
- TupleAccessorFunction(key) => {
- buf.indent(indent);
- buf.push('.');
- buf.push_str(key);
- }
TupleAccess(expr, key) => {
expr.format_with_options(buf, Parens::InApply, Newlines::Yes, indent);
buf.push('.');
@@ -464,6 +481,26 @@ impl<'a> Formattable for Expr<'a> {
}
}
+fn is_str_multiline(literal: &StrLiteral) -> bool {
+ use roc_parse::ast::StrLiteral::*;
+
+ match literal {
+ PlainLine(string) => {
+ // When a PlainLine contains '\n' or '"', format as a block string
+ string.contains('"') || string.contains('\n')
+ }
+ Line(_) => {
+ // If this had any newlines, it'd have parsed as Block.
+ false
+ }
+ Block(_) => {
+ // Block strings are always formatted on multiple lines,
+ // even if the string is only a single line.
+ true
+ }
+ }
+}
+
fn needs_unicode_escape(ch: char) -> bool {
matches!(ch, '\u{0000}'..='\u{001f}' | '\u{007f}'..='\u{009f}')
}
@@ -585,11 +622,11 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("\"\"\"");
- buf.newline();
+ buf.push_newline_literal();
for line in string.split('\n') {
buf.indent(indent);
buf.push_str_allow_spaces(line);
- buf.newline();
+ buf.push_newline_literal();
}
buf.indent(indent);
buf.push_str("\"\"\"");
@@ -613,7 +650,7 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.ensure_ends_with_newline();
buf.indent(indent);
buf.push_str("\"\"\"");
- buf.newline();
+ buf.push_newline_literal();
for segments in lines.iter() {
for seg in segments.iter() {
@@ -622,11 +659,11 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.indent(indent);
format_str_segment(seg, buf, indent);
} else {
- buf.newline();
+ buf.push_newline_literal();
}
}
- buf.newline();
+ buf.push_newline_literal();
}
buf.indent(indent);
buf.push_str("\"\"\"");
@@ -1282,7 +1319,7 @@ fn fmt_record<'a, 'buf>(
let loc_fields = fields.items;
let final_comments = fields.final_comments();
buf.indent(indent);
- if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) {
+ if loc_fields.is_empty() && final_comments.iter().all(|c| c.is_newline()) && update.is_none() {
buf.push_str("{}");
} else {
buf.push('{');
diff --git a/crates/compiler/fmt/src/lib.rs b/crates/compiler/fmt/src/lib.rs
index 95eca1d978..88a4974bf0 100644
--- a/crates/compiler/fmt/src/lib.rs
+++ b/crates/compiler/fmt/src/lib.rs
@@ -106,12 +106,20 @@ impl<'a> Buf<'a> {
self.spaces_to_flush += count;
}
- pub fn newline(&mut self) {
+ /// Only for use in emitting newlines in block strings, which don't follow the rule of
+ /// having at most two newlines in a row.
+ pub fn push_newline_literal(&mut self) {
self.spaces_to_flush = 0;
self.newlines_to_flush += 1;
self.beginning_of_line = true;
}
+ pub fn newline(&mut self) {
+ self.spaces_to_flush = 0;
+ self.newlines_to_flush = std::cmp::min(self.newlines_to_flush + 1, 2);
+ self.beginning_of_line = true;
+ }
+
/// Ensures the current buffer ends in a newline, if it didn't already.
/// Doesn't add a newline if the buffer already ends in one.
pub fn ensure_ends_with_newline(&mut self) {
diff --git a/crates/compiler/fmt/src/spaces.rs b/crates/compiler/fmt/src/spaces.rs
index b91fcad716..6cefc289a5 100644
--- a/crates/compiler/fmt/src/spaces.rs
+++ b/crates/compiler/fmt/src/spaces.rs
@@ -656,9 +656,8 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
},
Expr::Str(a) => Expr::Str(a.remove_spaces(arena)),
Expr::RecordAccess(a, b) => Expr::RecordAccess(arena.alloc(a.remove_spaces(arena)), b),
- Expr::RecordAccessorFunction(a) => Expr::RecordAccessorFunction(a),
+ Expr::AccessorFunction(a) => Expr::AccessorFunction(a),
Expr::TupleAccess(a, b) => Expr::TupleAccess(arena.alloc(a.remove_spaces(arena)), b),
- Expr::TupleAccessorFunction(a) => Expr::TupleAccessorFunction(a),
Expr::List(a) => Expr::List(a.remove_spaces(arena)),
Expr::RecordUpdate { update, fields } => Expr::RecordUpdate {
update: arena.alloc(update.remove_spaces(arena)),
@@ -745,6 +744,7 @@ fn remove_spaces_bad_ident(ident: BadIdent) -> BadIdent {
BadIdent::WeirdDotQualified(_) => BadIdent::WeirdDotQualified(Position::zero()),
BadIdent::StrayDot(_) => BadIdent::StrayDot(Position::zero()),
BadIdent::BadOpaqueRef(_) => BadIdent::BadOpaqueRef(Position::zero()),
+ BadIdent::QualifiedTupleAccessor(_) => BadIdent::QualifiedTupleAccessor(Position::zero()),
}
}
@@ -813,8 +813,8 @@ impl<'a> RemoveSpaces<'a> for TypeAnnotation<'a> {
vars: vars.remove_spaces(arena),
},
),
- TypeAnnotation::Tuple { fields, ext } => TypeAnnotation::Tuple {
- fields: fields.remove_spaces(arena),
+ TypeAnnotation::Tuple { elems: fields, ext } => TypeAnnotation::Tuple {
+ elems: fields.remove_spaces(arena),
ext: ext.remove_spaces(arena),
},
TypeAnnotation::Record { fields, ext } => TypeAnnotation::Record {
diff --git a/crates/compiler/gen_dev/Cargo.toml b/crates/compiler/gen_dev/Cargo.toml
index 5225d9f3c5..12680ffdb3 100644
--- a/crates/compiler/gen_dev/Cargo.toml
+++ b/crates/compiler/gen_dev/Cargo.toml
@@ -1,28 +1,29 @@
[package]
name = "roc_gen_dev"
description = "The development backend for the Roc compiler"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
[dependencies]
-roc_collections = { path = "../collections" }
-roc_region = { path = "../region" }
-roc_module = { path = "../module" }
-roc_problem = { path = "../problem" }
-roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
-roc_unify = { path = "../unify" }
-roc_solve = { path = "../solve" }
-roc_mono = { path = "../mono" }
-roc_target = { path = "../roc_target" }
+roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" }
+roc_module = { path = "../module" }
+roc_mono = { path = "../mono" }
+roc_problem = { path = "../problem" }
+roc_region = { path = "../region" }
+roc_solve = { path = "../solve" }
+roc_target = { path = "../roc_target" }
+roc_types = { path = "../types" }
+roc_unify = { path = "../unify" }
bumpalo.workspace = true
-target-lexicon.workspace = true
object.workspace = true
packed_struct.workspace = true
+target-lexicon.workspace = true
[dev-dependencies]
roc_can = { path = "../can" }
diff --git a/crates/compiler/gen_dev/README.md b/crates/compiler/gen_dev/README.md
index 15cffb0bc4..b2ab191534 100644
--- a/crates/compiler/gen_dev/README.md
+++ b/crates/compiler/gen_dev/README.md
@@ -86,6 +86,57 @@ This is the general procedure I follow with some helpful links:
1. If things aren't working, reach out on zulip. Get advice, maybe even pair.
1. Make a PR.
+## Debugging x86_64 backend output
+
+While working on the x86_64 backend it may be useful to inspect the assembly output of a given piece of Roc code. With the right tools, you can do this rather easily. You'll need `objdump` to follow along.
+
+We'll try to explore the x86 assembly output of some lines of Roc code:
+
+```elixir
+app "dbg"
+ provides [main] to "."
+
+main =
+ (List.len [1]) + 41
+```
+
+If this file exists somewhere in the repo as `dbg.roc`, we'll be able to compile an object file by issuing the following command:
+
+```console
+# `cargo run --` can be replaces with calling the compiled `roc` cli binary.
+$ cargo run -- build --dev main.roc --no-link
+```
+
+Which will produce a minimal `dbg.o` object file containing the output assembly code. This object file can be inspected by using `objdump` in the following way:
+
+```console
+$ objdump -M intel -dS dbg.o
+dbg.o: file format elf64-x86-64
+
+Disassembly of section .text.700000006:
+
+0000000000000000 :
+ 0: 55 push rbp
+ 1: 48 89 e5 mov rbp,rsp
+ 4: 48 8b 85 18 00 00 00 mov rax,QWORD PTR [rbp+0x18]
+ b: 5d pop rbp
+ c: c3 ret
+
+Disassembly of section .text.400000013:
+
+0000000000000000 :
+ 0: 55 push rbp
+# .. more output ..
+
+Disassembly of section .text.1000000000:
+
+0000000000000000 :
+ 0: 55 push rbp
+# .. more output ..
+```
+
+The output lines contain the hexadecimal representation of the x86 opcodes and fields followed by the `intel` assembly syntax. This setup is very useful for figuring out the causes of invalid pointer references (or equivalent) when running the resulting x86 assembly.
+
## Helpful Resources
- [Compiler Explorer](https://godbolt.org/) -
@@ -105,10 +156,10 @@ This is the general procedure I follow with some helpful links:
Also, sometimes it doesn't seem to generate things quite as you expect.
- [Alternative Online Assembler](http://shell-storm.org/online/Online-Assembler-and-Disassembler/) -
Like previous but with more architecture options.
-- [x86 and amd64 instruction reference](https://www.felixcloutier.com/x86/) -
+- [x86 and amd64 instruction reference](https://web.archive.org/web/20230221053750/https://www.felixcloutier.com/x86/) -
Great for looking up x86_64 instructions and there bytes.
Definitely missing information if you aren't used to reading it.
-- [Intel 64 ISA Reference](https://software.intel.com/content/dam/develop/public/us/en/documents/325383-sdm-vol-2abcd.pdf) -
+- [Intel 64 ISA Reference](https://community.intel.com/legacyfs/online/drupal_files/managed/a4/60/325383-sdm-vol-2abcd.pdf) -
Super dense manual.
Contains everything you would need to know for x86_64.
Also is like 2000 pages.
diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs
index 8f049e4f2c..ae3842c4a4 100644
--- a/crates/compiler/gen_dev/src/generic64/aarch64.rs
+++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs
@@ -2,10 +2,13 @@ use crate::generic64::{storage::StorageManager, Assembler, CallConv, RegTrait};
use crate::Relocation;
use bumpalo::collections::Vec;
use packed_struct::prelude::*;
+use roc_builtins::bitcode::FloatWidth;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{InLayout, STLayoutInterner};
+use super::CompareOperation;
+
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[allow(dead_code)]
pub enum AArch64GeneralReg {
@@ -609,9 +612,31 @@ impl Assembler for AArch64Assembler {
}
}
#[inline(always)]
+ fn mov_reg32_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
+ todo!()
+ }
+ #[inline(always)]
+ fn mov_reg16_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
+ todo!()
+ }
+ #[inline(always)]
+ fn mov_reg8_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg, _offset: i32) {
+ todo!()
+ }
+ #[inline(always)]
fn mov_base32_freg64(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64FloatReg) {
todo!("saving floating point reg to base offset for AArch64");
}
+ #[inline(always)]
+ fn movesd_mem64_offset32_freg64(
+ _buf: &mut Vec<'_, u8>,
+ _ptr: AArch64GeneralReg,
+ _offset: i32,
+ _src: AArch64FloatReg,
+ ) {
+ todo!()
+ }
+
#[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) {
if offset < 0 {
@@ -624,6 +649,19 @@ impl Assembler for AArch64Assembler {
}
}
+ #[inline(always)]
+ fn mov_base32_reg32(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
+ todo!()
+ }
+ #[inline(always)]
+ fn mov_base32_reg16(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
+ todo!()
+ }
+ #[inline(always)]
+ fn mov_base32_reg8(_buf: &mut Vec<'_, u8>, _offset: i32, _src: AArch64GeneralReg) {
+ todo!()
+ }
+
#[inline(always)]
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
@@ -640,6 +678,41 @@ impl Assembler for AArch64Assembler {
todo!("mem offsets over 32k for AArch64");
}
}
+ #[inline(always)]
+ fn mov_reg32_mem32_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: AArch64GeneralReg,
+ src: AArch64GeneralReg,
+ offset: i32,
+ ) {
+ if offset < 0 {
+ todo!("negative mem offsets for AArch64");
+ } else if offset < (0xFFF << 8) {
+ debug_assert!(offset % 8 == 0);
+ ldr_reg64_reg64_imm12(buf, dst, src, (offset as u16) >> 3);
+ } else {
+ todo!("mem offsets over 32k for AArch64");
+ }
+ }
+ #[inline(always)]
+ fn mov_reg16_mem16_offset32(
+ _buf: &mut Vec<'_, u8>,
+ _dst: AArch64GeneralReg,
+ _src: AArch64GeneralReg,
+ _offset: i32,
+ ) {
+ todo!()
+ }
+ #[inline(always)]
+ fn mov_reg8_mem8_offset32(
+ _buf: &mut Vec<'_, u8>,
+ _dst: AArch64GeneralReg,
+ _src: AArch64GeneralReg,
+ _offset: i32,
+ ) {
+ todo!()
+ }
+
#[inline(always)]
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
@@ -657,6 +730,36 @@ impl Assembler for AArch64Assembler {
}
}
+ #[inline(always)]
+ fn mov_mem32_offset32_reg32(
+ _buf: &mut Vec<'_, u8>,
+ _dst: AArch64GeneralReg,
+ _offset: i32,
+ _src: AArch64GeneralReg,
+ ) {
+ todo!()
+ }
+
+ #[inline(always)]
+ fn mov_mem16_offset32_reg16(
+ _buf: &mut Vec<'_, u8>,
+ _dst: AArch64GeneralReg,
+ _offset: i32,
+ _src: AArch64GeneralReg,
+ ) {
+ todo!()
+ }
+
+ #[inline(always)]
+ fn mov_mem8_offset32_reg8(
+ _buf: &mut Vec<'_, u8>,
+ _dst: AArch64GeneralReg,
+ _offset: i32,
+ _src: AArch64GeneralReg,
+ ) {
+ todo!()
+ }
+
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
@@ -740,12 +843,12 @@ impl Assembler for AArch64Assembler {
}
#[inline(always)]
fn sub_reg64_reg64_reg64(
- _buf: &mut Vec<'_, u8>,
- _dst: AArch64GeneralReg,
- _src1: AArch64GeneralReg,
- _src2: AArch64GeneralReg,
+ buf: &mut Vec<'_, u8>,
+ dst: AArch64GeneralReg,
+ src1: AArch64GeneralReg,
+ src2: AArch64GeneralReg,
) {
- todo!("registers subtractions for AArch64");
+ sub_reg64_reg64_reg64(buf, dst, src1, src2);
}
#[inline(always)]
@@ -788,6 +891,18 @@ impl Assembler for AArch64Assembler {
todo!("registers unsigned less than for AArch64");
}
+ #[inline(always)]
+ fn cmp_freg_freg_reg64(
+ _buf: &mut Vec<'_, u8>,
+ _dst: AArch64GeneralReg,
+ _src1: AArch64FloatReg,
+ _src2: AArch64FloatReg,
+ _width: FloatWidth,
+ _operation: CompareOperation,
+ ) {
+ todo!("registers float comparison for AArch64");
+ }
+
#[inline(always)]
fn igt_reg64_reg64_reg64(
_buf: &mut Vec<'_, u8>,
@@ -899,6 +1014,53 @@ impl Assembler for AArch64Assembler {
) {
todo!("bitwise xor for AArch64")
}
+
+ fn shl_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ _buf: &mut Vec<'a, u8>,
+ _storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
+ _dst: AArch64GeneralReg,
+ _src1: AArch64GeneralReg,
+ _src2: AArch64GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv,
+ {
+ todo!("shl for AArch64")
+ }
+
+ fn shr_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ _buf: &mut Vec<'a, u8>,
+ _storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
+ _dst: AArch64GeneralReg,
+ _src1: AArch64GeneralReg,
+ _src2: AArch64GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv,
+ {
+ todo!("shr for AArch64")
+ }
+
+ fn sar_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ _buf: &mut Vec<'a, u8>,
+ _storage_manager: &mut StorageManager<'a, 'r, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
+ _dst: AArch64GeneralReg,
+ _src1: AArch64GeneralReg,
+ _src2: AArch64GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv,
+ {
+ todo!("sar for AArch64")
+ }
+
+ fn sqrt_freg64_freg64(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
+ todo!("sqrt")
+ }
+
+ fn sqrt_freg32_freg32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _src: AArch64FloatReg) {
+ todo!("sqrt")
+ }
}
impl AArch64Assembler {}
@@ -1315,6 +1477,19 @@ fn sub_reg64_reg64_imm12(
buf.extend(inst.bytes());
}
+/// `SUB Xd, Xm, Xn` -> Subtract Xm and Xn and place the result into Xd.
+#[inline(always)]
+fn sub_reg64_reg64_reg64(
+ buf: &mut Vec<'_, u8>,
+ dst: AArch64GeneralReg,
+ src1: AArch64GeneralReg,
+ src2: AArch64GeneralReg,
+) {
+ let inst = ArithmeticShifted::new(true, false, ShiftType::LSL, 0, src2, src1, dst);
+
+ buf.extend(inst.bytes());
+}
+
/// `RET Xn` -> Return to the address stored in Xn.
#[inline(always)]
fn ret_reg64(buf: &mut Vec<'_, u8>, xn: AArch64GeneralReg) {
@@ -1535,6 +1710,34 @@ mod tests {
);
}
+ #[test]
+ fn test_sub_reg64_reg64_reg64() {
+ disassembler_test!(
+ sub_reg64_reg64_reg64,
+ |reg1: AArch64GeneralReg, reg2: AArch64GeneralReg, reg3: AArch64GeneralReg| {
+ if reg2 == AArch64GeneralReg::ZRSP {
+ // When the second register is ZR, it gets disassembled as neg,
+ // which is an alias for sub.
+ format!(
+ "neg {}, {}",
+ reg1.capstone_string(UsesZR),
+ reg3.capstone_string(UsesZR)
+ )
+ } else {
+ format!(
+ "sub {}, {}, {}",
+ reg1.capstone_string(UsesZR),
+ reg2.capstone_string(UsesZR),
+ reg3.capstone_string(UsesZR)
+ )
+ }
+ },
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS
+ );
+ }
+
#[test]
fn test_ret_reg64() {
disassembler_test!(
diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs
index c8c4ea209d..81e8f6bc7a 100644
--- a/crates/compiler/gen_dev/src/generic64/mod.rs
+++ b/crates/compiler/gen_dev/src/generic64/mod.rs
@@ -2,7 +2,7 @@ use crate::{
single_register_floats, single_register_int_builtins, single_register_integers, Backend, Env,
Relocation,
};
-use bumpalo::collections::Vec;
+use bumpalo::collections::{CollectIn, Vec};
use roc_builtins::bitcode::{self, FloatWidth, IntWidth};
use roc_collections::all::MutMap;
use roc_error_macros::internal_error;
@@ -25,7 +25,6 @@ pub(crate) mod x86_64;
use storage::{RegStorage, StorageManager};
-const REFCOUNT_ONE: u64 = i64::MIN as u64;
// TODO: on all number functions double check and deal with over/underflow.
pub trait CallConv>:
@@ -114,6 +113,13 @@ pub trait CallConv: Sized + Copy {
src2: GeneralReg,
);
+ fn shl_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
+ dst: GeneralReg,
+ src1: GeneralReg,
+ src2: GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv;
+
+ fn shr_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
+ dst: GeneralReg,
+ src1: GeneralReg,
+ src2: GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv;
+
+ fn sar_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
+ dst: GeneralReg,
+ src1: GeneralReg,
+ src2: GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv;
+
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String);
/// Jumps by an offset of offset bytes unconditionally.
@@ -207,22 +243,67 @@ pub trait Assembler: Sized + Copy {
// base32 is similar to stack based instructions but they reference the base/frame pointer.
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32);
- fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
- fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
- fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
+ fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
+ fn mov_reg32_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
+ fn mov_reg16_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
+ fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32);
+
+ fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
+
+ fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
+ fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
+ fn mov_base32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
+ fn mov_base32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
+
+ // move from memory (a pointer) to register
fn mov_reg64_mem64_offset32(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src: GeneralReg,
offset: i32,
);
+ fn mov_reg32_mem32_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: GeneralReg,
+ src: GeneralReg,
+ offset: i32,
+ );
+ fn mov_reg16_mem16_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: GeneralReg,
+ src: GeneralReg,
+ offset: i32,
+ );
+ fn mov_reg8_mem8_offset32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg, offset: i32);
+
+ // move from register to memory
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
offset: i32,
src: GeneralReg,
);
+ fn mov_mem32_offset32_reg32(
+ buf: &mut Vec<'_, u8>,
+ dst: GeneralReg,
+ offset: i32,
+ src: GeneralReg,
+ );
+ fn mov_mem16_offset32_reg16(
+ buf: &mut Vec<'_, u8>,
+ dst: GeneralReg,
+ offset: i32,
+ src: GeneralReg,
+ );
+ fn mov_mem8_offset32_reg8(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32, src: GeneralReg);
+
+ fn movesd_mem64_offset32_freg64(
+ buf: &mut Vec<'_, u8>,
+ ptr: GeneralReg,
+ offset: i32,
+ src: FloatReg,
+ );
/// Sign extends the data at `offset` with `size` as it copies it to `dst`
/// size must be less than or equal to 8.
@@ -236,6 +317,9 @@ pub trait Assembler: Sized + Copy {
fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg);
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
+ fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
+ fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg);
+
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
@@ -332,6 +416,15 @@ pub trait Assembler: Sized + Copy {
src2: GeneralReg,
);
+ fn cmp_freg_freg_reg64(
+ buf: &mut Vec<'_, u8>,
+ dst: GeneralReg,
+ src1: FloatReg,
+ src2: FloatReg,
+ width: FloatWidth,
+ operation: CompareOperation,
+ );
+
fn igt_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
@@ -686,14 +779,23 @@ impl<
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]);
}
- _ => {
- CC::load_returned_complex_symbol(
- &mut self.buf,
- &mut self.storage_manager,
- self.layout_interner,
- dst,
- ret_layout,
- );
+ other => {
+ //
+ match self.layout_interner.get(other) {
+ Layout::Boxed(_) => {
+ let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
+ ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]);
+ }
+ _ => {
+ CC::load_returned_complex_symbol(
+ &mut self.buf,
+ &mut self.storage_manager,
+ self.layout_interner,
+ dst,
+ ret_layout,
+ );
+ }
+ }
}
}
}
@@ -920,6 +1022,30 @@ impl<
}
}
+ fn build_num_sub_checked(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ num_layout: &InLayout<'a>,
+ return_layout: &InLayout<'a>,
+ ) {
+ let function_name = match self.interner().get(*num_layout) {
+ Layout::Builtin(Builtin::Int(width)) => &bitcode::NUM_SUB_CHECKED_INT[width],
+ Layout::Builtin(Builtin::Float(width)) => &bitcode::NUM_SUB_CHECKED_FLOAT[width],
+ Layout::Builtin(Builtin::Decimal) => bitcode::DEC_SUB_WITH_OVERFLOW,
+ x => internal_error!("NumSubChecked is not defined for {:?}", x),
+ };
+
+ self.build_fn_call(
+ dst,
+ function_name.to_string(),
+ &[*src1, *src2],
+ &[*num_layout, *num_layout],
+ return_layout,
+ )
+ }
+
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
use Builtin::Int;
@@ -1035,19 +1161,9 @@ impl<
}
fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) {
- match self.layout_interner.get(*layout) {
- Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
- let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
- let src1_reg = self
- .storage_manager
- .load_to_general_reg(&mut self.buf, src1);
- let src2_reg = self
- .storage_manager
- .load_to_general_reg(&mut self.buf, src2);
- ASM::sub_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
- }
- x => todo!("NumSub: layout, {:?}", x),
- }
+ // for the time being, `num_sub` is implemented as wrapping subtraction. In roc, the normal
+ // `sub` should panic on overflow, but we just don't do that yet
+ self.build_num_sub_wrap(dst, src1, src2, layout)
}
fn build_num_sub_wrap(
@@ -1058,7 +1174,7 @@ impl<
layout: &InLayout<'a>,
) {
match self.layout_interner.get(*layout) {
- Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
+ Layout::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@@ -1074,7 +1190,7 @@ impl<
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) {
match *arg_layout {
- single_register_int_builtins!() => {
+ single_register_int_builtins!() | Layout::BOOL => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@@ -1084,13 +1200,23 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::eq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
+ Layout::STR => {
+ // use a zig call
+ self.build_fn_call(
+ dst,
+ bitcode::STR_EQUAL.to_string(),
+ &[*src1, *src2],
+ &[Layout::STR, Layout::STR],
+ &Layout::BOOL,
+ )
+ }
x => todo!("NumEq: layout, {:?}", x),
}
}
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>) {
- match self.layout_interner.get(*arg_layout) {
- Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
+ match *arg_layout {
+ single_register_int_builtins!() | Layout::BOOL => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@@ -1100,10 +1226,44 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
+ Layout::STR => {
+ self.build_fn_call(
+ dst,
+ bitcode::STR_EQUAL.to_string(),
+ &[*src1, *src2],
+ &[Layout::STR, Layout::STR],
+ &Layout::BOOL,
+ );
+
+ // negate the result
+ let tmp = &Symbol::DEV_TMP;
+ let tmp_reg = self.storage_manager.claim_general_reg(&mut self.buf, tmp);
+ ASM::mov_reg64_imm64(&mut self.buf, tmp_reg, 164);
+
+ let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst);
+ ASM::neq_reg64_reg64_reg64(&mut self.buf, dst_reg, dst_reg, tmp_reg);
+ }
x => todo!("NumNeq: layout, {:?}", x),
}
}
+ fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>) {
+ match *arg_layout {
+ Layout::BOOL => {
+ let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
+ let src_reg = self.storage_manager.load_to_general_reg(&mut self.buf, src);
+
+ // Not would usually be implemented as `xor src, -1` followed by `and src, 1`
+ // but since our booleans are represented as `0x101010101010101` currently, we can simply XOR with that
+ let bool_val = [true as u8; 8];
+ ASM::mov_reg64_imm64(&mut self.buf, dst_reg, i64::from_ne_bytes(bool_val));
+ ASM::xor_reg64_reg64_reg64(&mut self.buf, src_reg, src_reg, dst_reg);
+ ASM::mov_reg64_reg64(&mut self.buf, dst_reg, src_reg);
+ }
+ x => todo!("Not: layout, {:?}", x),
+ }
+ }
+
fn build_num_lt(
&mut self,
dst: &Symbol,
@@ -1132,6 +1292,20 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::ult_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
+ Layout::Builtin(Builtin::Float(width)) => {
+ let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
+ let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
+ let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
+
+ ASM::cmp_freg_freg_reg64(
+ &mut self.buf,
+ dst_reg,
+ src1_reg,
+ src2_reg,
+ width,
+ CompareOperation::LessThan,
+ );
+ }
x => todo!("NumLt: layout, {:?}", x),
}
}
@@ -1164,6 +1338,20 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::ugt_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
+ Layout::Builtin(Builtin::Float(width)) => {
+ let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
+ let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
+ let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
+
+ ASM::cmp_freg_freg_reg64(
+ &mut self.buf,
+ dst_reg,
+ src1_reg,
+ src2_reg,
+ width,
+ CompareOperation::GreaterThan,
+ );
+ }
x => todo!("NumGt: layout, {:?}", x),
}
}
@@ -1244,6 +1432,26 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::lte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
+ Layout::F64 | Layout::F32 => {
+ let width = if *arg_layout == Layout::F64 {
+ FloatWidth::F64
+ } else {
+ FloatWidth::F32
+ };
+
+ let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
+ let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
+ let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
+
+ ASM::cmp_freg_freg_reg64(
+ &mut self.buf,
+ dst_reg,
+ src1_reg,
+ src2_reg,
+ width,
+ CompareOperation::LessThanOrEqual,
+ );
+ }
x => todo!("NumLte: layout, {:?}", x),
}
}
@@ -1266,6 +1474,26 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::gte_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
+ Layout::F64 | Layout::F32 => {
+ let width = if *arg_layout == Layout::F64 {
+ FloatWidth::F64
+ } else {
+ FloatWidth::F32
+ };
+
+ let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
+ let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
+ let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
+
+ ASM::cmp_freg_freg_reg64(
+ &mut self.buf,
+ dst_reg,
+ src1_reg,
+ src2_reg,
+ width,
+ CompareOperation::GreaterThanOrEqual,
+ );
+ }
x => todo!("NumGte: layout, {:?}", x),
}
}
@@ -1279,25 +1507,14 @@ impl<
dst: &Symbol,
capacity: Symbol,
capacity_layout: InLayout<'a>,
+ elem_layout: InLayout<'a>,
ret_layout: &InLayout<'a>,
) {
// List alignment argument (u32).
- let u32_layout = Layout::U32;
- let list_alignment = self.layout_interner.alignment_bytes(*ret_layout);
- self.load_literal(
- &Symbol::DEV_TMP,
- &u32_layout,
- &Literal::Int((list_alignment as i128).to_ne_bytes()),
- );
+ self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP);
// Load element_width argument (usize).
- let u64_layout = Layout::U64;
- let element_width = self.layout_interner.stack_size(*ret_layout);
- self.load_literal(
- &Symbol::DEV_TMP2,
- &u64_layout,
- &Literal::Int((element_width as i128).to_ne_bytes()),
- );
+ self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
// Setup the return location.
let base_offset = self
@@ -1312,12 +1529,7 @@ impl<
// element_width
Symbol::DEV_TMP2,
];
- let lowlevel_arg_layouts = bumpalo::vec![
- in self.env.arena;
- capacity_layout,
- u32_layout,
- u64_layout,
- ];
+ let lowlevel_arg_layouts = [capacity_layout, Layout::U32, Layout::U64];
self.build_fn_call(
&Symbol::DEV_TMP3,
@@ -1341,6 +1553,144 @@ impl<
self.free_symbol(&Symbol::DEV_TMP3);
}
+ fn build_list_reserve(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
+ ret_layout: &InLayout<'a>,
+ ) {
+ let list = args[0];
+ let list_layout = arg_layouts[0];
+ let spare = args[1];
+ let spare_layout = arg_layouts[1];
+
+ // Load list alignment argument (u32).
+ self.load_layout_alignment(list_layout, Symbol::DEV_TMP);
+
+ // Load element_width argument (usize).
+ self.load_layout_stack_size(*ret_layout, Symbol::DEV_TMP2);
+
+ // Load UpdateMode.Immutable argument (0u8)
+ let u8_layout = Layout::U8;
+ let update_mode = 0u8;
+ self.load_literal(
+ &Symbol::DEV_TMP3,
+ &u8_layout,
+ &Literal::Int((update_mode as i128).to_ne_bytes()),
+ );
+
+ // Setup the return location.
+ let base_offset = self
+ .storage_manager
+ .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout));
+
+ let lowlevel_args = bumpalo::vec![
+ in self.env.arena;
+ list,
+ // alignment
+ Symbol::DEV_TMP,
+ spare,
+ // element_width
+ Symbol::DEV_TMP2,
+ // update_mode
+ Symbol::DEV_TMP3,
+
+ ];
+ let lowlevel_arg_layouts = [
+ list_layout,
+ Layout::U32,
+ spare_layout,
+ Layout::U64,
+ u8_layout,
+ ];
+
+ self.build_fn_call(
+ &Symbol::DEV_TMP4,
+ bitcode::LIST_RESERVE.to_string(),
+ &lowlevel_args,
+ &lowlevel_arg_layouts,
+ ret_layout,
+ );
+ self.free_symbol(&Symbol::DEV_TMP);
+ self.free_symbol(&Symbol::DEV_TMP2);
+ self.free_symbol(&Symbol::DEV_TMP3);
+
+ // Return list value from fn call
+ self.storage_manager.copy_symbol_to_stack_offset(
+ self.layout_interner,
+ &mut self.buf,
+ base_offset,
+ &Symbol::DEV_TMP4,
+ ret_layout,
+ );
+
+ self.free_symbol(&Symbol::DEV_TMP4);
+ }
+
+ fn build_list_append_unsafe(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
+ ret_layout: &InLayout<'a>,
+ ) {
+ let list = args[0];
+ let list_layout = arg_layouts[0];
+ let elem = args[1];
+ let elem_layout = arg_layouts[1];
+
+ // Have to pass the input element by pointer, so put it on the stack and load it's address.
+ self.storage_manager
+ .ensure_symbol_on_stack(&mut self.buf, &elem);
+ let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem);
+
+ // Load address of output element into register.
+ let reg = self
+ .storage_manager
+ .claim_general_reg(&mut self.buf, &Symbol::DEV_TMP);
+ ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset);
+
+ // Load element_witdh argument (usize).
+ self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
+
+ // Setup the return location.
+ let base_offset = self
+ .storage_manager
+ .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout));
+
+ let lowlevel_args = bumpalo::vec![
+ in self.env.arena;
+ list,
+ // element
+ Symbol::DEV_TMP,
+ // element_width
+ Symbol::DEV_TMP2
+ ];
+ let lowlevel_arg_layouts = [list_layout, Layout::U64, Layout::U64];
+
+ self.build_fn_call(
+ &Symbol::DEV_TMP3,
+ bitcode::LIST_APPEND_UNSAFE.to_string(),
+ &lowlevel_args,
+ &lowlevel_arg_layouts,
+ ret_layout,
+ );
+ self.free_symbol(&Symbol::DEV_TMP);
+ self.free_symbol(&Symbol::DEV_TMP2);
+
+ // Return list value from fn call
+ self.storage_manager.copy_symbol_to_stack_offset(
+ self.layout_interner,
+ &mut self.buf,
+ base_offset,
+ &Symbol::DEV_TMP3,
+ ret_layout,
+ );
+
+ self.free_symbol(&Symbol::DEV_TMP3);
+ }
+
fn build_list_get_unsafe(
&mut self,
dst: &Symbol,
@@ -1360,16 +1710,22 @@ impl<
|storage_manager, buf, list_ptr| {
ASM::mov_reg64_base32(buf, list_ptr, base_offset as i32);
storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp| {
+ // calculate `element_width * index`
ASM::mov_reg64_imm64(buf, tmp, ret_stack_size as i64);
ASM::imul_reg64_reg64_reg64(buf, tmp, tmp, index_reg);
+
+ // add the offset to the list pointer, store in `tmp`
ASM::add_reg64_reg64_reg64(buf, tmp, tmp, list_ptr);
- match *ret_layout {
- single_register_integers!() if ret_stack_size == 8 => {
- let dst_reg = storage_manager.claim_general_reg(buf, dst);
- ASM::mov_reg64_mem64_offset32(buf, dst_reg, tmp, 0);
- }
- x => internal_error!("Loading list element with layout: {:?}", x),
- }
+ let element_ptr = tmp;
+
+ Self::ptr_read(
+ buf,
+ storage_manager,
+ self.layout_interner,
+ element_ptr,
+ *ret_layout,
+ *dst,
+ );
});
},
);
@@ -1393,13 +1749,8 @@ impl<
let elem = args[2];
let elem_layout = arg_layouts[2];
- let u32_layout = Layout::U32;
- let list_alignment = self.layout_interner.alignment_bytes(list_layout);
- self.load_literal(
- &Symbol::DEV_TMP,
- &u32_layout,
- &Literal::Int((list_alignment as i128).to_ne_bytes()),
- );
+ // Load list alignment argument (u32).
+ self.load_layout_alignment(list_layout, Symbol::DEV_TMP);
// Have to pass the input element by pointer, so put it on the stack and load it's address.
self.storage_manager
@@ -1413,12 +1764,7 @@ impl<
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset);
// Load the elements size.
- let elem_stack_size = self.layout_interner.stack_size(elem_layout);
- self.load_literal(
- &Symbol::DEV_TMP3,
- &u64_layout,
- &Literal::Int((elem_stack_size as i128).to_ne_bytes()),
- );
+ self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP3);
// Setup the return location.
let base_offset = self
@@ -1465,14 +1811,13 @@ impl<
Symbol::DEV_TMP3,
Symbol::DEV_TMP4,
];
- let lowlevel_arg_layouts = bumpalo::vec![
- in self.env.arena;
- list_layout,
- u32_layout,
- index_layout,
- u64_layout,
- u64_layout,
- u64_layout,
+ let lowlevel_arg_layouts = [
+ list_layout,
+ Layout::U32,
+ index_layout,
+ u64_layout,
+ u64_layout,
+ u64_layout,
];
self.build_fn_call(
@@ -1499,6 +1844,133 @@ impl<
self.free_symbol(&Symbol::DEV_TMP5);
}
+ fn build_list_concat(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
+ elem_layout: InLayout<'a>,
+ ret_layout: &InLayout<'a>,
+ ) {
+ let list_a = args[0];
+ let list_a_layout = arg_layouts[0];
+ let list_b = args[1];
+ let list_b_layout = arg_layouts[1];
+
+ // Load list alignment argument (u32).
+ self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP);
+
+ // Load element_width argument (usize).
+ self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP2);
+
+ // Setup the return location.
+ let base_offset = self
+ .storage_manager
+ .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout));
+
+ let lowlevel_args = bumpalo::vec![
+ in self.env.arena;
+ list_a,
+ list_b,
+ // alignment
+ Symbol::DEV_TMP,
+ // element_width
+ Symbol::DEV_TMP2,
+ ];
+ let lowlevel_arg_layouts = [list_a_layout, list_b_layout, Layout::U32, Layout::U64];
+
+ self.build_fn_call(
+ &Symbol::DEV_TMP3,
+ bitcode::LIST_CONCAT.to_string(),
+ &lowlevel_args,
+ &lowlevel_arg_layouts,
+ ret_layout,
+ );
+
+ self.free_symbol(&Symbol::DEV_TMP);
+ self.free_symbol(&Symbol::DEV_TMP2);
+
+ // Return list value from fn call
+ self.storage_manager.copy_symbol_to_stack_offset(
+ self.layout_interner,
+ &mut self.buf,
+ base_offset,
+ &Symbol::DEV_TMP3,
+ ret_layout,
+ );
+
+ self.free_symbol(&Symbol::DEV_TMP3);
+ }
+
+ fn build_list_prepend(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
+ ret_layout: &InLayout<'a>,
+ ) {
+ let list = args[0];
+ let list_layout = arg_layouts[0];
+ let elem = args[1];
+ let elem_layout = arg_layouts[1];
+
+ // List alignment argument (u32).
+ self.load_layout_alignment(*ret_layout, Symbol::DEV_TMP);
+
+ // Have to pass the input element by pointer, so put it on the stack and load it's address.
+ self.storage_manager
+ .ensure_symbol_on_stack(&mut self.buf, &elem);
+ let (new_elem_offset, _) = self.storage_manager.stack_offset_and_size(&elem);
+
+ // Load address of input element into register.
+ let reg = self
+ .storage_manager
+ .claim_general_reg(&mut self.buf, &Symbol::DEV_TMP2);
+ ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset);
+
+ // Load element_witdh argument (usize).
+ self.load_layout_stack_size(elem_layout, Symbol::DEV_TMP3);
+
+ // Setup the return location.
+ let base_offset = self
+ .storage_manager
+ .claim_stack_area(dst, self.layout_interner.stack_size(*ret_layout));
+
+ let lowlevel_args = bumpalo::vec![
+ in self.env.arena;
+ list,
+ // alignment
+ Symbol::DEV_TMP,
+ // element
+ Symbol::DEV_TMP2,
+ // element_width
+ Symbol::DEV_TMP3,
+ ];
+ let lowlevel_arg_layouts = [list_layout, Layout::U32, Layout::U64, Layout::U64];
+
+ self.build_fn_call(
+ &Symbol::DEV_TMP4,
+ bitcode::LIST_PREPEND.to_string(),
+ &lowlevel_args,
+ &lowlevel_arg_layouts,
+ ret_layout,
+ );
+ self.free_symbol(&Symbol::DEV_TMP);
+ self.free_symbol(&Symbol::DEV_TMP2);
+ self.free_symbol(&Symbol::DEV_TMP3);
+
+ // Return list value from fn call
+ self.storage_manager.copy_symbol_to_stack_offset(
+ self.layout_interner,
+ &mut self.buf,
+ base_offset,
+ &Symbol::DEV_TMP4,
+ ret_layout,
+ );
+
+ self.free_symbol(&Symbol::DEV_TMP4);
+ }
+
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol) {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
self.storage_manager
@@ -1521,102 +1993,68 @@ impl<
fn create_array(
&mut self,
sym: &Symbol,
- elem_layout: &InLayout<'a>,
- elems: &'a [ListLiteralElement<'a>],
+ element_in_layout: &InLayout<'a>,
+ elements: &[ListLiteralElement<'a>],
) {
- // Allocate
- // This requires at least 8 for the refcount alignment.
- let allocation_alignment = std::cmp::max(
- 8,
- self.layout_interner
- .allocation_alignment_bytes(*elem_layout) as u64,
- );
+ let element_layout = self.layout_interner.get(*element_in_layout);
+ let element_width = self.layout_interner.stack_size(*element_in_layout) as u64;
- let elem_size = self.layout_interner.stack_size(*elem_layout) as u64;
- let allocation_size = elem_size * elems.len() as u64 + allocation_alignment /* add space for refcount */;
- let u64_layout = Layout::U64;
+ // load the total size of the data we want to store (excludes refcount)
+ let data_bytes_symbol = Symbol::DEV_TMP;
+ let data_bytes = element_width * elements.len() as u64;
self.load_literal(
- &Symbol::DEV_TMP,
- &u64_layout,
- &Literal::Int((allocation_size as i128).to_ne_bytes()),
- );
- let u32_layout = Layout::U32;
- self.load_literal(
- &Symbol::DEV_TMP2,
- &u32_layout,
- &Literal::Int((allocation_alignment as i128).to_ne_bytes()),
+ &data_bytes_symbol,
+ &Layout::U64,
+ &Literal::Int((data_bytes as i128).to_ne_bytes()),
);
- self.build_fn_call(
- &Symbol::DEV_TMP3,
- "roc_alloc".to_string(),
- &[Symbol::DEV_TMP, Symbol::DEV_TMP2],
- &[u64_layout, u32_layout],
- &u64_layout,
- );
- self.free_symbol(&Symbol::DEV_TMP);
- self.free_symbol(&Symbol::DEV_TMP2);
+ // Load allocation alignment (u32)
+ let element_alignment_symbol = Symbol::DEV_TMP2;
+ self.load_layout_alignment(Layout::U32, element_alignment_symbol);
- // Fill pointer with elems
+ self.allocate_with_refcount(
+ Symbol::DEV_TMP3,
+ data_bytes_symbol,
+ element_alignment_symbol,
+ );
+
+ self.free_symbol(&data_bytes_symbol);
+ self.free_symbol(&element_alignment_symbol);
+
+ // The pointer already points to the first element
let ptr_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3);
- // Point to first element of array.
- ASM::add_reg64_reg64_imm32(&mut self.buf, ptr_reg, ptr_reg, allocation_alignment as i32);
-
- // fill refcount at -8.
- self.storage_manager.with_tmp_general_reg(
- &mut self.buf,
- |_storage_manager, buf, tmp_reg| {
- ASM::mov_reg64_imm64(buf, tmp_reg, REFCOUNT_ONE as i64);
- ASM::mov_mem64_offset32_reg64(buf, ptr_reg, -8, tmp_reg);
- },
- );
// Copy everything into output array.
- let mut elem_offset = 0;
- for elem in elems {
+ let mut element_offset = 0;
+ for elem in elements {
// TODO: this could be a lot faster when loading large lists
// if we move matching on the element layout to outside this loop.
// It also greatly bloats the code here.
// Refactor this and switch to one external match.
// We also could make loadining indivitual literals much faster
- let elem_sym = match elem {
- ListLiteralElement::Symbol(sym) => sym,
+ let element_symbol = match elem {
+ ListLiteralElement::Symbol(sym) => *sym,
ListLiteralElement::Literal(lit) => {
- self.load_literal(&Symbol::DEV_TMP, elem_layout, lit);
- &Symbol::DEV_TMP
+ self.load_literal(&Symbol::DEV_TMP, element_in_layout, lit);
+ Symbol::DEV_TMP
}
};
- // TODO: Expand to all types.
- match self.layout_interner.get(*elem_layout) {
- Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
- let sym_reg = self
- .storage_manager
- .load_to_general_reg(&mut self.buf, elem_sym);
- ASM::mov_mem64_offset32_reg64(&mut self.buf, ptr_reg, elem_offset, sym_reg);
- }
- _ if elem_size == 0 => {}
- _ if elem_size > 8 => {
- let (from_offset, size) = self.storage_manager.stack_offset_and_size(elem_sym);
- debug_assert!(from_offset % 8 == 0);
- debug_assert!(size % 8 == 0);
- debug_assert_eq!(size as u64, elem_size);
- self.storage_manager.with_tmp_general_reg(
- &mut self.buf,
- |_storage_manager, buf, tmp_reg| {
- for i in (0..size as i32).step_by(8) {
- ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i);
- ASM::mov_mem64_offset32_reg64(buf, ptr_reg, elem_offset, tmp_reg);
- }
- },
- );
- }
- x => todo!("copying data to list with layout, {:?}", x),
- }
- elem_offset += elem_size as i32;
- if elem_sym == &Symbol::DEV_TMP {
- self.free_symbol(elem_sym);
+
+ Self::ptr_write(
+ &mut self.buf,
+ &mut self.storage_manager,
+ ptr_reg,
+ element_offset,
+ element_width,
+ element_layout,
+ element_symbol,
+ );
+
+ element_offset += element_width as i32;
+ if element_symbol == Symbol::DEV_TMP {
+ self.free_symbol(&element_symbol);
}
}
@@ -1627,7 +2065,7 @@ impl<
let base_offset = storage_manager.claim_stack_area(sym, 24);
ASM::mov_base32_reg64(buf, base_offset, ptr_reg);
- ASM::mov_reg64_imm64(buf, tmp_reg, elems.len() as i64);
+ ASM::mov_reg64_imm64(buf, tmp_reg, elements.len() as i64);
ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg);
},
@@ -1679,10 +2117,77 @@ impl<
tag_layouts[tag_id as usize],
);
}
- x => todo!("loading from union type: {:?}", x),
+ _ => {
+ let union_in_layout = self.layout_interner.insert(Layout::Union(*union_layout));
+ todo!(
+ "loading from union type: {:?}",
+ self.layout_interner.dbg(union_in_layout)
+ )
+ }
}
}
+ fn expr_box(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>) {
+ let element_width_symbol = Symbol::DEV_TMP;
+ self.load_layout_stack_size(element_layout, element_width_symbol);
+
+ // Load allocation alignment (u32)
+ let element_alignment_symbol = Symbol::DEV_TMP2;
+ self.load_layout_alignment(Layout::U32, element_alignment_symbol);
+
+ self.allocate_with_refcount(
+ Symbol::DEV_TMP3,
+ element_width_symbol,
+ element_alignment_symbol,
+ );
+
+ self.free_symbol(&element_width_symbol);
+ self.free_symbol(&element_alignment_symbol);
+
+ // Fill pointer with the value
+ let ptr_reg = self
+ .storage_manager
+ .load_to_general_reg(&mut self.buf, &Symbol::DEV_TMP3);
+
+ let element_width = self.layout_interner.stack_size(element_layout) as u64;
+ let element_offset = 0;
+
+ Self::ptr_write(
+ &mut self.buf,
+ &mut self.storage_manager,
+ ptr_reg,
+ element_offset,
+ element_width,
+ self.layout_interner.get(element_layout),
+ value,
+ );
+
+ if value == Symbol::DEV_TMP {
+ self.free_symbol(&value);
+ }
+
+ // box is just a pointer on the stack
+ let base_offset = self.storage_manager.claim_stack_area(&sym, 8);
+ ASM::mov_base32_reg64(&mut self.buf, base_offset, ptr_reg);
+
+ self.free_symbol(&Symbol::DEV_TMP3);
+ }
+
+ fn expr_unbox(&mut self, dst: Symbol, ptr: Symbol, element_layout: InLayout<'a>) {
+ let ptr_reg = self
+ .storage_manager
+ .load_to_general_reg(&mut self.buf, &ptr);
+
+ Self::ptr_read(
+ &mut self.buf,
+ &mut self.storage_manager,
+ self.layout_interner,
+ ptr_reg,
+ element_layout,
+ dst,
+ );
+ }
+
fn get_tag_id(&mut self, sym: &Symbol, structure: &Symbol, union_layout: &UnionLayout<'a>) {
self.storage_manager.load_union_tag_id(
self.layout_interner,
@@ -1729,6 +2234,33 @@ impl<
let val = *x;
ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64);
}
+ (
+ Literal::Int(bytes),
+ Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128)),
+ ) => {
+ self.storage_manager.with_tmp_general_reg(
+ &mut self.buf,
+ |storage_manager, buf, reg| {
+ let base_offset = storage_manager.claim_stack_area(sym, 16);
+
+ let mut num_bytes = [0; 8];
+ num_bytes.copy_from_slice(&bytes[..8]);
+ let num = i64::from_ne_bytes(num_bytes);
+ ASM::mov_reg64_imm64(buf, reg, num);
+ ASM::mov_base32_reg64(buf, base_offset, reg);
+
+ num_bytes.copy_from_slice(&bytes[8..16]);
+ let num = i64::from_ne_bytes(num_bytes);
+ ASM::mov_reg64_imm64(buf, reg, num);
+ ASM::mov_base32_reg64(buf, base_offset + 8, reg);
+ },
+ );
+ }
+ (Literal::Byte(x), Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8))) => {
+ let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
+ let val = *x;
+ ASM::mov_reg64_imm64(&mut self.buf, reg, val as i64);
+ }
(Literal::Bool(x), Layout::Builtin(Builtin::Bool)) => {
let reg = self.storage_manager.claim_general_reg(&mut self.buf, sym);
let val = [*x as u8; 16];
@@ -1744,15 +2276,11 @@ impl<
let val = *x as f32;
ASM::mov_freg32_imm32(&mut self.buf, &mut self.relocs, reg, val);
}
- (Literal::Str(x), Layout::Builtin(Builtin::Str)) if x.len() < 24 => {
- // Load small string.
+ (Literal::Decimal(bytes), Layout::Builtin(Builtin::Decimal)) => {
self.storage_manager.with_tmp_general_reg(
&mut self.buf,
|storage_manager, buf, reg| {
- let base_offset = storage_manager.claim_stack_area(sym, 24);
- let mut bytes = [0; 24];
- bytes[..x.len()].copy_from_slice(x.as_bytes());
- bytes[23] = (x.len() as u8) | 0b1000_0000;
+ let base_offset = storage_manager.claim_stack_area(sym, 16);
let mut num_bytes = [0; 8];
num_bytes.copy_from_slice(&bytes[..8]);
@@ -1764,14 +2292,49 @@ impl<
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(buf, reg, num);
ASM::mov_base32_reg64(buf, base_offset + 8, reg);
-
- num_bytes.copy_from_slice(&bytes[16..]);
- let num = i64::from_ne_bytes(num_bytes);
- ASM::mov_reg64_imm64(buf, reg, num);
- ASM::mov_base32_reg64(buf, base_offset + 16, reg);
},
);
}
+ (Literal::Str(x), Layout::Builtin(Builtin::Str)) => {
+ if x.len() < 24 {
+ // Load small string.
+ self.storage_manager.with_tmp_general_reg(
+ &mut self.buf,
+ |storage_manager, buf, reg| {
+ let base_offset = storage_manager.claim_stack_area(sym, 24);
+ let mut bytes = [0; 24];
+ bytes[..x.len()].copy_from_slice(x.as_bytes());
+ bytes[23] = (x.len() as u8) | 0b1000_0000;
+
+ let mut num_bytes = [0; 8];
+ num_bytes.copy_from_slice(&bytes[..8]);
+ let num = i64::from_ne_bytes(num_bytes);
+ ASM::mov_reg64_imm64(buf, reg, num);
+ ASM::mov_base32_reg64(buf, base_offset, reg);
+
+ num_bytes.copy_from_slice(&bytes[8..16]);
+ let num = i64::from_ne_bytes(num_bytes);
+ ASM::mov_reg64_imm64(buf, reg, num);
+ ASM::mov_base32_reg64(buf, base_offset + 8, reg);
+
+ num_bytes.copy_from_slice(&bytes[16..]);
+ let num = i64::from_ne_bytes(num_bytes);
+ ASM::mov_reg64_imm64(buf, reg, num);
+ ASM::mov_base32_reg64(buf, base_offset + 16, reg);
+ },
+ );
+ } else {
+ // load large string (pretend it's a `List U8`). We should move this data into
+ // the binary eventually because our RC algorithm won't free this value
+ let elements: Vec<_> = x
+ .as_bytes()
+ .iter()
+ .map(|b| ListLiteralElement::Literal(Literal::Byte(*b)))
+ .collect_in(self.storage_manager.env.arena);
+
+ self.create_array(sym, &Layout::U8, elements.into_bump_slice())
+ }
+ }
x => todo!("loading literal, {:?}", x),
}
}
@@ -1799,9 +2362,19 @@ impl<
CC::FLOAT_RETURN_REGS[0],
);
}
- _ => {
- internal_error!("All primitive valuse should fit in a single register");
- }
+ other => match self.layout_interner.get(other) {
+ Layout::Boxed(_) => {
+ // treat like a 64-bit integer
+ self.storage_manager.load_to_specified_general_reg(
+ &mut self.buf,
+ sym,
+ CC::GENERAL_RETURN_REGS[0],
+ );
+ }
+ _ => {
+ internal_error!("All primitive values should fit in a single register");
+ }
+ },
}
} else {
CC::return_complex_symbol(
@@ -1880,6 +2453,139 @@ impl<
}
}
}
+
+ fn build_int_shift_left(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ int_width: IntWidth,
+ ) {
+ let buf = &mut self.buf;
+
+ match int_width {
+ IntWidth::U128 | IntWidth::I128 => todo!(),
+ _ => {
+ let dst_reg = self.storage_manager.claim_general_reg(buf, dst);
+ let src1_reg = self.storage_manager.load_to_general_reg(buf, src1);
+ let src2_reg = self.storage_manager.load_to_general_reg(buf, src2);
+
+ ASM::shl_reg64_reg64_reg64(
+ buf,
+ &mut self.storage_manager,
+ dst_reg,
+ src1_reg,
+ src2_reg,
+ );
+ }
+ }
+ }
+
+ fn build_int_shift_right(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ int_width: IntWidth,
+ ) {
+ let buf = &mut self.buf;
+
+ match int_width {
+ IntWidth::U128 | IntWidth::I128 => todo!(),
+ _ => {
+ let dst_reg = self.storage_manager.claim_general_reg(buf, dst);
+ let src1_reg = self.storage_manager.load_to_general_reg(buf, src1);
+ let src2_reg = self.storage_manager.load_to_general_reg(buf, src2);
+
+ // to get sign extension "for free", we move our bits to the left
+ // so the integers sign bit is stored in the register's sign bit.
+ // Then we arithmetic shift right, getting the correct sign extension behavior,
+ // then shift logical right to get the bits back into the position they should
+ // be for our particular integer width
+ let sign_extend_shift_amount = 64 - (int_width.stack_size() as i64 * 8);
+
+ if sign_extend_shift_amount > 0 {
+ self.storage_manager.with_tmp_general_reg(
+ buf,
+ |storage_manager, buf, tmp_reg| {
+ ASM::mov_reg64_imm64(buf, tmp_reg, sign_extend_shift_amount);
+ ASM::shl_reg64_reg64_reg64(
+ buf,
+ storage_manager,
+ src1_reg,
+ src1_reg,
+ tmp_reg,
+ );
+ },
+ )
+ }
+
+ ASM::sar_reg64_reg64_reg64(
+ buf,
+ &mut self.storage_manager,
+ dst_reg,
+ src1_reg,
+ src2_reg,
+ );
+
+ if sign_extend_shift_amount > 0 {
+ // shift back if needed
+ self.storage_manager.with_tmp_general_reg(
+ &mut self.buf,
+ |storage_manager, buf, tmp_reg| {
+ ASM::mov_reg64_imm64(buf, tmp_reg, sign_extend_shift_amount);
+ ASM::shr_reg64_reg64_reg64(
+ buf,
+ storage_manager,
+ dst_reg,
+ dst_reg,
+ tmp_reg,
+ );
+ },
+ )
+ }
+ }
+ }
+ }
+
+ fn build_int_shift_right_zero_fill(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ int_width: IntWidth,
+ ) {
+ let buf = &mut self.buf;
+
+ match int_width {
+ IntWidth::U128 | IntWidth::I128 => todo!(),
+ _ => {
+ let dst_reg = self.storage_manager.claim_general_reg(buf, dst);
+ let src1_reg = self.storage_manager.load_to_general_reg(buf, src1);
+ let src2_reg = self.storage_manager.load_to_general_reg(buf, src2);
+
+ ASM::shr_reg64_reg64_reg64(
+ buf,
+ &mut self.storage_manager,
+ dst_reg,
+ src1_reg,
+ src2_reg,
+ );
+ }
+ }
+ }
+
+ fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth) {
+ let buf = &mut self.buf;
+
+ let dst_reg = self.storage_manager.claim_float_reg(buf, &dst);
+ let src_reg = self.storage_manager.load_to_float_reg(buf, &src);
+
+ match float_width {
+ FloatWidth::F32 => ASM::sqrt_freg32_freg32(buf, dst_reg, src_reg),
+ FloatWidth::F64 => ASM::sqrt_freg64_freg64(buf, dst_reg, src_reg),
+ }
+ }
}
/// This impl block is for ir related instructions that need backend specific information.
@@ -1893,6 +2599,149 @@ impl<
CC: CallConv,
> Backend64Bit<'a, 'r, GeneralReg, FloatReg, ASM, CC>
{
+ fn allocate_with_refcount(
+ &mut self,
+ dst: Symbol,
+ data_bytes: Symbol,
+ element_alignment: Symbol,
+ ) {
+ self.build_fn_call(
+ &dst,
+ bitcode::UTILS_ALLOCATE_WITH_REFCOUNT.to_string(),
+ &[data_bytes, element_alignment],
+ &[Layout::U64, Layout::U32],
+ &Layout::U64,
+ );
+ }
+
+ fn unbox_str_or_list(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
+ dst: Symbol,
+ ptr_reg: GeneralReg,
+ tmp_reg: GeneralReg,
+ ) {
+ let base_offset = storage_manager.claim_stack_area(&dst, 24);
+
+ ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 0);
+ ASM::mov_base32_reg64(buf, base_offset, tmp_reg);
+
+ ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 8);
+ ASM::mov_base32_reg64(buf, base_offset + 8, tmp_reg);
+
+ ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, 16);
+ ASM::mov_base32_reg64(buf, base_offset + 16, tmp_reg);
+ }
+
+ fn ptr_read(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
+ layout_interner: &STLayoutInterner<'a>,
+ ptr_reg: GeneralReg,
+ element_in_layout: InLayout<'a>,
+ dst: Symbol,
+ ) {
+ match layout_interner.get(element_in_layout) {
+ Layout::Builtin(builtin) => match builtin {
+ Builtin::Int(int_width) => match int_width {
+ IntWidth::I128 | IntWidth::U128 => {
+ // can we treat this as 2 u64's?
+ todo!()
+ }
+ IntWidth::I64 | IntWidth::U64 => {
+ let dst_reg = storage_manager.claim_general_reg(buf, &dst);
+ ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0);
+ }
+ IntWidth::I32 | IntWidth::U32 => {
+ let dst_reg = storage_manager.claim_general_reg(buf, &dst);
+ ASM::mov_reg32_mem32_offset32(buf, dst_reg, ptr_reg, 0);
+ }
+ IntWidth::I16 | IntWidth::U16 => {
+ let dst_reg = storage_manager.claim_general_reg(buf, &dst);
+ ASM::mov_reg16_mem16_offset32(buf, dst_reg, ptr_reg, 0);
+ }
+ IntWidth::I8 | IntWidth::U8 => {
+ let dst_reg = storage_manager.claim_general_reg(buf, &dst);
+ ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0);
+ }
+ },
+ Builtin::Float(_) => {
+ let dst_reg = storage_manager.claim_float_reg(buf, &dst);
+ ASM::mov_freg64_freg64(buf, dst_reg, CC::FLOAT_RETURN_REGS[0]);
+ }
+ Builtin::Bool => {
+ // the same as an 8-bit integer
+ let dst_reg = storage_manager.claim_general_reg(buf, &dst);
+ ASM::mov_reg8_mem8_offset32(buf, dst_reg, ptr_reg, 0);
+ }
+ Builtin::Decimal => {
+ // same as 128-bit integer
+ }
+ Builtin::Str | Builtin::List(_) => {
+ storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| {
+ Self::unbox_str_or_list(buf, storage_manager, dst, ptr_reg, tmp_reg);
+ });
+ }
+ },
+
+ Layout::Boxed(_) => {
+ // the same as 64-bit integer (for 64-bit targets)
+ let dst_reg = storage_manager.claim_general_reg(buf, &dst);
+ ASM::mov_reg64_mem64_offset32(buf, dst_reg, ptr_reg, 0);
+ }
+
+ _ => todo!("unboxing of {:?}", layout_interner.dbg(element_in_layout)),
+ }
+ }
+
+ fn ptr_write(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, GeneralReg, FloatReg, ASM, CC>,
+ ptr_reg: GeneralReg,
+ element_offset: i32,
+ element_width: u64,
+ element_layout: Layout<'a>,
+ value: Symbol,
+ ) {
+ match element_layout {
+ Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
+ let sym_reg = storage_manager.load_to_general_reg(buf, &value);
+ ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset, sym_reg);
+ }
+ Layout::Builtin(Builtin::Int(IntWidth::I32 | IntWidth::U32)) => {
+ let sym_reg = storage_manager.load_to_general_reg(buf, &value);
+ ASM::mov_mem32_offset32_reg32(buf, ptr_reg, element_offset, sym_reg);
+ }
+ Layout::Builtin(Builtin::Int(IntWidth::I16 | IntWidth::U16)) => {
+ let sym_reg = storage_manager.load_to_general_reg(buf, &value);
+ ASM::mov_mem16_offset32_reg16(buf, ptr_reg, element_offset, sym_reg);
+ }
+ Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8) | Builtin::Bool) => {
+ let sym_reg = storage_manager.load_to_general_reg(buf, &value);
+ ASM::mov_mem8_offset32_reg8(buf, ptr_reg, element_offset, sym_reg);
+ }
+ Layout::Builtin(Builtin::Float(FloatWidth::F64 | FloatWidth::F32)) => {
+ let sym_reg = storage_manager.load_to_float_reg(buf, &value);
+ ASM::movesd_mem64_offset32_freg64(buf, ptr_reg, element_offset, sym_reg);
+ }
+ _ if element_width == 0 => {}
+ _ if element_width > 8 => {
+ let (from_offset, size) = storage_manager.stack_offset_and_size(&value);
+ debug_assert!(from_offset % 8 == 0);
+ debug_assert!(size % 8 == 0);
+ debug_assert_eq!(size as u64, element_width);
+ storage_manager.with_tmp_general_reg(buf, |_storage_manager, buf, tmp_reg| {
+ // a crude memcpy
+ for i in (0..size as i32).step_by(8) {
+ ASM::mov_reg64_base32(buf, tmp_reg, from_offset + i);
+ ASM::mov_mem64_offset32_reg64(buf, ptr_reg, element_offset + i, tmp_reg);
+ }
+ });
+ }
+ x => todo!("copying data to list with layout, {:?}", x),
+ }
+ }
+
/// Updates a jump instruction to a new offset and returns the number of bytes written.
fn update_jmp_imm32_offset(
&mut self,
@@ -1908,6 +2757,24 @@ impl<
self.buf[jmp_location as usize + i] = *byte;
}
}
+
+ /// Loads the alignment bytes of `layout` into the given `symbol`
+ fn load_layout_alignment(&mut self, layout: InLayout<'a>, symbol: Symbol) {
+ let u32_layout = Layout::U32;
+ let alignment = self.layout_interner.alignment_bytes(layout);
+ let alignment_literal = Literal::Int((alignment as i128).to_ne_bytes());
+
+ self.load_literal(&symbol, &u32_layout, &alignment_literal);
+ }
+
+ /// Loads the stack size of `layout` into the given `symbol`
+ fn load_layout_stack_size(&mut self, layout: InLayout<'a>, symbol: Symbol) {
+ let u64_layout = Layout::U64;
+ let width = self.layout_interner.stack_size(layout);
+ let width_literal = Literal::Int((width as i128).to_ne_bytes());
+
+ self.load_literal(&symbol, &u64_layout, &width_literal);
+ }
}
#[macro_export]
diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs
index 4a3090b0dd..7e2eb7d4b4 100644
--- a/crates/compiler/gen_dev/src/generic64/storage.rs
+++ b/crates/compiler/gen_dev/src/generic64/storage.rs
@@ -9,7 +9,6 @@ use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::{
- borrow::Ownership,
ir::{JoinPointId, Param},
layout::{
Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
@@ -315,7 +314,7 @@ impl<
reg: Some(Float(_)),
..
}) => {
- internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym)
+ internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}")
}
Stack(Primitive {
reg: None,
@@ -350,8 +349,10 @@ impl<
self.free_reference(sym);
reg
}
- Stack(Complex { .. }) => {
- internal_error!("Cannot load large values into general registers: {}", sym)
+ Stack(Complex { size, .. }) => {
+ internal_error!(
+ "Cannot load large values (size {size}) into general registers: {sym:?}",
+ )
}
NoData => {
internal_error!("Cannot load no data into general registers: {}", sym)
@@ -448,7 +449,7 @@ impl<
reg: Some(Float(_)),
..
}) => {
- internal_error!("Cannot load floating point symbol into GeneralReg: {}", sym)
+ internal_error!("Cannot load floating point symbol into GeneralReg: {sym:?}",)
}
Stack(Primitive {
reg: None,
@@ -458,19 +459,25 @@ impl<
ASM::mov_reg64_base32(buf, reg, *base_offset);
}
Stack(ReferencedPrimitive {
- base_offset, size, ..
- }) if base_offset % 8 == 0 && *size == 8 => {
- // The primitive is aligned and the data is exactly 8 bytes, treat it like regular stack.
- ASM::mov_reg64_base32(buf, reg, *base_offset);
+ base_offset,
+ size,
+ sign_extend,
+ }) => {
+ debug_assert!(*size <= 8);
+
+ if *sign_extend {
+ ASM::movsx_reg64_base32(buf, reg, *base_offset, *size as u8)
+ } else {
+ ASM::movzx_reg64_base32(buf, reg, *base_offset, *size as u8)
+ }
}
- Stack(ReferencedPrimitive { .. }) => {
- todo!("loading referenced primitives")
- }
- Stack(Complex { .. }) => {
- internal_error!("Cannot load large values into general registers: {}", sym)
+ Stack(Complex { size, .. }) => {
+ internal_error!(
+ "Cannot load large values (size {size}) into general registers: {sym:?}",
+ )
}
NoData => {
- internal_error!("Cannot load no data into general registers: {}", sym)
+ internal_error!("Cannot load no data into general registers: {:?}", sym)
}
}
}
@@ -553,7 +560,7 @@ impl<
self.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert(
*sym,
- Stack(if is_primitive(layout) {
+ Stack(if is_primitive(layout_interner, layout) {
ReferencedPrimitive {
base_offset: data_offset,
size,
@@ -739,15 +746,73 @@ impl<
layout: &InLayout<'a>,
) {
match layout_interner.get(*layout) {
- Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
+ Layout::Builtin(builtin) => match builtin {
+ Builtin::Int(int_width) => match int_width {
+ IntWidth::I128 | IntWidth::U128 => {
+ let (from_offset, size) = self.stack_offset_and_size(sym);
+ debug_assert_eq!(from_offset % 8, 0);
+ debug_assert_eq!(size % 8, 0);
+ debug_assert_eq!(size, layout_interner.stack_size(*layout));
+ self.copy_to_stack_offset(buf, size, from_offset, to_offset)
+ }
+ IntWidth::I64 | IntWidth::U64 => {
+ debug_assert_eq!(to_offset % 8, 0);
+ let reg = self.load_to_general_reg(buf, sym);
+ ASM::mov_base32_reg64(buf, to_offset, reg);
+ }
+ IntWidth::I32 | IntWidth::U32 => {
+ debug_assert_eq!(to_offset % 4, 0);
+ let reg = self.load_to_general_reg(buf, sym);
+ ASM::mov_base32_reg32(buf, to_offset, reg);
+ }
+ IntWidth::I16 | IntWidth::U16 => {
+ debug_assert_eq!(to_offset % 2, 0);
+ let reg = self.load_to_general_reg(buf, sym);
+ ASM::mov_base32_reg16(buf, to_offset, reg);
+ }
+ IntWidth::I8 | IntWidth::U8 => {
+ let reg = self.load_to_general_reg(buf, sym);
+ ASM::mov_base32_reg8(buf, to_offset, reg);
+ }
+ },
+
+ Builtin::Float(float_width) => match float_width {
+ FloatWidth::F64 => {
+ debug_assert_eq!(to_offset % 8, 0);
+ let reg = self.load_to_float_reg(buf, sym);
+ ASM::mov_base32_freg64(buf, to_offset, reg);
+ }
+ FloatWidth::F32 => todo!(),
+ },
+ Builtin::Bool => {
+ // same as 8-bit integer
+ let reg = self.load_to_general_reg(buf, sym);
+ ASM::mov_base32_reg8(buf, to_offset, reg);
+ }
+ Builtin::Decimal => todo!(),
+ Builtin::Str | Builtin::List(_) => {
+ let (from_offset, size) = self.stack_offset_and_size(sym);
+ debug_assert_eq!(from_offset % 8, 0);
+ debug_assert_eq!(size % 8, 0);
+ debug_assert_eq!(size, layout_interner.stack_size(*layout));
+ self.copy_to_stack_offset(buf, size, from_offset, to_offset)
+ }
+ },
+ Layout::Boxed(_) => {
+ // like a 64-bit integer
debug_assert_eq!(to_offset % 8, 0);
let reg = self.load_to_general_reg(buf, sym);
ASM::mov_base32_reg64(buf, to_offset, reg);
}
- Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
- debug_assert_eq!(to_offset % 8, 0);
- let reg = self.load_to_float_reg(buf, sym);
- ASM::mov_base32_freg64(buf, to_offset, reg);
+ Layout::LambdaSet(lambda_set) => {
+ // like its runtime representation
+ self.copy_symbol_to_stack_offset(
+ layout_interner,
+ buf,
+ to_offset,
+ sym,
+ &lambda_set.runtime_representation(),
+ )
}
_ if layout_interner.stack_size(*layout) == 0 => {}
// TODO: Verify this is always true.
@@ -756,20 +821,64 @@ impl<
// Later, it will be reloaded and stored in refcounted as needed.
_ if layout_interner.stack_size(*layout) > 8 => {
let (from_offset, size) = self.stack_offset_and_size(sym);
- debug_assert!(from_offset % 8 == 0);
- debug_assert!(size % 8 == 0);
+ debug_assert_eq!(from_offset % 8, 0);
+ debug_assert_eq!(size % 8, 0);
debug_assert_eq!(size, layout_interner.stack_size(*layout));
- self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
- for i in (0..size as i32).step_by(8) {
- ASM::mov_reg64_base32(buf, reg, from_offset + i);
- ASM::mov_base32_reg64(buf, to_offset + i, reg);
- }
- });
+ self.copy_to_stack_offset(buf, size, from_offset, to_offset)
}
x => todo!("copying data to the stack with layout, {:?}", x),
}
}
+ pub fn copy_to_stack_offset(
+ &mut self,
+ buf: &mut Vec<'a, u8>,
+ size: u32,
+ from_offset: i32,
+ to_offset: i32,
+ ) {
+ let mut copied = 0;
+ let size = size as i32;
+
+ self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
+ if size - copied >= 8 {
+ for _ in (0..(size - copied)).step_by(8) {
+ ASM::mov_reg64_base32(buf, reg, from_offset + copied);
+ ASM::mov_base32_reg64(buf, to_offset + copied, reg);
+
+ copied += 8;
+ }
+ }
+
+ if size - copied >= 4 {
+ for _ in (0..(size - copied)).step_by(4) {
+ ASM::mov_reg32_base32(buf, reg, from_offset + copied);
+ ASM::mov_base32_reg32(buf, to_offset + copied, reg);
+
+ copied += 4;
+ }
+ }
+
+ if size - copied >= 2 {
+ for _ in (0..(size - copied)).step_by(2) {
+ ASM::mov_reg16_base32(buf, reg, from_offset + copied);
+ ASM::mov_base32_reg16(buf, to_offset + copied, reg);
+
+ copied += 2;
+ }
+ }
+
+ if size - copied >= 1 {
+ for _ in (0..(size - copied)).step_by(1) {
+ ASM::mov_reg8_base32(buf, reg, from_offset + copied);
+ ASM::mov_base32_reg8(buf, to_offset + copied, reg);
+
+ copied += 1;
+ }
+ }
+ });
+ }
+
#[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free.
pub fn ensure_reg_free(
@@ -928,7 +1037,7 @@ impl<
) => (*base_offset, *size),
storage => {
internal_error!(
- "Data not on the stack for sym ({}) with storage ({:?})",
+ "Data not on the stack for sym {:?} with storage {:?}",
sym,
storage
)
@@ -1008,15 +1117,10 @@ impl<
param_storage.reserve(params.len());
for Param {
symbol,
- ownership,
+ ownership: _,
layout,
} in params
{
- if *ownership == Ownership::Borrowed {
- // These probably need to be passed by pointer/reference?
- // Otherwise, we probably need to copy back to the param at the end of the joinpoint.
- todo!("joinpoints with borrowed parameters");
- }
// Claim a location for every join point parameter to be loaded at.
// Put everything on the stack for simplicity.
match *layout {
@@ -1331,6 +1435,15 @@ impl<
}
}
-fn is_primitive(layout: InLayout<'_>) -> bool {
- matches!(layout, single_register_layouts!())
+fn is_primitive(layout_interner: &mut STLayoutInterner<'_>, layout: InLayout<'_>) -> bool {
+ match layout {
+ single_register_layouts!() => true,
+ _ => match layout_interner.get(layout) {
+ Layout::Boxed(_) => true,
+ Layout::LambdaSet(lambda_set) => {
+ is_primitive(layout_interner, lambda_set.runtime_representation())
+ }
+ _ => false,
+ },
+ }
}
diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs
index c52d567dec..0f3902e951 100644
--- a/crates/compiler/gen_dev/src/generic64/x86_64.rs
+++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs
@@ -4,10 +4,13 @@ use crate::{
single_register_layouts, Relocation,
};
use bumpalo::collections::Vec;
+use roc_builtins::bitcode::FloatWidth;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{InLayout, Layout, LayoutInterner, STLayoutInterner};
+use super::CompareOperation;
+
// Not sure exactly how I want to represent registers.
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
@@ -261,46 +264,22 @@ impl CallConv for X86_64Syste
args: &'a [(InLayout<'a>, Symbol)],
ret_layout: &InLayout<'a>,
) {
- let mut arg_offset = Self::SHADOW_SPACE_SIZE as i32 + 16; // 16 is the size of the pushed return address and base pointer.
- let mut general_i = 0;
- let mut float_i = 0;
- if X86_64SystemV::returns_via_arg_pointer(layout_interner, ret_layout) {
- storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]);
- general_i += 1;
+ let returns_via_pointer =
+ X86_64SystemV::returns_via_arg_pointer(layout_interner, ret_layout);
+
+ let mut state = X64_64SystemVLoadArgs {
+ general_i: usize::from(returns_via_pointer),
+ float_i: 0,
+ // 16 is the size of the pushed return address and base pointer.
+ argument_offset: X86_64SystemV::SHADOW_SPACE_SIZE as i32 + 16,
+ };
+
+ if returns_via_pointer {
+ storage_manager.ret_pointer_arg(X86_64SystemV::GENERAL_PARAM_REGS[0]);
}
- for (layout, sym) in args.iter() {
- let stack_size = layout_interner.stack_size(*layout);
- match *layout {
- single_register_integers!() => {
- if general_i < Self::GENERAL_PARAM_REGS.len() {
- storage_manager.general_reg_arg(sym, Self::GENERAL_PARAM_REGS[general_i]);
- general_i += 1;
- } else {
- storage_manager.primitive_stack_arg(sym, arg_offset);
- arg_offset += 8;
- }
- }
- single_register_floats!() => {
- if float_i < Self::FLOAT_PARAM_REGS.len() {
- storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[float_i]);
- float_i += 1;
- } else {
- storage_manager.primitive_stack_arg(sym, arg_offset);
- arg_offset += 8;
- }
- }
- _ if stack_size == 0 => {
- storage_manager.no_data_arg(sym);
- }
- _ if stack_size > 16 => {
- // TODO: Double check this.
- storage_manager.complex_stack_arg(sym, arg_offset, stack_size);
- arg_offset += stack_size as i32;
- }
- x => {
- todo!("Loading args with layout {:?}", x);
- }
- }
+
+ for (in_layout, sym) in args.iter() {
+ state.load_arg(storage_manager, layout_interner, *sym, *in_layout);
}
}
@@ -321,9 +300,8 @@ impl CallConv for X86_64Syste
arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
) {
- let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut general_i = 0;
- let mut float_i = 0;
+
if Self::returns_via_arg_pointer(layout_interner, ret_layout) {
// Save space on the stack for the result we will be return.
let base_offset =
@@ -338,81 +316,18 @@ impl CallConv for X86_64Syste
base_offset,
);
}
- for (sym, layout) in args.iter().zip(arg_layouts.iter()) {
- match *layout {
- single_register_integers!() => {
- if general_i < Self::GENERAL_PARAM_REGS.len() {
- storage_manager.load_to_specified_general_reg(
- buf,
- sym,
- Self::GENERAL_PARAM_REGS[general_i],
- );
- general_i += 1;
- } else {
- // Copy to stack using return reg as buffer.
- storage_manager.load_to_specified_general_reg(
- buf,
- sym,
- Self::GENERAL_RETURN_REGS[0],
- );
- X86_64Assembler::mov_stack32_reg64(
- buf,
- tmp_stack_offset,
- Self::GENERAL_RETURN_REGS[0],
- );
- tmp_stack_offset += 8;
- }
- }
- single_register_floats!() => {
- if float_i < Self::FLOAT_PARAM_REGS.len() {
- storage_manager.load_to_specified_float_reg(
- buf,
- sym,
- Self::FLOAT_PARAM_REGS[float_i],
- );
- float_i += 1;
- } else {
- // Copy to stack using return reg as buffer.
- storage_manager.load_to_specified_float_reg(
- buf,
- sym,
- Self::FLOAT_RETURN_REGS[0],
- );
- X86_64Assembler::mov_stack32_freg64(
- buf,
- tmp_stack_offset,
- Self::FLOAT_RETURN_REGS[0],
- );
- tmp_stack_offset += 8;
- }
- }
- x if layout_interner.stack_size(x) == 0 => {}
- x if layout_interner.stack_size(x) > 16 => {
- // TODO: Double check this.
- // Just copy onto the stack.
- // Use return reg as buffer because it will be empty right now.
- let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
- debug_assert_eq!(base_offset % 8, 0);
- for i in (0..size as i32).step_by(8) {
- X86_64Assembler::mov_reg64_base32(
- buf,
- Self::GENERAL_RETURN_REGS[0],
- base_offset + i,
- );
- X86_64Assembler::mov_stack32_reg64(
- buf,
- tmp_stack_offset + i,
- Self::GENERAL_RETURN_REGS[0],
- );
- }
- tmp_stack_offset += size as i32;
- }
- x => {
- todo!("calling with arg type, {:?}", x);
- }
- }
+
+ let mut state = X64_64SystemVStoreArgs {
+ general_i,
+ float_i: 0,
+ tmp_stack_offset: Self::SHADOW_SPACE_SIZE as i32,
+ };
+
+ for (sym, in_layout) in args.iter().zip(arg_layouts.iter()) {
+ state.store_arg(buf, storage_manager, layout_interner, *sym, *in_layout);
}
- storage_manager.update_fn_call_stack_size(tmp_stack_offset as u32);
+
+ storage_manager.update_fn_call_stack_size(state.tmp_stack_offset as u32);
}
fn return_complex_symbol<'a, 'r>(
@@ -519,6 +434,225 @@ impl CallConv for X86_64Syste
}
}
+struct X64_64SystemVStoreArgs {
+ general_i: usize,
+ float_i: usize,
+ tmp_stack_offset: i32,
+}
+
+impl X64_64SystemVStoreArgs {
+ const GENERAL_PARAM_REGS: &'static [X86_64GeneralReg] = X86_64SystemV::GENERAL_PARAM_REGS;
+ const GENERAL_RETURN_REGS: &'static [X86_64GeneralReg] = X86_64SystemV::GENERAL_RETURN_REGS;
+
+ const FLOAT_PARAM_REGS: &'static [X86_64FloatReg] = X86_64SystemV::FLOAT_PARAM_REGS;
+ const FLOAT_RETURN_REGS: &'static [X86_64FloatReg] = X86_64SystemV::FLOAT_RETURN_REGS;
+
+ fn store_arg<'a, 'r>(
+ &mut self,
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>,
+ layout_interner: &mut STLayoutInterner<'a>,
+ sym: Symbol,
+ in_layout: InLayout<'a>,
+ ) {
+ match in_layout {
+ single_register_integers!() => self.store_arg_general(buf, storage_manager, sym),
+ single_register_floats!() => self.store_arg_float(buf, storage_manager, sym),
+ x if layout_interner.stack_size(x) == 0 => {}
+ x if layout_interner.stack_size(x) > 16 => {
+ // TODO: Double check this.
+ // Just copy onto the stack.
+ // Use return reg as buffer because it will be empty right now.
+ let (base_offset, size) = storage_manager.stack_offset_and_size(&sym);
+ debug_assert_eq!(base_offset % 8, 0);
+ for i in (0..size as i32).step_by(8) {
+ X86_64Assembler::mov_reg64_base32(
+ buf,
+ Self::GENERAL_RETURN_REGS[0],
+ base_offset + i,
+ );
+ X86_64Assembler::mov_stack32_reg64(
+ buf,
+ self.tmp_stack_offset + i,
+ Self::GENERAL_RETURN_REGS[0],
+ );
+ }
+ self.tmp_stack_offset += size as i32;
+ }
+ other => {
+ // look at the layout in more detail
+ match layout_interner.get(other) {
+ Layout::Boxed(_) => {
+ // treat boxed like a 64-bit integer
+ self.store_arg_general(buf, storage_manager, sym)
+ }
+ Layout::LambdaSet(lambda_set) => self.store_arg(
+ buf,
+ storage_manager,
+ layout_interner,
+ sym,
+ lambda_set.runtime_representation(),
+ ),
+ Layout::Struct { .. } => {
+ // for now, just also store this on the stack
+ let (base_offset, size) = storage_manager.stack_offset_and_size(&sym);
+ debug_assert_eq!(base_offset % 8, 0);
+ for i in (0..size as i32).step_by(8) {
+ X86_64Assembler::mov_reg64_base32(
+ buf,
+ Self::GENERAL_RETURN_REGS[0],
+ base_offset + i,
+ );
+ X86_64Assembler::mov_stack32_reg64(
+ buf,
+ self.tmp_stack_offset + i,
+ Self::GENERAL_RETURN_REGS[0],
+ );
+ }
+ self.tmp_stack_offset += size as i32;
+ }
+ _ => {
+ todo!("calling with arg type, {:?}", layout_interner.dbg(other));
+ }
+ }
+ }
+ }
+ }
+
+ fn store_arg_general<'a, 'r>(
+ &mut self,
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>,
+ sym: Symbol,
+ ) {
+ if self.general_i < Self::GENERAL_PARAM_REGS.len() {
+ storage_manager.load_to_specified_general_reg(
+ buf,
+ &sym,
+ Self::GENERAL_PARAM_REGS[self.general_i],
+ );
+ self.general_i += 1;
+ } else {
+ // Copy to stack using return reg as buffer.
+ storage_manager.load_to_specified_general_reg(buf, &sym, Self::GENERAL_RETURN_REGS[0]);
+ X86_64Assembler::mov_stack32_reg64(
+ buf,
+ self.tmp_stack_offset,
+ Self::GENERAL_RETURN_REGS[0],
+ );
+ self.tmp_stack_offset += 8;
+ }
+ }
+
+ fn store_arg_float<'a, 'r>(
+ &mut self,
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>,
+ sym: Symbol,
+ ) {
+ if self.float_i < Self::FLOAT_PARAM_REGS.len() {
+ storage_manager.load_to_specified_float_reg(
+ buf,
+ &sym,
+ Self::FLOAT_PARAM_REGS[self.float_i],
+ );
+ self.float_i += 1;
+ } else {
+ // Copy to stack using return reg as buffer.
+ storage_manager.load_to_specified_float_reg(buf, &sym, Self::FLOAT_RETURN_REGS[0]);
+ X86_64Assembler::mov_stack32_freg64(
+ buf,
+ self.tmp_stack_offset,
+ Self::FLOAT_RETURN_REGS[0],
+ );
+ self.tmp_stack_offset += 8;
+ }
+ }
+}
+
+struct X64_64SystemVLoadArgs {
+ general_i: usize,
+ float_i: usize,
+ argument_offset: i32,
+}
+
+type X86_64StorageManager<'a, 'r, CallConv> =
+ StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, X86_64Assembler, CallConv>;
+
+impl X64_64SystemVLoadArgs {
+ fn load_arg<'a, 'r>(
+ &mut self,
+ storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>,
+ layout_interner: &mut STLayoutInterner<'a>,
+ sym: Symbol,
+ in_layout: InLayout<'a>,
+ ) {
+ let stack_size = layout_interner.stack_size(in_layout);
+ match in_layout {
+ single_register_integers!() => self.load_arg_general(storage_manager, sym),
+ single_register_floats!() => self.load_arg_float(storage_manager, sym),
+ _ if stack_size == 0 => {
+ storage_manager.no_data_arg(&sym);
+ }
+ _ if stack_size > 16 => {
+ // TODO: Double check this.
+ storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size);
+ self.argument_offset += stack_size as i32;
+ }
+ other => match layout_interner.get(other) {
+ Layout::Boxed(_) => {
+ // boxed layouts are pointers, which we treat as 64-bit integers
+ self.load_arg_general(storage_manager, sym)
+ }
+ Layout::LambdaSet(lambda_set) => self.load_arg(
+ storage_manager,
+ layout_interner,
+ sym,
+ lambda_set.runtime_representation(),
+ ),
+ Layout::Struct { .. } => {
+ // for now, just also store this on the stack
+ storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size);
+ self.argument_offset += stack_size as i32;
+ }
+ _ => {
+ todo!("Loading args with layout {:?}", layout_interner.dbg(other));
+ }
+ },
+ }
+ }
+
+ fn load_arg_general<'a, 'r>(
+ &mut self,
+ storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>,
+ sym: Symbol,
+ ) {
+ if self.general_i < X86_64SystemV::GENERAL_PARAM_REGS.len() {
+ let reg = X86_64SystemV::GENERAL_PARAM_REGS[self.general_i];
+ storage_manager.general_reg_arg(&sym, reg);
+ self.general_i += 1;
+ } else {
+ storage_manager.primitive_stack_arg(&sym, self.argument_offset);
+ self.argument_offset += 8;
+ }
+ }
+
+ fn load_arg_float<'a, 'r>(
+ &mut self,
+ storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64SystemV>,
+ sym: Symbol,
+ ) {
+ if self.general_i < X86_64SystemV::GENERAL_PARAM_REGS.len() {
+ let reg = X86_64SystemV::FLOAT_PARAM_REGS[self.general_i];
+ storage_manager.float_reg_arg(&sym, reg);
+ self.float_i += 1;
+ } else {
+ storage_manager.primitive_stack_arg(&sym, self.argument_offset);
+ self.argument_offset += 8;
+ }
+ }
+}
+
impl X86_64SystemV {
fn returns_via_arg_pointer<'a>(
interner: &STLayoutInterner<'a>,
@@ -662,14 +796,7 @@ impl CallConv for X86_64Windo
#[inline(always)]
fn load_args<'a, 'r>(
_buf: &mut Vec<'a, u8>,
- storage_manager: &mut StorageManager<
- 'a,
- 'r,
- X86_64GeneralReg,
- X86_64FloatReg,
- X86_64Assembler,
- X86_64WindowsFastcall,
- >,
+ storage_manager: &mut X86_64StorageManager<'a, 'r, X86_64WindowsFastcall>,
layout_interner: &mut STLayoutInterner<'a>,
args: &'a [(InLayout<'a>, Symbol)],
ret_layout: &InLayout<'a>,
@@ -1228,18 +1355,55 @@ impl Assembler for X86_64Assembler {
fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) {
movsd_freg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
}
+
#[inline(always)]
fn mov_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
mov_reg64_base64_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
}
+ #[inline(always)]
+ fn mov_reg32_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
+ mov_reg32_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
+ }
+ #[inline(always)]
+ fn mov_reg16_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
+ mov_reg16_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
+ }
+ #[inline(always)]
+ fn mov_reg8_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32) {
+ mov_reg8_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset)
+ }
+
#[inline(always)]
fn mov_base32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64FloatReg) {
movsd_base64_offset32_freg64(buf, X86_64GeneralReg::RBP, offset, src)
}
+
+ #[inline(always)]
+ fn movesd_mem64_offset32_freg64(
+ buf: &mut Vec<'_, u8>,
+ ptr: X86_64GeneralReg,
+ offset: i32,
+ src: X86_64FloatReg,
+ ) {
+ movsd_base64_offset32_freg64(buf, ptr, offset, src)
+ }
+
#[inline(always)]
fn mov_base32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
mov_base64_offset32_reg64(buf, X86_64GeneralReg::RBP, offset, src)
}
+ #[inline(always)]
+ fn mov_base32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
+ mov_base32_offset32_reg32(buf, X86_64GeneralReg::RBP, offset, src)
+ }
+ #[inline(always)]
+ fn mov_base32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
+ mov_base16_offset32_reg16(buf, X86_64GeneralReg::RBP, offset, src)
+ }
+ #[inline(always)]
+ fn mov_base32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) {
+ mov_base8_offset32_reg8(buf, X86_64GeneralReg::RBP, offset, src)
+ }
#[inline(always)]
fn mov_reg64_mem64_offset32(
@@ -1250,6 +1414,32 @@ impl Assembler for X86_64Assembler {
) {
mov_reg64_base64_offset32(buf, dst, src, offset)
}
+ #[inline(always)]
+ fn mov_reg32_mem32_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ src: X86_64GeneralReg,
+ offset: i32,
+ ) {
+ mov_reg32_base32_offset32(buf, dst, src, offset)
+ }
+ fn mov_reg16_mem16_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ src: X86_64GeneralReg,
+ offset: i32,
+ ) {
+ mov_reg16_base16_offset32(buf, dst, src, offset)
+ }
+ fn mov_reg8_mem8_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ src: X86_64GeneralReg,
+ offset: i32,
+ ) {
+ mov_reg8_base8_offset32(buf, dst, src, offset)
+ }
+
#[inline(always)]
fn mov_mem64_offset32_reg64(
buf: &mut Vec<'_, u8>,
@@ -1260,12 +1450,44 @@ impl Assembler for X86_64Assembler {
mov_base64_offset32_reg64(buf, dst, offset, src)
}
+ #[inline(always)]
+ fn mov_mem32_offset32_reg32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ offset: i32,
+ src: X86_64GeneralReg,
+ ) {
+ mov_base32_offset32_reg32(buf, dst, offset, src)
+ }
+
+ #[inline(always)]
+ fn mov_mem16_offset32_reg16(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ offset: i32,
+ src: X86_64GeneralReg,
+ ) {
+ mov_base16_offset32_reg16(buf, dst, offset, src)
+ }
+
+ #[inline(always)]
+ fn mov_mem8_offset32_reg8(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ offset: i32,
+ src: X86_64GeneralReg,
+ ) {
+ mov_base8_offset32_reg8(buf, dst, offset, src)
+ }
+
#[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8);
match size {
8 => Self::mov_reg64_base32(buf, dst, offset),
- 4 | 2 | 1 => todo!("sign extending {size} byte values"),
+ 4 => movsx_reg64_base32_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
+ 2 => movsx_reg64_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
+ 1 => movsx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
_ => internal_error!("Invalid size for sign extension: {size}"),
}
}
@@ -1274,7 +1496,12 @@ impl Assembler for X86_64Assembler {
debug_assert!(size <= 8);
match size {
8 => Self::mov_reg64_base32(buf, dst, offset),
- 4 | 2 => todo!("zero extending {size} byte values"),
+ 4 => {
+ // The Intel documentation (3.4.1.1 General-Purpose Registers in 64-Bit Mode in manual Basic Architecture))
+ // 32-bit operands generate a 32-bit result, zero-extended to a 64-bit result in the destination general-purpose register.
+ Self::mov_reg64_base32(buf, dst, offset)
+ }
+ 2 => movzx_reg64_base16_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
1 => movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
_ => internal_error!("Invalid size for zero extension: {size}"),
}
@@ -1368,6 +1595,33 @@ impl Assembler for X86_64Assembler {
setb_reg64(buf, dst);
}
+ #[inline(always)]
+ fn cmp_freg_freg_reg64(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ src1: X86_64FloatReg,
+ src2: X86_64FloatReg,
+ width: FloatWidth,
+ operation: CompareOperation,
+ ) {
+ use CompareOperation::*;
+
+ let (arg1, arg2) = match operation {
+ LessThan | LessThanOrEqual => (src1, src2),
+ GreaterThan | GreaterThanOrEqual => (src2, src1),
+ };
+
+ match width {
+ FloatWidth::F32 => cmp_freg32_freg32(buf, arg2, arg1),
+ FloatWidth::F64 => cmp_freg64_freg64(buf, arg2, arg1),
+ }
+
+ match operation {
+ LessThan | GreaterThan => seta_reg64(buf, dst),
+ LessThanOrEqual | GreaterThanOrEqual => setae_reg64(buf, dst),
+ };
+ }
+
#[inline(always)]
fn igt_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
@@ -1452,6 +1706,91 @@ impl Assembler for X86_64Assembler {
fn xor_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
binop_move_src_to_dst_reg64(buf, xor_reg64_reg64, dst, src1, src2)
}
+
+ fn shl_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
+ dst: X86_64GeneralReg,
+ src1: X86_64GeneralReg,
+ src2: X86_64GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv,
+ {
+ shift_reg64_reg64_reg64(buf, storage_manager, shl_reg64_reg64, dst, src1, src2)
+ }
+
+ fn shr_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
+ dst: X86_64GeneralReg,
+ src1: X86_64GeneralReg,
+ src2: X86_64GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv,
+ {
+ shift_reg64_reg64_reg64(buf, storage_manager, shr_reg64_reg64, dst, src1, src2)
+ }
+
+ fn sar_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
+ dst: X86_64GeneralReg,
+ src1: X86_64GeneralReg,
+ src2: X86_64GeneralReg,
+ ) where
+ ASM: Assembler,
+ CC: CallConv,
+ {
+ shift_reg64_reg64_reg64(buf, storage_manager, sar_reg64_reg64, dst, src1, src2)
+ }
+
+ fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
+ sqrtsd_freg64_freg64(buf, dst, src)
+ }
+
+ fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
+ sqrtss_freg32_freg32(buf, dst, src)
+ }
+}
+
+fn shift_reg64_reg64_reg64<'a, 'r, ASM, CC>(
+ buf: &mut Vec<'a, u8>,
+ storage_manager: &mut StorageManager<'a, 'r, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
+ shift_function: fn(buf: &mut Vec<'_, u8>, X86_64GeneralReg),
+ dst: X86_64GeneralReg,
+ src1: X86_64GeneralReg,
+ src2: X86_64GeneralReg,
+) where
+ ASM: Assembler,
+ CC: CallConv,
+{
+ macro_rules! helper {
+ ($buf:expr, $dst:expr, $src1:expr, $src2:expr) => {{
+ mov_reg64_reg64($buf, $dst, $src1);
+ mov_reg64_reg64($buf, X86_64GeneralReg::RCX, $src2);
+
+ shift_function($buf, $dst)
+ }};
+ }
+
+ // if RCX is one of our input registers, we need to move some stuff around
+ if let X86_64GeneralReg::RCX = dst {
+ storage_manager.with_tmp_general_reg(buf, |_, buf, tmp| {
+ helper!(buf, tmp, src1, src2);
+
+ mov_reg64_reg64(buf, dst, tmp);
+ })
+ } else if let X86_64GeneralReg::RCX = src2 {
+ storage_manager.with_tmp_general_reg(buf, |_, buf, tmp| {
+ mov_reg64_reg64(buf, tmp, src2);
+
+ helper!(buf, dst, src1, tmp);
+ })
+ } else {
+ helper!(buf, dst, src1, src2)
+ }
}
impl X86_64Assembler {
@@ -1465,12 +1804,16 @@ impl X86_64Assembler {
push_reg64(buf, reg);
}
}
+
+const GRP_4: u8 = 0x66;
+
const REX: u8 = 0x40;
// see https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding
/// If set, 64-bit operand size is used
const REX_PREFIX_W: u8 = 0b1000;
/// Extension to the MODRM.reg
+/// Permits access to additional registers
const REX_PREFIX_R: u8 = 0b0100;
#[allow(unused)]
/// Extension to the SIB.index field
@@ -1478,7 +1821,7 @@ const REX_PREFIX_X: u8 = 0b0010;
/// Extension to the MODRM.rm
const REX_PREFIX_B: u8 = 0b0001;
-/// Wide REX
+/// Wide REX (64-bit)
const REX_W: u8 = REX | REX_PREFIX_W;
#[inline(always)]
@@ -1576,6 +1919,36 @@ fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene
binop_reg64_reg64(0x33, buf, src, dst);
}
+/// `SHL r/m64, CL` -> Multiply r/m64 by 2, CL times.
+#[inline(always)]
+fn shl_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
+ let rex = add_rm_extension(dst, REX_W);
+ let rex = add_reg_extension(dst, rex);
+
+ let dst_mod = dst as u8 % 8;
+ buf.extend([rex, 0xD3, 0xC0 | (4 << 3) | dst_mod]);
+}
+
+/// `SHR r/m64, CL` -> Unsigned divide r/m64 by 2, CL times.
+#[inline(always)]
+fn shr_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
+ let rex = add_rm_extension(dst, REX_W);
+ let rex = add_reg_extension(dst, rex);
+
+ let dst_mod = dst as u8 % 8;
+ buf.extend([rex, 0xD3, 0xC0 | (5 << 3) | dst_mod]);
+}
+
+/// `SAR r/m64, CL` -> Signed divide r/m64 by 2, CL times.
+#[inline(always)]
+fn sar_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
+ let rex = add_rm_extension(dst, REX_W);
+ let rex = add_reg_extension(dst, rex);
+
+ let dst_mod = dst as u8 % 8;
+ buf.extend([rex, 0xD3, 0xC0 | (7 << 3) | dst_mod]);
+}
+
/// `ADDSD xmm1,xmm2/m64` -> Add the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
@@ -1750,6 +2123,90 @@ fn cmp_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene
binop_reg64_reg64(0x39, buf, dst, src);
}
+#[inline(always)]
+fn cmp_freg64_freg64(buf: &mut Vec<'_, u8>, src1: X86_64FloatReg, src2: X86_64FloatReg) {
+ let src1_high = src1 as u8 > 7;
+ let src1_mod = src1 as u8 % 8;
+
+ let src2_high = src2 as u8 > 7;
+ let src2_mod = src2 as u8 % 8;
+
+ if src1_high || src2_high {
+ buf.extend([
+ 0x66,
+ 0x40 | ((src1_high as u8) << 2) | (src2_high as u8),
+ 0x0F,
+ 0x2E,
+ 0xC0 | (src1_mod << 3) | (src2_mod),
+ ])
+ } else {
+ buf.extend([0x66, 0x0F, 0x2E, 0xC0 | (src1_mod << 3) | (src2_mod)])
+ }
+}
+
+#[inline(always)]
+fn cmp_freg32_freg32(buf: &mut Vec<'_, u8>, src1: X86_64FloatReg, src2: X86_64FloatReg) {
+ let src1_high = src1 as u8 > 7;
+ let src1_mod = src1 as u8 % 8;
+
+ let src2_high = src2 as u8 > 7;
+ let src2_mod = src2 as u8 % 8;
+
+ if src1_high || src2_high {
+ buf.extend([
+ 0x65,
+ 0x40 | ((src1_high as u8) << 2) | (src2_high as u8),
+ 0x0F,
+ 0x2E,
+ 0xC0 | (src1_mod << 3) | (src2_mod),
+ ])
+ } else {
+ buf.extend([0x65, 0x0F, 0x2E, 0xC0 | (src1_mod << 3) | (src2_mod)])
+ }
+}
+
+#[inline(always)]
+fn sqrtsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
+ let dst_high = dst as u8 > 7;
+ let dst_mod = dst as u8 % 8;
+
+ let src_high = src as u8 > 7;
+ let src_mod = src as u8 % 8;
+
+ if dst_high || src_high {
+ buf.extend([
+ 0xF2,
+ 0x40 | ((dst_high as u8) << 2) | (src_high as u8),
+ 0x0F,
+ 0x51,
+ 0xC0 | (dst_mod << 3) | (src_mod),
+ ])
+ } else {
+ buf.extend([0xF2, 0x0F, 0x51, 0xC0 | (dst_mod << 3) | (src_mod)])
+ }
+}
+
+#[inline(always)]
+fn sqrtss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
+ let dst_high = dst as u8 > 7;
+ let dst_mod = dst as u8 % 8;
+
+ let src_high = src as u8 > 7;
+ let src_mod = src as u8 % 8;
+
+ if dst_high || src_high {
+ buf.extend([
+ 0xF3,
+ 0x40 | ((dst_high as u8) << 2) | (src_high as u8),
+ 0x0F,
+ 0x51,
+ 0xC0 | (dst_mod << 3) | (src_mod),
+ ])
+ } else {
+ buf.extend([0xF3, 0x0F, 0x51, 0xC0 | (dst_mod << 3) | (src_mod)])
+ }
+}
+
/// `TEST r/m64,r64` -> AND r64 with r/m64; set SF, ZF, PF according to result.
#[allow(dead_code)]
#[inline(always)]
@@ -1899,6 +2356,117 @@ fn mov_base64_offset32_reg64(
buf.extend(offset.to_le_bytes());
}
+/// `MOV r/m32,r32` -> Move r32 to r/m32, where m32 references a base + offset.
+#[inline(always)]
+fn mov_base32_offset32_reg32(
+ buf: &mut Vec<'_, u8>,
+ base: X86_64GeneralReg,
+ offset: i32,
+ src: X86_64GeneralReg,
+) {
+ let rex = add_rm_extension(base, REX);
+ let rex = add_reg_extension(src, rex);
+ let src_mod = (src as u8 % 8) << 3;
+ let base_mod = base as u8 % 8;
+ buf.reserve(8);
+ buf.extend([rex, 0x89, 0x80 | src_mod | base_mod]);
+ // Using RSP or R12 requires a secondary index byte.
+ if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
+ buf.push(0x24);
+ }
+ buf.extend(offset.to_le_bytes());
+}
+
+/// `MOV r/m16,r16` -> Move r16 to r/m16, where m16 references a base + offset.
+#[inline(always)]
+fn mov_base16_offset32_reg16(
+ buf: &mut Vec<'_, u8>,
+ base: X86_64GeneralReg,
+ offset: i32,
+ src: X86_64GeneralReg,
+) {
+ let rex = add_rm_extension(base, REX);
+ let rex = add_reg_extension(src, rex);
+ let src_mod = (src as u8 % 8) << 3;
+ let base_mod = base as u8 % 8;
+ buf.reserve(8);
+ buf.extend([GRP_4, rex, 0x89, 0x80 | src_mod | base_mod]);
+ // Using RSP or R12 requires a secondary index byte.
+ if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
+ buf.push(0x24);
+ }
+ buf.extend(offset.to_le_bytes());
+}
+
+/// `MOV r/m8,r8` -> Move r8 to r/m8, where m8 references a base + offset.
+#[inline(always)]
+fn mov_base8_offset32_reg8(
+ buf: &mut Vec<'_, u8>,
+ base: X86_64GeneralReg,
+ offset: i32,
+ src: X86_64GeneralReg,
+) {
+ let rex = add_rm_extension(base, REX);
+ let rex = add_reg_extension(src, rex);
+ let src_mod = (src as u8 % 8) << 3;
+ let base_mod = base as u8 % 8;
+ buf.reserve(8);
+ buf.extend([rex, 0x88, 0x80 | src_mod | base_mod]);
+ // Using RSP or R12 requires a secondary index byte.
+ if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
+ buf.push(0x24);
+ }
+ buf.extend(offset.to_le_bytes());
+}
+
+enum RegisterWidth {
+ W8,
+ W16,
+ W32,
+ W64,
+}
+
+#[inline(always)]
+fn mov_reg_base_offset32(
+ buf: &mut Vec<'_, u8>,
+ register_width: RegisterWidth,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ use RegisterWidth::*;
+
+ let rex = match register_width {
+ W64 => REX_W,
+ _ => REX,
+ };
+
+ let rex = add_rm_extension(base, rex);
+ let rex = add_reg_extension(dst, rex);
+
+ let dst_mod = (dst as u8 % 8) << 3;
+ let base_mod = base as u8 % 8;
+ let operands = 0x80 | dst_mod | base_mod;
+
+ buf.reserve(8);
+
+ let instruction = match register_width {
+ W8 => 0x8A,
+ W16 | W32 | W64 => 0x8B,
+ };
+
+ match register_width {
+ W16 => buf.extend([GRP_4, rex, instruction, operands]),
+ _ => buf.extend([rex, instruction, operands]),
+ };
+
+ // Using RSP or R12 requires a secondary index byte.
+ if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
+ buf.push(0x24);
+ }
+ buf.extend(offset.to_le_bytes());
+}
+
/// `MOV r64,r/m64` -> Move r/m64 to r64, where m64 references a base + offset.
#[inline(always)]
fn mov_reg64_base64_offset32(
@@ -1906,13 +2474,116 @@ fn mov_reg64_base64_offset32(
dst: X86_64GeneralReg,
base: X86_64GeneralReg,
offset: i32,
+) {
+ mov_reg_base_offset32(buf, RegisterWidth::W64, dst, base, offset)
+}
+
+/// `MOV r/m32,r32` -> Move r32 to r/m32.
+#[inline(always)]
+fn mov_reg32_base32_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ mov_reg_base_offset32(buf, RegisterWidth::W32, dst, base, offset)
+}
+
+/// `MOV r/m16,r16` -> Move r16 to r/m16.
+#[inline(always)]
+fn mov_reg16_base16_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ mov_reg_base_offset32(buf, RegisterWidth::W16, dst, base, offset)
+}
+
+/// `MOV r/m8,r8` -> Move r8 to r/m8.
+#[inline(always)]
+fn mov_reg8_base8_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ mov_reg_base_offset32(buf, RegisterWidth::W8, dst, base, offset)
+}
+
+#[inline(always)]
+fn movsx_reg64_base_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+ opcode: &[u8],
) {
let rex = add_rm_extension(base, REX_W);
let rex = add_reg_extension(dst, rex);
let dst_mod = (dst as u8 % 8) << 3;
let base_mod = base as u8 % 8;
- buf.reserve(8);
- buf.extend([rex, 0x8B, 0x80 | dst_mod | base_mod]);
+ buf.reserve(9);
+
+ // our output is a 64-bit value, so rex is always needed
+ buf.push(rex);
+ buf.extend(opcode);
+ buf.push(0x80 | dst_mod | base_mod);
+
+ // Using RSP or R12 requires a secondary index byte.
+ if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
+ buf.push(0x24);
+ }
+ buf.extend(offset.to_le_bytes());
+}
+
+/// `MOVSX r64,r/m32` -> Move r/m32 with sign extention to r64, where m32 references a base + offset.
+#[inline(always)]
+fn movsx_reg64_base32_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ movsx_reg64_base_offset32(buf, dst, base, offset, &[0x63])
+}
+
+/// `MOVSX r64,r/m16` -> Move r/m16 with sign extention to r64, where m16 references a base + offset.
+#[inline(always)]
+fn movsx_reg64_base16_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ movsx_reg64_base_offset32(buf, dst, base, offset, &[0x0F, 0xBF])
+}
+
+/// `MOVSX r64,r/m8` -> Move r/m8 with sign extention to r64, where m8 references a base + offset.
+#[inline(always)]
+fn movsx_reg64_base8_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ movsx_reg64_base_offset32(buf, dst, base, offset, &[0x0F, 0xBE])
+}
+
+#[inline(always)]
+fn movzx_reg64_base_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+ opcode: u8,
+) {
+ let rex = add_rm_extension(base, REX_W);
+ let rex = add_reg_extension(dst, rex);
+ let dst_mod = (dst as u8 % 8) << 3;
+ let base_mod = base as u8 % 8;
+ buf.reserve(9);
+ buf.extend([rex, 0x0F, opcode, 0x80 | dst_mod | base_mod]);
// Using RSP or R12 requires a secondary index byte.
if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
buf.push(0x24);
@@ -1928,17 +2599,18 @@ fn movzx_reg64_base8_offset32(
base: X86_64GeneralReg,
offset: i32,
) {
- let rex = add_rm_extension(base, REX_W);
- let rex = add_reg_extension(dst, rex);
- let dst_mod = (dst as u8 % 8) << 3;
- let base_mod = base as u8 % 8;
- buf.reserve(9);
- buf.extend([rex, 0x0F, 0xB6, 0x80 | dst_mod | base_mod]);
- // Using RSP or R12 requires a secondary index byte.
- if base == X86_64GeneralReg::RSP || base == X86_64GeneralReg::R12 {
- buf.push(0x24);
- }
- buf.extend(offset.to_le_bytes());
+ movzx_reg64_base_offset32(buf, dst, base, offset, 0xB6)
+}
+
+/// `MOVZX r64,r/m16` -> Move r/m16 with zero extention to r64, where m16 references a base + offset.
+#[inline(always)]
+fn movzx_reg64_base16_offset32(
+ buf: &mut Vec<'_, u8>,
+ dst: X86_64GeneralReg,
+ base: X86_64GeneralReg,
+ offset: i32,
+) {
+ movzx_reg64_base_offset32(buf, dst, base, offset, 0xB7)
}
/// `MOVSD xmm1,xmm2` -> Move scalar double-precision floating-point value from xmm2 to xmm1 register.
@@ -2207,6 +2879,12 @@ fn seta_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
set_reg64_help(0x97, buf, reg);
}
+/// `SETAE r/m64` -> Set byte if above or equal (CF=0).
+#[inline(always)]
+fn setae_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
+ set_reg64_help(0x93, buf, reg);
+}
+
/// `SETLE r/m64` -> Set byte if less or equal (ZF=1 or SF≠ OF).
#[inline(always)]
fn setle_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
@@ -2281,6 +2959,50 @@ mod tests {
use capstone::prelude::*;
impl X86_64GeneralReg {
+ #[allow(dead_code)]
+ fn low_32bits_string(&self) -> &str {
+ match self {
+ X86_64GeneralReg::RAX => "eax",
+ X86_64GeneralReg::RBX => "ebx",
+ X86_64GeneralReg::RCX => "ecx",
+ X86_64GeneralReg::RDX => "edx",
+ X86_64GeneralReg::RBP => "ebp",
+ X86_64GeneralReg::RSP => "esp",
+ X86_64GeneralReg::RDI => "edi",
+ X86_64GeneralReg::RSI => "esi",
+ X86_64GeneralReg::R8 => "r8d",
+ X86_64GeneralReg::R9 => "r9d",
+ X86_64GeneralReg::R10 => "r10d",
+ X86_64GeneralReg::R11 => "r11d",
+ X86_64GeneralReg::R12 => "r12d",
+ X86_64GeneralReg::R13 => "r13d",
+ X86_64GeneralReg::R14 => "r14d",
+ X86_64GeneralReg::R15 => "r15d",
+ }
+ }
+
+ #[allow(dead_code)]
+ fn low_16bits_string(&self) -> &str {
+ match self {
+ X86_64GeneralReg::RAX => "ax",
+ X86_64GeneralReg::RBX => "bx",
+ X86_64GeneralReg::RCX => "cx",
+ X86_64GeneralReg::RDX => "dx",
+ X86_64GeneralReg::RBP => "bp",
+ X86_64GeneralReg::RSP => "sp",
+ X86_64GeneralReg::RDI => "di",
+ X86_64GeneralReg::RSI => "si",
+ X86_64GeneralReg::R8 => "r8w",
+ X86_64GeneralReg::R9 => "r9w",
+ X86_64GeneralReg::R10 => "r10w",
+ X86_64GeneralReg::R11 => "r11w",
+ X86_64GeneralReg::R12 => "r12w",
+ X86_64GeneralReg::R13 => "r13w",
+ X86_64GeneralReg::R14 => "r14w",
+ X86_64GeneralReg::R15 => "r15w",
+ }
+ }
+
#[allow(dead_code)]
fn low_8bits_string(&self) -> &str {
match self {
@@ -2447,6 +3169,33 @@ mod tests {
);
}
+ #[test]
+ fn test_shl_reg64_reg64() {
+ disassembler_test!(
+ shl_reg64_reg64,
+ |reg| format!("shl {reg}, cl"),
+ ALL_GENERAL_REGS
+ );
+ }
+
+ #[test]
+ fn test_shr_reg64_reg64() {
+ disassembler_test!(
+ shr_reg64_reg64,
+ |reg| format!("shr {reg}, cl"),
+ ALL_GENERAL_REGS
+ );
+ }
+
+ #[test]
+ fn test_sar_reg64_reg64() {
+ disassembler_test!(
+ sar_reg64_reg64,
+ |reg| format!("sar {reg}, cl"),
+ ALL_GENERAL_REGS
+ );
+ }
+
#[test]
fn test_cmovl_reg64_reg64() {
disassembler_test!(
@@ -2633,6 +3382,54 @@ mod tests {
);
}
+ #[test]
+ fn test_mov_reg32_base32_offset32() {
+ disassembler_test!(
+ mov_reg32_base32_offset32,
+ |reg1, reg2, imm| format!(
+ "mov {}, dword ptr [{} + 0x{:x}]",
+ X86_64GeneralReg::low_32bits_string(®1),
+ reg2,
+ imm
+ ),
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ [TEST_I32]
+ );
+ }
+
+ #[test]
+ fn test_mov_reg16_base16_offset32() {
+ disassembler_test!(
+ mov_reg16_base16_offset32,
+ |reg1, reg2, imm| format!(
+ "mov {}, word ptr [{} + 0x{:x}]",
+ X86_64GeneralReg::low_16bits_string(®1),
+ reg2,
+ imm
+ ),
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ [TEST_I32]
+ );
+ }
+
+ #[test]
+ fn test_mov_reg8_base8_offset32() {
+ disassembler_test!(
+ mov_reg8_base8_offset32,
+ |reg1, reg2, imm| format!(
+ "mov {}, byte ptr [{} + 0x{:x}]",
+ X86_64GeneralReg::low_8bits_string(®1),
+ reg2,
+ imm
+ ),
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ [TEST_I32]
+ );
+ }
+
#[test]
fn test_mov_base64_offset32_reg64() {
disassembler_test!(
@@ -2644,6 +3441,98 @@ mod tests {
);
}
+ #[test]
+ fn test_mov_base32_offset32_reg32() {
+ disassembler_test!(
+ mov_base32_offset32_reg32,
+ |reg1, imm, reg2| format!(
+ "mov dword ptr [{} + 0x{:x}], {}",
+ reg1,
+ imm,
+ X86_64GeneralReg::low_32bits_string(®2),
+ ),
+ ALL_GENERAL_REGS,
+ [TEST_I32],
+ ALL_GENERAL_REGS
+ );
+ }
+
+ #[test]
+ fn test_mov_base16_offset32_reg16() {
+ disassembler_test!(
+ mov_base16_offset32_reg16,
+ |reg1, imm, reg2| format!(
+ "mov word ptr [{} + 0x{:x}], {}",
+ reg1,
+ imm,
+ X86_64GeneralReg::low_16bits_string(®2),
+ ),
+ ALL_GENERAL_REGS,
+ [TEST_I32],
+ ALL_GENERAL_REGS
+ );
+ }
+
+ #[test]
+ fn test_mov_base8_offset32_reg8() {
+ disassembler_test!(
+ mov_base8_offset32_reg8,
+ |reg1, imm, reg2| format!(
+ "mov byte ptr [{} + 0x{:x}], {}",
+ reg1,
+ imm,
+ X86_64GeneralReg::low_8bits_string(®2),
+ ),
+ ALL_GENERAL_REGS,
+ [TEST_I32],
+ ALL_GENERAL_REGS
+ );
+ }
+
+ #[test]
+ fn test_movsx_reg64_base32_offset32() {
+ disassembler_test!(
+ movsx_reg64_base32_offset32,
+ |reg1, reg2, imm| format!("movsxd {}, dword ptr [{} + 0x{:x}]", reg1, reg2, imm),
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ [TEST_I32]
+ );
+ }
+
+ #[test]
+ fn test_movsx_reg64_base16_offset32() {
+ disassembler_test!(
+ movsx_reg64_base16_offset32,
+ |reg1, reg2, imm| format!("movsx {}, word ptr [{} + 0x{:x}]", reg1, reg2, imm),
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ [TEST_I32]
+ );
+ }
+
+ #[test]
+ fn test_movsx_reg64_base8_offset32() {
+ disassembler_test!(
+ movsx_reg64_base8_offset32,
+ |reg1, reg2, imm| format!("movsx {}, byte ptr [{} + 0x{:x}]", reg1, reg2, imm),
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ [TEST_I32]
+ );
+ }
+
+ #[test]
+ fn test_movzx_reg64_base16_offset32() {
+ disassembler_test!(
+ movzx_reg64_base16_offset32,
+ |reg1, reg2, imm| format!("movzx {}, word ptr [{} + 0x{:x}]", reg1, reg2, imm),
+ ALL_GENERAL_REGS,
+ ALL_GENERAL_REGS,
+ [TEST_I32]
+ );
+ }
+
#[test]
fn test_movzx_reg64_base8_offset32() {
disassembler_test!(
@@ -2762,4 +3651,24 @@ mod tests {
fn test_push_reg64() {
disassembler_test!(push_reg64, |reg| format!("push {}", reg), ALL_GENERAL_REGS);
}
+
+ #[test]
+ fn test_sqrt_freg64_freg64() {
+ disassembler_test!(
+ sqrtsd_freg64_freg64,
+ |dst, src| format!("sqrtsd {dst}, {src}"),
+ ALL_FLOAT_REGS,
+ ALL_FLOAT_REGS
+ );
+ }
+
+ #[test]
+ fn test_sqrt_freg32_freg32() {
+ disassembler_test!(
+ sqrtss_freg32_freg32,
+ |dst, src| format!("sqrtss {dst}, {src}"),
+ ALL_FLOAT_REGS,
+ ALL_FLOAT_REGS
+ );
+ }
}
diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs
index 6c15df866b..1aba223c90 100644
--- a/crates/compiler/gen_dev/src/lib.rs
+++ b/crates/compiler/gen_dev/src/lib.rs
@@ -21,6 +21,7 @@ use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutId, LayoutIds, LayoutInterner, STLayoutInterner, TagIdIntType,
UnionLayout,
};
+use roc_mono::list_element_layout;
mod generic64;
mod object_builder;
@@ -283,28 +284,31 @@ trait Backend<'a> {
if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
LowLevelWrapperType::from_symbol(func_sym.name())
{
- self.build_run_low_level(
+ return self.build_run_low_level(
sym,
&lowlevel,
arguments,
arg_layouts,
ret_layout,
- )
- } else if self.defined_in_app_module(func_sym.name()) {
- let layout_id = LayoutIds::default().get(func_sym.name(), layout);
- let fn_name = self.symbol_to_string(func_sym.name(), layout_id);
- // Now that the arguments are needed, load them if they are literals.
- self.load_literal_symbols(arguments);
- self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
- } else {
- self.build_builtin(
+ );
+ } else if sym.is_builtin() {
+ // These builtins can be built through `build_fn_call` as well, but the
+ // implementation in `build_builtin` inlines some of the symbols.
+ return self.build_builtin(
sym,
func_sym.name(),
arguments,
arg_layouts,
ret_layout,
- )
+ );
}
+
+ let layout_id = LayoutIds::default().get(func_sym.name(), layout);
+ let fn_name = self.symbol_to_string(func_sym.name(), layout_id);
+
+ // Now that the arguments are needed, load them if they are literals.
+ self.load_literal_symbols(arguments);
+ self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
}
CallType::LowLevel { op: lowlevel, .. } => {
@@ -380,6 +384,21 @@ trait Backend<'a> {
self.load_literal_symbols(arguments);
self.tag(sym, arguments, tag_layout, *tag_id);
}
+ Expr::ExprBox { symbol: value } => {
+ let element_layout = match self.interner().get(*layout) {
+ Layout::Boxed(boxed) => boxed,
+ _ => unreachable!("{:?}", self.interner().dbg(*layout)),
+ };
+
+ self.load_literal_symbols([*value].as_slice());
+ self.expr_box(*sym, *value, element_layout)
+ }
+ Expr::ExprUnbox { symbol: ptr } => {
+ let element_layout = *layout;
+
+ self.load_literal_symbols([*ptr].as_slice());
+ self.expr_unbox(*sym, *ptr, element_layout)
+ }
x => todo!("the expression, {:?}", x),
}
}
@@ -428,6 +447,9 @@ trait Backend<'a> {
LowLevel::NumAddChecked => {
self.build_num_add_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
}
+ LowLevel::NumSubChecked => {
+ self.build_num_sub_checked(sym, &args[0], &args[1], &arg_layouts[0], ret_layout)
+ }
LowLevel::NumAcos => self.build_fn_call(
sym,
bitcode::NUM_ACOS[FloatWidth::F64].to_string(),
@@ -532,6 +554,27 @@ trait Backend<'a> {
);
self.build_num_sub_wrap(sym, &args[0], &args[1], ret_layout)
}
+ LowLevel::NumSubSaturated => match self.interner().get(*ret_layout) {
+ Layout::Builtin(Builtin::Int(int_width)) => self.build_fn_call(
+ sym,
+ bitcode::NUM_SUB_SATURATED_INT[int_width].to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
+ self.build_num_sub(sym, &args[0], &args[1], ret_layout)
+ }
+ Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
+ // saturated sub is just normal sub
+ self.build_num_sub(sym, &args[0], &args[1], ret_layout)
+ }
+ Layout::Builtin(Builtin::Decimal) => {
+ // self.load_args_and_call_zig(backend, bitcode::DEC_SUB_SATURATED)
+ todo!()
+ }
+ _ => internal_error!("invalid return type"),
+ },
LowLevel::NumBitwiseAnd => {
if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
self.build_int_bitwise_and(sym, &args[0], &args[1], int_width)
@@ -553,6 +596,41 @@ trait Backend<'a> {
internal_error!("bitwise xor on a non-integer")
}
}
+ LowLevel::And => {
+ if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) {
+ self.build_int_bitwise_and(sym, &args[0], &args[1], IntWidth::U8)
+ } else {
+ internal_error!("bitwise and on a non-integer")
+ }
+ }
+ LowLevel::Or => {
+ if let Layout::Builtin(Builtin::Bool) = self.interner().get(*ret_layout) {
+ self.build_int_bitwise_or(sym, &args[0], &args[1], IntWidth::U8)
+ } else {
+ internal_error!("bitwise or on a non-integer")
+ }
+ }
+ LowLevel::NumShiftLeftBy => {
+ if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
+ self.build_int_shift_left(sym, &args[0], &args[1], int_width)
+ } else {
+ internal_error!("shift left on a non-integer")
+ }
+ }
+ LowLevel::NumShiftRightBy => {
+ if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
+ self.build_int_shift_right(sym, &args[0], &args[1], int_width)
+ } else {
+ internal_error!("shift right on a non-integer")
+ }
+ }
+ LowLevel::NumShiftRightZfBy => {
+ if let Layout::Builtin(Builtin::Int(int_width)) = self.interner().get(*ret_layout) {
+ self.build_int_shift_right_zero_fill(sym, &args[0], &args[1], int_width)
+ } else {
+ internal_error!("shift right zero-fill on a non-integer")
+ }
+ }
LowLevel::Eq => {
debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument");
debug_assert_eq!(
@@ -583,6 +661,15 @@ trait Backend<'a> {
);
self.build_neq(sym, &args[0], &args[1], &arg_layouts[0])
}
+ LowLevel::Not => {
+ debug_assert_eq!(1, args.len(), "Not: expected to have exactly one argument");
+ debug_assert_eq!(
+ Layout::BOOL,
+ *ret_layout,
+ "Not: expected to have return layout of type Bool"
+ );
+ self.build_not(sym, &args[0], &arg_layouts[0])
+ }
LowLevel::NumLt => {
debug_assert_eq!(
2,
@@ -664,6 +751,30 @@ trait Backend<'a> {
);
self.build_num_gte(sym, &args[0], &args[1], &arg_layouts[0])
}
+ LowLevel::NumLogUnchecked => {
+ let float_width = match arg_layouts[0] {
+ Layout::F64 => FloatWidth::F64,
+ Layout::F32 => FloatWidth::F32,
+ _ => unreachable!("invalid layout for sqrt"),
+ };
+
+ self.build_fn_call(
+ sym,
+ bitcode::NUM_LOG[float_width].to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ )
+ }
+ LowLevel::NumSqrtUnchecked => {
+ let float_width = match arg_layouts[0] {
+ Layout::F64 => FloatWidth::F64,
+ Layout::F32 => FloatWidth::F32,
+ _ => unreachable!("invalid layout for sqrt"),
+ };
+
+ self.build_num_sqrt(*sym, args[0], float_width);
+ }
LowLevel::NumRound => self.build_fn_call(
sym,
bitcode::NUM_ROUND_F64[IntWidth::I64].to_string(),
@@ -685,7 +796,24 @@ trait Backend<'a> {
args.len(),
"ListWithCapacity: expected to have exactly one argument"
);
- self.build_list_with_capacity(sym, args[0], arg_layouts[0], ret_layout)
+ let elem_layout = list_element_layout!(self.interner(), *ret_layout);
+ self.build_list_with_capacity(sym, args[0], arg_layouts[0], elem_layout, ret_layout)
+ }
+ LowLevel::ListReserve => {
+ debug_assert_eq!(
+ 2,
+ args.len(),
+ "ListReserve: expected to have exactly two arguments"
+ );
+ self.build_list_reserve(sym, args, arg_layouts, ret_layout)
+ }
+ LowLevel::ListAppendUnsafe => {
+ debug_assert_eq!(
+ 2,
+ args.len(),
+ "ListAppendUnsafe: expected to have exactly two arguments"
+ );
+ self.build_list_append_unsafe(sym, args, arg_layouts, ret_layout)
}
LowLevel::ListGetUnsafe => {
debug_assert_eq!(
@@ -703,6 +831,23 @@ trait Backend<'a> {
);
self.build_list_replace_unsafe(sym, args, arg_layouts, ret_layout)
}
+ LowLevel::ListConcat => {
+ debug_assert_eq!(
+ 2,
+ args.len(),
+ "ListConcat: expected to have exactly two arguments"
+ );
+ let elem_layout = list_element_layout!(self.interner(), *ret_layout);
+ self.build_list_concat(sym, args, arg_layouts, elem_layout, ret_layout)
+ }
+ LowLevel::ListPrepend => {
+ debug_assert_eq!(
+ 2,
+ args.len(),
+ "ListPrepend: expected to have exactly two arguments"
+ );
+ self.build_list_prepend(sym, args, arg_layouts, ret_layout)
+ }
LowLevel::StrConcat => self.build_fn_call(
sym,
bitcode::STR_CONCAT.to_string(),
@@ -710,6 +855,171 @@ trait Backend<'a> {
arg_layouts,
ret_layout,
),
+ LowLevel::StrJoinWith => self.build_fn_call(
+ sym,
+ bitcode::STR_JOIN_WITH.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrSplit => self.build_fn_call(
+ sym,
+ bitcode::STR_SPLIT.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrStartsWith => self.build_fn_call(
+ sym,
+ bitcode::STR_STARTS_WITH.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrStartsWithScalar => self.build_fn_call(
+ sym,
+ bitcode::STR_STARTS_WITH_SCALAR.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrAppendScalar => self.build_fn_call(
+ sym,
+ bitcode::STR_APPEND_SCALAR.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrEndsWith => self.build_fn_call(
+ sym,
+ bitcode::STR_ENDS_WITH.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrCountGraphemes => self.build_fn_call(
+ sym,
+ bitcode::STR_COUNT_GRAPEHEME_CLUSTERS.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrSubstringUnsafe => self.build_fn_call(
+ sym,
+ bitcode::STR_SUBSTRING_UNSAFE.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrToUtf8 => self.build_fn_call(
+ sym,
+ bitcode::STR_TO_UTF8.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrCountUtf8Bytes => self.build_fn_call(
+ sym,
+ bitcode::STR_COUNT_UTF8_BYTES.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrFromUtf8Range => self.build_fn_call(
+ sym,
+ bitcode::STR_FROM_UTF8_RANGE.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ // LowLevel::StrToUtf8 => self.build_fn_call(
+ // sym,
+ // bitcode::STR_TO_UTF8.to_string(),
+ // args,
+ // arg_layouts,
+ // ret_layout,
+ // ),
+ LowLevel::StrRepeat => self.build_fn_call(
+ sym,
+ bitcode::STR_REPEAT.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrTrim => self.build_fn_call(
+ sym,
+ bitcode::STR_TRIM.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrTrimLeft => self.build_fn_call(
+ sym,
+ bitcode::STR_TRIM_LEFT.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrTrimRight => self.build_fn_call(
+ sym,
+ bitcode::STR_TRIM_RIGHT.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrReserve => self.build_fn_call(
+ sym,
+ bitcode::STR_RESERVE.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrWithCapacity => self.build_fn_call(
+ sym,
+ bitcode::STR_WITH_CAPACITY.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrToScalars => self.build_fn_call(
+ sym,
+ bitcode::STR_TO_SCALARS.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrGetUnsafe => self.build_fn_call(
+ sym,
+ bitcode::STR_GET_UNSAFE.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrGetScalarUnsafe => self.build_fn_call(
+ sym,
+ bitcode::STR_GET_SCALAR_UNSAFE.to_string(),
+ args,
+ arg_layouts,
+ ret_layout,
+ ),
+ LowLevel::StrToNum => {
+ let number_layout = match self.interner().get(*ret_layout) {
+ Layout::Struct { field_layouts, .. } => field_layouts[0], // TODO: why is it sometimes a struct?
+ _ => unreachable!(),
+ };
+
+ // match on the return layout to figure out which zig builtin we need
+ let intrinsic = match self.interner().get(number_layout) {
+ Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width],
+ Layout::Builtin(Builtin::Float(float_width)) => {
+ &bitcode::STR_TO_FLOAT[float_width]
+ }
+ Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR,
+ _ => unreachable!(),
+ };
+
+ self.build_fn_call(sym, intrinsic.to_string(), args, arg_layouts, ret_layout)
+ }
LowLevel::PtrCast => {
debug_assert_eq!(
1,
@@ -746,7 +1056,6 @@ trait Backend<'a> {
arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
) {
- self.load_literal_symbols(args);
match func_sym {
Symbol::NUM_IS_ZERO => {
debug_assert_eq!(
@@ -760,6 +1069,7 @@ trait Backend<'a> {
"NumIsZero: expected to have return layout of type Bool"
);
+ self.load_literal_symbols(args);
self.load_literal(
&Symbol::DEV_TMP,
&arg_layouts[0],
@@ -768,7 +1078,7 @@ trait Backend<'a> {
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP)
}
- Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE => {
+ Symbol::LIST_GET | Symbol::LIST_SET | Symbol::LIST_REPLACE | Symbol::LIST_APPEND => {
// TODO: This is probably simple enough to be worth inlining.
let layout_id = LayoutIds::default().get(func_sym, ret_layout);
let fn_name = self.symbol_to_string(func_sym, layout_id);
@@ -776,24 +1086,36 @@ trait Backend<'a> {
self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
- Symbol::NUM_ADD_CHECKED => {
- let layout_id = LayoutIds::default().get(func_sym, ret_layout);
- let fn_name = self.symbol_to_string(func_sym, layout_id);
- // Now that the arguments are needed, load them if they are literals.
- self.load_literal_symbols(args);
- self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
- }
Symbol::BOOL_TRUE => {
let bool_layout = Layout::BOOL;
self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(true));
self.return_symbol(&Symbol::DEV_TMP, &bool_layout);
+ self.free_symbol(&Symbol::DEV_TMP)
}
Symbol::BOOL_FALSE => {
let bool_layout = Layout::BOOL;
self.load_literal(&Symbol::DEV_TMP, &bool_layout, &Literal::Bool(false));
self.return_symbol(&Symbol::DEV_TMP, &bool_layout);
+ self.free_symbol(&Symbol::DEV_TMP)
+ }
+ Symbol::STR_IS_VALID_SCALAR => {
+ // just call the function
+ let layout_id = LayoutIds::default().get(func_sym, ret_layout);
+ let fn_name = self.symbol_to_string(func_sym, layout_id);
+ // Now that the arguments are needed, load them if they are literals.
+ self.load_literal_symbols(args);
+ self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
+ }
+ other => {
+ eprintln!("maybe {other:?} should have a custom implementation?");
+
+ // just call the function
+ let layout_id = LayoutIds::default().get(func_sym, ret_layout);
+ let fn_name = self.symbol_to_string(func_sym, layout_id);
+ // Now that the arguments are needed, load them if they are literals.
+ self.load_literal_symbols(args);
+ self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout)
}
- _ => todo!("the function, {:?}", func_sym),
}
}
@@ -824,6 +1146,16 @@ trait Backend<'a> {
return_layout: &InLayout<'a>,
);
+ /// build_num_sub_checked stores the sum of src1 and src2 into dst.
+ fn build_num_sub_checked(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ num_layout: &InLayout<'a>,
+ return_layout: &InLayout<'a>,
+ );
+
/// build_num_mul stores `src1 * src2` into dst.
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>);
@@ -872,12 +1204,42 @@ trait Backend<'a> {
int_width: IntWidth,
);
+ /// stores the `Num.shiftLeftBy src1 src2` into dst.
+ fn build_int_shift_left(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ int_width: IntWidth,
+ );
+
+ /// stores the `Num.shiftRightBy src1 src2` into dst.
+ fn build_int_shift_right(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ int_width: IntWidth,
+ );
+
+ /// stores the `Num.shiftRightZfBy src1 src2` into dst.
+ fn build_int_shift_right_zero_fill(
+ &mut self,
+ dst: &Symbol,
+ src1: &Symbol,
+ src2: &Symbol,
+ int_width: IntWidth,
+ );
+
/// build_eq stores the result of `src1 == src2` into dst.
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>);
/// build_neq stores the result of `src1 != src2` into dst.
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &InLayout<'a>);
+ /// build_not stores the result of `!src` into dst.
+ fn build_not(&mut self, dst: &Symbol, src: &Symbol, arg_layout: &InLayout<'a>);
+
/// build_num_lt stores the result of `src1 < src2` into dst.
fn build_num_lt(
&mut self,
@@ -923,6 +1285,9 @@ trait Backend<'a> {
arg_layout: &InLayout<'a>,
);
+ /// build_sqrt stores the result of `sqrt(src)` into dst.
+ fn build_num_sqrt(&mut self, dst: Symbol, src: Symbol, float_width: FloatWidth);
+
/// build_list_len returns the length of a list.
fn build_list_len(&mut self, dst: &Symbol, list: &Symbol);
@@ -932,6 +1297,25 @@ trait Backend<'a> {
dst: &Symbol,
capacity: Symbol,
capacity_layout: InLayout<'a>,
+ elem_layout: InLayout<'a>,
+ ret_layout: &InLayout<'a>,
+ );
+
+ /// build_list_reserve enlarges a list to at least accommodate the given capacity.
+ fn build_list_reserve(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
+ ret_layout: &InLayout<'a>,
+ );
+
+ /// build_list_append_unsafe returns a new list with a given element appended.
+ fn build_list_append_unsafe(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
ret_layout: &InLayout<'a>,
);
@@ -953,6 +1337,25 @@ trait Backend<'a> {
ret_layout: &InLayout<'a>,
);
+ /// build_list_concat returns a new list containing the two argument lists concatenated.
+ fn build_list_concat(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
+ elem_layout: InLayout<'a>,
+ ret_layout: &InLayout<'a>,
+ );
+
+ /// build_list_prepend returns a new list with a given element prepended.
+ fn build_list_prepend(
+ &mut self,
+ dst: &Symbol,
+ args: &'a [Symbol],
+ arg_layouts: &[InLayout<'a>],
+ ret_layout: &InLayout<'a>,
+ );
+
/// build_refcount_getptr loads the pointer to the reference count of src into dst.
fn build_ptr_cast(&mut self, dst: &Symbol, src: &Symbol);
@@ -1021,6 +1424,12 @@ trait Backend<'a> {
tag_id: TagIdIntType,
);
+ /// load a value from a pointer
+ fn expr_unbox(&mut self, sym: Symbol, ptr: Symbol, element_layout: InLayout<'a>);
+
+ /// store a refcounted value on the heap
+ fn expr_box(&mut self, sym: Symbol, value: Symbol, element_layout: InLayout<'a>);
+
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
fn return_symbol(&mut self, sym: &Symbol, layout: &InLayout<'a>);
diff --git a/crates/compiler/gen_dev/src/run_roc.rs b/crates/compiler/gen_dev/src/run_roc.rs
index 981a60f8ae..a0d20c0d12 100644
--- a/crates/compiler/gen_dev/src/run_roc.rs
+++ b/crates/compiler/gen_dev/src/run_roc.rs
@@ -18,12 +18,16 @@ macro_rules! run_jit_function_raw {
let result = main();
- assert_eq!(
- $errors,
- std::vec::Vec::new(),
- "Encountered errors: {:?}",
- $errors
- );
+ if !$errors.is_empty() {
+ dbg!(&$errors);
+
+ assert_eq!(
+ $errors,
+ std::vec::Vec::new(),
+ "Encountered errors: {:?}",
+ $errors
+ );
+ }
$transform(result)
}
diff --git a/crates/compiler/gen_llvm/Cargo.toml b/crates/compiler/gen_llvm/Cargo.toml
index af3bf91a16..031c418bfc 100644
--- a/crates/compiler/gen_llvm/Cargo.toml
+++ b/crates/compiler/gen_llvm/Cargo.toml
@@ -1,24 +1,26 @@
[package]
name = "roc_gen_llvm"
description = "The LLVM backend for the Roc compiler"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
[dependencies]
-roc_alias_analysis = { path = "../alias_analysis" }
-roc_collections = { path = "../collections" }
-roc_module = { path = "../module" }
-roc_builtins = { path = "../builtins" }
-roc_error_macros = { path = "../../error_macros" }
-roc_mono = { path = "../mono" }
-roc_target = { path = "../roc_target" }
-roc_std = { path = "../../roc_std" }
-roc_debug_flags = { path = "../debug_flags" }
-roc_region = { path = "../region" }
morphic_lib = { path = "../../vendor/morphic_lib" }
+roc_alias_analysis = { path = "../alias_analysis" }
+roc_bitcode_bc = { path = "../builtins/bitcode/bc" }
+roc_builtins = { path = "../builtins" }
+roc_collections = { path = "../collections" }
+roc_debug_flags = { path = "../debug_flags" }
+roc_error_macros = { path = "../../error_macros" }
+roc_module = { path = "../module" }
+roc_mono = { path = "../mono" }
+roc_region = { path = "../region" }
+roc_std = { path = "../../roc_std" }
+roc_target = { path = "../roc_target" }
bumpalo.workspace = true
+inkwell.workspace = true
target-lexicon.workspace = true
-inkwell.workspace = true
diff --git a/crates/compiler/gen_llvm/src/llvm/bitcode.rs b/crates/compiler/gen_llvm/src/llvm/bitcode.rs
index 29812f73e6..96c6d9290e 100644
--- a/crates/compiler/gen_llvm/src/llvm/bitcode.rs
+++ b/crates/compiler/gen_llvm/src/llvm/bitcode.rs
@@ -11,8 +11,8 @@ use crate::llvm::refcounting::{
use inkwell::attributes::{Attribute, AttributeLoc};
use inkwell::types::{BasicType, BasicTypeEnum, StructType};
use inkwell::values::{
- BasicValue, BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue,
- PointerValue, StructValue,
+ BasicValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PointerValue,
+ StructValue,
};
use inkwell::AddressSpace;
use roc_error_macros::internal_error;
@@ -206,7 +206,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
- let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
+ let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@@ -244,7 +244,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type =
- basic_type_from_layout(env, layout_interner, *layout).ptr_type(AddressSpace::Generic);
+ basic_type_from_layout(env, layout_interner, *layout).ptr_type(AddressSpace::default());
let cast_ptr = env.builder.build_pointer_cast(
argument_ptr.into_pointer_value(),
@@ -274,7 +274,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
}
(true, layout) => {
let closure_type = basic_type_from_layout(env, layout_interner, layout)
- .ptr_type(AddressSpace::Generic);
+ .ptr_type(AddressSpace::default());
let closure_cast =
env.builder
@@ -310,8 +310,7 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
env.builder.build_return(None);
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function_value
}
@@ -375,7 +374,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
- let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
+ let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = match rc_operation {
Mode::Inc | Mode::Dec => crate::llvm::refcounting::build_header_help(
@@ -411,7 +410,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
generic_value_ptr.set_name(Symbol::ARG_1.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout_interner, layout);
- let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
+ let value_ptr_type = value_type.ptr_type(AddressSpace::default());
let value_ptr =
env.builder
.build_pointer_cast(generic_value_ptr, value_ptr_type, "load_opaque");
@@ -449,8 +448,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function_value
}
@@ -472,7 +470,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
let function_value = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
None => {
- let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
+ let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@@ -502,7 +500,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
value_ptr2.set_name(Symbol::ARG_2.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout_interner, layout)
- .ptr_type(AddressSpace::Generic);
+ .ptr_type(AddressSpace::default());
let value_cast1 = env
.builder
@@ -533,8 +531,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function_value
}
@@ -558,7 +555,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let function_value = match env.module.get_function(fn_name) {
Some(function_value) => function_value,
None => {
- let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
+ let arg_type = env.context.i8_type().ptr_type(AddressSpace::default());
let function_value = crate::llvm::refcounting::build_header_help(
env,
@@ -593,7 +590,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
value_ptr2.set_name(Symbol::ARG_3.as_str(&env.interns));
let value_type = basic_type_from_layout(env, layout_interner, layout);
- let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
+ let value_ptr_type = value_type.ptr_type(AddressSpace::default());
let value_cast1 =
env.builder
@@ -623,7 +620,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
_ => {
let closure_type =
basic_type_from_layout(env, layout_interner, closure_data_repr);
- let closure_ptr_type = closure_type.ptr_type(AddressSpace::Generic);
+ let closure_ptr_type = closure_type.ptr_type(AddressSpace::default());
let closure_cast = env.builder.build_pointer_cast(
closure_ptr,
@@ -659,8 +656,7 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function_value
}
@@ -798,7 +794,7 @@ fn ptr_len_cap<'a, 'ctx, 'env>(
let ptr = env.builder.build_int_to_ptr(
lower_word,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"list_ptr",
);
diff --git a/crates/compiler/gen_llvm/src/llvm/build.rs b/crates/compiler/gen_llvm/src/llvm/build.rs
index 8d051a7779..b9c888858f 100644
--- a/crates/compiler/gen_llvm/src/llvm/build.rs
+++ b/crates/compiler/gen_llvm/src/llvm/build.rs
@@ -24,8 +24,8 @@ use inkwell::types::{
};
use inkwell::values::BasicValueEnum::{self, *};
use inkwell::values::{
- BasicMetadataValueEnum, BasicValue, CallSiteValue, FunctionValue, InstructionValue, IntValue,
- PhiValue, PointerValue, StructValue,
+ BasicMetadataValueEnum, CallSiteValue, FunctionValue, InstructionValue, IntValue, PhiValue,
+ PointerValue, StructValue,
};
use inkwell::OptimizationLevel;
use inkwell::{AddressSpace, IntPredicate};
@@ -37,11 +37,10 @@ use roc_collections::all::{ImMap, MutMap, MutSet};
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::ROC_PRINT_LLVM_FN_VERIFICATION;
-use roc_error_macros::internal_error;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::ir::{
- BranchInfo, CallType, CrashTag, EntryPoint, JoinPointId, ListLiteralElement, ModifyRc,
- OptLevel, ProcLayout, SingleEntryPoint,
+ BranchInfo, CallType, CrashTag, EntryPoint, GlueLayouts, HostExposedLambdaSet, JoinPointId,
+ ListLiteralElement, ModifyRc, OptLevel, ProcLayout, SingleEntryPoint,
};
use roc_mono::layout::{
Builtin, InLayout, LambdaName, LambdaSet, Layout, LayoutIds, LayoutInterner, Niche,
@@ -157,7 +156,7 @@ macro_rules! debug_info_init {
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
- $env.builder.set_current_debug_location(&$env.context, loc);
+ $env.builder.set_current_debug_location(loc);
}};
}
@@ -201,6 +200,7 @@ pub enum LlvmBackendMode {
BinaryDev,
/// Creates a test wrapper around the main roc function to catch and report panics.
/// Provides a testing implementation of primitives (roc_alloc, roc_panic, etc)
+ BinaryGlue,
GenTest,
WasmGenTest,
CliTest,
@@ -211,6 +211,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => true,
LlvmBackendMode::BinaryDev => true,
+ LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => false,
@@ -222,6 +223,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => false,
+ LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => true,
LlvmBackendMode::WasmGenTest => true,
LlvmBackendMode::CliTest => true,
@@ -232,6 +234,7 @@ impl LlvmBackendMode {
match self {
LlvmBackendMode::Binary => false,
LlvmBackendMode::BinaryDev => true,
+ LlvmBackendMode::BinaryGlue => false,
LlvmBackendMode::GenTest => false,
LlvmBackendMode::WasmGenTest => false,
LlvmBackendMode::CliTest => true,
@@ -686,7 +689,7 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx, 'env>(
let output_type = match roc_main_fn.get_type().get_return_type() {
Some(return_type) => {
- let output_type = return_type.ptr_type(AddressSpace::Generic);
+ let output_type = return_type.ptr_type(AddressSpace::default());
output_type.into()
}
None => {
@@ -880,7 +883,7 @@ fn small_str_ptr_width_8<'a, 'ctx, 'env>(
let len = env.ptr_int().const_int(word2, false);
let cap = env.ptr_int().const_int(word3, false);
- let address_space = AddressSpace::Generic;
+ let address_space = AddressSpace::default();
let ptr_type = env.context.i8_type().ptr_type(address_space);
let ptr = env.builder.build_int_to_ptr(ptr, ptr_type, "to_u8_ptr");
@@ -907,7 +910,7 @@ fn small_str_ptr_width_4<'a, 'ctx, 'env>(
let len = env.ptr_int().const_int(word2 as u64, false);
let cap = env.ptr_int().const_int(word3 as u64, false);
- let address_space = AddressSpace::Generic;
+ let address_space = AddressSpace::default();
let ptr_type = env.context.i8_type().ptr_type(address_space);
let ptr = env.builder.build_int_to_ptr(ptr, ptr_type, "to_u8_ptr");
@@ -1049,7 +1052,7 @@ fn struct_pointer_from_fields<'a, 'ctx, 'env, I>(
.builder
.build_bitcast(
input_pointer,
- struct_type.ptr_type(AddressSpace::Generic),
+ struct_type.ptr_type(AddressSpace::default()),
"struct_ptr",
)
.into_pointer_value();
@@ -1310,7 +1313,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let data_ptr = env.builder.build_pointer_cast(
opaque_data_ptr,
- struct_type.ptr_type(AddressSpace::Generic),
+ struct_type.ptr_type(AddressSpace::default()),
"to_data_pointer",
);
@@ -1338,14 +1341,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let field_layouts = tag_layouts[*tag_id as usize];
let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value());
+ let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
lookup_at_index_ptr2(
env,
layout_interner,
- union_layout,
field_layouts,
*index as usize,
ptr,
+ target_loaded_type,
)
}
UnionLayout::NonNullableUnwrapped(field_layouts) => {
@@ -1353,15 +1357,16 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let struct_type = basic_type_from_layout(env, layout_interner, struct_layout);
+ let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
lookup_at_index_ptr(
env,
layout_interner,
- union_layout,
field_layouts,
*index as usize,
argument.into_pointer_value(),
struct_type.into_struct_type(),
+ target_loaded_type,
)
}
UnionLayout::NullableWrapped {
@@ -1380,13 +1385,15 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
let field_layouts = other_tags[tag_index as usize];
let ptr = tag_pointer_clear_tag_id(env, argument.into_pointer_value());
+ let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
+
lookup_at_index_ptr2(
env,
layout_interner,
- union_layout,
field_layouts,
*index as usize,
ptr,
+ target_loaded_type,
)
}
UnionLayout::NullableUnwrapped {
@@ -1401,16 +1408,17 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
layout_interner.insert(Layout::struct_no_name_order(field_layouts));
let struct_type = basic_type_from_layout(env, layout_interner, struct_layout);
+ let target_loaded_type = basic_type_from_layout(env, layout_interner, layout);
lookup_at_index_ptr(
env,
layout_interner,
- union_layout,
field_layouts,
// the tag id is not stored
*index as usize,
argument.into_pointer_value(),
struct_type.into_struct_type(),
+ target_loaded_type,
)
}
}
@@ -1535,7 +1543,7 @@ fn build_tag_field_value<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
value.into_pointer_value(),
- env.context.i64_type().ptr_type(AddressSpace::Generic),
+ env.context.i64_type().ptr_type(AddressSpace::default()),
"cast_recursive_pointer",
)
.into()
@@ -1751,7 +1759,7 @@ fn build_tag<'a, 'ctx, 'env>(
);
if tag_id == *nullable_id as _ {
- let output_type = roc_union.struct_type().ptr_type(AddressSpace::Generic);
+ let output_type = roc_union.struct_type().ptr_type(AddressSpace::default());
return output_type.const_null().into();
}
@@ -1993,17 +2001,17 @@ pub fn get_tag_id<'a, 'ctx, 'env>(
fn lookup_at_index_ptr<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
- union_layout: &UnionLayout<'a>,
field_layouts: &[InLayout<'a>],
index: usize,
value: PointerValue<'ctx>,
struct_type: StructType<'ctx>,
+ target_loaded_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let ptr = env.builder.build_pointer_cast(
value,
- struct_type.ptr_type(AddressSpace::Generic),
+ struct_type.ptr_type(AddressSpace::default()),
"cast_lookup_at_index_ptr",
);
@@ -2020,35 +2028,18 @@ fn lookup_at_index_ptr<'a, 'ctx, 'env>(
"load_at_index_ptr_old",
);
- if let Some(Layout::RecursivePointer(_)) = field_layouts
- .get(index as usize)
- .map(|l| layout_interner.get(*l))
- {
- // a recursive field is stored as a `i64*`, to use it we must cast it to
- // a pointer to the block of memory representation
- let union_layout = layout_interner.insert(Layout::Union(*union_layout));
- let actual_type = basic_type_from_layout(env, layout_interner, union_layout);
- debug_assert!(actual_type.is_pointer_type());
-
- builder
- .build_pointer_cast(
- result.into_pointer_value(),
- actual_type.into_pointer_type(),
- "cast_rec_pointer_lookup_at_index_ptr_old",
- )
- .into()
- } else {
- result
- }
+ // A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout
+ // might want a more precise structure. As such, cast it to the refined type if needed.
+ cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type)
}
fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
- union_layout: &UnionLayout<'a>,
field_layouts: &'a [InLayout<'a>],
index: usize,
value: PointerValue<'ctx>,
+ target_loaded_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
@@ -2058,7 +2049,7 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
let data_ptr = env.builder.build_pointer_cast(
value,
- struct_type.ptr_type(AddressSpace::Generic),
+ struct_type.ptr_type(AddressSpace::default()),
"cast_lookup_at_index_ptr",
);
@@ -2080,27 +2071,9 @@ fn lookup_at_index_ptr2<'a, 'ctx, 'env>(
"load_at_index_ptr",
);
- if let Some(Layout::RecursivePointer(_)) = field_layouts
- .get(index as usize)
- .map(|l| layout_interner.get(*l))
- {
- // a recursive field is stored as a `i64*`, to use it we must cast it to
- // a pointer to the block of memory representation
-
- let union_layout = layout_interner.insert(Layout::Union(*union_layout));
- let actual_type = basic_type_from_layout(env, layout_interner, union_layout);
- debug_assert!(actual_type.is_pointer_type());
-
- builder
- .build_pointer_cast(
- result.into_pointer_value(),
- actual_type.into_pointer_type(),
- "cast_rec_pointer_lookup_at_index_ptr_new",
- )
- .into()
- } else {
- result
- }
+ // A recursive pointer in the loaded structure is stored as a `i64*`, but the loaded layout
+ // might want a more precise structure. As such, cast it to the refined type if needed.
+ cast_if_necessary_for_opaque_recursive_pointers(env.builder, result, target_loaded_type)
}
pub fn reserve_with_refcount<'a, 'ctx, 'env>(
@@ -2181,7 +2154,7 @@ pub fn allocate_with_refcount_help<'a, 'ctx, 'env>(
)
.into_pointer_value();
- let ptr_type = value_type.ptr_type(AddressSpace::Generic);
+ let ptr_type = value_type.ptr_type(AddressSpace::default());
env.builder
.build_pointer_cast(ptr, ptr_type, "alloc_cast_to_desired")
@@ -2406,7 +2379,7 @@ pub fn store_roc_value_opaque<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>,
) {
let target_type =
- basic_type_from_layout(env, layout_interner, layout).ptr_type(AddressSpace::Generic);
+ basic_type_from_layout(env, layout_interner, layout).ptr_type(AddressSpace::default());
let destination =
env.builder
.build_pointer_cast(opaque_destination, target_type, "store_roc_value_opaque");
@@ -2659,7 +2632,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
let basic_type = basic_type_from_layout(env, layout_interner, param.layout);
let phi_type = if layout_interner.is_passed_by_reference(param.layout) {
- basic_type.ptr_type(AddressSpace::Generic).into()
+ basic_type.ptr_type(AddressSpace::default()).into()
} else {
basic_type
};
@@ -3043,6 +3016,24 @@ pub(crate) fn load_symbol_and_layout<'a, 'ctx, 'b>(
}
}
+fn equivalent_type_constructors(t1: &BasicTypeEnum, t2: &BasicTypeEnum) -> bool {
+ use BasicTypeEnum::*;
+ match (t1, t2) {
+ (ArrayType(_), ArrayType(_)) => true,
+ (ArrayType(_), _) => false,
+ (FloatType(_), FloatType(_)) => true,
+ (FloatType(_), _) => false,
+ (IntType(_), IntType(_)) => true,
+ (IntType(_), _) => false,
+ (PointerType(_), PointerType(_)) => true,
+ (PointerType(_), _) => false,
+ (StructType(_), StructType(_)) => true,
+ (StructType(_), _) => false,
+ (VectorType(_), VectorType(_)) => true,
+ (VectorType(_), _) => false,
+ }
+}
+
/// Cast a value to another value of the same size, but only if their types are not equivalent.
/// This is needed to allow us to interoperate between recursive pointers in unions that are
/// opaque, and well-typed.
@@ -3054,7 +3045,10 @@ pub fn cast_if_necessary_for_opaque_recursive_pointers<'ctx>(
from_value: BasicValueEnum<'ctx>,
to_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
- if from_value.get_type() != to_type {
+ if from_value.get_type() != to_type
+ // Only perform the cast if the target types are transumatble.
+ && equivalent_type_constructors(&from_value.get_type(), &to_type)
+ {
complex_bitcast(
builder,
from_value,
@@ -3202,7 +3196,7 @@ fn complex_bitcast_from_bigger_than_to<'ctx>(
// then read it back as a different type
let to_type_pointer = builder.build_pointer_cast(
argument_pointer,
- to_type.ptr_type(inkwell::AddressSpace::Generic),
+ to_type.ptr_type(inkwell::AddressSpace::default()),
name,
);
@@ -3225,7 +3219,7 @@ fn complex_bitcast_to_bigger_than_from<'ctx>(
storage,
from_value
.get_type()
- .ptr_type(inkwell::AddressSpace::Generic),
+ .ptr_type(inkwell::AddressSpace::default()),
name,
);
@@ -3586,7 +3580,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
argument_types.insert(0, output_type);
}
Some(return_type) => {
- let output_type = return_type.ptr_type(AddressSpace::Generic);
+ let output_type = return_type.ptr_type(AddressSpace::default());
argument_types.insert(0, output_type.into());
}
}
@@ -3638,7 +3632,7 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
// bitcast the ptr
let fastcc_ptr = env.builder.build_pointer_cast(
arg.into_pointer_value(),
- fastcc_type.ptr_type(AddressSpace::Generic),
+ fastcc_type.ptr_type(AddressSpace::default()),
"bitcast_arg",
);
@@ -3733,7 +3727,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
let return_type = wrapper_return_type;
let c_function_spec = {
- let output_type = return_type.ptr_type(AddressSpace::Generic);
+ let output_type = return_type.ptr_type(AddressSpace::default());
argument_types.push(output_type.into());
FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types)
};
@@ -3842,6 +3836,7 @@ fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
+
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func(
@@ -3897,7 +3892,10 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
let c_abi_roc_str_type = env.context.struct_type(
&[
- env.context.i8_type().ptr_type(AddressSpace::Generic).into(),
+ env.context
+ .i8_type()
+ .ptr_type(AddressSpace::default())
+ .into(),
env.ptr_int().into(),
env.ptr_int().into(),
],
@@ -3955,20 +3953,40 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
(RocReturn::ByPointer, CCReturn::Return) => {
// Roc currently puts the return pointer at the end of the argument list.
// As such, we drop the last element here instead of the first.
- (¶ms[..], ¶m_types[..param_types.len() - 1])
+ (
+ ¶ms[..],
+ ¶m_types[..param_types.len().saturating_sub(1)],
+ )
}
// Drop the return pointer the other way, if the C function returns by pointer but Roc
// doesn't
(RocReturn::Return, CCReturn::ByPointer) => (¶ms[1..], ¶m_types[..]),
(RocReturn::ByPointer, CCReturn::ByPointer) => {
// Both return by pointer but Roc puts it at the end and C puts it at the beginning
- (¶ms[1..], ¶m_types[..param_types.len() - 1])
+ (
+ ¶ms[1..],
+ ¶m_types[..param_types.len().saturating_sub(1)],
+ )
+ }
+ (RocReturn::Return, CCReturn::Void) => {
+ // the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`.
+ // In C, this is modelled as a function returning void
+ (¶ms[..], ¶m_types[..])
+ }
+ (RocReturn::ByPointer, CCReturn::Void) => {
+ // the roc function returns a unit value. like `{}` or `{ { {}, {} }, {} }`.
+ // In C, this is modelled as a function returning void
+ (
+ ¶ms[..],
+ ¶m_types[..param_types.len().saturating_sub(1)],
+ )
}
_ => (¶ms[..], ¶m_types[..]),
};
- debug_assert!(
- params.len() == param_types.len(),
+ debug_assert_eq!(
+ params.len(),
+ param_types.len(),
"when exposing a function to the host, params.len() was {}, but param_types.len() was {}",
params.len(),
param_types.len()
@@ -4016,7 +4034,7 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
// bitcast the ptr
let fastcc_ptr = env.builder.build_pointer_cast(
arg.into_pointer_value(),
- fastcc_type.ptr_type(AddressSpace::Generic),
+ fastcc_type.ptr_type(AddressSpace::default()),
"bitcast_arg",
);
@@ -4102,7 +4120,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
)
}
- LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {}
+ LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {}
}
// a generic version that writes the result into a passed *u8 pointer
@@ -4131,7 +4149,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
Some(env.context.i64_type().as_basic_type_enum()),
&[],
);
- let size_function_name: String = format!("roc__{}_size", ident_string);
+ let size_function_name: String = format!("{}_size", c_function_name);
let size_function = add_func(
env.context,
@@ -4155,7 +4173,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
roc_call_result_type(env, roc_function.get_type().get_return_type().unwrap()).into()
}
- LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev => {
+ LlvmBackendMode::Binary | LlvmBackendMode::BinaryDev | LlvmBackendMode::BinaryGlue => {
basic_type_from_layout(env, layout_interner, return_layout)
}
};
@@ -4193,7 +4211,7 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
env.builder.build_pointer_cast(
global.as_pointer_value(),
- env.context.i32_type().ptr_type(AddressSpace::Generic),
+ env.context.i32_type().ptr_type(AddressSpace::default()),
"cast_sjlj_buffer",
)
}
@@ -4210,12 +4228,12 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
let buf_type = env
.context
.i8_type()
- .ptr_type(AddressSpace::Generic)
+ .ptr_type(AddressSpace::default())
.array_type(5);
let jmp_buf_i8p_arr = env.builder.build_pointer_cast(
jmp_buf,
- buf_type.ptr_type(AddressSpace::Generic),
+ buf_type.ptr_type(AddressSpace::default()),
"jmp_buf [5 x i8*]",
);
@@ -4256,7 +4274,7 @@ pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValu
.builder
.build_pointer_cast(
jmp_buf,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"jmp_buf i8*",
)
.into();
@@ -4413,7 +4431,7 @@ fn roc_call_result_type<'a, 'ctx, 'env>(
env.context.struct_type(
&[
env.context.i64_type().into(),
- zig_str_type(env).ptr_type(AddressSpace::Generic).into(),
+ zig_str_type(env).ptr_type(AddressSpace::default()).into(),
return_type,
],
false,
@@ -4489,7 +4507,7 @@ fn make_exception_catching_wrapper<'a, 'ctx, 'env>(
basic_type_from_layout(env, layout_interner, return_layout),
);
- // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::Generic).into());
+ // argument_types.push(wrapper_return_type.ptr_type(AddressSpace::default()).into());
// let wrapper_function_type = env.context.void_type().fn_type(&argument_types, false);
let wrapper_function_spec = FunctionSpec::cconv(
@@ -4591,8 +4609,9 @@ pub fn build_procedures<'a, 'ctx, 'env>(
procedures: MutMap<(Symbol, ProcLayout<'a>), roc_mono::ir::Proc<'a>>,
entry_point: EntryPoint<'a>,
debug_output_file: Option<&Path>,
+ glue_layouts: &GlueLayouts<'a>,
) {
- build_procedures_help(
+ let mod_solutions = build_procedures_help(
env,
layout_interner,
opt_level,
@@ -4600,6 +4619,43 @@ pub fn build_procedures<'a, 'ctx, 'env>(
entry_point,
debug_output_file,
);
+
+ let niche = Niche::NONE;
+
+ for (symbol, top_level) in glue_layouts.getters.iter().copied() {
+ let it = top_level.arguments.iter().copied();
+ let bytes = roc_alias_analysis::func_name_bytes_help(symbol, it, niche, top_level.result);
+ let func_name = FuncName(&bytes);
+ let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
+
+ let mut it = func_solutions.specs();
+ let Some(func_spec) = it.next() else {
+ // TODO this means a function was not considered host-exposed in mono
+ continue;
+ };
+ debug_assert!(
+ it.next().is_none(),
+ "we expect only one specialization of this symbol"
+ );
+
+ // NOTE fake layout; it is only used for debug prints
+ let getter_fn =
+ function_value_by_func_spec(env, *func_spec, symbol, &[], niche, Layout::UNIT);
+
+ let name = getter_fn.get_name().to_str().unwrap();
+ let getter_name = symbol.as_str(&env.interns);
+
+ // Add the getter function to the module.
+ let _ = expose_function_to_host_help_c_abi(
+ env,
+ layout_interner,
+ name,
+ getter_fn,
+ top_level.arguments,
+ top_level.result,
+ getter_name,
+ );
+ }
}
pub fn build_wasm_test_wrapper<'a, 'ctx, 'env>(
@@ -4919,28 +4975,23 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
mod_solutions: &'a ModSolutions,
- proc_name: LambdaName,
+ fn_name: &str,
alias_symbol: Symbol,
- exposed_function_symbol: Symbol,
- top_level: ProcLayout<'a>,
- layout: RawFunctionLayout<'a>,
+ hels: &HostExposedLambdaSet<'a>,
) {
- let ident_string = proc_name.name().as_str(&env.interns);
- let fn_name: String = format!("{}_1", ident_string);
-
- match layout {
+ match hels.raw_function_layout {
RawFunctionLayout::Function(arguments, closure, result) => {
// define closure size and return value size, e.g.
//
// * roc__mainForHost_1_Update_size() -> i64
// * roc__mainForHost_1_Update_result_size() -> i64
- let it = top_level.arguments.iter().copied();
+ let it = hels.proc_layout.arguments.iter().copied();
let bytes = roc_alias_analysis::func_name_bytes_help(
- exposed_function_symbol,
+ hels.symbol,
it,
Niche::NONE,
- top_level.result,
+ hels.proc_layout.result,
);
let func_name = FuncName(&bytes);
let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
@@ -4956,24 +5007,24 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
function_value_by_func_spec(
env,
*func_spec,
- exposed_function_symbol,
- top_level.arguments,
+ hels.symbol,
+ hels.proc_layout.arguments,
Niche::NONE,
- top_level.result,
+ hels.proc_layout.result,
)
}
None => {
// morphic did not generate a specialization for this function,
// therefore it must actually be unused.
// An example is our closure callers
- panic!("morphic did not specialize {:?}", exposed_function_symbol);
+ panic!("morphic did not specialize {:?}", hels.symbol);
}
};
build_closure_caller(
env,
layout_interner,
- &fn_name,
+ fn_name,
evaluator,
alias_symbol,
arguments,
@@ -4992,7 +5043,7 @@ fn expose_alias_to_host<'a, 'ctx, 'env>(
build_host_exposed_alias_size_help(
env,
- &fn_name,
+ fn_name,
alias_symbol,
Some("result"),
result_type,
@@ -5016,7 +5067,7 @@ fn build_closure_caller<'a, 'ctx, 'env>(
for layout in arguments {
let arg_type = basic_type_from_layout(env, layout_interner, *layout);
- let arg_ptr_type = arg_type.ptr_type(AddressSpace::Generic);
+ let arg_ptr_type = arg_type.ptr_type(AddressSpace::default());
argument_types.push(arg_ptr_type.into());
}
@@ -5025,7 +5076,7 @@ fn build_closure_caller<'a, 'ctx, 'env>(
let basic_type =
basic_type_from_layout(env, layout_interner, lambda_set.runtime_representation());
- basic_type.ptr_type(AddressSpace::Generic)
+ basic_type.ptr_type(AddressSpace::default())
};
argument_types.push(closure_argument_type.into());
@@ -5034,18 +5085,13 @@ fn build_closure_caller<'a, 'ctx, 'env>(
let result_type = basic_type_from_layout(env, layout_interner, result);
- let output_type = { result_type.ptr_type(AddressSpace::Generic) };
+ let output_type = { result_type.ptr_type(AddressSpace::default()) };
argument_types.push(output_type.into());
// STEP 1: build function header
- // e.g. `roc__main_1_Fx_caller`
- let function_name = format!(
- "roc__{}_{}_{}_caller",
- def_name,
- alias_symbol.module_string(&env.interns),
- alias_symbol.as_str(&env.interns)
- );
+ // e.g. `roc__mainForHost_0_caller` (def_name is `mainForHost_0`)
+ let function_name = format!("roc__{}_caller", def_name);
let function_spec = FunctionSpec::cconv(env, CCReturn::Void, None, &argument_types);
@@ -5156,7 +5202,7 @@ fn build_host_exposed_alias_size<'a, 'r, 'ctx, 'env>(
fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
env: &'a Env<'a, 'ctx, 'env>,
def_name: &str,
- alias_symbol: Symbol,
+ _alias_symbol: Symbol,
opt_label: Option<&str>,
basic_type: BasicTypeEnum<'ctx>,
) {
@@ -5166,20 +5212,9 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
let i64 = env.context.i64_type().as_basic_type_enum();
let size_function_spec = FunctionSpec::cconv(env, CCReturn::Return, Some(i64), &[]);
let size_function_name: String = if let Some(label) = opt_label {
- format!(
- "roc__{}_{}_{}_{}_size",
- def_name,
- alias_symbol.module_string(&env.interns),
- alias_symbol.as_str(&env.interns),
- label
- )
+ format!("roc__{}_{}_size", def_name, label)
} else {
- format!(
- "roc__{}_{}_{}_size",
- def_name,
- alias_symbol.module_string(&env.interns),
- alias_symbol.as_str(&env.interns)
- )
+ format!("roc__{}_size", def_name,)
};
let size_function = add_func(
@@ -5198,7 +5233,7 @@ fn build_host_exposed_alias_size_help<'a, 'ctx, 'env>(
builder.build_return(Some(&size));
}
-pub fn build_proc<'a, 'ctx, 'env>(
+fn build_proc<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
mod_solutions: &'a ModSolutions,
@@ -5219,17 +5254,18 @@ pub fn build_proc<'a, 'ctx, 'env>(
GenTest | WasmGenTest | CliTest => {
/* no host, or exposing types is not supported */
}
- Binary | BinaryDev => {
- for (alias_name, (generated_function, top_level, layout)) in aliases.iter() {
+ Binary | BinaryDev | BinaryGlue => {
+ for (alias_name, hels) in aliases.iter() {
+ let ident_string = proc.name.name().as_str(&env.interns);
+ let fn_name: String = format!("{}_{}", ident_string, hels.id.0);
+
expose_alias_to_host(
env,
layout_interner,
mod_solutions,
- proc.name,
+ &fn_name,
*alias_name,
- *generated_function,
- *top_level,
- *layout,
+ hels,
)
}
}
@@ -5550,7 +5586,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
basic_type_from_builtin(env, builtin)
}
Builtin::Str | Builtin::List(_) => {
- let address_space = AddressSpace::Generic;
+ let address_space = AddressSpace::default();
let field_types: [BasicTypeEnum; 3] = [
env.context.i8_type().ptr_type(address_space).into(),
env.ptr_int().into(),
@@ -5665,7 +5701,7 @@ impl<'ctx> FunctionSpec<'ctx> {
let (typ, opt_sret_parameter) = match cc_return {
CCReturn::ByPointer => {
// turn the output type into a pointer type. Make it the first argument to the function
- let output_type = return_type.unwrap().ptr_type(AddressSpace::Generic);
+ let output_type = return_type.unwrap().ptr_type(AddressSpace::default());
let mut arguments: Vec<'_, BasicTypeEnum> =
bumpalo::vec![in env.arena; output_type.into()];
@@ -5683,6 +5719,8 @@ impl<'ctx> FunctionSpec<'ctx> {
(return_type.unwrap().fn_type(&arguments, false), None)
}
CCReturn::Void => {
+ // NOTE: there may be a valid return type, but it is zero-sized.
+ // for instance just `{}` or something more complex like `{ { {}, {} }, {} }`
let arguments = function_arguments(env, argument_types);
(env.context.void_type().fn_type(&arguments, false), None)
}
@@ -5707,7 +5745,7 @@ impl<'ctx> FunctionSpec<'ctx> {
return_type.fn_type(&function_arguments(env, &argument_types), false)
}
RocReturn::ByPointer => {
- argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
+ argument_types.push(return_type.ptr_type(AddressSpace::default()).into());
env.context
.void_type()
.fn_type(&function_arguments(env, &argument_types), false)
@@ -5955,7 +5993,7 @@ fn define_global_str_literal_ptr<'a, 'ctx, 'env>(
let ptr = env.builder.build_pointer_cast(
global.as_pointer_value(),
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"to_opaque",
);
@@ -6090,7 +6128,7 @@ pub fn add_func<'ctx>(
) -> FunctionValue<'ctx> {
if cfg!(debug_assertions) {
if let Some(func) = module.get_function(name) {
- panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:?}", name, func);
+ panic!("Attempting to redefine LLVM function {}, which was already defined in this module as:\n\n{:#?}", name, func);
}
}
@@ -6100,23 +6138,3 @@ pub fn add_func<'ctx>(
fn_val
}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub(crate) enum WhenRecursive<'a> {
- Unreachable,
- Loop(UnionLayout<'a>),
-}
-
-impl<'a> WhenRecursive<'a> {
- pub fn unwrap_recursive_pointer(&self, layout: Layout<'a>) -> Layout<'a> {
- match layout {
- Layout::RecursivePointer(_) => match self {
- WhenRecursive::Loop(lay) => Layout::Union(*lay),
- WhenRecursive::Unreachable => {
- internal_error!("cannot compare recursive pointers outside of a structure")
- }
- },
- _ => layout,
- }
- }
-}
diff --git a/crates/compiler/gen_llvm/src/llvm/build_list.rs b/crates/compiler/gen_llvm/src/llvm/build_list.rs
index 33eb9e0c47..fd8ebfd0fe 100644
--- a/crates/compiler/gen_llvm/src/llvm/build_list.rs
+++ b/crates/compiler/gen_llvm/src/llvm/build_list.rs
@@ -74,7 +74,7 @@ fn pass_element_as_opaque<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
element_ptr,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"pass_element_as_opaque",
)
.into()
@@ -97,7 +97,7 @@ pub(crate) fn pass_as_opaque<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
ptr,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"pass_as_opaque",
)
.into()
@@ -133,7 +133,7 @@ pub(crate) fn list_get_unsafe<'a, 'ctx, 'env>(
let builder = env.builder;
let elem_type = basic_type_from_layout(env, layout_interner, element_layout);
- let ptr_type = elem_type.ptr_type(AddressSpace::Generic);
+ let ptr_type = elem_type.ptr_type(AddressSpace::default());
// Load the pointer to the array data
let array_data_ptr = load_list_ptr(builder, wrapper_struct, ptr_type);
@@ -183,6 +183,26 @@ pub(crate) fn list_reserve<'a, 'ctx, 'env>(
)
}
+/// List.releaseExcessCapacity : List elem -> List elem
+pub(crate) fn list_release_excess_capacity<'a, 'ctx, 'env>(
+ env: &Env<'a, 'ctx, 'env>,
+ layout_interner: &mut STLayoutInterner<'a>,
+ list: BasicValueEnum<'ctx>,
+ element_layout: InLayout<'a>,
+ update_mode: UpdateMode,
+) -> BasicValueEnum<'ctx> {
+ call_list_bitcode_fn_1(
+ env,
+ list.into_struct_value(),
+ &[
+ env.alignment_intvalue(layout_interner, element_layout),
+ layout_width(env, layout_interner, element_layout),
+ pass_update_mode(env, update_mode),
+ ],
+ bitcode::LIST_RELEASE_EXCESS_CAPACITY,
+ )
+}
+
/// List.appendUnsafe : List elem, elem -> List elem
pub(crate) fn list_append_unsafe<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@@ -393,17 +413,36 @@ pub(crate) fn list_len<'ctx>(
.into_int_value()
}
-/// List.capacity : List * -> Nat
-pub(crate) fn list_capacity<'ctx>(
+pub(crate) fn list_capacity_or_ref_ptr<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
) -> IntValue<'ctx> {
builder
- .build_extract_value(wrapper_struct, Builtin::WRAPPER_CAPACITY, "list_capacity")
+ .build_extract_value(
+ wrapper_struct,
+ Builtin::WRAPPER_CAPACITY,
+ "list_capacity_or_ref_ptr",
+ )
.unwrap()
.into_int_value()
}
+// Gets a pointer to just after the refcount for a list or seamless slice.
+// The value is just after the refcount so that normal lists and seamless slices can share code paths easily.
+pub(crate) fn list_refcount_ptr<'a, 'ctx, 'env>(
+ env: &Env<'a, 'ctx, 'env>,
+ wrapper_struct: StructValue<'ctx>,
+) -> PointerValue<'ctx> {
+ call_list_bitcode_fn(
+ env,
+ &[wrapper_struct],
+ &[],
+ BitcodeReturns::Basic,
+ bitcode::LIST_REFCOUNT_PTR,
+ )
+ .into_pointer_value()
+}
+
pub(crate) fn destructure<'ctx>(
builder: &Builder<'ctx>,
wrapper_struct: StructValue<'ctx>,
@@ -801,11 +840,7 @@ pub(crate) fn decref<'a, 'ctx, 'env>(
wrapper_struct: StructValue<'ctx>,
alignment: u32,
) {
- let (_, pointer) = load_list(
- env.builder,
- wrapper_struct,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
- );
+ let refcount_ptr = list_refcount_ptr(env, wrapper_struct);
- crate::llvm::refcounting::decref_pointer_check_null(env, pointer, alignment);
+ crate::llvm::refcounting::decref_pointer_check_null(env, refcount_ptr, alignment);
}
diff --git a/crates/compiler/gen_llvm/src/llvm/build_str.rs b/crates/compiler/gen_llvm/src/llvm/build_str.rs
index faebc9cac0..ab824c8b14 100644
--- a/crates/compiler/gen_llvm/src/llvm/build_str.rs
+++ b/crates/compiler/gen_llvm/src/llvm/build_str.rs
@@ -32,7 +32,7 @@ pub(crate) fn decode_from_utf8_result<'a, 'ctx, 'env>(
PtrWidth::Bytes4 | PtrWidth::Bytes8 => {
let result_ptr_cast = env.builder.build_pointer_cast(
pointer,
- record_type.ptr_type(AddressSpace::Generic),
+ record_type.ptr_type(AddressSpace::default()),
"to_unnamed",
);
@@ -63,3 +63,19 @@ pub(crate) fn str_equal<'a, 'ctx, 'env>(
bitcode::STR_EQUAL,
)
}
+
+// Gets a pointer to just after the refcount for a list or seamless slice.
+// The value is just after the refcount so that normal lists and seamless slices can share code paths easily.
+pub(crate) fn str_refcount_ptr<'a, 'ctx, 'env>(
+ env: &Env<'a, 'ctx, 'env>,
+ value: BasicValueEnum<'ctx>,
+) -> PointerValue<'ctx> {
+ call_str_bitcode_fn(
+ env,
+ &[value],
+ &[],
+ BitcodeReturns::Basic,
+ bitcode::STR_REFCOUNT_PTR,
+ )
+ .into_pointer_value()
+}
diff --git a/crates/compiler/gen_llvm/src/llvm/compare.rs b/crates/compiler/gen_llvm/src/llvm/compare.rs
index b284386fa7..77847130b3 100644
--- a/crates/compiler/gen_llvm/src/llvm/compare.rs
+++ b/crates/compiler/gen_llvm/src/llvm/compare.rs
@@ -1,17 +1,14 @@
-use crate::llvm::build::{
- get_tag_id, tag_pointer_clear_tag_id, Env, WhenRecursive, FAST_CALL_CONV,
-};
+use crate::llvm::build::{get_tag_id, tag_pointer_clear_tag_id, Env, FAST_CALL_CONV};
use crate::llvm::build_list::{list_len, load_list_ptr};
use crate::llvm::build_str::str_equal;
use crate::llvm::convert::basic_type_from_layout;
use bumpalo::collections::Vec;
use inkwell::types::BasicType;
-use inkwell::values::{
- BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
-};
+use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, FloatPredicate, IntPredicate};
use roc_builtins::bitcode;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
+use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::layout::{
Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner, UnionLayout,
@@ -38,7 +35,6 @@ pub fn generic_eq<'a, 'ctx, 'env>(
rhs_val,
lhs_layout,
rhs_layout,
- WhenRecursive::Unreachable,
)
}
@@ -59,7 +55,6 @@ pub fn generic_neq<'a, 'ctx, 'env>(
rhs_val,
lhs_layout,
rhs_layout,
- WhenRecursive::Unreachable,
)
}
@@ -69,8 +64,8 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
+ builtin_layout: InLayout<'a>,
builtin: &Builtin<'a>,
- when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let int_cmp = |pred, label| {
let int_val = env.builder.build_int_compare(
@@ -129,19 +124,15 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::Decimal => dec_binop_with_unchecked(env, bitcode::DEC_EQ, lhs_val, rhs_val),
Builtin::Str => str_equal(env, lhs_val, rhs_val),
- Builtin::List(elem) => {
- let list_layout = layout_interner.insert(Layout::Builtin(*builtin));
- build_list_eq(
- env,
- layout_interner,
- layout_ids,
- list_layout,
- *elem,
- lhs_val.into_struct_value(),
- rhs_val.into_struct_value(),
- when_recursive,
- )
- }
+ Builtin::List(elem) => build_list_eq(
+ env,
+ layout_interner,
+ layout_ids,
+ builtin_layout,
+ *elem,
+ lhs_val.into_struct_value(),
+ rhs_val.into_struct_value(),
+ ),
}
}
@@ -153,7 +144,6 @@ fn build_eq<'a, 'ctx, 'env>(
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: InLayout<'a>,
rhs_layout: InLayout<'a>,
- when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let lhs_layout = &layout_interner.runtime_representation_in(lhs_layout);
let rhs_layout = &layout_interner.runtime_representation_in(rhs_layout);
@@ -171,16 +161,16 @@ fn build_eq<'a, 'ctx, 'env>(
layout_ids,
lhs_val,
rhs_val,
+ *lhs_layout,
&builtin,
- when_recursive,
),
Layout::Struct { field_layouts, .. } => build_struct_eq(
env,
layout_interner,
layout_ids,
+ *lhs_layout,
field_layouts,
- when_recursive,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
),
@@ -191,7 +181,7 @@ fn build_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
+ *lhs_layout,
&union_layout,
lhs_val,
rhs_val,
@@ -201,47 +191,48 @@ fn build_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
*lhs_layout,
inner_layout,
lhs_val,
rhs_val,
),
- Layout::RecursivePointer(_) => match when_recursive {
- WhenRecursive::Unreachable => {
- unreachable!("recursion pointers should never be compared directly")
- }
+ Layout::RecursivePointer(rec_layout) => {
+ let layout = rec_layout;
- WhenRecursive::Loop(union_layout) => {
- let layout = layout_interner.insert(Layout::Union(union_layout));
+ let bt = basic_type_from_layout(env, layout_interner, layout);
- let bt = basic_type_from_layout(env, layout_interner, layout);
+ // cast the i64 pointer to a pointer to block of memory
+ let field1_cast = env.builder.build_pointer_cast(
+ lhs_val.into_pointer_value(),
+ bt.into_pointer_type(),
+ "i64_to_opaque",
+ );
- // cast the i64 pointer to a pointer to block of memory
- let field1_cast = env.builder.build_pointer_cast(
- lhs_val.into_pointer_value(),
- bt.into_pointer_type(),
- "i64_to_opaque",
- );
+ let field2_cast = env.builder.build_pointer_cast(
+ rhs_val.into_pointer_value(),
+ bt.into_pointer_type(),
+ "i64_to_opaque",
+ );
- let field2_cast = env.builder.build_pointer_cast(
- rhs_val.into_pointer_value(),
- bt.into_pointer_type(),
- "i64_to_opaque",
- );
+ let union_layout = match layout_interner.get(rec_layout) {
+ Layout::Union(union_layout) => {
+ debug_assert!(!matches!(union_layout, UnionLayout::NonRecursive(..)));
+ union_layout
+ }
+ _ => internal_error!(),
+ };
- build_tag_eq(
- env,
- layout_interner,
- layout_ids,
- WhenRecursive::Loop(union_layout),
- &union_layout,
- field1_cast.into(),
- field2_cast.into(),
- )
- }
- },
+ build_tag_eq(
+ env,
+ layout_interner,
+ layout_ids,
+ rec_layout,
+ &union_layout,
+ field1_cast.into(),
+ field2_cast.into(),
+ )
+ }
}
}
@@ -251,8 +242,8 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
lhs_val: BasicValueEnum<'ctx>,
rhs_val: BasicValueEnum<'ctx>,
+ builtin_layout: InLayout<'a>,
builtin: &Builtin<'a>,
- when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let int_cmp = |pred, label| {
let int_val = env.builder.build_int_compare(
@@ -317,7 +308,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
result.into()
}
Builtin::List(elem) => {
- let builtin_layout = layout_interner.insert(Layout::Builtin(*builtin));
let is_equal = build_list_eq(
env,
layout_interner,
@@ -326,7 +316,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
*elem,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
- when_recursive,
)
.into_int_value();
@@ -345,7 +334,6 @@ fn build_neq<'a, 'ctx, 'env>(
rhs_val: BasicValueEnum<'ctx>,
lhs_layout: InLayout<'a>,
rhs_layout: InLayout<'a>,
- when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
if lhs_layout != rhs_layout {
panic!(
@@ -361,8 +349,8 @@ fn build_neq<'a, 'ctx, 'env>(
layout_ids,
lhs_val,
rhs_val,
+ lhs_layout,
&builtin,
- when_recursive,
),
Layout::Struct { field_layouts, .. } => {
@@ -370,8 +358,8 @@ fn build_neq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
+ lhs_layout,
field_layouts,
- when_recursive,
lhs_val.into_struct_value(),
rhs_val.into_struct_value(),
)
@@ -387,7 +375,7 @@ fn build_neq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
+ lhs_layout,
&union_layout,
lhs_val,
rhs_val,
@@ -404,7 +392,6 @@ fn build_neq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
lhs_layout,
inner_layout,
lhs_val,
@@ -432,15 +419,17 @@ fn build_list_eq<'a, 'ctx, 'env>(
element_layout: InLayout<'a>,
list1: StructValue<'ctx>,
list2: StructValue<'ctx>,
- when_recursive: WhenRecursive<'a>,
) -> BasicValueEnum<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::LIST_EQ;
- let element_layout = layout_interner.get(element_layout);
- let element_layout = when_recursive.unwrap_recursive_pointer(element_layout);
- let element_layout = layout_interner.insert(element_layout);
+ let element_layout = if let Layout::RecursivePointer(rec) = layout_interner.get(element_layout)
+ {
+ rec
+ } else {
+ element_layout
+ };
let fn_name = layout_ids
.get(symbol, &element_layout)
.to_symbol_string(symbol, &env.interns);
@@ -461,7 +450,6 @@ fn build_list_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
function_value,
element_layout,
);
@@ -471,8 +459,7 @@ fn build_list_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[list1.into(), list2.into()], "list_eq");
@@ -486,7 +473,6 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- when_recursive: WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
element_layout: InLayout<'a>,
) {
@@ -511,7 +497,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
- builder.set_current_debug_location(ctx, loc);
+ builder.set_current_debug_location(loc);
}
// Add args to scope
@@ -548,7 +534,7 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
let builder = env.builder;
let element_type = basic_type_from_layout(env, layout_interner, element_layout);
- let ptr_type = element_type.ptr_type(AddressSpace::Generic);
+ let ptr_type = element_type.ptr_type(AddressSpace::default());
let ptr1 = load_list_ptr(env.builder, list1, ptr_type);
let ptr2 = load_list_ptr(env.builder, list2, ptr_type);
@@ -605,7 +591,6 @@ fn build_list_eq_help<'a, 'ctx, 'env>(
elem2,
element_layout,
element_layout,
- when_recursive,
)
.into_int_value();
@@ -646,16 +631,14 @@ fn build_struct_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
+ struct_layout: InLayout<'a>,
field_layouts: &'a [InLayout<'a>],
- when_recursive: WhenRecursive<'a>,
struct1: StructValue<'ctx>,
struct2: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
- let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
-
let symbol = Symbol::GENERIC_EQ;
let fn_name = layout_ids
.get(symbol, &struct_layout)
@@ -678,7 +661,6 @@ fn build_struct_eq<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
function_value,
- when_recursive,
field_layouts,
);
@@ -687,8 +669,7 @@ fn build_struct_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[struct1.into(), struct2.into()], "struct_eq");
@@ -703,7 +684,6 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
parent: FunctionValue<'ctx>,
- when_recursive: WhenRecursive<'a>,
field_layouts: &[InLayout<'a>],
) {
let ctx = env.context;
@@ -727,7 +707,7 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
- builder.set_current_debug_location(ctx, loc);
+ builder.set_current_debug_location(loc);
}
// Add args to scope
@@ -761,42 +741,40 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
.build_extract_value(struct2, index as u32, "eq_field")
.unwrap();
- let are_equal = if let Layout::RecursivePointer(_) = layout_interner.get(*field_layout) {
- match &when_recursive {
- WhenRecursive::Unreachable => {
- unreachable!("The current layout should not be recursive, but is")
- }
- WhenRecursive::Loop(union_layout) => {
- let field_layout = layout_interner.insert(Layout::Union(*union_layout));
+ let are_equal = if let Layout::RecursivePointer(rec_layout) =
+ layout_interner.get(*field_layout)
+ {
+ debug_assert!(
+ matches!(layout_interner.get(rec_layout), Layout::Union(union_layout) if !matches!(union_layout, UnionLayout::NonRecursive(..)))
+ );
- let bt = basic_type_from_layout(env, layout_interner, field_layout);
+ let field_layout = rec_layout;
- // cast the i64 pointer to a pointer to block of memory
- let field1_cast = env.builder.build_pointer_cast(
- field1.into_pointer_value(),
- bt.into_pointer_type(),
- "i64_to_opaque",
- );
+ let bt = basic_type_from_layout(env, layout_interner, field_layout);
- let field2_cast = env.builder.build_pointer_cast(
- field2.into_pointer_value(),
- bt.into_pointer_type(),
- "i64_to_opaque",
- );
+ // cast the i64 pointer to a pointer to block of memory
+ let field1_cast = env.builder.build_pointer_cast(
+ field1.into_pointer_value(),
+ bt.into_pointer_type(),
+ "i64_to_opaque",
+ );
- build_eq(
- env,
- layout_interner,
- layout_ids,
- field1_cast.into(),
- field2_cast.into(),
- field_layout,
- field_layout,
- WhenRecursive::Loop(*union_layout),
- )
- .into_int_value()
- }
- }
+ let field2_cast = env.builder.build_pointer_cast(
+ field2.into_pointer_value(),
+ bt.into_pointer_type(),
+ "i64_to_opaque",
+ );
+
+ build_eq(
+ env,
+ layout_interner,
+ layout_ids,
+ field1_cast.into(),
+ field2_cast.into(),
+ field_layout,
+ field_layout,
+ )
+ .into_int_value()
} else {
let lhs = use_roc_value(env, layout_interner, *field_layout, field1, "field1");
let rhs = use_roc_value(env, layout_interner, *field_layout, field2, "field2");
@@ -808,7 +786,6 @@ fn build_struct_eq_help<'a, 'ctx, 'env>(
rhs,
*field_layout,
*field_layout,
- when_recursive,
)
.into_int_value()
};
@@ -839,7 +816,7 @@ fn build_tag_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- when_recursive: WhenRecursive<'a>,
+ tag_layout: InLayout<'a>,
union_layout: &UnionLayout<'a>,
tag1: BasicValueEnum<'ctx>,
tag2: BasicValueEnum<'ctx>,
@@ -847,7 +824,6 @@ fn build_tag_eq<'a, 'ctx, 'env>(
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
- let tag_layout = layout_interner.insert(Layout::Union(*union_layout));
let symbol = Symbol::GENERIC_EQ;
let fn_name = layout_ids
.get(symbol, &tag_layout)
@@ -869,7 +845,6 @@ fn build_tag_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
function_value,
union_layout,
);
@@ -879,8 +854,7 @@ fn build_tag_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[tag1.into(), tag2.into()], "tag_eq");
@@ -894,7 +868,6 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- when_recursive: WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
union_layout: &UnionLayout<'a>,
) {
@@ -919,7 +892,7 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
- builder.set_current_debug_location(ctx, loc);
+ builder.set_current_debug_location(loc);
}
// Add args to scope
@@ -999,12 +972,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
+ let struct_layout =
+ layout_interner.insert(Layout::struct_no_name_order(field_layouts));
+
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
- union_layout,
- Some(when_recursive),
+ struct_layout,
field_layouts,
tag1,
tag2,
@@ -1070,12 +1045,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
+ let struct_layout =
+ layout_interner.insert(Layout::struct_no_name_order(field_layouts));
+
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
- union_layout,
- None,
+ struct_layout,
field_layouts,
tag1,
tag2,
@@ -1131,12 +1108,13 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.position_at_end(compare_other);
+ let struct_layout = layout_interner.insert(Layout::struct_no_name_order(other_fields));
+
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
- union_layout,
- None,
+ struct_layout,
other_fields,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
@@ -1229,12 +1207,14 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
+ let struct_layout =
+ layout_interner.insert(Layout::struct_no_name_order(field_layouts));
+
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
- union_layout,
- None,
+ struct_layout,
field_layouts,
tag1,
tag2,
@@ -1268,12 +1248,13 @@ fn build_tag_eq_help<'a, 'ctx, 'env>(
env.builder.position_at_end(compare_fields);
+ let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
+
let answer = eq_ptr_to_struct(
env,
layout_interner,
layout_ids,
- union_layout,
- None,
+ struct_layout,
field_layouts,
tag1.into_pointer_value(),
tag2.into_pointer_value(),
@@ -1288,27 +1269,24 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- union_layout: &UnionLayout<'a>,
- opt_when_recursive: Option>,
+ struct_layout: InLayout<'a>,
field_layouts: &'a [InLayout<'a>],
tag1: PointerValue<'ctx>,
tag2: PointerValue<'ctx>,
) -> IntValue<'ctx> {
- let struct_layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
-
let wrapper_type = basic_type_from_layout(env, layout_interner, struct_layout);
debug_assert!(wrapper_type.is_struct_type());
// cast the opaque pointer to a pointer of the correct shape
let struct1_ptr = env.builder.build_pointer_cast(
tag1,
- wrapper_type.ptr_type(AddressSpace::Generic),
+ wrapper_type.ptr_type(AddressSpace::default()),
"opaque_to_correct",
);
let struct2_ptr = env.builder.build_pointer_cast(
tag2,
- wrapper_type.ptr_type(AddressSpace::Generic),
+ wrapper_type.ptr_type(AddressSpace::default()),
"opaque_to_correct",
);
@@ -1326,8 +1304,8 @@ fn eq_ptr_to_struct<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
+ struct_layout,
field_layouts,
- opt_when_recursive.unwrap_or(WhenRecursive::Loop(*union_layout)),
struct1,
struct2,
)
@@ -1340,7 +1318,6 @@ fn build_box_eq<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- when_recursive: WhenRecursive<'a>,
box_layout: InLayout<'a>,
inner_layout: InLayout<'a>,
tag1: BasicValueEnum<'ctx>,
@@ -1370,7 +1347,6 @@ fn build_box_eq<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
function_value,
inner_layout,
);
@@ -1380,8 +1356,7 @@ fn build_box_eq<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
let call = env
.builder
.build_call(function, &[tag1.into(), tag2.into()], "tag_eq");
@@ -1395,7 +1370,6 @@ fn build_box_eq_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- when_recursive: WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
inner_layout: InLayout<'a>,
) {
@@ -1420,7 +1394,7 @@ fn build_box_eq_help<'a, 'ctx, 'env>(
/* current_scope */ lexical_block.as_debug_info_scope(),
/* inlined_at */ None,
);
- builder.set_current_debug_location(ctx, loc);
+ builder.set_current_debug_location(loc);
}
// Add args to scope
@@ -1472,7 +1446,6 @@ fn build_box_eq_help<'a, 'ctx, 'env>(
value2,
inner_layout,
inner_layout,
- when_recursive,
);
env.builder.build_return(Some(&is_equal));
diff --git a/crates/compiler/gen_llvm/src/llvm/convert.rs b/crates/compiler/gen_llvm/src/llvm/convert.rs
index 7e9df11233..150b5bc8e8 100644
--- a/crates/compiler/gen_llvm/src/llvm/convert.rs
+++ b/crates/compiler/gen_llvm/src/llvm/convert.rs
@@ -18,7 +18,9 @@ fn basic_type_from_record<'a, 'ctx, 'env>(
let mut field_types = Vec::with_capacity_in(fields.len(), env.arena);
for field_layout in fields.iter() {
- field_types.push(basic_type_from_layout(env, layout_interner, *field_layout));
+ let typ = basic_type_from_layout(env, layout_interner, *field_layout);
+
+ field_types.push(typ);
}
env.context
@@ -44,13 +46,13 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
Boxed(inner_layout) => {
let inner_type = basic_type_from_layout(env, layout_interner, inner_layout);
- inner_type.ptr_type(AddressSpace::Generic).into()
+ inner_type.ptr_type(AddressSpace::default()).into()
}
Union(union_layout) => basic_type_from_union_layout(env, layout_interner, &union_layout),
RecursivePointer(_) => env
.context
.i64_type()
- .ptr_type(AddressSpace::Generic)
+ .ptr_type(AddressSpace::default())
.as_basic_type_enum(),
Builtin(builtin) => basic_type_from_builtin(env, &builtin),
@@ -65,6 +67,7 @@ pub fn struct_type_from_union_layout<'a, 'ctx, 'env>(
use UnionLayout::*;
match union_layout {
+ NonRecursive([]) => env.context.struct_type(&[], false),
NonRecursive(tags) => {
RocUnion::tagged_from_slices(layout_interner, env.context, tags, env.target_info)
.struct_type()
@@ -109,7 +112,7 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
Recursive(_)
| NonNullableUnwrapped(_)
| NullableWrapped { .. }
- | NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::Generic).into(),
+ | NullableUnwrapped { .. } => struct_type.ptr_type(AddressSpace::default()).into(),
}
}
@@ -158,7 +161,7 @@ pub fn argument_type_from_layout<'a, 'ctx, 'env>(
let base = basic_type_from_layout(env, layout_interner, layout);
if layout_interner.is_passed_by_reference(layout) {
- base.ptr_type(AddressSpace::Generic).into()
+ base.ptr_type(AddressSpace::default()).into()
} else {
base
}
@@ -176,7 +179,7 @@ pub fn argument_type_from_union_layout<'a, 'ctx, 'env>(
let heap_type = basic_type_from_union_layout(env, layout_interner, union_layout);
if let UnionLayout::NonRecursive(_) = union_layout {
- heap_type.ptr_type(AddressSpace::Generic).into()
+ heap_type.ptr_type(AddressSpace::default()).into()
} else {
heap_type
}
@@ -300,7 +303,8 @@ impl<'ctx> RocUnion<'ctx> {
target_info: TargetInfo,
) -> Self {
let tag_type = match layouts.len() {
- 0..=255 => TagType::I8,
+ 0 => unreachable!("zero-element tag union is not represented as a RocUnion"),
+ 1..=255 => TagType::I8,
_ => TagType::I16,
};
@@ -322,6 +326,10 @@ impl<'ctx> RocUnion<'ctx> {
Self::new(context, target_info, data_align, data_width, None)
}
+ pub fn data_width(&self) -> u32 {
+ self.data_width
+ }
+
pub fn tag_alignment(&self) -> u32 {
let tag_id_alignment = match self.tag_type {
None => 0,
@@ -375,7 +383,7 @@ impl<'ctx> RocUnion<'ctx> {
let cast_pointer = env.builder.build_pointer_cast(
data_buffer,
- data.get_type().ptr_type(AddressSpace::Generic),
+ data.get_type().ptr_type(AddressSpace::default()),
"to_data_ptr",
);
@@ -436,7 +444,7 @@ pub fn zig_dec_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ct
}
pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
- let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::Generic);
+ let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::default());
env.context
.struct_type(&[env.context.bool_type().into(), u8_ptr_t.into()], false)
diff --git a/crates/compiler/gen_llvm/src/llvm/expect.rs b/crates/compiler/gen_llvm/src/llvm/expect.rs
index 28d6a343d6..29ff21942d 100644
--- a/crates/compiler/gen_llvm/src/llvm/expect.rs
+++ b/crates/compiler/gen_llvm/src/llvm/expect.rs
@@ -9,6 +9,7 @@ use inkwell::types::{BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
+use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_mono::ir::LookupType;
use roc_mono::layout::{
@@ -19,7 +20,7 @@ use roc_region::all::Region;
use super::build::BuilderExt;
use super::build::{
add_func, load_roc_value, load_symbol_and_layout, use_roc_value, FunctionSpec, LlvmBackendMode,
- Scope, WhenRecursive,
+ Scope,
};
use super::convert::struct_type_from_union_layout;
@@ -98,7 +99,7 @@ fn read_state<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
ptr: PointerValue<'ctx>,
) -> (IntValue<'ctx>, IntValue<'ctx>) {
- let ptr_type = env.ptr_int().ptr_type(AddressSpace::Generic);
+ let ptr_type = env.ptr_int().ptr_type(AddressSpace::default());
let ptr = env.builder.build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
@@ -118,7 +119,7 @@ fn write_state<'a, 'ctx, 'env>(
count: IntValue<'ctx>,
offset: IntValue<'ctx>,
) {
- let ptr_type = env.ptr_int().ptr_type(AddressSpace::Generic);
+ let ptr_type = env.ptr_int().ptr_type(AddressSpace::default());
let ptr = env.builder.build_pointer_cast(ptr, ptr_type, "");
let one = env.ptr_int().const_int(1, false);
@@ -128,6 +129,15 @@ fn write_state<'a, 'ctx, 'env>(
env.builder.build_store(offset_ptr, offset);
}
+fn offset_add<'ctx>(
+ builder: &Builder<'ctx>,
+ current: IntValue<'ctx>,
+ extra: u32,
+) -> IntValue<'ctx> {
+ let intval = current.get_type().const_int(extra as _, false);
+ builder.build_int_add(current, intval, "offset_add")
+}
+
pub(crate) fn notify_parent_expect(env: &Env, shared_memory: &SharedMemoryPointer) {
let func = env
.module
@@ -220,7 +230,6 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
cursors,
value,
layout,
- WhenRecursive::Unreachable,
);
offset = extra_offset;
@@ -252,7 +261,7 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
)
};
- let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::Generic);
+ let u32_ptr = env.context.i32_type().ptr_type(AddressSpace::default());
let ptr = env
.builder
.build_pointer_cast(ptr, u32_ptr, "cast_ptr_type");
@@ -286,7 +295,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
layout: InLayout<'a>,
- when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
match layout_interner.get(layout) {
Layout::Builtin(builtin) => build_clone_builtin(
@@ -297,7 +305,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
builtin,
- when_recursive,
),
Layout::Struct { field_layouts, .. } => build_clone_struct(
@@ -308,7 +315,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
field_layouts,
- when_recursive,
),
// Since we will never actually display functions (and hence lambda sets)
@@ -326,7 +332,7 @@ fn build_clone<'a, 'ctx, 'env>(
)
};
- let ptr_type = value.get_type().ptr_type(AddressSpace::Generic);
+ let ptr_type = value.get_type().ptr_type(AddressSpace::default());
let ptr = env
.builder
.build_pointer_cast(ptr, ptr_type, "cast_ptr_type");
@@ -343,7 +349,6 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
union_layout,
- WhenRecursive::Loop(union_layout),
)
}
}
@@ -376,39 +381,39 @@ fn build_clone<'a, 'ctx, 'env>(
cursors,
value,
inner_layout,
- when_recursive,
)
}
- Layout::RecursivePointer(_) => match when_recursive {
- WhenRecursive::Unreachable => {
- unreachable!("recursion pointers should never be compared directly")
- }
+ Layout::RecursivePointer(rec_layout) => {
+ let layout = rec_layout;
- WhenRecursive::Loop(union_layout) => {
- let layout = layout_interner.insert(Layout::Union(union_layout));
+ let bt = basic_type_from_layout(env, layout_interner, layout);
- let bt = basic_type_from_layout(env, layout_interner, layout);
+ // cast the i64 pointer to a pointer to block of memory
+ let field1_cast = env.builder.build_pointer_cast(
+ value.into_pointer_value(),
+ bt.into_pointer_type(),
+ "i64_to_opaque",
+ );
- // cast the i64 pointer to a pointer to block of memory
- let field1_cast = env.builder.build_pointer_cast(
- value.into_pointer_value(),
- bt.into_pointer_type(),
- "i64_to_opaque",
- );
+ let union_layout = match layout_interner.get(rec_layout) {
+ Layout::Union(union_layout) => {
+ debug_assert!(!matches!(union_layout, UnionLayout::NonRecursive(..)));
+ union_layout
+ }
+ _ => internal_error!(),
+ };
- build_clone_tag(
- env,
- layout_interner,
- layout_ids,
- ptr,
- cursors,
- field1_cast.into(),
- union_layout,
- WhenRecursive::Loop(union_layout),
- )
- }
- },
+ build_clone_tag(
+ env,
+ layout_interner,
+ layout_ids,
+ ptr,
+ cursors,
+ field1_cast.into(),
+ union_layout,
+ )
+ }
}
}
@@ -420,7 +425,6 @@ fn build_clone_struct<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
field_layouts: &[InLayout<'a>],
- when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
let layout = Layout::struct_no_name_order(field_layouts);
@@ -447,7 +451,6 @@ fn build_clone_struct<'a, 'ctx, 'env>(
cursors,
field,
*field_layout,
- when_recursive,
);
let field_width = env
@@ -472,7 +475,6 @@ fn build_clone_tag<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
union_layout: UnionLayout<'a>,
- when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
let layout = layout_interner.insert(Layout::Union(union_layout));
let layout_id = layout_ids.get(Symbol::CLONE, &layout);
@@ -486,7 +488,10 @@ fn build_clone_tag<'a, 'ctx, 'env>(
let function_type = env.ptr_int().fn_type(
&[
- env.context.i8_type().ptr_type(AddressSpace::Generic).into(),
+ env.context
+ .i8_type()
+ .ptr_type(AddressSpace::default())
+ .into(),
env.ptr_int().into(),
env.ptr_int().into(),
BasicMetadataTypeEnum::from(value.get_type()),
@@ -512,13 +517,11 @@ fn build_clone_tag<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
union_layout,
- when_recursive,
function_value,
);
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function_value
}
@@ -563,19 +566,68 @@ fn load_tag_data<'a, 'ctx, 'env>(
let data_ptr = env.builder.build_pointer_cast(
raw_data_ptr,
- tag_type.ptr_type(AddressSpace::Generic),
+ tag_type.ptr_type(AddressSpace::default()),
"data_ptr",
);
env.builder.new_build_load(tag_type, data_ptr, "load_data")
}
+fn clone_tag_payload_and_id<'a, 'ctx, 'env>(
+ env: &Env<'a, 'ctx, 'env>,
+ layout_interner: &mut STLayoutInterner<'a>,
+ layout_ids: &mut LayoutIds<'a>,
+ ptr: PointerValue<'ctx>,
+ cursors: Cursors<'ctx>,
+ roc_union: RocUnion<'ctx>,
+ tag_id: usize,
+ payload_in_layout: InLayout<'a>,
+ opaque_payload_ptr: PointerValue<'ctx>,
+) -> IntValue<'ctx> {
+ let payload_type = basic_type_from_layout(env, layout_interner, payload_in_layout);
+
+ let payload_ptr = env.builder.build_pointer_cast(
+ opaque_payload_ptr,
+ payload_type.ptr_type(AddressSpace::default()),
+ "cast_payload_ptr",
+ );
+
+ let payload = env
+ .builder
+ .new_build_load(payload_type, payload_ptr, "payload");
+
+ // NOTE: `answer` includes any extra_offset that the tag payload may have needed
+ // (e.g. because it includes a list). That is what we want to return, but not what
+ // we need to write the padding and offset of this tag
+ let answer = build_clone(
+ env,
+ layout_interner,
+ layout_ids,
+ ptr,
+ cursors,
+ payload,
+ payload_in_layout,
+ );
+
+ // include padding between data and tag id
+ let tag_id_internal_offset = roc_union.data_width();
+
+ let tag_id_offset = offset_add(env.builder, cursors.offset, tag_id_internal_offset);
+
+ // write the tag id
+ let value = env.context.i8_type().const_int(tag_id as _, false);
+ build_copy(env, ptr, tag_id_offset, value.into());
+
+ // NOTE: padding after tag id (is taken care of by the cursor)
+
+ answer
+}
+
fn build_clone_tag_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
union_layout: UnionLayout<'a>,
- when_recursive: WhenRecursive<'a>,
fn_val: FunctionValue<'ctx>,
) {
use bumpalo::collections::Vec;
@@ -629,29 +681,37 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let block = env.context.append_basic_block(parent, "tag_id_modify");
env.builder.position_at_end(block);
- let layout = layout_interner.insert(Layout::struct_no_name_order(field_layouts));
- let layout = layout_interner.insert(Layout::struct_no_name_order(
- env.arena.alloc([layout, union_layout.tag_id_layout()]),
- ));
-
- let basic_type = basic_type_from_layout(env, layout_interner, layout);
- let data = load_tag_data(
- env,
+ let roc_union = RocUnion::tagged_from_slices(
layout_interner,
- union_layout,
- tag_value.into_pointer_value(),
- basic_type,
+ env.context,
+ tags,
+ env.target_info,
);
- let answer = build_clone(
+ // load the tag payload (if any)
+ let payload_layout = Layout::struct_no_name_order(field_layouts);
+ let payload_in_layout = layout_interner.insert(payload_layout);
+
+ let opaque_payload_ptr = env
+ .builder
+ .new_build_struct_gep(
+ roc_union.struct_type(),
+ tag_value.into_pointer_value(),
+ RocUnion::TAG_DATA_INDEX,
+ "data_buffer",
+ )
+ .unwrap();
+
+ let answer = clone_tag_payload_and_id(
env,
layout_interner,
layout_ids,
ptr,
cursors,
- data,
- layout,
- when_recursive,
+ roc_union,
+ tag_id,
+ payload_in_layout,
+ opaque_payload_ptr,
);
env.builder.build_return(Some(&answer));
@@ -712,17 +772,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
),
};
- let when_recursive = WhenRecursive::Loop(union_layout);
- let answer = build_clone(
- env,
- layout_interner,
- layout_ids,
- ptr,
- cursors,
- data,
- layout,
- when_recursive,
- );
+ let answer =
+ build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
@@ -762,17 +813,7 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let data = load_tag_data(env, layout_interner, union_layout, tag_value, basic_type);
- let when_recursive = WhenRecursive::Loop(union_layout);
- let answer = build_clone(
- env,
- layout_interner,
- layout_ids,
- ptr,
- cursors,
- data,
- layout,
- when_recursive,
- );
+ let answer = build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
}
@@ -831,17 +872,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let data =
load_tag_data(env, layout_interner, union_layout, tag_value, basic_type);
- let when_recursive = WhenRecursive::Loop(union_layout);
- let answer = build_clone(
- env,
- layout_interner,
- layout_ids,
- ptr,
- cursors,
- data,
- layout,
- when_recursive,
- );
+ let answer =
+ build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
@@ -917,17 +949,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
basic_type,
);
- let when_recursive = WhenRecursive::Loop(union_layout);
- let answer = build_clone(
- env,
- layout_interner,
- layout_ids,
- ptr,
- cursors,
- data,
- layout,
- when_recursive,
- );
+ let answer =
+ build_clone(env, layout_interner, layout_ids, ptr, cursors, data, layout);
env.builder.build_return(Some(&answer));
}
@@ -978,7 +1001,7 @@ fn build_copy<'a, 'ctx, 'env>(
)
};
- let ptr_type = value.get_type().ptr_type(AddressSpace::Generic);
+ let ptr_type = value.get_type().ptr_type(AddressSpace::default());
let ptr = env
.builder
.build_pointer_cast(ptr, ptr_type, "cast_ptr_type");
@@ -997,7 +1020,6 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
cursors: Cursors<'ctx>,
value: BasicValueEnum<'ctx>,
builtin: Builtin<'a>,
- when_recursive: WhenRecursive<'a>,
) -> IntValue<'ctx> {
use Builtin::*;
@@ -1051,7 +1073,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
let dest = pointer_at_offset(bd, env.context.i8_type(), ptr, elements_start_offset);
let src = bd.build_pointer_cast(
elements,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"to_bytes_pointer",
);
bd.build_memcpy(dest, 1, src, 1, elements_width).unwrap();
@@ -1061,7 +1083,7 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
let element_type = basic_type_from_layout(env, layout_interner, elem);
let elements = bd.build_pointer_cast(
elements,
- element_type.ptr_type(AddressSpace::Generic),
+ element_type.ptr_type(AddressSpace::default()),
"elements",
);
@@ -1102,7 +1124,6 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
cursors,
element,
elem,
- when_recursive,
);
bd.build_store(rest_offset, new_offset);
diff --git a/crates/compiler/gen_llvm/src/llvm/externs.rs b/crates/compiler/gen_llvm/src/llvm/externs.rs
index 0659461b9c..6bf96f83d3 100644
--- a/crates/compiler/gen_llvm/src/llvm/externs.rs
+++ b/crates/compiler/gen_llvm/src/llvm/externs.rs
@@ -4,7 +4,6 @@ use crate::llvm::build::{CCReturn, Env, FunctionSpec};
use crate::llvm::convert::zig_str_type;
use inkwell::module::Linkage;
use inkwell::types::BasicType;
-use inkwell::values::BasicValue;
use inkwell::AddressSpace;
use roc_builtins::bitcode;
@@ -19,7 +18,7 @@ pub fn add_default_roc_externs(env: &Env<'_, '_, '_>) {
let builder = env.builder;
let usize_type = env.ptr_int();
- let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
+ let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::default());
match env.mode {
super::build::LlvmBackendMode::CliTest => {
@@ -198,6 +197,11 @@ pub fn add_sjlj_roc_panic(env: &Env<'_, '_, '_>) {
let mut params = fn_val.get_param_iter();
let roc_str_arg = params.next().unwrap();
+ // normally, roc_panic is marked as external so it can be provided by the host. But when we
+ // define it here in LLVM IR, we never want it to be linked by the host (that would
+ // overwrite which implementation is used.
+ fn_val.set_linkage(Linkage::Internal);
+
let tag_id_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
@@ -271,7 +275,7 @@ pub fn build_longjmp_call(env: &Env) {
// Call the LLVM-intrinsic longjmp: `void @llvm.eh.sjlj.longjmp(i8* %setjmp_buf)`
let jmp_buf_i8p = env.builder.build_pointer_cast(
jmp_buf,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"jmp_buf i8*",
);
let _call = env.build_intrinsic_call(LLVM_LONGJMP, &[jmp_buf_i8p.into()]);
diff --git a/crates/compiler/gen_llvm/src/llvm/intrinsics.rs b/crates/compiler/gen_llvm/src/llvm/intrinsics.rs
index 94de9dfc5d..6c68f29889 100644
--- a/crates/compiler/gen_llvm/src/llvm/intrinsics.rs
+++ b/crates/compiler/gen_llvm/src/llvm/intrinsics.rs
@@ -75,7 +75,7 @@ pub(crate) fn add_intrinsics<'ctx>(ctx: &'ctx Context, module: &Module<'ctx>) {
// https://releases.llvm.org/10.0.0/docs/LangRef.html#standard-c-library-intrinsics
let i1_type = ctx.bool_type();
let i8_type = ctx.i8_type();
- let i8_ptr_type = i8_type.ptr_type(AddressSpace::Generic);
+ let i8_ptr_type = i8_type.ptr_type(AddressSpace::default());
let i32_type = ctx.i32_type();
let void_type = ctx.void_type();
diff --git a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs
index c71b07554f..e7c115e003 100644
--- a/crates/compiler/gen_llvm/src/llvm/lowlevel.rs
+++ b/crates/compiler/gen_llvm/src/llvm/lowlevel.rs
@@ -13,6 +13,7 @@ use roc_module::{low_level::LowLevel, symbol::Symbol};
use roc_mono::{
ir::HigherOrderLowLevel,
layout::{Builtin, InLayout, LambdaSet, Layout, LayoutIds, LayoutInterner, STLayoutInterner},
+ list_element_layout,
};
use roc_target::PtrWidth;
@@ -27,10 +28,10 @@ use crate::llvm::{
load_roc_value, roc_function_call, BuilderExt, RocReturn,
},
build_list::{
- list_append_unsafe, list_capacity, list_concat, list_drop_at, list_get_unsafe, list_len,
- list_map, list_map2, list_map3, list_map4, list_prepend, list_replace_unsafe, list_reserve,
- list_sort_with, list_sublist, list_swap, list_symbol_to_c_abi, list_with_capacity,
- pass_update_mode,
+ list_append_unsafe, list_concat, list_drop_at, list_get_unsafe, list_len, list_map,
+ list_map2, list_map3, list_map4, list_prepend, list_release_excess_capacity,
+ list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
+ list_symbol_to_c_abi, list_with_capacity, pass_update_mode,
},
compare::{generic_eq, generic_neq},
convert::{
@@ -49,15 +50,6 @@ use super::{
convert::zig_dec_type,
};
-macro_rules! list_element_layout {
- ($interner:expr, $list_layout:expr) => {
- match $interner.get($list_layout) {
- Layout::Builtin(Builtin::List(list_layout)) => list_layout,
- _ => unreachable!("invalid list layout"),
- }
- };
-}
-
pub(crate) fn run_low_level<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
@@ -256,7 +248,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
let roc_return_type =
basic_type_from_layout(env, layout_interner, layout)
- .ptr_type(AddressSpace::Generic);
+ .ptr_type(AddressSpace::default());
let roc_return_alloca = env.builder.build_pointer_cast(
zig_return_alloca,
@@ -497,7 +489,7 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
let return_type = basic_type_from_layout(env, layout_interner, layout);
let cast_result = env.builder.build_pointer_cast(
result,
- return_type.ptr_type(AddressSpace::Generic),
+ return_type.ptr_type(AddressSpace::default()),
"cast",
);
@@ -567,6 +559,18 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
bitcode::STR_RESERVE,
)
}
+ StrReleaseExcessCapacity => {
+ // Str.releaseExcessCapacity: Str -> Str
+ arguments!(string);
+
+ call_str_bitcode_fn(
+ env,
+ &[string],
+ &[],
+ BitcodeReturns::Str,
+ bitcode::STR_RELEASE_EXCESS_CAPACITY,
+ )
+ }
StrAppendScalar => {
// Str.appendScalar : Str, U32 -> Str
arguments!(string, capacity);
@@ -640,10 +644,16 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
list_len(env.builder, list.into_struct_value()).into()
}
ListGetCapacity => {
- // List.capacity : List * -> Nat
+ // List.capacity: List a -> Nat
arguments!(list);
- list_capacity(env.builder, list.into_struct_value()).into()
+ call_list_bitcode_fn(
+ env,
+ &[list.into_struct_value()],
+ &[],
+ BitcodeReturns::Basic,
+ bitcode::LIST_CAPACITY,
+ )
}
ListWithCapacity => {
// List.withCapacity : Nat -> List a
@@ -709,6 +719,15 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
update_mode,
)
}
+ ListReleaseExcessCapacity => {
+ // List.releaseExcessCapacity: List elem -> List elem
+ debug_assert_eq!(args.len(), 1);
+
+ let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
+ let element_layout = list_element_layout!(layout_interner, list_layout);
+
+ list_release_excess_capacity(env, layout_interner, list, element_layout, update_mode)
+ }
ListSwap => {
// List.swap : List elem, Nat, Nat -> List elem
debug_assert_eq!(args.len(), 3);
@@ -856,9 +875,24 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
_ => unreachable!(),
}
}
- NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
- | NumCeiling | NumFloor | NumToFrac | NumIsFinite | NumAtan | NumAcos | NumAsin
- | NumToIntChecked => {
+ NumAbs
+ | NumNeg
+ | NumRound
+ | NumSqrtUnchecked
+ | NumLogUnchecked
+ | NumSin
+ | NumCos
+ | NumCeiling
+ | NumFloor
+ | NumToFrac
+ | NumIsFinite
+ | NumAtan
+ | NumAcos
+ | NumAsin
+ | NumToIntChecked
+ | NumCountLeadingZeroBits
+ | NumCountTrailingZeroBits
+ | NumCountOneBits => {
arguments_with_layouts!((arg, arg_layout));
match layout_interner.get(arg_layout) {
@@ -922,6 +956,28 @@ pub(crate) fn run_low_level<'a, 'ctx, 'env>(
bitcode::NUM_BYTES_TO_U32,
)
}
+ NumBytesToU64 => {
+ arguments!(list, position);
+
+ call_list_bitcode_fn(
+ env,
+ &[list.into_struct_value()],
+ &[position],
+ BitcodeReturns::Basic,
+ bitcode::NUM_BYTES_TO_U64,
+ )
+ }
+ NumBytesToU128 => {
+ arguments!(list, position);
+
+ call_list_bitcode_fn(
+ env,
+ &[list.into_struct_value()],
+ &[position],
+ BitcodeReturns::Basic,
+ bitcode::NUM_BYTES_TO_U128,
+ )
+ }
NumCompare => {
arguments_with_layouts!((lhs_arg, lhs_layout), (rhs_arg, rhs_layout));
@@ -1663,7 +1719,7 @@ fn dec_alloca<'a, 'ctx, 'env>(
let ptr = env.builder.build_pointer_cast(
alloca,
- value.get_type().ptr_type(AddressSpace::Generic),
+ value.get_type().ptr_type(AddressSpace::default()),
"cast_to_i128_ptr",
);
@@ -2013,7 +2069,7 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
let roc_return_type =
basic_type_from_layout(env, layout_interner, return_layout)
- .ptr_type(AddressSpace::Generic);
+ .ptr_type(AddressSpace::default());
let roc_return_alloca = env.builder.build_pointer_cast(
zig_return_alloca,
@@ -2053,6 +2109,19 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
complex_bitcast_check_size(env, result, return_type.into(), "cast_bitpacked")
}
}
+ NumCountLeadingZeroBits => call_bitcode_fn(
+ env,
+ &[arg.into()],
+ &bitcode::NUM_COUNT_LEADING_ZERO_BITS[arg_width],
+ ),
+ NumCountTrailingZeroBits => call_bitcode_fn(
+ env,
+ &[arg.into()],
+ &bitcode::NUM_COUNT_TRAILING_ZERO_BITS[arg_width],
+ ),
+ NumCountOneBits => {
+ call_bitcode_fn(env, &[arg.into()], &bitcode::NUM_COUNT_ONE_BITS[arg_width])
+ }
_ => {
unreachable!("Unrecognized int unary operation: {:?}", op);
}
diff --git a/crates/compiler/gen_llvm/src/llvm/refcounting.rs b/crates/compiler/gen_llvm/src/llvm/refcounting.rs
index 51b1ad8a9f..2fc10ef52f 100644
--- a/crates/compiler/gen_llvm/src/llvm/refcounting.rs
+++ b/crates/compiler/gen_llvm/src/llvm/refcounting.rs
@@ -3,17 +3,18 @@ use crate::llvm::bitcode::call_void_bitcode_fn;
use crate::llvm::build::BuilderExt;
use crate::llvm::build::{
add_func, cast_basic_basic, get_tag_id, tag_pointer_clear_tag_id, use_roc_value, Env,
- WhenRecursive, FAST_CALL_CONV,
+ FAST_CALL_CONV,
};
-use crate::llvm::build_list::{incrementing_elem_loop, list_capacity, load_list};
+use crate::llvm::build_list::{
+ incrementing_elem_loop, list_capacity_or_ref_ptr, list_refcount_ptr, load_list,
+};
+use crate::llvm::build_str::str_refcount_ptr;
use crate::llvm::convert::{basic_type_from_layout, zig_str_type, RocUnion};
use bumpalo::collections::Vec;
use inkwell::basic_block::BasicBlock;
use inkwell::module::Linkage;
use inkwell::types::{AnyTypeEnum, BasicMetadataTypeEnum, BasicType, BasicTypeEnum};
-use inkwell::values::{
- BasicValue, BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue,
-};
+use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
@@ -40,7 +41,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let value = env.builder.build_pointer_cast(
ptr,
- refcount_type.ptr_type(AddressSpace::Generic),
+ refcount_type.ptr_type(AddressSpace::default()),
"to_refcount_ptr",
);
@@ -54,7 +55,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let builder = env.builder;
// pointer to usize
let refcount_type = env.ptr_int();
- let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::Generic);
+ let refcount_ptr_type = refcount_type.ptr_type(AddressSpace::default());
let ptr_as_usize_ptr =
builder.build_pointer_cast(data_ptr, refcount_ptr_type, "as_usize_ptr");
@@ -75,16 +76,6 @@ impl<'ctx> PointerToRefcount<'ctx> {
}
}
- fn from_list_wrapper(env: &Env<'_, 'ctx, '_>, list_wrapper: StructValue<'ctx>) -> Self {
- let data_ptr = env
- .builder
- .build_extract_value(list_wrapper, Builtin::WRAPPER_PTR, "read_list_ptr")
- .unwrap()
- .into_pointer_value();
-
- Self::from_ptr_to_data(env, data_ptr)
- }
-
pub fn is_1<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>) -> IntValue<'ctx> {
let current = self.get_refcount(env);
let one = match env.target_info.ptr_width() {
@@ -148,7 +139,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
None => {
// inc and dec return void
let fn_type = context.void_type().fn_type(
- &[env.ptr_int().ptr_type(AddressSpace::Generic).into()],
+ &[env.ptr_int().ptr_type(AddressSpace::default()).into()],
false,
);
@@ -172,8 +163,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
let refcount_ptr = self.value;
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
let call = env
.builder
@@ -216,7 +206,7 @@ fn incref_pointer<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
pointer,
- env.ptr_int().ptr_type(AddressSpace::Generic),
+ env.ptr_int().ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
@@ -238,7 +228,7 @@ fn decref_pointer<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
pointer,
- env.ptr_int().ptr_type(AddressSpace::Generic),
+ env.ptr_int().ptr_type(AddressSpace::default()),
"to_isize_ptr",
)
.into(),
@@ -261,7 +251,7 @@ pub fn decref_pointer_check_null<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(
pointer,
- env.context.i8_type().ptr_type(AddressSpace::Generic),
+ env.context.i8_type().ptr_type(AddressSpace::default()),
"to_i8_ptr",
)
.into(),
@@ -277,7 +267,6 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
layout_ids: &mut LayoutIds<'a>,
layouts: &'a [InLayout<'a>],
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
@@ -304,7 +293,6 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
- when_recursive,
layouts,
function_value,
);
@@ -314,8 +302,7 @@ fn modify_refcount_struct<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function
}
@@ -326,7 +313,6 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
layouts: &[InLayout<'a>],
fn_val: FunctionValue<'ctx>,
) {
@@ -368,7 +354,6 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
- when_recursive,
field_value,
*field_layout,
);
@@ -430,7 +415,6 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
layout: InLayout<'a>,
builtin: &Builtin<'a>,
) -> Option> {
@@ -438,14 +422,8 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
match builtin {
List(element_layout) => {
- let function = modify_refcount_list(
- env,
- layout_interner,
- layout_ids,
- mode,
- when_recursive,
- *element_layout,
- );
+ let function =
+ modify_refcount_list(env, layout_interner, layout_ids, mode, *element_layout);
Some(function)
}
@@ -473,15 +451,7 @@ fn modify_refcount_layout<'a, 'ctx, 'env>(
value: BasicValueEnum<'ctx>,
layout: InLayout<'a>,
) {
- modify_refcount_layout_help(
- env,
- layout_interner,
- layout_ids,
- call_mode,
- &WhenRecursive::Unreachable,
- value,
- layout,
- );
+ modify_refcount_layout_help(env, layout_interner, layout_ids, call_mode, value, layout);
}
fn modify_refcount_layout_help<'a, 'ctx, 'env>(
@@ -489,7 +459,6 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
call_mode: CallMode<'ctx>,
- when_recursive: &WhenRecursive<'a>,
value: BasicValueEnum<'ctx>,
layout: InLayout<'a>,
) {
@@ -498,38 +467,28 @@ fn modify_refcount_layout_help<'a, 'ctx, 'env>(
CallMode::Dec => Mode::Dec,
};
- let function = match modify_refcount_layout_build_function(
- env,
- layout_interner,
- layout_ids,
- mode,
- when_recursive,
- layout,
- ) {
- Some(f) => f,
- None => return,
- };
+ let function =
+ match modify_refcount_layout_build_function(env, layout_interner, layout_ids, mode, layout)
+ {
+ Some(f) => f,
+ None => return,
+ };
match layout_interner.get(layout) {
- Layout::RecursivePointer(_) => match when_recursive {
- WhenRecursive::Unreachable => {
- unreachable!("recursion pointers should never be hashed directly")
- }
- WhenRecursive::Loop(union_layout) => {
- let layout = layout_interner.insert(Layout::Union(*union_layout));
+ Layout::RecursivePointer(rec_layout) => {
+ let layout = rec_layout;
- let bt = basic_type_from_layout(env, layout_interner, layout);
+ let bt = basic_type_from_layout(env, layout_interner, layout);
- // cast the i64 pointer to a pointer to block of memory
- let field_cast = env.builder.build_pointer_cast(
- value.into_pointer_value(),
- bt.into_pointer_type(),
- "i64_to_opaque",
- );
+ // cast the i64 pointer to a pointer to block of memory
+ let field_cast = env.builder.build_pointer_cast(
+ value.into_pointer_value(),
+ bt.into_pointer_type(),
+ "i64_to_opaque",
+ );
- call_help(env, function, call_mode, field_cast.into());
- }
- },
+ call_help(env, function, call_mode, field_cast.into());
+ }
_ => {
call_help(env, function, call_mode, value);
}
@@ -568,21 +527,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
layout: InLayout<'a>,
) -> Option> {
use Layout::*;
match layout_interner.get(layout) {
- Builtin(builtin) => modify_refcount_builtin(
- env,
- layout_interner,
- layout_ids,
- mode,
- when_recursive,
- layout,
- &builtin,
- ),
+ Builtin(builtin) => {
+ modify_refcount_builtin(env, layout_interner, layout_ids, mode, layout, &builtin)
+ }
Boxed(inner) => {
let function = modify_refcount_boxed(env, layout_interner, layout_ids, mode, inner);
@@ -600,27 +552,14 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
NonRecursive(tags) => {
- let function = modify_refcount_nonrecursive(
- env,
- layout_interner,
- layout_ids,
- mode,
- when_recursive,
- tags,
- );
+ let function =
+ modify_refcount_nonrecursive(env, layout_interner, layout_ids, mode, tags);
Some(function)
}
_ => {
- let function = build_rec_union(
- env,
- layout_interner,
- layout_ids,
- mode,
- &WhenRecursive::Loop(variant),
- variant,
- );
+ let function = build_rec_union(env, layout_interner, layout_ids, mode, variant);
Some(function)
}
@@ -628,43 +567,30 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
Struct { field_layouts, .. } => {
- let function = modify_refcount_struct(
- env,
- layout_interner,
- layout_ids,
- field_layouts,
- mode,
- when_recursive,
- );
+ let function =
+ modify_refcount_struct(env, layout_interner, layout_ids, field_layouts, mode);
Some(function)
}
- Layout::RecursivePointer(_) => match when_recursive {
- WhenRecursive::Unreachable => {
- unreachable!("recursion pointers cannot be in/decremented directly")
- }
- WhenRecursive::Loop(union_layout) => {
- let layout = layout_interner.insert(Layout::Union(*union_layout));
+ Layout::RecursivePointer(rec_layout) => {
+ let layout = rec_layout;
- let function = modify_refcount_layout_build_function(
- env,
- layout_interner,
- layout_ids,
- mode,
- when_recursive,
- layout,
- )?;
+ let function = modify_refcount_layout_build_function(
+ env,
+ layout_interner,
+ layout_ids,
+ mode,
+ layout,
+ )?;
- Some(function)
- }
- },
+ Some(function)
+ }
LambdaSet(lambda_set) => modify_refcount_layout_build_function(
env,
layout_interner,
layout_ids,
mode,
- when_recursive,
lambda_set.runtime_representation(),
),
}
@@ -675,15 +601,18 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
element_layout: InLayout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
- let element_layout = layout_interner.get(element_layout);
- let element_layout = when_recursive.unwrap_recursive_pointer(element_layout);
- let element_layout = layout_interner.insert(element_layout);
+ let element_layout = if let Layout::RecursivePointer(rec) = layout_interner.get(element_layout)
+ {
+ rec
+ } else {
+ element_layout
+ };
+
let list_layout = layout_interner.insert(Layout::Builtin(Builtin::List(element_layout)));
let (_, fn_name) = function_name_from_mode(
layout_ids,
@@ -705,7 +634,6 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
- when_recursive,
list_layout,
element_layout,
function_value,
@@ -716,8 +644,7 @@ fn modify_refcount_list<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function
}
@@ -734,7 +661,6 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
layout: InLayout<'a>,
element_layout: InLayout<'a>,
fn_val: FunctionValue<'ctx>,
@@ -758,26 +684,27 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
let parent = fn_val;
let original_wrapper = arg_val.into_struct_value();
- let len = list_capacity(builder, original_wrapper);
+ // We use the raw capacity to ensure we always decrement the refcount of seamless slices.
+ let capacity = list_capacity_or_ref_ptr(builder, original_wrapper);
let is_non_empty = builder.build_int_compare(
IntPredicate::UGT,
- len,
+ capacity,
env.ptr_int().const_zero(),
- "len > 0",
+ "cap > 0",
);
// build blocks
- let modification_block = ctx.append_basic_block(parent, "modification_block");
+ let modification_list_block = ctx.append_basic_block(parent, "modification_list_block");
let cont_block = ctx.append_basic_block(parent, "modify_rc_list_cont");
- builder.build_conditional_branch(is_non_empty, modification_block, cont_block);
+ builder.build_conditional_branch(is_non_empty, modification_list_block, cont_block);
- builder.position_at_end(modification_block);
+ builder.position_at_end(modification_list_block);
if layout_interner.contains_refcounted(element_layout) {
let ptr_type = basic_type_from_layout(env, layout_interner, element_layout)
- .ptr_type(AddressSpace::Generic);
+ .ptr_type(AddressSpace::default());
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
@@ -787,7 +714,6 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
- when_recursive,
element,
element_layout,
);
@@ -805,7 +731,8 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
);
}
- let refcount_ptr = PointerToRefcount::from_list_wrapper(env, original_wrapper);
+ let refcount_ptr =
+ PointerToRefcount::from_ptr_to_data(env, list_refcount_ptr(env, original_wrapper));
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env, layout_interner);
@@ -849,8 +776,7 @@ fn modify_refcount_str<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function
}
@@ -880,9 +806,9 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
let parent = fn_val;
- let arg_val =
+ let str_type = zig_str_type(env);
+ let str_wrapper =
if Layout::Builtin(Builtin::Str).is_passed_by_reference(layout_interner, env.target_info) {
- let str_type = zig_str_type(env);
env.builder
.new_build_load(str_type, arg_val.into_pointer_value(), "load_str_to_stack")
} else {
@@ -890,7 +816,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
debug_assert!(arg_val.is_struct_value());
arg_val
};
- let str_wrapper = arg_val.into_struct_value();
+ let str_wrapper = str_wrapper.into_struct_value();
let capacity = builder
.build_extract_value(str_wrapper, Builtin::WRAPPER_CAPACITY, "read_str_capacity")
@@ -913,7 +839,7 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
builder.build_conditional_branch(is_big_and_non_empty, modification_block, cont_block);
builder.position_at_end(modification_block);
- let refcount_ptr = PointerToRefcount::from_list_wrapper(env, str_wrapper);
+ let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, str_refcount_ptr(env, arg_val));
let call_mode = mode_to_call_mode(fn_val, mode);
refcount_ptr.modify(call_mode, layout, env, layout_interner);
@@ -959,8 +885,7 @@ fn modify_refcount_boxed<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function
}
@@ -1093,7 +1018,6 @@ fn build_rec_union<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>,
) -> FunctionValue<'ctx> {
let layout = layout_interner.insert(Layout::Union(union_layout));
@@ -1121,14 +1045,12 @@ fn build_rec_union<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
- when_recursive,
union_layout,
function_value,
);
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function_value
}
@@ -1143,7 +1065,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>,
fn_val: FunctionValue<'ctx>,
) {
@@ -1235,7 +1156,6 @@ fn build_rec_union_help<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
parent,
fn_val,
union_layout,
@@ -1268,7 +1188,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- when_recursive: &WhenRecursive<'a>,
parent: FunctionValue<'ctx>,
decrement_fn: FunctionValue<'ctx>,
union_layout: UnionLayout<'a>,
@@ -1316,7 +1235,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
// cast the opaque pointer to a pointer of the correct shape
let struct_ptr = env.builder.build_pointer_cast(
value_ptr,
- wrapper_type.ptr_type(AddressSpace::Generic),
+ wrapper_type.ptr_type(AddressSpace::default()),
"opaque_to_correct_recursive_decrement",
);
@@ -1339,7 +1258,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
.unwrap();
let ptr_as_i64_ptr = env.builder.new_build_load(
- env.context.i64_type().ptr_type(AddressSpace::Generic),
+ env.context.i64_type().ptr_type(AddressSpace::default()),
elem_pointer,
"load_recursive_pointer",
);
@@ -1396,7 +1315,6 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(decrement_fn),
- when_recursive,
field,
*field_layout,
);
@@ -1422,11 +1340,16 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
union_layout,
UnionLayout::NullableUnwrapped { .. } | UnionLayout::NonNullableUnwrapped { .. }
) {
- debug_assert_eq!(cases.len(), 1);
+ debug_assert!(cases.len() <= 1, "{cases:?}");
- // in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id
- let (_, only_branch) = cases.pop().unwrap();
- env.builder.build_unconditional_branch(only_branch);
+ if cases.is_empty() {
+ // The only other layout doesn't need refcounting. Pass through.
+ builder.build_return(None);
+ } else {
+ // in this case, don't switch, because the `else` branch below would try to read the (nonexistent) tag id
+ let (_, only_branch) = cases.pop().unwrap();
+ env.builder.build_unconditional_branch(only_branch);
+ }
} else {
let default_block = env.context.append_basic_block(parent, "switch_default");
@@ -1449,6 +1372,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
}
}
+#[derive(Debug)]
struct UnionLayoutTags<'a> {
nullable_id: Option,
tags: &'a [&'a [InLayout<'a>]],
@@ -1503,15 +1427,7 @@ pub fn build_reset<'a, 'ctx, 'env>(
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_interner,
- layout_ids,
- Mode::Dec,
- &when_recursive,
- union_layout,
- );
+ let dec_function = build_rec_union(env, layout_interner, layout_ids, Mode::Dec, union_layout);
let function = match env.module.get_function(fn_name.as_str()) {
Some(function_value) => function_value,
@@ -1526,15 +1442,13 @@ pub fn build_reset<'a, 'ctx, 'env>(
env,
layout_interner,
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);
+ env.builder.set_current_debug_location(di_location);
function_value
}
@@ -1548,7 +1462,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
- when_recursive: &WhenRecursive<'a>,
union_layout: UnionLayout<'a>,
reset_function: FunctionValue<'ctx>,
dec_function: FunctionValue<'ctx>,
@@ -1626,7 +1539,6 @@ fn build_reuse_rec_union_help<'a, 'ctx, 'env>(
env,
layout_interner,
layout_ids,
- when_recursive,
parent,
dec_function,
union_layout,
@@ -1665,7 +1577,6 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
fields: &'a [&'a [InLayout<'a>]],
) -> FunctionValue<'ctx> {
let union_layout = UnionLayout::NonRecursive(fields);
@@ -1694,7 +1605,6 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode,
- when_recursive,
fields,
function_value,
);
@@ -1704,8 +1614,7 @@ fn modify_refcount_nonrecursive<'a, 'ctx, 'env>(
};
env.builder.position_at_end(block);
- env.builder
- .set_current_debug_location(env.context, di_location);
+ env.builder.set_current_debug_location(di_location);
function
}
@@ -1715,7 +1624,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
mode: Mode,
- when_recursive: &WhenRecursive<'a>,
tags: &'a [&'a [InLayout<'a>]],
fn_val: FunctionValue<'ctx>,
) {
@@ -1805,19 +1713,12 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
let cast_tag_data_pointer = env.builder.build_pointer_cast(
opaque_tag_data_ptr,
- data_struct_type.ptr_type(AddressSpace::Generic),
+ data_struct_type.ptr_type(AddressSpace::default()),
"cast_to_concrete_tag",
);
for (i, field_layout) in field_layouts.iter().enumerate() {
- if let Layout::RecursivePointer(_) = layout_interner.get(*field_layout) {
- let recursive_union_layout = match when_recursive {
- WhenRecursive::Unreachable => {
- panic!("non-recursive tag unions cannot contain naked recursion pointers!");
- }
- WhenRecursive::Loop(recursive_union_layout) => recursive_union_layout,
- };
-
+ if let Layout::RecursivePointer(union_layout) = layout_interner.get(*field_layout) {
// This field is a pointer to the recursive pointer.
let field_ptr = env
.builder
@@ -1831,7 +1732,7 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
// This is the actual pointer to the recursive data.
let field_value = env.builder.new_build_load(
- env.context.i64_type().ptr_type(AddressSpace::Generic),
+ env.context.i64_type().ptr_type(AddressSpace::default()),
field_ptr,
"load_recursive_pointer",
);
@@ -1839,7 +1740,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
debug_assert!(field_value.is_pointer_value());
// therefore we must cast it to our desired type
- let union_layout = layout_interner.insert(Layout::Union(*recursive_union_layout));
let union_type = basic_type_from_layout(env, layout_interner, union_layout);
let recursive_ptr_field_value =
cast_basic_basic(env.builder, field_value, union_type);
@@ -1849,7 +1749,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
- when_recursive,
recursive_ptr_field_value,
*field_layout,
)
@@ -1879,7 +1778,6 @@ fn modify_refcount_nonrecursive_help<'a, 'ctx, 'env>(
layout_interner,
layout_ids,
mode.to_call_mode(fn_val),
- when_recursive,
field_value,
*field_layout,
);
diff --git a/crates/compiler/gen_wasm/Cargo.toml b/crates/compiler/gen_wasm/Cargo.toml
index b96de60b50..a25ccf0e23 100644
--- a/crates/compiler/gen_wasm/Cargo.toml
+++ b/crates/compiler/gen_wasm/Cargo.toml
@@ -1,19 +1,20 @@
[package]
name = "roc_gen_wasm"
-version = "0.0.1"
-edition = "2021"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
description = "Provides the WASM backend to generate Roc binaries."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" }
+roc_error_macros = { path = "../../error_macros" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
-roc_target = { path = "../roc_target" }
roc_std = { path = "../../roc_std" }
-roc_error_macros = { path = "../../error_macros" }
+roc_target = { path = "../roc_target" }
roc_wasm_module = { path = "../../wasm_module" }
bitvec.workspace = true
diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs
index 5b3a099edd..6cfd49bea3 100644
--- a/crates/compiler/gen_wasm/src/low_level.rs
+++ b/crates/compiler/gen_wasm/src/low_level.rs
@@ -285,6 +285,9 @@ impl<'a> LowLevelCall<'a> {
StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT),
StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8),
StrReserve => self.load_args_and_call_zig(backend, bitcode::STR_RESERVE),
+ StrReleaseExcessCapacity => {
+ self.load_args_and_call_zig(backend, bitcode::STR_RELEASE_EXCESS_CAPACITY)
+ }
StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT),
StrAppendScalar => self.load_args_and_call_zig(backend, bitcode::STR_APPEND_SCALAR),
StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM),
@@ -318,25 +321,7 @@ impl<'a> LowLevelCall<'a> {
_ => internal_error!("invalid storage for List"),
},
- ListGetCapacity => match backend.storage.get(&self.arguments[0]) {
- StoredValue::StackMemory { location, .. } => {
- let (local_id, offset) =
- location.local_and_offset(backend.storage.stack_frame_pointer);
- backend.code_builder.get_local(local_id);
- // List is stored as (pointer, length, capacity),
- // with each of those fields being 4 bytes on wasm.
- // So the capacity is 8 bytes after the start of the struct.
- //
- // WRAPPER_CAPACITY represents the index of the capacity field
- // (which is 2 as of the writing of this comment). If the field order
- // ever changes, WRAPPER_CAPACITY should be updated and this logic should
- // continue to work even though this comment may become inaccurate.
- backend
- .code_builder
- .i32_load(Align::Bytes4, offset + (4 * Builtin::WRAPPER_CAPACITY));
- }
- _ => internal_error!("invalid storage for List"),
- },
+ ListGetCapacity => self.load_args_and_call_zig(backend, bitcode::LIST_CAPACITY),
ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE),
@@ -573,6 +558,46 @@ impl<'a> LowLevelCall<'a> {
backend.call_host_fn_after_loading_args(bitcode::LIST_RESERVE, 7, false);
}
+ ListReleaseExcessCapacity => {
+ // List.releaseExcessCapacity : List elem -> List elem
+
+ let list: Symbol = self.arguments[0];
+
+ let elem_layout = unwrap_list_elem_layout(self.ret_layout_raw);
+ let elem_layout = backend.layout_interner.get(elem_layout);
+ let (elem_width, elem_align) =
+ elem_layout.stack_size_and_alignment(backend.layout_interner, TARGET_INFO);
+
+ // Zig arguments Wasm types
+ // (return pointer) i32
+ // list: RocList i64, i32
+ // alignment: u32 i32
+ // element_width: usize i32
+ // update_mode: UpdateMode i32
+
+ // return pointer and list
+ backend.storage.load_symbols_for_call(
+ backend.env.arena,
+ &mut backend.code_builder,
+ &[list],
+ self.ret_symbol,
+ &WasmLayout::new(backend.layout_interner, self.ret_layout),
+ CallConv::Zig,
+ );
+
+ backend.code_builder.i32_const(elem_align as i32);
+
+ backend.code_builder.i32_const(elem_width as i32);
+
+ backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
+
+ backend.call_host_fn_after_loading_args(
+ bitcode::LIST_RELEASE_EXCESS_CAPACITY,
+ 6,
+ false,
+ );
+ }
+
ListAppendUnsafe => {
// List.append : List elem, elem -> List elem
@@ -1489,6 +1514,40 @@ impl<'a> LowLevelCall<'a> {
}
_ => panic_ret_type(),
},
+
+ NumCountLeadingZeroBits => match backend
+ .layout_interner
+ .get(backend.storage.symbol_layouts[&self.arguments[0]])
+ {
+ Layout::Builtin(Builtin::Int(width)) => {
+ self.load_args_and_call_zig(
+ backend,
+ &bitcode::NUM_COUNT_LEADING_ZERO_BITS[width],
+ );
+ }
+ _ => panic_ret_type(),
+ },
+ NumCountTrailingZeroBits => match backend
+ .layout_interner
+ .get(backend.storage.symbol_layouts[&self.arguments[0]])
+ {
+ Layout::Builtin(Builtin::Int(width)) => {
+ self.load_args_and_call_zig(
+ backend,
+ &bitcode::NUM_COUNT_TRAILING_ZERO_BITS[width],
+ );
+ }
+ _ => panic_ret_type(),
+ },
+ NumCountOneBits => match backend
+ .layout_interner
+ .get(backend.storage.symbol_layouts[&self.arguments[0]])
+ {
+ Layout::Builtin(Builtin::Int(width)) => {
+ self.load_args_and_call_zig(backend, &bitcode::NUM_COUNT_ONE_BITS[width]);
+ }
+ _ => panic_ret_type(),
+ },
NumRound => {
self.load_args(backend);
let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]);
@@ -1577,6 +1636,8 @@ impl<'a> LowLevelCall<'a> {
},
NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16),
NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32),
+ NumBytesToU64 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U64),
+ NumBytesToU128 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U128),
NumBitwiseAnd => {
self.load_args(backend);
match CodeGenNumType::from(self.ret_layout) {
diff --git a/crates/compiler/gen_wasm/src/wasm32_result.rs b/crates/compiler/gen_wasm/src/wasm32_result.rs
index 04c8fa0480..fc4cde114b 100644
--- a/crates/compiler/gen_wasm/src/wasm32_result.rs
+++ b/crates/compiler/gen_wasm/src/wasm32_result.rs
@@ -8,7 +8,7 @@ use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_mono::layout::{Builtin, InLayout, Layout, LayoutInterner, UnionLayout};
-use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
+use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
use roc_wasm_module::{
linking::SymInfo, linking::WasmObjectSymbol, Align, Export, ExportType, LocalId, Signature,
ValueType, WasmModule,
@@ -203,6 +203,13 @@ impl Wasm32Result for RocList {
}
}
+impl Wasm32Result for RocBox {
+ fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
+ // treat box as if it's just a isize value
+ ::build_wrapper_body(code_builder, main_function_index)
+ }
+}
+
impl Wasm32Result for RocResult {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(
diff --git a/crates/compiler/gen_wasm/src/wasm32_sized.rs b/crates/compiler/gen_wasm/src/wasm32_sized.rs
index 81c3b83bc0..05ac6d95ec 100644
--- a/crates/compiler/gen_wasm/src/wasm32_sized.rs
+++ b/crates/compiler/gen_wasm/src/wasm32_sized.rs
@@ -1,4 +1,4 @@
-use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
+use roc_std::{RocBox, RocDec, RocList, RocOrder, RocResult, RocStr, I128, U128};
pub trait Wasm32Sized: Sized {
const SIZE_OF_WASM: usize;
@@ -45,6 +45,11 @@ impl Wasm32Sized for RocList {
const ALIGN_OF_WASM: usize = 4;
}
+impl Wasm32Sized for RocBox {
+ const SIZE_OF_WASM: usize = 4;
+ const ALIGN_OF_WASM: usize = 4;
+}
+
impl Wasm32Sized for RocResult {
const ALIGN_OF_WASM: usize = max(&[T::ALIGN_OF_WASM, E::ALIGN_OF_WASM]);
const SIZE_OF_WASM: usize = max(&[T::ACTUAL_WIDTH, E::ACTUAL_WIDTH]) + 1;
diff --git a/crates/compiler/ident/Cargo.toml b/crates/compiler/ident/Cargo.toml
index febd0b050c..0c53fa90a9 100644
--- a/crates/compiler/ident/Cargo.toml
+++ b/crates/compiler/ident/Cargo.toml
@@ -1,7 +1,8 @@
[package]
name = "roc_ident"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Implements data structures used for efficiently representing small strings, like identifiers."
+
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
diff --git a/crates/compiler/late_solve/Cargo.toml b/crates/compiler/late_solve/Cargo.toml
index f4b11fa9c7..cd9af7f6cf 100644
--- a/crates/compiler/late_solve/Cargo.toml
+++ b/crates/compiler/late_solve/Cargo.toml
@@ -1,19 +1,20 @@
[package]
name = "roc_late_solve"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Provides type unification and solving primitives from the perspective of the compiler backend."
-[dependencies]
-roc_types = { path = "../types" }
-roc_can = { path = "../can" }
-roc_derive = { path = "../derive" }
-roc_module = { path = "../module" }
-roc_unify = { path = "../unify" }
-roc_solve = { path = "../solve" }
-roc_collections = { path = "../collections" }
-roc_error_macros = { path = "../../error_macros" }
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
-bumpalo.workspace = true
+[dependencies]
+roc_can = { path = "../can" }
+roc_collections = { path = "../collections" }
+roc_derive = { path = "../derive" }
+roc_error_macros = { path = "../../error_macros" }
+roc_module = { path = "../module" }
+roc_solve = { path = "../solve" }
+roc_types = { path = "../types" }
+roc_unify = { path = "../unify" }
+
+bumpalo.workspace = true
diff --git a/crates/compiler/late_solve/src/lib.rs b/crates/compiler/late_solve/src/lib.rs
index c3d3eb4ee1..15098bda7a 100644
--- a/crates/compiler/late_solve/src/lib.rs
+++ b/crates/compiler/late_solve/src/lib.rs
@@ -20,7 +20,7 @@ use roc_types::types::Polarity;
use roc_unify::unify::MetaCollector;
use roc_unify::unify::{Env, Mode, Unified};
-pub use roc_solve::ability::Resolved;
+pub use roc_solve::ability::{ResolveError, Resolved};
pub use roc_types::subs::instantiate_rigids;
pub mod storage;
@@ -161,7 +161,7 @@ pub fn resolve_ability_specialization(
abilities: &AbilitiesView,
ability_member: Symbol,
specialization_var: Variable,
-) -> Option {
+) -> Result {
let late_resolver = LateResolver { home, abilities };
roc_solve::ability::resolve_ability_specialization(
subs,
diff --git a/crates/compiler/load/Cargo.toml b/crates/compiler/load/Cargo.toml
index fa190af36f..537093b6a6 100644
--- a/crates/compiler/load/Cargo.toml
+++ b/crates/compiler/load/Cargo.toml
@@ -1,30 +1,31 @@
[package]
name = "roc_load"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Used to load a .roc file and coordinate the compiler pipeline, including parsing, type checking, and code generation."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-roc_load_internal = { path = "../load_internal" }
-roc_target = { path = "../roc_target" }
roc_can = { path = "../can" }
-roc_types = { path = "../types" }
-roc_module = { path = "../module" }
roc_collections = { path = "../collections" }
+roc_load_internal = { path = "../load_internal" }
+roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
+roc_target = { path = "../roc_target" }
+roc_types = { path = "../types" }
bumpalo.workspace = true
[build-dependencies]
roc_builtins = { path = "../builtins" }
+roc_can = { path = "../can" }
roc_module = { path = "../module" }
roc_packaging = { path = "../../packaging" }
roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" }
-roc_can = { path = "../can" }
bumpalo.workspace = true
diff --git a/crates/compiler/load/build.rs b/crates/compiler/load/build.rs
index d71559a59c..f53a9f75f1 100644
--- a/crates/compiler/load/build.rs
+++ b/crates/compiler/load/build.rs
@@ -3,7 +3,6 @@ use std::path::{Path, PathBuf};
#[cfg(not(windows))]
use bumpalo::Bump;
use roc_module::symbol::ModuleId;
-use roc_packaging::cache::RocCacheDir;
const SKIP_SUBS_CACHE: bool = {
match option_env!("ROC_SKIP_SUBS_CACHE") {
@@ -70,6 +69,7 @@ fn write_types_for_module_dummy(output_path: &Path) {
fn write_types_for_module_real(module_id: ModuleId, filename: &str, output_path: &Path) {
use roc_can::module::TypeState;
use roc_load_internal::file::{LoadingProblem, Threading};
+ use roc_packaging::cache::RocCacheDir;
use roc_reporting::cli::report_problems;
let arena = Bump::new();
diff --git a/crates/compiler/load/src/lib.rs b/crates/compiler/load/src/lib.rs
index e564c9d119..3f66ff742a 100644
--- a/crates/compiler/load/src/lib.rs
+++ b/crates/compiler/load/src/lib.rs
@@ -18,8 +18,8 @@ const SKIP_SUBS_CACHE: bool = {
pub use roc_load_internal::docs;
pub use roc_load_internal::file::{
- EntryPoint, ExecutionMode, ExpectMetadata, Expectations, LoadConfig, LoadResult, LoadStart,
- LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading,
+ EntryPoint, ExecutionMode, ExpectMetadata, Expectations, ExposedToHost, LoadConfig, LoadResult,
+ LoadStart, LoadedModule, LoadingProblem, MonomorphizedModule, Phase, Threading,
};
#[allow(clippy::too_many_arguments)]
diff --git a/crates/compiler/load_internal/Cargo.toml b/crates/compiler/load_internal/Cargo.toml
index a52d997f93..e53090ac0b 100644
--- a/crates/compiler/load_internal/Cargo.toml
+++ b/crates/compiler/load_internal/Cargo.toml
@@ -1,45 +1,46 @@
[package]
name = "roc_load_internal"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "The internal implementation of roc_load, separate from roc_load to support caching."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-roc_collections = { path = "../collections" }
-roc_error_macros = { path = "../../error_macros" }
-roc_region = { path = "../region" }
-roc_module = { path = "../module" }
-roc_types = { path = "../types" }
-roc_can = { path = "../can" }
-roc_constrain = { path = "../constrain" }
-roc_derive_key = { path = "../derive_key" }
-roc_derive = { path = "../derive" }
roc_builtins = { path = "../builtins" }
-roc_problem = { path = "../problem" }
-roc_unify = { path = "../unify" }
+roc_can = { path = "../can" }
+roc_collections = { path = "../collections" }
+roc_constrain = { path = "../constrain" }
+roc_debug_flags = { path = "../debug_flags" }
+roc_derive = { path = "../derive" }
+roc_derive_key = { path = "../derive_key" }
+roc_error_macros = { path = "../../error_macros" }
+roc_late_solve = { path = "../late_solve" }
+roc_module = { path = "../module" }
+roc_mono = { path = "../mono" }
+roc_packaging = { path = "../../packaging" }
roc_parse = { path = "../parse" }
+roc_problem = { path = "../problem" }
+roc_region = { path = "../region" }
+roc_reporting = { path = "../../reporting" }
roc_solve = { path = "../solve" }
roc_solve_problem = { path = "../solve_problem" }
-roc_late_solve = { path = "../late_solve" }
-roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" }
roc_tracing = { path = "../../tracing" }
-roc_packaging = { path = "../../packaging" }
-roc_reporting = { path = "../../reporting" }
-roc_debug_flags = { path = "../debug_flags" }
+roc_types = { path = "../types" }
+roc_unify = { path = "../unify" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo.workspace = true
-parking_lot.workspace = true
crossbeam.workspace = true
+parking_lot.workspace = true
tempfile.workspace = true
[dev-dependencies]
roc_test_utils = { path = "../../test_utils" }
-pretty_assertions.workspace = true
indoc.workspace = true
maplit.workspace = true
+pretty_assertions.workspace = true
diff --git a/crates/compiler/load_internal/src/docs.rs b/crates/compiler/load_internal/src/docs.rs
index eb8b7ed514..27dee8c8a0 100644
--- a/crates/compiler/load_internal/src/docs.rs
+++ b/crates/compiler/load_internal/src/docs.rs
@@ -403,7 +403,7 @@ fn contains_unexposed_type(
false
}
- Tuple { fields, ext } => {
+ Tuple { elems: fields, ext } => {
if let Some(loc_ext) = ext {
if contains_unexposed_type(&loc_ext.value, exposed_module_ids, module_ids) {
return true;
diff --git a/crates/compiler/load_internal/src/file.rs b/crates/compiler/load_internal/src/file.rs
index ddc044d154..9aee848673 100644
--- a/crates/compiler/load_internal/src/file.rs
+++ b/crates/compiler/load_internal/src/file.rs
@@ -1,7 +1,7 @@
#![allow(clippy::too_many_arguments)]
use crate::docs::ModuleDocumentation;
-use bumpalo::Bump;
+use bumpalo::{collections::CollectIn, Bump};
use crossbeam::channel::{bounded, Sender};
use crossbeam::deque::{Injector, Stealer, Worker};
use crossbeam::thread;
@@ -32,9 +32,10 @@ use roc_module::symbol::{
};
use roc_mono::fl_reuse;
use roc_mono::ir::{
- CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase,
- UpdateModeIds,
+ CapturedSymbols, ExternalSpecializations, GlueLayouts, LambdaSetId, PartialProc, Proc,
+ ProcLayout, Procs, ProcsBase, UpdateModeIds,
};
+use roc_mono::layout::LayoutInterner;
use roc_mono::layout::{
GlobalLayoutInterner, LambdaName, Layout, LayoutCache, LayoutProblem, Niche, STLayoutInterner,
};
@@ -118,11 +119,11 @@ pub enum ExecutionMode {
impl ExecutionMode {
fn goal_phase(&self) -> Phase {
+ use ExecutionMode::*;
+
match self {
- ExecutionMode::Executable => Phase::MakeSpecializations,
- ExecutionMode::Check | ExecutionMode::ExecutableIfCheck | ExecutionMode::Test => {
- Phase::SolveTypes
- }
+ Executable => Phase::MakeSpecializations,
+ Check | ExecutableIfCheck | Test => Phase::SolveTypes,
}
}
@@ -767,6 +768,7 @@ pub struct MonomorphizedModule<'a> {
pub timings: MutMap,
pub expectations: VecMap,
pub uses_prebuilt_platform: bool,
+ pub glue_layouts: GlueLayouts<'a>,
}
/// Values used to render expect output
@@ -797,9 +799,12 @@ pub struct Expectations {
#[derive(Clone, Debug, Default)]
pub struct ExposedToHost {
/// usually `mainForHost`
- pub values: MutMap,
+ pub top_level_values: MutMap,
/// exposed closure types, typically `Fx`
pub closure_types: Vec,
+ /// lambda_sets
+ pub lambda_sets: Vec<(Symbol, LambdaSetId)>,
+ pub getters: Vec,
}
impl<'a> MonomorphizedModule<'a> {
@@ -2777,7 +2782,7 @@ fn update<'a>(
!matches!(state.exec_mode, ExecutionMode::Test);
if add_to_host_exposed {
- state.exposed_to_host.values.extend(
+ state.exposed_to_host.top_level_values.extend(
solved_module
.exposed_vars_by_symbol
.iter()
@@ -3104,7 +3109,7 @@ fn update<'a>(
// debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_RESET_REUSE);
// let host_exposed_procs = bumpalo::collections::Vec::from_iter_in(
- // state.exposed_to_host.values.keys().copied(),
+ // state.exposed_to_host.top_level_values.keys().copied(),
// arena,
// );
@@ -3294,8 +3299,8 @@ fn finish_specialization<'a>(
arena: &'a Bump,
state: State<'a>,
subs: Subs,
- layout_interner: STLayoutInterner<'a>,
- exposed_to_host: ExposedToHost,
+ mut layout_interner: STLayoutInterner<'a>,
+ mut exposed_to_host: ExposedToHost,
module_expectations: VecMap,
) -> Result, LoadingProblem<'a>> {
if false {
@@ -3325,38 +3330,16 @@ fn finish_specialization<'a>(
all_ident_ids,
};
- let State {
- toplevel_expects,
- procedures,
- module_cache,
- output_path,
- platform_path,
- platform_data,
- exec_mode,
- ..
- } = state;
-
- let ModuleCache {
- type_problems,
- can_problems,
- sources,
- ..
- } = module_cache;
-
- let sources: MutMap)> = sources
- .into_iter()
- .map(|(id, (path, src))| (id, (path, src.into())))
- .collect();
-
let entry_point = {
- match exec_mode {
- ExecutionMode::Test => EntryPoint::Test,
+ let interns: &mut Interns = &mut interns;
+ match state.exec_mode {
+ ExecutionMode::Test => Ok(EntryPoint::Test),
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
use PlatformPath::*;
- let platform_path = match platform_path {
+ let platform_path = match &state.platform_path {
Valid(To::ExistingPackage(shorthand)) => {
- match (*state.arc_shorthands).lock().get(shorthand) {
+ match state.arc_shorthands.lock().get(shorthand) {
Some(shorthand_path) => shorthand_path.root_module().to_path_buf(),
None => unreachable!(),
}
@@ -3368,13 +3351,14 @@ fn finish_specialization<'a>(
}
};
- let exposed_symbols_and_layouts = match platform_data {
+ let exposed_symbols_and_layouts = match state.platform_data {
None => {
- let src = &exposed_to_host.values;
+ let src = &state.exposed_to_host.top_level_values;
let mut buf = bumpalo::collections::Vec::with_capacity_in(src.len(), arena);
for &symbol in src.keys() {
- let proc_layout = proc_layout_for(procedures.keys().copied(), symbol);
+ let proc_layout =
+ proc_layout_for(state.procedures.keys().copied(), symbol);
buf.push((symbol, proc_layout));
}
@@ -3393,7 +3377,8 @@ fn finish_specialization<'a>(
for (loc_name, _loc_typed_ident) in provides {
let ident_id = ident_ids.get_or_insert(loc_name.value.as_str());
let symbol = Symbol::new(module_id, ident_id);
- let proc_layout = proc_layout_for(procedures.keys().copied(), symbol);
+ let proc_layout =
+ proc_layout_for(state.procedures.keys().copied(), symbol);
buf.push((symbol, proc_layout));
}
@@ -3402,14 +3387,84 @@ fn finish_specialization<'a>(
}
};
- EntryPoint::Executable {
+ Ok(EntryPoint::Executable {
exposed_to_host: exposed_symbols_and_layouts,
platform_path,
- }
+ })
}
ExecutionMode::Check => unreachable!(),
}
- };
+ }?;
+
+ let State {
+ toplevel_expects,
+ procedures,
+ module_cache,
+ output_path,
+ platform_data,
+ ..
+ } = state;
+
+ let ModuleCache {
+ type_problems,
+ can_problems,
+ sources,
+ ..
+ } = module_cache;
+
+ let sources: MutMap)> = sources
+ .into_iter()
+ .map(|(id, (path, src))| (id, (path, src.into())))
+ .collect();
+
+ let module_id = state.root_id;
+ let mut glue_getters = Vec::new();
+
+ // the REPL does not have any platform data
+ if let (
+ EntryPoint::Executable {
+ exposed_to_host: exposed_top_levels,
+ ..
+ },
+ Some(platform_data),
+ ) = (&entry_point, platform_data.as_ref())
+ {
+ // Expose glue for the platform, not for the app module!
+ let module_id = platform_data.module_id;
+
+ for (_name, proc_layout) in exposed_top_levels.iter() {
+ let ret = &proc_layout.result;
+ for in_layout in proc_layout.arguments.iter().chain([ret]) {
+ let layout = layout_interner.get(*in_layout);
+ let ident_ids = interns.all_ident_ids.get_mut(&module_id).unwrap();
+ let all_glue_procs = roc_mono::ir::generate_glue_procs(
+ module_id,
+ ident_ids,
+ arena,
+ &mut layout_interner,
+ arena.alloc(layout),
+ );
+
+ let lambda_set_names = all_glue_procs
+ .extern_names
+ .iter()
+ .map(|(lambda_set_id, _)| (*_name, *lambda_set_id));
+ exposed_to_host.lambda_sets.extend(lambda_set_names);
+
+ let getter_names = all_glue_procs
+ .getters
+ .iter()
+ .flat_map(|(_, glue_procs)| glue_procs.iter().map(|glue_proc| glue_proc.name));
+ exposed_to_host.getters.extend(getter_names);
+
+ glue_getters.extend(all_glue_procs.getters.iter().flat_map(|(_, glue_procs)| {
+ glue_procs
+ .iter()
+ .map(|glue_proc| (glue_proc.name, glue_proc.proc_layout))
+ }));
+ }
+ }
+ }
let output_path = match output_path {
Some(path_str) => Path::new(path_str).into(),
@@ -3429,7 +3484,7 @@ fn finish_specialization<'a>(
output_path,
expectations: module_expectations,
exposed_to_host,
- module_id: state.root_id,
+ module_id,
subs,
interns,
layout_interner,
@@ -3438,6 +3493,9 @@ fn finish_specialization<'a>(
sources,
timings: state.timings,
toplevel_expects,
+ glue_layouts: GlueLayouts {
+ getters: glue_getters,
+ },
uses_prebuilt_platform,
})
}
@@ -3460,6 +3518,7 @@ fn proc_layout_for<'a>(
}
}
+#[allow(clippy::too_many_arguments)]
fn finish(
mut state: State,
solved: Solved,
@@ -5659,10 +5718,14 @@ fn make_specializations<'a>(
let mut procs = Procs::new_in(arena);
+ let host_exposed_symbols: bumpalo::collections::Vec<_> =
+ procs_base.get_host_exposed_symbols().collect_in(arena);
+
for (symbol, partial_proc) in procs_base.partial_procs.into_iter() {
procs.partial_procs.insert(symbol, partial_proc);
}
+ procs.host_exposed_symbols = host_exposed_symbols.into_bump_slice();
procs.module_thunks = procs_base.module_thunks;
procs.runtime_errors = procs_base.runtime_errors;
procs.imported_module_thunks = procs_base.imported_module_thunks;
@@ -5753,6 +5816,8 @@ fn build_pending_specializations<'a>(
derived_module: &derived_module,
};
+ let layout_cache_snapshot = layout_cache.snapshot();
+
// Add modules' decls to Procs
for index in 0..declarations.len() {
use roc_can::expr::DeclarationTag::*;
@@ -5760,7 +5825,7 @@ fn build_pending_specializations<'a>(
let symbol = declarations.symbols[index].value;
let expr_var = declarations.variables[index];
- let is_host_exposed = exposed_to_host.values.contains_key(&symbol);
+ let is_host_exposed = exposed_to_host.top_level_values.contains_key(&symbol);
// TODO remove clones (with drain)
let annotation = declarations.annotations[index].clone();
@@ -6130,6 +6195,8 @@ fn build_pending_specializations<'a>(
}
}
+ layout_cache.rollback_to(layout_cache_snapshot);
+
procs_base.module_thunks = module_thunks.into_bump_slice();
let find_specializations_end = Instant::now();
@@ -6665,7 +6732,7 @@ fn to_parse_problem_report<'a>(
buf
}
-fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> String {
+fn to_missing_platform_report(module_id: ModuleId, other: &PlatformPath) -> String {
use roc_reporting::report::{Report, RocDocAllocator, DEFAULT_PALETTE};
use ven_pretty::DocAllocator;
use PlatformPath::*;
diff --git a/crates/compiler/module/Cargo.toml b/crates/compiler/module/Cargo.toml
index 938ad44b83..6be6b4e716 100644
--- a/crates/compiler/module/Cargo.toml
+++ b/crates/compiler/module/Cargo.toml
@@ -1,20 +1,22 @@
[package]
name = "roc_module"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-edition = "2021"
-license = "UPL-1.0"
description = "Implements data structures used for efficiently representing unique modules and identifiers in Roc programs."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-roc_region = { path = "../region" }
-roc_ident = { path = "../ident" }
roc_collections = { path = "../collections" }
-roc_error_macros = {path = "../../error_macros"}
+roc_error_macros = { path = "../../error_macros" }
+roc_ident = { path = "../ident" }
+roc_region = { path = "../region" }
+
bumpalo.workspace = true
-static_assertions.workspace = true
snafu.workspace = true
+static_assertions.workspace = true
[features]
-default = []
debug-symbols = []
+default = []
diff --git a/crates/compiler/module/src/ident.rs b/crates/compiler/module/src/ident.rs
index bcf7d49cb0..5cbe019736 100644
--- a/crates/compiler/module/src/ident.rs
+++ b/crates/compiler/module/src/ident.rs
@@ -1,5 +1,5 @@
pub use roc_ident::IdentStr;
-use std::fmt;
+use std::fmt::{self, Debug};
use crate::symbol::PQModuleName;
@@ -74,7 +74,7 @@ pub type TagIdIntType = u16;
/// If tags had a Symbol representation, then each module would have to
/// deal with contention on a global mutex around translating tag strings
/// into integers. (Record field labels work the same way, for the same reason.)
-#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
+#[derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct TagName(pub Uppercase);
roc_error_macros::assert_sizeof_non_wasm!(TagName, 16);
@@ -86,6 +86,12 @@ impl TagName {
}
}
+impl Debug for TagName {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ Debug::fmt(&self.0, f)
+ }
+}
+
impl From<&str> for TagName {
fn from(string: &str) -> Self {
Self(string.into())
@@ -161,7 +167,7 @@ impl From for Box {
impl fmt::Display for ModuleName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- self.0.fmt(f)
+ fmt::Display::fmt(&self.0, f)
}
}
@@ -298,7 +304,7 @@ impl From for Box {
impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- self.0.fmt(f)
+ fmt::Display::fmt(&self.0, f)
}
}
@@ -317,7 +323,7 @@ impl fmt::Debug for Lowercase {
impl fmt::Display for Lowercase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- self.0.fmt(f)
+ fmt::Display::fmt(&self.0, f)
}
}
@@ -336,6 +342,6 @@ impl fmt::Debug for Uppercase {
impl fmt::Display for Uppercase {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- self.0.fmt(f)
+ fmt::Display::fmt(&self.0, f)
}
}
diff --git a/crates/compiler/module/src/low_level.rs b/crates/compiler/module/src/low_level.rs
index 4f3c343bc7..7c72a0ec3d 100644
--- a/crates/compiler/module/src/low_level.rs
+++ b/crates/compiler/module/src/low_level.rs
@@ -32,9 +32,11 @@ pub enum LowLevel {
StrGetCapacity,
StrWithCapacity,
StrGraphemes,
+ StrReleaseExcessCapacity,
ListLen,
ListWithCapacity,
ListReserve,
+ ListReleaseExcessCapacity,
ListAppendUnsafe,
ListGetUnsafe,
ListReplaceUnsafe,
@@ -90,6 +92,8 @@ pub enum LowLevel {
NumAsin,
NumBytesToU16,
NumBytesToU32,
+ NumBytesToU64,
+ NumBytesToU128,
NumBitwiseAnd,
NumBitwiseXor,
NumBitwiseOr,
@@ -101,6 +105,9 @@ pub enum LowLevel {
NumToIntChecked,
NumToFloatChecked,
NumToStr,
+ NumCountLeadingZeroBits,
+ NumCountTrailingZeroBits,
+ NumCountOneBits,
Eq,
NotEq,
And,
@@ -253,10 +260,12 @@ map_symbol_to_lowlevel! {
StrGetCapacity <= STR_CAPACITY,
StrWithCapacity <= STR_WITH_CAPACITY,
StrGraphemes <= STR_GRAPHEMES,
+ StrReleaseExcessCapacity <= STR_RELEASE_EXCESS_CAPACITY,
ListLen <= LIST_LEN,
ListGetCapacity <= LIST_CAPACITY,
ListWithCapacity <= LIST_WITH_CAPACITY,
ListReserve <= LIST_RESERVE,
+ ListReleaseExcessCapacity <= LIST_RELEASE_EXCESS_CAPACITY,
ListIsUnique <= LIST_IS_UNIQUE,
ListAppendUnsafe <= LIST_APPEND_UNSAFE,
ListPrepend <= LIST_PREPEND,
@@ -305,6 +314,8 @@ map_symbol_to_lowlevel! {
NumAsin <= NUM_ASIN,
NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL,
NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL,
+ NumBytesToU64 <= NUM_BYTES_TO_U64_LOWLEVEL,
+ NumBytesToU128 <= NUM_BYTES_TO_U128_LOWLEVEL,
NumBitwiseAnd <= NUM_BITWISE_AND,
NumBitwiseXor <= NUM_BITWISE_XOR,
NumBitwiseOr <= NUM_BITWISE_OR,
@@ -312,6 +323,9 @@ map_symbol_to_lowlevel! {
NumShiftRightBy <= NUM_SHIFT_RIGHT,
NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL,
NumToStr <= NUM_TO_STR,
+ NumCountLeadingZeroBits <= NUM_COUNT_LEADING_ZERO_BITS,
+ NumCountTrailingZeroBits <= NUM_COUNT_TRAILING_ZERO_BITS,
+ NumCountOneBits <= NUM_COUNT_ONE_BITS,
Eq <= BOOL_STRUCTURAL_EQ,
NotEq <= BOOL_STRUCTURAL_NOT_EQ,
And <= BOOL_AND,
diff --git a/crates/compiler/module/src/symbol.rs b/crates/compiler/module/src/symbol.rs
index 4ab92ec50c..4edc38e50d 100644
--- a/crates/compiler/module/src/symbol.rs
+++ b/crates/compiler/module/src/symbol.rs
@@ -1189,63 +1189,70 @@ define_builtins! {
88 NUM_DEC: "Dec" exposed_type=true // the Num.Dectype alias
89 NUM_BYTES_TO_U16: "bytesToU16"
90 NUM_BYTES_TO_U32: "bytesToU32"
- 91 NUM_CAST_TO_NAT: "#castToNat"
- 92 NUM_DIV_CEIL: "divCeil"
- 93 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
- 94 NUM_TO_STR: "toStr"
- 95 NUM_MIN_I8: "minI8"
- 96 NUM_MAX_I8: "maxI8"
- 97 NUM_MIN_U8: "minU8"
- 98 NUM_MAX_U8: "maxU8"
- 99 NUM_MIN_I16: "minI16"
- 100 NUM_MAX_I16: "maxI16"
- 101 NUM_MIN_U16: "minU16"
- 102 NUM_MAX_U16: "maxU16"
- 103 NUM_MIN_I32: "minI32"
- 104 NUM_MAX_I32: "maxI32"
- 105 NUM_MIN_U32: "minU32"
- 106 NUM_MAX_U32: "maxU32"
- 107 NUM_MIN_I64: "minI64"
- 108 NUM_MAX_I64: "maxI64"
- 109 NUM_MIN_U64: "minU64"
- 110 NUM_MAX_U64: "maxU64"
- 111 NUM_MIN_I128: "minI128"
- 112 NUM_MAX_I128: "maxI128"
- 113 NUM_MIN_U128: "minU128"
- 114 NUM_MAX_U128: "maxU128"
- 115 NUM_TO_I8: "toI8"
- 116 NUM_TO_I8_CHECKED: "toI8Checked"
- 117 NUM_TO_I16: "toI16"
- 118 NUM_TO_I16_CHECKED: "toI16Checked"
- 119 NUM_TO_I32: "toI32"
- 120 NUM_TO_I32_CHECKED: "toI32Checked"
- 121 NUM_TO_I64: "toI64"
- 122 NUM_TO_I64_CHECKED: "toI64Checked"
- 123 NUM_TO_I128: "toI128"
- 124 NUM_TO_I128_CHECKED: "toI128Checked"
- 125 NUM_TO_U8: "toU8"
- 126 NUM_TO_U8_CHECKED: "toU8Checked"
- 127 NUM_TO_U16: "toU16"
- 128 NUM_TO_U16_CHECKED: "toU16Checked"
- 129 NUM_TO_U32: "toU32"
- 130 NUM_TO_U32_CHECKED: "toU32Checked"
- 131 NUM_TO_U64: "toU64"
- 132 NUM_TO_U64_CHECKED: "toU64Checked"
- 133 NUM_TO_U128: "toU128"
- 134 NUM_TO_U128_CHECKED: "toU128Checked"
- 135 NUM_TO_NAT: "toNat"
- 136 NUM_TO_NAT_CHECKED: "toNatChecked"
- 137 NUM_TO_F32: "toF32"
- 138 NUM_TO_F32_CHECKED: "toF32Checked"
- 139 NUM_TO_F64: "toF64"
- 140 NUM_TO_F64_CHECKED: "toF64Checked"
- 141 NUM_MAX_F64: "maxF64"
- 142 NUM_MIN_F64: "minF64"
- 143 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
- 144 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
- 145 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
- 146 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
- 147 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
+ 91 NUM_BYTES_TO_U64: "bytesToU64"
+ 92 NUM_BYTES_TO_U128: "bytesToU128"
+ 93 NUM_CAST_TO_NAT: "#castToNat"
+ 94 NUM_DIV_CEIL: "divCeil"
+ 95 NUM_DIV_CEIL_CHECKED: "divCeilChecked"
+ 96 NUM_TO_STR: "toStr"
+ 97 NUM_MIN_I8: "minI8"
+ 98 NUM_MAX_I8: "maxI8"
+ 99 NUM_MIN_U8: "minU8"
+ 100 NUM_MAX_U8: "maxU8"
+ 101 NUM_MIN_I16: "minI16"
+ 102 NUM_MAX_I16: "maxI16"
+ 103 NUM_MIN_U16: "minU16"
+ 104 NUM_MAX_U16: "maxU16"
+ 105 NUM_MIN_I32: "minI32"
+ 106 NUM_MAX_I32: "maxI32"
+ 107 NUM_MIN_U32: "minU32"
+ 108 NUM_MAX_U32: "maxU32"
+ 109 NUM_MIN_I64: "minI64"
+ 110 NUM_MAX_I64: "maxI64"
+ 111 NUM_MIN_U64: "minU64"
+ 112 NUM_MAX_U64: "maxU64"
+ 113 NUM_MIN_I128: "minI128"
+ 114 NUM_MAX_I128: "maxI128"
+ 115 NUM_MIN_U128: "minU128"
+ 116 NUM_MAX_U128: "maxU128"
+ 117 NUM_TO_I8: "toI8"
+ 118 NUM_TO_I8_CHECKED: "toI8Checked"
+ 119 NUM_TO_I16: "toI16"
+ 120 NUM_TO_I16_CHECKED: "toI16Checked"
+ 121 NUM_TO_I32: "toI32"
+ 122 NUM_TO_I32_CHECKED: "toI32Checked"
+ 123 NUM_TO_I64: "toI64"
+ 124 NUM_TO_I64_CHECKED: "toI64Checked"
+ 125 NUM_TO_I128: "toI128"
+ 126 NUM_TO_I128_CHECKED: "toI128Checked"
+ 127 NUM_TO_U8: "toU8"
+ 128 NUM_TO_U8_CHECKED: "toU8Checked"
+ 129 NUM_TO_U16: "toU16"
+ 130 NUM_TO_U16_CHECKED: "toU16Checked"
+ 131 NUM_TO_U32: "toU32"
+ 132 NUM_TO_U32_CHECKED: "toU32Checked"
+ 133 NUM_TO_U64: "toU64"
+ 134 NUM_TO_U64_CHECKED: "toU64Checked"
+ 135 NUM_TO_U128: "toU128"
+ 136 NUM_TO_U128_CHECKED: "toU128Checked"
+ 137 NUM_TO_NAT: "toNat"
+ 138 NUM_TO_NAT_CHECKED: "toNatChecked"
+ 139 NUM_TO_F32: "toF32"
+ 140 NUM_TO_F32_CHECKED: "toF32Checked"
+ 141 NUM_TO_F64: "toF64"
+ 142 NUM_TO_F64_CHECKED: "toF64Checked"
+ 143 NUM_MAX_F64: "maxF64"
+ 144 NUM_MIN_F64: "minF64"
+ 145 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
+ 146 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
+ 147 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
+ 148 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
+ 149 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
+ 150 NUM_BYTES_TO_U64_LOWLEVEL: "bytesToU64Lowlevel"
+ 151 NUM_BYTES_TO_U128_LOWLEVEL: "bytesToU128Lowlevel"
+ 152 NUM_COUNT_LEADING_ZERO_BITS: "countLeadingZeroBits"
+ 153 NUM_COUNT_TRAILING_ZERO_BITS: "countTrailingZeroBits"
+ 154 NUM_COUNT_ONE_BITS: "countOneBits"
}
4 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" exposed_type=true // the Bool.Bool type alias
@@ -1319,6 +1326,8 @@ define_builtins! {
53 STR_WITH_CAPACITY: "withCapacity"
54 STR_WITH_PREFIX: "withPrefix"
55 STR_GRAPHEMES: "graphemes"
+ 56 STR_IS_VALID_SCALAR: "isValidScalar"
+ 57 STR_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity"
}
6 LIST: "List" => {
0 LIST_LIST: "List" exposed_apply_type=true // the List.List type alias
@@ -1401,6 +1410,8 @@ define_builtins! {
77 LIST_COUNT_IF: "countIf"
78 LIST_WALK_FROM: "walkFrom"
79 LIST_WALK_FROM_UNTIL: "walkFromUntil"
+ 80 LIST_ITER_HELP: "iterHelp"
+ 81 LIST_RELEASE_EXCESS_CAPACITY: "releaseExcessCapacity"
}
7 RESULT: "Result" => {
0 RESULT_RESULT: "Result" exposed_type=true // the Result.Result type alias
@@ -1489,11 +1500,12 @@ define_builtins! {
18 ENCODE_STRING: "string"
19 ENCODE_LIST: "list"
20 ENCODE_RECORD: "record"
- 21 ENCODE_TAG: "tag"
- 22 ENCODE_CUSTOM: "custom"
- 23 ENCODE_APPEND_WITH: "appendWith"
- 24 ENCODE_APPEND: "append"
- 25 ENCODE_TO_BYTES: "toBytes"
+ 21 ENCODE_TUPLE: "tuple"
+ 22 ENCODE_TAG: "tag"
+ 23 ENCODE_CUSTOM: "custom"
+ 24 ENCODE_APPEND_WITH: "appendWith"
+ 25 ENCODE_APPEND: "append"
+ 26 ENCODE_TO_BYTES: "toBytes"
}
12 DECODE: "Decode" => {
0 DECODE_DECODE_ERROR: "DecodeError" exposed_type=true
@@ -1519,11 +1531,12 @@ define_builtins! {
20 DECODE_STRING: "string"
21 DECODE_LIST: "list"
22 DECODE_RECORD: "record"
- 23 DECODE_CUSTOM: "custom"
- 24 DECODE_DECODE_WITH: "decodeWith"
- 25 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial"
- 26 DECODE_FROM_BYTES: "fromBytes"
- 27 DECODE_MAP_RESULT: "mapResult"
+ 23 DECODE_TUPLE: "tuple"
+ 24 DECODE_CUSTOM: "custom"
+ 25 DECODE_DECODE_WITH: "decodeWith"
+ 26 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial"
+ 27 DECODE_FROM_BYTES: "fromBytes"
+ 28 DECODE_MAP_RESULT: "mapResult"
}
13 HASH: "Hash" => {
0 HASH_HASH_ABILITY: "Hash" exposed_type=true
@@ -1535,16 +1548,17 @@ define_builtins! {
6 HASH_ADD_U32: "addU32"
7 HASH_ADD_U64: "addU64"
8 HASH_ADD_U128: "addU128"
- 9 HASH_HASH_I8: "hashI8"
- 10 HASH_HASH_I16: "hashI16"
- 11 HASH_HASH_I32: "hashI32"
- 12 HASH_HASH_I64: "hashI64"
- 13 HASH_HASH_I128: "hashI128"
- 14 HASH_HASH_NAT: "hashNat"
- 15 HASH_COMPLETE: "complete"
- 16 HASH_HASH_STR_BYTES: "hashStrBytes"
- 17 HASH_HASH_LIST: "hashList"
- 18 HASH_HASH_UNORDERED: "hashUnordered"
+ 9 HASH_HASH_BOOL: "hashBool"
+ 10 HASH_HASH_I8: "hashI8"
+ 11 HASH_HASH_I16: "hashI16"
+ 12 HASH_HASH_I32: "hashI32"
+ 13 HASH_HASH_I64: "hashI64"
+ 14 HASH_HASH_I128: "hashI128"
+ 15 HASH_HASH_NAT: "hashNat"
+ 16 HASH_COMPLETE: "complete"
+ 17 HASH_HASH_STR_BYTES: "hashStrBytes"
+ 18 HASH_HASH_LIST: "hashList"
+ 19 HASH_HASH_UNORDERED: "hashUnordered"
}
14 JSON: "Json" => {
0 JSON_JSON: "Json"
diff --git a/crates/compiler/mono/Cargo.toml b/crates/compiler/mono/Cargo.toml
index 73ce78d7a0..44887a5b8f 100644
--- a/crates/compiler/mono/Cargo.toml
+++ b/crates/compiler/mono/Cargo.toml
@@ -1,32 +1,33 @@
[package]
name = "roc_mono"
-version = "0.0.1"
-authors = ["The Roc Contributors"]
-license = "UPL-1.0"
-edition = "2021"
description = "Roc's main intermediate representation (IR), which is responsible for monomorphization, defunctionalization, inserting ref-count instructions, and transforming a Roc program into a form that is easy to consume by a backend."
+authors.workspace = true
+edition.workspace = true
+license.workspace = true
+version.workspace = true
+
[dependencies]
-roc_collections = { path = "../collections" }
-roc_exhaustive = { path = "../exhaustive" }
-roc_region = { path = "../region" }
-roc_module = { path = "../module" }
-roc_types = { path = "../types" }
-roc_can = { path = "../can" }
-roc_derive_key = { path = "../derive_key" }
-roc_derive = { path = "../derive" }
-roc_late_solve = { path = "../late_solve" }
-roc_std = { path = "../../roc_std" }
-roc_problem = { path = "../problem" }
roc_builtins = { path = "../builtins" }
+roc_can = { path = "../can" }
+roc_collections = { path = "../collections" }
+roc_debug_flags = { path = "../debug_flags" }
+roc_derive = { path = "../derive" }
+roc_derive_key = { path = "../derive_key" }
+roc_error_macros = { path = "../../error_macros" }
+roc_exhaustive = { path = "../exhaustive" }
+roc_late_solve = { path = "../late_solve" }
+roc_module = { path = "../module" }
+roc_problem = { path = "../problem" }
+roc_region = { path = "../region" }
+roc_std = { path = "../../roc_std" }
roc_target = { path = "../roc_target" }
-roc_error_macros = {path="../../error_macros"}
-roc_debug_flags = {path="../debug_flags"}
roc_tracing = { path = "../../tracing" }
+roc_types = { path = "../types" }
ven_pretty = { path = "../../vendor/pretty" }
+bitvec.workspace = true
bumpalo.workspace = true
hashbrown.workspace = true
-static_assertions.workspace = true
-bitvec.workspace = true
parking_lot.workspace = true
+static_assertions.workspace = true
diff --git a/crates/compiler/mono/src/borrow.rs b/crates/compiler/mono/src/borrow.rs
index f6e19109b1..f53e354175 100644
--- a/crates/compiler/mono/src/borrow.rs
+++ b/crates/compiler/mono/src/borrow.rs
@@ -100,8 +100,14 @@ pub fn infer_borrow<'a>(
// host-exposed functions must always own their arguments.
let is_host_exposed = host_exposed_procs.contains(&key.0);
- let param_offset = param_map.get_param_offset(key.0, key.1);
- env.collect_proc(&mut param_map, proc, param_offset, is_host_exposed);
+ let param_offset = param_map.get_param_offset(interner, key.0, key.1);
+ env.collect_proc(
+ interner,
+ &mut param_map,
+ proc,
+ param_offset,
+ is_host_exposed,
+ );
}
if !env.modified {
@@ -167,6 +173,7 @@ impl<'a> DeclarationToIndex<'a> {
fn get_param_offset(
&self,
+ interner: &STLayoutInterner<'a>,
needle_symbol: Symbol,
needle_layout: ProcLayout<'a>,
) -> ParamOffset {
@@ -181,12 +188,14 @@ impl<'a> DeclarationToIndex<'a> {
.elements
.iter()
.filter_map(|(Declaration { symbol, layout }, _)| {
- (*symbol == needle_symbol).then_some(layout)
+ (*symbol == needle_symbol)
+ .then_some(layout)
+ .map(|l| l.dbg_deep(interner))
})
.collect::>();
unreachable!(
"symbol/layout {:?} {:#?} combo must be in DeclarationToIndex\nHowever {} similar layouts were found:\n{:#?}",
- needle_symbol, needle_layout, similar.len(), similar
+ needle_symbol, needle_layout.dbg_deep(interner), similar.len(), similar,
)
}
}
@@ -206,13 +215,24 @@ pub struct ParamMap<'a> {
}
impl<'a> ParamMap<'a> {
- pub fn get_param_offset(&self, symbol: Symbol, layout: ProcLayout<'a>) -> ParamOffset {
- self.declaration_to_index.get_param_offset(symbol, layout)
+ pub fn get_param_offset(
+ &self,
+ interner: &STLayoutInterner<'a>,
+ symbol: Symbol,
+ layout: ProcLayout<'a>,
+ ) -> ParamOffset {
+ self.declaration_to_index
+ .get_param_offset(interner, symbol, layout)
}
- pub fn get_symbol(&self, symbol: Symbol, layout: ProcLayout<'a>) -> Option<&[Param<'a>]> {
+ pub fn get_symbol(
+ &self,
+ interner: &STLayoutInterner<'a>,
+ 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();
+ let index: usize = self.get_param_offset(interner, symbol, layout).into();
self.declarations.get(index..index + layout.arguments.len())
}
@@ -292,7 +312,7 @@ impl<'a> ParamMap<'a> {
// return;
// }
- let index: usize = self.get_param_offset(key.0, key.1).into();
+ let index: usize = self.get_param_offset(interner, key.0, key.1).into();
for (i, param) in Self::init_borrow_args(arena, interner, proc.args)
.iter()
@@ -312,7 +332,7 @@ impl<'a> ParamMap<'a> {
proc: &Proc<'a>,
key: (Symbol, ProcLayout<'a>),
) {
- let index: usize = self.get_param_offset(key.0, key.1).into();
+ let index: usize = self.get_param_offset(interner, key.0, key.1).into();
for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args)
.iter()
@@ -534,7 +554,13 @@ impl<'a> BorrowInfState<'a> {
///
/// and determines whether z and which of the symbols used in e
/// must be taken as owned parameters
- fn collect_call(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &crate::ir::Call<'a>) {
+ fn collect_call(
+ &mut self,
+ interner: &STLayoutInterner<'a>,
+ param_map: &mut ParamMap<'a>,
+ z: Symbol,
+ e: &crate::ir::Call<'a>,
+ ) {
use crate::ir::CallType::*;
let crate::ir::Call {
@@ -553,7 +579,7 @@ impl<'a> BorrowInfState<'a> {
// get the borrow signature of the applied function
let ps = param_map
- .get_symbol(name.name(), top_level)
+ .get_symbol(interner, name.name(), top_level)
.expect("function is defined");
// the return value will be owned
@@ -595,11 +621,14 @@ impl<'a> BorrowInfState<'a> {
niche: passed_function.name.niche(),
};
- let function_ps =
- match param_map.get_symbol(passed_function.name.name(), closure_layout) {
- Some(function_ps) => function_ps,
- None => unreachable!(),
- };
+ let function_ps = match param_map.get_symbol(
+ interner,
+ passed_function.name.name(),
+ closure_layout,
+ ) {
+ Some(function_ps) => function_ps,
+ None => unreachable!(),
+ };
match op {
ListMap { xs } => {
@@ -671,7 +700,13 @@ impl<'a> BorrowInfState<'a> {
}
}
- fn collect_expr(&mut self, param_map: &mut ParamMap<'a>, z: Symbol, e: &Expr<'a>) {
+ fn collect_expr(
+ &mut self,
+ interner: &STLayoutInterner<'a>,
+ param_map: &mut ParamMap<'a>,
+ z: Symbol,
+ e: &Expr<'a>,
+ ) {
use Expr::*;
match e {
@@ -724,7 +759,7 @@ impl<'a> BorrowInfState<'a> {
self.own_var(z);
}
- Call(call) => self.collect_call(param_map, z, call),
+ Call(call) => self.collect_call(interner, param_map, z, call),
Literal(_) | RuntimeErrorFunction(_) => {}
@@ -757,6 +792,7 @@ impl<'a> BorrowInfState<'a> {
#[allow(clippy::many_single_char_names)]
fn preserve_tail_call(
&mut self,
+ interner: &STLayoutInterner<'a>,
param_map: &mut ParamMap<'a>,
x: Symbol,
v: &Expr<'a>,
@@ -782,7 +818,7 @@ impl<'a> BorrowInfState<'a> {
if self.current_proc == g.name() && x == *z {
// anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine
- if let Some(ps) = param_map.get_symbol(g.name(), top_level) {
+ if let Some(ps) = param_map.get_symbol(interner, g.name(), top_level) {
self.own_params_using_args(ys, ps)
}
}
@@ -801,7 +837,12 @@ impl<'a> BorrowInfState<'a> {
}
}
- fn collect_stmt(&mut self, param_map: &mut ParamMap<'a>, stmt: &Stmt<'a>) {
+ fn collect_stmt(
+ &mut self,
+ interner: &STLayoutInterner<'a>,
+ param_map: &mut ParamMap<'a>,
+ stmt: &Stmt<'a>,
+ ) {
use Stmt::*;
match stmt {
@@ -813,11 +854,11 @@ impl<'a> BorrowInfState<'a> {
} => {
let old = self.param_set.clone();
self.update_param_set(ys);
- self.collect_stmt(param_map, v);
+ self.collect_stmt(interner, param_map, v);
self.param_set = old;
self.update_param_map_join_point(param_map, *j);
- self.collect_stmt(param_map, b);
+ self.collect_stmt(interner, param_map, b);
}
Let(x, v, _, mut b) => {
@@ -830,17 +871,17 @@ impl<'a> BorrowInfState<'a> {
stack.push((*symbol, expr));
}
- self.collect_stmt(param_map, b);
+ self.collect_stmt(interner, param_map, b);
let mut it = stack.into_iter().rev();
// collect the final expr, and see if we need to preserve a tail call
let (x, v) = it.next().unwrap();
- self.collect_expr(param_map, x, v);
- self.preserve_tail_call(param_map, x, v, b);
+ self.collect_expr(interner, param_map, x, v);
+ self.preserve_tail_call(interner, param_map, x, v, b);
for (x, v) in it {
- self.collect_expr(param_map, x, v);
+ self.collect_expr(interner, param_map, x, v);
}
}
@@ -859,21 +900,21 @@ impl<'a> BorrowInfState<'a> {
..
} => {
for (_, _, b) in branches.iter() {
- self.collect_stmt(param_map, b);
+ self.collect_stmt(interner, param_map, b);
}
- self.collect_stmt(param_map, default_branch.1);
+ self.collect_stmt(interner, param_map, default_branch.1);
}
Dbg { remainder, .. } => {
- self.collect_stmt(param_map, remainder);
+ self.collect_stmt(interner, param_map, remainder);
}
Expect { remainder, .. } => {
- self.collect_stmt(param_map, remainder);
+ self.collect_stmt(interner, param_map, remainder);
}
ExpectFx { remainder, .. } => {
- self.collect_stmt(param_map, remainder);
+ self.collect_stmt(interner, param_map, remainder);
}
Refcounting(_, _) => unreachable!("these have not been introduced yet"),
@@ -891,6 +932,7 @@ impl<'a> BorrowInfState<'a> {
fn collect_proc(
&mut self,
+ interner: &STLayoutInterner<'a>,
param_map: &mut ParamMap<'a>,
proc: &Proc<'a>,
param_offset: ParamOffset,
@@ -912,7 +954,7 @@ impl<'a> BorrowInfState<'a> {
owned_entry.extend(params.iter().map(|p| p.symbol));
}
- self.collect_stmt(param_map, &proc.body);
+ self.collect_stmt(interner, param_map, &proc.body);
self.update_param_map_declaration(param_map, param_offset, proc.args.len());
self.param_set = old;
@@ -974,6 +1016,8 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListSublist => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
+ ListReleaseExcessCapacity => arena.alloc_slice_copy(&[owned]),
+ StrReleaseExcessCapacity => arena.alloc_slice_copy(&[owned]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),
@@ -984,13 +1028,33 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumPow | NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
- NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
- | NumRound | NumCeiling | NumFloor | NumToFrac | Not | NumIsFinite | NumAtan | NumAcos
- | NumAsin | NumIntCast | NumToIntChecked | NumToFloatCast | NumToFloatChecked => {
- arena.alloc_slice_copy(&[irrelevant])
- }
+ NumToStr
+ | NumAbs
+ | NumNeg
+ | NumSin
+ | NumCos
+ | NumSqrtUnchecked
+ | NumLogUnchecked
+ | NumRound
+ | NumCeiling
+ | NumFloor
+ | NumToFrac
+ | Not
+ | NumIsFinite
+ | NumAtan
+ | NumAcos
+ | NumAsin
+ | NumIntCast
+ | NumToIntChecked
+ | NumToFloatCast
+ | NumToFloatChecked
+ | NumCountLeadingZeroBits
+ | NumCountTrailingZeroBits
+ | NumCountOneBits => arena.alloc_slice_copy(&[irrelevant]),
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
+ NumBytesToU64 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
+ NumBytesToU128 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromUtf8Range => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
diff --git a/crates/compiler/mono/src/code_gen_help/mod.rs b/crates/compiler/mono/src/code_gen_help/mod.rs
index e7170174c3..676d368932 100644
--- a/crates/compiler/mono/src/code_gen_help/mod.rs
+++ b/crates/compiler/mono/src/code_gen_help/mod.rs
@@ -125,16 +125,6 @@ impl<'a> CodeGenHelp<'a> {
modify: &ModifyRc,
following: &'a Stmt<'a>,
) -> (&'a Stmt<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) {
- if !refcount::is_rc_implemented_yet(layout_interner, layout) {
- // Just a warning, so we can decouple backend development from refcounting development.
- // When we are closer to completion, we can change it to a panic.
- println!(
- "WARNING! MEMORY LEAK! Refcounting not yet implemented for Layout {:?}",
- layout
- );
- return (following, Vec::new_in(self.arena));
- }
-
let op = match modify {
ModifyRc::Inc(..) => HelperOp::Inc,
ModifyRc::Dec(_) => HelperOp::Dec,
diff --git a/crates/compiler/mono/src/code_gen_help/refcount.rs b/crates/compiler/mono/src/code_gen_help/refcount.rs
index 3f5bbcea99..e18fcee734 100644
--- a/crates/compiler/mono/src/code_gen_help/refcount.rs
+++ b/crates/compiler/mono/src/code_gen_help/refcount.rs
@@ -74,7 +74,7 @@ pub fn refcount_stmt<'a>(
ModifyRc::DecRef(structure) => {
match layout_interner.get(layout) {
- // Str has no children, so we might as well do what we normally do and call the helper.
+ // Str has no children, so Dec is the same as DecRef.
Layout::Builtin(Builtin::Str) => {
ctx.op = HelperOp::Dec;
refcount_stmt(
@@ -127,8 +127,6 @@ pub fn refcount_generic<'a>(
layout: InLayout<'a>,
structure: Symbol,
) -> Stmt<'a> {
- debug_assert!(is_rc_implemented_yet(layout_interner, layout));
-
match layout_interner.get(layout) {
Layout::Builtin(Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal) => {
// Generate a dummy function that immediately returns Unit
@@ -141,7 +139,6 @@ pub fn refcount_generic<'a>(
ident_ids,
ctx,
layout_interner,
- layout,
elem_layout,
structure,
),
@@ -416,44 +413,6 @@ pub fn refcount_reset_proc_body<'a>(
rc_ptr_stmt
}
-// Check if refcounting is implemented yet. In the long term, this will be deleted.
-// In the short term, it helps us to skip refcounting and let it leak, so we can make
-// progress incrementally. Kept in sync with generate_procs using assertions.
-pub fn is_rc_implemented_yet<'a, I>(interner: &I, layout: InLayout<'a>) -> bool
-where
- I: LayoutInterner<'a>,
-{
- use UnionLayout::*;
-
- match interner.get(layout) {
- Layout::Builtin(Builtin::List(elem_layout)) => is_rc_implemented_yet(interner, elem_layout),
- Layout::Builtin(_) => true,
- Layout::Struct { field_layouts, .. } => field_layouts
- .iter()
- .all(|l| is_rc_implemented_yet(interner, *l)),
- Layout::Union(union_layout) => match union_layout {
- NonRecursive(tags) => tags
- .iter()
- .all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))),
- Recursive(tags) => tags
- .iter()
- .all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))),
- NonNullableUnwrapped(fields) => {
- fields.iter().all(|l| is_rc_implemented_yet(interner, *l))
- }
- NullableWrapped { other_tags, .. } => other_tags
- .iter()
- .all(|fields| fields.iter().all(|l| is_rc_implemented_yet(interner, *l))),
- NullableUnwrapped { other_fields, .. } => other_fields
- .iter()
- .all(|l| is_rc_implemented_yet(interner, *l)),
- },
- Layout::LambdaSet(lambda_set) => is_rc_implemented_yet(interner, lambda_set.representation),
- Layout::RecursivePointer(_) => true,
- Layout::Boxed(_) => true,
- }
-}
-
fn rc_return_stmt<'a>(
root: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
@@ -765,7 +724,6 @@ fn refcount_list<'a>(
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
layout_interner: &mut STLayoutInterner<'a>,
- layout: InLayout,
elem_layout: InLayout<'a>,
structure: Symbol,
) -> Stmt<'a> {
@@ -773,8 +731,7 @@ fn refcount_list<'a>(
let arena = root.arena;
// A "Box" layout (heap pointer to a single list element)
- let box_union_layout = UnionLayout::NonNullableUnwrapped(arena.alloc([elem_layout]));
- let box_layout = layout_interner.insert(Layout::Union(box_union_layout));
+ let box_layout = layout_interner.insert(Layout::Boxed(elem_layout));
//
// Check if the list is empty
@@ -814,7 +771,7 @@ fn refcount_list<'a>(
//
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
- let alignment = layout_interner.alignment_bytes(layout);
+ let elem_alignment = layout_interner.alignment_bytes(elem_layout);
let ret_stmt = rc_return_stmt(root, ident_ids, ctx);
let modify_list = modify_refcount(
@@ -822,7 +779,7 @@ fn refcount_list<'a>(
ident_ids,
ctx,
rc_ptr,
- alignment,
+ elem_alignment,
arena.alloc(ret_stmt),
);
@@ -845,7 +802,7 @@ fn refcount_list<'a>(
layout_interner,
elem_layout,
LAYOUT_UNIT,
- box_union_layout,
+ box_layout,
len,
elements,
get_rc_and_modify_list,
@@ -895,7 +852,7 @@ fn refcount_list_elems<'a>(
layout_interner: &mut STLayoutInterner<'a>,
elem_layout: InLayout<'a>,
ret_layout: InLayout<'a>,
- box_union_layout: UnionLayout<'a>,
+ box_layout: InLayout<'a>,
length: Symbol,
elements: Symbol,
following: Stmt<'a>,
@@ -955,17 +912,11 @@ fn refcount_list_elems<'a>(
// Cast integer to box pointer
let box_ptr = root.create_symbol(ident_ids, "box");
- let box_layout = layout_interner.insert(Layout::Union(box_union_layout));
let box_stmt = |next| let_lowlevel(arena, box_layout, box_ptr, PtrCast, &[addr], next);
// Dereference the box pointer to get the current element
let elem = root.create_symbol(ident_ids, "elem");
- let elem_expr = Expr::UnionAtIndex {
- structure: box_ptr,
- union_layout: box_union_layout,
- tag_id: 0,
- index: 0,
- };
+ let elem_expr = Expr::ExprUnbox { symbol: box_ptr };
let elem_stmt = |next| Stmt::Let(elem, elem_expr, elem_layout, next);
//
diff --git a/crates/compiler/mono/src/debug/checker.rs b/crates/compiler/mono/src/debug/checker.rs
index d540b8a2c0..94114322ba 100644
--- a/crates/compiler/mono/src/debug/checker.rs
+++ b/crates/compiler/mono/src/debug/checker.rs
@@ -10,8 +10,7 @@ use crate::{
ModifyRc, Param, Proc, ProcLayout, Stmt,
},
layout::{
- Builtin, InLayout, LambdaSet, Layout, LayoutInterner, STLayoutInterner, TagIdIntType,
- UnionLayout,
+ Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, TagIdIntType, UnionLayout,
},
};
@@ -87,7 +86,7 @@ pub enum ProblemKind<'a> {
structure: Symbol,
def_line: usize,
tag_id: u16,
- union_layout: UnionLayout<'a>,
+ union_layout: InLayout<'a>,
},
TagUnionStructIndexOOB {
structure: Symbol,
@@ -100,7 +99,7 @@ pub enum ProblemKind<'a> {
structure: Symbol,
def_line: usize,
tag_id: u16,
- union_layout: UnionLayout<'a>,
+ union_layout: InLayout<'a>,
},
UnboxNotABox {
symbol: Symbol,
@@ -108,7 +107,7 @@ pub enum ProblemKind<'a> {
},
CreatingTagIdNotInUnion {
tag_id: u16,
- union_layout: UnionLayout<'a>,
+ union_layout: InLayout<'a>,
},
CreateTagPayloadMismatch {
num_needed: usize,
@@ -193,11 +192,12 @@ impl<'a, 'r> Ctx<'a, 'r> {
r
}
- fn resolve(&mut self, mut layout: InLayout<'a>) -> InLayout<'a> {
+ fn resolve(&self, mut layout: InLayout<'a>) -> InLayout<'a> {
// Note that we are more aggressive than the usual `runtime_representation`
// here because we need strict equality, and so cannot unwrap lambda sets
// lazily.
loop {
+ layout = self.interner.chase_recursive_in(layout);
match self.interner.get(layout) {
Layout::LambdaSet(ls) => layout = ls.representation,
_ => return layout,
@@ -205,6 +205,12 @@ impl<'a, 'r> Ctx<'a, 'r> {
}
}
+ fn not_equiv(&mut self, layout1: InLayout<'a>, layout2: InLayout<'a>) -> bool {
+ !self
+ .interner
+ .equiv(self.resolve(layout1), self.resolve(layout2))
+ }
+
fn insert(&mut self, symbol: Symbol, layout: InLayout<'a>) {
if let Some((old_line, _)) = self.venv.insert(symbol, (self.line, layout)) {
self.problem(ProblemKind::RedefinedSymbol { symbol, old_line })
@@ -237,7 +243,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
use_kind: UseKind,
) {
if let Some(&(def_line, layout)) = self.venv.get(&symbol) {
- if self.resolve(layout) != self.resolve(expected_layout) {
+ if self.not_equiv(layout, expected_layout) {
self.problem(ProblemKind::SymbolUseMismatch {
symbol,
def_layout: layout,
@@ -265,7 +271,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
match body {
Stmt::Let(x, e, x_layout, rest) => {
if let Some(e_layout) = self.check_expr(e) {
- if self.resolve(e_layout) != self.resolve(*x_layout) {
+ if self.not_equiv(e_layout, *x_layout) {
self.problem(ProblemKind::SymbolDefMismatch {
symbol: *x,
def_layout: *x_layout,
@@ -390,8 +396,9 @@ impl<'a, 'r> Ctx<'a, 'r> {
tag_id,
arguments,
} => {
- self.check_tag_expr(tag_layout, tag_id, arguments);
- Some(self.interner.insert(Layout::Union(tag_layout)))
+ let interned_layout = self.interner.insert(Layout::Union(tag_layout));
+ self.check_tag_expr(interned_layout, tag_layout, tag_id, arguments);
+ Some(interned_layout)
}
Expr::Struct(syms) => {
for sym in syms.iter() {
@@ -415,7 +422,9 @@ impl<'a, 'r> Ctx<'a, 'r> {
tag_id,
union_layout,
index,
- } => self.check_union_at_index(structure, union_layout, tag_id, index),
+ } => self.with_sym_layout(structure, |ctx, _def_line, layout| {
+ ctx.check_union_at_index(structure, layout, union_layout, tag_id, index)
+ }),
Expr::Array { elem_layout, elems } => {
for elem in elems.iter() {
match elem {
@@ -503,6 +512,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
fn check_union_at_index(
&mut self,
structure: Symbol,
+ interned_union_layout: InLayout<'a>,
union_layout: UnionLayout<'a>,
tag_id: u16,
index: u64,
@@ -517,7 +527,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
structure,
def_line,
tag_id,
- union_layout,
+ union_layout: interned_union_layout,
});
None
}
@@ -532,12 +542,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
});
return None;
}
- let layout = resolve_recursive_layout(
- ctx.arena,
- ctx.interner,
- payloads[index as usize],
- union_layout,
- );
+ let layout = payloads[index as usize];
Some(layout)
}
}
@@ -605,12 +610,18 @@ impl<'a, 'r> Ctx<'a, 'r> {
}
}
- fn check_tag_expr(&mut self, union_layout: UnionLayout<'a>, tag_id: u16, arguments: &[Symbol]) {
+ fn check_tag_expr(
+ &mut self,
+ interned_union_layout: InLayout<'a>,
+ union_layout: UnionLayout<'a>,
+ tag_id: u16,
+ arguments: &[Symbol],
+ ) {
match get_tag_id_payloads(union_layout, tag_id) {
TagPayloads::IdNotInUnion => {
self.problem(ProblemKind::CreatingTagIdNotInUnion {
tag_id,
- union_layout,
+ union_layout: interned_union_layout,
});
}
TagPayloads::Payloads(payloads) => {
@@ -621,13 +632,7 @@ impl<'a, 'r> Ctx<'a, 'r> {
});
}
for (arg, wanted_layout) in arguments.iter().zip(payloads.iter()) {
- let wanted_layout = resolve_recursive_layout(
- self.arena,
- self.interner,
- *wanted_layout,
- union_layout,
- );
- self.check_sym_layout(*arg, wanted_layout, UseKind::TagPayloadArg);
+ self.check_sym_layout(*arg, *wanted_layout, UseKind::TagPayloadArg);
}
}
}
@@ -643,94 +648,6 @@ impl<'a, 'r> Ctx<'a, 'r> {
}
}
-fn resolve_recursive_layout<'a>(
- arena: &'a Bump,
- interner: &mut STLayoutInterner<'a>,
- layout: InLayout<'a>,
- when_recursive: UnionLayout<'a>,
-) -> InLayout<'a> {
- macro_rules! go {
- ($lay:expr) => {
- resolve_recursive_layout(arena, interner, $lay, when_recursive)
- };
- }
-
- // TODO check if recursive pointer not in recursive union
- let layout = match interner.get(layout) {
- Layout::RecursivePointer(_) => Layout::Union(when_recursive),
- Layout::Union(union_layout) => match union_layout {
- UnionLayout::NonRecursive(payloads) => {
- let payloads = payloads.iter().map(|args| {
- let args = args.iter().map(|lay| go!(*lay));
- &*arena.alloc_slice_fill_iter(args)
- });
- let payloads = arena.alloc_slice_fill_iter(payloads);
- Layout::Union(UnionLayout::NonRecursive(payloads))
- }
- UnionLayout::Recursive(_)
- | UnionLayout::NonNullableUnwrapped(_)
- | UnionLayout::NullableWrapped { .. }
- | UnionLayout::NullableUnwrapped { .. } => {
- // This is the recursive layout.
- // TODO will need fixing to be modified once we support multiple
- // recursive pointers in one structure.
- return layout;
- }
- },
- Layout::Boxed(inner) => {
- let inner = go!(inner);
- Layout::Boxed(inner)
- }
- Layout::Struct {
- field_order_hash,
- field_layouts,
- } => {
- let field_layouts = field_layouts
- .iter()
- .map(|lay| resolve_recursive_layout(arena, interner, *lay, when_recursive));
- let field_layouts = arena.alloc_slice_fill_iter(field_layouts);
- Layout::Struct {
- field_order_hash,
- field_layouts,
- }
- }
- Layout::Builtin(builtin) => match builtin {
- Builtin::List(inner) => {
- let inner = resolve_recursive_layout(arena, interner, inner, when_recursive);
- Layout::Builtin(Builtin::List(inner))
- }
- Builtin::Int(_)
- | Builtin::Float(_)
- | Builtin::Bool
- | Builtin::Decimal
- | Builtin::Str => return layout,
- },
- Layout::LambdaSet(LambdaSet {
- args,
- ret,
- set,
- representation,
- full_layout,
- }) => {
- let set = set.iter().map(|(symbol, captures)| {
- let captures = captures.iter().map(|lay_in| go!(*lay_in));
- let captures = &*arena.alloc_slice_fill_iter(captures);
- (*symbol, captures)
- });
- let set = arena.alloc_slice_fill_iter(set);
- Layout::LambdaSet(LambdaSet {
- args,
- ret,
- set: arena.alloc(&*set),
- representation,
- full_layout,
- })
- }
- };
-
- interner.insert(layout)
-}
-
enum TagPayloads<'a> {
IdNotInUnion,
Payloads(&'a [InLayout<'a>]),
diff --git a/crates/compiler/mono/src/debug/report.rs b/crates/compiler/mono/src/debug/report.rs
index 177ccab0db..48576d3df1 100644
--- a/crates/compiler/mono/src/debug/report.rs
+++ b/crates/compiler/mono/src/debug/report.rs
@@ -5,7 +5,7 @@ use ven_pretty::{Arena, DocAllocator, DocBuilder};
use crate::{
ir::{Parens, ProcLayout},
- layout::{Layout, LayoutInterner},
+ layout::LayoutInterner,
};
use super::{
@@ -157,7 +157,7 @@ where
f.concat([
format_symbol(f, interns, symbol),
f.reflow(" defined here with layout "),
- interner.to_doc(def_layout, f, Parens::NotNeeded),
+ interner.to_doc_top(def_layout, f),
]),
)];
f.concat([
@@ -165,7 +165,7 @@ where
f.reflow(" used as a "),
f.reflow(format_use_kind(use_kind)),
f.reflow(" here with layout "),
- interner.to_doc(use_layout, f, Parens::NotNeeded),
+ interner.to_doc_top(use_layout, f),
])
}
ProblemKind::SymbolDefMismatch {
@@ -178,9 +178,9 @@ where
f.concat([
format_symbol(f, interns, symbol),
f.reflow(" is defined as "),
- interner.to_doc(def_layout, f, Parens::NotNeeded),
+ interner.to_doc_top(def_layout, f),
f.reflow(" but its initializer is "),
- interner.to_doc(expr_layout, f, Parens::NotNeeded),
+ interner.to_doc_top(expr_layout, f),
])
}
ProblemKind::BadSwitchConditionLayout { found_layout } => {
@@ -188,7 +188,7 @@ where
docs_before = vec![];
f.concat([
f.reflow("This switch condition is a "),
- interner.to_doc(found_layout, f, Parens::NotNeeded),
+ interner.to_doc_top(found_layout, f),
])
}
ProblemKind::DuplicateSwitchBranch {} => {
@@ -324,7 +324,7 @@ where
f.reflow("The union "),
format_symbol(f, interns, structure),
f.reflow(" defined here has layout "),
- Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded),
+ interner.to_doc_top(union_layout, f),
]),
)];
f.concat([f.reflow("which has no tag of id "), f.as_string(tag_id)])
@@ -367,7 +367,7 @@ where
f.reflow("The union "),
format_symbol(f, interns, structure),
f.reflow(" defined here has layout "),
- Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded),
+ interner.to_doc_top(union_layout, f),
]),
)];
f.concat([
@@ -394,7 +394,7 @@ where
f.reflow("The variant "),
f.as_string(tag_id),
f.reflow(" is outside the target union layout "),
- Layout::Union(union_layout).to_doc(f, interner, Parens::NotNeeded),
+ interner.to_doc_top(union_layout, f),
])
}
ProblemKind::CreateTagPayloadMismatch {
@@ -469,16 +469,16 @@ where
let args = f.intersperse(
arguments
.iter()
- .map(|a| interner.to_doc(*a, f, Parens::InFunction)),
+ .map(|a| interner.to_doc(*a, f, &mut Default::default(), Parens::InFunction)),
f.reflow(", "),
);
let fun = f.concat([
f.concat([f.reflow("("), args, f.reflow(")")]),
f.reflow(" -> "),
- interner.to_doc(result, f, Parens::NotNeeded),
+ interner.to_doc_top(result, f),
]);
let niche = (f.text("("))
- .append(captures_niche.to_doc(f, interner))
+ .append(captures_niche.to_doc(f, interner, &mut Default::default()))
.append(f.text(")"));
f.concat([fun, f.space(), niche])
}
diff --git a/crates/compiler/mono/src/inc_dec.rs b/crates/compiler/mono/src/inc_dec.rs
index eab6ec8acf..56b543f612 100644
--- a/crates/compiler/mono/src/inc_dec.rs
+++ b/crates/compiler/mono/src/inc_dec.rs
@@ -605,7 +605,7 @@ impl<'a, 'i> Context<'a, 'i> {
// get the borrow signature
let ps = self
.param_map
- .get_symbol(name.name(), top_level)
+ .get_symbol(self.layout_interner, name.name(), top_level)
.expect("function is defined");
let v = Expr::Call(crate::ir::Call {
@@ -653,10 +653,11 @@ impl<'a, 'i> Context<'a, 'i> {
niche: passed_function.name.niche(),
};
- let function_ps = match self
- .param_map
- .get_symbol(passed_function.name.name(), function_layout)
- {
+ let function_ps = match self.param_map.get_symbol(
+ self.layout_interner,
+ passed_function.name.name(),
+ function_layout,
+ ) {
Some(function_ps) => function_ps,
None => unreachable!(),
};
@@ -671,14 +672,14 @@ impl<'a, 'i> Context<'a, 'i> {
match ownership {
DataOwnedFunctionOwns | DataBorrowedFunctionOwns => {
// elements have been consumed, must still consume the list itself
- let rest = self.arena.alloc($stmt);
+ let rest = self.arena.alloc(stmt);
let rc = Stmt::Refcounting(ModifyRc::DecRef(argument), rest);
stmt = self.arena.alloc(rc);
}
DataOwnedFunctionBorrows => {
// must consume list and elements
- let rest = self.arena.alloc($stmt);
+ let rest = self.arena.alloc(stmt);
let rc = Stmt::Refcounting(ModifyRc::Dec(argument), rest);
stmt = self.arena.alloc(rc);
@@ -1510,19 +1511,28 @@ pub fn visit_procs<'a, 'i>(
};
for (key, proc) in procs.iter_mut() {
- visit_proc(arena, &mut codegen, param_map, &ctx, proc, key.1);
+ visit_proc(
+ arena,
+ layout_interner,
+ &mut codegen,
+ param_map,
+ &ctx,
+ proc,
+ key.1,
+ );
}
}
fn visit_proc<'a, 'i>(
arena: &'a Bump,
+ interner: &STLayoutInterner<'a>,
codegen: &mut CodegenTools<'i>,
param_map: &'a ParamMap<'a>,
ctx: &Context<'a, 'i>,
proc: &mut Proc<'a>,
layout: ProcLayout<'a>,
) {
- let params = match param_map.get_symbol(proc.name.name(), layout) {
+ let params = match param_map.get_symbol(interner, proc.name.name(), layout) {
Some(slice) => slice,
None => Vec::from_iter_in(
proc.args.iter().cloned().map(|(layout, symbol)| Param {
diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs
index 0539bd5e05..d1ac1e6ab4 100644
--- a/crates/compiler/mono/src/ir.rs
+++ b/crates/compiler/mono/src/ir.rs
@@ -1,6 +1,7 @@
#![allow(clippy::manual_map)]
use crate::borrow::Ownership;
+use crate::ir::literal::{make_num_literal, IntOrFloatValue};
use crate::layout::{
self, Builtin, ClosureCallOptions, ClosureRepresentation, EnumDispatch, InLayout, LambdaName,
LambdaSet, Layout, LayoutCache, LayoutInterner, LayoutProblem, Niche, RawFunctionLayout,
@@ -8,9 +9,8 @@ use crate::layout::{
};
use bumpalo::collections::{CollectIn, Vec};
use bumpalo::Bump;
-use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_can::abilities::SpecializationId;
-use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup, IntValue};
+use roc_can::expr::{AnnotatedMark, ClosureData, ExpectLookup};
use roc_can::module::ExposedByModule;
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, MutMap};
use roc_collections::VecMap;
@@ -22,7 +22,6 @@ use roc_debug_flags::{
};
use roc_derive::SharedDerivedModule;
use roc_error_macros::{internal_error, todo_abilities};
-use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId};
use roc_late_solve::storage::{ExternalModuleStorage, ExternalModuleStorageSnapshot};
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
use roc_module::ident::{ForeignSymbol, Lowercase, TagName};
@@ -39,6 +38,14 @@ use roc_types::subs::{
use std::collections::HashMap;
use ven_pretty::{BoxAllocator, DocAllocator, DocBuilder};
+use pattern::{from_can_pattern, store_pattern, Pattern};
+
+pub use literal::{ListLiteralElement, Literal};
+
+mod decision_tree;
+mod literal;
+mod pattern;
+
#[inline(always)]
pub fn pretty_print_ir_symbols() -> bool {
dbg_do!(ROC_PRINT_IR_AFTER_SPECIALIZATION, {
@@ -305,12 +312,21 @@ pub struct Proc<'a> {
pub host_exposed_layouts: HostExposedLayouts<'a>,
}
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub struct HostExposedLambdaSet<'a> {
+ pub id: LambdaSetId,
+ /// Symbol of the exposed function
+ pub symbol: Symbol,
+ pub proc_layout: ProcLayout<'a>,
+ pub raw_function_layout: RawFunctionLayout<'a>,
+}
+
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum HostExposedLayouts<'a> {
NotHostExposed,
HostExposed {
rigids: BumpMap>,
- aliases: BumpMap, RawFunctionLayout<'a>)>,
+ aliases: BumpMap>,
},
}
@@ -344,11 +360,9 @@ impl<'a> Proc<'a> {
let args_doc = self.args.iter().map(|(layout, symbol)| {
let arg_doc = symbol_to_doc(alloc, *symbol, pretty);
if pretty_print_ir_symbols() {
- arg_doc.append(alloc.reflow(": ")).append(interner.to_doc(
- *layout,
- alloc,
- Parens::NotNeeded,
- ))
+ arg_doc
+ .append(alloc.reflow(": "))
+ .append(interner.to_doc_top(*layout, alloc))
} else {
arg_doc
}
@@ -359,7 +373,7 @@ impl<'a> Proc<'a> {
.text("procedure : ")
.append(symbol_to_doc(alloc, self.name.name(), pretty))
.append(" ")
- .append(interner.to_doc(self.ret_layout, alloc, Parens::NotNeeded))
+ .append(interner.to_doc_top(self.ret_layout, alloc))
.append(alloc.hardline())
.append(alloc.text("procedure = "))
.append(symbol_to_doc(alloc, self.name.name(), pretty))
@@ -937,6 +951,16 @@ pub struct ProcsBase<'a> {
pub imported_module_thunks: &'a [Symbol],
}
+impl<'a> ProcsBase<'a> {
+ pub fn get_host_exposed_symbols(&self) -> impl Iterator
- + '_ {
+ self.host_specializations
+ .symbol_or_lambdas
+ .iter()
+ .copied()
+ .map(|n| n.name())
+ }
+}
+
/// The current set of functions under specialization. They form a stack where the latest
/// specialization to be seen is at the head of the stack.
#[derive(Clone, Debug)]
@@ -955,14 +979,16 @@ impl<'a> SpecializationStack<'a> {
pub struct Procs<'a> {
pub partial_procs: PartialProcs<'a>,
ability_member_aliases: AbilityAliases,
- pub imported_module_thunks: &'a [Symbol],
- pub module_thunks: &'a [Symbol],
pending_specializations: PendingSpecializations<'a>,
specialized: Specialized<'a>,
pub runtime_errors: BumpMap,
pub externals_we_need: BumpMap>,
symbol_specializations: SymbolSpecializations<'a>,
specialization_stack: SpecializationStack<'a>,
+
+ pub imported_module_thunks: &'a [Symbol],
+ pub module_thunks: &'a [Symbol],
+ pub host_exposed_symbols: &'a [Symbol],
}
impl<'a> Procs<'a> {
@@ -970,14 +996,16 @@ impl<'a> Procs<'a> {
Self {
partial_procs: PartialProcs::new_in(arena),
ability_member_aliases: AbilityAliases::new_in(arena),
- imported_module_thunks: &[],
- module_thunks: &[],
pending_specializations: PendingSpecializations::Finding(Suspended::new_in(arena)),
specialized: Specialized::default(),
runtime_errors: BumpMap::new_in(arena),
externals_we_need: BumpMap::new_in(arena),
symbol_specializations: Default::default(),
specialization_stack: SpecializationStack(Vec::with_capacity_in(16, arena)),
+
+ imported_module_thunks: &[],
+ module_thunks: &[],
+ host_exposed_symbols: &[],
}
}
@@ -1106,6 +1134,7 @@ impl<'a> Procs<'a> {
let needs_suspended_specialization =
self.symbol_needs_suspended_specialization(name.name());
+
match (
&mut self.pending_specializations,
needs_suspended_specialization,
@@ -1178,7 +1207,6 @@ impl<'a> Procs<'a> {
name,
layout_cache,
annotation,
- &[],
partial_proc_id,
) {
Ok((proc, layout)) => {
@@ -1271,7 +1299,6 @@ impl<'a> Procs<'a> {
proc_name,
layout_cache,
fn_var,
- Default::default(),
partial_proc_id,
) {
Ok((proc, raw_layout)) => {
@@ -1481,7 +1508,7 @@ impl<'a, 'i> Env<'a, 'i> {
right,
)?;
- layout_cache.invalidate(changed_variables.iter().copied());
+ layout_cache.invalidate(self.subs, changed_variables.iter().copied());
external_specializations
.into_iter()
.for_each(|e| e.invalidate_cache(&changed_variables));
@@ -1689,43 +1716,6 @@ impl ModifyRc {
}
}
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum Literal<'a> {
- // Literals
- /// stored as raw bytes rather than a number to avoid an alignment bump
- Int([u8; 16]),
- /// stored as raw bytes rather than a number to avoid an alignment bump
- U128([u8; 16]),
- Float(f64),
- /// stored as raw bytes rather than a number to avoid an alignment bump
- Decimal([u8; 16]),
- Str(&'a str),
- /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool,
- /// so they can (at least potentially) be emitted as 1-bit machine bools.
- ///
- /// So [True, False] compiles to this, and so do [A, B] and [Foo, Bar].
- /// However, a union like [True, False, Other Int] would not.
- Bool(bool),
- /// Closed tag unions containing between 3 and 256 tags (all of 0 arity)
- /// compile to bytes, e.g. [Blue, Black, Red, Green, White]
- Byte(u8),
-}
-
-#[derive(Clone, Copy, Debug, PartialEq)]
-pub enum ListLiteralElement<'a> {
- Literal(Literal<'a>),
- Symbol(Symbol),
-}
-
-impl<'a> ListLiteralElement<'a> {
- pub fn to_symbol(&self) -> Option {
- match self {
- Self::Symbol(s) => Some(*s),
- _ => None,
- }
- }
-}
-
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Call<'a> {
pub call_type: CallType<'a>,
@@ -2152,7 +2142,7 @@ impl<'a> Stmt<'a> {
.text("let ")
.append(symbol_to_doc(alloc, *symbol, pretty))
.append(" : ")
- .append(interner.to_doc(*layout, alloc, Parens::NotNeeded))
+ .append(interner.to_doc_top(*layout, alloc))
.append(" = ")
.append(expr.to_doc(alloc, pretty))
.append(";")
@@ -2311,15 +2301,6 @@ impl<'a> Stmt<'a> {
String::from_utf8(w).unwrap()
}
- pub fn is_terminal(&self) -> bool {
- use Stmt::*;
-
- matches!(
- self,
- Switch { .. } | Ret(_) | Jump(_, _) // TODO for Switch; is this the reason Lean only looks at the outermost `when`?
- )
- }
-
pub fn if_then_else(
arena: &'a Bump,
condition_symbol: Symbol,
@@ -2815,7 +2796,10 @@ fn pattern_to_when<'a>(
(env.unique_symbol(), Loc::at_zero(RuntimeError(error)))
}
- AppliedTag { .. } | RecordDestructure { .. } | UnwrappedOpaque { .. } => {
+ AppliedTag { .. }
+ | RecordDestructure { .. }
+ | TupleDestructure { .. }
+ | UnwrappedOpaque { .. } => {
let symbol = env.unique_symbol();
let wrapped_body = When {
@@ -2909,7 +2893,7 @@ fn specialize_suspended<'a>(
}
};
- match specialize_variable(env, procs, name, layout_cache, var, &[], partial_proc) {
+ match specialize_variable(env, procs, name, layout_cache, var, partial_proc) {
Ok((proc, raw_layout)) => {
let proc_layout = ProcLayout::from_raw_named(env.arena, name, raw_layout);
procs
@@ -3002,15 +2986,8 @@ fn specialize_host_specializations<'a>(
let offset_variable = StorageSubs::merge_into(store, env.subs);
- for (symbol, variable, host_exposed_aliases) in it {
- specialize_external_help(
- env,
- procs,
- layout_cache,
- symbol,
- offset_variable(variable),
- &host_exposed_aliases,
- )
+ for (symbol, variable, _host_exposed_aliases) in it {
+ specialize_external_help(env, procs, layout_cache, symbol, offset_variable(variable))
}
}
@@ -3035,7 +3012,7 @@ fn specialize_external_specializations<'a>(
// duplicate specializations, and the insertion into a hash map
// below will deduplicate them.
- specialize_external_help(env, procs, layout_cache, symbol, imported_variable, &[])
+ specialize_external_help(env, procs, layout_cache, symbol, imported_variable)
}
}
}
@@ -3046,7 +3023,6 @@ fn specialize_external_help<'a>(
layout_cache: &mut LayoutCache<'a>,
name: LambdaName<'a>,
variable: Variable,
- host_exposed_aliases: &[(Symbol, Variable)],
) {
let partial_proc_id = match procs.partial_procs.symbol_to_id(name.name()) {
Some(v) => v,
@@ -3055,24 +3031,110 @@ fn specialize_external_help<'a>(
}
};
- let specialization_result = specialize_variable(
- env,
- procs,
- name,
- layout_cache,
- variable,
- host_exposed_aliases,
- partial_proc_id,
- );
+ let specialization_result =
+ specialize_variable(env, procs, name, layout_cache, variable, partial_proc_id);
match specialization_result {
- Ok((proc, layout)) => {
+ Ok((mut proc, layout)) => {
let top_level = ProcLayout::from_raw_named(env.arena, name, layout);
if procs.is_module_thunk(name.name()) {
debug_assert!(top_level.arguments.is_empty());
}
+ if procs.host_exposed_symbols.contains(&proc.name.name()) {
+ // layouts that are (transitively) used in the type of `mainForHost`.
+ let mut host_exposed_layouts: Vec<_> = top_level
+ .arguments
+ .iter()
+ .copied()
+ .chain([top_level.result])
+ .collect_in(env.arena);
+
+ // it is very likely we see the same types across functions, or in multiple arguments
+ host_exposed_layouts.sort();
+ host_exposed_layouts.dedup();
+
+ for in_layout in host_exposed_layouts {
+ let layout = layout_cache.interner.get(in_layout);
+
+ let all_glue_procs = generate_glue_procs(
+ env.home,
+ env.ident_ids,
+ env.arena,
+ &mut layout_cache.interner,
+ env.arena.alloc(layout),
+ );
+
+ // for now, getters are not processed here
+ let GlueProcs {
+ getters,
+ extern_names,
+ } = all_glue_procs;
+
+ for (_layout, glue_procs) in getters {
+ for glue_proc in glue_procs {
+ procs.specialized.insert_specialized(
+ glue_proc.proc.name.name(),
+ glue_proc.proc_layout,
+ glue_proc.proc,
+ );
+ }
+ }
+
+ let mut aliases = BumpMap::default();
+
+ for (id, mut raw_function_layout) in extern_names {
+ let symbol = env.unique_symbol();
+ let lambda_name = LambdaName::no_niche(symbol);
+
+ // fix the recursion in the rocLovesRust example
+ if false {
+ raw_function_layout = match raw_function_layout {
+ RawFunctionLayout::Function(a, mut lambda_set, _) => {
+ lambda_set.ret = in_layout;
+ RawFunctionLayout::Function(a, lambda_set, in_layout)
+ }
+ RawFunctionLayout::ZeroArgumentThunk(x) => {
+ RawFunctionLayout::ZeroArgumentThunk(x)
+ }
+ };
+ }
+
+ let (key, (top_level, proc)) = generate_host_exposed_function(
+ env,
+ procs,
+ layout_cache,
+ lambda_name,
+ raw_function_layout,
+ );
+
+ procs
+ .specialized
+ .insert_specialized(symbol, top_level, proc);
+
+ let hels = HostExposedLambdaSet {
+ id,
+ symbol,
+ proc_layout: top_level,
+ raw_function_layout,
+ };
+
+ aliases.insert(key, hels);
+ }
+
+ match &mut proc.host_exposed_layouts {
+ HostExposedLayouts::HostExposed { aliases: old, .. } => old.extend(aliases),
+ hep @ HostExposedLayouts::NotHostExposed => {
+ *hep = HostExposedLayouts::HostExposed {
+ aliases,
+ rigids: Default::default(),
+ };
+ }
+ }
+ }
+ }
+
procs
.specialized
.insert_specialized(name.name(), top_level, proc);
@@ -3197,6 +3259,119 @@ fn rollback_typestate(
}
}
+fn generate_host_exposed_function<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ lambda_name: LambdaName<'a>,
+ layout: RawFunctionLayout<'a>,
+) -> (Symbol, (ProcLayout<'a>, Proc<'a>)) {
+ let function_name = lambda_name.name();
+
+ match layout {
+ RawFunctionLayout::Function(_, lambda_set, _) => {
+ let (proc, top_level) = generate_host_exposed_lambda_set(
+ env,
+ procs,
+ layout_cache,
+ function_name,
+ lambda_set,
+ );
+
+ (function_name, (top_level, proc))
+ }
+ RawFunctionLayout::ZeroArgumentThunk(result) => {
+ let assigned = env.unique_symbol();
+ let hole = env.arena.alloc(Stmt::Ret(assigned));
+ let forced = force_thunk(env, function_name, result, assigned, hole);
+
+ let lambda_name = LambdaName::no_niche(function_name);
+ let proc = Proc {
+ name: lambda_name,
+ args: &[],
+ body: forced,
+ closure_data_layout: None,
+ ret_layout: result,
+ is_self_recursive: SelfRecursive::NotSelfRecursive,
+ must_own_arguments: false,
+ host_exposed_layouts: HostExposedLayouts::NotHostExposed,
+ };
+
+ let top_level = ProcLayout::from_raw_named(env.arena, lambda_name, layout);
+
+ (function_name, (top_level, proc))
+ }
+ }
+}
+
+fn generate_host_exposed_lambda_set<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ name: Symbol,
+ lambda_set: LambdaSet<'a>,
+) -> (Proc<'a>, ProcLayout<'a>) {
+ let assigned = env.unique_symbol();
+
+ let argument_layouts = *lambda_set.args;
+ let return_layout = lambda_set.ret;
+
+ let mut argument_symbols = Vec::with_capacity_in(argument_layouts.len(), env.arena);
+ let mut proc_arguments = Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
+ let mut top_level_arguments = Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
+
+ for layout in *lambda_set.args {
+ let symbol = env.unique_symbol();
+
+ proc_arguments.push((*layout, symbol));
+
+ argument_symbols.push(symbol);
+ top_level_arguments.push(*layout);
+ }
+
+ // the proc needs to take an extra closure argument
+ let lambda_set_layout = lambda_set.full_layout;
+ proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE));
+
+ // this should also be reflected in the TopLevel signature
+ top_level_arguments.push(lambda_set_layout);
+
+ let hole = env.arena.alloc(Stmt::Ret(assigned));
+
+ let body = match_on_lambda_set(
+ env,
+ layout_cache,
+ procs,
+ lambda_set,
+ Symbol::ARG_CLOSURE,
+ argument_symbols.into_bump_slice(),
+ argument_layouts,
+ return_layout,
+ assigned,
+ hole,
+ );
+
+ let proc = Proc {
+ name: LambdaName::no_niche(name),
+ args: proc_arguments.into_bump_slice(),
+ body,
+ closure_data_layout: None,
+ ret_layout: return_layout,
+ is_self_recursive: SelfRecursive::NotSelfRecursive,
+ must_own_arguments: false,
+ host_exposed_layouts: HostExposedLayouts::NotHostExposed,
+ };
+
+ let top_level = ProcLayout::new(
+ env.arena,
+ top_level_arguments.into_bump_slice(),
+ Niche::NONE,
+ return_layout,
+ );
+
+ (proc, top_level)
+}
+
/// Specialize a single proc.
///
/// The caller should snapshot and rollback the type state before and after calling this function,
@@ -3207,7 +3382,6 @@ fn specialize_proc_help<'a>(
lambda_name: LambdaName<'a>,
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
- host_exposed_variables: &[(Symbol, Variable)],
partial_proc_id: PartialProcId,
) -> Result, LayoutProblem> {
let partial_proc = procs.partial_procs.get_id(partial_proc_id);
@@ -3248,121 +3422,8 @@ fn specialize_proc_help<'a>(
let body = partial_proc.body.clone();
let body_var = partial_proc.body_var;
- // determine the layout of aliases/rigids exposed to the host
- let host_exposed_layouts = if host_exposed_variables.is_empty() {
- HostExposedLayouts::NotHostExposed
- } else {
- let mut aliases = BumpMap::new_in(env.arena);
-
- for (symbol, variable) in host_exposed_variables {
- let layout = layout_cache
- .raw_from_var(env.arena, *variable, env.subs)
- .unwrap();
-
- let name = env.unique_symbol();
-
- match layout {
- RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => {
- let assigned = env.unique_symbol();
-
- let mut argument_symbols =
- Vec::with_capacity_in(argument_layouts.len(), env.arena);
- let mut proc_arguments =
- Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
- let mut top_level_arguments =
- Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
-
- for layout in argument_layouts {
- let symbol = env.unique_symbol();
-
- proc_arguments.push((*layout, symbol));
-
- argument_symbols.push(symbol);
- top_level_arguments.push(*layout);
- }
-
- // the proc needs to take an extra closure argument
- let lambda_set_layout = lambda_set.full_layout;
- proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE));
-
- // this should also be reflected in the TopLevel signature
- top_level_arguments.push(lambda_set_layout);
-
- let hole = env.arena.alloc(Stmt::Ret(assigned));
-
- let body = match_on_lambda_set(
- env,
- layout_cache,
- procs,
- lambda_set,
- Symbol::ARG_CLOSURE,
- argument_symbols.into_bump_slice(),
- argument_layouts,
- return_layout,
- assigned,
- hole,
- );
-
- let proc = Proc {
- name: LambdaName::no_niche(name),
- args: proc_arguments.into_bump_slice(),
- body,
- closure_data_layout: None,
- ret_layout: return_layout,
- is_self_recursive: SelfRecursive::NotSelfRecursive,
- // must_own_arguments: false,
- host_exposed_layouts: HostExposedLayouts::NotHostExposed,
- };
-
- let top_level = ProcLayout::new(
- env.arena,
- top_level_arguments.into_bump_slice(),
- Niche::NONE,
- return_layout,
- );
-
- procs.specialized.insert_specialized(name, top_level, proc);
-
- aliases.insert(*symbol, (name, top_level, layout));
- }
- RawFunctionLayout::ZeroArgumentThunk(result) => {
- let assigned = env.unique_symbol();
- let hole = env.arena.alloc(Stmt::Ret(assigned));
- let forced = force_thunk(env, lambda_name.name(), result, assigned, hole);
-
- let lambda_name = LambdaName::no_niche(name);
- let proc = Proc {
- name: lambda_name,
- args: &[],
- body: forced,
- closure_data_layout: None,
- ret_layout: result,
- is_self_recursive: SelfRecursive::NotSelfRecursive,
- // must_own_arguments: false,
- host_exposed_layouts: HostExposedLayouts::NotHostExposed,
- };
-
- let top_level = ProcLayout::from_raw_named(env.arena, lambda_name, layout);
-
- procs.specialized.insert_specialized(name, top_level, proc);
-
- aliases.insert(
- *symbol,
- (
- name,
- ProcLayout::new(env.arena, &[], Niche::NONE, result),
- layout,
- ),
- );
- }
- }
- }
-
- HostExposedLayouts::HostExposed {
- rigids: BumpMap::new_in(env.arena),
- aliases,
- }
- };
+ // host-exposed functions are tagged on later
+ let host_exposed_layouts = HostExposedLayouts::NotHostExposed;
let mut specialized_body = from_can(env, body_var, body, procs, layout_cache);
@@ -3445,6 +3506,7 @@ fn specialize_proc_help<'a>(
UnionLayout::NonRecursive(_)
| UnionLayout::Recursive(_)
| UnionLayout::NullableUnwrapped { .. }
+ | UnionLayout::NullableWrapped { .. }
));
debug_assert_eq!(field_layouts.len(), captured.len());
@@ -3809,7 +3871,6 @@ fn specialize_variable<'a>(
proc_name: LambdaName<'a>,
layout_cache: &mut LayoutCache<'a>,
fn_var: Variable,
- host_exposed_variables: &[(Symbol, Variable)],
partial_proc_id: PartialProcId,
) -> Result, SpecializeFailure<'a>> {
let snapshot = snapshot_typestate(env.subs, procs, layout_cache);
@@ -3839,15 +3900,8 @@ fn specialize_variable<'a>(
procs.push_active_specialization(proc_name.name());
roc_tracing::debug!(?proc_name, ?fn_var, fn_content = ?roc_types::subs::SubsFmtContent(env.subs.get_content_without_compacting(fn_var), env.subs), "specialization start");
- let specialized = specialize_proc_help(
- env,
- procs,
- proc_name,
- layout_cache,
- fn_var,
- host_exposed_variables,
- partial_proc_id,
- );
+ let specialized =
+ specialize_proc_help(env, procs, proc_name, layout_cache, fn_var, partial_proc_id);
roc_tracing::debug!(
?proc_name,
@@ -3931,6 +3985,33 @@ impl<'a> ProcLayout<'a> {
}
}
}
+
+ pub fn dbg_deep<'r, I: LayoutInterner<'a>>(&self, interner: &'r I) -> DbgProcLayout<'a, 'r, I> {
+ DbgProcLayout {
+ layout: *self,
+ interner,
+ }
+ }
+}
+
+pub struct DbgProcLayout<'a, 'r, I: LayoutInterner<'a>> {
+ layout: ProcLayout<'a>,
+ interner: &'r I,
+}
+
+impl<'a, 'r, I: LayoutInterner<'a>> std::fmt::Debug for DbgProcLayout<'a, 'r, I> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let ProcLayout {
+ arguments,
+ result,
+ niche,
+ } = self.layout;
+ f.debug_struct("ProcLayout")
+ .field("arguments", &self.interner.dbg_deep_iter(arguments))
+ .field("result", &self.interner.dbg_deep(result))
+ .field("niche", &niche.dbg_deep(self.interner))
+ .finish()
+ }
}
fn specialize_naked_symbol<'a>(
@@ -4247,7 +4328,38 @@ pub fn with_hole<'a>(
}
}
- Tuple { .. } => todo!("implement tuple hole"),
+ Tuple {
+ tuple_var, elems, ..
+ } => {
+ let sorted_elems_result = {
+ let mut layout_env = layout::Env::from_components(
+ layout_cache,
+ env.subs,
+ env.arena,
+ env.target_info,
+ );
+ layout::sort_tuple_elems(&mut layout_env, tuple_var)
+ };
+ let sorted_elems = match sorted_elems_result {
+ Ok(elems) => elems,
+ Err(_) => return runtime_error(env, "Can't create tuple with improper layout"),
+ };
+
+ // Hacky way to let us remove the owned elements from the vector, possibly out-of-order.
+ let mut elems = Vec::from_iter_in(elems.into_iter().map(Some), env.arena);
+ let take_elem_expr = move |index: usize| elems[index].take();
+
+ compile_struct_like(
+ env,
+ procs,
+ layout_cache,
+ sorted_elems,
+ take_elem_expr,
+ tuple_var,
+ hole,
+ assigned,
+ )
+ }
Record {
record_var,
@@ -4268,100 +4380,19 @@ pub fn with_hole<'a>(
Err(_) => return runtime_error(env, "Can't create record with improper layout"),
};
- let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena);
- let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena);
+ let take_field_expr =
+ move |field: Lowercase| fields.remove(&field).map(|f| (f.var, f.loc_expr));
- #[allow(clippy::enum_variant_names)]
- enum Field {
- // TODO: rename this since it can handle unspecialized expressions now too
- FunctionOrUnspecialized(Symbol, Variable),
- ValueSymbol,
- Field(roc_can::expr::Field),
- }
-
- for (label, variable, _) in sorted_fields.into_iter() {
- // TODO how should function pointers be handled here?
- use ReuseSymbol::*;
- match fields.remove(&label) {
- Some(field) => {
- match can_reuse_symbol(env, procs, &field.loc_expr.value, field.var) {
- Imported(symbol)
- | LocalFunction(symbol)
- | UnspecializedExpr(symbol) => {
- field_symbols.push(symbol);
- can_fields.push(Field::FunctionOrUnspecialized(symbol, variable));
- }
- Value(symbol) => {
- let reusable = procs.get_or_insert_symbol_specialization(
- env,
- layout_cache,
- symbol,
- field.var,
- );
- field_symbols.push(reusable);
- can_fields.push(Field::ValueSymbol);
- }
- NotASymbol => {
- field_symbols.push(env.unique_symbol());
- can_fields.push(Field::Field(field));
- }
- }
- }
- None => {
- // this field was optional, but not given
- continue;
- }
- }
- }
-
- // creating a record from the var will unpack it if it's just a single field.
- let layout = match layout_cache.from_var(env.arena, record_var, env.subs) {
- Ok(layout) => layout,
- Err(_) => return runtime_error(env, "Can't create record with improper layout"),
- };
-
- let field_symbols = field_symbols.into_bump_slice();
-
- let mut stmt = if let [only_field] = field_symbols {
- let mut hole = hole.clone();
- substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
- hole
- } else {
- Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole)
- };
-
- for (opt_field, symbol) in can_fields.into_iter().rev().zip(field_symbols.iter().rev())
- {
- match opt_field {
- Field::ValueSymbol => {
- // this symbol is already defined; nothing to do
- }
- Field::FunctionOrUnspecialized(symbol, variable) => {
- stmt = specialize_symbol(
- env,
- procs,
- layout_cache,
- Some(variable),
- symbol,
- env.arena.alloc(stmt),
- symbol,
- );
- }
- Field::Field(field) => {
- stmt = with_hole(
- env,
- field.loc_expr.value,
- field.var,
- procs,
- layout_cache,
- *symbol,
- env.arena.alloc(stmt),
- );
- }
- }
- }
-
- stmt
+ compile_struct_like(
+ env,
+ procs,
+ layout_cache,
+ sorted_fields,
+ take_field_expr,
+ record_var,
+ hole,
+ assigned,
+ )
}
EmptyRecord => let_empty_struct(assigned, hole),
@@ -4681,49 +4712,18 @@ pub fn with_hole<'a>(
}
}
- let record_symbol = possible_reuse_symbol_or_specialize(
+ compile_struct_like_access(
env,
procs,
layout_cache,
- &loc_expr.value,
- record_var,
- );
-
- let mut stmt = match field_layouts.as_slice() {
- [_] => {
- let mut hole = hole.clone();
- substitute_in_exprs(env.arena, &mut hole, assigned, record_symbol);
-
- hole
- }
- _ => {
- let expr = Expr::StructAtIndex {
- index: index.expect("field not in its own type") as u64,
- field_layouts: field_layouts.into_bump_slice(),
- structure: record_symbol,
- };
-
- let layout = layout_cache
- .from_var(env.arena, field_var, env.subs)
- .unwrap_or_else(|err| {
- panic!("TODO turn fn_var into a RuntimeError {:?}", err)
- });
-
- Stmt::Let(assigned, expr, layout, hole)
- }
- };
-
- stmt = assign_to_symbol(
- env,
- procs,
- layout_cache,
- record_var,
+ field_layouts,
+ index.expect("field not in its own type") as _,
*loc_expr,
- record_symbol,
- stmt,
- );
-
- stmt
+ record_var,
+ hole,
+ assigned,
+ field_var,
+ )
}
RecordAccessor(accessor_data) => {
@@ -4781,8 +4781,51 @@ pub fn with_hole<'a>(
}
}
- TupleAccess { .. } => todo!(),
- TupleAccessor(_) => todo!(),
+ TupleAccess {
+ tuple_var,
+ elem_var,
+ index: accessed_index,
+ loc_expr,
+ ..
+ } => {
+ let sorted_elems_result = {
+ let mut layout_env = layout::Env::from_components(
+ layout_cache,
+ env.subs,
+ env.arena,
+ env.target_info,
+ );
+ layout::sort_tuple_elems(&mut layout_env, tuple_var)
+ };
+ let sorted_elems = match sorted_elems_result {
+ Ok(fields) => fields,
+ Err(_) => return runtime_error(env, "Can't access tuple with improper layout"),
+ };
+ let mut field_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena);
+
+ let mut final_index = None;
+
+ for (current, (index, _, elem_layout)) in sorted_elems.into_iter().enumerate() {
+ field_layouts.push(elem_layout);
+
+ if index == accessed_index {
+ final_index = Some(current);
+ }
+ }
+
+ compile_struct_like_access(
+ env,
+ procs,
+ layout_cache,
+ field_layouts,
+ final_index.expect("elem not in its own type") as u64,
+ *loc_expr,
+ tuple_var,
+ hole,
+ assigned,
+ elem_var,
+ )
+ }
OpaqueWrapFunction(wrap_fn_data) => {
let opaque_var = wrap_fn_data.opaque_var;
@@ -4920,9 +4963,6 @@ pub fn with_hole<'a>(
_ => arena.alloc([record_layout]),
};
- debug_assert_eq!(field_layouts.len(), symbols.len());
- debug_assert_eq!(fields.len(), symbols.len());
-
if symbols.len() == 1 {
// TODO we can probably special-case this more, skippiing the generation of
// UpdateExisting
@@ -5259,7 +5299,7 @@ pub fn with_hole<'a>(
let resolved_proc = match resolved_proc {
Resolved::Specialization(symbol) => symbol,
- Resolved::NeedsGenerated(_) => {
+ Resolved::Derive(_) => {
todo_abilities!("Generate impls for structural types")
}
};
@@ -5539,6 +5579,162 @@ pub fn with_hole<'a>(
}
}
+/// Compiles an access into a tuple or record.
+fn compile_struct_like_access<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ field_layouts: Vec<'a, InLayout<'a>>,
+ index: u64,
+ loc_expr: Loc,
+ struct_like_var: Variable,
+ hole: &'a Stmt<'a>,
+ assigned: Symbol,
+ elem_var: Variable,
+) -> Stmt<'a> {
+ let struct_symbol = possible_reuse_symbol_or_specialize(
+ env,
+ procs,
+ layout_cache,
+ &loc_expr.value,
+ struct_like_var,
+ );
+
+ let mut stmt = match field_layouts.as_slice() {
+ [_] => {
+ let mut hole = hole.clone();
+ substitute_in_exprs(env.arena, &mut hole, assigned, struct_symbol);
+
+ hole
+ }
+ _ => {
+ let expr = Expr::StructAtIndex {
+ index,
+ field_layouts: field_layouts.into_bump_slice(),
+ structure: struct_symbol,
+ };
+
+ let layout = layout_cache
+ .from_var(env.arena, elem_var, env.subs)
+ .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
+
+ Stmt::Let(assigned, expr, layout, hole)
+ }
+ };
+
+ stmt = assign_to_symbol(
+ env,
+ procs,
+ layout_cache,
+ struct_like_var,
+ loc_expr,
+ struct_symbol,
+ stmt,
+ );
+
+ stmt
+}
+
+/// Compiles a record or a tuple.
+// TODO: UnusedLayout is because `sort_record_fields` currently returns a three-tuple, but is, in
+// fact, unneeded for the compilation.
+fn compile_struct_like<'a, L, UnusedLayout>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ sorted_elems: Vec<(L, Variable, UnusedLayout)>,
+ mut take_elem_expr: impl FnMut(L) -> Option<(Variable, Box>)>,
+ struct_like_var: Variable,
+ hole: &'a Stmt<'a>,
+ assigned: Symbol,
+) -> Stmt<'a> {
+ let mut elem_symbols = Vec::with_capacity_in(sorted_elems.len(), env.arena);
+ let mut can_elems = Vec::with_capacity_in(sorted_elems.len(), env.arena);
+
+ #[allow(clippy::enum_variant_names)]
+ enum Field {
+ // TODO: rename this since it can handle unspecialized expressions now too
+ FunctionOrUnspecialized(Symbol, Variable),
+ ValueSymbol,
+ Field(Variable, Loc),
+ }
+
+ for (index, variable, _) in sorted_elems.into_iter() {
+ // TODO how should function pointers be handled here?
+ use ReuseSymbol::*;
+ match take_elem_expr(index) {
+ Some((var, loc_expr)) => match can_reuse_symbol(env, procs, &loc_expr.value, var) {
+ Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => {
+ elem_symbols.push(symbol);
+ can_elems.push(Field::FunctionOrUnspecialized(symbol, variable));
+ }
+ Value(symbol) => {
+ let reusable =
+ procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var);
+ elem_symbols.push(reusable);
+ can_elems.push(Field::ValueSymbol);
+ }
+ NotASymbol => {
+ elem_symbols.push(env.unique_symbol());
+ can_elems.push(Field::Field(var, *loc_expr));
+ }
+ },
+ None => {
+ // this field was optional, but not given
+ continue;
+ }
+ }
+ }
+
+ // creating a record from the var will unpack it if it's just a single field.
+ let layout = match layout_cache.from_var(env.arena, struct_like_var, env.subs) {
+ Ok(layout) => layout,
+ Err(_) => return runtime_error(env, "Can't create record with improper layout"),
+ };
+
+ let elem_symbols = elem_symbols.into_bump_slice();
+
+ let mut stmt = if let [only_field] = elem_symbols {
+ let mut hole = hole.clone();
+ substitute_in_exprs(env.arena, &mut hole, assigned, *only_field);
+ hole
+ } else {
+ Stmt::Let(assigned, Expr::Struct(elem_symbols), layout, hole)
+ };
+
+ for (opt_field, symbol) in can_elems.into_iter().rev().zip(elem_symbols.iter().rev()) {
+ match opt_field {
+ Field::ValueSymbol => {
+ // this symbol is already defined; nothing to do
+ }
+ Field::FunctionOrUnspecialized(symbol, variable) => {
+ stmt = specialize_symbol(
+ env,
+ procs,
+ layout_cache,
+ Some(variable),
+ symbol,
+ env.arena.alloc(stmt),
+ symbol,
+ );
+ }
+ Field::Field(var, loc_expr) => {
+ stmt = with_hole(
+ env,
+ loc_expr.value,
+ var,
+ procs,
+ layout_cache,
+ *symbol,
+ env.arena.alloc(stmt),
+ );
+ }
+ }
+ }
+
+ stmt
+}
+
#[inline(always)]
fn late_resolve_ability_specialization<'a>(
env: &mut Env<'a, '_>,
@@ -5564,12 +5760,17 @@ fn late_resolve_ability_specialization<'a>(
solved,
unspecialized,
recursion_var: _,
- ambient_function: _,
+ ambient_function,
} = env.subs.get_lambda_set(*lambda_set);
debug_assert!(unspecialized.is_empty());
let mut iter_lambda_set = solved.iter_all();
- debug_assert_eq!(iter_lambda_set.len(), 1);
+ debug_assert_eq!(
+ iter_lambda_set.len(),
+ 1,
+ "{:?}",
+ (env.subs.dbg(*lambda_set), env.subs.dbg(ambient_function))
+ );
let spec_symbol_index = iter_lambda_set.next().unwrap().0;
env.subs[spec_symbol_index]
} else {
@@ -5585,14 +5786,7 @@ fn late_resolve_ability_specialization<'a>(
match specialization {
Resolved::Specialization(symbol) => symbol,
- Resolved::NeedsGenerated(var) => {
- let derive_key = roc_derive_key::Derived::builtin(
- member.try_into().expect("derived symbols must be builtins"),
- env.subs,
- var,
- )
- .expect("specialization var not derivable!");
-
+ Resolved::Derive(derive_key) => {
match derive_key {
roc_derive_key::Derived::Immediate(imm)
| roc_derive_key::Derived::SingleLambdaSetImmediate(imm) => {
@@ -5927,7 +6121,7 @@ fn convert_tag_union<'a>(
layout_cache.from_var(env.arena, variant_var, env.subs),
"Wrapped"
);
- let union_layout = match layout_cache.get_in(variant_layout) {
+ let union_layout = match layout_cache.interner.chase_recursive(variant_layout) {
Layout::Union(ul) => ul,
other => internal_error!(
"unexpected layout {:?} for {:?}",
@@ -6807,28 +7001,18 @@ fn from_can_when<'a>(
}
};
- use crate::decision_tree::Guard;
+ use decision_tree::Guard;
let result = if let Some(loc_expr) = opt_guard {
- let id = JoinPointId(env.unique_symbol());
- let symbol = env.unique_symbol();
- let jump = env.arena.alloc(Stmt::Jump(id, env.arena.alloc([symbol])));
-
- let guard_stmt = with_hole(
- env,
- loc_expr.value,
- Variable::BOOL,
- procs,
- layout_cache,
- symbol,
- jump,
- );
+ let guard_spec = GuardStmtSpec {
+ guard_expr: loc_expr.value,
+ identity: env.next_call_specialization_id(),
+ };
(
pattern.clone(),
Guard::Guard {
- id,
pattern,
- stmt: guard_stmt,
+ stmt_spec: guard_spec,
},
branch_stmt,
)
@@ -6845,7 +7029,7 @@ fn from_can_when<'a>(
});
let mono_branches = Vec::from_iter_in(it, arena);
- crate::decision_tree::optimize_when(
+ decision_tree::optimize_when(
env,
procs,
layout_cache,
@@ -6856,6 +7040,85 @@ fn from_can_when<'a>(
)
}
+/// A functor to generate IR for a guard under a `when` branch.
+/// Used in the decision tree compiler, after building a decision tree and converting into IR.
+///
+/// A guard might appear more than once in various places in the compiled decision tree, so the
+/// functor here may be called more than once. As such, it implements clone, which duplicates the
+/// guard AST for subsequent IR-regeneration. This is a bit wasteful, but in practice, guard ASTs
+/// are quite small. Moreoever, they must be generated on a per-case basis, since the guard may
+/// have calls or joins, whose specialization IDs and joinpoint IDs, respectively, must be unique.
+#[derive(Debug, Clone)]
+pub(crate) struct GuardStmtSpec {
+ guard_expr: roc_can::expr::Expr,
+
+ /// Unique id to indentity identical guard statements, even across clones.
+ /// Needed so that we can implement [PartialEq] on this type. Re-uses call specialization IDs,
+ /// since the identity is kind of irrelevant.
+ identity: CallSpecId,
+}
+
+impl PartialEq for GuardStmtSpec {
+ fn eq(&self, other: &Self) -> bool {
+ self.identity == other.identity
+ }
+}
+
+impl std::hash::Hash for GuardStmtSpec {
+ fn hash(&self, state: &mut H) {
+ self.identity.id.hash(state);
+ }
+}
+
+impl GuardStmtSpec {
+ /// Generates IR for the guard, and the joinpoint that the guard will jump to with the
+ /// calculated guard boolean value.
+ ///
+ /// The caller should create a joinpoint with the given joinpoint ID and decide how to branch
+ /// after the guard has been evaluated.
+ ///
+ /// The compiled guard statement expects the pattern before the guard to be destructed before the
+ /// returned statement. The caller should layer on the pattern destructuring, as bound from the
+ /// `when` condition value.
+ pub(crate) fn generate_guard_and_join<'a>(
+ self,
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ ) -> CompiledGuardStmt<'a> {
+ let Self {
+ guard_expr,
+ identity: _,
+ } = self;
+
+ let join_point_id = JoinPointId(env.unique_symbol());
+ let symbol = env.unique_symbol();
+ let jump = env
+ .arena
+ .alloc(Stmt::Jump(join_point_id, env.arena.alloc([symbol])));
+
+ let stmt = with_hole(
+ env,
+ guard_expr,
+ Variable::BOOL,
+ procs,
+ layout_cache,
+ symbol,
+ jump,
+ );
+
+ CompiledGuardStmt {
+ join_point_id,
+ stmt,
+ }
+ }
+}
+
+pub(crate) struct CompiledGuardStmt<'a> {
+ pub join_point_id: JoinPointId,
+ pub stmt: Stmt<'a>,
+}
+
fn substitute(substitutions: &BumpMap, s: Symbol) -> Option {
match substitutions.get(&s) {
Some(new) => {
@@ -6877,6 +7140,18 @@ fn substitute_in_exprs<'a>(arena: &'a Bump, stmt: &mut Stmt<'a>, from: Symbol, t
}
}
+pub(crate) fn substitute_in_exprs_many<'a>(
+ arena: &'a Bump,
+ stmt: &mut Stmt<'a>,
+ subs: BumpMap,
+) {
+ // TODO clean this up
+ let ref_stmt = arena.alloc(stmt.clone());
+ if let Some(new) = substitute_in_stmt_help(arena, ref_stmt, &subs) {
+ *stmt = new.clone();
+ }
+}
+
fn substitute_in_stmt_help<'a>(
arena: &'a Bump,
stmt: &'a Stmt<'a>,
@@ -7294,584 +7569,9 @@ fn substitute_in_expr<'a>(
}
}
-#[allow(clippy::too_many_arguments)]
-pub fn store_pattern<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- can_pat: &Pattern<'a>,
- outer_symbol: Symbol,
- stmt: Stmt<'a>,
-) -> Stmt<'a> {
- match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
- StorePattern::Productive(new) => new,
- StorePattern::NotProductive(new) => new,
- }
-}
-
-enum StorePattern<'a> {
- /// we bound new symbols
- Productive(Stmt<'a>),
- /// no new symbols were bound in this pattern
- NotProductive(Stmt<'a>),
-}
-
-/// It is crucial for correct RC insertion that we don't create dead variables!
-#[allow(clippy::too_many_arguments)]
-fn store_pattern_help<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- can_pat: &Pattern<'a>,
- outer_symbol: Symbol,
- mut stmt: Stmt<'a>,
-) -> StorePattern<'a> {
- use Pattern::*;
-
- match can_pat {
- Identifier(symbol) => {
- substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
- }
- Underscore => {
- // do nothing
- return StorePattern::NotProductive(stmt);
- }
- As(subpattern, symbol) => {
- let stored_subpattern =
- store_pattern_help(env, procs, layout_cache, subpattern, outer_symbol, stmt);
-
- let mut stmt = match stored_subpattern {
- StorePattern::Productive(stmt) => stmt,
- StorePattern::NotProductive(stmt) => stmt,
- };
-
- substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
-
- return StorePattern::Productive(stmt);
- }
- IntLiteral(_, _)
- | FloatLiteral(_, _)
- | DecimalLiteral(_)
- | EnumLiteral { .. }
- | BitLiteral { .. }
- | StrLiteral(_) => {
- return StorePattern::NotProductive(stmt);
- }
- NewtypeDestructure { arguments, .. } => match arguments.as_slice() {
- [(pattern, _layout)] => {
- return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
- }
- _ => {
- let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
- fields.extend(arguments.iter().map(|x| x.1));
-
- let layout =
- layout_cache.put_in(Layout::struct_no_name_order(fields.into_bump_slice()));
-
- return store_newtype_pattern(
- env,
- procs,
- layout_cache,
- outer_symbol,
- layout,
- arguments,
- stmt,
- );
- }
- },
- AppliedTag {
- arguments,
- layout,
- tag_id,
- ..
- } => {
- return store_tag_pattern(
- env,
- procs,
- layout_cache,
- outer_symbol,
- *layout,
- arguments,
- *tag_id,
- stmt,
- );
- }
-
- List {
- arity,
- element_layout,
- elements,
- } => {
- return store_list_pattern(
- env,
- procs,
- layout_cache,
- outer_symbol,
- *arity,
- *element_layout,
- elements,
- stmt,
- )
- }
-
- Voided { .. } => {
- return StorePattern::NotProductive(stmt);
- }
-
- OpaqueUnwrap { argument, .. } => {
- let (pattern, _layout) = &**argument;
- return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
- }
-
- RecordDestructure(destructs, [_single_field]) => {
- for destruct in destructs {
- match &destruct.typ {
- DestructType::Required(symbol) => {
- substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
- }
- DestructType::Guard(guard_pattern) => {
- return store_pattern_help(
- env,
- procs,
- layout_cache,
- guard_pattern,
- outer_symbol,
- stmt,
- );
- }
- }
- }
- }
- RecordDestructure(destructs, sorted_fields) => {
- let mut is_productive = false;
- for (index, destruct) in destructs.iter().enumerate().rev() {
- match store_record_destruct(
- env,
- procs,
- layout_cache,
- destruct,
- index as u64,
- outer_symbol,
- sorted_fields,
- stmt,
- ) {
- StorePattern::Productive(new) => {
- is_productive = true;
- stmt = new;
- }
- StorePattern::NotProductive(new) => {
- stmt = new;
- }
- }
- }
-
- if !is_productive {
- return StorePattern::NotProductive(stmt);
- }
- }
- }
-
- StorePattern::Productive(stmt)
-}
-
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub(crate) struct ListIndex(
- /// Positive if we should index from the head, negative if we should index from the tail
- /// 0 is lst[0]
- /// -1 is lst[List.len lst - 1]
- i64,
-);
-
-impl ListIndex {
- pub fn from_pattern_index(index: usize, arity: ListArity) -> Self {
- match arity {
- ListArity::Exact(_) => Self(index as _),
- ListArity::Slice(head, tail) => {
- if index < head {
- Self(index as _)
- } else {
- // Slice(head=2, tail=5)
- //
- // s t ... w y z x q
- // 0 1 2 3 4 5 6 index
- // 0 1 2 3 4 (index - head)
- // 5 4 3 2 1 (tail - (index - head))
- Self(-((tail - (index - head)) as i64))
- }
- }
- }
- }
-}
-
-pub(crate) type Store<'a> = (Symbol, InLayout<'a>, Expr<'a>);
-
-/// Builds the list index we should index into
-#[must_use]
-pub(crate) fn build_list_index_probe<'a>(
- env: &mut Env<'a, '_>,
- list_sym: Symbol,
- list_index: &ListIndex,
-) -> (Symbol, impl DoubleEndedIterator
- >) {
- let usize_layout = Layout::usize(env.target_info);
-
- let list_index = list_index.0;
- let index_sym = env.unique_symbol();
-
- let (opt_len_store, opt_offset_store, index_store) = if list_index >= 0 {
- let index_expr = Expr::Literal(Literal::Int((list_index as i128).to_ne_bytes()));
-
- let index_store = (index_sym, usize_layout, index_expr);
-
- (None, None, index_store)
- } else {
- let len_sym = env.unique_symbol();
- let len_expr = Expr::Call(Call {
- call_type: CallType::LowLevel {
- op: LowLevel::ListLen,
- update_mode: env.next_update_mode_id(),
- },
- arguments: env.arena.alloc([list_sym]),
- });
-
- let offset = list_index.abs();
- let offset_sym = env.unique_symbol();
- let offset_expr = Expr::Literal(Literal::Int((offset as i128).to_ne_bytes()));
-
- let index_expr = Expr::Call(Call {
- call_type: CallType::LowLevel {
- op: LowLevel::NumSub,
- update_mode: env.next_update_mode_id(),
- },
- arguments: env.arena.alloc([len_sym, offset_sym]),
- });
-
- let len_store = (len_sym, usize_layout, len_expr);
- let offset_store = (offset_sym, usize_layout, offset_expr);
- let index_store = (index_sym, usize_layout, index_expr);
-
- (Some(len_store), Some(offset_store), index_store)
- };
-
- let stores = (opt_len_store.into_iter())
- .chain(opt_offset_store)
- .chain([index_store]);
-
- (index_sym, stores)
-}
-
-#[allow(clippy::too_many_arguments)]
-fn store_list_pattern<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- list_sym: Symbol,
- list_arity: ListArity,
- element_layout: InLayout<'a>,
- elements: &[Pattern<'a>],
- mut stmt: Stmt<'a>,
-) -> StorePattern<'a> {
- use Pattern::*;
-
- let mut is_productive = false;
-
- for (index, element) in elements.iter().enumerate().rev() {
- let compute_element_load = |env: &mut Env<'a, '_>| {
- let list_index = ListIndex::from_pattern_index(index, list_arity);
-
- let (index_sym, needed_stores) = build_list_index_probe(env, list_sym, &list_index);
-
- let load = Expr::Call(Call {
- call_type: CallType::LowLevel {
- op: LowLevel::ListGetUnsafe,
- update_mode: env.next_update_mode_id(),
- },
- arguments: env.arena.alloc([list_sym, index_sym]),
- });
-
- (load, needed_stores)
- };
-
- let (store_loaded, needed_stores) = match element {
- Identifier(symbol) => {
- let (load, needed_stores) = compute_element_load(env);
-
- // store immediately in the given symbol
- (
- Stmt::Let(*symbol, load, element_layout, env.arena.alloc(stmt)),
- needed_stores,
- )
- }
- Underscore
- | IntLiteral(_, _)
- | FloatLiteral(_, _)
- | DecimalLiteral(_)
- | EnumLiteral { .. }
- | BitLiteral { .. }
- | StrLiteral(_) => {
- // ignore
- continue;
- }
- _ => {
- // store the field in a symbol, and continue matching on it
- let symbol = env.unique_symbol();
-
- // first recurse, continuing to unpack symbol
- match store_pattern_help(env, procs, layout_cache, element, symbol, stmt) {
- StorePattern::Productive(new) => {
- stmt = new;
- let (load, needed_stores) = compute_element_load(env);
-
- // only if we bind one of its (sub)fields to a used name should we
- // extract the field
- (
- Stmt::Let(symbol, load, element_layout, env.arena.alloc(stmt)),
- needed_stores,
- )
- }
- StorePattern::NotProductive(new) => {
- // do nothing
- stmt = new;
- continue;
- }
- }
- }
- };
-
- is_productive = true;
-
- stmt = store_loaded;
- for (sym, lay, expr) in needed_stores.rev() {
- stmt = Stmt::Let(sym, expr, lay, env.arena.alloc(stmt));
- }
- }
-
- if is_productive {
- StorePattern::Productive(stmt)
- } else {
- StorePattern::NotProductive(stmt)
- }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn store_tag_pattern<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- structure: Symbol,
- union_layout: UnionLayout<'a>,
- arguments: &[(Pattern<'a>, InLayout<'a>)],
- tag_id: TagIdIntType,
- mut stmt: Stmt<'a>,
-) -> StorePattern<'a> {
- use Pattern::*;
-
- let mut is_productive = false;
-
- for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() {
- let mut arg_layout = *arg_layout;
-
- if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) {
- // TODO(recursive-layouts): fix after disjoint rec ptrs
- arg_layout = layout_cache.put_in(Layout::Union(union_layout));
- }
-
- let load = Expr::UnionAtIndex {
- index: index as u64,
- structure,
- tag_id,
- union_layout,
- };
-
- match argument {
- Identifier(symbol) => {
- // store immediately in the given symbol
- stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt));
- is_productive = true;
- }
- Underscore => {
- // ignore
- }
- IntLiteral(_, _)
- | FloatLiteral(_, _)
- | DecimalLiteral(_)
- | EnumLiteral { .. }
- | BitLiteral { .. }
- | StrLiteral(_) => {}
- _ => {
- // store the field in a symbol, and continue matching on it
- let symbol = env.unique_symbol();
-
- // first recurse, continuing to unpack symbol
- match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
- StorePattern::Productive(new) => {
- is_productive = true;
- stmt = new;
- // only if we bind one of its (sub)fields to a used name should we
- // extract the field
- stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt));
- }
- StorePattern::NotProductive(new) => {
- // do nothing
- stmt = new;
- }
- }
- }
- }
- }
-
- if is_productive {
- StorePattern::Productive(stmt)
- } else {
- StorePattern::NotProductive(stmt)
- }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn store_newtype_pattern<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- structure: Symbol,
- layout: InLayout<'a>,
- arguments: &[(Pattern<'a>, InLayout<'a>)],
- mut stmt: Stmt<'a>,
-) -> StorePattern<'a> {
- use Pattern::*;
-
- let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena);
- let mut is_productive = false;
-
- for (_, layout) in arguments {
- arg_layouts.push(*layout);
- }
-
- for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() {
- let mut arg_layout = *arg_layout;
-
- if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) {
- arg_layout = layout;
- }
-
- let load = Expr::StructAtIndex {
- index: index as u64,
- field_layouts: arg_layouts.clone().into_bump_slice(),
- structure,
- };
-
- match argument {
- Identifier(symbol) => {
- stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt));
- is_productive = true;
- }
- Underscore => {
- // ignore
- }
- IntLiteral(_, _)
- | FloatLiteral(_, _)
- | DecimalLiteral(_)
- | EnumLiteral { .. }
- | BitLiteral { .. }
- | StrLiteral(_) => {}
- _ => {
- // store the field in a symbol, and continue matching on it
- let symbol = env.unique_symbol();
-
- // first recurse, continuing to unpack symbol
- match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
- StorePattern::Productive(new) => {
- is_productive = true;
- stmt = new;
- // only if we bind one of its (sub)fields to a used name should we
- // extract the field
- stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt));
- }
- StorePattern::NotProductive(new) => {
- // do nothing
- stmt = new;
- }
- }
- }
- }
- }
-
- if is_productive {
- StorePattern::Productive(stmt)
- } else {
- StorePattern::NotProductive(stmt)
- }
-}
-
-#[allow(clippy::too_many_arguments)]
-fn store_record_destruct<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- destruct: &RecordDestruct<'a>,
- index: u64,
- outer_symbol: Symbol,
- sorted_fields: &'a [InLayout<'a>],
- mut stmt: Stmt<'a>,
-) -> StorePattern<'a> {
- use Pattern::*;
-
- let load = Expr::StructAtIndex {
- index,
- field_layouts: sorted_fields,
- structure: outer_symbol,
- };
-
- match &destruct.typ {
- DestructType::Required(symbol) => {
- stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt));
- }
- DestructType::Guard(guard_pattern) => match &guard_pattern {
- Identifier(symbol) => {
- stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt));
- }
- Underscore => {
- // important that this is special-cased to do nothing: mono record patterns will extract all the
- // fields, but those not bound in the source code are guarded with the underscore
- // pattern. So given some record `{ x : a, y : b }`, a match
- //
- // { x } -> ...
- //
- // is actually
- //
- // { x, y: _ } -> ...
- //
- // internally. But `y` is never used, so we must make sure it't not stored/loaded.
- return StorePattern::NotProductive(stmt);
- }
- IntLiteral(_, _)
- | FloatLiteral(_, _)
- | DecimalLiteral(_)
- | EnumLiteral { .. }
- | BitLiteral { .. }
- | StrLiteral(_) => {
- return StorePattern::NotProductive(stmt);
- }
-
- _ => {
- let symbol = env.unique_symbol();
-
- match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
- StorePattern::Productive(new) => {
- stmt = new;
- stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
- }
- StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
- }
- }
- },
- }
-
- StorePattern::Productive(stmt)
-}
-
/// We want to re-use symbols that are not function symbols
/// for any other expression, we create a new symbol, and will
/// later make sure it gets assigned the correct value.
-
#[derive(Debug)]
enum ReuseSymbol {
Imported(Symbol),
@@ -8735,7 +8435,6 @@ fn call_by_name_help<'a>(
proc_name,
layout_cache,
fn_var,
- &[],
partial_proc,
) {
Ok((proc, layout)) => {
@@ -8883,7 +8582,6 @@ fn call_by_name_module_thunk<'a>(
LambdaName::no_niche(proc_name),
layout_cache,
fn_var,
- &[],
partial_proc,
) {
Ok((proc, raw_layout)) => {
@@ -9060,947 +8758,6 @@ fn call_specialized_proc<'a>(
}
}
-/// A pattern, including possible problems (e.g. shadowing) so that
-/// codegen can generate a runtime error if this pattern is reached.
-#[derive(Clone, Debug, PartialEq)]
-pub enum Pattern<'a> {
- Identifier(Symbol),
- Underscore,
- As(Box>, Symbol),
- IntLiteral([u8; 16], IntWidth),
- FloatLiteral(u64, FloatWidth),
- DecimalLiteral([u8; 16]),
- BitLiteral {
- value: bool,
- tag_name: TagName,
- union: roc_exhaustive::Union,
- },
- EnumLiteral {
- tag_id: u8,
- tag_name: TagName,
- union: roc_exhaustive::Union,
- },
- StrLiteral(Box),
-
- RecordDestructure(Vec<'a, RecordDestruct<'a>>, &'a [InLayout<'a>]),
- NewtypeDestructure {
- tag_name: TagName,
- arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>,
- },
- AppliedTag {
- tag_name: TagName,
- tag_id: TagIdIntType,
- arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>,
- layout: UnionLayout<'a>,
- union: roc_exhaustive::Union,
- },
- Voided {
- tag_name: TagName,
- },
- OpaqueUnwrap {
- opaque: Symbol,
- argument: Box<(Pattern<'a>, InLayout<'a>)>,
- },
- List {
- arity: ListArity,
- element_layout: InLayout<'a>,
- elements: Vec<'a, Pattern<'a>>,
- },
-}
-
-impl<'a> Pattern<'a> {
- /// This pattern contains a pattern match on Void (i.e. [], the empty tag union)
- /// such branches are not reachable at runtime
- pub fn is_voided(&self) -> bool {
- let mut stack: std::vec::Vec<&Pattern> = vec![self];
-
- while let Some(pattern) = stack.pop() {
- match pattern {
- Pattern::Identifier(_)
- | Pattern::Underscore
- | Pattern::IntLiteral(_, _)
- | Pattern::FloatLiteral(_, _)
- | Pattern::DecimalLiteral(_)
- | Pattern::BitLiteral { .. }
- | Pattern::EnumLiteral { .. }
- | Pattern::StrLiteral(_) => { /* terminal */ }
- Pattern::As(subpattern, _) => stack.push(subpattern),
- Pattern::RecordDestructure(destructs, _) => {
- for destruct in destructs {
- match &destruct.typ {
- DestructType::Required(_) => { /* do nothing */ }
- DestructType::Guard(pattern) => {
- stack.push(pattern);
- }
- }
- }
- }
- Pattern::NewtypeDestructure { arguments, .. } => {
- stack.extend(arguments.iter().map(|(t, _)| t))
- }
- Pattern::Voided { .. } => return true,
- Pattern::AppliedTag { arguments, .. } => {
- stack.extend(arguments.iter().map(|(t, _)| t))
- }
- Pattern::OpaqueUnwrap { argument, .. } => stack.push(&argument.0),
- Pattern::List { elements, .. } => stack.extend(elements),
- }
- }
-
- false
- }
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct RecordDestruct<'a> {
- pub label: Lowercase,
- pub variable: Variable,
- pub layout: InLayout<'a>,
- pub typ: DestructType<'a>,
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub enum DestructType<'a> {
- Required(Symbol),
- Guard(Pattern<'a>),
-}
-
-#[derive(Clone, Debug, PartialEq)]
-pub struct WhenBranch<'a> {
- pub patterns: Vec<'a, Pattern<'a>>,
- pub value: Expr<'a>,
- pub guard: Option>,
-}
-
-#[allow(clippy::type_complexity)]
-fn from_can_pattern<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- can_pattern: &roc_can::pattern::Pattern,
-) -> Result<
- (
- Pattern<'a>,
- Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
- ),
- RuntimeError,
-> {
- let mut assignments = Vec::new_in(env.arena);
- let pattern = from_can_pattern_help(env, procs, layout_cache, can_pattern, &mut assignments)?;
-
- Ok((pattern, assignments))
-}
-
-fn from_can_pattern_help<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- can_pattern: &roc_can::pattern::Pattern,
- assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
-) -> Result, RuntimeError> {
- use roc_can::pattern::Pattern::*;
-
- match can_pattern {
- Underscore => Ok(Pattern::Underscore),
- Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
- As(subpattern, symbol) => {
- let mono_subpattern =
- from_can_pattern_help(env, procs, layout_cache, &subpattern.value, assignments)?;
-
- Ok(Pattern::As(Box::new(mono_subpattern), *symbol))
- }
- AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
- IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern(
- env,
- layout_cache,
- *var,
- int_str,
- IntOrFloatValue::Int(*int),
- )),
- FloatLiteral(var, _, float_str, float, _bound) => Ok(make_num_literal_pattern(
- env,
- layout_cache,
- *var,
- float_str,
- IntOrFloatValue::Float(*float),
- )),
- StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
- SingleQuote(var, _, c, _) => {
- let layout = layout_cache.from_var(env.arena, *var, env.subs);
- match layout.map(|l| layout_cache.get_in(l)) {
- Ok(Layout::Builtin(Builtin::Int(width))) => {
- Ok(Pattern::IntLiteral((*c as i128).to_ne_bytes(), width))
- }
- o => internal_error!("an integer width was expected, but we found {:?}", o),
- }
- }
- Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
- original_region: *region,
- shadow: ident.clone(),
- kind: ShadowKind::Variable,
- }),
- UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
- MalformedPattern(_problem, region) => {
- // TODO preserve malformed problem information here?
- Err(RuntimeError::UnsupportedPattern(*region))
- }
- OpaqueNotInScope(loc_ident) => {
- // TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
- Err(RuntimeError::UnsupportedPattern(loc_ident.region))
- }
- NumLiteral(var, num_str, num, _bound) => Ok(make_num_literal_pattern(
- env,
- layout_cache,
- *var,
- num_str,
- IntOrFloatValue::Int(*num),
- )),
-
- AppliedTag {
- whole_var,
- tag_name,
- arguments,
- ..
- } => {
- use crate::layout::UnionVariant::*;
- use roc_exhaustive::Union;
-
- let res_variant = {
- let mut layout_env = layout::Env::from_components(
- layout_cache,
- env.subs,
- env.arena,
- env.target_info,
- );
- crate::layout::union_sorted_tags(&mut layout_env, *whole_var).map_err(Into::into)
- };
-
- let variant = match res_variant {
- Ok(cached) => cached,
- Err(LayoutProblem::UnresolvedTypeVar(_)) => {
- return Err(RuntimeError::UnresolvedTypeVar)
- }
- Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType),
- };
-
- let result = match variant {
- Never => unreachable!(
- "there is no pattern of type `[]`, union var {:?}",
- *whole_var
- ),
- Unit => Pattern::EnumLiteral {
- tag_id: 0,
- tag_name: tag_name.clone(),
- union: Union {
- render_as: RenderAs::Tag,
- alternatives: vec![Ctor {
- tag_id: TagId(0),
- name: CtorName::Tag(tag_name.clone()),
- arity: 0,
- }],
- },
- },
- BoolUnion { ttrue, ffalse } => {
- let (ttrue, ffalse) = (ttrue.expect_tag(), ffalse.expect_tag());
- Pattern::BitLiteral {
- value: tag_name == &ttrue,
- tag_name: tag_name.clone(),
- union: Union {
- render_as: RenderAs::Tag,
- alternatives: vec![
- Ctor {
- tag_id: TagId(0),
- name: CtorName::Tag(ffalse),
- arity: 0,
- },
- Ctor {
- tag_id: TagId(1),
- name: CtorName::Tag(ttrue),
- arity: 0,
- },
- ],
- },
- }
- }
- ByteUnion(tag_names) => {
- let tag_id = tag_names
- .iter()
- .position(|key| tag_name == key.expect_tag_ref())
- .expect("tag must be in its own type");
-
- let mut ctors = std::vec::Vec::with_capacity(tag_names.len());
- for (i, tag_name) in tag_names.into_iter().enumerate() {
- ctors.push(Ctor {
- tag_id: TagId(i as _),
- name: CtorName::Tag(tag_name.expect_tag()),
- arity: 0,
- })
- }
-
- let union = roc_exhaustive::Union {
- render_as: RenderAs::Tag,
- alternatives: ctors,
- };
-
- Pattern::EnumLiteral {
- tag_id: tag_id as u8,
- tag_name: tag_name.clone(),
- union,
- }
- }
- Newtype {
- arguments: field_layouts,
- ..
- } => {
- let mut arguments = arguments.clone();
-
- arguments.sort_by(|arg1, arg2| {
- let size1 = layout_cache
- .from_var(env.arena, arg1.0, env.subs)
- .map(|x| layout_cache.interner.alignment_bytes(x))
- .unwrap_or(0);
-
- let size2 = layout_cache
- .from_var(env.arena, arg2.0, env.subs)
- .map(|x| layout_cache.interner.alignment_bytes(x))
- .unwrap_or(0);
-
- size2.cmp(&size1)
- });
-
- let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
- for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) {
- mono_args.push((
- from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_pat.value,
- assignments,
- )?,
- *layout,
- ));
- }
-
- Pattern::NewtypeDestructure {
- tag_name: tag_name.clone(),
- arguments: mono_args,
- }
- }
- NewtypeByVoid {
- data_tag_arguments,
- data_tag_name,
- ..
- } => {
- let data_tag_name = data_tag_name.expect_tag();
-
- if tag_name != &data_tag_name {
- // this tag is not represented at runtime
- Pattern::Voided {
- tag_name: tag_name.clone(),
- }
- } else {
- let mut arguments = arguments.clone();
-
- arguments.sort_by(|arg1, arg2| {
- let size1 = layout_cache
- .from_var(env.arena, arg1.0, env.subs)
- .map(|x| layout_cache.interner.alignment_bytes(x))
- .unwrap_or(0);
-
- let size2 = layout_cache
- .from_var(env.arena, arg2.0, env.subs)
- .map(|x| layout_cache.interner.alignment_bytes(x))
- .unwrap_or(0);
-
- size2.cmp(&size1)
- });
-
- let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
- let it = arguments.iter().zip(data_tag_arguments.iter());
- for ((_, loc_pat), layout) in it {
- mono_args.push((
- from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_pat.value,
- assignments,
- )?,
- *layout,
- ));
- }
-
- Pattern::NewtypeDestructure {
- tag_name: tag_name.clone(),
- arguments: mono_args,
- }
- }
- }
-
- Wrapped(variant) => {
- let (tag_id, argument_layouts) = variant.tag_name_to_id(tag_name);
- let number_of_tags = variant.number_of_tags();
- let mut ctors = std::vec::Vec::with_capacity(number_of_tags);
-
- let arguments = {
- let mut temp = arguments.clone();
-
- temp.sort_by(|arg1, arg2| {
- let layout1 =
- layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap();
- let layout2 =
- layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap();
-
- let size1 = layout_cache.interner.alignment_bytes(layout1);
- let size2 = layout_cache.interner.alignment_bytes(layout2);
-
- size2.cmp(&size1)
- });
-
- temp
- };
-
- // we must derive the union layout from the whole_var, building it up
- // from `layouts` would unroll recursive tag unions, and that leads to
- // problems down the line because we hash layouts and an unrolled
- // version is not the same as the minimal version.
- let whole_var_layout = layout_cache.from_var(env.arena, *whole_var, env.subs);
- let layout = match whole_var_layout.map(|l| layout_cache.get_in(l)) {
- Ok(Layout::Union(ul)) => ul,
- _ => unreachable!(),
- };
-
- use WrappedVariant::*;
- match variant {
- NonRecursive {
- sorted_tag_layouts: ref tags,
- } => {
- debug_assert!(tags.len() > 1);
-
- for (i, (tag_name, args)) in tags.iter().enumerate() {
- ctors.push(Ctor {
- tag_id: TagId(i as _),
- name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
- arity: args.len(),
- })
- }
-
- let union = roc_exhaustive::Union {
- render_as: RenderAs::Tag,
- alternatives: ctors,
- };
-
- let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
-
- debug_assert_eq!(
- arguments.len(),
- argument_layouts.len(),
- "The {:?} tag got {} arguments, but its layout expects {}!",
- tag_name,
- arguments.len(),
- argument_layouts.len(),
- );
- let it = argument_layouts.iter();
-
- for ((_, loc_pat), layout) in arguments.iter().zip(it) {
- mono_args.push((
- from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_pat.value,
- assignments,
- )?,
- *layout,
- ));
- }
-
- Pattern::AppliedTag {
- tag_name: tag_name.clone(),
- tag_id: tag_id as _,
- arguments: mono_args,
- union,
- layout,
- }
- }
-
- Recursive {
- sorted_tag_layouts: ref tags,
- } => {
- debug_assert!(tags.len() > 1);
-
- for (i, (tag_name, args)) in tags.iter().enumerate() {
- ctors.push(Ctor {
- tag_id: TagId(i as _),
- name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
- arity: args.len(),
- })
- }
-
- let union = roc_exhaustive::Union {
- render_as: RenderAs::Tag,
- alternatives: ctors,
- };
-
- let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
-
- debug_assert_eq!(arguments.len(), argument_layouts.len());
- let it = argument_layouts.iter();
-
- for ((_, loc_pat), layout) in arguments.iter().zip(it) {
- mono_args.push((
- from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_pat.value,
- assignments,
- )?,
- *layout,
- ));
- }
-
- Pattern::AppliedTag {
- tag_name: tag_name.clone(),
- tag_id: tag_id as _,
- arguments: mono_args,
- union,
- layout,
- }
- }
-
- NonNullableUnwrapped {
- tag_name: w_tag_name,
- fields,
- } => {
- debug_assert_eq!(w_tag_name.expect_tag_ref(), tag_name);
-
- ctors.push(Ctor {
- tag_id: TagId(0),
- name: CtorName::Tag(tag_name.clone()),
- arity: fields.len(),
- });
-
- let union = roc_exhaustive::Union {
- render_as: RenderAs::Tag,
- alternatives: ctors,
- };
-
- let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
-
- debug_assert_eq!(arguments.len(), argument_layouts.len());
- let it = argument_layouts.iter();
-
- for ((_, loc_pat), layout) in arguments.iter().zip(it) {
- mono_args.push((
- from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_pat.value,
- assignments,
- )?,
- *layout,
- ));
- }
-
- Pattern::AppliedTag {
- tag_name: tag_name.clone(),
- tag_id: tag_id as _,
- arguments: mono_args,
- union,
- layout,
- }
- }
-
- NullableWrapped {
- sorted_tag_layouts: ref non_nulled_tags,
- nullable_id,
- nullable_name,
- } => {
- for id in 0..(non_nulled_tags.len() + 1) {
- if id == nullable_id as usize {
- ctors.push(Ctor {
- tag_id: TagId(id as _),
- name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
- arity: 0,
- });
- } else {
- let i = if id < nullable_id.into() { id } else { id - 1 };
- let (tag_name, args) = &non_nulled_tags[i];
- ctors.push(Ctor {
- tag_id: TagId(i as _),
- name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
- arity: args.len(),
- });
- }
- }
-
- let union = roc_exhaustive::Union {
- render_as: RenderAs::Tag,
- alternatives: ctors,
- };
-
- let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
-
- let it = if tag_name == nullable_name.expect_tag_ref() {
- [].iter()
- } else {
- argument_layouts.iter()
- };
-
- for ((_, loc_pat), layout) in arguments.iter().zip(it) {
- mono_args.push((
- from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_pat.value,
- assignments,
- )?,
- *layout,
- ));
- }
-
- Pattern::AppliedTag {
- tag_name: tag_name.clone(),
- tag_id: tag_id as _,
- arguments: mono_args,
- union,
- layout,
- }
- }
-
- NullableUnwrapped {
- other_fields,
- nullable_id,
- nullable_name,
- other_name: _,
- } => {
- debug_assert!(!other_fields.is_empty());
-
- ctors.push(Ctor {
- tag_id: TagId(nullable_id as _),
- name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
- arity: 0,
- });
-
- ctors.push(Ctor {
- tag_id: TagId(!nullable_id as _),
- name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
- arity: other_fields.len(),
- });
-
- let union = roc_exhaustive::Union {
- render_as: RenderAs::Tag,
- alternatives: ctors,
- };
-
- let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
-
- let it = if tag_name == nullable_name.expect_tag_ref() {
- [].iter()
- } else {
- argument_layouts.iter()
- };
-
- for ((_, loc_pat), layout) in arguments.iter().zip(it) {
- mono_args.push((
- from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_pat.value,
- assignments,
- )?,
- *layout,
- ));
- }
-
- Pattern::AppliedTag {
- tag_name: tag_name.clone(),
- tag_id: tag_id as _,
- arguments: mono_args,
- union,
- layout,
- }
- }
- }
- }
- };
-
- Ok(result)
- }
-
- UnwrappedOpaque {
- opaque, argument, ..
- } => {
- let (arg_var, loc_arg_pattern) = &(**argument);
- let arg_layout = layout_cache
- .from_var(env.arena, *arg_var, env.subs)
- .unwrap();
- let mono_arg_pattern = from_can_pattern_help(
- env,
- procs,
- layout_cache,
- &loc_arg_pattern.value,
- assignments,
- )?;
- Ok(Pattern::OpaqueUnwrap {
- opaque: *opaque,
- argument: Box::new((mono_arg_pattern, arg_layout)),
- })
- }
-
- RecordDestructure {
- whole_var,
- destructs,
- ..
- } => {
- // sorted fields based on the type
- let sorted_fields = {
- let mut layout_env = layout::Env::from_components(
- layout_cache,
- env.subs,
- env.arena,
- env.target_info,
- );
- crate::layout::sort_record_fields(&mut layout_env, *whole_var)
- .map_err(RuntimeError::from)?
- };
-
- // sorted fields based on the destruct
- let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
- let mut destructs_by_label = BumpMap::with_capacity_in(destructs.len(), env.arena);
- destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x)));
-
- let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
-
- // next we step through both sequences of fields. The outer loop is the sequence based
- // on the type, since not all fields need to actually be destructured in the source
- // language.
- //
- // However in mono patterns, we do destruct all patterns (but use Underscore) when
- // in the source the field is not matche in the source language.
- //
- // Optional fields somewhat complicate the matter here
-
- for (label, variable, res_layout) in sorted_fields.into_iter() {
- match res_layout {
- Ok(field_layout) => {
- // the field is non-optional according to the type
-
- match destructs_by_label.remove(&label) {
- Some(destruct) => {
- // this field is destructured by the pattern
- mono_destructs.push(from_can_record_destruct(
- env,
- procs,
- layout_cache,
- &destruct.value,
- field_layout,
- assignments,
- )?);
- }
- None => {
- // this field is not destructured by the pattern
- // put in an underscore
- mono_destructs.push(RecordDestruct {
- label: label.clone(),
- variable,
- layout: field_layout,
- typ: DestructType::Guard(Pattern::Underscore),
- });
- }
- }
-
- // the layout of this field is part of the layout of the record
- field_layouts.push(field_layout);
- }
- Err(field_layout) => {
- // the field is optional according to the type
- match destructs_by_label.remove(&label) {
- Some(destruct) => {
- // this field is destructured by the pattern
- match &destruct.value.typ {
- roc_can::pattern::DestructType::Optional(_, loc_expr) => {
- // if we reach this stage, the optional field is not present
- // so we push the default assignment into the branch
- assignments.push((
- destruct.value.symbol,
- variable,
- loc_expr.value.clone(),
- ));
- }
- _ => unreachable!(
- "only optional destructs can be optional fields"
- ),
- };
- }
- None => {
- // this field is not destructured by the pattern
- // put in an underscore
- mono_destructs.push(RecordDestruct {
- label: label.clone(),
- variable,
- layout: field_layout,
- typ: DestructType::Guard(Pattern::Underscore),
- });
- }
- }
- }
- }
- }
-
- for (_, destruct) in destructs_by_label.drain() {
- // this destruct is not in the type, but is in the pattern
- // it must be an optional field, and we will use the default
- match &destruct.value.typ {
- roc_can::pattern::DestructType::Optional(field_var, loc_expr) => {
- assignments.push((
- destruct.value.symbol,
- // destruct.value.var,
- *field_var,
- loc_expr.value.clone(),
- ));
- }
- _ => unreachable!("only optional destructs can be optional fields"),
- }
- }
-
- Ok(Pattern::RecordDestructure(
- mono_destructs,
- field_layouts.into_bump_slice(),
- ))
- }
-
- List {
- list_var: _,
- elem_var,
- patterns,
- } => {
- let element_layout = match layout_cache.from_var(env.arena, *elem_var, env.subs) {
- Ok(lay) => lay,
- Err(LayoutProblem::UnresolvedTypeVar(_)) => {
- return Err(RuntimeError::UnresolvedTypeVar)
- }
- Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType),
- };
-
- let arity = patterns.arity();
-
- let mut mono_patterns = Vec::with_capacity_in(patterns.patterns.len(), env.arena);
- for loc_pat in patterns.patterns.iter() {
- let mono_pat =
- from_can_pattern_help(env, procs, layout_cache, &loc_pat.value, assignments)?;
- mono_patterns.push(mono_pat);
- }
-
- Ok(Pattern::List {
- arity,
- element_layout,
- elements: mono_patterns,
- })
- }
- }
-}
-
-fn from_can_record_destruct<'a>(
- env: &mut Env<'a, '_>,
- procs: &mut Procs<'a>,
- layout_cache: &mut LayoutCache<'a>,
- can_rd: &roc_can::pattern::RecordDestruct,
- field_layout: InLayout<'a>,
- assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
-) -> Result, RuntimeError> {
- Ok(RecordDestruct {
- label: can_rd.label.clone(),
- variable: can_rd.var,
- layout: field_layout,
- typ: match &can_rd.typ {
- roc_can::pattern::DestructType::Required => DestructType::Required(can_rd.symbol),
- roc_can::pattern::DestructType::Optional(_, _) => {
- // if we reach this stage, the optional field is present
- DestructType::Required(can_rd.symbol)
- }
- roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard(
- from_can_pattern_help(env, procs, layout_cache, &loc_pattern.value, assignments)?,
- ),
- },
- })
-}
-
-#[derive(Debug, Clone, Copy)]
-enum IntOrFloatValue {
- Int(IntValue),
- Float(f64),
-}
-
-enum NumLiteral {
- Int([u8; 16], IntWidth),
- U128([u8; 16]),
- Float(f64, FloatWidth),
- Decimal([u8; 16]),
-}
-
-impl NumLiteral {
- fn to_expr_literal(&self) -> Literal<'static> {
- match *self {
- NumLiteral::Int(n, _) => Literal::Int(n),
- NumLiteral::U128(n) => Literal::U128(n),
- NumLiteral::Float(n, _) => Literal::Float(n),
- NumLiteral::Decimal(n) => Literal::Decimal(n),
- }
- }
- fn to_pattern(&self) -> Pattern<'static> {
- match *self {
- NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w),
- NumLiteral::U128(n) => Pattern::IntLiteral(n, IntWidth::U128),
- NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w),
- NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n),
- }
- }
-}
-
-fn make_num_literal<'a>(
- interner: &TLLayoutInterner<'a>,
- layout: InLayout<'a>,
- num_str: &str,
- num_value: IntOrFloatValue,
-) -> NumLiteral {
- match interner.get(layout) {
- Layout::Builtin(Builtin::Int(width)) => match num_value {
- IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width),
- IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n),
- IntOrFloatValue::Float(..) => {
- internal_error!("Float value where int was expected, should have been a type error")
- }
- },
- Layout::Builtin(Builtin::Float(width)) => match num_value {
- IntOrFloatValue::Float(n) => NumLiteral::Float(n, width),
- IntOrFloatValue::Int(int_value) => match int_value {
- IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width),
- IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width),
- },
- },
- Layout::Builtin(Builtin::Decimal) => {
- let dec = match RocDec::from_str(num_str) {
- Some(d) => d,
- None => internal_error!(
- "Invalid decimal for float literal = {}. This should be a type error!",
- num_str
- ),
- };
- NumLiteral::Decimal(dec.to_ne_bytes())
- }
- layout => internal_error!(
- "Found a non-num layout where a number was expected: {:?}",
- layout
- ),
- }
-}
-
fn assign_num_literal_expr<'a>(
env: &mut Env<'a, '_>,
layout_cache: &mut LayoutCache<'a>,
@@ -10019,20 +8776,6 @@ fn assign_num_literal_expr<'a>(
Stmt::Let(assigned, Expr::Literal(literal), layout, hole)
}
-fn make_num_literal_pattern<'a>(
- env: &mut Env<'a, '_>,
- layout_cache: &mut LayoutCache<'a>,
- variable: Variable,
- num_str: &str,
- num_value: IntOrFloatValue,
-) -> Pattern<'a> {
- let layout = layout_cache
- .from_var(env.arena, variable, env.subs)
- .unwrap();
- let literal = make_num_literal(&layout_cache.interner, layout, num_str, num_value);
- literal.to_pattern()
-}
-
type ToLowLevelCallArguments<'a> = (
LambdaName<'a>,
Symbol,
@@ -10407,7 +9150,20 @@ fn union_lambda_set_to_switch<'a>(
return empty_lambda_set_error(env);
}
- let join_point_id = JoinPointId(env.unique_symbol());
+ let (opt_join, branch_assigned, branch_hole) = match hole {
+ Stmt::Ret(_) => {
+ // No need to jump to a joinpoint, inline the return in each statement as-is.
+ // This makes further analyses, like TCO, easier as well.
+ (None, assigned, hole)
+ }
+ _ => {
+ let join_point_id = JoinPointId(env.unique_symbol());
+ let assigned = env.unique_symbol();
+ let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned]));
+
+ (Some(join_point_id), assigned, &*env.arena.alloc(hole))
+ }
+ };
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
@@ -10423,12 +9179,13 @@ fn union_lambda_set_to_switch<'a>(
let stmt = union_lambda_set_branch(
env,
- join_point_id,
lambda_name,
closure_info,
argument_symbols,
argument_layouts,
return_layout,
+ branch_assigned,
+ branch_hole,
);
branches.push((i as u64, BranchInfo::None, stmt));
}
@@ -10447,34 +9204,36 @@ fn union_lambda_set_to_switch<'a>(
ret_layout: return_layout,
};
- let param = Param {
- symbol: assigned,
- layout: return_layout,
- ownership: Ownership::Owned,
- };
+ match opt_join {
+ None => switch,
+ Some(join_point_id) => {
+ let param = Param {
+ symbol: assigned,
+ layout: return_layout,
+ ownership: Ownership::Owned,
+ };
- Stmt::Join {
- id: join_point_id,
- parameters: &*env.arena.alloc([param]),
- body: hole,
- remainder: env.arena.alloc(switch),
+ Stmt::Join {
+ id: join_point_id,
+ parameters: &*env.arena.alloc([param]),
+ body: hole,
+ remainder: env.arena.alloc(switch),
+ }
+ }
}
}
#[allow(clippy::too_many_arguments)]
fn union_lambda_set_branch<'a>(
env: &mut Env<'a, '_>,
- join_point_id: JoinPointId,
lambda_name: LambdaName<'a>,
closure_info: ClosureInfo<'a>,
argument_symbols_slice: &'a [Symbol],
argument_layouts_slice: &'a [InLayout<'a>],
return_layout: InLayout<'a>,
+ assigned: Symbol,
+ hole: &'a Stmt<'a>,
) -> Stmt<'a> {
- let result_symbol = env.unique_symbol();
-
- let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
-
union_lambda_set_branch_help(
env,
lambda_name,
@@ -10482,7 +9241,7 @@ fn union_lambda_set_branch<'a>(
argument_symbols_slice,
argument_layouts_slice,
return_layout,
- result_symbol,
+ assigned,
env.arena.alloc(hole),
)
}
@@ -10568,18 +9327,32 @@ fn enum_lambda_set_to_switch<'a>(
) -> Stmt<'a> {
debug_assert_ne!(lambda_set.len(), 0);
- let join_point_id = JoinPointId(env.unique_symbol());
+ let (opt_join, branch_assigned, branch_hole) = match hole {
+ Stmt::Ret(_) => {
+ // No need to jump to a joinpoint, inline the return in each statement as-is.
+ // This makes further analyses, like TCO, easier as well.
+ (None, assigned, hole)
+ }
+ _ => {
+ let join_point_id = JoinPointId(env.unique_symbol());
+ let assigned = env.unique_symbol();
+ let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned]));
+
+ (Some(join_point_id), assigned, &*env.arena.alloc(hole))
+ }
+ };
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
for (i, lambda_name) in lambda_set.into_iter().enumerate() {
let stmt = enum_lambda_set_branch(
env,
- join_point_id,
lambda_name,
argument_symbols,
argument_layouts,
return_layout,
+ branch_assigned,
+ branch_hole,
);
branches.push((i as u64, BranchInfo::None, stmt));
}
@@ -10598,17 +9371,22 @@ fn enum_lambda_set_to_switch<'a>(
ret_layout: return_layout,
};
- let param = Param {
- symbol: assigned,
- layout: return_layout,
- ownership: Ownership::Owned,
- };
+ match opt_join {
+ None => switch,
+ Some(join_point_id) => {
+ let param = Param {
+ symbol: assigned,
+ layout: return_layout,
+ ownership: Ownership::Owned,
+ };
- Stmt::Join {
- id: join_point_id,
- parameters: &*env.arena.alloc([param]),
- body: hole,
- remainder: env.arena.alloc(switch),
+ Stmt::Join {
+ id: join_point_id,
+ parameters: &*env.arena.alloc([param]),
+ body: hole,
+ remainder: env.arena.alloc(switch),
+ }
+ }
}
}
@@ -10616,18 +9394,13 @@ fn enum_lambda_set_to_switch<'a>(
#[allow(clippy::too_many_arguments)]
fn enum_lambda_set_branch<'a>(
env: &mut Env<'a, '_>,
- join_point_id: JoinPointId,
lambda_name: LambdaName<'a>,
argument_symbols: &'a [Symbol],
argument_layouts: &'a [InLayout<'a>],
return_layout: InLayout<'a>,
+ assigned: Symbol,
+ hole: &'a Stmt<'a>,
) -> Stmt<'a> {
- let result_symbol = env.unique_symbol();
-
- let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
-
- let assigned = result_symbol;
-
let call = self::Call {
call_type: CallType::ByName {
name: lambda_name,
@@ -10637,7 +9410,7 @@ fn enum_lambda_set_branch<'a>(
},
arguments: argument_symbols,
};
- build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
+ build_call(env, call, assigned, return_layout, hole)
}
#[allow(clippy::too_many_arguments)]
@@ -10714,3 +9487,347 @@ where
remainder: env.arena.alloc(switch),
}
}
+
+#[derive(Debug, Default)]
+pub struct GlueLayouts<'a> {
+ pub getters: std::vec::Vec<(Symbol, ProcLayout<'a>)>,
+}
+
+type GlueProcId = u16;
+
+#[derive(Debug)]
+pub struct GlueProc<'a> {
+ pub name: Symbol,
+ pub proc_layout: ProcLayout<'a>,
+ pub proc: Proc<'a>,
+}
+
+pub struct GlueProcs<'a> {
+ pub getters: Vec<'a, (Layout<'a>, Vec<'a, GlueProc<'a>>)>,
+ pub extern_names: Vec<'a, (LambdaSetId, RawFunctionLayout<'a>)>,
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
+pub struct LambdaSetId(pub u32);
+
+impl LambdaSetId {
+ #[must_use]
+ pub fn next(self) -> Self {
+ debug_assert!(self.0 < u32::MAX);
+ Self(self.0 + 1)
+ }
+}
+
+pub fn generate_glue_procs<'a, 'i, I>(
+ home: ModuleId,
+ ident_ids: &mut IdentIds,
+ arena: &'a Bump,
+ layout_interner: &'i mut I,
+ layout: &'a Layout<'a>,
+) -> GlueProcs<'a>
+where
+ I: LayoutInterner<'a>,
+{
+ let mut answer = GlueProcs {
+ getters: Vec::new_in(arena),
+ extern_names: Vec::new_in(arena),
+ };
+
+ let mut lambda_set_id = LambdaSetId(0);
+
+ let mut stack: Vec<'a, Layout<'a>> = Vec::from_iter_in([*layout], arena);
+ let mut next_unique_id = 0;
+
+ macro_rules! handle_tag_field_layouts {
+ ($tag_id:expr, $layout:expr, $union_layout:expr, $field_layouts: expr) => {{
+ if $field_layouts.iter().any(|l| {
+ layout_interner
+ .get(*l)
+ .has_varying_stack_size(layout_interner, arena)
+ }) {
+ let procs = generate_glue_procs_for_tag_fields(
+ layout_interner,
+ home,
+ &mut next_unique_id,
+ ident_ids,
+ arena,
+ $tag_id,
+ &$layout,
+ $union_layout,
+ $field_layouts,
+ );
+
+ answer.getters.push(($layout, procs));
+ }
+
+ for in_layout in $field_layouts.iter().rev() {
+ stack.push(layout_interner.get(*in_layout));
+ }
+ }};
+ }
+
+ while let Some(layout) = stack.pop() {
+ match layout {
+ Layout::Builtin(builtin) => match builtin {
+ Builtin::Int(_)
+ | Builtin::Float(_)
+ | Builtin::Bool
+ | Builtin::Decimal
+ | Builtin::Str => { /* do nothing */ }
+ Builtin::List(element) => stack.push(layout_interner.get(element)),
+ },
+ Layout::Struct { field_layouts, .. } => {
+ if field_layouts.iter().any(|l| {
+ layout_interner
+ .get(*l)
+ .has_varying_stack_size(layout_interner, arena)
+ }) {
+ let procs = generate_glue_procs_for_struct_fields(
+ layout_interner,
+ home,
+ &mut next_unique_id,
+ ident_ids,
+ arena,
+ &layout,
+ field_layouts,
+ );
+
+ answer.getters.push((layout, procs));
+ }
+
+ for in_layout in field_layouts.iter().rev() {
+ stack.push(layout_interner.get(*in_layout));
+ }
+ }
+ Layout::Boxed(boxed) => {
+ stack.push(layout_interner.get(boxed));
+ }
+ Layout::Union(union_layout) => match union_layout {
+ UnionLayout::NonRecursive(tags) => {
+ for in_layout in tags.iter().flat_map(|e| e.iter()) {
+ stack.push(layout_interner.get(*in_layout));
+ }
+ }
+ UnionLayout::Recursive(tags) => {
+ for in_layout in tags.iter().flat_map(|e| e.iter()) {
+ stack.push(layout_interner.get(*in_layout));
+ }
+ }
+ UnionLayout::NonNullableUnwrapped(field_layouts) => {
+ handle_tag_field_layouts!(0, layout, union_layout, field_layouts);
+ }
+ UnionLayout::NullableWrapped {
+ other_tags,
+ nullable_id,
+ } => {
+ let tag_ids =
+ (0..nullable_id).chain(nullable_id + 1..other_tags.len() as u16 + 1);
+ for (i, field_layouts) in tag_ids.zip(other_tags) {
+ handle_tag_field_layouts!(i, layout, union_layout, *field_layouts);
+ }
+ }
+ UnionLayout::NullableUnwrapped { other_fields, .. } => {
+ for in_layout in other_fields.iter().rev() {
+ stack.push(layout_interner.get(*in_layout));
+ }
+ }
+ },
+ Layout::LambdaSet(lambda_set) => {
+ let raw_function_layout =
+ RawFunctionLayout::Function(lambda_set.args, lambda_set, lambda_set.ret);
+
+ let key = (lambda_set_id, raw_function_layout);
+ answer.extern_names.push(key);
+
+ // this id is used, increment for the next one
+ lambda_set_id = lambda_set_id.next();
+
+ stack.push(layout_interner.get(lambda_set.runtime_representation()));
+ }
+ Layout::RecursivePointer(_) => {
+ /* do nothing, we've already generated for this type through the Union(_) */
+ }
+ }
+ }
+
+ answer
+}
+
+fn generate_glue_procs_for_struct_fields<'a, 'i, I>(
+ layout_interner: &'i mut I,
+ home: ModuleId,
+ next_unique_id: &mut GlueProcId,
+ ident_ids: &mut IdentIds,
+ arena: &'a Bump,
+ unboxed_struct_layout: &Layout<'a>,
+ field_layouts: &[InLayout<'a>],
+) -> Vec<'a, GlueProc<'a>>
+where
+ I: LayoutInterner<'a>,
+{
+ let interned_unboxed_struct_layout = layout_interner.insert(*unboxed_struct_layout);
+ let boxed_struct_layout = Layout::Boxed(interned_unboxed_struct_layout);
+ let boxed_struct_layout = layout_interner.insert(boxed_struct_layout);
+ let mut answer = bumpalo::collections::Vec::with_capacity_in(field_layouts.len(), arena);
+
+ let field_layouts = match layout_interner.get(interned_unboxed_struct_layout) {
+ Layout::Struct { field_layouts, .. } => field_layouts,
+ other => {
+ unreachable!(
+ "{:?} {:?}",
+ layout_interner.dbg(interned_unboxed_struct_layout),
+ other
+ )
+ }
+ };
+
+ for (index, field) in field_layouts.iter().enumerate() {
+ let proc_layout = ProcLayout {
+ arguments: arena.alloc([boxed_struct_layout]),
+ result: *field,
+ niche: Niche::NONE,
+ };
+
+ let symbol = unique_glue_symbol(arena, next_unique_id, home, ident_ids);
+
+ let argument = Symbol::new(home, ident_ids.gen_unique());
+ let unboxed = Symbol::new(home, ident_ids.gen_unique());
+ let result = Symbol::new(home, ident_ids.gen_unique());
+
+ home.register_debug_idents(ident_ids);
+
+ let ret_stmt = arena.alloc(Stmt::Ret(result));
+
+ let field_get_expr = Expr::StructAtIndex {
+ index: index as u64,
+ field_layouts,
+ structure: unboxed,
+ };
+
+ let field_get_stmt = Stmt::Let(result, field_get_expr, *field, ret_stmt);
+
+ let unbox_expr = Expr::ExprUnbox { symbol: argument };
+
+ let unbox_stmt = Stmt::Let(
+ unboxed,
+ unbox_expr,
+ interned_unboxed_struct_layout,
+ arena.alloc(field_get_stmt),
+ );
+
+ let proc = Proc {
+ name: LambdaName::no_niche(symbol),
+ args: arena.alloc([(boxed_struct_layout, argument)]),
+ body: unbox_stmt,
+ closure_data_layout: None,
+ ret_layout: *field,
+ is_self_recursive: SelfRecursive::NotSelfRecursive,
+ must_own_arguments: false,
+ host_exposed_layouts: HostExposedLayouts::NotHostExposed,
+ };
+
+ answer.push(GlueProc {
+ name: symbol,
+ proc_layout,
+ proc,
+ });
+ }
+
+ answer
+}
+
+fn unique_glue_symbol(
+ arena: &Bump,
+ next_unique_id: &mut GlueProcId,
+ home: ModuleId,
+ ident_ids: &mut IdentIds,
+) -> Symbol {
+ let unique_id = *next_unique_id;
+
+ *next_unique_id = unique_id + 1;
+
+ // then name of the platform `main.roc` is the empty string
+ let module_name = "";
+
+ // Turn unique_id into a Symbol without doing a heap allocation.
+ use std::fmt::Write;
+ let mut string = bumpalo::collections::String::with_capacity_in(32, arena);
+
+ let _result = write!(&mut string, "roc__getter_{}_{}", module_name, unique_id);
+ debug_assert_eq!(_result, Ok(())); // This should never fail, but doesn't hurt to debug-check!
+
+ let bump_string = string.into_bump_str();
+ let ident_id = ident_ids.get_or_insert(bump_string);
+
+ Symbol::new(home, ident_id)
+}
+
+#[allow(clippy::too_many_arguments)]
+fn generate_glue_procs_for_tag_fields<'a, 'i, I>(
+ layout_interner: &'i mut I,
+ home: ModuleId,
+ next_unique_id: &mut GlueProcId,
+ ident_ids: &mut IdentIds,
+ arena: &'a Bump,
+ tag_id: TagIdIntType,
+ unboxed_struct_layout: &Layout<'a>,
+ union_layout: UnionLayout<'a>,
+ field_layouts: &'a [InLayout<'a>],
+) -> Vec<'a, GlueProc<'a>>
+where
+ I: LayoutInterner<'a>,
+{
+ let interned = layout_interner.insert(*unboxed_struct_layout);
+ let boxed_struct_layout = Layout::Boxed(interned);
+ let boxed_struct_layout = layout_interner.insert(boxed_struct_layout);
+ let mut answer = bumpalo::collections::Vec::with_capacity_in(field_layouts.len(), arena);
+
+ for (index, field) in field_layouts.iter().enumerate() {
+ let proc_layout = ProcLayout {
+ arguments: arena.alloc([boxed_struct_layout]),
+ result: *field,
+ niche: Niche::NONE,
+ };
+ let symbol = unique_glue_symbol(arena, next_unique_id, home, ident_ids);
+
+ let argument = Symbol::new(home, ident_ids.gen_unique());
+ let unboxed = Symbol::new(home, ident_ids.gen_unique());
+ let result = Symbol::new(home, ident_ids.gen_unique());
+
+ home.register_debug_idents(ident_ids);
+
+ let ret_stmt = arena.alloc(Stmt::Ret(result));
+
+ let field_get_expr = Expr::UnionAtIndex {
+ structure: unboxed,
+ tag_id,
+ union_layout,
+ index: index as u64,
+ };
+
+ let field_get_stmt = Stmt::Let(result, field_get_expr, *field, ret_stmt);
+
+ let unbox_expr = Expr::ExprUnbox { symbol: argument };
+
+ let unbox_stmt = Stmt::Let(unboxed, unbox_expr, interned, arena.alloc(field_get_stmt));
+
+ let proc = Proc {
+ name: LambdaName::no_niche(symbol),
+ args: arena.alloc([(boxed_struct_layout, argument)]),
+ body: unbox_stmt,
+ closure_data_layout: None,
+ ret_layout: *field,
+ is_self_recursive: SelfRecursive::NotSelfRecursive,
+ must_own_arguments: false,
+ host_exposed_layouts: HostExposedLayouts::NotHostExposed,
+ };
+
+ answer.push(GlueProc {
+ name: symbol,
+ proc_layout,
+ proc,
+ });
+ }
+
+ answer
+}
diff --git a/crates/compiler/mono/src/decision_tree.rs b/crates/compiler/mono/src/ir/decision_tree.rs
similarity index 88%
rename from crates/compiler/mono/src/decision_tree.rs
rename to crates/compiler/mono/src/ir/decision_tree.rs
index d9ed90e33b..c5c1a48425 100644
--- a/crates/compiler/mono/src/decision_tree.rs
+++ b/crates/compiler/mono/src/ir/decision_tree.rs
@@ -1,7 +1,8 @@
+use super::pattern::{build_list_index_probe, store_pattern, DestructType, ListIndex, Pattern};
use crate::borrow::Ownership;
use crate::ir::{
- build_list_index_probe, BranchInfo, Call, CallType, DestructType, Env, Expr, JoinPointId,
- ListIndex, Literal, Param, Pattern, Procs, Stmt,
+ substitute_in_exprs_many, BranchInfo, Call, CallType, CompiledGuardStmt, Env, Expr,
+ GuardStmtSpec, JoinPointId, Literal, Param, Procs, Stmt,
};
use crate::layout::{
Builtin, InLayout, Layout, LayoutCache, LayoutInterner, TLLayoutInterner, TagIdIntType,
@@ -9,6 +10,7 @@ use crate::layout::{
};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
+use roc_collections::BumpMap;
use roc_error_macros::internal_error;
use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId, Union};
use roc_module::ident::TagName;
@@ -19,6 +21,7 @@ use roc_module::symbol::Symbol;
type Label = u64;
const RECORD_TAG_NAME: &str = "#Record";
+const TUPLE_TAG_NAME: &str = "#Tuple";
/// Users of this module will mainly interact with this function. It takes
/// some normal branches and gives out a decision tree that has "labels" at all
@@ -41,14 +44,13 @@ fn compile<'a>(
}
#[derive(Clone, Debug, PartialEq)]
-pub enum Guard<'a> {
+pub(crate) enum Guard<'a> {
NoGuard,
Guard {
/// pattern
pattern: Pattern<'a>,
- /// after assigning to symbol, the stmt jumps to this label
- id: JoinPointId,
- stmt: Stmt<'a>,
+ /// How to compile the guard statement.
+ stmt_spec: GuardStmtSpec,
},
}
@@ -56,6 +58,10 @@ impl<'a> Guard<'a> {
fn is_none(&self) -> bool {
self == &Guard::NoGuard
}
+
+ fn is_some(&self) -> bool {
+ !self.is_none()
+ }
}
type Edge<'a> = (GuardedTest<'a>, DecisionTree<'a>);
@@ -72,19 +78,19 @@ enum DecisionTree<'a> {
#[derive(Clone, Debug, PartialEq)]
enum GuardedTest<'a> {
- // e.g. `_ if True -> ...`
+ // e.g. `x if True -> ...`
GuardedNoTest {
/// pattern
pattern: Pattern<'a>,
- /// after assigning to symbol, the stmt jumps to this label
- id: JoinPointId,
- /// body
- stmt: Stmt<'a>,
+ /// How to compile the guard body.
+ stmt_spec: GuardStmtSpec,
},
+ // e.g. ` -> ...`
TestNotGuarded {
test: Test<'a>,
},
- Placeholder,
+ // e.g. `_ -> ...` or `x -> ...`
+ PlaceholderWithGuard,
}
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
@@ -187,15 +193,15 @@ impl<'a> Hash for Test<'a> {
impl<'a> Hash for GuardedTest<'a> {
fn hash(&self, state: &mut H) {
match self {
- GuardedTest::GuardedNoTest { id, .. } => {
+ GuardedTest::GuardedNoTest { stmt_spec, .. } => {
state.write_u8(1);
- id.hash(state);
+ stmt_spec.hash(state);
}
GuardedTest::TestNotGuarded { test } => {
state.write_u8(0);
test.hash(state);
}
- GuardedTest::Placeholder => {
+ GuardedTest::PlaceholderWithGuard => {
state.write_u8(2);
}
}
@@ -231,8 +237,8 @@ fn to_decision_tree<'a>(
match first.guard {
Guard::NoGuard => unreachable!(),
- Guard::Guard { id, stmt, pattern } => {
- let guarded_test = GuardedTest::GuardedNoTest { id, stmt, pattern };
+ Guard::Guard { pattern, stmt_spec } => {
+ let guarded_test = GuardedTest::GuardedNoTest { pattern, stmt_spec };
// the guard test does not have a path
let path = vec![];
@@ -263,6 +269,7 @@ fn to_decision_tree<'a>(
let path = pick_path(&branches).clone();
let bs = branches.clone();
+
let (edges, fallback) = gather_edges(interner, branches, &path);
let mut decision_edges: Vec<_> = edges
@@ -307,7 +314,7 @@ fn break_out_guard<'a>(
) -> DecisionTree<'a> {
match edges
.iter()
- .position(|(t, _)| matches!(t, GuardedTest::Placeholder))
+ .position(|(t, _)| matches!(t, GuardedTest::PlaceholderWithGuard))
{
None => DecisionTree::Decision {
path,
@@ -346,7 +353,7 @@ fn guarded_tests_are_complete(tests: &[GuardedTest]) -> bool {
.all(|t| matches!(t, GuardedTest::TestNotGuarded { .. }));
match tests.last().unwrap() {
- GuardedTest::Placeholder => false,
+ GuardedTest::PlaceholderWithGuard => false,
GuardedTest::GuardedNoTest { .. } => false,
GuardedTest::TestNotGuarded { test } => no_guard && tests_are_complete_help(test, length),
}
@@ -572,6 +579,31 @@ fn test_for_pattern<'a>(pattern: &Pattern<'a>) -> Option> {
}
}
+ TupleDestructure(destructs, _) => {
+ // not rendered, so pick the easiest
+ let union = Union {
+ render_as: RenderAs::Tag,
+ alternatives: vec![Ctor {
+ tag_id: TagId(0),
+ name: CtorName::Tag(TagName(TUPLE_TAG_NAME.into())),
+ arity: destructs.len(),
+ }],
+ };
+
+ let mut arguments = std::vec::Vec::new();
+
+ for destruct in destructs {
+ arguments.push((destruct.pat.clone(), destruct.layout));
+ }
+
+ IsCtor {
+ tag_id: 0,
+ ctor_name: CtorName::Tag(TagName(TUPLE_TAG_NAME.into())),
+ union,
+ arguments,
+ }
+ }
+
NewtypeDestructure {
tag_name,
arguments,
@@ -661,7 +693,7 @@ fn test_at_path<'a>(
if let Guard::Guard { .. } = &branch.guard {
// no tests for this pattern remain, but we cannot discard it yet
// because it has a guard!
- Some(GuardedTest::Placeholder)
+ Some(GuardedTest::PlaceholderWithGuard)
} else {
None
}
@@ -683,10 +715,33 @@ fn edges_for<'a>(
// if we test for a guard, skip all branches until one that has a guard
let it = match test {
- GuardedTest::GuardedNoTest { .. } | GuardedTest::Placeholder => {
+ GuardedTest::GuardedNoTest { .. } => {
let index = branches
.iter()
- .position(|b| !b.guard.is_none())
+ .position(|b| b.guard.is_some())
+ .expect("if testing for a guard, one branch must have a guard");
+
+ branches[index..].iter()
+ }
+ GuardedTest::PlaceholderWithGuard => {
+ // Skip all branches until we hit the one with a placeholder and a guard.
+ let index = branches
+ .iter()
+ .position(|b| {
+ if b.guard.is_none() {
+ return false;
+ }
+
+ let (_, pattern) = b
+ .patterns
+ .iter()
+ .find(|(branch_path, _)| branch_path == path)
+ .expect(
+ "if testing for a placeholder with guard, must find a branch matching the path",
+ );
+
+ test_for_pattern(pattern).is_none()
+ })
.expect("if testing for a guard, one branch must have a guard");
branches[index..].iter()
@@ -715,7 +770,7 @@ fn to_relevant_branch<'a>(
found_pattern: pattern,
end,
} => match guarded_test {
- GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
+ GuardedTest::PlaceholderWithGuard | GuardedTest::GuardedNoTest { .. } => {
// if there is no test, the pattern should not require any
debug_assert!(
matches!(pattern, Pattern::Identifier(_) | Pattern::Underscore,),
@@ -790,6 +845,42 @@ fn to_relevant_branch_help<'a>(
_ => None,
},
+ TupleDestructure(destructs, _) => match test {
+ IsCtor {
+ ctor_name: test_name,
+ tag_id,
+ ..
+ } => {
+ debug_assert!(test_name == &CtorName::Tag(TagName(TUPLE_TAG_NAME.into())));
+ let destructs_len = destructs.len();
+ let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| {
+ let pattern = destruct.pat.clone();
+
+ let mut new_path = path.to_vec();
+ let next_instr = if destructs_len == 1 {
+ PathInstruction::NewType
+ } else {
+ PathInstruction::TagIndex {
+ index: index as u64,
+ tag_id: *tag_id,
+ }
+ };
+ new_path.push(next_instr);
+
+ (new_path, pattern)
+ });
+ start.extend(sub_positions);
+ start.extend(end);
+
+ Some(Branch {
+ goal: branch.goal,
+ guard: branch.guard.clone(),
+ patterns: start,
+ })
+ }
+ _ => None,
+ },
+
OpaqueUnwrap { opaque, argument } => match test {
IsCtor {
ctor_name: test_opaque_tag_name,
@@ -1126,6 +1217,7 @@ fn needs_tests(pattern: &Pattern) -> bool {
NewtypeDestructure { .. }
| RecordDestructure(..)
+ | TupleDestructure(..)
| AppliedTag { .. }
| OpaqueUnwrap { .. }
| BitLiteral { .. }
@@ -1269,15 +1361,15 @@ fn small_branching_factor(branches: &[Branch], path: &[PathInstruction]) -> usiz
relevant_tests.len() + (if !fallbacks { 0 } else { 1 })
}
-#[derive(Clone, Debug, PartialEq)]
+#[derive(Debug, PartialEq)]
enum Decider<'a, T> {
Leaf(T),
Guarded {
- /// after assigning to symbol, the stmt jumps to this label
- id: JoinPointId,
- stmt: Stmt<'a>,
pattern: Pattern<'a>,
+ /// The guard expression and how to compile it.
+ stmt_spec: GuardStmtSpec,
+
success: Box>,
failure: Box>,
},
@@ -1301,7 +1393,18 @@ enum Choice<'a> {
type StoresVec<'a> = bumpalo::collections::Vec<'a, (Symbol, InLayout<'a>, Expr<'a>)>;
-pub fn optimize_when<'a>(
+struct JumpSpec<'a> {
+ target_index: u64,
+ id: JoinPointId,
+ /// Symbols, from the unpacked pattern, to add on when jumping to the target.
+ jump_pattern_param_symbols: &'a [Symbol],
+
+ // Used to construct the joinpoint
+ join_params: &'a [Param<'a>],
+ join_body: Stmt<'a>,
+}
+
+pub(crate) fn optimize_when<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
@@ -1310,11 +1413,11 @@ pub fn optimize_when<'a>(
ret_layout: InLayout<'a>,
opt_branches: bumpalo::collections::Vec<'a, (Pattern<'a>, Guard<'a>, Stmt<'a>)>,
) -> Stmt<'a> {
- let (patterns, _indexed_branches) = opt_branches
+ let (patterns, indexed_branches): (_, Vec<_>) = opt_branches
.into_iter()
.enumerate()
.map(|(index, (pattern, guard, branch))| {
- let has_guard = !guard.is_none();
+ let has_guard = guard.is_some();
(
(guard, pattern.clone(), index as u64),
(index as u64, branch, pattern, has_guard),
@@ -1322,8 +1425,6 @@ pub fn optimize_when<'a>(
})
.unzip();
- let indexed_branches: Vec<_> = _indexed_branches;
-
let decision_tree = compile(&layout_cache.interner, patterns);
let decider = tree_to_decider(decision_tree);
@@ -1334,19 +1435,84 @@ pub fn optimize_when<'a>(
let mut choices = MutMap::default();
let mut jumps = Vec::new();
- 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);
+ for (target, mut branch, pattern, has_guard) in indexed_branches.into_iter() {
+ let should_inline = {
+ let target_counts = &target_counts;
+ match target_counts.get(target as usize) {
+ None => unreachable!(
+ "this should never happen: {:?} not in {:?}",
+ target, target_counts
+ ),
+ Some(count) => *count == 1,
+ }
+ };
+
+ let join_params: &'a [Param<'a>];
+ let jump_pattern_param_symbols: &'a [Symbol];
+ match (has_guard, should_inline) {
+ (false, _) => {
+ // Bind the fields referenced in the pattern.
+ branch = store_pattern(env, procs, layout_cache, &pattern, cond_symbol, branch);
+
+ join_params = &[];
+ jump_pattern_param_symbols = &[];
+ }
+ (true, true) => {
+ // Nothing more to do - the patterns will be bound when the guard is evaluated in
+ // `decide_to_branching`.
+ join_params = &[];
+ jump_pattern_param_symbols = &[];
+ }
+ (true, false) => {
+ // The patterns will be bound when the guard is evaluated, and then we need to get
+ // them back into the joinpoint here.
+ //
+ // So, figure out what symbols the pattern binds, and update the joinpoint
+ // parameter to take each symbol. Then, when the joinpoint is called, the unpacked
+ // symbols will be filled in.
+ //
+ // Since the joinpoint's parameters will be fresh symbols, the join body also needs
+ // updating.
+ let pattern_bindings = pattern.collect_symbols(cond_layout);
+
+ let mut parameters_buf = bumpalo::collections::Vec::with_capacity_in(1, env.arena);
+ let mut pattern_symbols_buf =
+ bumpalo::collections::Vec::with_capacity_in(1, env.arena);
+ let mut substitutions = BumpMap::default();
+
+ for (pattern_symbol, layout) in pattern_bindings {
+ let param_symbol = env.unique_symbol();
+ parameters_buf.push(Param {
+ symbol: param_symbol,
+ layout,
+ ownership: Ownership::Owned,
+ });
+ pattern_symbols_buf.push(pattern_symbol);
+ substitutions.insert(pattern_symbol, param_symbol);
+ }
+
+ join_params = parameters_buf.into_bump_slice();
+ jump_pattern_param_symbols = pattern_symbols_buf.into_bump_slice();
+
+ substitute_in_exprs_many(env.arena, &mut branch, substitutions);
+ }
}
- let ((branch_index, choice), opt_jump) = create_choices(&target_counts, index, branch);
+ let ((branch_index, choice), opt_jump) = if should_inline {
+ ((target, Choice::Inline(branch)), None)
+ } else {
+ ((target, Choice::Jump(target)), Some((target, branch)))
+ };
- if let Some((index, body)) = opt_jump {
+ if let Some((target_index, body)) = opt_jump {
let id = JoinPointId(env.unique_symbol());
- jumps.push((index, id, body));
+ jumps.push(JumpSpec {
+ target_index,
+ id,
+ jump_pattern_param_symbols,
+ join_params,
+ join_body: body,
+ });
}
choices.insert(branch_index, choice);
@@ -1365,11 +1531,18 @@ pub fn optimize_when<'a>(
&jumps,
);
- for (_, id, body) in jumps.into_iter() {
+ for JumpSpec {
+ target_index: _,
+ id,
+ jump_pattern_param_symbols: _,
+ join_params,
+ join_body,
+ } in jumps.into_iter()
+ {
stmt = Stmt::Join {
id,
- parameters: &[],
- body: env.arena.alloc(body),
+ parameters: join_params,
+ body: env.arena.alloc(join_body),
remainder: env.arena.alloc(stmt),
};
}
@@ -1406,7 +1579,7 @@ fn path_to_expr_help<'a>(
PathInstruction::TagIndex { index, tag_id } => {
let index = *index;
- match layout_interner.get(layout) {
+ match layout_interner.chase_recursive(layout) {
Layout::Union(union_layout) => {
let inner_expr = Expr::UnionAtIndex {
tag_id: *tag_id,
@@ -1506,7 +1679,7 @@ fn test_to_comparison<'a>(
// (e.g. record pattern guard matches)
debug_assert!(union.alternatives.len() > 1);
- match layout_interner.get(test_layout) {
+ match layout_interner.chase_recursive(test_layout) {
Layout::Union(union_layout) => {
let lhs = Expr::Literal(Literal::Int((tag_id as i128).to_ne_bytes()));
@@ -1866,7 +2039,7 @@ fn decide_to_branching<'a>(
cond_layout: InLayout<'a>,
ret_layout: InLayout<'a>,
decider: Decider<'a, Choice<'a>>,
- jumps: &[(u64, JoinPointId, Stmt<'a>)],
+ jumps: &[JumpSpec<'a>],
) -> Stmt<'a> {
use Choice::*;
use Decider::*;
@@ -1876,16 +2049,15 @@ fn decide_to_branching<'a>(
match decider {
Leaf(Jump(label)) => {
let index = jumps
- .binary_search_by_key(&label, |r| r.0)
+ .binary_search_by_key(&label, |r| r.target_index)
.expect("jump not in list of jumps");
- Stmt::Jump(jumps[index].1, &[])
+ Stmt::Jump(jumps[index].id, jumps[index].jump_pattern_param_symbols)
}
Leaf(Inline(expr)) => expr,
Guarded {
- id,
- stmt,
pattern,
+ stmt_spec,
success,
failure,
} => {
@@ -1931,14 +2103,19 @@ fn decide_to_branching<'a>(
ownership: Ownership::Owned,
};
+ let CompiledGuardStmt {
+ join_point_id,
+ stmt,
+ } = stmt_spec.generate_guard_and_join(env, procs, layout_cache);
+
let join = Stmt::Join {
- id,
+ id: join_point_id,
parameters: arena.alloc([param]),
- remainder: arena.alloc(stmt),
body: arena.alloc(decide),
+ remainder: arena.alloc(stmt),
};
- crate::ir::store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
+ store_pattern(env, procs, layout_cache, &pattern, cond_symbol, join)
}
Chain {
test_chain,
@@ -2108,7 +2285,7 @@ fn decide_to_branching<'a>(
// We have learned more about the exact layout of the cond (based on the path)
// but tests are still relative to the original cond symbol
- let inner_cond_layout_raw = layout_cache.get_in(inner_cond_layout);
+ let inner_cond_layout_raw = layout_cache.interner.chase_recursive(inner_cond_layout);
let mut switch = if let Layout::Union(union_layout) = inner_cond_layout_raw {
let tag_id_symbol = env.unique_symbol();
@@ -2219,15 +2396,17 @@ fn sort_edge_tests_by_priority(edges: &mut [Edge<'_>]) {
edges.sort_by(|(t1, _), (t2, _)| match (t1, t2) {
// Guarded takes priority
(GuardedNoTest { .. }, GuardedNoTest { .. }) => Equal,
- (GuardedNoTest { .. }, TestNotGuarded { .. }) | (GuardedNoTest { .. }, Placeholder) => Less,
+ (GuardedNoTest { .. }, TestNotGuarded { .. })
+ | (GuardedNoTest { .. }, PlaceholderWithGuard) => Less,
// Interesting case: what test do we pick?
(TestNotGuarded { test: t1 }, TestNotGuarded { test: t2 }) => order_tests(t1, t2),
// Otherwise we are between guarded and fall-backs
(TestNotGuarded { .. }, GuardedNoTest { .. }) => Greater,
- (TestNotGuarded { .. }, Placeholder) => Less,
+ (TestNotGuarded { .. }, PlaceholderWithGuard) => Less,
// Placeholder is always last
- (Placeholder, Placeholder) => Equal,
- (Placeholder, GuardedNoTest { .. }) | (Placeholder, TestNotGuarded { .. }) => Greater,
+ (PlaceholderWithGuard, PlaceholderWithGuard) => Equal,
+ (PlaceholderWithGuard, GuardedNoTest { .. })
+ | (PlaceholderWithGuard, TestNotGuarded { .. }) => Greater,
});
fn order_tests(t1: &Test, t2: &Test) -> Ordering {
@@ -2389,7 +2568,7 @@ fn fanout_decider_help<'a>(
guarded_test: GuardedTest<'a>,
) -> (Test<'a>, Decider<'a, u64>) {
match guarded_test {
- GuardedTest::Placeholder | GuardedTest::GuardedNoTest { .. } => {
+ GuardedTest::PlaceholderWithGuard | GuardedTest::GuardedNoTest { .. } => {
unreachable!("this would not end up in a switch")
}
GuardedTest::TestNotGuarded { test } => {
@@ -2406,16 +2585,15 @@ fn chain_decider<'a>(
success_tree: DecisionTree<'a>,
) -> Decider<'a, u64> {
match guarded_test {
- GuardedTest::GuardedNoTest { id, stmt, pattern } => {
+ GuardedTest::GuardedNoTest { pattern, stmt_spec } => {
let failure = Box::new(tree_to_decider(failure_tree));
let success = Box::new(tree_to_decider(success_tree));
Decider::Guarded {
- id,
- stmt,
pattern,
+ stmt_spec,
success,
- failure: failure.clone(),
+ failure,
}
}
GuardedTest::TestNotGuarded { test } => {
@@ -2426,7 +2604,7 @@ fn chain_decider<'a>(
}
}
- GuardedTest::Placeholder => {
+ GuardedTest::PlaceholderWithGuard => {
// ?
tree_to_decider(success_tree)
}
@@ -2509,22 +2687,6 @@ fn count_targets(targets: &mut bumpalo::collections::Vec, initial: &Decider
}
}
-#[allow(clippy::type_complexity)]
-fn create_choices<'a>(
- target_counts: &bumpalo::collections::Vec<'a, u64>,
- target: u64,
- branch: Stmt<'a>,
-) -> ((u64, Choice<'a>), Option<(u64, Stmt<'a>)>) {
- match target_counts.get(target as usize) {
- None => unreachable!(
- "this should never happen: {:?} not in {:?}",
- target, target_counts
- ),
- Some(1) => ((target, Choice::Inline(branch)), None),
- Some(_) => ((target, Choice::Jump(target)), Some((target, branch))),
- }
-}
-
fn insert_choices<'a>(
choice_dict: &MutMap>,
decider: Decider<'a, u64>,
@@ -2538,15 +2700,13 @@ fn insert_choices<'a>(
}
Guarded {
- id,
- stmt,
pattern,
+ stmt_spec,
success,
failure,
} => Guarded {
- id,
- stmt,
pattern,
+ stmt_spec,
success: Box::new(insert_choices(choice_dict, *success)),
failure: Box::new(insert_choices(choice_dict, *failure)),
},
diff --git a/crates/compiler/mono/src/ir/literal.rs b/crates/compiler/mono/src/ir/literal.rs
new file mode 100644
index 0000000000..8997bfa912
--- /dev/null
+++ b/crates/compiler/mono/src/ir/literal.rs
@@ -0,0 +1,116 @@
+use roc_builtins::bitcode::{FloatWidth, IntWidth};
+use roc_can::expr::IntValue;
+use roc_error_macros::internal_error;
+use roc_module::symbol::Symbol;
+use roc_std::RocDec;
+
+use crate::layout::{Builtin, InLayout, Layout, LayoutInterner, TLLayoutInterner};
+
+use super::pattern::Pattern;
+
+#[derive(Debug, Clone, Copy)]
+pub enum IntOrFloatValue {
+ Int(IntValue),
+ Float(f64),
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum Literal<'a> {
+ // Literals
+ /// stored as raw bytes rather than a number to avoid an alignment bump
+ Int([u8; 16]),
+ /// stored as raw bytes rather than a number to avoid an alignment bump
+ U128([u8; 16]),
+ Float(f64),
+ /// stored as raw bytes rather than a number to avoid an alignment bump
+ Decimal([u8; 16]),
+ Str(&'a str),
+ /// Closed tag unions containing exactly two (0-arity) tags compile to Expr::Bool,
+ /// so they can (at least potentially) be emitted as 1-bit machine bools.
+ ///
+ /// So [True, False] compiles to this, and so do [A, B] and [Foo, Bar].
+ /// However, a union like [True, False, Other Int] would not.
+ Bool(bool),
+ /// Closed tag unions containing between 3 and 256 tags (all of 0 arity)
+ /// compile to bytes, e.g. [Blue, Black, Red, Green, White]
+ Byte(u8),
+}
+
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum ListLiteralElement<'a> {
+ Literal(Literal<'a>),
+ Symbol(Symbol),
+}
+
+impl<'a> ListLiteralElement<'a> {
+ pub fn to_symbol(&self) -> Option {
+ match self {
+ Self::Symbol(s) => Some(*s),
+ _ => None,
+ }
+ }
+}
+
+pub enum NumLiteral {
+ Int([u8; 16], IntWidth),
+ U128([u8; 16]),
+ Float(f64, FloatWidth),
+ Decimal([u8; 16]),
+}
+
+impl NumLiteral {
+ pub fn to_expr_literal(&self) -> Literal<'static> {
+ match *self {
+ NumLiteral::Int(n, _) => Literal::Int(n),
+ NumLiteral::U128(n) => Literal::U128(n),
+ NumLiteral::Float(n, _) => Literal::Float(n),
+ NumLiteral::Decimal(n) => Literal::Decimal(n),
+ }
+ }
+ pub fn to_pattern(&self) -> Pattern<'static> {
+ match *self {
+ NumLiteral::Int(n, w) => Pattern::IntLiteral(n, w),
+ NumLiteral::U128(n) => Pattern::IntLiteral(n, IntWidth::U128),
+ NumLiteral::Float(n, w) => Pattern::FloatLiteral(f64::to_bits(n), w),
+ NumLiteral::Decimal(n) => Pattern::DecimalLiteral(n),
+ }
+ }
+}
+
+pub fn make_num_literal<'a>(
+ interner: &TLLayoutInterner<'a>,
+ layout: InLayout<'a>,
+ num_str: &str,
+ num_value: IntOrFloatValue,
+) -> NumLiteral {
+ match interner.get(layout) {
+ Layout::Builtin(Builtin::Int(width)) => match num_value {
+ IntOrFloatValue::Int(IntValue::I128(n)) => NumLiteral::Int(n, width),
+ IntOrFloatValue::Int(IntValue::U128(n)) => NumLiteral::U128(n),
+ IntOrFloatValue::Float(..) => {
+ internal_error!("Float value where int was expected, should have been a type error")
+ }
+ },
+ Layout::Builtin(Builtin::Float(width)) => match num_value {
+ IntOrFloatValue::Float(n) => NumLiteral::Float(n, width),
+ IntOrFloatValue::Int(int_value) => match int_value {
+ IntValue::I128(n) => NumLiteral::Float(i128::from_ne_bytes(n) as f64, width),
+ IntValue::U128(n) => NumLiteral::Float(u128::from_ne_bytes(n) as f64, width),
+ },
+ },
+ Layout::Builtin(Builtin::Decimal) => {
+ let dec = match RocDec::from_str(num_str) {
+ Some(d) => d,
+ None => internal_error!(
+ "Invalid decimal for float literal = {}. This should be a type error!",
+ num_str
+ ),
+ };
+ NumLiteral::Decimal(dec.to_ne_bytes())
+ }
+ layout => internal_error!(
+ "Found a non-num layout where a number was expected: {:?}",
+ layout
+ ),
+ }
+}
diff --git a/crates/compiler/mono/src/ir/pattern.rs b/crates/compiler/mono/src/ir/pattern.rs
new file mode 100644
index 0000000000..4370a1aaf5
--- /dev/null
+++ b/crates/compiler/mono/src/ir/pattern.rs
@@ -0,0 +1,1815 @@
+use crate::ir::{substitute_in_exprs, Env, Expr, Procs, Stmt};
+use crate::layout::{
+ self, Builtin, InLayout, Layout, LayoutCache, LayoutInterner, LayoutProblem, TagIdIntType,
+ UnionLayout, WrappedVariant,
+};
+use bumpalo::collections::Vec;
+use roc_builtins::bitcode::{FloatWidth, IntWidth};
+use roc_collections::all::{BumpMap, BumpMapDefault};
+use roc_error_macros::internal_error;
+use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId};
+use roc_module::ident::{Lowercase, TagName};
+use roc_module::low_level::LowLevel;
+use roc_module::symbol::Symbol;
+use roc_problem::can::{RuntimeError, ShadowKind};
+use roc_types::subs::Variable;
+
+use super::literal::{make_num_literal, IntOrFloatValue};
+use super::{Call, CallType, Literal};
+
+/// A pattern, including possible problems (e.g. shadowing) so that
+/// codegen can generate a runtime error if this pattern is reached.
+#[derive(Clone, Debug, PartialEq)]
+pub enum Pattern<'a> {
+ Identifier(Symbol),
+ Underscore,
+ As(Box>, Symbol),
+ IntLiteral([u8; 16], IntWidth),
+ FloatLiteral(u64, FloatWidth),
+ DecimalLiteral([u8; 16]),
+ BitLiteral {
+ value: bool,
+ tag_name: TagName,
+ union: roc_exhaustive::Union,
+ },
+ EnumLiteral {
+ tag_id: u8,
+ tag_name: TagName,
+ union: roc_exhaustive::Union,
+ },
+ StrLiteral(Box),
+
+ RecordDestructure(Vec<'a, RecordDestruct<'a>>, &'a [InLayout<'a>]),
+ TupleDestructure(Vec<'a, TupleDestruct<'a>>, &'a [InLayout<'a>]),
+ NewtypeDestructure {
+ tag_name: TagName,
+ arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>,
+ },
+ AppliedTag {
+ tag_name: TagName,
+ tag_id: TagIdIntType,
+ arguments: Vec<'a, (Pattern<'a>, InLayout<'a>)>,
+ layout: UnionLayout<'a>,
+ union: roc_exhaustive::Union,
+ },
+ Voided {
+ tag_name: TagName,
+ },
+ OpaqueUnwrap {
+ opaque: Symbol,
+ argument: Box<(Pattern<'a>, InLayout<'a>)>,
+ },
+ List {
+ arity: ListArity,
+ element_layout: InLayout<'a>,
+ elements: Vec<'a, Pattern<'a>>,
+ },
+}
+
+impl<'a> Pattern<'a> {
+ /// This pattern contains a pattern match on Void (i.e. [], the empty tag union)
+ /// such branches are not reachable at runtime
+ pub fn is_voided(&self) -> bool {
+ let mut stack: std::vec::Vec<&Pattern> = vec![self];
+
+ while let Some(pattern) = stack.pop() {
+ match pattern {
+ Pattern::Identifier(_)
+ | Pattern::Underscore
+ | Pattern::IntLiteral(_, _)
+ | Pattern::FloatLiteral(_, _)
+ | Pattern::DecimalLiteral(_)
+ | Pattern::BitLiteral { .. }
+ | Pattern::EnumLiteral { .. }
+ | Pattern::StrLiteral(_) => { /* terminal */ }
+ Pattern::As(subpattern, _) => stack.push(subpattern),
+ Pattern::RecordDestructure(destructs, _) => {
+ for destruct in destructs {
+ match &destruct.typ {
+ DestructType::Required(_) => { /* do nothing */ }
+ DestructType::Guard(pattern) => {
+ stack.push(pattern);
+ }
+ }
+ }
+ }
+ Pattern::TupleDestructure(destructs, _) => {
+ for destruct in destructs {
+ stack.push(&destruct.pat);
+ }
+ }
+ Pattern::NewtypeDestructure { arguments, .. } => {
+ stack.extend(arguments.iter().map(|(t, _)| t))
+ }
+ Pattern::Voided { .. } => return true,
+ Pattern::AppliedTag { arguments, .. } => {
+ stack.extend(arguments.iter().map(|(t, _)| t))
+ }
+ Pattern::OpaqueUnwrap { argument, .. } => stack.push(&argument.0),
+ Pattern::List { elements, .. } => stack.extend(elements),
+ }
+ }
+
+ false
+ }
+
+ pub fn collect_symbols(
+ &self,
+ layout: InLayout<'a>,
+ ) -> impl Iterator
- )> + '_ {
+ PatternBindingIter::One(self, layout)
+ }
+}
+
+enum PatternBindingIter<'r, 'a> {
+ Done,
+ One(&'r Pattern<'a>, InLayout<'a>),
+ Stack(std::vec::Vec<(PatternBindingWork<'r, 'a>, InLayout<'a>)>),
+}
+
+enum PatternBindingWork<'r, 'a> {
+ Pat(&'r Pattern<'a>),
+ RecordDestruct(&'r DestructType<'a>),
+}
+
+impl<'r, 'a> Iterator for PatternBindingIter<'r, 'a> {
+ type Item = (Symbol, InLayout<'a>);
+
+ fn next(&mut self) -> Option {
+ use Pattern::*;
+ use PatternBindingIter::*;
+ use PatternBindingWork::*;
+ match self {
+ Done => None,
+ One(pattern, layout) => {
+ let layout = *layout;
+ match pattern {
+ Identifier(symbol) => {
+ *self = Done;
+ (*symbol, layout).into()
+ }
+ Underscore => None,
+ As(pat, symbol) => {
+ *self = One(pat, layout);
+ (*symbol, layout).into()
+ }
+ RecordDestructure(destructs, _) => {
+ let stack = destructs
+ .iter()
+ .map(|destruct| (RecordDestruct(&destruct.typ), destruct.layout))
+ .rev()
+ .collect();
+ *self = Stack(stack);
+ self.next()
+ }
+ TupleDestructure(destructs, _) => {
+ let stack = destructs
+ .iter()
+ .map(|destruct| (Pat(&destruct.pat), destruct.layout))
+ .rev()
+ .collect();
+ *self = Stack(stack);
+ self.next()
+ }
+ NewtypeDestructure { arguments, .. } | AppliedTag { arguments, .. } => {
+ let stack = arguments.iter().map(|(p, l)| (Pat(p), *l)).rev().collect();
+ *self = Stack(stack);
+ self.next()
+ }
+ OpaqueUnwrap { argument, .. } => {
+ *self = One(&argument.0, layout);
+ self.next()
+ }
+ List {
+ element_layout,
+ elements,
+ ..
+ } => {
+ let stack = elements
+ .iter()
+ .map(|p| (Pat(p), *element_layout))
+ .rev()
+ .collect();
+ *self = Stack(stack);
+ self.next()
+ }
+ IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | BitLiteral { .. }
+ | EnumLiteral { .. }
+ | StrLiteral(_)
+ | Voided { .. } => None,
+ }
+ }
+ Stack(stack) => {
+ while let Some((pat, layout)) = stack.pop() {
+ match pat {
+ Pat(pattern) => match pattern {
+ Identifier(symbol) => return (*symbol, layout).into(),
+ As(pat, symbol) => {
+ stack.push((Pat(pat), layout));
+ return (*symbol, layout).into();
+ }
+ RecordDestructure(destructs, _) => stack.extend(
+ destructs
+ .iter()
+ .map(|destruct| {
+ (RecordDestruct(&destruct.typ), destruct.layout)
+ })
+ .rev(),
+ ),
+ TupleDestructure(destructs, _) => stack.extend(
+ destructs
+ .iter()
+ .map(|destruct| (Pat(&destruct.pat), destruct.layout))
+ .rev(),
+ ),
+ NewtypeDestructure { arguments, .. } | AppliedTag { arguments, .. } => {
+ stack.extend(arguments.iter().map(|(p, l)| (Pat(p), *l)).rev())
+ }
+ OpaqueUnwrap { argument, .. } => {
+ stack.push((Pat(&argument.0), layout));
+ }
+ List {
+ element_layout,
+ elements,
+ ..
+ } => {
+ stack.extend(
+ elements.iter().map(|p| (Pat(p), *element_layout)).rev(),
+ );
+ }
+ IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | BitLiteral { .. }
+ | EnumLiteral { .. }
+ | Underscore
+ | StrLiteral(_)
+ | Voided { .. } => {}
+ },
+ PatternBindingWork::RecordDestruct(_) => todo!(),
+ }
+ }
+
+ *self = Done;
+ None
+ }
+ }
+ }
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct RecordDestruct<'a> {
+ pub label: Lowercase,
+ pub variable: Variable,
+ pub layout: InLayout<'a>,
+ pub typ: DestructType<'a>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct TupleDestruct<'a> {
+ pub index: usize,
+ pub variable: Variable,
+ pub layout: InLayout<'a>,
+ pub pat: Pattern<'a>,
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub enum DestructType<'a> {
+ Required(Symbol),
+ Guard(Pattern<'a>),
+}
+
+#[derive(Clone, Debug, PartialEq)]
+pub struct WhenBranch<'a> {
+ pub patterns: Vec<'a, Pattern<'a>>,
+ pub value: Expr<'a>,
+ pub guard: Option>,
+}
+
+#[allow(clippy::type_complexity)]
+pub fn from_can_pattern<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ can_pattern: &roc_can::pattern::Pattern,
+) -> Result<
+ (
+ Pattern<'a>,
+ Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
+ ),
+ RuntimeError,
+> {
+ let mut assignments = Vec::new_in(env.arena);
+ let pattern = from_can_pattern_help(env, procs, layout_cache, can_pattern, &mut assignments)?;
+
+ Ok((pattern, assignments))
+}
+
+fn from_can_pattern_help<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ can_pattern: &roc_can::pattern::Pattern,
+ assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
+) -> Result, RuntimeError> {
+ use roc_can::pattern::Pattern::*;
+
+ match can_pattern {
+ Underscore => Ok(Pattern::Underscore),
+ Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
+ As(subpattern, symbol) => {
+ let mono_subpattern =
+ from_can_pattern_help(env, procs, layout_cache, &subpattern.value, assignments)?;
+
+ Ok(Pattern::As(Box::new(mono_subpattern), *symbol))
+ }
+ AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
+ IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern(
+ env,
+ layout_cache,
+ *var,
+ int_str,
+ IntOrFloatValue::Int(*int),
+ )),
+ FloatLiteral(var, _, float_str, float, _bound) => Ok(make_num_literal_pattern(
+ env,
+ layout_cache,
+ *var,
+ float_str,
+ IntOrFloatValue::Float(*float),
+ )),
+ StrLiteral(v) => Ok(Pattern::StrLiteral(v.clone())),
+ SingleQuote(var, _, c, _) => {
+ let layout = layout_cache.from_var(env.arena, *var, env.subs);
+ match layout.map(|l| layout_cache.get_in(l)) {
+ Ok(Layout::Builtin(Builtin::Int(width))) => {
+ Ok(Pattern::IntLiteral((*c as i128).to_ne_bytes(), width))
+ }
+ o => internal_error!("an integer width was expected, but we found {:?}", o),
+ }
+ }
+ Shadowed(region, ident, _new_symbol) => Err(RuntimeError::Shadowing {
+ original_region: *region,
+ shadow: ident.clone(),
+ kind: ShadowKind::Variable,
+ }),
+ UnsupportedPattern(region) => Err(RuntimeError::UnsupportedPattern(*region)),
+ MalformedPattern(_problem, region) => {
+ // TODO preserve malformed problem information here?
+ Err(RuntimeError::UnsupportedPattern(*region))
+ }
+ OpaqueNotInScope(loc_ident) => {
+ // TODO(opaques) should be `RuntimeError::OpaqueNotDefined`
+ Err(RuntimeError::UnsupportedPattern(loc_ident.region))
+ }
+ NumLiteral(var, num_str, num, _bound) => Ok(make_num_literal_pattern(
+ env,
+ layout_cache,
+ *var,
+ num_str,
+ IntOrFloatValue::Int(*num),
+ )),
+
+ AppliedTag {
+ whole_var,
+ tag_name,
+ arguments,
+ ..
+ } => {
+ use crate::layout::UnionVariant::*;
+ use roc_exhaustive::Union;
+
+ let res_variant = {
+ let mut layout_env = layout::Env::from_components(
+ layout_cache,
+ env.subs,
+ env.arena,
+ env.target_info,
+ );
+ crate::layout::union_sorted_tags(&mut layout_env, *whole_var).map_err(Into::into)
+ };
+
+ let variant = match res_variant {
+ Ok(cached) => cached,
+ Err(LayoutProblem::UnresolvedTypeVar(_)) => {
+ return Err(RuntimeError::UnresolvedTypeVar)
+ }
+ Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType),
+ };
+
+ let result = match variant {
+ Never => unreachable!(
+ "there is no pattern of type `[]`, union var {:?}",
+ *whole_var
+ ),
+ Unit => Pattern::EnumLiteral {
+ tag_id: 0,
+ tag_name: tag_name.clone(),
+ union: Union {
+ render_as: RenderAs::Tag,
+ alternatives: vec![Ctor {
+ tag_id: TagId(0),
+ name: CtorName::Tag(tag_name.clone()),
+ arity: 0,
+ }],
+ },
+ },
+ BoolUnion { ttrue, ffalse } => {
+ let (ttrue, ffalse) = (ttrue.expect_tag(), ffalse.expect_tag());
+ Pattern::BitLiteral {
+ value: tag_name == &ttrue,
+ tag_name: tag_name.clone(),
+ union: Union {
+ render_as: RenderAs::Tag,
+ alternatives: vec![
+ Ctor {
+ tag_id: TagId(0),
+ name: CtorName::Tag(ffalse),
+ arity: 0,
+ },
+ Ctor {
+ tag_id: TagId(1),
+ name: CtorName::Tag(ttrue),
+ arity: 0,
+ },
+ ],
+ },
+ }
+ }
+ ByteUnion(tag_names) => {
+ let tag_id = tag_names
+ .iter()
+ .position(|key| tag_name == key.expect_tag_ref())
+ .expect("tag must be in its own type");
+
+ let mut ctors = std::vec::Vec::with_capacity(tag_names.len());
+ for (i, tag_name) in tag_names.into_iter().enumerate() {
+ ctors.push(Ctor {
+ tag_id: TagId(i as _),
+ name: CtorName::Tag(tag_name.expect_tag()),
+ arity: 0,
+ })
+ }
+
+ let union = roc_exhaustive::Union {
+ render_as: RenderAs::Tag,
+ alternatives: ctors,
+ };
+
+ Pattern::EnumLiteral {
+ tag_id: tag_id as u8,
+ tag_name: tag_name.clone(),
+ union,
+ }
+ }
+ Newtype {
+ arguments: field_layouts,
+ ..
+ } => {
+ let mut arguments = arguments.clone();
+
+ arguments.sort_by(|arg1, arg2| {
+ let size1 = layout_cache
+ .from_var(env.arena, arg1.0, env.subs)
+ .map(|x| layout_cache.interner.alignment_bytes(x))
+ .unwrap_or(0);
+
+ let size2 = layout_cache
+ .from_var(env.arena, arg2.0, env.subs)
+ .map(|x| layout_cache.interner.alignment_bytes(x))
+ .unwrap_or(0);
+
+ size2.cmp(&size1)
+ });
+
+ let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
+ for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) {
+ mono_args.push((
+ from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_pat.value,
+ assignments,
+ )?,
+ *layout,
+ ));
+ }
+
+ Pattern::NewtypeDestructure {
+ tag_name: tag_name.clone(),
+ arguments: mono_args,
+ }
+ }
+ NewtypeByVoid {
+ data_tag_arguments,
+ data_tag_name,
+ ..
+ } => {
+ let data_tag_name = data_tag_name.expect_tag();
+
+ if tag_name != &data_tag_name {
+ // this tag is not represented at runtime
+ Pattern::Voided {
+ tag_name: tag_name.clone(),
+ }
+ } else {
+ let mut arguments = arguments.clone();
+
+ arguments.sort_by(|arg1, arg2| {
+ let size1 = layout_cache
+ .from_var(env.arena, arg1.0, env.subs)
+ .map(|x| layout_cache.interner.alignment_bytes(x))
+ .unwrap_or(0);
+
+ let size2 = layout_cache
+ .from_var(env.arena, arg2.0, env.subs)
+ .map(|x| layout_cache.interner.alignment_bytes(x))
+ .unwrap_or(0);
+
+ size2.cmp(&size1)
+ });
+
+ let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
+ let it = arguments.iter().zip(data_tag_arguments.iter());
+ for ((_, loc_pat), layout) in it {
+ mono_args.push((
+ from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_pat.value,
+ assignments,
+ )?,
+ *layout,
+ ));
+ }
+
+ Pattern::NewtypeDestructure {
+ tag_name: tag_name.clone(),
+ arguments: mono_args,
+ }
+ }
+ }
+
+ Wrapped(variant) => {
+ let (tag_id, argument_layouts) = variant.tag_name_to_id(tag_name);
+ let number_of_tags = variant.number_of_tags();
+ let mut ctors = std::vec::Vec::with_capacity(number_of_tags);
+
+ let arguments = {
+ let mut temp = arguments.clone();
+
+ temp.sort_by(|arg1, arg2| {
+ let layout1 =
+ layout_cache.from_var(env.arena, arg1.0, env.subs).unwrap();
+ let layout2 =
+ layout_cache.from_var(env.arena, arg2.0, env.subs).unwrap();
+
+ let size1 = layout_cache.interner.alignment_bytes(layout1);
+ let size2 = layout_cache.interner.alignment_bytes(layout2);
+
+ size2.cmp(&size1)
+ });
+
+ temp
+ };
+
+ // we must derive the union layout from the whole_var, building it up
+ // from `layouts` would unroll recursive tag unions, and that leads to
+ // problems down the line because we hash layouts and an unrolled
+ // version is not the same as the minimal version.
+ let whole_var_layout = layout_cache.from_var(env.arena, *whole_var, env.subs);
+ let layout =
+ match whole_var_layout.map(|l| layout_cache.interner.chase_recursive(l)) {
+ Ok(Layout::Union(ul)) => ul,
+ _ => internal_error!(),
+ };
+
+ use WrappedVariant::*;
+ match variant {
+ NonRecursive {
+ sorted_tag_layouts: ref tags,
+ } => {
+ debug_assert!(tags.len() > 1);
+
+ for (i, (tag_name, args)) in tags.iter().enumerate() {
+ ctors.push(Ctor {
+ tag_id: TagId(i as _),
+ name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
+ arity: args.len(),
+ })
+ }
+
+ let union = roc_exhaustive::Union {
+ render_as: RenderAs::Tag,
+ alternatives: ctors,
+ };
+
+ let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
+
+ debug_assert_eq!(
+ arguments.len(),
+ argument_layouts.len(),
+ "The {:?} tag got {} arguments, but its layout expects {}!",
+ tag_name,
+ arguments.len(),
+ argument_layouts.len(),
+ );
+ let it = argument_layouts.iter();
+
+ for ((_, loc_pat), layout) in arguments.iter().zip(it) {
+ mono_args.push((
+ from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_pat.value,
+ assignments,
+ )?,
+ *layout,
+ ));
+ }
+
+ Pattern::AppliedTag {
+ tag_name: tag_name.clone(),
+ tag_id: tag_id as _,
+ arguments: mono_args,
+ union,
+ layout,
+ }
+ }
+
+ Recursive {
+ sorted_tag_layouts: ref tags,
+ } => {
+ debug_assert!(tags.len() > 1);
+
+ for (i, (tag_name, args)) in tags.iter().enumerate() {
+ ctors.push(Ctor {
+ tag_id: TagId(i as _),
+ name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
+ arity: args.len(),
+ })
+ }
+
+ let union = roc_exhaustive::Union {
+ render_as: RenderAs::Tag,
+ alternatives: ctors,
+ };
+
+ let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
+
+ debug_assert_eq!(arguments.len(), argument_layouts.len());
+ let it = argument_layouts.iter();
+
+ for ((_, loc_pat), layout) in arguments.iter().zip(it) {
+ mono_args.push((
+ from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_pat.value,
+ assignments,
+ )?,
+ *layout,
+ ));
+ }
+
+ Pattern::AppliedTag {
+ tag_name: tag_name.clone(),
+ tag_id: tag_id as _,
+ arguments: mono_args,
+ union,
+ layout,
+ }
+ }
+
+ NonNullableUnwrapped {
+ tag_name: w_tag_name,
+ fields,
+ } => {
+ debug_assert_eq!(w_tag_name.expect_tag_ref(), tag_name);
+
+ ctors.push(Ctor {
+ tag_id: TagId(0),
+ name: CtorName::Tag(tag_name.clone()),
+ arity: fields.len(),
+ });
+
+ let union = roc_exhaustive::Union {
+ render_as: RenderAs::Tag,
+ alternatives: ctors,
+ };
+
+ let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
+
+ debug_assert_eq!(arguments.len(), argument_layouts.len());
+ let it = argument_layouts.iter();
+
+ for ((_, loc_pat), layout) in arguments.iter().zip(it) {
+ mono_args.push((
+ from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_pat.value,
+ assignments,
+ )?,
+ *layout,
+ ));
+ }
+
+ Pattern::AppliedTag {
+ tag_name: tag_name.clone(),
+ tag_id: tag_id as _,
+ arguments: mono_args,
+ union,
+ layout,
+ }
+ }
+
+ NullableWrapped {
+ sorted_tag_layouts: ref non_nulled_tags,
+ nullable_id,
+ nullable_name,
+ } => {
+ for id in 0..(non_nulled_tags.len() + 1) {
+ if id == nullable_id as usize {
+ ctors.push(Ctor {
+ tag_id: TagId(id as _),
+ name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
+ arity: 0,
+ });
+ } else {
+ let i = if id < nullable_id.into() { id } else { id - 1 };
+ let (tag_name, args) = &non_nulled_tags[i];
+ ctors.push(Ctor {
+ tag_id: TagId(i as _),
+ name: CtorName::Tag(tag_name.expect_tag_ref().clone()),
+ arity: args.len(),
+ });
+ }
+ }
+
+ let union = roc_exhaustive::Union {
+ render_as: RenderAs::Tag,
+ alternatives: ctors,
+ };
+
+ let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
+
+ let it = if tag_name == nullable_name.expect_tag_ref() {
+ [].iter()
+ } else {
+ argument_layouts.iter()
+ };
+
+ for ((_, loc_pat), layout) in arguments.iter().zip(it) {
+ mono_args.push((
+ from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_pat.value,
+ assignments,
+ )?,
+ *layout,
+ ));
+ }
+
+ Pattern::AppliedTag {
+ tag_name: tag_name.clone(),
+ tag_id: tag_id as _,
+ arguments: mono_args,
+ union,
+ layout,
+ }
+ }
+
+ NullableUnwrapped {
+ other_fields,
+ nullable_id,
+ nullable_name,
+ other_name: _,
+ } => {
+ debug_assert!(!other_fields.is_empty());
+
+ ctors.push(Ctor {
+ tag_id: TagId(nullable_id as _),
+ name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
+ arity: 0,
+ });
+
+ ctors.push(Ctor {
+ tag_id: TagId(!nullable_id as _),
+ name: CtorName::Tag(nullable_name.expect_tag_ref().clone()),
+ arity: other_fields.len(),
+ });
+
+ let union = roc_exhaustive::Union {
+ render_as: RenderAs::Tag,
+ alternatives: ctors,
+ };
+
+ let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
+
+ let it = if tag_name == nullable_name.expect_tag_ref() {
+ [].iter()
+ } else {
+ argument_layouts.iter()
+ };
+
+ for ((_, loc_pat), layout) in arguments.iter().zip(it) {
+ mono_args.push((
+ from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_pat.value,
+ assignments,
+ )?,
+ *layout,
+ ));
+ }
+
+ Pattern::AppliedTag {
+ tag_name: tag_name.clone(),
+ tag_id: tag_id as _,
+ arguments: mono_args,
+ union,
+ layout,
+ }
+ }
+ }
+ }
+ };
+
+ Ok(result)
+ }
+
+ UnwrappedOpaque {
+ opaque, argument, ..
+ } => {
+ let (arg_var, loc_arg_pattern) = &(**argument);
+ let arg_layout = layout_cache
+ .from_var(env.arena, *arg_var, env.subs)
+ .unwrap();
+ let mono_arg_pattern = from_can_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &loc_arg_pattern.value,
+ assignments,
+ )?;
+ Ok(Pattern::OpaqueUnwrap {
+ opaque: *opaque,
+ argument: Box::new((mono_arg_pattern, arg_layout)),
+ })
+ }
+
+ TupleDestructure {
+ whole_var,
+ destructs,
+ ..
+ } => {
+ // sorted fields based on the type
+ let sorted_elems = {
+ let mut layout_env = layout::Env::from_components(
+ layout_cache,
+ env.subs,
+ env.arena,
+ env.target_info,
+ );
+ crate::layout::sort_tuple_elems(&mut layout_env, *whole_var)
+ .map_err(RuntimeError::from)?
+ };
+
+ // sorted fields based on the destruct
+ let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
+ let mut destructs_by_index = Vec::with_capacity_in(destructs.len(), env.arena);
+ destructs_by_index.extend(destructs.iter().map(Some));
+
+ let mut elem_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena);
+
+ for (index, variable, res_layout) in sorted_elems.into_iter() {
+ if index < destructs.len() {
+ // this elem is destructured by the pattern
+ mono_destructs.push(from_can_tuple_destruct(
+ env,
+ procs,
+ layout_cache,
+ &destructs[index].value,
+ res_layout,
+ assignments,
+ )?);
+ } else {
+ // this elem is not destructured by the pattern
+ // put in an underscore
+ mono_destructs.push(TupleDestruct {
+ index,
+ variable,
+ layout: res_layout,
+ pat: Pattern::Underscore,
+ });
+ }
+
+ // the layout of this field is part of the layout of the record
+ elem_layouts.push(res_layout);
+ }
+
+ Ok(Pattern::TupleDestructure(
+ mono_destructs,
+ elem_layouts.into_bump_slice(),
+ ))
+ }
+
+ RecordDestructure {
+ whole_var,
+ destructs,
+ ..
+ } => {
+ // sorted fields based on the type
+ let sorted_fields = {
+ let mut layout_env = layout::Env::from_components(
+ layout_cache,
+ env.subs,
+ env.arena,
+ env.target_info,
+ );
+ crate::layout::sort_record_fields(&mut layout_env, *whole_var)
+ .map_err(RuntimeError::from)?
+ };
+
+ // sorted fields based on the destruct
+ let mut mono_destructs = Vec::with_capacity_in(destructs.len(), env.arena);
+ let mut destructs_by_label = BumpMap::with_capacity_in(destructs.len(), env.arena);
+ destructs_by_label.extend(destructs.iter().map(|x| (&x.value.label, x)));
+
+ let mut field_layouts = Vec::with_capacity_in(sorted_fields.len(), env.arena);
+
+ // next we step through both sequences of fields. The outer loop is the sequence based
+ // on the type, since not all fields need to actually be destructured in the source
+ // language.
+ //
+ // However in mono patterns, we do destruct all patterns (but use Underscore) when
+ // in the source the field is not matche in the source language.
+ //
+ // Optional fields somewhat complicate the matter here
+
+ for (label, variable, res_layout) in sorted_fields.into_iter() {
+ match res_layout {
+ Ok(field_layout) => {
+ // the field is non-optional according to the type
+
+ match destructs_by_label.remove(&label) {
+ Some(destruct) => {
+ // this field is destructured by the pattern
+ mono_destructs.push(from_can_record_destruct(
+ env,
+ procs,
+ layout_cache,
+ &destruct.value,
+ field_layout,
+ assignments,
+ )?);
+ }
+ None => {
+ // this field is not destructured by the pattern
+ // put in an underscore
+ mono_destructs.push(RecordDestruct {
+ label: label.clone(),
+ variable,
+ layout: field_layout,
+ typ: DestructType::Guard(Pattern::Underscore),
+ });
+ }
+ }
+
+ // the layout of this field is part of the layout of the record
+ field_layouts.push(field_layout);
+ }
+ Err(field_layout) => {
+ // the field is optional according to the type
+ match destructs_by_label.remove(&label) {
+ Some(destruct) => {
+ // this field is destructured by the pattern
+ match &destruct.value.typ {
+ roc_can::pattern::DestructType::Optional(_, loc_expr) => {
+ // if we reach this stage, the optional field is not present
+ // so we push the default assignment into the branch
+ assignments.push((
+ destruct.value.symbol,
+ variable,
+ loc_expr.value.clone(),
+ ));
+ }
+ _ => unreachable!(
+ "only optional destructs can be optional fields"
+ ),
+ };
+ }
+ None => {
+ // this field is not destructured by the pattern
+ // put in an underscore
+ mono_destructs.push(RecordDestruct {
+ label: label.clone(),
+ variable,
+ layout: field_layout,
+ typ: DestructType::Guard(Pattern::Underscore),
+ });
+ }
+ }
+ }
+ }
+ }
+
+ for (_, destruct) in destructs_by_label.drain() {
+ // this destruct is not in the type, but is in the pattern
+ // it must be an optional field, and we will use the default
+ match &destruct.value.typ {
+ roc_can::pattern::DestructType::Optional(field_var, loc_expr) => {
+ assignments.push((
+ destruct.value.symbol,
+ // destruct.value.var,
+ *field_var,
+ loc_expr.value.clone(),
+ ));
+ }
+ _ => unreachable!("only optional destructs can be optional fields"),
+ }
+ }
+
+ Ok(Pattern::RecordDestructure(
+ mono_destructs,
+ field_layouts.into_bump_slice(),
+ ))
+ }
+
+ List {
+ list_var: _,
+ elem_var,
+ patterns,
+ } => {
+ let element_layout = match layout_cache.from_var(env.arena, *elem_var, env.subs) {
+ Ok(lay) => lay,
+ Err(LayoutProblem::UnresolvedTypeVar(_)) => {
+ return Err(RuntimeError::UnresolvedTypeVar)
+ }
+ Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType),
+ };
+
+ let arity = patterns.arity();
+
+ let mut mono_patterns = Vec::with_capacity_in(patterns.patterns.len(), env.arena);
+ for loc_pat in patterns.patterns.iter() {
+ let mono_pat =
+ from_can_pattern_help(env, procs, layout_cache, &loc_pat.value, assignments)?;
+ mono_patterns.push(mono_pat);
+ }
+
+ Ok(Pattern::List {
+ arity,
+ element_layout,
+ elements: mono_patterns,
+ })
+ }
+ }
+}
+
+fn make_num_literal_pattern<'a>(
+ env: &mut Env<'a, '_>,
+ layout_cache: &mut LayoutCache<'a>,
+ variable: Variable,
+ num_str: &str,
+ num_value: IntOrFloatValue,
+) -> Pattern<'a> {
+ let layout = layout_cache
+ .from_var(env.arena, variable, env.subs)
+ .unwrap();
+ let literal = make_num_literal(&layout_cache.interner, layout, num_str, num_value);
+ literal.to_pattern()
+}
+
+fn from_can_record_destruct<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ can_rd: &roc_can::pattern::RecordDestruct,
+ field_layout: InLayout<'a>,
+ assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
+) -> Result, RuntimeError> {
+ Ok(RecordDestruct {
+ label: can_rd.label.clone(),
+ variable: can_rd.var,
+ layout: field_layout,
+ typ: match &can_rd.typ {
+ roc_can::pattern::DestructType::Required => DestructType::Required(can_rd.symbol),
+ roc_can::pattern::DestructType::Optional(_, _) => {
+ // if we reach this stage, the optional field is present
+ DestructType::Required(can_rd.symbol)
+ }
+ roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard(
+ from_can_pattern_help(env, procs, layout_cache, &loc_pattern.value, assignments)?,
+ ),
+ },
+ })
+}
+
+fn from_can_tuple_destruct<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ can_rd: &roc_can::pattern::TupleDestruct,
+ field_layout: InLayout<'a>,
+ assignments: &mut Vec<'a, (Symbol, Variable, roc_can::expr::Expr)>,
+) -> Result, RuntimeError> {
+ Ok(TupleDestruct {
+ index: can_rd.destruct_index,
+ variable: can_rd.var,
+ layout: field_layout,
+ pat: from_can_pattern_help(env, procs, layout_cache, &can_rd.typ.1.value, assignments)?,
+ })
+}
+
+#[allow(clippy::too_many_arguments)]
+pub fn store_pattern<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ can_pat: &Pattern<'a>,
+ outer_symbol: Symbol,
+ stmt: Stmt<'a>,
+) -> Stmt<'a> {
+ match store_pattern_help(env, procs, layout_cache, can_pat, outer_symbol, stmt) {
+ StorePattern::Productive(new) => new,
+ StorePattern::NotProductive(new) => new,
+ }
+}
+
+enum StorePattern<'a> {
+ /// we bound new symbols
+ Productive(Stmt<'a>),
+ /// no new symbols were bound in this pattern
+ NotProductive(Stmt<'a>),
+}
+
+/// It is crucial for correct RC insertion that we don't create dead variables!
+#[allow(clippy::too_many_arguments)]
+fn store_pattern_help<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ can_pat: &Pattern<'a>,
+ outer_symbol: Symbol,
+ mut stmt: Stmt<'a>,
+) -> StorePattern<'a> {
+ use Pattern::*;
+
+ match can_pat {
+ Identifier(symbol) => {
+ substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
+ }
+ Underscore => {
+ // do nothing
+ return StorePattern::NotProductive(stmt);
+ }
+ As(subpattern, symbol) => {
+ let stored_subpattern =
+ store_pattern_help(env, procs, layout_cache, subpattern, outer_symbol, stmt);
+
+ let mut stmt = match stored_subpattern {
+ StorePattern::Productive(stmt) => stmt,
+ StorePattern::NotProductive(stmt) => stmt,
+ };
+
+ substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
+
+ return StorePattern::Productive(stmt);
+ }
+ IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | EnumLiteral { .. }
+ | BitLiteral { .. }
+ | StrLiteral(_) => {
+ return StorePattern::NotProductive(stmt);
+ }
+ NewtypeDestructure { arguments, .. } => match arguments.as_slice() {
+ [(pattern, _layout)] => {
+ return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
+ }
+ _ => {
+ let mut fields = Vec::with_capacity_in(arguments.len(), env.arena);
+ fields.extend(arguments.iter().map(|x| x.1));
+
+ let layout =
+ layout_cache.put_in(Layout::struct_no_name_order(fields.into_bump_slice()));
+
+ return store_newtype_pattern(
+ env,
+ procs,
+ layout_cache,
+ outer_symbol,
+ layout,
+ arguments,
+ stmt,
+ );
+ }
+ },
+ AppliedTag {
+ arguments,
+ layout,
+ tag_id,
+ ..
+ } => {
+ return store_tag_pattern(
+ env,
+ procs,
+ layout_cache,
+ outer_symbol,
+ *layout,
+ arguments,
+ *tag_id,
+ stmt,
+ );
+ }
+
+ List {
+ arity,
+ element_layout,
+ elements,
+ } => {
+ return store_list_pattern(
+ env,
+ procs,
+ layout_cache,
+ outer_symbol,
+ *arity,
+ *element_layout,
+ elements,
+ stmt,
+ )
+ }
+
+ Voided { .. } => {
+ return StorePattern::NotProductive(stmt);
+ }
+
+ OpaqueUnwrap { argument, .. } => {
+ let (pattern, _layout) = &**argument;
+ return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
+ }
+
+ RecordDestructure(destructs, [_single_field]) => {
+ for destruct in destructs {
+ match &destruct.typ {
+ DestructType::Required(symbol) => {
+ substitute_in_exprs(env.arena, &mut stmt, *symbol, outer_symbol);
+ }
+ DestructType::Guard(guard_pattern) => {
+ return store_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ guard_pattern,
+ outer_symbol,
+ stmt,
+ );
+ }
+ }
+ }
+ }
+ RecordDestructure(destructs, sorted_fields) => {
+ let mut is_productive = false;
+ for (index, destruct) in destructs.iter().enumerate().rev() {
+ match store_record_destruct(
+ env,
+ procs,
+ layout_cache,
+ destruct,
+ index as u64,
+ outer_symbol,
+ sorted_fields,
+ stmt,
+ ) {
+ StorePattern::Productive(new) => {
+ is_productive = true;
+ stmt = new;
+ }
+ StorePattern::NotProductive(new) => {
+ stmt = new;
+ }
+ }
+ }
+
+ if !is_productive {
+ return StorePattern::NotProductive(stmt);
+ }
+ }
+
+ TupleDestructure(destructs, [_single_field]) => {
+ if let Some(destruct) = destructs.first() {
+ return store_pattern_help(
+ env,
+ procs,
+ layout_cache,
+ &destruct.pat,
+ outer_symbol,
+ stmt,
+ );
+ }
+ }
+ TupleDestructure(destructs, sorted_fields) => {
+ let mut is_productive = false;
+ for (index, destruct) in destructs.iter().enumerate().rev() {
+ match store_tuple_destruct(
+ env,
+ procs,
+ layout_cache,
+ destruct,
+ index as u64,
+ outer_symbol,
+ sorted_fields,
+ stmt,
+ ) {
+ StorePattern::Productive(new) => {
+ is_productive = true;
+ stmt = new;
+ }
+ StorePattern::NotProductive(new) => {
+ stmt = new;
+ }
+ }
+ }
+
+ if !is_productive {
+ return StorePattern::NotProductive(stmt);
+ }
+ }
+ }
+
+ StorePattern::Productive(stmt)
+}
+
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct ListIndex(
+ /// Positive if we should index from the head, negative if we should index from the tail
+ /// 0 is lst[0]
+ /// -1 is lst[List.len lst - 1]
+ i64,
+);
+
+impl ListIndex {
+ pub fn from_pattern_index(index: usize, arity: ListArity) -> Self {
+ match arity {
+ ListArity::Exact(_) => Self(index as _),
+ ListArity::Slice(head, tail) => {
+ if index < head {
+ Self(index as _)
+ } else {
+ // Slice(head=2, tail=5)
+ //
+ // s t ... w y z x q
+ // 0 1 2 3 4 5 6 index
+ // 0 1 2 3 4 (index - head)
+ // 5 4 3 2 1 (tail - (index - head))
+ Self(-((tail - (index - head)) as i64))
+ }
+ }
+ }
+ }
+}
+
+pub(crate) type Store<'a> = (Symbol, InLayout<'a>, Expr<'a>);
+
+/// Builds the list index we should index into
+#[must_use]
+pub(crate) fn build_list_index_probe<'a>(
+ env: &mut Env<'a, '_>,
+ list_sym: Symbol,
+ list_index: &ListIndex,
+) -> (Symbol, impl DoubleEndedIterator
- >) {
+ let usize_layout = Layout::usize(env.target_info);
+
+ let list_index = list_index.0;
+ let index_sym = env.unique_symbol();
+
+ let (opt_len_store, opt_offset_store, index_store) = if list_index >= 0 {
+ let index_expr = Expr::Literal(Literal::Int((list_index as i128).to_ne_bytes()));
+
+ let index_store = (index_sym, usize_layout, index_expr);
+
+ (None, None, index_store)
+ } else {
+ let len_sym = env.unique_symbol();
+ let len_expr = Expr::Call(Call {
+ call_type: CallType::LowLevel {
+ op: LowLevel::ListLen,
+ update_mode: env.next_update_mode_id(),
+ },
+ arguments: env.arena.alloc([list_sym]),
+ });
+
+ let offset = list_index.abs();
+ let offset_sym = env.unique_symbol();
+ let offset_expr = Expr::Literal(Literal::Int((offset as i128).to_ne_bytes()));
+
+ let index_expr = Expr::Call(Call {
+ call_type: CallType::LowLevel {
+ op: LowLevel::NumSub,
+ update_mode: env.next_update_mode_id(),
+ },
+ arguments: env.arena.alloc([len_sym, offset_sym]),
+ });
+
+ let len_store = (len_sym, usize_layout, len_expr);
+ let offset_store = (offset_sym, usize_layout, offset_expr);
+ let index_store = (index_sym, usize_layout, index_expr);
+
+ (Some(len_store), Some(offset_store), index_store)
+ };
+
+ let stores = (opt_len_store.into_iter())
+ .chain(opt_offset_store)
+ .chain([index_store]);
+
+ (index_sym, stores)
+}
+
+#[allow(clippy::too_many_arguments)]
+fn store_list_pattern<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ list_sym: Symbol,
+ list_arity: ListArity,
+ element_layout: InLayout<'a>,
+ elements: &[Pattern<'a>],
+ mut stmt: Stmt<'a>,
+) -> StorePattern<'a> {
+ use Pattern::*;
+
+ let mut is_productive = false;
+
+ for (index, element) in elements.iter().enumerate().rev() {
+ let compute_element_load = |env: &mut Env<'a, '_>| {
+ let list_index = ListIndex::from_pattern_index(index, list_arity);
+
+ let (index_sym, needed_stores) = build_list_index_probe(env, list_sym, &list_index);
+
+ let load = Expr::Call(Call {
+ call_type: CallType::LowLevel {
+ op: LowLevel::ListGetUnsafe,
+ update_mode: env.next_update_mode_id(),
+ },
+ arguments: env.arena.alloc([list_sym, index_sym]),
+ });
+
+ (load, needed_stores)
+ };
+
+ let (store_loaded, needed_stores) = match element {
+ Identifier(symbol) => {
+ let (load, needed_stores) = compute_element_load(env);
+
+ // store immediately in the given symbol
+ (
+ Stmt::Let(*symbol, load, element_layout, env.arena.alloc(stmt)),
+ needed_stores,
+ )
+ }
+ Underscore
+ | IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | EnumLiteral { .. }
+ | BitLiteral { .. }
+ | StrLiteral(_) => {
+ // ignore
+ continue;
+ }
+ _ => {
+ // store the field in a symbol, and continue matching on it
+ let symbol = env.unique_symbol();
+
+ // first recurse, continuing to unpack symbol
+ match store_pattern_help(env, procs, layout_cache, element, symbol, stmt) {
+ StorePattern::Productive(new) => {
+ stmt = new;
+ let (load, needed_stores) = compute_element_load(env);
+
+ // only if we bind one of its (sub)fields to a used name should we
+ // extract the field
+ (
+ Stmt::Let(symbol, load, element_layout, env.arena.alloc(stmt)),
+ needed_stores,
+ )
+ }
+ StorePattern::NotProductive(new) => {
+ // do nothing
+ stmt = new;
+ continue;
+ }
+ }
+ }
+ };
+
+ is_productive = true;
+
+ stmt = store_loaded;
+ for (sym, lay, expr) in needed_stores.rev() {
+ stmt = Stmt::Let(sym, expr, lay, env.arena.alloc(stmt));
+ }
+ }
+
+ if is_productive {
+ StorePattern::Productive(stmt)
+ } else {
+ StorePattern::NotProductive(stmt)
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+fn store_tag_pattern<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ structure: Symbol,
+ union_layout: UnionLayout<'a>,
+ arguments: &[(Pattern<'a>, InLayout<'a>)],
+ tag_id: TagIdIntType,
+ mut stmt: Stmt<'a>,
+) -> StorePattern<'a> {
+ use Pattern::*;
+
+ let mut is_productive = false;
+
+ for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() {
+ let mut arg_layout = *arg_layout;
+
+ if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) {
+ // TODO(recursive-layouts): fix after disjoint rec ptrs
+ arg_layout = layout_cache.put_in(Layout::Union(union_layout));
+ }
+
+ let load = Expr::UnionAtIndex {
+ index: index as u64,
+ structure,
+ tag_id,
+ union_layout,
+ };
+
+ match argument {
+ Identifier(symbol) => {
+ // store immediately in the given symbol
+ stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt));
+ is_productive = true;
+ }
+ Underscore => {
+ // ignore
+ }
+ IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | EnumLiteral { .. }
+ | BitLiteral { .. }
+ | StrLiteral(_) => {}
+ _ => {
+ // store the field in a symbol, and continue matching on it
+ let symbol = env.unique_symbol();
+
+ // first recurse, continuing to unpack symbol
+ match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
+ StorePattern::Productive(new) => {
+ is_productive = true;
+ stmt = new;
+ // only if we bind one of its (sub)fields to a used name should we
+ // extract the field
+ stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt));
+ }
+ StorePattern::NotProductive(new) => {
+ // do nothing
+ stmt = new;
+ }
+ }
+ }
+ }
+ }
+
+ if is_productive {
+ StorePattern::Productive(stmt)
+ } else {
+ StorePattern::NotProductive(stmt)
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+fn store_newtype_pattern<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ structure: Symbol,
+ layout: InLayout<'a>,
+ arguments: &[(Pattern<'a>, InLayout<'a>)],
+ mut stmt: Stmt<'a>,
+) -> StorePattern<'a> {
+ use Pattern::*;
+
+ let mut arg_layouts = Vec::with_capacity_in(arguments.len(), env.arena);
+ let mut is_productive = false;
+
+ for (_, layout) in arguments {
+ arg_layouts.push(*layout);
+ }
+
+ for (index, (argument, arg_layout)) in arguments.iter().enumerate().rev() {
+ let mut arg_layout = *arg_layout;
+
+ if let Layout::RecursivePointer(_) = layout_cache.get_in(arg_layout) {
+ arg_layout = layout;
+ }
+
+ let load = Expr::StructAtIndex {
+ index: index as u64,
+ field_layouts: arg_layouts.clone().into_bump_slice(),
+ structure,
+ };
+
+ match argument {
+ Identifier(symbol) => {
+ stmt = Stmt::Let(*symbol, load, arg_layout, env.arena.alloc(stmt));
+ is_productive = true;
+ }
+ Underscore => {
+ // ignore
+ }
+ IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | EnumLiteral { .. }
+ | BitLiteral { .. }
+ | StrLiteral(_) => {}
+ _ => {
+ // store the field in a symbol, and continue matching on it
+ let symbol = env.unique_symbol();
+
+ // first recurse, continuing to unpack symbol
+ match store_pattern_help(env, procs, layout_cache, argument, symbol, stmt) {
+ StorePattern::Productive(new) => {
+ is_productive = true;
+ stmt = new;
+ // only if we bind one of its (sub)fields to a used name should we
+ // extract the field
+ stmt = Stmt::Let(symbol, load, arg_layout, env.arena.alloc(stmt));
+ }
+ StorePattern::NotProductive(new) => {
+ // do nothing
+ stmt = new;
+ }
+ }
+ }
+ }
+ }
+
+ if is_productive {
+ StorePattern::Productive(stmt)
+ } else {
+ StorePattern::NotProductive(stmt)
+ }
+}
+
+#[allow(clippy::too_many_arguments)]
+fn store_tuple_destruct<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ destruct: &TupleDestruct<'a>,
+ index: u64,
+ outer_symbol: Symbol,
+ sorted_fields: &'a [InLayout<'a>],
+ mut stmt: Stmt<'a>,
+) -> StorePattern<'a> {
+ use Pattern::*;
+
+ let load = Expr::StructAtIndex {
+ index,
+ field_layouts: sorted_fields,
+ structure: outer_symbol,
+ };
+
+ match &destruct.pat {
+ Identifier(symbol) => {
+ stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt));
+ }
+ Underscore => {
+ // important that this is special-cased to do nothing: mono record patterns will extract all the
+ // fields, but those not bound in the source code are guarded with the underscore
+ // pattern. So given some record `{ x : a, y : b }`, a match
+ //
+ // { x } -> ...
+ //
+ // is actually
+ //
+ // { x, y: _ } -> ...
+ //
+ // internally. But `y` is never used, so we must make sure it't not stored/loaded.
+ //
+ // This also happens with tuples, so when matching a tuple `(a, b, c)`,
+ // a pattern like `(x, y)` will be internally rewritten to `(x, y, _)`.
+ return StorePattern::NotProductive(stmt);
+ }
+ IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | EnumLiteral { .. }
+ | BitLiteral { .. }
+ | StrLiteral(_) => {
+ return StorePattern::NotProductive(stmt);
+ }
+
+ _ => {
+ let symbol = env.unique_symbol();
+
+ match store_pattern_help(env, procs, layout_cache, &destruct.pat, symbol, stmt) {
+ StorePattern::Productive(new) => {
+ stmt = new;
+ stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
+ }
+ StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
+ }
+ }
+ }
+
+ StorePattern::Productive(stmt)
+}
+
+#[allow(clippy::too_many_arguments)]
+fn store_record_destruct<'a>(
+ env: &mut Env<'a, '_>,
+ procs: &mut Procs<'a>,
+ layout_cache: &mut LayoutCache<'a>,
+ destruct: &RecordDestruct<'a>,
+ index: u64,
+ outer_symbol: Symbol,
+ sorted_fields: &'a [InLayout<'a>],
+ mut stmt: Stmt<'a>,
+) -> StorePattern<'a> {
+ use Pattern::*;
+
+ let load = Expr::StructAtIndex {
+ index,
+ field_layouts: sorted_fields,
+ structure: outer_symbol,
+ };
+
+ match &destruct.typ {
+ DestructType::Required(symbol) => {
+ stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt));
+ }
+ DestructType::Guard(guard_pattern) => match &guard_pattern {
+ Identifier(symbol) => {
+ stmt = Stmt::Let(*symbol, load, destruct.layout, env.arena.alloc(stmt));
+ }
+ Underscore => {
+ // important that this is special-cased to do nothing: mono record patterns will extract all the
+ // fields, but those not bound in the source code are guarded with the underscore
+ // pattern. So given some record `{ x : a, y : b }`, a match
+ //
+ // { x } -> ...
+ //
+ // is actually
+ //
+ // { x, y: _ } -> ...
+ //
+ // internally. But `y` is never used, so we must make sure it't not stored/loaded.
+ return StorePattern::NotProductive(stmt);
+ }
+ IntLiteral(_, _)
+ | FloatLiteral(_, _)
+ | DecimalLiteral(_)
+ | EnumLiteral { .. }
+ | BitLiteral { .. }
+ | StrLiteral(_) => {
+ return StorePattern::NotProductive(stmt);
+ }
+
+ _ => {
+ let symbol = env.unique_symbol();
+
+ match store_pattern_help(env, procs, layout_cache, guard_pattern, symbol, stmt) {
+ StorePattern::Productive(new) => {
+ stmt = new;
+ stmt = Stmt::Let(symbol, load, destruct.layout, env.arena.alloc(stmt));
+ }
+ StorePattern::NotProductive(stmt) => return StorePattern::NotProductive(stmt),
+ }
+ }
+ },
+ }
+
+ StorePattern::Productive(stmt)
+}
diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs
index a311aaf388..871f93b3b4 100644
--- a/crates/compiler/mono/src/layout.rs
+++ b/crates/compiler/mono/src/layout.rs
@@ -1,9 +1,11 @@
use crate::ir::Parens;
+use crate::layout::intern::NeedsRecursionPointerFixup;
use bitvec::vec::BitVec;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{default_hasher, FnvMap, MutMap};
+use roc_collections::VecSet;
use roc_error_macros::{internal_error, todo_abilities};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, Symbol};
@@ -12,9 +14,12 @@ use roc_target::{PtrWidth, TargetInfo};
use roc_types::num::NumericRange;
use roc_types::subs::{
self, Content, FlatType, GetSubsSlice, Label, OptVariable, RecordFields, Subs, TagExt,
- UnsortedUnionLabels, Variable, VariableSubsSlice,
+ TupleElems, UnsortedUnionLabels, Variable, VariableSubsSlice,
+};
+use roc_types::types::{
+ gather_fields_unsorted_iter, gather_tuple_elems_unsorted_iter, RecordField, RecordFieldsError,
+ TupleElemsError,
};
-use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError};
use std::cmp::Ordering;
use std::collections::hash_map::{DefaultHasher, Entry};
use std::collections::HashMap;
@@ -120,9 +125,9 @@ pub struct LayoutCache<'a> {
impl<'a> LayoutCache<'a> {
pub fn new(interner: TLLayoutInterner<'a>, target_info: TargetInfo) -> Self {
let mut cache = std::vec::Vec::with_capacity(4);
- cache.push(CacheLayer::default());
+ cache.push(Default::default());
let mut raw_cache = std::vec::Vec::with_capacity(4);
- raw_cache.push(CacheLayer::default());
+ raw_cache.push(Default::default());
Self {
target_info,
cache,
@@ -299,14 +304,20 @@ impl<'a> LayoutCache<'a> {
/// Invalidates the list of given root variables.
/// Usually called after unification, when merged variables with changed contents need to be
/// invalidated.
- pub fn invalidate(&mut self, vars: impl IntoIterator
- ) {
+ pub fn invalidate(&mut self, subs: &Subs, vars: impl IntoIterator
- ) {
+ // TODO(layout-cache): optimize me somehow
for var in vars.into_iter() {
+ let var = subs.get_root_key_without_compacting(var);
for layer in self.cache.iter_mut().rev() {
- layer.0.remove(&var);
+ layer
+ .0
+ .retain(|k, _| !subs.equivalent_without_compacting(var, *k));
roc_tracing::debug!(?var, "invalidating cached layout");
}
for layer in self.raw_function_cache.iter_mut().rev() {
- layer.0.remove(&var);
+ layer
+ .0
+ .retain(|k, _| !subs.equivalent_without_compacting(var, *k));
roc_tracing::debug!(?var, "invalidating cached layout");
}
}
@@ -481,7 +492,9 @@ impl<'a> RawFunctionLayout<'a> {
let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, *structure_content)
}
- LambdaSet(lset) => Self::layout_from_lambda_set(env, lset),
+ LambdaSet(_) => {
+ internal_error!("lambda set should only appear under a function, where it's handled independently.");
+ }
Structure(flat_type) => Self::layout_from_flat_type(env, flat_type),
RangedNumber(..) => Layout::new_help(env, var, content).then(Self::ZeroArgumentThunk),
@@ -554,15 +567,6 @@ impl<'a> RawFunctionLayout<'a> {
}
}
- fn layout_from_lambda_set(
- _env: &mut Env<'a, '_>,
- _lset: subs::LambdaSet,
- ) -> Cacheable> {
- unreachable!()
- // Lambda set is just a tag union from the layout's perspective.
- // Self::layout_from_flat_type(env, lset.as_tag_union())
- }
-
fn layout_from_flat_type(
env: &mut Env<'a, '_>,
flat_type: FlatType,
@@ -653,6 +657,17 @@ impl FieldOrderHash {
fields.iter().for_each(|field| field.hash(&mut hasher));
Self(hasher.finish())
}
+
+ pub fn from_ordered_tuple_elems(elems: &[usize]) -> Self {
+ if elems.is_empty() {
+ // HACK: we must make sure this is always equivalent to a `ZERO_FIELD_HASH`.
+ return Self::ZERO_FIELD_HASH;
+ }
+
+ let mut hasher = DefaultHasher::new();
+ elems.iter().for_each(|elem| elem.hash(&mut hasher));
+ Self(hasher.finish())
+ }
}
/// Types for code gen must be monomorphic. No type variables allowed!
@@ -727,6 +742,7 @@ impl<'a> UnionLayout<'a> {
self,
alloc: &'b D,
interner: &I,
+ seen_rec: &mut SeenRecPtrs<'a>,
_parens: Parens,
) -> DocBuilder<'b, D, A>
where
@@ -740,14 +756,14 @@ impl<'a> UnionLayout<'a> {
match self {
NonRecursive(tags) => {
let tags_doc = tags.iter().map(|fields| {
- alloc.text("C ").append(alloc.intersperse(
- fields.iter().map(|x| {
- interner
- .get(*x)
- .to_doc(alloc, interner, Parens::InTypeParam)
- }),
- " ",
- ))
+ alloc.text("C ").append(
+ alloc.intersperse(
+ fields
+ .iter()
+ .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)),
+ " ",
+ ),
+ )
});
alloc
@@ -757,14 +773,14 @@ impl<'a> UnionLayout<'a> {
}
Recursive(tags) => {
let tags_doc = tags.iter().map(|fields| {
- alloc.text("C ").append(alloc.intersperse(
- fields.iter().map(|x| {
- interner
- .get(*x)
- .to_doc(alloc, interner, Parens::InTypeParam)
- }),
- " ",
- ))
+ alloc.text("C ").append(
+ alloc.intersperse(
+ fields
+ .iter()
+ .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)),
+ " ",
+ ),
+ )
});
alloc
.text("[")
@@ -772,14 +788,14 @@ impl<'a> UnionLayout<'a> {
.append(alloc.text("]"))
}
NonNullableUnwrapped(fields) => {
- let fields_doc = alloc.text("C ").append(alloc.intersperse(
- fields.iter().map(|x| {
- interner
- .get(*x)
- .to_doc(alloc, interner, Parens::InTypeParam)
- }),
- " ",
- ));
+ let fields_doc = alloc.text("C ").append(
+ alloc.intersperse(
+ fields
+ .iter()
+ .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)),
+ " ",
+ ),
+ );
alloc
.text("[")
.append(fields_doc)
@@ -789,14 +805,14 @@ impl<'a> UnionLayout<'a> {
nullable_id,
other_fields,
} => {
- let fields_doc = alloc.text("C ").append(alloc.intersperse(
- other_fields.iter().map(|x| {
- interner
- .get(*x)
- .to_doc(alloc, interner, Parens::InTypeParam)
- }),
- " ",
- ));
+ let fields_doc = alloc.text("C ").append(
+ alloc.intersperse(
+ other_fields
+ .iter()
+ .map(|x| interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)),
+ " ",
+ ),
+ );
let tags_doc = if nullable_id {
alloc.concat(vec![alloc.text(", "), fields_doc])
} else {
@@ -812,21 +828,20 @@ impl<'a> UnionLayout<'a> {
other_tags,
} => {
let nullable_id = nullable_id as usize;
- let tags_docs = (0..(other_tags.len() + 1)).map(|i| {
- if i == nullable_id {
- alloc.text("")
- } else {
- let idx = if i > nullable_id { i - 1 } else { i };
- alloc.text("C ").append(alloc.intersperse(
- other_tags[idx].iter().map(|x| {
- interner
- .get(*x)
- .to_doc(alloc, interner, Parens::InTypeParam)
- }),
- " ",
- ))
- }
- });
+ let tags_docs =
+ (0..(other_tags.len() + 1)).map(|i| {
+ if i == nullable_id {
+ alloc.text("")
+ } else {
+ let idx = if i > nullable_id { i - 1 } else { i };
+ alloc.text("C ").append(alloc.intersperse(
+ other_tags[idx].iter().map(|x| {
+ interner.to_doc(*x, alloc, seen_rec, Parens::InTypeParam)
+ }),
+ " ",
+ ))
+ }
+ });
let tags_docs = alloc.intersperse(tags_docs, alloc.text(", "));
alloc
.text("[")
@@ -1273,7 +1288,12 @@ pub struct Niche<'a>(NichePriv<'a>);
impl<'a> Niche<'a> {
pub const NONE: Niche<'a> = Niche(NichePriv::Captures(&[]));
- pub fn to_doc<'b, D, A, I>(self, alloc: &'b D, interner: &I) -> DocBuilder<'b, D, A>
+ pub fn to_doc<'b, D, A, I>(
+ self,
+ alloc: &'b D,
+ interner: &I,
+ seen_rec: &mut SeenRecPtrs<'a>,
+ ) -> DocBuilder<'b, D, A>
where
D: DocAllocator<'b, A>,
D::Doc: Clone,
@@ -1286,13 +1306,21 @@ impl<'a> Niche<'a> {
alloc.intersperse(
captures
.iter()
- .map(|c| interner.get(*c).to_doc(alloc, interner, Parens::NotNeeded)),
+ .map(|c| interner.to_doc(*c, alloc, seen_rec, Parens::NotNeeded)),
alloc.reflow(", "),
),
alloc.reflow("})"),
]),
}
}
+
+ pub fn dbg_deep<'r, I: LayoutInterner<'a>>(
+ &'r self,
+ interner: &'r I,
+ ) -> crate::layout::intern::dbg::DbgFields<'a, 'r, I> {
+ let NichePriv::Captures(caps) = &self.0;
+ interner.dbg_deep_iter(caps)
+ }
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
@@ -1517,35 +1545,7 @@ impl<'a> LambdaSet<'a> {
where
I: LayoutInterner<'a>,
{
- if left == right {
- return true;
- }
- let left = interner.get(*left);
- let right = interner.get(*right);
-
- let left = if matches!(left, Layout::RecursivePointer(_)) {
- let runtime_repr = self.runtime_representation();
- debug_assert!(matches!(
- interner.get(runtime_repr),
- Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. })
- ));
- Layout::LambdaSet(*self)
- } else {
- left
- };
-
- let right = if matches!(right, Layout::RecursivePointer(_)) {
- let runtime_repr = self.runtime_representation();
- debug_assert!(matches!(
- interner.get(runtime_repr),
- Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. })
- ));
- Layout::LambdaSet(*self)
- } else {
- right
- };
-
- left == right
+ interner.equiv(*left, *right)
}
fn layout_for_member(&self, interner: &I, comparator: F) -> ClosureRepresentation<'a>
@@ -1558,7 +1558,7 @@ impl<'a> LambdaSet<'a> {
return ClosureRepresentation::UnwrappedCapture(self.representation);
}
- let repr = interner.get(self.representation);
+ let repr = interner.chase_recursive(self.representation);
match repr {
Layout::Union(union) => {
@@ -1621,11 +1621,27 @@ impl<'a> LambdaSet<'a> {
union_layout: union,
}
}
- UnionLayout::NonNullableUnwrapped(_) => todo!("recursive closures"),
UnionLayout::NullableWrapped {
nullable_id: _,
other_tags: _,
- } => todo!("recursive closures"),
+ } => {
+ let (index, (name, fields)) = self
+ .set
+ .iter()
+ .enumerate()
+ .find(|(_, (s, layouts))| comparator(*s, layouts))
+ .unwrap();
+
+ let closure_name = *name;
+
+ ClosureRepresentation::Union {
+ tag_id: index as TagIdIntType,
+ alphabetic_order_fields: fields,
+ closure_name,
+ union_layout: union,
+ }
+ }
+ UnionLayout::NonNullableUnwrapped(_) => internal_error!("I thought a non-nullable-unwrapped variant for a lambda set was impossible: how could such a lambda set be created without a base case?"),
}
}
Layout::Struct { .. } => {
@@ -1642,7 +1658,7 @@ impl<'a> LambdaSet<'a> {
ClosureRepresentation::AlphabeticOrderStruct(fields)
}
layout => {
- debug_assert!(self.has_enum_dispatch_repr(),);
+ debug_assert!(self.has_enum_dispatch_repr());
let enum_repr = match layout {
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
@@ -1669,7 +1685,7 @@ impl<'a> LambdaSet<'a> {
return ClosureCallOptions::UnwrappedCapture(self.representation);
}
- let repr = interner.get(self.representation);
+ let repr = interner.chase_recursive(self.representation);
match repr {
Layout::Union(union_layout) => {
@@ -1764,7 +1780,7 @@ impl<'a> LambdaSet<'a> {
Cacheable(result, criteria)
});
- match result.map(|l| env.cache.get_in(l)) {
+ match result.map(|l| env.cache.interner.chase_recursive(l)) {
Ok(Layout::LambdaSet(lambda_set)) => Cacheable(Ok(lambda_set), criteria),
Err(err) => Cacheable(Err(err), criteria),
Ok(layout) => internal_error!("other layout found for lambda set: {:?}", layout),
@@ -1804,6 +1820,7 @@ impl<'a> LambdaSet<'a> {
Vec::with_capacity_in(lambdas.len(), env.arena);
let mut set_with_variables: std::vec::Vec<(&Symbol, &[Variable])> =
std::vec::Vec::with_capacity(lambdas.len());
+ let mut set_captures_have_naked_rec_ptr = false;
let mut last_function_symbol = None;
let mut lambdas_it = lambdas.iter().peekable();
@@ -1822,6 +1839,8 @@ impl<'a> LambdaSet<'a> {
let mut criteria = CACHEABLE;
let arg = cached!(Layout::from_var(env, *var), criteria);
arguments.push(arg);
+ set_captures_have_naked_rec_ptr =
+ set_captures_have_naked_rec_ptr || criteria.has_naked_recursion_pointer;
}
let arguments = arguments.into_bump_slice();
@@ -1882,10 +1901,16 @@ impl<'a> LambdaSet<'a> {
);
cache_criteria.and(criteria);
+ let needs_recursive_fixup = NeedsRecursionPointerFixup(
+ opt_recursion_var.is_some() && set_captures_have_naked_rec_ptr,
+ );
+
let lambda_set = env.cache.interner.insert_lambda_set(
+ env.arena,
fn_args,
ret,
env.arena.alloc(set.into_bump_slice()),
+ needs_recursive_fixup,
representation,
);
@@ -1895,9 +1920,11 @@ impl<'a> LambdaSet<'a> {
// The lambda set is unbound which means it must be unused. Just give it the empty lambda set.
// See also https://github.com/roc-lang/roc/issues/3163.
let lambda_set = env.cache.interner.insert_lambda_set(
+ env.arena,
fn_args,
ret,
&(&[] as &[(Symbol, &[InLayout])]),
+ NeedsRecursionPointerFixup(false),
Layout::UNIT,
);
Cacheable(Ok(lambda_set), cache_criteria)
@@ -2128,6 +2155,16 @@ pub enum Builtin<'a> {
List(InLayout<'a>),
}
+#[macro_export]
+macro_rules! list_element_layout {
+ ($interner:expr, $list_layout:expr) => {
+ match $interner.get($list_layout) {
+ Layout::Builtin(Builtin::List(list_layout)) => list_layout,
+ _ => internal_error!("invalid list layout"),
+ }
+ };
+}
+
pub struct Env<'a, 'b> {
target_info: TargetInfo,
arena: &'a Bump,
@@ -2353,7 +2390,9 @@ impl<'a> Layout<'a> {
let structure_content = env.subs.get_content_without_compacting(structure);
Self::new_help(env, structure, *structure_content)
}
- LambdaSet(lset) => layout_from_lambda_set(env, lset),
+ LambdaSet(_) => {
+ internal_error!("lambda set should only appear under a function, where it's handled independently.");
+ }
Structure(flat_type) => layout_from_flat_type(env, flat_type),
Alias(symbol, _args, actual_var, _) => {
@@ -2702,42 +2741,58 @@ impl<'a> Layout<'a> {
}
}
- pub fn to_doc<'b, D, A, I>(
- self,
- alloc: &'b D,
- interner: &I,
- parens: Parens,
- ) -> DocBuilder<'b, D, A>
+ pub fn has_varying_stack_size(self, interner: &I, arena: &bumpalo::Bump) -> bool
where
- D: DocAllocator<'b, A>,
- D::Doc: Clone,
- A: Clone,
I: LayoutInterner<'a>,
{
- use Layout::*;
+ let mut stack: Vec = bumpalo::collections::Vec::new_in(arena);
- match self {
- Builtin(builtin) => builtin.to_doc(alloc, interner, parens),
- Struct { field_layouts, .. } => {
- let fields_doc = field_layouts
- .iter()
- .map(|x| interner.get(*x).to_doc(alloc, interner, parens));
+ stack.push(self);
- alloc
- .text("{")
- .append(alloc.intersperse(fields_doc, ", "))
- .append(alloc.text("}"))
+ while let Some(layout) = stack.pop() {
+ match layout {
+ Layout::Builtin(builtin) => match builtin {
+ Builtin::Int(_)
+ | Builtin::Float(_)
+ | Builtin::Bool
+ | Builtin::Decimal
+ | Builtin::Str
+ // If there's any layer of indirection (behind a pointer), then it doesn't vary!
+ | Builtin::List(_) => { /* do nothing */ }
+ },
+ // If there's any layer of indirection (behind a pointer), then it doesn't vary!
+ Layout::Struct { field_layouts, .. } => {
+ stack.extend(field_layouts.iter().map(|interned| interner.get(*interned)))
+ }
+ Layout::Union(tag_union) => match tag_union {
+ UnionLayout::NonRecursive(tags) | UnionLayout::Recursive(tags) => {
+ for tag in tags {
+ stack.extend(tag.iter().map(|interned| interner.get(*interned)));
+ }
+ }
+ UnionLayout::NonNullableUnwrapped(fields) => {
+ stack.extend(fields.iter().map(|interned| interner.get(*interned)));
+ }
+ UnionLayout::NullableWrapped { other_tags, .. } => {
+ for tag in other_tags {
+ stack.extend(tag.iter().map(|interned| interner.get(*interned)));
+ }
+ }
+ UnionLayout::NullableUnwrapped { other_fields, .. } => {
+ stack.extend(other_fields.iter().map(|interned| interner.get(*interned)));
+ }
+ },
+ Layout::LambdaSet(_) => return true,
+ Layout::Boxed(_) => {
+ // If there's any layer of indirection (behind a pointer), then it doesn't vary!
+ }
+ Layout::RecursivePointer(_) => {
+ /* do nothing, we've already generated for this type through the Union(_) */
+ }
}
- Union(union_layout) => union_layout.to_doc(alloc, interner, parens),
- LambdaSet(lambda_set) => interner
- .get(lambda_set.runtime_representation())
- .to_doc(alloc, interner, parens),
- RecursivePointer(_) => alloc.text("*self"),
- Boxed(inner) => alloc
- .text("Boxed(")
- .append(interner.get(inner).to_doc(alloc, interner, parens))
- .append(")"),
}
+
+ false
}
/// Used to build a `Layout::Struct` where the field name order is irrelevant.
@@ -2773,6 +2828,8 @@ impl<'a> Layout<'a> {
}
}
+pub type SeenRecPtrs<'a> = VecSet>;
+
impl<'a> Layout<'a> {
pub fn usize(target_info: TargetInfo) -> InLayout<'a> {
match target_info.ptr_width() {
@@ -2821,6 +2878,18 @@ impl<'a> Layout<'a> {
Dec => Layout::DEC,
}
}
+
+ pub fn is_recursive_tag_union(self) -> bool {
+ matches!(
+ self,
+ Layout::Union(
+ UnionLayout::NullableUnwrapped { .. }
+ | UnionLayout::Recursive(_)
+ | UnionLayout::NullableWrapped { .. }
+ | UnionLayout::NonNullableUnwrapped { .. },
+ )
+ )
+ }
}
impl<'a> Builtin<'a> {
@@ -2900,6 +2969,7 @@ impl<'a> Builtin<'a> {
self,
alloc: &'b D,
interner: &I,
+ seen_rec: &mut SeenRecPtrs<'a>,
_parens: Parens,
) -> DocBuilder<'b, D, A>
where
@@ -2941,12 +3011,12 @@ impl<'a> Builtin<'a> {
Decimal => alloc.text("Decimal"),
Str => alloc.text("Str"),
- List(layout) => {
- let layout = interner.get(layout);
- alloc
- .text("List ")
- .append(layout.to_doc(alloc, interner, Parens::InTypeParam))
- }
+ List(layout) => alloc.text("List ").append(interner.to_doc(
+ layout,
+ alloc,
+ seen_rec,
+ Parens::InTypeParam,
+ )),
}
}
@@ -2973,37 +3043,6 @@ impl<'a> Builtin<'a> {
}
}
-fn layout_from_lambda_set<'a>(
- env: &mut Env<'a, '_>,
- lset: subs::LambdaSet,
-) -> Cacheable> {
- // Lambda set is just a tag union from the layout's perspective.
- let subs::LambdaSet {
- solved,
- recursion_var,
- unspecialized,
- ambient_function: _,
- } = lset;
-
- if !unspecialized.is_empty() {
- internal_error!(
- "unspecialized lambda sets remain during layout generation for {:?}",
- roc_types::subs::SubsFmtContent(&Content::LambdaSet(lset), env.subs)
- );
- }
-
- match recursion_var.into_variable() {
- None => {
- let labels = solved.unsorted_lambdas(env.subs);
- layout_from_non_recursive_union(env, &labels).map(Ok)
- }
- Some(rec_var) => {
- let labels = solved.unsorted_lambdas(env.subs);
- layout_from_recursive_union(env, rec_var, &labels)
- }
- }
-}
-
fn layout_from_flat_type<'a>(
env: &mut Env<'a, '_>,
flat_type: FlatType,
@@ -3189,8 +3228,52 @@ fn layout_from_flat_type<'a>(
Cacheable(result, criteria)
}
- Tuple(_elems, _ext_var) => {
- todo!();
+ Tuple(elems, ext_var) => {
+ let mut criteria = CACHEABLE;
+
+ // extract any values from the ext_var
+ let mut sortables = Vec::with_capacity_in(elems.len(), arena);
+ let it = match elems.unsorted_iterator(subs, ext_var) {
+ Ok(it) => it,
+ Err(TupleElemsError) => return Cacheable(Err(LayoutProblem::Erroneous), criteria),
+ };
+
+ for (index, elem) in it {
+ let elem_layout = cached!(Layout::from_var(env, elem), criteria);
+ sortables.push((index, elem_layout));
+ }
+
+ sortables.sort_by(|(index1, layout1), (index2, layout2)| {
+ cmp_fields(
+ &env.cache.interner,
+ index1,
+ *layout1,
+ index2,
+ *layout2,
+ target_info,
+ )
+ });
+
+ let ordered_field_names =
+ Vec::from_iter_in(sortables.iter().map(|(index, _)| *index), arena);
+ let field_order_hash =
+ FieldOrderHash::from_ordered_tuple_elems(ordered_field_names.as_slice());
+
+ let result = if sortables.len() == 1 {
+ // If the tuple has only one field that isn't zero-sized,
+ // unwrap it.
+ Ok(sortables.pop().unwrap().1)
+ } else {
+ let layouts = Vec::from_iter_in(sortables.into_iter().map(|t| t.1), arena);
+ let struct_layout = Layout::Struct {
+ field_order_hash,
+ field_layouts: layouts.into_bump_slice(),
+ };
+
+ Ok(env.cache.put_in(struct_layout))
+ };
+
+ Cacheable(result, criteria)
}
TagUnion(tags, ext_var) => {
let (tags, ext_var) = tags.unsorted_tags_and_ext(subs, ext_var);
@@ -3225,6 +3308,48 @@ fn layout_from_flat_type<'a>(
}
}
+pub type SortedTupleElem<'a> = (usize, Variable, InLayout<'a>);
+
+pub fn sort_tuple_elems<'a>(
+ env: &mut Env<'a, '_>,
+ var: Variable,
+) -> Result>, LayoutProblem> {
+ let (it, _) = match gather_tuple_elems_unsorted_iter(env.subs, TupleElems::empty(), var) {
+ Ok(it) => it,
+ Err(_) => return Err(LayoutProblem::Erroneous),
+ };
+
+ sort_tuple_elems_help(env, it)
+}
+
+fn sort_tuple_elems_help<'a>(
+ env: &mut Env<'a, '_>,
+ elems_map: impl Iterator
- ,
+) -> Result>, LayoutProblem> {
+ let target_info = env.target_info;
+
+ let mut sorted_elems = Vec::with_capacity_in(elems_map.size_hint().0, env.arena);
+
+ for (index, elem) in elems_map {
+ let Cacheable(layout, _) = Layout::from_var(env, elem);
+ let layout = layout?;
+ sorted_elems.push((index, elem, layout));
+ }
+
+ sorted_elems.sort_by(|(index1, _, res_layout1), (index2, _, res_layout2)| {
+ cmp_fields(
+ &env.cache.interner,
+ index1,
+ *res_layout1,
+ index2,
+ *res_layout2,
+ target_info,
+ )
+ });
+
+ Ok(sorted_elems)
+}
+
pub type SortedField<'a> = (Lowercase, Variable, Result, InLayout<'a>>);
pub fn sort_record_fields<'a>(
@@ -3500,18 +3625,6 @@ fn get_recursion_var(subs: &Subs, var: Variable) -> Option {
}
}
-fn is_recursive_tag_union(layout: &Layout) -> bool {
- matches!(
- layout,
- Layout::Union(
- UnionLayout::NullableUnwrapped { .. }
- | UnionLayout::Recursive(_)
- | UnionLayout::NullableWrapped { .. }
- | UnionLayout::NonNullableUnwrapped { .. },
- )
- )
-}
-
fn union_sorted_non_recursive_tags_help<'a, L>(
env: &mut Env<'a, '_>,
tags_list: &[(&'_ L, &[Variable])],
@@ -3804,7 +3917,7 @@ where
== env
.subs
.get_root_key_without_compacting(opt_rec_var.unwrap())
- && is_recursive_tag_union(&layout);
+ && layout.is_recursive_tag_union();
let arg_layout = if self_recursion {
Layout::NAKED_RECURSIVE_PTR
@@ -4075,6 +4188,7 @@ where
// The naked pointer will get fixed-up to loopback to the union below when we
// intern the union.
tag_layout.push(Layout::NAKED_RECURSIVE_PTR);
+ criteria.and(NAKED_RECURSION_PTR);
continue;
}
@@ -4115,12 +4229,17 @@ where
} else {
UnionLayout::Recursive(tag_layouts.into_bump_slice())
};
- criteria.pass_through_recursive_union(rec_var);
- let union_layout = env
- .cache
- .interner
- .insert_recursive(env.arena, Layout::Union(union_layout));
+ let union_layout = if criteria.has_naked_recursion_pointer {
+ env.cache
+ .interner
+ .insert_recursive(env.arena, Layout::Union(union_layout))
+ } else {
+ // There are no naked recursion pointers, so we can insert the layout as-is.
+ env.cache.interner.insert(Layout::Union(union_layout))
+ };
+
+ criteria.pass_through_recursive_union(rec_var);
Cacheable(Ok(union_layout), criteria)
}
diff --git a/crates/compiler/mono/src/layout/intern.rs b/crates/compiler/mono/src/layout/intern.rs
index 186049be43..158a47dd74 100644
--- a/crates/compiler/mono/src/layout/intern.rs
+++ b/crates/compiler/mono/src/layout/intern.rs
@@ -12,7 +12,7 @@ use roc_collections::{default_hasher, BumpMap};
use roc_module::symbol::Symbol;
use roc_target::TargetInfo;
-use super::{Builtin, FieldOrderHash, LambdaSet, Layout, UnionLayout};
+use super::{Builtin, FieldOrderHash, LambdaSet, Layout, SeenRecPtrs, UnionLayout};
macro_rules! cache_interned_layouts {
($($i:literal, $name:ident, $vis:vis, $layout:expr)*; $total_constants:literal) => {
@@ -121,6 +121,13 @@ impl<'a> Layout<'a> {
}
}
+/// Whether a recursive lambda set being inserted into an interner needs fixing-up of naked
+/// recursion pointers in the capture set.
+/// Applicable only if
+/// - the lambda set is indeed recursive, and
+/// - its capture set contain naked pointer references
+pub struct NeedsRecursionPointerFixup(pub bool);
+
pub trait LayoutInterner<'a>: Sized {
/// Interns a value, returning its interned representation.
/// If the value has been interned before, the old interned representation will be re-used.
@@ -135,9 +142,11 @@ pub trait LayoutInterner<'a>: Sized {
/// lambda set onto itself.
fn insert_lambda_set(
&mut self,
+ arena: &'a Bump,
args: &'a &'a [InLayout<'a>],
ret: InLayout<'a>,
set: &'a &'a [(Symbol, &'a [InLayout<'a>])],
+ needs_recursive_fixup: NeedsRecursionPointerFixup,
representation: InLayout<'a>,
) -> LambdaSet<'a>;
@@ -198,14 +207,63 @@ pub trait LayoutInterner<'a>: Sized {
Layout::runtime_representation_in(layout, self)
}
+ fn chase_recursive(&self, mut layout: InLayout<'a>) -> Layout<'a> {
+ loop {
+ match self.get(layout) {
+ Layout::RecursivePointer(l) => layout = l,
+ other => return other,
+ }
+ }
+ }
+
+ fn chase_recursive_in(&self, mut layout: InLayout<'a>) -> InLayout<'a> {
+ loop {
+ match self.get(layout) {
+ Layout::RecursivePointer(l) => layout = l,
+ _ => return layout,
+ }
+ }
+ }
+
fn safe_to_memcpy(&self, layout: InLayout<'a>) -> bool {
self.get(layout).safe_to_memcpy(self)
}
+ /// Checks if two layouts are equivalent up to isomorphism.
+ ///
+ /// This is only to be used when layouts need to be compared across statements and depths,
+ /// for example
+ /// - when looking up a layout index in a lambda set
+ /// - in the [checker][crate::debug::check_procs], where `x = UnionAtIndex(f, 0)` may have
+ /// that the recorded layout of `x` is at a different depth than that determined when we
+ /// index the recorded layout of `f` at 0. Hence the two layouts may have different
+ /// interned representations, even if they are in fact isomorphic.
+ fn equiv(&self, l1: InLayout<'a>, l2: InLayout<'a>) -> bool {
+ std::thread_local! {
+ static SCRATCHPAD: RefCell