Merge branch 'main' into swiftuidemo

Signed-off-by: Anton-4 <17049058+Anton-4@users.noreply.github.com>
This commit is contained in:
Anton-4 2022-09-21 17:24:25 +02:00 committed by GitHub
commit 04ea17e8a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
277 changed files with 15938 additions and 4413 deletions

View file

@ -7,10 +7,8 @@ name: Nightly Release Linux x86_64
jobs: jobs:
build: build:
name: Rust tests, build and package nightly release name: Rust tests, build and package nightly release
runs-on: [self-hosted, i5-4690K] runs-on: [self-hosted, i7-6700K]
timeout-minutes: 90 timeout-minutes: 90
env:
FORCE_COLOR: 1 # for earthly logging
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@ -21,14 +19,26 @@ jobs:
run: RUSTFLAGS="-C target-cpu=x86-64" cargo build --features with_sound --release --locked 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. # 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 - name: get commit SHA
run: ./ci/package_release.sh roc_linux_x86_64.tar.gz 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. - name: Upload roc nightly tar. Actually uploading to github releases has to be done manually.
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: roc_nightly-linux_x86_64.tar.gz name: ${{ env.RELEASE_TAR_FILENAME }}
path: roc_linux_x86_64.tar.gz path: ${{ env.RELEASE_TAR_FILENAME }}
retention-days: 4 retention-days: 4
- name: build wasm repl - name: build wasm repl

View file

@ -19,6 +19,18 @@ jobs:
- name: run tests - name: run tests
run: cargo test --locked --release 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 - name: write version to file
run: ./ci/write_version.sh run: ./ci/write_version.sh
@ -26,7 +38,7 @@ jobs:
run: cargo build --locked --release run: cargo build --locked --release
- name: package 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 - name: print short commit SHA
run: git rev-parse --short "$GITHUB_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 - name: Upload artifact Actually uploading to github releases has to be done manually
uses: actions/upload-artifact@v3 uses: actions/upload-artifact@v3
with: with:
name: roc_nightly-macos_apple_silicon.tar.gz name: ${{ env.RELEASE_TAR_FILENAME }}
path: roc_darwin_apple_silicon.tar.gz path: ${{ env.RELEASE_TAR_FILENAME }}
retention-days: 4 retention-days: 4

View file

@ -1,6 +1,6 @@
on: on:
schedule: schedule:
- cron: '0 9 * * 1' # 9=9am utc+0, 1=monday - cron: '0 9 * * *' # 9=9am utc+0
name: Nightly Release macOS x86_64 name: Nightly Release macOS x86_64
@ -9,13 +9,16 @@ env:
LLVM_SYS_130_PREFIX: /usr/local/opt/llvm LLVM_SYS_130_PREFIX: /usr/local/opt/llvm
jobs: jobs:
test-and-build: test-build-upload:
name: Rust tests, build and package nightly release name: build, test, package and upload nightly release
runs-on: [macos-12] runs-on: [macos-12]
timeout-minutes: 90 timeout-minutes: 120
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- name: write version to file
run: ./ci/write_version.sh
- name: Install zig - name: Install zig
run: | 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 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 run: zig version
- name: Install LLVM - name: Install LLVM
run: brew install llvm@13 run: brew install llvm@13
# build has to be done before tests #2572 # build has to be done before tests #2572
- name: build release - name: build release
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: build command: build
args: --release --locked args: --release --locked
- name: execute rust tests - name: execute rust tests
uses: actions-rs/cargo@v1 uses: actions-rs/cargo@v1
with: with:
command: test command: test
args: --locked # no --release yet until #3166 is fixed args: --release --locked -- --skip opaque_wrap_function --skip bool_list_literal
- name: write version to file
run: ./ci/write_version.sh - name: get commit SHA
- name: package release run: echo "SHA=$(git rev-parse --short "$GITHUB_SHA")" >> $GITHUB_ENV
run: ./ci/package_release.sh roc_darwin_x86_64.tar.gz
- name: Create pre-release with test_archive.tar.gz - name: get date
uses: Anton-4/deploy-nightly@1609d8dfe211b078674801113ab7a2ec2938b2a9 run: echo "DATE=$(date "+%Y-%m-%d")" >> $GITHUB_ENV
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions - name: build file name
with: env:
upload_url: https://uploads.github.com/repos/roc-lang/roc/releases/51880579/assets{?name,label} DATE: ${{ env.DATE }}
release_id: 51880579 SHA: ${{ env.SHA }}
asset_path: ./roc_darwin_x86_64.tar.gz run: echo "RELEASE_TAR_FILENAME=roc_nightly-macos_x86_64-$DATE-$SHA.tar.gz" >> $GITHUB_ENV
asset_name: roc_nightly-macos_x86_64-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)
asset_content_type: application/gzip - name: package release
max_releases: 3 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

View file

@ -31,6 +31,6 @@ jobs:
- name: execute tests with --release - name: execute tests with --release
run: nix develop -c cargo test --locked --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 - name: execute llvm wasm tests with --release
run: nix develop -c cargo test-gen-llvm-wasm --locked --release run: nix develop -c cargo test-gen-llvm-wasm --locked --release

View file

@ -12,7 +12,7 @@ env:
jobs: jobs:
spell-check: spell-check:
name: spell check name: spell check
runs-on: [self-hosted, i7-6700K] runs-on: [self-hosted]
timeout-minutes: 10 timeout-minutes: 10
env: env:
FORCE_COLOR: 1 FORCE_COLOR: 1
@ -21,8 +21,5 @@ jobs:
with: with:
clean: "true" clean: "true"
- name: Earthly version - name: do spell check with typos-cli 1.0.11 # to reproduce locally: cargo install typos-cli --version 1.0.11
run: earthly --version run: typos
- name: install spell checker, do spell check
run: ./ci/safe-earthly.sh +check-typos

View file

@ -5,7 +5,7 @@ on:
name: Test latest nightly release for macOS Apple Silicon name: Test latest nightly release for macOS Apple Silicon
jobs: jobs:
test-and-build: test-nightly:
name: test nightly macos aarch64 name: test nightly macos aarch64
runs-on: [self-hosted, macOS, ARM64] runs-on: [self-hosted, macOS, ARM64]
timeout-minutes: 90 timeout-minutes: 90
@ -16,7 +16,7 @@ jobs:
run: curl https://api.github.com/repos/roc-lang/roc/releases > roc_releases.json 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 - 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 - name: get the archive from the url
run: curl -OL ${{ env.RELEASE_URL }} run: curl -OL ${{ env.RELEASE_URL }}

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

View file

@ -95,3 +95,4 @@ Paul Young <84700+paulyoung@users.noreply.github.com>
Rod <randomer@users.noreply.github.com> Rod <randomer@users.noreply.github.com>
Marko Vujanic <crashxx@gmail.com> Marko Vujanic <crashxx@gmail.com>
kilianv <r0754877> kilianv <r0754877>
David Dunn <26876072+doubledup@users.noreply.github.com>

View file

@ -4,10 +4,9 @@ Installation should be a smooth process, let us now if anything does not work pe
## Using Nix ## 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". :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.
> This error can occur if you ran `cargo build` in the same folder without nix.
### On Linux x86_64/aarch64 or MacOS aarch64/arm64/x86_64 ### 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. 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): 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 sh <(curl -L https://nixos.org/nix/install) --daemon
``` ```
Open a new terminal and install nixFlakes in your environment: Open a new terminal and install nixFlakes in your environment:
```
```sh
nix-env -iA nixpkgs.nixFlakes nix-env -iA nixpkgs.nixFlakes
``` ```
Edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add: Edit either `~/.config/nix/nix.conf` or `/etc/nix/nix.conf` and add:
```
```text
experimental-features = nix-command flakes 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 #### Usage
Now with nix set up, you just need to run one command from the roc project root directory: Now with nix set up, you just need to run one command from the roc project root directory:
```
```sh
nix develop nix develop
``` ```
You should be in a shell with everything needed to build already installed. You should be in a shell with everything needed to build already installed.
Use `cargo run help` to see all subcommands. Use `cargo run help` to see all subcommands.
To use the `repl` subcommand, execute `cargo run repl`. 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. `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: 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 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: To build the compiler, you need these installed:
* [Zig](https://ziglang.org/), see below for version - [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` - `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` - On Debian/Ubuntu `sudo apt-get install pkg-config`
* LLVM, see below for version - LLVM, see below for version
* [rust](https://rustup.rs/) - [rust](https://rustup.rs/)
* Also run `cargo install bindgen` after installing rust. You may need to open a new terminal. - 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: 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. 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. 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: You may see an error like this during builds:
``` ```text
/usr/bin/ld: cannot find -lxcb-render /usr/bin/ld: cannot find -lxcb-render
/usr/bin/ld: cannot find -lxcb-shape /usr/bin/ld: cannot find -lxcb-shape
/usr/bin/ld: cannot find -lxcb-xfixes /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: If so, you can fix it like so:
``` ```sh
sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev sudo apt-get install libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev
``` ```
### Zig ### Zig
**version: 0.9.1** **version: 0.9.1**
For any OS, you can use [`zigup`](https://github.com/marler8997/zigup) to manage zig installations. 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: If you prefer a package manager, you can try the following:
- For MacOS, you can install with `brew install zig` - 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, 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) - 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. 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 ### LLVM
**version: 13.0.x** **version: 13.0.x**
For macOS, you can install LLVM 13 using `brew install llvm@13` and then adding 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 `$(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: You may also need to manually specify a prefix env var like so:
```
```sh
export LLVM_SYS_130_PREFIX=/usr/local/opt/llvm@13 export LLVM_SYS_130_PREFIX=/usr/local/opt/llvm@13
``` ```
For Ubuntu and Debian: For Ubuntu and Debian:
```
```sh
sudo apt -y install lsb-release software-properties-common gnupg sudo apt -y install lsb-release software-properties-common gnupg
wget https://apt.llvm.org/llvm.sh wget https://apt.llvm.org/llvm.sh
chmod +x 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`. 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: 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 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) [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 `cannot find -lz` run `sudo apt install zlib1g-dev`.
If you encounter: If you encounter:
```
```text
error: No suitable version of LLVM was found system-wide or pointed error: No suitable version of LLVM was found system-wide or pointed
to by LLVM_SYS_130_PREFIX. 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. Add `export LLVM_SYS_130_PREFIX=/usr/lib/llvm-13` to your `~/.bashrc` or equivalent file for your shell.
### LLVM installation on macOS ### 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: 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 LDFLAGS="-L/usr/local/opt/llvm/lib -Wl,-rpath,/usr/local/opt/llvm/lib"
export CPPFLAGS="-I/usr/local/opt/llvm/include" export CPPFLAGS="-I/usr/local/opt/llvm/include"
``` ```
@ -178,24 +200,24 @@ export CPPFLAGS="-I/usr/local/opt/llvm/include"
### LLVM installation on Windows ### 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. **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/roc-lang/llvm-package-windows/releases/download/v13.0.1/LLVM-13.0.1-win64.7z).
1. Download the custom LLVM 7z archive [here](https://github.com/PLC-lang/llvm-package-windows/releases/tag/v13.0.0).
1. [Download 7-zip](https://www.7-zip.org/) to be able to extract this archive. 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. 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-changes-to-environment-variables) to make this a permanent environment variable): 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):
```
[Environment]::SetEnvironmentVariable(
"Path",
[Environment]::GetEnvironmentVariable("Path", "User") + ";C:\Users\anton\Downloads\LLVM-13.0.0-win64\bin",
"User"
)
```
```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! 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 ### 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! 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: Create `~/.cargo/config.toml` if it does not exist and add this to it:
``` ```toml
[build] [build]
# Link with lld, per https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306 # 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 # Use target-cpu=native, per https://deterministic.space/high-performance-rust.html

View file

@ -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: Examples of behavior that contributes to creating a positive environment include:
* Demonstrating empathy and kindness toward other people - Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences - Being respectful of differing opinions, viewpoints, and experiences
* Kindly giving and gracefully accepting constructive feedback - Kindly giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience - 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 - Focusing on what is best not just for us as individuals, but for the overall
community community
Examples of unacceptable behavior include: Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of any kind - 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 - Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment - Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission - 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 - Telling others to be less sensitive, or that they should not feel hurt or offended by something
## Enforcement Responsibilities ## Enforcement Responsibilities
@ -41,4 +41,4 @@ Moderators who do not follow or enforce the Code of Conduct in good faith may fa
## Attribution ## 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>

View file

@ -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)! 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 ## Building from Source
Check [Building from source](BUILDING_FROM_SOURCE.md) for instructions. 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 ## Running Tests
Most contributors execute the following commands befor pushing their code: Most contributors execute the following commands befor pushing their code:
```
```sh
cargo test cargo test
cargo fmt --all -- --check cargo fmt --all -- --check
cargo clippy --workspace --tests -- --deny warnings cargo clippy --workspace --tests -- --deny warnings
``` ```
Execute `cargo fmt --all` to fix the formatting. Execute `cargo fmt --all` to fix the formatting.
## Contribution Tips ## 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. - 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. - 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). - You can find good first issues [here][good-first-issues].
- 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.
- [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. - [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. - 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: - 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). 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) 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: 4. Make git sign your commits automatically:
```
```sh
git config --global commit.gpgsign true git config --global commit.gpgsign true
``` ```
## Can we do better? ## Can we do better?
Feel free to open an issue if you think this document can be improved or is unclear in any way. 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
View file

@ -297,9 +297,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.10.0" version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" checksum = "c1ad822118d20d2c234f427000d5acc36eabe1e29a348c89b63dd60b13f28e5d"
[[package]] [[package]]
name = "byte-tools" name = "byte-tools"
@ -330,18 +330,18 @@ dependencies = [
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
version = "1.11.0" version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835" checksum = "2f5715e491b5a1598fc2bef5a606847b5dc1d48ea625bd3c02c00de8285591da"
dependencies = [ dependencies = [
"bytemuck_derive", "bytemuck_derive",
] ]
[[package]] [[package]]
name = "bytemuck_derive" name = "bytemuck_derive"
version = "1.1.0" version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "562e382481975bc61d11275ac5e62a19abd00b0547d99516a415336f183dcd0e" checksum = "1b9e1f5fa78f69496407a27ae9ed989e3c3b072310286f5ef385525e4cbc24a9"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -475,9 +475,9 @@ dependencies = [
[[package]] [[package]]
name = "clap" name = "clap"
version = "3.2.11" version = "3.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d646c7ade5eb07c4aa20e907a922750df0c448892513714fd3e4acbc7130829f" checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
@ -492,9 +492,9 @@ dependencies = [
[[package]] [[package]]
name = "clap_derive" name = "clap_derive"
version = "3.2.7" version = "3.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902" checksum = "ea0c8bce528c4be4da13ea6fead8965e95b6073585a2f05204bd8f4119f82a65"
dependencies = [ dependencies = [
"heck", "heck",
"proc-macro-error", "proc-macro-error",
@ -1150,9 +1150,9 @@ dependencies = [
[[package]] [[package]]
name = "dircpy" name = "dircpy"
version = "0.3.12" version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16e8f15af1ed7189d2bf43c7ae5d6fe0a840cd3b1e9c7bf7c08a384a5df441f" checksum = "73ff6269b47c0c5220a0ff5eb140424340276ec89a10e58cbd4cf366de52dfa9"
dependencies = [ dependencies = [
"jwalk", "jwalk",
"log", "log",
@ -1258,14 +1258,14 @@ checksum = "64fba5a42bd76a17cad4bfa00de168ee1cbfa06a5e8ce992ae880218c05641a9"
dependencies = [ dependencies = [
"byteorder", "byteorder",
"dynasm", "dynasm",
"memmap2 0.5.5", "memmap2 0.5.7",
] ]
[[package]] [[package]]
name = "either" name = "either"
version = "1.7.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f107b87b6afc2a64fd13cac55fe06d6c8859f12d4b14cbcdd2c67d0976781be" checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]] [[package]]
name = "encode_unicode" name = "encode_unicode"
@ -1472,9 +1472,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]] [[package]]
name = "futures" name = "futures"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" checksum = "7f21eda599937fba36daeb58a22e8f5cee2d14c4a17b5b7739c7c8e5e3b8230c"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -1487,9 +1487,9 @@ dependencies = [
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" checksum = "30bdd20c28fadd505d0fd6712cdfcb0d4b5648baf45faef7f852afb2399bb050"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-sink", "futures-sink",
@ -1497,15 +1497,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" checksum = "4e5aa3de05362c3fb88de6531e6296e85cde7739cccad4b9dfeeb7f6ebce56bf"
[[package]] [[package]]
name = "futures-executor" name = "futures-executor"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" checksum = "9ff63c23854bee61b6e9cd331d523909f238fc7636290b96826e9cfa5faa00ab"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-task", "futures-task",
@ -1514,15 +1514,15 @@ dependencies = [
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" checksum = "bbf4d2a7a308fd4578637c0b17c7e1c7ba127b8f6ba00b29f717e9655d85eb68"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" checksum = "42cd15d1c7456c04dbdf7e88bcd69760d74f3a798d6444e16974b505b0e62f17"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1531,21 +1531,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" checksum = "21b20ba5a92e727ba30e72834706623d94ac93a725410b6a6b6fbc1b07f7ba56"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" checksum = "a6508c467c73851293f390476d4491cf4d227dbabcd4170f3bb6044959b294f1"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.21" version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" checksum = "44fb6cb1be61cc1d2e43b262516aafcf63b241cffdb1d3fa115f91d9c7b09c90"
dependencies = [ dependencies = [
"futures-channel", "futures-channel",
"futures-core", "futures-core",
@ -1638,9 +1638,9 @@ dependencies = [
[[package]] [[package]]
name = "glyph_brush" name = "glyph_brush"
version = "0.7.4" version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a69c65dd1f1fbb6209aa00f78636e436ad0a55b7d8e5de886d00720dcad9c6e2" checksum = "ac02497410cdb5062cc056a33f2e1e19ff69fbf26a4be9a02bf29d6e17ea105b"
dependencies = [ dependencies = [
"glyph_brush_draw_cache", "glyph_brush_draw_cache",
"glyph_brush_layout", "glyph_brush_layout",
@ -1871,14 +1871,13 @@ checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca"
[[package]] [[package]]
name = "insta" name = "insta"
version = "1.18.2" version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57a9aec10c9a062ef0454fd49ebaefa59239f836d1b30891d9cc2289978dd970" checksum = "f37dc501f12ec0c339b385787fa89ffda3d5d2caa62e558da731134c24d6e0c4"
dependencies = [ dependencies = [
"console", "console",
"linked-hash-map", "linked-hash-map",
"once_cell", "once_cell",
"serde",
"similar", "similar",
"yaml-rust", "yaml-rust",
] ]
@ -1962,9 +1961,9 @@ dependencies = [
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.59" version = "0.3.60"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@ -2026,9 +2025,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.126" version = "0.2.132"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" checksum = "8371e4e5341c3a96db127eb2465ac681ced4c433e01dd0e938adbef26ba93ba5"
[[package]] [[package]]
name = "libloading" name = "libloading"
@ -2186,9 +2185,9 @@ dependencies = [
[[package]] [[package]]
name = "memmap2" name = "memmap2"
version = "0.5.5" version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" checksum = "95af15f345b17af2efc8ead6080fb8bc376f8cec1b35277b935637595fe77498"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -2616,9 +2615,9 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.13.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1" checksum = "2f7254b99e31cad77da24b08ebf628882739a608578bb1bcdfc1f9c21260d7c0"
[[package]] [[package]]
name = "oorandom" name = "oorandom"
@ -2810,10 +2809,11 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.1.3" version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" checksum = "cb779fcf4bb850fbbb0edc96ff6cf34fd90c4b1a112ce042653280d9a7364048"
dependencies = [ dependencies = [
"thiserror",
"ucd-trie", "ucd-trie",
] ]
@ -2946,14 +2946,14 @@ checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]] [[package]]
name = "pretty_assertions" name = "pretty_assertions"
version = "1.2.1" version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" checksum = "a25e9bcb20aa780fd0bb16b72403a9064d6b3f22f026946029acb941a50af755"
dependencies = [ dependencies = [
"ansi_term",
"ctor", "ctor",
"diff", "diff",
"output_vt100", "output_vt100",
"yansi",
] ]
[[package]] [[package]]
@ -3447,7 +3447,7 @@ name = "roc_cli"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"clap 3.2.11", "clap 3.2.20",
"cli_utils", "cli_utils",
"const_format", "const_format",
"criterion", "criterion",
@ -3458,6 +3458,8 @@ dependencies = [
"libloading", "libloading",
"memexec", "memexec",
"mimalloc", "mimalloc",
"once_cell",
"parking_lot 0.12.1",
"pretty_assertions", "pretty_assertions",
"roc_build", "roc_build",
"roc_builtins", "roc_builtins",
@ -3512,6 +3514,7 @@ version = "0.0.1"
dependencies = [ dependencies = [
"bitvec 1.0.1", "bitvec 1.0.1",
"bumpalo", "bumpalo",
"fnv",
"hashbrown 0.12.3", "hashbrown 0.12.3",
"im", "im",
"im-rc", "im-rc",
@ -3591,7 +3594,7 @@ dependencies = [
name = "roc_docs_cli" name = "roc_docs_cli"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"clap 3.2.11", "clap 3.2.20",
"libc", "libc",
"roc_docs", "roc_docs",
] ]
@ -3685,6 +3688,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros", "roc_error_macros",
"roc_intern",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_parse", "roc_parse",
@ -3727,6 +3731,7 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_error_macros", "roc_error_macros",
"roc_intern",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
"roc_std", "roc_std",
@ -3738,7 +3743,7 @@ name = "roc_glue"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"clap 3.2.11", "clap 3.2.20",
"cli_utils", "cli_utils",
"dircpy", "dircpy",
"fnv", "fnv",
@ -3749,6 +3754,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_error_macros", "roc_error_macros",
"roc_intern",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3756,6 +3762,7 @@ dependencies = [
"roc_std", "roc_std",
"roc_target", "roc_target",
"roc_test_utils", "roc_test_utils",
"roc_tracing",
"roc_types", "roc_types",
"strum", "strum",
"strum_macros", "strum_macros",
@ -3775,6 +3782,14 @@ dependencies = [
name = "roc_ident" name = "roc_ident"
version = "0.0.1" version = "0.0.1"
[[package]]
name = "roc_intern"
version = "0.0.1"
dependencies = [
"parking_lot 0.12.1",
"roc_collections",
]
[[package]] [[package]]
name = "roc_late_solve" name = "roc_late_solve"
version = "0.0.1" version = "0.0.1"
@ -3796,10 +3811,11 @@ version = "0.0.1"
dependencies = [ dependencies = [
"bincode", "bincode",
"bumpalo", "bumpalo",
"clap 3.2.11", "clap 3.2.20",
"iced-x86", "iced-x86",
"indoc",
"mach_object", "mach_object",
"memmap2 0.5.5", "memmap2 0.5.7",
"object 0.29.0", "object 0.29.0",
"roc_build", "roc_build",
"roc_collections", "roc_collections",
@ -3843,6 +3859,7 @@ dependencies = [
"roc_derive", "roc_derive",
"roc_derive_key", "roc_derive_key",
"roc_error_macros", "roc_error_macros",
"roc_intern",
"roc_late_solve", "roc_late_solve",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3854,6 +3871,7 @@ dependencies = [
"roc_solve_problem", "roc_solve_problem",
"roc_target", "roc_target",
"roc_test_utils", "roc_test_utils",
"roc_tracing",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"ven_pretty", "ven_pretty",
@ -3877,6 +3895,7 @@ dependencies = [
name = "roc_mono" name = "roc_mono"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"bitvec 1.0.1",
"bumpalo", "bumpalo",
"hashbrown 0.12.3", "hashbrown 0.12.3",
"roc_builtins", "roc_builtins",
@ -3887,6 +3906,7 @@ dependencies = [
"roc_derive_key", "roc_derive_key",
"roc_error_macros", "roc_error_macros",
"roc_exhaustive", "roc_exhaustive",
"roc_intern",
"roc_late_solve", "roc_late_solve",
"roc_module", "roc_module",
"roc_problem", "roc_problem",
@ -3946,6 +3966,7 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_gen_llvm", "roc_gen_llvm",
"roc_intern",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3970,6 +3991,7 @@ dependencies = [
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_fmt", "roc_fmt",
"roc_intern",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3995,6 +4017,7 @@ dependencies = [
"roc_builtins", "roc_builtins",
"roc_collections", "roc_collections",
"roc_gen_llvm", "roc_gen_llvm",
"roc_intern",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -4048,6 +4071,7 @@ dependencies = [
"roc_collections", "roc_collections",
"roc_constrain", "roc_constrain",
"roc_derive", "roc_derive",
"roc_error_macros",
"roc_exhaustive", "roc_exhaustive",
"roc_fmt", "roc_fmt",
"roc_load", "roc_load",
@ -4347,9 +4371,9 @@ dependencies = [
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.139" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" checksum = "0f747710de3dcd43b88c9168773254e809d8ddbdf9653b84e2554ab219f17860"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
@ -4387,9 +4411,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.139" version = "1.0.144"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" checksum = "94ed3a816fb1d101812f83e789f888322c34e291f894f19590dc310963e87a00"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4398,9 +4422,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.82" version = "1.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7" checksum = "e55a28e3aaef9d5ce0506d0a14dbba8054ddc7e499ef522dd8b26859ec9d4a44"
dependencies = [ dependencies = [
"itoa 1.0.2", "itoa 1.0.2",
"ryu", "ryu",
@ -4606,7 +4630,7 @@ dependencies = [
"dlib", "dlib",
"lazy_static", "lazy_static",
"log", "log",
"memmap2 0.5.5", "memmap2 0.5.7",
"nix 0.24.1", "nix 0.24.1",
"pkg-config", "pkg-config",
"wayland-client", "wayland-client",
@ -4775,9 +4799,9 @@ dependencies = [
[[package]] [[package]]
name = "syn" name = "syn"
version = "1.0.98" version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -4861,7 +4885,6 @@ version = "0.0.1"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"criterion", "criterion",
"either",
"indoc", "indoc",
"inkwell 0.1.0", "inkwell 0.1.0",
"lazy_static", "lazy_static",
@ -5294,9 +5317,9 @@ version = "0.0.1"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.82" version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268"
dependencies = [ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@ -5304,9 +5327,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.82" version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
@ -5331,9 +5354,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.82" version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@ -5341,9 +5364,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.82" version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -5354,9 +5377,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.82" version = "0.2.83"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f"
[[package]] [[package]]
name = "wasm-encoder" name = "wasm-encoder"
@ -5506,7 +5529,7 @@ dependencies = [
"enumset", "enumset",
"lazy_static", "lazy_static",
"loupe", "loupe",
"memmap2 0.5.5", "memmap2 0.5.7",
"more-asserts", "more-asserts",
"rustc-demangle", "rustc-demangle",
"serde", "serde",
@ -6139,3 +6162,9 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [ dependencies = [
"linked-hash-map", "linked-hash-map",
] ]
[[package]]
name = "yansi"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec"

View file

@ -17,6 +17,7 @@ members = [
"crates/compiler/late_solve", "crates/compiler/late_solve",
"crates/compiler/fmt", "crates/compiler/fmt",
"crates/compiler/derive_key", "crates/compiler/derive_key",
"crates/compiler/intern",
"crates/compiler/mono", "crates/compiler/mono",
"crates/compiler/alias_analysis", "crates/compiler/alias_analysis",
"crates/compiler/test_mono", "crates/compiler/test_mono",

View file

@ -52,84 +52,6 @@ copy-dirs:
FROM +install-zig-llvm-valgrind FROM +install-zig-llvm-valgrind
COPY --dir crates examples Cargo.toml Cargo.lock version.txt www ./ 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. # compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.
prep-bench-folder: prep-bench-folder:
FROM +copy-dirs FROM +copy-dirs

104
FAQ.md
View file

@ -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." 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. 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 ### Currying and the `|>` operator
In Roc, this code produces `"Hello, World!"` In Roc, both of these expressions evaluate to `"Hello, World!"`
```elm ```elixir
"Hello, World" Str.concat "Hello, " "World!"
|> Str.concat "!"
``` ```
This is because Roc's `|>` operator uses the expression before the `|>` as the _first_ argument to the function ```elixir
after it. For functions where both arguments have the same type, but it's obvious which argument goes where (e.g. "Hello, "
`Str.concat "Hello, " "World!"`, `List.concat [1, 2] [3, 4]`), this works out well. Another example would |> Str.concat "World!"
be `|> Num.sub 1`, which subtracts 1 from whatever came before the `|>`. ```
For this reason, "pipeline-friendliness" in Roc means that the first argument to each function is typically 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.
the one that's most likely to be built up using a pipeline. For example, `List.map`:
```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 numbers
|> List.map Num.abs |> List.map Num.abs
``` ```
This argument ordering convention also often makes it possible to pass anonymous functions to higher-order 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.
functions without needing parentheses, like so:
```elm 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.
List.map numbers \num -> Num.abs (num - 1)
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 ...to this:
extra parentheses would be required.)
Neither of these benefits is compatible with the argument ordering currying encourages. Currying encourages ```elixir
`List.map` to take the `List` as its second argument instead of the first, so that you can partially apply it answer =
like `(List.map Num.abs)`; if Roc introduced currying but kept the order of `List.map` the same way it is today, List.map
then partially applying `List.map` (e.g. `(List.map numbers)`) would be much less useful than if the arguments (\num ->
were swapped - but that in turn would make it less useful with `|>` and would require parentheses when passing someFunction
it an anonymous function. "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 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.)
today) and with passing anonymous functions to higher-order functions, and the other works well with currying.
It's impossible to have both.
Of note, one possible design is to have currying while also having `|>` pass the _last_ argument instead of the first. 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.
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.
### Currying and learning curve ### 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 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 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 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 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 since I first learned about the technique, and I'm still slower and less accurate at reading code that uses

View file

@ -1,14 +1,16 @@
# Work in progress! # Work in progress!
Roc is not ready for a 0.1 release yet, but we do have: 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) - [**installation** guide](https://github.com/roc-lang/roc/tree/main/getting_started)
- [**tutorial**](https://github.com/roc-lang/roc/blob/main/TUTORIAL.md) - [**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) - [frequently asked questions](https://github.com/roc-lang/roc/blob/main/FAQ.md)
- [Zulip chat](https://roc.zulipchat.com) for help, questions and discussions - [Zulip chat](https://roc.zulipchat.com) for help, questions and discussions
If you'd like to get involved in contributing to the language, the Zulip chat is also the best place to get help with [good first issues](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22). If you'd like to 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). We are very grateful to our sponsors [NoRedInk](https://www.noredink.com/), [rwx](https://www.rwx.com), and [Tweede golf](https://tweedegolf.nl/en).

View file

@ -15,13 +15,13 @@ Learn how to install roc on your machine [here](https://github.com/roc-lang/roc/
Lets start by getting acquainted with Rocs Read Eval Print Loop, or REPL for Lets start by getting acquainted with Rocs Read Eval Print Loop, or REPL for
short. Run this in a terminal: short. Run this in a terminal:
``` ```sh
$ roc repl $ roc repl
``` ```
You should see this: You should see this:
``` ```sh
The rockin roc repl The rockin roc repl
``` ```
@ -131,13 +131,13 @@ main = Stdout.line "I'm a Roc application!"
Try running this with: Try running this with:
``` ```sh
$ roc Hello.roc $ roc Hello.roc
``` ```
You should see this: You should see this:
``` ```sh
I'm a Roc application! 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: Now if you run `roc Hello.roc`, you should see this:
``` ```sh
There are 5 animals. There are 5 animals.
``` ```
@ -165,7 +165,8 @@ There are 5 animals.
short - namely, `main`, `birds`, `iguanas`, and `total`. short - namely, `main`, `birds`, `iguanas`, and `total`.
A definition names an expression. 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 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)`. - 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 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 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 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. 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.) (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: Finally, destructuring can be used in defs too:
```coffee ```coffee
@ -410,8 +401,8 @@ fromOriginal = { original & birds: 4, iguanas: 3 }
The `fromScratch` and `fromOriginal` records are equal, although they're assembled in The `fromScratch` and `fromOriginal` records are equal, although they're assembled in
different ways. different ways.
* `fromScratch` was built using the same record syntax we've been using up to this point. - `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 `&`. - `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, 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. 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" 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 ```coffee
stoplightStr = stoplightStr =
@ -712,6 +703,7 @@ in the list returns `True`:
List.any [1, 2, 3] Num.isOdd List.any [1, 2, 3] Num.isOdd
# returns True because 1 and 3 are odd # returns True because 1 and 3 are odd
``` ```
```coffee ```coffee
List.any [1, 2, 3] Num.isNegative List.any [1, 2, 3] Num.isNegative
# returns False because none of these is negative # 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 List.all [1, 2, 3] Num.isOdd
# returns False because 2 is not odd # returns False because 2 is not odd
``` ```
```coffee ```coffee
List.all [1, 2, 3] Num.isPositive List.all [1, 2, 3] Num.isPositive
# returns True because all of these are positive # returns True because all of these are positive
@ -802,6 +795,7 @@ For example, what do each of these return?
```coffee ```coffee
List.get ["a", "b", "c"] 1 List.get ["a", "b", "c"] 1
``` ```
```coffee ```coffee
List.get ["a", "b", "c"] 100 List.get ["a", "b", "c"] 100
``` ```
@ -875,6 +869,7 @@ functions where argument order matters. For example, these two uses of `List.app
```coffee ```coffee
List.append ["a", "b", "c"] "d" List.append ["a", "b", "c"] "d"
``` ```
```coffee ```coffee
["a", "b", "c"] ["a", "b", "c"]
|> List.append "d" |> List.append "d"
@ -886,9 +881,11 @@ sugar for `Num.div a b`:
```coffee ```coffee
first / second first / second
``` ```
```coffee ```coffee
Num.div first second Num.div first second
``` ```
```coffee ```coffee
first first
|> Num.div second |> Num.div second
@ -1031,9 +1028,11 @@ What we want is something like one of these:
```coffee ```coffee
reverse : List elem -> List elem reverse : List elem -> List elem
``` ```
```coffee ```coffee
reverse : List value -> List value reverse : List value -> List value
``` ```
```coffee ```coffee
reverse : List a -> List a 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: 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! - 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. - 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! - 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: 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: Roc has three fractional types:
* `F32`, a 32-bit [floating-point number](https://en.wikipedia.org/wiki/IEEE_754) - `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) - `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) - `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, These are different from integers in that they can represent numbers with fractional components,
such as 1.5 and -0.123. 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`. you can pass number literals to functions expecting even more constrained types, like `I32` or `F64`.
### Typed Number Literals ### Typed Number Literals
When writing a number literal in Roc you can specify the numeric type as a suffix of the literal. 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. `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: The full list of possible suffixes includes:
`i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `nat`, `f32`, `f64`, `dec` `i8`, `u8`, `i16`, `u16`, `i32`, `u32`, `i64`, `u64`, `i128`, `u128`, `nat`, `f32`, `f64`, `dec`
### Hexadecimal Integer Literals ### Hexadecimal Integer Literals
Integer literals can be written in hexadecimal form by prefixing with `0x` followed by hexadecimal characters. Integer literals can be written in hexadecimal form by prefixing with `0x` followed by hexadecimal characters.
`0xFE` evaluates to decimal `254` `0xFE` evaluates to decimal `254`
The integer type can be specified as a suffix to the hexadecimal literal, 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. so `0xC8u8` evaluates to decimal `200` as an unsigned 8-bit integer.
### Binary Integer Literals ### Binary Integer Literals
Integer literals can be written in binary form by prefixing with `0b` followed by the 1's and 0's representing 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` each bit. `0b0000_1000` evaluates to decimal `8`
The integer type can be specified as a suffix to the binary literal, 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: 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`. - 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). - 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 ## 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 should look for that `line` function in the `Stdout` module of the
`examples/interactive/cli-platform/main.roc` package. `examples/interactive/cli-platform/main.roc` package.
# Building a Command-Line Interface (CLI)
## Tasks ## Tasks
Tasks are technically not part of the Roc language, but they're very common in 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: In the CLI platform, we have four operations we can do:
* Write a string to the console - Write a string to the console
* Read a string from user input - Read a string from user input
* Write a string to a file - Write a string to a file
* Read a string from a file - Read a string from a file
We'll use these four operations to learn about tasks. We'll use these four operations to learn about tasks.
@ -1524,22 +1524,24 @@ main =
``` ```
This way, it reads like a series of instructions: 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 `_ <-`) 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`. 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. 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`: 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. 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 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! 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` 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: 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 `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: (and both of them are strings). So any of these calls would work:
* `fullName { firstName: "Sam", lastName: "Sample" }` - `fullName { firstName: "Sam", lastName: "Sample" }`
* `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }` - `fullName { firstName: "Sam", lastName: "Sample", email: "blah@example.com" }`
* `fullName { age: 5, firstName: "Sam", things: 3, lastName: "Sample", role: Admin }` - `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 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, 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, `{ 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. 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: 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 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` - This function takes a record which has at least a `url` field, and possibly others
* It returns a record of exactly the same type as the one it was given - 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 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`. 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), 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! 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 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 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 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. 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. 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 > 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. > 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" }`, 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 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): 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] -> Bool` - `[Ok Str] -> Bool`
* `[Ok Str, Err Bool]* -> Bool` - `[Ok Str, Err Bool]* -> Bool`
* `[Ok Str, Err Bool] -> Bool` - `[Ok Str, Err Bool] -> Bool`
* `[Ok Str, Err Bool, Whatever]* -> Bool` - `[Ok Str, Err Bool, Whatever]* -> Bool`
* `[Ok Str, Err Bool, Whatever] -> Bool` - `[Ok Str, Err Bool, Whatever] -> Bool`
* `Result Str Bool -> Bool` - `Result Str Bool -> Bool`
* `[Err Bool, Whatever]* -> Bool` - `[Err Bool, Whatever]* -> Bool`
That last one works because a function accepting an open union can accept any unrecognized tag, including 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 `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: 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* 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 *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* 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 *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: 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. 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: 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 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 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: 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 "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 > 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` > `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!] [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. Here are various Roc expressions involving operators, and what they desugar to.

