mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge branch 'main' into swiftuidemo
Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>
This commit is contained in:
commit
04ea17e8a1
277 changed files with 15938 additions and 4413 deletions
24
.github/workflows/nightly_linux_x86_64.yml
vendored
24
.github/workflows/nightly_linux_x86_64.yml
vendored
|
@ -7,10 +7,8 @@ name: Nightly Release Linux x86_64
|
|||
jobs:
|
||||
build:
|
||||
name: Rust tests, build and package nightly release
|
||||
runs-on: [self-hosted, i5-4690K]
|
||||
runs-on: [self-hosted, i7-6700K]
|
||||
timeout-minutes: 90
|
||||
env:
|
||||
FORCE_COLOR: 1 # for earthly logging
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
@ -21,14 +19,26 @@ jobs:
|
|||
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: Make release tar archive
|
||||
run: ./ci/package_release.sh roc_linux_x86_64.tar.gz
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-linux_x86_64-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
|
||||
- name: Make nightly release tar archive
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_TAR_FILENAME }}
|
||||
|
||||
- name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: roc_nightly-linux_x86_64.tar.gz
|
||||
path: roc_linux_x86_64.tar.gz
|
||||
name: ${{ env.RELEASE_TAR_FILENAME }}
|
||||
path: ${{ env.RELEASE_TAR_FILENAME }}
|
||||
retention-days: 4
|
||||
|
||||
- name: build wasm repl
|
||||
|
|
|
@ -19,6 +19,18 @@ jobs:
|
|||
- name: run tests
|
||||
run: cargo test --locked --release
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_apple_silicon-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
|
||||
- name: write version to file
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
|
@ -26,7 +38,7 @@ jobs:
|
|||
run: cargo build --locked --release
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh roc_darwin_apple_silicon.tar.gz
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_TAR_FILENAME }}
|
||||
|
||||
- name: print short commit SHA
|
||||
run: git rev-parse --short "$GITHUB_SHA"
|
||||
|
@ -35,6 +47,6 @@ jobs:
|
|||
- name: Upload artifact Actually uploading to github releases has to be done manually
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: roc_nightly-macos_apple_silicon.tar.gz
|
||||
path: roc_darwin_apple_silicon.tar.gz
|
||||
name: ${{ env.RELEASE_TAR_FILENAME }}
|
||||
path: ${{ env.RELEASE_TAR_FILENAME }}
|
||||
retention-days: 4
|
||||
|
|
52
.github/workflows/nightly_macos_x86_64.yml
vendored
52
.github/workflows/nightly_macos_x86_64.yml
vendored
|
@ -1,6 +1,6 @@
|
|||
on:
|
||||
schedule:
|
||||
- cron: '0 9 * * 1' # 9=9am utc+0, 1=monday
|
||||
- cron: '0 9 * * *' # 9=9am utc+0
|
||||
|
||||
name: Nightly Release macOS x86_64
|
||||
|
||||
|
@ -9,13 +9,16 @@ env:
|
|||
LLVM_SYS_130_PREFIX: /usr/local/opt/llvm
|
||||
|
||||
jobs:
|
||||
test-and-build:
|
||||
name: Rust tests, build and package nightly release
|
||||
test-build-upload:
|
||||
name: build, test, package and upload nightly release
|
||||
runs-on: [macos-12]
|
||||
timeout-minutes: 90
|
||||
timeout-minutes: 120
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: write version to file
|
||||
run: ./ci/write_version.sh
|
||||
|
||||
- name: Install zig
|
||||
run: |
|
||||
curl -L -o zig.tar.xz https://ziglang.org/download/${ZIG_VERSION}/zig-macos-x86_64-${ZIG_VERSION}.tar.xz && tar -xf zig.tar.xz
|
||||
|
@ -24,30 +27,39 @@ jobs:
|
|||
run: zig version
|
||||
- name: Install LLVM
|
||||
run: brew install llvm@13
|
||||
|
||||
# build has to be done before tests #2572
|
||||
- name: build release
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: build
|
||||
args: --release --locked
|
||||
|
||||
- name: execute rust tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --locked # no --release yet until #3166 is fixed
|
||||
- name: write version to file
|
||||
run: ./ci/write_version.sh
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh roc_darwin_x86_64.tar.gz
|
||||
- name: Create pre-release with test_archive.tar.gz
|
||||
uses: Anton-4/deploy-nightly@1609d8dfe211b078674801113ab7a2ec2938b2a9
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
|
||||
with:
|
||||
upload_url: https://uploads.github.com/repos/roc-lang/roc/releases/51880579/assets{?name,label}
|
||||
release_id: 51880579
|
||||
asset_path: ./roc_darwin_x86_64.tar.gz
|
||||
asset_name: roc_nightly-macos_x86_64-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)
|
||||
asset_content_type: application/gzip
|
||||
max_releases: 3
|
||||
args: --release --locked -- --skip opaque_wrap_function --skip bool_list_literal
|
||||
|
||||
- name: get commit SHA
|
||||
run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
|
||||
|
||||
- name: get date
|
||||
run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
|
||||
|
||||
- name: build file name
|
||||
env:
|
||||
DATE: ${{ env.DATE }}
|
||||
SHA: ${{ env.SHA }}
|
||||
run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_x86_64-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
|
||||
|
||||
- name: package release
|
||||
run: ./ci/package_release.sh ${{ env.RELEASE_TAR_FILENAME }}
|
||||
|
||||
- name: Upload artifact. Actually uploading to github releases has to be done manually.
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ env.RELEASE_TAR_FILENAME }}
|
||||
path: ${{ env.RELEASE_TAR_FILENAME }}
|
||||
retention-days: 4
|
||||
|
||||
|
|
|
@ -31,6 +31,6 @@ jobs:
|
|||
- name: execute tests with --release
|
||||
run: nix develop -c cargo test --locked --release
|
||||
|
||||
# we run the llvm wasm tests only on this machine because it is fast and wasm should be cross-platform
|
||||
# we run the llvm wasm tests only on this machine because it is fast and wasm should be cross-target
|
||||
- name: execute llvm wasm tests with --release
|
||||
run: nix develop -c cargo test-gen-llvm-wasm --locked --release
|
||||
|
|
9
.github/workflows/spellcheck.yml
vendored
9
.github/workflows/spellcheck.yml
vendored
|
@ -12,7 +12,7 @@ env:
|
|||
jobs:
|
||||
spell-check:
|
||||
name: spell check
|
||||
runs-on: [self-hosted, i7-6700K]
|
||||
runs-on: [self-hosted]
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
FORCE_COLOR: 1
|
||||
|
@ -21,8 +21,5 @@ jobs:
|
|||
with:
|
||||
clean: "true"
|
||||
|
||||
- name: Earthly version
|
||||
run: earthly --version
|
||||
|
||||
- name: install spell checker, do spell check
|
||||
run: ./ci/safe-earthly.sh +check-typos
|
||||
- name: do spell check with typos-cli 1.0.11 # to reproduce locally: cargo install typos-cli --version 1.0.11
|
||||
run: typos
|
||||
|
|
|
@ -5,7 +5,7 @@ on:
|
|||
name: Test latest nightly release for macOS Apple Silicon
|
||||
|
||||
jobs:
|
||||
test-and-build:
|
||||
test-nightly:
|
||||
name: test nightly macos aarch64
|
||||
runs-on: [self-hosted, macOS, ARM64]
|
||||
timeout-minutes: 90
|
||||
|
@ -16,7 +16,7 @@ jobs:
|
|||
run: curl https://api.github.com/repos/roc-lang/roc/releases > roc_releases.json
|
||||
|
||||
- name: get the url of today`s release for macos apple silicon
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh)" >> $GITHUB_ENV
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh silicon)" >> $GITHUB_ENV
|
||||
|
||||
- name: get the archive from the url
|
||||
run: curl -OL ${{ env.RELEASE_URL }}
|
47
.github/workflows/test_nightly_many_os.yml
vendored
Normal file
47
.github/workflows/test_nightly_many_os.yml
vendored
Normal file
|
@ -0,0 +1,47 @@
|
|||
on:
|
||||
schedule:
|
||||
- cron: '0 13 * * *'
|
||||
|
||||
name: Test latest nightly release for macOS, ubu 20.04, ubu 22.04 x86_64
|
||||
|
||||
jobs:
|
||||
test-nightly:
|
||||
name: test nightly macos, ubu 20.04, ubu 22.04
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ macos-12, ubuntu-20.04, ubuntu-22.04 ]
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: fetch releases data and save to file
|
||||
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: get the url of today`s release for linux x86_64
|
||||
if: startsWith(matrix.os, 'ubuntu')
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh linux_x86_64)" >> $GITHUB_ENV
|
||||
|
||||
- name: get the url of today`s release for macos x86_64
|
||||
if: startsWith(matrix.os, 'macos')
|
||||
run: echo "RELEASE_URL=$(./ci/get_latest_release_url.sh macos_x86_64)" >> $GITHUB_ENV
|
||||
|
||||
- name: get the archive from the url
|
||||
run: curl -OL ${{ env.RELEASE_URL }}
|
||||
|
||||
- name: remove everything in this dir except the tar # we want to test like a user who would have downloaded the release, so we clean up all files from the repo checkout
|
||||
run: ls | grep -v "roc_nightly.*tar\.gz" | xargs rm -rf
|
||||
|
||||
- name: decompress the tar
|
||||
run: ls | grep "roc_nightly.*tar\.gz" | xargs tar -xzvf
|
||||
|
||||
- name: test roc hello world
|
||||
run: ./roc examples/hello-world/main.roc
|
||||
|
||||
|
||||
|
38
.github/workflows/windows.yml
vendored
Normal file
38
.github/workflows/windows.yml
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
on: [pull_request]
|
||||
|
||||
name: Test windows build
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
RUST_BACKTRACE: 1
|
||||
|
||||
jobs:
|
||||
windows-cargo-build:
|
||||
name: windows-cargo-build
|
||||
runs-on: windows-2022
|
||||
env:
|
||||
LLVM_SYS_130_PREFIX: C:\LLVM-13.0.1-win64
|
||||
timeout-minutes: 90
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- 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: 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
|
||||
7z x LLVM-13.0.1-win64.7z -oC:\LLVM-13.0.1-win64
|
||||
|
||||
- name: build
|
||||
run: cargo build
|
1
AUTHORS
1
AUTHORS
|
@ -95,3 +95,4 @@ Paul Young <84700+paulyoung@users.noreply.github.com>
|
|||
Rod <randomer@users.noreply.github.com>
|
||||
Marko Vujanic <crashxx@gmail.com>
|
||||
kilianv <r0754877>
|
||||
David Dunn <26876072+doubledup@users.noreply.github.com>
|
||||
|
|
|
@ -4,10 +4,9 @@ Installation should be a smooth process, let us now if anything does not work pe
|
|||
|
||||
## Using Nix
|
||||
|
||||
We highly recommend Using [nix](https://nixos.org/download.html) to quickly install all dependencies necessary to build roc.
|
||||
On Macos and Linux, we highly recommend Using [nix](https://nixos.org/download.html) to quickly install all dependencies necessary to build roc.
|
||||
|
||||
> See issue [#3863](https://github.com/roc-lang/roc/issues/3863) if you encounter "version GLIBC_2.34 not found".
|
||||
> This error can occur if you ran `cargo build` in the same folder without nix.
|
||||
:warning: If you tried to run `cargo` in the repo folder before installing nix, make sure to execute `cargo clean` first. To prevent you from executing `cargo` outside of nix, tools like [direnv](https://github.com/nix-community/nix-direnv) and [lorri](https://github.com/nix-community/lorri) can put you in a nix shell automatically when you `cd` into the directory.
|
||||
|
||||
### On Linux x86_64/aarch64 or MacOS aarch64/arm64/x86_64
|
||||
|
||||
|
@ -16,17 +15,28 @@ We highly recommend Using [nix](https://nixos.org/download.html) to quickly inst
|
|||
If you are running ArchLinux or a derivative like Manjaro, you'll need to run `sudo sysctl -w kernel.unprivileged_userns_clone=1` before installing nix.
|
||||
|
||||
Install nix (not necessary on NixOS):
|
||||
|
||||
- If you are using WSL (Windows subsystem for Linux):
|
||||
|
||||
```sh
|
||||
sh <(curl -L https://nixos.org/nix/install) --no-daemon
|
||||
```
|
||||
|
||||
- For everything else:
|
||||
|
||||
```sh
|
||||
sh <(curl -L https://nixos.org/nix/install) --daemon
|
||||
```
|
||||
|
||||
Open a new terminal and install nixFlakes in your environment:
|
||||
```
|
||||
|
||||
```sh
|
||||
nix-env -iA nixpkgs.nixFlakes
|
||||
```
|
||||
|
||||
Edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add:
|
||||
```
|
||||
|
||||
```text
|
||||
experimental-features = nix-command flakes
|
||||
```
|
||||
|
||||
|
@ -36,9 +46,11 @@ If you don't know how to do this, restarting your computer will also do the job.
|
|||
#### Usage
|
||||
|
||||
Now with nix set up, you just need to run one command from the roc project root directory:
|
||||
```
|
||||
|
||||
```sh
|
||||
nix develop
|
||||
```
|
||||
|
||||
You should be in a shell with everything needed to build already installed.
|
||||
Use `cargo run help` to see all subcommands.
|
||||
To use the `repl` subcommand, execute `cargo run repl`.
|
||||
|
@ -57,7 +69,8 @@ The editor is a :construction:WIP:construction: and not ready yet to replace you
|
|||
`cargo run edit` should work on NixOS and MacOS. If you use Linux x86_64, follow the instructions below.
|
||||
|
||||
If you're not already in a nix shell, execute `nix develop` at the the root of the repo folder and then execute:
|
||||
```
|
||||
|
||||
```sh
|
||||
nixVulkanIntel cargo run edit
|
||||
```
|
||||
|
||||
|
@ -70,16 +83,16 @@ That will help us improve this document for everyone who reads it in the future!
|
|||
|
||||
To build the compiler, you need these installed:
|
||||
|
||||
* [Zig](https://ziglang.org/), see below for version
|
||||
* `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
|
||||
* On Debian/Ubuntu `sudo apt-get install pkg-config`
|
||||
* LLVM, see below for version
|
||||
* [rust](https://rustup.rs/)
|
||||
* Also run `cargo install bindgen` after installing rust. You may need to open a new terminal.
|
||||
- [Zig](https://ziglang.org/), see below for version
|
||||
- `libxkbcommon` - macOS seems to have it already; on Ubuntu or Debian you can get it with `apt-get install libxkbcommon-dev`
|
||||
- On Debian/Ubuntu `sudo apt-get install pkg-config`
|
||||
- LLVM, see below for version
|
||||
- [rust](https://rustup.rs/)
|
||||
- Also run `cargo install bindgen` after installing rust. You may need to open a new terminal.
|
||||
|
||||
To run the test suite (via `cargo test`), you additionally need to install:
|
||||
|
||||
* [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
|
||||
- [`valgrind`](https://www.valgrind.org/) (needs special treatment to [install on macOS](https://stackoverflow.com/a/61359781)
|
||||
Alternatively, you can use `cargo test --no-fail-fast` or `cargo test -p specific_tests` to skip over the valgrind failures & tests.
|
||||
|
||||
For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir). This dependency is only required to build with the `--debug` flag, and for normal development you should be fine without it.
|
||||
|
@ -88,7 +101,7 @@ For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir).
|
|||
|
||||
You may see an error like this during builds:
|
||||
|
||||
```
|
||||
```text
|
||||
/usr/bin/ld: cannot find -lxcb-render
|
||||
/usr/bin/ld: cannot find -lxcb-shape
|
||||
/usr/bin/ld: cannot find -lxcb-xfixes
|
||||
|
@ -96,35 +109,42 @@ You may see an error like this during builds:
|
|||
|
||||
If so, you can fix it like so:
|
||||
|
||||
```
|
||||
```sh
|
||||
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
|
||||
```
|
||||
|
||||
### Zig
|
||||
|
||||
**version: 0.9.1**
|
||||
|
||||
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations.
|
||||
|
||||
If you prefer a package manager, you can try the following:
|
||||
|
||||
- For MacOS, you can install with `brew install zig`
|
||||
- For, Ubuntu, you can use Snap, you can install with `snap install zig --classic --beta`
|
||||
- For other systems, checkout this [page](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
|
||||
|
||||
If you want to install it manually, you can also download Zig directly [here](https://ziglang.org/download/). Just make sure you download the right version, the bleeding edge master build is the first download link on this page.
|
||||
|
||||
> WINDOWS NOTE: when you unpack the Zig archive on windows, the result is nested in an extra directory. The instructions on the zig website will seem to not work. So, double-check that the path to zig executable does not include the same directory name twice.
|
||||
|
||||
### LLVM
|
||||
|
||||
**version: 13.0.x**
|
||||
|
||||
For macOS, you can install LLVM 13 using `brew install llvm@13` and then adding
|
||||
`$(brew --prefix llvm@13)/bin` to your `PATH`. You can confirm this worked by
|
||||
running `llc --version` - it should mention "LLVM version 13.0.0" at the top.
|
||||
running `llc --version` - it should mention "LLVM version 13.0.1" at the top.
|
||||
You may also need to manually specify a prefix env var like so:
|
||||
```
|
||||
|
||||
```sh
|
||||
export LLVM_SYS_130_PREFIX=/usr/local/opt/llvm@13
|
||||
```
|
||||
|
||||
For Ubuntu and Debian:
|
||||
```
|
||||
|
||||
```sh
|
||||
sudo apt -y install lsb-release software-properties-common gnupg
|
||||
wget https://apt.llvm.org/llvm.sh
|
||||
chmod +x llvm.sh
|
||||
|
@ -134,11 +154,11 @@ chmod +x llvm.sh
|
|||
If you use this script, you'll need to add `clang` to your `PATH`.
|
||||
By default, the script installs it as `clang-13`. You can address this with symlinks like so:
|
||||
|
||||
```
|
||||
```sh
|
||||
sudo ln -s /usr/bin/clang-13 /usr/bin/clang
|
||||
```
|
||||
|
||||
There are also alternative installation options at http://releases.llvm.org/download.html
|
||||
There are also alternative installation options at <http://releases.llvm.org/download.html>
|
||||
|
||||
[Troubleshooting](#troubleshooting)
|
||||
|
||||
|
@ -158,10 +178,12 @@ On Ubuntu, running `sudo apt install pkg-config cmake libx11-dev` fixed this.
|
|||
If you encounter `cannot find -lz` run `sudo apt install zlib1g-dev`.
|
||||
|
||||
If you encounter:
|
||||
```
|
||||
|
||||
```text
|
||||
error: No suitable version of LLVM was found system-wide or pointed
|
||||
to by LLVM_SYS_130_PREFIX.
|
||||
```
|
||||
|
||||
Add `export LLVM_SYS_130_PREFIX=/usr/lib/llvm-13` to your `~/.bashrc` or equivalent file for your shell.
|
||||
|
||||
### LLVM installation on macOS
|
||||
|
@ -170,7 +192,7 @@ If installing LLVM fails, it might help to run `sudo xcode-select -r` before ins
|
|||
|
||||
It might also be useful to add these exports to your shell:
|
||||
|
||||
```
|
||||
```sh
|
||||
export LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
|
||||
export CPPFLAGS="-I/usr/local/opt/llvm/include"
|
||||
```
|
||||
|
@ -178,24 +200,24 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
|
|||
### LLVM installation on Windows
|
||||
|
||||
**Warning** While `cargo build` works on windows, linking roc programs does not yet, see issue #2608. This also means the repl, the editor and many tests will not work on windows.
|
||||
Installing LLVM's prebuilt binaries doesn't seem to be enough for the `llvm-sys` crate that Roc depends on, so I had to follow the steps below:
|
||||
The official LLVM pre-built binaries for Windows lack features that roc needs. Instead:
|
||||
|
||||
1. I downloaded and installed [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools&rel=16) (a full Visual Studio install should work too; the Build Tools are just the CLI tools, which is all I wanted)
|
||||
1. Download the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v13.0.0).
|
||||
1. Download the custom LLVM 7z archive [here](https://github.com/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z).
|
||||
1. [Download 7-zip](https://www.7-zip.org/) to be able to extract this archive.
|
||||
1. Extract the 7z file to where you want to permanently keep the folder.
|
||||
1. In powershell, set the `LLVM_SYS_130_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-changes-to-environment-variables) to make this a permanent environment variable):
|
||||
```
|
||||
[Environment]::SetEnvironmentVariable(
|
||||
"Path",
|
||||
[Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-13.0.0-win64\bin",
|
||||
"User"
|
||||
)
|
||||
```
|
||||
1. Extract the 7z file to where you want to permanently keep the folder. We recommend you pick a path without any spaces in it.
|
||||
1. In powershell, set the `LLVM_SYS_130_PREFIX` environment variable (check [here](https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-7.2#saving-environment-variables-with-the-system-control-panel) to make this a permanent environment variable):
|
||||
|
||||
```text
|
||||
<# ! Replace YOUR_USERNAME ! #>
|
||||
$env:LLVM_SYS_130_PREFIX = 'C:\Users\YOUR_USERNAME\Downloads\LLVM-13.0.1-win64'
|
||||
```
|
||||
|
||||
Once all that was done, `cargo build` ran successfully for Roc!
|
||||
|
||||
#### Build issues on Windows
|
||||
|
||||
If you see the build failing because some internal file is not available, it might be your anti-virus program. Cargo's behavior is kind of similar to a virus (downloading files from the internet, creating many files), and this has been known to cause problems.
|
||||
|
||||
### Build speed on WSL/WSL2
|
||||
|
||||
If your Roc project folder is in the Windows filesystem but you're compiling from Linux, rebuilds may be as much as 20x slower than they should be!
|
||||
|
@ -208,7 +230,7 @@ makes build times a lot faster, and I highly recommend it.
|
|||
|
||||
Create `~/.cargo/config.toml` if it does not exist and add this to it:
|
||||
|
||||
```
|
||||
```toml
|
||||
[build]
|
||||
# Link with lld, per https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306
|
||||
# Use target-cpu=native, per https://deterministic.space/high-performance-rust.html
|
||||
|
|
|
@ -8,20 +8,20 @@ In the interest of fostering an open and welcoming environment, we as participan
|
|||
|
||||
Examples of behavior that contributes to creating a positive environment include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Kindly giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
- Demonstrating empathy and kindness toward other people
|
||||
- Being respectful of differing opinions, viewpoints, and experiences
|
||||
- Kindly giving and gracefully accepting constructive feedback
|
||||
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
||||
- Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address, without their explicit permission
|
||||
* Telling others to be less sensitive, or that they should not feel hurt or offended by something
|
||||
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
- Public or private harassment
|
||||
- Publishing others' private information, such as a physical or email address, without their explicit permission
|
||||
- Telling others to be less sensitive, or that they should not feel hurt or offended by something
|
||||
|
||||
## Enforcement Responsibilities
|
||||
|
||||
|
@ -41,4 +41,4 @@ Moderators who do not follow or enforce the Code of Conduct in good faith may fa
|
|||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at <https://www.contributor-covenant.org/version/1/4/code-of-conduct.html>
|
||||
|
|
|
@ -4,6 +4,19 @@
|
|||
|
||||
We are committed to providing a friendly, safe and welcoming environment for all. Make sure to take a look at the [Code of Conduct](CodeOfConduct.md)!
|
||||
|
||||
## How to contribute
|
||||
|
||||
All contributions are appreciated! Typo fixes, bug fixes, feature requests,
|
||||
bug reports are all helpful for the project.
|
||||
|
||||
If you are looking for a good place to start, consider reaching out on the `#contributing` channel on [Roc Zulip][roc-zulip].
|
||||
Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip][roc-zulip] first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
|
||||
|
||||
If you are interested in larger, implementation- or research-heavy projects
|
||||
related to Roc, check out [Roc Project Ideas][project-ideas] and reach out to us
|
||||
on Zulip! These projects may be suitable for academic theses, independent
|
||||
research, or even just valuable projects to learn from and improve Roc with.
|
||||
|
||||
## Building from Source
|
||||
|
||||
Check [Building from source](BUILDING_FROM_SOURCE.md) for instructions.
|
||||
|
@ -11,18 +24,20 @@ Check [Building from source](BUILDING_FROM_SOURCE.md) for instructions.
|
|||
## Running Tests
|
||||
|
||||
Most contributors execute the following commands befor pushing their code:
|
||||
```
|
||||
|
||||
```sh
|
||||
cargo test
|
||||
cargo fmt --all -- --check
|
||||
cargo clippy --workspace --tests -- --deny warnings
|
||||
```
|
||||
|
||||
Execute `cargo fmt --all` to fix the formatting.
|
||||
|
||||
## Contribution Tips
|
||||
|
||||
- If you've never made a pull request on github before, [this](https://www.freecodecamp.org/news/how-to-make-your-first-pull-request-on-github-3/) will be a good place to start.
|
||||
- Create an issue if the purpose of a struct/field/type/function/... is not immediately clear from its name or nearby comments.
|
||||
- You can find good first issues [here](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
|
||||
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
|
||||
- You can find good first issues [here][good-first-issues].
|
||||
- [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.
|
||||
- All your commits need to be signed to prevent impersonation:
|
||||
|
@ -30,10 +45,15 @@ Execute `cargo fmt --all` to fix the formatting.
|
|||
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
|
||||
```
|
||||
|
||||
## Can we do better?
|
||||
|
||||
Feel free to open an issue if you think this document can be improved or is unclear in any way.
|
||||
|
||||
[roc-zulip]: https://roc.zulipchat.com
|
||||
[good-first-issues]: https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22
|
||||
[project-ideas]: https://docs.google.com/document/d/1mMaxIi7vxyUyNAUCs98d68jYj6C9Fpq4JIZRU735Kwg/edit?usp=sharing
|
||||
|
|
183
Cargo.lock
generated
183
Cargo.lock
generated
|
@ -297,9 +297,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.10.0"
|
||||
version = "3.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
|
||||
checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
|
||||
|
||||
[[package]]
|
||||
name = "byte-tools"
|
||||
|
@ -330,18 +330,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.11.0"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835"
|
||||
checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.1.0"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "562e382481975bc61d11275ac5e62a19abd00b0547d99516a415336f183dcd0e"
|
||||
checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -475,9 +475,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "3.2.11"
|
||||
version = "3.2.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d646c7ade5eb07c4aa20e907a922750df0c448892513714fd3e4acbc7130829f"
|
||||
checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"bitflags",
|
||||
|
@ -492,9 +492,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "clap_derive"
|
||||
version = "3.2.7"
|
||||
version = "3.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
|
||||
checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro-error",
|
||||
|
@ -1150,9 +1150,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "dircpy"
|
||||
version = "0.3.12"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d16e8f15af1ed7189d2bf43c7ae5d6fe0a840cd3b1e9c7bf7c08a384a5df441f"
|
||||
checksum = "73ff6269b47c0c5220a0ff5eb140424340276ec89a10e58cbd4cf366de52dfa9"
|
||||
dependencies = [
|
||||
"jwalk",
|
||||
"log",
|
||||
|
@ -1258,14 +1258,14 @@ checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9"
|
|||
dependencies = [
|
||||
"byteorder",
|
||||
"dynasm",
|
||||
"memmap2 0.5.5",
|
||||
"memmap2 0.5.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.7.0"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be"
|
||||
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
|
||||
|
||||
[[package]]
|
||||
name = "encode_unicode"
|
||||
|
@ -1472,9 +1472,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
|||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e"
|
||||
checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -1487,9 +1487,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010"
|
||||
checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
|
@ -1497,15 +1497,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-core"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3"
|
||||
checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6"
|
||||
checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
|
@ -1514,15 +1514,15 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b"
|
||||
checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
|
||||
|
||||
[[package]]
|
||||
name = "futures-macro"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512"
|
||||
checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -1531,21 +1531,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "futures-sink"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868"
|
||||
checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
|
||||
|
||||
[[package]]
|
||||
name = "futures-task"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a"
|
||||
checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
|
||||
|
||||
[[package]]
|
||||
name = "futures-util"
|
||||
version = "0.3.21"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a"
|
||||
checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
|
@ -1638,9 +1638,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "glyph_brush"
|
||||
version = "0.7.4"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a69c65dd1f1fbb6209aa00f78636e436ad0a55b7d8e5de886d00720dcad9c6e2"
|
||||
checksum = "ac02497410cdb5062cc056a33f2e1e19ff69fbf26a4be9a02bf29d6e17ea105b"
|
||||
dependencies = [
|
||||
"glyph_brush_draw_cache",
|
||||
"glyph_brush_layout",
|
||||
|
@ -1871,14 +1871,13 @@ checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca"
|
|||
|
||||
[[package]]
|
||||
name = "insta"
|
||||
version = "1.18.2"
|
||||
version = "1.19.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57a9aec10c9a062ef0454fd49ebaefa59239f836d1b30891d9cc2289978dd970"
|
||||
checksum = "f37dc501f12ec0c339b385787fa89ffda3d5d2caa62e558da731134c24d6e0c4"
|
||||
dependencies = [
|
||||
"console",
|
||||
"linked-hash-map",
|
||||
"once_cell",
|
||||
"serde",
|
||||
"similar",
|
||||
"yaml-rust",
|
||||
]
|
||||
|
@ -1962,9 +1961,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.59"
|
||||
version = "0.3.60"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
|
||||
checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
@ -2026,9 +2025,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
version = "0.2.132"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
|
||||
|
||||
[[package]]
|
||||
name = "libloading"
|
||||
|
@ -2186,9 +2185,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "memmap2"
|
||||
version = "0.5.5"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7"
|
||||
checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
@ -2616,9 +2615,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
version = "1.14.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
|
||||
|
||||
[[package]]
|
||||
name = "oorandom"
|
||||
|
@ -2810,10 +2809,11 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
|||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.1.3"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
|
||||
checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
|
@ -2946,14 +2946,14 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
|||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.2.1"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563"
|
||||
checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"ctor",
|
||||
"diff",
|
||||
"output_vt100",
|
||||
"yansi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3447,7 +3447,7 @@ name = "roc_cli"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.2.11",
|
||||
"clap 3.2.20",
|
||||
"cli_utils",
|
||||
"const_format",
|
||||
"criterion",
|
||||
|
@ -3458,6 +3458,8 @@ dependencies = [
|
|||
"libloading",
|
||||
"memexec",
|
||||
"mimalloc",
|
||||
"once_cell",
|
||||
"parking_lot 0.12.1",
|
||||
"pretty_assertions",
|
||||
"roc_build",
|
||||
"roc_builtins",
|
||||
|
@ -3512,6 +3514,7 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"bumpalo",
|
||||
"fnv",
|
||||
"hashbrown 0.12.3",
|
||||
"im",
|
||||
"im-rc",
|
||||
|
@ -3591,7 +3594,7 @@ dependencies = [
|
|||
name = "roc_docs_cli"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"clap 3.2.11",
|
||||
"clap 3.2.20",
|
||||
"libc",
|
||||
"roc_docs",
|
||||
]
|
||||
|
@ -3685,6 +3688,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_intern",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_parse",
|
||||
|
@ -3727,6 +3731,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_intern",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
"roc_std",
|
||||
|
@ -3738,7 +3743,7 @@ name = "roc_glue"
|
|||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"clap 3.2.11",
|
||||
"clap 3.2.20",
|
||||
"cli_utils",
|
||||
"dircpy",
|
||||
"fnv",
|
||||
|
@ -3749,6 +3754,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_error_macros",
|
||||
"roc_intern",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
@ -3756,6 +3762,7 @@ dependencies = [
|
|||
"roc_std",
|
||||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"roc_tracing",
|
||||
"roc_types",
|
||||
"strum",
|
||||
"strum_macros",
|
||||
|
@ -3775,6 +3782,14 @@ dependencies = [
|
|||
name = "roc_ident"
|
||||
version = "0.0.1"
|
||||
|
||||
[[package]]
|
||||
name = "roc_intern"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"parking_lot 0.12.1",
|
||||
"roc_collections",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "roc_late_solve"
|
||||
version = "0.0.1"
|
||||
|
@ -3796,10 +3811,11 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"bincode",
|
||||
"bumpalo",
|
||||
"clap 3.2.11",
|
||||
"clap 3.2.20",
|
||||
"iced-x86",
|
||||
"indoc",
|
||||
"mach_object",
|
||||
"memmap2 0.5.5",
|
||||
"memmap2 0.5.7",
|
||||
"object 0.29.0",
|
||||
"roc_build",
|
||||
"roc_collections",
|
||||
|
@ -3843,6 +3859,7 @@ dependencies = [
|
|||
"roc_derive",
|
||||
"roc_derive_key",
|
||||
"roc_error_macros",
|
||||
"roc_intern",
|
||||
"roc_late_solve",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
@ -3854,6 +3871,7 @@ dependencies = [
|
|||
"roc_solve_problem",
|
||||
"roc_target",
|
||||
"roc_test_utils",
|
||||
"roc_tracing",
|
||||
"roc_types",
|
||||
"roc_unify",
|
||||
"ven_pretty",
|
||||
|
@ -3877,6 +3895,7 @@ dependencies = [
|
|||
name = "roc_mono"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"bumpalo",
|
||||
"hashbrown 0.12.3",
|
||||
"roc_builtins",
|
||||
|
@ -3887,6 +3906,7 @@ dependencies = [
|
|||
"roc_derive_key",
|
||||
"roc_error_macros",
|
||||
"roc_exhaustive",
|
||||
"roc_intern",
|
||||
"roc_late_solve",
|
||||
"roc_module",
|
||||
"roc_problem",
|
||||
|
@ -3946,6 +3966,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_gen_llvm",
|
||||
"roc_intern",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
@ -3970,6 +3991,7 @@ dependencies = [
|
|||
"roc_can",
|
||||
"roc_collections",
|
||||
"roc_fmt",
|
||||
"roc_intern",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
@ -3995,6 +4017,7 @@ dependencies = [
|
|||
"roc_builtins",
|
||||
"roc_collections",
|
||||
"roc_gen_llvm",
|
||||
"roc_intern",
|
||||
"roc_load",
|
||||
"roc_module",
|
||||
"roc_mono",
|
||||
|
@ -4048,6 +4071,7 @@ dependencies = [
|
|||
"roc_collections",
|
||||
"roc_constrain",
|
||||
"roc_derive",
|
||||
"roc_error_macros",
|
||||
"roc_exhaustive",
|
||||
"roc_fmt",
|
||||
"roc_load",
|
||||
|
@ -4347,9 +4371,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.139"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6"
|
||||
checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
@ -4387,9 +4411,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.139"
|
||||
version = "1.0.144"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb"
|
||||
checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -4398,9 +4422,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.82"
|
||||
version = "1.0.85"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
|
||||
checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
|
||||
dependencies = [
|
||||
"itoa 1.0.2",
|
||||
"ryu",
|
||||
|
@ -4606,7 +4630,7 @@ dependencies = [
|
|||
"dlib",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"memmap2 0.5.5",
|
||||
"memmap2 0.5.7",
|
||||
"nix 0.24.1",
|
||||
"pkg-config",
|
||||
"wayland-client",
|
||||
|
@ -4775,9 +4799,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.98"
|
||||
version = "1.0.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd"
|
||||
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -4861,7 +4885,6 @@ version = "0.0.1"
|
|||
dependencies = [
|
||||
"bumpalo",
|
||||
"criterion",
|
||||
"either",
|
||||
"indoc",
|
||||
"inkwell 0.1.0",
|
||||
"lazy_static",
|
||||
|
@ -5294,9 +5317,9 @@ version = "0.0.1"
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
|
||||
checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
|
@ -5304,9 +5327,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
|
||||
checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"log",
|
||||
|
@ -5331,9 +5354,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
|
||||
checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
|
@ -5341,9 +5364,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
|
||||
checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
@ -5354,9 +5377,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.82"
|
||||
version = "0.2.83"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
|
||||
checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-encoder"
|
||||
|
@ -5506,7 +5529,7 @@ dependencies = [
|
|||
"enumset",
|
||||
"lazy_static",
|
||||
"loupe",
|
||||
"memmap2 0.5.5",
|
||||
"memmap2 0.5.7",
|
||||
"more-asserts",
|
||||
"rustc-demangle",
|
||||
"serde",
|
||||
|
@ -6139,3 +6162,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
|
|||
dependencies = [
|
||||
"linked-hash-map",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "yansi"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"
|
||||
|
|
|
@ -17,6 +17,7 @@ members = [
|
|||
"crates/compiler/late_solve",
|
||||
"crates/compiler/fmt",
|
||||
"crates/compiler/derive_key",
|
||||
"crates/compiler/intern",
|
||||
"crates/compiler/mono",
|
||||
"crates/compiler/alias_analysis",
|
||||
"crates/compiler/test_mono",
|
||||
|
|
78
Earthfile
78
Earthfile
|
@ -52,84 +52,6 @@ copy-dirs:
|
|||
FROM +install-zig-llvm-valgrind
|
||||
COPY --dir crates examples Cargo.toml Cargo.lock version.txt www ./
|
||||
|
||||
test-zig:
|
||||
FROM +install-zig-llvm-valgrind
|
||||
COPY --dir crates/compiler/builtins/bitcode ./
|
||||
RUN cd bitcode && ./run-tests.sh && ./run-wasm-tests.sh
|
||||
|
||||
build-rust-test:
|
||||
FROM +copy-dirs
|
||||
RUN echo "deb http://deb.debian.org/debian testing main contrib non-free" >> /etc/apt/sources.list # to get gcc 10.3
|
||||
RUN apt -y update
|
||||
RUN apt -y install gcc-10 g++-10 && rm /usr/bin/gcc && ln -s /usr/bin/gcc-10 /usr/bin/gcc # gcc-9 maybe causes segfault
|
||||
RUN gcc --version
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --features with_sound serde --workspace --no-run && sccache --show-stats
|
||||
|
||||
check-typos:
|
||||
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
|
||||
COPY --dir .github ci crates examples nightly_benches www *.md LEGAL_DETAILS flake.nix version.txt ./
|
||||
RUN typos
|
||||
|
||||
test-rust:
|
||||
FROM +build-rust-test
|
||||
ENV ROC_WORKSPACE_DIR=/earthbuild
|
||||
ENV RUST_BACKTRACE=1
|
||||
# for race condition problem with cli test
|
||||
ENV ROC_NUM_WORKERS=1
|
||||
# run one of the benchmarks to make sure the host is compiled
|
||||
# not pre-compiling the host can cause race conditions
|
||||
RUN gcc --version
|
||||
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --features with_sound serde --workspace && sccache --show-stats
|
||||
# test the dev and wasm backend: they require an explicit feature flag.
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
|
||||
# gen-wasm has some multithreading problems to do with the wasmer runtime. Run it single-threaded as a separate job
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
|
||||
# run `roc test` on Str builtins
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
cargo run --release -- test crates/compiler/builtins/roc/Str.roc && sccache --show-stats
|
||||
# repl_test: build the compiler for wasm target, then run the tests on native target
|
||||
RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
crates/repl_test/test_wasm.sh && sccache --show-stats
|
||||
# run i386 (32-bit linux) cli tests
|
||||
# NOTE: disabled until zig 0.9
|
||||
# RUN echo "4" | cargo run --locked --release --features="target-x86" -- --target=x86_32 examples/benchmarks/NQueens.roc
|
||||
# RUN --mount=type=cache,target=$SCCACHE_DIR \
|
||||
# cargo test --locked --release --features with_sound serde --test cli_run i386 --features="i386-cli-run" && sccache --show-stats
|
||||
# make sure website deployment works (that is, make sure build.sh returns status code 0)
|
||||
ENV REPL_DEBUG=1
|
||||
RUN bash www/build.sh
|
||||
|
||||
|
||||
verify-no-git-changes:
|
||||
FROM +test-rust
|
||||
# If running tests caused anything to be changed or added (without being
|
||||
# included in a .gitignore somewhere), fail the build!
|
||||
#
|
||||
# How it works: the `git ls-files` command lists all the modified or
|
||||
# uncommitted files in the working tree, the `| grep -E .` command returns a
|
||||
# zero exit code if it listed any files and nonzero otherwise (which is the
|
||||
# opposite of what we want), and the `!` at the start inverts the exit code.
|
||||
RUN ! git ls-files --deleted --modified --others --exclude-standard | grep -E .
|
||||
|
||||
test-all:
|
||||
BUILD +test-zig
|
||||
BUILD +test-rust
|
||||
BUILD +verify-no-git-changes
|
||||
|
||||
build-nightly-release:
|
||||
FROM +test-rust
|
||||
COPY --dir .git LICENSE LEGAL_DETAILS ci ./
|
||||
# version.txt is used by the CLI: roc --version
|
||||
RUN ./ci/write_version.sh
|
||||
RUN RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release
|
||||
RUN ./ci/package_release.sh roc_linux_x86_64.tar.gz
|
||||
SAVE ARTIFACT ./roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
|
||||
|
||||
# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.
|
||||
prep-bench-folder:
|
||||
FROM +copy-dirs
|
||||
|
|
104
FAQ.md
104
FAQ.md
|
@ -29,7 +29,7 @@ fantastical, and it has incredible potential for puns. Here are some different w
|
|||
|
||||
Fun fact: "roc" translates to 鹏 in Chinese, [which means](https://www.mdbg.net/chinese/dictionary?page=worddict&wdrst=0&wdqb=%E9%B9%8F) "a large fabulous bird."
|
||||
|
||||
# Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
|
||||
## Why make a new editor instead of making an LSP plugin for VSCode, Vim or Emacs?
|
||||
|
||||
The Roc editor is one of the key areas where we want to innovate. Constraining ourselves to a plugin for existing editors would severely limit our possibilities for innovation.
|
||||
|
||||
|
@ -309,56 +309,83 @@ Here are some more details about the downsides as I see them.
|
|||
|
||||
### Currying and the `|>` operator
|
||||
|
||||
In Roc, this code produces `"Hello, World!"`
|
||||
In Roc, both of these expressions evaluate to `"Hello, World!"`
|
||||
|
||||
```elm
|
||||
"Hello, World"
|
||||
|> Str.concat "!"
|
||||
```elixir
|
||||
Str.concat "Hello, " "World!"
|
||||
```
|
||||
|
||||
This is because Roc's `|>` operator uses the expression before the `|>` as the _first_ argument to the function
|
||||
after it. For functions where both arguments have the same type, but it's obvious which argument goes where (e.g.
|
||||
`Str.concat "Hello, " "World!"`, `List.concat [1, 2] [3, 4]`), this works out well. Another example would
|
||||
be `|> Num.sub 1`, which subtracts 1 from whatever came before the `|>`.
|
||||
```elixir
|
||||
"Hello, "
|
||||
|> Str.concat "World!"
|
||||
```
|
||||
|
||||
For this reason, "pipeline-friendliness" in Roc means that the first argument to each function is typically
|
||||
the one that's most likely to be built up using a pipeline. For example, `List.map`:
|
||||
In curried languages with a `|>` operator, the first expression still returns `"Hello, World!"` but the second one returns `"World!Hello, "`. This is because Roc's `|>` operator uses the expression before the `|>` as the _first_ argument, whereas in curried languages, `|>` uses it as the _last_ argument.
|
||||
|
||||
```elm
|
||||
(For example, this is how `|>` works in both [F#](https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/symbol-and-operator-reference/#function-symbols-and-operators) and in [Elm](https://package.elm-lang.org/packages/elm/core/1.0.5/Basics#|%3E), both of which are curried languages. In contrast, Roc's `|>` design uses the same argument ordering as [Elixir](https://hexdocs.pm/elixir/1.14.0/Kernel.html#%7C%3E/2) and [Gleam](https://gleam.run/book/tour/functions.html#pipe-operator), neither of which is a curried language.)
|
||||
|
||||
This comes up in other situations as well. For example, consider subtraction and division:
|
||||
|
||||
```elixir
|
||||
someNumber
|
||||
|> Num.div 2
|
||||
```
|
||||
|
||||
```elixir
|
||||
someNumber
|
||||
|> Num.sub 1
|
||||
```
|
||||
|
||||
What do you expect these expressions to do?
|
||||
|
||||
In Roc, the first divides `someNumber` by 2 and the second one subtracts 1 from `someNumber`. In languages where `|>` uses the other argument ordering, the first example instead takes 2 and divides it by `someNumber`, while the second takes 1 and subtracts `someNumber` from it. This was a pain point I ran into with curried languages, and I was pleasantly surprised that changing the argument ordering in `|>` addressed the pain point.
|
||||
|
||||
This style has a second benefit when it comes to higher-order functions. Consider these two examples:
|
||||
|
||||
```elixir
|
||||
answer = List.map numbers \num ->
|
||||
someFunction
|
||||
"some argument"
|
||||
anotherArg
|
||||
someOtherArg
|
||||
```
|
||||
|
||||
```elixir
|
||||
numbers
|
||||
|> List.map Num.abs
|
||||
```
|
||||
|
||||
This argument ordering convention also often makes it possible to pass anonymous functions to higher-order
|
||||
functions without needing parentheses, like so:
|
||||
In Roc, `List.map` takes a list and then a function. Because of the way `|>` works in Roc, `numbers |> List.map Num.abs` passes `numbers` as the first argument to `List.map`, and `Num.abs` as the second argument. So both of these examples work fine.
|
||||
|
||||
```elm
|
||||
List.map numbers \num -> Num.abs (num - 1)
|
||||
In a curried language, these two examples couldn't both be valid. In order for `|> List.map Num.abs` to work in a curried language (where `|>` works the other way), `List.map` would have to take its arguments in the opposite order: the function first and the list second.
|
||||
|
||||
This means the first example would have to change from this...
|
||||
|
||||
```elixir
|
||||
answer = List.map numbers \num ->
|
||||
someFunction
|
||||
"some argument"
|
||||
anotherArg
|
||||
someOtherArg
|
||||
```
|
||||
|
||||
(If the arguments were reversed, this would be `List.map (\num -> Num.abs (num - 1)) numbers` and the
|
||||
extra parentheses would be required.)
|
||||
...to this:
|
||||
|
||||
Neither of these benefits is compatible with the argument ordering currying encourages. Currying encourages
|
||||
`List.map` to take the `List` as its second argument instead of the first, so that you can partially apply it
|
||||
like `(List.map Num.abs)`; if Roc introduced currying but kept the order of `List.map` the same way it is today,
|
||||
then partially applying `List.map` (e.g. `(List.map numbers)`) would be much less useful than if the arguments
|
||||
were swapped - but that in turn would make it less useful with `|>` and would require parentheses when passing
|
||||
it an anonymous function.
|
||||
```elixir
|
||||
answer =
|
||||
List.map
|
||||
(\num ->
|
||||
someFunction
|
||||
"some argument"
|
||||
anotherArg
|
||||
someOtherArg
|
||||
)
|
||||
numbers
|
||||
```
|
||||
|
||||
This is a fundamental design tension. One argument order works well with `|>` (at least the way it works in Roc
|
||||
today) and with passing anonymous functions to higher-order functions, and the other works well with currying.
|
||||
It's impossible to have both.
|
||||
This was also a pain point I'd encountered in curried languages. I prefer the way the former example reads, but that style doesn't work with the argument order that currying encourages for higher-order functions like `List.map`. (Prior to using curried languages, I'd used [CoffeeScript](https://coffeescript.org/) in a functional style with [`_.map`](https://underscorejs.org/#map), and was disappointed to realize that I could no longer use the enjoyable style of `answer = _.map numbers (num) -> …` as I had before. In Roc, this style works.)
|
||||
|
||||
Of note, one possible design is to have currying while also having `|>` pass the _last_ argument instead of the first.
|
||||
This is what Elm does, and it makes pipeline-friendliness and curry-friendliness the same thing. However, it also
|
||||
means that either `|> Str.concat "!"` would add the `"!"` to the front of the string, or else `Str.concat`'s
|
||||
arguments would have to be flipped - meaning that `Str.concat "Hello, World" "!"` would evaluate to `"!Hello, World"`.
|
||||
|
||||
The only way to have `Str.concat` work the way it does in Roc today (where both pipelines and non-pipeline calling
|
||||
do what you'd want them to) is to order function arguments in a way that is not conducive to currying. This design
|
||||
tension only exists if there's currying in the language; without it, you can order arguments for pipeline-friendliness
|
||||
without concern.
|
||||
As a historical note, these stylistic benefits (of `|> Num.sub 1` working as expected, and being able to write `List.map numbers \num ->`) were not among the original reasons Roc did not have currying. These benefits were discovered after the decision had already been made that Roc would not be a curried language, and they served to reinforce after the fact that the decision was the right one for Roc given the language's goals.
|
||||
|
||||
### Currying and learning curve
|
||||
|
||||
|
@ -406,9 +433,10 @@ reverseSort = \list -> List.reverse (List.sort list)
|
|||
|
||||
I've consistently found that I can more quickly and accurately understand function definitions that use
|
||||
named arguments, even though the code is longer. I suspect this is because I'm faster at reading than I am at
|
||||
desugaring, and whenever I read the top version I end up needing to mentally desugar it into the bottom version.
|
||||
eta-expanding ( e.g. converting `List.sort` into `\l -> List.sort l` ). Whenever I read
|
||||
the top version I end up needing to mentally eta-expand it into the bottom version.
|
||||
In more complex examples (this is among the tamest pointfree function composition examples I've seen), I make
|
||||
a mistake in my mental desugaring, and misunderstand what the function is doing - which can cause bugs.
|
||||
a mistake in my mental eta-expansion, and misunderstand what the function is doing - which can cause bugs.
|
||||
|
||||
I assumed I would get faster and more accurate at this over time. However, by now it's been about a decade
|
||||
since I first learned about the technique, and I'm still slower and less accurate at reading code that uses
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
# Work in progress!
|
||||
|
||||
Roc is not ready for a 0.1 release yet, but we do have:
|
||||
|
||||
- [**installation** guide](https://github.com/roc-lang/roc/tree/main/getting_started)
|
||||
- [**tutorial**](https://github.com/roc-lang/roc/blob/main/TUTORIAL.md)
|
||||
- [some docs for the standard library](https://www.roc-lang.org/builtins/Str)
|
||||
- [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).
|
||||
|
||||
# Sponsors
|
||||
## 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).
|
||||
|
||||
|
|
139
TUTORIAL.md
139
TUTORIAL.md
|
@ -15,13 +15,13 @@ Learn how to install roc on your machine [here](https://github.com/roc-lang/roc/
|
|||
Let’s start by getting acquainted with Roc’s Read Eval Print Loop, or REPL for
|
||||
short. Run this in a terminal:
|
||||
|
||||
```
|
||||
```sh
|
||||
$ roc repl
|
||||
```
|
||||
|
||||
You should see this:
|
||||
|
||||
```
|
||||
```sh
|
||||
The rockin’ roc repl
|
||||
```
|
||||
|
||||
|
@ -131,13 +131,13 @@ main = Stdout.line "I'm a Roc application!"
|
|||
|
||||
Try running this with:
|
||||
|
||||
```
|
||||
```sh
|
||||
$ roc Hello.roc
|
||||
```
|
||||
|
||||
You should see this:
|
||||
|
||||
```
|
||||
```sh
|
||||
I'm a Roc application!
|
||||
```
|
||||
|
||||
|
@ -157,7 +157,7 @@ total = Num.toStr (birds + iguanas)
|
|||
|
||||
Now if you run `roc Hello.roc`, you should see this:
|
||||
|
||||
```
|
||||
```sh
|
||||
There are 5 animals.
|
||||
```
|
||||
|
||||
|
@ -165,7 +165,8 @@ There are 5 animals.
|
|||
short - namely, `main`, `birds`, `iguanas`, and `total`.
|
||||
|
||||
A definition names an expression.
|
||||
- The first def assigns the name `main` to the expression `Stdout.line "I have \(numDefs) definitions."`. The `Stdout.line` function takes a string and prints it as a line to [`stdout`] (the terminal's standard output device).
|
||||
|
||||
- The first def assigns the name `main` to the expression `Stdout.line "There are \(total) animals."`. The `Stdout.line` function takes a string and prints it as a line to [`stdout`] (the terminal's standard output device).
|
||||
- The next two defs assign the names `birds` and `iguanas` to the expressions `3` and `2`.
|
||||
- The last def assigns the name `total` to the expression `Num.toStr (birds + iguanas)`.
|
||||
|
||||
|
@ -231,8 +232,9 @@ addAndStringify = \num1, num2 ->
|
|||
```
|
||||
|
||||
We did two things here:
|
||||
* We introduced a local def named `sum`, and set it equal to `num1 + num2`. Because we defined `sum` inside `addAndStringify`, it will not be accessible outside that function.
|
||||
* We added an `if` / `then` / `else` conditional to return either `""` or `Num.toStr sum` depending on whether `sum == 0`.
|
||||
|
||||
- We introduced a local def named `sum`, and set it equal to `num1 + num2`. Because we defined `sum` inside `addAndStringify`, it will not be accessible outside that function.
|
||||
- We added an `if` / `then` / `else` conditional to return either `""` or `Num.toStr sum` depending on whether `sum == 0`.
|
||||
|
||||
Of note, we couldn't have done `total = num1 + num2` because that would be
|
||||
redefining `total` in the global scope, and defs can't be redefined. (However, we could use the name
|
||||
|
@ -376,17 +378,6 @@ addAndStringify = \{ birds, iguanas: lizards } ->
|
|||
In this version, we created a `lizards` def that's assigned to the record's `iguanas` field.
|
||||
(We could also do something similar with the `birds` field if we like.)
|
||||
|
||||
It's possible to destructure a record while still naming it. Here's an example where we
|
||||
use the `as` keyword to name the record `counts` while also destructuring its fields:
|
||||
|
||||
```coffee
|
||||
addAndStringify = \{ iguanas: lizards } as counts ->
|
||||
Num.toStr (counts.birds + lizards)
|
||||
```
|
||||
|
||||
Notice that here we didn't bother destructuring the `birds` field. You can always omit fields
|
||||
from a destructure if you aren't going to use them!
|
||||
|
||||
Finally, destructuring can be used in defs too:
|
||||
|
||||
```coffee
|
||||
|
@ -410,8 +401,8 @@ fromOriginal = { original & birds: 4, iguanas: 3 }
|
|||
The `fromScratch` and `fromOriginal` records are equal, although they're assembled in
|
||||
different ways.
|
||||
|
||||
* `fromScratch` was built using the same record syntax we've been using up to this point.
|
||||
* `fromOriginal` created a new record using the contents of `original` as defaults for fields that it didn't specify after the `&`.
|
||||
- `fromScratch` was built using the same record syntax we've been using up to this point.
|
||||
- `fromOriginal` created a new record using the contents of `original` as defaults for fields that it didn't specify after the `&`.
|
||||
|
||||
Note that when we do this, the fields you're overriding must all be present on the original record,
|
||||
and their values must have the same type as the corresponding values in the original record.
|
||||
|
@ -519,7 +510,7 @@ stoplightStr =
|
|||
Green | Yellow -> "not red"
|
||||
```
|
||||
|
||||
This will give the same answer for `spotlightStr` as if we had written the following:
|
||||
This will give the same answer for `stoplightStr` as if we had written the following:
|
||||
|
||||
```coffee
|
||||
stoplightStr =
|
||||
|
@ -712,6 +703,7 @@ in the list returns `True`:
|
|||
List.any [1, 2, 3] Num.isOdd
|
||||
# returns True because 1 and 3 are odd
|
||||
```
|
||||
|
||||
```coffee
|
||||
List.any [1, 2, 3] Num.isNegative
|
||||
# returns False because none of these is negative
|
||||
|
@ -723,6 +715,7 @@ There's also `List.all` which only returns `True` if all the elements in the lis
|
|||
List.all [1, 2, 3] Num.isOdd
|
||||
# returns False because 2 is not odd
|
||||
```
|
||||
|
||||
```coffee
|
||||
List.all [1, 2, 3] Num.isPositive
|
||||
# returns True because all of these are positive
|
||||
|
@ -802,6 +795,7 @@ For example, what do each of these return?
|
|||
```coffee
|
||||
List.get ["a", "b", "c"] 1
|
||||
```
|
||||
|
||||
```coffee
|
||||
List.get ["a", "b", "c"] 100
|
||||
```
|
||||
|
@ -875,6 +869,7 @@ functions where argument order matters. For example, these two uses of `List.app
|
|||
```coffee
|
||||
List.append ["a", "b", "c"] "d"
|
||||
```
|
||||
|
||||
```coffee
|
||||
["a", "b", "c"]
|
||||
|> List.append "d"
|
||||
|
@ -886,9 +881,11 @@ sugar for `Num.div a b`:
|
|||
```coffee
|
||||
first / second
|
||||
```
|
||||
|
||||
```coffee
|
||||
Num.div first second
|
||||
```
|
||||
|
||||
```coffee
|
||||
first
|
||||
|> Num.div second
|
||||
|
@ -1031,9 +1028,11 @@ What we want is something like one of these:
|
|||
```coffee
|
||||
reverse : List elem -> List elem
|
||||
```
|
||||
|
||||
```coffee
|
||||
reverse : List value -> List value
|
||||
```
|
||||
|
||||
```coffee
|
||||
reverse : List a -> List a
|
||||
```
|
||||
|
@ -1094,9 +1093,9 @@ the lowest `U16` would be zero (since it always is for unsigned integers), and t
|
|||
|
||||
Choosing a size depends on your performance needs and the range of numbers you want to represent. Consider:
|
||||
|
||||
* Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them!
|
||||
* Smaller integer sizes take up less memory. These savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck.
|
||||
* Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly!
|
||||
- Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them!
|
||||
- Smaller integer sizes take up less memory. These savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can easily be a performance bottleneck.
|
||||
- Certain processors work faster on some numeric sizes than others. There isn't even a general rule like "larger numeric sizes run slower" (or the reverse, for that matter) that applies to all processors. In fact, if the CPU is taking too long to run numeric calculations, you may find a performance improvement by experimenting with numeric sizes that are larger than otherwise necessary. However, in practice, doing this typically degrades overall performance, so be careful to measure properly!
|
||||
|
||||
Here are the different fixed-size integer types that Roc supports:
|
||||
|
||||
|
@ -1137,9 +1136,9 @@ As such, it's very important to design your integer operations not to exceed the
|
|||
|
||||
Roc has three fractional types:
|
||||
|
||||
* `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
* `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
* `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
|
||||
- `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
- `F64`, a 64-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754)
|
||||
- `Dec`, a 128-bit decimal [fixed-point number](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
|
||||
|
||||
These are different from integers in that they can represent numbers with fractional components,
|
||||
such as 1.5 and -0.123.
|
||||
|
@ -1204,18 +1203,21 @@ and also `Num.cos 1` and have them all work as expected; the number literal `1`
|
|||
you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`.
|
||||
|
||||
### Typed Number Literals
|
||||
|
||||
When writing a number literal in Roc you can specify the numeric type as a suffix of the literal.
|
||||
`1u8` specifies `1` as an unsigned 8-bit integer, `5i32` specifies `5` as a signed 32-bit integer, etc.
|
||||
The full list of possible suffixes includes:
|
||||
`i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `nat`, `f32`, `f64`, `dec`
|
||||
|
||||
### Hexadecimal Integer Literals
|
||||
|
||||
Integer literals can be written in hexadecimal form by prefixing with `0x` followed by hexadecimal characters.
|
||||
`0xFE` evaluates to decimal `254`
|
||||
The integer type can be specified as a suffix to the hexadecimal literal,
|
||||
so `0xC8u8` evaluates to decimal `200` as an unsigned 8-bit integer.
|
||||
|
||||
### Binary Integer Literals
|
||||
|
||||
Integer literals can be written in binary form by prefixing with `0b` followed by the 1's and 0's representing
|
||||
each bit. `0b0000_1000` evaluates to decimal `8`
|
||||
The integer type can be specified as a suffix to the binary literal,
|
||||
|
@ -1246,8 +1248,8 @@ Roc compiler. That's why they're called "builtins!"
|
|||
|
||||
Besides being built into the compiler, the builtin modules are different from other modules in that:
|
||||
|
||||
* They are always imported. You never need to add them to `imports`.
|
||||
* All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [Num.{ Nat }]` (and the same for all the other types in the `Num` module).
|
||||
- They are always imported. You never need to add them to `imports`.
|
||||
- All their types are imported unqualified automatically. So you never need to write `Num.Nat`, because it's as if the `Num` module was imported using `imports [Num.{ Nat }]` (and the same for all the other types in the `Num` module).
|
||||
|
||||
## The app module header
|
||||
|
||||
|
@ -1306,8 +1308,6 @@ this `imports` line tells the Roc compiler that when we call `Stdout.line`, it
|
|||
should look for that `line` function in the `Stdout` module of the
|
||||
`examples/interactive/cli-platform/main.roc` package.
|
||||
|
||||
# Building a Command-Line Interface (CLI)
|
||||
|
||||
## Tasks
|
||||
|
||||
Tasks are technically not part of the Roc language, but they're very common in
|
||||
|
@ -1315,10 +1315,10 @@ platforms. Let's use the CLI platform in `examples/interactive/cli-platform/main
|
|||
|
||||
In the CLI platform, we have four operations we can do:
|
||||
|
||||
* Write a string to the console
|
||||
* Read a string from user input
|
||||
* Write a string to a file
|
||||
* Read a string from a file
|
||||
- Write a string to the console
|
||||
- Read a string from user input
|
||||
- Write a string to a file
|
||||
- Read a string from a file
|
||||
|
||||
We'll use these four operations to learn about tasks.
|
||||
|
||||
|
@ -1524,22 +1524,24 @@ main =
|
|||
```
|
||||
|
||||
This way, it reads like a series of instructions:
|
||||
|
||||
1. First, run the `Stdout.line` task and await its completion. Ignore its output (hence the underscore in `_ <-`)
|
||||
2. Next, run the `Stdin.line` task and await its completion. Name its output `text`.
|
||||
3. Finally, run the `Stdout.line` task again, using the `text` value we got from the `Stdin.line` effect.
|
||||
|
||||
Some important things to note about backpassing and `await`:
|
||||
* `await` is not a language keyword in Roc! It's referring to the `Task.await` function, which we imported unqualified by writing `Task.{ await }` in our module imports. (That said, it is playing a similar role here to the `await` keyword in languages that have `async`/`await` keywords, even though in this case it's a function instead of a special keyword.)
|
||||
* Backpassing syntax does not need to be used with `await` in particular. It can be used with any function.
|
||||
* Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you!
|
||||
|
||||
# Appendix: Advanced Concepts
|
||||
- `await` is not a language keyword in Roc! It's referring to the `Task.await` function, which we imported unqualified by writing `Task.{ await }` in our module imports. (That said, it is playing a similar role here to the `await` keyword in languages that have `async`/`await` keywords, even though in this case it's a function instead of a special keyword.)
|
||||
- Backpassing syntax does not need to be used with `await` in particular. It can be used with any function.
|
||||
- Roc's compiler treats functions defined with backpassing exactly the same way as functions defined the other way. The only difference between `\text ->` and `text <-` is how they look, so feel free to use whichever looks nicer to you!
|
||||
|
||||
## Appendix: Advanced Concepts
|
||||
|
||||
Here are some concepts you likely won't need as a beginner, but may want to know about eventually.
|
||||
This is listed as an appendix rather than the main tutorial, to emphasize that it's totally fine
|
||||
to stop reading here and go build things!
|
||||
|
||||
## Open Records and Closed Records
|
||||
### Open Records and Closed Records
|
||||
|
||||
Let's say I write a function which takes a record with a `firstName`
|
||||
and `lastName` field, and puts them together with a space in between:
|
||||
|
@ -1553,9 +1555,9 @@ I can pass this function a record that has more fields than just
|
|||
`firstName` and `lastName`, as long as it has *at least* both of those fields
|
||||
(and both of them are strings). So any of these calls would work:
|
||||
|
||||
* `fullName { firstName: "Sam", lastName: "Sample" }`
|
||||
* `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }`
|
||||
* `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }`
|
||||
- `fullName { firstName: "Sam", lastName: "Sample" }`
|
||||
- `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }`
|
||||
- `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }`
|
||||
|
||||
This `user` argument is an *open record* - that is, a description of a minimum set of fields
|
||||
on a record, and their types. When a function takes an open record as an argument,
|
||||
|
@ -1597,7 +1599,7 @@ a closed record by putting a `{}` as the type variable (so for example, `{ email
|
|||
`{ email : Str }`). In practice, closed records are basically always written without the `{}` on the end,
|
||||
but later on we'll see a situation where putting types other than `*` in that spot can be useful.
|
||||
|
||||
## Constrained Records
|
||||
### Constrained Records
|
||||
|
||||
The type variable can also be a named type variable, like so:
|
||||
|
||||
|
@ -1608,9 +1610,10 @@ addHttps = \record ->
|
|||
```
|
||||
|
||||
This function uses *constrained records* in its type. The annotation is saying:
|
||||
* This function takes a record which has at least a `url` field, and possibly others
|
||||
* That `url` field has the type `Str`
|
||||
* It returns a record of exactly the same type as the one it was given
|
||||
|
||||
- This function takes a record which has at least a `url` field, and possibly others
|
||||
- That `url` field has the type `Str`
|
||||
- It returns a record of exactly the same type as the one it was given
|
||||
|
||||
So if we give this function a record with five fields, it will return a record with those
|
||||
same five fields. The only requirement is that one of those fields must be `url : Str`.
|
||||
|
@ -1632,7 +1635,7 @@ field of that record. So if you passed it a record that was not guaranteed to ha
|
|||
present (such as an `{ a : Str, b : Bool }*` record, which only guarantees that the fields `a` and `b` are present),
|
||||
the function might try to access a `c` field at runtime that did not exist!
|
||||
|
||||
## Type Variables in Record Annotations
|
||||
### Type Variables in Record Annotations
|
||||
|
||||
You can add type annotations to make record types less flexible than what the compiler infers, but not more
|
||||
flexible. For example, you can use an annotation to tell the compiler to treat a record as closed when it would
|
||||
|
@ -1750,7 +1753,7 @@ prevent the compiler from raising an error that would have revealed the mistake.
|
|||
That said, this is a useful technique to know about if you want to (for example) make a record
|
||||
type that accumulates more and more fields as it progresses through a series of operations.
|
||||
|
||||
## Open and Closed Tag Unions
|
||||
### Open and Closed Tag Unions
|
||||
|
||||
Just like how Roc has open records and closed records, it also has open and closed tag unions.
|
||||
|
||||
|
@ -1802,7 +1805,7 @@ the implementation actually handles.
|
|||
> accept `example : [Foo Str, Bar Bool] -> Bool` as the type annotation, even though the catch-all branch
|
||||
> would permit the more flexible `example : [Foo Str, Bar Bool]* -> Bool` annotation instead.
|
||||
|
||||
## Combining Open Unions
|
||||
### Combining Open Unions
|
||||
|
||||
When we make a new record, it's inferred to be a closed record. For example, in `foo { a: "hi" }`,
|
||||
the type of `{ a: "hi" }` is inferred to be `{ a : Str }`. In contrast, when we make a new tag, it's inferred
|
||||
|
@ -1847,14 +1850,14 @@ the tags in the open union you're providing.
|
|||
|
||||
So if I have an `[Ok Str]*` value, I can pass it to functions with any of these types (among others):
|
||||
|
||||
* `[Ok Str]* -> Bool`
|
||||
* `[Ok Str] -> Bool`
|
||||
* `[Ok Str, Err Bool]* -> Bool`
|
||||
* `[Ok Str, Err Bool] -> Bool`
|
||||
* `[Ok Str, Err Bool, Whatever]* -> Bool`
|
||||
* `[Ok Str, Err Bool, Whatever] -> Bool`
|
||||
* `Result Str Bool -> Bool`
|
||||
* `[Err Bool, Whatever]* -> Bool`
|
||||
- `[Ok Str]* -> Bool`
|
||||
- `[Ok Str] -> Bool`
|
||||
- `[Ok Str, Err Bool]* -> Bool`
|
||||
- `[Ok Str, Err Bool] -> Bool`
|
||||
- `[Ok Str, Err Bool, Whatever]* -> Bool`
|
||||
- `[Ok Str, Err Bool, Whatever] -> Bool`
|
||||
- `Result Str Bool -> Bool`
|
||||
- `[Err Bool, Whatever]* -> Bool`
|
||||
|
||||
That last one works because a function accepting an open union can accept any unrecognized tag, including
|
||||
`Ok Str` - even though it is not mentioned as one of the tags in `[Err Bool, Whatever]*`! Remember, when
|
||||
|
@ -1878,12 +1881,12 @@ a catch-all `_ ->` branch, it might not know what to do with an `Ok Str` if it r
|
|||
|
||||
In summary, here's a way to think about the difference between open unions in a value you have, compared to a value you're accepting:
|
||||
|
||||
* If you *have* a closed union, that means it has all the tags it ever will, and can't accumulate more.
|
||||
* If you *have* an open union, that means it can accumulate more tags through conditional branches.
|
||||
* If you *accept* a closed union, that means you only have to handle the possibilities listed in the union.
|
||||
* If you *accept* an open union, that means you have to handle the possibility that it has a tag you can't know about.
|
||||
- If you *have* a closed union, that means it has all the tags it ever will, and can't accumulate more.
|
||||
- If you *have* an open union, that means it can accumulate more tags through conditional branches.
|
||||
- If you *accept* a closed union, that means you only have to handle the possibilities listed in the union.
|
||||
- If you *accept* an open union, that means you have to handle the possibility that it has a tag you can't know about.
|
||||
|
||||
## Type Variables in Tag Unions
|
||||
### Type Variables in Tag Unions
|
||||
|
||||
Earlier we saw these two examples, one with an open tag union and the other with a closed one:
|
||||
|
||||
|
@ -1929,8 +1932,8 @@ the `Foo Str` and `Bar Bool` we already know about).
|
|||
If we removed the type annotation from `example` above, Roc's compiler would infer the same type anyway.
|
||||
This may be surprising if you look closely at the body of the function, because:
|
||||
|
||||
* The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[Bar Bool]a` instead?
|
||||
* The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it?
|
||||
- The return type includes `Foo Str`, but no branch explicitly returns `Foo`. Couldn't the return type be `[Bar Bool]a` instead?
|
||||
- The argument type includes `Bar Bool` even though we never look at `Bar`'s payload. Couldn't the argument type be inferred to be `Bar *` instead of `Bar Bool`, since we never look at it?
|
||||
|
||||
The reason it has this type is the `other -> other` branch. Take a look at that branch, and ask this question:
|
||||
"What is the type of `other`?" There has to be exactly one answer! It can't be the case that `other` has one
|
||||
|
@ -1947,11 +1950,11 @@ be equivalent.
|
|||
> Also just like with records, you can use this to compose tag union type aliases. For example, you can write
|
||||
> `NetworkError : [Timeout, Disconnected]` and then `Problem : [InvalidInput, UnknownFormat]NetworkError`
|
||||
|
||||
## Phantom Types
|
||||
### Phantom Types
|
||||
|
||||
[This part of the tutorial has not been written yet. Coming soon!]
|
||||
|
||||
## Operator Desugaring Table
|
||||
### Operator Desugaring Table
|
||||
|
||||
Here are various Roc expressions involving operators, and what they desugar to.
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# assumes roc_releases.json is present
|
||||
|
||||
LATEST_RELEASE_URL=`cat roc_releases.json | jq --arg today $(date +'%Y-%m-%d') '.[0] | .assets | map(.browser_download_url) | map(select(. | contains("silicon-\($today)"))) | .[0]'`
|
||||
LATEST_RELEASE_URL=`cat roc_releases.json | jq --arg arch $1 --arg today $(date +'%Y-%m-%d') '.[0] | .assets | map(.browser_download_url) | map(select(. | contains("\($arch)-\($today)"))) | .[0]'`
|
||||
|
||||
if [[ "$LATEST_RELEASE_URL" == "null" ]]
|
||||
then
|
||||
|
|
|
@ -22,11 +22,11 @@ roc_target = { path = "../compiler/roc_target" }
|
|||
roc_error_macros = { path = "../error_macros" }
|
||||
roc_reporting = { path = "../reporting" }
|
||||
arrayvec = "0.7.2"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
page_size = "0.4.2"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
ven_graph = { path = "../vendor/pathfinding" }
|
||||
libc = "0.2.106"
|
||||
libc = "0.2.132"
|
||||
|
||||
[dev-dependencies]
|
||||
indoc = "1.0.7"
|
||||
|
|
|
@ -24,7 +24,7 @@ editor = ["roc_editor"]
|
|||
|
||||
run-wasm32 = ["wasmer", "wasmer-wasi"]
|
||||
|
||||
# Compiling for a different platform than the host can cause linker errors.
|
||||
# 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-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"]
|
||||
|
@ -60,11 +60,11 @@ roc_editor = { path = "../editor", optional = true }
|
|||
roc_linker = { path = "../linker" }
|
||||
roc_repl_cli = { path = "../repl_cli", optional = true }
|
||||
roc_tracing = { path = "../tracing" }
|
||||
clap = { version = "3.1.15", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
clap = { version = "3.2.20", default-features = false, features = ["std", "color", "suggestions"] }
|
||||
const_format = { version = "0.2.23", features = ["const_generics"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
mimalloc = { version = "0.1.26", default-features = false }
|
||||
libc = "0.2.106"
|
||||
libc = "0.2.132"
|
||||
errno = "0.2.8"
|
||||
ven_pretty = { path = "../vendor/pretty" }
|
||||
|
||||
|
@ -93,7 +93,7 @@ wasmer = { version = "2.2.1", optional = true, default-features = false, feature
|
|||
|
||||
[dev-dependencies]
|
||||
wasmer-wasi = "2.2.1"
|
||||
pretty_assertions = "1.0.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
roc_test_utils = { path = "../test_utils" }
|
||||
indoc = "1.0.7"
|
||||
serial_test = "0.9.0"
|
||||
|
@ -101,6 +101,8 @@ criterion = { git = "https://github.com/Anton-4/criterion.rs"}
|
|||
cli_utils = { path = "../cli_utils" }
|
||||
strum = "0.24.0"
|
||||
strum_macros = "0.24"
|
||||
once_cell = "1.14.0"
|
||||
parking_lot = "0.12"
|
||||
|
||||
# Wasmer singlepass compiler only works on x86_64.
|
||||
[target.'cfg(target_arch = "x86_64")'.dev-dependencies]
|
||||
|
|
|
@ -2,16 +2,19 @@
|
|||
# Running the benchmarks
|
||||
|
||||
Install cargo criterion:
|
||||
```
|
||||
|
||||
```sh
|
||||
cargo install cargo-criterion
|
||||
```
|
||||
|
||||
To prevent stack overflow on the `CFold` benchmark:
|
||||
```
|
||||
|
||||
```sh
|
||||
ulimit -s unlimited
|
||||
```
|
||||
|
||||
In the `cli` folder execute:
|
||||
```
|
||||
|
||||
```sh
|
||||
cargo criterion
|
||||
```
|
|
@ -65,7 +65,7 @@ pub fn build_file<'a>(
|
|||
emit_timings: bool,
|
||||
link_type: LinkType,
|
||||
linking_strategy: LinkingStrategy,
|
||||
precompiled: bool,
|
||||
prebuilt: bool,
|
||||
threading: Threading,
|
||||
wasm_dev_stack_bytes: Option<u32>,
|
||||
order: BuildOrdering,
|
||||
|
@ -151,7 +151,7 @@ pub fn build_file<'a>(
|
|||
// 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 precompiled hosts from there.
|
||||
// a package repository, as we can then get prebuilt platforms from there.
|
||||
|
||||
let exposed_values = loaded
|
||||
.exposed_to_host
|
||||
|
@ -182,7 +182,7 @@ pub fn build_file<'a>(
|
|||
let rebuild_thread = spawn_rebuild_thread(
|
||||
opt_level,
|
||||
linking_strategy,
|
||||
precompiled,
|
||||
prebuilt,
|
||||
host_input_path.clone(),
|
||||
preprocessed_host_path.clone(),
|
||||
binary_path.clone(),
|
||||
|
@ -191,8 +191,6 @@ pub fn build_file<'a>(
|
|||
exposed_closure_types,
|
||||
);
|
||||
|
||||
// TODO try to move as much of this linking as possible to the precompiled
|
||||
// host, to minimize the amount of host-application linking required.
|
||||
let app_o_file = Builder::new()
|
||||
.prefix("roc_app")
|
||||
.suffix(&format!(".{}", app_extension))
|
||||
|
@ -261,9 +259,9 @@ pub fn build_file<'a>(
|
|||
|
||||
let rebuild_timing = if linking_strategy == LinkingStrategy::Additive {
|
||||
let rebuild_duration = rebuild_thread.join().unwrap();
|
||||
if emit_timings && !precompiled {
|
||||
if emit_timings && !prebuilt {
|
||||
println!(
|
||||
"Finished rebuilding and preprocessing the host in {} ms\n",
|
||||
"Finished rebuilding the platform in {} ms\n",
|
||||
rebuild_duration
|
||||
);
|
||||
}
|
||||
|
@ -322,15 +320,15 @@ pub fn build_file<'a>(
|
|||
|
||||
if let HostRebuildTiming::ConcurrentWithApp(thread) = rebuild_timing {
|
||||
let rebuild_duration = thread.join().unwrap();
|
||||
if emit_timings && !precompiled {
|
||||
if emit_timings && !prebuilt {
|
||||
println!(
|
||||
"Finished rebuilding and preprocessing the host in {} ms\n",
|
||||
"Finished rebuilding the platform in {} ms\n",
|
||||
rebuild_duration
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: link the precompiled host and compiled app
|
||||
// Step 2: link the prebuilt platform and compiled app
|
||||
let link_start = Instant::now();
|
||||
let problems = match (linking_strategy, link_type) {
|
||||
(LinkingStrategy::Surgical, _) => {
|
||||
|
@ -397,7 +395,7 @@ pub fn build_file<'a>(
|
|||
fn spawn_rebuild_thread(
|
||||
opt_level: OptLevel,
|
||||
linking_strategy: LinkingStrategy,
|
||||
precompiled: bool,
|
||||
prebuilt: bool,
|
||||
host_input_path: PathBuf,
|
||||
preprocessed_host_path: PathBuf,
|
||||
binary_path: PathBuf,
|
||||
|
@ -407,13 +405,16 @@ fn spawn_rebuild_thread(
|
|||
) -> std::thread::JoinHandle<u128> {
|
||||
let thread_local_target = target.clone();
|
||||
std::thread::spawn(move || {
|
||||
if !precompiled {
|
||||
println!("🔨 Rebuilding host...");
|
||||
if !prebuilt {
|
||||
// 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();
|
||||
|
||||
if !precompiled {
|
||||
if !prebuilt {
|
||||
match linking_strategy {
|
||||
LinkingStrategy::Additive => {
|
||||
let host_dest = rebuild_host(
|
||||
|
|
|
@ -55,8 +55,7 @@ fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf>
|
|||
}
|
||||
|
||||
fn is_roc_file(path: &Path) -> bool {
|
||||
let ext = path.extension().and_then(OsStr::to_str);
|
||||
return matches!(ext, Some("roc"));
|
||||
matches!(path.extension().and_then(OsStr::to_str), Some("roc"))
|
||||
}
|
||||
|
||||
pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), String> {
|
||||
|
@ -101,11 +100,11 @@ pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), Str
|
|||
|
||||
let mut before_file = file.clone();
|
||||
before_file.set_extension("roc-format-failed-ast-before");
|
||||
std::fs::write(&before_file, &format!("{:#?}\n", ast)).unwrap();
|
||||
std::fs::write(&before_file, &format!("{:#?}\n", ast_normalized)).unwrap();
|
||||
|
||||
let mut after_file = file.clone();
|
||||
after_file.set_extension("roc-format-failed-ast-after");
|
||||
std::fs::write(&after_file, &format!("{:#?}\n", reparsed_ast)).unwrap();
|
||||
std::fs::write(&after_file, &format!("{:#?}\n", reparsed_ast_normalized)).unwrap();
|
||||
|
||||
internal_error!(
|
||||
"Formatting bug; formatting didn't reparse as the same tree\n\n\
|
||||
|
|
|
@ -13,6 +13,7 @@ use roc_mono::ir::OptLevel;
|
|||
use std::env;
|
||||
use std::ffi::{CString, OsStr};
|
||||
use std::io;
|
||||
use std::mem::ManuallyDrop;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
@ -53,7 +54,7 @@ pub const FLAG_NO_LINK: &str = "no-link";
|
|||
pub const FLAG_TARGET: &str = "target";
|
||||
pub const FLAG_TIME: &str = "time";
|
||||
pub const FLAG_LINKER: &str = "linker";
|
||||
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
|
||||
pub const FLAG_PREBUILT: &str = "prebuilt-platform";
|
||||
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";
|
||||
|
@ -67,51 +68,51 @@ const VERSION: &str = include_str!("../../../version.txt");
|
|||
pub fn build_app<'a>() -> Command<'a> {
|
||||
let flag_optimize = Arg::new(FLAG_OPTIMIZE)
|
||||
.long(FLAG_OPTIMIZE)
|
||||
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
|
||||
.help("Optimize the compiled program to run faster\n(Optimization takes time to complete.)")
|
||||
.required(false);
|
||||
|
||||
let flag_max_threads = Arg::new(FLAG_MAX_THREADS)
|
||||
.long(FLAG_MAX_THREADS)
|
||||
.help("Limit the number of threads (and hence cores) used during compilation.")
|
||||
.help("Limit the number of threads (and hence cores) used during compilation")
|
||||
.takes_value(true)
|
||||
.validator(|s| s.parse::<usize>())
|
||||
.required(false);
|
||||
|
||||
let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
|
||||
.long(FLAG_OPT_SIZE)
|
||||
.help("Optimize the compiled program to have a small binary size. (Optimization takes time to complete.)")
|
||||
.help("Optimize the compiled program to have a small binary size\n(Optimization takes time to complete.)")
|
||||
.required(false);
|
||||
|
||||
let flag_dev = Arg::new(FLAG_DEV)
|
||||
.long(FLAG_DEV)
|
||||
.help("Make compilation finish as soon as possible, at the expense of runtime performance.")
|
||||
.help("Make compilation finish as soon as possible, at the expense of runtime performance")
|
||||
.required(false);
|
||||
|
||||
let flag_debug = Arg::new(FLAG_DEBUG)
|
||||
.long(FLAG_DEBUG)
|
||||
.help("Store LLVM debug information in the generated program.")
|
||||
.help("Store LLVM debug information in the generated program")
|
||||
.required(false);
|
||||
|
||||
let flag_time = Arg::new(FLAG_TIME)
|
||||
.long(FLAG_TIME)
|
||||
.help("Prints detailed compilation time information.")
|
||||
.help("Print detailed compilation time information")
|
||||
.required(false);
|
||||
|
||||
let flag_linker = Arg::new(FLAG_LINKER)
|
||||
.long(FLAG_LINKER)
|
||||
.help("Sets which linker to use. The surgical linker is enabled by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.")
|
||||
.help("Set which linker to use\n(The surgical linker is enabled by default only when building for wasm32 or x86_64 Linux, because those are the only targets it currently supports. Otherwise the legacy linker is used by default.)")
|
||||
.possible_values(["surgical", "legacy"])
|
||||
.required(false);
|
||||
|
||||
let flag_precompiled = Arg::new(FLAG_PRECOMPILED)
|
||||
.long(FLAG_PRECOMPILED)
|
||||
.help("Assumes the host has been precompiled and skips recompiling the host. (Enabled by default when using `roc build` with a --target other than `--target host`)")
|
||||
let flag_prebuilt = Arg::new(FLAG_PREBUILT)
|
||||
.long(FLAG_PREBUILT)
|
||||
.help("Assume the platform has been prebuilt and skip rebuilding the platform\n(This is enabled by default when using `roc build` with a --target other than `--target <current machine>`.)")
|
||||
.possible_values(["true", "false"])
|
||||
.required(false);
|
||||
|
||||
let flag_wasm_stack_size_kb = Arg::new(FLAG_WASM_STACK_SIZE_KB)
|
||||
.long(FLAG_WASM_STACK_SIZE_KB)
|
||||
.help("Stack size in kilobytes for wasm32 target. Only applies when --dev also provided.")
|
||||
.help("Stack size in kilobytes for wasm32 target\n(This only applies when --dev also provided.)")
|
||||
.takes_value(true)
|
||||
.validator(|s| s.parse::<u32>())
|
||||
.required(false);
|
||||
|
@ -123,7 +124,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.default_value(DEFAULT_ROC_FILENAME);
|
||||
|
||||
let args_for_app = Arg::new(ARGS_FOR_APP)
|
||||
.help("Arguments to pass into the app being run, e.g. `roc run -- arg1 arg2`")
|
||||
.help("Arguments to pass into the app being run\ne.g. `roc run -- arg1 arg2`")
|
||||
.allow_invalid_utf8(true)
|
||||
.multiple_values(true)
|
||||
.takes_value(true)
|
||||
|
@ -132,7 +133,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
|
||||
let app = Command::new("roc")
|
||||
.version(concatcp!(VERSION, "\n"))
|
||||
.about("Runs the given .roc file, if there are no compilation errors.\nUse one of the SUBCOMMANDS below to do something else!")
|
||||
.about("Run the given .roc file, if there are no compilation errors.\nYou can use one of the SUBCOMMANDS below to do something else!")
|
||||
.subcommand(Command::new(CMD_BUILD)
|
||||
.about("Build a binary from the given .roc file, but don't run it")
|
||||
.arg(flag_optimize.clone())
|
||||
|
@ -142,7 +143,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_debug.clone())
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_precompiled.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(flag_wasm_stack_size_kb.clone())
|
||||
.arg(
|
||||
Arg::new(FLAG_TARGET)
|
||||
|
@ -155,13 +156,13 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(
|
||||
Arg::new(FLAG_LIB)
|
||||
.long(FLAG_LIB)
|
||||
.help("Build a C library instead of an executable.")
|
||||
.help("Build a C library instead of an executable")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::new(FLAG_NO_LINK)
|
||||
.long(FLAG_NO_LINK)
|
||||
.help("Does not link. Instead just outputs the `.o` file")
|
||||
.help("Do not link\n(Instead, just output the `.o` file.)")
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
|
@ -173,7 +174,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
)
|
||||
)
|
||||
.subcommand(Command::new(CMD_TEST)
|
||||
.about("Run all top-level `expect`s in a main module and any modules it imports.")
|
||||
.about("Run all top-level `expect`s in a main module and any modules it imports")
|
||||
.arg(flag_optimize.clone())
|
||||
.arg(flag_max_threads.clone())
|
||||
.arg(flag_opt_size.clone())
|
||||
|
@ -181,7 +182,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_debug.clone())
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_precompiled.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(
|
||||
Arg::new(ROC_FILE)
|
||||
.help("The .roc file for the main module")
|
||||
|
@ -203,12 +204,12 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_debug.clone())
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_precompiled.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(roc_file_to_run.clone())
|
||||
.arg(args_for_app.clone())
|
||||
)
|
||||
.subcommand(Command::new(CMD_DEV)
|
||||
.about("`check` a .roc file, and then run it if there were no errors.")
|
||||
.about("`check` a .roc file, and then run it if there were no errors")
|
||||
.arg(flag_optimize.clone())
|
||||
.arg(flag_max_threads.clone())
|
||||
.arg(flag_opt_size.clone())
|
||||
|
@ -216,7 +217,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_debug.clone())
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_linker.clone())
|
||||
.arg(flag_precompiled.clone())
|
||||
.arg(flag_prebuilt.clone())
|
||||
.arg(roc_file_to_run.clone())
|
||||
.arg(args_for_app.clone())
|
||||
)
|
||||
|
@ -231,14 +232,14 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(
|
||||
Arg::new(FLAG_CHECK)
|
||||
.long(FLAG_CHECK)
|
||||
.help("Checks that specified files are formatted. If formatting is needed, it will return a non-zero exit code.")
|
||||
.help("Checks that specified files are formatted\n(If formatting is needed, return a non-zero exit code.)")
|
||||
.required(false),
|
||||
)
|
||||
)
|
||||
.subcommand(Command::new(CMD_VERSION)
|
||||
.about(concatcp!("Print the Roc compiler’s version, which is currently ", VERSION)))
|
||||
.subcommand(Command::new(CMD_CHECK)
|
||||
.about("Check the code for problems, but doesn’t build or run it")
|
||||
.about("Check the code for problems, but don’t build or run it")
|
||||
.arg(flag_time.clone())
|
||||
.arg(flag_max_threads.clone())
|
||||
.arg(
|
||||
|
@ -260,7 +261,7 @@ 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.")
|
||||
.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")
|
||||
|
@ -269,7 +270,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
)
|
||||
.arg(
|
||||
Arg::new(GLUE_FILE)
|
||||
.help("The filename for the generated glue code. Currently, this must be a .rs file because only Rust glue generation is supported so far.")
|
||||
.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.)")
|
||||
.allow_invalid_utf8(true)
|
||||
.required(true)
|
||||
)
|
||||
|
@ -282,7 +283,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
.arg(flag_debug)
|
||||
.arg(flag_time)
|
||||
.arg(flag_linker)
|
||||
.arg(flag_precompiled)
|
||||
.arg(flag_prebuilt)
|
||||
.arg(roc_file_to_run.required(false))
|
||||
.arg(args_for_app);
|
||||
|
||||
|
@ -294,7 +295,7 @@ pub fn build_app<'a>() -> Command<'a> {
|
|||
Arg::new(DIRECTORY_OR_FILES)
|
||||
.multiple_values(true)
|
||||
.required(false)
|
||||
.help("(optional) The directory or files to open on launch."),
|
||||
.help("(optional) The directory or files to open on launch"),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
|
@ -396,7 +397,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
|
||||
let interns = loaded.interns.clone();
|
||||
|
||||
let (lib, expects) = roc_repl_expect::run::expect_mono_module_to_dylib(
|
||||
let (lib, expects, layout_interner) = roc_repl_expect::run::expect_mono_module_to_dylib(
|
||||
arena,
|
||||
target.clone(),
|
||||
loaded,
|
||||
|
@ -415,6 +416,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
|
|||
roc_reporting::report::RenderTarget::ColorTerminal,
|
||||
arena,
|
||||
interns,
|
||||
&layout_interner.into_global(),
|
||||
&lib,
|
||||
&mut expectations,
|
||||
expects,
|
||||
|
@ -497,11 +499,11 @@ pub fn build(
|
|||
LinkingStrategy::Surgical
|
||||
};
|
||||
|
||||
let precompiled = if matches.is_present(FLAG_PRECOMPILED) {
|
||||
matches.value_of(FLAG_PRECOMPILED) == Some("true")
|
||||
let prebuilt = if matches.is_present(FLAG_PREBUILT) {
|
||||
matches.value_of(FLAG_PREBUILT) == Some("true")
|
||||
} else {
|
||||
// When compiling for a different target, default to assuming a precompiled host.
|
||||
// Otherwise compilation would most likely fail because many toolchains assume you're compiling for the host
|
||||
// When compiling for a different target, default to assuming a prebuilt platform.
|
||||
// Otherwise compilation would most likely fail because many toolchains assume you're compiling for the current machine.
|
||||
// We make an exception for Wasm, because cross-compiling is the norm in that case.
|
||||
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
|
||||
};
|
||||
|
@ -545,7 +547,7 @@ pub fn build(
|
|||
emit_timings,
|
||||
link_type,
|
||||
linking_strategy,
|
||||
precompiled,
|
||||
prebuilt,
|
||||
threading,
|
||||
wasm_dev_stack_bytes,
|
||||
build_ordering,
|
||||
|
@ -634,14 +636,14 @@ pub fn build(
|
|||
|
||||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||||
|
||||
let mut bytes = std::fs::read(&binary_path).unwrap();
|
||||
let bytes = std::fs::read(&binary_path).unwrap();
|
||||
|
||||
let x = roc_run(
|
||||
arena,
|
||||
opt_level,
|
||||
triple,
|
||||
args,
|
||||
&mut bytes,
|
||||
&bytes,
|
||||
expectations,
|
||||
interns,
|
||||
);
|
||||
|
@ -669,19 +671,10 @@ pub fn build(
|
|||
|
||||
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
|
||||
|
||||
let mut bytes = std::fs::read(&binary_path).unwrap();
|
||||
// ManuallyDrop will leak the bytes because we don't drop manually
|
||||
let bytes = &ManuallyDrop::new(std::fs::read(&binary_path).unwrap());
|
||||
|
||||
let x = roc_run(
|
||||
arena,
|
||||
opt_level,
|
||||
triple,
|
||||
args,
|
||||
&mut bytes,
|
||||
expectations,
|
||||
interns,
|
||||
);
|
||||
std::mem::forget(bytes);
|
||||
x
|
||||
roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -746,7 +739,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
|
|||
opt_level: OptLevel,
|
||||
triple: Triple,
|
||||
args: I,
|
||||
binary_bytes: &mut [u8],
|
||||
binary_bytes: &[u8],
|
||||
expectations: VecMap<ModuleId, Expectations>,
|
||||
interns: Interns,
|
||||
) -> io::Result<i32> {
|
||||
|
@ -839,7 +832,7 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
|||
arena: Bump,
|
||||
opt_level: OptLevel,
|
||||
args: I,
|
||||
binary_bytes: &mut [u8],
|
||||
binary_bytes: &[u8],
|
||||
expectations: VecMap<ModuleId, Expectations>,
|
||||
interns: Interns,
|
||||
) -> std::io::Result<i32> {
|
||||
|
@ -952,7 +945,7 @@ unsafe fn roc_run_native_debug(
|
|||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<ExecutableFile> {
|
||||
fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<ExecutableFile> {
|
||||
// on linux, we use the `memfd_create` function to create an in-memory anonymous file.
|
||||
let flags = 0;
|
||||
let anonymous_file_name = "roc_file_descriptor\0";
|
||||
|
@ -974,7 +967,7 @@ fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<Exec
|
|||
}
|
||||
|
||||
#[cfg(all(target_family = "unix", not(target_os = "linux")))]
|
||||
fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<ExecutableFile> {
|
||||
fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<ExecutableFile> {
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
use std::os::unix::fs::OpenOptionsExt;
|
||||
|
@ -1000,7 +993,7 @@ fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<Exec
|
|||
}
|
||||
|
||||
#[cfg(all(target_family = "windows"))]
|
||||
fn roc_run_executable_file_path(binary_bytes: &mut [u8]) -> std::io::Result<ExecutableFile> {
|
||||
fn roc_run_executable_file_path(binary_bytes: &[u8]) -> std::io::Result<ExecutableFile> {
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Write;
|
||||
|
||||
|
@ -1030,7 +1023,7 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
|
|||
arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
|
||||
opt_level: OptLevel,
|
||||
_args: I,
|
||||
binary_bytes: &mut [u8],
|
||||
binary_bytes: &[u8],
|
||||
_expectations: VecMap<ModuleId, Expectations>,
|
||||
_interns: Interns,
|
||||
) -> io::Result<i32> {
|
||||
|
|
|
@ -16,23 +16,37 @@ mod cli_run {
|
|||
};
|
||||
use const_format::concatcp;
|
||||
use indoc::indoc;
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use roc_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN};
|
||||
use roc_test_utils::assert_multiline_str_eq;
|
||||
use serial_test::serial;
|
||||
use std::iter;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Once;
|
||||
use strum::IntoEnumIterator;
|
||||
use strum_macros::EnumIter;
|
||||
|
||||
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
|
||||
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER);
|
||||
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK);
|
||||
const PRECOMPILED_HOST: &str = concatcp!("--", roc_cli::FLAG_PRECOMPILED, "=true");
|
||||
const PREBUILT_PLATFORM: &str = concatcp!("--", roc_cli::FLAG_PREBUILT, "=true");
|
||||
#[allow(dead_code)]
|
||||
const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET);
|
||||
|
||||
use std::sync::Once;
|
||||
static BENCHMARKS_BUILD_PLATFORM: Once = Once::new();
|
||||
static POPULATED_EXAMPLE_LOCKS: Once = Once::new();
|
||||
|
||||
use std::collections::HashMap;
|
||||
static EXAMPLE_PLATFORM_LOCKS: Lazy<RwLock<HashMap<PathBuf, Mutex<()>>>> =
|
||||
once_cell::sync::Lazy::new(|| RwLock::new(HashMap::default()));
|
||||
|
||||
fn populate_example_locks(examples: impl Iterator<Item = PathBuf>) {
|
||||
let mut locks = EXAMPLE_PLATFORM_LOCKS.write();
|
||||
for example in examples {
|
||||
locks.insert(example, Default::default());
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, EnumIter)]
|
||||
enum CliMode {
|
||||
|
@ -61,12 +75,18 @@ mod cli_run {
|
|||
#[cfg(target_os = "macos")]
|
||||
const ALLOW_VALGRIND: bool = false;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum Arg<'a> {
|
||||
ExamplePath(&'a str),
|
||||
PlainText(&'a str),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
struct Example<'a> {
|
||||
filename: &'a str,
|
||||
executable_filename: &'a str,
|
||||
stdin: &'a [&'a str],
|
||||
input_file: Option<&'a str>,
|
||||
arguments: &'a [Arg<'a>],
|
||||
expected_ending: &'a str,
|
||||
use_valgrind: bool,
|
||||
}
|
||||
|
@ -93,33 +113,22 @@ mod cli_run {
|
|||
file: &'a Path,
|
||||
args: I,
|
||||
stdin: &[&str],
|
||||
opt_input_file: Option<PathBuf>,
|
||||
app_args: &[String],
|
||||
) -> Out {
|
||||
let compile_out = if let Some(input_file) = opt_input_file {
|
||||
run_roc(
|
||||
let compile_out = run_roc(
|
||||
// converting these all to String avoids lifetime issues
|
||||
args.into_iter().map(|arg| arg.to_string()).chain([
|
||||
file.to_str().unwrap().to_string(),
|
||||
"--".to_string(),
|
||||
input_file.to_str().unwrap().to_string(),
|
||||
]),
|
||||
args.into_iter()
|
||||
.map(|arg| arg.to_string())
|
||||
.chain([file.to_str().unwrap().to_string(), "--".to_string()])
|
||||
.chain(app_args.iter().cloned()),
|
||||
stdin,
|
||||
)
|
||||
} else {
|
||||
run_roc(
|
||||
args.into_iter().chain(iter::once(file.to_str().unwrap())),
|
||||
stdin,
|
||||
)
|
||||
};
|
||||
|
||||
// If there is any stderr, it should be reporting the runtime and that's it!
|
||||
if !(compile_out.stderr.is_empty()
|
||||
|| compile_out.stderr.starts_with("runtime: ") && compile_out.stderr.ends_with("ms\n"))
|
||||
{
|
||||
panic!(
|
||||
"`roc` command had unexpected stderr: {}",
|
||||
compile_out.stderr
|
||||
);
|
||||
|
||||
let ignorable = "🔨 Rebuilding platform...\n";
|
||||
let stderr = compile_out.stderr.replacen(ignorable, "", 1);
|
||||
let is_reporting_runtime = stderr.starts_with("runtime: ") && stderr.ends_with("ms\n");
|
||||
if !(stderr.is_empty() || is_reporting_runtime) {
|
||||
panic!("`roc` command had unexpected stderr: {}", stderr);
|
||||
}
|
||||
|
||||
assert!(compile_out.status.success(), "bad status {:?}", compile_out);
|
||||
|
@ -132,7 +141,7 @@ mod cli_run {
|
|||
stdin: &[&str],
|
||||
executable_filename: &str,
|
||||
flags: &[&str],
|
||||
opt_input_file: Option<PathBuf>,
|
||||
app_args: &[String],
|
||||
expected_ending: &str,
|
||||
use_valgrind: bool,
|
||||
) {
|
||||
|
@ -154,24 +163,17 @@ mod cli_run {
|
|||
|
||||
let out = match cli_mode {
|
||||
CliMode::RocBuild => {
|
||||
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], None);
|
||||
run_roc_on(file, iter::once(CMD_BUILD).chain(flags.clone()), &[], &[]);
|
||||
|
||||
if use_valgrind && ALLOW_VALGRIND {
|
||||
let (valgrind_out, raw_xml) = if let Some(ref input_file) = opt_input_file {
|
||||
run_with_valgrind(
|
||||
stdin.iter().copied(),
|
||||
&[
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
input_file.clone().to_str().unwrap(),
|
||||
],
|
||||
)
|
||||
} else {
|
||||
run_with_valgrind(
|
||||
stdin.iter().copied(),
|
||||
&[file.with_file_name(executable_filename).to_str().unwrap()],
|
||||
)
|
||||
};
|
||||
|
||||
let mut valgrind_args = vec![file
|
||||
.with_file_name(executable_filename)
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string()];
|
||||
valgrind_args.extend(app_args.iter().cloned());
|
||||
let (valgrind_out, raw_xml) =
|
||||
run_with_valgrind(stdin.iter().copied(), &valgrind_args);
|
||||
if valgrind_out.status.success() {
|
||||
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| {
|
||||
panic!("failed to parse the `valgrind` xml output. Error was:\n\n{:?}\n\nvalgrind xml was: \"{}\"\n\nvalgrind stdout was: \"{}\"\n\nvalgrind stderr was: \"{}\"", err, raw_xml, valgrind_out.stdout, valgrind_out.stderr);
|
||||
|
@ -207,26 +209,20 @@ mod cli_run {
|
|||
}
|
||||
|
||||
valgrind_out
|
||||
} else if let Some(ref input_file) = opt_input_file {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin.iter().copied(),
|
||||
&[input_file.to_str().unwrap()],
|
||||
)
|
||||
} else {
|
||||
run_cmd(
|
||||
file.with_file_name(executable_filename).to_str().unwrap(),
|
||||
stdin.iter().copied(),
|
||||
&[],
|
||||
app_args,
|
||||
)
|
||||
}
|
||||
}
|
||||
CliMode::Roc => run_roc_on(file, flags.clone(), stdin, opt_input_file.clone()),
|
||||
CliMode::Roc => run_roc_on(file, flags.clone(), stdin, app_args),
|
||||
CliMode::RocRun => run_roc_on(
|
||||
file,
|
||||
iter::once(CMD_RUN).chain(flags.clone()),
|
||||
stdin,
|
||||
opt_input_file.clone(),
|
||||
app_args,
|
||||
),
|
||||
};
|
||||
|
||||
|
@ -247,10 +243,10 @@ mod cli_run {
|
|||
stdin: &[&str],
|
||||
executable_filename: &str,
|
||||
flags: &[&str],
|
||||
input_file: Option<PathBuf>,
|
||||
args: &[&Arg],
|
||||
expected_ending: &str,
|
||||
) {
|
||||
assert_eq!(input_file, None, "Wasm does not support input files");
|
||||
assert!(input_paths.is_empty(), "Wasm does not support input files");
|
||||
let mut flags = flags.to_vec();
|
||||
flags.push(concatcp!(TARGET_FLAG, "=wasm32"));
|
||||
|
||||
|
@ -289,20 +285,42 @@ mod cli_run {
|
|||
/// add a test for it here!
|
||||
macro_rules! examples {
|
||||
($($test_name:ident:$name:expr => $example:expr,)+) => {
|
||||
static EXAMPLE_NAMES: &[&str] = &[$($name,)+];
|
||||
|
||||
$(
|
||||
#[test]
|
||||
#[allow(non_snake_case)]
|
||||
fn $test_name() {
|
||||
POPULATED_EXAMPLE_LOCKS.call_once( || {
|
||||
populate_example_locks(EXAMPLE_NAMES.iter().map(|name| examples_dir(name)))
|
||||
});
|
||||
|
||||
let dir_name = $name;
|
||||
let example = $example;
|
||||
let example_dir = examples_dir(dir_name);
|
||||
let file_name = example_file(dir_name, example.filename);
|
||||
|
||||
let mut app_args: Vec<String> = vec![];
|
||||
for arg in example.arguments {
|
||||
match arg {
|
||||
Arg::ExamplePath(file) => {
|
||||
app_args.push(example_file(dir_name, file).to_str().unwrap().to_string());
|
||||
}
|
||||
Arg::PlainText(arg) => {
|
||||
app_args.push(arg.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// workaround for surgical linker issue, see PR #3990
|
||||
let mut custom_flags : Vec<&str> = vec![];
|
||||
|
||||
match example.executable_filename {
|
||||
"form" | "hello-gui" | "breakout" | "ruby" => {
|
||||
// Since these require things the build system often doesn't have
|
||||
// (e.g. GUIs open a window, Ruby needs ruby installed, WASM needs a browser)
|
||||
// we do `roc build` on them but don't run them.
|
||||
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], None);
|
||||
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], &[]);
|
||||
return;
|
||||
}
|
||||
"swiftui" | "rocLovesSwift" => {
|
||||
|
@ -319,20 +337,35 @@ mod cli_run {
|
|||
eprintln!("WARNING: skipping testing example {} because it only works in a browser!", example.filename);
|
||||
return;
|
||||
}
|
||||
"args" => {
|
||||
custom_flags = vec![LINKER_FLAG, "legacy"];
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
// To avoid concurrent examples tests overwriting produced host binaries, lock
|
||||
// on the example's directory, so that only one example per directory runs at a
|
||||
// time.
|
||||
// NOTE: we are assuming that each example corresponds to one platform, under
|
||||
// the subdirectory. This is not necessarily true, and moreover is too
|
||||
// restrictive. To increase throughput we only need to lock the produced host
|
||||
// file, however, it is not trivial to recover what that file is today (without
|
||||
// enumerating all examples and their platforms).
|
||||
let locks = EXAMPLE_PLATFORM_LOCKS.read();
|
||||
let _example_guard = locks.get(&example_dir).unwrap().lock();
|
||||
|
||||
// Check with and without optimizations
|
||||
check_output_with_stdin(
|
||||
&file_name,
|
||||
example.stdin,
|
||||
example.executable_filename,
|
||||
&[],
|
||||
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
|
||||
&custom_flags,
|
||||
&app_args,
|
||||
example.expected_ending,
|
||||
example.use_valgrind,
|
||||
);
|
||||
|
||||
custom_flags.push(OPTIMIZE_FLAG);
|
||||
// This is mostly because the false interpreter is still very slow -
|
||||
// 25s for the cli tests is just not acceptable during development!
|
||||
#[cfg(not(debug_assertions))]
|
||||
|
@ -340,8 +373,8 @@ mod cli_run {
|
|||
&file_name,
|
||||
example.stdin,
|
||||
example.executable_filename,
|
||||
&[OPTIMIZE_FLAG],
|
||||
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
|
||||
&custom_flags,
|
||||
&app_args,
|
||||
example.expected_ending,
|
||||
example.use_valgrind,
|
||||
);
|
||||
|
@ -354,7 +387,7 @@ mod cli_run {
|
|||
example.stdin,
|
||||
example.executable_filename,
|
||||
&[LINKER_FLAG, "legacy"],
|
||||
example.input_file.and_then(|file| Some(example_file(dir_name, file))),
|
||||
&app_args,
|
||||
example.expected_ending,
|
||||
example.use_valgrind,
|
||||
);
|
||||
|
@ -391,7 +424,7 @@ mod cli_run {
|
|||
filename: "main.roc",
|
||||
executable_filename: "helloWorld",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"Hello, World!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -399,50 +432,50 @@ mod cli_run {
|
|||
filename: "main.roc",
|
||||
executable_filename: "rocLovesPlatforms",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"Which platform am I running on now?\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
// We exclude the C platforming switching example
|
||||
// because the main platform switching example runs the c platform.
|
||||
// If we don't a race condition leads to test flakiness.
|
||||
// platformSwitchingC:"platform-switching/c-platform" => Example {
|
||||
// platformSwitchingC:"platform-switching" => Example {
|
||||
// filename: "rocLovesC.roc",
|
||||
// executable_filename: "rocLovesC",
|
||||
// stdin: &[],
|
||||
// input_file: None,
|
||||
// arguments: &[],
|
||||
// expected_ending:"Roc <3 C!\n",
|
||||
// use_valgrind: true,
|
||||
// },
|
||||
platformSwitchingRust:"platform-switching/rust-platform" => Example {
|
||||
platformSwitchingRust:"platform-switching" => Example {
|
||||
filename: "rocLovesRust.roc",
|
||||
executable_filename: "rocLovesRust",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"Roc <3 Rust!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
platformSwitchingSwift:"platform-switching/swift-platform" => Example {
|
||||
platformSwitchingSwift:"platform-switching" => Example {
|
||||
filename: "rocLovesSwift.roc",
|
||||
executable_filename: "rocLovesSwift",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"Roc <3 Swift!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
platformSwitchingWebAssembly:"platform-switching/web-assembly-platform" => Example {
|
||||
platformSwitchingWebAssembly:"platform-switching" => Example {
|
||||
filename: "rocLovesWebAssembly.roc",
|
||||
executable_filename: "rocLovesWebAssembly",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"Roc <3 Web Assembly!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
platformSwitchingZig:"platform-switching/zig-platform" => Example {
|
||||
platformSwitchingZig:"platform-switching" => Example {
|
||||
filename: "rocLovesZig.roc",
|
||||
executable_filename: "rocLovesZig",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"Roc <3 Zig!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -450,7 +483,7 @@ mod cli_run {
|
|||
filename: "main.roc",
|
||||
executable_filename: "libhello",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -458,7 +491,7 @@ mod cli_run {
|
|||
filename: "fibonacci.roc",
|
||||
executable_filename: "fibonacci",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending:"55\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -466,7 +499,7 @@ mod cli_run {
|
|||
filename: "Hello.roc",
|
||||
executable_filename: "hello-gui",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "",
|
||||
use_valgrind: false,
|
||||
},
|
||||
|
@ -474,7 +507,7 @@ mod cli_run {
|
|||
filename: "breakout.roc",
|
||||
executable_filename: "breakout",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "",
|
||||
use_valgrind: false,
|
||||
},
|
||||
|
@ -482,7 +515,7 @@ mod cli_run {
|
|||
filename: "quicksort.roc",
|
||||
executable_filename: "quicksort",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -490,31 +523,39 @@ mod cli_run {
|
|||
// filename: "Quicksort.roc",
|
||||
// executable_filename: "quicksort",
|
||||
// stdin: &[],
|
||||
// input_file: None,
|
||||
// arguments: &[],
|
||||
// expected_ending: "[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2]\n",
|
||||
// use_valgrind: true,
|
||||
// },
|
||||
cli_args:"interactive" => Example {
|
||||
filename: "args.roc",
|
||||
executable_filename: "args",
|
||||
stdin: &[],
|
||||
arguments: &[Arg::PlainText("log"), Arg::PlainText("-b"), Arg::PlainText("3"), Arg::PlainText("--num"), Arg::PlainText("81")],
|
||||
expected_ending: "4\n",
|
||||
use_valgrind: false,
|
||||
},
|
||||
effects:"interactive" => Example {
|
||||
filename: "effects.roc",
|
||||
executable_filename: "effects",
|
||||
stdin: &["hi there!"],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "hi there!\nIt is known\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
// tea:"tea" => Example {
|
||||
// tui_tea:"tea" => Example {
|
||||
// filename: "Main.roc",
|
||||
// executable_filename: "tea-example",
|
||||
// stdin: &[],
|
||||
// input_file: None,
|
||||
// arguments: &[],
|
||||
// expected_ending: "",
|
||||
// use_valgrind: true,
|
||||
// },
|
||||
cli:"interactive" => Example {
|
||||
cli_form:"interactive" => Example {
|
||||
filename: "form.roc",
|
||||
executable_filename: "form",
|
||||
stdin: &["Giovanni\n", "Giorgio\n"],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "Hi, Giovanni Giorgio! 👋\n",
|
||||
use_valgrind: false,
|
||||
},
|
||||
|
@ -522,7 +563,7 @@ mod cli_run {
|
|||
filename: "tui.roc",
|
||||
executable_filename: "tui",
|
||||
stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "Hello Worldfoo!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -530,7 +571,7 @@ mod cli_run {
|
|||
// filename: "Main.roc",
|
||||
// executable_filename: "custom-malloc-example",
|
||||
// stdin: &[],
|
||||
// input_file: None,
|
||||
// arguments: &[],
|
||||
// expected_ending: "ms!\nThe list was small!\n",
|
||||
// use_valgrind: true,
|
||||
// },
|
||||
|
@ -538,7 +579,7 @@ mod cli_run {
|
|||
// filename: "Main.roc",
|
||||
// executable_filename: "task-example",
|
||||
// stdin: &[],
|
||||
// input_file: None,
|
||||
// arguments: &[],
|
||||
// expected_ending: "successfully wrote to file\n",
|
||||
// use_valgrind: true,
|
||||
// },
|
||||
|
@ -547,7 +588,7 @@ mod cli_run {
|
|||
filename: "False.roc",
|
||||
executable_filename: "false",
|
||||
stdin: &[],
|
||||
input_file: Some("examples/hello.false"),
|
||||
arguments: &[Arg::ExamplePath("examples/hello.false")],
|
||||
expected_ending:"Hello, World!\n",
|
||||
use_valgrind: false,
|
||||
}
|
||||
|
@ -560,6 +601,16 @@ mod cli_run {
|
|||
expected_ending: "",
|
||||
use_valgrind: false,
|
||||
},
|
||||
static_site_gen: "static-site-gen" => {
|
||||
Example {
|
||||
filename: "static-site.roc",
|
||||
executable_filename: "static-site",
|
||||
stdin: &[],
|
||||
arguments: &[Arg::ExamplePath("input"), Arg::ExamplePath("output")],
|
||||
expected_ending: "Processed 3 files with 3 successes and 0 errors\n",
|
||||
use_valgrind: false,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
macro_rules! benchmarks {
|
||||
|
@ -584,6 +635,18 @@ mod cli_run {
|
|||
|
||||
let mut ran_without_optimizations = false;
|
||||
|
||||
let mut app_args: Vec<String> = vec![];
|
||||
for arg in benchmark.arguments {
|
||||
match arg {
|
||||
Arg::ExamplePath(file) => {
|
||||
app_args.push(examples_dir("benchmarks").join(file).to_str().unwrap().to_string());
|
||||
}
|
||||
Arg::PlainText(arg) => {
|
||||
app_args.push(arg.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BENCHMARKS_BUILD_PLATFORM.call_once( || {
|
||||
// Check with and without optimizations
|
||||
check_output_with_stdin(
|
||||
|
@ -591,7 +654,7 @@ mod cli_run {
|
|||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&[],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
&app_args,
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
);
|
||||
|
@ -599,8 +662,8 @@ mod cli_run {
|
|||
ran_without_optimizations = true;
|
||||
});
|
||||
|
||||
// now we can pass the `PRECOMPILED_HOST` flag, because the `call_once` will
|
||||
// have compiled the host
|
||||
// now we can pass the `PREBUILT_PLATFORM` flag, because the
|
||||
// `call_once` will have built the platform
|
||||
|
||||
if !ran_without_optimizations {
|
||||
// Check with and without optimizations
|
||||
|
@ -608,8 +671,8 @@ mod cli_run {
|
|||
&file_name,
|
||||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&[PRECOMPILED_HOST],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
&[PREBUILT_PLATFORM],
|
||||
&app_args,
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
);
|
||||
|
@ -619,8 +682,8 @@ mod cli_run {
|
|||
&file_name,
|
||||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&[PRECOMPILED_HOST, OPTIMIZE_FLAG],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
&[PREBUILT_PLATFORM, OPTIMIZE_FLAG],
|
||||
&app_args,
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
);
|
||||
|
@ -653,7 +716,7 @@ mod cli_run {
|
|||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&[],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.input_paths.iter().map(|file| examples_dir("benchmarks").join(file)),
|
||||
benchmark.expected_ending,
|
||||
);
|
||||
|
||||
|
@ -662,7 +725,7 @@ mod cli_run {
|
|||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
&[OPTIMIZE_FLAG],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.input_paths.iter().map(|file| examples_dir("benchmarks").join(file)),
|
||||
benchmark.expected_ending,
|
||||
);
|
||||
}
|
||||
|
@ -694,7 +757,7 @@ mod cli_run {
|
|||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
[concatcp!(TARGET_FLAG, "=x86_32")],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.input_paths.iter().map(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
);
|
||||
|
@ -704,7 +767,7 @@ mod cli_run {
|
|||
benchmark.stdin,
|
||||
benchmark.executable_filename,
|
||||
[concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG],
|
||||
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.input_paths.iter().map(|file| Some(examples_dir("benchmarks").join(file))),
|
||||
benchmark.expected_ending,
|
||||
benchmark.use_valgrind,
|
||||
);
|
||||
|
@ -733,7 +796,7 @@ mod cli_run {
|
|||
filename: "NQueens.roc",
|
||||
executable_filename: "nqueens",
|
||||
stdin: &["6"],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "4\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -741,7 +804,7 @@ mod cli_run {
|
|||
filename: "CFold.roc",
|
||||
executable_filename: "cfold",
|
||||
stdin: &["3"],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "11 & 11\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -749,7 +812,7 @@ mod cli_run {
|
|||
filename: "Deriv.roc",
|
||||
executable_filename: "deriv",
|
||||
stdin: &["2"],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "1 count: 6\n2 count: 22\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -757,7 +820,7 @@ mod cli_run {
|
|||
filename: "RBTreeCk.roc",
|
||||
executable_filename: "rbtree-ck",
|
||||
stdin: &["100"],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "10\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -765,7 +828,7 @@ mod cli_run {
|
|||
filename: "RBTreeInsert.roc",
|
||||
executable_filename: "rbtree-insert",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "Node Black 0 {} Empty Empty\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -773,7 +836,7 @@ mod cli_run {
|
|||
// filename: "RBTreeDel.roc",
|
||||
// executable_filename: "rbtree-del",
|
||||
// stdin: &["420"],
|
||||
// input_file: None,
|
||||
// arguments: &[],
|
||||
// expected_ending: "30\n",
|
||||
// use_valgrind: true,
|
||||
// },
|
||||
|
@ -781,7 +844,7 @@ mod cli_run {
|
|||
filename: "TestAStar.roc",
|
||||
executable_filename: "test-astar",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "True\n",
|
||||
use_valgrind: false,
|
||||
},
|
||||
|
@ -789,7 +852,7 @@ mod cli_run {
|
|||
filename: "TestBase64.roc",
|
||||
executable_filename: "test-base64",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -797,7 +860,7 @@ mod cli_run {
|
|||
filename: "Closure.roc",
|
||||
executable_filename: "closure",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "",
|
||||
use_valgrind: false,
|
||||
},
|
||||
|
@ -805,7 +868,7 @@ mod cli_run {
|
|||
filename: "Issue2279.roc",
|
||||
executable_filename: "issue2279",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "Hello, world!\n",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -813,7 +876,7 @@ mod cli_run {
|
|||
filename: "QuicksortApp.roc",
|
||||
executable_filename: "quicksortapp",
|
||||
stdin: &[],
|
||||
input_file: None,
|
||||
arguments: &[],
|
||||
expected_ending: "todo put the correct quicksort answer here",
|
||||
use_valgrind: true,
|
||||
},
|
||||
|
@ -834,25 +897,6 @@ mod cli_run {
|
|||
if entry.file_type().unwrap().is_dir() {
|
||||
let example_dir_name = entry.file_name().into_string().unwrap();
|
||||
|
||||
// TODO: Improve this with a more-dynamic approach. (Read all subdirectories?)
|
||||
// Some platform-switching examples live in nested directories
|
||||
if example_dir_name == "platform-switching" {
|
||||
for sub_dir in [
|
||||
// We exclude the C platforming switching example
|
||||
// because the main platform switching example runs the c platform.
|
||||
// If we don't a race condition leads to test flakiness.
|
||||
// "c-platform",
|
||||
"rust-platform",
|
||||
"swift-platform",
|
||||
"web-assembly-platform",
|
||||
"zig-platform",
|
||||
] {
|
||||
all_examples.remove(format!("{}/{}", example_dir_name, sub_dir).as_str()).unwrap_or_else(|| {
|
||||
panic!("The example directory {}/{}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", examples_dir, example_dir_name, sub_dir);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// We test benchmarks separately
|
||||
if example_dir_name != "benchmarks" {
|
||||
all_examples.remove(example_dir_name.as_str()).unwrap_or_else(|| {
|
||||
|
@ -912,7 +956,7 @@ mod cli_run {
|
|||
&[],
|
||||
"multi-dep-str",
|
||||
&[],
|
||||
None,
|
||||
&[],
|
||||
"I am Dep2.str2\n",
|
||||
true,
|
||||
);
|
||||
|
@ -926,7 +970,7 @@ mod cli_run {
|
|||
&[],
|
||||
"multi-dep-str",
|
||||
&[OPTIMIZE_FLAG],
|
||||
None,
|
||||
&[],
|
||||
"I am Dep2.str2\n",
|
||||
true,
|
||||
);
|
||||
|
@ -940,7 +984,7 @@ mod cli_run {
|
|||
&[],
|
||||
"multi-dep-thunk",
|
||||
&[],
|
||||
None,
|
||||
&[],
|
||||
"I am Dep2.value2\n",
|
||||
true,
|
||||
);
|
||||
|
@ -954,7 +998,7 @@ mod cli_run {
|
|||
&[],
|
||||
"multi-dep-thunk",
|
||||
&[OPTIMIZE_FLAG],
|
||||
None,
|
||||
&[],
|
||||
"I am Dep2.value2\n",
|
||||
true,
|
||||
);
|
||||
|
@ -967,23 +1011,50 @@ mod cli_run {
|
|||
&[],
|
||||
indoc!(
|
||||
r#"
|
||||
── UNRECOGNIZED NAME ─────────────────────────── tests/known_bad/TypeError.roc ─
|
||||
── TYPE MISMATCH ─ ...d/../../../../examples/interactive/cli-platform/main.roc ─
|
||||
|
||||
Nothing is named `d` in this scope.
|
||||
Something is off with the type annotation of the main required symbol:
|
||||
|
||||
10│ _ <- await (line d)
|
||||
^
|
||||
2│ requires {} { main : InternalProgram }
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Did you mean one of these?
|
||||
This #UserApp.main value is a:
|
||||
|
||||
U8
|
||||
Ok
|
||||
I8
|
||||
F64
|
||||
Task.Task {} * [Write [Stdout]*]* ?
|
||||
|
||||
But the type annotation on main says it should be:
|
||||
|
||||
InternalProgram.InternalProgram ?
|
||||
|
||||
Tip: Type comparisons between an opaque type are only ever equal if
|
||||
both types are the same opaque type. Did you mean to create an opaque
|
||||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||||
an instance of this opaque type by doing @Age 23.
|
||||
|
||||
|
||||
── TYPE MISMATCH ─ ...d/../../../../examples/interactive/cli-platform/main.roc ─
|
||||
|
||||
This 1st argument to toEffect has an unexpected type:
|
||||
|
||||
9│ mainForHost = InternalProgram.toEffect main
|
||||
^^^^
|
||||
|
||||
This #UserApp.main value is a:
|
||||
|
||||
Task.Task {} * [Write [Stdout]*]* ?
|
||||
|
||||
But toEffect needs its 1st argument to be:
|
||||
|
||||
InternalProgram.InternalProgram ?
|
||||
|
||||
Tip: Type comparisons between an opaque type are only ever equal if
|
||||
both types are the same opaque type. Did you mean to create an opaque
|
||||
type by wrapping it? If I have an opaque type Age := U32 I can create
|
||||
an instance of this opaque type by doing @Age 23.
|
||||
|
||||
────────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
1 error and 0 warnings found in <ignored for test> ms."#
|
||||
2 errors and 1 warning found in <ignored for test> ms."#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
app "type-error"
|
||||
packages { pf: "../../../../examples/interactive/cli-platform/main.roc" }
|
||||
imports [pf.Stdout.{ line }, pf.Task.{ await }]
|
||||
imports [pf.Stdout.{ line }, pf.Task.{ await }, pf.Program]
|
||||
provides [main] to pf
|
||||
|
||||
main =
|
||||
_ <- await (line "a")
|
||||
_ <- await (line "b")
|
||||
_ <- await (line "c")
|
||||
_ <- await (line d)
|
||||
_ <- await (line "d")
|
||||
line "e"
|
||||
# Type mismatch because this line is missing:
|
||||
# |> Program.quick
|
||||
|
|
|
@ -18,7 +18,7 @@ fn exec_bench_w_input<T: Measurement>(
|
|||
&[stdin_str],
|
||||
);
|
||||
|
||||
if !compile_out.stderr.is_empty() {
|
||||
if !compile_out.stderr.is_empty() && compile_out.stderr != "🔨 Rebuilding platform...\n" {
|
||||
panic!("{}", compile_out.stderr);
|
||||
}
|
||||
|
||||
|
|
|
@ -164,7 +164,7 @@ where
|
|||
pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>>(
|
||||
cmd_name: &str,
|
||||
stdin_vals: I,
|
||||
args: &[&str],
|
||||
args: &[String],
|
||||
) -> Out {
|
||||
let mut cmd = Command::new(cmd_name);
|
||||
|
||||
|
@ -202,7 +202,7 @@ pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>>(
|
|||
|
||||
pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
|
||||
stdin_vals: I,
|
||||
args: &[&str],
|
||||
args: &[String],
|
||||
) -> (Out, String) {
|
||||
//TODO: figure out if there is a better way to get the valgrind executable.
|
||||
let mut cmd = Command::new("valgrind");
|
||||
|
@ -365,7 +365,7 @@ pub fn examples_dir(dir_name: &str) -> PathBuf {
|
|||
|
||||
// Descend into examples/{dir_name}
|
||||
path.push("examples");
|
||||
path.extend(dir_name.split("/")); // Make slashes cross-platform
|
||||
path.extend(dir_name.split("/")); // Make slashes cross-target
|
||||
|
||||
path
|
||||
}
|
||||
|
@ -388,7 +388,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-platform
|
||||
path.extend(dir_name.split("/")); // Make slashes cross-target
|
||||
|
||||
path
|
||||
}
|
||||
|
|
|
@ -10,8 +10,8 @@ description = "Our own markup language for Roc code. Used by the editor and the
|
|||
roc_ast = { path = "../ast" }
|
||||
roc_module = { path = "../compiler/module" }
|
||||
roc_utils = { path = "../utils" }
|
||||
serde = { version = "1.0.130", features = ["derive"] }
|
||||
serde = { version = "1.0.144", features = ["derive"] }
|
||||
palette = "0.6.1"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
itertools = "0.10.1"
|
||||
|
|
|
@ -114,7 +114,7 @@ pub fn new_assign_mn(
|
|||
|
||||
pub fn new_module_name_mn_id(mn_ids: Vec<MarkNodeId>, mark_node_pool: &mut SlowPool) -> MarkNodeId {
|
||||
if mn_ids.len() == 1 {
|
||||
*mn_ids.get(0).unwrap() // safe because we checked the length before
|
||||
*mn_ids.first().unwrap() // safe because we checked the length before
|
||||
} else {
|
||||
let nested_node = make_nested_mn(mn_ids, 0);
|
||||
mark_node_pool.add(nested_node)
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# The Roc Compiler
|
||||
|
||||
Here's how the compiler is laid out.
|
||||
|
||||
# Parsing
|
||||
## Parsing
|
||||
|
||||
The main goal of parsing is to take a plain old String (such as the contents a .roc source file read from the filesystem) and translate that String into an `Expr` value.
|
||||
|
||||
|
@ -45,7 +47,7 @@ This is gibberish to the parser, so it will produce an error rather than an `Exp
|
|||
|
||||
Roc's parser is implemented using the [`marwes/combine`](http://github.com/marwes/combine-language/) crate.
|
||||
|
||||
# Evaluating
|
||||
## Evaluating
|
||||
|
||||
One of the useful things we can do with an `Expr` is to evaluate it.
|
||||
|
||||
|
@ -123,7 +125,7 @@ If a function is "small enough" it's probably worth inlining too.
|
|||
|
||||
## Fusion
|
||||
|
||||
https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/deforestation-short-cut.pdf
|
||||
<https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/deforestation-short-cut.pdf>
|
||||
|
||||
Basic approach:
|
||||
|
||||
|
@ -139,9 +141,9 @@ Advanced approach:
|
|||
Express operations like map and filter in terms of toStream and fromStream, to unlock more deforestation.
|
||||
More info on here:
|
||||
|
||||
https://wiki.haskell.org/GHC_optimisations#Fusion
|
||||
<https://wiki.haskell.org/GHC_optimisations#Fusion>
|
||||
|
||||
# Getting started with the code
|
||||
## Getting started with the code
|
||||
|
||||
The compiler contains a lot of code! If you're new to the project it can be hard to know where to start. It's useful to have some sort of "main entry point", or at least a "good place to start" for each of the main phases.
|
||||
|
||||
|
@ -172,7 +174,7 @@ ask the compiler to emit debug information during various stages of compilation.
|
|||
|
||||
There are some goals for more sophisticated debugging tools:
|
||||
|
||||
- A nicer unification debugger, see https://github.com/roc-lang/roc/issues/2486.
|
||||
- A nicer unification debugger, see <https://github.com/roc-lang/roc/issues/2486>.
|
||||
Any interest in helping out here is greatly appreciated.
|
||||
|
||||
### General Tips
|
||||
|
|
|
@ -12,7 +12,9 @@ use roc_mono::ir::{
|
|||
Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal,
|
||||
ModifyRc, OptLevel, Proc, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{Builtin, CapturesNiche, Layout, RawFunctionLayout, UnionLayout};
|
||||
use roc_mono::layout::{
|
||||
Builtin, CapturesNiche, Layout, RawFunctionLayout, STLayoutInterner, UnionLayout,
|
||||
};
|
||||
|
||||
// just using one module for now
|
||||
pub const MOD_APP: ModName = ModName(b"UserApp");
|
||||
|
@ -130,6 +132,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
|
|||
}
|
||||
|
||||
pub fn spec_program<'a, I>(
|
||||
interner: &STLayoutInterner,
|
||||
opt_level: OptLevel,
|
||||
opt_entry_point: Option<roc_mono::ir::EntryPoint<'a>>,
|
||||
procs: I,
|
||||
|
@ -214,7 +217,7 @@ where
|
|||
);
|
||||
}
|
||||
|
||||
let (spec, type_names) = proc_spec(proc)?;
|
||||
let (spec, type_names) = proc_spec(interner, proc)?;
|
||||
|
||||
type_definitions.extend(type_names);
|
||||
|
||||
|
@ -231,8 +234,12 @@ where
|
|||
);
|
||||
let roc_main = FuncName(&roc_main_bytes);
|
||||
|
||||
let entry_point_function =
|
||||
build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?;
|
||||
let entry_point_function = build_entry_point(
|
||||
interner,
|
||||
entry_point.layout,
|
||||
roc_main,
|
||||
&host_exposed_functions,
|
||||
)?;
|
||||
let entry_point_name = FuncName(ENTRY_POINT_NAME);
|
||||
m.add_func(entry_point_name, entry_point_function)?;
|
||||
}
|
||||
|
@ -243,7 +250,7 @@ where
|
|||
|
||||
let mut builder = TypeDefBuilder::new();
|
||||
|
||||
let variant_types = recursive_variant_types(&mut builder, &union_layout)?;
|
||||
let variant_types = recursive_variant_types(&mut builder, interner, &union_layout)?;
|
||||
let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout {
|
||||
debug_assert_eq!(variant_types.len(), 1);
|
||||
variant_types[0]
|
||||
|
@ -301,6 +308,7 @@ fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId)
|
|||
}
|
||||
|
||||
fn build_entry_point(
|
||||
interner: &STLayoutInterner,
|
||||
layout: roc_mono::ir::ProcLayout,
|
||||
func_name: FuncName,
|
||||
host_exposed_functions: &[([u8; SIZE], &[Layout])],
|
||||
|
@ -314,8 +322,12 @@ fn build_entry_point(
|
|||
let block = builder.add_block();
|
||||
|
||||
// to the modelling language, the arguments appear out of thin air
|
||||
let argument_type =
|
||||
build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?;
|
||||
let argument_type = build_tuple_type(
|
||||
&mut builder,
|
||||
interner,
|
||||
layout.arguments,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
// does not make any assumptions about the input
|
||||
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
|
||||
|
@ -346,6 +358,7 @@ fn build_entry_point(
|
|||
|
||||
let type_id = layout_spec(
|
||||
&mut builder,
|
||||
interner,
|
||||
&Layout::struct_no_name_order(layouts),
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
@ -371,7 +384,10 @@ fn build_entry_point(
|
|||
Ok(spec)
|
||||
}
|
||||
|
||||
fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)> {
|
||||
fn proc_spec<'a>(
|
||||
interner: &STLayoutInterner<'a>,
|
||||
proc: &Proc<'a>,
|
||||
) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)> {
|
||||
let mut builder = FuncDefBuilder::new();
|
||||
let mut env = Env::default();
|
||||
|
||||
|
@ -386,15 +402,28 @@ fn proc_spec<'a>(proc: &Proc<'a>) -> Result<(FuncDef, MutSet<UnionLayout<'a>>)>
|
|||
argument_layouts.push(*layout);
|
||||
}
|
||||
|
||||
let value_id = stmt_spec(&mut builder, &mut env, block, &proc.ret_layout, &proc.body)?;
|
||||
let value_id = stmt_spec(
|
||||
&mut builder,
|
||||
interner,
|
||||
&mut env,
|
||||
block,
|
||||
&proc.ret_layout,
|
||||
&proc.body,
|
||||
)?;
|
||||
|
||||
let root = BlockExpr(block, value_id);
|
||||
let arg_type_id = layout_spec(
|
||||
&mut builder,
|
||||
interner,
|
||||
&Layout::struct_no_name_order(&argument_layouts),
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout, &WhenRecursive::Unreachable)?;
|
||||
let ret_type_id = layout_spec(
|
||||
&mut builder,
|
||||
interner,
|
||||
&proc.ret_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let spec = builder.build(arg_type_id, ret_type_id, root)?;
|
||||
|
||||
|
@ -410,6 +439,7 @@ struct Env<'a> {
|
|||
|
||||
fn stmt_spec<'a>(
|
||||
builder: &mut FuncDefBuilder,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
env: &mut Env<'a>,
|
||||
block: BlockId,
|
||||
layout: &Layout,
|
||||
|
@ -419,20 +449,20 @@ fn stmt_spec<'a>(
|
|||
|
||||
match stmt {
|
||||
Let(symbol, expr, expr_layout, mut continuation) => {
|
||||
let value_id = expr_spec(builder, env, block, expr_layout, expr)?;
|
||||
let value_id = expr_spec(builder, interner, env, block, expr_layout, expr)?;
|
||||
env.symbols.insert(*symbol, value_id);
|
||||
|
||||
let mut queue = vec![symbol];
|
||||
|
||||
while let Let(symbol, expr, expr_layout, c) = continuation {
|
||||
let value_id = expr_spec(builder, env, block, expr_layout, expr)?;
|
||||
let value_id = expr_spec(builder, interner, env, block, expr_layout, expr)?;
|
||||
env.symbols.insert(*symbol, value_id);
|
||||
|
||||
queue.push(symbol);
|
||||
continuation = c;
|
||||
}
|
||||
|
||||
let result = stmt_spec(builder, env, block, layout, continuation)?;
|
||||
let result = stmt_spec(builder, interner, env, block, layout, continuation)?;
|
||||
|
||||
for symbol in queue {
|
||||
env.symbols.remove(symbol);
|
||||
|
@ -456,14 +486,14 @@ fn stmt_spec<'a>(
|
|||
|
||||
for branch in it {
|
||||
let block = builder.add_block();
|
||||
let value_id = stmt_spec(builder, env, block, layout, branch)?;
|
||||
let value_id = stmt_spec(builder, interner, env, block, layout, branch)?;
|
||||
cases.push(BlockExpr(block, value_id));
|
||||
}
|
||||
|
||||
builder.add_choice(block, &cases)
|
||||
}
|
||||
Expect { remainder, .. } => stmt_spec(builder, env, block, layout, remainder),
|
||||
ExpectFx { remainder, .. } => stmt_spec(builder, env, block, layout, remainder),
|
||||
Expect { remainder, .. } => stmt_spec(builder, interner, env, block, layout, remainder),
|
||||
ExpectFx { remainder, .. } => stmt_spec(builder, interner, env, block, layout, remainder),
|
||||
Ret(symbol) => Ok(env.symbols[symbol]),
|
||||
Refcounting(modify_rc, continuation) => match modify_rc {
|
||||
ModifyRc::Inc(symbol, _) => {
|
||||
|
@ -473,7 +503,7 @@ fn stmt_spec<'a>(
|
|||
// and a bit more permissive in its type
|
||||
builder.add_recursive_touch(block, argument)?;
|
||||
|
||||
stmt_spec(builder, env, block, layout, continuation)
|
||||
stmt_spec(builder, interner, env, block, layout, continuation)
|
||||
}
|
||||
|
||||
ModifyRc::Dec(symbol) => {
|
||||
|
@ -481,14 +511,14 @@ fn stmt_spec<'a>(
|
|||
|
||||
builder.add_recursive_touch(block, argument)?;
|
||||
|
||||
stmt_spec(builder, env, block, layout, continuation)
|
||||
stmt_spec(builder, interner, env, block, layout, continuation)
|
||||
}
|
||||
ModifyRc::DecRef(symbol) => {
|
||||
let argument = env.symbols[symbol];
|
||||
|
||||
builder.add_recursive_touch(block, argument)?;
|
||||
|
||||
stmt_spec(builder, env, block, layout, continuation)
|
||||
stmt_spec(builder, interner, env, block, layout, continuation)
|
||||
}
|
||||
},
|
||||
Join {
|
||||
|
@ -502,12 +532,13 @@ fn stmt_spec<'a>(
|
|||
for p in parameters.iter() {
|
||||
type_ids.push(layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
&p.layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?);
|
||||
}
|
||||
|
||||
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let ret_type_id = layout_spec(builder, interner, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
|
||||
|
||||
|
@ -522,7 +553,7 @@ fn stmt_spec<'a>(
|
|||
|
||||
// first, with the current variable bindings, process the remainder
|
||||
let cont_block = builder.add_block();
|
||||
let cont_value_id = stmt_spec(builder, env, cont_block, layout, remainder)?;
|
||||
let cont_value_id = stmt_spec(builder, interner, env, cont_block, layout, remainder)?;
|
||||
|
||||
// only then introduce variables bound by the jump point, and process its body
|
||||
let join_body_sub_block = {
|
||||
|
@ -536,7 +567,8 @@ fn stmt_spec<'a>(
|
|||
env.symbols.insert(p.symbol, value_id);
|
||||
}
|
||||
|
||||
let jp_body_value_id = stmt_spec(builder, env, jp_body_block, layout, body)?;
|
||||
let jp_body_value_id =
|
||||
stmt_spec(builder, interner, env, jp_body_block, layout, body)?;
|
||||
|
||||
BlockExpr(jp_body_block, jp_body_value_id)
|
||||
};
|
||||
|
@ -547,14 +579,14 @@ fn stmt_spec<'a>(
|
|||
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
|
||||
}
|
||||
Jump(id, symbols) => {
|
||||
let ret_type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let ret_type_id = layout_spec(builder, interner, layout, &WhenRecursive::Unreachable)?;
|
||||
let argument = build_tuple_value(builder, env, block, symbols)?;
|
||||
|
||||
let jpid = env.join_points[id];
|
||||
builder.add_jump(block, jpid, argument, ret_type_id)
|
||||
}
|
||||
RuntimeError(_) => {
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let type_id = layout_spec(builder, interner, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_terminate(block, type_id)
|
||||
}
|
||||
|
@ -591,13 +623,14 @@ enum WhenRecursive<'a> {
|
|||
|
||||
fn build_recursive_tuple_type(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
layouts: &[Layout],
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<TypeId> {
|
||||
let mut field_types = Vec::new();
|
||||
|
||||
for field in layouts.iter() {
|
||||
let type_id = layout_spec_help(builder, field, when_recursive)?;
|
||||
let type_id = layout_spec_help(builder, interner, field, when_recursive)?;
|
||||
field_types.push(type_id);
|
||||
}
|
||||
|
||||
|
@ -606,13 +639,14 @@ fn build_recursive_tuple_type(
|
|||
|
||||
fn build_tuple_type(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
layouts: &[Layout],
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<TypeId> {
|
||||
let mut field_types = Vec::new();
|
||||
|
||||
for field in layouts.iter() {
|
||||
field_types.push(layout_spec(builder, field, when_recursive)?);
|
||||
field_types.push(layout_spec(builder, interner, field, when_recursive)?);
|
||||
}
|
||||
|
||||
builder.add_tuple_type(&field_types)
|
||||
|
@ -646,6 +680,7 @@ fn add_loop(
|
|||
|
||||
fn call_spec(
|
||||
builder: &mut FuncDefBuilder,
|
||||
interner: &STLayoutInterner,
|
||||
env: &Env,
|
||||
block: BlockId,
|
||||
layout: &Layout,
|
||||
|
@ -681,12 +716,14 @@ fn call_spec(
|
|||
.map(|symbol| env.symbols[symbol])
|
||||
.collect();
|
||||
|
||||
let result_type = layout_spec(builder, ret_layout, &WhenRecursive::Unreachable)?;
|
||||
let result_type =
|
||||
layout_spec(builder, interner, ret_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_unknown_with(block, &arguments, result_type)
|
||||
}
|
||||
LowLevel { op, update_mode } => lowlevel_spec(
|
||||
builder,
|
||||
interner,
|
||||
env,
|
||||
block,
|
||||
layout,
|
||||
|
@ -751,12 +788,20 @@ fn call_spec(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
let output_element_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
return_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let state_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
&state_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
|
@ -781,8 +826,12 @@ fn call_spec(
|
|||
};
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let state_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
&state_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
let init_state = list;
|
||||
|
||||
add_loop(builder, block, state_type, init_state, loop_body)
|
||||
|
@ -806,12 +855,20 @@ fn call_spec(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
let output_element_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
return_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let state_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
&state_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
|
@ -841,12 +898,20 @@ fn call_spec(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
let output_element_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
return_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let state_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
&state_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
|
@ -882,12 +947,20 @@ fn call_spec(
|
|||
list_append(builder, block, update_mode_var, state, new_element)
|
||||
};
|
||||
|
||||
let output_element_type =
|
||||
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?;
|
||||
let output_element_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
return_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||
let state_type =
|
||||
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?;
|
||||
let state_type = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
&state_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
|
||||
let init_state = new_list(builder, block, output_element_type)?;
|
||||
|
||||
|
@ -929,8 +1002,10 @@ fn list_clone(
|
|||
with_new_heap_cell(builder, block, bag)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn lowlevel_spec(
|
||||
builder: &mut FuncDefBuilder,
|
||||
interner: &STLayoutInterner,
|
||||
env: &Env,
|
||||
block: BlockId,
|
||||
layout: &Layout,
|
||||
|
@ -940,7 +1015,7 @@ fn lowlevel_spec(
|
|||
) -> Result<ValueId> {
|
||||
use LowLevel::*;
|
||||
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let type_id = layout_spec(builder, interner, layout, &WhenRecursive::Unreachable)?;
|
||||
let mode = update_mode.to_bytes();
|
||||
let update_mode_var = UpdateModeVar(&mode);
|
||||
|
||||
|
@ -1048,8 +1123,12 @@ fn lowlevel_spec(
|
|||
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::List(element_layout)) => {
|
||||
let type_id =
|
||||
layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?;
|
||||
let type_id = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
element_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
new_list(builder, block, type_id)
|
||||
}
|
||||
_ => unreachable!("empty array does not have a list layout"),
|
||||
|
@ -1092,7 +1171,7 @@ fn lowlevel_spec(
|
|||
// TODO overly pessimstic
|
||||
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
|
||||
|
||||
let result_type = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let result_type = layout_spec(builder, interner, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_unknown_with(block, &arguments, result_type)
|
||||
}
|
||||
|
@ -1101,16 +1180,18 @@ fn lowlevel_spec(
|
|||
|
||||
fn recursive_tag_variant(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
union_layout: &UnionLayout,
|
||||
fields: &[Layout],
|
||||
) -> Result<TypeId> {
|
||||
let when_recursive = WhenRecursive::Loop(*union_layout);
|
||||
|
||||
build_recursive_tuple_type(builder, fields, &when_recursive)
|
||||
build_recursive_tuple_type(builder, interner, fields, &when_recursive)
|
||||
}
|
||||
|
||||
fn recursive_variant_types(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
union_layout: &UnionLayout,
|
||||
) -> Result<Vec<TypeId>> {
|
||||
use UnionLayout::*;
|
||||
|
@ -1125,11 +1206,16 @@ fn recursive_variant_types(
|
|||
result = Vec::with_capacity(tags.len());
|
||||
|
||||
for tag in tags.iter() {
|
||||
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||
result.push(recursive_tag_variant(builder, interner, union_layout, tag)?);
|
||||
}
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
result = vec![recursive_tag_variant(builder, union_layout, fields)?];
|
||||
result = vec![recursive_tag_variant(
|
||||
builder,
|
||||
interner,
|
||||
union_layout,
|
||||
fields,
|
||||
)?];
|
||||
}
|
||||
NullableWrapped {
|
||||
nullable_id,
|
||||
|
@ -1140,21 +1226,21 @@ fn recursive_variant_types(
|
|||
let cutoff = *nullable_id as usize;
|
||||
|
||||
for tag in tags[..cutoff].iter() {
|
||||
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||
result.push(recursive_tag_variant(builder, interner, union_layout, tag)?);
|
||||
}
|
||||
|
||||
result.push(recursive_tag_variant(builder, union_layout, &[])?);
|
||||
result.push(recursive_tag_variant(builder, interner, union_layout, &[])?);
|
||||
|
||||
for tag in tags[cutoff..].iter() {
|
||||
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||
result.push(recursive_tag_variant(builder, interner, union_layout, tag)?);
|
||||
}
|
||||
}
|
||||
NullableUnwrapped {
|
||||
nullable_id,
|
||||
other_fields: fields,
|
||||
} => {
|
||||
let unit = recursive_tag_variant(builder, union_layout, &[])?;
|
||||
let other_type = recursive_tag_variant(builder, union_layout, fields)?;
|
||||
let unit = recursive_tag_variant(builder, interner, union_layout, &[])?;
|
||||
let other_type = recursive_tag_variant(builder, interner, union_layout, fields)?;
|
||||
|
||||
if *nullable_id {
|
||||
// nullable_id == 1
|
||||
|
@ -1176,6 +1262,7 @@ fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
|
|||
|
||||
fn expr_spec<'a>(
|
||||
builder: &mut FuncDefBuilder,
|
||||
interner: &STLayoutInterner,
|
||||
env: &mut Env<'a>,
|
||||
block: BlockId,
|
||||
layout: &Layout<'a>,
|
||||
|
@ -1185,7 +1272,7 @@ fn expr_spec<'a>(
|
|||
|
||||
match expr {
|
||||
Literal(literal) => literal_spec(builder, block, literal),
|
||||
Call(call) => call_spec(builder, env, block, layout, call),
|
||||
Call(call) => call_spec(builder, interner, env, block, layout, call),
|
||||
Reuse {
|
||||
tag_layout,
|
||||
tag_id,
|
||||
|
@ -1201,8 +1288,12 @@ fn expr_spec<'a>(
|
|||
|
||||
let value_id = match tag_layout {
|
||||
UnionLayout::NonRecursive(tags) => {
|
||||
let variant_types =
|
||||
non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?;
|
||||
let variant_types = non_recursive_variant_types(
|
||||
builder,
|
||||
interner,
|
||||
tags,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
let value_id = build_tuple_value(builder, env, block, arguments)?;
|
||||
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
|
||||
}
|
||||
|
@ -1221,7 +1312,7 @@ fn expr_spec<'a>(
|
|||
UnionLayout::NullableUnwrapped { .. } => data_id,
|
||||
};
|
||||
|
||||
let variant_types = recursive_variant_types(builder, tag_layout)?;
|
||||
let variant_types = recursive_variant_types(builder, interner, tag_layout)?;
|
||||
|
||||
let union_id =
|
||||
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?;
|
||||
|
@ -1307,7 +1398,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(builder, elem_layout, &WhenRecursive::Unreachable)?;
|
||||
let type_id = layout_spec(builder, interner, elem_layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
let list = new_list(builder, block, type_id)?;
|
||||
|
||||
|
@ -1334,19 +1425,24 @@ fn expr_spec<'a>(
|
|||
|
||||
EmptyArray => match layout {
|
||||
Layout::Builtin(Builtin::List(element_layout)) => {
|
||||
let type_id = layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?;
|
||||
let type_id = layout_spec(
|
||||
builder,
|
||||
interner,
|
||||
element_layout,
|
||||
&WhenRecursive::Unreachable,
|
||||
)?;
|
||||
new_list(builder, block, type_id)
|
||||
}
|
||||
_ => unreachable!("empty array does not have a list layout"),
|
||||
},
|
||||
Reset { symbol, .. } => {
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let type_id = layout_spec(builder, interner, layout, &WhenRecursive::Unreachable)?;
|
||||
let value_id = env.symbols[symbol];
|
||||
|
||||
builder.add_unknown_with(block, &[value_id], type_id)
|
||||
}
|
||||
RuntimeErrorFunction(_) => {
|
||||
let type_id = layout_spec(builder, layout, &WhenRecursive::Unreachable)?;
|
||||
let type_id = layout_spec(builder, interner, layout, &WhenRecursive::Unreachable)?;
|
||||
|
||||
builder.add_terminate(block, type_id)
|
||||
}
|
||||
|
@ -1375,14 +1471,16 @@ fn literal_spec(
|
|||
|
||||
fn layout_spec(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
layout: &Layout,
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<TypeId> {
|
||||
layout_spec_help(builder, layout, when_recursive)
|
||||
layout_spec_help(builder, interner, layout, when_recursive)
|
||||
}
|
||||
|
||||
fn non_recursive_variant_types(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
tags: &[&[Layout]],
|
||||
// If there is a recursive pointer latent within this layout, coming from a containing layout.
|
||||
when_recursive: &WhenRecursive,
|
||||
|
@ -1390,7 +1488,7 @@ fn non_recursive_variant_types(
|
|||
let mut result = Vec::with_capacity(tags.len());
|
||||
|
||||
for tag in tags.iter() {
|
||||
result.push(build_tuple_type(builder, tag, when_recursive)?);
|
||||
result.push(build_tuple_type(builder, interner, tag, when_recursive)?);
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
|
@ -1398,19 +1496,21 @@ fn non_recursive_variant_types(
|
|||
|
||||
fn layout_spec_help(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
layout: &Layout,
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<TypeId> {
|
||||
use Layout::*;
|
||||
|
||||
match layout {
|
||||
Builtin(builtin) => builtin_spec(builder, builtin, when_recursive),
|
||||
Builtin(builtin) => builtin_spec(builder, interner, builtin, when_recursive),
|
||||
Struct { field_layouts, .. } => {
|
||||
build_recursive_tuple_type(builder, field_layouts, when_recursive)
|
||||
build_recursive_tuple_type(builder, interner, field_layouts, when_recursive)
|
||||
}
|
||||
LambdaSet(lambda_set) => layout_spec_help(
|
||||
builder,
|
||||
&lambda_set.runtime_representation(),
|
||||
interner,
|
||||
&lambda_set.runtime_representation(interner),
|
||||
when_recursive,
|
||||
),
|
||||
Union(union_layout) => {
|
||||
|
@ -1422,7 +1522,8 @@ fn layout_spec_help(
|
|||
builder.add_tuple_type(&[])
|
||||
}
|
||||
UnionLayout::NonRecursive(tags) => {
|
||||
let variant_types = non_recursive_variant_types(builder, tags, when_recursive)?;
|
||||
let variant_types =
|
||||
non_recursive_variant_types(builder, interner, tags, when_recursive)?;
|
||||
builder.add_union_type(&variant_types)
|
||||
}
|
||||
UnionLayout::Recursive(_)
|
||||
|
@ -1438,7 +1539,7 @@ fn layout_spec_help(
|
|||
}
|
||||
|
||||
Boxed(inner_layout) => {
|
||||
let inner_type = layout_spec_help(builder, inner_layout, when_recursive)?;
|
||||
let inner_type = layout_spec_help(builder, interner, inner_layout, when_recursive)?;
|
||||
let cell_type = builder.add_heap_cell_type();
|
||||
|
||||
builder.add_tuple_type(&[cell_type, inner_type])
|
||||
|
@ -1465,6 +1566,7 @@ fn layout_spec_help(
|
|||
|
||||
fn builtin_spec(
|
||||
builder: &mut impl TypeContext,
|
||||
interner: &STLayoutInterner,
|
||||
builtin: &Builtin,
|
||||
when_recursive: &WhenRecursive,
|
||||
) -> Result<TypeId> {
|
||||
|
@ -1475,7 +1577,7 @@ fn builtin_spec(
|
|||
Decimal | Float(_) => builder.add_tuple_type(&[]),
|
||||
Str => str_type(builder),
|
||||
List(element_layout) => {
|
||||
let element_type = layout_spec_help(builder, element_layout, when_recursive)?;
|
||||
let element_type = layout_spec_help(builder, interner, element_layout, when_recursive)?;
|
||||
|
||||
let cell = builder.add_heap_cell_type();
|
||||
let bag = builder.add_bag_type(element_type)?;
|
||||
|
|
|
@ -27,7 +27,7 @@ roc_reporting = { path = "../../reporting" }
|
|||
roc_error_macros = { path = "../../error_macros" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_utils = { path = "../../utils" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
libloading = "0.7.1"
|
||||
tempfile = "3.2.0"
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
|
@ -35,7 +35,7 @@ target-lexicon = "0.12.3"
|
|||
wasi_libc_sys = { path = "../../wasi-libc-sys" }
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
serde_json = "1.0.69"
|
||||
serde_json = "1.0.85"
|
||||
|
||||
[features]
|
||||
target-arm = []
|
||||
|
|
|
@ -121,11 +121,20 @@ pub fn build_zig_host_native(
|
|||
.env("HOME", env_home);
|
||||
|
||||
if let Some(shared_lib_path) = shared_lib_path {
|
||||
// with LLVM, the builtins are already part of the roc app,
|
||||
// but with the dev backend, they are missing. To minimize work,
|
||||
// we link them as part of the host executable
|
||||
let builtins_obj = if target.contains("windows") {
|
||||
bitcode::get_builtins_windows_obj_path()
|
||||
} else {
|
||||
bitcode::get_builtins_host_obj_path()
|
||||
};
|
||||
|
||||
command.args(&[
|
||||
"build-exe",
|
||||
"-fPIE",
|
||||
shared_lib_path.to_str().unwrap(),
|
||||
&bitcode::get_builtins_host_obj_path(),
|
||||
&builtins_obj,
|
||||
]);
|
||||
} else {
|
||||
command.args(&["build-obj", "-fPIC"]);
|
||||
|
@ -492,15 +501,29 @@ pub fn rebuild_host(
|
|||
host_input_path.with_file_name("host.bc")
|
||||
}
|
||||
} else {
|
||||
host_input_path.with_file_name(if shared_lib_path.is_some() {
|
||||
"dynhost"
|
||||
let os = roc_target::OperatingSystem::from(target.operating_system);
|
||||
|
||||
if shared_lib_path.is_some() {
|
||||
let extension = match os {
|
||||
roc_target::OperatingSystem::Windows => "exe",
|
||||
roc_target::OperatingSystem::Unix => "",
|
||||
roc_target::OperatingSystem::Wasi => "",
|
||||
};
|
||||
|
||||
host_input_path
|
||||
.with_file_name("dynhost")
|
||||
.with_extension(extension)
|
||||
} else {
|
||||
match roc_target::OperatingSystem::from(target.operating_system) {
|
||||
roc_target::OperatingSystem::Windows => "host.obj",
|
||||
roc_target::OperatingSystem::Unix => "host.o",
|
||||
roc_target::OperatingSystem::Wasi => "host.o",
|
||||
let extension = match os {
|
||||
roc_target::OperatingSystem::Windows => "obj",
|
||||
roc_target::OperatingSystem::Unix => "o",
|
||||
roc_target::OperatingSystem::Wasi => "o",
|
||||
};
|
||||
|
||||
host_input_path
|
||||
.with_file_name("host")
|
||||
.with_extension(extension)
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
|
||||
|
|
|
@ -256,6 +256,7 @@ pub fn gen_from_mono_module_llvm(
|
|||
// Compile and add all the Procs before adding main
|
||||
let env = roc_gen_llvm::llvm::build::Env {
|
||||
arena,
|
||||
layout_interner: &loaded.layout_interner,
|
||||
builder: &builder,
|
||||
dibuilder: &dibuilder,
|
||||
compile_unit: &compile_unit,
|
||||
|
@ -473,6 +474,7 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
module_id,
|
||||
procedures,
|
||||
mut interns,
|
||||
layout_interner,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
|
@ -485,6 +487,7 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
|
||||
let env = roc_gen_wasm::Env {
|
||||
arena,
|
||||
layout_interner: &layout_interner,
|
||||
module_id,
|
||||
exposed_to_host,
|
||||
stack_bytes: wasm_dev_stack_bytes.unwrap_or(roc_gen_wasm::Env::DEFAULT_STACK_BYTES),
|
||||
|
@ -492,7 +495,7 @@ fn gen_from_mono_module_dev_wasm32(
|
|||
|
||||
let host_bytes = std::fs::read(preprocessed_host_path).unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Failed to read host object file {}! Try setting --precompiled-host=false",
|
||||
"Failed to read host object file {}! Try setting --prebuilt-platform=false",
|
||||
preprocessed_host_path.display()
|
||||
)
|
||||
});
|
||||
|
@ -545,11 +548,13 @@ fn gen_from_mono_module_dev_assembly(
|
|||
procedures,
|
||||
mut interns,
|
||||
exposed_to_host,
|
||||
layout_interner,
|
||||
..
|
||||
} = loaded;
|
||||
|
||||
let env = roc_gen_dev::Env {
|
||||
arena,
|
||||
layout_interner: &layout_interner,
|
||||
module_id,
|
||||
exposed_to_host: exposed_to_host.values.keys().copied().collect(),
|
||||
lazy_literals,
|
||||
|
|
|
@ -6,7 +6,7 @@ Builtins are the functions and modules that are implicitly imported into every m
|
|||
|
||||
Edit the appropriate `roc/*.roc` file with your new implementation. All normal rules for writing Roc code apply. Be sure to add a declaration, definition, some documentation and add it to the exposes list it in the module head.
|
||||
|
||||
Next, look towards the bottom of the `compiler/builtins/module/src/symbol.rs` file. Inside the `define_builtins!` macro, there is a list for each of the builtin modules and the function or value names it contains. Add a new entry to the appropriate list for your new function.
|
||||
Next, look towards the bottom of the `compiler/module/src/symbol.rs` file. Inside the `define_builtins!` macro, there is a list for each of the builtin modules and the function or value names it contains. Add a new entry to the appropriate list for your new function.
|
||||
|
||||
For each of the builtin modules, there is a file in `compiler/test_gen/src/` like `gen_num.rs`, `gen_str.rs` etc. Add new tests for the module you are changing to the appropriate file here. You can look at the existing test cases for examples and inspiration.
|
||||
|
||||
|
@ -21,16 +21,17 @@ Towards the bottom of `symbol.rs` there is a `define_builtins!` macro being used
|
|||
Some of these have `#` inside their name (`first#list`, `#lt` ..). This is a trick we are doing to hide implementation details from Roc programmers. To a Roc programmer, a name with `#` in it is invalid, because `#` means everything after it is parsed to a comment. We are constructing these functions manually, so we are circumventing the parsing step and dont have such restrictions. We get to make functions and values with `#` which as a consequence are not accessible to Roc programmers. Roc programmers simply cannot reference them.
|
||||
|
||||
But we can use these values and some of these are necessary for implementing builtins. For example, `List.get` returns tags, and it is not easy for us to create tags when composing LLVM. What is easier however, is:
|
||||
|
||||
- ..writing `List.#getUnsafe` that has the dangerous signature of `List elem, Nat -> elem` in LLVM
|
||||
- ..writing `List elem, Nat -> Result elem [OutOfBounds]*` in a type safe way that uses `getUnsafe` internally, only after it checks if the `elem` at `Nat` index exists.
|
||||
|
||||
|
||||
### can/src/builtins.rs
|
||||
|
||||
Right at the top of this module is a function called `builtin_defs`. All this is doing is mapping the `Symbol` defined in `module/src/symbol.rs` to its implementation. Some of the builtins are quite complex, such as `list_get`. What makes `list_get` is that it returns tags, and in order to return tags it first has to defer to lower-level functions via an if statement.
|
||||
|
||||
Lets look at `List.repeat : elem, Nat -> List elem`, which is more straight-forward, and points directly to its lower level implementation:
|
||||
```
|
||||
|
||||
```rust
|
||||
fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||
let elem_var = var_store.fresh();
|
||||
let len_var = var_store.fresh();
|
||||
|
@ -54,30 +55,42 @@ fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|||
)
|
||||
}
|
||||
```
|
||||
|
||||
In these builtin definitions you will need to allocate for and list the arguments. For `List.repeat`, the arguments are the `elem_var` and the `len_var`. So in both the `body` and `defn` we list these arguments in a vector, with the `Symbol::ARG_1` and `Symvol::ARG_2` designating which argument is which.
|
||||
|
||||
Since `List.repeat` is implemented entirely as low level functions, its `body` is a `RunLowLevel`, and the `op` is `LowLevel::ListRepeat`. Lets talk about `LowLevel` in the next section.
|
||||
|
||||
## Connecting the definition to the implementation
|
||||
|
||||
### module/src/low_level.rs
|
||||
|
||||
This `LowLevel` thing connects the builtin defined in this module to its implementation. It's referenced in `can/src/builtins.rs` and it is used in `gen/src/llvm/build.rs`.
|
||||
|
||||
## Bottom level LLVM values and functions
|
||||
|
||||
### gen/src/llvm/build.rs
|
||||
|
||||
This is where bottom-level functions that need to be written as LLVM are created. If the function leads to a tag thats a good sign it should not be written here in `build.rs`. If it's simple fundamental stuff like `INT_ADD` then it certainly should be written here.
|
||||
|
||||
## Letting the compiler know these functions exist
|
||||
|
||||
### builtins/src/std.rs
|
||||
|
||||
It's one thing to actually write these functions, it's _another_ thing to let the Roc compiler know they exist as part of the standard library. You have to tell the compiler "Hey, this function exists, and it has this type signature". That happens in `std.rs`.
|
||||
|
||||
## Specifying how we pass args to the function
|
||||
|
||||
### builtins/mono/src/borrow.rs
|
||||
|
||||
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelevant. Towards the bottom of this file, add a new case for your builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
|
||||
|
||||
## Testing it
|
||||
|
||||
### solve/tests/solve_expr.rs
|
||||
|
||||
To make sure that Roc is properly inferring the type of the new builtin, add a test to this file similar to:
|
||||
```
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn atan() {
|
||||
infer_eq_without_problem(
|
||||
|
@ -90,19 +103,23 @@ fn atan() {
|
|||
);
|
||||
}
|
||||
```
|
||||
|
||||
But replace `Num.atan` and the type signature with the new builtin.
|
||||
|
||||
### test_gen/test/*.rs
|
||||
|
||||
In this directory, there are a couple files like `gen_num.rs`, `gen_str.rs`, etc. For the `Str` module builtins, put the test in `gen_str.rs`, etc. Find the one for the new builtin, and add a test like:
|
||||
```
|
||||
|
||||
```rust
|
||||
#[test]
|
||||
fn atan() {
|
||||
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
|
||||
}
|
||||
```
|
||||
|
||||
But replace `Num.atan`, the return value, and the return type with your new builtin.
|
||||
|
||||
# Mistakes that are easy to make!!
|
||||
## Mistakes that are easy to make!!
|
||||
|
||||
When implementing a new builtin, it is often easy to copy and paste the implementation for an existing builtin. This can take you quite far since many builtins are very similar, but it also risks forgetting to change one small part of what you copy and pasted and losing a lot of time later on when you cant figure out why things dont work. So, speaking from experience, even if you are copying an existing builtin, try and implement it manually without copying and pasting. Two recent instances of this (as of September 7th, 2020):
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
## Adding a bitcode builtin
|
||||
|
||||
To add a builtin:
|
||||
|
||||
1. Add the function to the relevant module. For `Num` builtin use it in `src/num.zig`, for `Str` builtins use `src/str.zig`, and so on. **For anything you add, you must add tests for it!** Not only does to make the builtins more maintainable, it's the the easiest way to test these functions on Zig. To run the test, run: `zig build test`
|
||||
2. Make sure the function is public with the `pub` keyword and uses the C calling convention. This is really easy, just add `pub` and `callconv(.C)` to the function declaration like so: `pub fn atan(num: f64) callconv(.C) f64 { ... }`
|
||||
3. In `src/main.zig`, export the function. This is also organized by module. For example, for a `Num` function find the `Num` section and add: `comptime { exportNumFn(num.atan, "atan"); }`. The first argument is the function, the second is the name of it in LLVM.
|
||||
|
|
|
@ -42,6 +42,7 @@ pub fn build(b: *Builder) void {
|
|||
|
||||
// Generate Object Files
|
||||
generateObjectFile(b, mode, host_target, main_path, "object", "builtins-host");
|
||||
generateObjectFile(b, mode, windows64_target, main_path, "windows-x86_64-object", "builtins-windows-x86_64");
|
||||
generateObjectFile(b, mode, wasm32_target, main_path, "wasm32-object", "builtins-wasm32");
|
||||
|
||||
removeInstallSteps(b);
|
||||
|
|
|
@ -148,11 +148,14 @@ pub const RocList = extern struct {
|
|||
) 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 };
|
||||
} else {
|
||||
const new_source = utils.unsafeReallocate(source_ptr, alignment, self.len(), new_length, element_width);
|
||||
|
||||
return RocList{ .bytes = new_source, .length = new_length, .capacity = new_length };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self.reallocateFresh(alignment, new_length, element_width);
|
||||
}
|
||||
|
@ -727,11 +730,14 @@ pub fn listConcat(list_a: RocList, list_b: RocList, alignment: u32, element_widt
|
|||
return list_b;
|
||||
} else if (list_b.isEmpty()) {
|
||||
return list_a;
|
||||
} else if (!list_a.isEmpty() and list_a.isUnique()) {
|
||||
} else if (list_a.isUnique()) {
|
||||
const total_length: usize = list_a.len() + list_b.len();
|
||||
|
||||
if (list_a.bytes) |source| {
|
||||
const new_source = utils.unsafeReallocate(
|
||||
const new_source = if (list_a.capacity >= total_length)
|
||||
source
|
||||
else
|
||||
utils.unsafeReallocate(
|
||||
source,
|
||||
alignment,
|
||||
list_a.len(),
|
||||
|
|
|
@ -253,8 +253,10 @@ test "" {
|
|||
|
||||
// Export it as weak incase it is already linked in by something else.
|
||||
comptime {
|
||||
if (builtin.target.os.tag != .windows) {
|
||||
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
|
||||
}
|
||||
}
|
||||
fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
|
||||
// @setRuntimeSafety(std.builtin.is_test);
|
||||
|
||||
|
|
|
@ -215,11 +215,14 @@ pub const RocStr = extern struct {
|
|||
return result;
|
||||
}
|
||||
|
||||
// NOTE: returns false for empty string!
|
||||
pub fn isSmallStr(self: RocStr) bool {
|
||||
return @bitCast(isize, self.str_capacity) < 0;
|
||||
}
|
||||
|
||||
test "isSmallStr: returns true for empty string" {
|
||||
try expect(isSmallStr(RocStr.empty()));
|
||||
}
|
||||
|
||||
fn asArray(self: RocStr) [@sizeOf(RocStr)]u8 {
|
||||
const as_ptr = @ptrCast([*]const u8, &self);
|
||||
const slice = as_ptr[0..@sizeOf(RocStr)];
|
||||
|
@ -1652,17 +1655,17 @@ pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList {
|
|||
}
|
||||
|
||||
inline fn strToBytes(arg: RocStr) RocList {
|
||||
if (arg.isEmpty()) {
|
||||
const length = arg.len();
|
||||
if (length == 0) {
|
||||
return RocList.empty();
|
||||
} else if (arg.isSmallStr()) {
|
||||
const length = arg.len();
|
||||
const ptr = utils.allocateWithRefcount(length, RocStr.alignment);
|
||||
|
||||
@memcpy(ptr, arg.asU8ptr(), length);
|
||||
|
||||
return RocList{ .length = length, .bytes = ptr, .capacity = length };
|
||||
} else {
|
||||
return RocList{ .length = arg.len(), .bytes = arg.str_bytes, .capacity = arg.str_capacity };
|
||||
return RocList{ .length = length, .bytes = arg.str_bytes, .capacity = arg.str_capacity };
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -254,7 +254,7 @@ pub fn unsafeReallocate(
|
|||
const old_width = align_width + old_length * element_width;
|
||||
const new_width = align_width + new_length * element_width;
|
||||
|
||||
if (old_width == new_width) {
|
||||
if (old_width >= new_width) {
|
||||
return source_ptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -60,6 +60,12 @@ fn main() {
|
|||
|
||||
generate_object_file(&bitcode_path, "object", BUILTINS_HOST_FILE);
|
||||
|
||||
generate_object_file(
|
||||
&bitcode_path,
|
||||
"windows-x86_64-object",
|
||||
"builtins-windows-x86_64.obj",
|
||||
);
|
||||
|
||||
generate_object_file(&bitcode_path, "wasm32-object", "builtins-wasm32.o");
|
||||
|
||||
copy_zig_builtins_to_target_dir(&bitcode_path);
|
||||
|
|
|
@ -393,11 +393,11 @@ contains = \list, needle ->
|
|||
## `fold`, `foldLeft`, or `foldl`.
|
||||
walk : List elem, state, (state, elem -> state) -> state
|
||||
walk = \list, state, func ->
|
||||
walkHelp : _, _ -> [Continue _, Break []]
|
||||
walkHelp = \currentState, element -> Continue (func currentState element)
|
||||
|
||||
when List.iterate list state walkHelp is
|
||||
Continue newState -> newState
|
||||
Break void -> List.unreachable void
|
||||
|
||||
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
|
||||
## `fold`, `foldRight`, or `foldr`.
|
||||
|
@ -1006,6 +1006,3 @@ iterBackwardsHelp = \list, state, f, prevIndex ->
|
|||
Break b -> Break b
|
||||
else
|
||||
Continue state
|
||||
|
||||
## useful for typechecking guaranteed-unreachable cases
|
||||
unreachable : [] -> a
|
||||
|
|
|
@ -33,6 +33,9 @@ interface Str
|
|||
toU8,
|
||||
toI8,
|
||||
toScalars,
|
||||
replaceEach,
|
||||
replaceFirst,
|
||||
replaceLast,
|
||||
splitFirst,
|
||||
splitLast,
|
||||
walkUtf8WithIndex,
|
||||
|
@ -276,6 +279,65 @@ countUtf8Bytes : Str -> Nat
|
|||
## string slice that does not do bounds checking or utf-8 verification
|
||||
substringUnsafe : Str, Nat, Nat -> Str
|
||||
|
||||
## Returns the string with each occurrence of a substring replaced with a replacement.
|
||||
## If the substring is not found, returns `Err NotFound`.
|
||||
##
|
||||
## Str.replaceEach "foo/bar/baz" "/" "_" == Ok "foo_bar_baz"
|
||||
replaceEach : Str, Str, Str -> Result Str [NotFound]*
|
||||
replaceEach = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
# We found at least one needle, so start the buffer off with
|
||||
# `before` followed by the first replacement flower.
|
||||
Str.reserve "" (Str.countUtf8Bytes haystack)
|
||||
|> Str.concat before
|
||||
|> Str.concat flower
|
||||
|> replaceEachHelp after needle flower
|
||||
|> Ok
|
||||
|
||||
Err err -> Err err
|
||||
|
||||
replaceEachHelp : Str, Str, Str, Str -> Str
|
||||
replaceEachHelp = \buf, haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
buf
|
||||
|> Str.concat before
|
||||
|> Str.concat flower
|
||||
|> replaceEachHelp after needle flower
|
||||
|
||||
Err NotFound -> Str.concat buf haystack
|
||||
|
||||
expect Str.replaceEach "abXdeXghi" "X" "_" == Ok "ab_de_ghi"
|
||||
|
||||
## Returns the string with the first occurrence of a substring replaced with a replacement.
|
||||
## If the substring is not found, returns `Err NotFound`.
|
||||
##
|
||||
## Str.replaceFirst "foo/bar/baz" "/" "_" == Ok "foo_bar/baz"
|
||||
replaceFirst : Str, Str, Str -> Result Str [NotFound]*
|
||||
replaceFirst = \haystack, needle, flower ->
|
||||
when splitFirst haystack needle is
|
||||
Ok { before, after } ->
|
||||
Ok "\(before)\(flower)\(after)"
|
||||
|
||||
Err err -> Err err
|
||||
|
||||
expect Str.replaceFirst "abXdeXghi" "X" "_" == Ok "ab_deXghi"
|
||||
|
||||
## Returns the string with the last occurrence of a substring replaced with a replacement.
|
||||
## If the substring is not found, returns `Err NotFound`.
|
||||
##
|
||||
## Str.replaceLast "foo/bar/baz" "/" "_" == Ok "foo/bar_baz"
|
||||
replaceLast : Str, Str, Str -> Result Str [NotFound]*
|
||||
replaceLast = \haystack, needle, flower ->
|
||||
when splitLast haystack needle is
|
||||
Ok { before, after } ->
|
||||
Ok "\(before)\(flower)\(after)"
|
||||
|
||||
Err err -> Err err
|
||||
|
||||
expect Str.replaceLast "abXdeXghi" "X" "_" == Ok "abXde_ghi"
|
||||
|
||||
## Returns the string before the first occurrence of a delimiter, as well as the
|
||||
## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
|
||||
##
|
||||
|
@ -294,6 +356,18 @@ splitFirst = \haystack, needle ->
|
|||
None ->
|
||||
Err NotFound
|
||||
|
||||
# splitFirst when needle isn't in haystack
|
||||
expect splitFirst "foo" "z" == Err NotFound
|
||||
|
||||
# splitFirst when haystack ends with needle repeated
|
||||
expect splitFirst "foo" "o" == Ok { before: "f", after: "o" }
|
||||
|
||||
# splitFirst with multi-byte needle
|
||||
expect splitFirst "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
|
||||
|
||||
# splitFirst when needle is haystack
|
||||
expect splitFirst "foo" "foo" == Ok { before: "", after: "" }
|
||||
|
||||
firstMatch : Str, Str -> [Some Nat, None]
|
||||
firstMatch = \haystack, needle ->
|
||||
haystackLength = Str.countUtf8Bytes haystack
|
||||
|
@ -304,7 +378,7 @@ firstMatch = \haystack, needle ->
|
|||
|
||||
firstMatchHelp : Str, Str, Nat, Nat -> [Some Nat, None]
|
||||
firstMatchHelp = \haystack, needle, index, lastPossible ->
|
||||
if index < lastPossible then
|
||||
if index <= lastPossible then
|
||||
if matchesAt haystack index needle then
|
||||
Some index
|
||||
else
|
||||
|
@ -339,6 +413,9 @@ expect Str.splitLast "foo" "o" == Ok { before: "fo", after: "" }
|
|||
# splitLast with multi-byte needle
|
||||
expect Str.splitLast "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" }
|
||||
|
||||
# splitLast when needle is haystack
|
||||
expect Str.splitLast "foo" "foo" == Ok { before: "", after: "" }
|
||||
|
||||
lastMatch : Str, Str -> [Some Nat, None]
|
||||
lastMatch = \haystack, needle ->
|
||||
haystackLength = Str.countUtf8Bytes haystack
|
||||
|
|
|
@ -14,6 +14,17 @@ pub fn get_builtins_host_obj_path() -> String {
|
|||
.expect("Failed to convert builtins_host_path to str")
|
||||
}
|
||||
|
||||
pub fn get_builtins_windows_obj_path() -> String {
|
||||
let builtins_host_path = get_lib_path()
|
||||
.expect(LIB_DIR_ERROR)
|
||||
.join("builtins-windows-x86_64.obj");
|
||||
|
||||
builtins_host_path
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.expect("Failed to convert builtins_host_path to str")
|
||||
}
|
||||
|
||||
pub fn get_builtins_wasm32_obj_path() -> String {
|
||||
let builtins_wasm32_path = get_lib_path()
|
||||
.expect(LIB_DIR_ERROR)
|
||||
|
|
|
@ -14,10 +14,10 @@ roc_module = { path = "../module" }
|
|||
roc_parse = { path = "../parse" }
|
||||
roc_problem = { path = "../problem" }
|
||||
roc_types = { path = "../types" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
static_assertions = "1.1.0"
|
||||
bitvec = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.0.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
indoc = "1.0.7"
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use crate::expr::{self, IntValue, WhenBranch};
|
||||
use crate::pattern::DestructType;
|
||||
use roc_collections::all::HumanIndex;
|
||||
use roc_collections::VecMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_exhaustive::{
|
||||
is_useful, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union,
|
||||
|
@ -23,6 +24,7 @@ pub struct ExhaustiveSummary {
|
|||
|
||||
pub fn check(
|
||||
subs: &Subs,
|
||||
real_var: Variable,
|
||||
sketched_rows: SketchedRows,
|
||||
context: ExhaustiveContext,
|
||||
) -> ExhaustiveSummary {
|
||||
|
@ -33,7 +35,7 @@ pub fn check(
|
|||
non_redundant_rows,
|
||||
errors,
|
||||
redundancies,
|
||||
} = sketched_rows.reify_to_non_redundant(subs);
|
||||
} = sketched_rows.reify_to_non_redundant(subs, real_var);
|
||||
all_errors.extend(errors);
|
||||
|
||||
let exhaustive = match roc_exhaustive::check(overall_region, context, non_redundant_rows) {
|
||||
|
@ -55,27 +57,164 @@ pub fn check(
|
|||
enum SketchedPattern {
|
||||
Anything,
|
||||
Literal(Literal),
|
||||
Ctor(Variable, TagName, Vec<SketchedPattern>),
|
||||
KnownCtor(Union, TagId, Vec<SketchedPattern>),
|
||||
/// A constructor whose expected union is not yet known.
|
||||
/// We'll know the whole union when reifying the sketched pattern against an expected case type.
|
||||
Ctor(TagName, Vec<SketchedPattern>),
|
||||
KnownCtor(Union, IndexCtor<'static>, TagId, Vec<SketchedPattern>),
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
enum IndexCtor<'a> {
|
||||
/// Index an opaque type. There should be one argument.
|
||||
Opaque,
|
||||
/// Index a record type. The arguments are the types of the record fields.
|
||||
Record,
|
||||
/// 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,
|
||||
/// Index a tag union with the given tag constructor.
|
||||
Tag(&'a TagName),
|
||||
}
|
||||
|
||||
/// Index a variable as a certain constructor, to get the expected argument types of that constructor.
|
||||
fn index_var(
|
||||
subs: &Subs,
|
||||
mut var: Variable,
|
||||
ctor: IndexCtor,
|
||||
render_as: &RenderAs,
|
||||
) -> Vec<Variable> {
|
||||
if matches!(ctor, IndexCtor::Guard) {
|
||||
// `A B if g` becomes Guard { [True, (A B)] }, so the arguments are a bool, and the type
|
||||
// of the pattern.
|
||||
return vec![Variable::BOOL, var];
|
||||
}
|
||||
loop {
|
||||
match subs.get_content_without_compacting(var) {
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _)
|
||||
| Content::LambdaSet(_)
|
||||
| Content::RangedNumber(..) => internal_error!("not a indexable constructor"),
|
||||
Content::Error => {
|
||||
internal_error!("errors should not be reachable during exhautiveness checking")
|
||||
}
|
||||
Content::RecursionVar {
|
||||
structure,
|
||||
opt_name: _,
|
||||
} => {
|
||||
var = *structure;
|
||||
}
|
||||
Content::Structure(structure) => match structure {
|
||||
FlatType::Apply(_, _)
|
||||
| FlatType::Func(_, _, _)
|
||||
| FlatType::FunctionOrTagUnion(_, _, _) => {
|
||||
internal_error!("not an indexable constructor")
|
||||
}
|
||||
FlatType::Erroneous(_) => {
|
||||
internal_error!("errors should not be reachable during exhautiveness checking")
|
||||
}
|
||||
FlatType::Record(fields, ext) => {
|
||||
let fields_order = match render_as {
|
||||
RenderAs::Record(fields) => fields,
|
||||
_ => internal_error!(
|
||||
"record constructors must always be rendered as records"
|
||||
),
|
||||
};
|
||||
let iter = fields
|
||||
.unsorted_iterator(subs, *ext)
|
||||
.expect("should not have errors if performing exhautiveness checking");
|
||||
|
||||
let map: VecMap<_, _> = iter
|
||||
.map(|(name, field)| (name, *field.as_inner()))
|
||||
.collect();
|
||||
|
||||
let field_types = fields_order
|
||||
.iter()
|
||||
.map(|field| {
|
||||
*map.get(&field)
|
||||
.expect("field must be present during exhautiveness checking")
|
||||
})
|
||||
.collect();
|
||||
|
||||
return field_types;
|
||||
}
|
||||
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
|
||||
let tag_ctor = match ctor {
|
||||
IndexCtor::Tag(name) => name,
|
||||
_ => {
|
||||
internal_error!("constructor in a tag union must be tag")
|
||||
}
|
||||
};
|
||||
let mut iter = tags.unsorted_iterator(subs, *ext);
|
||||
let opt_vars = iter.find_map(|(tag, vars)| {
|
||||
if tag == tag_ctor {
|
||||
Some(vars.to_vec())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
let vars = opt_vars.expect("constructor must be known in the indexable type if we are exhautiveness checking");
|
||||
return vars;
|
||||
}
|
||||
FlatType::EmptyRecord => {
|
||||
debug_assert!(matches!(ctor, IndexCtor::Record));
|
||||
// If there are optional record fields we don't unify them, but we need to
|
||||
// cover them. Since optional fields correspond to "any" patterns, we can pass
|
||||
// through arbitrary types.
|
||||
let num_fields = match render_as {
|
||||
RenderAs::Record(fields) => fields.len(),
|
||||
_ => internal_error!(
|
||||
"record constructors must always be rendered as records"
|
||||
),
|
||||
};
|
||||
return std::iter::repeat(Variable::NULL).take(num_fields).collect();
|
||||
}
|
||||
FlatType::EmptyTagUnion => {
|
||||
internal_error!("empty tag unions are not indexable")
|
||||
}
|
||||
},
|
||||
Content::Alias(_, _, var, AliasKind::Opaque) => {
|
||||
debug_assert!(matches!(ctor, IndexCtor::Opaque));
|
||||
return vec![*var];
|
||||
}
|
||||
Content::Alias(_, _, inner, AliasKind::Structural) => {
|
||||
var = *inner;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SketchedPattern {
|
||||
fn reify(self, subs: &Subs) -> Pattern {
|
||||
fn reify(self, subs: &Subs, real_var: Variable) -> Pattern {
|
||||
match self {
|
||||
Self::Anything => Pattern::Anything,
|
||||
Self::Literal(lit) => Pattern::Literal(lit),
|
||||
Self::KnownCtor(union, tag_id, patterns) => Pattern::Ctor(
|
||||
union,
|
||||
tag_id,
|
||||
patterns.into_iter().map(|pat| pat.reify(subs)).collect(),
|
||||
),
|
||||
Self::Ctor(var, tag_name, patterns) => {
|
||||
let (union, tag_id) = convert_tag(subs, var, &tag_name);
|
||||
Pattern::Ctor(
|
||||
union,
|
||||
tag_id,
|
||||
patterns.into_iter().map(|pat| pat.reify(subs)).collect(),
|
||||
)
|
||||
Self::KnownCtor(union, index_ctor, tag_id, patterns) => {
|
||||
let arg_vars = index_var(subs, real_var, index_ctor, &union.render_as);
|
||||
|
||||
debug_assert!(arg_vars.len() == patterns.len());
|
||||
let args = (patterns.into_iter())
|
||||
.zip(arg_vars)
|
||||
.map(|(pat, var)| {
|
||||
// FIXME
|
||||
pat.reify(subs, var)
|
||||
})
|
||||
.collect();
|
||||
|
||||
Pattern::Ctor(union, tag_id, args)
|
||||
}
|
||||
Self::Ctor(tag_name, patterns) => {
|
||||
let arg_vars = index_var(subs, real_var, IndexCtor::Tag(&tag_name), &RenderAs::Tag);
|
||||
let (union, tag_id) = convert_tag(subs, real_var, &tag_name);
|
||||
|
||||
debug_assert!(arg_vars.len() == patterns.len());
|
||||
let args = (patterns.into_iter())
|
||||
.zip(arg_vars)
|
||||
.map(|(pat, var)| pat.reify(subs, var))
|
||||
.collect();
|
||||
|
||||
Pattern::Ctor(union, tag_id, args)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -96,12 +235,12 @@ pub struct SketchedRows {
|
|||
}
|
||||
|
||||
impl SketchedRows {
|
||||
fn reify_to_non_redundant(self, subs: &Subs) -> NonRedundantSummary {
|
||||
to_nonredundant_rows(subs, self)
|
||||
fn reify_to_non_redundant(self, subs: &Subs, real_var: Variable) -> NonRedundantSummary {
|
||||
to_nonredundant_rows(subs, real_var, self)
|
||||
}
|
||||
}
|
||||
|
||||
fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedPattern {
|
||||
fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
|
||||
use crate::pattern::Pattern::*;
|
||||
use SketchedPattern as SP;
|
||||
|
||||
|
@ -131,9 +270,7 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
|
|||
DestructType::Required | DestructType::Optional(..) => {
|
||||
patterns.push(SP::Anything)
|
||||
}
|
||||
DestructType::Guard(_, guard) => {
|
||||
patterns.push(sketch_pattern(destruct.var, &guard.value))
|
||||
}
|
||||
DestructType::Guard(_, guard) => patterns.push(sketch_pattern(&guard.value)),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,7 +283,7 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
|
|||
}],
|
||||
};
|
||||
|
||||
SP::KnownCtor(union, tag_id, patterns)
|
||||
SP::KnownCtor(union, IndexCtor::Record, tag_id, patterns)
|
||||
}
|
||||
|
||||
AppliedTag {
|
||||
|
@ -156,16 +293,16 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
|
|||
} => {
|
||||
let simplified_args: std::vec::Vec<_> = arguments
|
||||
.iter()
|
||||
.map(|(var, arg)| sketch_pattern(*var, &arg.value))
|
||||
.map(|(_, arg)| sketch_pattern(&arg.value))
|
||||
.collect();
|
||||
|
||||
SP::Ctor(var, tag_name.clone(), simplified_args)
|
||||
SP::Ctor(tag_name.clone(), simplified_args)
|
||||
}
|
||||
|
||||
UnwrappedOpaque {
|
||||
opaque, argument, ..
|
||||
} => {
|
||||
let (arg_var, argument) = &(**argument);
|
||||
let (_, argument) = &(**argument);
|
||||
|
||||
let tag_id = TagId(0);
|
||||
|
||||
|
@ -180,8 +317,9 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
|
|||
|
||||
SP::KnownCtor(
|
||||
union,
|
||||
IndexCtor::Opaque,
|
||||
tag_id,
|
||||
vec![sketch_pattern(*arg_var, &argument.value)],
|
||||
vec![sketch_pattern(&argument.value)],
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -197,11 +335,7 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
|
|||
}
|
||||
}
|
||||
|
||||
pub fn sketch_when_branches(
|
||||
target_var: Variable,
|
||||
region: Region,
|
||||
patterns: &[expr::WhenBranch],
|
||||
) -> SketchedRows {
|
||||
pub fn sketch_when_branches(region: Region, patterns: &[expr::WhenBranch]) -> SketchedRows {
|
||||
let mut rows: Vec<SketchedRow> = Vec::with_capacity(patterns.len());
|
||||
|
||||
// If any of the branches has a guard, e.g.
|
||||
|
@ -256,18 +390,16 @@ pub fn sketch_when_branches(
|
|||
|
||||
vec![SP::KnownCtor(
|
||||
union,
|
||||
IndexCtor::Guard,
|
||||
tag_id,
|
||||
// NB: ordering the guard pattern first seems to be better at catching
|
||||
// non-exhaustive constructors in the second argument; see the paper to see if
|
||||
// there is a way to improve this in general.
|
||||
vec![
|
||||
guard_pattern,
|
||||
sketch_pattern(target_var, &loc_pat.pattern.value),
|
||||
],
|
||||
vec![guard_pattern, sketch_pattern(&loc_pat.pattern.value)],
|
||||
)]
|
||||
} else {
|
||||
// Simple case
|
||||
vec![sketch_pattern(target_var, &loc_pat.pattern.value)]
|
||||
vec![sketch_pattern(&loc_pat.pattern.value)]
|
||||
};
|
||||
|
||||
let row = SketchedRow {
|
||||
|
@ -286,13 +418,9 @@ pub fn sketch_when_branches(
|
|||
}
|
||||
}
|
||||
|
||||
pub fn sketch_pattern_to_rows(
|
||||
target_var: Variable,
|
||||
region: Region,
|
||||
pattern: &crate::pattern::Pattern,
|
||||
) -> SketchedRows {
|
||||
pub fn sketch_pattern_to_rows(region: Region, pattern: &crate::pattern::Pattern) -> SketchedRows {
|
||||
let row = SketchedRow {
|
||||
patterns: vec![sketch_pattern(target_var, pattern)],
|
||||
patterns: vec![sketch_pattern(pattern)],
|
||||
region,
|
||||
// A single row cannot be redundant!
|
||||
redundant_mark: RedundantMark::known_non_redundant(),
|
||||
|
@ -313,7 +441,11 @@ struct NonRedundantSummary {
|
|||
}
|
||||
|
||||
/// INVARIANT: Produces a list of rows where (forall row. length row == 1)
|
||||
fn to_nonredundant_rows(subs: &Subs, rows: SketchedRows) -> NonRedundantSummary {
|
||||
fn to_nonredundant_rows(
|
||||
subs: &Subs,
|
||||
real_var: Variable,
|
||||
rows: SketchedRows,
|
||||
) -> NonRedundantSummary {
|
||||
let SketchedRows {
|
||||
rows,
|
||||
overall_region,
|
||||
|
@ -323,27 +455,47 @@ fn to_nonredundant_rows(subs: &Subs, rows: SketchedRows) -> NonRedundantSummary
|
|||
let mut redundancies = vec![];
|
||||
let mut errors = vec![];
|
||||
|
||||
for SketchedRow {
|
||||
for (
|
||||
row_number,
|
||||
SketchedRow {
|
||||
patterns,
|
||||
guard,
|
||||
region,
|
||||
redundant_mark,
|
||||
} in rows.into_iter()
|
||||
},
|
||||
) in rows.into_iter().enumerate()
|
||||
{
|
||||
let next_row: Vec<Pattern> = patterns
|
||||
.into_iter()
|
||||
.map(|pattern| pattern.reify(subs))
|
||||
.map(|pattern| pattern.reify(subs, real_var))
|
||||
.collect();
|
||||
|
||||
if matches!(guard, Guard::HasGuard) || is_useful(checked_rows.clone(), next_row.clone()) {
|
||||
checked_rows.push(next_row);
|
||||
} else {
|
||||
redundancies.push(redundant_mark);
|
||||
errors.push(Error::Redundant {
|
||||
let redundant_err = if !is_inhabited_row(&next_row) {
|
||||
Some(Error::Unmatchable {
|
||||
overall_region,
|
||||
branch_region: region,
|
||||
index: HumanIndex::zero_based(checked_rows.len()),
|
||||
});
|
||||
index: HumanIndex::zero_based(row_number),
|
||||
})
|
||||
} else if !(matches!(guard, Guard::HasGuard)
|
||||
|| is_useful(checked_rows.clone(), next_row.clone()))
|
||||
{
|
||||
Some(Error::Redundant {
|
||||
overall_region,
|
||||
branch_region: region,
|
||||
index: HumanIndex::zero_based(row_number),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
match redundant_err {
|
||||
None => {
|
||||
checked_rows.push(next_row);
|
||||
}
|
||||
Some(err) => {
|
||||
redundancies.push(redundant_mark);
|
||||
errors.push(err);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -354,6 +506,27 @@ fn to_nonredundant_rows(subs: &Subs, rows: SketchedRows) -> NonRedundantSummary
|
|||
}
|
||||
}
|
||||
|
||||
fn is_inhabited_row(patterns: &[Pattern]) -> bool {
|
||||
patterns.iter().any(is_inhabited_pattern)
|
||||
}
|
||||
|
||||
fn is_inhabited_pattern(pat: &Pattern) -> bool {
|
||||
let mut stack = vec![pat];
|
||||
while let Some(pat) = stack.pop() {
|
||||
match pat {
|
||||
Pattern::Anything => {}
|
||||
Pattern::Literal(_) => {}
|
||||
Pattern::Ctor(union, id, pats) => {
|
||||
if !union.alternatives.iter().any(|alt| alt.tag_id == *id) {
|
||||
return false;
|
||||
}
|
||||
stack.extend(pats);
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union, TagId) {
|
||||
let content = subs.get_content_without_compacting(whole_var);
|
||||
|
||||
|
@ -384,8 +557,17 @@ fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union,
|
|||
let mut alternatives = Vec::with_capacity(num_tags);
|
||||
let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter());
|
||||
|
||||
for (index, (tag, args)) in alternatives_iter.enumerate() {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ license = "UPL-1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
fnv = "1.0.7"
|
||||
im = "15.0.0"
|
||||
im-rc = "15.0.0"
|
||||
wyhash = "0.5.0"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
|
||||
bitvec = "1"
|
||||
|
|
|
@ -36,6 +36,8 @@ pub type SendSet<K> = im::hashset::HashSet<K, BuildHasher>;
|
|||
pub type BumpMap<K, V> = hashbrown::HashMap<K, V, BuildHasher>;
|
||||
pub type BumpSet<K> = hashbrown::HashSet<K, BuildHasher>;
|
||||
|
||||
pub type FnvMap<K, V> = fnv::FnvHashMap<K, V>;
|
||||
|
||||
pub trait BumpMapDefault<'a> {
|
||||
fn new_in(arena: &'a bumpalo::Bump) -> Self;
|
||||
|
||||
|
|
|
@ -210,7 +210,7 @@ impl<K, V> ExactSizeIterator for IntoIter<K, V> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<K: Ord, V> std::iter::FromIterator<(K, V)> for VecMap<K, V> {
|
||||
impl<K: PartialEq, V> std::iter::FromIterator<(K, V)> for VecMap<K, V> {
|
||||
fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
|
||||
let mut this = Self::default();
|
||||
this.extend(iter);
|
||||
|
|
|
@ -202,7 +202,7 @@ pub fn constrain_expr(
|
|||
let field_var = field.var;
|
||||
let loc_field_expr = &field.loc_expr;
|
||||
let (field_type, field_con) =
|
||||
constrain_field(constraints, env, field_var, &*loc_field_expr);
|
||||
constrain_field(constraints, env, field_var, loc_field_expr);
|
||||
|
||||
field_vars.push(field_var);
|
||||
field_types.insert(label.clone(), RecordField::Required(field_type));
|
||||
|
@ -724,11 +724,7 @@ pub fn constrain_expr(
|
|||
|
||||
let branches_region = {
|
||||
debug_assert!(!branches.is_empty());
|
||||
Region::span_across(
|
||||
&loc_cond.region,
|
||||
// &branches.first().unwrap().region(),
|
||||
&branches.last().unwrap().pattern_region(),
|
||||
)
|
||||
Region::span_across(&loc_cond.region, &branches.last().unwrap().value.region)
|
||||
};
|
||||
|
||||
let branch_expr_reason =
|
||||
|
@ -866,7 +862,7 @@ pub fn constrain_expr(
|
|||
pattern_cons.push(cond_constraint);
|
||||
|
||||
// Now check the condition against the type expected by the branches.
|
||||
let sketched_rows = sketch_when_branches(real_cond_var, branches_region, branches);
|
||||
let sketched_rows = sketch_when_branches(branches_region, branches);
|
||||
let cond_matches_branches_constraint = constraints.exhaustive(
|
||||
real_cond_var,
|
||||
loc_cond.region,
|
||||
|
@ -2452,8 +2448,7 @@ fn constrain_typed_function_arguments(
|
|||
|
||||
// Exhaustiveness-check the type in the pattern against what the
|
||||
// annotation wants.
|
||||
let sketched_rows =
|
||||
sketch_pattern_to_rows(annotation_var, loc_pattern.region, &loc_pattern.value);
|
||||
let sketched_rows = sketch_pattern_to_rows(loc_pattern.region, &loc_pattern.value);
|
||||
let category = loc_pattern.value.category();
|
||||
let expected = PExpected::ForReason(
|
||||
PReason::TypedArg {
|
||||
|
@ -2564,8 +2559,7 @@ fn constrain_typed_function_arguments_simple(
|
|||
{
|
||||
// Exhaustiveness-check the type in the pattern against what the
|
||||
// annotation wants.
|
||||
let sketched_rows =
|
||||
sketch_pattern_to_rows(annotation_var, loc_pattern.region, &loc_pattern.value);
|
||||
let sketched_rows = sketch_pattern_to_rows(loc_pattern.region, &loc_pattern.value);
|
||||
let category = loc_pattern.value.category();
|
||||
let expected = PExpected::ForReason(
|
||||
PReason::TypedArg {
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
use roc_module::{ident::Lowercase, symbol::Symbol};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
|
||||
use crate::{util::debug_name_record, DeriveError};
|
||||
use crate::{
|
||||
util::{check_derivable_ext_var, debug_name_record},
|
||||
DeriveError,
|
||||
};
|
||||
|
||||
#[derive(Hash)]
|
||||
pub enum FlatDecodable {
|
||||
|
@ -38,10 +41,11 @@ impl FlatDecodable {
|
|||
_ => Err(Underivable),
|
||||
},
|
||||
FlatType::Record(fields, ext) => {
|
||||
let fields_iter = match fields.unsorted_iterator(subs, ext) {
|
||||
Ok(it) => it,
|
||||
Err(_) => return Err(Underivable),
|
||||
};
|
||||
let (fields_iter, ext) = fields.unsorted_iterator_and_ext(subs, ext);
|
||||
|
||||
check_derivable_ext_var(subs, ext, |ext| {
|
||||
matches!(ext, Content::Structure(FlatType::EmptyRecord))
|
||||
})?;
|
||||
|
||||
let mut field_names = Vec::with_capacity(fields.len());
|
||||
for (field_name, record_field) in fields_iter {
|
||||
|
|
|
@ -2,10 +2,10 @@ use roc_module::{
|
|||
ident::{Lowercase, TagName},
|
||||
symbol::Symbol,
|
||||
};
|
||||
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
|
||||
use crate::{
|
||||
util::{check_empty_ext_var, debug_name_record},
|
||||
util::{check_derivable_ext_var, debug_name_record},
|
||||
DeriveError,
|
||||
};
|
||||
|
||||
|
@ -63,12 +63,17 @@ impl FlatEncodable {
|
|||
_ => Err(Underivable),
|
||||
},
|
||||
FlatType::Record(fields, ext) => {
|
||||
check_empty_ext_var(subs, ext, |ext| {
|
||||
let (fields_iter, ext) = fields.unsorted_iterator_and_ext(subs, ext);
|
||||
|
||||
check_derivable_ext_var(subs, ext, |ext| {
|
||||
matches!(ext, Content::Structure(FlatType::EmptyRecord))
|
||||
})?;
|
||||
|
||||
let mut field_names: Vec<_> =
|
||||
subs.get_subs_slice(fields.field_names()).to_vec();
|
||||
let mut field_names = Vec::with_capacity(fields.len());
|
||||
for (field_name, _) in fields_iter {
|
||||
field_names.push(field_name.clone());
|
||||
}
|
||||
|
||||
field_names.sort();
|
||||
|
||||
Ok(Key(FlatEncodableKey::Record(field_names)))
|
||||
|
@ -83,20 +88,23 @@ impl FlatEncodable {
|
|||
// [ A t1, B t1 t2 ] as R
|
||||
// look the same on the surface, because `R` is only somewhere inside of the
|
||||
// `t`-prefixed payload types.
|
||||
check_empty_ext_var(subs, ext, |ext| {
|
||||
let (tags_iter, ext) = tags.unsorted_tags_and_ext(subs, ext);
|
||||
|
||||
check_derivable_ext_var(subs, ext, |ext| {
|
||||
matches!(ext, Content::Structure(FlatType::EmptyTagUnion))
|
||||
})?;
|
||||
|
||||
let mut tag_names_and_payload_sizes: Vec<_> = tags
|
||||
.iter_all()
|
||||
.map(|(name_index, payload_slice_index)| {
|
||||
let payload_slice = subs[payload_slice_index];
|
||||
let payload_size = payload_slice.length;
|
||||
let name = &subs[name_index];
|
||||
(name.clone(), payload_size)
|
||||
let mut tag_names_and_payload_sizes: Vec<_> = tags_iter
|
||||
.tags
|
||||
.into_iter()
|
||||
.map(|(name, payload_slice)| {
|
||||
let payload_size = payload_slice.len();
|
||||
(name.clone(), payload_size as _)
|
||||
})
|
||||
.collect();
|
||||
|
||||
tag_names_and_payload_sizes.sort_by(|(t1, _), (t2, _)| t1.cmp(t2));
|
||||
|
||||
Ok(Key(FlatEncodableKey::TagUnion(tag_names_and_payload_sizes)))
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(name_index, _, _) => Ok(Key(
|
||||
|
|
|
@ -3,13 +3,25 @@ use roc_types::subs::{Content, Subs, Variable};
|
|||
|
||||
use crate::DeriveError;
|
||||
|
||||
pub(crate) fn check_empty_ext_var(
|
||||
pub(crate) fn check_derivable_ext_var(
|
||||
subs: &Subs,
|
||||
ext_var: Variable,
|
||||
is_empty_ext: impl Fn(&Content) -> bool,
|
||||
) -> Result<(), DeriveError> {
|
||||
let ext_content = subs.get_content_without_compacting(ext_var);
|
||||
if is_empty_ext(ext_content) {
|
||||
if is_empty_ext(ext_content)
|
||||
|| matches!(
|
||||
ext_content,
|
||||
// It's fine to have either a flex/rigid or flex-able/rigid-able in the extension.
|
||||
// Since we don't know the rest of the type concretely, any implementation (derived or
|
||||
// not) would only be able to work on the concrete part of the type regardless. So,
|
||||
// just admit them, and they will be excluded from the deriving scheme.
|
||||
Content::FlexVar(_)
|
||||
| Content::FlexAbleVar(..)
|
||||
| Content::RigidVar(_)
|
||||
| Content::RigidAbleVar(..)
|
||||
)
|
||||
{
|
||||
Ok(())
|
||||
} else {
|
||||
match ext_content {
|
||||
|
|
|
@ -93,6 +93,11 @@ pub enum Error {
|
|||
branch_region: Region,
|
||||
index: HumanIndex,
|
||||
},
|
||||
Unmatchable {
|
||||
overall_region: Region,
|
||||
branch_region: Region,
|
||||
index: HumanIndex,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
@ -451,7 +456,7 @@ fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap<TagId, Union> {
|
|||
let mut ctors = MutMap::default();
|
||||
|
||||
for row in matrix {
|
||||
if let Some(Ctor(union, id, _)) = row.get(row.len() - 1) {
|
||||
if let Some(Ctor(union, id, _)) = row.last() {
|
||||
ctors.insert(*id, union.clone());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@ roc_collections = { path = "../collections" }
|
|||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_parse = { path = "../parse" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.0.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
indoc = "1.0.7"
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
walkdir = "2.3.2"
|
||||
|
|
|
@ -206,7 +206,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
|||
use roc_parse::ast::TypeAnnotation::*;
|
||||
|
||||
match self {
|
||||
Function(arguments, result) => {
|
||||
Function(args, ret) => {
|
||||
let needs_parens = parens != Parens::NotNeeded;
|
||||
|
||||
buf.indent(indent);
|
||||
|
@ -215,7 +215,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
|||
buf.push('(')
|
||||
}
|
||||
|
||||
let mut it = arguments.iter().enumerate().peekable();
|
||||
let mut it = args.iter().enumerate().peekable();
|
||||
let should_add_newlines = newlines == Newlines::Yes;
|
||||
|
||||
while let Some((index, argument)) = it.next() {
|
||||
|
@ -251,12 +251,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
|||
buf.push_str("->");
|
||||
buf.spaces(1);
|
||||
|
||||
(&result.value).format_with_options(
|
||||
buf,
|
||||
Parens::InFunctionType,
|
||||
Newlines::No,
|
||||
indent,
|
||||
);
|
||||
(&ret.value).format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
|
||||
|
||||
if needs_parens {
|
||||
buf.push(')')
|
||||
|
@ -345,8 +340,10 @@ impl<'a> Formattable for TypeAnnotation<'a> {
|
|||
Newlines::No
|
||||
};
|
||||
|
||||
if !buf.ends_with_newline() {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
ann.format_with_options(buf, parens, next_newlines, indent)
|
||||
}
|
||||
|
@ -439,7 +436,7 @@ fn format_assigned_field_help<'a, 'buf, T>(
|
|||
}
|
||||
|
||||
buf.spaces(separator_spaces);
|
||||
buf.push_str(":");
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
|
@ -524,7 +521,12 @@ impl<'a> Formattable for Tag<'a> {
|
|||
|
||||
for arg in *args {
|
||||
buf.newline();
|
||||
arg.format_with_options(buf, Parens::InApply, Newlines::No, arg_indent);
|
||||
arg.value.format_with_options(
|
||||
buf,
|
||||
Parens::InApply,
|
||||
Newlines::No,
|
||||
arg_indent,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
for arg in *args {
|
||||
|
@ -544,7 +546,7 @@ impl<'a> Formattable for Tag<'a> {
|
|||
|
||||
impl<'a> Formattable for HasClause<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
self.var.value.is_multiline() || self.ability.is_multiline()
|
||||
self.ability.is_multiline()
|
||||
}
|
||||
|
||||
fn format_with_options<'buf>(
|
||||
|
|
|
@ -220,7 +220,7 @@ impl<'a> Formattable for ValueDef<'a> {
|
|||
}
|
||||
} else {
|
||||
buf.spaces(1);
|
||||
buf.push_str(":");
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
loc_annotation.format_with_options(
|
||||
buf,
|
||||
|
|
|
@ -419,7 +419,15 @@ fn format_str_segment<'a, 'buf>(seg: &StrSegment<'a>, buf: &mut Buf<'buf>, inden
|
|||
|
||||
match seg {
|
||||
Plaintext(string) => {
|
||||
buf.push_str_allow_spaces(string);
|
||||
// Lines in block strings will end with Plaintext ending in "\n" to indicate
|
||||
// a line break in the input string
|
||||
match string.strip_suffix('\n') {
|
||||
Some(string_without_newline) => {
|
||||
buf.push_str_allow_spaces(string_without_newline);
|
||||
buf.newline();
|
||||
}
|
||||
None => buf.push_str_allow_spaces(string),
|
||||
}
|
||||
}
|
||||
Unicode(loc_str) => {
|
||||
buf.push_str("\\u(");
|
||||
|
@ -476,24 +484,20 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
|
|||
buf.push('"');
|
||||
match literal {
|
||||
PlainLine(string) => {
|
||||
// When a PlainLine contains "\n" it is formatted as a block string using """
|
||||
let mut lines = string.split('\n');
|
||||
match (lines.next(), lines.next()) {
|
||||
(Some(first), Some(second)) => {
|
||||
// When a PlainLine contains '\n' or '"', format as a block string
|
||||
if string.contains('"') || string.contains('\n') {
|
||||
buf.push_str("\"\"");
|
||||
buf.newline();
|
||||
|
||||
for line in [first, second].into_iter().chain(lines) {
|
||||
for line in string.split('\n') {
|
||||
buf.indent(indent);
|
||||
buf.push_str_allow_spaces(line);
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str("\"\"");
|
||||
}
|
||||
_ => buf.push_str_allow_spaces(string),
|
||||
}
|
||||
} else {
|
||||
buf.push_str_allow_spaces(string);
|
||||
};
|
||||
}
|
||||
Line(segments) => {
|
||||
for seg in segments.iter() {
|
||||
|
@ -501,36 +505,19 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
|
|||
}
|
||||
}
|
||||
Block(lines) => {
|
||||
// Block strings will always be formatted with """ on new lines
|
||||
buf.push_str("\"\"");
|
||||
|
||||
if lines.len() > 1 {
|
||||
// Since we have multiple lines, format this with
|
||||
// the `"""` symbols on their own lines, and the
|
||||
buf.newline();
|
||||
|
||||
for segments in lines.iter() {
|
||||
for seg in segments.iter() {
|
||||
buf.indent(indent);
|
||||
format_str_segment(seg, buf, indent);
|
||||
}
|
||||
|
||||
buf.newline();
|
||||
}
|
||||
} else {
|
||||
// This is a single-line block string, for example:
|
||||
//
|
||||
// """Whee, "quotes" inside quotes!"""
|
||||
|
||||
// This loop will run either 0 or 1 times.
|
||||
for segments in lines.iter() {
|
||||
for seg in segments.iter() {
|
||||
format_str_segment(seg, buf, indent);
|
||||
}
|
||||
|
||||
// Don't print a newline here, because we either
|
||||
// just printed 1 or 0 lines.
|
||||
}
|
||||
}
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str("\"\"");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -190,8 +190,9 @@ impl<'a> Formattable for TypedIdent<'a> {
|
|||
buf.indent(indent);
|
||||
buf.push_str(self.ident.value);
|
||||
fmt_default_spaces(buf, self.spaces_before_colon, indent);
|
||||
buf.push_str(":");
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
|
||||
self.ann.value.format(buf, indent);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
use crate::annotation::{Formattable, Newlines, Parens};
|
||||
use crate::expr::fmt_str_literal;
|
||||
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt};
|
||||
use crate::Buf;
|
||||
use roc_parse::ast::{Base, CommentOrNewline, Pattern};
|
||||
|
@ -146,9 +147,7 @@ impl<'a> Formattable for Pattern<'a> {
|
|||
buf.indent(indent);
|
||||
buf.push_str(string);
|
||||
}
|
||||
StrLiteral(literal) => {
|
||||
todo!("Format string literal: {:?}", literal);
|
||||
}
|
||||
StrLiteral(literal) => fmt_str_literal(buf, *literal, indent),
|
||||
SingleQuote(string) => {
|
||||
buf.push('\'');
|
||||
buf.push_str(string);
|
||||
|
|
|
@ -542,17 +542,17 @@ impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
|
|||
},
|
||||
Expect {
|
||||
condition,
|
||||
preceding_comment,
|
||||
preceding_comment: _,
|
||||
} => Expect {
|
||||
condition: arena.alloc(condition.remove_spaces(arena)),
|
||||
preceding_comment,
|
||||
preceding_comment: Region::zero(),
|
||||
},
|
||||
ExpectFx {
|
||||
condition,
|
||||
preceding_comment,
|
||||
preceding_comment: _,
|
||||
} => ExpectFx {
|
||||
condition: arena.alloc(condition.remove_spaces(arena)),
|
||||
preceding_comment,
|
||||
preceding_comment: Region::zero(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1098,47 +1098,114 @@ mod test_fmt {
|
|||
));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn empty_block_string() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// """"""
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
#[test]
|
||||
fn empty_block_string() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"""
|
||||
"""
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn basic_block_string() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// """blah"""
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
#[test]
|
||||
fn oneline_empty_block_string() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
""""""
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
"""
|
||||
"""
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn newlines_block_string() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// """blah
|
||||
// spam
|
||||
// foo"""
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
#[test]
|
||||
fn basic_block_string() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
"""griffin"""
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
"griffin"
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// #[test]
|
||||
// fn quotes_block_string() {
|
||||
// expr_formats_same(indoc!(
|
||||
// r#"
|
||||
// """
|
||||
#[test]
|
||||
fn multiline_basic_block_string() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
"""griffin
|
||||
harpy"""
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
"""
|
||||
griffin
|
||||
harpy
|
||||
"""
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// "" \""" ""\"
|
||||
#[test]
|
||||
fn newlines_block_string() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
"""griffin
|
||||
harpy
|
||||
phoenix"""
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
"""
|
||||
griffin
|
||||
harpy
|
||||
phoenix
|
||||
"""
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// """
|
||||
// "#
|
||||
// ));
|
||||
// }
|
||||
#[test]
|
||||
fn quotes_block_string_single_segment() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"""
|
||||
"griffin"
|
||||
"""
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quotes_block_string() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
"""
|
||||
|
||||
"" \""" ""\"
|
||||
|
||||
"""
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero() {
|
||||
|
@ -5457,6 +5524,49 @@ mod test_fmt {
|
|||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_string_literal_in_pattern() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
"abc" -> ""
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_string_literal_in_pattern() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
"""
|
||||
abc
|
||||
def
|
||||
""" -> ""
|
||||
"#
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_string_literal_that_can_be_single_line_in_pattern() {
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
"""
|
||||
abc
|
||||
""" -> ""
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
when foo is
|
||||
"abc" -> ""
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// this is a parse error atm
|
||||
// #[test]
|
||||
// fn multiline_apply() {
|
||||
|
|
|
@ -8,6 +8,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_region = { path = "../region" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_problem = { path = "../problem" }
|
||||
|
@ -18,7 +19,7 @@ roc_solve = { path = "../solve" }
|
|||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
target-lexicon = "0.12.3"
|
||||
# TODO: Deal with the update of object to 0.27.
|
||||
# It looks like it breaks linking the generated objects.
|
||||
|
@ -31,7 +32,7 @@ packed_struct = "0.10.0"
|
|||
roc_can = { path = "../can" }
|
||||
roc_parse = { path = "../parse" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
capstone = "0.11.0"
|
||||
|
||||
[features]
|
||||
|
|
|
@ -826,10 +826,41 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
|
|||
todo!("registers greater than or equal for AArch64");
|
||||
}
|
||||
|
||||
fn set_if_overflow(_buf: &mut Vec<'_, u8>, _dst: AArch64GeneralReg) {
|
||||
todo!("set if overflow for AArch64");
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn ret(buf: &mut Vec<'_, u8>) {
|
||||
ret_reg64(buf, AArch64GeneralReg::LR)
|
||||
}
|
||||
|
||||
fn and_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_dst: AArch64GeneralReg,
|
||||
_src1: AArch64GeneralReg,
|
||||
_src2: AArch64GeneralReg,
|
||||
) {
|
||||
todo!("bitwise and for AArch64")
|
||||
}
|
||||
|
||||
fn or_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_dst: AArch64GeneralReg,
|
||||
_src1: AArch64GeneralReg,
|
||||
_src2: AArch64GeneralReg,
|
||||
) {
|
||||
todo!("bitwise or for AArch64")
|
||||
}
|
||||
|
||||
fn xor_reg64_reg64_reg64(
|
||||
_buf: &mut Vec<'_, u8>,
|
||||
_dst: AArch64GeneralReg,
|
||||
_src1: AArch64GeneralReg,
|
||||
_src2: AArch64GeneralReg,
|
||||
) {
|
||||
todo!("bitwise xor for AArch64")
|
||||
}
|
||||
}
|
||||
|
||||
impl AArch64Assembler {}
|
||||
|
|
|
@ -143,6 +143,27 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
|||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn and_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn or_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn xor_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: GeneralReg,
|
||||
src1: GeneralReg,
|
||||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String);
|
||||
|
||||
/// Jumps by an offset of offset bytes unconditionally.
|
||||
|
@ -320,6 +341,8 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
|
|||
src2: GeneralReg,
|
||||
);
|
||||
|
||||
fn set_if_overflow(buf: &mut Vec<'_, u8>, dst: GeneralReg);
|
||||
|
||||
fn ret(buf: &mut Vec<'_, u8>);
|
||||
}
|
||||
|
||||
|
@ -340,6 +363,7 @@ pub struct Backend64Bit<
|
|||
// They are likely to be small enough that it is faster to use a vec and linearly scan it or keep it sorted and binary search.
|
||||
phantom_asm: PhantomData<ASM>,
|
||||
phantom_cc: PhantomData<CC>,
|
||||
target_info: TargetInfo,
|
||||
env: &'a Env<'a>,
|
||||
interns: &'a mut Interns,
|
||||
helper_proc_gen: CodeGenHelp<'a>,
|
||||
|
@ -374,9 +398,15 @@ pub fn new_backend_64bit<
|
|||
Backend64Bit {
|
||||
phantom_asm: PhantomData,
|
||||
phantom_cc: PhantomData,
|
||||
target_info,
|
||||
env,
|
||||
interns,
|
||||
helper_proc_gen: CodeGenHelp::new(env.arena, target_info, env.module_id),
|
||||
helper_proc_gen: CodeGenHelp::new(
|
||||
env.arena,
|
||||
env.layout_interner,
|
||||
target_info,
|
||||
env.module_id,
|
||||
),
|
||||
helper_proc_symbols: bumpalo::vec![in env.arena],
|
||||
proc_name: None,
|
||||
is_self_recursive: None,
|
||||
|
@ -790,11 +820,62 @@ impl<
|
|||
}
|
||||
}
|
||||
|
||||
fn build_num_add_checked(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
num_layout: &Layout<'a>,
|
||||
return_layout: &Layout<'a>,
|
||||
) {
|
||||
use Builtin::Int;
|
||||
|
||||
let buf = &mut self.buf;
|
||||
|
||||
let struct_size = return_layout.stack_size(self.env.layout_interner, self.target_info);
|
||||
|
||||
let base_offset = self.storage_manager.claim_stack_area(dst, struct_size);
|
||||
|
||||
match num_layout {
|
||||
Layout::Builtin(Int(IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8)) => {
|
||||
let dst_reg = self
|
||||
.storage_manager
|
||||
.claim_general_reg(buf, &Symbol::DEV_TMP);
|
||||
|
||||
let overflow_reg = self
|
||||
.storage_manager
|
||||
.claim_general_reg(buf, &Symbol::DEV_TMP2);
|
||||
|
||||
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::add_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
|
||||
ASM::set_if_overflow(buf, overflow_reg);
|
||||
|
||||
ASM::mov_base32_reg64(buf, base_offset, dst_reg);
|
||||
ASM::mov_base32_reg64(buf, base_offset + 8, overflow_reg);
|
||||
|
||||
self.free_symbol(&Symbol::DEV_TMP);
|
||||
self.free_symbol(&Symbol::DEV_TMP2);
|
||||
}
|
||||
Layout::Builtin(Int(IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8)) => {
|
||||
todo!("addChecked for unsigned integers")
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
|
||||
todo!("addChecked for f64")
|
||||
}
|
||||
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
|
||||
todo!("addChecked for f32")
|
||||
}
|
||||
x => todo!("NumAdd: layout, {:?}", x),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
|
||||
use Builtin::Int;
|
||||
|
||||
match layout {
|
||||
Layout::Builtin(Builtin::Int(
|
||||
IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8,
|
||||
)) => {
|
||||
Layout::Builtin(Int(IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8)) => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src1_reg = self
|
||||
.storage_manager
|
||||
|
@ -804,9 +885,7 @@ impl<
|
|||
.load_to_general_reg(&mut self.buf, src2);
|
||||
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
Layout::Builtin(Builtin::Int(
|
||||
IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8,
|
||||
)) => {
|
||||
Layout::Builtin(Int(IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8)) => {
|
||||
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
|
||||
let src1_reg = self
|
||||
.storage_manager
|
||||
|
@ -1090,7 +1169,8 @@ impl<
|
|||
let index_reg = self
|
||||
.storage_manager
|
||||
.load_to_general_reg(&mut self.buf, index);
|
||||
let ret_stack_size = ret_layout.stack_size(self.storage_manager.target_info());
|
||||
let ret_stack_size =
|
||||
ret_layout.stack_size(self.env.layout_interner, self.storage_manager.target_info());
|
||||
// TODO: This can be optimized with smarter instructions.
|
||||
// Also can probably be moved into storage manager at least partly.
|
||||
self.storage_manager.with_tmp_general_reg(
|
||||
|
@ -1132,7 +1212,8 @@ impl<
|
|||
let elem_layout = arg_layouts[2];
|
||||
|
||||
let u32_layout = &Layout::Builtin(Builtin::Int(IntWidth::U32));
|
||||
let list_alignment = list_layout.alignment_bytes(self.storage_manager.target_info());
|
||||
let list_alignment = list_layout
|
||||
.alignment_bytes(self.env.layout_interner, self.storage_manager.target_info());
|
||||
self.load_literal(
|
||||
&Symbol::DEV_TMP,
|
||||
u32_layout,
|
||||
|
@ -1151,7 +1232,8 @@ 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 = elem_layout.stack_size(self.storage_manager.target_info());
|
||||
let elem_stack_size =
|
||||
elem_layout.stack_size(self.env.layout_interner, self.storage_manager.target_info());
|
||||
self.load_literal(
|
||||
&Symbol::DEV_TMP3,
|
||||
u64_layout,
|
||||
|
@ -1161,7 +1243,7 @@ impl<
|
|||
// Setup the return location.
|
||||
let base_offset = self.storage_manager.claim_stack_area(
|
||||
dst,
|
||||
ret_layout.stack_size(self.storage_manager.target_info()),
|
||||
ret_layout.stack_size(self.env.layout_interner, self.storage_manager.target_info()),
|
||||
);
|
||||
|
||||
let ret_fields = if let Layout::Struct { field_layouts, .. } = ret_layout {
|
||||
|
@ -1178,13 +1260,19 @@ impl<
|
|||
|
||||
let (out_list_offset, out_elem_offset) = if ret_fields[0] == elem_layout {
|
||||
(
|
||||
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
|
||||
base_offset
|
||||
+ ret_fields[0]
|
||||
.stack_size(self.env.layout_interner, self.storage_manager.target_info())
|
||||
as i32,
|
||||
base_offset,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
base_offset,
|
||||
base_offset + ret_fields[0].stack_size(self.storage_manager.target_info()) as i32,
|
||||
base_offset
|
||||
+ ret_fields[0]
|
||||
.stack_size(self.env.layout_interner, self.storage_manager.target_info())
|
||||
as i32,
|
||||
)
|
||||
};
|
||||
|
||||
|
@ -1265,10 +1353,15 @@ impl<
|
|||
// This requires at least 8 for the refcount alignment.
|
||||
let allocation_alignment = std::cmp::max(
|
||||
8,
|
||||
elem_layout.allocation_alignment_bytes(self.storage_manager.target_info()) as u64,
|
||||
elem_layout.allocation_alignment_bytes(
|
||||
self.env.layout_interner,
|
||||
self.storage_manager.target_info(),
|
||||
) as u64,
|
||||
);
|
||||
|
||||
let elem_size = elem_layout.stack_size(self.storage_manager.target_info()) as u64;
|
||||
let elem_size = elem_layout
|
||||
.stack_size(self.env.layout_interner, self.storage_manager.target_info())
|
||||
as u64;
|
||||
let allocation_size = elem_size * elems.len() as u64 + allocation_alignment /* add space for refcount */;
|
||||
let u64_layout = Layout::Builtin(Builtin::Int(IntWidth::U64));
|
||||
self.load_literal(
|
||||
|
@ -1528,6 +1621,66 @@ impl<
|
|||
offset,
|
||||
});
|
||||
}
|
||||
|
||||
fn build_int_bitwise_and(
|
||||
&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::and_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_int_bitwise_or(
|
||||
&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::or_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_int_bitwise_xor(
|
||||
&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::xor_reg64_reg64_reg64(buf, dst_reg, src1_reg, src2_reg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This impl block is for ir related instructions that need backend specific information.
|
||||
|
|
|
@ -86,7 +86,7 @@ pub struct StorageManager<
|
|||
> {
|
||||
phantom_cc: PhantomData<CC>,
|
||||
phantom_asm: PhantomData<ASM>,
|
||||
env: &'a Env<'a>,
|
||||
pub(crate) env: &'a Env<'a>,
|
||||
target_info: TargetInfo,
|
||||
// Data about where each symbol is stored.
|
||||
symbol_storage_map: MutMap<Symbol, Storage<GeneralReg, FloatReg>>,
|
||||
|
@ -541,12 +541,12 @@ impl<
|
|||
let (base_offset, size) = (*base_offset, *size);
|
||||
let mut data_offset = base_offset;
|
||||
for layout in field_layouts.iter().take(index as usize) {
|
||||
let field_size = layout.stack_size(self.target_info);
|
||||
let field_size = layout.stack_size(self.env.layout_interner, self.target_info);
|
||||
data_offset += field_size as i32;
|
||||
}
|
||||
debug_assert!(data_offset < base_offset + size as i32);
|
||||
let layout = field_layouts[index as usize];
|
||||
let size = layout.stack_size(self.target_info);
|
||||
let size = layout.stack_size(self.env.layout_interner, self.target_info);
|
||||
self.allocation_map.insert(*sym, owned_data);
|
||||
self.symbol_storage_map.insert(
|
||||
*sym,
|
||||
|
@ -591,8 +591,8 @@ impl<
|
|||
UnionLayout::NonRecursive(_) => {
|
||||
let (union_offset, _) = self.stack_offset_and_size(structure);
|
||||
|
||||
let (data_size, data_alignment) =
|
||||
union_layout.data_size_and_alignment(self.target_info);
|
||||
let (data_size, data_alignment) = union_layout
|
||||
.data_size_and_alignment(self.env.layout_interner, self.target_info);
|
||||
let id_offset = data_size - data_alignment;
|
||||
let discriminant = union_layout.discriminant();
|
||||
|
||||
|
@ -635,7 +635,7 @@ impl<
|
|||
layout: &Layout<'a>,
|
||||
fields: &'a [Symbol],
|
||||
) {
|
||||
let struct_size = layout.stack_size(self.target_info);
|
||||
let struct_size = layout.stack_size(self.env.layout_interner, self.target_info);
|
||||
if struct_size == 0 {
|
||||
self.symbol_storage_map.insert(*sym, NoData);
|
||||
return;
|
||||
|
@ -646,7 +646,8 @@ impl<
|
|||
let mut current_offset = base_offset;
|
||||
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
|
||||
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);
|
||||
let field_size = field_layout.stack_size(self.target_info);
|
||||
let field_size =
|
||||
field_layout.stack_size(self.env.layout_interner, self.target_info);
|
||||
current_offset += field_size as i32;
|
||||
}
|
||||
} else {
|
||||
|
@ -667,8 +668,8 @@ impl<
|
|||
) {
|
||||
match union_layout {
|
||||
UnionLayout::NonRecursive(field_layouts) => {
|
||||
let (data_size, data_alignment) =
|
||||
union_layout.data_size_and_alignment(self.target_info);
|
||||
let (data_size, data_alignment) = union_layout
|
||||
.data_size_and_alignment(self.env.layout_interner, self.target_info);
|
||||
let id_offset = data_size - data_alignment;
|
||||
if data_alignment < 8 || data_alignment % 8 != 0 {
|
||||
todo!("small/unaligned tagging");
|
||||
|
@ -679,7 +680,8 @@ impl<
|
|||
fields.iter().zip(field_layouts[tag_id as usize].iter())
|
||||
{
|
||||
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout);
|
||||
let field_size = field_layout.stack_size(self.target_info);
|
||||
let field_size =
|
||||
field_layout.stack_size(self.env.layout_interner, self.target_info);
|
||||
current_offset += field_size as i32;
|
||||
}
|
||||
self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
|
||||
|
@ -733,16 +735,19 @@ impl<
|
|||
let reg = self.load_to_float_reg(buf, sym);
|
||||
ASM::mov_base32_freg64(buf, to_offset, reg);
|
||||
}
|
||||
_ if layout.stack_size(self.target_info) == 0 => {}
|
||||
_ if layout.stack_size(self.env.layout_interner, self.target_info) == 0 => {}
|
||||
// TODO: Verify this is always true.
|
||||
// The dev backend does not deal with refcounting and does not care about if data is safe to memcpy.
|
||||
// It is just temporarily storing the value due to needing to free registers.
|
||||
// Later, it will be reloaded and stored in refcounted as needed.
|
||||
_ if layout.stack_size(self.target_info) > 8 => {
|
||||
_ if layout.stack_size(self.env.layout_interner, self.target_info) > 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!(size, layout.stack_size(self.target_info));
|
||||
debug_assert_eq!(
|
||||
size,
|
||||
layout.stack_size(self.env.layout_interner, self.target_info)
|
||||
);
|
||||
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);
|
||||
|
@ -1016,7 +1021,7 @@ impl<
|
|||
.insert(*symbol, Rc::new((base_offset, 8)));
|
||||
}
|
||||
_ => {
|
||||
let stack_size = layout.stack_size(self.target_info);
|
||||
let stack_size = layout.stack_size(self.env.layout_interner, self.target_info);
|
||||
if stack_size == 0 {
|
||||
self.symbol_storage_map.insert(*symbol, NoData);
|
||||
} else {
|
||||
|
|
|
@ -7,7 +7,7 @@ use bumpalo::collections::Vec;
|
|||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout};
|
||||
use roc_mono::layout::{Builtin, Layout, STLayoutInterner};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64();
|
||||
|
@ -266,12 +266,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
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(ret_layout) {
|
||||
if X86_64SystemV::returns_via_arg_pointer(storage_manager.env.layout_interner, ret_layout) {
|
||||
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]);
|
||||
general_i += 1;
|
||||
}
|
||||
for (layout, sym) in args.iter() {
|
||||
let stack_size = layout.stack_size(TARGET_INFO);
|
||||
let stack_size = layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO);
|
||||
match layout {
|
||||
single_register_integers!() => {
|
||||
if general_i < Self::GENERAL_PARAM_REGS.len() {
|
||||
|
@ -324,10 +324,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
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(ret_layout) {
|
||||
if Self::returns_via_arg_pointer(storage_manager.env.layout_interner, ret_layout) {
|
||||
// Save space on the stack for the result we will be return.
|
||||
let base_offset =
|
||||
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
|
||||
let base_offset = storage_manager.claim_stack_area(
|
||||
dst,
|
||||
ret_layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO),
|
||||
);
|
||||
// Set the first reg to the address base + offset.
|
||||
let ret_reg = Self::GENERAL_PARAM_REGS[general_i];
|
||||
general_i += 1;
|
||||
|
@ -386,8 +388,8 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x if x.stack_size(TARGET_INFO) > 16 => {
|
||||
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
|
||||
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) > 16 => {
|
||||
// TODO: Double check this.
|
||||
// Just copy onto the stack.
|
||||
// Use return reg as buffer because it will be empty right now.
|
||||
|
@ -431,8 +433,8 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
single_register_layouts!() => {
|
||||
internal_error!("single register layouts are not complex symbols");
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x if !Self::returns_via_arg_pointer(x) => {
|
||||
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
|
||||
x if !Self::returns_via_arg_pointer(storage_manager.env.layout_interner, x) => {
|
||||
let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
|
||||
debug_assert_eq!(base_offset % 8, 0);
|
||||
if size <= 8 {
|
||||
|
@ -487,9 +489,9 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
single_register_layouts!() => {
|
||||
internal_error!("single register layouts are not complex symbols");
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x if !Self::returns_via_arg_pointer(x) => {
|
||||
let size = layout.stack_size(TARGET_INFO);
|
||||
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
|
||||
x if !Self::returns_via_arg_pointer(storage_manager.env.layout_interner, x) => {
|
||||
let size = layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO);
|
||||
let offset = storage_manager.claim_stack_area(sym, size);
|
||||
if size <= 8 {
|
||||
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]);
|
||||
|
@ -516,10 +518,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
|
|||
}
|
||||
|
||||
impl X86_64SystemV {
|
||||
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
|
||||
fn returns_via_arg_pointer<'a>(
|
||||
interner: &STLayoutInterner<'a>,
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> bool {
|
||||
// TODO: This will need to be more complex/extended to fully support the calling convention.
|
||||
// details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
|
||||
ret_layout.stack_size(TARGET_INFO) > 16
|
||||
ret_layout.stack_size(interner, TARGET_INFO) > 16
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -667,7 +672,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
|
|||
) {
|
||||
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 i = 0;
|
||||
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) {
|
||||
if X86_64WindowsFastcall::returns_via_arg_pointer(
|
||||
storage_manager.env.layout_interner,
|
||||
ret_layout,
|
||||
) {
|
||||
storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
|
@ -682,7 +690,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
|
|||
storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[i]);
|
||||
i += 1;
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("Loading args with layout {:?}", x);
|
||||
}
|
||||
|
@ -717,9 +725,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
|
|||
ret_layout: &Layout<'a>,
|
||||
) {
|
||||
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||
if Self::returns_via_arg_pointer(ret_layout) {
|
||||
if Self::returns_via_arg_pointer(storage_manager.env.layout_interner, ret_layout) {
|
||||
// Save space on the stack for the arg we will return.
|
||||
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO));
|
||||
storage_manager.claim_stack_area(
|
||||
dst,
|
||||
ret_layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO),
|
||||
);
|
||||
todo!("claim first parama reg for the address");
|
||||
}
|
||||
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() {
|
||||
|
@ -768,7 +779,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
|
|||
tmp_stack_offset += 8;
|
||||
}
|
||||
}
|
||||
x if x.stack_size(TARGET_INFO) == 0 => {}
|
||||
x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
|
||||
x => {
|
||||
todo!("calling with arg type, {:?}", x);
|
||||
}
|
||||
|
@ -809,10 +820,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Windo
|
|||
}
|
||||
|
||||
impl X86_64WindowsFastcall {
|
||||
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
|
||||
fn returns_via_arg_pointer<'a>(
|
||||
interner: &STLayoutInterner<'a>,
|
||||
ret_layout: &Layout<'a>,
|
||||
) -> bool {
|
||||
// TODO: This is not fully correct there are some exceptions for "vector" types.
|
||||
// details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values
|
||||
ret_layout.stack_size(TARGET_INFO) > 8
|
||||
ret_layout.stack_size(interner, TARGET_INFO) > 8
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -902,6 +916,22 @@ fn x86_64_generic_cleanup_stack<'a>(
|
|||
X86_64Assembler::pop_reg64(buf, X86_64GeneralReg::RBP);
|
||||
}
|
||||
|
||||
type Reg64 = X86_64GeneralReg;
|
||||
|
||||
fn binop_move_src_to_dst_reg64<F>(buf: &mut Vec<'_, u8>, f: F, dst: Reg64, src1: Reg64, src2: Reg64)
|
||||
where
|
||||
F: FnOnce(&mut Vec<'_, u8>, X86_64GeneralReg, X86_64GeneralReg),
|
||||
{
|
||||
if dst == src1 {
|
||||
f(buf, dst, src2);
|
||||
} else if dst == src2 {
|
||||
f(buf, dst, src1);
|
||||
} else {
|
||||
mov_reg64_reg64(buf, dst, src1);
|
||||
f(buf, dst, src2);
|
||||
}
|
||||
}
|
||||
|
||||
impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
||||
// These functions should map to the raw assembly functions below.
|
||||
// In some cases, that means you can just directly call one of the direct assembly functions.
|
||||
|
@ -940,22 +970,12 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
|||
mov_reg64_reg64(buf, dst, src1);
|
||||
add_reg64_imm32(buf, dst, imm32);
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_reg64_reg64_reg64(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
dst: X86_64GeneralReg,
|
||||
src1: X86_64GeneralReg,
|
||||
src2: X86_64GeneralReg,
|
||||
) {
|
||||
if dst == src1 {
|
||||
add_reg64_reg64(buf, dst, src2);
|
||||
} else if dst == src2 {
|
||||
add_reg64_reg64(buf, dst, src1);
|
||||
} else {
|
||||
mov_reg64_reg64(buf, dst, src1);
|
||||
add_reg64_reg64(buf, dst, src2);
|
||||
}
|
||||
fn add_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
|
||||
binop_move_src_to_dst_reg64(buf, add_reg64_reg64, dst, src1, src2)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn add_freg32_freg32_freg32(
|
||||
buf: &mut Vec<'_, u8>,
|
||||
|
@ -1239,31 +1259,20 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
|||
#[inline(always)]
|
||||
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("sign extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("sign extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
todo!("sign extending 1 byte values");
|
||||
} else {
|
||||
internal_error!("Invalid size for sign extension: {}", size);
|
||||
match size {
|
||||
8 => Self::mov_reg64_base32(buf, dst, offset),
|
||||
4 | 2 | 1 => todo!("sign extending {size} byte values"),
|
||||
_ => internal_error!("Invalid size for sign extension: {size}"),
|
||||
}
|
||||
}
|
||||
#[inline(always)]
|
||||
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
|
||||
debug_assert!(size <= 8);
|
||||
if size == 8 {
|
||||
Self::mov_reg64_base32(buf, dst, offset);
|
||||
} else if size == 4 {
|
||||
todo!("zero extending 4 byte values");
|
||||
} else if size == 2 {
|
||||
todo!("zero extending 2 byte values");
|
||||
} else if size == 1 {
|
||||
movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset);
|
||||
} else {
|
||||
internal_error!("Invalid size for zero extension: {}", size);
|
||||
match size {
|
||||
8 => Self::mov_reg64_base32(buf, dst, offset),
|
||||
4 | 2 => todo!("zero extending {size} byte values"),
|
||||
1 => movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
|
||||
_ => internal_error!("Invalid size for zero extension: {size}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1390,6 +1399,22 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
|||
fn ret(buf: &mut Vec<'_, u8>) {
|
||||
ret(buf);
|
||||
}
|
||||
|
||||
fn set_if_overflow(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) {
|
||||
seto_reg64(buf, dst);
|
||||
}
|
||||
|
||||
fn and_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
|
||||
binop_move_src_to_dst_reg64(buf, and_reg64_reg64, dst, src1, src2)
|
||||
}
|
||||
|
||||
fn or_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
|
||||
binop_move_src_to_dst_reg64(buf, or_reg64_reg64, dst, src1, src2)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
impl X86_64Assembler {
|
||||
|
@ -1493,6 +1518,27 @@ fn add_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gene
|
|||
binop_reg64_reg64(0x01, buf, dst, src);
|
||||
}
|
||||
|
||||
/// `AND r/m64,r64` -> Bitwise logical and r64 to r/m64.
|
||||
#[inline(always)]
|
||||
fn and_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
|
||||
// NOTE: src and dst are flipped by design
|
||||
binop_reg64_reg64(0x23, buf, src, dst);
|
||||
}
|
||||
|
||||
/// `OR r/m64,r64` -> Bitwise logical or r64 to r/m64.
|
||||
#[inline(always)]
|
||||
fn or_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
|
||||
// NOTE: src and dst are flipped by design
|
||||
binop_reg64_reg64(0x0B, buf, src, dst);
|
||||
}
|
||||
|
||||
/// `XOR r/m64,r64` -> Bitwise logical exclusive or r64 to r/m64.
|
||||
#[inline(always)]
|
||||
fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
|
||||
// NOTE: src and dst are flipped by design
|
||||
binop_reg64_reg64(0x33, buf, src, dst);
|
||||
}
|
||||
|
||||
/// `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) {
|
||||
|
@ -2118,6 +2164,12 @@ fn setge_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
|||
set_reg64_help(0x9d, buf, reg);
|
||||
}
|
||||
|
||||
/// `SETO r/m64` -> Set byte if oveflow flag is set.
|
||||
#[inline(always)]
|
||||
fn seto_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
||||
set_reg64_help(0x90, buf, reg);
|
||||
}
|
||||
|
||||
/// `RET` -> Near return to calling procedure.
|
||||
#[inline(always)]
|
||||
fn ret(buf: &mut Vec<'_, u8>) {
|
||||
|
@ -2165,13 +2217,6 @@ fn push_reg64(buf: &mut Vec<'_, u8>, reg: X86_64GeneralReg) {
|
|||
}
|
||||
}
|
||||
|
||||
/// `XOR r/m64,r64` -> Xor r64 to r/m64.
|
||||
#[inline(always)]
|
||||
#[allow(dead_code)]
|
||||
fn xor_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) {
|
||||
binop_reg64_reg64(0x31, buf, dst, src);
|
||||
}
|
||||
|
||||
// When writing tests, it is a good idea to test both a number and unnumbered register.
|
||||
// This is because R8-R15 often have special instruction prefixes.
|
||||
#[cfg(test)]
|
||||
|
@ -2317,11 +2362,31 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_and_reg64_reg64() {
|
||||
disassembler_test!(
|
||||
and_reg64_reg64,
|
||||
|reg1, reg2| format!("and {reg1}, {reg2}"),
|
||||
ALL_GENERAL_REGS,
|
||||
ALL_GENERAL_REGS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_or_reg64_reg64() {
|
||||
disassembler_test!(
|
||||
or_reg64_reg64,
|
||||
|reg1, reg2| format!("or {reg1}, {reg2}"),
|
||||
ALL_GENERAL_REGS,
|
||||
ALL_GENERAL_REGS
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_xor_reg64_reg64() {
|
||||
disassembler_test!(
|
||||
xor_reg64_reg64,
|
||||
|reg1, reg2| format!("xor {}, {}", reg1, reg2),
|
||||
|reg1, reg2| format!("xor {reg1}, {reg2}"),
|
||||
ALL_GENERAL_REGS,
|
||||
ALL_GENERAL_REGS
|
||||
);
|
||||
|
|
|
@ -14,7 +14,9 @@ use roc_mono::ir::{
|
|||
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
|
||||
SelfRecursive, Stmt,
|
||||
};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutId, LayoutIds, TagIdIntType, UnionLayout};
|
||||
use roc_mono::layout::{
|
||||
Builtin, Layout, LayoutId, LayoutIds, STLayoutInterner, TagIdIntType, UnionLayout,
|
||||
};
|
||||
|
||||
mod generic64;
|
||||
mod object_builder;
|
||||
|
@ -23,6 +25,7 @@ mod run_roc;
|
|||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump,
|
||||
pub layout_interner: &'a STLayoutInterner<'a>,
|
||||
pub module_id: ModuleId,
|
||||
pub exposed_to_host: MutSet<Symbol>,
|
||||
pub lazy_literals: bool,
|
||||
|
@ -405,6 +408,9 @@ trait Backend<'a> {
|
|||
);
|
||||
self.build_num_add(sym, &args[0], &args[1], ret_layout)
|
||||
}
|
||||
LowLevel::NumAddChecked => {
|
||||
self.build_num_add_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(),
|
||||
|
@ -493,6 +499,27 @@ trait Backend<'a> {
|
|||
);
|
||||
self.build_num_sub(sym, &args[0], &args[1], ret_layout)
|
||||
}
|
||||
LowLevel::NumBitwiseAnd => {
|
||||
if let Layout::Builtin(Builtin::Int(int_width)) = ret_layout {
|
||||
self.build_int_bitwise_and(sym, &args[0], &args[1], *int_width)
|
||||
} else {
|
||||
internal_error!("bitwise and on a non-integer")
|
||||
}
|
||||
}
|
||||
LowLevel::NumBitwiseOr => {
|
||||
if let Layout::Builtin(Builtin::Int(int_width)) = ret_layout {
|
||||
self.build_int_bitwise_or(sym, &args[0], &args[1], *int_width)
|
||||
} else {
|
||||
internal_error!("bitwise or on a non-integer")
|
||||
}
|
||||
}
|
||||
LowLevel::NumBitwiseXor => {
|
||||
if let Layout::Builtin(Builtin::Int(int_width)) = ret_layout {
|
||||
self.build_int_bitwise_xor(sym, &args[0], &args[1], *int_width)
|
||||
} else {
|
||||
internal_error!("bitwise xor on a non-integer")
|
||||
}
|
||||
}
|
||||
LowLevel::Eq => {
|
||||
debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument");
|
||||
debug_assert_eq!(
|
||||
|
@ -694,6 +721,13 @@ 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)
|
||||
}
|
||||
_ => todo!("the function, {:?}", func_sym),
|
||||
}
|
||||
}
|
||||
|
@ -715,6 +749,16 @@ trait Backend<'a> {
|
|||
/// build_num_add stores the sum of src1 and src2 into dst.
|
||||
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
|
||||
|
||||
/// build_num_add_checked stores the sum of src1 and src2 into dst.
|
||||
fn build_num_add_checked(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
num_layout: &Layout<'a>,
|
||||
return_layout: &Layout<'a>,
|
||||
);
|
||||
|
||||
/// build_num_mul stores `src1 * src2` into dst.
|
||||
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
|
||||
|
||||
|
@ -727,6 +771,33 @@ trait Backend<'a> {
|
|||
/// build_num_sub stores the `src1 - src2` difference into dst.
|
||||
fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
|
||||
|
||||
/// stores the `src1 & src2` into dst.
|
||||
fn build_int_bitwise_and(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
int_width: IntWidth,
|
||||
);
|
||||
|
||||
/// stores the `src1 | src2` into dst.
|
||||
fn build_int_bitwise_or(
|
||||
&mut self,
|
||||
dst: &Symbol,
|
||||
src1: &Symbol,
|
||||
src2: &Symbol,
|
||||
int_width: IntWidth,
|
||||
);
|
||||
|
||||
/// stores the `src1 ^ src2` into dst.
|
||||
fn build_int_bitwise_xor(
|
||||
&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: &Layout<'a>);
|
||||
|
||||
|
|
|
@ -18,6 +18,6 @@ roc_std = { path = "../../roc_std" }
|
|||
roc_debug_flags = { path = "../debug_flags" }
|
||||
roc_region = { path = "../region" }
|
||||
morphic_lib = { path = "../../vendor/morphic_lib" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
inkwell = { path = "../../vendor/inkwell" }
|
||||
target-lexicon = "0.12.3"
|
||||
|
|
|
@ -247,29 +247,22 @@ 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).ptr_type(AddressSpace::Generic);
|
||||
|
||||
let argument = if layout.is_passed_by_reference(env.target_info) {
|
||||
env.builder
|
||||
.build_pointer_cast(
|
||||
let cast_ptr = env.builder.build_pointer_cast(
|
||||
argument_ptr.into_pointer_value(),
|
||||
basic_type,
|
||||
"cast_ptr_to_tag_build_transform_caller_help",
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
let argument_cast = env
|
||||
.builder
|
||||
.build_bitcast(*argument_ptr, basic_type, "load_opaque_1")
|
||||
.into_pointer_value();
|
||||
);
|
||||
|
||||
env.builder.build_load(argument_cast, "load_opaque_2")
|
||||
};
|
||||
let argument = load_roc_value(env, *layout, cast_ptr, "zig_helper_load_opaque");
|
||||
|
||||
arguments_cast.push(argument);
|
||||
}
|
||||
|
||||
match (
|
||||
closure_data_layout.is_represented().is_some(),
|
||||
closure_data_layout.runtime_representation(),
|
||||
closure_data_layout
|
||||
.is_represented(env.layout_interner)
|
||||
.is_some(),
|
||||
closure_data_layout.runtime_representation(env.layout_interner),
|
||||
) {
|
||||
(false, _) => {
|
||||
// the function doesn't expect a closure argument, nothing to add
|
||||
|
@ -279,10 +272,10 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
|
|||
|
||||
let closure_cast = env
|
||||
.builder
|
||||
.build_bitcast(closure_ptr, closure_type, "load_opaque")
|
||||
.build_bitcast(closure_ptr, closure_type, "cast_opaque_closure")
|
||||
.into_pointer_value();
|
||||
|
||||
let closure_data = env.builder.build_load(closure_cast, "load_opaque");
|
||||
let closure_data = load_roc_value(env, layout, closure_cast, "load_closure");
|
||||
|
||||
arguments_cast.push(closure_data);
|
||||
}
|
||||
|
@ -402,7 +395,7 @@ fn build_rc_wrapper<'a, 'ctx, 'env>(
|
|||
|
||||
let value_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
|
||||
|
||||
let value = if layout.is_passed_by_reference(env.target_info) {
|
||||
let value = if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
env.builder
|
||||
.build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper")
|
||||
.into()
|
||||
|
@ -590,7 +583,8 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
|
|||
|
||||
let default = [value1.into(), value2.into()];
|
||||
|
||||
let arguments_cast = match closure_data_layout.runtime_representation() {
|
||||
let arguments_cast =
|
||||
match closure_data_layout.runtime_representation(env.layout_interner) {
|
||||
Layout::Struct {
|
||||
field_layouts: &[], ..
|
||||
} => {
|
||||
|
|
|
@ -8,7 +8,6 @@ use crate::llvm::build_list::{
|
|||
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,
|
||||
};
|
||||
use crate::llvm::build_str::dec_to_str;
|
||||
use crate::llvm::compare::{generic_eq, generic_neq};
|
||||
use crate::llvm::convert::{
|
||||
self, argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout, zig_str_type,
|
||||
|
@ -57,7 +56,7 @@ use roc_mono::ir::{
|
|||
};
|
||||
use roc_mono::layout::{
|
||||
Builtin, CapturesNiche, LambdaName, LambdaSet, Layout, LayoutIds, RawFunctionLayout,
|
||||
TagIdIntType, UnionLayout,
|
||||
STLayoutInterner, TagIdIntType, UnionLayout,
|
||||
};
|
||||
use roc_std::RocDec;
|
||||
use roc_target::{PtrWidth, TargetInfo};
|
||||
|
@ -65,7 +64,7 @@ use std::convert::TryInto;
|
|||
use std::path::Path;
|
||||
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||
|
||||
use super::convert::{zig_with_overflow_roc_dec, RocUnion};
|
||||
use super::convert::{zig_dec_type, zig_with_overflow_roc_dec, RocUnion};
|
||||
|
||||
#[inline(always)]
|
||||
fn print_fn_verification_output() -> bool {
|
||||
|
@ -203,6 +202,7 @@ impl LlvmBackendMode {
|
|||
|
||||
pub struct Env<'a, 'ctx, 'env> {
|
||||
pub arena: &'a Bump,
|
||||
pub layout_interner: &'env STLayoutInterner<'a>,
|
||||
pub context: &'ctx Context,
|
||||
pub builder: &'env Builder<'ctx>,
|
||||
pub dibuilder: &'env DebugInfoBuilder<'ctx>,
|
||||
|
@ -311,7 +311,7 @@ impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> {
|
|||
}
|
||||
|
||||
pub fn alignment_intvalue(&self, element_layout: &Layout<'a>) -> BasicValueEnum<'ctx> {
|
||||
let alignment = element_layout.alignment_bytes(self.target_info);
|
||||
let alignment = element_layout.alignment_bytes(self.layout_interner, self.target_info);
|
||||
let alignment_iv = self.alignment_const(alignment);
|
||||
|
||||
alignment_iv.into()
|
||||
|
@ -880,7 +880,9 @@ fn promote_to_wasm_test_wrapper<'a, 'ctx, 'env>(
|
|||
let roc_main_fn_result = call_roc_function(env, roc_main_fn, &top_level.result, &[]);
|
||||
|
||||
// For consistency, we always return with a heap-allocated value
|
||||
let (size, alignment) = top_level.result.stack_size_and_alignment(env.target_info);
|
||||
let (size, alignment) = top_level
|
||||
.result
|
||||
.stack_size_and_alignment(env.layout_interner, env.target_info);
|
||||
let number_of_bytes = env.ptr_int().const_int(size as _, false);
|
||||
let void_ptr = env.call_alloc(number_of_bytes, alignment);
|
||||
|
||||
|
@ -1249,8 +1251,8 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
let allocation = reserve_with_refcount_help(
|
||||
env,
|
||||
basic_type,
|
||||
layout.stack_size(env.target_info),
|
||||
layout.alignment_bytes(env.target_info),
|
||||
layout.stack_size(env.layout_interner, env.target_info),
|
||||
layout.alignment_bytes(env.layout_interner, env.target_info),
|
||||
);
|
||||
|
||||
store_roc_value(env, *layout, allocation, value);
|
||||
|
@ -1328,7 +1330,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
|||
let (value, layout) = load_symbol_and_layout(scope, structure);
|
||||
|
||||
let layout = if let Layout::LambdaSet(lambda_set) = layout {
|
||||
lambda_set.runtime_representation()
|
||||
lambda_set.runtime_representation(env.layout_interner)
|
||||
} else {
|
||||
*layout
|
||||
};
|
||||
|
@ -1600,7 +1602,7 @@ fn build_tag_field_value<'a, 'ctx, 'env>(
|
|||
env.context.i64_type().ptr_type(AddressSpace::Generic),
|
||||
"cast_recursive_pointer",
|
||||
)
|
||||
} else if tag_field_layout.is_passed_by_reference(env.target_info) {
|
||||
} else if tag_field_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
debug_assert!(value.is_pointer_value());
|
||||
|
||||
// NOTE: we rely on this being passed to `store_roc_value` so that
|
||||
|
@ -1661,7 +1663,7 @@ fn build_struct<'a, 'ctx, 'env>(
|
|||
if !field_layout.is_dropped_because_empty() {
|
||||
field_types.push(basic_type_from_layout(env, field_layout));
|
||||
|
||||
if field_layout.is_passed_by_reference(env.target_info) {
|
||||
if field_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
let field_value = env
|
||||
.builder
|
||||
.build_load(field_expr.into_pointer_value(), "load_tag_to_put_in_struct");
|
||||
|
@ -1697,7 +1699,12 @@ fn build_tag<'a, 'ctx, 'env>(
|
|||
|
||||
let data = build_struct(env, scope, arguments);
|
||||
|
||||
let roc_union = RocUnion::tagged_from_slices(env.context, tags, env.target_info);
|
||||
let roc_union = RocUnion::tagged_from_slices(
|
||||
env.layout_interner,
|
||||
env.context,
|
||||
tags,
|
||||
env.target_info,
|
||||
);
|
||||
let value = roc_union.as_struct_value(env, data, Some(tag_id as _));
|
||||
|
||||
let alloca = create_entry_block_alloca(
|
||||
|
@ -1788,8 +1795,12 @@ fn build_tag<'a, 'ctx, 'env>(
|
|||
nullable_id,
|
||||
other_fields,
|
||||
} => {
|
||||
let roc_union =
|
||||
RocUnion::untagged_from_slices(env.context, &[other_fields], env.target_info);
|
||||
let roc_union = RocUnion::untagged_from_slices(
|
||||
env.layout_interner,
|
||||
env.context,
|
||||
&[other_fields],
|
||||
env.target_info,
|
||||
);
|
||||
|
||||
if tag_id == *nullable_id as _ {
|
||||
let output_type = roc_union.struct_type().ptr_type(AddressSpace::Generic);
|
||||
|
@ -2104,8 +2115,8 @@ pub fn reserve_with_refcount<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'a>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let stack_size = layout.stack_size(env.target_info);
|
||||
let alignment_bytes = layout.alignment_bytes(env.target_info);
|
||||
let stack_size = layout.stack_size(env.layout_interner, env.target_info);
|
||||
let alignment_bytes = layout.alignment_bytes(env.layout_interner, env.target_info);
|
||||
|
||||
let basic_type = basic_type_from_layout(env, layout);
|
||||
|
||||
|
@ -2120,9 +2131,9 @@ fn reserve_with_refcount_union_as_block_of_memory<'a, 'ctx, 'env>(
|
|||
let ptr_bytes = env.target_info;
|
||||
|
||||
let roc_union = if union_layout.stores_tag_id_as_data(ptr_bytes) {
|
||||
RocUnion::tagged_from_slices(env.context, fields, env.target_info)
|
||||
RocUnion::tagged_from_slices(env.layout_interner, env.context, fields, env.target_info)
|
||||
} else {
|
||||
RocUnion::untagged_from_slices(env.context, fields, env.target_info)
|
||||
RocUnion::untagged_from_slices(env.layout_interner, env.context, fields, env.target_info)
|
||||
};
|
||||
|
||||
reserve_with_refcount_help(
|
||||
|
@ -2211,10 +2222,10 @@ fn list_literal<'a, 'ctx, 'env>(
|
|||
// if element_type.is_int_type() {
|
||||
if false {
|
||||
let element_type = element_type.into_int_type();
|
||||
let element_width = element_layout.stack_size(env.target_info);
|
||||
let element_width = element_layout.stack_size(env.layout_interner, env.target_info);
|
||||
let size = list_length * element_width as usize;
|
||||
let alignment = element_layout
|
||||
.alignment_bytes(env.target_info)
|
||||
.alignment_bytes(env.layout_interner, env.target_info)
|
||||
.max(env.target_info.ptr_width() as u32);
|
||||
|
||||
let mut is_all_constant = true;
|
||||
|
@ -2351,7 +2362,7 @@ pub fn load_roc_value<'a, 'ctx, 'env>(
|
|||
source: PointerValue<'ctx>,
|
||||
name: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
if layout.is_passed_by_reference(env.target_info) {
|
||||
if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
let alloca = entry_block_alloca_zerofill(env, basic_type_from_layout(env, &layout), name);
|
||||
|
||||
store_roc_value(env, layout, alloca, source.into());
|
||||
|
@ -2368,7 +2379,7 @@ pub fn use_roc_value<'a, 'ctx, 'env>(
|
|||
source: BasicValueEnum<'ctx>,
|
||||
name: &str,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
if layout.is_passed_by_reference(env.target_info) {
|
||||
if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
let alloca = entry_block_alloca_zerofill(env, basic_type_from_layout(env, &layout), name);
|
||||
|
||||
env.builder.build_store(alloca, source);
|
||||
|
@ -2399,15 +2410,16 @@ pub fn store_roc_value<'a, 'ctx, 'env>(
|
|||
destination: PointerValue<'ctx>,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) {
|
||||
if layout.is_passed_by_reference(env.target_info) {
|
||||
if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
debug_assert!(value.is_pointer_value());
|
||||
|
||||
let align_bytes = layout.alignment_bytes(env.target_info);
|
||||
let align_bytes = layout.alignment_bytes(env.layout_interner, env.target_info);
|
||||
|
||||
if align_bytes > 0 {
|
||||
let size = env
|
||||
.ptr_int()
|
||||
.const_int(layout.stack_size(env.target_info) as u64, false);
|
||||
let size = env.ptr_int().const_int(
|
||||
layout.stack_size(env.layout_interner, env.target_info) as u64,
|
||||
false,
|
||||
);
|
||||
|
||||
env.builder
|
||||
.build_memcpy(
|
||||
|
@ -2420,6 +2432,15 @@ pub fn store_roc_value<'a, 'ctx, 'env>(
|
|||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
let destination_type = destination
|
||||
.get_type()
|
||||
.get_element_type()
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let value =
|
||||
cast_if_necessary_for_opaque_recursive_pointers(env.builder, value, destination_type);
|
||||
|
||||
env.builder.build_store(destination, value);
|
||||
}
|
||||
}
|
||||
|
@ -2502,8 +2523,9 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
// store_roc_value(env, *layout, out_parameter.into_pointer_value(), value);
|
||||
|
||||
let destination = out_parameter.into_pointer_value();
|
||||
if layout.is_passed_by_reference(env.target_info) {
|
||||
let align_bytes = layout.alignment_bytes(env.target_info);
|
||||
if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
let align_bytes =
|
||||
layout.alignment_bytes(env.layout_interner, env.target_info);
|
||||
|
||||
if align_bytes > 0 {
|
||||
debug_assert!(
|
||||
|
@ -2534,7 +2556,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
// Hence, we explicitly memcpy source to destination, and rely on
|
||||
// LLVM optimizing away any inefficiencies.
|
||||
let target_info = env.target_info;
|
||||
let width = layout.stack_size(target_info);
|
||||
let width = layout.stack_size(env.layout_interner, target_info);
|
||||
let size = env.ptr_int().const_int(width as _, false);
|
||||
|
||||
env.builder
|
||||
|
@ -2614,7 +2636,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
for param in parameters.iter() {
|
||||
let basic_type = basic_type_from_layout(env, ¶m.layout);
|
||||
|
||||
let phi_type = if param.layout.is_passed_by_reference(env.target_info) {
|
||||
let phi_type = if param
|
||||
.layout
|
||||
.is_passed_by_reference(env.layout_interner, env.target_info)
|
||||
{
|
||||
basic_type.ptr_type(AddressSpace::Generic).into()
|
||||
} else {
|
||||
basic_type
|
||||
|
@ -2697,7 +2722,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
let (value, layout) = load_symbol_and_layout(scope, symbol);
|
||||
let layout = *layout;
|
||||
|
||||
if layout.contains_refcounted() {
|
||||
if layout.contains_refcounted(env.layout_interner) {
|
||||
increment_refcount_layout(
|
||||
env,
|
||||
parent,
|
||||
|
@ -2713,7 +2738,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
Dec(symbol) => {
|
||||
let (value, layout) = load_symbol_and_layout(scope, symbol);
|
||||
|
||||
if layout.contains_refcounted() {
|
||||
if layout.contains_refcounted(env.layout_interner) {
|
||||
decrement_refcount_layout(env, parent, layout_ids, value, layout);
|
||||
}
|
||||
|
||||
|
@ -2726,7 +2751,8 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
|||
Layout::Builtin(Builtin::Str) => todo!(),
|
||||
Layout::Builtin(Builtin::List(element_layout)) => {
|
||||
debug_assert!(value.is_struct_value());
|
||||
let alignment = element_layout.alignment_bytes(env.target_info);
|
||||
let alignment = element_layout
|
||||
.alignment_bytes(env.layout_interner, env.target_info);
|
||||
|
||||
build_list::decref(env, value.into_struct_value(), alignment);
|
||||
}
|
||||
|
@ -2933,6 +2959,29 @@ pub fn load_symbol_and_lambda_set<'a, 'ctx, 'b>(
|
|||
}
|
||||
}
|
||||
|
||||
/// 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.
|
||||
///
|
||||
/// This will no longer be necessary and should be removed after we employ opaque pointers from
|
||||
/// LLVM.
|
||||
pub fn cast_if_necessary_for_opaque_recursive_pointers<'ctx>(
|
||||
builder: &Builder<'ctx>,
|
||||
from_value: BasicValueEnum<'ctx>,
|
||||
to_type: BasicTypeEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
if from_value.get_type() != to_type {
|
||||
complex_bitcast(
|
||||
builder,
|
||||
from_value,
|
||||
to_type,
|
||||
"bitcast_for_opaque_recursive_pointer",
|
||||
)
|
||||
} else {
|
||||
from_value
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast a value to another value of the same (or smaller?) size
|
||||
pub fn cast_basic_basic<'ctx>(
|
||||
builder: &Builder<'ctx>,
|
||||
|
@ -3701,6 +3750,37 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
|
|||
Linkage::External,
|
||||
);
|
||||
|
||||
// a temporary solution to be able to pass RocStr by-value from a host language.
|
||||
{
|
||||
let extra = match cc_return {
|
||||
CCReturn::Return => 0,
|
||||
CCReturn::ByPointer => 1,
|
||||
CCReturn::Void => 0,
|
||||
};
|
||||
|
||||
for (i, layout) in arguments.iter().enumerate() {
|
||||
if let Layout::Builtin(Builtin::Str) = layout {
|
||||
// Indicate to LLVM that this argument is semantically passed by-value
|
||||
// even though technically (because of its size) it is passed by-reference
|
||||
let byval_attribute_id = Attribute::get_named_enum_kind_id("byval");
|
||||
debug_assert!(byval_attribute_id > 0);
|
||||
|
||||
// if ret_typ is a pointer type. We need the base type here.
|
||||
let ret_typ = c_function.get_type().get_param_types()[i + extra];
|
||||
let ret_base_typ = if ret_typ.is_pointer_type() {
|
||||
ret_typ.into_pointer_type().get_element_type()
|
||||
} else {
|
||||
ret_typ.as_any_type_enum()
|
||||
};
|
||||
|
||||
let byval_attribute = env
|
||||
.context
|
||||
.create_type_attribute(byval_attribute_id, ret_base_typ);
|
||||
c_function.add_attribute(AttributeLoc::Param((i + extra) as u32), byval_attribute);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let subprogram = env.new_subprogram(c_function_name);
|
||||
c_function.set_subprogram(subprogram);
|
||||
|
||||
|
@ -3726,6 +3806,10 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
|
|||
// 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[..], ¶m_types[..]),
|
||||
};
|
||||
|
||||
|
@ -3807,8 +3891,20 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
|
|||
},
|
||||
CCReturn::ByPointer => {
|
||||
let out_ptr = c_function.get_nth_param(0).unwrap().into_pointer_value();
|
||||
|
||||
match roc_return {
|
||||
RocReturn::Return => {
|
||||
env.builder.build_store(out_ptr, value);
|
||||
}
|
||||
RocReturn::ByPointer => {
|
||||
// TODO: ideally, in this case, we should pass the C return pointer directly
|
||||
// into the call_roc_function rather than forcing an extra alloca, load, and
|
||||
// store!
|
||||
let value = env
|
||||
.builder
|
||||
.build_load(value.into_pointer_value(), "load_roc_result");
|
||||
env.builder.build_store(out_ptr, value);
|
||||
}
|
||||
}
|
||||
env.builder.build_return(None);
|
||||
}
|
||||
CCReturn::Void => {
|
||||
|
@ -3900,7 +3996,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValue<'ctx> {
|
||||
// The size of jump_buf is platform-dependent.
|
||||
// The size of jump_buf is target-dependent.
|
||||
// - AArch64 needs 3 machine-sized words
|
||||
// - LLVM says the following about the SJLJ intrinsic:
|
||||
//
|
||||
|
@ -4139,7 +4235,7 @@ fn make_good_roc_result<'a, 'ctx, 'env>(
|
|||
.build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
|
||||
.unwrap();
|
||||
|
||||
let v3 = if return_layout.is_passed_by_reference(env.target_info) {
|
||||
let v3 = if return_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
let loaded = env.builder.build_load(
|
||||
return_value.into_pointer_value(),
|
||||
"load_call_result_passed_by_ptr",
|
||||
|
@ -4402,7 +4498,9 @@ fn build_procedures_help<'a, 'ctx, 'env>(
|
|||
|
||||
let it = procedures.iter().map(|x| x.1);
|
||||
|
||||
let solutions = match roc_alias_analysis::spec_program(opt_level, opt_entry_point, it) {
|
||||
let solutions =
|
||||
match roc_alias_analysis::spec_program(env.layout_interner, opt_level, opt_entry_point, it)
|
||||
{
|
||||
Err(e) => panic!("Error in alias analysis: {}", e),
|
||||
Ok(solutions) => solutions,
|
||||
};
|
||||
|
@ -4671,7 +4769,8 @@ fn build_closure_caller<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
let closure_argument_type = {
|
||||
let basic_type = basic_type_from_layout(env, &lambda_set.runtime_representation());
|
||||
let basic_type =
|
||||
basic_type_from_layout(env, &lambda_set.runtime_representation(env.layout_interner));
|
||||
|
||||
basic_type.ptr_type(AddressSpace::Generic)
|
||||
};
|
||||
|
@ -4718,10 +4817,12 @@ fn build_closure_caller<'a, 'ctx, 'env>(
|
|||
|
||||
// NOTE this may be incorrect in the long run
|
||||
// here we load any argument that is a pointer
|
||||
let closure_layout = lambda_set.runtime_representation();
|
||||
let closure_layout = lambda_set.runtime_representation(env.layout_interner);
|
||||
let layouts_it = arguments.iter().chain(std::iter::once(&closure_layout));
|
||||
for (param, layout) in evaluator_arguments.iter_mut().zip(layouts_it) {
|
||||
if param.is_pointer_value() && !layout.is_passed_by_reference(env.target_info) {
|
||||
if param.is_pointer_value()
|
||||
&& !layout.is_passed_by_reference(env.layout_interner, env.target_info)
|
||||
{
|
||||
*param = builder.build_load(param.into_pointer_value(), "load_param");
|
||||
}
|
||||
}
|
||||
|
@ -4739,13 +4840,14 @@ fn build_closure_caller<'a, 'ctx, 'env>(
|
|||
} else {
|
||||
let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments);
|
||||
|
||||
if return_layout.is_passed_by_reference(env.target_info) {
|
||||
let align_bytes = return_layout.alignment_bytes(env.target_info);
|
||||
if return_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
let align_bytes = return_layout.alignment_bytes(env.layout_interner, env.target_info);
|
||||
|
||||
if align_bytes > 0 {
|
||||
let size = env
|
||||
.ptr_int()
|
||||
.const_int(return_layout.stack_size(env.target_info) as u64, false);
|
||||
let size = env.ptr_int().const_int(
|
||||
return_layout.stack_size(env.layout_interner, env.target_info) as u64,
|
||||
false,
|
||||
);
|
||||
|
||||
env.builder
|
||||
.build_memcpy(
|
||||
|
@ -4772,7 +4874,7 @@ fn build_closure_caller<'a, 'ctx, 'env>(
|
|||
env,
|
||||
def_name,
|
||||
alias_symbol,
|
||||
lambda_set.runtime_representation(),
|
||||
lambda_set.runtime_representation(env.layout_interner),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -5042,7 +5144,7 @@ pub fn call_roc_function<'a, 'ctx, 'env>(
|
|||
debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
|
||||
call.set_call_convention(FAST_CALL_CONV);
|
||||
|
||||
if result_layout.is_passed_by_reference(env.target_info) {
|
||||
if result_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
result_alloca.into()
|
||||
} else {
|
||||
env.builder
|
||||
|
@ -5124,8 +5226,11 @@ fn roc_function_call<'a, 'ctx, 'env>(
|
|||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
||||
let inc_closure_data =
|
||||
build_inc_n_wrapper(env, layout_ids, &lambda_set.runtime_representation())
|
||||
let inc_closure_data = build_inc_n_wrapper(
|
||||
env,
|
||||
layout_ids,
|
||||
&lambda_set.runtime_representation(env.layout_interner),
|
||||
)
|
||||
.as_global_value()
|
||||
.as_pointer_value();
|
||||
|
||||
|
@ -5778,9 +5883,42 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
// Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 }
|
||||
debug_assert_eq!(args.len(), 2);
|
||||
|
||||
use roc_target::OperatingSystem::*;
|
||||
|
||||
let string = load_symbol(scope, &args[0]);
|
||||
let index = load_symbol(scope, &args[1]);
|
||||
|
||||
match env.target_info.operating_system {
|
||||
Windows => {
|
||||
// we have to go digging to find the return type
|
||||
let function = env
|
||||
.module
|
||||
.get_function(bitcode::STR_GET_SCALAR_UNSAFE)
|
||||
.unwrap();
|
||||
|
||||
let return_type = function.get_type().get_param_types()[0]
|
||||
.into_pointer_type()
|
||||
.get_element_type()
|
||||
.into_struct_type();
|
||||
|
||||
let result = env.builder.build_alloca(return_type, "result");
|
||||
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[result.into(), string, index],
|
||||
bitcode::STR_GET_SCALAR_UNSAFE,
|
||||
);
|
||||
|
||||
let return_type = basic_type_from_layout(env, layout);
|
||||
let cast_result = env.builder.build_pointer_cast(
|
||||
result,
|
||||
return_type.ptr_type(AddressSpace::Generic),
|
||||
"cast",
|
||||
);
|
||||
|
||||
env.builder.build_load(cast_result, "load_result")
|
||||
}
|
||||
Unix => {
|
||||
let result = call_str_bitcode_fn(
|
||||
env,
|
||||
&[string],
|
||||
|
@ -5789,7 +5927,7 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
bitcode::STR_GET_SCALAR_UNSAFE,
|
||||
);
|
||||
|
||||
// on 32-bit platforms, zig bitpacks the struct
|
||||
// on 32-bit targets, zig bitpacks the struct
|
||||
match env.target_info.ptr_width() {
|
||||
PtrWidth::Bytes8 => result,
|
||||
PtrWidth::Bytes4 => {
|
||||
|
@ -5798,6 +5936,9 @@ fn run_low_level<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
}
|
||||
Wasi => unimplemented!(),
|
||||
}
|
||||
}
|
||||
StrCountUtf8Bytes => {
|
||||
// Str.countUtf8Bytes : Str -> Nat
|
||||
debug_assert_eq!(args.len(), 1);
|
||||
|
@ -6466,7 +6607,7 @@ fn to_cc_type<'a, 'ctx, 'env>(
|
|||
env: &Env<'a, 'ctx, 'env>,
|
||||
layout: &Layout<'a>,
|
||||
) -> BasicTypeEnum<'ctx> {
|
||||
match layout.runtime_representation() {
|
||||
match layout.runtime_representation(env.layout_interner) {
|
||||
Layout::Builtin(builtin) => to_cc_type_builtin(env, &builtin),
|
||||
layout => {
|
||||
// TODO this is almost certainly incorrect for bigger structs
|
||||
|
@ -6498,7 +6639,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
enum RocReturn {
|
||||
/// Return as normal
|
||||
Return,
|
||||
|
@ -6508,7 +6649,11 @@ enum RocReturn {
|
|||
}
|
||||
|
||||
impl RocReturn {
|
||||
fn roc_return_by_pointer(target_info: TargetInfo, layout: Layout) -> bool {
|
||||
fn roc_return_by_pointer(
|
||||
interner: &STLayoutInterner,
|
||||
target_info: TargetInfo,
|
||||
layout: Layout,
|
||||
) -> bool {
|
||||
match layout {
|
||||
Layout::Builtin(builtin) => {
|
||||
use Builtin::*;
|
||||
|
@ -6523,15 +6668,17 @@ impl RocReturn {
|
|||
}
|
||||
}
|
||||
Layout::Union(UnionLayout::NonRecursive(_)) => true,
|
||||
Layout::LambdaSet(lambda_set) => {
|
||||
RocReturn::roc_return_by_pointer(target_info, lambda_set.runtime_representation())
|
||||
}
|
||||
Layout::LambdaSet(lambda_set) => RocReturn::roc_return_by_pointer(
|
||||
interner,
|
||||
target_info,
|
||||
lambda_set.runtime_representation(interner),
|
||||
),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn from_layout<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> Self {
|
||||
if Self::roc_return_by_pointer(env.target_info, *layout) {
|
||||
if Self::roc_return_by_pointer(env.layout_interner, env.target_info, *layout) {
|
||||
RocReturn::ByPointer
|
||||
} else {
|
||||
RocReturn::Return
|
||||
|
@ -6669,8 +6816,14 @@ impl<'ctx> FunctionSpec<'ctx> {
|
|||
|
||||
/// According to the C ABI, how should we return a value with the given layout?
|
||||
pub fn to_cc_return<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> CCReturn {
|
||||
let return_size = layout.stack_size(env.target_info);
|
||||
let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32;
|
||||
let return_size = layout.stack_size(env.layout_interner, env.target_info);
|
||||
let pass_result_by_pointer = match env.target_info.operating_system {
|
||||
roc_target::OperatingSystem::Windows => {
|
||||
return_size >= 2 * env.target_info.ptr_width() as u32
|
||||
}
|
||||
roc_target::OperatingSystem::Unix => return_size > 2 * env.target_info.ptr_width() as u32,
|
||||
roc_target::OperatingSystem::Wasi => unreachable!(),
|
||||
};
|
||||
|
||||
if return_size == 0 {
|
||||
CCReturn::Void
|
||||
|
@ -6830,7 +6983,16 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
|
|||
builder.build_return(Some(&return_value));
|
||||
}
|
||||
RocReturn::ByPointer => {
|
||||
debug_assert!(matches!(cc_return, CCReturn::ByPointer));
|
||||
match cc_return {
|
||||
CCReturn::Return => {
|
||||
let result = call.try_as_basic_value().left().unwrap();
|
||||
env.builder.build_store(return_pointer, result);
|
||||
}
|
||||
|
||||
CCReturn::ByPointer | CCReturn::Void => {
|
||||
// the return value (if any) is already written to the return pointer
|
||||
}
|
||||
}
|
||||
|
||||
builder.build_return(None);
|
||||
}
|
||||
|
@ -7268,39 +7430,123 @@ fn build_float_binop<'a, 'ctx, 'env>(
|
|||
}
|
||||
}
|
||||
|
||||
fn dec_split_into_words<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
value: IntValue<'ctx>,
|
||||
) -> (IntValue<'ctx>, IntValue<'ctx>) {
|
||||
let int_64 = env.context.i128_type().const_int(64, false);
|
||||
let int_64_type = env.context.i64_type();
|
||||
|
||||
let left_bits_i128 = env
|
||||
.builder
|
||||
.build_right_shift(value, int_64, false, "left_bits_i128");
|
||||
|
||||
(
|
||||
env.builder.build_int_cast(value, int_64_type, ""),
|
||||
env.builder.build_int_cast(left_bits_i128, int_64_type, ""),
|
||||
)
|
||||
}
|
||||
|
||||
fn dec_alloca<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
value: IntValue<'ctx>,
|
||||
) -> PointerValue<'ctx> {
|
||||
let dec_type = zig_dec_type(env);
|
||||
|
||||
let alloca = env.builder.build_alloca(dec_type, "dec_alloca");
|
||||
|
||||
let instruction = alloca.as_instruction_value().unwrap();
|
||||
instruction.set_alignment(16).unwrap();
|
||||
|
||||
let ptr = env.builder.build_pointer_cast(
|
||||
alloca,
|
||||
value.get_type().ptr_type(AddressSpace::Generic),
|
||||
"cast_to_i128_ptr",
|
||||
);
|
||||
|
||||
env.builder.build_store(ptr, value);
|
||||
|
||||
alloca
|
||||
}
|
||||
|
||||
fn dec_to_str<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
dec: BasicValueEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_target::OperatingSystem::*;
|
||||
|
||||
let dec = dec.into_int_value();
|
||||
|
||||
match env.target_info.operating_system {
|
||||
Windows => {
|
||||
//
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[],
|
||||
&[dec_alloca(env, dec).into()],
|
||||
BitcodeReturns::Str,
|
||||
bitcode::DEC_TO_STR,
|
||||
)
|
||||
}
|
||||
Unix => {
|
||||
let (low, high) = dec_split_into_words(env, dec);
|
||||
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[],
|
||||
&[low.into(), high.into()],
|
||||
BitcodeReturns::Str,
|
||||
bitcode::DEC_TO_STR,
|
||||
)
|
||||
}
|
||||
Wasi => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn dec_binop_with_overflow<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
fn_name: &str,
|
||||
lhs: BasicValueEnum<'ctx>,
|
||||
rhs: BasicValueEnum<'ctx>,
|
||||
) -> StructValue<'ctx> {
|
||||
use roc_target::OperatingSystem::*;
|
||||
|
||||
let lhs = lhs.into_int_value();
|
||||
let rhs = rhs.into_int_value();
|
||||
|
||||
let return_type = zig_with_overflow_roc_dec(env);
|
||||
let return_alloca = env.builder.build_alloca(return_type, "return_alloca");
|
||||
|
||||
let int_64 = env.context.i128_type().const_int(64, false);
|
||||
let int_64_type = env.context.i64_type();
|
||||
|
||||
let lhs1 = env
|
||||
.builder
|
||||
.build_right_shift(lhs, int_64, false, "lhs_left_bits");
|
||||
let rhs1 = env
|
||||
.builder
|
||||
.build_right_shift(rhs, int_64, false, "rhs_left_bits");
|
||||
match env.target_info.operating_system {
|
||||
Windows => {
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
return_alloca.into(),
|
||||
dec_alloca(env, lhs).into(),
|
||||
dec_alloca(env, rhs).into(),
|
||||
],
|
||||
fn_name,
|
||||
);
|
||||
}
|
||||
Unix => {
|
||||
let (lhs_low, lhs_high) = dec_split_into_words(env, lhs);
|
||||
let (rhs_low, rhs_high) = dec_split_into_words(env, rhs);
|
||||
|
||||
call_void_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
return_alloca.into(),
|
||||
env.builder.build_int_cast(lhs, int_64_type, "").into(),
|
||||
env.builder.build_int_cast(lhs1, int_64_type, "").into(),
|
||||
env.builder.build_int_cast(rhs, int_64_type, "").into(),
|
||||
env.builder.build_int_cast(rhs1, int_64_type, "").into(),
|
||||
lhs_low.into(),
|
||||
lhs_high.into(),
|
||||
rhs_low.into(),
|
||||
rhs_high.into(),
|
||||
],
|
||||
fn_name,
|
||||
);
|
||||
}
|
||||
Wasi => unimplemented!(),
|
||||
}
|
||||
|
||||
env.builder
|
||||
.build_load(return_alloca, "load_dec")
|
||||
|
@ -7313,30 +7559,38 @@ pub fn dec_binop_with_unchecked<'a, 'ctx, 'env>(
|
|||
lhs: BasicValueEnum<'ctx>,
|
||||
rhs: BasicValueEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
use roc_target::OperatingSystem::*;
|
||||
|
||||
let lhs = lhs.into_int_value();
|
||||
let rhs = rhs.into_int_value();
|
||||
|
||||
let int_64 = env.context.i128_type().const_int(64, false);
|
||||
let int_64_type = env.context.i64_type();
|
||||
|
||||
let lhs1 = env
|
||||
.builder
|
||||
.build_right_shift(lhs, int_64, false, "lhs_left_bits");
|
||||
let rhs1 = env
|
||||
.builder
|
||||
.build_right_shift(rhs, int_64, false, "rhs_left_bits");
|
||||
match env.target_info.operating_system {
|
||||
Windows => {
|
||||
// windows is much nicer for us here
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[dec_alloca(env, lhs).into(), dec_alloca(env, rhs).into()],
|
||||
fn_name,
|
||||
)
|
||||
}
|
||||
Unix => {
|
||||
let (lhs_low, lhs_high) = dec_split_into_words(env, lhs);
|
||||
let (rhs_low, rhs_high) = dec_split_into_words(env, rhs);
|
||||
|
||||
call_bitcode_fn(
|
||||
env,
|
||||
&[
|
||||
env.builder.build_int_cast(lhs, int_64_type, "").into(),
|
||||
env.builder.build_int_cast(lhs1, int_64_type, "").into(),
|
||||
env.builder.build_int_cast(rhs, int_64_type, "").into(),
|
||||
env.builder.build_int_cast(rhs1, int_64_type, "").into(),
|
||||
lhs_low.into(),
|
||||
lhs_high.into(),
|
||||
rhs_low.into(),
|
||||
rhs_high.into(),
|
||||
],
|
||||
fn_name,
|
||||
)
|
||||
}
|
||||
Wasi => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_dec_binop<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
|
|
|
@ -82,7 +82,10 @@ pub(crate) fn layout_width<'a, 'ctx, 'env>(
|
|||
layout: &Layout<'a>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
env.ptr_int()
|
||||
.const_int(layout.stack_size(env.target_info) as u64, false)
|
||||
.const_int(
|
||||
layout.stack_size(env.layout_interner, env.target_info) as u64,
|
||||
false,
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
|
@ -317,7 +320,7 @@ pub(crate) fn list_replace_unsafe<'a, 'ctx, 'env>(
|
|||
|
||||
// the list has the same alignment as a usize / ptr. The element comes first in the struct if
|
||||
// its alignment is bigger than that of a list.
|
||||
let element_align = element_layout.alignment_bytes(env.target_info);
|
||||
let element_align = element_layout.alignment_bytes(env.layout_interner, env.target_info);
|
||||
let element_first = element_align > env.target_info.ptr_width() as u32;
|
||||
|
||||
let fields = if element_first {
|
||||
|
@ -715,13 +718,13 @@ pub(crate) fn allocate_list<'a, 'ctx, 'env>(
|
|||
let builder = env.builder;
|
||||
|
||||
let len_type = env.ptr_int();
|
||||
let elem_bytes = elem_layout.stack_size(env.target_info) as u64;
|
||||
let elem_bytes = elem_layout.stack_size(env.layout_interner, env.target_info) as u64;
|
||||
let bytes_per_element = len_type.const_int(elem_bytes, false);
|
||||
let number_of_data_bytes =
|
||||
builder.build_int_mul(bytes_per_element, number_of_elements, "data_length");
|
||||
|
||||
let basic_type = basic_type_from_layout(env, elem_layout);
|
||||
let alignment_bytes = elem_layout.alignment_bytes(env.target_info);
|
||||
let alignment_bytes = elem_layout.alignment_bytes(env.layout_interner, env.target_info);
|
||||
allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes)
|
||||
}
|
||||
|
||||
|
|
|
@ -46,30 +46,6 @@ pub(crate) fn decode_from_utf8_result<'a, 'ctx, 'env>(
|
|||
}
|
||||
|
||||
/// Dec.toStr : Dec -> Str
|
||||
pub(crate) fn dec_to_str<'a, 'ctx, 'env>(
|
||||
env: &Env<'a, 'ctx, 'env>,
|
||||
dec: BasicValueEnum<'ctx>,
|
||||
) -> BasicValueEnum<'ctx> {
|
||||
let dec = dec.into_int_value();
|
||||
|
||||
let int_64 = env.context.i128_type().const_int(64, false);
|
||||
let int_64_type = env.context.i64_type();
|
||||
|
||||
let dec_right_shift = env
|
||||
.builder
|
||||
.build_right_shift(dec, int_64, false, "dec_left_bits");
|
||||
|
||||
let right_bits = env.builder.build_int_cast(dec, int_64_type, "");
|
||||
let left_bits = env.builder.build_int_cast(dec_right_shift, int_64_type, "");
|
||||
|
||||
call_str_bitcode_fn(
|
||||
env,
|
||||
&[],
|
||||
&[right_bits.into(), left_bits.into()],
|
||||
BitcodeReturns::Str,
|
||||
bitcode::DEC_TO_STR,
|
||||
)
|
||||
}
|
||||
|
||||
/// Str.equal : Str, Str -> Bool
|
||||
pub(crate) fn str_equal<'a, 'ctx, 'env>(
|
||||
|
|
|
@ -5,7 +5,7 @@ use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
|
|||
use inkwell::values::StructValue;
|
||||
use inkwell::AddressSpace;
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_mono::layout::{round_up_to_alignment, Builtin, Layout, UnionLayout};
|
||||
use roc_mono::layout::{round_up_to_alignment, Builtin, Layout, STLayoutInterner, UnionLayout};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
fn basic_type_from_record<'a, 'ctx, 'env>(
|
||||
|
@ -34,7 +34,9 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
|
|||
field_layouts: sorted_fields,
|
||||
..
|
||||
} => basic_type_from_record(env, sorted_fields),
|
||||
LambdaSet(lambda_set) => basic_type_from_layout(env, &lambda_set.runtime_representation()),
|
||||
LambdaSet(lambda_set) => {
|
||||
basic_type_from_layout(env, &lambda_set.runtime_representation(env.layout_interner))
|
||||
}
|
||||
Boxed(inner_layout) => {
|
||||
let inner_type = basic_type_from_layout(env, inner_layout);
|
||||
|
||||
|
@ -60,7 +62,7 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
|
|||
match union_layout {
|
||||
NonRecursive(tags) => {
|
||||
//
|
||||
RocUnion::tagged_from_slices(env.context, tags, env.target_info)
|
||||
RocUnion::tagged_from_slices(env.layout_interner, env.context, tags, env.target_info)
|
||||
.struct_type()
|
||||
.into()
|
||||
}
|
||||
|
@ -69,29 +71,45 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
|
|||
other_tags: tags, ..
|
||||
} => {
|
||||
if union_layout.stores_tag_id_as_data(env.target_info) {
|
||||
RocUnion::tagged_from_slices(env.context, tags, env.target_info)
|
||||
RocUnion::tagged_from_slices(
|
||||
env.layout_interner,
|
||||
env.context,
|
||||
tags,
|
||||
env.target_info,
|
||||
)
|
||||
.struct_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into()
|
||||
} else {
|
||||
RocUnion::untagged_from_slices(env.context, tags, env.target_info)
|
||||
RocUnion::untagged_from_slices(
|
||||
env.layout_interner,
|
||||
env.context,
|
||||
tags,
|
||||
env.target_info,
|
||||
)
|
||||
.struct_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
NullableUnwrapped { other_fields, .. } => {
|
||||
RocUnion::untagged_from_slices(env.context, &[other_fields], env.target_info)
|
||||
NullableUnwrapped { other_fields, .. } => RocUnion::untagged_from_slices(
|
||||
env.layout_interner,
|
||||
env.context,
|
||||
&[other_fields],
|
||||
env.target_info,
|
||||
)
|
||||
.struct_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into()
|
||||
}
|
||||
NonNullableUnwrapped(fields) => {
|
||||
RocUnion::untagged_from_slices(env.context, &[fields], env.target_info)
|
||||
.into(),
|
||||
NonNullableUnwrapped(fields) => RocUnion::untagged_from_slices(
|
||||
env.layout_interner,
|
||||
env.context,
|
||||
&[fields],
|
||||
env.target_info,
|
||||
)
|
||||
.struct_type()
|
||||
.ptr_type(AddressSpace::Generic)
|
||||
.into()
|
||||
}
|
||||
.into(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -132,13 +150,13 @@ pub fn argument_type_from_layout<'a, 'ctx, 'env>(
|
|||
|
||||
match layout {
|
||||
LambdaSet(lambda_set) => {
|
||||
argument_type_from_layout(env, &lambda_set.runtime_representation())
|
||||
argument_type_from_layout(env, &lambda_set.runtime_representation(env.layout_interner))
|
||||
}
|
||||
Union(union_layout) => argument_type_from_union_layout(env, union_layout),
|
||||
Builtin(_) => {
|
||||
let base = basic_type_from_layout(env, layout);
|
||||
|
||||
if layout.is_passed_by_reference(env.target_info) {
|
||||
if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
base.ptr_type(AddressSpace::Generic).into()
|
||||
} else {
|
||||
base
|
||||
|
@ -275,6 +293,7 @@ impl<'ctx> RocUnion<'ctx> {
|
|||
}
|
||||
|
||||
pub fn tagged_from_slices(
|
||||
interner: &STLayoutInterner,
|
||||
context: &'ctx Context,
|
||||
layouts: &[&[Layout<'_>]],
|
||||
target_info: TargetInfo,
|
||||
|
@ -285,18 +304,19 @@ impl<'ctx> RocUnion<'ctx> {
|
|||
};
|
||||
|
||||
let (data_width, data_align) =
|
||||
Layout::stack_size_and_alignment_slices(layouts, target_info);
|
||||
Layout::stack_size_and_alignment_slices(interner, layouts, target_info);
|
||||
|
||||
Self::new(context, target_info, data_align, data_width, Some(tag_type))
|
||||
}
|
||||
|
||||
pub fn untagged_from_slices(
|
||||
interner: &STLayoutInterner,
|
||||
context: &'ctx Context,
|
||||
layouts: &[&[Layout<'_>]],
|
||||
target_info: TargetInfo,
|
||||
) -> Self {
|
||||
let (data_width, data_align) =
|
||||
Layout::stack_size_and_alignment_slices(layouts, target_info);
|
||||
Layout::stack_size_and_alignment_slices(interner, layouts, target_info);
|
||||
|
||||
Self::new(context, target_info, data_align, data_width, None)
|
||||
}
|
||||
|
@ -400,6 +420,10 @@ pub fn zig_str_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ct
|
|||
env.module.get_struct_type("str.RocStr").unwrap()
|
||||
}
|
||||
|
||||
pub fn zig_dec_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
|
||||
env.module.get_struct_type("dec.RocDec").unwrap()
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
|
|
|
@ -137,9 +137,10 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
|
|||
|
||||
let (value, layout) = load_symbol_and_layout(scope, lookup);
|
||||
|
||||
let stack_size = env
|
||||
.ptr_int()
|
||||
.const_int(layout.stack_size(env.target_info) as u64, false);
|
||||
let stack_size = env.ptr_int().const_int(
|
||||
layout.stack_size(env.layout_interner, env.target_info) as u64,
|
||||
false,
|
||||
);
|
||||
|
||||
let mut extra_offset = env.builder.build_int_add(offset, stack_size, "offset");
|
||||
|
||||
|
@ -218,10 +219,12 @@ fn build_clone<'a, 'ctx, 'env>(
|
|||
when_recursive,
|
||||
),
|
||||
|
||||
Layout::LambdaSet(_) => unreachable!("cannot compare closures"),
|
||||
// Since we will never actually display functions (and hence lambda sets)
|
||||
// we just write nothing to the buffer
|
||||
Layout::LambdaSet(_) => cursors.extra_offset,
|
||||
|
||||
Layout::Union(union_layout) => {
|
||||
if layout.safe_to_memcpy() {
|
||||
if layout.safe_to_memcpy(env.layout_interner) {
|
||||
let ptr = unsafe {
|
||||
env.builder
|
||||
.build_in_bounds_gep(ptr, &[cursors.offset], "at_current_offset")
|
||||
|
@ -255,9 +258,10 @@ fn build_clone<'a, 'ctx, 'env>(
|
|||
let source = value.into_pointer_value();
|
||||
let value = load_roc_value(env, *inner_layout, source, "inner");
|
||||
|
||||
let inner_width = env
|
||||
.ptr_int()
|
||||
.const_int(inner_layout.stack_size(env.target_info) as u64, false);
|
||||
let inner_width = env.ptr_int().const_int(
|
||||
inner_layout.stack_size(env.layout_interner, env.target_info) as u64,
|
||||
false,
|
||||
);
|
||||
|
||||
let new_extra = env
|
||||
.builder
|
||||
|
@ -318,7 +322,7 @@ fn build_clone_struct<'a, 'ctx, 'env>(
|
|||
) -> IntValue<'ctx> {
|
||||
let layout = Layout::struct_no_name_order(field_layouts);
|
||||
|
||||
if layout.safe_to_memcpy() {
|
||||
if layout.safe_to_memcpy(env.layout_interner) {
|
||||
build_copy(env, ptr, cursors.offset, value)
|
||||
} else {
|
||||
let mut cursors = cursors;
|
||||
|
@ -343,9 +347,10 @@ fn build_clone_struct<'a, 'ctx, 'env>(
|
|||
when_recursive,
|
||||
);
|
||||
|
||||
let field_width = env
|
||||
.ptr_int()
|
||||
.const_int(field_layout.stack_size(env.target_info) as u64, false);
|
||||
let field_width = env.ptr_int().const_int(
|
||||
field_layout.stack_size(env.layout_interner, env.target_info) as u64,
|
||||
false,
|
||||
);
|
||||
|
||||
cursors.extra_offset = new_extra;
|
||||
cursors.offset = env
|
||||
|
@ -576,7 +581,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
|
||||
let data = env.builder.build_load(data_ptr, "load_data");
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
let (width, _) =
|
||||
union_layout.data_size_and_alignment(env.layout_interner, env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
|
@ -618,7 +624,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
let layout = Layout::struct_no_name_order(fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
let (width, _) =
|
||||
union_layout.data_size_and_alignment(env.layout_interner, env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
|
@ -686,7 +693,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
let layout = Layout::struct_no_name_order(fields);
|
||||
let basic_type = basic_type_from_layout(env, &layout);
|
||||
|
||||
let (width, _) = union_layout.data_size_and_alignment(env.target_info);
|
||||
let (width, _) =
|
||||
union_layout.data_size_and_alignment(env.layout_interner, env.target_info);
|
||||
|
||||
let cursors = Cursors {
|
||||
offset: extra_offset,
|
||||
|
@ -776,8 +784,10 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
|
|||
offset: extra_offset,
|
||||
extra_offset: env.builder.build_int_add(
|
||||
extra_offset,
|
||||
env.ptr_int()
|
||||
.const_int(layout.stack_size(env.target_info) as _, false),
|
||||
env.ptr_int().const_int(
|
||||
layout.stack_size(env.layout_interner, env.target_info) as _,
|
||||
false,
|
||||
),
|
||||
"new_offset",
|
||||
),
|
||||
};
|
||||
|
@ -907,12 +917,13 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
|
|||
offset = build_copy(env, ptr, offset, len.into());
|
||||
offset = build_copy(env, ptr, offset, len.into());
|
||||
|
||||
let (element_width, _element_align) = elem.stack_size_and_alignment(env.target_info);
|
||||
let (element_width, _element_align) =
|
||||
elem.stack_size_and_alignment(env.layout_interner, env.target_info);
|
||||
let element_width = env.ptr_int().const_int(element_width as _, false);
|
||||
|
||||
let elements_width = bd.build_int_mul(element_width, len, "elements_width");
|
||||
|
||||
if elem.safe_to_memcpy() {
|
||||
if elem.safe_to_memcpy(env.layout_interner) {
|
||||
// NOTE we are not actually sure the dest is properly aligned
|
||||
let dest = pointer_at_offset(bd, ptr, offset);
|
||||
let src = bd.build_pointer_cast(
|
||||
|
@ -924,7 +935,8 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
|
|||
|
||||
bd.build_int_add(offset, elements_width, "new_offset")
|
||||
} else {
|
||||
let elements_start_offset = offset;
|
||||
// We cloned the elements into the extra_offset address.
|
||||
let elements_start_offset = cursors.extra_offset;
|
||||
|
||||
let element_type = basic_type_from_layout(env, elem);
|
||||
let elements = bd.build_pointer_cast(
|
||||
|
@ -936,9 +948,10 @@ fn build_clone_builtin<'a, 'ctx, 'env>(
|
|||
// if the element has any pointers, we clone them to this offset
|
||||
let rest_offset = bd.build_alloca(env.ptr_int(), "rest_offset");
|
||||
|
||||
let element_stack_size = env
|
||||
.ptr_int()
|
||||
.const_int(elem.stack_size(env.target_info) as u64, false);
|
||||
let element_stack_size = env.ptr_int().const_int(
|
||||
elem.stack_size(env.layout_interner, env.target_info) as u64,
|
||||
false,
|
||||
);
|
||||
let rest_start_offset = bd.build_int_add(
|
||||
cursors.extra_offset,
|
||||
bd.build_int_mul(len, element_stack_size, "elements_width"),
|
||||
|
|
|
@ -16,9 +16,9 @@ use inkwell::values::{
|
|||
use inkwell::{AddressSpace, IntPredicate};
|
||||
use roc_module::symbol::Interns;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, STLayoutInterner, UnionLayout};
|
||||
|
||||
use super::build::{load_roc_value, FunctionSpec};
|
||||
use super::build::{cast_if_necessary_for_opaque_recursive_pointers, load_roc_value, FunctionSpec};
|
||||
use super::convert::{argument_type_from_layout, argument_type_from_union_layout};
|
||||
|
||||
pub struct PointerToRefcount<'ctx> {
|
||||
|
@ -124,7 +124,7 @@ impl<'ctx> PointerToRefcount<'ctx> {
|
|||
|
||||
pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) {
|
||||
let alignment = layout
|
||||
.allocation_alignment_bytes(env.target_info)
|
||||
.allocation_alignment_bytes(env.layout_interner, env.target_info)
|
||||
.max(env.target_info.ptr_width() as u32);
|
||||
|
||||
let context = env.context;
|
||||
|
@ -332,7 +332,7 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
|
|||
let wrapper_struct = arg_val.into_struct_value();
|
||||
|
||||
for (i, field_layout) in layouts.iter().enumerate() {
|
||||
if field_layout.contains_refcounted() {
|
||||
if field_layout.contains_refcounted(env.layout_interner) {
|
||||
let raw_value = env
|
||||
.builder
|
||||
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field")
|
||||
|
@ -515,6 +515,12 @@ fn call_help<'a, 'ctx, 'env>(
|
|||
call_mode: CallMode<'ctx>,
|
||||
value: BasicValueEnum<'ctx>,
|
||||
) -> inkwell::values::CallSiteValue<'ctx> {
|
||||
let value = cast_if_necessary_for_opaque_recursive_pointers(
|
||||
env.builder,
|
||||
value,
|
||||
function.get_params()[0].get_type(),
|
||||
);
|
||||
|
||||
let call = match call_mode {
|
||||
CallMode::Inc(inc_amount) => {
|
||||
env.builder
|
||||
|
@ -613,7 +619,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
|
|||
layout_ids,
|
||||
mode,
|
||||
when_recursive,
|
||||
&lambda_set.runtime_representation(),
|
||||
&lambda_set.runtime_representation(env.layout_interner),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -717,7 +723,7 @@ fn modify_refcount_list_help<'a, 'ctx, 'env>(
|
|||
|
||||
builder.position_at_end(modification_block);
|
||||
|
||||
if element_layout.contains_refcounted() {
|
||||
if element_layout.contains_refcounted(env.layout_interner) {
|
||||
let ptr_type = basic_type_from_layout(env, element_layout).ptr_type(AddressSpace::Generic);
|
||||
|
||||
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type);
|
||||
|
@ -818,7 +824,9 @@ fn modify_refcount_str_help<'a, 'ctx, 'env>(
|
|||
|
||||
let parent = fn_val;
|
||||
|
||||
let arg_val = if Layout::Builtin(Builtin::Str).is_passed_by_reference(env.target_info) {
|
||||
let arg_val = if Layout::Builtin(Builtin::Str)
|
||||
.is_passed_by_reference(env.layout_interner, env.target_info)
|
||||
{
|
||||
env.builder
|
||||
.build_load(arg_val.into_pointer_value(), "load_str_to_stack")
|
||||
} else {
|
||||
|
@ -1178,10 +1186,10 @@ enum DecOrReuse {
|
|||
Reuse,
|
||||
}
|
||||
|
||||
fn fields_need_no_refcounting(field_layouts: &[Layout]) -> bool {
|
||||
fn fields_need_no_refcounting(interner: &STLayoutInterner, field_layouts: &[Layout]) -> bool {
|
||||
!field_layouts
|
||||
.iter()
|
||||
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
||||
.any(|x| x.is_refcounted() || x.contains_refcounted(interner))
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -1211,7 +1219,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
|||
|
||||
for (tag_id, field_layouts) in tags.iter().enumerate() {
|
||||
// if none of the fields are or contain anything refcounted, just move on
|
||||
if fields_need_no_refcounting(field_layouts) {
|
||||
if fields_need_no_refcounting(env.layout_interner, field_layouts) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1256,7 +1264,7 @@ fn build_rec_union_recursive_decrement<'a, 'ctx, 'env>(
|
|||
let recursive_field_ptr = cast_basic_basic(env.builder, ptr_as_i64_ptr, union_type);
|
||||
|
||||
deferred_rec.push(recursive_field_ptr);
|
||||
} else if field_layout.contains_refcounted() {
|
||||
} else if field_layout.contains_refcounted(env.layout_interner) {
|
||||
let elem_pointer = env
|
||||
.builder
|
||||
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer")
|
||||
|
@ -1620,7 +1628,7 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
|||
// if none of the fields are or contain anything refcounted, just move on
|
||||
if !field_layouts
|
||||
.iter()
|
||||
.any(|x| x.is_refcounted() || x.contains_refcounted())
|
||||
.any(|x| x.is_refcounted() || x.contains_refcounted(env.layout_interner))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
@ -1678,13 +1686,14 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
|
|||
recursive_ptr_field_value,
|
||||
&Layout::RecursivePointer,
|
||||
)
|
||||
} else if field_layout.contains_refcounted() {
|
||||
} else if field_layout.contains_refcounted(env.layout_interner) {
|
||||
let field_ptr = env
|
||||
.builder
|
||||
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
|
||||
.unwrap();
|
||||
|
||||
let field_value = if field_layout.is_passed_by_reference(env.target_info) {
|
||||
let field_value =
|
||||
if field_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
|
||||
field_ptr.into()
|
||||
} else {
|
||||
env.builder.build_load(field_ptr, "field_value")
|
||||
|
|
|
@ -162,7 +162,7 @@ macro_rules! run_jit_function_dynamic_type {
|
|||
// first field is a char pointer (to the error message)
|
||||
// read value, and transmute to a pointer
|
||||
let ptr_as_int = *(result as *const u64).offset(1);
|
||||
let ptr = std::mem::transmute::<u64, *mut c_char>(ptr_as_int);
|
||||
let ptr = ptr_as_int as *mut c_char;
|
||||
|
||||
// make CString (null-terminated)
|
||||
let raw = CString::from_raw(ptr);
|
||||
|
|
|
@ -7,9 +7,10 @@ license = "UPL-1.0"
|
|||
|
||||
[dependencies]
|
||||
bitvec = "1"
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
roc_builtins = { path = "../builtins" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_module = { path = "../module" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
|
|
|
@ -81,14 +81,13 @@ WebAssembly functions can have any number of local variables. They are declared
|
|||
In this backend, each symbol in the Mono IR gets one WebAssembly local. To illustrate, let's translate a simple Roc example to WebAssembly text format.
|
||||
The WebAssembly code below is completely unoptimised and uses far more locals than necessary. But that does help to illustrate the concept of locals.
|
||||
|
||||
```
|
||||
```coffee
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
1 + 2 + 4
|
||||
```
|
||||
|
||||
|
||||
### Direct translation of Mono IR
|
||||
|
||||
The Mono IR contains two functions, `Num.add` and `main`, so we generate two corresponding WebAssembly functions.
|
||||
|
@ -97,7 +96,7 @@ The code ends up being quite bloated, with lots of `local.set` and `local.get` i
|
|||
|
||||
I've added comments on each line to show what is on the stack and in the locals at each point in the program.
|
||||
|
||||
```
|
||||
```text
|
||||
(func (;0;) (param i64 i64) (result i64) ; declare function index 0 (Num.add) with two i64 parameters and an i64 result
|
||||
local.get 0 ; load param 0 stack=[param0]
|
||||
local.get 1 ; load param 1 stack=[param0, param1]
|
||||
|
@ -127,7 +126,7 @@ I've added comments on each line to show what is on the stack and in the locals
|
|||
This code doesn't actually require any locals at all.
|
||||
(It also doesn't need the `return` instructions, but that's less of a problem.)
|
||||
|
||||
```
|
||||
```text
|
||||
(func (;0;) (param i64 i64) (result i64)
|
||||
local.get 0
|
||||
local.get 1
|
||||
|
@ -154,7 +153,7 @@ When the `WasmBackend` generates code for a `Let` statement, it can "label" the
|
|||
|
||||
In practice it should be very common for values to appear on the VM stack in the right order, because in the Mono IR, statements occur in dependency order! We should only generate locals when the dependency graph is a little more complicated, and we actually need them.
|
||||
|
||||
```
|
||||
```text
|
||||
┌─────────────────┐ ┌─────────────┐
|
||||
│ │ │ │
|
||||
│ ├─────► Storage ├──────┐
|
||||
|
@ -234,12 +233,14 @@ We implement a few linking operations in the Wasm backend. The most important ar
|
|||
In the host .wasm file, `roc__mainForHost_1_exposed` is defined as a Wasm Import, as if it were an external JavaScript function. But when we link the host and app, we need to make it an internal function instead.
|
||||
|
||||
There are a few important facts to note about the Wasm binary format:
|
||||
|
||||
- Function calls refer to the callee by its function index in the file.
|
||||
- If we move a function from one index to another, all of its call sites need to be updated. So we want to minimise this to make linking fast.
|
||||
- If we _remove_ a function, then all functions above it will implicitly have their indices shifted down by 1! This is not good for speed. We should try to _swap_ rather than remove.
|
||||
- JavaScript imports always get the lower indices.
|
||||
|
||||
With that background, here are the linking steps for a single app function that gets called by the host:
|
||||
|
||||
- Remove `roc__mainForHost_1_exposed` from the imports, updating all call sites to the new index, which is somewhere in the app.
|
||||
- Swap the _last_ JavaScript import into the slot where `roc__mainForHost_1_exposed` was, updating all of its call sites in the host.
|
||||
- Insert an internally-defined dummy function at the index where the last JavaScript import used to be.
|
||||
|
|
|
@ -400,7 +400,7 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
fn start_proc(&mut self, proc: &Proc<'a>) {
|
||||
use ReturnMethod::*;
|
||||
let ret_layout = WasmLayout::new(&proc.ret_layout);
|
||||
let ret_layout = WasmLayout::new(self.env.layout_interner, &proc.ret_layout);
|
||||
|
||||
let ret_type = match ret_layout.return_method(CallConv::C) {
|
||||
Primitive(ty, _) => Some(ty),
|
||||
|
@ -418,8 +418,12 @@ impl<'a> WasmBackend<'a> {
|
|||
// We never use the `return` instruction. Instead, we break from this block.
|
||||
self.start_block();
|
||||
|
||||
self.storage
|
||||
.allocate_args(proc.args, &mut self.code_builder, self.env.arena);
|
||||
self.storage.allocate_args(
|
||||
self.env.layout_interner,
|
||||
proc.args,
|
||||
&mut self.code_builder,
|
||||
self.env.arena,
|
||||
);
|
||||
|
||||
if let Some(ty) = ret_type {
|
||||
let ret_var = self.storage.create_anonymous_local(ty);
|
||||
|
@ -493,7 +497,7 @@ impl<'a> WasmBackend<'a> {
|
|||
// Our convention is that the last arg of the wrapper is the heap return pointer
|
||||
let heap_return_ptr_id = LocalId(wrapper_arg_layouts.len() as u32 - 1);
|
||||
let inner_ret_layout = match wrapper_arg_layouts.last() {
|
||||
Some(Layout::Boxed(inner)) => WasmLayout::new(inner),
|
||||
Some(Layout::Boxed(inner)) => WasmLayout::new(self.env.layout_interner, inner),
|
||||
x => internal_error!("Higher-order wrapper: invalid return layout {:?}", x),
|
||||
};
|
||||
|
||||
|
@ -527,7 +531,7 @@ impl<'a> WasmBackend<'a> {
|
|||
Layout::Boxed(inner) => inner,
|
||||
x => internal_error!("Expected a Boxed layout, got {:?}", x),
|
||||
};
|
||||
if inner_layout.stack_size(TARGET_INFO) == 0 {
|
||||
if inner_layout.stack_size(self.env.layout_interner, TARGET_INFO) == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -539,7 +543,7 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
// If the inner function has closure data, it's the last arg of the inner fn
|
||||
let closure_data_layout = wrapper_arg_layouts[0];
|
||||
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
|
||||
if closure_data_layout.stack_size(self.env.layout_interner, TARGET_INFO) > 0 {
|
||||
// The closure data exists, and will have been passed in to the wrapper as a
|
||||
// one-element struct.
|
||||
let inner_closure_data_layout = match closure_data_layout {
|
||||
|
@ -614,7 +618,7 @@ impl<'a> WasmBackend<'a> {
|
|||
let value_layout = wrapper_proc_layout.arguments[1];
|
||||
|
||||
let mut n_inner_args = 2;
|
||||
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
|
||||
if closure_data_layout.stack_size(self.env.layout_interner, TARGET_INFO) > 0 {
|
||||
self.code_builder.get_local(LocalId(0));
|
||||
n_inner_args += 1;
|
||||
}
|
||||
|
@ -760,7 +764,9 @@ impl<'a> WasmBackend<'a> {
|
|||
expr: &Expr<'a>,
|
||||
kind: StoredVarKind,
|
||||
) {
|
||||
let sym_storage = self.storage.allocate_var(*layout, sym, kind);
|
||||
let sym_storage = self
|
||||
.storage
|
||||
.allocate_var(self.env.layout_interner, *layout, sym, kind);
|
||||
|
||||
self.expr(sym, expr, layout, &sym_storage);
|
||||
|
||||
|
@ -837,7 +843,8 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
|
||||
let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool));
|
||||
let cond_type = WasmLayout::new(cond_layout).arg_types(CallConv::C)[0];
|
||||
let cond_type =
|
||||
WasmLayout::new(self.env.layout_interner, cond_layout).arg_types(CallConv::C)[0];
|
||||
|
||||
// then, we jump whenever the value under scrutiny is equal to the value of a branch
|
||||
for (i, (value, _, _)) in branches.iter().enumerate() {
|
||||
|
@ -899,6 +906,7 @@ impl<'a> WasmBackend<'a> {
|
|||
let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena);
|
||||
for parameter in parameters.iter() {
|
||||
let mut param_storage = self.storage.allocate_var(
|
||||
self.env.layout_interner,
|
||||
parameter.layout,
|
||||
parameter.symbol,
|
||||
StoredVarKind::Variable,
|
||||
|
@ -961,7 +969,11 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
if false {
|
||||
self.register_symbol_debug_names();
|
||||
println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt);
|
||||
println!(
|
||||
"## rc_stmt:\n{}\n{:?}",
|
||||
rc_stmt.to_pretty(self.env.layout_interner, 200),
|
||||
rc_stmt
|
||||
);
|
||||
}
|
||||
|
||||
// If any new specializations were created, register their symbol data
|
||||
|
@ -1233,7 +1245,7 @@ impl<'a> WasmBackend<'a> {
|
|||
ret_layout,
|
||||
} => {
|
||||
let name = foreign_symbol.as_str();
|
||||
let wasm_layout = WasmLayout::new(ret_layout);
|
||||
let wasm_layout = WasmLayout::new(self.env.layout_interner, ret_layout);
|
||||
let (num_wasm_args, has_return_val, ret_zig_packed_struct) =
|
||||
self.storage.load_symbols_for_call(
|
||||
self.env.arena,
|
||||
|
@ -1258,7 +1270,7 @@ impl<'a> WasmBackend<'a> {
|
|||
ret_layout: &Layout<'a>,
|
||||
ret_storage: &StoredValue,
|
||||
) {
|
||||
let wasm_layout = WasmLayout::new(ret_layout);
|
||||
let wasm_layout = WasmLayout::new(self.env.layout_interner, ret_layout);
|
||||
|
||||
// If this function is just a lowlevel wrapper, then inline it
|
||||
if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
|
||||
|
@ -1414,9 +1426,12 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
};
|
||||
}
|
||||
Layout::LambdaSet(lambdaset) => {
|
||||
self.expr_struct(sym, &lambdaset.runtime_representation(), storage, fields)
|
||||
}
|
||||
Layout::LambdaSet(lambdaset) => self.expr_struct(
|
||||
sym,
|
||||
&lambdaset.runtime_representation(self.env.layout_interner),
|
||||
storage,
|
||||
fields,
|
||||
),
|
||||
_ => {
|
||||
if !fields.is_empty() {
|
||||
// Struct expression but not Struct layout => single element. Copy it.
|
||||
|
@ -1464,7 +1479,7 @@ impl<'a> WasmBackend<'a> {
|
|||
}
|
||||
};
|
||||
for field in field_layouts.iter().take(index as usize) {
|
||||
offset += field.stack_size(TARGET_INFO);
|
||||
offset += field.stack_size(self.env.layout_interner, TARGET_INFO);
|
||||
}
|
||||
self.storage
|
||||
.copy_value_from_memory(&mut self.code_builder, sym, from_addr_val, offset);
|
||||
|
@ -1482,11 +1497,12 @@ impl<'a> WasmBackend<'a> {
|
|||
elems: &'a [ListLiteralElement<'a>],
|
||||
) {
|
||||
if let StoredValue::StackMemory { location, .. } = storage {
|
||||
let size = elem_layout.stack_size(TARGET_INFO) * (elems.len() as u32);
|
||||
let size = elem_layout.stack_size(self.env.layout_interner, TARGET_INFO)
|
||||
* (elems.len() as u32);
|
||||
|
||||
// Allocate heap space and store its address in a local variable
|
||||
let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE);
|
||||
let heap_alignment = elem_layout.alignment_bytes(TARGET_INFO);
|
||||
let heap_alignment = elem_layout.alignment_bytes(self.env.layout_interner, TARGET_INFO);
|
||||
self.allocate_with_refcount(Some(size), heap_alignment, 1);
|
||||
self.code_builder.set_local(heap_local_id);
|
||||
|
||||
|
@ -1583,7 +1599,8 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
let stores_tag_id_as_data = union_layout.stores_tag_id_as_data(TARGET_INFO);
|
||||
let stores_tag_id_in_pointer = union_layout.stores_tag_id_in_pointer(TARGET_INFO);
|
||||
let (data_size, data_alignment) = union_layout.data_size_and_alignment(TARGET_INFO);
|
||||
let (data_size, data_alignment) =
|
||||
union_layout.data_size_and_alignment(self.env.layout_interner, TARGET_INFO);
|
||||
|
||||
// We're going to use the pointer many times, so put it in a local variable
|
||||
let stored_with_local =
|
||||
|
@ -1635,7 +1652,10 @@ impl<'a> WasmBackend<'a> {
|
|||
|
||||
// Store the tag ID (if any)
|
||||
if stores_tag_id_as_data {
|
||||
let id_offset = data_offset + union_layout.tag_id_offset(TARGET_INFO).unwrap();
|
||||
let id_offset = data_offset
|
||||
+ union_layout
|
||||
.tag_id_offset(self.env.layout_interner, TARGET_INFO)
|
||||
.unwrap();
|
||||
|
||||
let id_align = union_layout.discriminant().alignment_bytes();
|
||||
let id_align = Align::from(id_align);
|
||||
|
@ -1718,7 +1738,9 @@ impl<'a> WasmBackend<'a> {
|
|||
};
|
||||
|
||||
if union_layout.stores_tag_id_as_data(TARGET_INFO) {
|
||||
let id_offset = union_layout.tag_id_offset(TARGET_INFO).unwrap();
|
||||
let id_offset = union_layout
|
||||
.tag_id_offset(self.env.layout_interner, TARGET_INFO)
|
||||
.unwrap();
|
||||
|
||||
let id_align = union_layout.discriminant().alignment_bytes();
|
||||
let id_align = Align::from(id_align);
|
||||
|
@ -1778,7 +1800,7 @@ impl<'a> WasmBackend<'a> {
|
|||
let field_offset: u32 = field_layouts
|
||||
.iter()
|
||||
.take(index as usize)
|
||||
.map(|field_layout| field_layout.stack_size(TARGET_INFO))
|
||||
.map(|field_layout| field_layout.stack_size(self.env.layout_interner, TARGET_INFO))
|
||||
.sum();
|
||||
|
||||
// Get pointer and offset to the tag's data
|
||||
|
@ -1844,7 +1866,8 @@ impl<'a> WasmBackend<'a> {
|
|||
Layout::Boxed(arg) => *arg,
|
||||
_ => internal_error!("ExprBox should always produce a Boxed layout"),
|
||||
};
|
||||
let (size, alignment) = arg_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (size, alignment) =
|
||||
arg_layout.stack_size_and_alignment(self.env.layout_interner, TARGET_INFO);
|
||||
self.allocate_with_refcount(Some(size), alignment, 1);
|
||||
|
||||
// store the pointer value from the value stack into the local variable
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_mono::layout::{Layout, UnionLayout};
|
||||
use roc_mono::layout::{Layout, STLayoutInterner, UnionLayout};
|
||||
|
||||
use crate::wasm_module::ValueType;
|
||||
use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO};
|
||||
|
@ -41,12 +41,12 @@ pub enum WasmLayout {
|
|||
}
|
||||
|
||||
impl WasmLayout {
|
||||
pub fn new(layout: &Layout) -> Self {
|
||||
pub fn new<'a>(interner: &STLayoutInterner<'a>, layout: &Layout<'a>) -> Self {
|
||||
use roc_mono::layout::Builtin::*;
|
||||
use UnionLayout::*;
|
||||
use ValueType::*;
|
||||
|
||||
let (size, alignment_bytes) = layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (size, alignment_bytes) = layout.stack_size_and_alignment(interner, TARGET_INFO);
|
||||
|
||||
match layout {
|
||||
Layout::Builtin(Int(int_width)) => {
|
||||
|
@ -85,7 +85,9 @@ impl WasmLayout {
|
|||
format: StackMemoryFormat::Decimal,
|
||||
},
|
||||
|
||||
Layout::LambdaSet(lambda_set) => WasmLayout::new(&lambda_set.runtime_representation()),
|
||||
Layout::LambdaSet(lambda_set) => {
|
||||
WasmLayout::new(interner, &lambda_set.runtime_representation(interner))
|
||||
}
|
||||
|
||||
Layout::Builtin(Str | List(_))
|
||||
| Layout::Struct { .. }
|
||||
|
|
|
@ -16,7 +16,7 @@ use roc_collections::all::{MutMap, MutSet};
|
|||
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||
use roc_mono::code_gen_help::CodeGenHelp;
|
||||
use roc_mono::ir::{Proc, ProcLayout};
|
||||
use roc_mono::layout::LayoutIds;
|
||||
use roc_mono::layout::{LayoutIds, STLayoutInterner};
|
||||
use roc_target::TargetInfo;
|
||||
use wasm_module::parse::ParseError;
|
||||
|
||||
|
@ -43,6 +43,7 @@ pub const STACK_POINTER_NAME: &str = "__stack_pointer";
|
|||
|
||||
pub struct Env<'a> {
|
||||
pub arena: &'a Bump,
|
||||
pub layout_interner: &'a STLayoutInterner<'a>,
|
||||
pub module_id: ModuleId,
|
||||
pub exposed_to_host: MutSet<Symbol>,
|
||||
pub stack_bytes: u32,
|
||||
|
@ -131,13 +132,18 @@ pub fn build_app_module<'a>(
|
|||
host_to_app_map,
|
||||
host_module,
|
||||
fn_index_offset,
|
||||
CodeGenHelp::new(env.arena, TargetInfo::default_wasm32(), env.module_id),
|
||||
CodeGenHelp::new(
|
||||
env.arena,
|
||||
env.layout_interner,
|
||||
TargetInfo::default_wasm32(),
|
||||
env.module_id,
|
||||
),
|
||||
);
|
||||
|
||||
if DEBUG_SETTINGS.user_procs_ir {
|
||||
println!("## procs");
|
||||
for proc in procs.iter() {
|
||||
println!("{}", proc.to_pretty(200));
|
||||
println!("{}", proc.to_pretty(env.layout_interner, 200));
|
||||
// println!("{:?}", proc);
|
||||
}
|
||||
}
|
||||
|
@ -155,7 +161,7 @@ pub fn build_app_module<'a>(
|
|||
if DEBUG_SETTINGS.helper_procs_ir {
|
||||
println!("## helper_procs");
|
||||
for proc in helper_procs.iter() {
|
||||
println!("{}", proc.to_pretty(200));
|
||||
println!("{}", proc.to_pretty(env.layout_interner, 200));
|
||||
// println!("{:#?}", proc);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -149,7 +149,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
self.arguments,
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
)
|
||||
}
|
||||
|
@ -283,7 +283,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
self.arguments,
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
|
||||
|
@ -358,7 +358,9 @@ impl<'a> LowLevelCall<'a> {
|
|||
backend
|
||||
.storage
|
||||
.load_symbols(&mut backend.code_builder, &[index]);
|
||||
let elem_size = self.ret_layout.stack_size(TARGET_INFO);
|
||||
let elem_size = self
|
||||
.ret_layout
|
||||
.stack_size(backend.env.layout_interner, TARGET_INFO);
|
||||
backend.code_builder.i32_const(elem_size as i32);
|
||||
backend.code_builder.i32_mul(); // index*size
|
||||
|
||||
|
@ -415,15 +417,16 @@ impl<'a> LowLevelCall<'a> {
|
|||
..
|
||||
} if value_layout == *list_elem => {
|
||||
let list_offset = 0;
|
||||
let elem_offset =
|
||||
Layout::Builtin(Builtin::List(list_elem)).stack_size(TARGET_INFO);
|
||||
let elem_offset = Layout::Builtin(Builtin::List(list_elem))
|
||||
.stack_size(backend.env.layout_interner, TARGET_INFO);
|
||||
(list_offset, elem_offset, value_layout)
|
||||
}
|
||||
Layout::Struct {
|
||||
field_layouts: &[value_layout, Layout::Builtin(Builtin::List(list_elem))],
|
||||
..
|
||||
} if value_layout == *list_elem => {
|
||||
let list_offset = value_layout.stack_size(TARGET_INFO);
|
||||
let list_offset =
|
||||
value_layout.stack_size(backend.env.layout_interner, TARGET_INFO);
|
||||
let elem_offset = 0;
|
||||
(list_offset, elem_offset, value_layout)
|
||||
}
|
||||
|
@ -431,7 +434,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
};
|
||||
|
||||
let (elem_width, elem_alignment) =
|
||||
elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
|
||||
// Ensure the new element is stored in memory so we can pass a pointer to Zig
|
||||
let (new_elem_local, new_elem_offset, _) =
|
||||
|
@ -480,7 +483,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
|
||||
let capacity: Symbol = self.arguments[0];
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_width, elem_align) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
|
||||
// Zig arguments Wasm types
|
||||
// (return pointer) i32
|
||||
|
@ -511,13 +515,14 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
self.arguments,
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
|
||||
// Load monomorphization constants
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_width, elem_align) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
backend.code_builder.i32_const(elem_align as i32);
|
||||
backend.code_builder.i32_const(elem_width as i32);
|
||||
|
||||
|
@ -531,7 +536,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
let spare: Symbol = self.arguments[1];
|
||||
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_width, elem_align) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
let (spare_local, spare_offset, _) = ensure_symbol_is_in_memory(
|
||||
backend,
|
||||
spare,
|
||||
|
@ -553,7 +559,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
&[list],
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
|
||||
|
@ -579,7 +585,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
let elem: Symbol = self.arguments[1];
|
||||
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let elem_width = elem_layout.stack_size(TARGET_INFO);
|
||||
let elem_width = elem_layout.stack_size(backend.env.layout_interner, TARGET_INFO);
|
||||
let (elem_local, elem_offset, _) =
|
||||
ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena);
|
||||
|
||||
|
@ -595,7 +601,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
&[list],
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
|
||||
|
@ -616,7 +622,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
let elem: Symbol = self.arguments[1];
|
||||
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_width, elem_align) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
let (elem_local, elem_offset, _) =
|
||||
ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena);
|
||||
|
||||
|
@ -633,7 +640,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
&[list],
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
|
||||
|
@ -657,7 +664,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
let len: Symbol = self.arguments[2];
|
||||
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_width, elem_align) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
|
||||
// The refcount function receives a pointer to an element in the list
|
||||
// This is the same as a Struct containing the element
|
||||
|
@ -682,7 +690,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
&[list],
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
|
||||
|
@ -701,7 +709,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
let drop_index: Symbol = self.arguments[1];
|
||||
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_width, elem_align) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
|
||||
// The refcount function receives a pointer to an element in the list
|
||||
// This is the same as a Struct containing the element
|
||||
|
@ -726,7 +735,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
&[list],
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
|
||||
|
@ -746,7 +755,8 @@ impl<'a> LowLevelCall<'a> {
|
|||
let index_2: Symbol = self.arguments[2];
|
||||
|
||||
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
|
||||
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_width, elem_align) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
|
||||
// Zig arguments Wasm types
|
||||
// (return pointer) i32
|
||||
|
@ -763,7 +773,7 @@ impl<'a> LowLevelCall<'a> {
|
|||
&mut backend.code_builder,
|
||||
&[list],
|
||||
self.ret_symbol,
|
||||
&WasmLayout::new(&self.ret_layout),
|
||||
&WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
|
||||
CallConv::Zig,
|
||||
);
|
||||
|
||||
|
@ -1625,7 +1635,10 @@ impl<'a> LowLevelCall<'a> {
|
|||
// In most languages this operation is for signed numbers, but Roc defines it on all integers.
|
||||
// So the argument is implicitly converted to signed before the shift operator.
|
||||
// We need to make that conversion explicit for i8 and i16, which use Wasm's i32 type.
|
||||
let bit_width = 8 * self.ret_layout.stack_size(TARGET_INFO) as i32;
|
||||
let bit_width = 8 * self
|
||||
.ret_layout
|
||||
.stack_size(backend.env.layout_interner, TARGET_INFO)
|
||||
as i32;
|
||||
if bit_width < 32 && !symbol_is_signed_int(backend, num) {
|
||||
// Sign-extend the number by shifting left and right again
|
||||
backend
|
||||
|
@ -1670,7 +1683,9 @@ impl<'a> LowLevelCall<'a> {
|
|||
// In most languages this operation is for unsigned numbers, but Roc defines it on all integers.
|
||||
// So the argument is implicitly converted to unsigned before the shift operator.
|
||||
// We need to make that conversion explicit for i8 and i16, which use Wasm's i32 type.
|
||||
let bit_width = 8 * self.ret_layout.stack_size(TARGET_INFO);
|
||||
let bit_width = 8 * self
|
||||
.ret_layout
|
||||
.stack_size(backend.env.layout_interner, TARGET_INFO);
|
||||
if bit_width < 32 && symbol_is_signed_int(backend, bits) {
|
||||
let mask = (1 << bit_width) - 1;
|
||||
|
||||
|
@ -1861,10 +1876,10 @@ impl<'a> LowLevelCall<'a> {
|
|||
/// Equality and inequality
|
||||
/// These can operate on any data type (except functions) so they're more complex than other operators.
|
||||
fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) {
|
||||
let arg_layout =
|
||||
backend.storage.symbol_layouts[&self.arguments[0]].runtime_representation();
|
||||
let other_arg_layout =
|
||||
backend.storage.symbol_layouts[&self.arguments[1]].runtime_representation();
|
||||
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]]
|
||||
.runtime_representation(backend.env.layout_interner);
|
||||
let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]]
|
||||
.runtime_representation(backend.env.layout_interner);
|
||||
debug_assert!(
|
||||
arg_layout == other_arg_layout,
|
||||
"Cannot do `==` comparison on different types: {:?} vs {:?}",
|
||||
|
@ -2136,8 +2151,14 @@ pub fn call_higher_order_lowlevel<'a>(
|
|||
let (closure_data_layout, closure_data_exists) =
|
||||
match backend.storage.symbol_layouts[captured_environment] {
|
||||
Layout::LambdaSet(lambda_set) => {
|
||||
if lambda_set.is_represented().is_some() {
|
||||
(lambda_set.runtime_representation(), true)
|
||||
if lambda_set
|
||||
.is_represented(backend.env.layout_interner)
|
||||
.is_some()
|
||||
{
|
||||
(
|
||||
lambda_set.runtime_representation(backend.env.layout_interner),
|
||||
true,
|
||||
)
|
||||
} else {
|
||||
// Closure data is a lambda set, which *itself* has no closure data!
|
||||
// The higher-order wrapper doesn't need to pass this down, that's
|
||||
|
@ -2161,6 +2182,7 @@ pub fn call_higher_order_lowlevel<'a>(
|
|||
// make sure that the wrapping struct is available in stack memory, so we can hand out a
|
||||
// pointer to it.
|
||||
let wrapped_storage = backend.storage.allocate_var(
|
||||
backend.env.layout_interner,
|
||||
wrapped_captures_layout,
|
||||
wrapped_closure_data_sym,
|
||||
crate::storage::StoredVarKind::Variable,
|
||||
|
@ -2323,7 +2345,8 @@ pub fn call_higher_order_lowlevel<'a>(
|
|||
|
||||
ListSortWith { xs } => {
|
||||
let elem_layout = unwrap_list_elem_layout(backend.storage.symbol_layouts[xs]);
|
||||
let (element_width, alignment) = elem_layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (element_width, alignment) =
|
||||
elem_layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
|
||||
let cb = &mut backend.code_builder;
|
||||
|
||||
|
@ -2386,7 +2409,8 @@ fn list_map_n<'a>(
|
|||
);
|
||||
|
||||
let elem_ret = unwrap_list_elem_layout(return_layout);
|
||||
let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO);
|
||||
let (elem_ret_size, elem_ret_align) =
|
||||
elem_ret.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
|
||||
let cb = &mut backend.code_builder;
|
||||
|
||||
|
@ -2407,7 +2431,7 @@ fn list_map_n<'a>(
|
|||
cb.i32_const(owns_captured_environment as i32);
|
||||
cb.i32_const(elem_ret_align as i32);
|
||||
for el in arg_elem_layouts.iter() {
|
||||
cb.i32_const(el.stack_size(TARGET_INFO) as i32);
|
||||
cb.i32_const(el.stack_size(backend.env.layout_interner, TARGET_INFO) as i32);
|
||||
}
|
||||
cb.i32_const(elem_ret_size as i32);
|
||||
|
||||
|
@ -2446,7 +2470,8 @@ fn ensure_symbol_is_in_memory<'a>(
|
|||
(local, offset, layout)
|
||||
}
|
||||
_ => {
|
||||
let (width, alignment) = layout.stack_size_and_alignment(TARGET_INFO);
|
||||
let (width, alignment) =
|
||||
layout.stack_size_and_alignment(backend.env.layout_interner, TARGET_INFO);
|
||||
let (frame_ptr, offset) = backend
|
||||
.storage
|
||||
.allocate_anonymous_stack_memory(width, alignment);
|
||||
|
|
|
@ -4,7 +4,7 @@ use bumpalo::Bump;
|
|||
use roc_collections::all::MutMap;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_mono::layout::Layout;
|
||||
use roc_mono::layout::{Layout, STLayoutInterner};
|
||||
|
||||
use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout};
|
||||
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||
|
@ -168,11 +168,12 @@ impl<'a> Storage<'a> {
|
|||
/// They are allocated a certain offset and size in the stack frame.
|
||||
pub fn allocate_var(
|
||||
&mut self,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
layout: Layout<'a>,
|
||||
symbol: Symbol,
|
||||
kind: StoredVarKind,
|
||||
) -> StoredValue {
|
||||
let wasm_layout = WasmLayout::new(&layout);
|
||||
let wasm_layout = WasmLayout::new(interner, &layout);
|
||||
self.symbol_layouts.insert(symbol, layout);
|
||||
|
||||
let storage = match wasm_layout {
|
||||
|
@ -217,6 +218,7 @@ impl<'a> Storage<'a> {
|
|||
/// stack frame, because it's a lot easier to keep track of the data flow.
|
||||
pub fn allocate_args(
|
||||
&mut self,
|
||||
interner: &STLayoutInterner<'a>,
|
||||
args: &[(Layout<'a>, Symbol)],
|
||||
code_builder: &mut CodeBuilder,
|
||||
arena: &'a Bump,
|
||||
|
@ -226,7 +228,7 @@ impl<'a> Storage<'a> {
|
|||
|
||||
for (layout, symbol) in args {
|
||||
self.symbol_layouts.insert(*symbol, *layout);
|
||||
let wasm_layout = WasmLayout::new(layout);
|
||||
let wasm_layout = WasmLayout::new(interner, layout);
|
||||
let local_index = self.arg_types.len() as u32;
|
||||
|
||||
let storage = match wasm_layout {
|
||||
|
|
|
@ -6,6 +6,7 @@ The user needs to analyse the Wasm module's memory to decode the result.
|
|||
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||
use roc_intern::Interner;
|
||||
use roc_mono::layout::{Builtin, Layout, UnionLayout};
|
||||
use roc_target::TargetInfo;
|
||||
|
||||
|
@ -36,13 +37,14 @@ pub trait Wasm32Result {
|
|||
/// Layout-driven wrapper generation
|
||||
pub fn insert_wrapper_for_layout<'a>(
|
||||
arena: &'a Bump,
|
||||
interner: &impl Interner<'a, Layout<'a>>,
|
||||
module: &mut WasmModule<'a>,
|
||||
wrapper_name: &'static str,
|
||||
main_fn_index: u32,
|
||||
layout: &Layout<'a>,
|
||||
) {
|
||||
let mut stack_data_structure = || {
|
||||
let size = layout.stack_size(TargetInfo::default_wasm32());
|
||||
let size = layout.stack_size(interner, TargetInfo::default_wasm32());
|
||||
if size == 0 {
|
||||
<() as Wasm32Result>::insert_wrapper(arena, module, wrapper_name, main_fn_index);
|
||||
} else {
|
||||
|
|
10
crates/compiler/intern/Cargo.toml
Normal file
10
crates/compiler/intern/Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "roc_intern"
|
||||
version = "0.0.1"
|
||||
authors = ["The Roc Contributors"]
|
||||
license = "UPL-1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
roc_collections = { path = "../collections" }
|
||||
parking_lot = "0.12"
|
225
crates/compiler/intern/src/lib.rs
Normal file
225
crates/compiler/intern/src/lib.rs
Normal file
|
@ -0,0 +1,225 @@
|
|||
//! Generic interners for concurrent and single-thread use cases.
|
||||
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
hash::{BuildHasher, Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use roc_collections::{default_hasher, BumpMap};
|
||||
|
||||
/// An interned value.
|
||||
///
|
||||
/// When possible, prefer comparing/hashing on the [Interned] representation of a value, rather
|
||||
/// than the value itself.
|
||||
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
pub struct Interned<T>(usize, std::marker::PhantomData<T>);
|
||||
impl<T> Clone for Interned<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self(self.0, Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for Interned<T> {}
|
||||
|
||||
/// A concurrent interner, suitable for usage between threads.
|
||||
///
|
||||
/// The interner does not currently maintain its own arena; you will have to supply
|
||||
/// values-to-be-interned as allocated in an independent arena.
|
||||
///
|
||||
/// If you need a concurrent global interner, you'll likely want each thread to take a
|
||||
/// [ThreadLocalInterner] via [GlobalInterner::fork], for caching purposes.
|
||||
///
|
||||
/// Originally derived from https://gist.github.com/matklad/44ba1a5a6168bc0c26c995131c007907;
|
||||
/// thank you, Aleksey!
|
||||
#[derive(Debug)]
|
||||
pub struct GlobalInterner<'a, K> {
|
||||
map: Mutex<BumpMap<&'a K, Interned<K>>>,
|
||||
vec: RwLock<Vec<&'a K>>,
|
||||
}
|
||||
|
||||
/// A derivative of a [GlobalInterner] interner that provides caching desirable for
|
||||
/// thread-local workloads. The only way to get a [ThreadLocalInterner] is via
|
||||
/// [GlobalInterner::fork].
|
||||
///
|
||||
/// All values interned into a [ThreadLocalInterner] are made available in its parent
|
||||
/// [GlobalInterner], making this suitable for global sharing of interned values.
|
||||
///
|
||||
/// Originally derived from https://gist.github.com/matklad/44ba1a5a6168bc0c26c995131c007907;
|
||||
/// thank you, Aleksey!
|
||||
#[derive(Debug)]
|
||||
pub struct ThreadLocalInterner<'a, K> {
|
||||
parent: Arc<GlobalInterner<'a, K>>,
|
||||
map: BumpMap<&'a K, Interned<K>>,
|
||||
/// Cache of interned values from the parent for local access.
|
||||
vec: RefCell<Vec<Option<&'a K>>>,
|
||||
}
|
||||
|
||||
/// A single-threaded interner, with no concurrency properties.
|
||||
///
|
||||
/// The only way to construct such an interner is to collapse a shared [GlobalInterner] into
|
||||
/// a [SingleThreadedInterner], via [GlobalInterner::unwrap].
|
||||
#[derive(Debug)]
|
||||
pub struct SingleThreadedInterner<'a, K> {
|
||||
map: BumpMap<&'a K, Interned<K>>,
|
||||
vec: Vec<&'a K>,
|
||||
}
|
||||
|
||||
/// Generic hasher for a value, to be used by all interners.
|
||||
///
|
||||
/// This uses the [default_hasher], so interner maps should also rely on [default_hasher].
|
||||
fn hash<V: Hash>(val: V) -> u64 {
|
||||
let mut state = roc_collections::all::BuildHasher::default().build_hasher();
|
||||
val.hash(&mut state);
|
||||
state.finish()
|
||||
}
|
||||
|
||||
pub trait Interner<'a, K: Hash + Eq> {
|
||||
/// Interns a value, returning its interned representation.
|
||||
/// If the value has been interned before, the old interned representation will be re-used.
|
||||
///
|
||||
/// Note that the provided value must be allocated into an arena of your choosing, but which
|
||||
/// must live at least as long as the interner lives.
|
||||
// TODO: we should consider maintaining our own arena in the interner, to avoid redundant
|
||||
// allocations when values already have interned representations.
|
||||
fn insert(&mut self, value: &'a K) -> Interned<K>;
|
||||
|
||||
/// Retrieves a value from the interner.
|
||||
fn get(&self, key: Interned<K>) -> &'a K;
|
||||
}
|
||||
|
||||
impl<'a, K: Hash + Eq> GlobalInterner<'a, K> {
|
||||
/// Creates a new global interner with the given capacity.
|
||||
pub fn with_capacity(cap: usize) -> Arc<GlobalInterner<'a, K>> {
|
||||
let map: BumpMap<&'a K, Interned<K>> =
|
||||
BumpMap::with_capacity_and_hasher(cap, default_hasher());
|
||||
|
||||
Arc::new(GlobalInterner {
|
||||
map: Mutex::new(map),
|
||||
vec: RwLock::new(Vec::with_capacity(cap)),
|
||||
})
|
||||
}
|
||||
|
||||
/// Creates a derivative [ThreadLocalInterner] pointing back to this global interner.
|
||||
pub fn fork(self: &Arc<GlobalInterner<'a, K>>) -> ThreadLocalInterner<'a, K> {
|
||||
ThreadLocalInterner {
|
||||
parent: Arc::clone(self),
|
||||
map: Default::default(),
|
||||
vec: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Collapses a shared [GlobalInterner] into a [SingleThreadedInterner].
|
||||
///
|
||||
/// Returns an [Err] with `self` if there are outstanding references to the [GlobalInterner].
|
||||
pub fn unwrap(
|
||||
self: Arc<GlobalInterner<'a, K>>,
|
||||
) -> Result<SingleThreadedInterner<'a, K>, Arc<Self>> {
|
||||
let GlobalInterner { map, vec } = Arc::try_unwrap(self)?;
|
||||
let map = Mutex::into_inner(map);
|
||||
let vec = RwLock::into_inner(vec);
|
||||
Ok(SingleThreadedInterner { map, vec })
|
||||
}
|
||||
|
||||
/// Interns a value with a pre-computed hash.
|
||||
/// Prefer calling this when possible, especially from [ThreadLocalInterner], to avoid
|
||||
/// re-computing hashes.
|
||||
fn insert_hashed(&self, value: &'a K, hash: u64) -> Interned<K> {
|
||||
let mut map = self.map.lock();
|
||||
let (_, interned) = map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, &value)
|
||||
.or_insert_with(|| {
|
||||
let mut vec = self.vec.write();
|
||||
let interned = Interned(vec.len(), Default::default());
|
||||
vec.push(value);
|
||||
(value, interned)
|
||||
});
|
||||
*interned
|
||||
}
|
||||
|
||||
fn get(&self, interned: Interned<K>) -> &'a K {
|
||||
let Interned(index, _) = interned;
|
||||
self.vec.read()[index]
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Hash + Eq> ThreadLocalInterner<'a, K> {
|
||||
/// Records an interned value in thread-specific storage, for faster access on lookups.
|
||||
fn record(&self, key: &'a K, interned: Interned<K>) {
|
||||
let mut vec = self.vec.borrow_mut();
|
||||
let len = vec.len().max(interned.0 + 1);
|
||||
vec.resize(len, None);
|
||||
vec[interned.0] = Some(key);
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Hash + Eq> Interner<'a, K> for ThreadLocalInterner<'a, K> {
|
||||
fn insert(&mut self, value: &'a K) -> Interned<K> {
|
||||
let global = &*self.parent;
|
||||
let hash = hash(value);
|
||||
let (&mut value, &mut interned) = self
|
||||
.map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, &value)
|
||||
.or_insert_with(|| {
|
||||
let interned = global.insert_hashed(value, hash);
|
||||
(value, interned)
|
||||
});
|
||||
self.record(value, interned);
|
||||
interned
|
||||
}
|
||||
|
||||
fn get(&self, key: Interned<K>) -> &'a K {
|
||||
if let Some(Some(value)) = self.vec.borrow().get(key.0) {
|
||||
return value;
|
||||
}
|
||||
let value = self.parent.get(key);
|
||||
self.record(value, key);
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K> SingleThreadedInterner<'a, K> {
|
||||
/// Creates a new single threaded interner with the given capacity.
|
||||
pub fn with_capacity(cap: usize) -> Self {
|
||||
Self {
|
||||
map: BumpMap::with_capacity_and_hasher(cap, default_hasher()),
|
||||
vec: Vec::with_capacity(cap),
|
||||
}
|
||||
}
|
||||
|
||||
/// Promotes the [SingleThreadedInterner] back to a [GlobalInterner].
|
||||
///
|
||||
/// You should *only* use this if you need to go from a single-threaded to a concurrent context,
|
||||
/// or in a case where you explicitly need access to [ThreadLocalInterner]s.
|
||||
pub fn into_global(self) -> Arc<GlobalInterner<'a, K>> {
|
||||
let SingleThreadedInterner { map, vec } = self;
|
||||
Arc::new(GlobalInterner {
|
||||
map: Mutex::new(map),
|
||||
vec: RwLock::new(vec),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, K: Hash + Eq> Interner<'a, K> for SingleThreadedInterner<'a, K> {
|
||||
fn insert(&mut self, value: &'a K) -> Interned<K> {
|
||||
let hash = hash(value);
|
||||
let (_, interned) = self
|
||||
.map
|
||||
.raw_entry_mut()
|
||||
.from_key_hashed_nocheck(hash, value)
|
||||
.or_insert_with(|| {
|
||||
let interned = Interned(self.vec.len(), Default::default());
|
||||
self.vec.push(value);
|
||||
(value, interned)
|
||||
});
|
||||
*interned
|
||||
}
|
||||
|
||||
fn get(&self, key: Interned<K>) -> &'a K {
|
||||
let Interned(index, _) = key;
|
||||
self.vec[index]
|
||||
}
|
||||
}
|
|
@ -14,4 +14,4 @@ roc_unify = { path = "../unify" }
|
|||
roc_solve = { path = "../solve" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = { path = "../../error_macros" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
|
|
|
@ -16,11 +16,14 @@ use roc_solve::solve::Pools;
|
|||
use roc_solve::specialize::{compact_lambda_sets_of_vars, DerivedEnv, Phase};
|
||||
use roc_types::subs::{get_member_lambda_sets_at_region, Content, FlatType, LambdaSet};
|
||||
use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable};
|
||||
use roc_unify::unify::{unify as unify_unify, Env, Mode, Unified};
|
||||
use roc_unify::unify::MetaCollector;
|
||||
use roc_unify::unify::{Env, Mode, Unified};
|
||||
|
||||
pub use roc_solve::ability::Resolved;
|
||||
pub use roc_types::subs::instantiate_rigids;
|
||||
|
||||
pub mod storage;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnificationFailed;
|
||||
|
||||
|
@ -313,6 +316,29 @@ impl Phase for LatePhase<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct ChangedVariableCollector {
|
||||
changed: Vec<Variable>,
|
||||
}
|
||||
|
||||
impl MetaCollector for ChangedVariableCollector {
|
||||
const UNIFYING_SPECIALIZATION: bool = false;
|
||||
const IS_LATE: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
fn record_specialization_lambda_set(&mut self, _member: Symbol, _region: u8, _var: Variable) {}
|
||||
|
||||
#[inline(always)]
|
||||
fn record_changed_variable(&mut self, subs: &Subs, var: Variable) {
|
||||
self.changed.push(subs.get_root_key_without_compacting(var))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn union(&mut self, other: Self) {
|
||||
self.changed.extend(other.changed)
|
||||
}
|
||||
}
|
||||
|
||||
/// Unifies two variables and performs lambda set compaction.
|
||||
/// Ranks and other ability demands are disregarded.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
|
@ -325,20 +351,25 @@ pub fn unify(
|
|||
exposed_by_module: &ExposedByModule,
|
||||
left: Variable,
|
||||
right: Variable,
|
||||
) -> Result<(), UnificationFailed> {
|
||||
) -> Result<Vec<Variable>, UnificationFailed> {
|
||||
debug_assert_ne!(
|
||||
home,
|
||||
ModuleId::DERIVED_SYNTH,
|
||||
"derived module can only unify its subs in its own context!"
|
||||
);
|
||||
let unified = unify_unify(&mut Env::new(subs), left, right, Mode::EQ);
|
||||
let unified = roc_unify::unify::unify_with_collector::<ChangedVariableCollector>(
|
||||
&mut Env::new(subs),
|
||||
left,
|
||||
right,
|
||||
Mode::EQ,
|
||||
);
|
||||
|
||||
match unified {
|
||||
Unified::Success {
|
||||
vars: _,
|
||||
must_implement_ability: _,
|
||||
lambda_sets_to_specialize,
|
||||
extra_metadata: _,
|
||||
extra_metadata,
|
||||
} => {
|
||||
let mut pools = Pools::default();
|
||||
|
||||
|
@ -366,7 +397,7 @@ pub fn unify(
|
|||
// here. We only need it for `compact_lambda_sets_of_vars`, which is also used in a
|
||||
// solving context where pools are relevant.
|
||||
|
||||
Ok(())
|
||||
Ok(extra_metadata.changed)
|
||||
}
|
||||
Unified::Failure(..) | Unified::BadType(..) => Err(UnificationFailed),
|
||||
}
|
||||
|
|
75
crates/compiler/late_solve/src/storage.rs
Normal file
75
crates/compiler/late_solve/src/storage.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
use roc_types::subs::StorageSubs;
|
||||
use roc_types::subs::{storage_copy_var_to, Subs, Variable, VariableMapCache};
|
||||
use std::iter::Iterator;
|
||||
|
||||
/// Storage for types to be sent to an external module, and written to only by one module's subs.
|
||||
/// Maintains a cache so that independent writes can re-use types previously inserted into the
|
||||
/// storage.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ExternalModuleStorage {
|
||||
storage: StorageSubs,
|
||||
/// Variable they expose -> variable we record into storage
|
||||
variable_mapping_cache: VariableMapCache,
|
||||
}
|
||||
|
||||
pub struct ExternalModuleStorageSnapshot {
|
||||
mapping_cache_len: usize,
|
||||
}
|
||||
|
||||
impl ExternalModuleStorage {
|
||||
pub fn new(subs: Subs) -> Self {
|
||||
Self {
|
||||
storage: StorageSubs::new(subs),
|
||||
variable_mapping_cache: VariableMapCache::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn extend_with_variable(&mut self, source: &Subs, variable: Variable) -> Variable {
|
||||
storage_copy_var_to(
|
||||
&mut self.variable_mapping_cache,
|
||||
source,
|
||||
self.storage.as_inner_mut(),
|
||||
variable,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn into_storage_subs(self) -> StorageSubs {
|
||||
self.storage
|
||||
}
|
||||
|
||||
/// Invalidates the whole cache given a sequence of variables that should no longer be indexed
|
||||
/// from the cache.
|
||||
pub fn invalidate_cache(&mut self, changed_variables: &[Variable]) {
|
||||
for var in changed_variables {
|
||||
for cache in self.variable_mapping_cache.0.iter_mut().rev() {
|
||||
cache.remove(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalidates the whole cache.
|
||||
/// Should only be called if you need to invalidate the cache but don't have a snapshot.
|
||||
/// Generally you should prefer to create a snapshot and invalidate that snapshot, which avoids
|
||||
/// unnecessary cache invalidation.
|
||||
pub fn invalidate_whole_cache(&mut self) {
|
||||
debug_assert_eq!(self.variable_mapping_cache.0.len(), 1);
|
||||
self.variable_mapping_cache.0.last_mut().unwrap().clear();
|
||||
}
|
||||
|
||||
/// Creates a snapshot of the cache, making it suitable for new ephemeral entries.
|
||||
/// The cache can be rolled back to the state it was in prior to the snapshot with [rollback_cache].
|
||||
pub fn snapshot_cache(&mut self) -> ExternalModuleStorageSnapshot {
|
||||
self.variable_mapping_cache.0.push(Default::default());
|
||||
ExternalModuleStorageSnapshot {
|
||||
mapping_cache_len: self.variable_mapping_cache.0.len(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn rollback_cache(&mut self, snapshot: ExternalModuleStorageSnapshot) {
|
||||
debug_assert_eq!(
|
||||
self.variable_mapping_cache.0.len(),
|
||||
snapshot.mapping_cache_len
|
||||
);
|
||||
self.variable_mapping_cache.0.pop();
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_can = { path = "../can" }
|
||||
roc_types = { path = "../types" }
|
||||
|
@ -20,7 +20,7 @@ roc_builtins = { path = "../builtins" }
|
|||
roc_module = { path = "../module" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
|
||||
[target.'cfg(not(windows))'.build-dependencies]
|
||||
roc_load_internal = { path = "../load_internal" }
|
||||
|
|
|
@ -23,16 +23,18 @@ roc_solve = { path = "../solve" }
|
|||
roc_solve_problem = { path = "../solve_problem" }
|
||||
roc_late_solve = { path = "../late_solve" }
|
||||
roc_mono = { path = "../mono" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_target = { path = "../roc_target" }
|
||||
roc_tracing = { path = "../../tracing" }
|
||||
roc_reporting = { path = "../../reporting" }
|
||||
roc_debug_flags = { path = "../debug_flags" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
parking_lot = "0.12"
|
||||
crossbeam = "0.8.2"
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = "1.0.0"
|
||||
pretty_assertions = "1.3.0"
|
||||
maplit = "1.0.2"
|
||||
indoc = "1.0.7"
|
||||
roc_test_utils = { path = "../../test_utils" }
|
||||
|
|
|
@ -23,6 +23,7 @@ use roc_debug_flags::{
|
|||
};
|
||||
use roc_derive::SharedDerivedModule;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_intern::{GlobalInterner, SingleThreadedInterner};
|
||||
use roc_late_solve::{AbilitiesView, WorldAbilities};
|
||||
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
|
||||
use roc_module::symbol::{
|
||||
|
@ -33,7 +34,9 @@ use roc_mono::ir::{
|
|||
CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase,
|
||||
UpdateModeIds,
|
||||
};
|
||||
use roc_mono::layout::{CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem};
|
||||
use roc_mono::layout::{
|
||||
CapturesNiche, LambdaName, Layout, LayoutCache, LayoutProblem, STLayoutInterner,
|
||||
};
|
||||
use roc_parse::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
|
||||
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
|
||||
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
|
||||
|
@ -461,9 +464,11 @@ fn start_phase<'a>(
|
|||
|
||||
let derived_module = SharedDerivedModule::clone(&state.derived_module);
|
||||
|
||||
let build_expects = matches!(state.exec_mode, ExecutionMode::Test)
|
||||
&& state.module_cache.expectations.contains_key(&module_id);
|
||||
|
||||
BuildTask::BuildPendingSpecializations {
|
||||
layout_cache,
|
||||
execution_mode: state.exec_mode,
|
||||
module_id,
|
||||
module_timing,
|
||||
solved_subs,
|
||||
|
@ -475,6 +480,7 @@ fn start_phase<'a>(
|
|||
// TODO: awful, how can we get rid of the clone?
|
||||
exposed_by_module: state.exposed_types.clone(),
|
||||
derived_module,
|
||||
build_expects,
|
||||
}
|
||||
}
|
||||
Phase::MakeSpecializations => {
|
||||
|
@ -506,7 +512,7 @@ fn start_phase<'a>(
|
|||
IdentIds::default(),
|
||||
Subs::default(),
|
||||
ProcsBase::default(),
|
||||
LayoutCache::new(state.target_info),
|
||||
LayoutCache::new(state.layout_interner.fork(), state.target_info),
|
||||
ModuleTiming::new(Instant::now()),
|
||||
)
|
||||
} else if state.make_specializations_pass.current_pass() == 1 {
|
||||
|
@ -718,6 +724,7 @@ pub struct MonomorphizedModule<'a> {
|
|||
pub module_id: ModuleId,
|
||||
pub interns: Interns,
|
||||
pub subs: Subs,
|
||||
pub layout_interner: SingleThreadedInterner<'a, Layout<'a>>,
|
||||
pub output_path: Box<Path>,
|
||||
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
|
||||
pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
|
||||
|
@ -845,6 +852,9 @@ enum Msg<'a> {
|
|||
/// all modules are now monomorphized, we are done
|
||||
FinishedAllSpecialization {
|
||||
subs: Subs,
|
||||
/// The layout interner after all passes in mono are done.
|
||||
/// DO NOT use the one on state; that is left in an empty state after specialization is complete!
|
||||
layout_interner: STLayoutInterner<'a>,
|
||||
exposed_to_host: ExposedToHost,
|
||||
},
|
||||
|
||||
|
@ -952,6 +962,8 @@ struct State<'a> {
|
|||
|
||||
// cached subs (used for builtin modules, could include packages in the future too)
|
||||
cached_subs: CachedSubs,
|
||||
|
||||
layout_interner: Arc<GlobalInterner<'a, Layout<'a>>>,
|
||||
}
|
||||
|
||||
type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>;
|
||||
|
@ -1004,6 +1016,7 @@ impl<'a> State<'a> {
|
|||
exec_mode,
|
||||
make_specializations_pass: MakeSpecializationsPass::Pass(1),
|
||||
world_abilities: Default::default(),
|
||||
layout_interner: GlobalInterner::with_capacity(128),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1116,7 +1129,6 @@ enum BuildTask<'a> {
|
|||
},
|
||||
BuildPendingSpecializations {
|
||||
module_timing: ModuleTiming,
|
||||
execution_mode: ExecutionMode,
|
||||
layout_cache: LayoutCache<'a>,
|
||||
solved_subs: Solved<Subs>,
|
||||
imported_module_thunks: &'a [Symbol],
|
||||
|
@ -1127,6 +1139,7 @@ enum BuildTask<'a> {
|
|||
exposed_by_module: ExposedByModule,
|
||||
abilities_store: AbilitiesStore,
|
||||
derived_module: SharedDerivedModule,
|
||||
build_expects: bool,
|
||||
},
|
||||
MakeSpecializations {
|
||||
module_id: ModuleId,
|
||||
|
@ -1602,12 +1615,14 @@ fn state_thread_step<'a>(
|
|||
}
|
||||
Msg::FinishedAllSpecialization {
|
||||
subs,
|
||||
layout_interner,
|
||||
exposed_to_host,
|
||||
} => {
|
||||
// We're done! There should be no more messages pending.
|
||||
debug_assert!(msg_rx.is_empty());
|
||||
|
||||
let monomorphized = finish_specialization(state, subs, exposed_to_host)?;
|
||||
let monomorphized =
|
||||
finish_specialization(state, subs, layout_interner, exposed_to_host)?;
|
||||
|
||||
Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
|
||||
}
|
||||
|
@ -1983,12 +1998,12 @@ fn start_tasks<'a>(
|
|||
}
|
||||
|
||||
macro_rules! debug_print_ir {
|
||||
($state:expr, $flag:path) => {
|
||||
($state:expr, $interner:expr, $flag:path) => {
|
||||
dbg_do!($flag, {
|
||||
let procs_string = $state
|
||||
.procedures
|
||||
.values()
|
||||
.map(|proc| proc.to_pretty(200))
|
||||
.map(|proc| proc.to_pretty($interner, 200))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let result = procs_string.join("\n");
|
||||
|
@ -2358,7 +2373,14 @@ fn update<'a>(
|
|||
.type_problems
|
||||
.insert(module_id, solved_module.problems);
|
||||
|
||||
if !loc_expects.is_empty() {
|
||||
let should_include_expects = !loc_expects.is_empty() && {
|
||||
let modules = state.arc_modules.lock();
|
||||
modules
|
||||
.package_eq(module_id, state.root_id)
|
||||
.expect("root or this module is not yet known - that's a bug!")
|
||||
};
|
||||
|
||||
if should_include_expects {
|
||||
let (path, _) = state.module_cache.sources.get(&module_id).unwrap();
|
||||
|
||||
let expectations = Expectations {
|
||||
|
@ -2383,7 +2405,11 @@ fn update<'a>(
|
|||
Some(ref platform_data) => module_id == platform_data.module_id,
|
||||
};
|
||||
|
||||
if is_host_exposed {
|
||||
let add_to_host_exposed = is_host_exposed &&
|
||||
// During testing, we don't need to expose anything to the host.
|
||||
!matches!(state.exec_mode, ExecutionMode::Test);
|
||||
|
||||
if add_to_host_exposed {
|
||||
state.exposed_to_host.values.extend(
|
||||
solved_module
|
||||
.exposed_vars_by_symbol
|
||||
|
@ -2471,10 +2497,9 @@ fn update<'a>(
|
|||
if state.goal_phase() > Phase::SolveTypes
|
||||
|| matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
|
||||
{
|
||||
let layout_cache = state
|
||||
.layout_caches
|
||||
.pop()
|
||||
.unwrap_or_else(|| LayoutCache::new(state.target_info));
|
||||
let layout_cache = state.layout_caches.pop().unwrap_or_else(|| {
|
||||
LayoutCache::new(state.layout_interner.fork(), state.target_info)
|
||||
});
|
||||
|
||||
let typechecked = TypeCheckedModule {
|
||||
module_id,
|
||||
|
@ -2659,7 +2684,7 @@ fn update<'a>(
|
|||
ident_ids,
|
||||
subs,
|
||||
module_timing,
|
||||
layout_cache: _,
|
||||
layout_cache: _layout_cache,
|
||||
procs_base: _,
|
||||
},
|
||||
) in state.module_cache.late_specializations.drain()
|
||||
|
@ -2669,11 +2694,25 @@ fn update<'a>(
|
|||
state.root_subs = Some(subs);
|
||||
}
|
||||
state.timings.insert(module_id, module_timing);
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
log_layout_stats(module_id, &_layout_cache);
|
||||
}
|
||||
}
|
||||
|
||||
let layout_interner = {
|
||||
let mut taken = GlobalInterner::with_capacity(0);
|
||||
std::mem::swap(&mut state.layout_interner, &mut taken);
|
||||
taken
|
||||
};
|
||||
let layout_interner = layout_interner
|
||||
.unwrap()
|
||||
.expect("outstanding references to global layout interener, but we just drained all layout caches");
|
||||
|
||||
log!("specializations complete from {:?}", module_id);
|
||||
|
||||
debug_print_ir!(state, ROC_PRINT_IR_AFTER_SPECIALIZATION);
|
||||
debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_SPECIALIZATION);
|
||||
|
||||
let ident_ids = state.constrained_ident_ids.get_mut(&module_id).unwrap();
|
||||
|
||||
|
@ -2685,17 +2724,18 @@ fn update<'a>(
|
|||
&mut state.procedures,
|
||||
);
|
||||
|
||||
debug_print_ir!(state, ROC_PRINT_IR_AFTER_RESET_REUSE);
|
||||
debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_RESET_REUSE);
|
||||
|
||||
Proc::insert_refcount_operations(
|
||||
arena,
|
||||
&layout_interner,
|
||||
module_id,
|
||||
ident_ids,
|
||||
&mut update_mode_ids,
|
||||
&mut state.procedures,
|
||||
);
|
||||
|
||||
debug_print_ir!(state, ROC_PRINT_IR_AFTER_REFCOUNT);
|
||||
debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_REFCOUNT);
|
||||
|
||||
// This is not safe with the new non-recursive RC updates that we do for tag unions
|
||||
//
|
||||
|
@ -2713,7 +2753,7 @@ fn update<'a>(
|
|||
msg_tx
|
||||
.send(Msg::FinishedAllSpecialization {
|
||||
subs,
|
||||
// TODO thread through mono problems
|
||||
layout_interner,
|
||||
exposed_to_host: state.exposed_to_host.clone(),
|
||||
})
|
||||
.map_err(|_| LoadingProblem::MsgChannelDied)?;
|
||||
|
@ -2813,11 +2853,35 @@ fn update<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn finish_specialization(
|
||||
state: State,
|
||||
#[cfg(debug_assertions)]
|
||||
fn log_layout_stats(module_id: ModuleId, layout_cache: &LayoutCache) {
|
||||
let (cache_stats, raw_function_cache_stats) = layout_cache.statistics();
|
||||
roc_tracing::info!(
|
||||
module = ?module_id,
|
||||
insertions = cache_stats.insertions,
|
||||
hits = cache_stats.hits,
|
||||
misses = cache_stats.misses,
|
||||
non_insertable = cache_stats.non_insertable,
|
||||
non_reusable = cache_stats.non_reusable,
|
||||
"cache stats"
|
||||
);
|
||||
roc_tracing::info!(
|
||||
module = ?module_id,
|
||||
insertions = raw_function_cache_stats.insertions,
|
||||
hits = raw_function_cache_stats.hits,
|
||||
misses = raw_function_cache_stats.misses,
|
||||
non_insertable = raw_function_cache_stats.non_insertable,
|
||||
non_reusable = raw_function_cache_stats.non_reusable,
|
||||
"raw function cache stats"
|
||||
);
|
||||
}
|
||||
|
||||
fn finish_specialization<'a>(
|
||||
state: State<'a>,
|
||||
subs: Subs,
|
||||
layout_interner: STLayoutInterner<'a>,
|
||||
exposed_to_host: ExposedToHost,
|
||||
) -> Result<MonomorphizedModule, LoadingProblem> {
|
||||
) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
|
||||
if false {
|
||||
println!(
|
||||
"total Type clones: {} ",
|
||||
|
@ -2940,6 +3004,7 @@ fn finish_specialization(
|
|||
module_id: state.root_id,
|
||||
subs,
|
||||
interns,
|
||||
layout_interner,
|
||||
procedures,
|
||||
entry_point,
|
||||
sources,
|
||||
|
@ -4808,7 +4873,6 @@ fn make_specializations<'a>(
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
fn build_pending_specializations<'a>(
|
||||
arena: &'a Bump,
|
||||
execution_mode: ExecutionMode,
|
||||
solved_subs: Solved<Subs>,
|
||||
imported_module_thunks: &'a [Symbol],
|
||||
home: ModuleId,
|
||||
|
@ -4821,6 +4885,7 @@ fn build_pending_specializations<'a>(
|
|||
exposed_by_module: &ExposedByModule,
|
||||
abilities_store: AbilitiesStore,
|
||||
derived_module: SharedDerivedModule,
|
||||
build_expects: bool,
|
||||
) -> Msg<'a> {
|
||||
let find_specializations_start = Instant::now();
|
||||
|
||||
|
@ -5067,11 +5132,8 @@ fn build_pending_specializations<'a>(
|
|||
}
|
||||
Expectation => {
|
||||
// skip expectations if we're not going to run them
|
||||
match execution_mode {
|
||||
ExecutionMode::Test => { /* fall through */ }
|
||||
ExecutionMode::Check
|
||||
| ExecutionMode::Executable
|
||||
| ExecutionMode::ExecutableIfCheck => continue,
|
||||
if !build_expects {
|
||||
continue;
|
||||
}
|
||||
|
||||
// mark this symbol as a top-level thunk before any other work on the procs
|
||||
|
@ -5143,11 +5205,8 @@ fn build_pending_specializations<'a>(
|
|||
}
|
||||
ExpectationFx => {
|
||||
// skip expectations if we're not going to run them
|
||||
match execution_mode {
|
||||
ExecutionMode::Test => { /* fall through */ }
|
||||
ExecutionMode::Check
|
||||
| ExecutionMode::Executable
|
||||
| ExecutionMode::ExecutableIfCheck => continue,
|
||||
if !build_expects {
|
||||
continue;
|
||||
}
|
||||
|
||||
// mark this symbol as a top-level thunk before any other work on the procs
|
||||
|
@ -5422,7 +5481,6 @@ fn run_task<'a>(
|
|||
)),
|
||||
BuildPendingSpecializations {
|
||||
module_id,
|
||||
execution_mode,
|
||||
ident_ids,
|
||||
decls,
|
||||
module_timing,
|
||||
|
@ -5433,9 +5491,9 @@ fn run_task<'a>(
|
|||
abilities_store,
|
||||
exposed_by_module,
|
||||
derived_module,
|
||||
build_expects,
|
||||
} => Ok(build_pending_specializations(
|
||||
arena,
|
||||
execution_mode,
|
||||
solved_subs,
|
||||
imported_module_thunks,
|
||||
module_id,
|
||||
|
@ -5448,6 +5506,7 @@ fn run_task<'a>(
|
|||
&exposed_by_module,
|
||||
abilities_store,
|
||||
derived_module,
|
||||
build_expects,
|
||||
)),
|
||||
MakeSpecializations {
|
||||
module_id,
|
||||
|
|
|
@ -10,7 +10,7 @@ roc_region = { path = "../region" }
|
|||
roc_ident = { path = "../ident" }
|
||||
roc_collections = { path = "../collections" }
|
||||
roc_error_macros = {path = "../../error_macros"}
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
lazy_static = "1.4.0"
|
||||
static_assertions = "1.1.0"
|
||||
snafu = { version = "0.7.1", features = ["backtraces"] }
|
||||
|
|
|
@ -512,6 +512,20 @@ impl<'a> PackageModuleIds<'a> {
|
|||
pub fn available_modules(&self) -> impl Iterator<Item = &PQModuleName> {
|
||||
self.by_id.iter()
|
||||
}
|
||||
|
||||
/// Returns true iff two modules belong to the same package.
|
||||
/// Returns [None] if one module is unknown.
|
||||
pub fn package_eq(&self, left: ModuleId, right: ModuleId) -> Option<bool> {
|
||||
if left.is_builtin() ^ right.is_builtin() {
|
||||
return Some(false);
|
||||
}
|
||||
let result = match (self.get_name(left)?, self.get_name(right)?) {
|
||||
(PQModuleName::Unqualified(_), PQModuleName::Unqualified(_)) => true,
|
||||
(PQModuleName::Qualified(pkg1, _), PQModuleName::Qualified(pkg2, _)) => pkg1 == pkg2,
|
||||
_ => false,
|
||||
};
|
||||
Some(result)
|
||||
}
|
||||
}
|
||||
|
||||
/// Stores a mapping between ModuleId and InlinableString.
|
||||
|
@ -1229,6 +1243,9 @@ define_builtins! {
|
|||
47 STR_TO_NUM: "strToNum"
|
||||
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
|
||||
49 STR_CAPACITY: "capacity"
|
||||
50 STR_REPLACE_EACH: "replaceEach"
|
||||
51 STR_REPLACE_FIRST: "replaceFirst"
|
||||
52 STR_REPLACE_LAST: "replaceLast"
|
||||
}
|
||||
6 LIST: "List" => {
|
||||
0 LIST_LIST: "List" imported // the List.List type alias
|
||||
|
|
|
@ -14,6 +14,7 @@ roc_types = { path = "../types" }
|
|||
roc_can = { path = "../can" }
|
||||
roc_derive_key = { path = "../derive_key" }
|
||||
roc_derive = { path = "../derive" }
|
||||
roc_intern = { path = "../intern" }
|
||||
roc_late_solve = { path = "../late_solve" }
|
||||
roc_std = { path = "../../roc_std" }
|
||||
roc_problem = { path = "../problem" }
|
||||
|
@ -23,6 +24,7 @@ roc_error_macros = {path="../../error_macros"}
|
|||
roc_debug_flags = {path="../debug_flags"}
|
||||
roc_tracing = { path = "../../tracing" }
|
||||
ven_pretty = { path = "../../vendor/pretty" }
|
||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||
bumpalo = { version = "3.11.0", features = ["collections"] }
|
||||
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
|
||||
static_assertions = "1.1.0"
|
||||
bitvec = "1.0.1"
|
||||
|
|
|
@ -629,7 +629,7 @@ fn eq_list<'a>(
|
|||
// let size = literal int
|
||||
let size = root.create_symbol(ident_ids, "size");
|
||||
let size_expr = Expr::Literal(Literal::Int(
|
||||
(elem_layout.stack_size(root.target_info) as i128).to_ne_bytes(),
|
||||
(elem_layout.stack_size(root.layout_interner, root.target_info) as i128).to_ne_bytes(),
|
||||
));
|
||||
let size_stmt = |next| Stmt::Let(size, size_expr, layout_isize, next);
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue