Merge remote-tracking branch 'origin/main' into expect-fx-codegen

This commit is contained in:
Folkert 2022-08-23 16:28:21 +02:00
commit a22e04361c
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
222 changed files with 10039 additions and 1945 deletions

9
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,9 @@
version: 2
updates:
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
time: "07:00"
timezone: "Europe/Brussels"

View file

@ -20,14 +20,14 @@ jobs:
steps:
- uses: actions/checkout@v2
with:
ref: "trunk"
ref: "main"
clean: "true"
- name: Earthly version
run: earthly --version
- name: on trunk; prepare a self-contained benchmark folder
run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder
- name: on main; prepare a self-contained benchmark folder
run: ./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=main +prep-bench-folder
- uses: actions/checkout@v2
with:

View file

@ -1,28 +0,0 @@
on: [pull_request]
name: CI
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
jobs:
build-fmt-clippy-test:
name: fmt, clippy, test --release
runs-on: [self-hosted, i5-4690K]
timeout-minutes: 90
env:
FORCE_COLOR: 1
steps:
- uses: actions/checkout@v2
with:
clean: "true"
- name: Earthly version
run: earthly --version
- name: install dependencies, build, run zig tests, rustfmt, clippy, cargo test --release
run: ./ci/safe-earthly.sh +test-all

View file

@ -22,7 +22,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label}
upload_url: https://uploads.github.com/repos/roc-lang/roc/releases/51880579/assets{?name,label}
release_id: 51880579
asset_path: ./roc_linux_x86_64.tar.gz
asset_name: roc_nightly-linux_x86_64-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)

View file

@ -15,23 +15,26 @@ jobs:
run: zig version
- name: llvm version
run: llc --version | grep LLVM
- name: run tests
run: cargo test --locked --release
- name: write version to file
run: ./ci/write_version.sh
- name: build nightly release
run: cargo build --locked --release
- name: package release
run: ./ci/package_release.sh roc_darwin_apple_silicon.tar.gz
- name: Create pre-release with test_archive.tar.gz
uses: Anton-4/deploy-nightly@1609d8dfe211b078674801113ab7a2ec2938b2a9
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label}
release_id: 51880579
asset_path: ./roc_darwin_apple_silicon.tar.gz
asset_name: roc_nightly-macos_apple_silicon-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)
asset_content_type: application/gzip
max_releases: 3
- name: print short commit SHA
run: git rev-parse --short "$GITHUB_SHA"
- name: print date
run: date
- name: Upload artifact Actually uploading to github releases has to be done manually
uses: actions/upload-artifact@v3
with:
name: roc_nightly-macos_apple_silicon.tar.gz
path: roc_darwin_apple_silicon.tar.gz
retention-days: 4

View file

@ -44,7 +44,7 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
upload_url: https://uploads.github.com/repos/rtfeldman/roc/releases/51880579/assets{?name,label}
upload_url: https://uploads.github.com/repos/roc-lang/roc/releases/51880579/assets{?name,label}
release_id: 51880579
asset_path: ./roc_darwin_x86_64.tar.gz
asset_name: roc_nightly-macos_x86_64-$$.tar.gz # $$ inserts 6 char commit hash and date (YYYY-MM-DD)

View file

@ -12,7 +12,7 @@ env:
jobs:
spell-check:
name: spell check
runs-on: [self-hosted, linux]
runs-on: [self-hosted, i7-6700K]
timeout-minutes: 10
env:
FORCE_COLOR: 1

51
.github/workflows/ubuntu_x86_64.yml vendored Normal file
View file

@ -0,0 +1,51 @@
on: [pull_request]
name: CI
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
RUST_BACKTRACE: 1
jobs:
test-zig-rust-wasm:
name: test zig, rust, wasm
runs-on: [self-hosted, i5-4690K]
timeout-minutes: 90
env:
RUSTC_WRAPPER: /home/small-ci-user/.cargo/bin/sccache
steps:
- uses: actions/checkout@v2
with:
clean: "true"
- name: zig fmt check, zig tests
run: cd crates/compiler/builtins/bitcode && ./run-tests.sh
- name: zig wasm tests
run: cd crates/compiler/builtins/bitcode && ./run-wasm-tests.sh
- name: regular rust tests
run: cargo test --locked --release --features with_sound serde --workspace && sccache --show-stats
- name: test the dev backend # these tests require an explicit feature flag
run: cargo test --locked --release --package test_gen --no-default-features --features gen-dev && sccache --show-stats
- name: test gen-wasm single threaded # gen-wasm has some multithreading problems to do with the wasmer runtime
run: cargo test --locked --release --package test_gen --no-default-features --features gen-wasm -- --test-threads=1 && sccache --show-stats
- name: run `roc test` on Str builtins
run: cargo run --locked --release -- test crates/compiler/builtins/roc/Str.roc && sccache --show-stats
#TODO pass --locked into the script here as well, this avoids rebuilding dependencies unnecessarily
- name: wasm repl test
run: crates/repl_test/test_wasm.sh && sccache --show-stats
#TODO i386 (32-bit linux) cli tests
#TODO verify-no-git-changes
- name: test website build script
run: REPL_DEBUG=1 bash www/build.sh

View file

@ -1,10 +1,10 @@
name: deploy www.roc-lang.org
# Whenever a commit lands on trunk, deploy the site
# Whenever a commit lands on `main`, deploy the site
on:
push:
branches:
- deploy-www # TODO change to trunk
- deploy-www
jobs:
deploy:

View file

@ -1,7 +1,7 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Upstream-Name: Roc
Upstream-Contact: Richard Feldman <oss@rtfeldman.com>
Source: https://github.com/rtfeldman/roc
Source: https://github.com/roc-lang/roc
Files: *
Copyright: © The Roc Contributors

View file

@ -89,3 +89,10 @@ Christoph Rüßler <christoph.ruessler@mailbox.org>
Ralf Engbers <raleng@users.noreply.github.com>
Mostly Void <7rat13@gmail.com>
Luis F. Gutierrez <luis@gutierrezhiller.com>
Oskar Hahn <mail@oshahn.de>
Ross Smyth <crs2017@gmail.com>
David A. Kunz <david.kunz@sap.com>
Paul Young <84700+paulyoung@users.noreply.github.com>
Rod <randomer@users.noreply.github.com>
Marko Vujanic <crashxx@gmail.com>
kilianv <r0754877>

View file