View file

@ -2,7 +2,7 @@
# assumes roc_releases.json is present # 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" ]] if [[ "$LATEST_RELEASE_URL" == "null" ]]
then then

View file

@ -22,11 +22,11 @@ roc_target = { path = "../compiler/roc_target" }
roc_error_macros = { path = "../error_macros" } roc_error_macros = { path = "../error_macros" }
roc_reporting = { path = "../reporting" } roc_reporting = { path = "../reporting" }
arrayvec = "0.7.2" arrayvec = "0.7.2"
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
page_size = "0.4.2" page_size = "0.4.2"
snafu = { version = "0.7.1", features = ["backtraces"] } snafu = { version = "0.7.1", features = ["backtraces"] }
ven_graph = { path = "../vendor/pathfinding" } ven_graph = { path = "../vendor/pathfinding" }
libc = "0.2.106" libc = "0.2.132"
[dev-dependencies] [dev-dependencies]
indoc = "1.0.7" indoc = "1.0.7"

View file

@ -24,7 +24,7 @@ editor = ["roc_editor"]
run-wasm32 = ["wasmer", "wasmer-wasi"] 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-arm = ["roc_build/target-arm", "roc_repl_cli/target-arm"]
target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"] target-aarch64 = ["roc_build/target-aarch64", "roc_repl_cli/target-aarch64"]
target-x86 = ["roc_build/target-x86", "roc_repl_cli/target-x86"] 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_linker = { path = "../linker" }
roc_repl_cli = { path = "../repl_cli", optional = true } roc_repl_cli = { path = "../repl_cli", optional = true }
roc_tracing = { path = "../tracing" } 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"] } 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 } mimalloc = { version = "0.1.26", default-features = false }
libc = "0.2.106" libc = "0.2.132"
errno = "0.2.8" errno = "0.2.8"
ven_pretty = { path = "../vendor/pretty" } ven_pretty = { path = "../vendor/pretty" }
@ -93,7 +93,7 @@ wasmer = { version = "2.2.1", optional = true, default-features = false, feature
[dev-dependencies] [dev-dependencies]
wasmer-wasi = "2.2.1" wasmer-wasi = "2.2.1"
pretty_assertions = "1.0.0" pretty_assertions = "1.3.0"
roc_test_utils = { path = "../test_utils" } roc_test_utils = { path = "../test_utils" }
indoc = "1.0.7" indoc = "1.0.7"
serial_test = "0.9.0" serial_test = "0.9.0"
@ -101,6 +101,8 @@ criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" } cli_utils = { path = "../cli_utils" }
strum = "0.24.0" strum = "0.24.0"
strum_macros = "0.24" strum_macros = "0.24"
once_cell = "1.14.0"
parking_lot = "0.12"
# Wasmer singlepass compiler only works on x86_64. # Wasmer singlepass compiler only works on x86_64.
[target.'cfg(target_arch = "x86_64")'.dev-dependencies] [target.'cfg(target_arch = "x86_64")'.dev-dependencies]

View file

@ -2,16 +2,19 @@
# Running the benchmarks # Running the benchmarks
Install cargo criterion: Install cargo criterion:
```
```sh
cargo install cargo-criterion cargo install cargo-criterion
``` ```
To prevent stack overflow on the `CFold` benchmark: To prevent stack overflow on the `CFold` benchmark:
```
```sh
ulimit -s unlimited ulimit -s unlimited
``` ```
In the `cli` folder execute: In the `cli` folder execute:
```
```sh
cargo criterion cargo criterion
``` ```

View file

@ -65,7 +65,7 @@ pub fn build_file<'a>(
emit_timings: bool, emit_timings: bool,
link_type: LinkType, link_type: LinkType,
linking_strategy: LinkingStrategy, linking_strategy: LinkingStrategy,
precompiled: bool, prebuilt: bool,
threading: Threading, threading: Threading,
wasm_dev_stack_bytes: Option<u32>, wasm_dev_stack_bytes: Option<u32>,
order: BuildOrdering, order: BuildOrdering,
@ -151,7 +151,7 @@ pub fn build_file<'a>(
// TODO this should probably be moved before load_and_monomorphize. // TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols. // 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 // 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 let exposed_values = loaded
.exposed_to_host .exposed_to_host
@ -182,7 +182,7 @@ pub fn build_file<'a>(
let rebuild_thread = spawn_rebuild_thread( let rebuild_thread = spawn_rebuild_thread(
opt_level, opt_level,
linking_strategy, linking_strategy,
precompiled, prebuilt,
host_input_path.clone(), host_input_path.clone(),
preprocessed_host_path.clone(), preprocessed_host_path.clone(),
binary_path.clone(), binary_path.clone(),
@ -191,8 +191,6 @@ pub fn build_file<'a>(
exposed_closure_types, 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() let app_o_file = Builder::new()
.prefix("roc_app") .prefix("roc_app")
.suffix(&format!(".{}", app_extension)) .suffix(&format!(".{}", app_extension))
@ -261,9 +259,9 @@ pub fn build_file<'a>(
let rebuild_timing = if linking_strategy == LinkingStrategy::Additive { let rebuild_timing = if linking_strategy == LinkingStrategy::Additive {
let rebuild_duration = rebuild_thread.join().unwrap(); let rebuild_duration = rebuild_thread.join().unwrap();
if emit_timings && !precompiled { if emit_timings && !prebuilt {
println!( println!(
"Finished rebuilding and preprocessing the host in {} ms\n", "Finished rebuilding the platform in {} ms\n",
rebuild_duration rebuild_duration
); );
} }
@ -322,15 +320,15 @@ pub fn build_file<'a>(
if let HostRebuildTiming::ConcurrentWithApp(thread) = rebuild_timing { if let HostRebuildTiming::ConcurrentWithApp(thread) = rebuild_timing {
let rebuild_duration = thread.join().unwrap(); let rebuild_duration = thread.join().unwrap();
if emit_timings && !precompiled { if emit_timings && !prebuilt {
println!( println!(
"Finished rebuilding and preprocessing the host in {} ms\n", "Finished rebuilding the platform in {} ms\n",
rebuild_duration 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 link_start = Instant::now();
let problems = match (linking_strategy, link_type) { let problems = match (linking_strategy, link_type) {
(LinkingStrategy::Surgical, _) => { (LinkingStrategy::Surgical, _) => {
@ -397,7 +395,7 @@ pub fn build_file<'a>(
fn spawn_rebuild_thread( fn spawn_rebuild_thread(
opt_level: OptLevel, opt_level: OptLevel,
linking_strategy: LinkingStrategy, linking_strategy: LinkingStrategy,
precompiled: bool, prebuilt: bool,
host_input_path: PathBuf, host_input_path: PathBuf,
preprocessed_host_path: PathBuf, preprocessed_host_path: PathBuf,
binary_path: PathBuf, binary_path: PathBuf,
@ -407,13 +405,16 @@ fn spawn_rebuild_thread(
) -> std::thread::JoinHandle<u128> { ) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone(); let thread_local_target = target.clone();
std::thread::spawn(move || { std::thread::spawn(move || {
if !precompiled { if !prebuilt {
println!("🔨 Rebuilding host..."); // 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(); let rebuild_host_start = Instant::now();
if !precompiled { if !prebuilt {
match linking_strategy { match linking_strategy {
LinkingStrategy::Additive => { LinkingStrategy::Additive => {
let host_dest = rebuild_host( let host_dest = rebuild_host(

View file

@ -55,8 +55,7 @@ fn flatten_directories(files: std::vec::Vec<PathBuf>) -> std::vec::Vec<PathBuf>
} }
fn is_roc_file(path: &Path) -> bool { fn is_roc_file(path: &Path) -> bool {
let ext = path.extension().and_then(OsStr::to_str); matches!(path.extension().and_then(OsStr::to_str), Some("roc"))
return matches!(ext, Some("roc"));
} }
pub fn format(files: std::vec::Vec<PathBuf>, mode: FormatMode) -> Result<(), String> { 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(); let mut before_file = file.clone();
before_file.set_extension("roc-format-failed-ast-before"); 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(); let mut after_file = file.clone();
after_file.set_extension("roc-format-failed-ast-after"); 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!( internal_error!(
"Formatting bug; formatting didn't reparse as the same tree\n\n\ "Formatting bug; formatting didn't reparse as the same tree\n\n\

View file

@ -13,6 +13,7 @@ use roc_mono::ir::OptLevel;
use std::env; use std::env;
use std::ffi::{CString, OsStr}; use std::ffi::{CString, OsStr};
use std::io; use std::io;
use std::mem::ManuallyDrop;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process; use std::process;
@ -53,7 +54,7 @@ pub const FLAG_NO_LINK: &str = "no-link";
pub const FLAG_TARGET: &str = "target"; pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_LINKER: &str = "linker"; 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_CHECK: &str = "check";
pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb"; pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb";
pub const ROC_FILE: &str = "ROC_FILE"; 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> { pub fn build_app<'a>() -> Command<'a> {
let flag_optimize = Arg::new(FLAG_OPTIMIZE) let flag_optimize = Arg::new(FLAG_OPTIMIZE)
.long(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); .required(false);
let flag_max_threads = Arg::new(FLAG_MAX_THREADS) let flag_max_threads = Arg::new(FLAG_MAX_THREADS)
.long(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) .takes_value(true)
.validator(|s| s.parse::<usize>()) .validator(|s| s.parse::<usize>())
.required(false); .required(false);
let flag_opt_size = Arg::new(FLAG_OPT_SIZE) let flag_opt_size = Arg::new(FLAG_OPT_SIZE)
.long(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); .required(false);
let flag_dev = Arg::new(FLAG_DEV) let flag_dev = Arg::new(FLAG_DEV)
.long(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); .required(false);
let flag_debug = Arg::new(FLAG_DEBUG) let flag_debug = Arg::new(FLAG_DEBUG)
.long(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); .required(false);
let flag_time = Arg::new(FLAG_TIME) let flag_time = Arg::new(FLAG_TIME)
.long(FLAG_TIME) .long(FLAG_TIME)
.help("Prints detailed compilation time information.") .help("Print detailed compilation time information")
.required(false); .required(false);
let flag_linker = Arg::new(FLAG_LINKER) let flag_linker = Arg::new(FLAG_LINKER)
.long(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"]) .possible_values(["surgical", "legacy"])
.required(false); .required(false);
let flag_precompiled = Arg::new(FLAG_PRECOMPILED) let flag_prebuilt = Arg::new(FLAG_PREBUILT)
.long(FLAG_PRECOMPILED) .long(FLAG_PREBUILT)
.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`)") .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"]) .possible_values(["true", "false"])
.required(false); .required(false);
let flag_wasm_stack_size_kb = Arg::new(FLAG_WASM_STACK_SIZE_KB) let flag_wasm_stack_size_kb = Arg::new(FLAG_WASM_STACK_SIZE_KB)
.long(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) .takes_value(true)
.validator(|s| s.parse::<u32>()) .validator(|s| s.parse::<u32>())
.required(false); .required(false);
@ -123,7 +124,7 @@ pub fn build_app<'a>() -> Command<'a> {
.default_value(DEFAULT_ROC_FILENAME); .default_value(DEFAULT_ROC_FILENAME);
let args_for_app = Arg::new(ARGS_FOR_APP) 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) .allow_invalid_utf8(true)
.multiple_values(true) .multiple_values(true)
.takes_value(true) .takes_value(true)
@ -132,7 +133,7 @@ pub fn build_app<'a>() -> Command<'a> {
let app = Command::new("roc") let app = Command::new("roc")
.version(concatcp!(VERSION, "\n")) .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) .subcommand(Command::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it") .about("Build a binary from the given .roc file, but don't run it")
.arg(flag_optimize.clone()) .arg(flag_optimize.clone())
@ -142,7 +143,7 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_debug.clone()) .arg(flag_debug.clone())
.arg(flag_time.clone()) .arg(flag_time.clone())
.arg(flag_linker.clone()) .arg(flag_linker.clone())
.arg(flag_precompiled.clone()) .arg(flag_prebuilt.clone())
.arg(flag_wasm_stack_size_kb.clone()) .arg(flag_wasm_stack_size_kb.clone())
.arg( .arg(
Arg::new(FLAG_TARGET) Arg::new(FLAG_TARGET)
@ -155,13 +156,13 @@ pub fn build_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(FLAG_LIB) Arg::new(FLAG_LIB)
.long(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), .required(false),
) )
.arg( .arg(
Arg::new(FLAG_NO_LINK) Arg::new(FLAG_NO_LINK)
.long(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), .required(false),
) )
.arg( .arg(
@ -173,7 +174,7 @@ pub fn build_app<'a>() -> Command<'a> {
) )
) )
.subcommand(Command::new(CMD_TEST) .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_optimize.clone())
.arg(flag_max_threads.clone()) .arg(flag_max_threads.clone())
.arg(flag_opt_size.clone()) .arg(flag_opt_size.clone())
@ -181,7 +182,7 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_debug.clone()) .arg(flag_debug.clone())
.arg(flag_time.clone()) .arg(flag_time.clone())
.arg(flag_linker.clone()) .arg(flag_linker.clone())
.arg(flag_precompiled.clone()) .arg(flag_prebuilt.clone())
.arg( .arg(
Arg::new(ROC_FILE) Arg::new(ROC_FILE)
.help("The .roc file for the main module") .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_debug.clone())
.arg(flag_time.clone()) .arg(flag_time.clone())
.arg(flag_linker.clone()) .arg(flag_linker.clone())
.arg(flag_precompiled.clone()) .arg(flag_prebuilt.clone())
.arg(roc_file_to_run.clone()) .arg(roc_file_to_run.clone())
.arg(args_for_app.clone()) .arg(args_for_app.clone())
) )
.subcommand(Command::new(CMD_DEV) .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_optimize.clone())
.arg(flag_max_threads.clone()) .arg(flag_max_threads.clone())
.arg(flag_opt_size.clone()) .arg(flag_opt_size.clone())
@ -216,7 +217,7 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_debug.clone()) .arg(flag_debug.clone())
.arg(flag_time.clone()) .arg(flag_time.clone())
.arg(flag_linker.clone()) .arg(flag_linker.clone())
.arg(flag_precompiled.clone()) .arg(flag_prebuilt.clone())
.arg(roc_file_to_run.clone()) .arg(roc_file_to_run.clone())
.arg(args_for_app.clone()) .arg(args_for_app.clone())
) )
@ -231,14 +232,14 @@ pub fn build_app<'a>() -> Command<'a> {
.arg( .arg(
Arg::new(FLAG_CHECK) Arg::new(FLAG_CHECK)
.long(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), .required(false),
) )
) )
.subcommand(Command::new(CMD_VERSION) .subcommand(Command::new(CMD_VERSION)
.about(concatcp!("Print the Roc compilers version, which is currently ", VERSION))) .about(concatcp!("Print the Roc compilers version, which is currently ", VERSION)))
.subcommand(Command::new(CMD_CHECK) .subcommand(Command::new(CMD_CHECK)
.about("Check the code for problems, but doesnt build or run it") .about("Check the code for problems, but dont build or run it")
.arg(flag_time.clone()) .arg(flag_time.clone())
.arg(flag_max_threads.clone()) .arg(flag_max_threads.clone())
.arg( .arg(
@ -260,7 +261,7 @@ pub fn build_app<'a>() -> Command<'a> {
) )
) )
.subcommand(Command::new(CMD_GLUE) .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(
Arg::new(ROC_FILE) Arg::new(ROC_FILE)
.help("The .roc file for the platform module") .help("The .roc file for the platform module")
@ -269,7 +270,7 @@ pub fn build_app<'a>() -> Command<'a> {
) )
.arg( .arg(
Arg::new(GLUE_FILE) 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) .allow_invalid_utf8(true)
.required(true) .required(true)
) )
@ -282,7 +283,7 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_debug) .arg(flag_debug)
.arg(flag_time) .arg(flag_time)
.arg(flag_linker) .arg(flag_linker)
.arg(flag_precompiled) .arg(flag_prebuilt)
.arg(roc_file_to_run.required(false)) .arg(roc_file_to_run.required(false))
.arg(args_for_app); .arg(args_for_app);
@ -294,7 +295,7 @@ pub fn build_app<'a>() -> Command<'a> {
Arg::new(DIRECTORY_OR_FILES) Arg::new(DIRECTORY_OR_FILES)
.multiple_values(true) .multiple_values(true)
.required(false) .required(false)
.help("(optional) The directory or files to open on launch."), .help("(optional) The directory or files to open on launch"),
), ),
) )
} else { } else {
@ -396,7 +397,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
let interns = loaded.interns.clone(); 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, arena,
target.clone(), target.clone(),
loaded, loaded,
@ -415,6 +416,7 @@ pub fn test(matches: &ArgMatches, triple: Triple) -> io::Result<i32> {
roc_reporting::report::RenderTarget::ColorTerminal, roc_reporting::report::RenderTarget::ColorTerminal,
arena, arena,
interns, interns,
&layout_interner.into_global(),
&lib, &lib,
&mut expectations, &mut expectations,
expects, expects,
@ -497,11 +499,11 @@ pub fn build(
LinkingStrategy::Surgical LinkingStrategy::Surgical
}; };
let precompiled = if matches.is_present(FLAG_PRECOMPILED) { let prebuilt = if matches.is_present(FLAG_PREBUILT) {
matches.value_of(FLAG_PRECOMPILED) == Some("true") matches.value_of(FLAG_PREBUILT) == Some("true")
} else { } else {
// When compiling for a different target, default to assuming a precompiled 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 host // 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. // We make an exception for Wasm, because cross-compiling is the norm in that case.
triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32) triple != Triple::host() && !matches!(triple.architecture, Architecture::Wasm32)
}; };
@ -545,7 +547,7 @@ pub fn build(
emit_timings, emit_timings,
link_type, link_type,
linking_strategy, linking_strategy,
precompiled, prebuilt,
threading, threading,
wasm_dev_stack_bytes, wasm_dev_stack_bytes,
build_ordering, build_ordering,
@ -634,14 +636,14 @@ pub fn build(
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default(); 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( let x = roc_run(
arena, arena,
opt_level, opt_level,
triple, triple,
args, args,
&mut bytes, &bytes,
expectations, expectations,
interns, interns,
); );
@ -669,19 +671,10 @@ pub fn build(
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default(); 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( roc_run(arena, opt_level, triple, args, bytes, expectations, interns)
arena,
opt_level,
triple,
args,
&mut bytes,
expectations,
interns,
);
std::mem::forget(bytes);
x
} }
} }
} }
@ -746,7 +739,7 @@ fn roc_run<'a, I: IntoIterator<Item = &'a OsStr>>(
opt_level: OptLevel, opt_level: OptLevel,
triple: Triple, triple: Triple,
args: I, args: I,
binary_bytes: &mut [u8], binary_bytes: &[u8],
expectations: VecMap<ModuleId, Expectations>, expectations: VecMap<ModuleId, Expectations>,
interns: Interns, interns: Interns,
) -> io::Result<i32> { ) -> io::Result<i32> {
@ -839,7 +832,7 @@ fn roc_run_native<I: IntoIterator<Item = S>, S: AsRef<OsStr>>(
arena: Bump, arena: Bump,
opt_level: OptLevel, opt_level: OptLevel,
args: I, args: I,
binary_bytes: &mut [u8], binary_bytes: &[u8],
expectations: VecMap<ModuleId, Expectations>, expectations: VecMap<ModuleId, Expectations>,
interns: Interns, interns: Interns,
) -> std::io::Result<i32> { ) -> std::io::Result<i32> {
@ -952,7 +945,7 @@ unsafe fn roc_run_native_debug(
} }
#[cfg(target_os = "linux")] #[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. // on linux, we use the `memfd_create` function to create an in-memory anonymous file.
let flags = 0; let flags = 0;
let anonymous_file_name = "roc_file_descriptor\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")))] #[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::fs::OpenOptions;
use std::io::Write; use std::io::Write;
use std::os::unix::fs::OpenOptionsExt; 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"))] #[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::fs::OpenOptions;
use std::io::Write; 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! arena: Bump, // This should be passed an owned value, not a reference, so we can usefully mem::forget it!
opt_level: OptLevel, opt_level: OptLevel,
_args: I, _args: I,
binary_bytes: &mut [u8], binary_bytes: &[u8],
_expectations: VecMap<ModuleId, Expectations>, _expectations: VecMap<ModuleId, Expectations>,
_interns: Interns, _interns: Interns,
) -> io::Result<i32> { ) -> io::Result<i32> {

View file

@ -16,23 +16,37 @@ mod cli_run {
}; };
use const_format::concatcp; use const_format::concatcp;
use indoc::indoc; 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_cli::{CMD_BUILD, CMD_CHECK, CMD_FORMAT, CMD_RUN};
use roc_test_utils::assert_multiline_str_eq; use roc_test_utils::assert_multiline_str_eq;
use serial_test::serial; use serial_test::serial;
use std::iter; use std::iter;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::sync::Once;
use strum::IntoEnumIterator; use strum::IntoEnumIterator;
use strum_macros::EnumIter; use strum_macros::EnumIter;
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE); const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER); const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER);
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK); 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)] #[allow(dead_code)]
const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET); const TARGET_FLAG: &str = concatcp!("--", roc_cli::FLAG_TARGET);
use std::sync::Once;
static BENCHMARKS_BUILD_PLATFORM: Once = Once::new(); 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)] #[derive(Debug, EnumIter)]
enum CliMode { enum CliMode {
@ -61,12 +75,18 @@ mod cli_run {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
const ALLOW_VALGRIND: bool = false; const ALLOW_VALGRIND: bool = false;
#[derive(Debug, PartialEq, Eq)]
enum Arg<'a> {
ExamplePath(&'a str),
PlainText(&'a str),
}
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq)]
struct Example<'a> { struct Example<'a> {
filename: &'a str, filename: &'a str,
executable_filename: &'a str, executable_filename: &'a str,
stdin: &'a [&'a str], stdin: &'a [&'a str],
input_file: Option<&'a str>, arguments: &'a [Arg<'a>],
expected_ending: &'a str, expected_ending: &'a str,
use_valgrind: bool, use_valgrind: bool,
} }
@ -93,33 +113,22 @@ mod cli_run {
file: &'a Path, file: &'a Path,
args: I, args: I,
stdin: &[&str], stdin: &[&str],
opt_input_file: Option<PathBuf>, app_args: &[String],
) -> Out { ) -> Out {
let compile_out = if let Some(input_file) = opt_input_file { let compile_out = run_roc(
run_roc(
// converting these all to String avoids lifetime issues // converting these all to String avoids lifetime issues
args.into_iter().map(|arg| arg.to_string()).chain([ args.into_iter()
file.to_str().unwrap().to_string(), .map(|arg| arg.to_string())
"--".to_string(), .chain([file.to_str().unwrap().to_string(), "--".to_string()])
input_file.to_str().unwrap().to_string(), .chain(app_args.iter().cloned()),
]),
stdin, 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); assert!(compile_out.status.success(), "bad status {:?}", compile_out);
@ -132,7 +141,7 @@ mod cli_run {
stdin: &[&str], stdin: &[&str],
executable_filename: &str, executable_filename: &str,
flags: &[&str], flags: &[&str],
opt_input_file: Option<PathBuf>, app_args: &[String],
expected_ending: &str, expected_ending: &str,
use_valgrind: bool, use_valgrind: bool,
) { ) {
@ -154,24 +163,17 @@ mod cli_run {
let out = match cli_mode { let out = match cli_mode {
CliMode::RocBuild => { 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 { if use_valgrind && ALLOW_VALGRIND {
let (valgrind_out, raw_xml) = if let Some(ref input_file) = opt_input_file { let mut valgrind_args = vec![file
run_with_valgrind( .with_file_name(executable_filename)
stdin.iter().copied(), .to_str()
&[ .unwrap()
file.with_file_name(executable_filename).to_str().unwrap(), .to_string()];
input_file.clone().to_str().unwrap(), valgrind_args.extend(app_args.iter().cloned());
], let (valgrind_out, raw_xml) =
) run_with_valgrind(stdin.iter().copied(), &valgrind_args);
} else {
run_with_valgrind(
stdin.iter().copied(),
&[file.with_file_name(executable_filename).to_str().unwrap()],
)
};
if valgrind_out.status.success() { if valgrind_out.status.success() {
let memory_errors = extract_valgrind_errors(&raw_xml).unwrap_or_else(|err| { 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); 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 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 { } else {
run_cmd( run_cmd(
file.with_file_name(executable_filename).to_str().unwrap(), file.with_file_name(executable_filename).to_str().unwrap(),
stdin.iter().copied(), 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( CliMode::RocRun => run_roc_on(
file, file,
iter::once(CMD_RUN).chain(flags.clone()), iter::once(CMD_RUN).chain(flags.clone()),
stdin, stdin,
opt_input_file.clone(), app_args,
), ),
}; };
@ -247,10 +243,10 @@ mod cli_run {
stdin: &[&str], stdin: &[&str],
executable_filename: &str, executable_filename: &str,
flags: &[&str], flags: &[&str],
input_file: Option<PathBuf>, args: &[&Arg],
expected_ending: &str, 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(); let mut flags = flags.to_vec();
flags.push(concatcp!(TARGET_FLAG, "=wasm32")); flags.push(concatcp!(TARGET_FLAG, "=wasm32"));
@ -289,20 +285,42 @@ mod cli_run {
/// add a test for it here! /// add a test for it here!
macro_rules! examples { macro_rules! examples {
($($test_name:ident:$name:expr => $example:expr,)+) => { ($($test_name:ident:$name:expr => $example:expr,)+) => {
static EXAMPLE_NAMES: &[&str] = &[$($name,)+];
$( $(
#[test] #[test]
#[allow(non_snake_case)] #[allow(non_snake_case)]
fn $test_name() { fn $test_name() {
POPULATED_EXAMPLE_LOCKS.call_once( || {
populate_example_locks(EXAMPLE_NAMES.iter().map(|name| examples_dir(name)))
});
let dir_name = $name; let dir_name = $name;
let example = $example; let example = $example;
let example_dir = examples_dir(dir_name);
let file_name = example_file(dir_name, example.filename); 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 { match example.executable_filename {
"form" | "hello-gui" | "breakout" | "ruby" => { "form" | "hello-gui" | "breakout" | "ruby" => {
// Since these require things the build system often doesn't have // 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) // (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. // 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; return;
} }
"swiftui" | "rocLovesSwift" => { "swiftui" | "rocLovesSwift" => {
@ -319,20 +337,35 @@ mod cli_run {
eprintln!("WARNING: skipping testing example {} because it only works in a browser!", example.filename); eprintln!("WARNING: skipping testing example {} because it only works in a browser!", example.filename);
return; 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 with and without optimizations
check_output_with_stdin( check_output_with_stdin(
&file_name, &file_name,
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&[], &custom_flags,
example.input_file.and_then(|file| Some(example_file(dir_name, file))), &app_args,
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
); );
custom_flags.push(OPTIMIZE_FLAG);
// This is mostly because the false interpreter is still very slow - // This is mostly because the false interpreter is still very slow -
// 25s for the cli tests is just not acceptable during development! // 25s for the cli tests is just not acceptable during development!
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
@ -340,8 +373,8 @@ mod cli_run {
&file_name, &file_name,
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&[OPTIMIZE_FLAG], &custom_flags,
example.input_file.and_then(|file| Some(example_file(dir_name, file))), &app_args,
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
); );
@ -354,7 +387,7 @@ mod cli_run {
example.stdin, example.stdin,
example.executable_filename, example.executable_filename,
&[LINKER_FLAG, "legacy"], &[LINKER_FLAG, "legacy"],
example.input_file.and_then(|file| Some(example_file(dir_name, file))), &app_args,
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
); );
@ -391,7 +424,7 @@ mod cli_run {
filename: "main.roc", filename: "main.roc",
executable_filename: "helloWorld", executable_filename: "helloWorld",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -399,50 +432,50 @@ mod cli_run {
filename: "main.roc", filename: "main.roc",
executable_filename: "rocLovesPlatforms", executable_filename: "rocLovesPlatforms",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"Which platform am I running on now?\n", expected_ending:"Which platform am I running on now?\n",
use_valgrind: true, use_valgrind: true,
}, },
// We exclude the C platforming switching example // We exclude the C platforming switching example
// because the main platform switching example runs the c platform. // because the main platform switching example runs the c platform.
// If we don't a race condition leads to test flakiness. // If we don't a race condition leads to test flakiness.
// platformSwitchingC:"platform-switching/c-platform" => Example { // platformSwitchingC:"platform-switching" => Example {
// filename: "rocLovesC.roc", // filename: "rocLovesC.roc",
// executable_filename: "rocLovesC", // executable_filename: "rocLovesC",
// stdin: &[], // stdin: &[],
// input_file: None, // arguments: &[],
// expected_ending:"Roc <3 C!\n", // expected_ending:"Roc <3 C!\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
platformSwitchingRust:"platform-switching/rust-platform" => Example { platformSwitchingRust:"platform-switching" => Example {
filename: "rocLovesRust.roc", filename: "rocLovesRust.roc",
executable_filename: "rocLovesRust", executable_filename: "rocLovesRust",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"Roc <3 Rust!\n", expected_ending:"Roc <3 Rust!\n",
use_valgrind: true, use_valgrind: true,
}, },
platformSwitchingSwift:"platform-switching/swift-platform" => Example { platformSwitchingSwift:"platform-switching" => Example {
filename: "rocLovesSwift.roc", filename: "rocLovesSwift.roc",
executable_filename: "rocLovesSwift", executable_filename: "rocLovesSwift",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"Roc <3 Swift!\n", expected_ending:"Roc <3 Swift!\n",
use_valgrind: true, use_valgrind: true,
}, },
platformSwitchingWebAssembly:"platform-switching/web-assembly-platform" => Example { platformSwitchingWebAssembly:"platform-switching" => Example {
filename: "rocLovesWebAssembly.roc", filename: "rocLovesWebAssembly.roc",
executable_filename: "rocLovesWebAssembly", executable_filename: "rocLovesWebAssembly",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"Roc <3 Web Assembly!\n", expected_ending:"Roc <3 Web Assembly!\n",
use_valgrind: true, use_valgrind: true,
}, },
platformSwitchingZig:"platform-switching/zig-platform" => Example { platformSwitchingZig:"platform-switching" => Example {
filename: "rocLovesZig.roc", filename: "rocLovesZig.roc",
executable_filename: "rocLovesZig", executable_filename: "rocLovesZig",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"Roc <3 Zig!\n", expected_ending:"Roc <3 Zig!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -450,7 +483,7 @@ mod cli_run {
filename: "main.roc", filename: "main.roc",
executable_filename: "libhello", executable_filename: "libhello",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"", expected_ending:"",
use_valgrind: true, use_valgrind: true,
}, },
@ -458,7 +491,7 @@ mod cli_run {
filename: "fibonacci.roc", filename: "fibonacci.roc",
executable_filename: "fibonacci", executable_filename: "fibonacci",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending:"55\n", expected_ending:"55\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -466,7 +499,7 @@ mod cli_run {
filename: "Hello.roc", filename: "Hello.roc",
executable_filename: "hello-gui", executable_filename: "hello-gui",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "", expected_ending: "",
use_valgrind: false, use_valgrind: false,
}, },
@ -474,7 +507,7 @@ mod cli_run {
filename: "breakout.roc", filename: "breakout.roc",
executable_filename: "breakout", executable_filename: "breakout",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "", expected_ending: "",
use_valgrind: false, use_valgrind: false,
}, },
@ -482,7 +515,7 @@ mod cli_run {
filename: "quicksort.roc", filename: "quicksort.roc",
executable_filename: "quicksort", executable_filename: "quicksort",
stdin: &[], 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", 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, use_valgrind: true,
}, },
@ -490,31 +523,39 @@ mod cli_run {
// filename: "Quicksort.roc", // filename: "Quicksort.roc",
// executable_filename: "quicksort", // executable_filename: "quicksort",
// stdin: &[], // 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", // 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, // 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 { effects:"interactive" => Example {
filename: "effects.roc", filename: "effects.roc",
executable_filename: "effects", executable_filename: "effects",
stdin: &["hi there!"], stdin: &["hi there!"],
input_file: None, arguments: &[],
expected_ending: "hi there!\nIt is known\n", expected_ending: "hi there!\nIt is known\n",
use_valgrind: true, use_valgrind: true,
}, },
// tea:"tea" => Example { // tui_tea:"tea" => Example {
// filename: "Main.roc", // filename: "Main.roc",
// executable_filename: "tea-example", // executable_filename: "tea-example",
// stdin: &[], // stdin: &[],
// input_file: None, // arguments: &[],
// expected_ending: "", // expected_ending: "",
// use_valgrind: true, // use_valgrind: true,
// }, // },
cli:"interactive" => Example { cli_form:"interactive" => Example {
filename: "form.roc", filename: "form.roc",
executable_filename: "form", executable_filename: "form",
stdin: &["Giovanni\n", "Giorgio\n"], stdin: &["Giovanni\n", "Giorgio\n"],
input_file: None, arguments: &[],
expected_ending: "Hi, Giovanni Giorgio! 👋\n", expected_ending: "Hi, Giovanni Giorgio! 👋\n",
use_valgrind: false, use_valgrind: false,
}, },
@ -522,7 +563,7 @@ mod cli_run {
filename: "tui.roc", filename: "tui.roc",
executable_filename: "tui", executable_filename: "tui",
stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks stdin: &["foo\n"], // NOTE: adding more lines leads to memory leaks
input_file: None, arguments: &[],
expected_ending: "Hello Worldfoo!\n", expected_ending: "Hello Worldfoo!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -530,7 +571,7 @@ mod cli_run {
// filename: "Main.roc", // filename: "Main.roc",
// executable_filename: "custom-malloc-example", // executable_filename: "custom-malloc-example",
// stdin: &[], // stdin: &[],
// input_file: None, // arguments: &[],
// expected_ending: "ms!\nThe list was small!\n", // expected_ending: "ms!\nThe list was small!\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
@ -538,7 +579,7 @@ mod cli_run {
// filename: "Main.roc", // filename: "Main.roc",
// executable_filename: "task-example", // executable_filename: "task-example",
// stdin: &[], // stdin: &[],
// input_file: None, // arguments: &[],
// expected_ending: "successfully wrote to file\n", // expected_ending: "successfully wrote to file\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
@ -547,7 +588,7 @@ mod cli_run {
filename: "False.roc", filename: "False.roc",
executable_filename: "false", executable_filename: "false",
stdin: &[], stdin: &[],
input_file: Some("examples/hello.false"), arguments: &[Arg::ExamplePath("examples/hello.false")],
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: false, use_valgrind: false,
} }
@ -560,6 +601,16 @@ mod cli_run {
expected_ending: "", expected_ending: "",
use_valgrind: false, 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 { macro_rules! benchmarks {
@ -584,6 +635,18 @@ mod cli_run {
let mut ran_without_optimizations = false; 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( || { BENCHMARKS_BUILD_PLATFORM.call_once( || {
// Check with and without optimizations // Check with and without optimizations
check_output_with_stdin( check_output_with_stdin(
@ -591,7 +654,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&[], &[],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), &app_args,
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -599,8 +662,8 @@ mod cli_run {
ran_without_optimizations = true; ran_without_optimizations = true;
}); });
// now we can pass the `PRECOMPILED_HOST` flag, because the `call_once` will // now we can pass the `PREBUILT_PLATFORM` flag, because the
// have compiled the host // `call_once` will have built the platform
if !ran_without_optimizations { if !ran_without_optimizations {
// Check with and without optimizations // Check with and without optimizations
@ -608,8 +671,8 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&[PRECOMPILED_HOST], &[PREBUILT_PLATFORM],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), &app_args,
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -619,8 +682,8 @@ mod cli_run {
&file_name, &file_name,
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&[PRECOMPILED_HOST, OPTIMIZE_FLAG], &[PREBUILT_PLATFORM, OPTIMIZE_FLAG],
benchmark.input_file.and_then(|file| Some(examples_dir("benchmarks").join(file))), &app_args,
benchmark.expected_ending, benchmark.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -653,7 +716,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, 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, benchmark.expected_ending,
); );
@ -662,7 +725,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
&[OPTIMIZE_FLAG], &[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, benchmark.expected_ending,
); );
} }
@ -694,7 +757,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
[concatcp!(TARGET_FLAG, "=x86_32")], [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.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -704,7 +767,7 @@ mod cli_run {
benchmark.stdin, benchmark.stdin,
benchmark.executable_filename, benchmark.executable_filename,
[concatcp!(TARGET_FLAG, "=x86_32"), OPTIMIZE_FLAG], [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.expected_ending,
benchmark.use_valgrind, benchmark.use_valgrind,
); );
@ -733,7 +796,7 @@ mod cli_run {
filename: "NQueens.roc", filename: "NQueens.roc",
executable_filename: "nqueens", executable_filename: "nqueens",
stdin: &["6"], stdin: &["6"],
input_file: None, arguments: &[],
expected_ending: "4\n", expected_ending: "4\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -741,7 +804,7 @@ mod cli_run {
filename: "CFold.roc", filename: "CFold.roc",
executable_filename: "cfold", executable_filename: "cfold",
stdin: &["3"], stdin: &["3"],
input_file: None, arguments: &[],
expected_ending: "11 & 11\n", expected_ending: "11 & 11\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -749,7 +812,7 @@ mod cli_run {
filename: "Deriv.roc", filename: "Deriv.roc",
executable_filename: "deriv", executable_filename: "deriv",
stdin: &["2"], stdin: &["2"],
input_file: None, arguments: &[],
expected_ending: "1 count: 6\n2 count: 22\n", expected_ending: "1 count: 6\n2 count: 22\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -757,7 +820,7 @@ mod cli_run {
filename: "RBTreeCk.roc", filename: "RBTreeCk.roc",
executable_filename: "rbtree-ck", executable_filename: "rbtree-ck",
stdin: &["100"], stdin: &["100"],
input_file: None, arguments: &[],
expected_ending: "10\n", expected_ending: "10\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -765,7 +828,7 @@ mod cli_run {
filename: "RBTreeInsert.roc", filename: "RBTreeInsert.roc",
executable_filename: "rbtree-insert", executable_filename: "rbtree-insert",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "Node Black 0 {} Empty Empty\n", expected_ending: "Node Black 0 {} Empty Empty\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -773,7 +836,7 @@ mod cli_run {
// filename: "RBTreeDel.roc", // filename: "RBTreeDel.roc",
// executable_filename: "rbtree-del", // executable_filename: "rbtree-del",
// stdin: &["420"], // stdin: &["420"],
// input_file: None, // arguments: &[],
// expected_ending: "30\n", // expected_ending: "30\n",
// use_valgrind: true, // use_valgrind: true,
// }, // },
@ -781,7 +844,7 @@ mod cli_run {
filename: "TestAStar.roc", filename: "TestAStar.roc",
executable_filename: "test-astar", executable_filename: "test-astar",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "True\n", expected_ending: "True\n",
use_valgrind: false, use_valgrind: false,
}, },
@ -789,7 +852,7 @@ mod cli_run {
filename: "TestBase64.roc", filename: "TestBase64.roc",
executable_filename: "test-base64", executable_filename: "test-base64",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -797,7 +860,7 @@ mod cli_run {
filename: "Closure.roc", filename: "Closure.roc",
executable_filename: "closure", executable_filename: "closure",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "", expected_ending: "",
use_valgrind: false, use_valgrind: false,
}, },
@ -805,7 +868,7 @@ mod cli_run {
filename: "Issue2279.roc", filename: "Issue2279.roc",
executable_filename: "issue2279", executable_filename: "issue2279",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "Hello, world!\n", expected_ending: "Hello, world!\n",
use_valgrind: true, use_valgrind: true,
}, },
@ -813,7 +876,7 @@ mod cli_run {
filename: "QuicksortApp.roc", filename: "QuicksortApp.roc",
executable_filename: "quicksortapp", executable_filename: "quicksortapp",
stdin: &[], stdin: &[],
input_file: None, arguments: &[],
expected_ending: "todo put the correct quicksort answer here", expected_ending: "todo put the correct quicksort answer here",
use_valgrind: true, use_valgrind: true,
}, },
@ -834,25 +897,6 @@ mod cli_run {
if entry.file_type().unwrap().is_dir() { if entry.file_type().unwrap().is_dir() {
let example_dir_name = entry.file_name().into_string().unwrap(); 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 // We test benchmarks separately
if example_dir_name != "benchmarks" { if example_dir_name != "benchmarks" {
all_examples.remove(example_dir_name.as_str()).unwrap_or_else(|| { all_examples.remove(example_dir_name.as_str()).unwrap_or_else(|| {
@ -912,7 +956,7 @@ mod cli_run {
&[], &[],
"multi-dep-str", "multi-dep-str",
&[], &[],
None, &[],
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
); );
@ -926,7 +970,7 @@ mod cli_run {
&[], &[],
"multi-dep-str", "multi-dep-str",
&[OPTIMIZE_FLAG], &[OPTIMIZE_FLAG],
None, &[],
"I am Dep2.str2\n", "I am Dep2.str2\n",
true, true,
); );
@ -940,7 +984,7 @@ mod cli_run {
&[], &[],
"multi-dep-thunk", "multi-dep-thunk",
&[], &[],
None, &[],
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,
); );
@ -954,7 +998,7 @@ mod cli_run {
&[], &[],
"multi-dep-thunk", "multi-dep-thunk",
&[OPTIMIZE_FLAG], &[OPTIMIZE_FLAG],
None, &[],
"I am Dep2.value2\n", "I am Dep2.value2\n",
true, true,
); );
@ -967,23 +1011,50 @@ mod cli_run {
&[], &[],
indoc!( indoc!(
r#" 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 Task.Task {} * [Write [Stdout]*]* ?
Ok
I8 But the type annotation on main says it should be:
F64
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."#
), ),
); );
} }

View file

@ -1,11 +1,13 @@
app "type-error" app "type-error"
packages { pf: "../../../../examples/interactive/cli-platform/main.roc" } 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 provides [main] to pf
main = main =
_ <- await (line "a") _ <- await (line "a")
_ <- await (line "b") _ <- await (line "b")
_ <- await (line "c") _ <- await (line "c")
_ <- await (line d) _ <- await (line "d")
line "e" line "e"
# Type mismatch because this line is missing:
# |> Program.quick

View file

@ -18,7 +18,7 @@ fn exec_bench_w_input<T: Measurement>(
&[stdin_str], &[stdin_str],
); );
if !compile_out.stderr.is_empty() { if !compile_out.stderr.is_empty() && compile_out.stderr != "🔨 Rebuilding platform...\n" {
panic!("{}", compile_out.stderr); panic!("{}", compile_out.stderr);
} }

View file

@ -164,7 +164,7 @@ where
pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>>( pub fn run_cmd<'a, I: IntoIterator<Item = &'a str>>(
cmd_name: &str, cmd_name: &str,
stdin_vals: I, stdin_vals: I,
args: &[&str], args: &[String],
) -> Out { ) -> Out {
let mut cmd = Command::new(cmd_name); 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>>( pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
stdin_vals: I, stdin_vals: I,
args: &[&str], args: &[String],
) -> (Out, String) { ) -> (Out, String) {
//TODO: figure out if there is a better way to get the valgrind executable. //TODO: figure out if there is a better way to get the valgrind executable.
let mut cmd = Command::new("valgrind"); let mut cmd = Command::new("valgrind");
@ -365,7 +365,7 @@ pub fn examples_dir(dir_name: &str) -> PathBuf {
// Descend into examples/{dir_name} // Descend into examples/{dir_name}
path.push("examples"); path.push("examples");
path.extend(dir_name.split("/")); // Make slashes cross-platform path.extend(dir_name.split("/")); // Make slashes cross-target
path path
} }
@ -388,7 +388,7 @@ pub fn fixtures_dir(dir_name: &str) -> PathBuf {
path.push("cli"); path.push("cli");
path.push("tests"); path.push("tests");
path.push("fixtures"); path.push("fixtures");
path.extend(dir_name.split("/")); // Make slashes cross-platform path.extend(dir_name.split("/")); // Make slashes cross-target
path path
} }

View file

@ -10,8 +10,8 @@ description = "Our own markup language for Roc code. Used by the editor and the
roc_ast = { path = "../ast" } roc_ast = { path = "../ast" }
roc_module = { path = "../compiler/module" } roc_module = { path = "../compiler/module" }
roc_utils = { path = "../utils" } roc_utils = { path = "../utils" }
serde = { version = "1.0.130", features = ["derive"] } serde = { version = "1.0.144", features = ["derive"] }
palette = "0.6.1" palette = "0.6.1"
snafu = { version = "0.7.1", features = ["backtraces"] } 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" itertools = "0.10.1"

View file

@ -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 { pub fn new_module_name_mn_id(mn_ids: Vec<MarkNodeId>, mark_node_pool: &mut SlowPool) -> MarkNodeId {
if mn_ids.len() == 1 { 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 { } else {
let nested_node = make_nested_mn(mn_ids, 0); let nested_node = make_nested_mn(mn_ids, 0);
mark_node_pool.add(nested_node) mark_node_pool.add(nested_node)

View file

@ -1,6 +1,8 @@
# The Roc Compiler
Here's how the compiler is laid out. 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. 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. 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. 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 ## 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: Basic approach:
@ -139,9 +141,9 @@ Advanced approach:
Express operations like map and filter in terms of toStream and fromStream, to unlock more deforestation. Express operations like map and filter in terms of toStream and fromStream, to unlock more deforestation.
More info on here: 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. 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: 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. Any interest in helping out here is greatly appreciated.
### General Tips ### General Tips

View file

@ -12,7 +12,9 @@ use roc_mono::ir::{
Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal, Call, CallType, Expr, HigherOrderLowLevel, HostExposedLayouts, ListLiteralElement, Literal,
ModifyRc, OptLevel, Proc, Stmt, 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 // just using one module for now
pub const MOD_APP: ModName = ModName(b"UserApp"); 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>( pub fn spec_program<'a, I>(
interner: &STLayoutInterner,
opt_level: OptLevel, opt_level: OptLevel,
opt_entry_point: Option<roc_mono::ir::EntryPoint<'a>>, opt_entry_point: Option<roc_mono::ir::EntryPoint<'a>>,
procs: I, 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); type_definitions.extend(type_names);
@ -231,8 +234,12 @@ where
); );
let roc_main = FuncName(&roc_main_bytes); let roc_main = FuncName(&roc_main_bytes);
let entry_point_function = let entry_point_function = build_entry_point(
build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?; interner,
entry_point.layout,
roc_main,
&host_exposed_functions,
)?;
let entry_point_name = FuncName(ENTRY_POINT_NAME); let entry_point_name = FuncName(ENTRY_POINT_NAME);
m.add_func(entry_point_name, entry_point_function)?; m.add_func(entry_point_name, entry_point_function)?;
} }
@ -243,7 +250,7 @@ where
let mut builder = TypeDefBuilder::new(); 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 { let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout {
debug_assert_eq!(variant_types.len(), 1); debug_assert_eq!(variant_types.len(), 1);
variant_types[0] variant_types[0]
@ -301,6 +308,7 @@ fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId)
} }
fn build_entry_point( fn build_entry_point(
interner: &STLayoutInterner,
layout: roc_mono::ir::ProcLayout, layout: roc_mono::ir::ProcLayout,
func_name: FuncName, func_name: FuncName,
host_exposed_functions: &[([u8; SIZE], &[Layout])], host_exposed_functions: &[([u8; SIZE], &[Layout])],
@ -314,8 +322,12 @@ fn build_entry_point(
let block = builder.add_block(); let block = builder.add_block();
// to the modelling language, the arguments appear out of thin air // to the modelling language, the arguments appear out of thin air
let argument_type = let argument_type = build_tuple_type(
build_tuple_type(&mut builder, layout.arguments, &WhenRecursive::Unreachable)?; &mut builder,
interner,
layout.arguments,
&WhenRecursive::Unreachable,
)?;
// does not make any assumptions about the input // does not make any assumptions about the input
// let argument = builder.add_unknown_with(block, &[], argument_type)?; // let argument = builder.add_unknown_with(block, &[], argument_type)?;
@ -346,6 +358,7 @@ fn build_entry_point(
let type_id = layout_spec( let type_id = layout_spec(
&mut builder, &mut builder,
interner,
&Layout::struct_no_name_order(layouts), &Layout::struct_no_name_order(layouts),
&WhenRecursive::Unreachable, &WhenRecursive::Unreachable,
)?; )?;
@ -371,7 +384,10 @@ fn build_entry_point(
Ok(spec) 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 builder = FuncDefBuilder::new();
let mut env = Env::default(); 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); 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 root = BlockExpr(block, value_id);
let arg_type_id = layout_spec( let arg_type_id = layout_spec(
&mut builder, &mut builder,
interner,
&Layout::struct_no_name_order(&argument_layouts), &Layout::struct_no_name_order(&argument_layouts),
&WhenRecursive::Unreachable, &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)?; let spec = builder.build(arg_type_id, ret_type_id, root)?;
@ -410,6 +439,7 @@ struct Env<'a> {
fn stmt_spec<'a>( fn stmt_spec<'a>(
builder: &mut FuncDefBuilder, builder: &mut FuncDefBuilder,
interner: &STLayoutInterner<'a>,
env: &mut Env<'a>, env: &mut Env<'a>,
block: BlockId, block: BlockId,
layout: &Layout, layout: &Layout,
@ -419,20 +449,20 @@ fn stmt_spec<'a>(
match stmt { match stmt {
Let(symbol, expr, expr_layout, mut continuation) => { 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); env.symbols.insert(*symbol, value_id);
let mut queue = vec![symbol]; let mut queue = vec![symbol];
while let Let(symbol, expr, expr_layout, c) = continuation { 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); env.symbols.insert(*symbol, value_id);
queue.push(symbol); queue.push(symbol);
continuation = c; 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 { for symbol in queue {
env.symbols.remove(symbol); env.symbols.remove(symbol);
@ -456,14 +486,14 @@ fn stmt_spec<'a>(
for branch in it { for branch in it {
let block = builder.add_block(); 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)); cases.push(BlockExpr(block, value_id));
} }
builder.add_choice(block, &cases) builder.add_choice(block, &cases)
} }
Expect { remainder, .. } => stmt_spec(builder, env, block, layout, remainder), Expect { remainder, .. } => stmt_spec(builder, interner, env, block, layout, remainder),
ExpectFx { remainder, .. } => stmt_spec(builder, env, block, layout, remainder), ExpectFx { remainder, .. } => stmt_spec(builder, interner, env, block, layout, remainder),
Ret(symbol) => Ok(env.symbols[symbol]), Ret(symbol) => Ok(env.symbols[symbol]),
Refcounting(modify_rc, continuation) => match modify_rc { Refcounting(modify_rc, continuation) => match modify_rc {
ModifyRc::Inc(symbol, _) => { ModifyRc::Inc(symbol, _) => {
@ -473,7 +503,7 @@ fn stmt_spec<'a>(
// and a bit more permissive in its type // and a bit more permissive in its type
builder.add_recursive_touch(block, argument)?; builder.add_recursive_touch(block, argument)?;
stmt_spec(builder, env, block, layout, continuation) stmt_spec(builder, interner, env, block, layout, continuation)
} }
ModifyRc::Dec(symbol) => { ModifyRc::Dec(symbol) => {
@ -481,14 +511,14 @@ fn stmt_spec<'a>(
builder.add_recursive_touch(block, argument)?; builder.add_recursive_touch(block, argument)?;
stmt_spec(builder, env, block, layout, continuation) stmt_spec(builder, interner, env, block, layout, continuation)
} }
ModifyRc::DecRef(symbol) => { ModifyRc::DecRef(symbol) => {
let argument = env.symbols[symbol]; let argument = env.symbols[symbol];
builder.add_recursive_touch(block, argument)?; builder.add_recursive_touch(block, argument)?;
stmt_spec(builder, env, block, layout, continuation) stmt_spec(builder, interner, env, block, layout, continuation)
} }
}, },
Join { Join {
@ -502,12 +532,13 @@ fn stmt_spec<'a>(
for p in parameters.iter() { for p in parameters.iter() {
type_ids.push(layout_spec( type_ids.push(layout_spec(
builder, builder,
interner,
&p.layout, &p.layout,
&WhenRecursive::Unreachable, &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)?; 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 // first, with the current variable bindings, process the remainder
let cont_block = builder.add_block(); 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 // only then introduce variables bound by the jump point, and process its body
let join_body_sub_block = { let join_body_sub_block = {
@ -536,7 +567,8 @@ fn stmt_spec<'a>(
env.symbols.insert(p.symbol, value_id); 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) 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)) builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
} }
Jump(id, symbols) => { 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 argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id]; let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id) builder.add_jump(block, jpid, argument, ret_type_id)
} }
RuntimeError(_) => { 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) builder.add_terminate(block, type_id)
} }
@ -591,13 +623,14 @@ enum WhenRecursive<'a> {
fn build_recursive_tuple_type( fn build_recursive_tuple_type(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
layouts: &[Layout], layouts: &[Layout],
when_recursive: &WhenRecursive, when_recursive: &WhenRecursive,
) -> Result<TypeId> { ) -> Result<TypeId> {
let mut field_types = Vec::new(); let mut field_types = Vec::new();
for field in layouts.iter() { 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); field_types.push(type_id);
} }
@ -606,13 +639,14 @@ fn build_recursive_tuple_type(
fn build_tuple_type( fn build_tuple_type(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
layouts: &[Layout], layouts: &[Layout],
when_recursive: &WhenRecursive, when_recursive: &WhenRecursive,
) -> Result<TypeId> { ) -> Result<TypeId> {
let mut field_types = Vec::new(); let mut field_types = Vec::new();
for field in layouts.iter() { 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) builder.add_tuple_type(&field_types)
@ -646,6 +680,7 @@ fn add_loop(
fn call_spec( fn call_spec(
builder: &mut FuncDefBuilder, builder: &mut FuncDefBuilder,
interner: &STLayoutInterner,
env: &Env, env: &Env,
block: BlockId, block: BlockId,
layout: &Layout, layout: &Layout,
@ -681,12 +716,14 @@ fn call_spec(
.map(|symbol| env.symbols[symbol]) .map(|symbol| env.symbols[symbol])
.collect(); .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) builder.add_unknown_with(block, &arguments, result_type)
} }
LowLevel { op, update_mode } => lowlevel_spec( LowLevel { op, update_mode } => lowlevel_spec(
builder, builder,
interner,
env, env,
block, block,
layout, layout,
@ -751,12 +788,20 @@ fn call_spec(
list_append(builder, block, update_mode_var, state, new_element) list_append(builder, block, update_mode_var, state, new_element)
}; };
let output_element_type = let output_element_type = layout_spec(
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; builder,
interner,
return_layout,
&WhenRecursive::Unreachable,
)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = let state_type = layout_spec(
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; builder,
interner,
&state_layout,
&WhenRecursive::Unreachable,
)?;
let init_state = new_list(builder, block, output_element_type)?; 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_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
let state_type = let state_type = layout_spec(
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; builder,
interner,
&state_layout,
&WhenRecursive::Unreachable,
)?;
let init_state = list; let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body) 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) list_append(builder, block, update_mode_var, state, new_element)
}; };
let output_element_type = let output_element_type = layout_spec(
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; builder,
interner,
return_layout,
&WhenRecursive::Unreachable,
)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = let state_type = layout_spec(
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; builder,
interner,
&state_layout,
&WhenRecursive::Unreachable,
)?;
let init_state = new_list(builder, block, output_element_type)?; 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) list_append(builder, block, update_mode_var, state, new_element)
}; };
let output_element_type = let output_element_type = layout_spec(
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; builder,
interner,
return_layout,
&WhenRecursive::Unreachable,
)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = let state_type = layout_spec(
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; builder,
interner,
&state_layout,
&WhenRecursive::Unreachable,
)?;
let init_state = new_list(builder, block, output_element_type)?; 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) list_append(builder, block, update_mode_var, state, new_element)
}; };
let output_element_type = let output_element_type = layout_spec(
layout_spec(builder, return_layout, &WhenRecursive::Unreachable)?; builder,
interner,
return_layout,
&WhenRecursive::Unreachable,
)?;
let state_layout = Layout::Builtin(Builtin::List(return_layout)); let state_layout = Layout::Builtin(Builtin::List(return_layout));
let state_type = let state_type = layout_spec(
layout_spec(builder, &state_layout, &WhenRecursive::Unreachable)?; builder,
interner,
&state_layout,
&WhenRecursive::Unreachable,
)?;
let init_state = new_list(builder, block, output_element_type)?; let init_state = new_list(builder, block, output_element_type)?;
@ -929,8 +1002,10 @@ fn list_clone(
with_new_heap_cell(builder, block, bag) with_new_heap_cell(builder, block, bag)
} }
#[allow(clippy::too_many_arguments)]
fn lowlevel_spec( fn lowlevel_spec(
builder: &mut FuncDefBuilder, builder: &mut FuncDefBuilder,
interner: &STLayoutInterner,
env: &Env, env: &Env,
block: BlockId, block: BlockId,
layout: &Layout, layout: &Layout,
@ -940,7 +1015,7 @@ fn lowlevel_spec(
) -> Result<ValueId> { ) -> Result<ValueId> {
use LowLevel::*; 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 mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode); let update_mode_var = UpdateModeVar(&mode);
@ -1048,8 +1123,12 @@ fn lowlevel_spec(
match layout { match layout {
Layout::Builtin(Builtin::List(element_layout)) => { Layout::Builtin(Builtin::List(element_layout)) => {
let type_id = let type_id = layout_spec(
layout_spec(builder, element_layout, &WhenRecursive::Unreachable)?; builder,
interner,
element_layout,
&WhenRecursive::Unreachable,
)?;
new_list(builder, block, type_id) new_list(builder, block, type_id)
} }
_ => unreachable!("empty array does not have a list layout"), _ => unreachable!("empty array does not have a list layout"),
@ -1092,7 +1171,7 @@ fn lowlevel_spec(
// TODO overly pessimstic // TODO overly pessimstic
let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect(); let arguments: Vec<_> = arguments.iter().map(|symbol| env.symbols[symbol]).collect();
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) builder.add_unknown_with(block, &arguments, result_type)
} }
@ -1101,16 +1180,18 @@ fn lowlevel_spec(
fn recursive_tag_variant( fn recursive_tag_variant(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
union_layout: &UnionLayout, union_layout: &UnionLayout,
fields: &[Layout], fields: &[Layout],
) -> Result<TypeId> { ) -> Result<TypeId> {
let when_recursive = WhenRecursive::Loop(*union_layout); 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( fn recursive_variant_types(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
union_layout: &UnionLayout, union_layout: &UnionLayout,
) -> Result<Vec<TypeId>> { ) -> Result<Vec<TypeId>> {
use UnionLayout::*; use UnionLayout::*;
@ -1125,11 +1206,16 @@ fn recursive_variant_types(
result = Vec::with_capacity(tags.len()); result = Vec::with_capacity(tags.len());
for tag in tags.iter() { 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) => { NonNullableUnwrapped(fields) => {
result = vec![recursive_tag_variant(builder, union_layout, fields)?]; result = vec![recursive_tag_variant(
builder,
interner,
union_layout,
fields,
)?];
} }
NullableWrapped { NullableWrapped {
nullable_id, nullable_id,
@ -1140,21 +1226,21 @@ fn recursive_variant_types(
let cutoff = *nullable_id as usize; let cutoff = *nullable_id as usize;
for tag in tags[..cutoff].iter() { 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() { 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 { NullableUnwrapped {
nullable_id, nullable_id,
other_fields: fields, other_fields: fields,
} => { } => {
let unit = recursive_tag_variant(builder, union_layout, &[])?; let unit = recursive_tag_variant(builder, interner, union_layout, &[])?;
let other_type = recursive_tag_variant(builder, union_layout, fields)?; let other_type = recursive_tag_variant(builder, interner, union_layout, fields)?;
if *nullable_id { if *nullable_id {
// nullable_id == 1 // nullable_id == 1
@ -1176,6 +1262,7 @@ fn worst_case_type(context: &mut impl TypeContext) -> Result<TypeId> {
fn expr_spec<'a>( fn expr_spec<'a>(
builder: &mut FuncDefBuilder, builder: &mut FuncDefBuilder,
interner: &STLayoutInterner,
env: &mut Env<'a>, env: &mut Env<'a>,
block: BlockId, block: BlockId,
layout: &Layout<'a>, layout: &Layout<'a>,
@ -1185,7 +1272,7 @@ fn expr_spec<'a>(
match expr { match expr {
Literal(literal) => literal_spec(builder, block, literal), Literal(literal) => literal_spec(builder, block, literal),
Call(call) => call_spec(builder, env, block, layout, call), Call(call) => call_spec(builder, interner, env, block, layout, call),
Reuse { Reuse {
tag_layout, tag_layout,
tag_id, tag_id,
@ -1201,8 +1288,12 @@ fn expr_spec<'a>(
let value_id = match tag_layout { let value_id = match tag_layout {
UnionLayout::NonRecursive(tags) => { UnionLayout::NonRecursive(tags) => {
let variant_types = let variant_types = non_recursive_variant_types(
non_recursive_variant_types(builder, tags, &WhenRecursive::Unreachable)?; builder,
interner,
tags,
&WhenRecursive::Unreachable,
)?;
let value_id = build_tuple_value(builder, env, block, arguments)?; let value_id = build_tuple_value(builder, env, block, arguments)?;
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id); 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, 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 = let union_id =
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?; builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?;
@ -1307,7 +1398,7 @@ fn expr_spec<'a>(
builder.add_get_tuple_field(block, value_id, *index as u32) builder.add_get_tuple_field(block, value_id, *index as u32)
} }
Array { elem_layout, elems } => { 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)?; let list = new_list(builder, block, type_id)?;
@ -1334,19 +1425,24 @@ fn expr_spec<'a>(
EmptyArray => match layout { EmptyArray => match layout {
Layout::Builtin(Builtin::List(element_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) new_list(builder, block, type_id)
} }
_ => unreachable!("empty array does not have a list layout"), _ => unreachable!("empty array does not have a list layout"),
}, },
Reset { symbol, .. } => { 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]; let value_id = env.symbols[symbol];
builder.add_unknown_with(block, &[value_id], type_id) builder.add_unknown_with(block, &[value_id], type_id)
} }
RuntimeErrorFunction(_) => { 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) builder.add_terminate(block, type_id)
} }
@ -1375,14 +1471,16 @@ fn literal_spec(
fn layout_spec( fn layout_spec(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
layout: &Layout, layout: &Layout,
when_recursive: &WhenRecursive, when_recursive: &WhenRecursive,
) -> Result<TypeId> { ) -> Result<TypeId> {
layout_spec_help(builder, layout, when_recursive) layout_spec_help(builder, interner, layout, when_recursive)
} }
fn non_recursive_variant_types( fn non_recursive_variant_types(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
tags: &[&[Layout]], tags: &[&[Layout]],
// If there is a recursive pointer latent within this layout, coming from a containing layout. // If there is a recursive pointer latent within this layout, coming from a containing layout.
when_recursive: &WhenRecursive, when_recursive: &WhenRecursive,
@ -1390,7 +1488,7 @@ fn non_recursive_variant_types(
let mut result = Vec::with_capacity(tags.len()); let mut result = Vec::with_capacity(tags.len());
for tag in tags.iter() { 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) Ok(result)
@ -1398,19 +1496,21 @@ fn non_recursive_variant_types(
fn layout_spec_help( fn layout_spec_help(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
layout: &Layout, layout: &Layout,
when_recursive: &WhenRecursive, when_recursive: &WhenRecursive,
) -> Result<TypeId> { ) -> Result<TypeId> {
use Layout::*; use Layout::*;
match layout { match layout {
Builtin(builtin) => builtin_spec(builder, builtin, when_recursive), Builtin(builtin) => builtin_spec(builder, interner, builtin, when_recursive),
Struct { field_layouts, .. } => { 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( LambdaSet(lambda_set) => layout_spec_help(
builder, builder,
&lambda_set.runtime_representation(), interner,
&lambda_set.runtime_representation(interner),
when_recursive, when_recursive,
), ),
Union(union_layout) => { Union(union_layout) => {
@ -1422,7 +1522,8 @@ fn layout_spec_help(
builder.add_tuple_type(&[]) builder.add_tuple_type(&[])
} }
UnionLayout::NonRecursive(tags) => { 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) builder.add_union_type(&variant_types)
} }
UnionLayout::Recursive(_) UnionLayout::Recursive(_)
@ -1438,7 +1539,7 @@ fn layout_spec_help(
} }
Boxed(inner_layout) => { 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(); let cell_type = builder.add_heap_cell_type();
builder.add_tuple_type(&[cell_type, inner_type]) builder.add_tuple_type(&[cell_type, inner_type])
@ -1465,6 +1566,7 @@ fn layout_spec_help(
fn builtin_spec( fn builtin_spec(
builder: &mut impl TypeContext, builder: &mut impl TypeContext,
interner: &STLayoutInterner,
builtin: &Builtin, builtin: &Builtin,
when_recursive: &WhenRecursive, when_recursive: &WhenRecursive,
) -> Result<TypeId> { ) -> Result<TypeId> {
@ -1475,7 +1577,7 @@ fn builtin_spec(
Decimal | Float(_) => builder.add_tuple_type(&[]), Decimal | Float(_) => builder.add_tuple_type(&[]),
Str => str_type(builder), Str => str_type(builder),
List(element_layout) => { 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 cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(element_type)?; let bag = builder.add_bag_type(element_type)?;

View file

@ -27,7 +27,7 @@ roc_reporting = { path = "../../reporting" }
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
roc_utils = { path = "../../utils" } roc_utils = { path = "../../utils" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
libloading = "0.7.1" libloading = "0.7.1"
tempfile = "3.2.0" tempfile = "3.2.0"
inkwell = { path = "../../vendor/inkwell" } inkwell = { path = "../../vendor/inkwell" }
@ -35,7 +35,7 @@ target-lexicon = "0.12.3"
wasi_libc_sys = { path = "../../wasi-libc-sys" } wasi_libc_sys = { path = "../../wasi-libc-sys" }
[target.'cfg(target_os = "macos")'.dependencies] [target.'cfg(target_os = "macos")'.dependencies]
serde_json = "1.0.69" serde_json = "1.0.85"
[features] [features]
target-arm = [] target-arm = []

View file

@ -121,11 +121,20 @@ pub fn build_zig_host_native(
.env("HOME", env_home); .env("HOME", env_home);
if let Some(shared_lib_path) = shared_lib_path { 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(&[ command.args(&[
"build-exe", "build-exe",
"-fPIE", "-fPIE",
shared_lib_path.to_str().unwrap(), shared_lib_path.to_str().unwrap(),
&bitcode::get_builtins_host_obj_path(), &builtins_obj,
]); ]);
} else { } else {
command.args(&["build-obj", "-fPIC"]); command.args(&["build-obj", "-fPIC"]);
@ -492,15 +501,29 @@ pub fn rebuild_host(
host_input_path.with_file_name("host.bc") host_input_path.with_file_name("host.bc")
} }
} else { } else {
host_input_path.with_file_name(if shared_lib_path.is_some() { let os = roc_target::OperatingSystem::from(target.operating_system);
"dynhost"
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 { } else {
match roc_target::OperatingSystem::from(target.operating_system) { let extension = match os {
roc_target::OperatingSystem::Windows => "host.obj", roc_target::OperatingSystem::Windows => "obj",
roc_target::OperatingSystem::Unix => "host.o", roc_target::OperatingSystem::Unix => "o",
roc_target::OperatingSystem::Wasi => "host.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()); let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());

View file

@ -256,6 +256,7 @@ pub fn gen_from_mono_module_llvm(
// Compile and add all the Procs before adding main // Compile and add all the Procs before adding main
let env = roc_gen_llvm::llvm::build::Env { let env = roc_gen_llvm::llvm::build::Env {
arena, arena,
layout_interner: &loaded.layout_interner,
builder: &builder, builder: &builder,
dibuilder: &dibuilder, dibuilder: &dibuilder,
compile_unit: &compile_unit, compile_unit: &compile_unit,
@ -473,6 +474,7 @@ fn gen_from_mono_module_dev_wasm32(
module_id, module_id,
procedures, procedures,
mut interns, mut interns,
layout_interner,
.. ..
} = loaded; } = loaded;
@ -485,6 +487,7 @@ fn gen_from_mono_module_dev_wasm32(
let env = roc_gen_wasm::Env { let env = roc_gen_wasm::Env {
arena, arena,
layout_interner: &layout_interner,
module_id, module_id,
exposed_to_host, exposed_to_host,
stack_bytes: wasm_dev_stack_bytes.unwrap_or(roc_gen_wasm::Env::DEFAULT_STACK_BYTES), 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(|_| { let host_bytes = std::fs::read(preprocessed_host_path).unwrap_or_else(|_| {
panic!( 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() preprocessed_host_path.display()
) )
}); });
@ -545,11 +548,13 @@ fn gen_from_mono_module_dev_assembly(
procedures, procedures,
mut interns, mut interns,
exposed_to_host, exposed_to_host,
layout_interner,
.. ..
} = loaded; } = loaded;
let env = roc_gen_dev::Env { let env = roc_gen_dev::Env {
arena, arena,
layout_interner: &layout_interner,
module_id, module_id,
exposed_to_host: exposed_to_host.values.keys().copied().collect(), exposed_to_host: exposed_to_host.values.keys().copied().collect(),
lazy_literals, lazy_literals,

View file

@ -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. 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. 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. 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: 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.#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. - ..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 ### 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. 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: 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 { fn list_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let elem_var = var_store.fresh(); let elem_var = var_store.fresh();
let len_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. 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. 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 ## Connecting the definition to the implementation
### module/src/low_level.rs ### 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`. 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 ## Bottom level LLVM values and functions
### gen/src/llvm/build.rs ### 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. 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 ## Letting the compiler know these functions exist
### builtins/src/std.rs ### 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`. 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 ## Specifying how we pass args to the function
### builtins/mono/src/borrow.rs ### 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. 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 ## Testing it
### solve/tests/solve_expr.rs ### 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: To make sure that Roc is properly inferring the type of the new builtin, add a test to this file similar to:
```
```rust
#[test] #[test]
fn atan() { fn atan() {
infer_eq_without_problem( infer_eq_without_problem(
@ -90,19 +103,23 @@ fn atan() {
); );
} }
``` ```
But replace `Num.atan` and the type signature with the new builtin. But replace `Num.atan` and the type signature with the new builtin.
### test_gen/test/*.rs ### 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: 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] #[test]
fn atan() { fn atan() {
assert_evals_to!("Num.atan 10", 1.4711276743037347, f64); assert_evals_to!("Num.atan 10", 1.4711276743037347, f64);
} }
``` ```
But replace `Num.atan`, the return value, and the return type with your new builtin. 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): 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):

View file

@ -3,6 +3,7 @@
## Adding a bitcode builtin ## Adding a bitcode builtin
To add a 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` 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 { ... }` 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. 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.

View file

@ -42,6 +42,7 @@ pub fn build(b: *Builder) void {
// Generate Object Files // Generate Object Files
generateObjectFile(b, mode, host_target, main_path, "object", "builtins-host"); 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"); generateObjectFile(b, mode, wasm32_target, main_path, "wasm32-object", "builtins-wasm32");
removeInstallSteps(b); removeInstallSteps(b);

View file

@ -148,11 +148,14 @@ pub const RocList = extern struct {
) RocList { ) RocList {
if (self.bytes) |source_ptr| { if (self.bytes) |source_ptr| {
if (self.isUnique()) { 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); 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 RocList{ .bytes = new_source, .length = new_length, .capacity = new_length };
} }
} }
}
return self.reallocateFresh(alignment, new_length, element_width); 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; return list_b;
} else if (list_b.isEmpty()) { } else if (list_b.isEmpty()) {
return list_a; 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(); const total_length: usize = list_a.len() + list_b.len();
if (list_a.bytes) |source| { if (list_a.bytes) |source| {
const new_source = utils.unsafeReallocate( const new_source = if (list_a.capacity >= total_length)
source
else
utils.unsafeReallocate(
source, source,
alignment, alignment,
list_a.len(), list_a.len(),

View file

@ -253,8 +253,10 @@ test "" {
// Export it as weak incase it is already linked in by something else. // Export it as weak incase it is already linked in by something else.
comptime { comptime {
if (builtin.target.os.tag != .windows) {
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak }); @export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
} }
}
fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 { fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
// @setRuntimeSafety(std.builtin.is_test); // @setRuntimeSafety(std.builtin.is_test);

View file

@ -215,11 +215,14 @@ pub const RocStr = extern struct {
return result; return result;
} }
// NOTE: returns false for empty string!
pub fn isSmallStr(self: RocStr) bool { pub fn isSmallStr(self: RocStr) bool {
return @bitCast(isize, self.str_capacity) < 0; 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 { fn asArray(self: RocStr) [@sizeOf(RocStr)]u8 {
const as_ptr = @ptrCast([*]const u8, &self); const as_ptr = @ptrCast([*]const u8, &self);
const slice = as_ptr[0..@sizeOf(RocStr)]; 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 { inline fn strToBytes(arg: RocStr) RocList {
if (arg.isEmpty()) { const length = arg.len();
if (length == 0) {
return RocList.empty(); return RocList.empty();
} else if (arg.isSmallStr()) { } else if (arg.isSmallStr()) {
const length = arg.len();
const ptr = utils.allocateWithRefcount(length, RocStr.alignment); const ptr = utils.allocateWithRefcount(length, RocStr.alignment);
@memcpy(ptr, arg.asU8ptr(), length); @memcpy(ptr, arg.asU8ptr(), length);
return RocList{ .length = length, .bytes = ptr, .capacity = length }; return RocList{ .length = length, .bytes = ptr, .capacity = length };
} else { } 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 };
} }
} }

View file

@ -254,7 +254,7 @@ pub fn unsafeReallocate(
const old_width = align_width + old_length * element_width; const old_width = align_width + old_length * element_width;
const new_width = align_width + new_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; return source_ptr;
} }

View file

@ -60,6 +60,12 @@ fn main() {
generate_object_file(&bitcode_path, "object", BUILTINS_HOST_FILE); 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"); generate_object_file(&bitcode_path, "wasm32-object", "builtins-wasm32.o");
copy_zig_builtins_to_target_dir(&bitcode_path); copy_zig_builtins_to_target_dir(&bitcode_path);

View file

@ -393,11 +393,11 @@ contains = \list, needle ->
## `fold`, `foldLeft`, or `foldl`. ## `fold`, `foldLeft`, or `foldl`.
walk : List elem, state, (state, elem -> state) -> state walk : List elem, state, (state, elem -> state) -> state
walk = \list, state, func -> walk = \list, state, func ->
walkHelp : _, _ -> [Continue _, Break []]
walkHelp = \currentState, element -> Continue (func currentState element) walkHelp = \currentState, element -> Continue (func currentState element)
when List.iterate list state walkHelp is when List.iterate list state walkHelp is
Continue newState -> newState Continue newState -> newState
Break void -> List.unreachable void
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`, ## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
## `fold`, `foldRight`, or `foldr`. ## `fold`, `foldRight`, or `foldr`.
@ -1006,6 +1006,3 @@ iterBackwardsHelp = \list, state, f, prevIndex ->
Break b -> Break b Break b -> Break b
else else
Continue state Continue state
## useful for typechecking guaranteed-unreachable cases
unreachable : [] -> a

View file

@ -33,6 +33,9 @@ interface Str
toU8, toU8,
toI8, toI8,
toScalars, toScalars,
replaceEach,
replaceFirst,
replaceLast,
splitFirst, splitFirst,
splitLast, splitLast,
walkUtf8WithIndex, walkUtf8WithIndex,
@ -276,6 +279,65 @@ countUtf8Bytes : Str -> Nat
## string slice that does not do bounds checking or utf-8 verification ## string slice that does not do bounds checking or utf-8 verification
substringUnsafe : Str, Nat, Nat -> Str 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 ## 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`. ## rest of the string after that occurrence. If the delimiter is not found, returns `Err`.
## ##
@ -294,6 +356,18 @@ splitFirst = \haystack, needle ->
None -> None ->
Err NotFound 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 : Str, Str -> [Some Nat, None]
firstMatch = \haystack, needle -> firstMatch = \haystack, needle ->
haystackLength = Str.countUtf8Bytes haystack haystackLength = Str.countUtf8Bytes haystack
@ -304,7 +378,7 @@ firstMatch = \haystack, needle ->
firstMatchHelp : Str, Str, Nat, Nat -> [Some Nat, None] firstMatchHelp : Str, Str, Nat, Nat -> [Some Nat, None]
firstMatchHelp = \haystack, needle, index, lastPossible -> firstMatchHelp = \haystack, needle, index, lastPossible ->
if index < lastPossible then if index <= lastPossible then
if matchesAt haystack index needle then if matchesAt haystack index needle then
Some index Some index
else else
@ -339,6 +413,9 @@ expect Str.splitLast "foo" "o" == Ok { before: "fo", after: "" }
# splitLast with multi-byte needle # splitLast with multi-byte needle
expect Str.splitLast "hullabaloo" "ab" == Ok { before: "hull", after: "aloo" } 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 : Str, Str -> [Some Nat, None]
lastMatch = \haystack, needle -> lastMatch = \haystack, needle ->
haystackLength = Str.countUtf8Bytes haystack haystackLength = Str.countUtf8Bytes haystack

View file

@ -14,6 +14,17 @@ pub fn get_builtins_host_obj_path() -> String {
.expect("Failed to convert builtins_host_path to str") .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 { pub fn get_builtins_wasm32_obj_path() -> String {
let builtins_wasm32_path = get_lib_path() let builtins_wasm32_path = get_lib_path()
.expect(LIB_DIR_ERROR) .expect(LIB_DIR_ERROR)

View file

@ -14,10 +14,10 @@ roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
static_assertions = "1.1.0" static_assertions = "1.1.0"
bitvec = "1" bitvec = "1"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.0.0" pretty_assertions = "1.3.0"
indoc = "1.0.7" indoc = "1.0.7"

View file

@ -1,6 +1,7 @@
use crate::expr::{self, IntValue, WhenBranch}; use crate::expr::{self, IntValue, WhenBranch};
use crate::pattern::DestructType; use crate::pattern::DestructType;
use roc_collections::all::HumanIndex; use roc_collections::all::HumanIndex;
use roc_collections::VecMap;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_exhaustive::{ use roc_exhaustive::{
is_useful, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union, is_useful, Ctor, CtorName, Error, Guard, Literal, Pattern, RenderAs, TagId, Union,
@ -23,6 +24,7 @@ pub struct ExhaustiveSummary {
pub fn check( pub fn check(
subs: &Subs, subs: &Subs,
real_var: Variable,
sketched_rows: SketchedRows, sketched_rows: SketchedRows,
context: ExhaustiveContext, context: ExhaustiveContext,
) -> ExhaustiveSummary { ) -> ExhaustiveSummary {
@ -33,7 +35,7 @@ pub fn check(
non_redundant_rows, non_redundant_rows,
errors, errors,
redundancies, redundancies,
} = sketched_rows.reify_to_non_redundant(subs); } = sketched_rows.reify_to_non_redundant(subs, real_var);
all_errors.extend(errors); all_errors.extend(errors);
let exhaustive = match roc_exhaustive::check(overall_region, context, non_redundant_rows) { let exhaustive = match roc_exhaustive::check(overall_region, context, non_redundant_rows) {
@ -55,27 +57,164 @@ pub fn check(
enum SketchedPattern { enum SketchedPattern {
Anything, Anything,
Literal(Literal), Literal(Literal),
Ctor(Variable, TagName, Vec<SketchedPattern>), /// A constructor whose expected union is not yet known.
KnownCtor(Union, TagId, Vec<SketchedPattern>), /// 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 { impl SketchedPattern {
fn reify(self, subs: &Subs) -> Pattern { fn reify(self, subs: &Subs, real_var: Variable) -> Pattern {
match self { match self {
Self::Anything => Pattern::Anything, Self::Anything => Pattern::Anything,
Self::Literal(lit) => Pattern::Literal(lit), Self::Literal(lit) => Pattern::Literal(lit),
Self::KnownCtor(union, tag_id, patterns) => Pattern::Ctor( Self::KnownCtor(union, index_ctor, tag_id, patterns) => {
union, let arg_vars = index_var(subs, real_var, index_ctor, &union.render_as);
tag_id,
patterns.into_iter().map(|pat| pat.reify(subs)).collect(), debug_assert!(arg_vars.len() == patterns.len());
), let args = (patterns.into_iter())
Self::Ctor(var, tag_name, patterns) => { .zip(arg_vars)
let (union, tag_id) = convert_tag(subs, var, &tag_name); .map(|(pat, var)| {
Pattern::Ctor( // FIXME
union, pat.reify(subs, var)
tag_id, })
patterns.into_iter().map(|pat| pat.reify(subs)).collect(), .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 { impl SketchedRows {
fn reify_to_non_redundant(self, subs: &Subs) -> NonRedundantSummary { fn reify_to_non_redundant(self, subs: &Subs, real_var: Variable) -> NonRedundantSummary {
to_nonredundant_rows(subs, self) 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 crate::pattern::Pattern::*;
use SketchedPattern as SP; use SketchedPattern as SP;
@ -131,9 +270,7 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
DestructType::Required | DestructType::Optional(..) => { DestructType::Required | DestructType::Optional(..) => {
patterns.push(SP::Anything) patterns.push(SP::Anything)
} }
DestructType::Guard(_, guard) => { DestructType::Guard(_, guard) => patterns.push(sketch_pattern(&guard.value)),
patterns.push(sketch_pattern(destruct.var, &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 { AppliedTag {
@ -156,16 +293,16 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
} => { } => {
let simplified_args: std::vec::Vec<_> = arguments let simplified_args: std::vec::Vec<_> = arguments
.iter() .iter()
.map(|(var, arg)| sketch_pattern(*var, &arg.value)) .map(|(_, arg)| sketch_pattern(&arg.value))
.collect(); .collect();
SP::Ctor(var, tag_name.clone(), simplified_args) SP::Ctor(tag_name.clone(), simplified_args)
} }
UnwrappedOpaque { UnwrappedOpaque {
opaque, argument, .. opaque, argument, ..
} => { } => {
let (arg_var, argument) = &(**argument); let (_, argument) = &(**argument);
let tag_id = TagId(0); let tag_id = TagId(0);
@ -180,8 +317,9 @@ fn sketch_pattern(var: Variable, pattern: &crate::pattern::Pattern) -> SketchedP
SP::KnownCtor( SP::KnownCtor(
union, union,
IndexCtor::Opaque,
tag_id, 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( pub fn sketch_when_branches(region: Region, patterns: &[expr::WhenBranch]) -> SketchedRows {
target_var: Variable,
region: Region,
patterns: &[expr::WhenBranch],
) -> SketchedRows {
let mut rows: Vec<SketchedRow> = Vec::with_capacity(patterns.len()); let mut rows: Vec<SketchedRow> = Vec::with_capacity(patterns.len());
// If any of the branches has a guard, e.g. // If any of the branches has a guard, e.g.
@ -256,18 +390,16 @@ pub fn sketch_when_branches(
vec![SP::KnownCtor( vec![SP::KnownCtor(
union, union,
IndexCtor::Guard,
tag_id, tag_id,
// NB: ordering the guard pattern first seems to be better at catching // 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 // non-exhaustive constructors in the second argument; see the paper to see if
// there is a way to improve this in general. // there is a way to improve this in general.
vec![ vec![guard_pattern, sketch_pattern(&loc_pat.pattern.value)],
guard_pattern,
sketch_pattern(target_var, &loc_pat.pattern.value),
],
)] )]
} else { } else {
// Simple case // Simple case
vec![sketch_pattern(target_var, &loc_pat.pattern.value)] vec![sketch_pattern(&loc_pat.pattern.value)]
}; };
let row = SketchedRow { let row = SketchedRow {
@ -286,13 +418,9 @@ pub fn sketch_when_branches(
} }
} }
pub fn sketch_pattern_to_rows( pub fn sketch_pattern_to_rows(region: Region, pattern: &crate::pattern::Pattern) -> SketchedRows {
target_var: Variable,
region: Region,
pattern: &crate::pattern::Pattern,
) -> SketchedRows {
let row = SketchedRow { let row = SketchedRow {
patterns: vec![sketch_pattern(target_var, pattern)], patterns: vec![sketch_pattern(pattern)],
region, region,
// A single row cannot be redundant! // A single row cannot be redundant!
redundant_mark: RedundantMark::known_non_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) /// 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 { let SketchedRows {
rows, rows,
overall_region, overall_region,
@ -323,27 +455,47 @@ fn to_nonredundant_rows(subs: &Subs, rows: SketchedRows) -> NonRedundantSummary
let mut redundancies = vec![]; let mut redundancies = vec![];
let mut errors = vec![]; let mut errors = vec![];
for SketchedRow { for (
row_number,
SketchedRow {
patterns, patterns,
guard, guard,
region, region,
redundant_mark, redundant_mark,
} in rows.into_iter() },
) in rows.into_iter().enumerate()
{ {
let next_row: Vec<Pattern> = patterns let next_row: Vec<Pattern> = patterns
.into_iter() .into_iter()
.map(|pattern| pattern.reify(subs)) .map(|pattern| pattern.reify(subs, real_var))
.collect(); .collect();
if matches!(guard, Guard::HasGuard) || is_useful(checked_rows.clone(), next_row.clone()) { let redundant_err = if !is_inhabited_row(&next_row) {
checked_rows.push(next_row); Some(Error::Unmatchable {
} else {
redundancies.push(redundant_mark);
errors.push(Error::Redundant {
overall_region, overall_region,
branch_region: 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) { fn convert_tag(subs: &Subs, whole_var: Variable, this_tag: &TagName) -> (Union, TagId) {
let content = subs.get_content_without_compacting(whole_var); 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 mut alternatives = Vec::with_capacity(num_tags);
let alternatives_iter = sorted_tags.into_iter().chain(opt_openness_tag.into_iter()); 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); let tag_id = TagId(index as TagIdIntType);
index += 1;
if this_tag == &tag { if this_tag == &tag {
my_tag_id = tag_id; my_tag_id = tag_id;
} }

View file

@ -6,9 +6,10 @@ license = "UPL-1.0"
edition = "2021" edition = "2021"
[dependencies] [dependencies]
fnv = "1.0.7"
im = "15.0.0" im = "15.0.0"
im-rc = "15.0.0" im-rc = "15.0.0"
wyhash = "0.5.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" ] } hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
bitvec = "1" bitvec = "1"

View file

@ -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 BumpMap<K, V> = hashbrown::HashMap<K, V, BuildHasher>;
pub type BumpSet<K> = hashbrown::HashSet<K, BuildHasher>; pub type BumpSet<K> = hashbrown::HashSet<K, BuildHasher>;
pub type FnvMap<K, V> = fnv::FnvHashMap<K, V>;
pub trait BumpMapDefault<'a> { pub trait BumpMapDefault<'a> {
fn new_in(arena: &'a bumpalo::Bump) -> Self; fn new_in(arena: &'a bumpalo::Bump) -> Self;

View file

@ -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 { fn from_iter<T: IntoIterator<Item = (K, V)>>(iter: T) -> Self {
let mut this = Self::default(); let mut this = Self::default();
this.extend(iter); this.extend(iter);

View file

@ -202,7 +202,7 @@ pub fn constrain_expr(
let field_var = field.var; let field_var = field.var;
let loc_field_expr = &field.loc_expr; let loc_field_expr = &field.loc_expr;
let (field_type, field_con) = 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_vars.push(field_var);
field_types.insert(label.clone(), RecordField::Required(field_type)); field_types.insert(label.clone(), RecordField::Required(field_type));
@ -724,11 +724,7 @@ pub fn constrain_expr(
let branches_region = { let branches_region = {
debug_assert!(!branches.is_empty()); debug_assert!(!branches.is_empty());
Region::span_across( Region::span_across(&loc_cond.region, &branches.last().unwrap().value.region)
&loc_cond.region,
// &branches.first().unwrap().region(),
&branches.last().unwrap().pattern_region(),
)
}; };
let branch_expr_reason = let branch_expr_reason =
@ -866,7 +862,7 @@ pub fn constrain_expr(
pattern_cons.push(cond_constraint); pattern_cons.push(cond_constraint);
// Now check the condition against the type expected by the branches. // 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( let cond_matches_branches_constraint = constraints.exhaustive(
real_cond_var, real_cond_var,
loc_cond.region, loc_cond.region,
@ -2452,8 +2448,7 @@ fn constrain_typed_function_arguments(
// Exhaustiveness-check the type in the pattern against what the // Exhaustiveness-check the type in the pattern against what the
// annotation wants. // annotation wants.
let sketched_rows = let sketched_rows = sketch_pattern_to_rows(loc_pattern.region, &loc_pattern.value);
sketch_pattern_to_rows(annotation_var, loc_pattern.region, &loc_pattern.value);
let category = loc_pattern.value.category(); let category = loc_pattern.value.category();
let expected = PExpected::ForReason( let expected = PExpected::ForReason(
PReason::TypedArg { PReason::TypedArg {
@ -2564,8 +2559,7 @@ fn constrain_typed_function_arguments_simple(
{ {
// Exhaustiveness-check the type in the pattern against what the // Exhaustiveness-check the type in the pattern against what the
// annotation wants. // annotation wants.
let sketched_rows = let sketched_rows = sketch_pattern_to_rows(loc_pattern.region, &loc_pattern.value);
sketch_pattern_to_rows(annotation_var, loc_pattern.region, &loc_pattern.value);
let category = loc_pattern.value.category(); let category = loc_pattern.value.category();
let expected = PExpected::ForReason( let expected = PExpected::ForReason(
PReason::TypedArg { PReason::TypedArg {

View file

@ -1,7 +1,10 @@
use roc_module::{ident::Lowercase, symbol::Symbol}; use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable}; 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)] #[derive(Hash)]
pub enum FlatDecodable { pub enum FlatDecodable {
@ -38,10 +41,11 @@ impl FlatDecodable {
_ => Err(Underivable), _ => Err(Underivable),
}, },
FlatType::Record(fields, ext) => { FlatType::Record(fields, ext) => {
let fields_iter = match fields.unsorted_iterator(subs, ext) { let (fields_iter, ext) = fields.unsorted_iterator_and_ext(subs, ext);
Ok(it) => it,
Err(_) => return Err(Underivable), check_derivable_ext_var(subs, ext, |ext| {
}; matches!(ext, Content::Structure(FlatType::EmptyRecord))
})?;
let mut field_names = Vec::with_capacity(fields.len()); let mut field_names = Vec::with_capacity(fields.len());
for (field_name, record_field) in fields_iter { for (field_name, record_field) in fields_iter {

View file

@ -2,10 +2,10 @@ use roc_module::{
ident::{Lowercase, TagName}, ident::{Lowercase, TagName},
symbol::Symbol, symbol::Symbol,
}; };
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable}; use roc_types::subs::{Content, FlatType, Subs, Variable};
use crate::{ use crate::{
util::{check_empty_ext_var, debug_name_record}, util::{check_derivable_ext_var, debug_name_record},
DeriveError, DeriveError,
}; };
@ -63,12 +63,17 @@ impl FlatEncodable {
_ => Err(Underivable), _ => Err(Underivable),
}, },
FlatType::Record(fields, ext) => { 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)) matches!(ext, Content::Structure(FlatType::EmptyRecord))
})?; })?;
let mut field_names: Vec<_> = let mut field_names = Vec::with_capacity(fields.len());
subs.get_subs_slice(fields.field_names()).to_vec(); for (field_name, _) in fields_iter {
field_names.push(field_name.clone());
}
field_names.sort(); field_names.sort();
Ok(Key(FlatEncodableKey::Record(field_names))) Ok(Key(FlatEncodableKey::Record(field_names)))
@ -83,20 +88,23 @@ impl FlatEncodable {
// [ A t1, B t1 t2 ] as R // [ A t1, B t1 t2 ] as R
// look the same on the surface, because `R` is only somewhere inside of the // look the same on the surface, because `R` is only somewhere inside of the
// `t`-prefixed payload types. // `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)) matches!(ext, Content::Structure(FlatType::EmptyTagUnion))
})?; })?;
let mut tag_names_and_payload_sizes: Vec<_> = tags let mut tag_names_and_payload_sizes: Vec<_> = tags_iter
.iter_all() .tags
.map(|(name_index, payload_slice_index)| { .into_iter()
let payload_slice = subs[payload_slice_index]; .map(|(name, payload_slice)| {
let payload_size = payload_slice.length; let payload_size = payload_slice.len();
let name = &subs[name_index]; (name.clone(), payload_size as _)
(name.clone(), payload_size)
}) })
.collect(); .collect();
tag_names_and_payload_sizes.sort_by(|(t1, _), (t2, _)| t1.cmp(t2)); tag_names_and_payload_sizes.sort_by(|(t1, _), (t2, _)| t1.cmp(t2));
Ok(Key(FlatEncodableKey::TagUnion(tag_names_and_payload_sizes))) Ok(Key(FlatEncodableKey::TagUnion(tag_names_and_payload_sizes)))
} }
FlatType::FunctionOrTagUnion(name_index, _, _) => Ok(Key( FlatType::FunctionOrTagUnion(name_index, _, _) => Ok(Key(

View file

@ -3,13 +3,25 @@ use roc_types::subs::{Content, Subs, Variable};
use crate::DeriveError; use crate::DeriveError;
pub(crate) fn check_empty_ext_var( pub(crate) fn check_derivable_ext_var(
subs: &Subs, subs: &Subs,
ext_var: Variable, ext_var: Variable,
is_empty_ext: impl Fn(&Content) -> bool, is_empty_ext: impl Fn(&Content) -> bool,
) -> Result<(), DeriveError> { ) -> Result<(), DeriveError> {
let ext_content = subs.get_content_without_compacting(ext_var); 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(()) Ok(())
} else { } else {
match ext_content { match ext_content {

View file

@ -93,6 +93,11 @@ pub enum Error {
branch_region: Region, branch_region: Region,
index: HumanIndex, index: HumanIndex,
}, },
Unmatchable {
overall_region: Region,
branch_region: Region,
index: HumanIndex,
},
} }
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
@ -451,7 +456,7 @@ fn collect_ctors(matrix: &RefPatternMatrix) -> MutMap<TagId, Union> {
let mut ctors = MutMap::default(); let mut ctors = MutMap::default();
for row in matrix { 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()); ctors.insert(*id, union.clone());
} }
} }

View file

@ -10,10 +10,10 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.0.0" pretty_assertions = "1.3.0"
indoc = "1.0.7" indoc = "1.0.7"
roc_test_utils = { path = "../../test_utils" } roc_test_utils = { path = "../../test_utils" }
walkdir = "2.3.2" walkdir = "2.3.2"

View file

@ -206,7 +206,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
use roc_parse::ast::TypeAnnotation::*; use roc_parse::ast::TypeAnnotation::*;
match self { match self {
Function(arguments, result) => { Function(args, ret) => {
let needs_parens = parens != Parens::NotNeeded; let needs_parens = parens != Parens::NotNeeded;
buf.indent(indent); buf.indent(indent);
@ -215,7 +215,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
buf.push('(') buf.push('(')
} }
let mut it = arguments.iter().enumerate().peekable(); let mut it = args.iter().enumerate().peekable();
let should_add_newlines = newlines == Newlines::Yes; let should_add_newlines = newlines == Newlines::Yes;
while let Some((index, argument)) = it.next() { while let Some((index, argument)) = it.next() {
@ -251,12 +251,7 @@ impl<'a> Formattable for TypeAnnotation<'a> {
buf.push_str("->"); buf.push_str("->");
buf.spaces(1); buf.spaces(1);
(&result.value).format_with_options( (&ret.value).format_with_options(buf, Parens::InFunctionType, Newlines::No, indent);
buf,
Parens::InFunctionType,
Newlines::No,
indent,
);
if needs_parens { if needs_parens {
buf.push(')') buf.push(')')
@ -345,8 +340,10 @@ impl<'a> Formattable for TypeAnnotation<'a> {
Newlines::No Newlines::No
}; };
if !buf.ends_with_newline() {
buf.newline(); buf.newline();
buf.indent(indent); buf.indent(indent);
}
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent); fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
ann.format_with_options(buf, parens, next_newlines, 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.spaces(separator_spaces);
buf.push_str(":"); buf.push(':');
buf.spaces(1); buf.spaces(1);
ann.value.format(buf, indent); ann.value.format(buf, indent);
} }
@ -524,7 +521,12 @@ impl<'a> Formattable for Tag<'a> {
for arg in *args { for arg in *args {
buf.newline(); 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 { } else {
for arg in *args { for arg in *args {
@ -544,7 +546,7 @@ impl<'a> Formattable for Tag<'a> {
impl<'a> Formattable for HasClause<'a> { impl<'a> Formattable for HasClause<'a> {
fn is_multiline(&self) -> bool { fn is_multiline(&self) -> bool {
self.var.value.is_multiline() || self.ability.is_multiline() self.ability.is_multiline()
} }
fn format_with_options<'buf>( fn format_with_options<'buf>(

View file

@ -220,7 +220,7 @@ impl<'a> Formattable for ValueDef<'a> {
} }
} else { } else {
buf.spaces(1); buf.spaces(1);
buf.push_str(":"); buf.push(':');
buf.spaces(1); buf.spaces(1);
loc_annotation.format_with_options( loc_annotation.format_with_options(
buf, buf,

View file

@ -419,7 +419,15 @@ fn format_str_segment<'a, 'buf>(seg: &StrSegment<'a>, buf: &mut Buf<'buf>, inden
match seg { match seg {
Plaintext(string) => { 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) => { Unicode(loc_str) => {
buf.push_str("\\u("); buf.push_str("\\u(");
@ -476,24 +484,20 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
buf.push('"'); buf.push('"');
match literal { match literal {
PlainLine(string) => { PlainLine(string) => {
// When a PlainLine contains "\n" it is formatted as a block string using """ // When a PlainLine contains '\n' or '"', format as a block string
let mut lines = string.split('\n'); if string.contains('"') || string.contains('\n') {
match (lines.next(), lines.next()) {
(Some(first), Some(second)) => {
buf.push_str("\"\""); buf.push_str("\"\"");
buf.newline(); buf.newline();
for line in string.split('\n') {
for line in [first, second].into_iter().chain(lines) {
buf.indent(indent); buf.indent(indent);
buf.push_str_allow_spaces(line); buf.push_str_allow_spaces(line);
buf.newline(); buf.newline();
} }
buf.indent(indent); buf.indent(indent);
buf.push_str("\"\""); buf.push_str("\"\"");
} } else {
_ => buf.push_str_allow_spaces(string), buf.push_str_allow_spaces(string);
} };
} }
Line(segments) => { Line(segments) => {
for seg in segments.iter() { 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(lines) => {
// Block strings will always be formatted with """ on new lines
buf.push_str("\"\""); 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(); buf.newline();
for segments in lines.iter() { for segments in lines.iter() {
for seg in segments.iter() { for seg in segments.iter() {
buf.indent(indent);
format_str_segment(seg, buf, indent); format_str_segment(seg, buf, indent);
} }
buf.newline(); buf.newline();
} }
} else { buf.indent(indent);
// 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.push_str("\"\""); buf.push_str("\"\"");
} }
} }

View file

@ -190,8 +190,9 @@ impl<'a> Formattable for TypedIdent<'a> {
buf.indent(indent); buf.indent(indent);
buf.push_str(self.ident.value); buf.push_str(self.ident.value);
fmt_default_spaces(buf, self.spaces_before_colon, indent); fmt_default_spaces(buf, self.spaces_before_colon, indent);
buf.push_str(":"); buf.push(':');
buf.spaces(1); buf.spaces(1);
self.ann.value.format(buf, indent); self.ann.value.format(buf, indent);
} }
} }

View file

@ -1,4 +1,5 @@
use crate::annotation::{Formattable, Newlines, Parens}; use crate::annotation::{Formattable, Newlines, Parens};
use crate::expr::fmt_str_literal;
use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt}; use crate::spaces::{fmt_comments_only, fmt_spaces, NewlineAt};
use crate::Buf; use crate::Buf;
use roc_parse::ast::{Base, CommentOrNewline, Pattern}; use roc_parse::ast::{Base, CommentOrNewline, Pattern};
@ -146,9 +147,7 @@ impl<'a> Formattable for Pattern<'a> {
buf.indent(indent); buf.indent(indent);
buf.push_str(string); buf.push_str(string);
} }
StrLiteral(literal) => { StrLiteral(literal) => fmt_str_literal(buf, *literal, indent),
todo!("Format string literal: {:?}", literal);
}
SingleQuote(string) => { SingleQuote(string) => {
buf.push('\''); buf.push('\'');
buf.push_str(string); buf.push_str(string);

View file

@ -542,17 +542,17 @@ impl<'a> RemoveSpaces<'a> for ValueDef<'a> {
}, },
Expect { Expect {
condition, condition,
preceding_comment, preceding_comment: _,
} => Expect { } => Expect {
condition: arena.alloc(condition.remove_spaces(arena)), condition: arena.alloc(condition.remove_spaces(arena)),
preceding_comment, preceding_comment: Region::zero(),
}, },
ExpectFx { ExpectFx {
condition, condition,
preceding_comment, preceding_comment: _,
} => ExpectFx { } => ExpectFx {
condition: arena.alloc(condition.remove_spaces(arena)), condition: arena.alloc(condition.remove_spaces(arena)),
preceding_comment, preceding_comment: Region::zero(),
}, },
} }
} }

View file

@ -1098,47 +1098,114 @@ mod test_fmt {
)); ));
} }
// #[test] #[test]
// fn empty_block_string() { fn empty_block_string() {
// expr_formats_same(indoc!( expr_formats_same(indoc!(
// r#" r#"
// """""" """
// "# """
// )); "#
// } ));
}
// #[test] #[test]
// fn basic_block_string() { fn oneline_empty_block_string() {
// expr_formats_same(indoc!( expr_formats_to(
// r#" indoc!(
// """blah""" r#"
// "# """"""
// )); "#
// } ),
indoc!(
r#"
"""
"""
"#
),
);
}
// #[test] #[test]
// fn newlines_block_string() { fn basic_block_string() {
// expr_formats_same(indoc!( expr_formats_to(
// r#" indoc!(
// """blah r#"
// spam """griffin"""
// foo""" "#
// "# ),
// )); indoc!(
// } r#"
"griffin"
"#
),
);
}
// #[test] #[test]
// fn quotes_block_string() { fn multiline_basic_block_string() {
// expr_formats_same(indoc!( expr_formats_to(
// r#" 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] #[test]
fn zero() { 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 // this is a parse error atm
// #[test] // #[test]
// fn multiline_apply() { // fn multiline_apply() {

View file

@ -8,6 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_intern = { path = "../intern" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
@ -18,7 +19,7 @@ roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_error_macros = { path = "../../error_macros" } 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" target-lexicon = "0.12.3"
# TODO: Deal with the update of object to 0.27. # TODO: Deal with the update of object to 0.27.
# It looks like it breaks linking the generated objects. # It looks like it breaks linking the generated objects.
@ -31,7 +32,7 @@ packed_struct = "0.10.0"
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_parse = { path = "../parse" } roc_parse = { path = "../parse" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
capstone = "0.11.0" capstone = "0.11.0"
[features] [features]

View file

@ -826,10 +826,41 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
todo!("registers greater than or equal for AArch64"); 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)] #[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
ret_reg64(buf, AArch64GeneralReg::LR) 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 {} impl AArch64Assembler {}

View file

@ -143,6 +143,27 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
src2: GeneralReg, 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); fn call(buf: &mut Vec<'_, u8>, relocs: &mut Vec<'_, Relocation>, fn_name: String);
/// Jumps by an offset of offset bytes unconditionally. /// Jumps by an offset of offset bytes unconditionally.
@ -320,6 +341,8 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
src2: GeneralReg, src2: GeneralReg,
); );
fn set_if_overflow(buf: &mut Vec<'_, u8>, dst: GeneralReg);
fn ret(buf: &mut Vec<'_, u8>); 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. // 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_asm: PhantomData<ASM>,
phantom_cc: PhantomData<CC>, phantom_cc: PhantomData<CC>,
target_info: TargetInfo,
env: &'a Env<'a>, env: &'a Env<'a>,
interns: &'a mut Interns, interns: &'a mut Interns,
helper_proc_gen: CodeGenHelp<'a>, helper_proc_gen: CodeGenHelp<'a>,
@ -374,9 +398,15 @@ pub fn new_backend_64bit<
Backend64Bit { Backend64Bit {
phantom_asm: PhantomData, phantom_asm: PhantomData,
phantom_cc: PhantomData, phantom_cc: PhantomData,
target_info,
env, env,
interns, 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], helper_proc_symbols: bumpalo::vec![in env.arena],
proc_name: None, proc_name: None,
is_self_recursive: 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>) { fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
use Builtin::Int;
match layout { match layout {
Layout::Builtin(Builtin::Int( Layout::Builtin(Int(IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8)) => {
IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .storage_manager
@ -804,9 +885,7 @@ impl<
.load_to_general_reg(&mut self.buf, src2); .load_to_general_reg(&mut self.buf, src2);
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg); ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
} }
Layout::Builtin(Builtin::Int( Layout::Builtin(Int(IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8)) => {
IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self let src1_reg = self
.storage_manager .storage_manager
@ -1090,7 +1169,8 @@ impl<
let index_reg = self let index_reg = self
.storage_manager .storage_manager
.load_to_general_reg(&mut self.buf, index); .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. // TODO: This can be optimized with smarter instructions.
// Also can probably be moved into storage manager at least partly. // Also can probably be moved into storage manager at least partly.
self.storage_manager.with_tmp_general_reg( self.storage_manager.with_tmp_general_reg(
@ -1132,7 +1212,8 @@ impl<
let elem_layout = arg_layouts[2]; let elem_layout = arg_layouts[2];
let u32_layout = &Layout::Builtin(Builtin::Int(IntWidth::U32)); 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( self.load_literal(
&Symbol::DEV_TMP, &Symbol::DEV_TMP,
u32_layout, u32_layout,
@ -1151,7 +1232,8 @@ impl<
ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset); ASM::add_reg64_reg64_imm32(&mut self.buf, reg, CC::BASE_PTR_REG, new_elem_offset);
// Load the elements size. // 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( self.load_literal(
&Symbol::DEV_TMP3, &Symbol::DEV_TMP3,
u64_layout, u64_layout,
@ -1161,7 +1243,7 @@ impl<
// Setup the return location. // Setup the return location.
let base_offset = self.storage_manager.claim_stack_area( let base_offset = self.storage_manager.claim_stack_area(
dst, 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 { 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 { 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, base_offset,
) )
} else { } else {
( (
base_offset, 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. // This requires at least 8 for the refcount alignment.
let allocation_alignment = std::cmp::max( let allocation_alignment = std::cmp::max(
8, 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 allocation_size = elem_size * elems.len() as u64 + allocation_alignment /* add space for refcount */;
let u64_layout = Layout::Builtin(Builtin::Int(IntWidth::U64)); let u64_layout = Layout::Builtin(Builtin::Int(IntWidth::U64));
self.load_literal( self.load_literal(
@ -1528,6 +1621,66 @@ impl<
offset, 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. /// This impl block is for ir related instructions that need backend specific information.

View file

@ -86,7 +86,7 @@ pub struct StorageManager<
> { > {
phantom_cc: PhantomData<CC>, phantom_cc: PhantomData<CC>,
phantom_asm: PhantomData<ASM>, phantom_asm: PhantomData<ASM>,
env: &'a Env<'a>, pub(crate) env: &'a Env<'a>,
target_info: TargetInfo, target_info: TargetInfo,
// Data about where each symbol is stored. // Data about where each symbol is stored.
symbol_storage_map: MutMap<Symbol, Storage<GeneralReg, FloatReg>>, symbol_storage_map: MutMap<Symbol, Storage<GeneralReg, FloatReg>>,
@ -541,12 +541,12 @@ impl<
let (base_offset, size) = (*base_offset, *size); let (base_offset, size) = (*base_offset, *size);
let mut data_offset = base_offset; let mut data_offset = base_offset;
for layout in field_layouts.iter().take(index as usize) { 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; data_offset += field_size as i32;
} }
debug_assert!(data_offset < base_offset + size as i32); debug_assert!(data_offset < base_offset + size as i32);
let layout = field_layouts[index as usize]; 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.allocation_map.insert(*sym, owned_data);
self.symbol_storage_map.insert( self.symbol_storage_map.insert(
*sym, *sym,
@ -591,8 +591,8 @@ impl<
UnionLayout::NonRecursive(_) => { UnionLayout::NonRecursive(_) => {
let (union_offset, _) = self.stack_offset_and_size(structure); let (union_offset, _) = self.stack_offset_and_size(structure);
let (data_size, data_alignment) = let (data_size, data_alignment) = union_layout
union_layout.data_size_and_alignment(self.target_info); .data_size_and_alignment(self.env.layout_interner, self.target_info);
let id_offset = data_size - data_alignment; let id_offset = data_size - data_alignment;
let discriminant = union_layout.discriminant(); let discriminant = union_layout.discriminant();
@ -635,7 +635,7 @@ impl<
layout: &Layout<'a>, layout: &Layout<'a>,
fields: &'a [Symbol], 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 { if struct_size == 0 {
self.symbol_storage_map.insert(*sym, NoData); self.symbol_storage_map.insert(*sym, NoData);
return; return;
@ -646,7 +646,8 @@ impl<
let mut current_offset = base_offset; let mut current_offset = base_offset;
for (field, field_layout) in fields.iter().zip(field_layouts.iter()) { for (field, field_layout) in fields.iter().zip(field_layouts.iter()) {
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout); 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; current_offset += field_size as i32;
} }
} else { } else {
@ -667,8 +668,8 @@ impl<
) { ) {
match union_layout { match union_layout {
UnionLayout::NonRecursive(field_layouts) => { UnionLayout::NonRecursive(field_layouts) => {
let (data_size, data_alignment) = let (data_size, data_alignment) = union_layout
union_layout.data_size_and_alignment(self.target_info); .data_size_and_alignment(self.env.layout_interner, self.target_info);
let id_offset = data_size - data_alignment; let id_offset = data_size - data_alignment;
if data_alignment < 8 || data_alignment % 8 != 0 { if data_alignment < 8 || data_alignment % 8 != 0 {
todo!("small/unaligned tagging"); todo!("small/unaligned tagging");
@ -679,7 +680,8 @@ impl<
fields.iter().zip(field_layouts[tag_id as usize].iter()) fields.iter().zip(field_layouts[tag_id as usize].iter())
{ {
self.copy_symbol_to_stack_offset(buf, current_offset, field, field_layout); 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; current_offset += field_size as i32;
} }
self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| { self.with_tmp_general_reg(buf, |_symbol_storage, buf, reg| {
@ -733,16 +735,19 @@ impl<
let reg = self.load_to_float_reg(buf, sym); let reg = self.load_to_float_reg(buf, sym);
ASM::mov_base32_freg64(buf, to_offset, reg); 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. // 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. // 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. // It is just temporarily storing the value due to needing to free registers.
// Later, it will be reloaded and stored in refcounted as needed. // 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); let (from_offset, size) = self.stack_offset_and_size(sym);
debug_assert!(from_offset % 8 == 0); debug_assert!(from_offset % 8 == 0);
debug_assert!(size % 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| { self.with_tmp_general_reg(buf, |_storage_manager, buf, reg| {
for i in (0..size as i32).step_by(8) { for i in (0..size as i32).step_by(8) {
ASM::mov_reg64_base32(buf, reg, from_offset + i); ASM::mov_reg64_base32(buf, reg, from_offset + i);
@ -1016,7 +1021,7 @@ impl<
.insert(*symbol, Rc::new((base_offset, 8))); .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 { if stack_size == 0 {
self.symbol_storage_map.insert(*symbol, NoData); self.symbol_storage_map.insert(*symbol, NoData);
} else { } else {

View file

@ -7,7 +7,7 @@ use bumpalo::collections::Vec;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout, STLayoutInterner};
use roc_target::TargetInfo; use roc_target::TargetInfo;
const TARGET_INFO: TargetInfo = TargetInfo::default_x86_64(); 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 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 general_i = 0;
let mut float_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]); storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[0]);
general_i += 1; general_i += 1;
} }
for (layout, sym) in args.iter() { 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 { match layout {
single_register_integers!() => { single_register_integers!() => {
if general_i < Self::GENERAL_PARAM_REGS.len() { 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 tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut general_i = 0; let mut general_i = 0;
let mut float_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. // Save space on the stack for the result we will be return.
let base_offset = let base_offset = storage_manager.claim_stack_area(
storage_manager.claim_stack_area(dst, ret_layout.stack_size(TARGET_INFO)); dst,
ret_layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO),
);
// Set the first reg to the address base + offset. // Set the first reg to the address base + offset.
let ret_reg = Self::GENERAL_PARAM_REGS[general_i]; let ret_reg = Self::GENERAL_PARAM_REGS[general_i];
general_i += 1; general_i += 1;
@ -386,8 +388,8 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
tmp_stack_offset += 8; 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 if x.stack_size(TARGET_INFO) > 16 => { x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) > 16 => {
// TODO: Double check this. // TODO: Double check this.
// Just copy onto the stack. // Just copy onto the stack.
// Use return reg as buffer because it will be empty right now. // 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!() => { single_register_layouts!() => {
internal_error!("single register layouts are not complex symbols"); internal_error!("single register layouts are not complex symbols");
} }
x if x.stack_size(TARGET_INFO) == 0 => {} x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x if !Self::returns_via_arg_pointer(x) => { x if !Self::returns_via_arg_pointer(storage_manager.env.layout_interner, x) => {
let (base_offset, size) = storage_manager.stack_offset_and_size(sym); let (base_offset, size) = storage_manager.stack_offset_and_size(sym);
debug_assert_eq!(base_offset % 8, 0); debug_assert_eq!(base_offset % 8, 0);
if size <= 8 { if size <= 8 {
@ -487,9 +489,9 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg, X86_64Assembler> for X86_64Syste
single_register_layouts!() => { single_register_layouts!() => {
internal_error!("single register layouts are not complex symbols"); internal_error!("single register layouts are not complex symbols");
} }
x if x.stack_size(TARGET_INFO) == 0 => {} x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x if !Self::returns_via_arg_pointer(x) => { x if !Self::returns_via_arg_pointer(storage_manager.env.layout_interner, x) => {
let size = layout.stack_size(TARGET_INFO); let size = layout.stack_size(storage_manager.env.layout_interner, TARGET_INFO);
let offset = storage_manager.claim_stack_area(sym, size); let offset = storage_manager.claim_stack_area(sym, size);
if size <= 8 { if size <= 8 {
X86_64Assembler::mov_base32_reg64(buf, offset, Self::GENERAL_RETURN_REGS[0]); 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 { 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. // 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 // 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 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; 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]); storage_manager.ret_pointer_arg(Self::GENERAL_PARAM_REGS[i]);
i += 1; 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]); storage_manager.float_reg_arg(sym, Self::FLOAT_PARAM_REGS[i]);
i += 1; i += 1;
} }
x if x.stack_size(TARGET_INFO) == 0 => {} x if x.stack_size(storage_manager.env.layout_interner, TARGET_INFO) == 0 => {}
x => { x => {
todo!("Loading args with layout {:?}", 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>, ret_layout: &Layout<'a>,
) { ) {
let mut tmp_stack_offset = Self::SHADOW_SPACE_SIZE as i32; 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. // 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"); todo!("claim first parama reg for the address");
} }
for (i, (sym, layout)) in args.iter().zip(arg_layouts.iter()).enumerate() { 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; 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 => { x => {
todo!("calling with arg type, {:?}", 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 { 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. // 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 // 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); 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 { impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
// These functions should map to the raw assembly functions below. // 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. // 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); mov_reg64_reg64(buf, dst, src1);
add_reg64_imm32(buf, dst, imm32); add_reg64_imm32(buf, dst, imm32);
} }
#[inline(always)] #[inline(always)]
fn add_reg64_reg64_reg64( fn add_reg64_reg64_reg64(buf: &mut Vec<'_, u8>, dst: Reg64, src1: Reg64, src2: Reg64) {
buf: &mut Vec<'_, u8>, binop_move_src_to_dst_reg64(buf, add_reg64_reg64, dst, src1, src2)
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);
}
} }
#[inline(always)] #[inline(always)]
fn add_freg32_freg32_freg32( fn add_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>, buf: &mut Vec<'_, u8>,
@ -1239,31 +1259,20 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
#[inline(always)] #[inline(always)]
fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { fn movsx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8); debug_assert!(size <= 8);
if size == 8 { match size {
Self::mov_reg64_base32(buf, dst, offset); 8 => Self::mov_reg64_base32(buf, dst, offset),
} else if size == 4 { 4 | 2 | 1 => todo!("sign extending {size} byte values"),
todo!("sign extending 4 byte values"); _ => internal_error!("Invalid size for sign extension: {size}"),
} 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);
} }
} }
#[inline(always)] #[inline(always)]
fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) { fn movzx_reg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, offset: i32, size: u8) {
debug_assert!(size <= 8); debug_assert!(size <= 8);
if size == 8 { match size {
Self::mov_reg64_base32(buf, dst, offset); 8 => Self::mov_reg64_base32(buf, dst, offset),
} else if size == 4 { 4 | 2 => todo!("zero extending {size} byte values"),
todo!("zero extending 4 byte values"); 1 => movzx_reg64_base8_offset32(buf, dst, X86_64GeneralReg::RBP, offset),
} else if size == 2 { _ => internal_error!("Invalid size for zero extension: {size}"),
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);
} }
} }
@ -1390,6 +1399,22 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
fn ret(buf: &mut Vec<'_, u8>) { fn ret(buf: &mut Vec<'_, u8>) {
ret(buf); 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 { 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); 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. /// `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)] #[inline(always)]
fn addsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) { 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); 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. /// `RET` -> Near return to calling procedure.
#[inline(always)] #[inline(always)]
fn ret(buf: &mut Vec<'_, u8>) { 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. // 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. // This is because R8-R15 often have special instruction prefixes.
#[cfg(test)] #[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] #[test]
fn test_xor_reg64_reg64() { fn test_xor_reg64_reg64() {
disassembler_test!( disassembler_test!(
xor_reg64_reg64, xor_reg64_reg64,
|reg1, reg2| format!("xor {}, {}", reg1, reg2), |reg1, reg2| format!("xor {reg1}, {reg2}"),
ALL_GENERAL_REGS, ALL_GENERAL_REGS,
ALL_GENERAL_REGS ALL_GENERAL_REGS
); );

View file

@ -14,7 +14,9 @@ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout, BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc, ProcLayout,
SelfRecursive, Stmt, 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 generic64;
mod object_builder; mod object_builder;
@ -23,6 +25,7 @@ mod run_roc;
pub struct Env<'a> { pub struct Env<'a> {
pub arena: &'a Bump, pub arena: &'a Bump,
pub layout_interner: &'a STLayoutInterner<'a>,
pub module_id: ModuleId, pub module_id: ModuleId,
pub exposed_to_host: MutSet<Symbol>, pub exposed_to_host: MutSet<Symbol>,
pub lazy_literals: bool, pub lazy_literals: bool,
@ -405,6 +408,9 @@ trait Backend<'a> {
); );
self.build_num_add(sym, &args[0], &args[1], ret_layout) 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( LowLevel::NumAcos => self.build_fn_call(
sym, sym,
bitcode::NUM_ACOS[FloatWidth::F64].to_string(), 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) 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 => { LowLevel::Eq => {
debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument"); debug_assert_eq!(2, args.len(), "Eq: expected to have exactly two argument");
debug_assert_eq!( debug_assert_eq!(
@ -694,6 +721,13 @@ trait Backend<'a> {
self.load_literal_symbols(args); self.load_literal_symbols(args);
self.build_fn_call(sym, fn_name, args, arg_layouts, ret_layout) 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), _ => todo!("the function, {:?}", func_sym),
} }
} }
@ -715,6 +749,16 @@ trait Backend<'a> {
/// build_num_add stores the sum of src1 and src2 into dst. /// 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>); 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. /// build_num_mul stores `src1 * src2` into dst.
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>); 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. /// 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>); 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. /// 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>); fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);

View file

@ -18,6 +18,6 @@ roc_std = { path = "../../roc_std" }
roc_debug_flags = { path = "../debug_flags" } roc_debug_flags = { path = "../debug_flags" }
roc_region = { path = "../region" } roc_region = { path = "../region" }
morphic_lib = { path = "../../vendor/morphic_lib" } morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
inkwell = { path = "../../vendor/inkwell" } inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.3" target-lexicon = "0.12.3"

View file

@ -247,29 +247,22 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) { for (argument_ptr, layout) in arguments.iter().zip(argument_layouts) {
let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic); let basic_type = basic_type_from_layout(env, layout).ptr_type(AddressSpace::Generic);
let argument = if layout.is_passed_by_reference(env.target_info) { let cast_ptr = env.builder.build_pointer_cast(
env.builder
.build_pointer_cast(
argument_ptr.into_pointer_value(), argument_ptr.into_pointer_value(),
basic_type, basic_type,
"cast_ptr_to_tag_build_transform_caller_help", "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); arguments_cast.push(argument);
} }
match ( match (
closure_data_layout.is_represented().is_some(), closure_data_layout
closure_data_layout.runtime_representation(), .is_represented(env.layout_interner)
.is_some(),
closure_data_layout.runtime_representation(env.layout_interner),
) { ) {
(false, _) => { (false, _) => {
// the function doesn't expect a closure argument, nothing to add // 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 let closure_cast = env
.builder .builder
.build_bitcast(closure_ptr, closure_type, "load_opaque") .build_bitcast(closure_ptr, closure_type, "cast_opaque_closure")
.into_pointer_value(); .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); 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_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 env.builder
.build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper") .build_pointer_cast(value_ptr, value_type, "cast_ptr_to_tag_build_rc_wrapper")
.into() .into()
@ -590,7 +583,8 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let default = [value1.into(), value2.into()]; 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 { Layout::Struct {
field_layouts: &[], .. field_layouts: &[], ..
} => { } => {

View file

@ -8,7 +8,6 @@ use crate::llvm::build_list::{
list_prepend, list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap, list_prepend, list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
list_symbol_to_c_abi, list_with_capacity, pass_update_mode, list_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::compare::{generic_eq, generic_neq};
use crate::llvm::convert::{ use crate::llvm::convert::{
self, argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout, zig_str_type, 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::{ use roc_mono::layout::{
Builtin, CapturesNiche, LambdaName, LambdaSet, Layout, LayoutIds, RawFunctionLayout, Builtin, CapturesNiche, LambdaName, LambdaSet, Layout, LayoutIds, RawFunctionLayout,
TagIdIntType, UnionLayout, STLayoutInterner, TagIdIntType, UnionLayout,
}; };
use roc_std::RocDec; use roc_std::RocDec;
use roc_target::{PtrWidth, TargetInfo}; use roc_target::{PtrWidth, TargetInfo};
@ -65,7 +64,7 @@ use std::convert::TryInto;
use std::path::Path; use std::path::Path;
use target_lexicon::{Architecture, OperatingSystem, Triple}; 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)] #[inline(always)]
fn print_fn_verification_output() -> bool { fn print_fn_verification_output() -> bool {
@ -203,6 +202,7 @@ impl LlvmBackendMode {
pub struct Env<'a, 'ctx, 'env> { pub struct Env<'a, 'ctx, 'env> {
pub arena: &'a Bump, pub arena: &'a Bump,
pub layout_interner: &'env STLayoutInterner<'a>,
pub context: &'ctx Context, pub context: &'ctx Context,
pub builder: &'env Builder<'ctx>, pub builder: &'env Builder<'ctx>,
pub dibuilder: &'env DebugInfoBuilder<'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> { 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); let alignment_iv = self.alignment_const(alignment);
alignment_iv.into() 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, &[]); 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 // 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 number_of_bytes = env.ptr_int().const_int(size as _, false);
let void_ptr = env.call_alloc(number_of_bytes, alignment); 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( let allocation = reserve_with_refcount_help(
env, env,
basic_type, basic_type,
layout.stack_size(env.target_info), layout.stack_size(env.layout_interner, env.target_info),
layout.alignment_bytes(env.target_info), layout.alignment_bytes(env.layout_interner, env.target_info),
); );
store_roc_value(env, *layout, allocation, value); 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 (value, layout) = load_symbol_and_layout(scope, structure);
let layout = if let Layout::LambdaSet(lambda_set) = layout { let layout = if let Layout::LambdaSet(lambda_set) = layout {
lambda_set.runtime_representation() lambda_set.runtime_representation(env.layout_interner)
} else { } else {
*layout *layout
}; };
@ -1600,7 +1602,7 @@ fn build_tag_field_value<'a, 'ctx, 'env>(
env.context.i64_type().ptr_type(AddressSpace::Generic), env.context.i64_type().ptr_type(AddressSpace::Generic),
"cast_recursive_pointer", "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()); debug_assert!(value.is_pointer_value());
// NOTE: we rely on this being passed to `store_roc_value` so that // 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() { if !field_layout.is_dropped_because_empty() {
field_types.push(basic_type_from_layout(env, field_layout)); field_types.push(basic_type_from_layout(env, field_layout));
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 let field_value = env
.builder .builder
.build_load(field_expr.into_pointer_value(), "load_tag_to_put_in_struct"); .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 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 value = roc_union.as_struct_value(env, data, Some(tag_id as _));
let alloca = create_entry_block_alloca( let alloca = create_entry_block_alloca(
@ -1788,8 +1795,12 @@ fn build_tag<'a, 'ctx, 'env>(
nullable_id, nullable_id,
other_fields, other_fields,
} => { } => {
let roc_union = let roc_union = RocUnion::untagged_from_slices(
RocUnion::untagged_from_slices(env.context, &[other_fields], env.target_info); env.layout_interner,
env.context,
&[other_fields],
env.target_info,
);
if tag_id == *nullable_id as _ { if tag_id == *nullable_id as _ {
let output_type = roc_union.struct_type().ptr_type(AddressSpace::Generic); let output_type = roc_union.struct_type().ptr_type(AddressSpace::Generic);
@ -2104,8 +2115,8 @@ pub fn reserve_with_refcount<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>, layout: &Layout<'a>,
) -> PointerValue<'ctx> { ) -> PointerValue<'ctx> {
let stack_size = layout.stack_size(env.target_info); let stack_size = layout.stack_size(env.layout_interner, env.target_info);
let alignment_bytes = layout.alignment_bytes(env.target_info); let alignment_bytes = layout.alignment_bytes(env.layout_interner, env.target_info);
let basic_type = basic_type_from_layout(env, layout); 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 ptr_bytes = env.target_info;
let roc_union = if union_layout.stores_tag_id_as_data(ptr_bytes) { 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 { } 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( reserve_with_refcount_help(
@ -2211,10 +2222,10 @@ fn list_literal<'a, 'ctx, 'env>(
// if element_type.is_int_type() { // if element_type.is_int_type() {
if false { if false {
let element_type = element_type.into_int_type(); 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 size = list_length * element_width as usize;
let alignment = element_layout 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); .max(env.target_info.ptr_width() as u32);
let mut is_all_constant = true; let mut is_all_constant = true;
@ -2351,7 +2362,7 @@ pub fn load_roc_value<'a, 'ctx, 'env>(
source: PointerValue<'ctx>, source: PointerValue<'ctx>,
name: &str, name: &str,
) -> BasicValueEnum<'ctx> { ) -> 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); let alloca = entry_block_alloca_zerofill(env, basic_type_from_layout(env, &layout), name);
store_roc_value(env, layout, alloca, source.into()); store_roc_value(env, layout, alloca, source.into());
@ -2368,7 +2379,7 @@ pub fn use_roc_value<'a, 'ctx, 'env>(
source: BasicValueEnum<'ctx>, source: BasicValueEnum<'ctx>,
name: &str, name: &str,
) -> BasicValueEnum<'ctx> { ) -> 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); let alloca = entry_block_alloca_zerofill(env, basic_type_from_layout(env, &layout), name);
env.builder.build_store(alloca, source); env.builder.build_store(alloca, source);
@ -2399,15 +2410,16 @@ pub fn store_roc_value<'a, 'ctx, 'env>(
destination: PointerValue<'ctx>, destination: PointerValue<'ctx>,
value: BasicValueEnum<'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()); 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 { if align_bytes > 0 {
let size = env let size = env.ptr_int().const_int(
.ptr_int() layout.stack_size(env.layout_interner, env.target_info) as u64,
.const_int(layout.stack_size(env.target_info) as u64, false); false,
);
env.builder env.builder
.build_memcpy( .build_memcpy(
@ -2420,6 +2432,15 @@ pub fn store_roc_value<'a, 'ctx, 'env>(
.unwrap(); .unwrap();
} }
} else { } 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); 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); // store_roc_value(env, *layout, out_parameter.into_pointer_value(), value);
let destination = out_parameter.into_pointer_value(); let destination = out_parameter.into_pointer_value();
if layout.is_passed_by_reference(env.target_info) { if layout.is_passed_by_reference(env.layout_interner, env.target_info) {
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 { if align_bytes > 0 {
debug_assert!( debug_assert!(
@ -2534,7 +2556,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
// Hence, we explicitly memcpy source to destination, and rely on // Hence, we explicitly memcpy source to destination, and rely on
// LLVM optimizing away any inefficiencies. // LLVM optimizing away any inefficiencies.
let target_info = env.target_info; 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); let size = env.ptr_int().const_int(width as _, false);
env.builder env.builder
@ -2614,7 +2636,10 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
for param in parameters.iter() { for param in parameters.iter() {
let basic_type = basic_type_from_layout(env, &param.layout); let basic_type = basic_type_from_layout(env, &param.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() basic_type.ptr_type(AddressSpace::Generic).into()
} else { } else {
basic_type basic_type
@ -2697,7 +2722,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
let (value, layout) = load_symbol_and_layout(scope, symbol); let (value, layout) = load_symbol_and_layout(scope, symbol);
let layout = *layout; let layout = *layout;
if layout.contains_refcounted() { if layout.contains_refcounted(env.layout_interner) {
increment_refcount_layout( increment_refcount_layout(
env, env,
parent, parent,
@ -2713,7 +2738,7 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
Dec(symbol) => { Dec(symbol) => {
let (value, layout) = load_symbol_and_layout(scope, 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); 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::Str) => todo!(),
Layout::Builtin(Builtin::List(element_layout)) => { Layout::Builtin(Builtin::List(element_layout)) => {
debug_assert!(value.is_struct_value()); 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); 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 /// Cast a value to another value of the same (or smaller?) size
pub fn cast_basic_basic<'ctx>( pub fn cast_basic_basic<'ctx>(
builder: &Builder<'ctx>, builder: &Builder<'ctx>,
@ -3701,6 +3750,37 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
Linkage::External, 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); let subprogram = env.new_subprogram(c_function_name);
c_function.set_subprogram(subprogram); 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 // Drop the return pointer the other way, if the C function returns by pointer but Roc
// doesn't // doesn't
(RocReturn::Return, CCReturn::ByPointer) => (&params[1..], &param_types[..]), (RocReturn::Return, CCReturn::ByPointer) => (&params[1..], &param_types[..]),
(RocReturn::ByPointer, CCReturn::ByPointer) => {
// Both return by pointer but Roc puts it at the end and C puts it at the beginning
(&params[1..], &param_types[..param_types.len() - 1])
}
_ => (&params[..], &param_types[..]), _ => (&params[..], &param_types[..]),
}; };
@ -3807,8 +3891,20 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
}, },
CCReturn::ByPointer => { CCReturn::ByPointer => {
let out_ptr = c_function.get_nth_param(0).unwrap().into_pointer_value(); 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); 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); env.builder.build_return(None);
} }
CCReturn::Void => { 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> { 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 // - AArch64 needs 3 machine-sized words
// - LLVM says the following about the SJLJ intrinsic: // - 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") .build_insert_value(v1, context.i64_type().const_zero(), 0, "set_no_error")
.unwrap(); .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( let loaded = env.builder.build_load(
return_value.into_pointer_value(), return_value.into_pointer_value(),
"load_call_result_passed_by_ptr", "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 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), Err(e) => panic!("Error in alias analysis: {}", e),
Ok(solutions) => solutions, Ok(solutions) => solutions,
}; };
@ -4671,7 +4769,8 @@ fn build_closure_caller<'a, 'ctx, 'env>(
} }
let closure_argument_type = { 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) 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 // NOTE this may be incorrect in the long run
// here we load any argument that is a pointer // 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)); let layouts_it = arguments.iter().chain(std::iter::once(&closure_layout));
for (param, layout) in evaluator_arguments.iter_mut().zip(layouts_it) { 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"); *param = builder.build_load(param.into_pointer_value(), "load_param");
} }
} }
@ -4739,13 +4840,14 @@ fn build_closure_caller<'a, 'ctx, 'env>(
} else { } else {
let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments); let call_result = call_roc_function(env, evaluator, return_layout, &evaluator_arguments);
if return_layout.is_passed_by_reference(env.target_info) { if return_layout.is_passed_by_reference(env.layout_interner, env.target_info) {
let align_bytes = return_layout.alignment_bytes(env.target_info); let align_bytes = return_layout.alignment_bytes(env.layout_interner, env.target_info);
if align_bytes > 0 { if align_bytes > 0 {
let size = env let size = env.ptr_int().const_int(
.ptr_int() return_layout.stack_size(env.layout_interner, env.target_info) as u64,
.const_int(return_layout.stack_size(env.target_info) as u64, false); false,
);
env.builder env.builder
.build_memcpy( .build_memcpy(
@ -4772,7 +4874,7 @@ fn build_closure_caller<'a, 'ctx, 'env>(
env, env,
def_name, def_name,
alias_symbol, 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); debug_assert_eq!(roc_function.get_call_conventions(), FAST_CALL_CONV);
call.set_call_convention(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() result_alloca.into()
} else { } else {
env.builder env.builder
@ -5124,8 +5226,11 @@ fn roc_function_call<'a, 'ctx, 'env>(
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();
let inc_closure_data = let inc_closure_data = build_inc_n_wrapper(
build_inc_n_wrapper(env, layout_ids, &lambda_set.runtime_representation()) env,
layout_ids,
&lambda_set.runtime_representation(env.layout_interner),
)
.as_global_value() .as_global_value()
.as_pointer_value(); .as_pointer_value();
@ -5778,9 +5883,42 @@ fn run_low_level<'a, 'ctx, 'env>(
// Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 } // Str.getScalarUnsafe : Str, Nat -> { bytesParsed : Nat, scalar : U32 }
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
use roc_target::OperatingSystem::*;
let string = load_symbol(scope, &args[0]); let string = load_symbol(scope, &args[0]);
let index = load_symbol(scope, &args[1]); 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( let result = call_str_bitcode_fn(
env, env,
&[string], &[string],
@ -5789,7 +5927,7 @@ fn run_low_level<'a, 'ctx, 'env>(
bitcode::STR_GET_SCALAR_UNSAFE, 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() { match env.target_info.ptr_width() {
PtrWidth::Bytes8 => result, PtrWidth::Bytes8 => result,
PtrWidth::Bytes4 => { PtrWidth::Bytes4 => {
@ -5798,6 +5936,9 @@ fn run_low_level<'a, 'ctx, 'env>(
} }
} }
} }
Wasi => unimplemented!(),
}
}
StrCountUtf8Bytes => { StrCountUtf8Bytes => {
// Str.countUtf8Bytes : Str -> Nat // Str.countUtf8Bytes : Str -> Nat
debug_assert_eq!(args.len(), 1); debug_assert_eq!(args.len(), 1);
@ -6466,7 +6607,7 @@ fn to_cc_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>, layout: &Layout<'a>,
) -> BasicTypeEnum<'ctx> { ) -> BasicTypeEnum<'ctx> {
match layout.runtime_representation() { match layout.runtime_representation(env.layout_interner) {
Layout::Builtin(builtin) => to_cc_type_builtin(env, &builtin), Layout::Builtin(builtin) => to_cc_type_builtin(env, &builtin),
layout => { layout => {
// TODO this is almost certainly incorrect for bigger structs // 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 { enum RocReturn {
/// Return as normal /// Return as normal
Return, Return,
@ -6508,7 +6649,11 @@ enum RocReturn {
} }
impl 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 { match layout {
Layout::Builtin(builtin) => { Layout::Builtin(builtin) => {
use Builtin::*; use Builtin::*;
@ -6523,15 +6668,17 @@ impl RocReturn {
} }
} }
Layout::Union(UnionLayout::NonRecursive(_)) => true, Layout::Union(UnionLayout::NonRecursive(_)) => true,
Layout::LambdaSet(lambda_set) => { Layout::LambdaSet(lambda_set) => RocReturn::roc_return_by_pointer(
RocReturn::roc_return_by_pointer(target_info, lambda_set.runtime_representation()) interner,
} target_info,
lambda_set.runtime_representation(interner),
),
_ => false, _ => false,
} }
} }
fn from_layout<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) -> Self { 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 RocReturn::ByPointer
} else { } else {
RocReturn::Return 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? /// 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 { 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 return_size = layout.stack_size(env.layout_interner, env.target_info);
let pass_result_by_pointer = return_size > 2 * env.target_info.ptr_width() as u32; 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 { if return_size == 0 {
CCReturn::Void CCReturn::Void
@ -6830,7 +6983,16 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
builder.build_return(Some(&return_value)); builder.build_return(Some(&return_value));
} }
RocReturn::ByPointer => { 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); 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>( fn dec_binop_with_overflow<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
fn_name: &str, fn_name: &str,
lhs: BasicValueEnum<'ctx>, lhs: BasicValueEnum<'ctx>,
rhs: BasicValueEnum<'ctx>, rhs: BasicValueEnum<'ctx>,
) -> StructValue<'ctx> { ) -> StructValue<'ctx> {
use roc_target::OperatingSystem::*;
let lhs = lhs.into_int_value(); let lhs = lhs.into_int_value();
let rhs = rhs.into_int_value(); let rhs = rhs.into_int_value();
let return_type = zig_with_overflow_roc_dec(env); let return_type = zig_with_overflow_roc_dec(env);
let return_alloca = env.builder.build_alloca(return_type, "return_alloca"); let return_alloca = env.builder.build_alloca(return_type, "return_alloca");
let int_64 = env.context.i128_type().const_int(64, false); match env.target_info.operating_system {
let int_64_type = env.context.i64_type(); Windows => {
call_void_bitcode_fn(
let lhs1 = env env,
.builder &[
.build_right_shift(lhs, int_64, false, "lhs_left_bits"); return_alloca.into(),
let rhs1 = env dec_alloca(env, lhs).into(),
.builder dec_alloca(env, rhs).into(),
.build_right_shift(rhs, int_64, false, "rhs_left_bits"); ],
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( call_void_bitcode_fn(
env, env,
&[ &[
return_alloca.into(), return_alloca.into(),
env.builder.build_int_cast(lhs, int_64_type, "").into(), lhs_low.into(),
env.builder.build_int_cast(lhs1, int_64_type, "").into(), lhs_high.into(),
env.builder.build_int_cast(rhs, int_64_type, "").into(), rhs_low.into(),
env.builder.build_int_cast(rhs1, int_64_type, "").into(), rhs_high.into(),
], ],
fn_name, fn_name,
); );
}
Wasi => unimplemented!(),
}
env.builder env.builder
.build_load(return_alloca, "load_dec") .build_load(return_alloca, "load_dec")
@ -7313,30 +7559,38 @@ pub fn dec_binop_with_unchecked<'a, 'ctx, 'env>(
lhs: BasicValueEnum<'ctx>, lhs: BasicValueEnum<'ctx>,
rhs: BasicValueEnum<'ctx>, rhs: BasicValueEnum<'ctx>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use roc_target::OperatingSystem::*;
let lhs = lhs.into_int_value(); let lhs = lhs.into_int_value();
let rhs = rhs.into_int_value(); let rhs = rhs.into_int_value();
let int_64 = env.context.i128_type().const_int(64, false); match env.target_info.operating_system {
let int_64_type = env.context.i64_type(); Windows => {
// windows is much nicer for us here
let lhs1 = env call_bitcode_fn(
.builder env,
.build_right_shift(lhs, int_64, false, "lhs_left_bits"); &[dec_alloca(env, lhs).into(), dec_alloca(env, rhs).into()],
let rhs1 = env fn_name,
.builder )
.build_right_shift(rhs, int_64, false, "rhs_left_bits"); }
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( call_bitcode_fn(
env, env,
&[ &[
env.builder.build_int_cast(lhs, int_64_type, "").into(), lhs_low.into(),
env.builder.build_int_cast(lhs1, int_64_type, "").into(), lhs_high.into(),
env.builder.build_int_cast(rhs, int_64_type, "").into(), rhs_low.into(),
env.builder.build_int_cast(rhs1, int_64_type, "").into(), rhs_high.into(),
], ],
fn_name, fn_name,
) )
} }
Wasi => unimplemented!(),
}
}
fn build_dec_binop<'a, 'ctx, 'env>( fn build_dec_binop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,

View file

@ -82,7 +82,10 @@ pub(crate) fn layout_width<'a, 'ctx, 'env>(
layout: &Layout<'a>, layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
env.ptr_int() 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() .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 // 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. // 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 element_first = element_align > env.target_info.ptr_width() as u32;
let fields = if element_first { let fields = if element_first {
@ -715,13 +718,13 @@ pub(crate) fn allocate_list<'a, 'ctx, 'env>(
let builder = env.builder; let builder = env.builder;
let len_type = env.ptr_int(); 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 bytes_per_element = len_type.const_int(elem_bytes, false);
let number_of_data_bytes = let number_of_data_bytes =
builder.build_int_mul(bytes_per_element, number_of_elements, "data_length"); builder.build_int_mul(bytes_per_element, number_of_elements, "data_length");
let basic_type = basic_type_from_layout(env, elem_layout); 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) allocate_with_refcount_help(env, basic_type, alignment_bytes, number_of_data_bytes)
} }

View file

@ -46,30 +46,6 @@ pub(crate) fn decode_from_utf8_result<'a, 'ctx, 'env>(
} }
/// Dec.toStr : Dec -> Str /// 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 /// Str.equal : Str, Str -> Bool
pub(crate) fn str_equal<'a, 'ctx, 'env>( pub(crate) fn str_equal<'a, 'ctx, 'env>(

View file

@ -5,7 +5,7 @@ use inkwell::types::{BasicType, BasicTypeEnum, FloatType, IntType, StructType};
use inkwell::values::StructValue; use inkwell::values::StructValue;
use inkwell::AddressSpace; use inkwell::AddressSpace;
use roc_builtins::bitcode::{FloatWidth, IntWidth}; 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; use roc_target::TargetInfo;
fn basic_type_from_record<'a, 'ctx, 'env>( 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, field_layouts: sorted_fields,
.. ..
} => basic_type_from_record(env, 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) => { Boxed(inner_layout) => {
let inner_type = basic_type_from_layout(env, 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 { match union_layout {
NonRecursive(tags) => { 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() .struct_type()
.into() .into()
} }
@ -69,29 +71,45 @@ pub fn basic_type_from_union_layout<'a, 'ctx, 'env>(
other_tags: tags, .. other_tags: tags, ..
} => { } => {
if union_layout.stores_tag_id_as_data(env.target_info) { 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() .struct_type()
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)
.into() .into()
} else { } 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() .struct_type()
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)
.into() .into()
} }
} }
NullableUnwrapped { other_fields, .. } => { NullableUnwrapped { other_fields, .. } => RocUnion::untagged_from_slices(
RocUnion::untagged_from_slices(env.context, &[other_fields], env.target_info) env.layout_interner,
env.context,
&[other_fields],
env.target_info,
)
.struct_type() .struct_type()
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)
.into() .into(),
} NonNullableUnwrapped(fields) => RocUnion::untagged_from_slices(
NonNullableUnwrapped(fields) => { env.layout_interner,
RocUnion::untagged_from_slices(env.context, &[fields], env.target_info) env.context,
&[fields],
env.target_info,
)
.struct_type() .struct_type()
.ptr_type(AddressSpace::Generic) .ptr_type(AddressSpace::Generic)
.into() .into(),
}
} }
} }
@ -132,13 +150,13 @@ pub fn argument_type_from_layout<'a, 'ctx, 'env>(
match layout { match layout {
LambdaSet(lambda_set) => { 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), Union(union_layout) => argument_type_from_union_layout(env, union_layout),
Builtin(_) => { Builtin(_) => {
let base = basic_type_from_layout(env, layout); 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() base.ptr_type(AddressSpace::Generic).into()
} else { } else {
base base
@ -275,6 +293,7 @@ impl<'ctx> RocUnion<'ctx> {
} }
pub fn tagged_from_slices( pub fn tagged_from_slices(
interner: &STLayoutInterner,
context: &'ctx Context, context: &'ctx Context,
layouts: &[&[Layout<'_>]], layouts: &[&[Layout<'_>]],
target_info: TargetInfo, target_info: TargetInfo,
@ -285,18 +304,19 @@ impl<'ctx> RocUnion<'ctx> {
}; };
let (data_width, data_align) = 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)) Self::new(context, target_info, data_align, data_width, Some(tag_type))
} }
pub fn untagged_from_slices( pub fn untagged_from_slices(
interner: &STLayoutInterner,
context: &'ctx Context, context: &'ctx Context,
layouts: &[&[Layout<'_>]], layouts: &[&[Layout<'_>]],
target_info: TargetInfo, target_info: TargetInfo,
) -> Self { ) -> Self {
let (data_width, data_align) = 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) 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() 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> { pub fn zig_has_tag_id_type<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> StructType<'ctx> {
let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::Generic); let u8_ptr_t = env.context.i8_type().ptr_type(AddressSpace::Generic);

View file

@ -137,9 +137,10 @@ pub(crate) fn clone_to_shared_memory<'a, 'ctx, 'env>(
let (value, layout) = load_symbol_and_layout(scope, lookup); let (value, layout) = load_symbol_and_layout(scope, lookup);
let stack_size = env let stack_size = env.ptr_int().const_int(
.ptr_int() layout.stack_size(env.layout_interner, env.target_info) as u64,
.const_int(layout.stack_size(env.target_info) as u64, false); false,
);
let mut extra_offset = env.builder.build_int_add(offset, stack_size, "offset"); 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, 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) => { Layout::Union(union_layout) => {
if layout.safe_to_memcpy() { if layout.safe_to_memcpy(env.layout_interner) {
let ptr = unsafe { let ptr = unsafe {
env.builder env.builder
.build_in_bounds_gep(ptr, &[cursors.offset], "at_current_offset") .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 source = value.into_pointer_value();
let value = load_roc_value(env, *inner_layout, source, "inner"); let value = load_roc_value(env, *inner_layout, source, "inner");
let inner_width = env let inner_width = env.ptr_int().const_int(
.ptr_int() inner_layout.stack_size(env.layout_interner, env.target_info) as u64,
.const_int(inner_layout.stack_size(env.target_info) as u64, false); false,
);
let new_extra = env let new_extra = env
.builder .builder
@ -318,7 +322,7 @@ fn build_clone_struct<'a, 'ctx, 'env>(
) -> IntValue<'ctx> { ) -> IntValue<'ctx> {
let layout = Layout::struct_no_name_order(field_layouts); 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) build_copy(env, ptr, cursors.offset, value)
} else { } else {
let mut cursors = cursors; let mut cursors = cursors;
@ -343,9 +347,10 @@ fn build_clone_struct<'a, 'ctx, 'env>(
when_recursive, when_recursive,
); );
let field_width = env let field_width = env.ptr_int().const_int(
.ptr_int() field_layout.stack_size(env.layout_interner, env.target_info) as u64,
.const_int(field_layout.stack_size(env.target_info) as u64, false); false,
);
cursors.extra_offset = new_extra; cursors.extra_offset = new_extra;
cursors.offset = env 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 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 { let cursors = Cursors {
offset: extra_offset, offset: extra_offset,
@ -618,7 +624,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let layout = Layout::struct_no_name_order(fields); let layout = Layout::struct_no_name_order(fields);
let basic_type = basic_type_from_layout(env, &layout); 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 { let cursors = Cursors {
offset: extra_offset, offset: extra_offset,
@ -686,7 +693,8 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
let layout = Layout::struct_no_name_order(fields); let layout = Layout::struct_no_name_order(fields);
let basic_type = basic_type_from_layout(env, &layout); 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 { let cursors = Cursors {
offset: extra_offset, offset: extra_offset,
@ -776,8 +784,10 @@ fn build_clone_tag_help<'a, 'ctx, 'env>(
offset: extra_offset, offset: extra_offset,
extra_offset: env.builder.build_int_add( extra_offset: env.builder.build_int_add(
extra_offset, extra_offset,
env.ptr_int() env.ptr_int().const_int(
.const_int(layout.stack_size(env.target_info) as _, false), layout.stack_size(env.layout_interner, env.target_info) as _,
false,
),
"new_offset", "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());
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 element_width = env.ptr_int().const_int(element_width as _, false);
let elements_width = bd.build_int_mul(element_width, len, "elements_width"); 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 // NOTE we are not actually sure the dest is properly aligned
let dest = pointer_at_offset(bd, ptr, offset); let dest = pointer_at_offset(bd, ptr, offset);
let src = bd.build_pointer_cast( 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") bd.build_int_add(offset, elements_width, "new_offset")
} else { } 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 element_type = basic_type_from_layout(env, elem);
let elements = bd.build_pointer_cast( 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 // if the element has any pointers, we clone them to this offset
let rest_offset = bd.build_alloca(env.ptr_int(), "rest_offset"); let rest_offset = bd.build_alloca(env.ptr_int(), "rest_offset");
let element_stack_size = env let element_stack_size = env.ptr_int().const_int(
.ptr_int() elem.stack_size(env.layout_interner, env.target_info) as u64,
.const_int(elem.stack_size(env.target_info) as u64, false); false,
);
let rest_start_offset = bd.build_int_add( let rest_start_offset = bd.build_int_add(
cursors.extra_offset, cursors.extra_offset,
bd.build_int_mul(len, element_stack_size, "elements_width"), bd.build_int_mul(len, element_stack_size, "elements_width"),

View file

@ -16,9 +16,9 @@ use inkwell::values::{
use inkwell::{AddressSpace, IntPredicate}; use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns; use roc_module::symbol::Interns;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout}; use roc_mono::layout::{Builtin, Layout, LayoutIds, 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}; use super::convert::{argument_type_from_layout, argument_type_from_union_layout};
pub struct PointerToRefcount<'ctx> { 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>) { pub fn decrement<'a, 'env>(&self, env: &Env<'a, 'ctx, 'env>, layout: &Layout<'a>) {
let alignment = layout 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); .max(env.target_info.ptr_width() as u32);
let context = env.context; let context = env.context;
@ -332,7 +332,7 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
let wrapper_struct = arg_val.into_struct_value(); let wrapper_struct = arg_val.into_struct_value();
for (i, field_layout) in layouts.iter().enumerate() { 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 let raw_value = env
.builder .builder
.build_extract_value(wrapper_struct, i as u32, "decrement_struct_field") .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>, call_mode: CallMode<'ctx>,
value: BasicValueEnum<'ctx>, value: BasicValueEnum<'ctx>,
) -> inkwell::values::CallSiteValue<'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 { let call = match call_mode {
CallMode::Inc(inc_amount) => { CallMode::Inc(inc_amount) => {
env.builder env.builder
@ -613,7 +619,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
layout_ids, layout_ids,
mode, mode,
when_recursive, 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); 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 ptr_type = basic_type_from_layout(env, element_layout).ptr_type(AddressSpace::Generic);
let (len, ptr) = load_list(env.builder, original_wrapper, ptr_type); 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 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 env.builder
.build_load(arg_val.into_pointer_value(), "load_str_to_stack") .build_load(arg_val.into_pointer_value(), "load_str_to_stack")
} else { } else {
@ -1178,10 +1186,10 @@ enum DecOrReuse {
Reuse, Reuse,
} }
fn fields_need_no_refcounting(field_layouts: &[Layout]) -> bool { fn fields_need_no_refcounting(interner: &STLayoutInterner, field_layouts: &[Layout]) -> bool {
!field_layouts !field_layouts
.iter() .iter()
.any(|x| x.is_refcounted() || x.contains_refcounted()) .any(|x| x.is_refcounted() || x.contains_refcounted(interner))
} }
#[allow(clippy::too_many_arguments)] #[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() { for (tag_id, field_layouts) in tags.iter().enumerate() {
// if none of the fields are or contain anything refcounted, just move on // 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; 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); let recursive_field_ptr = cast_basic_basic(env.builder, ptr_as_i64_ptr, union_type);
deferred_rec.push(recursive_field_ptr); 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 let elem_pointer = env
.builder .builder
.build_struct_gep(struct_ptr, i as u32, "gep_recursive_pointer") .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 none of the fields are or contain anything refcounted, just move on
if !field_layouts if !field_layouts
.iter() .iter()
.any(|x| x.is_refcounted() || x.contains_refcounted()) .any(|x| x.is_refcounted() || x.contains_refcounted(env.layout_interner))
{ {
continue; continue;
} }
@ -1678,13 +1686,14 @@ fn modify_refcount_union_help<'a, 'ctx, 'env>(
recursive_ptr_field_value, recursive_ptr_field_value,
&Layout::RecursivePointer, &Layout::RecursivePointer,
) )
} else if field_layout.contains_refcounted() { } else if field_layout.contains_refcounted(env.layout_interner) {
let field_ptr = env let field_ptr = env
.builder .builder
.build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field") .build_struct_gep(cast_tag_data_pointer, i as u32, "modify_tag_field")
.unwrap(); .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() field_ptr.into()
} else { } else {
env.builder.build_load(field_ptr, "field_value") env.builder.build_load(field_ptr, "field_value")

View file

@ -162,7 +162,7 @@ macro_rules! run_jit_function_dynamic_type {
// first field is a char pointer (to the error message) // first field is a char pointer (to the error message)
// read value, and transmute to a pointer // read value, and transmute to a pointer
let ptr_as_int = *(result as *const u64).offset(1); 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) // make CString (null-terminated)
let raw = CString::from_raw(ptr); let raw = CString::from_raw(ptr);

View file

@ -7,9 +7,10 @@ license = "UPL-1.0"
[dependencies] [dependencies]
bitvec = "1" bitvec = "1"
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_intern = { path = "../intern" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }

View file

@ -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. 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. 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" app "test" provides [main] to "./platform"
main = main =
1 + 2 + 4 1 + 2 + 4
``` ```
### Direct translation of Mono IR ### Direct translation of Mono IR
The Mono IR contains two functions, `Num.add` and `main`, so we generate two corresponding WebAssembly functions. 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. 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 (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 0 ; load param 0 stack=[param0]
local.get 1 ; load param 1 stack=[param0, param1] 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. 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.) (It also doesn't need the `return` instructions, but that's less of a problem.)
``` ```text
(func (;0;) (param i64 i64) (result i64) (func (;0;) (param i64 i64) (result i64)
local.get 0 local.get 0
local.get 1 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. 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 ├──────┐ │ ├─────► 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. 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: 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. - 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 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. - 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. - 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: 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. - 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. - 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. - Insert an internally-defined dummy function at the index where the last JavaScript import used to be.

View file

@ -400,7 +400,7 @@ impl<'a> WasmBackend<'a> {
fn start_proc(&mut self, proc: &Proc<'a>) { fn start_proc(&mut self, proc: &Proc<'a>) {
use ReturnMethod::*; 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) { let ret_type = match ret_layout.return_method(CallConv::C) {
Primitive(ty, _) => Some(ty), Primitive(ty, _) => Some(ty),
@ -418,8 +418,12 @@ impl<'a> WasmBackend<'a> {
// We never use the `return` instruction. Instead, we break from this block. // We never use the `return` instruction. Instead, we break from this block.
self.start_block(); self.start_block();
self.storage self.storage.allocate_args(
.allocate_args(proc.args, &mut self.code_builder, self.env.arena); self.env.layout_interner,
proc.args,
&mut self.code_builder,
self.env.arena,
);
if let Some(ty) = ret_type { if let Some(ty) = ret_type {
let ret_var = self.storage.create_anonymous_local(ty); 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 // 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 heap_return_ptr_id = LocalId(wrapper_arg_layouts.len() as u32 - 1);
let inner_ret_layout = match wrapper_arg_layouts.last() { 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), x => internal_error!("Higher-order wrapper: invalid return layout {:?}", x),
}; };
@ -527,7 +531,7 @@ impl<'a> WasmBackend<'a> {
Layout::Boxed(inner) => inner, Layout::Boxed(inner) => inner,
x => internal_error!("Expected a Boxed layout, got {:?}", x), 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; 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 // If the inner function has closure data, it's the last arg of the inner fn
let closure_data_layout = wrapper_arg_layouts[0]; 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 // The closure data exists, and will have been passed in to the wrapper as a
// one-element struct. // one-element struct.
let inner_closure_data_layout = match closure_data_layout { 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 value_layout = wrapper_proc_layout.arguments[1];
let mut n_inner_args = 2; 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)); self.code_builder.get_local(LocalId(0));
n_inner_args += 1; n_inner_args += 1;
} }
@ -760,7 +764,9 @@ impl<'a> WasmBackend<'a> {
expr: &Expr<'a>, expr: &Expr<'a>,
kind: StoredVarKind, 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); 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 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 // then, we jump whenever the value under scrutiny is equal to the value of a branch
for (i, (value, _, _)) in branches.iter().enumerate() { 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); let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena);
for parameter in parameters.iter() { for parameter in parameters.iter() {
let mut param_storage = self.storage.allocate_var( let mut param_storage = self.storage.allocate_var(
self.env.layout_interner,
parameter.layout, parameter.layout,
parameter.symbol, parameter.symbol,
StoredVarKind::Variable, StoredVarKind::Variable,
@ -961,7 +969,11 @@ impl<'a> WasmBackend<'a> {
if false { if false {
self.register_symbol_debug_names(); 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 // If any new specializations were created, register their symbol data
@ -1233,7 +1245,7 @@ impl<'a> WasmBackend<'a> {
ret_layout, ret_layout,
} => { } => {
let name = foreign_symbol.as_str(); 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) = let (num_wasm_args, has_return_val, ret_zig_packed_struct) =
self.storage.load_symbols_for_call( self.storage.load_symbols_for_call(
self.env.arena, self.env.arena,
@ -1258,7 +1270,7 @@ impl<'a> WasmBackend<'a> {
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
ret_storage: &StoredValue, 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 this function is just a lowlevel wrapper, then inline it
if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) = if let LowLevelWrapperType::CanBeReplacedBy(lowlevel) =
@ -1414,9 +1426,12 @@ impl<'a> WasmBackend<'a> {
} }
}; };
} }
Layout::LambdaSet(lambdaset) => { Layout::LambdaSet(lambdaset) => self.expr_struct(
self.expr_struct(sym, &lambdaset.runtime_representation(), storage, fields) sym,
} &lambdaset.runtime_representation(self.env.layout_interner),
storage,
fields,
),
_ => { _ => {
if !fields.is_empty() { if !fields.is_empty() {
// Struct expression but not Struct layout => single element. Copy it. // 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) { 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 self.storage
.copy_value_from_memory(&mut self.code_builder, sym, from_addr_val, offset); .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>], elems: &'a [ListLiteralElement<'a>],
) { ) {
if let StoredValue::StackMemory { location, .. } = storage { 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 // Allocate heap space and store its address in a local variable
let heap_local_id = self.storage.create_anonymous_local(PTR_TYPE); 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.allocate_with_refcount(Some(size), heap_alignment, 1);
self.code_builder.set_local(heap_local_id); 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_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 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 // We're going to use the pointer many times, so put it in a local variable
let stored_with_local = let stored_with_local =
@ -1635,7 +1652,10 @@ impl<'a> WasmBackend<'a> {
// Store the tag ID (if any) // Store the tag ID (if any)
if stores_tag_id_as_data { 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 = union_layout.discriminant().alignment_bytes();
let id_align = Align::from(id_align); 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) { 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 = union_layout.discriminant().alignment_bytes();
let id_align = Align::from(id_align); let id_align = Align::from(id_align);
@ -1778,7 +1800,7 @@ impl<'a> WasmBackend<'a> {
let field_offset: u32 = field_layouts let field_offset: u32 = field_layouts
.iter() .iter()
.take(index as usize) .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(); .sum();
// Get pointer and offset to the tag's data // Get pointer and offset to the tag's data
@ -1844,7 +1866,8 @@ impl<'a> WasmBackend<'a> {
Layout::Boxed(arg) => *arg, Layout::Boxed(arg) => *arg,
_ => internal_error!("ExprBox should always produce a Boxed layout"), _ => 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); self.allocate_with_refcount(Some(size), alignment, 1);
// store the pointer value from the value stack into the local variable // store the pointer value from the value stack into the local variable

View file

@ -1,5 +1,5 @@
use roc_builtins::bitcode::{FloatWidth, IntWidth}; 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::wasm_module::ValueType;
use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO}; use crate::{PTR_SIZE, PTR_TYPE, TARGET_INFO};
@ -41,12 +41,12 @@ pub enum WasmLayout {
} }
impl 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 roc_mono::layout::Builtin::*;
use UnionLayout::*; use UnionLayout::*;
use ValueType::*; 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 { match layout {
Layout::Builtin(Int(int_width)) => { Layout::Builtin(Int(int_width)) => {
@ -85,7 +85,9 @@ impl WasmLayout {
format: StackMemoryFormat::Decimal, 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::Builtin(Str | List(_))
| Layout::Struct { .. } | Layout::Struct { .. }

View file

@ -16,7 +16,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::CodeGenHelp; use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{Proc, ProcLayout}; use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds; use roc_mono::layout::{LayoutIds, STLayoutInterner};
use roc_target::TargetInfo; use roc_target::TargetInfo;
use wasm_module::parse::ParseError; use wasm_module::parse::ParseError;
@ -43,6 +43,7 @@ pub const STACK_POINTER_NAME: &str = "__stack_pointer";
pub struct Env<'a> { pub struct Env<'a> {
pub arena: &'a Bump, pub arena: &'a Bump,
pub layout_interner: &'a STLayoutInterner<'a>,
pub module_id: ModuleId, pub module_id: ModuleId,
pub exposed_to_host: MutSet<Symbol>, pub exposed_to_host: MutSet<Symbol>,
pub stack_bytes: u32, pub stack_bytes: u32,
@ -131,13 +132,18 @@ pub fn build_app_module<'a>(
host_to_app_map, host_to_app_map,
host_module, host_module,
fn_index_offset, 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 { if DEBUG_SETTINGS.user_procs_ir {
println!("## procs"); println!("## procs");
for proc in procs.iter() { for proc in procs.iter() {
println!("{}", proc.to_pretty(200)); println!("{}", proc.to_pretty(env.layout_interner, 200));
// println!("{:?}", proc); // println!("{:?}", proc);
} }
} }
@ -155,7 +161,7 @@ pub fn build_app_module<'a>(
if DEBUG_SETTINGS.helper_procs_ir { if DEBUG_SETTINGS.helper_procs_ir {
println!("## helper_procs"); println!("## helper_procs");
for proc in helper_procs.iter() { for proc in helper_procs.iter() {
println!("{}", proc.to_pretty(200)); println!("{}", proc.to_pretty(env.layout_interner, 200));
// println!("{:#?}", proc); // println!("{:#?}", proc);
} }
} }

View file

@ -149,7 +149,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
self.arguments, self.arguments,
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
) )
} }
@ -283,7 +283,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
self.arguments, self.arguments,
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
); );
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE); backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
@ -358,7 +358,9 @@ impl<'a> LowLevelCall<'a> {
backend backend
.storage .storage
.load_symbols(&mut backend.code_builder, &[index]); .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_const(elem_size as i32);
backend.code_builder.i32_mul(); // index*size backend.code_builder.i32_mul(); // index*size
@ -415,15 +417,16 @@ impl<'a> LowLevelCall<'a> {
.. ..
} if value_layout == *list_elem => { } if value_layout == *list_elem => {
let list_offset = 0; let list_offset = 0;
let elem_offset = let elem_offset = Layout::Builtin(Builtin::List(list_elem))
Layout::Builtin(Builtin::List(list_elem)).stack_size(TARGET_INFO); .stack_size(backend.env.layout_interner, TARGET_INFO);
(list_offset, elem_offset, value_layout) (list_offset, elem_offset, value_layout)
} }
Layout::Struct { Layout::Struct {
field_layouts: &[value_layout, Layout::Builtin(Builtin::List(list_elem))], field_layouts: &[value_layout, Layout::Builtin(Builtin::List(list_elem))],
.. ..
} if value_layout == *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; let elem_offset = 0;
(list_offset, elem_offset, value_layout) (list_offset, elem_offset, value_layout)
} }
@ -431,7 +434,7 @@ impl<'a> LowLevelCall<'a> {
}; };
let (elem_width, elem_alignment) = 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 // Ensure the new element is stored in memory so we can pass a pointer to Zig
let (new_elem_local, new_elem_offset, _) = let (new_elem_local, new_elem_offset, _) =
@ -480,7 +483,8 @@ impl<'a> LowLevelCall<'a> {
let capacity: Symbol = self.arguments[0]; let capacity: Symbol = self.arguments[0];
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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 // Zig arguments Wasm types
// (return pointer) i32 // (return pointer) i32
@ -511,13 +515,14 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
self.arguments, self.arguments,
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
); );
// Load monomorphization constants // Load monomorphization constants
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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_align as i32);
backend.code_builder.i32_const(elem_width 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 spare: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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( let (spare_local, spare_offset, _) = ensure_symbol_is_in_memory(
backend, backend,
spare, spare,
@ -553,7 +559,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
&[list], &[list],
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
); );
@ -579,7 +585,7 @@ impl<'a> LowLevelCall<'a> {
let elem: Symbol = self.arguments[1]; let elem: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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, _) = let (elem_local, elem_offset, _) =
ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena); ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena);
@ -595,7 +601,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
&[list], &[list],
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
); );
@ -616,7 +622,8 @@ impl<'a> LowLevelCall<'a> {
let elem: Symbol = self.arguments[1]; let elem: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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, _) = let (elem_local, elem_offset, _) =
ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena); ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena);
@ -633,7 +640,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
&[list], &[list],
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
); );
@ -657,7 +664,8 @@ impl<'a> LowLevelCall<'a> {
let len: Symbol = self.arguments[2]; let len: Symbol = self.arguments[2];
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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 // The refcount function receives a pointer to an element in the list
// This is the same as a Struct containing the element // This is the same as a Struct containing the element
@ -682,7 +690,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
&[list], &[list],
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
); );
@ -701,7 +709,8 @@ impl<'a> LowLevelCall<'a> {
let drop_index: Symbol = self.arguments[1]; let drop_index: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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 // The refcount function receives a pointer to an element in the list
// This is the same as a Struct containing the element // This is the same as a Struct containing the element
@ -726,7 +735,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
&[list], &[list],
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, CallConv::Zig,
); );
@ -746,7 +755,8 @@ impl<'a> LowLevelCall<'a> {
let index_2: Symbol = self.arguments[2]; let index_2: Symbol = self.arguments[2];
let elem_layout = unwrap_list_elem_layout(self.ret_layout); 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 // Zig arguments Wasm types
// (return pointer) i32 // (return pointer) i32
@ -763,7 +773,7 @@ impl<'a> LowLevelCall<'a> {
&mut backend.code_builder, &mut backend.code_builder,
&[list], &[list],
self.ret_symbol, self.ret_symbol,
&WasmLayout::new(&self.ret_layout), &WasmLayout::new(backend.env.layout_interner, &self.ret_layout),
CallConv::Zig, 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. // 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. // 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. // 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) { if bit_width < 32 && !symbol_is_signed_int(backend, num) {
// Sign-extend the number by shifting left and right again // Sign-extend the number by shifting left and right again
backend 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. // 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. // 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. // 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) { if bit_width < 32 && symbol_is_signed_int(backend, bits) {
let mask = (1 << bit_width) - 1; let mask = (1 << bit_width) - 1;
@ -1861,10 +1876,10 @@ impl<'a> LowLevelCall<'a> {
/// Equality and inequality /// Equality and inequality
/// These can operate on any data type (except functions) so they're more complex than other operators. /// 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>) { fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) {
let arg_layout = let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]]
backend.storage.symbol_layouts[&self.arguments[0]].runtime_representation(); .runtime_representation(backend.env.layout_interner);
let other_arg_layout = let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]]
backend.storage.symbol_layouts[&self.arguments[1]].runtime_representation(); .runtime_representation(backend.env.layout_interner);
debug_assert!( debug_assert!(
arg_layout == other_arg_layout, arg_layout == other_arg_layout,
"Cannot do `==` comparison on different types: {:?} vs {:?}", "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) = let (closure_data_layout, closure_data_exists) =
match backend.storage.symbol_layouts[captured_environment] { match backend.storage.symbol_layouts[captured_environment] {
Layout::LambdaSet(lambda_set) => { Layout::LambdaSet(lambda_set) => {
if lambda_set.is_represented().is_some() { if lambda_set
(lambda_set.runtime_representation(), true) .is_represented(backend.env.layout_interner)
.is_some()
{
(
lambda_set.runtime_representation(backend.env.layout_interner),
true,
)
} else { } else {
// Closure data is a lambda set, which *itself* has no closure data! // 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 // 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 // make sure that the wrapping struct is available in stack memory, so we can hand out a
// pointer to it. // pointer to it.
let wrapped_storage = backend.storage.allocate_var( let wrapped_storage = backend.storage.allocate_var(
backend.env.layout_interner,
wrapped_captures_layout, wrapped_captures_layout,
wrapped_closure_data_sym, wrapped_closure_data_sym,
crate::storage::StoredVarKind::Variable, crate::storage::StoredVarKind::Variable,
@ -2323,7 +2345,8 @@ pub fn call_higher_order_lowlevel<'a>(
ListSortWith { xs } => { ListSortWith { xs } => {
let elem_layout = unwrap_list_elem_layout(backend.storage.symbol_layouts[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; 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 = 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; 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(owns_captured_environment as i32);
cb.i32_const(elem_ret_align as i32); cb.i32_const(elem_ret_align as i32);
for el in arg_elem_layouts.iter() { 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); cb.i32_const(elem_ret_size as i32);
@ -2446,7 +2470,8 @@ fn ensure_symbol_is_in_memory<'a>(
(local, offset, layout) (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 let (frame_ptr, offset) = backend
.storage .storage
.allocate_anonymous_stack_memory(width, alignment); .allocate_anonymous_stack_memory(width, alignment);

View file

@ -4,7 +4,7 @@ use bumpalo::Bump;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_module::symbol::Symbol; 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::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout};
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; 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. /// They are allocated a certain offset and size in the stack frame.
pub fn allocate_var( pub fn allocate_var(
&mut self, &mut self,
interner: &STLayoutInterner<'a>,
layout: Layout<'a>, layout: Layout<'a>,
symbol: Symbol, symbol: Symbol,
kind: StoredVarKind, kind: StoredVarKind,
) -> StoredValue { ) -> StoredValue {
let wasm_layout = WasmLayout::new(&layout); let wasm_layout = WasmLayout::new(interner, &layout);
self.symbol_layouts.insert(symbol, layout); self.symbol_layouts.insert(symbol, layout);
let storage = match wasm_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. /// stack frame, because it's a lot easier to keep track of the data flow.
pub fn allocate_args( pub fn allocate_args(
&mut self, &mut self,
interner: &STLayoutInterner<'a>,
args: &[(Layout<'a>, Symbol)], args: &[(Layout<'a>, Symbol)],
code_builder: &mut CodeBuilder, code_builder: &mut CodeBuilder,
arena: &'a Bump, arena: &'a Bump,
@ -226,7 +228,7 @@ impl<'a> Storage<'a> {
for (layout, symbol) in args { for (layout, symbol) in args {
self.symbol_layouts.insert(*symbol, *layout); 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 local_index = self.arg_types.len() as u32;
let storage = match wasm_layout { let storage = match wasm_layout {

View file

@ -6,6 +6,7 @@ The user needs to analyse the Wasm module's memory to decode the result.
use bumpalo::{collections::Vec, Bump}; use bumpalo::{collections::Vec, Bump};
use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_intern::Interner;
use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout};
use roc_target::TargetInfo; use roc_target::TargetInfo;
@ -36,13 +37,14 @@ pub trait Wasm32Result {
/// Layout-driven wrapper generation /// Layout-driven wrapper generation
pub fn insert_wrapper_for_layout<'a>( pub fn insert_wrapper_for_layout<'a>(
arena: &'a Bump, arena: &'a Bump,
interner: &impl Interner<'a, Layout<'a>>,
module: &mut WasmModule<'a>, module: &mut WasmModule<'a>,
wrapper_name: &'static str, wrapper_name: &'static str,
main_fn_index: u32, main_fn_index: u32,
layout: &Layout<'a>, layout: &Layout<'a>,
) { ) {
let mut stack_data_structure = || { 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 { if size == 0 {
<() as Wasm32Result>::insert_wrapper(arena, module, wrapper_name, main_fn_index); <() as Wasm32Result>::insert_wrapper(arena, module, wrapper_name, main_fn_index);
} else { } else {

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

View 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]
}
}

View file

@ -14,4 +14,4 @@ roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" } roc_solve = { path = "../solve" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = { path = "../../error_macros" } roc_error_macros = { path = "../../error_macros" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }

View file

@ -16,11 +16,14 @@ use roc_solve::solve::Pools;
use roc_solve::specialize::{compact_lambda_sets_of_vars, DerivedEnv, Phase}; 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::{get_member_lambda_sets_at_region, Content, FlatType, LambdaSet};
use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable}; 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_solve::ability::Resolved;
pub use roc_types::subs::instantiate_rigids; pub use roc_types::subs::instantiate_rigids;
pub mod storage;
#[derive(Debug)] #[derive(Debug)]
pub struct UnificationFailed; 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. /// Unifies two variables and performs lambda set compaction.
/// Ranks and other ability demands are disregarded. /// Ranks and other ability demands are disregarded.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
@ -325,20 +351,25 @@ pub fn unify(
exposed_by_module: &ExposedByModule, exposed_by_module: &ExposedByModule,
left: Variable, left: Variable,
right: Variable, right: Variable,
) -> Result<(), UnificationFailed> { ) -> Result<Vec<Variable>, UnificationFailed> {
debug_assert_ne!( debug_assert_ne!(
home, home,
ModuleId::DERIVED_SYNTH, ModuleId::DERIVED_SYNTH,
"derived module can only unify its subs in its own context!" "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 { match unified {
Unified::Success { Unified::Success {
vars: _, vars: _,
must_implement_ability: _, must_implement_ability: _,
lambda_sets_to_specialize, lambda_sets_to_specialize,
extra_metadata: _, extra_metadata,
} => { } => {
let mut pools = Pools::default(); 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 // here. We only need it for `compact_lambda_sets_of_vars`, which is also used in a
// solving context where pools are relevant. // solving context where pools are relevant.
Ok(()) Ok(extra_metadata.changed)
} }
Unified::Failure(..) | Unified::BadType(..) => Err(UnificationFailed), Unified::Failure(..) | Unified::BadType(..) => Err(UnificationFailed),
} }

View 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();
}
}

View file

@ -7,7 +7,7 @@ edition = "2021"
[dependencies] [dependencies]
roc_load_internal = { path = "../load_internal" } 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_target = { path = "../roc_target" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
@ -20,7 +20,7 @@ roc_builtins = { path = "../builtins" }
roc_module = { path = "../module" } roc_module = { path = "../module" }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
roc_target = { path = "../roc_target" } 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] [target.'cfg(not(windows))'.build-dependencies]
roc_load_internal = { path = "../load_internal" } roc_load_internal = { path = "../load_internal" }

View file

@ -23,16 +23,18 @@ roc_solve = { path = "../solve" }
roc_solve_problem = { path = "../solve_problem" } roc_solve_problem = { path = "../solve_problem" }
roc_late_solve = { path = "../late_solve" } roc_late_solve = { path = "../late_solve" }
roc_mono = { path = "../mono" } roc_mono = { path = "../mono" }
roc_intern = { path = "../intern" }
roc_target = { path = "../roc_target" } roc_target = { path = "../roc_target" }
roc_tracing = { path = "../../tracing" }
roc_reporting = { path = "../../reporting" } roc_reporting = { path = "../../reporting" }
roc_debug_flags = { path = "../debug_flags" } roc_debug_flags = { path = "../debug_flags" }
ven_pretty = { path = "../../vendor/pretty" } ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] } bumpalo = { version = "3.11.0", features = ["collections"] }
parking_lot = "0.12" parking_lot = "0.12"
crossbeam = "0.8.2" crossbeam = "0.8.2"
[dev-dependencies] [dev-dependencies]
pretty_assertions = "1.0.0" pretty_assertions = "1.3.0"
maplit = "1.0.2" maplit = "1.0.2"
indoc = "1.0.7" indoc = "1.0.7"
roc_test_utils = { path = "../../test_utils" } roc_test_utils = { path = "../../test_utils" }

View file

@ -23,6 +23,7 @@ use roc_debug_flags::{
}; };
use roc_derive::SharedDerivedModule; use roc_derive::SharedDerivedModule;
use roc_error_macros::internal_error; use roc_error_macros::internal_error;
use roc_intern::{GlobalInterner, SingleThreadedInterner};
use roc_late_solve::{AbilitiesView, WorldAbilities}; use roc_late_solve::{AbilitiesView, WorldAbilities};
use roc_module::ident::{Ident, ModuleName, QualifiedModuleName}; use roc_module::ident::{Ident, ModuleName, QualifiedModuleName};
use roc_module::symbol::{ use roc_module::symbol::{
@ -33,7 +34,9 @@ use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase, CapturedSymbols, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs, ProcsBase,
UpdateModeIds, 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::ast::{self, Defs, ExtractSpaces, Spaced, StrLiteral, TypeAnnotation};
use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent}; use roc_parse::header::{ExposedName, ImportsEntry, PackageEntry, PlatformHeader, To, TypedIdent};
use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName}; use roc_parse::header::{HeaderFor, ModuleNameEnum, PackageName};
@ -461,9 +464,11 @@ fn start_phase<'a>(
let derived_module = SharedDerivedModule::clone(&state.derived_module); 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 { BuildTask::BuildPendingSpecializations {
layout_cache, layout_cache,
execution_mode: state.exec_mode,
module_id, module_id,
module_timing, module_timing,
solved_subs, solved_subs,
@ -475,6 +480,7 @@ fn start_phase<'a>(
// TODO: awful, how can we get rid of the clone? // TODO: awful, how can we get rid of the clone?
exposed_by_module: state.exposed_types.clone(), exposed_by_module: state.exposed_types.clone(),
derived_module, derived_module,
build_expects,
} }
} }
Phase::MakeSpecializations => { Phase::MakeSpecializations => {
@ -506,7 +512,7 @@ fn start_phase<'a>(
IdentIds::default(), IdentIds::default(),
Subs::default(), Subs::default(),
ProcsBase::default(), ProcsBase::default(),
LayoutCache::new(state.target_info), LayoutCache::new(state.layout_interner.fork(), state.target_info),
ModuleTiming::new(Instant::now()), ModuleTiming::new(Instant::now()),
) )
} else if state.make_specializations_pass.current_pass() == 1 { } else if state.make_specializations_pass.current_pass() == 1 {
@ -718,6 +724,7 @@ pub struct MonomorphizedModule<'a> {
pub module_id: ModuleId, pub module_id: ModuleId,
pub interns: Interns, pub interns: Interns,
pub subs: Subs, pub subs: Subs,
pub layout_interner: SingleThreadedInterner<'a, Layout<'a>>,
pub output_path: Box<Path>, pub output_path: Box<Path>,
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>, pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: MutMap<ModuleId, Vec<TypeError>>, pub type_problems: MutMap<ModuleId, Vec<TypeError>>,
@ -845,6 +852,9 @@ enum Msg<'a> {
/// all modules are now monomorphized, we are done /// all modules are now monomorphized, we are done
FinishedAllSpecialization { FinishedAllSpecialization {
subs: Subs, 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, 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 (used for builtin modules, could include packages in the future too)
cached_subs: CachedSubs, cached_subs: CachedSubs,
layout_interner: Arc<GlobalInterner<'a, Layout<'a>>>,
} }
type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>; type CachedSubs = Arc<Mutex<MutMap<ModuleId, (Subs, Vec<(Symbol, Variable)>)>>>;
@ -1004,6 +1016,7 @@ impl<'a> State<'a> {
exec_mode, exec_mode,
make_specializations_pass: MakeSpecializationsPass::Pass(1), make_specializations_pass: MakeSpecializationsPass::Pass(1),
world_abilities: Default::default(), world_abilities: Default::default(),
layout_interner: GlobalInterner::with_capacity(128),
} }
} }
} }
@ -1116,7 +1129,6 @@ enum BuildTask<'a> {
}, },
BuildPendingSpecializations { BuildPendingSpecializations {
module_timing: ModuleTiming, module_timing: ModuleTiming,
execution_mode: ExecutionMode,
layout_cache: LayoutCache<'a>, layout_cache: LayoutCache<'a>,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
imported_module_thunks: &'a [Symbol], imported_module_thunks: &'a [Symbol],
@ -1127,6 +1139,7 @@ enum BuildTask<'a> {
exposed_by_module: ExposedByModule, exposed_by_module: ExposedByModule,
abilities_store: AbilitiesStore, abilities_store: AbilitiesStore,
derived_module: SharedDerivedModule, derived_module: SharedDerivedModule,
build_expects: bool,
}, },
MakeSpecializations { MakeSpecializations {
module_id: ModuleId, module_id: ModuleId,
@ -1602,12 +1615,14 @@ fn state_thread_step<'a>(
} }
Msg::FinishedAllSpecialization { Msg::FinishedAllSpecialization {
subs, subs,
layout_interner,
exposed_to_host, exposed_to_host,
} => { } => {
// We're done! There should be no more messages pending. // We're done! There should be no more messages pending.
debug_assert!(msg_rx.is_empty()); 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))) Ok(ControlFlow::Break(LoadResult::Monomorphized(monomorphized)))
} }
@ -1983,12 +1998,12 @@ fn start_tasks<'a>(
} }
macro_rules! debug_print_ir { macro_rules! debug_print_ir {
($state:expr, $flag:path) => { ($state:expr, $interner:expr, $flag:path) => {
dbg_do!($flag, { dbg_do!($flag, {
let procs_string = $state let procs_string = $state
.procedures .procedures
.values() .values()
.map(|proc| proc.to_pretty(200)) .map(|proc| proc.to_pretty($interner, 200))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let result = procs_string.join("\n"); let result = procs_string.join("\n");
@ -2358,7 +2373,14 @@ fn update<'a>(
.type_problems .type_problems
.insert(module_id, solved_module.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 (path, _) = state.module_cache.sources.get(&module_id).unwrap();
let expectations = Expectations { let expectations = Expectations {
@ -2383,7 +2405,11 @@ fn update<'a>(
Some(ref platform_data) => module_id == platform_data.module_id, 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( state.exposed_to_host.values.extend(
solved_module solved_module
.exposed_vars_by_symbol .exposed_vars_by_symbol
@ -2471,10 +2497,9 @@ fn update<'a>(
if state.goal_phase() > Phase::SolveTypes if state.goal_phase() > Phase::SolveTypes
|| matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) || matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
{ {
let layout_cache = state let layout_cache = state.layout_caches.pop().unwrap_or_else(|| {
.layout_caches LayoutCache::new(state.layout_interner.fork(), state.target_info)
.pop() });
.unwrap_or_else(|| LayoutCache::new(state.target_info));
let typechecked = TypeCheckedModule { let typechecked = TypeCheckedModule {
module_id, module_id,
@ -2659,7 +2684,7 @@ fn update<'a>(
ident_ids, ident_ids,
subs, subs,
module_timing, module_timing,
layout_cache: _, layout_cache: _layout_cache,
procs_base: _, procs_base: _,
}, },
) in state.module_cache.late_specializations.drain() ) in state.module_cache.late_specializations.drain()
@ -2669,11 +2694,25 @@ fn update<'a>(
state.root_subs = Some(subs); state.root_subs = Some(subs);
} }
state.timings.insert(module_id, module_timing); 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); 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(); let ident_ids = state.constrained_ident_ids.get_mut(&module_id).unwrap();
@ -2685,17 +2724,18 @@ fn update<'a>(
&mut state.procedures, &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( Proc::insert_refcount_operations(
arena, arena,
&layout_interner,
module_id, module_id,
ident_ids, ident_ids,
&mut update_mode_ids, &mut update_mode_ids,
&mut state.procedures, &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 // 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 msg_tx
.send(Msg::FinishedAllSpecialization { .send(Msg::FinishedAllSpecialization {
subs, subs,
// TODO thread through mono problems layout_interner,
exposed_to_host: state.exposed_to_host.clone(), exposed_to_host: state.exposed_to_host.clone(),
}) })
.map_err(|_| LoadingProblem::MsgChannelDied)?; .map_err(|_| LoadingProblem::MsgChannelDied)?;
@ -2813,11 +2853,35 @@ fn update<'a>(
} }
} }
fn finish_specialization( #[cfg(debug_assertions)]
state: State, 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, subs: Subs,
layout_interner: STLayoutInterner<'a>,
exposed_to_host: ExposedToHost, exposed_to_host: ExposedToHost,
) -> Result<MonomorphizedModule, LoadingProblem> { ) -> Result<MonomorphizedModule<'a>, LoadingProblem<'a>> {
if false { if false {
println!( println!(
"total Type clones: {} ", "total Type clones: {} ",
@ -2940,6 +3004,7 @@ fn finish_specialization(
module_id: state.root_id, module_id: state.root_id,
subs, subs,
interns, interns,
layout_interner,
procedures, procedures,
entry_point, entry_point,
sources, sources,
@ -4808,7 +4873,6 @@ fn make_specializations<'a>(
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn build_pending_specializations<'a>( fn build_pending_specializations<'a>(
arena: &'a Bump, arena: &'a Bump,
execution_mode: ExecutionMode,
solved_subs: Solved<Subs>, solved_subs: Solved<Subs>,
imported_module_thunks: &'a [Symbol], imported_module_thunks: &'a [Symbol],
home: ModuleId, home: ModuleId,
@ -4821,6 +4885,7 @@ fn build_pending_specializations<'a>(
exposed_by_module: &ExposedByModule, exposed_by_module: &ExposedByModule,
abilities_store: AbilitiesStore, abilities_store: AbilitiesStore,
derived_module: SharedDerivedModule, derived_module: SharedDerivedModule,
build_expects: bool,
) -> Msg<'a> { ) -> Msg<'a> {
let find_specializations_start = Instant::now(); let find_specializations_start = Instant::now();
@ -5067,11 +5132,8 @@ fn build_pending_specializations<'a>(
} }
Expectation => { Expectation => {
// skip expectations if we're not going to run them // skip expectations if we're not going to run them
match execution_mode { if !build_expects {
ExecutionMode::Test => { /* fall through */ } continue;
ExecutionMode::Check
| ExecutionMode::Executable
| ExecutionMode::ExecutableIfCheck => continue,
} }
// mark this symbol as a top-level thunk before any other work on the procs // 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 => { ExpectationFx => {
// skip expectations if we're not going to run them // skip expectations if we're not going to run them
match execution_mode { if !build_expects {
ExecutionMode::Test => { /* fall through */ } continue;
ExecutionMode::Check
| ExecutionMode::Executable
| ExecutionMode::ExecutableIfCheck => continue,
} }
// mark this symbol as a top-level thunk before any other work on the procs // mark this symbol as a top-level thunk before any other work on the procs
@ -5422,7 +5481,6 @@ fn run_task<'a>(
)), )),
BuildPendingSpecializations { BuildPendingSpecializations {
module_id, module_id,
execution_mode,
ident_ids, ident_ids,
decls, decls,
module_timing, module_timing,
@ -5433,9 +5491,9 @@ fn run_task<'a>(
abilities_store, abilities_store,
exposed_by_module, exposed_by_module,
derived_module, derived_module,
build_expects,
} => Ok(build_pending_specializations( } => Ok(build_pending_specializations(
arena, arena,
execution_mode,
solved_subs, solved_subs,
imported_module_thunks, imported_module_thunks,
module_id, module_id,
@ -5448,6 +5506,7 @@ fn run_task<'a>(
&exposed_by_module, &exposed_by_module,
abilities_store, abilities_store,
derived_module, derived_module,
build_expects,
)), )),
MakeSpecializations { MakeSpecializations {
module_id, module_id,

View file

@ -10,7 +10,7 @@ roc_region = { path = "../region" }
roc_ident = { path = "../ident" } roc_ident = { path = "../ident" }
roc_collections = { path = "../collections" } roc_collections = { path = "../collections" }
roc_error_macros = {path = "../../error_macros"} 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" lazy_static = "1.4.0"
static_assertions = "1.1.0" static_assertions = "1.1.0"
snafu = { version = "0.7.1", features = ["backtraces"] } snafu = { version = "0.7.1", features = ["backtraces"] }

View file

@ -512,6 +512,20 @@ impl<'a> PackageModuleIds<'a> {
pub fn available_modules(&self) -> impl Iterator<Item = &PQModuleName> { pub fn available_modules(&self) -> impl Iterator<Item = &PQModuleName> {
self.by_id.iter() 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. /// Stores a mapping between ModuleId and InlinableString.
@ -1229,6 +1243,9 @@ define_builtins! {
47 STR_TO_NUM: "strToNum" 47 STR_TO_NUM: "strToNum"
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel" 48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
49 STR_CAPACITY: "capacity" 49 STR_CAPACITY: "capacity"
50 STR_REPLACE_EACH: "replaceEach"
51 STR_REPLACE_FIRST: "replaceFirst"
52 STR_REPLACE_LAST: "replaceLast"
} }
6 LIST: "List" => { 6 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias

View file

@ -14,6 +14,7 @@ roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_derive_key = { path = "../derive_key" } roc_derive_key = { path = "../derive_key" }
roc_derive = { path = "../derive" } roc_derive = { path = "../derive" }
roc_intern = { path = "../intern" }
roc_late_solve = { path = "../late_solve" } roc_late_solve = { path = "../late_solve" }
roc_std = { path = "../../roc_std" } roc_std = { path = "../../roc_std" }
roc_problem = { path = "../problem" } roc_problem = { path = "../problem" }
@ -23,6 +24,7 @@ roc_error_macros = {path="../../error_macros"}
roc_debug_flags = {path="../debug_flags"} roc_debug_flags = {path="../debug_flags"}
roc_tracing = { path = "../../tracing" } roc_tracing = { path = "../../tracing" }
ven_pretty = { path = "../../vendor/pretty" } 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" ] } hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
static_assertions = "1.1.0" static_assertions = "1.1.0"
bitvec = "1.0.1"

View file

@ -629,7 +629,7 @@ fn eq_list<'a>(
// let size = literal int // let size = literal int
let size = root.create_symbol(ident_ids, "size"); let size = root.create_symbol(ident_ids, "size");
let size_expr = Expr::Literal(Literal::Int( 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); 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