Merge remote-tracking branch 'origin/trunk' into tag-union-imitate-rust

This commit is contained in:
Folkert 2021-11-05 09:28:36 +01:00
commit 2174db6534
162 changed files with 9277 additions and 5412 deletions

1
.envrc
View file

@ -1 +0,0 @@
use nix

View file

@ -1,32 +1,31 @@
on:
schedule:
- cron: '0 0 * * *'
- cron: '0 9 * * *'
name: Nightly Release Build
jobs:
build:
name: Test and Build
runs-on: ubuntu-latest
runs-on: [self-hosted, i5-4690K]
timeout-minutes: 90
env:
FORCE_COLOR: 1 # for earthly logging
steps:
- uses: actions/checkout@v2
- uses: actions-rs/toolchain@v1
- name: Earthly print version
run: earthly --version
- name: install dependencies, build, run tests, build release
run: ./ci/safe-earthly.sh +build-nightly-release
- name: Create pre-release with test_archive.tar.gz
uses: WebFreak001/deploy-nightly@v1.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # automatically provided by github actions
with:
profile: minimal
toolchain: stable
override: true
- run: rustup component add rustfmt
- uses: actions-rs/cargo@v1
name: cargo update (needed for Inkwell)
with:
command: update
- uses: actions-rs/cargo@v1
name: cargo test
with:
command: test
args: --release
- uses: actions-rs/cargo@v1
name: cargo build
with:
command: build
args: --release
upload_url: https://uploads.github.com/repos/rtfeldman/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 # $$ to inserts date (YYYYMMDD) and 6 letter commit hash
asset_content_type: application/gzip
max_releases: 3

4
.gitignore vendored
View file

@ -2,6 +2,7 @@ target
generated-docs
zig-cache
.direnv
.envrc
*.rs.bk
*.o
*.tmp
@ -38,3 +39,6 @@ bench-folder*
# earthly
earthly_log.txt
# created to test release
roc_linux_x86_64.tar.gz

View file

@ -37,3 +37,10 @@ Kofi Gumbs <h.kofigumbs@gmail.com>
Luiz de Oliveira <luizcarlos1405@gmail.com>
Chelsea Troy <chelsea.dommert@gmail.com>
Shritesh Bhattarai <shr@ite.sh>
Kevin Sjöberg <mail@kevinsjoberg.com>
Viktor Fröberg <vikfroberg@gmail.com>
Locria Cyber <locriacyber@noreply.users.github.com>
Matthias Beyer <mail@beyermatthias.de>
Tim Whiting <tim@whitings.org>
Logan Lowder <logan.lowder@logikcull.com>
Joshua Warner <joshuawarner32@gmail.com>

View file