@ -6,6 +6,9 @@ Installation should be a smooth process, let us now if anything does not work pe
We highly recommend Using [nix](https://nixos.org/download.html) to quickly install all dependencies necessary to build roc.
> See issue [#3863](https://github.com/roc-lang/roc/issues/3863) if you encounter "version GLIBC_2.34 not found".
> This error can occur if you ran `cargo build` in the same folder without nix.
### On Linux x86_64/aarch64 or MacOS aarch64/arm64/x86_64
#### Install

View file

@ -28,9 +28,9 @@ Earthly may temporarily use a lot of disk space, up to 90 GB. This disk space is
## Contribution Tips
- Create an issue if the purpose of a struct/field/type/function/... is not immediately clear from its name or nearby comments.
- You find good first issues [here](https://github.com/rtfeldman/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
- You find good first issues [here](https://github.com/roc-lang/roc/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22).
- Before making your first pull request, definitely talk to an existing contributor on [Roc Zulip](https://roc.zulipchat.com) first about what you plan to do! This can not only avoid duplicated effort, it can also avoid making a whole PR only to discover it won't be accepted because the change doesn't fit with the goals of the language's design or implementation.
- It's a good idea to open a work-in-progress pull request as you begin working on something. This way, others can see that you're working on it, which avoids duplicate effort, and others can give feedback sooner rather than later if they notice a problem in the direction things are going. Be sure to include "WIP" in the title of the PR as long as it's not ready for review!
- It's a good idea to open a 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.
- Make sure to create a branch on the roc repository for your changes. We do not allow CI to be run on forks for security.
- All your commits need to be signed to prevent impersonation:
1. If you have a Yubikey, follow [guide 1](https://dev.to/paulmicheli/using-your-yubikey-to-get-started-with-gpg-3h4k), [guide 2](https://dev.to/paulmicheli/using-your-yubikey-for-signed-git-commits-4l73) and skip the steps below.

158
Cargo.lock generated
View file

@ -330,9 +330,9 @@ dependencies = [
[[package]]
name = "bytemuck"
version = "1.10.0"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c53dfa917ec274df8ed3c572698f381a24eef2efba9492d797301b72b6db408a"
checksum = "a5377c8865e74a160d21f29c2d40669f53286db6eab59b88540cbb12ffc8b835"
dependencies = [
"bytemuck_derive",
]
@ -631,11 +631,11 @@ dependencies = [
[[package]]
name = "console"
version = "0.15.0"
version = "0.15.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
dependencies = [
"encode_unicode",
"encode_unicode 0.3.6",
"libc",
"once_cell",
"terminal_size",
@ -950,9 +950,9 @@ dependencies = [
[[package]]
name = "crossbeam"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845"
checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-channel",
@ -1111,6 +1111,18 @@ dependencies = [
"syn",
]
[[package]]
name = "dashmap"
version = "5.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3495912c9c1ccf2e18976439f4443f3fee0fd61f424ff99fde6a66b15ecb448f"
dependencies = [
"cfg-if 1.0.0",
"hashbrown 0.12.3",
"lock_api",
"parking_lot_core 0.9.3",
]
[[package]]
name = "diff"
version = "0.1.13"
@ -1138,9 +1150,9 @@ dependencies = [
[[package]]
name = "dircpy"
version = "0.3.10"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4388680a28717a3ff2b6b60824bf62d67076232d9416c34d17a148dade0e6cd3"
checksum = "d16e8f15af1ed7189d2bf43c7ae5d6fe0a840cd3b1e9c7bf7c08a384a5df441f"
dependencies = [
"jwalk",
"log",
@ -1261,6 +1273,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "endian-type"
version = "0.1.2"
@ -1713,9 +1731,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.12.2"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "607c8a29735385251a339424dd462993c0fed8fa09d378f259377df08c126022"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
"bumpalo",
@ -1805,27 +1823,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
dependencies = [
"autocfg",
"hashbrown 0.12.2",
"hashbrown 0.12.3",
"serde",
]
[[package]]
name = "indoc"
version = "1.0.6"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e"
checksum = "adab1eaa3408fb7f0c777a73e7465fd5656136fc93b670eb6df3c88c2c1344e3"
[[package]]
name = "inkwell"
version = "0.1.0"
dependencies = [
"inkwell 0.1.0 (git+https://github.com/rtfeldman/inkwell?branch=master)",
"inkwell 0.1.0 (git+https://github.com/roc-lang/inkwell?branch=master)",
]
[[package]]
name = "inkwell"
version = "0.1.0"
source = "git+https://github.com/rtfeldman/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e"
source = "git+https://github.com/roc-lang/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e"
dependencies = [
"either",
"inkwell_internals",
@ -1838,7 +1856,7 @@ dependencies = [
[[package]]
name = "inkwell_internals"
version = "0.5.0"
source = "git+https://github.com/rtfeldman/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e"
source = "git+https://github.com/roc-lang/inkwell?branch=master#accd406858a40ca2a1463ff77d79f3c5e4c96f4e"
dependencies = [
"proc-macro2",
"quote",
@ -1853,16 +1871,16 @@ checksum = "90953f308a79fe6d62a4643e51f848fbfddcd05975a38e69fdf4ab86a7baf7ca"
[[package]]
name = "insta"
version = "1.15.0"
version = "1.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4126dd76ebfe2561486a1bd6738a33d2029ffb068a99ac446b7f8c77b2e58dbc"
checksum = "57a9aec10c9a062ef0454fd49ebaefa59239f836d1b30891d9cc2289978dd970"
dependencies = [
"console",
"linked-hash-map",
"once_cell",
"serde",
"serde_json",
"serde_yaml",
"similar",
"yaml-rust",
]
[[package]]
@ -1944,9 +1962,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.58"
version = "0.3.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27"
checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2"
dependencies = [
"wasm-bindgen",
]
@ -2421,9 +2439,9 @@ dependencies = [
[[package]]
name = "nonempty"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7"
checksum = "09f1f8e5676e1a1f2ee8b21f38238e1243c827531c9435624c7bfb305102cee4"
[[package]]
name = "num-derive"
@ -2556,7 +2574,7 @@ checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"crc32fast",
"flate2",
"hashbrown 0.12.2",
"hashbrown 0.12.3",
"indexmap",
"memchr",
]
@ -2678,9 +2696,9 @@ dependencies = [
[[package]]
name = "palette"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9735f7e1e51a3f740bacd5dc2724b61a7806f23597a8736e679f38ee3435d18"
checksum = "8f9cd68f7112581033f157e56c77ac4a5538ec5836a2e39284e65bd7d7275e49"
dependencies = [
"approx 0.5.1",
"num-traits",
@ -2690,9 +2708,9 @@ dependencies = [
[[package]]
name = "palette_derive"
version = "0.6.0"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7799c3053ea8a6d8a1193c7ba42f534e7863cf52e378a7f90406f4a645d33bad"
checksum = "05eedf46a8e7c27f74af0c9cfcdb004ceca158cb1b918c6f68f8d7a549b3e427"
dependencies = [
"find-crate",
"proc-macro2",
@ -2832,20 +2850,19 @@ dependencies = [
[[package]]
name = "phf"
version = "0.9.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ac8b67553a7ca9457ce0e526948cad581819238f4a9d1ea74545851fa24f37"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
dependencies = [
"phf_macros",
"phf_shared",
"proc-macro-hack",
]
[[package]]
name = "phf_generator"
version = "0.9.1"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d43f3220d96e0080cc9ea234978ccd80d904eafb17be31bb0f76daaea6493082"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [
"phf_shared",
"rand",
@ -2853,13 +2870,12 @@ dependencies = [
[[package]]
name = "phf_macros"
version = "0.9.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b706f5936eb50ed880ae3009395b43ed19db5bff2ebd459c95e7bf013a89ab86"
checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro-hack",
"proc-macro2",
"quote",
"syn",
@ -2867,9 +2883,9 @@ dependencies = [
[[package]]
name = "phf_shared"
version = "0.9.0"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a68318426de33640f02be62b4ae8eb1261be2efbc337b60c54d845bf4484e0d9"
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
dependencies = [
"siphasher",
]
@ -3014,9 +3030,9 @@ dependencies = [
[[package]]
name = "pulldown-cmark"
version = "0.9.1"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34f197a544b0c9ab3ae46c359a7ec9cbbb5c7bf97054266fecb7ead794a181d6"
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [
"bitflags",
"memchr",
@ -3287,7 +3303,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15"
dependencies = [
"bytecheck",
"hashbrown 0.12.2",
"hashbrown 0.12.3",
"ptr_meta",
"rend",
"rkyv_derive",
@ -3488,7 +3504,7 @@ version = "0.0.1"
dependencies = [
"bitvec 1.0.1",
"bumpalo",
"hashbrown 0.12.2",
"hashbrown 0.12.3",
"im",
"im-rc",
"wyhash",
@ -3853,7 +3869,7 @@ name = "roc_mono"
version = "0.0.1"
dependencies = [
"bumpalo",
"hashbrown 0.12.2",
"hashbrown 0.12.3",
"roc_builtins",
"roc_can",
"roc_collections",
@ -3879,7 +3895,7 @@ version = "0.0.1"
dependencies = [
"bumpalo",
"criterion",
"encode_unicode",
"encode_unicode 1.0.0",
"indoc",
"pretty_assertions",
"quickcheck",
@ -4209,7 +4225,7 @@ checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf"
[[package]]
name = "rustyline"
version = "9.1.1"
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
source = "git+https://github.com/roc-lang/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
dependencies = [
"bitflags",
"cfg-if 1.0.0",
@ -4232,7 +4248,7 @@ dependencies = [
[[package]]
name = "rustyline-derive"
version = "0.6.0"
source = "git+https://github.com/rtfeldman/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
source = "git+https://github.com/roc-lang/rustyline?rev=e74333c#e74333c0d618896b88175bf06645108f996fe6d0"
dependencies = [
"quote",
"syn",
@ -4386,10 +4402,11 @@ dependencies = [
[[package]]
name = "serial_test"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7eec42e7232e5ca56aa59d63af3c7f991fe71ee6a3ddd2d3480834cf3902b007"
checksum = "92761393ee4dc3ff8f4af487bd58f4307c9329bbedea02cac0089ad9c411e153"
dependencies = [
"dashmap",
"futures",
"lazy_static",
"log",
@ -4399,14 +4416,13 @@ dependencies = [
[[package]]
name = "serial_test_derive"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1b95bb2f4f624565e8fe8140c789af7e2082c0e0561b5a82a1b678baa9703dc"
checksum = "4b6f5d1c3087fb119617cff2966fe3808a80e5eb59a8c1601d5994d66f4346a5"
dependencies = [
"proc-macro-error",
"proc-macro2",
"quote",
"rustversion",
"syn",
]
@ -5193,9 +5209,9 @@ version = "0.0.1"
[[package]]
name = "wasm-bindgen"
version = "0.2.81"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994"
checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@ -5203,13 +5219,13 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.81"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a"
checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
@ -5218,9 +5234,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.31"
version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de9a9cec1733468a8c657e57fa2413d2ae2c0129b95e87c5b72b8ace4d13f31f"
checksum = "fa76fb221a1f8acddf5b54ace85912606980ad661ac7a503b4570ffd3a624dad"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@ -5230,9 +5246,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.81"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa"
checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -5240,9 +5256,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.81"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048"
checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da"
dependencies = [
"proc-macro2",
"quote",
@ -5253,9 +5269,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.81"
version = "0.2.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be"
checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a"
[[package]]
name = "wasm-encoder"
@ -5268,9 +5284,8 @@ dependencies = [
[[package]]
name = "wasm3"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7dde97449e99be474a432bbb0b1ab40b8f7ce3e97aa7ac640e9ecd018bbf88"
version = "0.5.0"
source = "git+https://github.com/roc-lang/wasm3-rs?rev=f0f807d1fc0a50d1d68e5799e54ee62c05af00f5#f0f807d1fc0a50d1d68e5799e54ee62c05af00f5"
dependencies = [
"cty",
"wasm3-sys",
@ -5278,9 +5293,8 @@ dependencies = [
[[package]]
name = "wasm3-sys"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a4e5d10bf1ffe7753275d4bbae3dc135dd2d2decd90e615accf9fef8bc52bab"
version = "0.5.0"
source = "git+https://github.com/roc-lang/wasm3-rs?rev=f0f807d1fc0a50d1d68e5799e54ee62c05af00f5#f0f807d1fc0a50d1d68e5799e54ee62c05af00f5"
dependencies = [
"cc",
"cty",

View file

@ -126,7 +126,7 @@ build-nightly-release:
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 serde --release
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

8
FAQ.md
View file

@ -234,7 +234,7 @@ Culturally, to support HKP is to take a side, and to decline to support it is al
Given this, language designers have three options:
- Have HKP and have Monad in the standard library. Embrace them and build a culture and ecosystem around them.
- Have HKP and don't have Monad in the standard library. An alternate standard lbirary built around monads will inevitably emerge, and both the community and ecosystem will divide themselves along pro-monad and anti-monad lines.
- Have HKP and don't have Monad in the standard library. An alternate standard library built around monads will inevitably emerge, and both the community and ecosystem will divide themselves along pro-monad and anti-monad lines.
- Don't have HKP; build a culture and ecosystem around other things.
Considering that these are the only three options, I think the best choice for Roc—not only on a technical
@ -458,7 +458,7 @@ The short explanation for why Roc is released under the [Universal Permissive Li
- It's one license, unlike "MIT or Apache2, at your choice" (which is how [Rust addressed the problem](https://internals.rust-lang.org/t/rationale-of-apache-dual-licensing/8952/4) of MIT not having patent protections but Apache2 not being GPLv2 compatible)
- It's been approved by OSI, FSF, and Oracle's lawyers, so it has been not only vetted by three giants in the world of software licensing, but also three giants with competing interests - and they all approved it.
There's also [a longer explanation](https://github.com/rtfeldman/roc/issues/1199) with more detail about the motivation and thought process, if you're interested.
There's also [a longer explanation](https://github.com/roc-lang/roc/issues/1199) with more detail about the motivation and thought process, if you're interested.
## Why does Roc use both Rust and Zig?
@ -472,3 +472,7 @@ There were a few reasons for this rewrite.
4. Zig has more tools for working in a memory-unsafe environment, such as reporting memory leaks in tests. These have been helpful in finding bugs that are out of scope for safe Rust.
The split of Rust for the compiler and Zig for the standard library has worked well so far, and there are no plans to change it.
## Why is the website so basic?
We have a very basic website on purpose, it helps set expectations that roc is a work in progress and not ready yet for a first release.

View file

@ -1,8 +1,8 @@
# Work in progress!
Roc is not ready for an 0.1 release yet, but we have a [Zulip chat](https://roc.zulipchat.com) where you can learn more about the project.
Roc is not ready for a 0.1 release yet, but we have [a guide](https://github.com/roc-lang/roc/tree/main/getting_started) for installing it, [a tutorial](https://github.com/roc-lang/roc/blob/main/TUTORIAL.md) for using it, and [a Zulip chat](https://roc.zulipchat.com) for asking questions.
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/rtfeldman/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

View file

@ -8,7 +8,7 @@ Enjoy!
## Getting started
Learn how to install roc on your machine [here](https://github.com/rtfeldman/roc#getting-started).
Learn how to install roc on your machine [here](https://github.com/roc-lang/roc/tree/main/getting_started#installation).
## Strings and Numbers
@ -115,7 +115,7 @@ Create a new file called `Hello.roc` and put this inside it:
```coffee
app "hello"
packages { pf: "examples/interactive/cli-platform" }
packages { pf: "examples/interactive/cli-platform/main.roc" }
imports [pf.Stdout]
provides [main] to pf
@ -124,8 +124,8 @@ main = Stdout.line "I'm a Roc application!"
> **NOTE:** This assumes you've put Hello.roc in the root directory of the Roc
> source code. If you'd like to put it somewhere else, you'll need to replace
> `"examples/interactive/cli-platform"` with the path to the
> `examples/interactive/cli-platform` folder in that source code. In the future,
> `"examples/interactive/cli-platform/main.roc"` with the path to the
> `examples/interactive/cli-platform/main.roc` file in that source code. In the future,
> Roc will have the tutorial built in, and this aside will no longer be
> necessary!
@ -1255,7 +1255,7 @@ Let's take a closer look at the part of `Hello.roc` above `main`:
```coffee
app "hello"
packages { pf: "examples/interactive/cli-platform" }
packages { pf: "examples/interactive/cli-platform/main.roc" }
imports [pf.Stdout]
provides main to pf
```
@ -1273,14 +1273,14 @@ without running it by running `roc build Hello.roc`.
The remaining lines all involve the *platform* this application is built on:
```coffee
packages { pf: "examples/interactive/cli-platform" }
packages { pf: "examples/interactive/cli-platform/main.roc" }
imports [pf.Stdout]
provides main to pf
```
The `packages { pf: "examples/interactive/cli-platform" }` part says two things:
The `packages { pf: "examples/interactive/cli-platform/main.roc" }` part says two things:
- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform"`
- We're going to be using a *package* (that is, a collection of modules) called `"examples/interactive/cli-platform/main.roc"`
- We're going to name that package `pf` so we can refer to it more concisely in the future.
The `imports [pf.Stdout]` line says that we want to import the `Stdout` module
@ -1300,18 +1300,18 @@ calling a function named `line` which is exposed by a module named
When we write `imports [pf.Stdout]`, it specifies that the `Stdout`
module comes from the `pf` package.
Since `pf` was the name we chose for the `examples/interactive/cli-platform`
package (when we wrote `packages { pf: "examples/interactive/cli-platform" }`),
Since `pf` was the name we chose for the `examples/interactive/cli-platform/main.roc`
package (when we wrote `packages { pf: "examples/interactive/cli-platform/main.roc" }`),
this `imports` line tells the Roc compiler that when we call `Stdout.line`, it
should look for that `line` function in the `Stdout` module of the
`examples/interactive/cli-platform` package.
`examples/interactive/cli-platform/main.roc` package.
# Building a Command-Line Interface (CLI)
## Tasks
Tasks are technically not part of the Roc language, but they're very common in
platforms. Let's use the CLI platform in `examples/interactive/cli-platform` as an example!
platforms. Let's use the CLI platform in `examples/interactive/cli-platform/main.roc` as an example!
In the CLI platform, we have four operations we can do:
@ -1326,7 +1326,7 @@ First, let's do a basic "Hello World" using the tutorial app.
```coffee
app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform" }
packages { pf: "examples/interactive/cli-platform/main.roc" }
imports [pf.Stdout]
provides [main] to pf
@ -1363,7 +1363,7 @@ Let's change `main` to read a line from `stdin`, and then print it back out agai
```swift
app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform" }
packages { pf: "examples/interactive/cli-platform/main.roc" }
imports [pf.Stdout, pf.Stdin, pf.Task]
provides [main] to pf
@ -1413,7 +1413,7 @@ This works, but we can make it a little nicer to read. Let's change it to the fo
```haskell
app "cli-tutorial"
packages { pf: "examples/interactive/cli-platform" }
packages { pf: "examples/interactive/cli-platform/main.roc" }
imports [pf.Stdout, pf.Stdin, pf.Task.{ await }]
provides [main] to pf

View file

@ -12,13 +12,13 @@ use std::{
process::{self, Command, Stdio},
};
const BENCH_FOLDER_TRUNK: &str = "bench-folder-trunk";
const BENCH_FOLDER_MAIN: &str = "bench-folder-main";
const BENCH_FOLDER_BRANCH: &str = "bench-folder-branch";
fn main() {
let optional_args: OptionalArgs = OptionalArgs::parse();
if Path::new(BENCH_FOLDER_TRUNK).exists() && Path::new(BENCH_FOLDER_BRANCH).exists() {
if Path::new(BENCH_FOLDER_MAIN).exists() && Path::new(BENCH_FOLDER_BRANCH).exists() {
delete_old_bench_results();
if optional_args.check_executables_changed {
@ -26,7 +26,7 @@ fn main() {
std::env::set_var("BENCH_DRY_RUN", "1");
do_benchmark("trunk");
do_benchmark("main");
do_benchmark("branch");
std::env::set_var("BENCH_DRY_RUN", "0");
@ -49,9 +49,9 @@ fn main() {
}
} else {
eprintln!(
r#"I can't find bench-folder-trunk and bench-folder-branch from the current directory.
r#"I can't find bench-folder-main and bench-folder-branch from the current directory.
I should be executed from the repo root.
Use `./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=trunk +prep-bench-folder` to generate bench-folder-trunk.
Use `./ci/safe-earthly.sh --build-arg BENCH_SUFFIX=main +prep-bench-folder` to generate bench-folder-main.
Use `./ci/safe-earthly.sh +prep-bench-folder` to generate bench-folder-branch."#
);
@ -77,7 +77,7 @@ fn finish(all_regressed_benches: HashSet<String>, nr_repeat_benchmarks: usize) {
// returns all benchmarks that have regressed
fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
delete_old_bench_results();
do_benchmark("trunk");
do_benchmark("main");
let mut all_regressed_benches = do_benchmark("branch");
// if no benches regressed this round, abort early
@ -87,7 +87,7 @@ fn do_all_benches(nr_repeat_benchmarks: usize) -> HashSet<String> {
for _ in 1..nr_repeat_benchmarks {
delete_old_bench_results();
do_benchmark("trunk");
do_benchmark("main");
let regressed_benches = do_benchmark("branch");
// if no benches regressed this round, abort early
@ -229,17 +229,17 @@ fn calc_hashes_for_folder(benches_path_str: &str) -> HashMap<String, String> {
fn check_if_bench_executables_changed() -> bool {
let bench_folder_str = "/examples/benchmarks/";
let trunk_benches_path_str = [BENCH_FOLDER_TRUNK, bench_folder_str].join("");
let trunk_bench_hashes = calc_hashes_for_folder(&trunk_benches_path_str);
let main_benches_path_str = [BENCH_FOLDER_MAIN, bench_folder_str].join("");
let main_bench_hashes = calc_hashes_for_folder(&main_benches_path_str);
let branch_benches_path_str = [BENCH_FOLDER_BRANCH, bench_folder_str].join("");
let branch_bench_hashes = calc_hashes_for_folder(&branch_benches_path_str);
if trunk_bench_hashes.keys().len() == branch_bench_hashes.keys().len() {
for key in trunk_bench_hashes.keys() {
if let Some(trunk_hash_val) = trunk_bench_hashes.get(key) {
if main_bench_hashes.keys().len() == branch_bench_hashes.keys().len() {
for key in main_bench_hashes.keys() {
if let Some(main_hash_val) = main_bench_hashes.get(key) {
if let Some(branch_hash_val) = branch_bench_hashes.get(key) {
if !trunk_hash_val.eq(branch_hash_val) {
if !main_hash_val.eq(branch_hash_val) {
return true;
}
} else {

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
mkdir -p $HOME/.cargo
echo -e "[build]\nrustflags = [\"-C\", \"link-arg=-fuse-ld=lld\", \"-C\", \"target-cpu=native\"]" > $HOME/.cargo/config

View file

@ -29,7 +29,7 @@ ven_graph = { path = "../vendor/pathfinding" }
libc = "0.2.106"
[dev-dependencies]
indoc = "1.0.3"
indoc = "1.0.7"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["memoryapi"]}

View file

@ -2614,7 +2614,7 @@ pub mod test_constrain {
#[test]
fn inference_var_tag_union_ext() {
// TODO: we should really be inferring [Blue, Orange]a -> [Lavender, Peach]a here.
// See https://github.com/rtfeldman/roc/issues/2053
// See https://github.com/roc-lang/roc/issues/2053
infer_eq(
indoc!(
r#"

View file

@ -943,7 +943,7 @@ pub fn canonicalize_defs<'a>(
)
}
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Declaration {

View file

@ -3,7 +3,7 @@ name = "roc_cli"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "A CLI for Roc"
default-run = "roc"
@ -88,8 +88,8 @@ wasmer = { version = "2.2.1", optional = true, default-features = false, feature
wasmer-wasi = "2.2.1"
pretty_assertions = "1.0.0"
roc_test_utils = { path = "../test_utils" }
indoc = "1.0.3"
serial_test = "0.8.0"
indoc = "1.0.7"
serial_test = "0.9.0"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
cli_utils = { path = "../cli_utils" }
strum = "0.24.0"

View file

@ -5,7 +5,10 @@ use roc_build::{
};
use roc_builtins::bitcode;
use roc_collections::VecMap;
use roc_load::{EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadingProblem, Threading};
use roc_load::{
EntryPoint, ExecutionMode, Expectations, LoadConfig, LoadMonomorphizedError, LoadedModule,
LoadingProblem, Threading,
};
use roc_module::symbol::{Interns, ModuleId};
use roc_mono::ir::OptLevel;
use roc_reporting::report::RenderTarget;
@ -35,6 +38,23 @@ pub struct BuiltFile {
pub interns: Interns,
}
pub enum BuildOrdering {
/// Run up through typechecking first; continue building iff that is successful.
BuildIfChecks,
/// Always build the Roc binary, even if there are type errors.
AlwaysBuild,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum BuildFileError<'a> {
LoadingProblem(LoadingProblem<'a>),
ErrorModule {
module: LoadedModule,
total_time: Duration,
},
}
#[allow(clippy::too_many_arguments)]
pub fn build_file<'a>(
arena: &'a Bump,
@ -46,29 +66,46 @@ pub fn build_file<'a>(
link_type: LinkType,
linking_strategy: LinkingStrategy,
precompiled: bool,
target_valgrind: bool,
threading: Threading,
wasm_dev_stack_bytes: Option<u32>,
) -> Result<BuiltFile, LoadingProblem<'a>> {
order: BuildOrdering,
) -> Result<BuiltFile, BuildFileError<'a>> {
let compilation_start = Instant::now();
let target_info = TargetInfo::from(target);
// Step 1: compile the app and generate the .o file
let subs_by_module = Default::default();
let exec_mode = match order {
BuildOrdering::BuildIfChecks => ExecutionMode::ExecutableIfCheck,
BuildOrdering::AlwaysBuild => ExecutionMode::Executable,
};
let load_config = LoadConfig {
target_info,
// TODO: expose this from CLI?
render: RenderTarget::ColorTerminal,
threading,
exec_mode: ExecutionMode::Executable,
exec_mode,
};
let loaded = roc_load::load_and_monomorphize(
let load_result = roc_load::load_and_monomorphize(
arena,
app_module_path.clone(),
subs_by_module,
load_config,
)?;
);
let loaded = match load_result {
Ok(loaded) => loaded,
Err(LoadMonomorphizedError::LoadingProblem(problem)) => {
return Err(BuildFileError::LoadingProblem(problem))
}
Err(LoadMonomorphizedError::ErrorModule(module)) => {
return Err(BuildFileError::ErrorModule {
module,
total_time: compilation_start.elapsed(),
})
}
};
use target_lexicon::Architecture;
let emit_wasm = matches!(target.architecture, Architecture::Wasm32);
@ -152,7 +189,6 @@ pub fn build_file<'a>(
target,
exposed_values,
exposed_closure_types,
target_valgrind,
);
// TODO try to move as much of this linking as possible to the precompiled
@ -161,9 +197,7 @@ pub fn build_file<'a>(
.prefix("roc_app")
.suffix(&format!(".{}", app_extension))
.tempfile()
.map_err(|err| {
todo!("TODO Gracefully handle tempfile creation error {:?}", err);
})?;
.map_err(|err| todo!("TODO Gracefully handle tempfile creation error {:?}", err))?;
let app_o_file = app_o_file.path();
let buf = &mut String::with_capacity(1024);
@ -329,12 +363,12 @@ pub fn build_file<'a>(
link_type
)
.map_err(|_| {
todo!("gracefully handle `ld` failing to spawn.");
todo!("gracefully handle `ld` failing to spawn.")
})?;
let exit_status = child.wait().map_err(|_| {
todo!("gracefully handle error after `ld` spawned");
})?;
let exit_status = child
.wait()
.map_err(|_| todo!("gracefully handle error after `ld` spawned"))?;
if exit_status.success() {
problems
@ -377,7 +411,6 @@ fn spawn_rebuild_thread(
target: &Triple,
exported_symbols: Vec<String>,
exported_closure_types: Vec<String>,
target_valgrind: bool,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
@ -395,7 +428,6 @@ fn spawn_rebuild_thread(
&thread_local_target,
host_input_path.as_path(),
None,
target_valgrind,
);
preprocess_host_wasm32(host_dest.as_path(), &preprocessed_host_path);
@ -408,7 +440,6 @@ fn spawn_rebuild_thread(
preprocessed_host_path.as_path(),
exported_symbols,
exported_closure_types,
target_valgrind,
);
}
LinkingStrategy::Legacy => {
@ -417,7 +448,6 @@ fn spawn_rebuild_thread(
&thread_local_target,
host_input_path.as_path(),
None,
target_valgrind,
);
}
}

View file

@ -31,10 +31,13 @@ pub mod build;
mod format;
pub use format::format;
use crate::build::{BuildFileError, BuildOrdering};
const DEFAULT_ROC_FILENAME: &str = "main.roc";
pub const CMD_BUILD: &str = "build";
pub const CMD_RUN: &str = "run";
pub const CMD_DEV: &str = "dev";
pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit";
pub const CMD_DOCS: &str = "docs";
@ -55,7 +58,6 @@ pub const FLAG_TARGET: &str = "target";
pub const FLAG_TIME: &str = "time";
pub const FLAG_LINKER: &str = "linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const FLAG_VALGRIND: &str = "valgrind";
pub const FLAG_CHECK: &str = "check";
pub const FLAG_WASM_STACK_SIZE_KB: &str = "wasm-stack-size-kb";
pub const ROC_FILE: &str = "ROC_FILE";
@ -94,11 +96,6 @@ pub fn build_app<'a>() -> Command<'a> {
.help("Store LLVM debug information in the generated program.")
.required(false);
let flag_valgrind = Arg::new(FLAG_VALGRIND)
.long(FLAG_VALGRIND)
.help("Some assembly instructions are not supported by valgrind, this flag prevents those from being output when building the host.")
.required(false);
let flag_time = Arg::new(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
@ -150,7 +147,6 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(flag_wasm_stack_size_kb.clone())
.arg(
Arg::new(FLAG_TARGET)
@ -190,7 +186,6 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(
Arg::new(ROC_FILE)
.help("The .roc file for the main module")
@ -213,7 +208,19 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(flag_valgrind.clone())
.arg(roc_file_to_run.clone())
.arg(args_for_app.clone())
)
.subcommand(Command::new(CMD_DEV)
.about("`check` a .roc file, and then run it if there were no errors.")
.arg(flag_optimize.clone())
.arg(flag_max_threads.clone())
.arg(flag_opt_size.clone())
.arg(flag_dev.clone())
.arg(flag_debug.clone())
.arg(flag_time.clone())
.arg(flag_linker.clone())
.arg(flag_precompiled.clone())
.arg(roc_file_to_run.clone())
.arg(args_for_app.clone())
)
@ -280,7 +287,6 @@ pub fn build_app<'a>() -> Command<'a> {
.arg(flag_time)
.arg(flag_linker)
.arg(flag_precompiled)
.arg(flag_valgrind)
.arg(roc_file_to_run.required(false))
.arg(args_for_app);
@ -519,7 +525,10 @@ pub fn build(
.and_then(|s| s.parse::<u32>().ok())
.map(|x| x * 1024);
let target_valgrind = matches.is_present(FLAG_VALGRIND);
let build_ordering = match config {
BuildAndRunIfNoErrors => BuildOrdering::BuildIfChecks,
_ => BuildOrdering::AlwaysBuild,
};
let res_binary_path = build_file(
&arena,
&triple,
@ -530,9 +539,9 @@ pub fn build(
link_type,
linking_strategy,
precompiled,
target_valgrind,
threading,
wasm_dev_stack_bytes,
build_ordering,
);
match res_binary_path {
@ -633,55 +642,13 @@ pub fn build(
x
}
BuildAndRunIfNoErrors => {
if problems.errors == 0 {
if problems.warnings > 0 {
println!(
"\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
problems.warnings,
if problems.warnings == 1 {
"warning"
} else {
"warnings"
},
total_time.as_millis(),
"".repeat(80)
);
}
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
let mut bytes = std::fs::read(&binary_path).unwrap();
let x = roc_run(
arena,
opt_level,
triple,
args,
&mut bytes,
expectations,
interns,
);
std::mem::forget(bytes);
x
} else {
let mut output = format!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with \x1B[32mroc run",
if problems.errors == 0 {
32 // green
} else {
33 // yellow
},
problems.errors,
if problems.errors == 1 {
"error"
} else {
"errors"
},
if problems.warnings == 0 {
32 // green
} else {
33 // yellow
},
debug_assert!(
problems.errors == 0,
"if there are errors, they should have been returned as an error variant"
);
if problems.warnings > 0 {
println!(
"\x1B[32m0\x1B[39m errors and \x1B[33m{}\x1B[39m {} found in {} ms.\n\nRunning program…\n\n\x1B[36m{}\x1B[39m",
problems.warnings,
if problems.warnings == 1 {
"warning"
@ -689,22 +656,74 @@ pub fn build(
"warnings"
},
total_time.as_millis(),
"".repeat(80)
);
// If you're running "main.roc" then you can just do `roc run`
// to re-run the program.
if filename != DEFAULT_ROC_FILENAME {
output.push(' ');
output.push_str(&filename.to_string_lossy());
}
println!("{}\x1B[39m", output);
Ok(problems.exit_code())
}
let args = matches.values_of_os(ARGS_FOR_APP).unwrap_or_default();
let mut bytes = std::fs::read(&binary_path).unwrap();
let x = roc_run(
arena,
opt_level,
triple,
args,
&mut bytes,
expectations,
interns,
);
std::mem::forget(bytes);
x
}
}
}
Err(LoadingProblem::FormattedReport(report)) => {
Err(BuildFileError::ErrorModule {
mut module,
total_time,
}) => {
debug_assert!(module.total_problems() > 0);
let problems = roc_build::program::report_problems_typechecked(&mut module);
let mut output = format!(
"\x1B[{}m{}\x1B[39m {} and \x1B[{}m{}\x1B[39m {} found in {} ms.\n\nYou can run the program anyway with \x1B[32mroc run",
if problems.errors == 0 {
32 // green
} else {
33 // yellow
},
problems.errors,
if problems.errors == 1 {
"error"
} else {
"errors"
},
if problems.warnings == 0 {
32 // green
} else {
33 // yellow
},
problems.warnings,
if problems.warnings == 1 {
"warning"
} else {
"warnings"
},
total_time.as_millis(),
);
// If you're running "main.roc" then you can just do `roc run`
// to re-run the program.
if filename != DEFAULT_ROC_FILENAME {
output.push(' ');
output.push_str(&filename.to_string_lossy());
}
println!("{}\x1B[39m", output);
Ok(problems.exit_code())
}
Err(BuildFileError::LoadingProblem(LoadingProblem::FormattedReport(report))) => {
print!("{}", report);
Ok(1)

View file

@ -1,9 +1,10 @@
use roc_build::link::LinkType;
use roc_cli::build::check_file;
use roc_cli::{
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DOCS,
CMD_EDIT, CMD_FORMAT, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION, DIRECTORY_OR_FILES,
FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, GLUE_FILE, ROC_FILE,
build_app, format, test, BuildConfig, FormatMode, Target, CMD_BUILD, CMD_CHECK, CMD_DEV,
CMD_DOCS, CMD_EDIT, CMD_FORMAT, CMD_GLUE, CMD_REPL, CMD_RUN, CMD_TEST, CMD_VERSION,
DIRECTORY_OR_FILES, FLAG_CHECK, FLAG_LIB, FLAG_NO_LINK, FLAG_TARGET, FLAG_TIME, GLUE_FILE,
ROC_FILE,
};
use roc_docs::generate_docs_html;
use roc_error_macros::user_error;
@ -64,6 +65,20 @@ fn main() -> io::Result<()> {
Ok(1)
}
}
Some((CMD_DEV, matches)) => {
if matches.is_present(ROC_FILE) {
build(
matches,
BuildConfig::BuildAndRunIfNoErrors,
Triple::host(),
LinkType::Executable,
)
} else {
eprintln!("What .roc file do you want to build? Specify it at the end of the `roc run` command.");
Ok(1)
}
}
Some((CMD_GLUE, matches)) => {
let input_path = Path::new(matches.value_of_os(ROC_FILE).unwrap());
let output_path = Path::new(matches.value_of_os(GLUE_FILE).unwrap());

View file

@ -25,7 +25,6 @@ mod cli_run {
use strum_macros::EnumIter;
const OPTIMIZE_FLAG: &str = concatcp!("--", roc_cli::FLAG_OPTIMIZE);
const VALGRIND_FLAG: &str = concatcp!("--", roc_cli::FLAG_VALGRIND);
const LINKER_FLAG: &str = concatcp!("--", roc_cli::FLAG_LINKER);
const CHECK_FLAG: &str = concatcp!("--", roc_cli::FLAG_CHECK);
const PRECOMPILED_HOST: &str = concatcp!("--", roc_cli::FLAG_PRECOMPILED, "=true");
@ -137,14 +136,17 @@ mod cli_run {
expected_ending: &str,
use_valgrind: bool,
) {
// valgrind does not yet support avx512 instructions, see #1963.
// we can't enable this only when testing with valgrind because of host re-use between tests
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
if is_x86_feature_detected!("avx512f") {
std::env::set_var("NO_AVX512", "1");
}
for cli_mode in CliMode::iter() {
let flags = {
let mut vec = flags.to_vec();
if use_valgrind {
vec.push(VALGRIND_FLAG);
}
vec.push("--max-threads=1");
vec.into_iter()
@ -295,13 +297,10 @@ mod cli_run {
let file_name = example_file(dir_name, example.filename);
match example.executable_filename {
"form" => {
// test is skipped until we upgrate to zig 0.9 / llvm 13
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
return;
}
"hello-gui" | "breakout" => {
// Since these require opening a window, we do `roc build` on them but don't run them.
"form" | "hello-gui" | "breakout" | "ruby" => {
// Since these require things the build system often doesn't have
// (e.g. GUIs open a window, Ruby needs ruby installed, WASM needs a browser)
// we do `roc build` on them but don't run them.
run_roc_on(&file_name, [CMD_BUILD, OPTIMIZE_FLAG], &[], None);
return;
}
@ -443,6 +442,14 @@ mod cli_run {
expected_ending:"Roc <3 Zig!\n",
use_valgrind: true,
},
ruby:"ruby-interop" => Example {
filename: "main.roc",
executable_filename: "libhello",
stdin: &[],
input_file: None,
expected_ending:"",
use_valgrind: true,
},
fib:"algorithms" => Example {
filename: "fibonacci.roc",
executable_filename: "fibonacci",

View file

@ -3,7 +3,7 @@ name = "cli_utils"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "Shared code for cli tests and benchmarks"

View file

@ -214,7 +214,7 @@ pub fn run_with_valgrind<'a, I: IntoIterator<Item = &'a str>>(
cmd.arg("--xml=yes");
// If you are having valgrind issues on MacOS, you may need to suppress some
// of the errors. Read more here: https://github.com/rtfeldman/roc/issues/746
// of the errors. Read more here: https://github.com/roc-lang/roc/issues/746
if let Some(suppressions_file_os_str) = env::var_os("VALGRIND_SUPPRESSIONS") {
match suppressions_file_os_str.to_str() {
None => {

View file

@ -11,7 +11,7 @@ roc_ast = { path = "../ast" }
roc_module = { path = "../compiler/module" }
roc_utils = { path = "../utils" }
serde = { version = "1.0.130", features = ["derive"] }
palette = "0.6.0"
palette = "0.6.1"
snafu = { version = "0.7.1", features = ["backtraces"] }
bumpalo = { version = "3.8.0", features = ["collections"] }
itertools = "0.10.1"

View file

@ -172,7 +172,7 @@ ask the compiler to emit debug information during various stages of compilation.
There are some goals for more sophisticated debugging tools:
- A nicer unification debugger, see https://github.com/rtfeldman/roc/issues/2486.
- A nicer unification debugger, see https://github.com/roc-lang/roc/issues/2486.
Any interest in helping out here is greatly appreciated.
### General Tips

View file

@ -3,6 +3,6 @@ name = "arena-pool"
version = "0.0.1"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
repository = "https://github.com/rtfeldman/roc"
repository = "https://github.com/roc-lang/roc"
edition = "2021"
description = "A CLI for Roc"

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod link;
pub mod program;

View file

@ -113,7 +113,6 @@ pub fn build_zig_host_native(
target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
_target_valgrind: bool,
) -> Output {
let mut command = Command::new(&zig_executable());
command
@ -149,13 +148,10 @@ pub fn build_zig_host_native(
target,
]);
// use single threaded testing for cli_run and enable this code if valgrind fails with unhandled instruction bytes, see #1963.
/*if target_valgrind {
command.args(&[
"-mcpu",
"x86_64"
]);
}*/
// valgrind does not yet support avx512 instructions, see #1963.
if env::var("NO_AVX512").is_ok() {
command.args(&["-mcpu", "x86_64"]);
}
if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
@ -177,7 +173,6 @@ pub fn build_zig_host_native(
target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
_target_valgrind: bool,
) -> Output {
let mut command = Command::new(&zig_executable());
command
@ -234,7 +229,6 @@ pub fn build_zig_host_native(
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
// For compatibility with the non-macOS def above. Keep these in sync.
_target_valgrind: bool,
) -> Output {
use serde_json::Value;
@ -463,7 +457,6 @@ pub fn rebuild_host(
target: &Triple,
host_input_path: &Path,
shared_lib_path: Option<&Path>,
target_valgrind: bool,
) -> PathBuf {
let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o");
@ -535,7 +528,6 @@ pub fn rebuild_host(
"native",
opt_level,
shared_lib_path,
target_valgrind,
)
}
Architecture::X86_32(_) => {
@ -549,7 +541,6 @@ pub fn rebuild_host(
"i386-linux-musl",
opt_level,
shared_lib_path,
target_valgrind,
)
}
@ -564,7 +555,6 @@ pub fn rebuild_host(
target_zig_str(target),
opt_level,
shared_lib_path,
target_valgrind,
)
}
_ => panic!("Unsupported architecture {:?}", target.architecture),
@ -971,7 +961,7 @@ fn link_linux(
// ld.lld requires this argument, and does not accept --arch
// .args(&["-L/usr/lib/x86_64-linux-gnu"])
.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496365925
// for discussion and further references
"-lc",
"-lm",
@ -1054,7 +1044,7 @@ fn link_macos(
}
ld_command.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// Libraries - see https://github.com/roc-lang/roc/pull/554#discussion_r496392274
// for discussion and further references
"-lSystem",
"-lresolv",
@ -1079,7 +1069,7 @@ fn link_macos(
"QuartzCore",
// "-lrt", // TODO shouldn't we need this?
// "-lc_nonshared", // TODO shouldn't we need this?
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/roc-lang/roc/pull/554#discussion_r496370840
"-framework",
"Security",
// Output

View file

@ -1,4 +1,4 @@
#!/bin/bash
#!/usr/bin/env bash
set -euxo pipefail

View file

@ -1,9 +1,10 @@
#!/bin/bash
#!/usr/bin/env bash
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
# Test every zig
# Execute zig tests (see build.zig)
zig build test
# fmt every zig
# check formatting of zig files
find src/*.zig -type f -print0 | xargs -n 1 -0 zig fmt --check || (echo "zig fmt --check FAILED! Check the previous lines to see which files were improperly formatted." && exit 1)

View file

@ -1,5 +1,6 @@
#!/bin/bash
#!/usr/bin/env bash
# https://vaneyckt.io/posts/safer_bash_scripts_with_set_euxo_pipefail/
set -euxo pipefail
# Test failures will always point at the _start function

View file

@ -181,7 +181,7 @@ comptime {
// Utils continued - SJLJ
// For tests (in particular test_gen), roc_panic is implemented in terms of
// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/rtfeldman/roc/issues/2965),
// setjmp/longjmp. LLVM is unable to generate code for longjmp on AArch64 (https://github.com/roc-lang/roc/issues/2965),
// so instead we ask Zig to please provide implementations for us, which is does
// (seemingly via musl).
pub extern fn setjmp([*c]c_int) c_int;

View file

@ -22,6 +22,7 @@ interface Decode
bool,
string,
list,
record,
custom,
decodeWith,
fromBytesPartial,
@ -57,6 +58,7 @@ DecoderFormatting has
bool : Decoder Bool fmt | fmt has DecoderFormatting
string : Decoder Str fmt | fmt has DecoderFormatting
list : Decoder elem fmt -> Decoder (List elem) fmt | fmt has DecoderFormatting
record : state, (state, Str -> [Keep (Decoder state fmt), Skip]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting
custom : (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
custom = \decode -> @Decoder decode

View file

@ -16,6 +16,7 @@ interface Json
Decode,
Decode.{
DecoderFormatting,
DecodeResult,
},
]
@ -57,6 +58,7 @@ Json := {} has [
bool: decodeBool,
string: decodeString,
list: decodeList,
record: decodeRecord,
},
]
@ -316,7 +318,8 @@ decodeBool = Decode.custom \bytes, @Json {} ->
else
{ result: Err TooShort, rest: bytes }
decodeString = Decode.custom \bytes, @Json {} ->
jsonString : List U8 -> DecodeResult Str
jsonString = \bytes ->
{ before, others: afterStartingQuote } = List.split bytes 1
if
@ -335,6 +338,9 @@ decodeString = Decode.custom \bytes, @Json {} ->
else
{ result: Err TooShort, rest: bytes }
decodeString = Decode.custom \bytes, @Json {} ->
jsonString bytes
decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
decodeElems = \chunk, accum ->
when Decode.decodeWith chunk decodeElem (@Json {}) is
@ -372,3 +378,72 @@ decodeList = \decodeElem -> Decode.custom \bytes, @Json {} ->
{ result: Err TooShort, rest }
else
{ result: Err TooShort, rest: bytes }
parseExactChar : List U8, U8 -> DecodeResult {}
parseExactChar = \bytes, char ->
when List.get bytes 0 is
Ok c ->
if
c == char
then
{ result: Ok {}, rest: (List.split bytes 1).others }
else
{ result: Err TooShort, rest: bytes }
Err _ -> { result: Err TooShort, rest: bytes }
openBrace : List U8 -> DecodeResult {}
openBrace = \bytes -> parseExactChar bytes (asciiByte '{')
closingBrace : List U8 -> DecodeResult {}
closingBrace = \bytes -> parseExactChar bytes (asciiByte '}')
recordKey : List U8 -> DecodeResult Str
recordKey = \bytes -> jsonString bytes
anything : List U8 -> DecodeResult {}
anything = \bytes -> { result: Err TooShort, rest: bytes }
colon : List U8 -> DecodeResult {}
colon = \bytes -> parseExactChar bytes (asciiByte ':')
comma : List U8 -> DecodeResult {}
comma = \bytes -> parseExactChar bytes (asciiByte ',')
tryDecode : DecodeResult a, ({ val : a, rest : List U8 } -> DecodeResult b) -> DecodeResult b
tryDecode = \{ result, rest }, mapper ->
when result is
Ok val -> mapper { val, rest }
Err e -> { result: Err e, rest }
decodeRecord = \initialState, stepField, finalizer -> Decode.custom \bytes, @Json {} ->
# NB: the stepper function must be passed explicitly until #2894 is resolved.
decodeFields = \stepper, state, kvBytes ->
{ val: key, rest } <- recordKey kvBytes |> tryDecode
{ rest: afterColonBytes } <- colon rest |> tryDecode
{ val: newState, rest: beforeCommaOrBreak } <- tryDecode
(
when stepper state key is
Skip ->
{ rest: beforeCommaOrBreak } <- afterColonBytes |> anything |> tryDecode
{ result: Ok state, rest: beforeCommaOrBreak }
Keep decoder ->
Decode.decodeWith afterColonBytes decoder (@Json {})
)
{ result: commaResult, rest: nextBytes } = comma beforeCommaOrBreak
when commaResult is
Ok {} -> decodeFields stepField newState nextBytes
Err _ -> { result: Ok newState, rest: nextBytes }
{ rest: afterBraceBytes } <- bytes |> openBrace |> tryDecode
{ val: endStateResult, rest: beforeClosingBraceBytes } <- decodeFields stepField initialState afterBraceBytes |> tryDecode
{ rest: afterRecordBytes } <- beforeClosingBraceBytes |> closingBrace |> tryDecode
when finalizer endStateResult is
Ok val -> { result: Ok val, rest: afterRecordBytes }
Err e -> { result: Err e, rest: afterRecordBytes }

View file

@ -857,8 +857,44 @@ isMultipleOf : Int a, Int a -> Bool
bitwiseAnd : Int a, Int a -> Int a
bitwiseXor : Int a, Int a -> Int a
bitwiseOr : Int a, Int a -> Int a
## Bitwise left shift of a number by another
##
## The least significant bits always become 0. This means that shifting left is
## like multiplying by factors of two for unsigned integers.
##
## >>> shiftLeftBy 0b0000_0011 2 == 0b0000_1100
##
## >>> 0b0000_0101 |> shiftLeftBy 2 == 0b0000_1100
##
## In some languages `shiftLeftBy` is implemented as a binary operator `<<`.
shiftLeftBy : Int a, Int a -> Int a
## Bitwise arithmetic shift of a number by another
##
## The most significant bits are copied from the current.
##
## >>> shiftRightBy 0b0000_0011 2 == 0b0000_1100
##
## >>> 0b0001_0100 |> shiftRightBy 2 == 0b0000_0101
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b1110_0100
##
## In some languages `shiftRightBy` is implemented as a binary operator `>>>`.
shiftRightBy : Int a, Int a -> Int a
## Bitwise logical right shift of a number by another
##
## The most significant bits always become 0. This means that shifting left is
## like dividing by factors of two for unsigned integers.
##
## >>> shiftRightBy 0b0010_1000 2 == 0b0000_1010
##
## >>> 0b0010_1000 |> shiftRightBy 2 == 0b0000_1010
##
## >>> 0b1001_0000 |> shiftRightBy 2 == 0b0010_0100
##
## In some languages `shiftRightBy` is implemented as a binary operator `>>`.
shiftRightZfBy : Int a, Int a -> Int a
## Round off the given fraction to the nearest integer.

View file

@ -41,10 +41,39 @@ insert = \@Set dict, key ->
|> Dict.insert key {}
|> @Set
# Inserting a duplicate key has no effect.
expect
actual =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"
expected =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "baz"
expected == actual
len : Set k -> Nat
len = \@Set dict ->
Dict.len dict
# Inserting a duplicate key has no effect on length.
expect
actual =
Set.empty
|> Set.insert "foo"
|> Set.insert "bar"
|> Set.insert "foo"
|> Set.insert "baz"
|> Set.len
actual == 3
## Drops the given element from the set.
remove : Set k, k -> Set k
remove = \@Set dict, key ->

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod bitcode;
pub mod roc;

View file

@ -20,4 +20,4 @@ bitvec = "1"
[dev-dependencies]
pretty_assertions = "1.0.0"
indoc = "1.0.3"
indoc = "1.0.7"

View file

@ -444,7 +444,7 @@ fn no_region<T>(value: T) -> Loc<T> {
#[inline(always)]
fn tag(name: &'static str, args: Vec<Expr>, var_store: &mut VarStore) -> Expr {
Expr::Tag {
variant_var: var_store.fresh(),
tag_union_var: var_store.fresh(),
ext_var: var_store.fresh(),
name: TagName(name.into()),
arguments: args

View file

@ -535,12 +535,12 @@ fn deep_copy_expr_help<C: CopyEnv>(env: &mut C, copied: &mut Vec<Variable>, expr
},
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,
} => Tag {
variant_var: sub!(*variant_var),
tag_union_var: sub!(*variant_var),
ext_var: sub!(*ext_var),
name: name.clone(),
arguments: arguments
@ -1164,13 +1164,13 @@ mod test {
let var2 = new_var(&mut subs, FlexVar(Some(b)));
let expr = Expr::Tag {
variant_var: var1,
tag_union_var: var1,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("F".into()),
arguments: vec![(
var2,
Loc::at_zero(Expr::Tag {
variant_var: var2,
tag_union_var: var2,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("G".into()),
arguments: vec![],
@ -1185,7 +1185,7 @@ mod test {
match expr {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
mut arguments,
@ -1219,7 +1219,7 @@ mod test {
match arg.value {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,
@ -1250,13 +1250,13 @@ mod test {
let var2 = new_var(&mut source, FlexVar(Some(b)));
let expr = Expr::Tag {
variant_var: var1,
tag_union_var: var1,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("F".into()),
arguments: vec![(
var2,
Loc::at_zero(Expr::Tag {
variant_var: var2,
tag_union_var: var2,
ext_var: Variable::EMPTY_TAG_UNION,
name: TagName("G".into()),
arguments: vec![],
@ -1271,7 +1271,7 @@ mod test {
match expr {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
mut arguments,
@ -1300,7 +1300,7 @@ mod test {
match arg.value {
Expr::Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,

View file

@ -232,7 +232,7 @@ impl PendingTypeDef<'_> {
}
}
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Declaration {

View file

@ -184,7 +184,7 @@ pub enum Expr {
// Sum Types
Tag {
variant_var: Variable,
tag_union_var: Variable,
ext_var: Variable,
name: TagName,
arguments: Vec<(Variable, Loc<Expr>)>,
@ -780,12 +780,12 @@ pub fn canonicalize_expr<'a>(
return (fn_expr, output);
}
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
..
} => Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments: args,
@ -796,7 +796,7 @@ pub fn canonicalize_expr<'a>(
name,
..
} => Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments: args,
@ -1422,7 +1422,7 @@ fn canonicalize_when_branch<'a>(
if output.references.has_value_lookup(symbol) {
pattern_bound_symbols_body_needs.insert(symbol);
} else {
env.problem(Problem::UnusedDef(symbol, region));
env.problem(Problem::UnusedBranchDef(symbol, region));
}
}
@ -1893,7 +1893,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
}
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod abilities;
pub mod annotation;

View file

@ -696,15 +696,26 @@ pub fn canonicalize_module_defs<'a>(
referenced_values.extend(env.qualified_value_lookups.iter().copied());
referenced_types.extend(env.qualified_type_lookups.iter().copied());
let mut fix_closures_no_capture_symbols = VecSet::default();
let mut fix_closures_closure_captures = VecMap::default();
for index in 0..declarations.len() {
use crate::expr::DeclarationTag::*;
// For each declaration, we need to fixup the closures inside its def.
// Reuse the fixup buffer allocations from the previous iteration.
fix_closures_no_capture_symbols.clear();
fix_closures_closure_captures.clear();
match declarations.declarations[index] {
Value => {
// def pattern has no default expressions, so skip
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
Function(f_index) | Recursive(f_index) | TailRecursive(f_index) => {
let name = declarations.symbols[index].value;
@ -722,30 +733,51 @@ pub fn canonicalize_module_defs<'a>(
for (_, _, loc_pat) in function_def.arguments.iter_mut() {
fix_values_captured_in_closure_pattern(
&mut loc_pat.value,
&mut no_capture_symbols,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
Destructure(d_index) => {
let destruct_def = &mut declarations.destructs[d_index.index()];
let loc_pat = &mut destruct_def.loc_pattern;
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_pattern(&mut loc_pat.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_pattern(
&mut loc_pat.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
MutualRecursion { .. } => {
// the declarations of this group will be treaded individually by later iterations
}
Expectation => {
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
ExpectationFx => {
let loc_expr = &mut declarations.expressions[index];
fix_values_captured_in_closure_expr(&mut loc_expr.value, &mut VecSet::default());
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
&mut fix_closures_no_capture_symbols,
&mut fix_closures_closure_captures,
);
}
}
}
@ -771,16 +803,26 @@ pub fn canonicalize_module_defs<'a>(
fn fix_values_captured_in_closure_def(
def: &mut crate::def::Def,
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
// patterns can contain default expressions, so much go over them too!
fix_values_captured_in_closure_pattern(&mut def.loc_pattern.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut def.loc_pattern.value,
no_capture_symbols,
closure_captures,
);
fix_values_captured_in_closure_expr(&mut def.loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut def.loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
fn fix_values_captured_in_closure_defs(
defs: &mut [crate::def::Def],
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
// recursive defs cannot capture each other
for def in defs.iter() {
@ -789,16 +831,38 @@ fn fix_values_captured_in_closure_defs(
);
}
// TODO mutually recursive functions should both capture the union of both their capture sets
for def in defs.iter_mut() {
fix_values_captured_in_closure_def(def, no_capture_symbols);
fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
}
// Mutually recursive functions should both capture the union of all their capture sets
//
// Really unfortunate we make a lot of clones here, can this be done more efficiently?
let mut total_capture_set = Vec::default();
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
}) = &def.loc_expr.value
{
total_capture_set.extend(captured_symbols.iter().copied());
}
}
total_capture_set.sort_by_key(|(sym, _)| *sym);
total_capture_set.dedup_by_key(|(sym, _)| *sym);
for def in defs.iter_mut() {
if let Expr::Closure(ClosureData {
captured_symbols, ..
}) = &mut def.loc_expr.value
{
*captured_symbols = total_capture_set.clone();
}
}
}
fn fix_values_captured_in_closure_pattern(
pattern: &mut crate::pattern::Pattern,
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
use crate::pattern::Pattern::*;
@ -808,24 +872,35 @@ fn fix_values_captured_in_closure_pattern(
..
} => {
for (_, loc_arg) in loc_args.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
}
UnwrappedOpaque { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_pattern(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
RecordDestructure { destructs, .. } => {
for loc_destruct in destructs.iter_mut() {
use crate::pattern::DestructType::*;
match &mut loc_destruct.value.typ {
Required => {}
Optional(_, loc_expr) => {
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols)
}
Optional(_, loc_expr) => fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
),
Guard(_, loc_pattern) => fix_values_captured_in_closure_pattern(
&mut loc_pattern.value,
no_capture_symbols,
closure_captures,
),
}
}
@ -848,19 +923,28 @@ fn fix_values_captured_in_closure_pattern(
fn fix_values_captured_in_closure_expr(
expr: &mut crate::expr::Expr,
no_capture_symbols: &mut VecSet<Symbol>,
closure_captures: &mut VecMap<Symbol, Vec<(Symbol, Variable)>>,
) {
use crate::expr::Expr::*;
match expr {
LetNonRec(def, loc_expr) => {
// LetNonRec(Box<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_def(def, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_def(def, no_capture_symbols, closure_captures);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
LetRec(defs, loc_expr, _) => {
// LetRec(Vec<Def>, Box<Located<Expr>>, Variable, Aliases),
fix_values_captured_in_closure_defs(defs, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_defs(defs, no_capture_symbols, closure_captures);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
Expect {
@ -868,8 +952,16 @@ fn fix_values_captured_in_closure_expr(
loc_continuation,
lookups_in_cond: _,
} => {
fix_values_captured_in_closure_expr(&mut loc_condition.value, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_continuation.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_condition.value,
no_capture_symbols,
closure_captures,
);
fix_values_captured_in_closure_expr(
&mut loc_continuation.value,
no_capture_symbols,
closure_captures,
);
}
ExpectFx {
@ -891,16 +983,58 @@ fn fix_values_captured_in_closure_expr(
captured_symbols.retain(|(s, _)| !no_capture_symbols.contains(s));
captured_symbols.retain(|(s, _)| s != name);
let original_captures_len = captured_symbols.len();
let mut num_visited = 0;
let mut i = 0;
while num_visited < original_captures_len {
// If we've captured a capturing closure, replace the captured closure symbol with
// the symbols of its captures. That way, we can construct the closure with the
// captures it needs inside our body.
//
// E.g.
// x = ""
// inner = \{} -> x
// outer = \{} -> inner {}
//
// initially `outer` captures [inner], but this is then replaced with just [x].
let (captured_symbol, _) = captured_symbols[i];
if let Some(captures) = closure_captures.get(&captured_symbol) {
debug_assert!(!captures.is_empty());
captured_symbols.swap_remove(i);
captured_symbols.extend(captures);
// Jump two, because the next element is now one of the newly-added captures,
// which we don't need to check.
i += 2;
} else {
i += 1;
}
num_visited += 1;
}
if captured_symbols.len() > original_captures_len {
// Re-sort, since we've added new captures.
captured_symbols.sort_by_key(|(sym, _)| *sym);
}
if captured_symbols.is_empty() {
no_capture_symbols.insert(*name);
} else {
closure_captures.insert(*name, captured_symbols.to_vec());
}
// patterns can contain default expressions, so much go over them too!
for (_, _, loc_pat) in arguments.iter_mut() {
fix_values_captured_in_closure_pattern(&mut loc_pat.value, no_capture_symbols);
fix_values_captured_in_closure_pattern(
&mut loc_pat.value,
no_capture_symbols,
closure_captures,
);
}
fix_values_captured_in_closure_expr(&mut loc_body.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_body.value,
no_capture_symbols,
closure_captures,
);
}
Num(..)
@ -918,28 +1052,45 @@ fn fix_values_captured_in_closure_expr(
List { loc_elems, .. } => {
for elem in loc_elems.iter_mut() {
fix_values_captured_in_closure_expr(&mut elem.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut elem.value,
no_capture_symbols,
closure_captures,
);
}
}
When {
loc_cond, branches, ..
} => {
fix_values_captured_in_closure_expr(&mut loc_cond.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_cond.value,
no_capture_symbols,
closure_captures,
);
for branch in branches.iter_mut() {
fix_values_captured_in_closure_expr(&mut branch.value.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut branch.value.value,
no_capture_symbols,
closure_captures,
);
// patterns can contain default expressions, so much go over them too!
for loc_pat in branch.patterns.iter_mut() {
fix_values_captured_in_closure_pattern(
&mut loc_pat.pattern.value,
no_capture_symbols,
closure_captures,
);
}
if let Some(guard) = &mut branch.guard {
fix_values_captured_in_closure_expr(&mut guard.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut guard.value,
no_capture_symbols,
closure_captures,
);
}
}
}
@ -950,23 +1101,43 @@ fn fix_values_captured_in_closure_expr(
..
} => {
for (loc_cond, loc_then) in branches.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_cond.value, no_capture_symbols);
fix_values_captured_in_closure_expr(&mut loc_then.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_cond.value,
no_capture_symbols,
closure_captures,
);
fix_values_captured_in_closure_expr(
&mut loc_then.value,
no_capture_symbols,
closure_captures,
);
}
fix_values_captured_in_closure_expr(&mut final_else.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut final_else.value,
no_capture_symbols,
closure_captures,
);
}
Call(function, arguments, _) => {
fix_values_captured_in_closure_expr(&mut function.1.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut function.1.value,
no_capture_symbols,
closure_captures,
);
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
}
RunLowLevel { args, .. } | ForeignCall { args, .. } => {
for (_, arg) in args.iter_mut() {
fix_values_captured_in_closure_expr(arg, no_capture_symbols);
fix_values_captured_in_closure_expr(arg, no_capture_symbols, closure_captures);
}
}
@ -975,22 +1146,38 @@ fn fix_values_captured_in_closure_expr(
updates: fields, ..
} => {
for (_, field) in fields.iter_mut() {
fix_values_captured_in_closure_expr(&mut field.loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut field.loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
}
Access { loc_expr, .. } => {
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_expr.value,
no_capture_symbols,
closure_captures,
);
}
Tag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
}
OpaqueRef { argument, .. } => {
let (_, loc_arg) = &mut **argument;
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
fix_values_captured_in_closure_expr(
&mut loc_arg.value,
no_capture_symbols,
closure_captures,
);
}
OpaqueWrapFunction(_) => {}
}

View file

@ -244,7 +244,7 @@ pub fn walk_expr<V: Visitor>(visitor: &mut V, expr: &Expr, var: Variable) {
walk_record_fields(visitor, updates.iter());
}
Expr::Tag {
variant_var: _,
tag_union_var: _,
ext_var: _,
name: _,
arguments,

View file

@ -1023,7 +1023,7 @@ mod test_can {
# There was a bug where annotating a def meant that its
# references no longer got reported.
#
# https://github.com/rtfeldman/roc/issues/298
# https://github.com/roc-lang/roc/issues/298
x : List Booly
x = [y]

View file

@ -10,5 +10,5 @@ im = "15.0.0"
im-rc = "15.0.0"
wyhash = "0.5.0"
bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.12.1", features = [ "bumpalo" ] }
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
bitvec = "1"

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod all;

View file

@ -140,6 +140,12 @@ impl<K: PartialEq, V> VecMap<K, V> {
cur_idx: 0,
}
}
/// Removes all key/value pairs from the map, without affecting its allocated capacity.
pub fn clear(&mut self) {
self.keys.clear();
self.values.clear();
}
}
impl<K: PartialEq, V> Extend<(K, V)> for VecMap<K, V> {

View file

@ -83,6 +83,11 @@ impl<T: PartialEq> VecSet<T> {
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
self.elements.iter_mut()
}
/// Removes all elements from the set, without affecting its allocated capacity.
pub fn clear(&mut self) {
self.elements.clear()
}
}
impl<A: Ord> Extend<A> for VecSet<A> {

View file

@ -1040,7 +1040,7 @@ pub fn constrain_expr(
body_con
}
Tag {
variant_var,
tag_union_var: variant_var,
ext_var,
name,
arguments,

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod builtins;
pub mod expr;

View file

@ -19,3 +19,7 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
[features]
default = []
debug-derived-symbols = ["roc_module/debug-symbols"]
# Enables open extension variables for constructed records and tag unions.
# This is not necessary for code generation, but may be necessary if you are
# constraining and solving generated derived bodies.
open-extension-vars = []

File diff suppressed because it is too large Load diff

View file

@ -133,6 +133,12 @@ impl DerivedModule {
self.map.entry(key).or_insert(triple)
}
pub fn is_derived_def(&self, def_symbol: Symbol) -> bool {
self.map
.iter()
.any(|(_, (symbol, _, _))| *symbol == def_symbol)
}
pub fn iter_all(
&self,
) -> impl Iterator<Item = (&DeriveKey, &(Symbol, Def, SpecializationLambdaSets))> {

View file

@ -130,7 +130,7 @@ impl Env<'_> {
// we only expect `bar` to polymorphic at this stage!
//
// TODO: it would be better if `unify` could prune these for us. See also
// https://github.com/rtfeldman/roc/issues/3207; that is a blocker for this TODO.
// https://github.com/roc-lang/roc/issues/3207; that is a blocker for this TODO.
#[cfg(debug_assertions)]
{
for (spec_var, lambda_sets) in _lambda_sets_to_specialize.drain() {
@ -152,4 +152,27 @@ impl Env<'_> {
}
}
}
/// Creates an extension variable for a tag union or record.
///
/// Derivers should always construct tag union and record types such that they are closed.
/// If the `open-extension-vars` feature is turned on, flex extension vars will be
/// returned; otherwise, the appropriate closed extension variable for the type will be
/// returned.
#[inline(always)]
pub fn new_ext_var(&mut self, kind: ExtensionKind) -> Variable {
if cfg!(feature = "open-extension-vars") {
self.subs.fresh_unnamed_flex_var()
} else {
match kind {
ExtensionKind::Record => Variable::EMPTY_RECORD,
ExtensionKind::TagUnion => Variable::EMPTY_TAG_UNION,
}
}
}
}
pub(crate) enum ExtensionKind {
Record,
TagUnion,
}

View file

@ -1,7 +1,7 @@
use roc_module::symbol::Symbol;
use roc_module::{ident::Lowercase, symbol::Symbol};
use roc_types::subs::{Content, FlatType, Subs, Variable};
use crate::DeriveError;
use crate::{util::debug_name_record, DeriveError};
#[derive(Hash)]
pub enum FlatDecodable {
@ -12,12 +12,16 @@ pub enum FlatDecodable {
#[derive(Hash, PartialEq, Eq, Debug, Clone)]
pub enum FlatDecodableKey {
List(/* takes one variable */),
// Unfortunate that we must allocate here, c'est la vie
Record(Vec<Lowercase>),
}
impl FlatDecodableKey {
pub(crate) fn debug_name(&self) -> String {
match self {
FlatDecodableKey::List() => "list".to_string(),
FlatDecodableKey::Record(fields) => debug_name_record(fields),
}
}
}
@ -33,8 +37,25 @@ impl FlatDecodable {
Symbol::STR_STR => Ok(Immediate(Symbol::DECODE_STRING)),
_ => Err(Underivable),
},
FlatType::Record(_fields, _ext) => {
Err(Underivable) // yet
FlatType::Record(fields, ext) => {
let fields_iter = match fields.unsorted_iterator(subs, ext) {
Ok(it) => it,
Err(_) => return Err(Underivable),
};
let mut field_names = Vec::with_capacity(fields.len());
for (field_name, record_field) in fields_iter {
if record_field.is_optional() {
// Can't derive a concrete decoder for optional fields, since those are
// compile-time-polymorphic
return Err(Underivable);
}
field_names.push(field_name.clone());
}
field_names.sort();
Ok(Key(FlatDecodableKey::Record(field_names)))
}
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
Err(Underivable) // yet
@ -42,9 +63,7 @@ impl FlatDecodable {
FlatType::FunctionOrTagUnion(_name_index, _, _) => {
Err(Underivable) // yet
}
FlatType::EmptyRecord => {
Err(Underivable) // yet
}
FlatType::EmptyRecord => Ok(Key(FlatDecodableKey::Record(vec![]))),
FlatType::EmptyTagUnion => {
Err(Underivable) // yet
}

View file

@ -4,7 +4,10 @@ use roc_module::{
};
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
use crate::DeriveError;
use crate::{
util::{check_empty_ext_var, debug_name_record},
DeriveError,
};
#[derive(Hash)]
pub enum FlatEncodable {
@ -28,17 +31,7 @@ impl FlatEncodableKey {
FlatEncodableKey::List() => "list".to_string(),
FlatEncodableKey::Set() => "set".to_string(),
FlatEncodableKey::Dict() => "dict".to_string(),
FlatEncodableKey::Record(fields) => {
let mut str = String::from('{');
fields.iter().enumerate().for_each(|(i, f)| {
if i > 0 {
str.push(',');
}
str.push_str(f.as_str());
});
str.push('}');
str
}
FlatEncodableKey::Record(fields) => debug_name_record(fields),
FlatEncodableKey::TagUnion(tags) => {
let mut str = String::from('[');
tags.iter().enumerate().for_each(|(i, (tag, arity))| {
@ -56,22 +49,6 @@ impl FlatEncodableKey {
}
}
fn check_ext_var(
subs: &Subs,
ext_var: Variable,
is_empty_ext: impl Fn(&Content) -> bool,
) -> Result<(), DeriveError> {
let ext_content = subs.get_content_without_compacting(ext_var);
if is_empty_ext(ext_content) {
Ok(())
} else {
match ext_content {
Content::FlexVar(_) => Err(DeriveError::UnboundVar),
_ => Err(DeriveError::Underivable),
}
}
}
impl FlatEncodable {
pub(crate) fn from_var(subs: &Subs, var: Variable) -> Result<FlatEncodable, DeriveError> {
use DeriveError::*;
@ -86,7 +63,7 @@ impl FlatEncodable {
_ => Err(Underivable),
},
FlatType::Record(fields, ext) => {
check_ext_var(subs, ext, |ext| {
check_empty_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyRecord))
})?;
@ -106,7 +83,7 @@ impl FlatEncodable {
// [ A t1, B t1 t2 ] as R
// look the same on the surface, because `R` is only somewhere inside of the
// `t`-prefixed payload types.
check_ext_var(subs, ext, |ext| {
check_empty_ext_var(subs, ext, |ext| {
matches!(ext, Content::Structure(FlatType::EmptyTagUnion))
})?;

View file

@ -15,6 +15,7 @@
pub mod decoding;
pub mod encoding;
mod util;
use decoding::{FlatDecodable, FlatDecodableKey};
use encoding::{FlatEncodable, FlatEncodableKey};

View file

@ -0,0 +1,32 @@
use roc_module::ident::Lowercase;
use roc_types::subs::{Content, Subs, Variable};
use crate::DeriveError;
pub(crate) fn check_empty_ext_var(
subs: &Subs,
ext_var: Variable,
is_empty_ext: impl Fn(&Content) -> bool,
) -> Result<(), DeriveError> {
let ext_content = subs.get_content_without_compacting(ext_var);
if is_empty_ext(ext_content) {
Ok(())
} else {
match ext_content {
Content::FlexVar(_) => Err(DeriveError::UnboundVar),
_ => Err(DeriveError::Underivable),
}
}
}
pub(crate) fn debug_name_record(fields: &[Lowercase]) -> String {
let mut str = String::from('{');
fields.iter().enumerate().for_each(|(i, f)| {
if i > 0 {
str.push(',');
}
str.push_str(f.as_str());
});
str.push('}');
str
}

View file

@ -14,6 +14,6 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "1.0.0"
indoc = "1.0.3"
indoc = "1.0.7"
roc_test_utils = { path = "../../test_utils" }
walkdir = "2.3.2"

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod annotation;
pub mod collection;

View file

@ -1,7 +1,7 @@
# Dev Backend
The dev backend is focused on generating decent binaries extremely fast.
It goes from Roc's [mono ir](https://github.com/rtfeldman/roc/blob/trunk/compiler/mono/src/ir.rs) to an object file ready to be linked.
It goes from Roc's [mono ir](https://github.com/roc-lang/roc/blob/main/crates/compiler/mono/src/ir.rs) to an object file ready to be linked.
## General Process
@ -22,14 +22,14 @@ rust should be abled compile each specific target (`linux-arm`, `darwin-x86_64`,
### Backend
[Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) is the core abstraction.
[Backend](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/lib.rs) is the core abstraction.
It understands Roc's mono ir and some high level ideas about the generation process.
The main job of Backend is to do high level optimizatons (like lazy literal loading) and parse the mono ir.
Every target specific backend must implement this trait.
### Backend64Bit
[Backend64Bit](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is more or less what it sounds like.
[Backend64Bit](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs) is more or less what it sounds like.
It is the backend that understands 64 bit architectures.
Currently it is the only backend implementation, but a 32 bit implementation will probably come in the future.
This backend understands that the unit of data movement is 64 bit.
@ -44,39 +44,39 @@ Backend64Bit is generic over these types instead of containing these types withi
### Assembler
[Assembler](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the trait for generating assembly bytes.
[Assembler](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs) is the trait for generating assembly bytes.
It defines a set of RISC-like assembly calls that must be implemented for each architecture.
A lot of these calls may not map one to one with actual assembly instructions for each architecture.
Instead, they are a general abstraction over functionality shared between all architectures.
This will grow regularly as more Roc builtins are added.
Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/x86_64.rs).
Here are example implementations for [arm](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/x86_64.rs).
### CallConv
[CallConv](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs) is the abstraction over calling conventions.
[CallConv](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs) is the abstraction over calling conventions.
It deals with register and stack specific information related to passing and returning arguments.
Here are example implementations for [arm](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/x86_64.rs).
Here are example implementations for [arm](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/aarch64.rs) and [x86_64](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/x86_64.rs).
## Adding New Features
Adding a new builtin to the dev backend can be pretty simple.
Here is [an example](https://github.com/rtfeldman/roc/pull/893/files) of adding `Num.Sub`.
Here is [an example](https://github.com/roc-lang/roc/pull/893/files) of adding `Num.Sub`.
This is the general procedure I follow with some helpful links:
1. Find a feature that is just n+1.
For example, since we already have integers, adding a builtin that functions on them should be n+1.
On the other hand, since we don't yet have booleans/conditionals, adding if statements may not yet be n+1.
A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/rtfeldman/roc/tree/trunk/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend.
A good place to look for missing features is in the test files for generation in [test_gen](https://github.com/roc-lang/roc/tree/main/crates/compiler/test_gen). Any test that is not enabled for the `gen-dev` feature still needs to be added to the dev backend. Eventually all features should be enabled for the dev backend.
1. Pick/write the simplest test case you can find for the new feature.
Just add `feature = "gen-dev"` to the `cfg` line for the test case.
1. Uncomment the code to print out procedures [from here](https://github.com/rtfeldman/roc/blob/b03ed18553569314a420d5bf1fb0ead4b6b5ecda/compiler/test_gen/src/helpers/dev.rs#L76) and run the test.
1. Uncomment the code to print out procedures [from here](https://github.com/roc-lang/roc/blob/b03ed18553569314a420d5bf1fb0ead4b6b5ecda/compiler/test_gen/src/helpers/dev.rs#L76) and run the test.
It should fail and print out the mono ir for this test case.
Seeing the actual mono ir tends to be very helpful for complex additions.
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/lib.rs) trait.
1. Generally it will fail in one of the match statements in the [Backend](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/lib.rs) trait.
Add the correct pattern matching and likely new function for your new builtin.
This will break the compile until you add the same function to places that implement the trait,
like [Backend64Bit](https://github.com/rtfeldman/roc/blob/trunk/compiler/gen_dev/src/generic64/mod.rs).
like [Backend64Bit](https://github.com/roc-lang/roc/blob/main/crates/compiler/gen_dev/src/generic64/mod.rs).
1. Keep following the chain down.
To implement the function in Backend64Bit, you may need to add new assembly calls.
Feel free to ignore backends that aren't x86_64 for now and just add `unimplemented!`.

View file

@ -440,7 +440,39 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) {
todo!("register multiplication for AArch64");
todo!("register signed multiplication for AArch64");
}
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
_buf: &mut Vec<'a, u8>,
_storage_manager: &mut StorageManager<'a, AArch64GeneralReg, AArch64FloatReg, ASM, CC>,
_dst: AArch64GeneralReg,
_src1: AArch64GeneralReg,
_src2: AArch64GeneralReg,
) where
ASM: Assembler<AArch64GeneralReg, AArch64FloatReg>,
CC: CallConv<AArch64GeneralReg, AArch64FloatReg, ASM>,
{
todo!("register unsigned multiplication for AArch64");
}
#[inline(always)]
fn mul_freg32_freg32_freg32(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("multiplication for floats for AArch64");
}
#[inline(always)]
fn mul_freg64_freg64_freg64(
_buf: &mut Vec<'_, u8>,
_dst: AArch64FloatReg,
_src1: AArch64FloatReg,
_src2: AArch64FloatReg,
) {
todo!("multiplication for floats for AArch64");
}
#[inline(always)]

View file

@ -21,7 +21,7 @@ mod disassembler_test_macro;
pub(crate) mod storage;
pub(crate) mod x86_64;
use storage::StorageManager;
use storage::{RegStorage, StorageManager};
const REFCOUNT_ONE: u64 = i64::MIN as u64;
// TODO: on all number functions double check and deal with over/underflow.
@ -210,12 +210,33 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait>: Sized + Copy {
fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg);
fn neg_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg);
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn mul_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: FloatReg,
src1: FloatReg,
src2: FloatReg,
);
fn imul_reg64_reg64_reg64(
buf: &mut Vec<'_, u8>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
);
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, GeneralReg, FloatReg, ASM, CC>,
dst: GeneralReg,
src1: GeneralReg,
src2: GeneralReg,
) where
ASM: Assembler<GeneralReg, FloatReg>,
CC: CallConv<GeneralReg, FloatReg, ASM>;
fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32);
fn sub_reg64_reg64_reg64(
@ -339,6 +360,19 @@ pub fn new_backend_64bit<
}
}
macro_rules! quadword_and_smaller {
() => {
IntWidth::I64
| IntWidth::U64
| IntWidth::I32
| IntWidth::U32
| IntWidth::I16
| IntWidth::U16
| IntWidth::I8
| IntWidth::U8
};
}
impl<
'a,
GeneralReg: RegTrait,
@ -699,16 +733,7 @@ impl<
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
match layout {
Layout::Builtin(Builtin::Int(
IntWidth::I64
| IntWidth::U64
| IntWidth::I32
| IntWidth::U32
| IntWidth::I16
| IntWidth::U16
| IntWidth::I8
| IntWidth::U8,
)) => {
Layout::Builtin(Builtin::Int(quadword_and_smaller!())) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -736,7 +761,9 @@ impl<
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>) {
match layout {
Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => {
Layout::Builtin(Builtin::Int(
IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
@ -746,6 +773,37 @@ impl<
.load_to_general_reg(&mut self.buf, src2);
ASM::imul_reg64_reg64_reg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
Layout::Builtin(Builtin::Int(
IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8,
)) => {
let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst);
let src1_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src1);
let src2_reg = self
.storage_manager
.load_to_general_reg(&mut self.buf, src2);
ASM::umul_reg64_reg64_reg64(
&mut self.buf,
&mut self.storage_manager,
dst_reg,
src1_reg,
src2_reg,
);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::mul_freg64_freg64_freg64(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst);
let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1);
let src2_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src2);
ASM::mul_freg32_freg32_freg32(&mut self.buf, dst_reg, src1_reg, src2_reg);
}
x => todo!("NumMul: layout, {:?}", x),
}
}

View file

@ -22,7 +22,7 @@ use StackStorage::*;
use Storage::*;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum RegStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
pub enum RegStorage<GeneralReg: RegTrait, FloatReg: RegTrait> {
General(GeneralReg),
Float(FloatReg),
}
@ -756,7 +756,7 @@ impl<
#[allow(dead_code)]
/// Ensures that a register is free. If it is not free, data will be moved to make it free.
fn ensure_reg_free(
pub fn ensure_reg_free(
&mut self,
buf: &mut Vec<'a, u8>,
wanted_reg: RegStorage<GeneralReg, FloatReg>,

View file

@ -1009,6 +1009,58 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
imul_reg64_reg64(buf, dst, src2);
}
fn umul_reg64_reg64_reg64<'a, ASM, CC>(
buf: &mut Vec<'a, u8>,
storage_manager: &mut StorageManager<'a, X86_64GeneralReg, X86_64FloatReg, ASM, CC>,
dst: X86_64GeneralReg,
src1: X86_64GeneralReg,
src2: X86_64GeneralReg,
) where
ASM: Assembler<X86_64GeneralReg, X86_64FloatReg>,
CC: CallConv<X86_64GeneralReg, X86_64FloatReg, ASM>,
{
use crate::generic64::RegStorage;
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RAX));
storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RDX));
mov_reg64_reg64(buf, X86_64GeneralReg::RAX, src1);
mul_reg64_reg64(buf, src2);
mov_reg64_reg64(buf, dst, X86_64GeneralReg::RAX);
}
fn mul_freg32_freg32_freg32(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
mulss_freg32_freg32(buf, dst, src2);
} else if dst == src2 {
mulss_freg32_freg32(buf, dst, src1);
} else {
movss_freg32_freg32(buf, dst, src1);
mulss_freg32_freg32(buf, dst, src2);
}
}
#[inline(always)]
fn mul_freg64_freg64_freg64(
buf: &mut Vec<'_, u8>,
dst: X86_64FloatReg,
src1: X86_64FloatReg,
src2: X86_64FloatReg,
) {
if dst == src1 {
mulsd_freg64_freg64(buf, dst, src2);
} else if dst == src2 {
mulsd_freg64_freg64(buf, dst, src1);
} else {
movsd_freg64_freg64(buf, dst, src1);
mulsd_freg64_freg64(buf, dst, src2);
}
}
#[inline(always)]
fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize {
jmp_imm32(buf, offset);
@ -1280,12 +1332,25 @@ impl X86_64Assembler {
}
}
const REX: u8 = 0x40;
const REX_W: u8 = REX | 0x8;
// see https://wiki.osdev.org/X86-64_Instruction_Encoding#Encoding
/// If set, 64-bit operand size is used
const REX_PREFIX_W: u8 = 0b1000;
/// Extension to the MODRM.reg
const REX_PREFIX_R: u8 = 0b0100;
#[allow(unused)]
/// Extension to the SIB.index field
const REX_PREFIX_X: u8 = 0b0010;
/// Extension to the MODRM.rm
const REX_PREFIX_B: u8 = 0b0001;
/// Wide REX
const REX_W: u8 = REX | REX_PREFIX_W;
#[inline(always)]
fn add_rm_extension<T: RegTrait>(reg: T, byte: u8) -> u8 {
if reg.value() > 7 {
byte | 1
byte | REX_PREFIX_B
} else {
byte
}
@ -1299,7 +1364,7 @@ fn add_opcode_extension(reg: X86_64GeneralReg, byte: u8) -> u8 {
#[inline(always)]
fn add_reg_extension<T: RegTrait>(reg: T, byte: u8) -> u8 {
if reg.value() > 7 {
byte | 4
byte | REX_PREFIX_R
} else {
byte
}
@ -1396,6 +1461,46 @@ fn addss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64Fl
}
}
/// `MULSD xmm1,xmm2/m64` -> Multiply the low double-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulsd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF2,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF2, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
/// `ADDSS xmm1,xmm2/m64` -> Add the low single-precision floating-point value from xmm2/mem to xmm1 and store the result in xmm1.
#[inline(always)]
fn mulss_freg32_freg32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
let dst_mod = dst as u8 % 8;
let src_high = src as u8 > 7;
let src_mod = src as u8 % 8;
if dst_high || src_high {
buf.extend(&[
0xF3,
0x40 | ((dst_high as u8) << 2) | (src_high as u8),
0x0F,
0x59,
0xC0 | (dst_mod << 3) | (src_mod),
])
} else {
buf.extend(&[0xF3, 0x0F, 0x59, 0xC0 | (dst_mod << 3) | (src_mod)])
}
}
#[inline(always)]
fn andpd_freg64_freg64(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, src: X86_64FloatReg) {
let dst_high = dst as u8 > 7;
@ -1465,6 +1570,19 @@ fn imul_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64Gen
extended_binop_reg64_reg64(0x0F, 0xAF, buf, src, dst);
}
/// `MUL r/m64` -> Unsigned Multiply r/m64 to r64.
#[inline(always)]
fn mul_reg64_reg64(buf: &mut Vec<'_, u8>, src: X86_64GeneralReg) {
let mut rex = REX_W;
rex = add_reg_extension(src, rex);
if src.value() > 7 {
rex |= REX_PREFIX_B;
}
buf.extend(&[rex, 0xF7, 0b1110_0000 | (src as u8 % 8)]);
}
/// Jump near, relative, RIP = RIP + 32-bit displacement sign extended to 64-bits.
#[inline(always)]
fn jmp_imm32(buf: &mut Vec<'_, u8>, imm: i32) {
@ -2086,6 +2204,35 @@ mod tests {
);
}
#[test]
fn test_mul_reg64_reg64() {
disassembler_test!(
mul_reg64_reg64,
|reg| format!("mul {}", reg),
ALL_GENERAL_REGS
);
}
#[test]
fn test_mulsd_freg64_freg64() {
disassembler_test!(
mulsd_freg64_freg64,
|reg1, reg2| format!("mulsd {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_mulss_freg32_freg32() {
disassembler_test!(
mulss_freg32_freg32,
|reg1, reg2| format!("mulss {}, {}", reg1, reg2),
ALL_FLOAT_REGS,
ALL_FLOAT_REGS
);
}
#[test]
fn test_jmp_imm32() {
const INST_SIZE: i32 = 5;

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
use bumpalo::{collections::Vec, Bump};

View file

@ -172,7 +172,7 @@ fn build_object<'a, B: Backend<'a>>(
let arena = backend.env().arena;
/*
// Commented out because we couldn't figure out how to get it to work on mac - see https://github.com/rtfeldman/roc/pull/1323
// Commented out because we couldn't figure out how to get it to work on mac - see https://github.com/roc-lang/roc/pull/1323
let comment = output.add_section(vec![], b".comment".to_vec(), SectionKind::OtherString);
output.append_section_data(
comment,

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
// we actually want to compare against the literal float bits
#![allow(clippy::float_cmp)]

View file

@ -3929,7 +3929,7 @@ pub fn get_sjlj_buffer<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> PointerValu
pub fn build_setjmp_call<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let jmp_buf = get_sjlj_buffer(env);
if cfg!(target_arch = "aarch64") {
// Due to https://github.com/rtfeldman/roc/issues/2965, we use a setjmp we linked in from Zig
// Due to https://github.com/roc-lang/roc/issues/2965, we use a setjmp we linked in from Zig
call_bitcode_fn(env, &[jmp_buf.into()], bitcode::UTILS_SETJMP)
} else {
// Anywhere else, use the LLVM intrinsic.
@ -6459,11 +6459,11 @@ fn to_cc_type<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
) -> BasicTypeEnum<'ctx> {
match layout {
Layout::Builtin(builtin) => to_cc_type_builtin(env, builtin),
_ => {
match layout.runtime_representation() {
Layout::Builtin(builtin) => to_cc_type_builtin(env, &builtin),
layout => {
// TODO this is almost certainly incorrect for bigger structs
basic_type_from_layout(env, layout)
basic_type_from_layout(env, &layout)
}
}
}
@ -7087,22 +7087,13 @@ fn build_int_binop<'a, 'ctx, 'env>(
NumBitwiseAnd => bd.build_and(lhs, rhs, "int_bitwise_and").into(),
NumBitwiseXor => bd.build_xor(lhs, rhs, "int_bitwise_xor").into(),
NumBitwiseOr => bd.build_or(lhs, rhs, "int_bitwise_or").into(),
NumShiftLeftBy => {
// NOTE arguments are flipped;
// we write `assert_eq!(0b0000_0001 << 0, 0b0000_0001);`
// as `Num.shiftLeftBy 0 0b0000_0001
bd.build_left_shift(rhs, lhs, "int_shift_left").into()
}
NumShiftRightBy => {
// NOTE arguments are flipped;
bd.build_right_shift(rhs, lhs, true, "int_shift_right")
.into()
}
NumShiftRightZfBy => {
// NOTE arguments are flipped;
bd.build_right_shift(rhs, lhs, false, "int_shift_right_zf")
.into()
}
NumShiftLeftBy => bd.build_left_shift(lhs, rhs, "int_shift_left").into(),
NumShiftRightBy => bd
.build_right_shift(lhs, rhs, true, "int_shift_right")
.into(),
NumShiftRightZfBy => bd
.build_right_shift(lhs, rhs, false, "int_shift_right_zf")
.into(),
_ => {
unreachable!("Unrecognized int binary operation: {:?}", op);

View file

@ -361,7 +361,7 @@ impl<'ctx> RocUnion<'ctx> {
// set the tag id
//
// NOTE: setting the tag id initially happened before writing the data into it.
// That turned out to expose UB. More info at https://github.com/rtfeldman/roc/issues/3554
// That turned out to expose UB. More info at https://github.com/roc-lang/roc/issues/3554
if let Some(tag_id) = tag_id {
let tag_id_type = match self.tag_type.unwrap() {
TagType::I8 => env.context.i8_type(),

View file

@ -517,7 +517,7 @@ impl<'a> WasmBackend<'a> {
// Load all the arguments for the inner function
for (i, wrapper_arg) in wrapper_arg_layouts.iter().enumerate() {
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner)
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner). We'll handle it below.
let is_return_pointer = i == wrapper_arg_layouts.len() - 1; // Skip return pointer (may not be an arg for inner. And if it is, swaps from end to start)
if is_closure_data || is_return_pointer {
continue;
@ -540,7 +540,23 @@ impl<'a> WasmBackend<'a> {
// If the inner function has closure data, it's the last arg of the inner fn
let closure_data_layout = wrapper_arg_layouts[0];
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
// The closure data exists, and will have been passed in to the wrapper as a
// one-element struct.
let inner_closure_data_layout = match closure_data_layout {
Layout::Struct {
field_layouts: [inner],
..
} => inner,
other => internal_error!(
"Expected a boxed layout for wrapped closure data, got {:?}",
other
),
};
self.code_builder.get_local(LocalId(0));
// Since the closure data is wrapped in a one-element struct, we've been passed in the
// pointer to that struct in the stack memory. To get the closure data we just need to
// dereference the pointer.
self.dereference_boxed_value(inner_closure_data_layout);
}
// Call the wrapped inner function

View file

@ -183,7 +183,7 @@ impl<'a> LowLevelCall<'a> {
/// Wrap an integer that should have less than 32 bits, but is represented in Wasm as i32.
/// This may seem like deliberately introducing an error!
/// But we want all targets to behave the same, and hash algos rely on wrapping.
/// Discussion: https://github.com/rtfeldman/roc/pull/2117#discussion_r760723063
/// Discussion: https://github.com/roc-lang/roc/pull/2117#discussion_r760723063
fn wrap_small_int(&self, backend: &mut WasmBackend<'a>, int_width: IntWidth) {
let bits = 8 * int_width.stack_size() as i32;
let shift = 32 - bits;
@ -1599,11 +1599,11 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftLeftBy => {
// Swap order of arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
let num = self.arguments[0];
let bits = self.arguments[1];
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
match CodeGenNumType::from(self.ret_layout) {
I32 => backend.code_builder.i32_shl(),
I64 => backend.code_builder.i64_shl(),
@ -1612,8 +1612,8 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftRightBy => {
let bits = self.arguments[0];
let num = self.arguments[1];
let num = self.arguments[0];
let bits = self.arguments[1];
match CodeGenNumType::from(self.ret_layout) {
I32 => {
// In most languages this operation is for signed numbers, but Roc defines it on all integers.
@ -1657,41 +1657,39 @@ impl<'a> LowLevelCall<'a> {
}
}
NumShiftRightZfBy => {
let num = self.arguments[0];
let bits = self.arguments[1];
match CodeGenNumType::from(self.ret_layout) {
I32 => {
// In most languages this operation is for unsigned numbers, but Roc defines it on all integers.
// So the argument is implicitly converted to unsigned before the shift operator.
// We need to make that conversion explicit for i8 and i16, which use Wasm's i32 type.
let bit_width = 8 * self.ret_layout.stack_size(TARGET_INFO);
if bit_width < 32 && symbol_is_signed_int(backend, self.arguments[0]) {
if bit_width < 32 && symbol_is_signed_int(backend, bits) {
let mask = (1 << bit_width) - 1;
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.arguments[1]]);
.load_symbols(&mut backend.code_builder, &[num]);
backend.code_builder.i32_const(mask);
backend.code_builder.i32_and();
backend
.storage
.load_symbols(&mut backend.code_builder, &[self.arguments[0]]);
.load_symbols(&mut backend.code_builder, &[bits]);
} else {
// swap the arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
}
backend.code_builder.i32_shr_u();
}
I64 => {
// swap the arguments
backend.storage.load_symbols(
&mut backend.code_builder,
&[self.arguments[1], self.arguments[0]],
);
backend
.storage
.load_symbols(&mut backend.code_builder, &[num, bits]);
backend.code_builder.i64_shr_u();
}
I128 => todo!("{:?} for I128", self.lowlevel),
@ -1857,11 +1855,15 @@ impl<'a> LowLevelCall<'a> {
/// Equality and inequality
/// These can operate on any data type (except functions) so they're more complex than other operators.
fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) {
let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]];
let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]];
let arg_layout =
backend.storage.symbol_layouts[&self.arguments[0]].runtime_representation();
let other_arg_layout =
backend.storage.symbol_layouts[&self.arguments[1]].runtime_representation();
debug_assert!(
arg_layout == other_arg_layout,
"Cannot do `==` comparison on different types"
"Cannot do `==` comparison on different types: {:?} vs {:?}",
arg_layout,
other_arg_layout
);
let invert_result = matches!(self.lowlevel, LowLevel::NotEq);
@ -2113,6 +2115,18 @@ pub fn call_higher_order_lowlevel<'a>(
..
} = passed_function;
// The zig lowlevel builtins expect the passed functions' closure data to always
// be sent as an opaque pointer. On the Roc side, however, we need to call the passed function
// with the Roc representation of the closure data. There are three possible cases for that
// representation:
//
// 1. The closure data is a struct
// 2. The closure data is an unwrapped value
// 3. There is no closure data
//
// To uniformly deal with the first two cases, always put the closure data, when it exists,
// into a one-element struct. That way, we get a pointer (i32) that can be passed to the zig lowlevels.
// The wrapper around the passed function will access the actual closure data in the struct.
let (closure_data_layout, closure_data_exists) =
match backend.storage.symbol_layouts[captured_environment] {
Layout::LambdaSet(lambda_set) => {
@ -2131,6 +2145,46 @@ pub fn call_higher_order_lowlevel<'a>(
x => internal_error!("Closure data has an invalid layout\n{:?}", x),
};
let (wrapped_captured_environment, wrapped_captures_layout) = if closure_data_exists {
// If there is closure data, make sure we put in a struct it before passing it to the
// external builtin impl. That way it's always an `i32` pointer.
let wrapped_closure_data_sym = backend.create_symbol("wrapped_captures");
let wrapped_captures_layout =
Layout::struct_no_name_order(backend.env.arena.alloc([closure_data_layout]));
// make sure that the wrapping struct is available in stack memory, so we can hand out a
// pointer to it.
let wrapped_storage = backend.storage.allocate_var(
wrapped_captures_layout,
wrapped_closure_data_sym,
crate::storage::StoredVarKind::Variable,
);
let (wrapped_storage_local_ptr, wrapped_storage_offset) = match wrapped_storage {
StoredValue::StackMemory { location, .. } => {
location.local_and_offset(backend.storage.stack_frame_pointer)
}
other => internal_error!(
"Struct should be allocated in stack memory, but it's in {:?}",
other
),
};
// copy the actual closure data into the first and only element of the wrapping struct.
backend.storage.copy_value_to_memory(
&mut backend.code_builder,
wrapped_storage_local_ptr,
wrapped_storage_offset,
*captured_environment,
);
(wrapped_closure_data_sym, wrapped_captures_layout)
} else {
// If we don't capture anything, pass along the captured environment as-is - the wrapper
// function will take care not to unwrap this.
(*captured_environment, closure_data_layout)
};
// We create a wrapper around the passed function, which just unboxes the arguments.
// This allows Zig builtins to have a generic pointer-based interface.
let helper_proc_source = {
@ -2164,7 +2218,7 @@ pub fn call_higher_order_lowlevel<'a>(
argument_layouts.len()
};
wrapper_arg_layouts.push(closure_data_layout);
wrapper_arg_layouts.push(wrapped_captures_layout);
wrapper_arg_layouts.extend(
argument_layouts
.iter()
@ -2204,7 +2258,7 @@ pub fn call_higher_order_lowlevel<'a>(
.get_refcount_fn_index(Layout::Builtin(Builtin::Int(IntWidth::I32)), HelperOp::Inc);
backend.get_fn_ptr(inc_fn)
} else {
let inc_fn = backend.get_refcount_fn_index(closure_data_layout, HelperOp::Inc);
let inc_fn = backend.get_refcount_fn_index(wrapped_captures_layout, HelperOp::Inc);
backend.get_fn_ptr(inc_fn)
};
@ -2218,7 +2272,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2231,7 +2285,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2244,7 +2298,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2257,7 +2311,7 @@ pub fn call_higher_order_lowlevel<'a>(
wrapper_fn_ptr,
inc_fn_ptr,
closure_data_exists,
*captured_environment,
wrapped_captured_environment,
*owns_captured_environment,
),
@ -2280,7 +2334,9 @@ pub fn call_higher_order_lowlevel<'a>(
backend.storage.load_symbol_zig(cb, *xs);
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[*captured_environment]);
backend
.storage
.load_symbols(cb, &[wrapped_captured_environment]);
} else {
// load_symbols assumes that a zero-size arg should be eliminated in code gen,
// but that's a specialization that our Zig code doesn't have! Pass a null pointer.

View file

@ -719,9 +719,10 @@ impl<'a> Storage<'a> {
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
// Note: load_symbols will not destroy the value, so we can use it again later.
// It will leave a Popped marker in the VM stack model in CodeBuilder
self.load_symbols(code_builder, &[from_symbol]);
code_builder.set_local(*to_local_id);
self.symbol_storage_map.insert(from_symbol, to.clone());
}
(

View file

@ -234,6 +234,14 @@ impl Wasm32Result for () {
}
}
impl Wasm32Result for std::convert::Infallible {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
code_builder.call(main_function_index, 0, false);
code_builder.get_global(0);
code_builder.build_fn_header_and_footer(&[], 0, None);
}
}
impl<T, U> Wasm32Result for (T, U)
where
T: Wasm32Result + Wasm32Sized,

View file

@ -203,6 +203,30 @@ impl From<&str> for IdentStr {
}
}
impl From<IdentStr> for String {
fn from(ident_str: IdentStr) -> Self {
if ident_str.is_small_str() {
// Copy it to a heap allocation
ident_str.as_str().to_string()
} else {
// Reuse the existing heap allocation
let string = unsafe {
String::from_raw_parts(
ident_str.as_ptr() as *mut u8,
ident_str.len(),
ident_str.len(),
)
};
// Make sure not to drop the IdentStr, since now there's
// a String referencing its heap-allocated contents.
std::mem::forget(ident_str);
string
}
}
}
impl From<String> for IdentStr {
fn from(string: String) -> Self {
if string.len() <= Self::SMALL_STR_BYTES {

View file

@ -358,7 +358,7 @@ pub fn unify(
);
// At this point we can't do anything with must-implement constraints, since we're no
// longer solving. We must assume that they were totally caught during solving.
// After we land https://github.com/rtfeldman/roc/issues/3207 this concern should totally
// After we land https://github.com/roc-lang/roc/issues/3207 this concern should totally
// go away.
let _ = must_implement_constraints;
// Pools are only used to keep track of variable ranks for generalization purposes.

View file

@ -54,6 +54,29 @@ pub fn load_single_threaded<'a>(
)
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum LoadMonomorphizedError<'a> {
LoadingProblem(LoadingProblem<'a>),
/// Errors in the module that should be reported, without compiling the executable.
/// Relevant in check-and-then-build mode.
ErrorModule(LoadedModule),
}
impl<'a> From<LoadingProblem<'a>> for LoadMonomorphizedError<'a> {
fn from(problem: LoadingProblem<'a>) -> Self {
Self::LoadingProblem(problem)
}
}
// HACK only relevant because of some uses of `map_err` that decay into this error, but call `todo` -
// rustc seems to be unhappy with that.
impl<'a> From<()> for LoadMonomorphizedError<'a> {
fn from(_: ()) -> Self {
todo!()
}
}
#[allow(clippy::too_many_arguments)]
pub fn load_and_monomorphize_from_str<'a>(
arena: &'a Bump,
@ -78,14 +101,14 @@ pub fn load_and_monomorphize(
filename: PathBuf,
exposed_types: ExposedByModule,
load_config: LoadConfig,
) -> Result<MonomorphizedModule<'_>, LoadingProblem<'_>> {
) -> Result<MonomorphizedModule<'_>, LoadMonomorphizedError<'_>> {
use LoadResult::*;
let load_start = LoadStart::from_path(arena, filename, load_config.render)?;
match load(arena, load_start, exposed_types, load_config)? {
Monomorphized(module) => Ok(module),
TypeChecked(_) => unreachable!(""),
TypeChecked(module) => Err(LoadMonomorphizedError::ErrorModule(module)),
}
}

View file

@ -29,10 +29,10 @@ roc_debug_flags = { path = "../debug_flags" }
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] }
parking_lot = "0.12"
crossbeam = "0.8.1"
crossbeam = "0.8.2"
[dev-dependencies]
pretty_assertions = "1.0.0"
maplit = "1.0.2"
indoc = "1.0.3"
indoc = "1.0.7"
roc_test_utils = { path = "../../test_utils" }

View file

@ -130,13 +130,15 @@ pub enum ExecutionMode {
Test,
Check,
Executable,
/// Like [`ExecutionMode::Executable`], but stops in the presence of type errors.
ExecutableIfCheck,
}
impl ExecutionMode {
fn goal_phase(&self) -> Phase {
match self {
ExecutionMode::Test | ExecutionMode::Executable => Phase::MakeSpecializations,
ExecutionMode::Check => Phase::SolveTypes,
ExecutionMode::Check | ExecutionMode::ExecutableIfCheck => Phase::SolveTypes,
}
}
}
@ -168,6 +170,22 @@ struct ModuleCache<'a> {
sources: MutMap<ModuleId, (PathBuf, &'a str)>,
}
impl<'a> ModuleCache<'a> {
pub fn total_problems(&self) -> usize {
let mut total = 0;
for problems in self.can_problems.values() {
total += problems.len();
}
for problems in self.type_problems.values() {
total += problems.len();
}
total
}
}
impl Default for ModuleCache<'_> {
fn default() -> Self {
let mut module_names = MutMap::default();
@ -2385,12 +2403,35 @@ fn update<'a>(
.extend(solved_module.aliases.keys().copied());
}
if is_host_exposed && state.goal_phase() == Phase::SolveTypes {
let finish_type_checking = is_host_exposed &&
(state.goal_phase() == Phase::SolveTypes)
// If we're running in check-and-then-build mode, only exit now there are errors.
&& (!matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) || state.module_cache.total_problems() > 0);
if finish_type_checking {
debug_assert!(work.is_empty());
debug_assert!(state.dependencies.solved_all());
state.timings.insert(module_id, module_timing);
if matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck) {
// We there may outstanding modules in the typecheked cache whose ident IDs
// aren't registered; transfer all of their idents over to the state, since
// we're now done and ready to report errors.
for (
module_id,
TypeCheckedModule {
ident_ids,
module_timing,
..
},
) in state.module_cache.typechecked.drain()
{
state.constrained_ident_ids.insert(module_id, ident_ids);
state.timings.insert(module_id, module_timing);
}
}
let documentation = {
let mut empty = MutMap::default();
std::mem::swap(&mut empty, &mut state.module_cache.documentation);
@ -2427,7 +2468,9 @@ fn update<'a>(
},
);
if state.goal_phase() > Phase::SolveTypes {
if state.goal_phase() > Phase::SolveTypes
|| matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
{
let layout_cache = state
.layout_caches
.pop()
@ -2452,6 +2495,25 @@ fn update<'a>(
state.timings.insert(module_id, module_timing);
}
let work = if is_host_exposed
&& matches!(state.exec_mode, ExecutionMode::ExecutableIfCheck)
{
debug_assert!(
work.is_empty(),
"work left over after host exposed is checked"
);
// Update the goal phase to target full codegen.
state.exec_mode = ExecutionMode::Executable;
// Load the find + make specializations portion of the dependency graph.
state
.dependencies
.load_find_and_make_specializations_after_check()
} else {
work
};
start_tasks(arena, &mut state, work, injector, worker_listeners)?;
}
@ -2810,7 +2872,7 @@ fn finish_specialization(
let entry_point = {
match exec_mode {
ExecutionMode::Test => EntryPoint::Test,
ExecutionMode::Executable => {
ExecutionMode::Executable | ExecutionMode::ExecutableIfCheck => {
let path_to_platform = {
use PlatformPath::*;
let package_name = match platform_path {
@ -5007,7 +5069,9 @@ fn build_pending_specializations<'a>(
// skip expectations if we're not going to run them
match execution_mode {
ExecutionMode::Test => { /* fall through */ }
ExecutionMode::Check | ExecutionMode::Executable => continue,
ExecutionMode::Check
| ExecutionMode::Executable
| ExecutionMode::ExecutableIfCheck => continue,
}
// mark this symbol as a top-level thunk before any other work on the procs
@ -5081,7 +5145,9 @@ fn build_pending_specializations<'a>(
// skip expectations if we're not going to run them
match execution_mode {
ExecutionMode::Test => { /* fall through */ }
ExecutionMode::Check | ExecutionMode::Executable => continue,
ExecutionMode::Check
| ExecutionMode::Executable
| ExecutionMode::ExecutableIfCheck => continue,
}
// mark this symbol as a top-level thunk before any other work on the procs

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
pub mod docs;
pub mod file;

View file

@ -166,11 +166,10 @@ impl<'a> Dependencies<'a> {
}
}
if goal_phase >= MakeSpecializations {
// Add make specialization dependents
self.make_specializations_dependents
.add_succ(module_id, dependencies.iter().map(|dep| *dep.as_inner()));
}
// Add "make specialization" dependents. Even if we're not targeting making
// specializations right now, we may re-enter to do so later.
self.make_specializations_dependents
.add_succ(module_id, dependencies.iter().map(|dep| *dep.as_inner()));
// add dependencies for self
// phase i + 1 of a file always depends on phase i being completed
@ -374,6 +373,80 @@ impl<'a> Dependencies<'a> {
}
}
/// Loads the dependency graph to find and make specializations, and returns the next jobs to
/// be run.
///
/// This should be used when the compiler wants to build or run a Roc executable if and only if
/// previous stages succeed; in such cases we load the dependency graph dynamically.
pub fn load_find_and_make_specializations_after_check(&mut self) -> MutSet<(ModuleId, Phase)> {
let mut output = MutSet::default();
let mut make_specializations_dependents = MakeSpecializationsDependents::default();
let default_make_specializations_dependents_len = make_specializations_dependents.0.len();
std::mem::swap(
&mut self.make_specializations_dependents,
&mut make_specializations_dependents,
);
for (&module, info) in make_specializations_dependents.0.iter_mut() {
debug_assert!(self.status.get_mut(&Job::Step(module, Phase::FindSpecializations)).is_none(), "should only have targeted solving types, but there is already a goal to find specializations");
debug_assert!(self.status.get_mut(&Job::Step(module, Phase::MakeSpecializations)).is_none(), "should only have targeted solving types, but there is already a goal to make specializations");
debug_assert!(
module == ModuleId::DERIVED_GEN || info.succ.contains(&ModuleId::DERIVED_GEN),
"derived module not accounted for in {:?}",
(module, info)
);
let mut has_find_specialization_dep = false;
for &module_dep in info.succ.iter() {
// The modules in `succ` are the modules for which specializations should be made
// after the current one. But, their specializations should be found before the
// current one.
if module_dep != ModuleId::DERIVED_GEN {
// We never find specializations for DERIVED_GEN
self.add_dependency(module, module_dep, Phase::FindSpecializations);
has_find_specialization_dep = true;
}
self.add_dependency(module_dep, module, Phase::MakeSpecializations);
self.add_dependency(ModuleId::DERIVED_GEN, module, Phase::MakeSpecializations);
// `module_dep` can't make its specializations until the current module does.
info.has_pred = true;
}
if module != ModuleId::DERIVED_GEN {
self.add_to_status_for_phase(module, Phase::FindSpecializations);
self.add_dependency_help(
module,
module,
Phase::MakeSpecializations,
Phase::FindSpecializations,
);
}
self.add_to_status_for_phase(module, Phase::MakeSpecializations);
if !has_find_specialization_dep && module != ModuleId::DERIVED_GEN {
// We don't depend on any other modules having their specializations found first,
// so start finding specializations from this module.
output.insert((module, Phase::FindSpecializations));
}
}
std::mem::swap(
&mut self.make_specializations_dependents,
&mut make_specializations_dependents,
);
debug_assert_eq!(
make_specializations_dependents.0.len(),
default_make_specializations_dependents_len,
"more modules were added to the graph: {:?}",
make_specializations_dependents
);
output
}
/// Load the entire "make specializations" dependency graph and start from the top.
pub fn reload_make_specialization_pass(&mut self) -> MutSet<(ModuleId, Phase)> {
let mut output = MutSet::default();

View file

@ -699,7 +699,7 @@ fn platform_parse_error() {
}
#[test]
// See https://github.com/rtfeldman/roc/issues/2413
// See https://github.com/roc-lang/roc/issues/2413
fn platform_exposes_main_return_by_pointer_issue() {
let modules = vec![
(

View file

@ -65,6 +65,12 @@ impl TagName {
}
}
impl From<&str> for TagName {
fn from(string: &str) -> Self {
Self(string.into())
}
}
impl ModuleName {
// NOTE: After adding one of these, go to `impl ModuleId` and
// add a corresponding ModuleId to there!
@ -187,6 +193,20 @@ impl Lowercase {
}
}
impl From<Lowercase> for String {
fn from(lowercase: Lowercase) -> Self {
lowercase.0.into()
}
}
impl From<Lowercase> for Box<str> {
fn from(lowercase: Lowercase) -> Self {
let string: String = lowercase.0.into();
string.into()
}
}
impl<'a> From<&'a str> for Lowercase {
fn from(string: &'a str) -> Self {
Self(string.into())

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod called_via;

View file

@ -152,6 +152,7 @@ impl Symbol {
}
pub const fn to_ne_bytes(self) -> [u8; 8] {
// repr(packed(4)) is repr(c), and with the fields as defined will not having padding.
unsafe { std::mem::transmute(self) }
}
@ -1416,10 +1417,11 @@ define_builtins! {
19 DECODE_BOOL: "bool"
20 DECODE_STRING: "string"
21 DECODE_LIST: "list"
22 DECODE_CUSTOM: "custom"
23 DECODE_DECODE_WITH: "decodeWith"
24 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial"
25 DECODE_FROM_BYTES: "fromBytes"
22 DECODE_RECORD: "record"
23 DECODE_CUSTOM: "custom"
24 DECODE_DECODE_WITH: "decodeWith"
25 DECODE_FROM_BYTES_PARTIAL: "fromBytesPartial"
26 DECODE_FROM_BYTES: "fromBytes"
}
13 JSON: "Json" => {
0 JSON_JSON: "Json"

View file

@ -23,5 +23,5 @@ roc_error_macros = {path="../../error_macros"}
roc_debug_flags = {path="../debug_flags"}
ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.8.0", features = ["collections"] }
hashbrown = { version = "0.12.1", features = [ "bumpalo" ] }
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
static_assertions = "1.1.0"

View file

@ -1,8 +1,9 @@
#![allow(clippy::manual_map)]
use crate::layout::{
Builtin, CapturesNiche, ClosureRepresentation, LambdaName, LambdaSet, Layout, LayoutCache,
LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout, WrappedVariant,
Builtin, CapturesNiche, ClosureCallOptions, ClosureRepresentation, EnumDispatch, LambdaName,
LambdaSet, Layout, LayoutCache, LayoutProblem, RawFunctionLayout, TagIdIntType, UnionLayout,
WrappedVariant,
};
use bumpalo::collections::{CollectIn, Vec};
use bumpalo::Bump;
@ -343,10 +344,16 @@ impl<'a> Proc<'a> {
D::Doc: Clone,
A: Clone,
{
let args_doc = self
.args
.iter()
.map(|(_, symbol)| symbol_to_doc(alloc, *symbol));
let args_doc = self.args.iter().map(|(layout, symbol)| {
let arg_doc = symbol_to_doc(alloc, *symbol);
if pretty_print_ir_symbols() {
arg_doc
.append(alloc.reflow(": "))
.append(layout.to_doc(alloc, Parens::NotNeeded))
} else {
arg_doc
}
});
if pretty_print_ir_symbols() {
alloc
@ -621,6 +628,10 @@ impl<'a> Suspended<'a> {
}
}
fn is_empty(&self) -> bool {
self.symbol_or_lambdas.is_empty()
}
fn specialization(
&mut self,
subs: &mut Subs,
@ -654,8 +665,22 @@ enum PendingSpecializations<'a> {
/// that we can give specializations we need to modules higher up in the dependency chain, so
/// that they can start making specializations too
Finding(Suspended<'a>),
/// We are making specializations. If any new one comes up, we can just make it immediately
Making,
/// We are making specializations.
/// If any new one comes up while specializing a body, we can do one of two things:
/// - if the new specialization is for a symbol that is not in the current stack of symbols
/// being specialized, make it immediately
/// - if it is, we must suspend the specialization, and we'll do it once the stack is clear
/// again.
Making(Suspended<'a>),
}
impl<'a> PendingSpecializations<'a> {
fn is_empty(&self) -> bool {
match self {
PendingSpecializations::Finding(suspended)
| PendingSpecializations::Making(suspended) => suspended.is_empty(),
}
}
}
#[derive(Clone, Debug, Default)]
@ -940,6 +965,8 @@ pub struct Procs<'a> {
pub runtime_errors: BumpMap<Symbol, &'a str>,
pub externals_we_need: BumpMap<ModuleId, ExternalSpecializations<'a>>,
symbol_specializations: SymbolSpecializations<'a>,
/// The current set of functions under specialization.
pub specialization_stack: Vec<'a, Symbol>,
}
impl<'a> Procs<'a> {
@ -954,8 +981,43 @@ impl<'a> Procs<'a> {
runtime_errors: BumpMap::new_in(arena),
externals_we_need: BumpMap::new_in(arena),
symbol_specializations: Default::default(),
specialization_stack: Vec::with_capacity_in(16, arena),
}
}
fn push_active_specialization(&mut self, specialization: Symbol) {
self.specialization_stack.push(specialization);
}
fn pop_active_specialization(&mut self, specialization: Symbol) {
let popped = self
.specialization_stack
.pop()
.expect("specialization stack is empty");
debug_assert_eq!(
popped, specialization,
"incorrect popped specialization: passed {:?}, but was {:?}",
specialization, popped
);
}
/// If we need to specialize a function that is already in the stack, we must wait to do so
/// until that function is popped off. That's because the type environment will be configured
/// for the existing specialization on the stack.
///
/// For example, in
///
/// foo = \val, b -> if b then "done" else bar val
/// bar = \_ -> foo {} True
/// foo "" False
///
/// During the specialization of `foo : Str False -> Str`, we specialize `bar : Str -> Str`,
/// which in turn needs a specialization of `foo : {} False -> Str`. However, we can't
/// specialize both `foo : Str False -> Str` and `foo : {} False -> Str` at the same time, so
/// the latter specialization must be deferred.
fn symbol_needs_suspended_specialization(&self, specialization: Symbol) -> bool {
self.specialization_stack.contains(&specialization)
}
}
#[derive(Clone, Debug, PartialEq)]
@ -1045,8 +1107,14 @@ impl<'a> Procs<'a> {
debug_assert!(layout.arguments.is_empty());
}
match &mut self.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization =
self.symbol_needs_suspended_specialization(name.name());
match (
&mut self.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
// register the pending specialization, so this gets code genned later
suspended.specialization(env.subs, name, layout, annotation);
@ -1080,7 +1148,7 @@ impl<'a> Procs<'a> {
}
}
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
// Mark this proc as in-progress, so if we're dealing with
// mutually recursive functions, we don't loop forever.
// (We had a bug around this before this system existed!)
@ -1178,7 +1246,8 @@ impl<'a> Procs<'a> {
}
// If this is an imported symbol, let its home module make this specialization
if env.is_imported_symbol(name.name()) {
if env.is_imported_symbol(name.name()) || env.is_unloaded_derived_symbol(name.name(), self)
{
add_needed_external(self, env, fn_var, name);
return;
}
@ -1190,11 +1259,17 @@ impl<'a> Procs<'a> {
// This should only be called when pending_specializations is Some.
// Otherwise, it's being called in the wrong pass!
match &mut self.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization =
self.symbol_needs_suspended_specialization(name.name());
match (
&mut self.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
suspended.specialization(env.subs, name, layout, fn_var);
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
let symbol = name;
let partial_proc_id = match self.partial_procs.symbol_to_id(symbol.name()) {
@ -1207,7 +1282,7 @@ impl<'a> Procs<'a> {
// (We had a bug around this before this system existed!)
self.specialized.mark_in_progress(symbol.name(), layout);
// See https://github.com/rtfeldman/roc/issues/1600
// See https://github.com/roc-lang/roc/issues/1600
//
// The annotation variable is the generic/lifted/top-level annotation.
// It is connected to the variables of the function's body
@ -1342,6 +1417,13 @@ impl<'a, 'i> Env<'a, 'i> {
self.home == ModuleId::DERIVED_GEN
&& symbol.module_id() == ModuleId::DERIVED_SYNTH
&& !procs.partial_procs.contains_key(symbol)
// TODO: locking to find the answer in the `Derived_gen` module is not great, since
// Derived_gen also blocks other modules specializing. Improve this later.
&& self
.derived_module
.lock()
.expect("derived module is poisoned")
.is_derived_def(symbol)
}
/// Unifies two variables and performs lambda set compaction.
@ -2501,7 +2583,7 @@ fn patterns_to_when<'a>(
// are only stores anyway, no branches.
//
// NOTE this fails if the pattern contains rigid variables,
// see https://github.com/rtfeldman/roc/issues/786
// see https://github.com/roc-lang/roc/issues/786
// this must be fixed when moving exhaustiveness checking to the new canonical AST
for (pattern_var, annotated_mark, pattern) in patterns.into_iter() {
if annotated_mark.exhaustive.is_non_exhaustive(env.subs) {
@ -2739,26 +2821,51 @@ pub fn specialize_all<'a>(
specializations_for_host: HostSpecializations<'a>,
layout_cache: &mut LayoutCache<'a>,
) -> Procs<'a> {
for externals in externals_others_need {
specialize_external_specializations(env, &mut procs, layout_cache, externals);
}
// When calling from_can, pending_specializations should be unavailable.
// This must be a single pass, and we must not add any more entries to it!
let pending_specializations = std::mem::replace(
&mut procs.pending_specializations,
PendingSpecializations::Making,
PendingSpecializations::Making(Suspended::new_in(env.arena)),
);
// Add all of our existing pending specializations.
match pending_specializations {
PendingSpecializations::Making => {}
PendingSpecializations::Finding(suspended) => {
specialize_suspended(env, &mut procs, layout_cache, suspended)
}
PendingSpecializations::Making(suspended) => {
debug_assert!(
suspended.is_empty(),
"suspended specializations cannot ever start off non-empty when making"
);
}
}
// Specialize all the symbols everyone else needs.
for externals in externals_others_need {
specialize_external_specializations(env, &mut procs, layout_cache, externals);
}
// Specialize any symbols the host needs.
specialize_host_specializations(env, &mut procs, layout_cache, specializations_for_host);
// Now, we must go through and continuously complete any new suspended specializations that were
// discovered in specializing the other demanded symbols.
while !procs.pending_specializations.is_empty() {
let pending_specializations = std::mem::replace(
&mut procs.pending_specializations,
PendingSpecializations::Making(Suspended::new_in(env.arena)),
);
match pending_specializations {
PendingSpecializations::Making(suspended) => {
specialize_suspended(env, &mut procs, layout_cache, suspended);
}
PendingSpecializations::Finding(_) => {
internal_error!("should not have this variant after making specializations")
}
}
}
debug_assert!(
procs.symbol_specializations.is_empty(),
"{:?}",
@ -3127,6 +3234,8 @@ fn specialize_external<'a>(
closure: opt_closure_layout,
ret_layout,
} => {
let mut proc_args = Vec::from_iter_in(proc_args.iter().copied(), env.arena);
// unpack the closure symbols, if any
match (opt_closure_layout, captured_symbols) {
(Some(closure_layout), CapturedSymbols::Captured(captured)) => {
@ -3196,6 +3305,9 @@ fn specialize_external<'a>(
ClosureRepresentation::AlphabeticOrderStruct(field_layouts) => {
// captured variables are in symbol-alphabetic order, but now we want
// them ordered by their alignment requirements
//
// TODO: sort only the fields and apply the found permutation to the symbols
// TODO: can we move this ordering to `layout_for_member`?
let mut combined = Vec::from_iter_in(
captured.iter().map(|(x, _)| x).zip(field_layouts.iter()),
env.arena,
@ -3210,19 +3322,25 @@ fn specialize_external<'a>(
size2.cmp(&size1)
});
let ordered_field_layouts = Vec::from_iter_in(
combined.iter().map(|(_, layout)| **layout),
env.arena,
);
let ordered_field_layouts = ordered_field_layouts.into_bump_slice();
debug_assert_eq!(
captured.len(),
field_layouts.len(),
ordered_field_layouts.len(),
"{:?} captures {:?} but has layout {:?}",
lambda_name,
&captured,
&field_layouts
&ordered_field_layouts
);
for (index, (symbol, layout)) in combined.iter().enumerate() {
let expr = Expr::StructAtIndex {
index: index as _,
field_layouts,
field_layouts: ordered_field_layouts,
structure: Symbol::ARG_CLOSURE,
};
@ -3235,51 +3353,37 @@ fn specialize_external<'a>(
env.arena.alloc(specialized_body),
);
}
// let symbol = captured[0].0;
//
// substitute_in_exprs(
// env.arena,
// &mut specialized_body,
// symbol,
// Symbol::ARG_CLOSURE,
// );
}
ClosureRepresentation::Other(layout) => match layout {
Layout::Builtin(Builtin::Bool) => {
// just ignore this value
// IDEA don't pass this value in the future
}
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
// just ignore this value
// IDEA don't pass this value in the future
}
other => {
// NOTE other values always should be wrapped in a 1-element record
unreachable!(
"{:?} is not a valid closure data representation",
other
)
}
},
ClosureRepresentation::UnwrappedCapture(_layout) => {
debug_assert_eq!(captured.len(), 1);
let (captured_symbol, _captured_layout) = captured[0];
// The capture set is unwrapped, so simply replace the closure argument
// to the function with the unwrapped capture name.
let captured_symbol = get_specialized_name(captured_symbol);
let closure_arg = proc_args.last_mut().unwrap();
debug_assert_eq!(closure_arg.1, Symbol::ARG_CLOSURE);
closure_arg.1 = captured_symbol;
}
ClosureRepresentation::EnumDispatch(_) => {
// just ignore this value, since it's not a capture
// IDEA don't pass this value in the future
}
}
}
(None, CapturedSymbols::None) | (None, CapturedSymbols::Captured([])) => {}
_ => unreachable!("to closure or not to closure?"),
}
let proc_args: Vec<_> = proc_args
.iter()
.map(|&(layout, symbol)| {
// Grab the specialization symbol, if it exists.
let symbol = procs
.symbol_specializations
.remove_single(symbol)
.unwrap_or(symbol);
(layout, symbol)
})
.collect_in(env.arena);
proc_args.iter_mut().for_each(|(_layout, symbol)| {
// Grab the specialization symbol, if it exists.
*symbol = procs
.symbol_specializations
.remove_single(*symbol)
.unwrap_or(*symbol);
});
// reset subs, so we don't get type errors when specializing for a different signature
layout_cache.rollback_to(cache_snapshot);
@ -3566,6 +3670,8 @@ where
let annotation_var = procs.partial_procs.get_id(partial_proc_id).annotation;
instantiate_rigids(env.subs, annotation_var);
procs.push_active_specialization(proc_name.name());
let specialized = specialize_external(
env,
procs,
@ -3576,6 +3682,8 @@ where
partial_proc_id,
);
procs.pop_active_specialization(proc_name.name());
match specialized {
Ok(proc) => {
// when successful, the layout after unification should be the layout before unification
@ -3869,7 +3977,7 @@ pub fn with_hole<'a>(
)
}
Tag {
variant_var,
tag_union_var: variant_var,
name: tag_name,
arguments: args,
..
@ -5396,29 +5504,42 @@ where
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
ClosureRepresentation::Other(Layout::Builtin(Builtin::Bool)) => {
debug_assert_eq!(symbols.len(), 0);
ClosureRepresentation::UnwrappedCapture(_layout) => {
debug_assert_eq!(symbols.len(), 1);
debug_assert_eq!(lambda_set.set.len(), 2);
let tag_id = name.name() != lambda_set.iter_set().next().unwrap().name();
let expr = Expr::Literal(Literal::Bool(tag_id));
let mut symbols = symbols;
let (captured_symbol, _) = symbols.next().unwrap();
Stmt::Let(assigned, expr, lambda_set_layout, hole)
// The capture set is unwrapped, so just replaced the assigned capture symbol with the
// only capture.
let mut hole = hole.clone();
substitute_in_exprs(env.arena, &mut hole, assigned, *captured_symbol);
hole
}
ClosureRepresentation::Other(Layout::Builtin(Builtin::Int(IntWidth::U8))) => {
debug_assert_eq!(symbols.len(), 0);
ClosureRepresentation::EnumDispatch(repr) => match repr {
EnumDispatch::Bool => {
debug_assert_eq!(symbols.len(), 0);
debug_assert!(lambda_set.set.len() > 2);
let tag_id = lambda_set
.iter_set()
.position(|s| s.name() == name.name())
.unwrap() as u8;
debug_assert_eq!(lambda_set.len(), 2);
let tag_id = name.name() != lambda_set.iter_set().next().unwrap().name();
let expr = Expr::Literal(Literal::Bool(tag_id));
let expr = Expr::Literal(Literal::Byte(tag_id));
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
EnumDispatch::U8 => {
debug_assert_eq!(symbols.len(), 0);
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
_ => unreachable!(),
debug_assert!(lambda_set.len() > 2);
let tag_id = lambda_set
.iter_set()
.position(|s| s.name() == name.name())
.unwrap() as u8;
let expr = Expr::Literal(Literal::Byte(tag_id));
Stmt::Let(assigned, expr, lambda_set_layout, hole)
}
},
};
result
@ -5692,7 +5813,7 @@ fn tag_union_to_function<'a>(
}
let loc_body = Loc::at_zero(roc_can::expr::Expr::Tag {
variant_var: return_variable,
tag_union_var: return_variable,
name: tag_name,
arguments: loc_expr_args,
ext_var,
@ -5857,10 +5978,7 @@ fn register_capturing_closure<'a>(
Content::Structure(FlatType::Func(_, closure_var, _)) => {
match LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info) {
Ok(lambda_set) => {
if let Layout::Struct {
field_layouts: &[], ..
} = lambda_set.runtime_representation()
{
if lambda_set.is_represented().is_none() {
CapturedSymbols::None
} else {
let mut temp = Vec::from_iter_in(captured_symbols, env.arena);
@ -5869,7 +5987,7 @@ fn register_capturing_closure<'a>(
}
}
Err(_) => {
// just allow this. see https://github.com/rtfeldman/roc/issues/1585
// just allow this. see https://github.com/roc-lang/roc/issues/1585
if captured_symbols.is_empty() {
CapturedSymbols::None
} else {
@ -7162,7 +7280,7 @@ fn can_reuse_symbol<'a>(
if arguments.contains(&symbol) {
Value(symbol)
} else if env.is_imported_symbol(symbol) {
} else if env.is_imported_symbol(symbol) || env.is_unloaded_derived_symbol(symbol, procs) {
Imported(symbol)
} else if procs.partial_procs.contains_key(symbol) {
LocalFunction(symbol)
@ -7334,7 +7452,10 @@ fn specialize_symbol<'a>(
match procs.get_partial_proc(original) {
None => {
match arg_var {
Some(arg_var) if env.is_imported_symbol(original) => {
Some(arg_var)
if env.is_imported_symbol(original)
|| env.is_unloaded_derived_symbol(original, procs) =>
{
let raw = match layout_cache.raw_from_var(env.arena, arg_var, env.subs) {
Ok(v) => v,
Err(e) => return_on_layout_error_help!(env, e, "specialize_symbol"),
@ -7603,7 +7724,7 @@ fn build_call<'a>(
Stmt::Let(assigned, Expr::Call(call), return_layout, hole)
}
/// See https://github.com/rtfeldman/roc/issues/1549
/// See https://github.com/roc-lang/roc/issues/1549
///
/// What happened is that a function has a type error, but the arguments are not processed.
/// That means specializations were missing. Normally that is not a problem, but because
@ -7936,8 +8057,14 @@ fn call_by_name_help<'a>(
debug_assert!(top_level_layout.arguments.is_empty());
}
match &mut procs.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization =
procs.symbol_needs_suspended_specialization(proc_name.name());
match (
&mut procs.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
debug_assert!(!env.is_imported_symbol(proc_name.name()));
// register the pending specialization, so this gets code genned later
@ -7966,7 +8093,7 @@ fn call_by_name_help<'a>(
hole,
)
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name.name());
let field_symbols = field_symbols.into_bump_slice();
@ -8100,8 +8227,13 @@ fn call_by_name_module_thunk<'a>(
debug_assert!(top_level_layout.arguments.is_empty());
}
match &mut procs.pending_specializations {
PendingSpecializations::Finding(suspended) => {
let needs_suspended_specialization = procs.symbol_needs_suspended_specialization(proc_name);
match (
&mut procs.pending_specializations,
needs_suspended_specialization,
) {
(PendingSpecializations::Finding(suspended), _)
| (PendingSpecializations::Making(suspended), true) => {
debug_assert!(!env.is_imported_symbol(proc_name));
// register the pending specialization, so this gets code genned later
@ -8114,7 +8246,7 @@ fn call_by_name_module_thunk<'a>(
force_thunk(env, proc_name, inner_layout, assigned, hole)
}
PendingSpecializations::Making => {
(PendingSpecializations::Making(_), false) => {
let opt_partial_proc = procs.partial_procs.symbol_to_id(proc_name);
match opt_partial_proc {
@ -9166,9 +9298,9 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
match lambda_set.runtime_representation() {
Layout::VOID => empty_lambda_set_error(),
Layout::Union(union_layout) => {
match lambda_set.call_by_name_options() {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
let result = lowlevel_union_lambda_set_to_switch(
@ -9197,7 +9329,7 @@ where
env.arena.alloc(result),
)
}
Layout::Struct { .. } => match lambda_set.iter_set().next() {
ClosureCallOptions::Struct { .. } => match lambda_set.iter_set().next() {
Some(lambda_name) => {
let call_spec_id = env.next_call_specialization_id();
let update_mode = env.next_update_mode_id();
@ -9222,39 +9354,58 @@ where
hole.clone()
}
},
Layout::Builtin(Builtin::Bool) => {
let closure_tag_id_symbol = closure_data_symbol;
ClosureCallOptions::UnwrappedCapture(_) => {
let lambda_name = lambda_set
.iter_set()
.next()
.expect("no function in lambda set");
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
let call_spec_id = env.next_call_specialization_id();
let update_mode = env.next_update_mode_id();
let call = to_lowlevel_call((
lambda_name,
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
}
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
let closure_tag_id_symbol = closure_data_symbol;
call_spec_id,
update_mode,
));
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
}
other => todo!("{:?}", other),
ClosureCallOptions::EnumDispatch(repr) => match repr {
EnumDispatch::Bool => {
let closure_tag_id_symbol = closure_data_symbol;
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
}
EnumDispatch::U8 => {
let closure_tag_id_symbol = closure_data_symbol;
lowlevel_enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
closure_data_symbol,
lambda_set.is_represented(),
to_lowlevel_call,
return_layout,
assigned,
hole,
)
}
},
}
}
@ -9345,15 +9496,14 @@ fn match_on_lambda_set<'a>(
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
match lambda_set.runtime_representation() {
Layout::VOID => empty_lambda_set_error(),
Layout::Union(union_layout) => {
match lambda_set.call_by_name_options() {
ClosureCallOptions::Void => empty_lambda_set_error(),
ClosureCallOptions::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
let result = union_lambda_set_to_switch(
env,
lambda_set,
Layout::Union(union_layout),
closure_tag_id_symbol,
union_layout.tag_id_layout(),
closure_data_symbol,
@ -9377,9 +9527,9 @@ fn match_on_lambda_set<'a>(
env.arena.alloc(result),
)
}
Layout::Struct {
ClosureCallOptions::Struct {
field_layouts,
field_order_hash,
field_order_hash: _,
} => {
let function_symbol = match lambda_set.iter_set().next() {
Some(function_symbol) => function_symbol,
@ -9403,10 +9553,6 @@ fn match_on_lambda_set<'a>(
_ => ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
closure_data_layout: Layout::Struct {
field_layouts,
field_order_hash,
},
},
};
@ -9421,15 +9567,21 @@ fn match_on_lambda_set<'a>(
hole,
)
}
Layout::Builtin(Builtin::Bool) => {
let closure_tag_id_symbol = closure_data_symbol;
ClosureCallOptions::UnwrappedCapture(_) => {
let function_symbol = lambda_set
.iter_set()
.next()
.expect("no function in lambda set");
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
let closure_info = ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
};
union_lambda_set_branch_help(
env,
function_symbol,
closure_info,
argument_symbols,
argument_layouts,
return_layout,
@ -9437,23 +9589,38 @@ fn match_on_lambda_set<'a>(
hole,
)
}
Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
let closure_tag_id_symbol = closure_data_symbol;
ClosureCallOptions::EnumDispatch(repr) => match repr {
EnumDispatch::Bool => {
let closure_tag_id_symbol = closure_data_symbol;
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
closure_data_symbol,
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
other => todo!("{:?}", other),
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Bool),
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
EnumDispatch::U8 => {
let closure_tag_id_symbol = closure_data_symbol;
enum_lambda_set_to_switch(
env,
lambda_set.iter_set(),
closure_tag_id_symbol,
Layout::Builtin(Builtin::Int(IntWidth::U8)),
argument_symbols,
argument_layouts,
return_layout,
assigned,
hole,
)
}
},
}
}
@ -9461,7 +9628,6 @@ fn match_on_lambda_set<'a>(
fn union_lambda_set_to_switch<'a>(
env: &mut Env<'a, '_>,
lambda_set: LambdaSet<'a>,
closure_layout: Layout<'a>,
closure_tag_id_symbol: Symbol,
closure_tag_id_layout: Layout<'a>,
closure_data_symbol: Symbol,
@ -9471,7 +9637,7 @@ fn union_lambda_set_to_switch<'a>(
assigned: Symbol,
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
if lambda_set.set.is_empty() {
if lambda_set.is_empty() {
// NOTE this can happen if there is a type error somewhere. Since the lambda set is empty,
// there is really nothing we can do here. We generate a runtime error here which allows
// code gen to proceed. We then assume that we hit another (more descriptive) error before
@ -9481,7 +9647,7 @@ fn union_lambda_set_to_switch<'a>(
let join_point_id = JoinPointId(env.unique_symbol());
let mut branches = Vec::with_capacity_in(lambda_set.set.len(), env.arena);
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
for (i, lambda_name) in lambda_set.iter_set().enumerate() {
let closure_info = if lambda_name.no_captures() {
@ -9490,7 +9656,6 @@ fn union_lambda_set_to_switch<'a>(
ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
closure_data_layout: closure_layout,
}
};
@ -9560,11 +9725,10 @@ fn union_lambda_set_branch<'a>(
)
}
#[derive(Clone, Copy)]
enum ClosureInfo<'a> {
Captures {
closure_data_symbol: Symbol,
/// The layout of this closure variant
closure_data_layout: Layout<'a>,
/// The whole lambda set representation this closure is a variant of
lambda_set: LambdaSet<'a>,
},
@ -9586,34 +9750,21 @@ fn union_lambda_set_branch_help<'a>(
ClosureInfo::Captures {
lambda_set,
closure_data_symbol,
closure_data_layout,
} => match closure_data_layout {
Layout::Struct {
field_layouts: &[], ..
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
(argument_layouts_slice, argument_symbols_slice)
}
_ => {
// extend layouts with the layout of the closure environment
let mut argument_layouts =
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
argument_layouts.extend(argument_layouts_slice);
argument_layouts.push(Layout::LambdaSet(lambda_set));
} => {
let argument_layouts =
lambda_set.extend_argument_list(env.arena, argument_layouts_slice);
let argument_symbols = if argument_layouts.len() > argument_layouts_slice.len() {
// extend symbols with the symbol of the closure environment
let mut argument_symbols =
Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena);
argument_symbols.extend(argument_symbols_slice);
argument_symbols.push(closure_data_symbol);
(
argument_layouts.into_bump_slice(),
argument_symbols.into_bump_slice(),
)
}
},
argument_symbols.into_bump_slice()
} else {
argument_symbols_slice
};
(argument_layouts, argument_symbols)
}
ClosureInfo::DoesNotCapture => {
// sometimes unification causes a function that does not itself capture anything
// to still get a lambda set that does store information. We must not pass a closure
@ -9637,13 +9788,14 @@ fn union_lambda_set_branch_help<'a>(
build_call(env, call, assigned, *return_layout, hole)
}
/// Switches over a enum lambda set, which may dispatch to different functions, none of which
/// capture.
#[allow(clippy::too_many_arguments)]
fn enum_lambda_set_to_switch<'a>(
env: &mut Env<'a, '_>,
lambda_set: impl ExactSizeIterator<Item = LambdaName<'a>>,
closure_tag_id_symbol: Symbol,
closure_tag_id_layout: Layout<'a>,
closure_data_symbol: Symbol,
argument_symbols: &'a [Symbol],
argument_layouts: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
@ -9656,15 +9808,11 @@ fn enum_lambda_set_to_switch<'a>(
let mut branches = Vec::with_capacity_in(lambda_set.len(), env.arena);
let closure_layout = closure_tag_id_layout;
for (i, lambda_name) in lambda_set.into_iter().enumerate() {
let stmt = enum_lambda_set_branch(
env,
join_point_id,
lambda_name,
closure_data_symbol,
closure_layout,
argument_symbols,
argument_layouts,
return_layout,
@ -9700,15 +9848,14 @@ fn enum_lambda_set_to_switch<'a>(
}
}
/// A branch for an enum lambda set branch dispatch, which never capture!
#[allow(clippy::too_many_arguments)]
fn enum_lambda_set_branch<'a>(
env: &mut Env<'a, '_>,
join_point_id: JoinPointId,
lambda_name: LambdaName<'a>,
closure_data_symbol: Symbol,
closure_data_layout: Layout<'a>,
argument_symbols_slice: &'a [Symbol],
argument_layouts_slice: &'a [Layout<'a>],
argument_symbols: &'a [Symbol],
argument_layouts: &'a [Layout<'a>],
return_layout: &'a Layout<'a>,
) -> Stmt<'a> {
let result_symbol = env.unique_symbol();
@ -9717,34 +9864,6 @@ fn enum_lambda_set_branch<'a>(
let assigned = result_symbol;
let (argument_layouts, argument_symbols) = match closure_data_layout {
Layout::Struct {
field_layouts: &[], ..
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::U8)) => {
(argument_layouts_slice, argument_symbols_slice)
}
_ => {
// extend layouts with the layout of the closure environment
let mut argument_layouts =
Vec::with_capacity_in(argument_layouts_slice.len() + 1, env.arena);
argument_layouts.extend(argument_layouts_slice);
argument_layouts.push(closure_data_layout);
// extend symbols with the symbol of the closure environment
let mut argument_symbols =
Vec::with_capacity_in(argument_symbols_slice.len() + 1, env.arena);
argument_symbols.extend(argument_symbols_slice);
argument_symbols.push(closure_data_symbol);
(
argument_layouts.into_bump_slice(),
argument_symbols.into_bump_slice(),
)
}
};
let call = self::Call {
call_type: CallType::ByName {
name: lambda_name,

View file

@ -263,7 +263,7 @@ pub enum Layout<'a> {
/// so keep a hash of the record order for disambiguation. This still of course may result
/// in collisions, but it's unlikely.
///
/// See also https://github.com/rtfeldman/roc/issues/2535.
/// See also https://github.com/roc-lang/roc/issues/2535.
field_order_hash: FieldOrderHash,
field_layouts: &'a [Layout<'a>],
},
@ -731,7 +731,7 @@ impl std::fmt::Debug for LambdaSet<'_> {
/// By recording the captures layouts this lambda expects in its identifier, we can distinguish
/// between such differences when constructing closure capture data.
///
/// See also https://github.com/rtfeldman/roc/issues/3336.
/// See also https://github.com/roc-lang/roc/issues/3336.
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct CapturesNiche<'a>(&'a [Layout<'a>]);
@ -783,28 +783,56 @@ impl<'a> LambdaName<'a> {
#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LambdaSet<'a> {
/// collection of function names and their closure arguments
pub set: &'a [(Symbol, &'a [Layout<'a>])],
set: &'a [(Symbol, &'a [Layout<'a>])],
/// how the closure will be represented at runtime
representation: &'a Layout<'a>,
}
#[derive(Debug)]
pub enum EnumDispatch {
Bool,
U8,
}
/// representation of the closure *for a particular function*
#[derive(Debug)]
pub enum ClosureRepresentation<'a> {
/// the closure is represented as a union. Includes the tag ID!
/// The closure is represented as a union. Includes the tag ID!
/// Each variant is a different function, and its payloads are the captures.
Union {
alphabetic_order_fields: &'a [Layout<'a>],
closure_name: Symbol,
tag_id: TagIdIntType,
union_layout: UnionLayout<'a>,
},
/// The closure is represented as a struct. The layouts are sorted
/// alphabetically by the identifier that is captured.
/// The closure is one function, whose captures are represented as a struct.
/// The layouts are sorted alphabetically by the identifier that is captured.
///
/// We MUST sort these according to their stack size before code gen!
AlphabeticOrderStruct(&'a [Layout<'a>]),
/// the representation is anything but a union
Other(Layout<'a>),
/// The closure is one function that captures a single identifier, whose value is unwrapped.
UnwrappedCapture(Layout<'a>),
/// The closure dispatches to multiple functions, but none of them capture anything, so this is
/// a boolean or integer flag.
EnumDispatch(EnumDispatch),
}
/// How the closure should be seen when determining a call-by-name.
#[derive(Debug)]
pub enum ClosureCallOptions<'a> {
/// This is an empty lambda set, dispatching is an error
Void,
/// One of a few capturing functions can be called to
Union(UnionLayout<'a>),
/// The closure is one function, whose captures are represented as a struct.
Struct {
field_layouts: &'a [Layout<'a>],
field_order_hash: FieldOrderHash,
},
/// The closure is one function that captures a single identifier, whose value is unwrapped.
UnwrappedCapture(Layout<'a>),
/// The closure dispatches to multiple possible functions, none of which capture.
EnumDispatch(EnumDispatch),
}
impl<'a> LambdaSet<'a> {
@ -818,13 +846,17 @@ impl<'a> LambdaSet<'a> {
}
pub fn is_represented(&self) -> Option<Layout<'a>> {
match self.representation {
Layout::Struct {
field_layouts: &[], ..
if self.has_unwrapped_capture_repr() {
Some(*self.representation)
} else if self.has_enum_dispatch_repr() {
None
} else {
match self.representation {
Layout::Struct {
field_layouts: &[], ..
} => None,
repr => Some(*repr),
}
| Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(..)) => None,
repr => Some(*repr),
}
}
@ -835,6 +867,16 @@ impl<'a> LambdaSet<'a> {
})
}
#[inline(always)]
pub fn len(&self) -> usize {
self.set.len()
}
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.set.is_empty()
}
pub fn layout_for_member_with_lambda_name(
&self,
lambda_name: LambdaName,
@ -923,6 +965,11 @@ impl<'a> LambdaSet<'a> {
where
F: Fn(Symbol, &[Layout]) -> bool,
{
if self.has_unwrapped_capture_repr() {
// Only one function, that captures one identifier.
return ClosureRepresentation::UnwrappedCapture(*self.representation);
}
match self.representation {
Layout::Union(union) => {
// here we rely on the fact that a union in a closure would be stored in a one-element record.
@ -1004,7 +1051,58 @@ impl<'a> LambdaSet<'a> {
ClosureRepresentation::AlphabeticOrderStruct(fields)
}
_ => ClosureRepresentation::Other(*self.representation),
layout => {
debug_assert!(self.has_enum_dispatch_repr(),);
let enum_repr = match layout {
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
other => internal_error!("Invalid layout for enum dispatch: {:?}", other),
};
ClosureRepresentation::EnumDispatch(enum_repr)
}
}
}
fn has_unwrapped_capture_repr(&self) -> bool {
self.set.len() == 1 && self.set[0].1.len() == 1
}
fn has_enum_dispatch_repr(&self) -> bool {
self.set.len() > 1 && self.set.iter().all(|(_, captures)| captures.is_empty())
}
pub fn call_by_name_options(&self) -> ClosureCallOptions<'a> {
if self.has_unwrapped_capture_repr() {
return ClosureCallOptions::UnwrappedCapture(*self.representation);
}
match self.representation {
Layout::Union(union_layout) => {
if self.representation == &Layout::VOID {
debug_assert!(self.set.is_empty());
return ClosureCallOptions::Void;
}
ClosureCallOptions::Union(*union_layout)
}
Layout::Struct {
field_layouts,
field_order_hash,
} => {
debug_assert_eq!(self.set.len(), 1);
ClosureCallOptions::Struct {
field_layouts,
field_order_hash: *field_order_hash,
}
}
layout => {
debug_assert!(self.has_enum_dispatch_repr());
let enum_repr = match layout {
Layout::Builtin(Builtin::Bool) => EnumDispatch::Bool,
Layout::Builtin(Builtin::Int(IntWidth::U8)) => EnumDispatch::U8,
other => internal_error!("Invalid layout for enum dispatch: {:?}", other),
};
ClosureCallOptions::EnumDispatch(enum_repr)
}
}
}
@ -1013,30 +1111,27 @@ impl<'a> LambdaSet<'a> {
arena: &'a Bump,
argument_layouts: &'a [Layout<'a>],
) -> &'a [Layout<'a>] {
if let [] = self.set {
// TERRIBLE HACK for builting functions
argument_layouts
} else {
match self.representation {
Layout::Struct {
field_layouts: &[], ..
} => {
// this function does not have anything in its closure, and the lambda set is a
// singleton, so we pass no extra argument
argument_layouts
}
Layout::Builtin(Builtin::Bool)
| Layout::Builtin(Builtin::Int(IntWidth::I8 | IntWidth::U8)) => {
// we don't pass this along either
argument_layouts
}
_ => {
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
arguments.extend(argument_layouts);
arguments.push(Layout::LambdaSet(*self));
match self.call_by_name_options() {
ClosureCallOptions::Void => argument_layouts,
ClosureCallOptions::Struct {
field_layouts: &[], ..
} => {
// this function does not have anything in its closure, and the lambda set is a
// singleton, so we pass no extra argument
argument_layouts
}
ClosureCallOptions::Struct { .. }
| ClosureCallOptions::Union(_)
| ClosureCallOptions::UnwrappedCapture(_) => {
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
arguments.extend(argument_layouts);
arguments.push(Layout::LambdaSet(*self));
arguments.into_bump_slice()
}
arguments.into_bump_slice()
}
ClosureCallOptions::EnumDispatch(_) => {
// No captures, don't pass this along
argument_layouts
}
}
}
@ -1053,7 +1148,7 @@ impl<'a> LambdaSet<'a> {
lambdas.sort_by_key(|(sym, _)| *sym);
let mut set: Vec<(Symbol, &[Layout])> = Vec::with_capacity_in(lambdas.len(), arena);
let mut set_with_variables: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)> =
let mut set_with_variables: std::vec::Vec<(&Symbol, &[Variable])> =
std::vec::Vec::with_capacity(lambdas.len());
let mut last_function_symbol = None;
@ -1090,7 +1185,7 @@ impl<'a> LambdaSet<'a> {
has_duplicate_lambda_names = has_duplicate_lambda_names || is_multimorphic;
set.push((*function_symbol, arguments));
set_with_variables.push((*function_symbol, variables.to_vec()));
set_with_variables.push((function_symbol, variables.as_slice()));
last_function_symbol = Some(function_symbol);
}
@ -1139,7 +1234,7 @@ impl<'a> LambdaSet<'a> {
}
ResolvedLambdaSet::Unbound => {
// The lambda set is unbound which means it must be unused. Just give it the empty lambda set.
// See also https://github.com/rtfeldman/roc/issues/3163.
// See also https://github.com/roc-lang/roc/issues/3163.
Ok(LambdaSet {
set: &[],
representation: arena.alloc(Layout::UNIT),
@ -1151,70 +1246,23 @@ impl<'a> LambdaSet<'a> {
fn make_representation(
arena: &'a Bump,
subs: &Subs,
tags: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)>,
tags: std::vec::Vec<(&Symbol, &[Variable])>,
opt_rec_var: Option<Variable>,
target_info: TargetInfo,
) -> Layout<'a> {
if let Some(rec_var) = opt_rec_var {
let tags: std::vec::Vec<_> = tags
.iter()
.map(|(sym, vars)| (sym, vars.as_slice()))
.collect();
let tags = UnsortedUnionLabels { tags };
let mut env = Env {
seen: Vec::new_in(arena),
target_info,
arena,
subs,
};
let union_labels = UnsortedUnionLabels { tags };
let mut env = Env {
seen: Vec::new_in(arena),
target_info,
arena,
subs,
};
return layout_from_recursive_union(&mut env, rec_var, &tags)
.expect("unable to create lambda set representation");
}
match opt_rec_var {
Some(rec_var) => layout_from_recursive_union(&mut env, rec_var, &union_labels)
.expect("unable to create lambda set representation"),
// otherwise, this is a closure with a payload
let variant = union_sorted_tags_help(arena, tags, opt_rec_var, subs, target_info);
use UnionVariant::*;
match variant {
Never => Layout::VOID,
BoolUnion { .. } => Layout::bool(),
ByteUnion { .. } => Layout::u8(),
Unit | UnitWithArguments => {
// no useful information to store
Layout::UNIT
}
Newtype {
arguments: layouts, ..
} => Layout::struct_no_name_order(layouts.into_bump_slice()),
Wrapped(variant) => {
use WrappedVariant::*;
match variant {
NonRecursive {
sorted_tag_layouts: tags,
} => {
debug_assert!(tags.len() > 1);
// if the closed-over value is actually a layout, it should be wrapped in a 1-element record
debug_assert!(matches!(tags[0].0, TagOrClosure::Closure(_)));
let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena);
for (_, tag_args) in tags.iter() {
tag_arguments.push(&tag_args[0..]);
}
Layout::Union(UnionLayout::NonRecursive(tag_arguments.into_bump_slice()))
}
Recursive { .. }
| NullableUnwrapped { .. }
| NullableWrapped { .. }
| NonNullableUnwrapped { .. } => {
internal_error!("Recursive layouts should be produced in an earlier branch")
}
}
}
None => layout_from_union(&mut env, &union_labels),
}
}
@ -1239,7 +1287,7 @@ enum ResolvedLambdaSet {
OptVariable,
),
/// TODO: figure out if this can happen in a correct program, or is the result of a bug in our
/// compiler. See https://github.com/rtfeldman/roc/issues/3163.
/// compiler. See https://github.com/roc-lang/roc/issues/3163.
Unbound,
}
@ -1777,6 +1825,13 @@ impl<'a> Layout<'a> {
}
}
}
pub fn runtime_representation(&self) -> Self {
match self {
Layout::LambdaSet(lambda_set) => lambda_set.runtime_representation(),
other => *other,
}
}
}
/// Avoid recomputing Layout from Variable multiple times.

View file

@ -1,5 +1,5 @@
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
// See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
pub mod borrow;

View file

@ -13,12 +13,12 @@ roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
bumpalo = { version = "3.8.0", features = ["collections"] }
encode_unicode = "0.3.6"
encode_unicode = "1.0.0"
[dev-dependencies]
criterion = { git = "https://github.com/Anton-4/criterion.rs", features = ["html_reports"]}
pretty_assertions = "1.0.0"
indoc = "1.0.3"
indoc = "1.0.7"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
roc_test_utils = { path = "../../test_utils" }

View file

@ -1,11 +1,54 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
dependencies = [
"getrandom",
"once_cell",
"version_check",
]
[[package]]
name = "arbitrary"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db55d72333851e17d572bec876e390cd3b11eb1ef53ae821dd9f3b653d2b4569"
[[package]]
name = "backtrace"
version = "0.3.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab84319d616cfb654d03394f38ab7e6f0919e181b1b57e1fd15e7fb4077d9a7"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "bitmaps"
version = "2.1.0"
@ -16,16 +59,40 @@ dependencies = [
]
[[package]]
name = "bumpalo"
version = "3.4.0"
name = "bitvec"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c"
dependencies = [
"funty",
"radium",
"tap",
"wyz",
]
[[package]]
name = "bumpalo"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3"
[[package]]
name = "cc"
version = "1.0.61"
version = "1.0.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed67cbde08356238e75fc4656be4749481eeffb09e19f320a25237d5221c985d"
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "doc-comment"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
[[package]]
name = "encode_unicode"
@ -34,13 +101,52 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "im"
version = "14.3.0"
name = "funty"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "696059c87b83c5a258817ecd67c3af915e3ed141891fc35a1e79908801cf0ce7"
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
[[package]]
name = "getrandom"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d"
[[package]]
name = "hashbrown"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
dependencies = [
"ahash",
"bumpalo",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "im"
version = "15.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0acd33ff0285af998aaf9b57342af478078f53492322fafc47450e09397e0e9"
dependencies = [
"bitmaps",
"rand_core 0.5.1",
"rand_core",
"rand_xoshiro",
"sized-chunks",
"typenum",
@ -49,30 +155,30 @@ dependencies = [
[[package]]
name = "im-rc"
version = "14.3.0"
version = "15.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "303f7e6256d546e01979071417432425f15c1891fb309a5f2d724ee908fabd6e"
checksum = "af1955a75fa080c677d3972822ec4bad316169ab1cfc6c257a942c2265dbe5fe"
dependencies = [
"bitmaps",
"rand_core 0.5.1",
"rand_core",
"rand_xoshiro",
"sized-chunks",
"typenum",
"version_check",
]
[[package]]
name = "inlinable_string"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.131"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04c3b4822ccebfa39c02fc03d1534441b22ead323fa0f48bb7ddd8e6ba076a40"
[[package]]
name = "libfuzzer-sys"
version = "0.3.4"
@ -84,54 +190,114 @@ dependencies = [
]
[[package]]
name = "rand_core"
version = "0.4.2"
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "miniz_oxide"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
dependencies = [
"adler",
]
[[package]]
name = "object"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
[[package]]
name = "proc-macro2"
version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a2ca2c61bc9f3d74d2886294ab7b9853abd9c1ad903a3ac7815c58989bb7bab"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbe448f377a7d6961e30f5955f9b8d106c3f5e449d493ee1b125c1d43c2b5179"
dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand_core"
version = "0.5.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
[[package]]
name = "rand_xoshiro"
version = "0.4.0"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9fcdd2e881d02f1d9390ae47ad8e5696a9e4be7b547a1da2afbc61973217004"
checksum = "6f97cdb2a36ed4183de61b2f824cc45c9f1037f28afe0a322e9fff4c108b5aaa"
dependencies = [
"rand_core 0.5.1",
"rand_core",
]
[[package]]
name = "roc_collections"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bitvec",
"bumpalo",
"hashbrown",
"im",
"im-rc",
"wyhash",
]
[[package]]
name = "roc_error_macros"
version = "0.0.1"
[[package]]
name = "roc_ident"
version = "0.0.1"
[[package]]
name = "roc_module"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bumpalo",
"inlinable_string",
"lazy_static",
"roc_collections",
"roc_error_macros",
"roc_ident",
"roc_region",
"snafu",
"static_assertions",
]
[[package]]
name = "roc_parse"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"bumpalo",
"encode_unicode",
"inlinable_string",
"roc_collections",
"roc_module",
"roc_region",
@ -148,24 +314,85 @@ dependencies = [
[[package]]
name = "roc_region"
version = "0.1.0"
version = "0.0.1"
dependencies = [
"static_assertions",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "sized-chunks"
version = "0.5.3"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d59044ea371ad781ff976f7b06480b9f0180e834eda94114f2afb4afc12b7718"
checksum = "16d69225bde7a69b235da73377861095455d298f2b970996eec25ddbb42b3d1e"
dependencies = [
"bitmaps",
"typenum",
]
[[package]]
name = "snafu"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5177903bf45656592d9eb5c0e22f408fc023aae51dbe2088889b71633ba451f2"
dependencies = [
"backtrace",
"doc-comment",
"snafu-derive",
]
[[package]]
name = "snafu-derive"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "410b26ed97440d90ced3e2488c868d56a86e2064f5d7d6f417909b286afe25e5"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "syn"
version = "1.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58dbef6ec655055e20b86b15a8cc6d439cca19b667537ac6a1369572d151ab13"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicode-ident"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4f5b37a154999a8f3f98cc23a628d850e154479cd94decf3414696e12e31aaf"
[[package]]
name = "version_check"
version = "0.9.2"
@ -173,10 +400,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "wyhash"
version = "0.3.0"
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "782a50f48ac4336916227cd199c61c7b42f38d0ad705421b49eb12c74c53ae00"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wyhash"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf6e163c25e3fac820b4b453185ea2dea3b6a3e0a721d4d23d75bd33734c295"
dependencies = [
"rand_core 0.4.2",
"rand_core",
]
[[package]]
name = "wyz"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e"
dependencies = [
"tap",
]

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