@ -50,8 +50,12 @@ If you want to install it manually, you can also download Zig directly [here](ht
**version: 12.0.x**
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
`/usr/local/opt/llvm/bin` to your `PATH`. You can confirm this worked by
`/usr/local/opt/llvm@12/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
You may also need to manually specify a prefix env var like so:
```
export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12
```
For Ubuntu and Debian:
```
@ -62,7 +66,7 @@ chmod +x llvm.sh
```
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
By default, the script installs them as `llvm-as-12` and `clang-12`,
By default, the script installs them as `clang-12` and `llvm-as-12`,
respectively. You can address this with symlinks like so:
```
@ -78,7 +82,7 @@ There are also alternative installation options at http://releases.llvm.org/down
## Using Nix
:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation:
:exclamation: **Our Nix setup is not yet working on MacOS, you'll have to install manually for now** :exclamation:
### Install
@ -90,7 +94,7 @@ First, install nix:
`curl -L https://nixos.org/nix/install | sh`
If MacOS and using a version >= 10.15:
If you're on MacOS and using a OS version >= 10.15:
`sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume`
@ -100,7 +104,7 @@ You may prefer to setup up the volume manually by following nix documentation.
### Usage
Now with nix installed you just need to run one command:
Now with nix installed, you just need to run one command:
`nix-shell`
@ -116,33 +120,49 @@ You should be in a repl now. Have fun!
### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/target/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
### Editor
When you want to run the editor from Ubuntu inside nix you need to install [nixGL](https://github.com/guibou/nixGL) as well:
`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below.
#### Nvidia GPU
Outside of a nix shell, execute the following:
```
nix-channel --add https://github.com/guibou/nixGL/archive/main.tar.gz nixgl && nix-channel --update
nix-env -iA nixgl.auto.nixVulkanNvidia
```
Running the editor does not work with `nix-shell --pure`.
```
nix-shell
```
460.91.03 may be different for you, type nixVulkanNvidia and press tab to autocomplete for your version.
```
nixVulkanNvidia-460.91.03 cargo run edit
```
#### Integrated Intel Graphics
:exclamation: ** Our Nix setup currently cannot run the editor with integrated intel graphics, see #1856 ** :exclamation:
Outside of a nix shell, run:
```bash
nix-shell
git clone https://github.com/guibou/nixGL
cd nixGL
```
If you have an Nvidia graphics card, run:
```
nix-env -f ./ -iA nixVulkanNvidia
```
If you have integrated Intel graphics, run:
```
nix-env -f ./ -iA nixVulkanIntel
```
Check the [nixGL repo](https://github.com/guibou/nixGL) for other configurations.
Now you should be able to run the editor:
```bash
cd roc
nixVulkanNvidia cargo run edit `# replace Nvidia with the config you chose in the previous step`
cd to the roc repo, and run (without --pure):
```
nix-shell
nixVulkanIntel cargo run edit
```
#### Other configs
Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics configurations.
## Troubleshooting
@ -193,6 +213,11 @@ on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMa
Once all that was done, `cargo` ran successfully for Roc!
### Build speed on WSL/WSL2
If your Roc project folder is in the Windows filesystem but you're compiling from Linux, rebuilds may be as much as 20x slower than they should be!
Disk access during linking seems to be the bottleneck. It's recommended to move your folder to the Linux filesystem.
## Use LLD for the linker
Using [`lld` for Rust's linker](https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306)

184
Cargo.lock generated
View file

@ -457,14 +457,16 @@ dependencies = [
[[package]]
name = "clap"
version = "3.0.0-beta.1"
source = "git+https://github.com/rtfeldman/clap?branch=master#e1d83a78804a271b053d4d21f69b67f7eb01ad01"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "860643c53f980f0d38a5e25dfab6c3c93b2cb3aa1fe192643d17a293c6c41936"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"strsim 0.9.3",
"os_str_bytes",
"strsim 0.10.0",
"termcolor",
"textwrap",
"unicode-width",
@ -473,11 +475,12 @@ dependencies = [
[[package]]
name = "clap_derive"
version = "3.0.0-beta.1"
source = "git+https://github.com/rtfeldman/clap?branch=master#e1d83a78804a271b053d4d21f69b67f7eb01ad01"
version = "3.0.0-beta.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3"
dependencies = [
"heck",
"proc-macro-error 0.4.12",
"proc-macro-error",
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
@ -495,7 +498,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"criterion",
"inlinable_string",
"rlimit",
"roc_cli",
"roc_collections",
@ -1848,12 +1850,6 @@ dependencies = [
"syn 1.0.76",
]
[[package]]
name = "inlinable_string"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77"
[[package]]
name = "inplace_it"
version = "0.3.3"
@ -2711,6 +2707,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "os_str_bytes"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
[[package]]
name = "output_vt100"
version = "0.1.2"
@ -2772,12 +2774,6 @@ dependencies = [
"syn 1.0.76",
]
[[package]]
name = "parity-wasm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be5e13c266502aadf83426d87d81a0f5d1ef45b8027f5a471c360abfe4bfae92"
[[package]]
name = "parking_lot"
version = "0.11.2"
@ -3017,45 +3013,19 @@ dependencies = [
"toml",
]
[[package]]
name = "proc-macro-error"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7"
dependencies = [
"proc-macro-error-attr 0.4.12",
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
"version_check",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr 1.0.4",
"proc-macro-error-attr",
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
dependencies = [
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
"syn-mid",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
@ -3597,9 +3567,7 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"inkwell 0.1.0",
"inlinable_string",
"libloading 0.6.7",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3631,7 +3599,6 @@ name = "roc_builtins"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3649,7 +3616,6 @@ dependencies = [
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3676,9 +3642,7 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"inkwell 0.1.0",
"libc",
"libloading 0.6.7",
"maplit",
"mimalloc",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -3717,6 +3681,7 @@ name = "roc_code_markup"
version = "0.1.0"
dependencies = [
"bumpalo",
"itertools 0.10.1",
"palette",
"roc_ast",
"roc_module",
@ -3741,7 +3706,6 @@ name = "roc_constrain"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3759,7 +3723,6 @@ name = "roc_docs"
version = "0.1.0"
dependencies = [
"bumpalo",
"maplit",
"pretty_assertions 0.5.1",
"pulldown-cmark",
"roc_ast",
@ -3767,12 +3730,12 @@ dependencies = [
"roc_can",
"roc_code_markup",
"roc_collections",
"roc_fmt",
"roc_load",
"roc_module",
"roc_parse",
"roc_region",
"roc_types",
"snafu",
"tempfile",
"uuid",
]
@ -3797,7 +3760,6 @@ dependencies = [
"indoc 1.0.3",
"libc",
"log",
"maplit",
"nonempty",
"page_size",
"palette",
@ -3812,7 +3774,6 @@ dependencies = [
"roc_can",
"roc_code_markup",
"roc_collections",
"roc_fmt",
"roc_load",
"roc_module",
"roc_parse",
@ -3823,7 +3784,6 @@ dependencies = [
"roc_types",
"roc_unify",
"rodio",
"ropey",
"serde",
"snafu",
"tempfile",
@ -3833,7 +3793,6 @@ dependencies = [
"wgpu",
"wgpu_glyph",
"winit",
"zerocopy",
]
[[package]]
@ -3844,7 +3803,6 @@ dependencies = [
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3863,9 +3821,7 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"itertools 0.9.0",
"libc",
"libloading 0.6.7",
"maplit",
"object 0.24.0",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -3900,8 +3856,6 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"inkwell 0.1.0",
"libc",
"maplit",
"morphic_lib",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -3931,8 +3885,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"indoc 0.3.6",
"libc",
"parity-wasm",
"pretty_assertions 0.5.1",
"roc_builtins",
"roc_can",
@ -4008,11 +3960,11 @@ dependencies = [
"bumpalo",
"indoc 0.3.6",
"lazy_static",
"maplit",
"pretty_assertions 0.5.1",
"roc_collections",
"roc_ident",
"roc_region",
"snafu",
"static_assertions",
]
@ -4024,7 +3976,6 @@ dependencies = [
"hashbrown 0.11.2",
"indoc 0.3.6",
"linked-hash-map",
"maplit",
"morphic_lib",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -4066,7 +4017,6 @@ name = "roc_problem"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4089,7 +4039,6 @@ dependencies = [
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4113,7 +4062,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4140,7 +4088,6 @@ name = "roc_types"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4156,7 +4103,6 @@ name = "roc_unify"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4185,15 +4131,6 @@ dependencies = [
"minimp3",
]
[[package]]
name = "ropey"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28"
dependencies = [
"smallvec",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
@ -4639,29 +4576,6 @@ dependencies = [
"unicode-xid 0.2.2",
]
[[package]]
name = "syn-mid"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9"
dependencies = [
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
]
[[package]]
name = "synstructure"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa"
dependencies = [
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
"unicode-xid 0.2.2",
]
[[package]]
name = "target-lexicon"
version = "0.12.2"
@ -4703,7 +4617,6 @@ dependencies = [
"inkwell 0.1.0",
"libc",
"libloading 0.6.7",
"maplit",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
"roc_build",
@ -4736,31 +4649,16 @@ name = "test_mono"
version = "0.1.0"
dependencies = [
"bumpalo",
"either",
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"libc",
"libloading 0.6.7",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_constrain",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_types",
"roc_unify",
"target-lexicon",
"test_mono_macros",
]
@ -4768,12 +4666,33 @@ dependencies = [
name = "test_mono_macros"
version = "0.1.0"
dependencies = [
"darling 0.10.2",
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
]
[[package]]
name = "test_wasm"
version = "0.1.0"
dependencies = [
"bumpalo",
"indoc 0.3.6",
"libc",
"pretty_assertions 0.5.1",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_gen_wasm",
"roc_load",
"roc_module",
"roc_std",
"roc_types",
"target-lexicon",
"tempfile",
"wasmer",
"wasmer-wasi",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@ -5256,7 +5175,7 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee7b351bcc1e782997c72dc0b5b328f3ddcad4813b8ce3cac3f25ae5a4ab56b"
dependencies = [
"proc-macro-error 1.0.4",
"proc-macro-error",
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
@ -5775,24 +5694,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zerocopy"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
dependencies = [
"proc-macro2 1.0.29",
"syn 1.0.76",
"synstructure",
]

View file

@ -24,6 +24,7 @@ members = [
"compiler/build",
"compiler/arena_pool",
"compiler/test_gen",
"compiler/test_wasm",
"vendor/ena",
"vendor/inkwell",
"vendor/pathfinding",

View file

@ -34,8 +34,8 @@ In the Roc community we strive to go the extra step to look out for each other.
And if someone takes issue with something you said or did, resist the urge to be defensive. Just stop doing what it was they complained about and apologize. Even if you feel you were misinterpreted or unfairly accused, chances are good there was something you could've communicated better — remember that it's your responsibility to make your fellow Roc programmers comfortable. Everyone wants to get along and we are all here first and foremost because we want to talk about cool technology. You will find that people will be eager to assume good intent and forgive as long as you earn their trust.
The enforcement policies listed above apply to all official Roc venues; including official IRC channels (#rust, #rust-internals, #rust-tools, #rust-libs, #rustc, #rust-beginners, #rust-docs, #rust-community, #rust-lang, and #cargo); GitHub repositories under rust-lang, rust-lang-nursery, and rust-lang-deprecated; and all forums under rust-lang.org (users.rust-lang.org, internals.rust-lang.org). For other projects adopting the Roc Code of Conduct, please contact the maintainers of those projects for enforcement. If you wish to use this code of conduct for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
The enforcement policies listed above apply to all official Roc venues; including official Zulip chat (https://roc.zulipchat.com); and GitHub repositories under the roc-lang organization. If you wish to use this code of conduct (or the Rust code of conduct, on which it is based) for your own project, consider explicitly mentioning your moderation policy or making a copy with your own moderation policy so as to avoid confusion.
*Adapted from the [Node.js Policy on Trolling](http://blog.izs.me/post/30036893703/policy-on-trolling) as well as the [Contributor Covenant v1.3.0](https://www.contributor-covenant.org/version/1/3/0/).*
[mod_team]: https://www.rust-lang.org/team.html#Moderation-team
[mod_team]: https://www.roc-lang.org/moderation

View file

@ -1,4 +1,4 @@
FROM rust:1.54-slim-bullseye
FROM rust:1.56.1-slim-bullseye
WORKDIR /earthbuild
prep-debian:
@ -35,7 +35,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
RUN rustup component add rustfmt
# criterion
RUN cargo install cargo-criterion
# wasm
# editor
RUN apt -y install libxkbcommon-dev
# sccache
RUN apt -y install libssl-dev
@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock ./
COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt
@ -67,12 +67,14 @@ check-rustfmt:
check-typos:
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix version.txt ./
RUN typos
test-rust:
FROM +copy-dirs
ENV RUST_BACKTRACE=1
# for race condition problem with cli test
ENV ROC_NUM_WORKERS=1
# run one of the benchmarks to make sure the host is compiled
# not pre-compiling the host can cause race conditions
RUN echo "4" | cargo run --release examples/benchmarks/NQueens.roc
@ -101,6 +103,16 @@ test-all:
BUILD +test-rust
BUILD +verify-no-git-changes
build-nightly-release:
FROM +test-rust
COPY --dir .git ./
# version.txt is used by the CLI: roc --version
RUN printf "nightly pre-release, built from commit " > version.txt
RUN git log --pretty=format:'%h' -n 1 >> version.txt
RUN cargo build --release
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc
SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
# compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.
prep-bench-folder:
FROM +copy-dirs

View file

@ -10,7 +10,7 @@ If you're curious about where the language's name and logo came from,
## State of Roc
Roc is not ready for production yet. You are likely to encounter bugs. Publishing packages or documentation is not yet supported.
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
Many programs can however be compiled correctly. Check out [examples](examples) and [examples/benchmarks](examples/benchmarks). There are minimal platforms for Rust, Zig, C, Swift and an HTTP server. We are hard at work to make programming in Roc a delightful experience!
## Getting started
@ -64,7 +64,7 @@ By using systems-level programming languages like C and C++, platform authors sa
Roc is designed to make the "systems-level platform, higher-level application" experience as nice as possible.
* **Application** authors code exclusively in Roc. It's a language designed for nice ergonomics. The syntax resembles Ruby or CoffeeScript, and it has a fast compiler with full type inference.
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
* **Platform** authors code almost exclusively in a systems-level language like C, C++, Rust, Swift or [Zig](https://ziglang.org/), except for the thin Roc API they expose to application authors. Roc application code compiles to machine code, and production builds of Roc apps benefit from the same [LLVM](https://llvm.org/) optimizations that C++, Rust, Swift and Zig do. Roc application authors do not need to know this lower-level code exists; all they have to interact with is the platform's API, which is exposed as an ordinary Roc API.
Every Roc application is built on top of exactly one Roc platform. There is no such thing as a Roc application that runs without a platform, and there is no default platform. You must choose one!

View file

@ -23,7 +23,7 @@ libc = "0.2"
page_size = "0.4"
snafu = { version = "0.6", features = ["backtraces"] }
ven_graph = { path = "../vendor/pathfinding" }
indoc = "1.0"
[dev-dependencies]
pretty_assertions = "0.6"
indoc = "1.0"

View file

@ -1,3 +1,6 @@
use roc_module::{ident::Ident, module_err::ModuleError};
use roc_parse::parser::SyntaxError;
use roc_region::all::{Located, Region};
use snafu::{Backtrace, Snafu};
use crate::lang::core::ast::ASTNodeId;
@ -33,6 +36,38 @@ pub enum ASTError {
encountered_pattern2: String,
backtrace: Backtrace,
},
#[snafu(display("IdentExistsError: {}", msg))]
IdentExistsError { msg: String },
WrapModuleError {
#[snafu(backtrace)]
source: ModuleError,
},
#[snafu(display("SyntaxError: {}", msg))]
SyntaxErrorNoBacktrace { msg: String },
}
pub type ASTResult<T, E = ASTError> = std::result::Result<T, E>;
impl From<ModuleError> for ASTError {
fn from(module_err: ModuleError) -> Self {
Self::WrapModuleError { source: module_err }
}
}
impl From<(Region, Located<Ident>)> for ASTError {
fn from(ident_exists_err: (Region, Located<Ident>)) -> Self {
Self::IdentExistsError {
msg: format!("{:?}", ident_exists_err),
}
}
}
impl<'a> From<SyntaxError<'a>> for ASTError {
fn from(syntax_err: SyntaxError) -> Self {
Self::SyntaxErrorNoBacktrace {
msg: format!("{:?}", syntax_err),
}
}
}

View file

@ -273,7 +273,7 @@ pub fn constrain_expr<'a>(
Expr2::Call {
args,
expr_var,
expr: expr_node_id,
expr_id: expr_node_id,
closure_var,
fn_var,
..
@ -944,8 +944,8 @@ pub fn constrain_expr<'a>(
}
Expr2::Closure {
args,
name,
body: body_id,
uniq_symbol,
body_id,
function_type: fn_var,
extra,
..
@ -992,7 +992,7 @@ pub fn constrain_expr<'a>(
let closure_constraint = constrain_closure_size(
arena,
env,
*name,
*uniq_symbol,
region,
captured_symbols,
*closure_var,

View file

@ -488,9 +488,9 @@ fn canonicalize_pending_def<'a>(
match loc_can_expr {
Expr2::Closure {
args: closure_args,
body,
body_id,
extra,
name: closure_symbol,
uniq_symbol: closure_symbol,
..
} => {
let symbol = match env.pool[loc_can_pattern] {
@ -570,7 +570,7 @@ fn canonicalize_pending_def<'a>(
arguments,
rigids: env.pool.add(rigids),
return_type,
body,
body_id,
};
let def = Def::Function(function_def);
@ -656,9 +656,9 @@ fn canonicalize_pending_def<'a>(
match loc_can_expr {
Expr2::Closure {
args: closure_args,
body,
body_id,
extra,
name: closure_symbol,
uniq_symbol: closure_symbol,
..
} => {
let symbol = match env.pool[loc_can_pattern] {
@ -703,7 +703,7 @@ fn canonicalize_pending_def<'a>(
name: symbol,
arguments,
return_var: env.var_store.fresh(),
body,
body_id,
};
let def = Def::Function(function_def);

View file

@ -1,8 +1,7 @@
use roc_module::symbol::IdentId;
use crate::{
lang::core::{
expr::{expr2::Expr2, expr2_to_string::expr2_to_string},
pattern::Pattern2,
},
lang::core::expr::{expr2::Expr2, expr2_to_string::expr2_to_string},
mem_pool::pool::{NodeId, Pool},
};
@ -11,7 +10,7 @@ use crate::{
pub enum Def2 {
// ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!"
ValueDef {
identifier_id: NodeId<Pattern2>,
identifier_id: IdentId,
expr_id: NodeId<Expr2>,
},
Blank,
@ -30,7 +29,7 @@ pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String {
} => {
full_string.push_str(&format!(
"Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})",
pool.get(*identifier_id),
identifier_id,
expr2_to_string(*expr_id, pool)
));
}

View file

@ -1,13 +1,10 @@
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use roc_parse::{parser::SyntaxError, pattern::PatternType};
use roc_module::ident::{Ident, IdentStr};
use roc_parse::parser::SyntaxError;
use roc_region::all::Region;
use crate::lang::{
core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2},
env::Env,
scope::Scope,
};
use crate::lang::{core::expr::expr_to_expr2::loc_expr_to_expr2, env::Env, scope::Scope};
use super::def2::Def2;
@ -37,26 +34,18 @@ pub fn def_to_def2<'a>(
SpaceBefore(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region),
SpaceAfter(inner_def, _) => def_to_def2(arena, env, scope, inner_def, region),
Body(&loc_pattern, &loc_expr) => {
// TODO loc_pattern use identifier
let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0;
let expr_id = env.pool.add(expr2);
use roc_parse::ast::Pattern::*;
match loc_pattern.value {
Identifier(_) => {
let (_, pattern2) = to_pattern2(
env,
scope,
PatternType::TopLevelDef,
&loc_pattern.value,
region,
);
let pattern_id = env.pool.add(pattern2);
Identifier(id_str) => {
let identifier_id = env.ident_ids.get_or_insert(&Ident(IdentStr::from(id_str)));
// TODO support with annotation
Def2::ValueDef {
identifier_id: pattern_id,
identifier_id,
expr_id,
}
}

View file

@ -95,7 +95,7 @@ pub enum Expr2 {
},
Call {
args: PoolVec<(Variable, ExprId)>, // 8B
expr: ExprId, // 4B
expr_id: ExprId, // 4B
expr_var: Variable, // 4B
fn_var: Variable, // 4B
closure_var: Variable, // 4B
@ -108,8 +108,8 @@ pub enum Expr2 {
},
Closure {
args: PoolVec<(Variable, NodeId<Pattern2>)>, // 8B
name: Symbol, // 8B
body: ExprId, // 4B
uniq_symbol: Symbol, // 8B This is a globally uniqe symbol for the closure
body_id: ExprId, // 4B
function_type: Variable, // 4B
recursive: Recursive, // 1B
extra: NodeId<ClosureExtra>, // 4B

View file

@ -128,6 +128,26 @@ fn expr2_to_string_helper(
pool.get(*body_id)
));
}
Expr2::Call { .. } => {
out_string.push_str(&format!("Call({:?})", expr2,));
}
Expr2::Closure { args, .. } => {
out_string.push_str("Closure:\n");
out_string.push_str(&format!("{}args: [\n", get_spacing(indent_level + 1)));
for (_, pattern_id) in args.iter(pool) {
let arg_pattern2 = pool.get(*pattern_id);
out_string.push_str(&format!(
"{}{:?}\n",
get_spacing(indent_level + 2),
arg_pattern2
));
}
}
&Expr2::Var { .. } => {
out_string.push_str(&format!("{:?}", expr2,));
}
other => todo!("Implement for {:?}", other),
}

View file

@ -232,7 +232,7 @@ pub fn expr_to_expr2<'a>(
// env.problems.push(Problem::RuntimeError(error));
//
// (answer, Output::default())
todo!()
todo!("{:?}", &can_update)
}
}
@ -487,10 +487,10 @@ pub fn expr_to_expr2<'a>(
(
Expr2::Closure {
function_type: env.var_store.fresh(),
name: symbol,
uniq_symbol: symbol,
recursive: Recursive::NotRecursive,
args: can_args,
body: env.add(body_expr, loc_body_expr.region),
body_id: env.add(body_expr, loc_body_expr.region),
extra: env.pool.add(extra),
},
output,
@ -504,7 +504,6 @@ pub fn expr_to_expr2<'a>(
// Canonicalize the function expression and its arguments
let (fn_expr, mut output) = expr_to_expr2(env, scope, &loc_fn.value, fn_region);
// The function's return type
let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool);
@ -533,7 +532,7 @@ pub fn expr_to_expr2<'a>(
let fn_expr_id = env.add(fn_expr, fn_region);
Expr2::Call {
args,
expr: fn_expr_id,
expr_id: fn_expr_id,
expr_var: env.var_store.fresh(),
fn_var: env.var_store.fresh(),
closure_var: env.var_store.fresh(),
@ -571,7 +570,7 @@ pub fn expr_to_expr2<'a>(
let fn_expr_id = env.add(fn_expr, fn_region);
Expr2::Call {
args,
expr: fn_expr_id,
expr_id: fn_expr_id,
expr_var: env.var_store.fresh(),
fn_var: env.var_store.fresh(),
closure_var: env.var_store.fresh(),
@ -662,7 +661,10 @@ pub fn expr_to_expr2<'a>(
// (RuntimeError(problem), Output::default())
todo!()
}
Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region),
Var {
module_name, // module_name will only be filled if the original Roc code stated something like `5 + SomeModule.myVar`, module_name will be blank if it was `5 + myVar`
ident,
} => canonicalize_lookup(env, scope, module_name, ident, region),
// Below this point, we shouln't see any of these nodes anymore because
// operator desugaring should have removed them!

View file

@ -18,13 +18,13 @@ pub enum FunctionDef {
arguments: PoolVec<(PatternId, Type2)>, // 8B
rigids: NodeId<Rigids>, // 4B
return_type: TypeId, // 4B
body: ExprId, // 4B
body_id: ExprId, // 4B
},
NoAnnotation {
name: Symbol, // 8B
arguments: PoolVec<(PatternId, Variable)>, // 8B
return_var: Variable, // 4B
body: ExprId, // 4B
body_id: ExprId, // 4B
},
}
@ -36,25 +36,25 @@ impl ShallowClone for FunctionDef {
arguments,
rigids,
return_type,
body,
body_id,
} => Self::WithAnnotation {
name: *name,
arguments: arguments.shallow_clone(),
rigids: *rigids,
return_type: *return_type,
body: *body,
body_id: *body_id,
},
Self::NoAnnotation {
name,
arguments,
return_var,
body,
body_id,
} => Self::NoAnnotation {
name: *name,
arguments: arguments.shallow_clone(),
return_var: *return_var,
body: *body,
body_id: *body_id,
},
}
}

View file

@ -157,7 +157,7 @@ fn desugar_str_segments(env: &mut Env, segments: Vec<StrSegment>) -> Expr2 {
let new_call = Expr2::Call {
args,
expr: concat_expr_id,
expr_id: concat_expr_id,
expr_var: var_store.fresh(),
fn_var: var_store.fresh(),
closure_var: var_store.fresh(),

View file

@ -1,3 +1,4 @@
use crate::mem_pool::pool::{NodeId, Pool};
use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName};
@ -6,8 +7,6 @@ use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_types::subs::VarStore;
use crate::mem_pool::pool::{NodeId, Pool};
use super::core::def::def::References;
#[derive(Debug)]
@ -35,6 +34,7 @@ pub struct Env<'a> {
}
impl<'a> Env<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
home: ModuleId,
arena: &'a Bump,
@ -52,7 +52,7 @@ impl<'a> Env<'a> {
var_store,
dep_idents,
module_ids,
ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later
ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later using Scope.introduce
exposed_ident_ids,
closures: MutMap::default(),
qualified_lookups: MutSet::default(),

View file

@ -2,13 +2,18 @@
#![allow(dead_code)]
#![allow(unused_imports)]
use std::fmt;
use crate::ast_error::ASTResult;
use crate::mem_pool::pool::Pool;
use crate::mem_pool::pool_str::PoolStr;
use crate::mem_pool::pool_vec::PoolVec;
use crate::mem_pool::shallow_clone::ShallowClone;
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_module::symbol::{
get_module_ident_ids, get_module_ident_ids_mut, IdentIds, Interns, ModuleId, Symbol,
};
use roc_problem::can::RuntimeError;
use roc_region::all::{Located, Region};
use roc_types::{
@ -18,6 +23,7 @@ use roc_types::{
};
use super::core::types::{Alias, Type2, TypeId};
use super::env::Env;
fn solved_type_to_type_id(
pool: &mut Pool,
@ -312,6 +318,25 @@ impl Scope {
pub fn contains_alias(&mut self, name: Symbol) -> bool {
self.aliases.contains_key(&name)
}
pub fn fill_scope(
&mut self,
env: &Env,
all_ident_ids: &mut MutMap<ModuleId, IdentIds>,
) -> ASTResult<()> {
let ident_ids = get_module_ident_ids(all_ident_ids, &env.home)?.clone();
for (_, ident_ref) in ident_ids.idents() {
self.introduce(
ident_ref.as_inline_str().as_str().into(),
&env.exposed_ident_ids,
get_module_ident_ids_mut(all_ident_ids, &env.home)?,
Region::zero(),
)?;
}
Ok(())
}
}
impl ShallowClone for Scope {

View file

@ -10,8 +10,9 @@
///
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
/// This is important for performance.
use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use std::any::type_name;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::null;

View file

@ -1,6 +1,6 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;

View file

@ -1,8 +1,8 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::any::type_name;
use std::cmp::Ordering;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;

View file

@ -1,8 +1,10 @@
use bumpalo::Bump;
use roc_parse::parser::SyntaxError;
use roc_module::symbol::Interns;
use roc_region::all::Region;
use crate::lang::{
use crate::{
ast_error::ASTResult,
lang::{
core::{
ast::AST,
def::{def2::DefId, def_to_def2::str_to_def2},
@ -10,6 +12,7 @@ use crate::lang::{
},
env::Env,
scope::Scope,
},
};
use super::parse_header;
@ -18,7 +21,8 @@ pub fn parse_from_string<'a>(
code_str: &'a str,
env: &mut Env<'a>,
ast_arena: &'a Bump,
) -> Result<AST, SyntaxError<'a>> {
interns: &mut Interns,
) -> ASTResult<AST> {
let blank_line_indx = code_str
.find("\n\n")
.expect("I was expecting a double newline to split header and rest of code.");
@ -27,6 +31,8 @@ pub fn parse_from_string<'a>(
let tail_str = &code_str[blank_line_indx..];
let mut scope = Scope::new(env.home, env.pool, env.var_store);
scope.fill_scope(env, &mut interns.all_ident_ids)?;
let region = Region::new(0, 0, 0, 0);
let mut def_ids = Vec::<DefId>::new();

View file

@ -58,15 +58,13 @@ roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
clap = "= 3.0.0-beta.1"
const_format = "0.2"
rustyline = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prompt-fix" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2"
libloading = "0.6"
mimalloc = { version = "0.1.26", default-features = false }
@ -79,7 +77,6 @@ wasmer-wasi = "2.0.0"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
@ -91,4 +88,3 @@ cli_utils = { path = "cli_utils" }
[[bench]]
name = "time_bench"
harness = false

View file

@ -16,7 +16,6 @@ roc_load = { path = "../../compiler/load" }
roc_module = { path = "../../compiler/module" }
bumpalo = { version = "3.6.1", features = ["collections"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
inlinable_string = "0.1"
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
strip-ansi-escapes = "0.1"

View file

@ -1,6 +1,3 @@
#[macro_use]
extern crate clap;
#[macro_use]
extern crate const_format;
@ -21,7 +18,6 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, X86_32Architecture};
pub mod build;
pub mod repl;
pub const CMD_RUN: &str = "run";
pub const CMD_BUILD: &str = "build";
pub const CMD_REPL: &str = "repl";
pub const CMD_EDIT: &str = "edit";
@ -43,31 +39,31 @@ pub const ARGS_FOR_APP: &str = "ARGS_FOR_APP";
pub fn build_app<'a>() -> App<'a> {
let app = App::new("roc")
.version(concatcp!(crate_version!(), "\n"))
.version(concatcp!(include_str!("../../version.txt"), "\n"))
.about("Runs the given .roc file. Use one of the SUBCOMMANDS below to do something else!")
.subcommand(App::new(CMD_BUILD)
.about("Build a binary from the given .roc file, but don't run it")
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file to build")
.about("The .roc file to build")
.required(true),
)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.about("Optimize your compiled Roc program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEV)
.long(FLAG_DEV)
.help("Make compilation as fast as possible. (Runtime performance may suffer)")
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
.long(FLAG_BACKEND)
.help("Choose a different backend")
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
@ -76,66 +72,34 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::with_name(FLAG_LIB)
.long(FLAG_LIB)
.help("Build a C library instead of an executable.")
.about("Build a C library instead of an executable.")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
.about("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::with_name(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(FLAG_LINK)
.long(FLAG_LINK)
.help("Uses the roc linker instead of the system linker.")
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.help("Assumes the host has been precompiled and skips recompiling the host.")
.about("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
)
.subcommand(App::new(CMD_RUN)
.about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`")
.setting(AppSettings::TrailingVarArg)
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEV)
.long(FLAG_DEV)
.help("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to run")
.required(true),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
.help("Arguments to pass into the app being run")
.multiple(true),
)
)
.subcommand(App::new(CMD_REPL)
.about("Launch the interactive Read Eval Print Loop (REPL)")
)
@ -144,23 +108,23 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::with_name(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to run")
.about("The .roc file of an app to run")
.required(true),
)
)
.subcommand(
App::new(CMD_DOCS)
.about("Generate documentation for Roc modules")
.about("Generate documentation for Roc modules (Work In Progress)")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)
.required(false)
.help("The directory or files to build documentation for")
.about("The directory or files to build documentation for")
)
)
@ -168,45 +132,45 @@ pub fn build_app<'a>() -> App<'a> {
.arg(
Arg::with_name(FLAG_OPTIMIZE)
.long(FLAG_OPTIMIZE)
.help("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.about("Optimize the compiled program to run faster. (Optimization takes time to complete.)")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(FLAG_DEV)
.long(FLAG_DEV)
.help("Make compilation as fast as possible. (Runtime performance may suffer)")
.about("Make compilation as fast as possible. (Runtime performance may suffer)")
.required(false),
)
.arg(
Arg::with_name(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Store LLVM debug information in the generated program")
.about("Store LLVM debug information in the generated program")
.requires(ROC_FILE)
.required(false),
)
.arg(
Arg::with_name(FLAG_TIME)
.long(FLAG_TIME)
.help("Prints detailed compilation time information.")
.about("Prints detailed compilation time information.")
.required(false),
)
.arg(
Arg::with_name(FLAG_LINK)
.long(FLAG_LINK)
.help("Uses the roc linker instead of the system linker.")
.about("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.help("Assumes the host has been precompiled and skips recompiling the host.")
.about("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
.arg(
Arg::with_name(FLAG_BACKEND)
.long(FLAG_BACKEND)
.help("Choose a different backend")
.about("Choose a different backend")
// .requires(BACKEND)
.default_value(Backend::default().as_str())
.possible_values(Backend::OPTIONS)
@ -214,12 +178,12 @@ pub fn build_app<'a>() -> App<'a> {
)
.arg(
Arg::with_name(ROC_FILE)
.help("The .roc file of an app to build and run")
.about("The .roc file of an app to build and run")
.required(false),
)
.arg(
Arg::with_name(ARGS_FOR_APP)
.help("Arguments to pass into the app being run")
.about("Arguments to pass into the app being run")
.requires(ROC_FILE)
.multiple(true),
);
@ -231,7 +195,7 @@ pub fn build_app<'a>() -> App<'a> {
.index(1)
.multiple(true)
.required(false)
.help("(optional) The directory or files to open on launch."),
.about("(optional) The directory or files to open on launch."),
),
)
} else {

View file

@ -1,7 +1,7 @@
use roc_cli::build::check_file;
use roc_cli::{
build_app, docs, repl, BuildConfig, CMD_BUILD, CMD_CHECK, CMD_DOCS, CMD_EDIT, CMD_REPL,
CMD_RUN, DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
DIRECTORY_OR_FILES, FLAG_TIME, ROC_FILE,
};
use roc_load::file::LoadingProblem;
use std::fs::{self, FileType};
@ -44,17 +44,6 @@ fn main() -> io::Result<()> {
matches.subcommand_matches(CMD_BUILD).unwrap(),
BuildConfig::BuildOnly,
)?),
Some(CMD_RUN) => {
// TODO remove CMD_RUN altogether if it is currently September 2021 or later.
println!(
r#"`roc run` is deprecated!
If you're using a prebuilt binary, you no longer need the `run` - just do `roc [FILE]` instead of `roc run [FILE]`.
If you're building the compiler from source you'll want to do `cargo run [FILE]` instead of `cargo run run [FILE]`.
"#
);
Ok(1)
}
Some(CMD_CHECK) => {
let arena = bumpalo::Bump::new();

View file

@ -129,7 +129,7 @@ mod cli_run {
if !&out.stdout.ends_with(expected_ending) {
panic!(
"expected output to end with {:?} but instead got {:#?}",
expected_ending, out
expected_ending, out.stdout
);
}
assert!(out.status.success());
@ -191,6 +191,12 @@ mod cli_run {
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
return;
}
"hello-swift" => {
if cfg!(not(target_os = "macos")) {
eprintln!("WARNING: skipping testing example {} because it only works on MacOS.", example.filename);
return;
}
}
_ => {}
}
@ -205,6 +211,9 @@ mod cli_run {
example.use_valgrind,
);
// This is mostly because the false interpreter is still very slow -
// 25s for the cli tests is just not acceptable during development!
#[cfg(not(debug_assertions))]
check_output_with_stdin(
&file_name,
example.stdin,
@ -280,6 +289,14 @@ mod cli_run {
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_swift:"hello-swift" => Example {
filename: "Hello.roc",
executable_filename: "hello-swift",
stdin: &[],
input_file: None,
expected_ending:"Hello Swift, meet Roc\n",
use_valgrind: true,
},
hello_web:"hello-web" => Example {
filename: "Hello.roc",
executable_filename: "hello-web",
@ -359,7 +376,7 @@ mod cli_run {
stdin: &[],
input_file: Some("examples/hello.false"),
expected_ending:"Hello, World!\n",
use_valgrind: false,
use_valgrind: true,
}
},
}

View file

@ -14,5 +14,6 @@ serde = { version = "1.0.123", features = ["derive"] }
palette = "0.5"
snafu = { version = "0.6", features = ["backtraces"] }
bumpalo = { version = "3.2", features = ["collections"] }
itertools = "0.10.1"
[dev-dependencies]

View file

@ -16,9 +16,13 @@ pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>)
}
pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
new_comma_mn_ast(ASTNodeId::AExprId(expr_id), parent_id_opt)
}
pub fn new_comma_mn_ast(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Text {
content: nodes::COMMA.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id),
ast_node_id,
syn_high_style: HighlightStyle::Comma,
attributes: Attributes::default(),
parent_id_opt,
@ -29,7 +33,6 @@ pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> Marku
pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
MarkupNode::Blank {
ast_node_id,
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: 0,
@ -43,7 +46,6 @@ pub fn new_blank_mn_w_nls(
) -> MarkupNode {
MarkupNode::Blank {
ast_node_id,
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::default(),
parent_id_opt,
newlines_at_end: nr_of_newlines,
@ -51,8 +53,16 @@ pub fn new_blank_mn_w_nls(
}
pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> MarkupNode {
new_operator_mn(nodes::COLON.to_owned(), expr_id, parent_id_opt)
}
pub fn new_operator_mn(
content: String,
expr_id: ExprId,
parent_id_opt: Option<MarkNodeId>,
) -> MarkupNode {
MarkupNode::Text {
content: nodes::COLON.to_owned(),
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::default(),
@ -104,3 +114,36 @@ pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -
newlines_at_end: 0,
}
}
pub fn new_func_name_mn(content: String, expr_id: ExprId) -> MarkupNode {
MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::FunctionName,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
}
}
pub fn new_arg_name_mn(content: String, expr_id: ExprId) -> MarkupNode {
MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::FunctionArgName,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
}
}
pub fn new_arrow_mn(ast_node_id: ASTNodeId, newlines_at_end: usize) -> MarkupNode {
MarkupNode::Text {
content: nodes::ARROW.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Operator,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end,
}
}

View file

@ -0,0 +1,34 @@
use roc_ast::{
ast_error::ASTResult,
lang::{core::ast::AST, env::Env},
};
use roc_module::symbol::Interns;
use crate::{
markup::{
convert::{from_def2::def2_to_markup, from_header::header_to_markup},
nodes::set_parent_for_all,
},
slow_pool::{MarkNodeId, SlowPool},
};
pub fn ast_to_mark_nodes<'a>(
env: &mut Env<'a>,
ast: &AST,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> ASTResult<Vec<MarkNodeId>> {
let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)];
for &def_id in ast.def_ids.iter() {
let def2 = env.pool.get(def_id);
let expr2_markup_id = def2_to_markup(env, def2, def_id, mark_node_pool, interns)?;
set_parent_for_all(expr2_markup_id, mark_node_pool);
all_mark_node_ids.push(expr2_markup_id);
}
Ok(all_mark_node_ids)
}

View file

@ -0,0 +1,52 @@
use crate::{
markup::{common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node},
slow_pool::{MarkNodeId, SlowPool},
};
use super::from_expr2::expr2_to_markup;
use roc_ast::{
ast_error::ASTResult,
lang::{
core::{
ast::ASTNodeId,
def::def2::{Def2, DefId},
},
env::Env,
},
};
use roc_module::symbol::Interns;
pub fn def2_to_markup<'a>(
env: &mut Env<'a>,
def2: &Def2,
def2_node_id: DefId,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> ASTResult<MarkNodeId> {
let ast_node_id = ASTNodeId::ADefId(def2_node_id);
let mark_node_id = match def2 {
Def2::ValueDef {
identifier_id,
expr_id,
} => {
let expr_mn_id = expr2_to_markup(
env,
env.pool.get(*expr_id),
*expr_id,
mark_node_pool,
interns,
0,
)?;
let tld_mn =
tld_mark_node(*identifier_id, expr_mn_id, ast_node_id, mark_node_pool, env)?;
mark_node_pool.add(tld_mn)
}
Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)),
};
Ok(mark_node_id)
}

View file

@ -0,0 +1,389 @@
use crate::{
markup::{
attribute::Attributes,
common_nodes::{
new_arg_name_mn, new_arrow_mn, new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn,
new_left_accolade_mn, new_left_square_mn, new_operator_mn, new_right_accolade_mn,
new_right_square_mn,
},
nodes::{
get_string, join_mark_nodes_commas, join_mark_nodes_spaces, new_markup_node, MarkupNode,
},
},
slow_pool::{MarkNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
use itertools::Itertools;
use roc_ast::{
ast_error::ASTResult,
lang::{
core::{
ast::ASTNodeId,
expr::{
expr2::{Expr2, ExprId},
record_field::RecordField,
},
pattern::{get_identifier_string, Pattern2},
val_def::ValueDef,
},
env::Env,
},
};
use roc_module::{module_err::ModuleResult, symbol::Interns};
// make Markup Nodes: generate String representation, assign Highlighting Style
pub fn expr2_to_markup<'a>(
env: &Env<'a>,
expr2: &Expr2,
expr2_node_id: ExprId,
mark_node_pool: &mut SlowPool,
interns: &Interns,
indent_level: usize,
) -> ASTResult<MarkNodeId> {
let ast_node_id = ASTNodeId::AExprId(expr2_node_id);
// for debugging
//println!("EXPR2 {:?}", expr2);
let mark_node_id = match expr2 {
Expr2::SmallInt { text, .. }
| Expr2::I128 { text, .. }
| Expr2::U128 { text, .. }
| Expr2::Float { text, .. } => {
let num_str = get_string(env, text);
new_markup_node(
with_indent(indent_level, &num_str),
ast_node_id,
HighlightStyle::Number,
mark_node_pool,
indent_level,
)
}
Expr2::Str(text) => {
let content = format!("\"{}\"", text.as_str(env.pool));
new_markup_node(
with_indent(indent_level, &content),
ast_node_id,
HighlightStyle::String,
mark_node_pool,
indent_level,
)
}
Expr2::GlobalTag { name, .. } => new_markup_node(
with_indent(indent_level, &get_string(env, name)),
ast_node_id,
HighlightStyle::Type,
mark_node_pool,
indent_level,
),
Expr2::Call { args, expr_id, .. } => {
let expr = env.pool.get(*expr_id);
let fun_call_mark_id =
expr2_to_markup(env, expr, *expr_id, mark_node_pool, interns, indent_level)?;
let arg_expr_ids: Vec<ExprId> =
args.iter(env.pool).map(|(_, arg_id)| *arg_id).collect();
let arg_call_mark_ids: Vec<MarkNodeId> = arg_expr_ids
.iter()
.map(|arg_id| {
let arg_expr = env.pool.get(*arg_id);
expr2_to_markup(env, arg_expr, *arg_id, mark_node_pool, interns, 0)
})
.collect::<ASTResult<Vec<MarkNodeId>>>()?;
let mut args_with_sapces =
join_mark_nodes_spaces(arg_call_mark_ids, true, ast_node_id, mark_node_pool);
let mut children_ids = vec![fun_call_mark_id];
children_ids.append(&mut args_with_sapces);
let call_node = MarkupNode::Nested {
ast_node_id,
children_ids,
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(call_node)
}
Expr2::Var(symbol) => {
let text = symbol.fully_qualified(interns, env.home);
new_markup_node(
text.to_string(),
ast_node_id,
HighlightStyle::Value,
mark_node_pool,
indent_level,
)
}
Expr2::List { elems, .. } => {
let mut children_ids =
vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))];
let indexed_node_ids: Vec<(usize, ExprId)> =
elems.iter(env.pool).copied().enumerate().collect();
for (idx, node_id) in indexed_node_ids.iter() {
let sub_expr2 = env.pool.get(*node_id);
children_ids.push(expr2_to_markup(
env,
sub_expr2,
*node_id,
mark_node_pool,
interns,
indent_level,
)?);
if idx + 1 < elems.len() {
children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
}
}
children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None)));
let list_node = MarkupNode::Nested {
ast_node_id,
children_ids,
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(list_node)
}
Expr2::EmptyRecord => {
let children_ids = vec![
mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)),
mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)),
];
let record_node = MarkupNode::Nested {
ast_node_id,
children_ids,
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(record_node)
}
Expr2::Record { fields, .. } => {
let mut children_ids =
vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))];
for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
let record_field = env.pool.get(field_node_id);
let field_name = record_field.get_record_field_pool_str();
children_ids.push(new_markup_node(
field_name.as_str(env.pool).to_owned(),
ast_node_id,
HighlightStyle::RecordField,
mark_node_pool,
indent_level,
));
match record_field {
RecordField::InvalidLabelOnly(_, _) => (),
RecordField::LabelOnly(_, _, _) => (),
RecordField::LabeledValue(_, _, sub_expr2_node_id) => {
children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None)));
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
children_ids.push(expr2_to_markup(
env,
sub_expr2,
*sub_expr2_node_id,
mark_node_pool,
interns,
indent_level,
)?);
}
}
if idx + 1 < fields.len() {
children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
}
}
children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)));
let record_node = MarkupNode::Nested {
ast_node_id,
children_ids,
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(record_node)
}
Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)),
Expr2::LetValue {
def_id,
body_id: _,
body_var: _,
} => {
let pattern_id = env.pool.get(*def_id).get_pattern_id();
let pattern2 = env.pool.get(pattern_id);
let val_name = get_identifier_string(pattern2, interns)?;
let val_name_mn = MarkupNode::Text {
content: val_name,
ast_node_id,
syn_high_style: HighlightStyle::Value,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
let val_name_mn_id = mark_node_pool.add(val_name_mn);
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
let value_def = env.pool.get(*def_id);
match value_def {
ValueDef::NoAnnotation {
pattern_id: _,
expr_id,
expr_var: _,
} => {
let body_mn_id = expr2_to_markup(
env,
env.pool.get(*expr_id),
*expr_id,
mark_node_pool,
interns,
indent_level,
)?;
let body_mn = mark_node_pool.get_mut(body_mn_id);
body_mn.add_newline_at_end();
let full_let_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id],
parent_id_opt: None,
newlines_at_end: 1,
};
mark_node_pool.add(full_let_node)
}
other => {
unimplemented!(
"I don't know how to convert {:?} into a MarkupNode yet.",
other
)
}
}
}
Expr2::Closure {
function_type: _,
uniq_symbol: _,
recursive: _,
args,
body_id,
extra: _,
} => {
let backslash_mn = new_operator_mn("\\".to_string(), expr2_node_id, None);
let backslash_mn_id = mark_node_pool.add(backslash_mn);
let arg_names: Vec<&str> = args
.iter(env.pool)
.map(|(_, arg_node_id)| {
let arg_pattern2 = env.pool.get(*arg_node_id);
match arg_pattern2 {
Pattern2::Identifier(id_symbol) => {
let ident_id = id_symbol.ident_id();
env.ident_ids.get_name_str_res(ident_id)
}
Pattern2::Shadowed { shadowed_ident } => {
Ok(shadowed_ident.as_str(env.pool))
}
other => {
todo!(
"TODO: support the following pattern2 as function arg: {:?}",
other
);
}
}
})
.collect::<ModuleResult<Vec<&str>>>()?;
let arg_mark_nodes = arg_names
.iter()
.map(|arg_name| new_arg_name_mn(arg_name.to_string(), expr2_node_id))
.collect_vec();
let args_with_commas: Vec<MarkupNode> =
join_mark_nodes_commas(arg_mark_nodes, ASTNodeId::AExprId(expr2_node_id));
let mut args_with_commas_ids: Vec<MarkNodeId> = args_with_commas
.into_iter()
.map(|mark_node| mark_node_pool.add(mark_node))
.collect();
let arrow_mn = new_arrow_mn(ASTNodeId::AExprId(expr2_node_id), 1);
let arrow_mn_id = mark_node_pool.add(arrow_mn);
let mut children_ids = vec![backslash_mn_id];
children_ids.append(&mut args_with_commas_ids);
children_ids.push(arrow_mn_id);
let args_mn = MarkupNode::Nested {
ast_node_id: ASTNodeId::AExprId(expr2_node_id),
children_ids,
parent_id_opt: None,
newlines_at_end: 0,
};
let args_mn_id = mark_node_pool.add(args_mn);
let body_expr = env.pool.get(*body_id);
let body_mn_id = expr2_to_markup(
env,
body_expr,
*body_id,
mark_node_pool,
interns,
indent_level + 1,
)?;
let function_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![args_mn_id, body_mn_id],
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(function_node)
}
Expr2::RuntimeError() => new_markup_node(
"RunTimeError".to_string(),
ast_node_id,
HighlightStyle::Blank,
mark_node_pool,
indent_level,
),
rest => todo!("implement expr2_to_markup for {:?}", rest),
};
Ok(mark_node_id)
}
fn with_indent(indent_level: usize, some_str: &str) -> String {
let full_indent = std::iter::repeat(" ").take(indent_level * 4);
let mut full_string: String = full_indent.collect();
full_string.push_str(some_str);
full_string
}

View file

@ -0,0 +1,205 @@
use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId, header::AppHeader};
use crate::{
markup::{
attribute::Attributes,
common_nodes::{
new_comma_mn, new_left_accolade_mn, new_left_square_mn, new_right_accolade_mn,
new_right_square_mn,
},
nodes::{set_parent_for_all, MarkupNode},
},
slow_pool::{MarkNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId {
let expr_id = app_header.ast_node_id;
let ast_node_id = ASTNodeId::AExprId(expr_id);
let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool);
let app_name_node_id = header_val_mn(
app_header.app_name.clone(),
expr_id,
HighlightStyle::String,
mark_node_pool,
);
let full_app_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![app_node_id, app_name_node_id],
parent_id_opt: None,
newlines_at_end: 1,
};
let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool);
let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None));
let pack_base_node_id = header_val_mn(
"base: ".to_owned(),
expr_id,
HighlightStyle::RecordField,
mark_node_pool,
);
let pack_val_node_id = header_val_mn(
app_header.packages_base.clone(),
expr_id,
HighlightStyle::String,
mark_node_pool,
);
let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None));
let full_packages_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![
packages_node_id,
pack_left_acc_node_id,
pack_base_node_id,
pack_val_node_id,
pack_right_acc_node_id,
],
parent_id_opt: None,
newlines_at_end: 1,
};
let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool);
let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
let mut import_child_ids: Vec<MarkNodeId> = add_header_mn_list(
&app_header.imports,
expr_id,
HighlightStyle::Import,
mark_node_pool,
);
let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
let mut full_import_children = vec![imports_node_id, imports_left_square_node_id];
full_import_children.append(&mut import_child_ids);
full_import_children.push(imports_right_square_node_id);
let full_import_node = MarkupNode::Nested {
ast_node_id,
children_ids: full_import_children,
parent_id_opt: None,
newlines_at_end: 1,
};
let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool);
let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
let mut provides_val_node_ids: Vec<MarkNodeId> = add_header_mn_list(
&app_header.provides,
expr_id,
HighlightStyle::Provides,
mark_node_pool,
);
let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool);
let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id];
full_provides_children.append(&mut provides_val_node_ids);
full_provides_children.push(provides_right_square_node_id);
full_provides_children.push(provides_end_node_id);
let full_provides_node = MarkupNode::Nested {
ast_node_id,
children_ids: full_provides_children,
parent_id_opt: None,
newlines_at_end: 1,
};
let full_app_node_id = mark_node_pool.add(full_app_node);
let full_packages_node = mark_node_pool.add(full_packages_node);
let full_import_node_id = mark_node_pool.add(full_import_node);
let full_provides_node_id = mark_node_pool.add(full_provides_node);
let header_mark_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![
full_app_node_id,
full_packages_node,
full_import_node_id,
full_provides_node_id,
],
parent_id_opt: None,
newlines_at_end: 1,
};
let header_mn_id = mark_node_pool.add(header_mark_node);
set_parent_for_all(header_mn_id, mark_node_pool);
header_mn_id
}
// Used for provides and imports
fn add_header_mn_list(
str_vec: &[String],
expr_id: ExprId,
highlight_style: HighlightStyle,
mark_node_pool: &mut SlowPool,
) -> Vec<MarkNodeId> {
let nr_of_elts = str_vec.len();
str_vec
.iter()
.enumerate()
.map(|(indx, provide_str)| {
let provide_str = header_val_mn(
provide_str.to_owned(),
expr_id,
highlight_style,
mark_node_pool,
);
if indx != nr_of_elts - 1 {
vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))]
} else {
vec![provide_str]
}
})
.flatten()
.collect()
}
fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId {
let mark_node = MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::PackageRelated,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(mark_node)
}
fn header_val_mn(
content: String,
expr_id: ExprId,
highlight_style: HighlightStyle,
mark_node_pool: &mut SlowPool,
) -> MarkNodeId {
let mark_node = MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: highlight_style,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(mark_node)
}

View file

@ -0,0 +1,4 @@
pub mod from_ast;
pub mod from_def2;
pub mod from_expr2;
pub mod from_header;

View file

@ -1,4 +1,5 @@
pub mod attribute;
pub mod common_nodes;
pub mod convert;
pub mod nodes;
pub mod top_level_def;

View file

@ -1,37 +1,17 @@
use crate::{
markup::common_nodes::{
new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn,
new_left_square_mn, new_right_accolade_mn, new_right_square_mn,
},
markup_error::MarkResult,
slow_pool::{MarkNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
use super::{
attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node,
};
use super::{attribute::Attributes, common_nodes::new_comma_mn_ast};
use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired};
use itertools::Itertools;
use roc_ast::{
ast_error::ASTResult,
lang::{
core::{
ast::{ASTNodeId, AST},
def::def2::{Def2, DefId},
expr::{
expr2::{Expr2, ExprId},
record_field::RecordField,
},
header::AppHeader,
pattern::get_identifier_string,
val_def::ValueDef,
},
env::Env,
},
lang::{core::ast::ASTNodeId, env::Env},
mem_pool::pool_str::PoolStr,
};
use roc_module::symbol::Interns;
use roc_utils::{index_of, slice_get};
use std::fmt;
@ -54,10 +34,14 @@ pub enum MarkupNode {
Blank {
ast_node_id: ASTNodeId,
attributes: Attributes,
syn_high_style: HighlightStyle, // TODO remove HighlightStyle, this is always HighlightStyle::Blank
parent_id_opt: Option<MarkNodeId>,
newlines_at_end: usize,
},
Indent {
ast_node_id: ASTNodeId,
indent_level: usize,
parent_id_opt: Option<MarkNodeId>,
},
}
impl MarkupNode {
@ -66,6 +50,7 @@ impl MarkupNode {
MarkupNode::Nested { ast_node_id, .. } => *ast_node_id,
MarkupNode::Text { ast_node_id, .. } => *ast_node_id,
MarkupNode::Blank { ast_node_id, .. } => *ast_node_id,
MarkupNode::Indent { ast_node_id, .. } => *ast_node_id,
}
}
@ -74,6 +59,7 @@ impl MarkupNode {
MarkupNode::Nested { parent_id_opt, .. } => *parent_id_opt,
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt,
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt,
MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt,
}
}
@ -82,6 +68,7 @@ impl MarkupNode {
MarkupNode::Nested { children_ids, .. } => children_ids.to_vec(),
MarkupNode::Text { .. } => vec![],
MarkupNode::Blank { .. } => vec![],
MarkupNode::Indent { .. } => vec![],
}
}
@ -176,6 +163,7 @@ impl MarkupNode {
MarkupNode::Nested { .. } => "".to_owned(),
MarkupNode::Text { content, .. } => content.clone(),
MarkupNode::Blank { .. } => BLANK_PLACEHOLDER.to_owned(),
MarkupNode::Indent { indent_level, .. } => SINGLE_INDENT.repeat(*indent_level),
}
}
@ -192,13 +180,8 @@ impl MarkupNode {
pub fn get_content_mut(&mut self) -> MarkResult<&mut String> {
match self {
MarkupNode::Nested { .. } => ExpectedTextNode {
function_name: "set_content".to_owned(),
node_type: self.node_type_as_string(),
}
.fail(),
MarkupNode::Text { content, .. } => Ok(content),
MarkupNode::Blank { .. } => ExpectedTextNode {
_ => ExpectedTextNode {
function_name: "set_content".to_owned(),
node_type: self.node_type_as_string(),
}
@ -230,6 +213,7 @@ impl MarkupNode {
MarkupNode::Nested { .. } => "Nested",
MarkupNode::Text { .. } => "Text",
MarkupNode::Blank { .. } => "Blank",
MarkupNode::Indent { .. } => "Indent",
};
type_str.to_owned()
@ -254,6 +238,7 @@ impl MarkupNode {
MarkupNode::Blank {
newlines_at_end, ..
} => *newlines_at_end,
MarkupNode::Indent { .. } => 0,
}
}
@ -268,11 +253,12 @@ impl MarkupNode {
MarkupNode::Blank {
newlines_at_end, ..
} => *newlines_at_end += 1,
_ => {}
}
}
}
fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
pub fn get_string<'a>(env: &Env<'a>, pool_str: &PoolStr) -> String {
pool_str.as_str(env.pool).to_owned()
}
@ -285,14 +271,17 @@ pub const COLON: &str = ": ";
pub const COMMA: &str = ", ";
pub const STRING_QUOTES: &str = "\"\"";
pub const EQUALS: &str = " = ";
pub const ARROW: &str = " -> ";
pub const SINGLE_INDENT: &str = " "; // 4 spaces
fn new_markup_node(
pub fn new_markup_node(
text: String,
node_id: ASTNodeId,
highlight_style: HighlightStyle,
mark_node_pool: &mut SlowPool,
indent_level: usize,
) -> MarkNodeId {
let node = MarkupNode::Text {
let content_node = MarkupNode::Text {
content: text,
ast_node_id: node_id,
syn_high_style: highlight_style,
@ -301,256 +290,28 @@ fn new_markup_node(
newlines_at_end: 0,
};
mark_node_pool.add(node)
}
let content_node_id = mark_node_pool.add(content_node);
pub fn def2_to_markup<'a>(
env: &mut Env<'a>,
def2: &Def2,
def2_node_id: DefId,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> ASTResult<MarkNodeId> {
let ast_node_id = ASTNodeId::ADefId(def2_node_id);
let mark_node_id = match def2 {
Def2::ValueDef {
identifier_id,
expr_id,
} => {
let expr_mn_id = expr2_to_markup(
env,
env.pool.get(*expr_id),
*expr_id,
mark_node_pool,
interns,
)?;
let tld_mn = tld_mark_node(
*identifier_id,
expr_mn_id,
ast_node_id,
mark_node_pool,
env,
interns,
)?;
mark_node_pool.add(tld_mn)
}
Def2::Blank => mark_node_pool.add(new_blank_mn_w_nls(ast_node_id, None, 2)),
if indent_level > 0 {
let indent_node = MarkupNode::Indent {
ast_node_id: node_id,
indent_level,
parent_id_opt: None,
};
Ok(mark_node_id)
}
let indent_node_id = mark_node_pool.add(indent_node);
// make Markup Nodes: generate String representation, assign Highlighting Style
pub fn expr2_to_markup<'a>(
env: &mut Env<'a>,
expr2: &Expr2,
expr2_node_id: ExprId,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> ASTResult<MarkNodeId> {
let ast_node_id = ASTNodeId::AExprId(expr2_node_id);
let mark_node_id = match expr2 {
Expr2::SmallInt { text, .. }
| Expr2::I128 { text, .. }
| Expr2::U128 { text, .. }
| Expr2::Float { text, .. } => {
let num_str = get_string(env, text);
new_markup_node(num_str, ast_node_id, HighlightStyle::Number, mark_node_pool)
}
Expr2::Str(text) => new_markup_node(
"\"".to_owned() + text.as_str(env.pool) + "\"",
ast_node_id,
HighlightStyle::String,
mark_node_pool,
),
Expr2::GlobalTag { name, .. } => new_markup_node(
get_string(env, name),
ast_node_id,
HighlightStyle::Type,
mark_node_pool,
),
Expr2::Call { expr: expr_id, .. } => {
let expr = env.pool.get(*expr_id);
expr2_to_markup(env, expr, *expr_id, mark_node_pool, interns)?
}
Expr2::Var(symbol) => {
//TODO make bump_format with arena
let text = format!("{:?}", symbol);
new_markup_node(text, ast_node_id, HighlightStyle::Variable, mark_node_pool)
}
Expr2::List { elems, .. } => {
let mut children_ids =
vec![mark_node_pool.add(new_left_square_mn(expr2_node_id, None))];
let indexed_node_ids: Vec<(usize, ExprId)> =
elems.iter(env.pool).copied().enumerate().collect();
for (idx, node_id) in indexed_node_ids.iter() {
let sub_expr2 = env.pool.get(*node_id);
children_ids.push(expr2_to_markup(
env,
sub_expr2,
*node_id,
mark_node_pool,
interns,
)?);
if idx + 1 < elems.len() {
children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
}
}
children_ids.push(mark_node_pool.add(new_right_square_mn(expr2_node_id, None)));
let list_node = MarkupNode::Nested {
ast_node_id,
children_ids,
let nested_node = MarkupNode::Nested {
ast_node_id: node_id,
children_ids: vec![indent_node_id, content_node_id],
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(list_node)
mark_node_pool.add(nested_node)
} else {
content_node_id
}
Expr2::EmptyRecord => {
let children_ids = vec![
mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None)),
mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)),
];
let record_node = MarkupNode::Nested {
ast_node_id,
children_ids,
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(record_node)
}
Expr2::Record { fields, .. } => {
let mut children_ids =
vec![mark_node_pool.add(new_left_accolade_mn(expr2_node_id, None))];
for (idx, field_node_id) in fields.iter_node_ids().enumerate() {
let record_field = env.pool.get(field_node_id);
let field_name = record_field.get_record_field_pool_str();
children_ids.push(new_markup_node(
field_name.as_str(env.pool).to_owned(),
ast_node_id,
HighlightStyle::RecordField,
mark_node_pool,
));
match record_field {
RecordField::InvalidLabelOnly(_, _) => (),
RecordField::LabelOnly(_, _, _) => (),
RecordField::LabeledValue(_, _, sub_expr2_node_id) => {
children_ids.push(mark_node_pool.add(new_colon_mn(expr2_node_id, None)));
let sub_expr2 = env.pool.get(*sub_expr2_node_id);
children_ids.push(expr2_to_markup(
env,
sub_expr2,
*sub_expr2_node_id,
mark_node_pool,
interns,
)?);
}
}
if idx + 1 < fields.len() {
children_ids.push(mark_node_pool.add(new_comma_mn(expr2_node_id, None)));
}
}
children_ids.push(mark_node_pool.add(new_right_accolade_mn(expr2_node_id, None)));
let record_node = MarkupNode::Nested {
ast_node_id,
children_ids,
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(record_node)
}
Expr2::Blank => mark_node_pool.add(new_blank_mn(ast_node_id, None)),
Expr2::LetValue {
def_id,
body_id: _,
body_var: _,
} => {
let pattern_id = env.pool.get(*def_id).get_pattern_id();
let pattern2 = env.pool.get(pattern_id);
let val_name = get_identifier_string(pattern2, interns)?;
let val_name_mn = MarkupNode::Text {
content: val_name,
ast_node_id,
syn_high_style: HighlightStyle::Variable,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
let val_name_mn_id = mark_node_pool.add(val_name_mn);
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
let value_def = env.pool.get(*def_id);
match value_def {
ValueDef::NoAnnotation {
pattern_id: _,
expr_id,
expr_var: _,
} => {
let body_mn_id = expr2_to_markup(
env,
env.pool.get(*expr_id),
*expr_id,
mark_node_pool,
interns,
)?;
let body_mn = mark_node_pool.get_mut(body_mn_id);
body_mn.add_newline_at_end();
let full_let_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, body_mn_id],
parent_id_opt: None,
newlines_at_end: 1,
};
mark_node_pool.add(full_let_node)
}
other => {
unimplemented!(
"I don't know how to convert {:?} into a MarkupNode yet.",
other
)
}
}
}
Expr2::RuntimeError() => new_markup_node(
"RunTimeError".to_string(),
ast_node_id,
HighlightStyle::Blank,
mark_node_pool,
),
rest => todo!("implement expr2_to_markup for {:?}", rest),
};
Ok(mark_node_id)
}
pub fn set_parent_for_all(markup_node_id: MarkNodeId, mark_node_pool: &mut SlowPool) {
@ -596,221 +357,10 @@ pub fn set_parent_for_all_helper(
}
MarkupNode::Text { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
MarkupNode::Blank { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
MarkupNode::Indent { parent_id_opt, .. } => *parent_id_opt = Some(parent_node_id),
}
}
fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) -> MarkNodeId {
let mark_node = MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::PackageRelated,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(mark_node)
}
fn header_val_mn(
content: String,
expr_id: ExprId,
highlight_style: HighlightStyle,
mark_node_pool: &mut SlowPool,
) -> MarkNodeId {
let mark_node = MarkupNode::Text {
content,
ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: highlight_style,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(mark_node)
}
pub fn header_to_markup(app_header: &AppHeader, mark_node_pool: &mut SlowPool) -> MarkNodeId {
let expr_id = app_header.ast_node_id;
let ast_node_id = ASTNodeId::AExprId(expr_id);
let app_node_id = header_mn("app ".to_owned(), expr_id, mark_node_pool);
let app_name_node_id = header_val_mn(
app_header.app_name.clone(),
expr_id,
HighlightStyle::String,
mark_node_pool,
);
let full_app_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![app_node_id, app_name_node_id],
parent_id_opt: None,
newlines_at_end: 1,
};
let packages_node_id = header_mn(" packages ".to_owned(), expr_id, mark_node_pool);
let pack_left_acc_node_id = mark_node_pool.add(new_left_accolade_mn(expr_id, None));
let pack_base_node_id = header_val_mn(
"base: ".to_owned(),
expr_id,
HighlightStyle::RecordField,
mark_node_pool,
);
let pack_val_node_id = header_val_mn(
app_header.packages_base.clone(),
expr_id,
HighlightStyle::String,
mark_node_pool,
);
let pack_right_acc_node_id = mark_node_pool.add(new_right_accolade_mn(expr_id, None));
let full_packages_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![
packages_node_id,
pack_left_acc_node_id,
pack_base_node_id,
pack_val_node_id,
pack_right_acc_node_id,
],
parent_id_opt: None,
newlines_at_end: 1,
};
let imports_node_id = header_mn(" imports ".to_owned(), expr_id, mark_node_pool);
let imports_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
let mut import_child_ids: Vec<MarkNodeId> = add_header_mn_list(
&app_header.imports,
expr_id,
HighlightStyle::Import,
mark_node_pool,
);
let imports_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
let mut full_import_children = vec![imports_node_id, imports_left_square_node_id];
full_import_children.append(&mut import_child_ids);
full_import_children.push(imports_right_square_node_id);
let full_import_node = MarkupNode::Nested {
ast_node_id,
children_ids: full_import_children,
parent_id_opt: None,
newlines_at_end: 1,
};
let provides_node_id = header_mn(" provides ".to_owned(), expr_id, mark_node_pool);
let provides_left_square_node_id = mark_node_pool.add(new_left_square_mn(expr_id, None));
let mut provides_val_node_ids: Vec<MarkNodeId> = add_header_mn_list(
&app_header.provides,
expr_id,
HighlightStyle::Provides,
mark_node_pool,
);
let provides_right_square_node_id = mark_node_pool.add(new_right_square_mn(expr_id, None));
let provides_end_node_id = header_mn(" to base".to_owned(), expr_id, mark_node_pool);
let mut full_provides_children = vec![provides_node_id, provides_left_square_node_id];
full_provides_children.append(&mut provides_val_node_ids);
full_provides_children.push(provides_right_square_node_id);
full_provides_children.push(provides_end_node_id);
let full_provides_node = MarkupNode::Nested {
ast_node_id,
children_ids: full_provides_children,
parent_id_opt: None,
newlines_at_end: 1,
};
let full_app_node_id = mark_node_pool.add(full_app_node);
let full_packages_node = mark_node_pool.add(full_packages_node);
let full_import_node_id = mark_node_pool.add(full_import_node);
let full_provides_node_id = mark_node_pool.add(full_provides_node);
let header_mark_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![
full_app_node_id,
full_packages_node,
full_import_node_id,
full_provides_node_id,
],
parent_id_opt: None,
newlines_at_end: 1,
};
let header_mn_id = mark_node_pool.add(header_mark_node);
set_parent_for_all(header_mn_id, mark_node_pool);
header_mn_id
}
// Used for provides and imports
fn add_header_mn_list(
str_vec: &[String],
expr_id: ExprId,
highlight_style: HighlightStyle,
mark_node_pool: &mut SlowPool,
) -> Vec<MarkNodeId> {
let nr_of_elts = str_vec.len();
str_vec
.iter()
.enumerate()
.map(|(indx, provide_str)| {
let provide_str = header_val_mn(
provide_str.to_owned(),
expr_id,
highlight_style,
mark_node_pool,
);
if indx != nr_of_elts - 1 {
vec![provide_str, mark_node_pool.add(new_comma_mn(expr_id, None))]
} else {
vec![provide_str]
}
})
.flatten()
.collect()
}
pub fn ast_to_mark_nodes<'a>(
env: &mut Env<'a>,
ast: &AST,
mark_node_pool: &mut SlowPool,
interns: &Interns,
) -> ASTResult<Vec<MarkNodeId>> {
let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)];
for &def_id in ast.def_ids.iter() {
let def2 = env.pool.get(def_id);
let def2_markup_id = def2_to_markup(env, def2, def_id, mark_node_pool, interns)?;
set_parent_for_all(def2_markup_id, mark_node_pool);
all_mark_node_ids.push(def2_markup_id);
}
Ok(all_mark_node_ids)
}
impl fmt::Display for MarkupNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
@ -871,3 +421,50 @@ pub fn get_root_mark_node_id(mark_node_id: MarkNodeId, mark_node_pool: &SlowPool
curr_mark_node_id
}
// put space mark nodes between each node in mark_nodes
pub fn join_mark_nodes_spaces(
mark_nodes_ids: Vec<MarkNodeId>,
with_prepend: bool,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
) -> Vec<MarkNodeId> {
let space_range_max = if with_prepend {
mark_nodes_ids.len()
} else {
mark_nodes_ids.len() - 1
};
let join_nodes: Vec<MarkNodeId> = (0..space_range_max)
.map(|_| {
let space_node = MarkupNode::Text {
content: " ".to_string(),
ast_node_id,
syn_high_style: HighlightStyle::Blank,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
mark_node_pool.add(space_node)
})
.collect();
if with_prepend {
join_nodes.into_iter().interleave(mark_nodes_ids).collect()
} else {
mark_nodes_ids.into_iter().interleave(join_nodes).collect()
}
}
// put comma mark nodes between each node in mark_nodes
pub fn join_mark_nodes_commas(
mark_nodes: Vec<MarkupNode>,
ast_node_id: ASTNodeId,
) -> Vec<MarkupNode> {
let join_nodes: Vec<MarkupNode> = (0..(mark_nodes.len() - 1))
.map(|_| new_comma_mn_ast(ast_node_id, None))
.collect();
mark_nodes.into_iter().interleave(join_nodes).collect()
}

View file

@ -1,14 +1,8 @@
use roc_ast::{
ast_error::ASTResult,
lang::{
core::{
ast::ASTNodeId,
pattern::{get_identifier_string, PatternId},
},
env::Env,
},
lang::{core::ast::ASTNodeId, env::Env},
};
use roc_module::symbol::Interns;
use roc_module::symbol::IdentId;
use crate::{
markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode},
@ -17,20 +11,18 @@ use crate::{
};
pub fn tld_mark_node<'a>(
identifier_id: PatternId,
identifier_id: IdentId,
expr_mark_node_id: MarkNodeId,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
env: &Env<'a>,
interns: &Interns,
) -> ASTResult<MarkupNode> {
let pattern2 = env.pool.get(identifier_id);
let val_name = get_identifier_string(pattern2, interns)?;
let val_name = env.ident_ids.get_name_str_res(identifier_id)?;
let val_name_mn = MarkupNode::Text {
content: val_name,
content: val_name.to_owned(),
ast_node_id,
syn_high_style: HighlightStyle::Variable,
syn_high_style: HighlightStyle::Value,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,

View file

@ -38,7 +38,7 @@ pub enum MarkError {
node_type: String,
backtrace: Backtrace,
},
#[snafu(display("UIError: {}", msg))]
#[snafu(display("UtilError: {}", msg))]
UtilErrorBacktrace { msg: String, backtrace: Backtrace },
}

View file

@ -1,7 +1,7 @@
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use crate::colors::{self, from_hsb, RgbaTup};
use crate::colors::{from_hsb, RgbaTup};
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
pub enum HighlightStyle {
@ -9,11 +9,12 @@ pub enum HighlightStyle {
Comma,
String,
FunctionName,
FunctionArgName,
Type,
Bracket,
Number,
PackageRelated, // app, packages, imports, exposes, provides...
Variable,
Value,
RecordField,
Import,
Provides,
@ -23,20 +24,23 @@ pub enum HighlightStyle {
pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
use HighlightStyle::*;
let almost_white = from_hsb(258, 5, 95);
let mut highlight_map = HashMap::new();
[
(Operator, colors::WHITE),
(Operator, from_hsb(185, 50, 75)),
(Comma, from_hsb(258, 50, 90)),
(String, from_hsb(346, 65, 97)),
(FunctionName, colors::WHITE),
(Type, colors::WHITE),
(FunctionName, almost_white),
(FunctionArgName, from_hsb(225, 50, 100)),
(Type, almost_white),
(Bracket, from_hsb(347, 80, 100)),
(Number, from_hsb(185, 50, 75)),
(PackageRelated, colors::WHITE),
(Variable, colors::WHITE),
(Number, from_hsb(225, 50, 100)),
(PackageRelated, almost_white),
(Value, almost_white),
(RecordField, from_hsb(258, 50, 90)),
(Import, from_hsb(185, 50, 75)),
(Provides, from_hsb(185, 50, 75)),
(Import, from_hsb(225, 50, 100)),
(Provides, from_hsb(225, 50, 100)),
(Blank, from_hsb(258, 50, 90)),
// comment from_hsb(285, 6, 47) or 186, 35, 40
]

View file

@ -27,7 +27,6 @@ roc_std = { path = "../../roc_std" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
@ -36,7 +35,6 @@ target-lexicon = "0.12.2"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -1,4 +1,4 @@
use crate::target::arch_str;
use crate::target::{arch_str, target_triple_str};
#[cfg(feature = "llvm")]
use libloading::{Error, Library};
use roc_builtins::bitcode;
@ -300,6 +300,40 @@ pub fn build_c_host_native(
command.output().unwrap()
}
pub fn build_swift_host_native(
env_path: &str,
env_home: &str,
dest: &str,
sources: &[&str],
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
objc_header_path: Option<&str>,
) -> Output {
if shared_lib_path.is_some() {
unimplemented!("Linking a shared library to Swift not yet implemented");
}
let mut command = Command::new("swiftc");
command
.env_clear()
.env("PATH", &env_path)
.env("HOME", &env_home)
.args(sources)
.arg("-emit-object")
.arg("-parse-as-library")
.args(&["-o", dest]);
if let Some(objc_header) = objc_header_path {
command.args(&["-import-objc-header", objc_header]);
}
if matches!(opt_level, OptLevel::Optimize) {
command.arg("-O");
}
command.output().unwrap()
}
pub fn rebuild_host(
opt_level: OptLevel,
target: &Triple,
@ -312,6 +346,9 @@ pub fn rebuild_host(
let rust_host_src = host_input_path.with_file_name("host.rs");
let rust_host_dest = host_input_path.with_file_name("rust_host.o");
let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
let swift_host_src = host_input_path.with_file_name("host.swift");
let swift_host_header_src = host_input_path.with_file_name("host.h");
let host_dest_native = host_input_path.with_file_name(if shared_lib_path.is_some() {
"dynhost"
} else {
@ -372,6 +409,20 @@ pub fn rebuild_host(
shared_lib_path,
)
}
Architecture::Aarch64(_) => {
let emit_bin = format!("-femit-bin={}", host_dest_native.to_str().unwrap());
build_zig_host_native(
&env_path,
&env_home,
&emit_bin,
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
target_triple_str(target),
opt_level,
shared_lib_path,
)
}
_ => panic!("Unsupported architecture {:?}", target.architecture),
};
@ -526,18 +577,25 @@ pub fn rebuild_host(
shared_lib_path,
);
validate_output("host.c", "clang", output);
} else if swift_host_src.exists() {
// Compile host.swift, if it exists
let output = build_swift_host_native(
&env_path,
&env_home,
host_dest_native.to_str().unwrap(),
&[swift_host_src.to_str().unwrap()],
opt_level,
shared_lib_path,
swift_host_header_src
.exists()
.then(|| swift_host_header_src.to_str().unwrap()),
);
validate_output("host.swift", "swiftc", output);
}
}
fn nixos_path() -> String {
env::var("NIXOS_GLIBC_PATH").unwrap_or_else(|_| {
panic!(
"We couldn't find glibc! We tried looking for NIXOS_GLIBC_PATH
to find it via Nix, but that didn't work either. Please file a bug report.
This will only be an issue until we implement surgical linking.",
)
})
fn nix_path_opt() -> Option<String> {
env::var_os("NIX_GLIBC_PATH").map(|path| path.into_string().unwrap())
}
fn library_path<const N: usize>(segments: [&str; N]) -> Option<PathBuf> {
@ -586,21 +644,39 @@ fn link_linux(
));
}
let libcrt_path = library_path(["/usr", "lib", &architecture])
let libcrt_path =
// give preference to nix_path if it's defined, this prevents bugs
if let Some(nix_path) = nix_path_opt() {
library_path([&nix_path])
.unwrap()
} else {
library_path(["/usr", "lib", &architecture])
.or_else(|| library_path(["/usr", "lib"]))
.or_else(|| library_path([&nixos_path()]))
.unwrap();
.unwrap()
};
let libgcc_name = "libgcc_s.so.1";
let libgcc_path = library_path(["/lib", &architecture, libgcc_name])
let libgcc_path =
// give preference to nix_path if it's defined, this prevents bugs
if let Some(nix_path) = nix_path_opt() {
library_path([&nix_path, libgcc_name])
.unwrap()
} else {
library_path(["/lib", &architecture, libgcc_name])
.or_else(|| library_path(["/usr", "lib", &architecture, libgcc_name]))
.or_else(|| library_path(["/usr", "lib", libgcc_name]))
.or_else(|| library_path([&nixos_path(), libgcc_name]))
.unwrap();
.unwrap()
};
let ld_linux = match target.architecture {
Architecture::X86_64 => library_path(["/lib64", "ld-linux-x86-64.so.2"])
.or_else(|| library_path([&nixos_path(), "ld-linux-x86-64.so.2"])),
Architecture::X86_64 => {
// give preference to nix_path if it's defined, this prevents bugs
if let Some(nix_path) = nix_path_opt() {
library_path([&nix_path, "ld-linux-x86-64.so.2"])
} else {
library_path(["/lib64", "ld-linux-x86-64.so.2"])
}
}
Architecture::Aarch64(_) => library_path(["/lib", "ld-linux-aarch64.so.1"]),
_ => panic!(
"TODO gracefully handle unsupported linux architecture: {:?}",
@ -665,7 +741,7 @@ fn link_linux(
.args(&[
"--gc-sections",
"--eh-frame-hdr",
"-arch",
"-A",
arch_str(target),
"-pie",
libcrt_path.join("crti.o").to_str().unwrap(),
@ -674,7 +750,7 @@ fn link_linux(
.args(&base_args)
.args(&["-dynamic-linker", ld_linux])
.args(input_paths)
// ld.lld requires this argument, and does not accept -arch
// 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
@ -713,22 +789,14 @@ fn link_macos(
}
};
// This path only exists on macOS Big Sur, and it causes ld errors
// on Catalina if it's specified with -L, so we replace it with a
// redundant -lSystem if the directory isn't there.
let big_sur_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
let big_sur_fix = if Path::new(big_sur_path).exists() {
format!("-L{}", big_sur_path)
} else {
String::from("-lSystem")
};
let arch = match target.architecture {
Architecture::Aarch64(_) => "arm64".to_string(),
_ => target.architecture.to_string(),
};
let mut ld_child = Command::new("ld")
let mut ld_command = Command::new("ld");
ld_command
// NOTE: order of arguments to `ld` matters here!
// The `-l` flags should go after the `.o` arguments
// Don't allow LD_ env vars to affect this
@ -743,11 +811,17 @@ fn link_macos(
"-arch",
&arch,
])
.args(input_paths)
.args(&[
.args(input_paths);
let sdk_path = "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/lib";
if Path::new(sdk_path).exists() {
ld_command.arg(format!("-L{}", sdk_path));
ld_command.arg(format!("-L{}/swift", sdk_path));
};
ld_command.args(&[
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
// for discussion and further references
&big_sur_fix,
"-lSystem",
"-lresolv",
"-lpthread",
@ -759,17 +833,18 @@ fn link_macos(
// Output
"-o",
output_path.to_str().unwrap(), // app
])
.spawn()?;
]);
let mut ld_child = ld_command.spawn()?;
match target.architecture {
Architecture::Aarch64(_) => {
ld_child.wait()?;
let child = Command::new("codesign")
let codesign_child = Command::new("codesign")
.args(&["-s", "-", output_path.to_str().unwrap()])
.spawn()?;
Ok((child, output_path))
Ok((codesign_child, output_path))
}
_ => Ok((ld_child, output_path)),
}

View file

@ -12,7 +12,6 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -86,8 +86,8 @@ pub fn build(b: *Builder) void {
fn removeInstallSteps(b: *Builder) void {
for (b.top_level_steps.items) |top_level_step, i| {
if (mem.eql(u8, top_level_step.step.name, "install") or mem.eql(u8, top_level_step.step.name, "uninstall")) {
const name = top_level_step.step.name;
if (mem.eql(u8, name, "install") or mem.eql(u8, name, "uninstall")) {
_ = b.top_level_steps.swapRemove(i);
}
}

View file

@ -140,6 +140,7 @@ const Caller0 = fn (?[*]u8, ?[*]u8) callconv(.C) void;
const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller4 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
@ -352,6 +353,70 @@ pub fn listMap3(
}
}
pub fn listMap4(
list1: RocList,
list2: RocList,
list3: RocList,
list4: RocList,
caller: Caller4,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
a_width: usize,
b_width: usize,
c_width: usize,
d_width: usize,
e_width: usize,
dec_a: Dec,
dec_b: Dec,
dec_c: Dec,
dec_d: Dec,
) callconv(.C) RocList {
const output_length = std.math.min(std.math.min(list1.len(), list2.len()), std.math.min(list3.len(), list4.len()));
decrementTail(list1, output_length, a_width, dec_a);
decrementTail(list2, output_length, b_width, dec_b);
decrementTail(list3, output_length, c_width, dec_c);
decrementTail(list4, output_length, d_width, dec_d);
if (data_is_owned) {
inc_n_data(data, output_length);
}
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| {
if (list4.bytes) |source_d| {
const output = RocList.allocate(alignment, output_length, e_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const element_c = source_c + i * c_width;
const element_d = source_d + i * d_width;
const target = target_ptr + i * e_width;
caller(data, element_a, element_b, element_c, element_d, target);
}
return output;
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
}
pub fn listKeepIf(
list: RocList,
caller: Caller1,

View file

@ -26,6 +26,7 @@ comptime {
exportListFn(list.listMap, "map");
exportListFn(list.listMap2, "map2");
exportListFn(list.listMap3, "map3");
exportListFn(list.listMap4, "map4");
exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk");
@ -121,6 +122,7 @@ comptime {
exportStrFn(str.fromUtf8C, "from_utf8");
exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim");
}
// Utils
@ -159,7 +161,8 @@ fn exportUtilsFn(comptime func: anytype, comptime func_name: []const u8) void {
// Custom panic function, as builtin Zig version errors during LLVM verification
pub fn panic(message: []const u8, stacktrace: ?*std.builtin.StackTrace) noreturn {
if (std.builtin.is_test) {
const builtin = @import("builtin");
if (builtin.is_test) {
std.debug.print("{s}: {?}", .{ message, stacktrace });
} else {
_ = message;

View file

@ -93,7 +93,7 @@ pub const RocStr = extern struct {
if (length < roc_str_size) {
return RocStr.empty();
} else {
var new_bytes: []T = utils.alloc(length, RocStr.alignment) catch unreachable;
var new_bytes = utils.alloc(length, RocStr.alignment) catch unreachable;
var new_bytes_ptr: [*]u8 = @ptrCast([*]u8, &new_bytes);
@ -163,7 +163,7 @@ pub const RocStr = extern struct {
) RocStr {
const element_width = 1;
if (self.bytes) |source_ptr| {
if (self.str_bytes) |source_ptr| {
if (self.isUnique()) {
const new_source = utils.unsafeReallocate(source_ptr, RocStr.alignment, self.len(), new_length, element_width);
@ -171,7 +171,7 @@ pub const RocStr = extern struct {
}
}
return self.reallocateFresh(RocStr.alignment, new_length, element_width);
return self.reallocateFresh(new_length);
}
/// reallocate by explicitly making a new allocation and copying elements over
@ -294,7 +294,7 @@ pub const RocStr = extern struct {
}
pub fn isUnique(self: RocStr) bool {
// the empty list is unique (in the sense that copying it will not leak memory)
// the empty string is unique (in the sense that copying it will not leak memory)
if (self.isEmpty()) {
return true;
}
@ -305,6 +305,10 @@ pub const RocStr = extern struct {
}
// otherwise, check if the refcount is one
return @call(.{ .modifier = always_inline }, RocStr.isRefcountOne, .{self});
}
fn isRefcountOne(self: RocStr) bool {
const ptr: [*]usize = @ptrCast([*]usize, @alignCast(8, self.str_bytes));
return (ptr - 1)[0] == utils.REFCOUNT_ONE;
}
@ -1473,3 +1477,253 @@ test "validateUtf8Bytes: surrogate halves" {
try expectErr(list, 3, error.Utf8EncodesSurrogateHalf, Utf8ByteProblem.EncodesSurrogateHalf);
}
fn isWhitespace(codepoint: u21) bool {
// https://www.unicode.org/Public/UCD/latest/ucd/PropList.txt
return switch (codepoint) {
0x0009...0x000D => true, // control characters
0x0020 => true, // space
0x0085 => true, // control character
0x00A0 => true, // no-break space
0x1680 => true, // ogham space
0x2000...0x200A => true, // en quad..hair space
0x200E...0x200F => true, // left-to-right & right-to-left marks
0x2028 => true, // line separator
0x2029 => true, // paragraph separator
0x202F => true, // narrow no-break space
0x205F => true, // medium mathematical space
0x3000 => true, // ideographic space
else => false,
};
}
test "isWhitespace" {
try expect(isWhitespace(' '));
try expect(isWhitespace('\u{00A0}'));
try expect(!isWhitespace('x'));
}
pub fn strTrim(string: RocStr) callconv(.C) RocStr {
if (string.str_bytes) |bytes_ptr| {
const leading_bytes = countLeadingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == leading_bytes) {
string.deinit();
return RocStr.empty();
}
const trailing_bytes = countTrailingWhitespaceBytes(string);
const new_len = original_len - leading_bytes - trailing_bytes;
const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne();
if (small_or_shared) {
return RocStr.init(string.asU8ptr() + leading_bytes, new_len);
}
// nonempty, large, and unique:
if (leading_bytes > 0) {
var i: usize = 0;
while (i < new_len) : (i += 1) {
const dest = bytes_ptr + i;
const source = dest + leading_bytes;
@memcpy(dest, source, 1);
}
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
}
return RocStr.empty();
}
fn countLeadingWhitespaceBytes(string: RocStr) usize {
var byte_count: usize = 0;
var bytes = string.asU8ptr()[0..string.len()];
var iter = unicode.Utf8View.initUnchecked(bytes).iterator();
while (iter.nextCodepoint()) |codepoint| {
if (isWhitespace(codepoint)) {
byte_count += unicode.utf8CodepointSequenceLength(codepoint) catch break;
} else {
break;
}
}
return byte_count;
}
fn countTrailingWhitespaceBytes(string: RocStr) usize {
var byte_count: usize = 0;
var bytes = string.asU8ptr()[0..string.len()];
var iter = ReverseUtf8View.initUnchecked(bytes).iterator();
while (iter.nextCodepoint()) |codepoint| {
if (isWhitespace(codepoint)) {
byte_count += unicode.utf8CodepointSequenceLength(codepoint) catch break;
} else {
break;
}
}
return byte_count;
}
/// A backwards version of Utf8View from std.unicode
const ReverseUtf8View = struct {
bytes: []const u8,
pub fn initUnchecked(s: []const u8) ReverseUtf8View {
return ReverseUtf8View{ .bytes = s };
}
pub fn iterator(s: ReverseUtf8View) ReverseUtf8Iterator {
return ReverseUtf8Iterator{
.bytes = s.bytes,
.i = if (s.bytes.len > 0) s.bytes.len - 1 else null,
};
}
};
/// A backwards version of Utf8Iterator from std.unicode
const ReverseUtf8Iterator = struct {
bytes: []const u8,
// NOTE null signifies complete/empty
i: ?usize,
pub fn nextCodepointSlice(it: *ReverseUtf8Iterator) ?[]const u8 {
if (it.i) |index| {
var i = index;
// NOTE this relies on the string being valid utf8 to not run off the end
while (!utf8BeginByte(it.bytes[i])) {
i -= 1;
}
const cp_len = unicode.utf8ByteSequenceLength(it.bytes[i]) catch unreachable;
const slice = it.bytes[i .. i + cp_len];
it.i = if (i == 0) null else i - 1;
return slice;
} else {
return null;
}
}
pub fn nextCodepoint(it: *ReverseUtf8Iterator) ?u21 {
const slice = it.nextCodepointSlice() orelse return null;
return switch (slice.len) {
1 => @as(u21, slice[0]),
2 => unicode.utf8Decode2(slice) catch unreachable,
3 => unicode.utf8Decode3(slice) catch unreachable,
4 => unicode.utf8Decode4(slice) catch unreachable,
else => unreachable,
};
}
};
fn utf8BeginByte(byte: u8) bool {
return switch (byte) {
0b1000_0000...0b1011_1111 => false,
else => true,
};
}
test "strTrim: empty" {
const trimmedEmpty = strTrim(RocStr.empty());
try expect(trimmedEmpty.eq(RocStr.empty()));
}
test "strTrim: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
const trimmed = strTrim(original);
try expect(trimmed.eq(RocStr.empty()));
}
test "strTrim: large to large" {
const original_bytes = " hello giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = "hello giant world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(!expected.isSmallStr());
const trimmed = strTrim(original);
try expect(trimmed.eq(expected));
}
test "strTrim: large to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = "hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrim(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "strTrim: small to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(original.isSmallStr());
const expected_bytes = "hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrim(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "ReverseUtf8View: hello world" {
const original_bytes = "hello world";
const expected_bytes = "dlrow olleh";
var i: usize = 0;
var iter = ReverseUtf8View.initUnchecked(original_bytes).iterator();
while (iter.nextCodepoint()) |codepoint| {
try expect(expected_bytes[i] == codepoint);
i += 1;
}
}
test "ReverseUtf8View: empty" {
const original_bytes = "";
var iter = ReverseUtf8View.initUnchecked(original_bytes).iterator();
while (iter.nextCodepoint()) |codepoint| {
try expect(false);
}
}

View file

@ -19,8 +19,9 @@ extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
extern fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void;
comptime {
const builtin = @import("builtin");
// During tetsts, use the testing allocators to satisfy these functions.
if (std.builtin.is_test) {
if (builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
@ -257,7 +258,7 @@ pub const Ordering = enum(u8) {
LT = 2,
};
pub const UpdateMode = extern enum(u8) {
pub const UpdateMode = enum(u8) {
Immutable = 0,
InPlace = 1,
};

View file

@ -25,6 +25,7 @@ interface List
map,
map2,
map3,
map4,
mapWithIndex,
mapOrDrop,
mapJoin,
@ -34,6 +35,7 @@ interface List
sortWith,
drop,
dropAt,
dropLast,
swap
]
imports []
@ -268,6 +270,11 @@ map2 : List a, List b, (a, b -> c) -> List c
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after
@ -439,6 +446,9 @@ drop : List elem, Nat -> List elem
## To replace the element at a given index, instead of dropping it, see [List.set].
dropAt : List elem, Nat -> List elem
## Drops the last element in a List.
dropLast : List elem -> List elem
## Adds a new element to the end of the list.
##
## >>> List.append [ "a", "b" ] "c"

View file

@ -142,6 +142,7 @@ pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
@ -164,6 +165,7 @@ pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
pub const LIST_MAP3: &str = "roc_builtins.list.map3";
pub const LIST_MAP4: &str = "roc_builtins.list.map4";
pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";

View file

@ -64,7 +64,7 @@ const TVAR1: VarId = VarId::from_u32(1);
const TVAR2: VarId = VarId::from_u32(2);
const TVAR3: VarId = VarId::from_u32(3);
const TVAR4: VarId = VarId::from_u32(4);
const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(5);
const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(10);
pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
let mut types = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher());
@ -632,6 +632,9 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(str_type())
);
// trim : Str -> Str
add_top_level_function_type!(Symbol::STR_TRIM, vec![str_type()], Box::new(str_type()));
// fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
{
let bad_utf8 = SolvedType::TagUnion(
@ -722,7 +725,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::LIST_LAST,
vec![list_type(flex(TVAR1))],
Box::new(result_type(flex(TVAR1), list_was_empty)),
Box::new(result_type(flex(TVAR1), list_was_empty.clone())),
);
// set : List elem, Nat, elem -> List elem
@ -746,6 +749,20 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(bool_type()),
);
// min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
add_top_level_function_type!(
Symbol::LIST_MIN,
vec![list_type(num_type(flex(TVAR1)))],
Box::new(result_type(num_type(flex(TVAR1)), list_was_empty.clone())),
);
// max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
add_top_level_function_type!(
Symbol::LIST_MAX,
vec![list_type(num_type(flex(TVAR1)))],
Box::new(result_type(num_type(flex(TVAR1)), list_was_empty)),
);
// sum : List (Num a) -> Num a
add_top_level_function_type!(
Symbol::LIST_SUM,
@ -913,6 +930,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
)
};
{
let_tvars! {a, b, c, d, e, cvar};
// map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
add_top_level_function_type!(
Symbol::LIST_MAP4,
vec![
list_type(flex(a)),
list_type(flex(b)),
list_type(flex(c)),
list_type(flex(d)),
closure(
vec![flex(a), flex(b), flex(c), flex(d)],
cvar,
Box::new(flex(e))
),
],
Box::new(list_type(flex(e))),
)
};
// append : List elem, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_APPEND,
@ -934,6 +972,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))),
);
// dropLast : List elem -> List elem
add_top_level_function_type!(
Symbol::LIST_DROP_LAST,
vec![list_type(flex(TVAR1))],
Box::new(list_type(flex(TVAR1))),
);
// swap : List elem, Nat, Nat -> List elem
add_top_level_function_type!(
Symbol::LIST_SWAP,

View file

@ -20,7 +20,6 @@ bumpalo = { version = "3.6.1", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -1,5 +1,5 @@
use crate::def::Def;
use crate::expr::Expr::*;
use crate::expr::{ClosureData, Expr::*};
use crate::expr::{Expr, Recursive, WhenBranch};
use crate::pattern::Pattern;
use roc_collections::all::SendMap;
@ -67,6 +67,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_TO_UTF8 => str_to_utf8,
STR_FROM_FLOAT=> str_from_float,
STR_REPEAT => str_repeat,
STR_TRIM => str_trim,
LIST_LEN => list_len,
LIST_GET => list_get,
LIST_SET => list_set,
@ -79,6 +80,8 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_REVERSE => list_reverse,
LIST_CONCAT => list_concat,
LIST_CONTAINS => list_contains,
LIST_MIN => list_min,
LIST_MAX => list_max,
LIST_SUM => list_sum,
LIST_PRODUCT => list_product,
LIST_PREPEND => list_prepend,
@ -86,8 +89,10 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP => list_map,
LIST_MAP2 => list_map2,
LIST_MAP3 => list_map3,
LIST_MAP4 => list_map4,
LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
LIST_DROP_LAST => list_drop_last,
LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if,
@ -286,6 +291,41 @@ fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
let arg2_var = var_store.fresh();
let arg3_var = var_store.fresh();
let arg4_var = var_store.fresh();
let arg5_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![
(arg1_var, Var(Symbol::ARG_1)),
(arg2_var, Var(Symbol::ARG_2)),
(arg3_var, Var(Symbol::ARG_3)),
(arg4_var, Var(Symbol::ARG_4)),
(arg5_var, Var(Symbol::ARG_5)),
],
ret_var,
};
defn(
symbol,
vec![
(arg1_var, Symbol::ARG_1),
(arg2_var, Symbol::ARG_2),
(arg3_var, Symbol::ARG_3),
(arg4_var, Symbol::ARG_4),
(arg5_var, Symbol::ARG_5),
],
var_store,
body,
ret_var,
)
}
/// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
@ -1236,6 +1276,11 @@ fn str_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// Str.trim : Str -> Str
fn str_trim(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrim, var_store)
}
/// Str.repeat : Str, Nat -> Str
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
@ -2004,6 +2049,51 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def {
)
}
/// List.dropLast: List elem -> List elem
fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let arg_var = var_store.fresh();
let len_var = Variable::NAT;
let num_var = len_var;
let num_precision_var = Variable::NATURAL;
let body = RunLowLevel {
op: LowLevel::ListDropAt,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(
index_var,
// Num.sub (List.len list) 1
RunLowLevel {
op: LowLevel::NumSubWrap,
args: vec![
(
arg_var,
// List.len list
RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: len_var,
},
),
(arg_var, int(num_var, num_precision_var, 1)),
],
ret_var: len_var,
},
),
],
ret_var: list_var,
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
list_var,
)
}
/// List.append : List elem, elem -> List elem
fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
@ -2085,6 +2175,266 @@ fn list_walk_until(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_3(symbol, LowLevel::ListWalkUntil, var_store)
}
// min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
fn list_min(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_var = var_store.fresh();
let bool_var = var_store.fresh();
let list_var = var_store.fresh();
let len_var = Variable::NAT;
let num_var = len_var;
let num_precision_var = Variable::NATURAL;
let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh();
let closure_var = var_store.fresh();
// Perform a bounds check. If it passes, delegate to List.getUnsafe.
let body = If {
cond_var: bool_var,
branch_var: var_store.fresh(),
branches: vec![(
// if-condition
no_region(
// List.len list != 0
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int(num_var, num_precision_var, 0)),
(
len_var,
RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: len_var,
},
),
],
ret_var: bool_var,
},
),
// list was not empty
no_region(
// Ok ( List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc )
tag(
"Ok",
vec![
// List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc
RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
// (List.getUnsafe list 0)
(
list_elem_var,
RunLowLevel {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(arg_var, int(num_var, num_precision_var, 0)),
],
ret_var: list_elem_var,
},
),
// \acc,elem -> if elem < acc then elem else acc
(closure_var, list_min_lt(list_elem_var, var_store)),
],
ret_var: list_elem_var,
},
],
var_store,
),
),
)],
final_else: Box::new(
// list was empty
no_region(
// Err ListWasEmpty
tag(
"Err",
vec![tag("ListWasEmpty", Vec::new(), var_store)],
var_store,
),
),
),
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
// \acc,elem -> if elem < acc then elem else acc
fn list_min_lt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr {
let bool_var = var_store.fresh();
// if elem < acc then elem else acc
let body = If {
cond_var: bool_var,
branch_var: list_elem_var,
branches: vec![(
// if-condition
no_region(
// elem < acc
RunLowLevel {
op: LowLevel::NumLt,
args: vec![
(list_elem_var, Var(Symbol::ARG_4)),
(list_elem_var, Var(Symbol::ARG_3)),
],
ret_var: bool_var,
},
),
// return elem
no_region(Var(Symbol::ARG_4)),
)],
// return acc
final_else: Box::new(no_region(Var(Symbol::ARG_3))),
};
defn_help(
Symbol::LIST_MIN_LT,
vec![
(list_elem_var, Symbol::ARG_3),
(list_elem_var, Symbol::ARG_4),
],
var_store,
body,
list_elem_var,
)
}
// max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_var = var_store.fresh();
let bool_var = var_store.fresh();
let list_var = var_store.fresh();
let len_var = Variable::NAT;
let num_var = len_var;
let num_precision_var = Variable::NATURAL;
let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh();
let closure_var = var_store.fresh();
// Perform a bounds check. If it passes, delegate to List.getUnsafe.
let body = If {
cond_var: bool_var,
branch_var: var_store.fresh(),
branches: vec![(
// if-condition
no_region(
// List.len list != 0
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int(num_var, num_precision_var, 0)),
(
len_var,
RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: len_var,
},
),
],
ret_var: bool_var,
},
),
// list was not empty
no_region(
// Ok ( List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc )
tag(
"Ok",
vec![
// List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc
RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
// (List.getUnsafe list 0)
(
list_elem_var,
RunLowLevel {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(arg_var, int(num_var, num_precision_var, 0)),
],
ret_var: list_elem_var,
},
),
// \acc,elem -> if elem < acc then elem else acc
(closure_var, list_max_gt(list_elem_var, var_store)),
],
ret_var: list_elem_var,
},
],
var_store,
),
),
)],
final_else: Box::new(
// list was empty
no_region(
// Err ListWasEmpty
tag(
"Err",
vec![tag("ListWasEmpty", Vec::new(), var_store)],
var_store,
),
),
),
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
// \acc,elem -> if elem > acc then elem else acc
fn list_max_gt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr {
let bool_var = var_store.fresh();
// if elem > acc then elem else acc
let body = If {
cond_var: bool_var,
branch_var: list_elem_var,
branches: vec![(
// if-condition
no_region(
// elem > acc
RunLowLevel {
op: LowLevel::NumGt,
args: vec![
(list_elem_var, Var(Symbol::ARG_4)),
(list_elem_var, Var(Symbol::ARG_3)),
],
ret_var: bool_var,
},
),
// return elem
no_region(Var(Symbol::ARG_4)),
)],
// return acc
final_else: Box::new(no_region(Var(Symbol::ARG_3))),
};
defn_help(
Symbol::LIST_MAX_GT,
vec![
(list_elem_var, Symbol::ARG_3),
(list_elem_var, Symbol::ARG_4),
],
var_store,
body,
list_elem_var,
)
}
/// List.sum : List (Num a) -> Num a
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
@ -2227,11 +2577,16 @@ fn list_map2(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_3(symbol, LowLevel::ListMap2, var_store)
}
/// List.map3 : List a, List b, (a, b -> c) -> List c
/// List.map3 : List a, List b, List c, (a, b, c -> d) -> List d
fn list_map3(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_4(symbol, LowLevel::ListMap3, var_store)
}
/// List.map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
fn list_map4(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_5(symbol, LowLevel::ListMap4, var_store)
}
/// List.sortWith : List a, (a, a -> Ordering) -> List a
fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListSortWith, var_store)
@ -2575,7 +2930,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
CalledVia::Space,
);
let wrapper = Closure {
let wrapper = Closure(ClosureData {
function_type: wrapper_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -2589,7 +2944,7 @@ fn set_walk(symbol: Symbol, var_store: &mut VarStore) -> Def {
(Variable::EMPTY_RECORD, no_region(Pattern::Underscore)),
],
loc_body: Box::new(no_region(call_func)),
};
});
let body = RunLowLevel {
op: LowLevel::DictWalk,
@ -3604,7 +3959,7 @@ fn defn_help(
.map(|(var, symbol)| (var, no_region(Identifier(symbol))))
.collect();
Closure {
Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -3614,7 +3969,7 @@ fn defn_help(
recursive: Recursive::NotRecursive,
arguments: closure_args,
loc_body: Box::new(no_region(body)),
}
})
}
#[inline(always)]

View file

@ -1,6 +1,7 @@
use crate::annotation::canonicalize_annotation;
use crate::annotation::IntroducedVariables;
use crate::env::Env;
use crate::expr::ClosureData;
use crate::expr::Expr::{self, *};
use crate::expr::{
canonicalize_expr, local_successors, references_from_call, references_from_local, Output,
@ -670,10 +671,10 @@ fn group_to_declaration(
let mut new_def = can_def.clone();
// Determine recursivity of closures that are not tail-recursive
if let Closure {
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
..
} = &mut new_def.loc_expr.value
}) = &mut new_def.loc_expr.value
{
*recursive = closure_recursivity(*symbol, closures);
}
@ -698,10 +699,10 @@ fn group_to_declaration(
let mut new_def = can_def.clone();
// Determine recursivity of closures that are not tail-recursive
if let Closure {
if let Closure(ClosureData {
recursive: recursive @ Recursive::NotRecursive,
..
} = &mut new_def.loc_expr.value
}) = &mut new_def.loc_expr.value
{
*recursive = closure_recursivity(symbol, closures);
}
@ -838,7 +839,7 @@ fn canonicalize_pending_def<'a>(
};
Located {
value: Closure {
value: Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -848,7 +849,7 @@ fn canonicalize_pending_def<'a>(
recursive: Recursive::NotRecursive,
arguments: underscores,
loc_body: Box::new(body_expr),
},
}),
region: loc_ann.region,
}
};
@ -1001,7 +1002,7 @@ fn canonicalize_pending_def<'a>(
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure {
&Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1011,7 +1012,7 @@ fn canonicalize_pending_def<'a>(
loc_body: ref body,
ref captured_symbols,
..
},
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
@ -1049,7 +1050,7 @@ fn canonicalize_pending_def<'a>(
});
// renamed_closure_def = Some(&defined_symbol);
loc_can_expr.value = Closure {
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1059,7 +1060,7 @@ fn canonicalize_pending_def<'a>(
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
};
});
}
// Store the referenced locals in the refs_by_symbol map, so we can later figure out
@ -1144,7 +1145,7 @@ fn canonicalize_pending_def<'a>(
if let (
&ast::Pattern::Identifier(_name),
&Pattern::Identifier(ref defined_symbol),
&Closure {
&Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1154,7 +1155,7 @@ fn canonicalize_pending_def<'a>(
loc_body: ref body,
ref captured_symbols,
..
},
}),
) = (
&loc_pattern.value,
&loc_can_pattern.value,
@ -1191,7 +1192,7 @@ fn canonicalize_pending_def<'a>(
refs.lookups = refs.lookups.without(defined_symbol);
});
loc_can_expr.value = Closure {
loc_can_expr.value = Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1201,7 +1202,7 @@ fn canonicalize_pending_def<'a>(
recursive: is_recursive,
arguments: arguments.clone(),
loc_body: body.clone(),
};
});
}
// Store the referenced locals in the refs_by_symbol map, so we can later figure out

View file

@ -102,17 +102,7 @@ pub enum Expr {
ret_var: Variable,
},
Closure {
function_type: Variable,
closure_type: Variable,
closure_ext_var: Variable,
return_type: Variable,
name: Symbol,
captured_symbols: Vec<(Symbol, Variable)>,
recursive: Recursive,
arguments: Vec<(Variable, Located<Pattern>)>,
loc_body: Box<Located<Expr>>,
},
Closure(ClosureData),
// Product Types
Record {
@ -173,6 +163,18 @@ pub enum Expr {
// Compiles, but will crash if reached
RuntimeError(RuntimeError),
}
#[derive(Clone, Debug, PartialEq)]
pub struct ClosureData {
pub function_type: Variable,
pub closure_type: Variable,
pub closure_ext_var: Variable,
pub return_type: Variable,
pub name: Symbol,
pub captured_symbols: Vec<(Symbol, Variable)>,
pub recursive: Recursive,
pub arguments: Vec<(Variable, Located<Pattern>)>,
pub loc_body: Box<Located<Expr>>,
}
#[derive(Clone, Debug, PartialEq)]
pub struct Field {
@ -572,7 +574,7 @@ pub fn canonicalize_expr<'a>(
}
(
Closure {
Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -582,7 +584,7 @@ pub fn canonicalize_expr<'a>(
recursive: Recursive::NotRecursive,
arguments: can_args,
loc_body: Box::new(loc_body_expr),
},
}),
output,
)
}
@ -1403,7 +1405,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
LetNonRec(Box::new(def), Box::new(loc_expr), var)
}
Closure {
Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1413,14 +1415,14 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
captured_symbols,
arguments,
loc_body,
} => {
}) => {
let loc_expr = *loc_body;
let loc_expr = Located {
value: inline_calls(var_store, scope, loc_expr.value),
region: loc_expr.region,
};
Closure {
Closure(ClosureData {
function_type,
closure_type,
closure_ext_var,
@ -1430,7 +1432,7 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
captured_symbols,
arguments,
loc_body: Box::new(loc_expr),
}
})
}
Record { record_var, fields } => {
@ -1492,12 +1494,12 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
loc_expr:
Located {
value:
Closure {
Closure(ClosureData {
recursive,
arguments: params,
loc_body: boxed_body,
..
},
}),
..
},
..

View file

@ -1,6 +1,6 @@
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::env::Env;
use crate::expr::{Expr, Output};
use crate::expr::{ClosureData, Expr, Output};
use crate::operator::desugar_def;
use crate::pattern::Pattern;
use crate::scope::Scope;
@ -416,13 +416,13 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Closure {
Closure(ClosureData {
captured_symbols,
name,
arguments,
loc_body,
..
} => {
}) => {
captured_symbols.retain(|(s, _)| !no_capture_symbols.contains(s));
captured_symbols.retain(|(s, _)| s != name);

View file

@ -15,7 +15,7 @@ mod test_can {
use crate::helpers::{can_expr_with, test_home, CanExprOut};
use bumpalo::Bump;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::Recursive;
use roc_can::expr::{ClosureData, Recursive};
use roc_problem::can::{CycleEntry, FloatErrorKind, IntErrorKind, Problem, RuntimeError};
use roc_region::all::Region;
use std::{f64, i64};
@ -654,10 +654,10 @@ mod test_can {
match expr {
LetRec(assignments, body, _) => {
match &assignments.get(i).map(|def| &def.loc_expr.value) {
Some(Closure {
Some(Closure(ClosureData {
recursive: recursion,
..
}) => recursion.clone(),
})) => recursion.clone(),
Some(other) => {
panic!("assignment at {} is not a closure, but a {:?}", i, other)
}
@ -676,10 +676,10 @@ mod test_can {
get_closure(&body.value, i - 1)
} else {
match &def.loc_expr.value {
Closure {
Closure(ClosureData {
recursive: recursion,
..
} => recursion.clone(),
}) => recursion.clone(),
other => {
panic!("assignment at {} is not a closure, but a {:?}", i, other)
}

View file

@ -16,7 +16,6 @@ roc_builtins = { path = "../builtins" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -7,7 +7,7 @@ use roc_can::def::{Declaration, Def};
use roc_can::expected::Expected::{self, *};
use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *};
use roc_can::expr::{Field, WhenBranch};
use roc_can::expr::{ClosureData, Field, WhenBranch};
use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName};
@ -333,7 +333,7 @@ pub fn constrain_expr(
// make lookup constraint to lookup this symbol's type in the environment
Lookup(*symbol, expected, region)
}
Closure {
Closure(ClosureData {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
@ -343,7 +343,7 @@ pub fn constrain_expr(
captured_symbols,
name,
..
} => {
}) => {
// NOTE defs are treated somewhere else!
let loc_body_expr = &**boxed;
@ -1203,7 +1203,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
// instead of the more generic "something is wrong with the body of `f`"
match (&def.loc_expr.value, &signature) {
(
Closure {
Closure(ClosureData {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
@ -1213,7 +1213,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
loc_body,
name,
..
},
}),
Type::Function(arg_types, signature_closure_type, ret_type),
) => {
// NOTE if we ever have problems with the closure, the ignored `_closure_type`
@ -1561,7 +1561,7 @@ pub fn rec_defs_help(
// instead of the more generic "something is wrong with the body of `f`"
match (&def.loc_expr.value, &signature) {
(
Closure {
Closure(ClosureData {
function_type: fn_var,
closure_type: closure_var,
closure_ext_var,
@ -1571,7 +1571,7 @@ pub fn rec_defs_help(
loc_body,
name,
..
},
}),
Type::Function(arg_types, _closure_type, ret_type),
) => {
// NOTE if we ever have trouble with closure type unification, the ignored

View file

@ -16,7 +16,6 @@ bumpalo = { version = "3.6.1", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -9,12 +9,10 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
@ -22,7 +20,6 @@ im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
target-lexicon = "0.12.2"
libloading = "0.6"
object = { version = "0.24", features = ["write"] }
[dev-dependencies]
@ -30,17 +27,18 @@ roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_load = { path = "../load" }
roc_constrain = { path = "../constrain" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"
tempfile = "3.1.0"
itertools = "0.9"
libloading = "0.6"
[features]
target-aarch64 = ["roc_build/target-aarch64"]

View file

@ -830,8 +830,9 @@ impl<
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String> {
if let Layout::Struct(field_layouts) = layout {
let struct_size = layout.stack_size(PTR_SIZE);
if let Layout::Struct(field_layouts) = layout {
if struct_size > 0 {
let offset = self.claim_stack_size(struct_size)?;
self.symbol_storage_map.insert(
@ -862,7 +863,6 @@ impl<
Ok(())
} else {
// This is a single element struct. Just copy the single field to the stack.
let struct_size = layout.stack_size(PTR_SIZE);
let offset = self.claim_stack_size(struct_size)?;
self.symbol_storage_map.insert(
*sym,

View file

@ -31,10 +31,8 @@ roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"

View file

@ -10,14 +10,14 @@ use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{
self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
list_contains, list_drop, list_drop_at, list_get_unsafe, list_join, list_keep_errs,
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index,
list_prepend, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with,
list_swap,
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4,
list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, list_set,
list_single, list_sort_with, list_swap,
};
use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split,
str_starts_with, str_starts_with_code_point, str_to_utf8,
str_starts_with, str_starts_with_code_point, str_to_utf8, str_trim,
};
use crate::llvm::compare::{generic_eq, generic_neq};
use crate::llvm::convert::{
@ -1470,6 +1470,26 @@ pub fn build_tag<'a, 'ctx, 'env>(
.struct_type(&[internal_type, tag_id_type.into()], false);
let result_alloca = env.builder.build_alloca(wrapper_type, "tag_opaque");
// Initialize all memory of the alloca. This _should_ not be required, but currently
// LLVM can access uninitialized memory after applying some optimizations. Hopefully
// we can in the future adjust code gen so this is no longer an issue.
//
// An example is
//
// main : Task.Task {} []
// main =
// when List.len [ Ok "foo", Err 42, Ok "spam" ] is
// n -> Task.putLine (Str.fromInt n)
//
// Here the decrement function of result must first check it's an Ok tag,
// then defers to string decrement, which must check is the string is small or large
//
// After inlining, those checks are combined. That means that even if the tag is Err,
// a check is done on the "string" to see if it is big or small, which will touch the
// uninitialized memory.
let all_zeros = wrapper_type.const_zero();
env.builder.build_store(result_alloca, all_zeros);
// Determine types
let num_fields = arguments.len() + 1;
let mut field_types = Vec::with_capacity_in(num_fields, env.arena);
@ -2687,14 +2707,6 @@ pub fn complex_bitcast_struct_struct<'ctx>(
complex_bitcast(builder, from_value.into(), to_type.into(), name).into_struct_value()
}
fn cast_tag_to_block_of_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
from_value: StructValue<'ctx>,
to_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
complex_bitcast_check_size(env, from_value.into(), to_type, "tag_to_block_of_memory")
}
pub fn cast_block_of_memory_to_tag<'ctx>(
builder: &Builder<'ctx>,
from_value: StructValue<'ctx>,
@ -3898,7 +3910,7 @@ fn build_procedures_help<'a, 'ctx, 'env>(
let it = procedures.iter().map(|x| x.1);
let solutions = match roc_mono::alias_analysis::spec_program(entry_point, it) {
let solutions = match roc_mono::alias_analysis::spec_program(opt_level, entry_point, it) {
Err(e) => panic!("Error in alias analysis: {}", e),
Ok(solutions) => solutions,
};
@ -4715,6 +4727,67 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
_ => unreachable!("invalid list layout"),
}
}
ListMap4 { xs, ys, zs, ws } => {
let (list1, list1_layout) = load_symbol_and_layout(scope, &xs);
let (list2, list2_layout) = load_symbol_and_layout(scope, &ys);
let (list3, list3_layout) = load_symbol_and_layout(scope, &zs);
let (list4, list4_layout) = load_symbol_and_layout(scope, &ws);
let (function, closure, closure_layout) = function_details!();
match (
list1_layout,
list2_layout,
list3_layout,
list4_layout,
return_layout,
) {
(
Layout::Builtin(Builtin::List(element1_layout)),
Layout::Builtin(Builtin::List(element2_layout)),
Layout::Builtin(Builtin::List(element3_layout)),
Layout::Builtin(Builtin::List(element4_layout)),
Layout::Builtin(Builtin::List(result_layout)),
) => {
let argument_layouts = &[
**element1_layout,
**element2_layout,
**element3_layout,
**element4_layout,
];
let roc_function_call = roc_function_call(
env,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
);
list_map4(
env,
layout_ids,
roc_function_call,
list1,
list2,
list3,
list4,
element1_layout,
element2_layout,
element3_layout,
element4_layout,
result_layout,
)
}
(Layout::Builtin(Builtin::EmptyList), _, _, _, _)
| (_, Layout::Builtin(Builtin::EmptyList), _, _, _)
| (_, _, Layout::Builtin(Builtin::EmptyList), _, _)
| (_, _, _, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
_ => unreachable!("invalid list layout"),
}
}
ListMapWithIndex { xs } => {
// List.mapWithIndex : List before, (Nat, before -> after) -> List after
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
@ -5059,6 +5132,12 @@ fn run_low_level<'a, 'ctx, 'env>(
str_count_graphemes(env, scope, args[0])
}
StrTrim => {
// Str.trim : Str -> Str
debug_assert_eq!(args.len(), 1);
str_trim(env, scope, args[0])
}
ListLen => {
// List.len : List * -> Int
debug_assert_eq!(args.len(), 1);
@ -5740,7 +5819,7 @@ fn run_low_level<'a, 'ctx, 'env>(
cond
}
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| DictWalk => unreachable!("these are higher order, and are handled elsewhere"),
}

View file

@ -821,6 +821,51 @@ pub fn list_map3<'a, 'ctx, 'env>(
)
}
pub fn list_map4<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
list3: BasicValueEnum<'ctx>,
list4: BasicValueEnum<'ctx>,
element1_layout: &Layout<'a>,
element2_layout: &Layout<'a>,
element3_layout: &Layout<'a>,
element4_layout: &Layout<'a>,
result_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_a = build_dec_wrapper(env, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
let dec_c = build_dec_wrapper(env, layout_ids, element3_layout);
let dec_d = build_dec_wrapper(env, layout_ids, element4_layout);
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(env, list1),
pass_list_cc(env, list2),
pass_list_cc(env, list3),
pass_list_cc(env, list4),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(result_layout),
layout_width(env, element1_layout),
layout_width(env, element2_layout),
layout_width(env, element3_layout),
layout_width(env, element4_layout),
layout_width(env, result_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
dec_c.as_global_value().as_pointer_value().into(),
dec_d.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_MAP4,
)
}
/// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -965,6 +1010,8 @@ where
let ctx = env.context;
let builder = env.builder;
let entry = env.builder.get_insert_block().unwrap();
// constant 1i64
let one = env.ptr_int().const_int(1, false);
@ -976,15 +1023,15 @@ where
builder.build_unconditional_branch(loop_bb);
builder.position_at_end(loop_bb);
let curr_index = builder
.build_load(index_alloca, index_name)
.into_int_value();
let next_index = builder.build_int_add(curr_index, one, "nextindex");
let current_index_phi = env.builder.build_phi(env.ptr_int(), "current_index");
let current_index = current_index_phi.as_basic_value().into_int_value();
builder.build_store(index_alloca, next_index);
let next_index = builder.build_int_add(current_index, one, "next_index");
current_index_phi.add_incoming(&[(&next_index, loop_bb), (&env.ptr_int().const_zero(), entry)]);
// The body of the loop
loop_fn(curr_index);
loop_fn(current_index);
// #index < end
let loop_end_cond = bounds_check_comparison(builder, next_index, end);

View file

@ -249,6 +249,16 @@ pub fn str_count_graphemes<'a, 'ctx, 'env>(
)
}
/// Str.trim : Str -> Str
pub fn str_trim<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM)
}
/// Str.fromInt : Int -> Str
pub fn str_from_int<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -10,11 +10,9 @@ roc_collections = { path = "../collections" }
roc_module = { path = "../module" }
roc_mono = { path = "../mono" }
bumpalo = { version = "3.6.1", features = ["collections"] }
parity-wasm = "0.42"
roc_std = { path = "../../roc_std" }
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
[dev-dependencies]
roc_can = { path = "../can" }
@ -24,6 +22,6 @@ roc_types = { path = "../types" }
roc_module = { path = "../module" }
indoc = "0.3.3"
pretty_assertions = "0.5.1"
libc = "0.2"
target-lexicon = "0.12.2"
tempfile = "3.1.0"
wasmer-wasi = "2.0.0"

View file

@ -16,7 +16,7 @@
- [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc.
- [x] Ensure early Return statements don't skip stack cleanup
- [x] Model the stack machine as a storage mechanism, to make generated code "less bad"
- [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec`
- [x] Switch vectors to `bumpalo::Vec` where possible
- [ ] Implement relocations
- Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec<Instruction>` rather than a `Vec<u8>`. It may be worth serialising each instruction as it is inserted.
@ -225,6 +225,4 @@ The Module is a _specification_ for how to create an Instance of the program. Th
A WebAssembly module is equivalent to an executable file. It doesn't normally need relocations since at the WebAssembly layer, there is no Address Space Layout Randomisation. If it has relocations then it's an object file.
The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it has room for "custom sections" that in practice seem to be used for that.
The WebAssembly `tool-conventions` repo has a document on [linking](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md), and the `parity_wasm` crate supports "name" and "relocation" [sections](https://docs.rs/parity-wasm/0.42.2/parity_wasm/elements/enum.Section.html).
The [official spec](https://webassembly.github.io/spec/core/binary/modules.html#sections) lists the sections that are part of the final module. It doesn't mention any sections for relocations or symbol names, but it does support "custom" sections. Conventions to use those for linking are documented in the WebAssembly `tool-conventions` repo [here](https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md) and it mentions that LLVM is using those conventions.

View file

@ -1,8 +1,4 @@
use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder};
use parity_wasm::elements::{
BlockType, Instruction, Instruction::*, Instructions, Local, ValueType,
};
use bumpalo::collections::Vec;
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
@ -10,10 +6,10 @@ use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout};
use crate::code_builder::CodeBuilder;
use crate::layout::WasmLayout;
use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::{copy_memory, pop_stack_frame, push_stack_frame, CopyMemoryConfig, LocalId, PTR_TYPE};
use crate::wasm_module::{BlockType, CodeBuilder, LocalId, Signature, ValueType, WasmModule};
use crate::{copy_memory, CopyMemoryConfig, Env, PTR_TYPE};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
// Follow Emscripten's example by using 1kB (4 bytes would probably do)
@ -22,47 +18,50 @@ const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
#[derive(Clone, Copy, Debug)]
struct LabelId(u32);
// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43)
pub struct WasmBackend<'a> {
// Module level: Wasm AST
pub module_builder: ModuleBuilder,
env: &'a Env<'a>,
// Module level: internal state & IR mappings
// Module-level data
pub module: WasmModule<'a>,
_data_offset_map: MutMap<Literal<'a>, u32>,
_data_offset_next: u32,
proc_symbol_map: MutMap<Symbol, CodeLocation>,
proc_symbols: Vec<'a, Symbol>,
// Function level
code_builder: CodeBuilder,
storage: Storage,
// Function-level data
code_builder: CodeBuilder<'a>,
storage: Storage<'a>,
/// how many blocks deep are we (used for jumps)
block_depth: u32,
joinpoint_label_map: MutMap<JoinPointId, (u32, std::vec::Vec<StoredValue>)>,
joinpoint_label_map: MutMap<JoinPointId, (u32, Vec<'a, StoredValue>)>,
}
impl<'a> WasmBackend<'a> {
pub fn new() -> Self {
pub fn new(env: &'a Env<'a>, proc_symbols: Vec<'a, Symbol>) -> Self {
WasmBackend {
// Module: Wasm AST
module_builder: builder::module(),
env,
// Module: internal state & IR mappings
// Module-level data
module: WasmModule::new(env.arena),
_data_offset_map: MutMap::default(),
_data_offset_next: UNUSED_DATA_SECTION_BYTES,
proc_symbol_map: MutMap::default(),
proc_symbols,
// Function-level data
block_depth: 0,
joinpoint_label_map: MutMap::default(),
// Functions
code_builder: CodeBuilder::new(),
storage: Storage::new(),
code_builder: CodeBuilder::new(env.arena),
storage: Storage::new(env.arena),
}
}
/// Reset function-level data
fn reset(&mut self) {
self.code_builder.clear();
// Push the completed CodeBuilder into the module and swap it for a new empty one
let mut swap_code_builder = CodeBuilder::new(self.env.arena);
std::mem::swap(&mut swap_code_builder, &mut self.code_builder);
self.module.code.code_builders.push(swap_code_builder);
self.storage.clear();
self.joinpoint_label_map.clear();
assert_eq!(self.block_depth, 0);
@ -74,98 +73,57 @@ impl<'a> WasmBackend<'a> {
***********************************************************/
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
// println!("\ngenerating procedure {:?}\n", sym);
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<(), String> {
// println!("\ngenerating procedure {:?}\n", _sym);
let signature_builder = self.start_proc(&proc);
self.start_proc(&proc);
self.build_stmt(&proc.body, &proc.ret_layout)?;
let function_def = self.finalize_proc(signature_builder);
let location = self.module_builder.push_function(function_def);
let function_index = location.body;
self.proc_symbol_map.insert(sym, location);
self.finalize_proc()?;
self.reset();
// println!("\nfinished generating {:?}\n", sym);
Ok(function_index)
// println!("\nfinished generating {:?}\n", _sym);
Ok(())
}
fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder {
fn start_proc(&mut self, proc: &Proc<'a>) {
let ret_layout = WasmLayout::new(&proc.ret_layout);
let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
let ret_type = if ret_layout.is_stack_memory() {
self.storage.arg_types.push(PTR_TYPE);
self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
builder::signature()
None
} else {
let ret_type = ret_layout.value_type();
self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any)
builder::signature().with_result(ret_type)
let ty = ret_layout.value_type();
self.start_block(BlockType::Value(ty)); // block to ensure all paths pop stack memory (if any)
Some(ty)
};
for (layout, symbol) in proc.args {
self.storage.allocate(
&WasmLayout::new(layout),
*symbol,
StoredValueKind::Parameter,
);
let arg_layout = WasmLayout::new(layout);
self.storage
.allocate(&arg_layout, *symbol, StoredValueKind::Parameter);
}
signature_builder.with_params(self.storage.arg_types.clone())
self.module.add_function_signature(Signature {
param_types: self.storage.arg_types.clone(),
ret_type,
});
}
fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition {
self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any)
fn finalize_proc(&mut self) -> Result<(), String> {
// end the block from start_proc, to ensure all paths pop stack memory (if any)
self.end_block();
const STACK_FRAME_INSTRUCTIONS_LEN: usize = 10;
let mut final_instructions =
Vec::with_capacity(self.code_builder.len() + STACK_FRAME_INSTRUCTIONS_LEN);
if self.storage.stack_frame_size > 0 {
push_stack_frame(
&mut final_instructions,
// Write local declarations and stack frame push/pop code
self.code_builder.finalize(
&self.storage.local_types,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer.unwrap(),
self.storage.stack_frame_pointer,
);
}
self.code_builder.finalize_into(&mut final_instructions);
if self.storage.stack_frame_size > 0 {
pop_stack_frame(
&mut final_instructions,
self.storage.stack_frame_size,
self.storage.stack_frame_pointer.unwrap(),
);
}
final_instructions.push(End);
// Declare local variables (in batches of the same type)
let num_locals = self.storage.local_types.len();
let mut locals = Vec::with_capacity(num_locals);
if num_locals > 0 {
let mut batch_type = self.storage.local_types[0];
let mut batch_size = 0;
for t in &self.storage.local_types {
if *t == batch_type {
batch_size += 1;
} else {
locals.push(Local::new(batch_size, batch_type));
batch_type = *t;
batch_size = 1;
}
}
locals.push(Local::new(batch_size, batch_type));
}
builder::function()
.with_signature(signature_builder.build_sig())
.body()
.with_locals(locals)
.with_instructions(Instructions::new(final_instructions))
.build() // body
.build() // function
Ok(())
}
/**********************************************************
@ -177,17 +135,17 @@ impl<'a> WasmBackend<'a> {
/// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) {
self.block_depth += 1;
self.code_builder.push(Loop(BlockType::Value(value_type)));
self.code_builder.loop_(BlockType::Value(value_type));
}
fn start_block(&mut self, block_type: BlockType) {
self.block_depth += 1;
self.code_builder.push(Block(block_type));
self.code_builder.block(block_type);
}
fn end_block(&mut self) {
self.block_depth -= 1;
self.code_builder.push(End);
self.code_builder.end();
}
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
@ -250,7 +208,7 @@ impl<'a> WasmBackend<'a> {
_ => {
self.storage.load_symbols(&mut self.code_builder, &[*sym]);
self.code_builder.push(Br(self.block_depth)); // jump to end of function (for stack frame pop)
self.code_builder.br(self.block_depth); // jump to end of function (for stack frame pop)
}
}
@ -288,13 +246,13 @@ impl<'a> WasmBackend<'a> {
self.storage
.load_symbols(&mut self.code_builder, &[*cond_symbol]);
self.code_builder.push(I32Const(*value as i32));
self.code_builder.i32_const(*value as i32);
// compare the 2 topmost values
self.code_builder.push(I32Eq);
self.code_builder.i32_eq();
// "break" out of `i` surrounding blocks
self.code_builder.push(BrIf(i as u32));
self.code_builder.br_if(i as u32);
}
// if we never jumped because a value matched, we're in the default case
@ -318,7 +276,7 @@ impl<'a> WasmBackend<'a> {
remainder,
} => {
// make locals for join pointer parameters
let mut jp_param_storages = std::vec::Vec::with_capacity(parameters.len());
let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena);
for parameter in parameters.iter() {
let wasm_layout = WasmLayout::new(&parameter.layout);
let mut param_storage = self.storage.allocate(
@ -370,7 +328,7 @@ impl<'a> WasmBackend<'a> {
// jump
let levels = self.block_depth - target;
self.code_builder.push(Br(levels));
self.code_builder.br(levels);
Ok(())
}
@ -406,7 +364,8 @@ impl<'a> WasmBackend<'a> {
let mut wasm_args_tmp: Vec<Symbol>;
let (wasm_args, has_return_val) = match wasm_layout {
WasmLayout::StackMemory { .. } => {
wasm_args_tmp = Vec::with_capacity(arguments.len() + 1); // TODO: bumpalo
wasm_args_tmp =
Vec::with_capacity_in(arguments.len() + 1, self.env.arena);
wasm_args_tmp.push(*sym);
wasm_args_tmp.extend_from_slice(*arguments);
(wasm_args_tmp.as_slice(), false)
@ -416,16 +375,29 @@ impl<'a> WasmBackend<'a> {
self.storage.load_symbols(&mut self.code_builder, wasm_args);
let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!(
"Cannot find function {:?} called from {:?}",
func_sym, sym
))?;
// Index of the called function in the code section
// TODO: account for inlined functions when we start doing that (remember we emit procs out of order)
let func_index = match self.proc_symbols.iter().position(|s| s == func_sym) {
Some(i) => i as u32,
None => {
// TODO: actually useful linking! Push a relocation for it.
return Err(format!(
"Not yet supporteed: calling foreign function {:?}",
func_sym
));
}
};
self.code_builder.push_call(
function_location.body,
// Index of the function's name in the symbol table
let symbol_index = func_index; // TODO: update this when we add other things to the symbol table
self.code_builder.call(
func_index,
symbol_index,
wasm_args.len(),
has_return_val,
);
Ok(())
}
@ -442,25 +414,25 @@ impl<'a> WasmBackend<'a> {
}
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
let instruction = match lit {
Literal::Bool(x) => I32Const(*x as i32),
Literal::Byte(x) => I32Const(*x as i32),
match lit {
Literal::Bool(x) => self.code_builder.i32_const(*x as i32),
Literal::Byte(x) => self.code_builder.i32_const(*x as i32),
Literal::Int(x) => match layout {
Layout::Builtin(Builtin::Int64) => I64Const(*x as i64),
Layout::Builtin(Builtin::Int64) => self.code_builder.i64_const(*x as i64),
Layout::Builtin(
Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize,
) => I32Const(*x as i32),
) => self.code_builder.i32_const(*x as i32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
},
Literal::Float(x) => match layout {
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()),
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()),
Layout::Builtin(Builtin::Float64) => self.code_builder.f64_const(*x as f64),
Layout::Builtin(Builtin::Float32) => self.code_builder.f32_const(*x as f32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
@ -469,7 +441,6 @@ impl<'a> WasmBackend<'a> {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
};
self.code_builder.push(instruction);
Ok(())
}
@ -540,34 +511,33 @@ impl<'a> WasmBackend<'a> {
// Some Roc low-level ops care about wrapping, clipping, sign-extending...
// For those, we'll need to pre-process each argument before the main op,
// so simple arrays of instructions won't work. But there are common patterns.
let instructions: &[Instruction] = match lowlevel {
match lowlevel {
LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add],
ValueType::I64 => &[I64Add],
ValueType::F32 => &[F32Add],
ValueType::F64 => &[F64Add],
ValueType::I32 => self.code_builder.i32_add(),
ValueType::I64 => self.code_builder.i64_add(),
ValueType::F32 => self.code_builder.f32_add(),
ValueType::F64 => self.code_builder.f64_add(),
},
LowLevel::NumSub => match return_value_type {
ValueType::I32 => &[I32Sub],
ValueType::I64 => &[I64Sub],
ValueType::F32 => &[F32Sub],
ValueType::F64 => &[F64Sub],
ValueType::I32 => self.code_builder.i32_sub(),
ValueType::I64 => self.code_builder.i64_sub(),
ValueType::F32 => self.code_builder.f32_sub(),
ValueType::F64 => self.code_builder.f64_sub(),
},
LowLevel::NumMul => match return_value_type {
ValueType::I32 => &[I32Mul],
ValueType::I64 => &[I64Mul],
ValueType::F32 => &[F32Mul],
ValueType::F64 => &[F64Mul],
ValueType::I32 => self.code_builder.i32_mul(),
ValueType::I64 => self.code_builder.i64_mul(),
ValueType::F32 => self.code_builder.f32_mul(),
ValueType::F64 => self.code_builder.f64_mul(),
},
LowLevel::NumGt => {
// needs layout of the argument to be implemented fully
&[I32GtS]
self.code_builder.i32_gt_s()
}
_ => {
return Err(format!("unsupported low-level op {:?}", lowlevel));
}
};
self.code_builder.extend_from_slice(instructions);
Ok(())
}
}

View file

@ -1,456 +0,0 @@
use core::panic;
use std::collections::BTreeMap;
use std::fmt::Debug;
use parity_wasm::elements::{Instruction, Instruction::*};
use roc_module::symbol::Symbol;
use crate::LocalId;
const DEBUG_LOG: bool = false;
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum VirtualMachineSymbolState {
/// Value doesn't exist yet
NotYetPushed,
/// Value has been pushed onto the VM stack but not yet popped
/// Remember where it was pushed, in case we need to insert another instruction there later
Pushed { pushed_at: usize },
/// Value has been pushed and popped, so it's not on the VM stack any more.
/// If we want to use it again later, we will have to create a local for it,
/// by going back to insert a local.tee instruction at pushed_at
Popped { pushed_at: usize },
}
#[derive(Debug)]
pub struct CodeBuilder {
/// The main container for the instructions
code: Vec<Instruction>,
/// Extra instructions to insert at specific positions during finalisation
/// (Go back and set locals when we realise we need them)
/// We need BTree rather than Map or Vec, to ensure keys are sorted.
/// Entries may not be added in order. They are created when a Symbol
/// is used for the second time, or is in an inconvenient VM stack position,
/// so it's not a simple predictable order.
insertions: BTreeMap<usize, Instruction>,
/// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack
vm_stack: Vec<Symbol>,
}
#[allow(clippy::new_without_default)]
impl CodeBuilder {
pub fn new() -> Self {
CodeBuilder {
vm_stack: Vec::with_capacity(32),
insertions: BTreeMap::default(),
code: Vec::with_capacity(1024),
}
}
pub fn clear(&mut self) {
self.code.clear();
self.insertions.clear();
self.vm_stack.clear();
}
/// Add an instruction
pub fn push(&mut self, inst: Instruction) {
let (pops, push) = get_pops_and_pushes(&inst);
let new_len = self.vm_stack.len() - pops as usize;
self.vm_stack.truncate(new_len);
if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
if DEBUG_LOG {
println!("{:?} {:?}", inst, self.vm_stack);
}
self.code.push(inst);
}
/// Add many instructions
pub fn extend_from_slice(&mut self, instructions: &[Instruction]) {
let old_len = self.vm_stack.len();
let mut len = old_len;
let mut min_len = len;
for inst in instructions {
let (pops, push) = get_pops_and_pushes(inst);
len -= pops as usize;
if len < min_len {
min_len = len;
}
if push {
len += 1;
}
}
self.vm_stack.truncate(min_len);
self.vm_stack
.resize(len, Symbol::WASM_ANONYMOUS_STACK_VALUE);
if DEBUG_LOG {
println!("{:?} {:?}", instructions, self.vm_stack);
}
self.code.extend_from_slice(instructions);
}
/// Special-case method to add a Call instruction
/// Specify the number of arguments the function pops from the VM stack, and whether it pushes a return value
pub fn push_call(&mut self, function_index: u32, pops: usize, push: bool) {
let stack_depth = self.vm_stack.len();
if pops > stack_depth {
let mut final_code = Vec::with_capacity(self.code.len() + self.insertions.len());
self.finalize_into(&mut final_code);
panic!(
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\nfinal_code={:?}\nvm_stack={:?}",
function_index, pops, stack_depth, final_code, self.vm_stack
);
}
self.vm_stack.truncate(stack_depth - pops);
if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
let inst = Call(function_index);
if DEBUG_LOG {
println!("{:?} {:?}", inst, self.vm_stack);
}
self.code.push(inst);
}
/// Finalize a function body by copying all instructions into a vector
pub fn finalize_into(&mut self, final_code: &mut Vec<Instruction>) {
let mut insertions_iter = self.insertions.iter();
let mut next_insertion = insertions_iter.next();
for (pos, instruction) in self.code.drain(0..).enumerate() {
match next_insertion {
Some((&insert_pos, insert_inst)) if insert_pos == pos => {
final_code.push(insert_inst.to_owned());
next_insertion = insertions_iter.next();
}
_ => {}
}
final_code.push(instruction);
}
debug_assert!(next_insertion == None);
}
/// Total number of instructions in the final output
pub fn len(&self) -> usize {
self.code.len() + self.insertions.len()
}
/// Set the Symbol that is at the top of the VM stack right now
/// We will use this later when we need to load the Symbol
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
let len = self.vm_stack.len();
let pushed_at = self.code.len();
if len == 0 {
panic!(
"trying to set symbol with nothing on stack, code = {:?}",
self.code
);
}
self.vm_stack[len - 1] = sym;
VirtualMachineSymbolState::Pushed { pushed_at }
}
/// Verify if a sequence of symbols is at the top of the stack
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
let n_symbols = symbols.len();
let stack_depth = self.vm_stack.len();
if n_symbols > stack_depth {
return false;
}
let offset = stack_depth - n_symbols;
for (i, sym) in symbols.iter().enumerate() {
if self.vm_stack[offset + i] != *sym {
return false;
}
}
true
}
/// Load a Symbol that is stored in the VM stack
/// If it's already at the top of the stack, no code will be generated.
/// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided.
///
/// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call.
/// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local.
/// (In this case, the caller must remember to declare the local in the function header.)
pub fn load_symbol(
&mut self,
symbol: Symbol,
vm_state: VirtualMachineSymbolState,
next_local_id: LocalId,
) -> Option<VirtualMachineSymbolState> {
use VirtualMachineSymbolState::*;
match vm_state {
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
Pushed { pushed_at } => {
let &top = self.vm_stack.last().unwrap();
if top == symbol {
// We're lucky, the symbol is already on top of the VM stack
// No code to generate! (This reduces code size by up to 25% in tests.)
// Just let the caller know what happened
Some(Popped { pushed_at })
} else {
// Symbol is not on top of the stack. Find it.
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
// Insert a SetLocal where the value was created (this removes it from the VM stack)
self.insertions.insert(pushed_at, SetLocal(next_local_id.0));
self.vm_stack.remove(found_index);
// Insert a GetLocal at the current position
let inst = GetLocal(next_local_id.0);
if DEBUG_LOG {
println!(
"{:?} {:?} (& insert {:?} at {:?})",
inst,
self.vm_stack,
SetLocal(next_local_id.0),
pushed_at
);
}
self.code.push(inst);
self.vm_stack.push(symbol);
// This Symbol is no longer stored in the VM stack, but in a local
None
} else {
panic!(
"{:?} has state {:?} but not found in VM stack",
symbol, vm_state
);
}
}
}
Popped { pushed_at } => {
// This Symbol is being used for a second time
// Insert a TeeLocal where it was created (must remain on the stack for the first usage)
self.insertions.insert(pushed_at, TeeLocal(next_local_id.0));
// Insert a GetLocal at the current position
let inst = GetLocal(next_local_id.0);
if DEBUG_LOG {
println!(
"{:?} {:?} (& insert {:?} at {:?})",
inst,
self.vm_stack,
TeeLocal(next_local_id.0),
pushed_at
);
}
self.code.push(inst);
self.vm_stack.push(symbol);
// This symbol has been promoted to a Local
// Tell the caller it no longer has a VirtualMachineSymbolState
None
}
}
}
}
fn get_pops_and_pushes(inst: &Instruction) -> (u8, bool) {
match inst {
Unreachable => (0, false),
Nop => (0, false),
Block(_) => (0, false),
Loop(_) => (0, false),
If(_) => (1, false),
Else => (0, false),
End => (0, false),
Br(_) => (0, false),
BrIf(_) => (1, false),
BrTable(_) => (1, false),
Return => (0, false),
Call(_) | CallIndirect(_, _) => {
panic!("Unknown number of pushes and pops. Use add_call()");
}
Drop => (1, false),
Select => (3, true),
GetLocal(_) => (0, true),
SetLocal(_) => (1, false),
TeeLocal(_) => (1, true),
GetGlobal(_) => (0, true),
SetGlobal(_) => (1, false),
I32Load(_, _) => (1, true),
I64Load(_, _) => (1, true),
F32Load(_, _) => (1, true),
F64Load(_, _) => (1, true),
I32Load8S(_, _) => (1, true),
I32Load8U(_, _) => (1, true),
I32Load16S(_, _) => (1, true),
I32Load16U(_, _) => (1, true),
I64Load8S(_, _) => (1, true),
I64Load8U(_, _) => (1, true),
I64Load16S(_, _) => (1, true),
I64Load16U(_, _) => (1, true),
I64Load32S(_, _) => (1, true),
I64Load32U(_, _) => (1, true),
I32Store(_, _) => (2, false),
I64Store(_, _) => (2, false),
F32Store(_, _) => (2, false),
F64Store(_, _) => (2, false),
I32Store8(_, _) => (2, false),
I32Store16(_, _) => (2, false),
I64Store8(_, _) => (2, false),
I64Store16(_, _) => (2, false),
I64Store32(_, _) => (2, false),
CurrentMemory(_) => (0, true),
GrowMemory(_) => (1, true),
I32Const(_) => (0, true),
I64Const(_) => (0, true),
F32Const(_) => (0, true),
F64Const(_) => (0, true),
I32Eqz => (1, true),
I32Eq => (2, true),
I32Ne => (2, true),
I32LtS => (2, true),
I32LtU => (2, true),
I32GtS => (2, true),
I32GtU => (2, true),
I32LeS => (2, true),
I32LeU => (2, true),
I32GeS => (2, true),
I32GeU => (2, true),
I64Eqz => (1, true),
I64Eq => (2, true),
I64Ne => (2, true),
I64LtS => (2, true),
I64LtU => (2, true),
I64GtS => (2, true),
I64GtU => (2, true),
I64LeS => (2, true),
I64LeU => (2, true),
I64GeS => (2, true),
I64GeU => (2, true),
F32Eq => (2, true),
F32Ne => (2, true),
F32Lt => (2, true),
F32Gt => (2, true),
F32Le => (2, true),
F32Ge => (2, true),
F64Eq => (2, true),
F64Ne => (2, true),
F64Lt => (2, true),
F64Gt => (2, true),
F64Le => (2, true),
F64Ge => (2, true),
I32Clz => (1, true),
I32Ctz => (1, true),
I32Popcnt => (1, true),
I32Add => (2, true),
I32Sub => (2, true),
I32Mul => (2, true),
I32DivS => (2, true),
I32DivU => (2, true),
I32RemS => (2, true),
I32RemU => (2, true),
I32And => (2, true),
I32Or => (2, true),
I32Xor => (2, true),
I32Shl => (2, true),
I32ShrS => (2, true),
I32ShrU => (2, true),
I32Rotl => (2, true),
I32Rotr => (2, true),
I64Clz => (1, true),
I64Ctz => (1, true),
I64Popcnt => (1, true),
I64Add => (2, true),
I64Sub => (2, true),
I64Mul => (2, true),
I64DivS => (2, true),
I64DivU => (2, true),
I64RemS => (2, true),
I64RemU => (2, true),
I64And => (2, true),
I64Or => (2, true),
I64Xor => (2, true),
I64Shl => (2, true),
I64ShrS => (2, true),
I64ShrU => (2, true),
I64Rotl => (2, true),
I64Rotr => (2, true),
F32Abs => (1, true),
F32Neg => (1, true),
F32Ceil => (1, true),
F32Floor => (1, true),
F32Trunc => (1, true),
F32Nearest => (1, true),
F32Sqrt => (1, true),
F32Add => (2, true),
F32Sub => (2, true),
F32Mul => (2, true),
F32Div => (2, true),
F32Min => (2, true),
F32Max => (2, true),
F32Copysign => (2, true),
F64Abs => (1, true),
F64Neg => (1, true),
F64Ceil => (1, true),
F64Floor => (1, true),
F64Trunc => (1, true),
F64Nearest => (1, true),
F64Sqrt => (1, true),
F64Add => (2, true),
F64Sub => (2, true),
F64Mul => (2, true),
F64Div => (2, true),
F64Min => (2, true),
F64Max => (2, true),
F64Copysign => (2, true),
I32WrapI64 => (1, true),
I32TruncSF32 => (1, true),
I32TruncUF32 => (1, true),
I32TruncSF64 => (1, true),
I32TruncUF64 => (1, true),
I64ExtendSI32 => (1, true),
I64ExtendUI32 => (1, true),
I64TruncSF32 => (1, true),
I64TruncUF32 => (1, true),
I64TruncSF64 => (1, true),
I64TruncUF64 => (1, true),
F32ConvertSI32 => (1, true),
F32ConvertUI32 => (1, true),
F32ConvertSI64 => (1, true),
F32ConvertUI64 => (1, true),
F32DemoteF64 => (1, true),
F64ConvertSI32 => (1, true),
F64ConvertUI32 => (1, true),
F64ConvertSI64 => (1, true),
F64ConvertUI64 => (1, true),
F64PromoteF32 => (1, true),
I32ReinterpretF32 => (1, true),
I64ReinterpretF64 => (1, true),
F32ReinterpretI32 => (1, true),
F64ReinterpretI64 => (1, true),
}
}

View file

@ -1,7 +1,6 @@
use parity_wasm::elements::ValueType;
use roc_mono::layout::{Layout, UnionLayout};
use crate::{PTR_SIZE, PTR_TYPE};
use crate::{wasm_module::ValueType, PTR_SIZE, PTR_TYPE};
// See README for background information on Wasm locals, memory and function calls
#[derive(Debug, Clone)]
@ -72,11 +71,7 @@ impl WasmLayout {
}
}
#[allow(dead_code)]
pub fn stack_memory(&self) -> u32 {
match self {
Self::StackMemory { size, .. } => *size,
_ => 0,
}
pub fn is_stack_memory(&self) -> bool {
matches!(self, Self::StackMemory { .. })
}
}

View file

@ -1,12 +1,10 @@
mod backend;
mod code_builder;
pub mod from_wasm32_memory;
mod layout;
mod storage;
pub mod wasm_module;
use bumpalo::Bump;
use parity_wasm::builder;
use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType};
use bumpalo::{self, collections::Vec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol};
@ -14,25 +12,19 @@ use roc_mono::ir::{Proc, ProcLayout};
use roc_mono::layout::LayoutIds;
use crate::backend::WasmBackend;
use crate::code_builder::CodeBuilder;
use crate::wasm_module::{
Align, CodeBuilder, Export, ExportType, Global, GlobalInitValue, GlobalType, LinkingSubSection,
LocalId, SymInfo, ValueType, WasmModule,
};
const PTR_SIZE: u32 = 4;
const PTR_TYPE: ValueType = ValueType::I32;
// All usages of these alignment constants take u32, so an enum wouldn't add any safety.
pub const ALIGN_1: u32 = 0;
pub const ALIGN_2: u32 = 1;
pub const ALIGN_4: u32 = 2;
pub const ALIGN_8: u32 = 3;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
pub const STACK_ALIGNMENT_BYTES: i32 = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32);
pub const FRAME_ALIGNMENT_BYTES: i32 = 16;
pub struct Env<'a> {
pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot
pub arena: &'a Bump,
pub interns: Interns,
pub exposed_to_host: MutSet<Symbol>,
}
@ -40,87 +32,63 @@ pub struct Env<'a> {
pub fn build_module<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<Vec<u8>, String> {
let (builder, _) = build_module_help(env, procedures)?;
let module = builder.build();
module
.to_bytes()
.map_err(|e| -> String { format!("Error serialising Wasm module {:?}", e) })
) -> Result<std::vec::Vec<u8>, String> {
let mut wasm_module = build_module_help(env, procedures)?;
let mut buffer = std::vec::Vec::with_capacity(4096);
wasm_module.serialize(&mut buffer);
Ok(buffer)
}
pub fn build_module_help<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<(builder::ModuleBuilder, u32), String> {
let mut backend = WasmBackend::new();
) -> Result<WasmModule<'a>, String> {
let proc_symbols = Vec::from_iter_in(procedures.keys().map(|(sym, _)| *sym), env.arena);
let mut backend = WasmBackend::new(env, proc_symbols);
let mut layout_ids = LayoutIds::default();
let mut symbol_table_entries = Vec::with_capacity_in(procedures.len(), env.arena);
// Sort procedures by occurrence order
//
// We sort by the "name", but those are interned strings, and the name that is
// interned first will have a lower number.
//
// But, the name that occurs first is always `main` because it is in the (implicit)
// file header. Therefore sorting high to low will put other functions before main
//
// This means that for now other functions in the file have to be ordered "in reverse": if A
// uses B, then the name of A must first occur after the first occurrence of the name of B
let mut procedures: std::vec::Vec<_> = procedures.into_iter().collect();
procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0));
for (i, ((sym, layout), proc)) in procedures.into_iter().enumerate() {
let proc_name = layout_ids
.get(proc.name, &proc.ret_layout)
.to_symbol_string(proc.name, &env.interns);
symbol_table_entries.push(SymInfo::for_function(i as u32, proc_name));
backend.build_proc(proc, sym)?;
let mut function_index: u32 = 0;
for ((sym, layout), proc) in procedures {
function_index = backend.build_proc(proc, sym)?;
if env.exposed_to_host.contains(&sym) {
let fn_name = layout_ids
.get_toplevel(sym, &layout)
.to_symbol_string(sym, &env.interns);
let export = builder::export()
.field(fn_name.as_str())
.with_internal(Internal::Function(function_index))
.build();
backend.module_builder.push_export(export);
backend.module.export.entries.push(Export {
name: fn_name,
ty: ExportType::Func,
index: i as u32,
});
}
}
// Because of the sorting above, we know the last function in the `for` is the main function.
// Here we grab its index and return it, so that the test_wrapper is able to call it.
// This is a workaround until we implement object files with symbols and relocations.
let main_function_index = function_index;
let symbol_table = LinkingSubSection::SymbolTable(symbol_table_entries);
backend.module.linking.subsections.push(symbol_table);
const MIN_MEMORY_SIZE_KB: u32 = 1024;
const PAGE_SIZE_KB: u32 = 64;
backend.module.export.entries.push(Export {
name: "memory".to_string(),
ty: ExportType::Mem,
index: 0,
});
let memory = builder::MemoryBuilder::new()
.with_min(MIN_MEMORY_SIZE_KB / PAGE_SIZE_KB)
.build();
backend.module_builder.push_memory(memory);
let memory_export = builder::export()
.field("memory")
.with_internal(Internal::Memory(0))
.build();
backend.module_builder.push_export(memory_export);
let stack_pointer_init = backend.module.memory.min_size().unwrap() as i32;
backend.module.global.entries.push(Global {
ty: GlobalType {
value_type: ValueType::I32,
is_mutable: true,
},
init_value: GlobalInitValue::I32(stack_pointer_init),
});
let stack_pointer_global = builder::global()
.with_type(PTR_TYPE)
.mutable()
.init_expr(Instruction::I32Const((MIN_MEMORY_SIZE_KB * 1024) as i32))
.build();
backend.module_builder.push_global(stack_pointer_global);
Ok((backend.module_builder, main_function_index))
}
fn encode_alignment(bytes: u32) -> u32 {
match bytes {
1 => ALIGN_1,
2 => ALIGN_2,
4 => ALIGN_4,
8 => ALIGN_8,
_ => panic!("{:?}-byte alignment is not supported", bytes),
}
Ok(backend.module)
}
pub struct CopyMemoryConfig {
@ -137,33 +105,27 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
return;
}
let alignment_flag = encode_alignment(config.alignment_bytes);
let alignment = Align::from(config.alignment_bytes);
let mut i = 0;
while config.size - i >= 8 {
code_builder.extend_from_slice(&[
GetLocal(config.to_ptr.0),
GetLocal(config.from_ptr.0),
I64Load(alignment_flag, i + config.from_offset),
I64Store(alignment_flag, i + config.to_offset),
]);
code_builder.get_local(config.to_ptr);
code_builder.get_local(config.from_ptr);
code_builder.i64_load(alignment, i + config.from_offset);
code_builder.i64_store(alignment, i + config.to_offset);
i += 8;
}
if config.size - i >= 4 {
code_builder.extend_from_slice(&[
GetLocal(config.to_ptr.0),
GetLocal(config.from_ptr.0),
I32Load(alignment_flag, i + config.from_offset),
I32Store(alignment_flag, i + config.to_offset),
]);
code_builder.get_local(config.to_ptr);
code_builder.get_local(config.from_ptr);
code_builder.i32_load(alignment, i + config.from_offset);
code_builder.i32_store(alignment, i + config.to_offset);
i += 4;
}
while config.size - i > 0 {
code_builder.extend_from_slice(&[
GetLocal(config.to_ptr.0),
GetLocal(config.from_ptr.0),
I32Load8U(alignment_flag, i + config.from_offset),
I32Store8(alignment_flag, i + config.to_offset),
]);
code_builder.get_local(config.to_ptr);
code_builder.get_local(config.from_ptr);
code_builder.i32_load8_u(alignment, i + config.from_offset);
code_builder.i32_store8(alignment, i + config.to_offset);
i += 1;
}
}
@ -177,31 +139,6 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
aligned
}
pub fn push_stack_frame(
instructions: &mut Vec<Instruction>,
size: i32,
local_frame_pointer: LocalId,
) {
let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES);
instructions.extend([
GetGlobal(STACK_POINTER_GLOBAL_ID),
I32Const(aligned_size),
I32Sub,
TeeLocal(local_frame_pointer.0),
SetGlobal(STACK_POINTER_GLOBAL_ID),
]);
}
pub fn pop_stack_frame(
instructions: &mut Vec<Instruction>,
size: i32,
local_frame_pointer: LocalId,
) {
let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES);
instructions.extend([
GetLocal(local_frame_pointer.0),
I32Const(aligned_size),
I32Add,
SetGlobal(STACK_POINTER_GLOBAL_ID),
]);
pub fn debug_panic<E: std::fmt::Debug>(error: E) {
panic!("{:?}", error);
}

View file

@ -1,14 +1,12 @@
use parity_wasm::elements::{Instruction::*, ValueType};
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_collections::all::MutMap;
use roc_module::symbol::Symbol;
use crate::code_builder::{CodeBuilder, VirtualMachineSymbolState};
use crate::layout::WasmLayout;
use crate::{
copy_memory, round_up_to_alignment, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4,
ALIGN_8, PTR_SIZE, PTR_TYPE,
};
use crate::wasm_module::{CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState};
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
pub enum StoredValueKind {
Parameter,
@ -58,19 +56,19 @@ pub enum StoredValue {
/// Helper structure for WasmBackend, to keep track of how values are stored,
/// including the VM stack, local variables, and linear memory
pub struct Storage {
pub arg_types: std::vec::Vec<ValueType>,
pub local_types: std::vec::Vec<ValueType>,
pub struct Storage<'a> {
pub arg_types: Vec<'a, ValueType>,
pub local_types: Vec<'a, ValueType>,
pub symbol_storage_map: MutMap<Symbol, StoredValue>,
pub stack_frame_pointer: Option<LocalId>,
pub stack_frame_size: i32,
}
impl Storage {
pub fn new() -> Self {
impl<'a> Storage<'a> {
pub fn new(arena: &'a Bump) -> Self {
Storage {
arg_types: std::vec::Vec::with_capacity(8),
local_types: std::vec::Vec::with_capacity(32),
arg_types: Vec::with_capacity_in(8, arena),
local_types: Vec::with_capacity_in(32, arena),
symbol_storage_map: MutMap::default(),
stack_frame_pointer: None,
stack_frame_size: 0,
@ -239,7 +237,7 @@ impl Storage {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
code_builder.push(GetLocal(local_id.0));
code_builder.get_local(local_id);
code_builder.set_top_symbol(*sym);
}
@ -247,11 +245,9 @@ impl Storage {
location: StackMemoryLocation::FrameOffset(offset),
..
} => {
code_builder.extend_from_slice(&[
GetLocal(self.stack_frame_pointer.unwrap().0),
I32Const(offset as i32),
I32Add,
]);
code_builder.get_local(self.stack_frame_pointer.unwrap());
code_builder.i32_const(offset as i32);
code_builder.i32_add();
code_builder.set_top_symbol(*sym);
}
}
@ -295,20 +291,20 @@ impl Storage {
| StoredValue::Local {
value_type, size, ..
} => {
let store_instruction = match (value_type, size) {
(ValueType::I64, 8) => I64Store(ALIGN_8, to_offset),
(ValueType::I32, 4) => I32Store(ALIGN_4, to_offset),
(ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset),
(ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset),
(ValueType::F32, 4) => F32Store(ALIGN_4, to_offset),
(ValueType::F64, 8) => F64Store(ALIGN_8, to_offset),
use crate::wasm_module::Align::*;
code_builder.get_local(to_ptr);
self.load_symbols(code_builder, &[from_symbol]);
match (value_type, size) {
(ValueType::I64, 8) => code_builder.i64_store(Bytes8, to_offset),
(ValueType::I32, 4) => code_builder.i32_store(Bytes4, to_offset),
(ValueType::I32, 2) => code_builder.i32_store16(Bytes2, to_offset),
(ValueType::I32, 1) => code_builder.i32_store8(Bytes1, to_offset),
(ValueType::F32, 4) => code_builder.f32_store(Bytes4, to_offset),
(ValueType::F64, 8) => code_builder.f64_store(Bytes8, to_offset),
_ => {
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
}
};
code_builder.push(GetLocal(to_ptr.0));
self.load_symbols(code_builder, &[from_symbol]);
code_builder.push(store_instruction);
size
}
}
@ -341,7 +337,7 @@ impl Storage {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
self.load_symbols(code_builder, &[from_symbol]);
code_builder.push(SetLocal(to_local_id.0));
code_builder.set_local(*to_local_id);
self.symbol_storage_map.insert(from_symbol, to.clone());
}
@ -359,8 +355,8 @@ impl Storage {
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
code_builder
.extend_from_slice(&[GetLocal(from_local_id.0), SetLocal(to_local_id.0)]);
code_builder.get_local(*from_local_id);
code_builder.set_local(*to_local_id);
}
(
@ -419,7 +415,7 @@ impl Storage {
let local_id = self.get_next_local_id();
if vm_state != VirtualMachineSymbolState::NotYetPushed {
code_builder.load_symbol(symbol, vm_state, local_id);
code_builder.push(SetLocal(local_id.0));
code_builder.set_local(local_id);
}
self.local_types.push(value_type);

View file

@ -0,0 +1,729 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use core::panic;
use std::fmt::Debug;
use roc_module::symbol::Symbol;
use super::linking::{IndexRelocType, RelocationEntry};
use super::opcodes::*;
use super::serialize::{SerialBuffer, Serialize};
use crate::{round_up_to_alignment, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32);
/// Wasm value type. (Rust representation matches Wasm encoding)
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ValueType {
I32 = 0x7f,
I64 = 0x7e,
F32 = 0x7d,
F64 = 0x7c,
}
impl Serialize for ValueType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(*self as u8);
}
}
pub enum BlockType {
NoResult,
Value(ValueType),
}
impl BlockType {
pub fn as_byte(&self) -> u8 {
match self {
Self::NoResult => 0x40,
Self::Value(t) => *t as u8,
}
}
}
/// Wasm memory alignment. (Rust representation matches Wasm encoding)
#[repr(u8)]
#[derive(Clone, Copy, Debug)]
pub enum Align {
Bytes1 = 0,
Bytes2 = 1,
Bytes4 = 2,
Bytes8 = 3,
Bytes16 = 4,
Bytes32 = 5,
Bytes64 = 6,
// ... we can add more if we need them ...
}
impl From<u32> for Align {
fn from(x: u32) -> Align {
match x {
1 => Align::Bytes1,
2 => Align::Bytes2,
4 => Align::Bytes4,
8 => Align::Bytes8,
16 => Align::Bytes16,
32 => Align::Bytes32,
64 => Align::Bytes64,
_ => panic!("{:?}-byte alignment not supported", x),
}
}
}
#[derive(Debug, Clone, PartialEq, Copy)]
pub enum VirtualMachineSymbolState {
/// Value doesn't exist yet
NotYetPushed,
/// Value has been pushed onto the VM stack but not yet popped
/// Remember where it was pushed, in case we need to insert another instruction there later
Pushed { pushed_at: usize },
/// Value has been pushed and popped, so it's not on the VM stack any more.
/// If we want to use it again later, we will have to create a local for it,
/// by going back to insert a local.tee instruction at pushed_at
Popped { pushed_at: usize },
}
// An instruction (local.set or local.tee) to be inserted into the function code
#[derive(Debug)]
struct Insertion {
at: usize,
start: usize,
end: usize,
}
macro_rules! instruction_no_args {
($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => {
pub fn $method_name(&mut self) {
self.inst($opcode, $pops, $push);
}
};
}
macro_rules! instruction_memargs {
($method_name: ident, $opcode: expr, $pops: expr, $push: expr) => {
pub fn $method_name(&mut self, align: Align, offset: u32) {
self.inst_mem($opcode, $pops, $push, align, offset);
}
};
}
#[derive(Debug)]
pub struct CodeBuilder<'a> {
/// The main container for the instructions
code: Vec<'a, u8>,
/// Instruction bytes to be inserted into the code when finalizing the function
/// (Used for setting locals when we realise they are used multiple times)
insert_bytes: Vec<'a, u8>,
/// Code locations where the insert_bytes should go
insertions: Vec<'a, Insertion>,
/// Bytes for local variable declarations and stack-frame setup code.
/// We can't write this until we've finished the main code. But it goes
/// before it in the final output, so we need a separate vector.
preamble: Vec<'a, u8>,
/// Encoded bytes for the inner length of the function, locals + code.
/// ("inner" because it doesn't include its own length!)
/// Again, we can't write this until we've finished the code and preamble,
/// but it goes before them in the binary, so it's a separate vector.
inner_length: Vec<'a, u8>,
/// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack
vm_stack: Vec<'a, Symbol>,
/// Linker info to help combine the Roc module with builtin & platform modules,
/// e.g. to modify call instructions when function indices change
relocations: Vec<'a, RelocationEntry>,
}
#[allow(clippy::new_without_default)]
impl<'a> CodeBuilder<'a> {
pub fn new(arena: &'a Bump) -> Self {
CodeBuilder {
code: Vec::with_capacity_in(1024, arena),
insertions: Vec::with_capacity_in(32, arena),
insert_bytes: Vec::with_capacity_in(64, arena),
preamble: Vec::with_capacity_in(32, arena),
inner_length: Vec::with_capacity_in(5, arena),
vm_stack: Vec::with_capacity_in(32, arena),
relocations: Vec::with_capacity_in(32, arena),
}
}
/**********************************************************
SYMBOLS
The Wasm VM stores temporary values in its stack machine.
We track which stack positions correspond to IR Symbols,
because it helps to generate more efficient code.
***********************************************************/
/// Set the Symbol that is at the top of the VM stack right now
/// We will use this later when we need to load the Symbol
pub fn set_top_symbol(&mut self, sym: Symbol) -> VirtualMachineSymbolState {
let len = self.vm_stack.len();
let pushed_at = self.code.len();
if len == 0 {
panic!(
"trying to set symbol with nothing on stack, code = {:?}",
self.code
);
}
self.vm_stack[len - 1] = sym;
VirtualMachineSymbolState::Pushed { pushed_at }
}
/// Verify if a sequence of symbols is at the top of the stack
pub fn verify_stack_match(&self, symbols: &[Symbol]) -> bool {
let n_symbols = symbols.len();
let stack_depth = self.vm_stack.len();
if n_symbols > stack_depth {
return false;
}
let offset = stack_depth - n_symbols;
for (i, sym) in symbols.iter().enumerate() {
if self.vm_stack[offset + i] != *sym {
return false;
}
}
true
}
fn add_insertion(&mut self, insert_at: usize, opcode: u8, immediate: u32) {
let start = self.insert_bytes.len();
self.insert_bytes.push(opcode);
self.insert_bytes.encode_u32(immediate);
self.insertions.push(Insertion {
at: insert_at,
start,
end: self.insert_bytes.len(),
});
}
/// Load a Symbol that is stored in the VM stack
/// If it's already at the top of the stack, no code will be generated.
/// Otherwise, local.set and local.get instructions will be inserted, using the LocalId provided.
///
/// If the return value is `Some(s)`, `s` should be stored by the caller, and provided in the next call.
/// If the return value is `None`, the Symbol is no longer stored in the VM stack, but in a local.
/// (In this case, the caller must remember to declare the local in the function header.)
pub fn load_symbol(
&mut self,
symbol: Symbol,
vm_state: VirtualMachineSymbolState,
next_local_id: LocalId,
) -> Option<VirtualMachineSymbolState> {
use VirtualMachineSymbolState::*;
match vm_state {
NotYetPushed => panic!("Symbol {:?} has no value yet. Nothing to load.", symbol),
Pushed { pushed_at } => {
let &top = self.vm_stack.last().unwrap();
if top == symbol {
// We're lucky, the symbol is already on top of the VM stack
// No code to generate! (This reduces code size by up to 25% in tests.)
// Just let the caller know what happened
Some(Popped { pushed_at })
} else {
// Symbol is not on top of the stack. Find it.
if let Some(found_index) = self.vm_stack.iter().rposition(|&s| s == symbol) {
// Insert a local.set where the value was created
self.add_insertion(pushed_at, SETLOCAL, next_local_id.0);
// Take the value out of the stack where local.set was inserted
self.vm_stack.remove(found_index);
// Insert a local.get at the current position
self.get_local(next_local_id);
self.vm_stack.push(symbol);
// This Symbol is no longer stored in the VM stack, but in a local
None
} else {
panic!(
"{:?} has state {:?} but not found in VM stack",
symbol, vm_state
);
}
}
}
Popped { pushed_at } => {
// This Symbol is being used for a second time
// Insert a local.tee where it was pushed, so we don't interfere with the first usage
self.add_insertion(pushed_at, TEELOCAL, next_local_id.0);
// Insert a local.get at the current position
self.get_local(next_local_id);
self.vm_stack.push(symbol);
// This symbol has been promoted to a Local
// Tell the caller it no longer has a VirtualMachineSymbolState
None
}
}
}
/**********************************************************
FINALIZE AND SERIALIZE
***********************************************************/
/// Generate bytes to declare the function's local variables
fn build_local_declarations(&mut self, local_types: &[ValueType]) {
// reserve one byte for num_batches
self.preamble.push(0);
if local_types.is_empty() {
return;
}
// Write declarations in batches of the same ValueType
let mut num_batches: u32 = 0;
let mut batch_type = local_types[0];
let mut batch_size = 0;
for t in local_types {
if *t == batch_type {
batch_size += 1;
} else {
self.preamble.encode_u32(batch_size);
self.preamble.push(batch_type as u8);
batch_type = *t;
batch_size = 1;
num_batches += 1;
}
}
self.preamble.encode_u32(batch_size);
self.preamble.push(batch_type as u8);
num_batches += 1;
// Go back and write the number of batches at the start
if num_batches < 128 {
self.preamble[0] = num_batches as u8;
} else {
// We need more than 1 byte to encode num_batches!
// This is a ridiculous edge case, so just pad to 5 bytes for simplicity
let old_len = self.preamble.len();
self.preamble.resize(old_len + 4, 0);
self.preamble.copy_within(1..old_len, 5);
self.preamble.overwrite_padded_u32(0, num_batches);
}
}
/// Generate instruction bytes to grab a frame of stack memory on entering the function
fn build_stack_frame_push(&mut self, frame_size: i32, frame_pointer: LocalId) {
// Can't use the usual instruction methods because they push to self.code.
// This is the only case where we push instructions somewhere different.
self.preamble.push(GETGLOBAL);
self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID);
self.preamble.push(I32CONST);
self.preamble.encode_i32(frame_size);
self.preamble.push(I32SUB);
self.preamble.push(TEELOCAL);
self.preamble.encode_u32(frame_pointer.0);
self.preamble.push(SETGLOBAL);
self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID);
}
/// Generate instruction bytes to release a frame of stack memory on leaving the function
fn build_stack_frame_pop(&mut self, frame_size: i32, frame_pointer: LocalId) {
self.get_local(frame_pointer);
self.i32_const(frame_size);
self.i32_add();
self.set_global(STACK_POINTER_GLOBAL_ID);
}
/// Finalize the function
/// Generate all the "extra" bytes: local declarations, stack frame push/pop code, and function length
/// After this, bytes will have been _generated_, but not yet _serialized_ into a single stream.
/// Returns the final number of bytes the function will occupy in the target binary
pub fn finalize(
&mut self,
local_types: &[ValueType],
frame_size: i32,
frame_pointer: Option<LocalId>,
) {
self.build_local_declarations(local_types);
if let Some(frame_ptr_id) = frame_pointer {
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES);
self.build_stack_frame_push(aligned_size, frame_ptr_id);
self.build_stack_frame_pop(aligned_size, frame_ptr_id);
}
self.code.push(END);
let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len();
self.inner_length.encode_u32(inner_len as u32);
// Sort insertions. They are not created in order of assignment, but in order of *second* usage.
self.insertions.sort_by_key(|ins| ins.at);
}
/// Serialize all byte vectors in the right order
/// Also update relocation offsets relative to the provided base offset in the buffer
pub fn serialize_with_relocs<T: SerialBuffer>(
&self,
buffer: &mut T,
final_relocs: &mut Vec<'a, RelocationEntry>,
reloc_base_offset: usize,
) {
buffer.append_slice(&self.inner_length);
buffer.append_slice(&self.preamble);
// Do the insertions & update relocation offsets
let mut reloc_index = 0;
let mut code_pos = 0;
let mut insert_iter = self.insertions.iter();
loop {
let next_insert = insert_iter.next();
let next_pos = next_insert.map(|i| i.at).unwrap_or_else(|| self.code.len());
// Relocation offset needs to be an index into the body of the code section, but
// at this point it is an index into self.code. Need to adjust for all previous functions
// in the code section, and for insertions in the current function.
let section_body_pos = buffer.size() - reloc_base_offset;
while reloc_index < self.relocations.len()
&& self.relocations[reloc_index].offset() < next_pos as u32
{
let mut reloc_clone = self.relocations[reloc_index].clone();
*reloc_clone.offset_mut() += (section_body_pos - code_pos) as u32;
final_relocs.push(reloc_clone);
reloc_index += 1;
}
buffer.append_slice(&self.code[code_pos..next_pos]);
match next_insert {
Some(Insertion { at, start, end }) => {
buffer.append_slice(&self.insert_bytes[*start..*end]);
code_pos = *at;
}
None => {
break;
}
}
}
}
/**********************************************************
INSTRUCTION HELPER METHODS
***********************************************************/
/// Base method for generating instructions
/// Emits the opcode and simulates VM stack push/pop
fn inst(&mut self, opcode: u8, pops: usize, push: bool) {
let new_len = self.vm_stack.len() - pops as usize;
self.vm_stack.truncate(new_len);
if push {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
self.code.push(opcode);
}
fn inst_imm8(&mut self, opcode: u8, pops: usize, push: bool, immediate: u8) {
self.inst(opcode, pops, push);
self.code.push(immediate);
}
// public for use in test code
pub fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) {
self.inst(opcode, pops, push);
self.code.encode_u32(immediate);
}
fn inst_mem(&mut self, opcode: u8, pops: usize, push: bool, align: Align, offset: u32) {
self.inst(opcode, pops, push);
self.code.push(align as u8);
self.code.encode_u32(offset);
}
/**********************************************************
INSTRUCTION METHODS
One method for each Wasm instruction (in same order as the spec)
macros are for compactness & readability for the most common cases
Patterns that don't repeat very much don't have macros
***********************************************************/
instruction_no_args!(unreachable_, UNREACHABLE, 0, false);
instruction_no_args!(nop, NOP, 0, false);
pub fn block(&mut self, ty: BlockType) {
self.inst_imm8(BLOCK, 0, false, ty.as_byte());
}
pub fn loop_(&mut self, ty: BlockType) {
self.inst_imm8(LOOP, 0, false, ty.as_byte());
}
pub fn if_(&mut self, ty: BlockType) {
self.inst_imm8(IF, 1, false, ty.as_byte());
}
instruction_no_args!(else_, ELSE, 0, false);
instruction_no_args!(end, END, 0, false);
pub fn br(&mut self, levels: u32) {
self.inst_imm32(BR, 0, false, levels);
}
pub fn br_if(&mut self, levels: u32) {
self.inst_imm32(BRIF, 1, false, levels);
}
#[allow(dead_code)]
fn br_table() {
panic!("TODO");
}
instruction_no_args!(return_, RETURN, 0, false);
pub fn call(
&mut self,
function_index: u32,
symbol_index: u32,
n_args: usize,
has_return_val: bool,
) {
let stack_depth = self.vm_stack.len();
if n_args > stack_depth {
panic!(
"Trying to call to call function {:?} with {:?} values but only {:?} on the VM stack\n{:?}",
function_index, n_args, stack_depth, self
);
}
self.vm_stack.truncate(stack_depth - n_args);
if has_return_val {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
self.code.push(CALL);
// Write the index of the function to be called.
// Also make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
// other factors to make it relative to the code section. (All insertions will be known then.)
let offset = self.code.len() as u32;
self.code.encode_padded_u32(function_index);
self.relocations.push(RelocationEntry::Index {
type_id: IndexRelocType::FunctionIndexLeb,
offset,
symbol_index,
});
}
#[allow(dead_code)]
fn call_indirect() {
panic!("Not implemented. Roc doesn't use function pointers");
}
instruction_no_args!(drop_, DROP, 1, false);
instruction_no_args!(select, SELECT, 3, true);
pub fn get_local(&mut self, id: LocalId) {
self.inst_imm32(GETLOCAL, 0, true, id.0);
}
pub fn set_local(&mut self, id: LocalId) {
self.inst_imm32(SETLOCAL, 1, false, id.0);
}
pub fn tee_local(&mut self, id: LocalId) {
self.inst_imm32(TEELOCAL, 0, false, id.0);
}
pub fn get_global(&mut self, id: u32) {
self.inst_imm32(GETGLOBAL, 0, true, id);
}
pub fn set_global(&mut self, id: u32) {
self.inst_imm32(SETGLOBAL, 1, false, id);
}
instruction_memargs!(i32_load, I32LOAD, 1, true);
instruction_memargs!(i64_load, I64LOAD, 1, true);
instruction_memargs!(f32_load, F32LOAD, 1, true);
instruction_memargs!(f64_load, F64LOAD, 1, true);
instruction_memargs!(i32_load8_s, I32LOAD8S, 1, true);
instruction_memargs!(i32_load8_u, I32LOAD8U, 1, true);
instruction_memargs!(i32_load16_s, I32LOAD16S, 1, true);
instruction_memargs!(i32_load16_u, I32LOAD16U, 1, true);
instruction_memargs!(i64_load8_s, I64LOAD8S, 1, true);
instruction_memargs!(i64_load8_u, I64LOAD8U, 1, true);
instruction_memargs!(i64_load16_s, I64LOAD16S, 1, true);
instruction_memargs!(i64_load16_u, I64LOAD16U, 1, true);
instruction_memargs!(i64_load32_s, I64LOAD32S, 1, true);
instruction_memargs!(i64_load32_u, I64LOAD32U, 1, true);
instruction_memargs!(i32_store, I32STORE, 2, false);
instruction_memargs!(i64_store, I64STORE, 2, false);
instruction_memargs!(f32_store, F32STORE, 2, false);
instruction_memargs!(f64_store, F64STORE, 2, false);
instruction_memargs!(i32_store8, I32STORE8, 2, false);
instruction_memargs!(i32_store16, I32STORE16, 2, false);
instruction_memargs!(i64_store8, I64STORE8, 2, false);
instruction_memargs!(i64_store16, I64STORE16, 2, false);
instruction_memargs!(i64_store32, I64STORE32, 2, false);
pub fn memory_size(&mut self) {
self.inst_imm8(CURRENTMEMORY, 0, true, 0);
}
pub fn memory_grow(&mut self) {
self.inst_imm8(GROWMEMORY, 1, true, 0);
}
pub fn i32_const(&mut self, x: i32) {
self.inst(I32CONST, 0, true);
self.code.encode_i32(x);
}
pub fn i64_const(&mut self, x: i64) {
self.inst(I64CONST, 0, true);
self.code.encode_i64(x);
}
pub fn f32_const(&mut self, x: f32) {
self.inst(F32CONST, 0, true);
self.code.encode_f32(x);
}
pub fn f64_const(&mut self, x: f64) {
self.inst(F64CONST, 0, true);
self.code.encode_f64(x);
}
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',
// passing the ValueType as an argument. Could simplify lowlevel code gen.
instruction_no_args!(i32_eqz, I32EQZ, 1, true);
instruction_no_args!(i32_eq, I32EQ, 2, true);
instruction_no_args!(i32_ne, I32NE, 2, true);
instruction_no_args!(i32_lt_s, I32LTS, 2, true);
instruction_no_args!(i32_lt_u, I32LTU, 2, true);
instruction_no_args!(i32_gt_s, I32GTS, 2, true);
instruction_no_args!(i32_gt_u, I32GTU, 2, true);
instruction_no_args!(i32_le_s, I32LES, 2, true);
instruction_no_args!(i32_le_u, I32LEU, 2, true);
instruction_no_args!(i32_ge_s, I32GES, 2, true);
instruction_no_args!(i32_ge_u, I32GEU, 2, true);
instruction_no_args!(i64_eqz, I64EQZ, 1, true);
instruction_no_args!(i64_eq, I64EQ, 2, true);
instruction_no_args!(i64_ne, I64NE, 2, true);
instruction_no_args!(i64_lt_s, I64LTS, 2, true);
instruction_no_args!(i64_lt_u, I64LTU, 2, true);
instruction_no_args!(i64_gt_s, I64GTS, 2, true);
instruction_no_args!(i64_gt_u, I64GTU, 2, true);
instruction_no_args!(i64_le_s, I64LES, 2, true);
instruction_no_args!(i64_le_u, I64LEU, 2, true);
instruction_no_args!(i64_ge_s, I64GES, 2, true);
instruction_no_args!(i64_ge_u, I64GEU, 2, true);
instruction_no_args!(f32_eq, F32EQ, 2, true);
instruction_no_args!(f32_ne, F32NE, 2, true);
instruction_no_args!(f32_lt, F32LT, 2, true);
instruction_no_args!(f32_gt, F32GT, 2, true);
instruction_no_args!(f32_le, F32LE, 2, true);
instruction_no_args!(f32_ge, F32GE, 2, true);
instruction_no_args!(f64_eq, F64EQ, 2, true);
instruction_no_args!(f64_ne, F64NE, 2, true);
instruction_no_args!(f64_lt, F64LT, 2, true);
instruction_no_args!(f64_gt, F64GT, 2, true);
instruction_no_args!(f64_le, F64LE, 2, true);
instruction_no_args!(f64_ge, F64GE, 2, true);
instruction_no_args!(i32_clz, I32CLZ, 1, true);
instruction_no_args!(i32_ctz, I32CTZ, 1, true);
instruction_no_args!(i32_popcnt, I32POPCNT, 1, true);
instruction_no_args!(i32_add, I32ADD, 2, true);
instruction_no_args!(i32_sub, I32SUB, 2, true);
instruction_no_args!(i32_mul, I32MUL, 2, true);
instruction_no_args!(i32_div_s, I32DIVS, 2, true);
instruction_no_args!(i32_div_u, I32DIVU, 2, true);
instruction_no_args!(i32_rem_s, I32REMS, 2, true);
instruction_no_args!(i32_rem_u, I32REMU, 2, true);
instruction_no_args!(i32_and, I32AND, 2, true);
instruction_no_args!(i32_or, I32OR, 2, true);
instruction_no_args!(i32_xor, I32XOR, 2, true);
instruction_no_args!(i32_shl, I32SHL, 2, true);
instruction_no_args!(i32_shr_s, I32SHRS, 2, true);
instruction_no_args!(i32_shr_u, I32SHRU, 2, true);
instruction_no_args!(i32_rotl, I32ROTL, 2, true);
instruction_no_args!(i32_rotr, I32ROTR, 2, true);
instruction_no_args!(i64_clz, I64CLZ, 1, true);
instruction_no_args!(i64_ctz, I64CTZ, 1, true);
instruction_no_args!(i64_popcnt, I64POPCNT, 1, true);
instruction_no_args!(i64_add, I64ADD, 2, true);
instruction_no_args!(i64_sub, I64SUB, 2, true);
instruction_no_args!(i64_mul, I64MUL, 2, true);
instruction_no_args!(i64_div_s, I64DIVS, 2, true);
instruction_no_args!(i64_div_u, I64DIVU, 2, true);
instruction_no_args!(i64_rem_s, I64REMS, 2, true);
instruction_no_args!(i64_rem_u, I64REMU, 2, true);
instruction_no_args!(i64_and, I64AND, 2, true);
instruction_no_args!(i64_or, I64OR, 2, true);
instruction_no_args!(i64_xor, I64XOR, 2, true);
instruction_no_args!(i64_shl, I64SHL, 2, true);
instruction_no_args!(i64_shr_s, I64SHRS, 2, true);
instruction_no_args!(i64_shr_u, I64SHRU, 2, true);
instruction_no_args!(i64_rotl, I64ROTL, 2, true);
instruction_no_args!(i64_rotr, I64ROTR, 2, true);
instruction_no_args!(f32_abs, F32ABS, 1, true);
instruction_no_args!(f32_neg, F32NEG, 1, true);
instruction_no_args!(f32_ceil, F32CEIL, 1, true);
instruction_no_args!(f32_floor, F32FLOOR, 1, true);
instruction_no_args!(f32_trunc, F32TRUNC, 1, true);
instruction_no_args!(f32_nearest, F32NEAREST, 1, true);
instruction_no_args!(f32_sqrt, F32SQRT, 1, true);
instruction_no_args!(f32_add, F32ADD, 2, true);
instruction_no_args!(f32_sub, F32SUB, 2, true);
instruction_no_args!(f32_mul, F32MUL, 2, true);
instruction_no_args!(f32_div, F32DIV, 2, true);
instruction_no_args!(f32_min, F32MIN, 2, true);
instruction_no_args!(f32_max, F32MAX, 2, true);
instruction_no_args!(f32_copysign, F32COPYSIGN, 2, true);
instruction_no_args!(f64_abs, F64ABS, 1, true);
instruction_no_args!(f64_neg, F64NEG, 1, true);
instruction_no_args!(f64_ceil, F64CEIL, 1, true);
instruction_no_args!(f64_floor, F64FLOOR, 1, true);
instruction_no_args!(f64_trunc, F64TRUNC, 1, true);
instruction_no_args!(f64_nearest, F64NEAREST, 1, true);
instruction_no_args!(f64_sqrt, F64SQRT, 1, true);
instruction_no_args!(f64_add, F64ADD, 2, true);
instruction_no_args!(f64_sub, F64SUB, 2, true);
instruction_no_args!(f64_mul, F64MUL, 2, true);
instruction_no_args!(f64_div, F64DIV, 2, true);
instruction_no_args!(f64_min, F64MIN, 2, true);
instruction_no_args!(f64_max, F64MAX, 2, true);
instruction_no_args!(f64_copysign, F64COPYSIGN, 2, true);
instruction_no_args!(i32_wrap_i64, I32WRAPI64, 1, true);
instruction_no_args!(i32_trunc_s_f32, I32TRUNCSF32, 1, true);
instruction_no_args!(i32_trunc_u_f32, I32TRUNCUF32, 1, true);
instruction_no_args!(i32_trunc_s_f64, I32TRUNCSF64, 1, true);
instruction_no_args!(i32_trunc_u_f64, I32TRUNCUF64, 1, true);
instruction_no_args!(i64_extend_s_i32, I64EXTENDSI32, 1, true);
instruction_no_args!(i64_extend_u_i32, I64EXTENDUI32, 1, true);
instruction_no_args!(i64_trunc_s_f32, I64TRUNCSF32, 1, true);
instruction_no_args!(i64_trunc_u_f32, I64TRUNCUF32, 1, true);
instruction_no_args!(i64_trunc_s_f64, I64TRUNCSF64, 1, true);
instruction_no_args!(i64_trunc_u_f64, I64TRUNCUF64, 1, true);
instruction_no_args!(f32_convert_s_i32, F32CONVERTSI32, 1, true);
instruction_no_args!(f32_convert_u_i32, F32CONVERTUI32, 1, true);
instruction_no_args!(f32_convert_s_i64, F32CONVERTSI64, 1, true);
instruction_no_args!(f32_convert_u_i64, F32CONVERTUI64, 1, true);
instruction_no_args!(f32_demote_f64, F32DEMOTEF64, 1, true);
instruction_no_args!(f64_convert_s_i32, F64CONVERTSI32, 1, true);
instruction_no_args!(f64_convert_u_i32, F64CONVERTUI32, 1, true);
instruction_no_args!(f64_convert_s_i64, F64CONVERTSI64, 1, true);
instruction_no_args!(f64_convert_u_i64, F64CONVERTUI64, 1, true);
instruction_no_args!(f64_promote_f32, F64PROMOTEF32, 1, true);
instruction_no_args!(i32_reinterpret_f32, I32REINTERPRETF32, 1, true);
instruction_no_args!(i64_reinterpret_f64, I64REINTERPRETF64, 1, true);
instruction_no_args!(f32_reinterpret_i32, F32REINTERPRETI32, 1, true);
instruction_no_args!(f64_reinterpret_i64, F64REINTERPRETI64, 1, true);
}

View file

@ -0,0 +1,462 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use super::sections::{update_section_size, write_custom_section_header};
use super::serialize::{SerialBuffer, Serialize};
use super::Align;
/*******************************************************************
*
* Relocation sections
*
* https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum IndexRelocType {
/// a function index encoded as a 5-byte [varuint32]. Used for the immediate argument of a `call` instruction.
FunctionIndexLeb = 0,
/// a function table index encoded as a 5-byte [varint32].
/// Used to refer to the immediate argument of a `i32.const` instruction, e.g. taking the address of a function.
TableIndexSleb = 1,
/// a function table index encoded as a [uint32], e.g. taking the address of a function in a static data initializer.
TableIndexI32 = 2,
/// a type index encoded as a 5-byte [varuint32], e.g. the type immediate in a `call_indirect`.
TypeIndexLeb = 6,
/// a global index encoded as a 5-byte [varuint32], e.g. the index immediate in a `get_global`.
GlobalIndexLeb = 7,
/// an event index encoded as a 5-byte [varuint32]. Used for the immediate argument of a `throw` and `if_except` instruction.
EventIndexLeb = 10,
/// a global index encoded as [uint32].
GlobalIndexI32 = 13,
/// the 64-bit counterpart of `R_WASM_TABLE_INDEX_SLEB`. A function table index encoded as a 10-byte [varint64].
/// Used to refer to the immediate argument of a `i64.const` instruction, e.g. taking the address of a function in Wasm64.
TableIndexSleb64 = 18,
/// the 64-bit counterpart of `R_WASM_TABLE_INDEX_I32`.
/// A function table index encoded as a [uint64], e.g. taking the address of a function in a static data initializer.
TableIndexI64 = 19,
/// a table number encoded as a 5-byte [varuint32]. Used for the table immediate argument in the table.* instructions.
TableNumberLeb = 20,
}
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum OffsetRelocType {
/// a linear memory index encoded as a 5-byte [varuint32].
/// Used for the immediate argument of a `load` or `store` instruction, e.g. directly loading from or storing to a C++ global.
MemoryAddrLeb = 3,
/// a linear memory index encoded as a 5-byte [varint32].
/// Used for the immediate argument of a `i32.const` instruction, e.g. taking the address of a C++ global.
MemoryAddrSleb = 4,
/// a linear memory index encoded as a [uint32], e.g. taking the address of a C++ global in a static data initializer.
MemoryAddrI32 = 5,
/// a byte offset within code section for the specific function encoded as a [uint32].
/// The offsets start at the actual function code excluding its size field.
FunctionOffsetI32 = 8,
/// a byte offset from start of the specified section encoded as a [uint32].
SectionOffsetI32 = 9,
/// the 64-bit counterpart of `R_WASM_MEMORY_ADDR_LEB`. A 64-bit linear memory index encoded as a 10-byte [varuint64],
/// Used for the immediate argument of a `load` or `store` instruction on a 64-bit linear memory array.
MemoryAddrLeb64 = 14,
/// the 64-bit counterpart of `R_WASM_MEMORY_ADDR_SLEB`. A 64-bit linear memory index encoded as a 10-byte [varint64].
/// Used for the immediate argument of a `i64.const` instruction.
MemoryAddrSleb64 = 15,
/// the 64-bit counterpart of `R_WASM_MEMORY_ADDR`. A 64-bit linear memory index encoded as a [uint64],
/// e.g. taking the 64-bit address of a C++ global in a static data initializer.
MemoryAddrI64 = 16,
}
#[derive(Debug, Clone)]
pub enum RelocationEntry {
Index {
type_id: IndexRelocType,
offset: u32, // offset 0 means the next byte after section id and size
symbol_index: u32, // index in symbol table
},
Offset {
type_id: OffsetRelocType,
offset: u32, // offset 0 means the next byte after section id and size
symbol_index: u32, // index in symbol table
addend: i32, // addend to add to the address
},
}
impl RelocationEntry {
pub fn offset(&self) -> u32 {
match self {
Self::Index { offset, .. } => *offset,
Self::Offset { offset, .. } => *offset,
}
}
pub fn offset_mut(&mut self) -> &mut u32 {
match self {
Self::Index { offset, .. } => offset,
Self::Offset { offset, .. } => offset,
}
}
}
impl RelocationEntry {
pub fn for_function_call(offset: u32, symbol_index: u32) -> Self {
RelocationEntry::Index {
type_id: IndexRelocType::FunctionIndexLeb,
offset,
symbol_index,
}
}
}
impl Serialize for RelocationEntry {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Index {
type_id,
offset,
symbol_index,
} => {
buffer.append_u8(*type_id as u8);
buffer.encode_u32(*offset);
buffer.encode_u32(*symbol_index);
}
Self::Offset {
type_id,
offset,
symbol_index,
addend,
} => {
buffer.append_u8(*type_id as u8);
buffer.encode_u32(*offset);
buffer.encode_u32(*symbol_index);
buffer.encode_i32(*addend);
}
}
}
}
#[derive(Debug)]
pub struct RelocationSection<'a> {
pub name: &'a str,
/// The *index* (not ID!) of the target section in the module
pub target_section_index: Option<u32>,
pub entries: Vec<'a, RelocationEntry>,
}
impl<'a> RelocationSection<'a> {
pub fn new(arena: &'a Bump, name: &'a str) -> Self {
RelocationSection {
name,
target_section_index: None,
entries: Vec::with_capacity_in(64, arena),
}
}
}
impl<'a> Serialize for RelocationSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if !self.entries.is_empty() {
let header_indices = write_custom_section_header(buffer, self.name);
buffer.encode_u32(self.target_section_index.unwrap());
self.entries.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
}
/*******************************************************************
*
* Linking section
*
* https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#linking-metadata-section
*
*******************************************************************/
/// Linking metadata for data segments
pub struct LinkingSegment {
pub name: String,
pub alignment: Align,
pub flags: u32,
}
impl Serialize for LinkingSegment {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
/// Linking metadata for init (start) functions
pub struct LinkingInitFunc {
pub priority: u32,
pub symbol_index: u32, // index in the symbol table, not the function index
}
impl Serialize for LinkingInitFunc {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
//------------------------------------------------
// Common data
//------------------------------------------------
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ComdatSymKind {
Data = 0,
Function = 1,
Global = 2,
Event = 3,
Table = 4,
Section = 5,
}
pub struct ComdatSym {
pub kind: ComdatSymKind,
pub index: u32,
}
impl Serialize for ComdatSym {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
/// Linking metadata for common data
/// A COMDAT group may contain one or more functions, data segments, and/or custom sections.
/// The linker will include all of these elements with a given group name from one object file,
/// and will exclude any element with this group name from all other object files.
#[allow(dead_code)]
pub struct LinkingComdat<'a> {
name: String,
flags: u32,
syms: Vec<'a, ComdatSym>,
}
impl<'a> Serialize for LinkingComdat<'a> {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
//------------------------------------------------
// Symbol table
//------------------------------------------------
/// Indicating that this is a weak symbol. When
/// linking multiple modules defining the same symbol, all weak definitions are
/// discarded if any strong definitions exist; then if multiple weak definitions
/// exist all but one (unspecified) are discarded; and finally it is an error if
/// more than one definition remains.
pub const WASM_SYM_BINDING_WEAK: u32 = 1;
/// Indicating that this is a local symbol (this is exclusive with `WASM_SYM_BINDING_WEAK`).
/// Local symbols are not to be exported, or linked to other modules/sections.
/// The names of all non-local symbols must be unique, but the names of local symbols
/// are not considered for uniqueness. A local function or global symbol cannot reference an import.
pub const WASM_SYM_BINDING_LOCAL: u32 = 2;
/// Indicating that this is a hidden symbol.
/// Hidden symbols are not to be exported when performing the final link, but
/// may be linked to other modules.
pub const WASM_SYM_VISIBILITY_HIDDEN: u32 = 4;
/// Indicating that this symbol is not defined.
/// For non-data symbols, this must match whether the symbol is an import
/// or is defined; for data symbols, determines whether a segment is specified.
pub const WASM_SYM_UNDEFINED: u32 = 0x10; // required if the symbol refers to an import
/// The symbol is intended to be exported from the
/// wasm module to the host environment. This differs from the visibility flags
/// in that it effects the static linker.
pub const WASM_SYM_EXPORTED: u32 = 0x20;
/// The symbol uses an explicit symbol name,
/// rather than reusing the name from a wasm import. This allows it to remap
/// imports from foreign WebAssembly modules into local symbols with different
/// names.
pub const WASM_SYM_EXPLICIT_NAME: u32 = 0x40; // use the name from the symbol table, not from the import
/// The symbol is intended to be included in the
/// linker output, regardless of whether it is used by the program.
pub const WASM_SYM_NO_STRIP: u32 = 0x80;
pub enum WasmObjectSymbol {
Defined { index: u32, name: String },
Imported { index: u32 },
}
impl Serialize for WasmObjectSymbol {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Defined { index, name } => {
buffer.encode_u32(*index);
buffer.encode_u32(name.len() as u32);
buffer.append_slice(name.as_bytes());
}
Self::Imported { index } => {
buffer.encode_u32(*index);
}
}
}
}
pub enum DataSymbol {
Defined {
name: String,
index: u32,
offset: u32,
size: u32,
},
Imported {
name: String,
},
}
impl Serialize for DataSymbol {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Defined {
name,
index,
offset,
size,
} => {
buffer.encode_u32(name.len() as u32);
buffer.append_slice(name.as_bytes());
buffer.encode_u32(*index);
buffer.encode_u32(*offset);
buffer.encode_u32(*size);
}
Self::Imported { name } => {
buffer.encode_u32(name.len() as u32);
buffer.append_slice(name.as_bytes());
}
}
}
}
/// section index (not section id!)
#[derive(Clone, Copy, Debug)]
pub struct SectionIndex(u32);
pub enum SymInfoFields {
Function(WasmObjectSymbol),
Data(DataSymbol),
Global(WasmObjectSymbol),
Section(SectionIndex),
Event(WasmObjectSymbol),
Table(WasmObjectSymbol),
}
pub struct SymInfo {
flags: u32,
info: SymInfoFields,
}
impl SymInfo {
pub fn for_function(wasm_function_index: u32, name: String) -> Self {
let linking_symbol = WasmObjectSymbol::Defined {
index: wasm_function_index,
name,
};
SymInfo {
flags: 0,
info: SymInfoFields::Function(linking_symbol),
}
}
}
impl Serialize for SymInfo {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(match self.info {
SymInfoFields::Function(_) => 0,
SymInfoFields::Data(_) => 1,
SymInfoFields::Global(_) => 2,
SymInfoFields::Section(_) => 3,
SymInfoFields::Event(_) => 4,
SymInfoFields::Table(_) => 5,
});
buffer.encode_u32(self.flags);
match &self.info {
SymInfoFields::Function(x) => x.serialize(buffer),
SymInfoFields::Data(x) => x.serialize(buffer),
SymInfoFields::Global(x) => x.serialize(buffer),
SymInfoFields::Section(SectionIndex(x)) => {
buffer.encode_u32(*x);
}
SymInfoFields::Event(x) => x.serialize(buffer),
SymInfoFields::Table(x) => x.serialize(buffer),
};
}
}
//----------------------------------------------------------------
// Linking subsections
//----------------------------------------------------------------
pub enum LinkingSubSection<'a> {
/// Extra metadata about the data segments.
SegmentInfo(Vec<'a, LinkingSegment>),
/// Specifies a list of constructor functions to be called at startup.
/// These constructors will be called in priority order after memory has been initialized.
InitFuncs(Vec<'a, LinkingInitFunc>),
/// Specifies the COMDAT groups of associated linking objects, which are linked only once and all together.
ComdatInfo(Vec<'a, LinkingComdat<'a>>),
/// Specifies extra information about the symbols present in the module.
SymbolTable(Vec<'a, SymInfo>),
}
impl<'a> Serialize for LinkingSubSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(match self {
Self::SegmentInfo(_) => 5,
Self::InitFuncs(_) => 6,
Self::ComdatInfo(_) => 7,
Self::SymbolTable(_) => 8,
});
let payload_len_index = buffer.reserve_padded_u32();
let payload_start_index = buffer.size();
match self {
Self::SegmentInfo(items) => items.serialize(buffer),
Self::InitFuncs(items) => items.serialize(buffer),
Self::ComdatInfo(items) => items.serialize(buffer),
Self::SymbolTable(items) => items.serialize(buffer),
}
buffer.overwrite_padded_u32(
payload_len_index,
(buffer.size() - payload_start_index) as u32,
);
}
}
//----------------------------------------------------------------
// Linking metadata section
//----------------------------------------------------------------
const LINKING_VERSION: u8 = 2;
pub struct LinkingSection<'a> {
pub subsections: Vec<'a, LinkingSubSection<'a>>,
}
impl<'a> LinkingSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
LinkingSection {
subsections: Vec::with_capacity_in(1, arena),
}
}
}
impl<'a> Serialize for LinkingSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
let header_indices = write_custom_section_header(buffer, "linking");
buffer.append_u8(LINKING_VERSION);
for subsection in self.subsections.iter() {
subsection.serialize(buffer);
}
update_section_size(buffer, header_indices);
}
}

View file

@ -0,0 +1,13 @@
pub mod code_builder;
pub mod linking;
pub mod opcodes;
pub mod sections;
pub mod serialize;
pub use code_builder::{
Align, BlockType, CodeBuilder, LocalId, ValueType, VirtualMachineSymbolState,
};
pub use linking::{LinkingSubSection, SymInfo};
pub use sections::{
Export, ExportType, Global, GlobalInitValue, GlobalType, Signature, WasmModule,
};

View file

@ -0,0 +1,178 @@
pub const UNREACHABLE: u8 = 0x00;
pub const NOP: u8 = 0x01;
pub const BLOCK: u8 = 0x02;
pub const LOOP: u8 = 0x03;
pub const IF: u8 = 0x04;
pub const ELSE: u8 = 0x05;
pub const END: u8 = 0x0b;
pub const BR: u8 = 0x0c;
pub const BRIF: u8 = 0x0d;
pub const BRTABLE: u8 = 0x0e;
pub const RETURN: u8 = 0x0f;
pub const CALL: u8 = 0x10;
pub const CALLINDIRECT: u8 = 0x11;
pub const DROP: u8 = 0x1a;
pub const SELECT: u8 = 0x1b;
pub const GETLOCAL: u8 = 0x20;
pub const SETLOCAL: u8 = 0x21;
pub const TEELOCAL: u8 = 0x22;
pub const GETGLOBAL: u8 = 0x23;
pub const SETGLOBAL: u8 = 0x24;
pub const I32LOAD: u8 = 0x28;
pub const I64LOAD: u8 = 0x29;
pub const F32LOAD: u8 = 0x2a;
pub const F64LOAD: u8 = 0x2b;
pub const I32LOAD8S: u8 = 0x2c;
pub const I32LOAD8U: u8 = 0x2d;
pub const I32LOAD16S: u8 = 0x2e;
pub const I32LOAD16U: u8 = 0x2f;
pub const I64LOAD8S: u8 = 0x30;
pub const I64LOAD8U: u8 = 0x31;
pub const I64LOAD16S: u8 = 0x32;
pub const I64LOAD16U: u8 = 0x33;
pub const I64LOAD32S: u8 = 0x34;
pub const I64LOAD32U: u8 = 0x35;
pub const I32STORE: u8 = 0x36;
pub const I64STORE: u8 = 0x37;
pub const F32STORE: u8 = 0x38;
pub const F64STORE: u8 = 0x39;
pub const I32STORE8: u8 = 0x3a;
pub const I32STORE16: u8 = 0x3b;
pub const I64STORE8: u8 = 0x3c;
pub const I64STORE16: u8 = 0x3d;
pub const I64STORE32: u8 = 0x3e;
pub const CURRENTMEMORY: u8 = 0x3f;
pub const GROWMEMORY: u8 = 0x40;
pub const I32CONST: u8 = 0x41;
pub const I64CONST: u8 = 0x42;
pub const F32CONST: u8 = 0x43;
pub const F64CONST: u8 = 0x44;
pub const I32EQZ: u8 = 0x45;
pub const I32EQ: u8 = 0x46;
pub const I32NE: u8 = 0x47;
pub const I32LTS: u8 = 0x48;
pub const I32LTU: u8 = 0x49;
pub const I32GTS: u8 = 0x4a;
pub const I32GTU: u8 = 0x4b;
pub const I32LES: u8 = 0x4c;
pub const I32LEU: u8 = 0x4d;
pub const I32GES: u8 = 0x4e;
pub const I32GEU: u8 = 0x4f;
pub const I64EQZ: u8 = 0x50;
pub const I64EQ: u8 = 0x51;
pub const I64NE: u8 = 0x52;
pub const I64LTS: u8 = 0x53;
pub const I64LTU: u8 = 0x54;
pub const I64GTS: u8 = 0x55;
pub const I64GTU: u8 = 0x56;
pub const I64LES: u8 = 0x57;
pub const I64LEU: u8 = 0x58;
pub const I64GES: u8 = 0x59;
pub const I64GEU: u8 = 0x5a;
pub const F32EQ: u8 = 0x5b;
pub const F32NE: u8 = 0x5c;
pub const F32LT: u8 = 0x5d;
pub const F32GT: u8 = 0x5e;
pub const F32LE: u8 = 0x5f;
pub const F32GE: u8 = 0x60;
pub const F64EQ: u8 = 0x61;
pub const F64NE: u8 = 0x62;
pub const F64LT: u8 = 0x63;
pub const F64GT: u8 = 0x64;
pub const F64LE: u8 = 0x65;
pub const F64GE: u8 = 0x66;
pub const I32CLZ: u8 = 0x67;
pub const I32CTZ: u8 = 0x68;
pub const I32POPCNT: u8 = 0x69;
pub const I32ADD: u8 = 0x6a;
pub const I32SUB: u8 = 0x6b;
pub const I32MUL: u8 = 0x6c;
pub const I32DIVS: u8 = 0x6d;
pub const I32DIVU: u8 = 0x6e;
pub const I32REMS: u8 = 0x6f;
pub const I32REMU: u8 = 0x70;
pub const I32AND: u8 = 0x71;
pub const I32OR: u8 = 0x72;
pub const I32XOR: u8 = 0x73;
pub const I32SHL: u8 = 0x74;
pub const I32SHRS: u8 = 0x75;
pub const I32SHRU: u8 = 0x76;
pub const I32ROTL: u8 = 0x77;
pub const I32ROTR: u8 = 0x78;
pub const I64CLZ: u8 = 0x79;
pub const I64CTZ: u8 = 0x7a;
pub const I64POPCNT: u8 = 0x7b;
pub const I64ADD: u8 = 0x7c;
pub const I64SUB: u8 = 0x7d;
pub const I64MUL: u8 = 0x7e;
pub const I64DIVS: u8 = 0x7f;
pub const I64DIVU: u8 = 0x80;
pub const I64REMS: u8 = 0x81;
pub const I64REMU: u8 = 0x82;
pub const I64AND: u8 = 0x83;
pub const I64OR: u8 = 0x84;
pub const I64XOR: u8 = 0x85;
pub const I64SHL: u8 = 0x86;
pub const I64SHRS: u8 = 0x87;
pub const I64SHRU: u8 = 0x88;
pub const I64ROTL: u8 = 0x89;
pub const I64ROTR: u8 = 0x8a;
pub const F32ABS: u8 = 0x8b;
pub const F32NEG: u8 = 0x8c;
pub const F32CEIL: u8 = 0x8d;
pub const F32FLOOR: u8 = 0x8e;
pub const F32TRUNC: u8 = 0x8f;
pub const F32NEAREST: u8 = 0x90;
pub const F32SQRT: u8 = 0x91;
pub const F32ADD: u8 = 0x92;
pub const F32SUB: u8 = 0x93;
pub const F32MUL: u8 = 0x94;
pub const F32DIV: u8 = 0x95;
pub const F32MIN: u8 = 0x96;
pub const F32MAX: u8 = 0x97;
pub const F32COPYSIGN: u8 = 0x98;
pub const F64ABS: u8 = 0x99;
pub const F64NEG: u8 = 0x9a;
pub const F64CEIL: u8 = 0x9b;
pub const F64FLOOR: u8 = 0x9c;
pub const F64TRUNC: u8 = 0x9d;
pub const F64NEAREST: u8 = 0x9e;
pub const F64SQRT: u8 = 0x9f;
pub const F64ADD: u8 = 0xa0;
pub const F64SUB: u8 = 0xa1;
pub const F64MUL: u8 = 0xa2;
pub const F64DIV: u8 = 0xa3;
pub const F64MIN: u8 = 0xa4;
pub const F64MAX: u8 = 0xa5;
pub const F64COPYSIGN: u8 = 0xa6;
pub const I32WRAPI64: u8 = 0xa7;
pub const I32TRUNCSF32: u8 = 0xa8;
pub const I32TRUNCUF32: u8 = 0xa9;
pub const I32TRUNCSF64: u8 = 0xaa;
pub const I32TRUNCUF64: u8 = 0xab;
pub const I64EXTENDSI32: u8 = 0xac;
pub const I64EXTENDUI32: u8 = 0xad;
pub const I64TRUNCSF32: u8 = 0xae;
pub const I64TRUNCUF32: u8 = 0xaf;
pub const I64TRUNCSF64: u8 = 0xb0;
pub const I64TRUNCUF64: u8 = 0xb1;
pub const F32CONVERTSI32: u8 = 0xb2;
pub const F32CONVERTUI32: u8 = 0xb3;
pub const F32CONVERTSI64: u8 = 0xb4;
pub const F32CONVERTUI64: u8 = 0xb5;
pub const F32DEMOTEF64: u8 = 0xb6;
pub const F64CONVERTSI32: u8 = 0xb7;
pub const F64CONVERTUI32: u8 = 0xb8;
pub const F64CONVERTSI64: u8 = 0xb9;
pub const F64CONVERTUI64: u8 = 0xba;
pub const F64PROMOTEF32: u8 = 0xbb;
pub const I32REINTERPRETF32: u8 = 0xbc;
pub const I64REINTERPRETF64: u8 = 0xbd;
pub const F32REINTERPRETI32: u8 = 0xbe;
pub const F64REINTERPRETI64: u8 = 0xbf;

View file

@ -0,0 +1,578 @@
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
use super::linking::{LinkingSection, RelocationEntry, RelocationSection};
use super::opcodes;
use super::serialize::{SerialBuffer, Serialize};
use super::{CodeBuilder, ValueType};
/*******************************************************************
*
* Helpers
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
}
pub struct SectionHeaderIndices {
size_index: usize,
body_index: usize,
}
/// Write a section header, returning the position of the encoded length
fn write_section_header<T: SerialBuffer>(buffer: &mut T, id: SectionId) -> SectionHeaderIndices {
buffer.append_u8(id as u8);
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Write a custom section header, returning the position of the encoded length
pub fn write_custom_section_header<T: SerialBuffer>(
buffer: &mut T,
name: &str,
) -> SectionHeaderIndices {
buffer.append_u8(SectionId::Custom as u8);
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
name.serialize(buffer);
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Update a section header with its final size, after writing the bytes
pub fn update_section_size<T: SerialBuffer>(buffer: &mut T, header_indices: SectionHeaderIndices) {
let size = buffer.size() - header_indices.body_index;
buffer.overwrite_padded_u32(header_indices.size_index, size as u32);
}
/// Serialize a section that is just a vector of some struct
fn serialize_vector_section<B: SerialBuffer, T: Serialize>(
buffer: &mut B,
section_id: SectionId,
subsections: &[T],
) {
if !subsections.is_empty() {
let header_indices = write_section_header(buffer, section_id);
subsections.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
/*******************************************************************
*
* Type section
* Deduplicated list of function type signatures
*
*******************************************************************/
#[derive(PartialEq, Eq)]
pub struct Signature<'a> {
pub param_types: Vec<'a, ValueType>,
pub ret_type: Option<ValueType>,
}
impl<'a> Serialize for Signature<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(0x60);
self.param_types.serialize(buffer);
self.ret_type.serialize(buffer);
}
}
pub struct TypeSection<'a> {
/// Private. See WasmModule::add_function_signature
signatures: Vec<'a, Signature<'a>>,
}
impl<'a> TypeSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
TypeSection {
signatures: Vec::with_capacity_in(8, arena),
}
}
/// Find a matching signature or insert a new one. Return the index.
fn insert(&mut self, signature: Signature<'a>) -> u32 {
// Using linear search because we need to preserve indices stored in
// the Function section. (Also for practical sizes it's fast)
let maybe_index = self.signatures.iter().position(|s| *s == signature);
match maybe_index {
Some(index) => index as u32,
None => {
let index = self.signatures.len();
self.signatures.push(signature);
index as u32
}
}
}
}
impl<'a> Serialize for TypeSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Type, &self.signatures);
}
}
/*******************************************************************
*
* Import section
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum RefType {
Func = 0x70,
Extern = 0x6f,
}
pub struct TableType {
pub ref_type: RefType,
pub limits: Limits,
}
impl Serialize for TableType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(self.ref_type as u8);
self.limits.serialize(buffer);
}
}
pub enum ImportDesc {
Func { signature_index: u32 },
Table { ty: TableType },
Mem { limits: Limits },
Global { ty: GlobalType },
}
pub struct Import {
pub module: String,
pub name: String,
pub description: ImportDesc,
}
impl Serialize for Import {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
self.module.serialize(buffer);
self.name.serialize(buffer);
match &self.description {
ImportDesc::Func { signature_index } => {
buffer.append_u8(0);
buffer.encode_u32(*signature_index);
}
ImportDesc::Table { ty } => {
buffer.append_u8(1);
ty.serialize(buffer);
}
ImportDesc::Mem { limits } => {
buffer.append_u8(2);
limits.serialize(buffer);
}
ImportDesc::Global { ty } => {
buffer.append_u8(3);
ty.serialize(buffer);
}
}
}
}
pub struct ImportSection<'a> {
entries: Vec<'a, Import>,
}
impl<'a> ImportSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
ImportSection {
entries: bumpalo::vec![in arena],
}
}
}
impl<'a> Serialize for ImportSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Import, &self.entries);
}
}
/*******************************************************************
*
* Function section
* Maps function indices (Code section) to signature indices (Type section)
*
*******************************************************************/
pub struct FunctionSection<'a> {
/// Private. See WasmModule::add_function_signature
signature_indices: Vec<'a, u32>,
}
impl<'a> FunctionSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
FunctionSection {
signature_indices: Vec::with_capacity_in(8, arena),
}
}
}
impl<'a> Serialize for FunctionSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Function, &self.signature_indices);
}
}
/*******************************************************************
*
* Memory section
*
*******************************************************************/
pub enum Limits {
Min(u32),
MinMax(u32, u32),
}
impl Serialize for Limits {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Min(min) => {
buffer.append_u8(0);
buffer.encode_u32(*min);
}
Self::MinMax(min, max) => {
buffer.append_u8(1);
buffer.encode_u32(*min);
buffer.encode_u32(*max);
}
}
}
}
pub struct MemorySection(Option<Limits>);
impl MemorySection {
pub const PAGE_SIZE: u32 = 64 * 1024;
pub fn new(bytes: u32) -> Self {
if bytes == 0 {
MemorySection(None)
} else {
let pages = (bytes + Self::PAGE_SIZE - 1) / Self::PAGE_SIZE;
MemorySection(Some(Limits::Min(pages)))
}
}
pub fn min_size(&self) -> Option<u32> {
match self {
MemorySection(Some(Limits::Min(min))) | MemorySection(Some(Limits::MinMax(min, _))) => {
Some(min * Self::PAGE_SIZE)
}
MemorySection(None) => None,
}
}
}
impl Serialize for MemorySection {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
if let Some(limits) = &self.0 {
let header_indices = write_section_header(buffer, SectionId::Memory);
buffer.append_u8(1);
limits.serialize(buffer);
update_section_size(buffer, header_indices);
}
}
}
/*******************************************************************
*
* Global section
*
*******************************************************************/
pub struct GlobalType {
pub value_type: ValueType,
pub is_mutable: bool,
}
impl Serialize for GlobalType {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_u8(self.value_type as u8);
buffer.append_u8(self.is_mutable as u8);
}
}
pub enum GlobalInitValue {
I32(i32),
I64(i64),
F32(f32),
F64(f64),
}
pub struct Global {
pub ty: GlobalType,
pub init_value: GlobalInitValue,
}
impl Serialize for Global {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
self.ty.serialize(buffer);
match self.init_value {
GlobalInitValue::I32(x) => {
buffer.append_u8(opcodes::I32CONST);
buffer.encode_i32(x);
}
GlobalInitValue::I64(x) => {
buffer.append_u8(opcodes::I64CONST);
buffer.encode_i64(x);
}
GlobalInitValue::F32(x) => {
buffer.append_u8(opcodes::F32CONST);
buffer.encode_f32(x);
}
GlobalInitValue::F64(x) => {
buffer.append_u8(opcodes::F64CONST);
buffer.encode_f64(x);
}
}
buffer.append_u8(opcodes::END);
}
}
pub struct GlobalSection<'a> {
pub entries: Vec<'a, Global>,
}
impl<'a> GlobalSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
GlobalSection {
entries: Vec::with_capacity_in(1, arena),
}
}
}
impl<'a> Serialize for GlobalSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Global, &self.entries);
}
}
/*******************************************************************
*
* Export section
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ExportType {
Func = 0,
Table = 1,
Mem = 2,
Global = 3,
}
pub struct Export {
pub name: String,
pub ty: ExportType,
pub index: u32,
}
impl Serialize for Export {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
self.name.serialize(buffer);
buffer.append_u8(self.ty as u8);
buffer.encode_u32(self.index);
}
}
pub struct ExportSection<'a> {
pub entries: Vec<'a, Export>,
}
impl<'a> ExportSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
ExportSection {
entries: bumpalo::vec![in arena],
}
}
}
impl<'a> Serialize for ExportSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
serialize_vector_section(buffer, SectionId::Export, &self.entries);
}
}
/*******************************************************************
*
* Code section (see also code_builder.rs)
*
*******************************************************************/
#[derive(Debug)]
pub struct CodeSection<'a> {
pub code_builders: Vec<'a, CodeBuilder<'a>>,
}
impl<'a> CodeSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
CodeSection {
code_builders: Vec::with_capacity_in(8, arena),
}
}
/// Serialize the code builders for all functions, and get code relocations with final offsets
pub fn serialize_mut<T: SerialBuffer>(
&mut self,
buffer: &mut T,
relocations: &mut Vec<'a, RelocationEntry>,
) {
let header_indices = write_section_header(buffer, SectionId::Code);
buffer.encode_u32(self.code_builders.len() as u32);
for code_builder in self.code_builders.iter_mut() {
code_builder.serialize_with_relocs(buffer, relocations, header_indices.body_index);
}
update_section_size(buffer, header_indices);
}
}
/*******************************************************************
*
* Module
*
* https://webassembly.github.io/spec/core/binary/modules.html
*
*******************************************************************/
pub struct WasmModule<'a> {
pub types: TypeSection<'a>,
pub import: ImportSection<'a>,
pub function: FunctionSection<'a>,
/// Dummy placeholder for tables (used for function pointers and host references)
pub table: (),
pub memory: MemorySection,
pub global: GlobalSection<'a>,
pub export: ExportSection<'a>,
/// Dummy placeholder for start function. In Roc, this would be part of the platform.
pub start: (),
/// Dummy placeholder for table elements. Roc does not use tables.
pub element: (),
/// Dummy placeholder for data count section, not yet implemented
pub data_count: (),
pub code: CodeSection<'a>,
/// Dummy placeholder for data section, not yet implemented
pub data: (),
pub linking: LinkingSection<'a>,
pub reloc_code: RelocationSection<'a>,
pub reloc_data: RelocationSection<'a>,
}
impl<'a> WasmModule<'a> {
pub const WASM_VERSION: u32 = 1;
pub fn new(arena: &'a Bump) -> Self {
WasmModule {
types: TypeSection::new(arena),
import: ImportSection::new(arena),
function: FunctionSection::new(arena),
table: (), // Unused in Roc (mainly for function pointers)
memory: MemorySection::new(1024 * 1024),
global: GlobalSection::new(arena),
export: ExportSection::new(arena),
start: (), // Entry function. In Roc this would be part of the platform.
element: (), // Unused in Roc (related to table section)
data_count: (), // TODO, related to data section
code: CodeSection::new(arena),
data: (), // TODO: program constants (e.g. string literals)
linking: LinkingSection::new(arena),
reloc_code: RelocationSection::new(arena, "reloc.CODE"),
reloc_data: RelocationSection::new(arena, "reloc.DATA"),
}
}
/// Create entries in the Type and Function sections for a function signature
pub fn add_function_signature(&mut self, signature: Signature<'a>) {
let index = self.types.insert(signature);
self.function.signature_indices.push(index);
}
#[allow(clippy::unit_arg)]
pub fn serialize<T: SerialBuffer>(&mut self, buffer: &mut T) {
buffer.append_u8(0);
buffer.append_slice("asm".as_bytes());
buffer.write_unencoded_u32(Self::WASM_VERSION);
let mut index: u32 = 0;
let mut prev_size = buffer.size();
self.types.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.import.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.function.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.table.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.memory.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.global.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.export.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.start.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.element.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.data_count.serialize(buffer);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.reloc_code.target_section_index = Some(index);
self.code
.serialize_mut(buffer, &mut self.reloc_code.entries);
maybe_increment_section(buffer.size(), &mut prev_size, &mut index);
self.data.serialize(buffer);
self.reloc_data.target_section_index = Some(index);
self.linking.serialize(buffer);
self.reloc_code.serialize(buffer);
self.reloc_data.serialize(buffer);
}
}
fn maybe_increment_section(size: usize, prev_size: &mut usize, index: &mut u32) {
if size > *prev_size {
*index += 1;
*prev_size = size;
}
}

View file

@ -0,0 +1,395 @@
use std::fmt::Debug;
use bumpalo::collections::vec::Vec;
pub trait Serialize {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
}
impl Serialize for str {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(self.len() as u32);
buffer.append_slice(self.as_bytes());
}
}
impl Serialize for u32 {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(*self);
}
}
// Unit is used as a placeholder in parts of the Wasm spec we don't use yet
impl Serialize for () {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {}
}
impl<S: Serialize> Serialize for [S] {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(self.len() as u32);
for item in self.iter() {
item.serialize(buffer);
}
}
}
impl<S: Serialize> Serialize for Option<S> {
/// serialize Option as a vector of length 1 or 0
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Some(x) => {
buffer.append_u8(1);
x.serialize(buffer);
}
None => {
buffer.append_u8(0);
}
}
}
}
/// Write an unsigned integer into the provided buffer in LEB-128 format, returning byte length
///
/// All integers in Wasm are variable-length encoded, which saves space for small values.
/// The most significant bit indicates "more bytes are coming", and the other 7 are payload.
macro_rules! encode_uleb128 {
($name: ident, $ty: ty) => {
fn $name(&mut self, value: $ty) -> usize {
let mut x = value;
let start_len = self.size();
while x >= 0x80 {
self.append_u8(0x80 | ((x & 0x7f) as u8));
x >>= 7;
}
self.append_u8(x as u8);
self.size() - start_len
}
};
}
/// Write a signed integer into the provided buffer in LEB-128 format, returning byte length
macro_rules! encode_sleb128 {
($name: ident, $ty: ty) => {
fn $name(&mut self, value: $ty) -> usize {
let mut x = value;
let start_len = self.size();
loop {
let byte = (x & 0x7f) as u8;
x >>= 7;
let byte_is_negative = (byte & 0x40) != 0;
if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) {
self.append_u8(byte);
break;
}
self.append_u8(byte | 0x80);
}
self.size() - start_len
}
};
}
macro_rules! write_unencoded {
($name: ident, $ty: ty) => {
/// write an unencoded little-endian integer (only used in relocations)
fn $name(&mut self, value: $ty) {
let mut x = value;
let size = std::mem::size_of::<$ty>();
for _ in 0..size {
self.append_u8((x & 0xff) as u8);
x >>= 8;
}
}
};
}
macro_rules! encode_padded_sleb128 {
($name: ident, $ty: ty) => {
/// write a maximally-padded SLEB128 integer (only used in relocations)
fn $name(&mut self, value: $ty) {
let mut x = value;
let size = (std::mem::size_of::<$ty>() / 4) * 5;
for _ in 0..(size - 1) {
self.append_u8(0x80 | (x & 0x7f) as u8);
x >>= 7;
}
self.append_u8((x & 0x7f) as u8);
}
};
}
pub trait SerialBuffer: Debug {
fn append_u8(&mut self, b: u8);
fn overwrite_u8(&mut self, index: usize, b: u8);
fn append_slice(&mut self, b: &[u8]);
fn size(&self) -> usize;
encode_uleb128!(encode_u32, u32);
encode_uleb128!(encode_u64, u64);
encode_sleb128!(encode_i32, i32);
encode_sleb128!(encode_i64, i64);
fn reserve_padded_u32(&mut self) -> usize;
fn encode_padded_u32(&mut self, value: u32) -> usize;
fn overwrite_padded_u32(&mut self, index: usize, value: u32);
fn encode_f32(&mut self, value: f32) {
self.write_unencoded_u32(value.to_bits());
}
fn encode_f64(&mut self, value: f64) {
self.write_unencoded_u64(value.to_bits());
}
// methods for relocations
write_unencoded!(write_unencoded_u32, u32);
write_unencoded!(write_unencoded_u64, u64);
encode_padded_sleb128!(encode_padded_i32, i32);
encode_padded_sleb128!(encode_padded_i64, i64);
}
fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) {
let mut x = value;
for byte in buffer.iter_mut().take(4) {
*byte = 0x80 | ((x & 0x7f) as u8);
x >>= 7;
}
buffer[4] = x as u8;
}
impl SerialBuffer for std::vec::Vec<u8> {
fn append_u8(&mut self, b: u8) {
self.push(b);
}
fn overwrite_u8(&mut self, index: usize, b: u8) {
self[index] = b;
}
fn append_slice(&mut self, b: &[u8]) {
self.extend_from_slice(b);
}
fn size(&self) -> usize {
self.len()
}
fn reserve_padded_u32(&mut self) -> usize {
let index = self.len();
self.resize(index + 5, 0xff);
index
}
fn encode_padded_u32(&mut self, value: u32) -> usize {
let index = self.len();
let new_len = index + 5;
self.resize(new_len, 0);
overwrite_padded_u32_help(&mut self[index..new_len], value);
index
}
fn overwrite_padded_u32(&mut self, index: usize, value: u32) {
overwrite_padded_u32_help(&mut self[index..(index + 5)], value);
}
}
impl<'a> SerialBuffer for Vec<'a, u8> {
fn append_u8(&mut self, b: u8) {
self.push(b);
}
fn overwrite_u8(&mut self, index: usize, b: u8) {
self[index] = b;
}
fn append_slice(&mut self, b: &[u8]) {
self.extend_from_slice(b);
}
fn size(&self) -> usize {
self.len()
}
fn reserve_padded_u32(&mut self) -> usize {
let index = self.len();
self.resize(index + 5, 0xff);
index
}
fn encode_padded_u32(&mut self, value: u32) -> usize {
let index = self.len();
let new_len = index + 5;
self.resize(new_len, 0);
overwrite_padded_u32_help(&mut self[index..new_len], value);
index
}
fn overwrite_padded_u32(&mut self, index: usize, value: u32) {
overwrite_padded_u32_help(&mut self[index..(index + 5)], value);
}
}
#[cfg(test)]
mod tests {
use super::*;
use bumpalo::{self, collections::Vec, Bump};
fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(5, arena);
buffer.encode_u32(value);
buffer
}
#[test]
fn test_encode_u32() {
let a = &Bump::new();
assert_eq!(help_u32(a, 0), &[0]);
assert_eq!(help_u32(a, 64), &[64]);
assert_eq!(help_u32(a, 0x7f), &[0x7f]);
assert_eq!(help_u32(a, 0x80), &[0x80, 0x01]);
assert_eq!(help_u32(a, 0x3fff), &[0xff, 0x7f]);
assert_eq!(help_u32(a, 0x4000), &[0x80, 0x80, 0x01]);
assert_eq!(help_u32(a, u32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x0f]);
}
fn help_u64<'a>(arena: &'a Bump, value: u64) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(10, arena);
buffer.encode_u64(value);
buffer
}
#[test]
fn test_encode_u64() {
let a = &Bump::new();
assert_eq!(help_u64(a, 0), &[0]);
assert_eq!(help_u64(a, 64), &[64]);
assert_eq!(help_u64(a, 0x7f), &[0x7f]);
assert_eq!(help_u64(a, 0x80), &[0x80, 0x01]);
assert_eq!(help_u64(a, 0x3fff), &[0xff, 0x7f]);
assert_eq!(help_u64(a, 0x4000), &[0x80, 0x80, 0x01]);
assert_eq!(
help_u64(a, u64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01],
);
}
fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(5, arena);
buffer.encode_i32(value);
buffer
}
#[test]
fn test_encode_i32() {
let a = &Bump::new();
assert_eq!(help_i32(a, 0), &[0]);
assert_eq!(help_i32(a, 1), &[1]);
assert_eq!(help_i32(a, -1), &[0x7f]);
assert_eq!(help_i32(a, 63), &[63]);
assert_eq!(help_i32(a, 64), &[0xc0, 0x0]);
assert_eq!(help_i32(a, -64), &[0x40]);
assert_eq!(help_i32(a, -65), &[0xbf, 0x7f]);
assert_eq!(help_i32(a, i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]);
assert_eq!(help_i32(a, i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]);
}
fn help_i64<'a>(arena: &'a Bump, value: i64) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(10, arena);
buffer.encode_i64(value);
buffer
}
#[test]
fn test_encode_i64() {
let a = &Bump::new();
assert_eq!(help_i64(a, 0), &[0]);
assert_eq!(help_i64(a, 1), &[1]);
assert_eq!(help_i64(a, -1), &[0x7f]);
assert_eq!(help_i64(a, 63), &[63]);
assert_eq!(help_i64(a, 64), &[0xc0, 0x0]);
assert_eq!(help_i64(a, -64), &[0x40]);
assert_eq!(help_i64(a, -65), &[0xbf, 0x7f]);
assert_eq!(
help_i64(a, i64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00],
);
assert_eq!(
help_i64(a, i64::MIN),
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f],
);
}
#[test]
fn test_overwrite_u32_padded() {
let mut buffer = [0, 0, 0, 0, 0];
overwrite_padded_u32_help(&mut buffer, u32::MAX);
assert_eq!(buffer, [0xff, 0xff, 0xff, 0xff, 0x0f]);
overwrite_padded_u32_help(&mut buffer, 0);
assert_eq!(buffer, [0x80, 0x80, 0x80, 0x80, 0x00]);
overwrite_padded_u32_help(&mut buffer, 127);
assert_eq!(buffer, [0xff, 0x80, 0x80, 0x80, 0x00]);
overwrite_padded_u32_help(&mut buffer, 128);
assert_eq!(buffer, [0x80, 0x81, 0x80, 0x80, 0x00]);
}
#[test]
fn test_write_unencoded_u32() {
let mut buffer = std::vec::Vec::with_capacity(4);
buffer.write_unencoded_u32(0);
assert_eq!(buffer, &[0, 0, 0, 0]);
buffer.clear();
buffer.write_unencoded_u32(u32::MAX);
assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff]);
}
#[test]
fn test_write_unencoded_u64() {
let mut buffer = std::vec::Vec::with_capacity(8);
buffer.write_unencoded_u64(0);
assert_eq!(buffer, &[0, 0, 0, 0, 0, 0, 0, 0]);
buffer.clear();
buffer.write_unencoded_u64(u64::MAX);
assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
}
fn help_pad_i32(val: i32) -> std::vec::Vec<u8> {
let mut buffer = std::vec::Vec::with_capacity(5);
buffer.encode_padded_i32(val);
buffer
}
#[test]
fn test_encode_padded_i32() {
assert_eq!(help_pad_i32(0), &[0x80, 0x80, 0x80, 0x80, 0x00]);
assert_eq!(help_pad_i32(1), &[0x81, 0x80, 0x80, 0x80, 0x00]);
assert_eq!(help_pad_i32(-1), &[0xff, 0xff, 0xff, 0xff, 0x7f]);
assert_eq!(help_pad_i32(i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]);
assert_eq!(help_pad_i32(i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]);
}
fn help_pad_i64(val: i64) -> std::vec::Vec<u8> {
let mut buffer = std::vec::Vec::with_capacity(10);
buffer.encode_padded_i64(val);
buffer
}
#[test]
fn test_encode_padded_i64() {
assert_eq!(
help_pad_i64(0),
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]
);
assert_eq!(
help_pad_i64(1),
&[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]
);
assert_eq!(
help_pad_i64(-1),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]
);
assert_eq!(
help_pad_i64(i64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00],
);
assert_eq!(
help_pad_i64(i64::MIN),
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f],
);
}
}

View file

@ -1,278 +0,0 @@
use parity_wasm::builder;
use parity_wasm::builder::ModuleBuilder;
use parity_wasm::elements::{
Instruction, Instruction::*, Instructions, Internal, Local, ValueType,
};
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
use roc_gen_wasm::*;
use roc_std::{RocDec, RocList, RocOrder, RocStr};
const STACK_POINTER_LOCAL_ID: u32 = 0;
pub trait Wasm32TestResult {
fn insert_test_wrapper(
module_builder: &mut ModuleBuilder,
wrapper_name: &str,
main_function_index: u32,
) {
let instructions = Self::build_wrapper_body(main_function_index);
let signature = builder::signature().with_result(ValueType::I32).build_sig();
let stack_frame_pointer = Local::new(1, ValueType::I32);
let function_def = builder::function()
.with_signature(signature)
.body()
.with_locals(vec![stack_frame_pointer])
.with_instructions(Instructions::new(instructions))
.build() // body
.build(); // function
let location = module_builder.push_function(function_def);
let export = builder::export()
.field(wrapper_name)
.with_internal(Internal::Function(location.body))
.build();
module_builder.push_export(export);
}
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>;
}
macro_rules! build_wrapper_body_primitive {
($store_instruction: expr, $align: expr) => {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
let size: i32 = 8;
let mut instructions = Vec::with_capacity(16);
push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.extend([
// load result address to prepare for the store instruction later
GetLocal(STACK_POINTER_LOCAL_ID),
//
// Call the main function with no arguments. Get primitive back.
Call(main_function_index),
//
// Store the primitive at the allocated address
$store_instruction($align, 0),
//
// Return the result pointer
GetLocal(STACK_POINTER_LOCAL_ID),
]);
pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.push(End);
instructions
}
};
}
macro_rules! wasm_test_result_primitive {
($type_name: ident, $store_instruction: expr, $align: expr) => {
impl Wasm32TestResult for $type_name {
build_wrapper_body_primitive!($store_instruction, $align);
}
};
}
fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> {
let mut instructions = Vec::with_capacity(16);
push_stack_frame(
&mut instructions,
size as i32,
LocalId(STACK_POINTER_LOCAL_ID),
);
instructions.extend([
//
// Call the main function with the allocated address to write the result.
// No value is returned to the VM stack. This is the same as in compiled C.
GetLocal(STACK_POINTER_LOCAL_ID),
Call(main_function_index),
//
// Return the result address
GetLocal(STACK_POINTER_LOCAL_ID),
]);
pop_stack_frame(
&mut instructions,
size as i32,
LocalId(STACK_POINTER_LOCAL_ID),
);
instructions.push(End);
instructions
}
macro_rules! wasm_test_result_stack_memory {
($type_name: ident) => {
impl Wasm32TestResult for $type_name {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(main_function_index, $type_name::ACTUAL_WIDTH)
}
}
};
}
wasm_test_result_primitive!(bool, I32Store8, ALIGN_1);
wasm_test_result_primitive!(RocOrder, I32Store8, ALIGN_1);
wasm_test_result_primitive!(u8, I32Store8, ALIGN_1);
wasm_test_result_primitive!(i8, I32Store8, ALIGN_1);
wasm_test_result_primitive!(u16, I32Store16, ALIGN_2);
wasm_test_result_primitive!(i16, I32Store16, ALIGN_2);
wasm_test_result_primitive!(u32, I32Store, ALIGN_4);
wasm_test_result_primitive!(i32, I32Store, ALIGN_4);
wasm_test_result_primitive!(u64, I64Store, ALIGN_8);
wasm_test_result_primitive!(i64, I64Store, ALIGN_8);
wasm_test_result_primitive!(f32, F32Store, ALIGN_8);
wasm_test_result_primitive!(f64, F64Store, ALIGN_8);
wasm_test_result_stack_memory!(u128);
wasm_test_result_stack_memory!(i128);
wasm_test_result_stack_memory!(RocDec);
wasm_test_result_stack_memory!(RocStr);
impl<T: Wasm32TestResult> Wasm32TestResult for RocList<T> {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(main_function_index, 12)
}
}
impl<T: Wasm32TestResult> Wasm32TestResult for &'_ T {
build_wrapper_body_primitive!(I32Store, ALIGN_4);
}
impl<T, const N: usize> Wasm32TestResult for [T; N]
where
T: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(main_function_index, N * T::ACTUAL_WIDTH)
}
}
impl<T, U> Wasm32TestResult for (T, U)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(main_function_index, T::ACTUAL_WIDTH + U::ACTUAL_WIDTH)
}
}
impl<T, U, V> Wasm32TestResult for (T, U, V)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W> Wasm32TestResult for (T, U, V, W)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X> Wasm32TestResult for (T, U, V, W, X)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X, Y> Wasm32TestResult for (T, U, V, W, X, Y)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
Y: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH
+ V::ACTUAL_WIDTH
+ W::ACTUAL_WIDTH
+ X::ACTUAL_WIDTH
+ Y::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X, Y, Z> Wasm32TestResult for (T, U, V, W, X, Y, Z)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
Y: Wasm32TestResult + FromWasm32Memory,
Z: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH
+ V::ACTUAL_WIDTH
+ W::ACTUAL_WIDTH
+ X::ACTUAL_WIDTH
+ Y::ACTUAL_WIDTH
+ Z::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X, Y, Z, A> Wasm32TestResult for (T, U, V, W, X, Y, Z, A)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
Y: Wasm32TestResult + FromWasm32Memory,
Z: Wasm32TestResult + FromWasm32Memory,
A: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH
+ V::ACTUAL_WIDTH
+ W::ACTUAL_WIDTH
+ X::ACTUAL_WIDTH
+ Y::ACTUAL_WIDTH
+ Z::ACTUAL_WIDTH
+ A::ACTUAL_WIDTH,
)
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,916 +0,0 @@
#[macro_use]
extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod wasm_records {
// #[test]
// fn basic_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { y: 17, x: 15, z: 19 }.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: 17, z: 19 }.y
// "#
// ),
// 17,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: 17, z: 19 }.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
// "#
// ),
// 12,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
// "#
// ),
// 2,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn f64_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.x
// "#
// ),
// 15.1,
// f64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.y
// "#
// ),
// 17.2,
// f64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.z
// "#
// ),
// 19.3,
// f64
// );
// }
// #[test]
// fn fn_record() {
// assert_evals_to!(
// indoc!(
// r#"
// getRec = \x -> { y: 17, x, z: 19 }
// (getRec 15).x
// "#
// ),
// 15,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.y
// "#
// ),
// 17,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z
// "#
// ),
// 19,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z + rec.x
// "#
// ),
// 34,
// i64
// );
// }
// #[test]
// fn def_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17, x: 15, z: 19 }
//
// rec.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
//
// rec.y
// "#
// ),
// 17,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
//
// rec.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn when_on_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2 } is
// { x } -> x + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn when_record_with_guard_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2, y: 3.14 } is
// { x: var } -> var + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn let_with_record_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// { x } = { x: 0x2, y: 3.14 }
//
// x
// "#
// ),
// 2,
// i64
// );
// }
//
// #[test]
// fn record_guard_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2, y: 3.14 } is
// { x: 0x4 } -> 5
// { x } -> x + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn twice_record_access() {
// assert_evals_to!(
// indoc!(
// r#"
// x = {a: 0x2, b: 0x3 }
//
// x.a + x.b
// "#
// ),
// 5,
// i64
// );
// }
// #[test]
// fn empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// v = {}
//
// v
// "#
// ),
// (),
// ()
// );
// }
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3 }
"#
),
3,
i64
);
}
#[test]
fn i64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
(3, 5),
(i64, i64)
);
}
#[test]
fn i64_record3_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 17 }
"#
),
(3, 5, 17),
(i64, i64, i64)
);
}
#[test]
fn f64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1 }
"#
),
(3.1, 5.1),
(f64, f64)
);
}
#[test]
fn f64_record3_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1, z: 17.1 }
"#
),
(3.1, 5.1, 17.1),
(f64, f64, f64)
);
}
#[test]
fn bool_record4_literal() {
assert_evals_to!(
indoc!(
r#"
record : { a : Bool, b : Bool, c : Bool, d : Bool }
record = { a: True, b: False, c : False, d : True }
record
"#
),
[true, false, false, true],
[bool; 4]
);
}
#[test]
fn i64_record9_literal() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
"#
),
[3, 5, 17, 1, 9, 12, 13, 14, 15],
[i64; 9]
);
}
#[test]
fn bool_literal() {
assert_evals_to!(
indoc!(
r#"
x : Bool
x = True
x
"#
),
true,
bool
);
}
// #[test]
// fn optional_field_when_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// main =
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_function_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \{ x ? 10, y } -> x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default_nested() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_singleton_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x : 4 } is
// { x ? 3 } -> x
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn optional_field_empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { } is
// { x ? 3 } -> x
// "#
// ),
// 3,
// i64
// );
// }
#[test]
fn return_record_3() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 4 }
"#
),
(3, 5, 4),
(i64, i64, i64)
);
}
#[test]
fn return_record_4() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2 }
"#
),
[3, 5, 4, 2],
[i64; 4]
);
}
#[test]
fn return_record_5() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1 }
"#
),
[3, 5, 4, 2, 1],
[i64; 5]
);
}
#[test]
fn return_record_6() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
"#
),
[3, 5, 4, 2, 1, 7],
[i64; 6]
);
}
#[test]
fn return_record_7() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
"#
),
[3, 5, 4, 2, 1, 7, 8],
[i64; 7]
);
}
#[test]
fn return_record_float_int() {
assert_evals_to!(
indoc!(
r#"
{ a: 3.14, b: 0x1 }
"#
),
(3.14, 0x1),
(f64, i64)
);
}
#[test]
fn return_record_int_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 0x1, b: 3.14 }
"#
),
(0x1, 3.14),
(i64, f64)
);
}
#[test]
fn return_record_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14 }
"#
),
(6.28, 3.14),
(f64, f64)
);
}
#[test]
fn return_record_float_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14, c: 0.1 }
"#
),
(6.28, 3.14, 0.1),
(f64, f64, f64)
);
}
// #[test]
// fn return_nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
// "#
// ),
// (0x0, (6.28, 3.14, 0.1)),
// (i64, (f64, f64, f64))
// );
// }
// #[test]
// fn accessor() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn accessor_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 }
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn update_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42, bar: 6 }
// { rec & foo: rec.foo + 1 }
// "#
// ),
// (6, 43),
// (i64, i64)
// );
// }
// #[test]
// fn update_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42}
// { rec & foo: rec.foo + 1 }
// "#
// ),
// 43,
// i64
// );
// }
// #[test]
// fn booleans_in_record() {
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
// (true, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
// (false, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
// (true, false),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
// (false, false),
// (bool, bool)
// );
// }
// #[test]
// fn alignment_in_record() {
// assert_evals_to!(
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
// (32i64, true, 2u8),
// (i64, bool, u8)
// );
// }
#[test]
fn stack_memory_return_from_branch() {
// stack memory pointer should end up in the right place after returning from a branch
assert_evals_to!(
indoc!(
r#"
stackMemoryJunk = { x: 999, y: 111 }
if True then
{ x: 123, y: 321 }
else
stackMemoryJunk
"#
),
(123, 321),
(i64, i64)
);
}
// #[test]
// fn blue_and_present() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue, y: 7 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn blue_and_absent() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue }
// "#
// ),
// 3,
// i64
// );
// }
}

View file

@ -231,9 +231,9 @@ impl fmt::Debug for IdentStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// IdentStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] }
f.debug_struct("IdentStr")
.field("is_small_str", &self.is_small_str())
//.field("is_small_str", &self.is_small_str())
.field("string", &self.as_str())
.field("elements", &self.as_slice())
//.field("elements", &self.as_slice())
.finish()
}
}

View file

@ -1,7 +1,7 @@
use roc_can::annotation::IntroducedVariables;
use roc_can::def::{Declaration, Def};
use roc_can::env::Env;
use roc_can::expr::{Expr, Recursive};
use roc_can::expr::{ClosureData, Expr, Recursive};
use roc_can::pattern::Pattern;
use roc_can::scope::Scope;
use roc_collections::all::{MutSet, SendMap};
@ -117,7 +117,7 @@ fn build_effect_always(
let body = Expr::Var(value_symbol);
Expr::Closure {
Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -127,7 +127,7 @@ fn build_effect_always(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
}
})
};
// \value -> @Effect \{} -> value
@ -146,7 +146,7 @@ fn build_effect_always(
)];
let function_var = var_store.fresh();
let closure = Expr::Closure {
let closure = Expr::Closure(ClosureData {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -156,7 +156,7 @@ fn build_effect_always(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
};
});
(function_var, closure)
};
@ -295,7 +295,7 @@ fn build_effect_map(
Located::at_zero(empty_record_pattern(var_store)),
)];
Expr::Closure {
Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -308,7 +308,7 @@ fn build_effect_map(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(mapper_call)),
}
})
};
let arguments = vec![
@ -339,7 +339,7 @@ fn build_effect_map(
};
let function_var = var_store.fresh();
let map_closure = Expr::Closure {
let map_closure = Expr::Closure(ClosureData {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -349,7 +349,7 @@ fn build_effect_map(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
};
});
let mut introduced_variables = IntroducedVariables::default();
@ -509,7 +509,7 @@ fn build_effect_after(
];
let function_var = var_store.fresh();
let after_closure = Expr::Closure {
let after_closure = Expr::Closure(ClosureData {
function_type: function_var,
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -519,7 +519,7 @@ fn build_effect_after(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(to_effect_call)),
};
});
let mut introduced_variables = IntroducedVariables::default();
@ -653,7 +653,7 @@ pub fn build_host_exposed_def(
.unwrap()
};
let effect_closure = Expr::Closure {
let effect_closure = Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -666,7 +666,7 @@ pub fn build_host_exposed_def(
Located::at_zero(empty_record_pattern(var_store)),
)],
loc_body: Box::new(Located::at_zero(low_level_call)),
};
});
let body = Expr::Tag {
variant_var: var_store.fresh(),
@ -675,7 +675,7 @@ pub fn build_host_exposed_def(
arguments: vec![(var_store.fresh(), Located::at_zero(effect_closure))],
};
Expr::Closure {
Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -685,7 +685,7 @@ pub fn build_host_exposed_def(
recursive: Recursive::NotRecursive,
arguments,
loc_body: Box::new(Located::at_zero(body)),
}
})
}
_ => {
// not a function
@ -717,7 +717,7 @@ pub fn build_host_exposed_def(
destructs: vec![],
};
let effect_closure = Expr::Closure {
let effect_closure = Expr::Closure(ClosureData {
function_type: var_store.fresh(),
closure_type: var_store.fresh(),
closure_ext_var: var_store.fresh(),
@ -727,7 +727,7 @@ pub fn build_host_exposed_def(
recursive: Recursive::NotRecursive,
arguments: vec![(var_store.fresh(), Located::at_zero(empty_record_pattern))],
loc_body: Box::new(Located::at_zero(low_level_call)),
};
});
Expr::Tag {
variant_var: var_store.fresh(),

View file

@ -8,7 +8,7 @@ use roc_builtins::std::StdLib;
use roc_can::constraint::Constraint;
use roc_can::def::{Declaration, Def};
use roc_can::module::{canonicalize_module_defs, Module};
use roc_collections::all::{default_hasher, BumpMap, BumpMapDefault, BumpSet, MutMap, MutSet};
use roc_collections::all::{default_hasher, BumpMap, MutMap, MutSet};
use roc_constrain::module::{
constrain_imports, pre_constrain_imports, ConstrainableImports, Import,
};
@ -553,7 +553,7 @@ fn start_phase<'a>(
ident_ids,
} = typechecked;
let mut imported_module_thunks = BumpSet::new_in(arena);
let mut imported_module_thunks = bumpalo::collections::Vec::new_in(arena);
if let Some(imports) = state.module_cache.imports.get(&module_id) {
for imported in imports.iter() {
@ -570,7 +570,7 @@ fn start_phase<'a>(
module_id,
module_timing,
solved_subs,
imported_module_thunks,
imported_module_thunks: imported_module_thunks.into_bump_slice(),
decls,
ident_ids,
exposed_to_host: state.exposed_to_host.clone(),
@ -593,7 +593,7 @@ fn start_phase<'a>(
module_id,
ident_ids,
subs,
procs,
procs_base,
layout_cache,
module_timing,
} = found_specializations;
@ -602,7 +602,7 @@ fn start_phase<'a>(
module_id,
ident_ids,
subs,
procs,
procs_base,
layout_cache,
specializations_we_must_make,
module_timing,
@ -646,6 +646,13 @@ impl LoadedModule {
total
}
pub fn exposed_values_str(&self) -> Vec<&str> {
self.exposed_values
.iter()
.map(|symbol| symbol.ident_str(&self.interns).as_str())
.collect()
}
}
#[derive(Debug)]
@ -710,13 +717,13 @@ pub struct TypeCheckedModule<'a> {
}
#[derive(Debug)]
pub struct FoundSpecializationsModule<'a> {
pub module_id: ModuleId,
pub ident_ids: IdentIds,
pub layout_cache: LayoutCache<'a>,
pub procs: Procs<'a>,
pub subs: Subs,
pub module_timing: ModuleTiming,
struct FoundSpecializationsModule<'a> {
module_id: ModuleId,
ident_ids: IdentIds,
layout_cache: LayoutCache<'a>,
procs_base: ProcsBase<'a>,
subs: Subs,
module_timing: ModuleTiming,
}
#[derive(Debug)]
@ -815,7 +822,7 @@ enum Msg<'a> {
module_id: ModuleId,
ident_ids: IdentIds,
layout_cache: LayoutCache<'a>,
procs: Procs<'a>,
procs_base: ProcsBase<'a>,
problems: Vec<roc_mono::ir::MonoProblem>,
solved_subs: Solved<Subs>,
module_timing: ModuleTiming,
@ -997,7 +1004,7 @@ impl ModuleTiming {
.checked_sub(*read_roc_file)
};
calculate(end_time.duration_since(*start_time)).unwrap_or_else(Duration::default)
calculate(end_time.duration_since(*start_time)).unwrap_or_default()
}
}
@ -1035,7 +1042,7 @@ enum BuildTask<'a> {
module_timing: ModuleTiming,
layout_cache: LayoutCache<'a>,
solved_subs: Solved<Subs>,
imported_module_thunks: BumpSet<Symbol>,
imported_module_thunks: &'a [Symbol],
module_id: ModuleId,
ident_ids: IdentIds,
decls: Vec<Declaration>,
@ -1045,7 +1052,7 @@ enum BuildTask<'a> {
module_id: ModuleId,
ident_ids: IdentIds,
subs: Subs,
procs: Procs<'a>,
procs_base: ProcsBase<'a>,
layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>,
module_timing: ModuleTiming,
@ -2050,7 +2057,7 @@ fn update<'a>(
}
FoundSpecializations {
module_id,
procs,
procs_base,
solved_subs,
ident_ids,
layout_cache,
@ -2060,8 +2067,7 @@ fn update<'a>(
log!("found specializations for {:?}", module_id);
let subs = solved_subs.into_inner();
if let Some(pending) = &procs.pending_specializations {
for (symbol, specs) in pending {
for (symbol, specs) in &procs_base.specializations_for_host {
let existing = match state.all_pending_specializations.entry(*symbol) {
Vacant(entry) => entry.insert(MutMap::default()),
Occupied(entry) => entry.into_mut(),
@ -2071,20 +2077,19 @@ fn update<'a>(
existing.insert(*layout, pend.clone());
}
}
}
state
.module_cache
.top_level_thunks
.entry(module_id)
.or_default()
.extend(procs.module_thunks.iter().copied());
.extend(procs_base.module_thunks.iter().copied());
let found_specializations_module = FoundSpecializationsModule {
module_id,
ident_ids,
layout_cache,
procs,
procs_base,
subs,
module_timing,
};
@ -3930,7 +3935,7 @@ fn make_specializations<'a>(
home: ModuleId,
mut ident_ids: IdentIds,
mut subs: Subs,
mut procs: Procs<'a>,
procs_base: ProcsBase<'a>,
mut layout_cache: LayoutCache<'a>,
specializations_we_must_make: ExternalSpecializations<'a>,
mut module_timing: ModuleTiming,
@ -3951,6 +3956,16 @@ fn make_specializations<'a>(
call_specialization_counter: 1,
};
let mut procs = Procs::new_in(arena);
for (symbol, partial_proc) in procs_base.partial_procs.into_iter() {
procs.partial_procs.insert(symbol, partial_proc);
}
procs.module_thunks = procs_base.module_thunks;
procs.runtime_errors = procs_base.runtime_errors;
procs.imported_module_thunks = procs_base.imported_module_thunks;
// TODO: for now this final specialization pass is sequential,
// with no parallelization at all. We should try to parallelize
// this, but doing so will require a redesign of Procs.
@ -3958,12 +3973,16 @@ fn make_specializations<'a>(
&mut mono_env,
procs,
specializations_we_must_make,
procs_base.specializations_for_host,
&mut layout_cache,
);
let external_specializations_requested = procs.externals_we_need.clone();
let procedures = procs.get_specialized_procs_without_rc(&mut mono_env);
// Turn `Bytes.Decode.IdentId(238)` into `Bytes.Decode.238`, we rely on this in mono tests
mono_env.home.register_debug_idents(mono_env.ident_ids);
let make_specializations_end = SystemTime::now();
module_timing.make_specializations = make_specializations_end
.duration_since(make_specializations_start)
@ -3981,11 +4000,37 @@ fn make_specializations<'a>(
}
}
#[derive(Clone, Debug)]
struct ProcsBase<'a> {
partial_procs: BumpMap<Symbol, PartialProc<'a>>,
module_thunks: &'a [Symbol],
/// A host-exposed function must be specialized; it's a seed for subsequent specializations
specializations_for_host: BumpMap<Symbol, MutMap<ProcLayout<'a>, PendingSpecialization<'a>>>,
runtime_errors: BumpMap<Symbol, &'a str>,
imported_module_thunks: &'a [Symbol],
}
impl<'a> ProcsBase<'a> {
fn add_specialization_for_host(
&mut self,
symbol: Symbol,
layout: ProcLayout<'a>,
pending: PendingSpecialization<'a>,
) {
let all_pending = self
.specializations_for_host
.entry(symbol)
.or_insert_with(|| HashMap::with_capacity_and_hasher(1, default_hasher()));
all_pending.insert(layout, pending);
}
}
#[allow(clippy::too_many_arguments)]
fn build_pending_specializations<'a>(
arena: &'a Bump,
solved_subs: Solved<Subs>,
imported_module_thunks: BumpSet<Symbol>,
imported_module_thunks: &'a [Symbol],
home: ModuleId,
mut ident_ids: IdentIds,
decls: Vec<Declaration>,
@ -3996,10 +4041,16 @@ fn build_pending_specializations<'a>(
exposed_to_host: MutMap<Symbol, Variable>,
) -> Msg<'a> {
let find_specializations_start = SystemTime::now();
let mut procs = Procs::new_in(arena);
debug_assert!(procs.imported_module_thunks.is_empty());
procs.imported_module_thunks = imported_module_thunks;
let mut module_thunks = bumpalo::collections::Vec::new_in(arena);
let mut procs_base = ProcsBase {
partial_procs: BumpMap::default(),
module_thunks: &[],
specializations_for_host: BumpMap::default(),
runtime_errors: BumpMap::default(),
imported_module_thunks,
};
let mut mono_problems = std::vec::Vec::new();
let mut subs = solved_subs.into_inner();
@ -4022,7 +4073,8 @@ fn build_pending_specializations<'a>(
match decl {
Declare(def) | Builtin(def) => add_def_to_module(
&mut layout_cache,
&mut procs,
&mut procs_base,
&mut module_thunks,
&mut mono_env,
def,
&exposed_to_host,
@ -4032,7 +4084,8 @@ fn build_pending_specializations<'a>(
for def in defs {
add_def_to_module(
&mut layout_cache,
&mut procs,
&mut procs_base,
&mut module_thunks,
&mut mono_env,
def,
&exposed_to_host,
@ -4047,6 +4100,8 @@ fn build_pending_specializations<'a>(
}
}
procs_base.module_thunks = module_thunks.into_bump_slice();
let problems = mono_env.problems.to_vec();
let find_specializations_end = SystemTime::now();
@ -4059,7 +4114,7 @@ fn build_pending_specializations<'a>(
solved_subs: roc_types::solved_types::Solved(subs),
ident_ids,
layout_cache,
procs,
procs_base,
problems,
module_timing,
}
@ -4067,12 +4122,14 @@ fn build_pending_specializations<'a>(
fn add_def_to_module<'a>(
layout_cache: &mut LayoutCache<'a>,
procs: &mut Procs<'a>,
procs: &mut ProcsBase<'a>,
module_thunks: &mut bumpalo::collections::Vec<'a, Symbol>,
mono_env: &mut roc_mono::ir::Env<'a, '_>,
def: roc_can::def::Def,
exposed_to_host: &MutMap<Symbol, Variable>,
is_recursive: bool,
) {
use roc_can::expr::ClosureData;
use roc_can::expr::Expr::*;
use roc_can::pattern::Pattern::*;
@ -4081,14 +4138,14 @@ fn add_def_to_module<'a>(
let is_exposed = exposed_to_host.contains_key(&symbol);
match def.loc_expr.value {
Closure {
Closure(ClosureData {
function_type: annotation,
return_type: ret_var,
arguments: loc_args,
loc_body,
captured_symbols,
..
} => {
}) => {
// this is a top-level definition, it should not capture anything
debug_assert!(captured_symbols.is_empty());
@ -4097,15 +4154,6 @@ fn add_def_to_module<'a>(
// never gets called by Roc code, it will never
// get specialized!
if is_exposed {
let mut pattern_vars = bumpalo::collections::Vec::with_capacity_in(
loc_args.len(),
mono_env.arena,
);
for (var, _) in loc_args.iter() {
pattern_vars.push(*var);
}
let layout = match layout_cache.raw_from_var(
mono_env.arena,
annotation,
@ -4129,20 +4177,23 @@ fn add_def_to_module<'a>(
}
};
procs.insert_exposed(
symbol,
ProcLayout::from_raw(mono_env.arena, layout),
let pending = PendingSpecialization::from_exposed_function(
mono_env.arena,
mono_env.subs,
def.annotation,
annotation,
);
procs.add_specialization_for_host(
symbol,
ProcLayout::from_raw(mono_env.arena, layout),
pending,
);
}
procs.insert_named(
let partial_proc = PartialProc::from_named_function(
mono_env,
layout_cache,
symbol,
annotation,
loc_args,
*loc_body,
@ -4150,10 +4201,12 @@ fn add_def_to_module<'a>(
is_recursive,
ret_var,
);
procs.partial_procs.insert(symbol, partial_proc);
}
body => {
// mark this symbols as a top-level thunk before any other work on the procs
procs.module_thunks.insert(symbol);
module_thunks.push(symbol);
// If this is an exposed symbol, we need to
// register it as such. Otherwise, since it
@ -4188,14 +4241,14 @@ fn add_def_to_module<'a>(
}
};
procs.insert_exposed(
symbol,
top_level,
let pending = PendingSpecialization::from_exposed_function(
mono_env.arena,
mono_env.subs,
def.annotation,
annotation,
);
procs.add_specialization_for_host(symbol, top_level, pending);
}
let proc = PartialProc {
@ -4309,7 +4362,7 @@ where
module_id,
ident_ids,
subs,
procs,
procs_base,
layout_cache,
specializations_we_must_make,
module_timing,
@ -4318,7 +4371,7 @@ where
module_id,
ident_ids,
subs,
procs,
procs_base,
layout_cache,
specializations_we_must_make,
module_timing,
@ -4477,7 +4530,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a interface file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
@ -4493,7 +4546,7 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
let doc = alloc.stack(vec![
alloc.reflow(r"The input file is a package config file, but only app modules can be ran."),
alloc.concat(vec![
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies,"),
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);

View file

@ -12,8 +12,8 @@ roc_collections = { path = "../collections" }
bumpalo = { version = "3.6.1", features = ["collections"] }
lazy_static = "1.4"
static_assertions = "1.1.0"
snafu = { version = "0.6", features = ["backtraces"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"

View file

@ -4,7 +4,7 @@ use std::fmt;
/// This could be uppercase or lowercase, qualified or unqualified.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Ident(IdentStr);
pub struct Ident(pub IdentStr);
impl Ident {
pub fn as_inline_str(&self) -> &IdentStr {

View file

@ -4,6 +4,7 @@
pub mod ident;
pub mod low_level;
pub mod module_err;
pub mod operator;
pub mod symbol;

View file

@ -17,6 +17,7 @@ pub enum LowLevel {
StrToUtf8,
StrRepeat,
StrFromFloat,
StrTrim,
ListLen,
ListGetUnsafe,
ListSet,
@ -32,6 +33,7 @@ pub enum LowLevel {
ListMap,
ListMap2,
ListMap3,
ListMap4,
ListMapWithIndex,
ListKeepIf,
ListWalk,
@ -123,6 +125,7 @@ macro_rules! first_order {
| StrFromUtf8Range
| StrToUtf8
| StrRepeat
| StrTrim
| StrFromFloat
| ListLen
| ListGetUnsafe
@ -209,6 +212,7 @@ macro_rules! higher_order {
ListMap
| ListMap2
| ListMap3
| ListMap4
| ListMapWithIndex
| ListKeepIf
| ListWalk
@ -241,6 +245,7 @@ impl LowLevel {
ListMap => 1,
ListMap2 => 2,
ListMap3 => 3,
ListMap4 => 4,
ListMapWithIndex => 1,
ListKeepIf => 1,
ListWalk => 2,

View file

@ -0,0 +1,30 @@
use snafu::{Backtrace, Snafu};
use crate::symbol::IdentId;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum ModuleError {
#[snafu(display(
"ModuleIdNotFound: I could not find the ModuleId {} in Interns.all_ident_ids: {}.",
module_id,
all_ident_ids
))]
ModuleIdNotFound {
module_id: String,
all_ident_ids: String,
backtrace: Backtrace,
},
#[snafu(display(
"IdentIdNotFound: I could not find IdentId {:?} in ident_ids {:?}.",
ident_id,
ident_ids_str
))]
IdentIdNotFound {
ident_id: IdentId,
ident_ids_str: String,
backtrace: Backtrace,
},
}
pub type ModuleResult<T, E = ModuleError> = std::result::Result<T, E>;

View file

@ -1,7 +1,9 @@
use crate::ident::{Ident, ModuleName};
use crate::module_err::{IdentIdNotFound, ModuleIdNotFound, ModuleResult};
use roc_collections::all::{default_hasher, MutMap, SendMap};
use roc_ident::IdentStr;
use roc_region::all::Region;
use snafu::OptionExt;
use std::collections::HashMap;
use std::{fmt, u32};
@ -253,6 +255,30 @@ impl Interns {
}
}
pub fn get_module_ident_ids<'a>(
all_ident_ids: &'a MutMap<ModuleId, IdentIds>,
module_id: &ModuleId,
) -> ModuleResult<&'a IdentIds> {
all_ident_ids
.get(module_id)
.with_context(|| ModuleIdNotFound {
module_id: format!("{:?}", module_id),
all_ident_ids: format!("{:?}", all_ident_ids),
})
}
pub fn get_module_ident_ids_mut<'a>(
all_ident_ids: &'a mut MutMap<ModuleId, IdentIds>,
module_id: &ModuleId,
) -> ModuleResult<&'a mut IdentIds> {
all_ident_ids
.get_mut(module_id)
.with_context(|| ModuleIdNotFound {
module_id: format!("{:?}", module_id),
all_ident_ids: "I could not return all_ident_ids here because of borrowing issues.",
})
}
#[cfg(debug_assertions)]
lazy_static! {
/// This is used in Debug builds only, to let us have a Debug instance
@ -536,15 +562,17 @@ impl IdentIds {
}
pub fn get_or_insert(&mut self, name: &Ident) -> IdentId {
match self.by_ident.get(name) {
Some(id) => *id,
None => {
use std::collections::hash_map::Entry;
match self.by_ident.entry(name.clone()) {
Entry::Occupied(occupied) => *occupied.get(),
Entry::Vacant(vacant) => {
let by_id = &mut self.by_id;
let ident_id = IdentId(by_id.len() as u32);
by_id.push(name.clone());
self.by_ident.insert(name.clone(), ident_id);
vacant.insert(ident_id);
ident_id
}
@ -621,6 +649,17 @@ impl IdentIds {
pub fn get_name(&self, id: IdentId) -> Option<&Ident> {
self.by_id.get(id.0 as usize)
}
pub fn get_name_str_res(&self, ident_id: IdentId) -> ModuleResult<&str> {
Ok(self
.get_name(ident_id)
.with_context(|| IdentIdNotFound {
ident_id,
ident_ids_str: format!("{:?}", self),
})?
.as_inline_str()
.as_str())
}
}
// BUILTINS
@ -978,6 +1017,7 @@ define_builtins! {
17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
18 STR_FROM_UTF8_RANGE: "fromUtf8Range"
19 STR_REPEAT: "repeat"
20 STR_TRIM: "trim"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
@ -1015,6 +1055,12 @@ define_builtins! {
32 LIST_DROP: "drop"
33 LIST_SWAP: "swap"
34 LIST_DROP_AT: "dropAt"
35 LIST_DROP_LAST: "dropLast"
36 LIST_MIN: "min"
37 LIST_MIN_LT: "#minlt"
38 LIST_MAX: "max"
39 LIST_MAX_GT: "#maxGt"
40 LIST_MAP4: "map4"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -29,7 +29,6 @@ roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View file

@ -10,7 +10,8 @@ use roc_module::symbol::Symbol;
use std::convert::TryFrom;
use crate::ir::{
Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, Proc, Stmt,
Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, OptLevel,
Proc, Stmt,
};
use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout};
@ -109,6 +110,7 @@ fn bytes_as_ascii(bytes: &[u8]) -> String {
}
pub fn spec_program<'a, I>(
opt_level: OptLevel,
entry_point: crate::ir::EntryPoint<'a>,
procs: I,
) -> Result<morphic_lib::Solutions>
@ -239,7 +241,10 @@ where
eprintln!("{}", program.to_source_string());
}
morphic_lib::solve(program)
match opt_level {
OptLevel::Development | OptLevel::Normal => morphic_lib::solve_trivial(program),
OptLevel::Optimize => morphic_lib::solve(program),
}
}
/// if you want an "escape hatch" which allows you construct "best-case scenario" values
@ -556,6 +561,84 @@ fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Resul
builder.add_tuple_type(&field_types)
}
#[repr(u32)]
#[derive(Clone, Copy)]
enum KeepResult {
Errs = ERR_TAG_ID,
Oks = OK_TAG_ID,
}
impl KeepResult {
fn invert(&self) -> Self {
match self {
KeepResult::Errs => KeepResult::Oks,
KeepResult::Oks => KeepResult::Errs,
}
}
}
#[derive(Clone, Copy)]
enum ResultRepr<'a> {
Int1,
NonRecursive { err: Layout<'a>, ok: Layout<'a> },
}
impl<'a> ResultRepr<'a> {
fn from_layout(layout: &Layout<'a>) -> Self {
match layout {
Layout::Union(UnionLayout::NonRecursive(tags)) => ResultRepr::NonRecursive {
err: tags[ERR_TAG_ID as usize][0],
ok: tags[OK_TAG_ID as usize][0],
},
Layout::Builtin(Builtin::Int1) => ResultRepr::Int1,
other => unreachable!("unexpected layout: {:?}", other),
}
}
fn unwrap(
&self,
builder: &mut FuncDefBuilder,
block: BlockId,
err_or_ok: ValueId,
keep_tag_id: u32,
) -> Result<ValueId> {
match self {
ResultRepr::NonRecursive { .. } => {
let unwrapped = builder.add_unwrap_union(block, err_or_ok, keep_tag_id)?;
builder.add_get_tuple_field(block, unwrapped, 0)
}
ResultRepr::Int1 => builder.add_make_tuple(block, &[]),
}
}
}
fn add_loop(
builder: &mut FuncDefBuilder,
block: BlockId,
state_type: TypeId,
init_state: ValueId,
make_body: impl for<'a> FnOnce(&'a mut FuncDefBuilder, BlockId, ValueId) -> Result<ValueId>,
) -> Result<ValueId> {
let sub_block = builder.add_block();
let (loop_cont, loop_arg) = builder.declare_continuation(sub_block, state_type, state_type)?;
let body = builder.add_block();
let ret_branch = builder.add_block();
let loop_branch = builder.add_block();
let new_state = make_body(builder, loop_branch, loop_arg)?;
let unreachable = builder.add_jump(loop_branch, loop_cont, new_state, state_type)?;
let result = builder.add_choice(
body,
&[
BlockExpr(ret_branch, loop_arg),
BlockExpr(loop_branch, unreachable),
],
)?;
builder.define_continuation(loop_cont, BlockExpr(body, result))?;
let unreachable = builder.add_jump(sub_block, loop_cont, init_state, state_type)?;
builder.add_sub_block(block, BlockExpr(sub_block, unreachable))
}
fn call_spec(
builder: &mut FuncDefBuilder,
env: &Env,
@ -608,6 +691,7 @@ fn call_spec(
HigherOrderLowLevel {
specialization_id,
closure_env_layout,
update_mode,
op,
arg_layouts,
ret_layout,
@ -615,192 +699,403 @@ fn call_spec(
function_env,
..
} => {
use crate::low_level::HigherOrder::*;
let array = specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array);
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(*function_name, it, *ret_layout);
let name = FuncName(&bytes);
let module = MOD_APP;
use crate::low_level::HigherOrder::*;
let closure_env = env.symbols[function_env];
macro_rules! call_function {
($builder: expr, $block:expr, [$($arg:expr),+ $(,)?]) => {{
let argument = if closure_env_layout.is_none() {
$builder.add_make_tuple($block, &[$($arg),+])?
} else {
$builder.add_make_tuple($block, &[$($arg),+, closure_env])?
};
$builder.add_call($block, spec_var, module, name, argument)?
}};
}
match op {
DictWalk { xs, state } => {
let dict = env.symbols[xs];
let state = env.symbols[state];
let closure_env = env.symbols[function_env];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let element = builder.add_bag_get(block, bag)?;
let key = builder.add_get_tuple_field(block, first, 0)?;
let val = builder.add_get_tuple_field(block, first, 1)?;
let key = builder.add_get_tuple_field(block, element, 0)?;
let val = builder.add_get_tuple_field(block, element, 1)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[state, key, val])?
} else {
builder.add_make_tuple(block, &[state, key, val, closure_env])?
let new_state = call_function!(builder, block, [state, key, val]);
Ok(new_state)
};
builder.add_call(block, spec_var, module, name, argument)?;
let state_layout = arg_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListWalk { xs, state }
| ListWalkBackwards { xs, state }
| ListWalkUntil { xs, state } => {
ListWalk { xs, state } | ListWalkBackwards { xs, state } => {
let list = env.symbols[xs];
let state = env.symbols[state];
let closure_env = env.symbols[function_env];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let element = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[state, first])?
} else {
builder.add_make_tuple(block, &[state, first, closure_env])?
let new_state = call_function!(builder, block, [state, element]);
Ok(new_state)
};
builder.add_call(block, spec_var, module, name, argument)?;
let state_layout = arg_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListWalkUntil { xs, state } => {
let list = env.symbols[xs];
let state = env.symbols[state];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let continue_or_stop = call_function!(builder, block, [state, element]);
// just assume it is a continue
let unwrapped = builder.add_unwrap_union(block, continue_or_stop, 0)?;
let new_state = builder.add_get_tuple_field(block, unwrapped, 0)?;
Ok(new_state)
};
let state_layout = arg_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMapWithIndex { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let element = builder.add_bag_get(block, input_bag)?;
let index = builder.add_make_tuple(block, &[])?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[index, first])?
} else {
builder.add_make_tuple(block, &[index, first, closure_env])?
let new_element = call_function!(builder, block, [index, element]);
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let element = builder.add_bag_get(block, input_bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1])?
} else {
builder.add_make_tuple(block, &[elem1, closure_env])?
let new_element = call_function!(builder, block, [element]);
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListSortWith { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, state, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, state, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let element_1 = builder.add_bag_get(block, bag)?;
let element_2 = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem1])?
} else {
builder.add_make_tuple(block, &[elem1, elem1, closure_env])?
let _ = call_function!(builder, block, [element_1, element_2]);
builder.add_update(block, update_mode_var, cell)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
};
builder.add_call(block, spec_var, module, name, argument)?;
let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap2 { xs, ys } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, closure_env])?
let new_element = call_function!(builder, block, [element_1, element_2]);
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap3 { xs, ys, zs } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let list3 = env.symbols[zs];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let input_bag_3 =
builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let element_3 = builder.add_bag_get(block, input_bag_3)?;
let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?;
let elem3 = builder.add_bag_get(block, bag3)?;
let new_element =
call_function!(builder, block, [element_1, element_2, element_3]);
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2, elem3])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])?
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap4 { xs, ys, zs, ws } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let list3 = env.symbols[zs];
let list4 = env.symbols[ws];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let input_bag_3 =
builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let input_bag_4 =
builder.add_get_tuple_field(block, list4, LIST_BAG_INDEX)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let element_3 = builder.add_bag_get(block, input_bag_3)?;
let element_4 = builder.add_bag_get(block, input_bag_4)?;
let new_element = call_function!(
builder,
block,
[element_1, element_2, element_3, element_4]
);
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => {
ListKeepIf { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
// let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, state, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, state, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let element = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[first])?
} else {
builder.add_make_tuple(block, &[first, closure_env])?
let _ = call_function!(builder, block, [element]);
// NOTE: we assume the element is not kept
builder.add_update(block, update_mode_var, cell)?;
let removed = builder.add_bag_remove(block, bag)?;
// decrement the removed element
let removed_element = builder.add_get_tuple_field(block, removed, 1)?;
builder.add_recursive_touch(block, removed_element)?;
let new_bag = builder.add_get_tuple_field(block, removed, 0)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, new_bag])
};
let result = builder.add_call(block, spec_var, module, name, argument)?;
let unit = builder.add_tuple_type(&[])?;
builder.add_unknown_with(block, &[result], unit)?;
}
}
// TODO overly pessimstic
// filter_map because one of the arguments is a function name, which
// is not defined in the env
let arguments: Vec<_> = call
.arguments
.iter()
.filter_map(|symbol| env.symbols.get(symbol))
.copied()
.collect();
let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = list;
let result_type = layout_spec(builder, layout)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListKeepOks { xs } | ListKeepErrs { xs } => {
let list = env.symbols[xs];
builder.add_unknown_with(block, &arguments, result_type)
let keep_result = match op {
ListKeepOks { .. } => KeepResult::Oks,
ListKeepErrs { .. } => KeepResult::Errs,
_ => unreachable!(),
};
let result_repr = ResultRepr::from_layout(ret_layout);
let output_element_layout = match (keep_result, result_repr) {
(KeepResult::Errs, ResultRepr::NonRecursive { err, .. }) => err,
(KeepResult::Oks, ResultRepr::NonRecursive { ok, .. }) => ok,
(_, ResultRepr::Int1) => Layout::Struct(&[]),
};
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let err_or_ok = call_function!(builder, block, [element]);
let kept_branch = builder.add_block();
let not_kept_branch = builder.add_block();
let element_kept = {
let block = kept_branch;
// a Result can be represented as a Int1
let new_element = result_repr.unwrap(
builder,
block,
err_or_ok,
keep_result as u32,
)?;
list_append(builder, block, update_mode_var, state, new_element)?
};
let element_not_kept = {
let block = not_kept_branch;
// a Result can be represented as a Int1
let dropped_element = result_repr.unwrap(
builder,
block,
err_or_ok,
keep_result.invert() as u32,
)?;
// decrement the element we will not keep
builder.add_recursive_touch(block, dropped_element)?;
state
};
builder.add_choice(
block,
&[
BlockExpr(not_kept_branch, element_not_kept),
BlockExpr(kept_branch, element_kept),
],
)
};
let output_element_type = layout_spec(builder, &output_element_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
let state_layout = Layout::Builtin(Builtin::List(&output_element_layout));
let state_type = layout_spec(builder, &state_layout)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
}
}
}
}
fn list_append(
builder: &mut FuncDefBuilder,
block: BlockId,
update_mode_var: UpdateModeVar,
list: ValueId,
to_insert: ValueId,
) -> Result<ValueId> {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
let new_bag = builder.add_bag_insert(block, bag, to_insert)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, new_bag])
}
fn lowlevel_spec(
@ -878,6 +1173,10 @@ fn lowlevel_spec(
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
// decrement the overwritten element
let overwritten = builder.add_bag_get(block, bag)?;
let _unit = builder.add_recursive_touch(block, overwritten)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
builder.add_bag_insert(block, bag, to_insert)?;
@ -911,16 +1210,7 @@ fn lowlevel_spec(
let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[1]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
// TODO new heap cell
builder.add_bag_insert(block, bag, to_insert)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
list_append(builder, block, update_mode_var, list, to_insert)
}
StrToUtf8 => {
let string = env.symbols[&arguments[0]];
@ -1420,8 +1710,8 @@ fn static_list_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
builder.add_tuple_type(&[cell, bag])
}
// const OK_TAG_ID: u8 = 1u8;
// const ERR_TAG_ID: u8 = 0u8;
const OK_TAG_ID: u32 = 1;
const ERR_TAG_ID: u32 = 0;
const LIST_CELL_INDEX: u32 = 0;
const LIST_BAG_INDEX: u32 = 1;

View file

@ -651,6 +651,21 @@ impl<'a> BorrowInfState<'a> {
self.own_var(*zs);
}
}
ListMap4 { xs, ys, zs, ws } => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(*xs);
}
if !function_ps[1].borrow {
self.own_var(*ys);
}
if !function_ps[2].borrow {
self.own_var(*zs);
}
if !function_ps[3].borrow {
self.own_var(*ws);
}
}
ListSortWith { xs } => {
// always own the input list
self.own_var(*xs);
@ -922,6 +937,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListGetUnsafe => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListConcat => arena.alloc_slice_copy(&[owned, owned]),
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
StrTrim => arena.alloc_slice_copy(&[owned]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),
@ -932,6 +948,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, function, closure_data]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]),
ListKeepIf | ListKeepOks | ListKeepErrs => {
arena.alloc_slice_copy(&[owned, function, closure_data])
}

View file

@ -179,16 +179,12 @@ impl<'a, 'i> Env<'a, 'i> {
pub fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique();
self.home.register_debug_idents(self.ident_ids);
Symbol::new(self.home, ident_id)
}
#[allow(dead_code)]
fn manual_unique_symbol(home: ModuleId, ident_ids: &mut IdentIds) -> Symbol {
let ident_id = ident_ids.gen_unique();
home.register_debug_idents(ident_ids);
Symbol::new(home, ident_id)
}
}

View file

@ -467,6 +467,7 @@ impl<'a> Context<'a> {
op,
closure_env_layout,
specialization_id,
update_mode,
arg_layouts,
ret_layout,
function_name,
@ -485,6 +486,7 @@ impl<'a> Context<'a> {
closure_env_layout: *closure_env_layout,
function_owns_closure_data: true,
specialization_id: *specialization_id,
update_mode: *update_mode,
function_name: *function_name,
function_env: *function_env,
arg_layouts,
@ -576,6 +578,27 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMap4 { xs, ys, zs, ws } => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
function_ps[2].borrow,
function_ps[3].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b);
let b = decref_if_owned!(function_ps[3].borrow, *ws, b);
let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMapWithIndex { xs } => {
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];

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