mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 06:44:46 +00:00
Merge branch 'trunk' of github.com:rtfeldman/roc into str-split
This commit is contained in:
commit
415a37a891
86 changed files with 5156 additions and 2360 deletions
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
|
@ -6,14 +6,15 @@ jobs:
|
||||||
test:
|
test:
|
||||||
name: fmt, clippy, test, test --release
|
name: fmt, clippy, test, test --release
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 60
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v1
|
- uses: actions/checkout@v1
|
||||||
|
|
||||||
- name: Verify compiler/builtin/bitcode/regenerate.sh was run if necessary
|
- name: Verify compiler/builtin/bitcode/regenerate.sh was run if necessary
|
||||||
run: pushd compiler/builtins/bitcode && ./regenerate.sh && git diff --exit-code ../../gen/src/llvm/builtins.bc && popd
|
run: pushd compiler/builtins/bitcode && ./regenerate.sh && git diff --exit-code ../../gen/src/llvm/builtins.bc && popd
|
||||||
|
|
||||||
- name: Install LLVM
|
- name: Install CI Libraries
|
||||||
run: sudo ./ci/install-llvm.sh 10
|
run: sudo ./ci/install-ci-libraries.sh 10
|
||||||
|
|
||||||
- name: Enable LLD
|
- name: Enable LLD
|
||||||
run: sudo ./ci/enable-lld.sh
|
run: sudo ./ci/enable-lld.sh
|
||||||
|
|
|
@ -1,9 +1,21 @@
|
||||||
# Building the Roc compiler from source
|
# Building the Roc compiler from source
|
||||||
|
|
||||||
|
|
||||||
## Installing LLVM and libc++abi
|
## Installing LLVM, valgrind, libunwind, and libc++-dev
|
||||||
|
|
||||||
To build the compiler, you need both `libc++abi` and a particular version of LLVM installed on your system. Some systems may already have `libc++abi` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `apt-get install libc++abi-dev`.)
|
To build the compiler, you need these installed:
|
||||||
|
|
||||||
|
* `libunwind` (macOS should already have this one installed)
|
||||||
|
* `libc++-dev`
|
||||||
|
* a particular version of LLVM
|
||||||
|
|
||||||
|
To run the test suite (via `cargo test`), you additionally need to install:
|
||||||
|
|
||||||
|
* [`valgrind`](https://www.valgrind.org/)
|
||||||
|
|
||||||
|
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.) macOS systems
|
||||||
|
should already have `libunwind`, but other systems will need to install it
|
||||||
|
(e.g. with `sudo apt-get install libunwind-dev`).
|
||||||
|
|
||||||
To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need.
|
To see which version of LLVM you need, take a look at `Cargo.toml`, in particular the `branch` section of the `inkwell` dependency. It should have something like `llvmX-Y` where X and Y are the major and minor revisions of LLVM you need.
|
||||||
|
|
||||||
|
|
196
Cargo.lock
generated
196
Cargo.lock
generated
|
@ -16,6 +16,21 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2692800d602527d2b8fea50036119c37df74ab565b10e285706a3dcec0ec3e16"
|
checksum = "2692800d602527d2b8fea50036119c37df74ab565b10e285706a3dcec0ec3e16"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.13.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072"
|
||||||
|
dependencies = [
|
||||||
|
"gimli",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.13"
|
version = "0.7.13"
|
||||||
|
@ -120,6 +135,20 @@ version = "1.0.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "backtrace"
|
||||||
|
version = "0.3.52"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f813291114c186a042350e787af10c26534601062603d888be110f59f85ef8fa"
|
||||||
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"miniz_oxide",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
|
@ -279,6 +308,15 @@ dependencies = [
|
||||||
"bitflags",
|
"bitflags",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cloudabi"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cocoa"
|
name = "cocoa"
|
||||||
version = "0.20.2"
|
version = "0.20.2"
|
||||||
|
@ -634,6 +672,12 @@ version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fixedbitset"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.7"
|
version = "1.0.7"
|
||||||
|
@ -829,7 +873,7 @@ dependencies = [
|
||||||
"gfx-hal",
|
"gfx-hal",
|
||||||
"libloading",
|
"libloading",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot 0.10.2",
|
||||||
"range-alloc",
|
"range-alloc",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
@ -885,7 +929,7 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
"metal",
|
"metal",
|
||||||
"objc",
|
"objc",
|
||||||
"parking_lot",
|
"parking_lot 0.10.2",
|
||||||
"range-alloc",
|
"range-alloc",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
@ -947,6 +991,12 @@ dependencies = [
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "glyph_brush"
|
name = "glyph_brush"
|
||||||
version = "0.7.0"
|
version = "0.7.0"
|
||||||
|
@ -1119,7 +1169,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"llvm-sys",
|
"llvm-sys",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"parking_lot",
|
"parking_lot 0.10.2",
|
||||||
"regex",
|
"regex",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1259,6 +1309,15 @@ dependencies = [
|
||||||
"scopeguard",
|
"scopeguard",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.11"
|
version = "0.4.11"
|
||||||
|
@ -1328,6 +1387,16 @@ dependencies = [
|
||||||
"objc",
|
"objc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
|
||||||
|
dependencies = [
|
||||||
|
"adler",
|
||||||
|
"autocfg 1.0.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "mio"
|
name = "mio"
|
||||||
version = "0.6.22"
|
version = "0.6.22"
|
||||||
|
@ -1519,6 +1588,12 @@ dependencies = [
|
||||||
"cc",
|
"cc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.20.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
|
@ -1561,8 +1636,19 @@ version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
|
checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api 0.3.4",
|
||||||
"parking_lot_core",
|
"parking_lot_core 0.7.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733"
|
||||||
|
dependencies = [
|
||||||
|
"instant",
|
||||||
|
"lock_api 0.4.1",
|
||||||
|
"parking_lot_core 0.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1572,13 +1658,31 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
|
checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"cloudabi",
|
"cloudabi 0.0.3",
|
||||||
"libc",
|
"libc",
|
||||||
"redox_syscall",
|
"redox_syscall",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b"
|
||||||
|
dependencies = [
|
||||||
|
"backtrace",
|
||||||
|
"cfg-if",
|
||||||
|
"cloudabi 0.1.0",
|
||||||
|
"instant",
|
||||||
|
"libc",
|
||||||
|
"petgraph",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"thread-id",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "peek-poke"
|
name = "peek-poke"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -1650,6 +1754,16 @@ dependencies = [
|
||||||
"sha-1",
|
"sha-1",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "petgraph"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
|
||||||
|
dependencies = [
|
||||||
|
"fixedbitset",
|
||||||
|
"indexmap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "0.4.23"
|
version = "0.4.23"
|
||||||
|
@ -1965,7 +2079,7 @@ version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cloudabi",
|
"cloudabi 0.0.3",
|
||||||
"fuchsia-cprng",
|
"fuchsia-cprng",
|
||||||
"libc",
|
"libc",
|
||||||
"rand_core 0.4.2",
|
"rand_core 0.4.2",
|
||||||
|
@ -2206,6 +2320,8 @@ dependencies = [
|
||||||
"roc_types",
|
"roc_types",
|
||||||
"roc_unify",
|
"roc_unify",
|
||||||
"roc_uniq",
|
"roc_uniq",
|
||||||
|
"serde",
|
||||||
|
"serde-xml-rs",
|
||||||
"strip-ansi-escapes",
|
"strip-ansi-escapes",
|
||||||
"target-lexicon",
|
"target-lexicon",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
@ -2322,6 +2438,7 @@ dependencies = [
|
||||||
"roc_can",
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_constrain",
|
"roc_constrain",
|
||||||
|
"roc_load",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_mono",
|
"roc_mono",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
|
@ -2346,6 +2463,7 @@ dependencies = [
|
||||||
"inlinable_string",
|
"inlinable_string",
|
||||||
"maplit",
|
"maplit",
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
|
"parking_lot 0.11.0",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
"quickcheck",
|
"quickcheck",
|
||||||
"quickcheck_macros",
|
"quickcheck_macros",
|
||||||
|
@ -2354,6 +2472,7 @@ dependencies = [
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_constrain",
|
"roc_constrain",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
|
"roc_mono",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_problem",
|
"roc_problem",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
@ -2397,6 +2516,7 @@ dependencies = [
|
||||||
"roc_solve",
|
"roc_solve",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
"roc_unify",
|
"roc_unify",
|
||||||
|
"ven_ena",
|
||||||
"ven_pretty",
|
"ven_pretty",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -2551,6 +2671,12 @@ dependencies = [
|
||||||
"roc_types",
|
"roc_types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2610b7f643d18c87dff3b489950269617e6601a51f1f05aa5daefee36f64f0b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc-hash"
|
name = "rustc-hash"
|
||||||
version = "1.1.0"
|
version = "1.1.0"
|
||||||
|
@ -2627,6 +2753,21 @@ name = "serde"
|
||||||
version = "1.0.116"
|
version = "1.0.116"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
|
checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde-xml-rs"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efe415925cf3d0bbb2fc47d09b56ce03eef51c5d56846468a39bcc293c7a846c"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"serde",
|
||||||
|
"thiserror",
|
||||||
|
"xml-rs",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_cbor"
|
name = "serde_cbor"
|
||||||
|
@ -2758,7 +2899,7 @@ version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fd0a4829a5c591dc24a944a736d6b1e4053e51339a79fd5d4702c4c999a9c45e"
|
checksum = "fd0a4829a5c591dc24a944a736d6b1e4053e51339a79fd5d4702c4c999a9c45e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lock_api",
|
"lock_api 0.3.4",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -2859,6 +3000,37 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2 1.0.21",
|
||||||
|
"quote 1.0.7",
|
||||||
|
"syn 1.0.40",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thread-id"
|
||||||
|
version = "3.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"winapi 0.3.9",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -3186,7 +3358,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b5dece29f3cd403aabf4056595eabe4b9af56b8bfae12445f097cf8666a41829"
|
checksum = "b5dece29f3cd403aabf4056595eabe4b9af56b8bfae12445f097cf8666a41829"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"parking_lot",
|
"parking_lot 0.10.2",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"wgpu-core",
|
"wgpu-core",
|
||||||
|
@ -3213,7 +3385,7 @@ dependencies = [
|
||||||
"gfx-hal",
|
"gfx-hal",
|
||||||
"gfx-memory",
|
"gfx-memory",
|
||||||
"log",
|
"log",
|
||||||
"parking_lot",
|
"parking_lot 0.10.2",
|
||||||
"peek-poke",
|
"peek-poke",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"vec_map",
|
"vec_map",
|
||||||
|
@ -3230,7 +3402,7 @@ dependencies = [
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"libc",
|
"libc",
|
||||||
"objc",
|
"objc",
|
||||||
"parking_lot",
|
"parking_lot 0.10.2",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"wgpu-core",
|
"wgpu-core",
|
||||||
"wgpu-types",
|
"wgpu-types",
|
||||||
|
@ -3323,7 +3495,7 @@ dependencies = [
|
||||||
"ndk-glue",
|
"ndk-glue",
|
||||||
"ndk-sys",
|
"ndk-sys",
|
||||||
"objc",
|
"objc",
|
||||||
"parking_lot",
|
"parking_lot 0.10.2",
|
||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
"raw-window-handle",
|
"raw-window-handle",
|
||||||
"smithay-client-toolkit",
|
"smithay-client-toolkit",
|
||||||
|
|
|
@ -59,4 +59,4 @@ esac
|
||||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
|
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
|
||||||
add-apt-repository "${REPO_NAME}"
|
add-apt-repository "${REPO_NAME}"
|
||||||
apt-get update
|
apt-get update
|
||||||
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION
|
apt-get install -y clang-$LLVM_VERSION lldb-$LLVM_VERSION lld-$LLVM_VERSION clangd-$LLVM_VERSION libc++abi-dev libunwind-dev valgrind
|
|
@ -86,3 +86,5 @@ indoc = "0.3.3"
|
||||||
quickcheck = "0.8"
|
quickcheck = "0.8"
|
||||||
quickcheck_macros = "0.8"
|
quickcheck_macros = "0.8"
|
||||||
strip-ansi-escapes = "0.1"
|
strip-ansi-escapes = "0.1"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde-xml-rs = "0.4"
|
||||||
|
|
117
cli/src/build.rs
Normal file
117
cli/src/build.rs
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_build::{link::link, program};
|
||||||
|
use roc_collections::all::MutMap;
|
||||||
|
use roc_gen::llvm::build::OptLevel;
|
||||||
|
use roc_load::file::LoadingProblem;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
|
||||||
|
buf.push_str(&format!(
|
||||||
|
" {:.3} ms {}\n",
|
||||||
|
duration.as_secs_f64() * 1000.0,
|
||||||
|
label,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build_file(
|
||||||
|
target: &Triple,
|
||||||
|
src_dir: PathBuf,
|
||||||
|
filename: PathBuf,
|
||||||
|
opt_level: OptLevel,
|
||||||
|
) -> Result<PathBuf, LoadingProblem> {
|
||||||
|
let compilation_start = SystemTime::now();
|
||||||
|
let arena = Bump::new();
|
||||||
|
|
||||||
|
// Step 1: compile the app and generate the .o file
|
||||||
|
let subs_by_module = MutMap::default();
|
||||||
|
|
||||||
|
// Release builds use uniqueness optimizations
|
||||||
|
let stdlib = match opt_level {
|
||||||
|
OptLevel::Normal => roc_builtins::std::standard_stdlib(),
|
||||||
|
OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(),
|
||||||
|
};
|
||||||
|
let loaded = roc_load::file::load_and_monomorphize(
|
||||||
|
&arena,
|
||||||
|
filename.clone(),
|
||||||
|
stdlib,
|
||||||
|
src_dir.as_path(),
|
||||||
|
subs_by_module,
|
||||||
|
)?;
|
||||||
|
let dest_filename = filename.with_file_name("roc_app.o");
|
||||||
|
let buf = &mut String::with_capacity(1024);
|
||||||
|
|
||||||
|
for (module_id, module_timing) in loaded.timings.iter() {
|
||||||
|
let module_name = loaded.interns.module_name(*module_id);
|
||||||
|
|
||||||
|
buf.push_str(" ");
|
||||||
|
buf.push_str(module_name);
|
||||||
|
buf.push('\n');
|
||||||
|
|
||||||
|
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
|
||||||
|
report_timing(buf, "Parse header", module_timing.parse_header);
|
||||||
|
report_timing(buf, "Parse body", module_timing.parse_body);
|
||||||
|
report_timing(buf, "Canonicalize", module_timing.canonicalize);
|
||||||
|
report_timing(buf, "Constrain", module_timing.constrain);
|
||||||
|
report_timing(buf, "Solve", module_timing.solve);
|
||||||
|
report_timing(buf, "Other", module_timing.other());
|
||||||
|
buf.push('\n');
|
||||||
|
report_timing(buf, "Total", module_timing.total());
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
|
||||||
|
buf
|
||||||
|
);
|
||||||
|
|
||||||
|
program::gen_from_mono_module(
|
||||||
|
&arena,
|
||||||
|
loaded,
|
||||||
|
filename,
|
||||||
|
Triple::host(),
|
||||||
|
&dest_filename,
|
||||||
|
opt_level,
|
||||||
|
);
|
||||||
|
|
||||||
|
let compilation_end = compilation_start.elapsed().unwrap();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Finished compilation and code gen in {} ms\n",
|
||||||
|
compilation_end.as_millis()
|
||||||
|
);
|
||||||
|
|
||||||
|
let cwd = dest_filename.parent().unwrap();
|
||||||
|
|
||||||
|
// Step 2: link the precompiled host and compiled app
|
||||||
|
let host_input_path = cwd.join("platform").join("host.o");
|
||||||
|
let binary_path = cwd.join("app"); // TODO should be app.exe on Windows
|
||||||
|
|
||||||
|
// TODO try to move as much of this linking as possible to the precompiled
|
||||||
|
// host, to minimize the amount of host-application linking required.
|
||||||
|
let cmd_result = // TODO use lld
|
||||||
|
link(
|
||||||
|
target,
|
||||||
|
binary_path.as_path(),
|
||||||
|
host_input_path.as_path(),
|
||||||
|
dest_filename.as_path(),
|
||||||
|
)
|
||||||
|
.map_err(|_| {
|
||||||
|
todo!("gracefully handle `rustc` failing to spawn.");
|
||||||
|
})?
|
||||||
|
.wait()
|
||||||
|
.map_err(|_| {
|
||||||
|
todo!("gracefully handle error after `rustc` spawned");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Clean up the leftover .o file from the Roc, if possible.
|
||||||
|
// (If cleaning it up fails, that's fine. No need to take action.)
|
||||||
|
// TODO compile the dest_filename to a tmpdir, as an extra precaution.
|
||||||
|
let _ = fs::remove_file(dest_filename);
|
||||||
|
|
||||||
|
// If the cmd errored out, return the Err.
|
||||||
|
cmd_result?;
|
||||||
|
|
||||||
|
Ok(binary_path)
|
||||||
|
}
|
138
cli/src/lib.rs
138
cli/src/lib.rs
|
@ -1,20 +1,16 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use roc_build::program::gen;
|
|
||||||
use roc_collections::all::MutMap;
|
|
||||||
use roc_gen::llvm::build::OptLevel;
|
use roc_gen::llvm::build::OptLevel;
|
||||||
use roc_load::file::LoadingProblem;
|
use std::io;
|
||||||
use std::io::{self, ErrorKind};
|
use std::path::Path;
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::time::{Duration, SystemTime};
|
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
pub mod build;
|
||||||
pub mod repl;
|
pub mod repl;
|
||||||
|
|
||||||
pub static FLAG_OPTIMIZE: &str = "optimize";
|
pub static FLAG_OPTIMIZE: &str = "optimize";
|
||||||
|
@ -66,7 +62,7 @@ pub fn build_app<'a>() -> App<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
|
pub fn build(target: &Triple, matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
|
||||||
let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
|
let filename = matches.value_of(FLAG_ROC_FILE).unwrap();
|
||||||
let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
|
let opt_level = if matches.is_present(FLAG_OPTIMIZE) {
|
||||||
OptLevel::Optimize
|
OptLevel::Optimize
|
||||||
|
@ -78,7 +74,7 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
|
||||||
|
|
||||||
// Spawn the root task
|
// Spawn the root task
|
||||||
let path = path.canonicalize().unwrap_or_else(|err| {
|
let path = path.canonicalize().unwrap_or_else(|err| {
|
||||||
use ErrorKind::*;
|
use io::ErrorKind::*;
|
||||||
|
|
||||||
match err.kind() {
|
match err.kind() {
|
||||||
NotFound => {
|
NotFound => {
|
||||||
|
@ -95,8 +91,8 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let binary_path =
|
let binary_path = build::build_file(target, src_dir, path, opt_level)
|
||||||
build_file(src_dir, path, opt_level).expect("TODO gracefully handle build_file failing");
|
.expect("TODO gracefully handle build_file failing");
|
||||||
|
|
||||||
if run_after_build {
|
if run_after_build {
|
||||||
// Run the compiled app
|
// Run the compiled app
|
||||||
|
@ -109,123 +105,3 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn report_timing(buf: &mut String, label: &str, duration: Duration) {
|
|
||||||
buf.push_str(&format!(
|
|
||||||
" {:.3} ms {}\n",
|
|
||||||
duration.as_secs_f64() * 1000.0,
|
|
||||||
label,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_file(
|
|
||||||
src_dir: PathBuf,
|
|
||||||
filename: PathBuf,
|
|
||||||
opt_level: OptLevel,
|
|
||||||
) -> Result<PathBuf, LoadingProblem> {
|
|
||||||
let compilation_start = SystemTime::now();
|
|
||||||
let arena = Bump::new();
|
|
||||||
|
|
||||||
// Step 1: compile the app and generate the .o file
|
|
||||||
let subs_by_module = MutMap::default();
|
|
||||||
|
|
||||||
// Release builds use uniqueness optimizations
|
|
||||||
let stdlib = match opt_level {
|
|
||||||
OptLevel::Normal => roc_builtins::std::standard_stdlib(),
|
|
||||||
OptLevel::Optimize => roc_builtins::unique::uniq_stdlib(),
|
|
||||||
};
|
|
||||||
let loaded =
|
|
||||||
roc_load::file::load(filename.clone(), &stdlib, src_dir.as_path(), subs_by_module)?;
|
|
||||||
let dest_filename = filename.with_extension("o");
|
|
||||||
|
|
||||||
let buf = &mut String::with_capacity(1024);
|
|
||||||
|
|
||||||
for (module_id, module_timing) in loaded.timings.iter() {
|
|
||||||
let module_name = loaded.interns.module_name(*module_id);
|
|
||||||
|
|
||||||
buf.push_str(" ");
|
|
||||||
buf.push_str(module_name);
|
|
||||||
buf.push_str("\n");
|
|
||||||
|
|
||||||
report_timing(buf, "Read .roc file from disk", module_timing.read_roc_file);
|
|
||||||
report_timing(buf, "Parse header", module_timing.parse_header);
|
|
||||||
report_timing(buf, "Parse body", module_timing.parse_body);
|
|
||||||
report_timing(buf, "Canonicalize", module_timing.canonicalize);
|
|
||||||
report_timing(buf, "Constrain", module_timing.constrain);
|
|
||||||
report_timing(buf, "Solve", module_timing.solve);
|
|
||||||
report_timing(buf, "Other", module_timing.other());
|
|
||||||
buf.push('\n');
|
|
||||||
report_timing(buf, "Total", module_timing.total());
|
|
||||||
}
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"\n\nCompilation finished! Here's how long each module took to compile:\n\n{}",
|
|
||||||
buf
|
|
||||||
);
|
|
||||||
|
|
||||||
gen(
|
|
||||||
&arena,
|
|
||||||
loaded,
|
|
||||||
filename,
|
|
||||||
Triple::host(),
|
|
||||||
&dest_filename,
|
|
||||||
opt_level,
|
|
||||||
);
|
|
||||||
|
|
||||||
let compilation_end = compilation_start.elapsed().unwrap();
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"Finished compilation and code gen in {} ms\n",
|
|
||||||
compilation_end.as_millis()
|
|
||||||
);
|
|
||||||
|
|
||||||
let cwd = dest_filename.parent().unwrap();
|
|
||||||
let lib_path = dest_filename.with_file_name("libroc_app.a");
|
|
||||||
|
|
||||||
// Step 2: turn the .o file into a .a static library
|
|
||||||
Command::new("ar") // TODO on Windows, use `link`
|
|
||||||
.args(&[
|
|
||||||
"rcs",
|
|
||||||
lib_path.to_str().unwrap(),
|
|
||||||
dest_filename.to_str().unwrap(),
|
|
||||||
])
|
|
||||||
.spawn()
|
|
||||||
.map_err(|_| {
|
|
||||||
todo!("gracefully handle `ar` failing to spawn.");
|
|
||||||
})?
|
|
||||||
.wait()
|
|
||||||
.map_err(|_| {
|
|
||||||
todo!("gracefully handle error after `ar` spawned");
|
|
||||||
})?;
|
|
||||||
|
|
||||||
// Step 3: have rustc compile the host and link in the .a file
|
|
||||||
let binary_path = cwd.join("app");
|
|
||||||
|
|
||||||
Command::new("rustc")
|
|
||||||
.args(&[
|
|
||||||
"-L",
|
|
||||||
".",
|
|
||||||
"--crate-type",
|
|
||||||
"bin",
|
|
||||||
"host.rs",
|
|
||||||
"-o",
|
|
||||||
binary_path.as_path().to_str().unwrap(),
|
|
||||||
// ensure we don't make a position-independent executable
|
|
||||||
"-C",
|
|
||||||
"link-arg=-no-pie",
|
|
||||||
// explicitly link in the c++ stdlib, for exceptions
|
|
||||||
"-C",
|
|
||||||
"link-arg=-lc++",
|
|
||||||
])
|
|
||||||
.current_dir(cwd)
|
|
||||||
.spawn()
|
|
||||||
.map_err(|_| {
|
|
||||||
todo!("gracefully handle `rustc` failing to spawn.");
|
|
||||||
})?
|
|
||||||
.wait()
|
|
||||||
.map_err(|_| {
|
|
||||||
todo!("gracefully handle error after `rustc` spawned");
|
|
||||||
})?;
|
|
||||||
|
|
||||||
Ok(binary_path)
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,14 +1,23 @@
|
||||||
use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES};
|
use roc_cli::{build, build_app, repl, DIRECTORY_OR_FILES};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let matches = build_app().get_matches();
|
let matches = build_app().get_matches();
|
||||||
|
|
||||||
match matches.subcommand_name() {
|
match matches.subcommand_name() {
|
||||||
None => roc_editor::launch(&[]),
|
None => roc_editor::launch(&[]),
|
||||||
Some("build") => build(matches.subcommand_matches("build").unwrap(), false),
|
Some("build") => build(
|
||||||
Some("run") => build(matches.subcommand_matches("run").unwrap(), true),
|
&Triple::host(),
|
||||||
|
matches.subcommand_matches("build").unwrap(),
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
Some("run") => build(
|
||||||
|
&Triple::host(),
|
||||||
|
matches.subcommand_matches("run").unwrap(),
|
||||||
|
true,
|
||||||
|
),
|
||||||
Some("repl") => repl::main(),
|
Some("repl") => repl::main(),
|
||||||
Some("edit") => {
|
Some("edit") => {
|
||||||
match matches
|
match matches
|
||||||
|
|
277
cli/src/repl.rs
277
cli/src/repl.rs
|
@ -12,12 +12,9 @@ use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap, SendSet};
|
||||||
use roc_constrain::expr::constrain_expr;
|
use roc_constrain::expr::constrain_expr;
|
||||||
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
||||||
use roc_fmt::annotation::{Formattable, Newlines, Parens};
|
use roc_fmt::annotation::{Formattable, Newlines, Parens};
|
||||||
use roc_gen::layout_id::LayoutIds;
|
|
||||||
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
|
||||||
use roc_module::ident::Ident;
|
use roc_module::ident::Ident;
|
||||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
||||||
use roc_mono::ir::Procs;
|
|
||||||
use roc_mono::layout::{Layout, LayoutCache};
|
|
||||||
use roc_parse::ast::{self, Attempting};
|
use roc_parse::ast::{self, Attempting};
|
||||||
use roc_parse::blankspace::space0_before;
|
use roc_parse::blankspace::space0_before;
|
||||||
use roc_parse::parser::{loc, Fail, FailReason, Parser, State};
|
use roc_parse::parser::{loc, Fail, FailReason, Parser, State};
|
||||||
|
@ -29,7 +26,7 @@ use roc_types::subs::{Content, Subs, VarStore, Variable};
|
||||||
use roc_types::types::Type;
|
use roc_types::types::Type;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::from_utf8_unchecked;
|
use std::str::from_utf8_unchecked;
|
||||||
use target_lexicon::Triple;
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
|
@ -158,61 +155,135 @@ pub fn repl_home() -> ModuleId {
|
||||||
ModuleIds::default().get_or_insert(&"REPL".into())
|
ModuleIds::default().get_or_insert(&"REPL".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn promote_expr_to_module(src: &str) -> String {
|
||||||
|
let mut buffer = String::from("app Repl provides [ replOutput ] imports []\n\nreplOutput =\n");
|
||||||
|
|
||||||
|
for line in src.lines() {
|
||||||
|
// indent the body!
|
||||||
|
buffer.push_str(" ");
|
||||||
|
buffer.push_str(line);
|
||||||
|
buffer.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
|
fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fail> {
|
||||||
use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE};
|
use roc_reporting::report::{
|
||||||
|
can_problem, mono_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE,
|
||||||
|
};
|
||||||
|
|
||||||
// Look up the types and expressions of the `provided` values
|
|
||||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let CanExprOut {
|
|
||||||
loc_expr,
|
|
||||||
var_store,
|
|
||||||
var,
|
|
||||||
constraint,
|
|
||||||
home,
|
|
||||||
interns,
|
|
||||||
problems: can_problems,
|
|
||||||
..
|
|
||||||
} = can_expr(src)?; // IMPORTANT: we must bail out here if there were UTF-8 errors!
|
|
||||||
|
|
||||||
let subs = Subs::new(var_store.into());
|
|
||||||
let mut type_problems = Vec::new();
|
|
||||||
let (content, mut subs) = infer_expr(subs, &mut type_problems, &constraint, var);
|
|
||||||
|
|
||||||
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
|
// SAFETY: we've already verified that this is valid UTF-8 during parsing.
|
||||||
let src_lines: Vec<&str> = unsafe { from_utf8_unchecked(src).split('\n').collect() };
|
let src_str: &str = unsafe { from_utf8_unchecked(src) };
|
||||||
|
|
||||||
// Report problems
|
let stdlib = roc_builtins::std::standard_stdlib();
|
||||||
let palette = DEFAULT_PALETTE;
|
let stdlib_mode = stdlib.mode;
|
||||||
|
let filename = PathBuf::from("REPL.roc");
|
||||||
|
let src_dir = Path::new("fake/test/path");
|
||||||
|
|
||||||
// Report parsing and canonicalization problems
|
let module_src = promote_expr_to_module(src_str);
|
||||||
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
|
||||||
|
|
||||||
// Used for reporting where an error came from.
|
let exposed_types = MutMap::default();
|
||||||
//
|
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||||
// TODO: maybe Reporting should have this be an Option?
|
&arena,
|
||||||
let path = PathBuf::new();
|
filename,
|
||||||
let total_problems = can_problems.len() + type_problems.len();
|
&module_src,
|
||||||
|
stdlib,
|
||||||
|
src_dir,
|
||||||
|
exposed_types,
|
||||||
|
);
|
||||||
|
|
||||||
if total_problems == 0 {
|
let loaded = loaded.expect("failed to load module");
|
||||||
|
|
||||||
|
use roc_load::file::MonomorphizedModule;
|
||||||
|
let MonomorphizedModule {
|
||||||
|
can_problems,
|
||||||
|
type_problems,
|
||||||
|
mono_problems,
|
||||||
|
mut procedures,
|
||||||
|
interns,
|
||||||
|
exposed_to_host,
|
||||||
|
mut subs,
|
||||||
|
module_id: home,
|
||||||
|
..
|
||||||
|
} = loaded;
|
||||||
|
|
||||||
|
let error_count = can_problems.len() + type_problems.len() + mono_problems.len();
|
||||||
|
|
||||||
|
if error_count > 0 {
|
||||||
|
// There were problems; report them and return.
|
||||||
|
let src_lines: Vec<&str> = module_src.split('\n').collect();
|
||||||
|
|
||||||
|
// Used for reporting where an error came from.
|
||||||
|
//
|
||||||
|
// TODO: maybe Reporting should have this be an Option?
|
||||||
|
let path = PathBuf::new();
|
||||||
|
|
||||||
|
// Report problems
|
||||||
|
let palette = DEFAULT_PALETTE;
|
||||||
|
|
||||||
|
// Report parsing and canonicalization problems
|
||||||
|
let alloc = RocDocAllocator::new(&src_lines, home, &interns);
|
||||||
|
|
||||||
|
let mut lines = Vec::with_capacity(error_count);
|
||||||
|
|
||||||
|
for problem in can_problems.into_iter() {
|
||||||
|
let report = can_problem(&alloc, path.clone(), problem);
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||||
|
|
||||||
|
lines.push(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for problem in type_problems.into_iter() {
|
||||||
|
let report = type_problem(&alloc, path.clone(), problem);
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||||
|
|
||||||
|
lines.push(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for problem in mono_problems.into_iter() {
|
||||||
|
let report = mono_problem(&alloc, path.clone(), problem);
|
||||||
|
let mut buf = String::new();
|
||||||
|
|
||||||
|
report.render_color_terminal(&mut buf, &alloc, &palette);
|
||||||
|
|
||||||
|
lines.push(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(ReplOutput::Problems(lines))
|
||||||
|
} else {
|
||||||
let context = Context::create();
|
let context = Context::create();
|
||||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
|
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, "app"));
|
||||||
let builder = context.create_builder();
|
let builder = context.create_builder();
|
||||||
|
|
||||||
// pretty-print the expr type string for later.
|
debug_assert_eq!(exposed_to_host.len(), 1);
|
||||||
name_all_type_vars(var, &mut subs);
|
let (main_fn_symbol, main_fn_var) = exposed_to_host.iter().next().unwrap();
|
||||||
|
let main_fn_symbol = *main_fn_symbol;
|
||||||
|
let main_fn_var = *main_fn_var;
|
||||||
|
|
||||||
|
// pretty-print the expr type string for later.
|
||||||
|
name_all_type_vars(main_fn_var, &mut subs);
|
||||||
|
let content = subs.get(main_fn_var).content;
|
||||||
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
let expr_type_str = content_to_string(content.clone(), &subs, home, &interns);
|
||||||
|
|
||||||
|
let (_, main_fn_layout) = procedures
|
||||||
|
.keys()
|
||||||
|
.find(|(s, _)| *s == main_fn_symbol)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||||
|
|
||||||
|
let module = arena.alloc(module);
|
||||||
let (module_pass, function_pass) =
|
let (module_pass, function_pass) =
|
||||||
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||||
|
|
||||||
// Compute main_fn_type before moving subs to Env
|
|
||||||
let main_ret_layout = Layout::new(&arena, content.clone(), &subs).unwrap_or_else(|err| {
|
|
||||||
panic!(
|
|
||||||
"Code gen error in test: could not convert Content to main_layout. Err was {:?}",
|
|
||||||
err
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let execution_engine = module
|
let execution_engine = module
|
||||||
.create_jit_execution_engine(OptimizationLevel::None)
|
.create_jit_execution_engine(OptimizationLevel::None)
|
||||||
.expect("Error creating JIT execution engine for test");
|
.expect("Error creating JIT execution engine for test");
|
||||||
|
@ -222,7 +293,7 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
|
||||||
ExecutionEngine::link_in_mc_jit();
|
ExecutionEngine::link_in_mc_jit();
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
let mut env = roc_gen::llvm::build::Env {
|
let env = roc_gen::llvm::build::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
builder: &builder,
|
builder: &builder,
|
||||||
context: &context,
|
context: &context,
|
||||||
|
@ -230,97 +301,70 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
|
||||||
module,
|
module,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
leak: false,
|
leak: false,
|
||||||
|
// important! we don't want any procedures to get the C calling convention
|
||||||
exposed_to_host: MutSet::default(),
|
exposed_to_host: MutSet::default(),
|
||||||
};
|
};
|
||||||
let mut procs = Procs::default();
|
|
||||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
|
||||||
let mut layout_ids = LayoutIds::default();
|
|
||||||
|
|
||||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
|
||||||
let mut mono_problems = Vec::new();
|
let mut headers = Vec::with_capacity(procedures.len());
|
||||||
let mut mono_env = roc_mono::ir::Env {
|
|
||||||
arena: &arena,
|
|
||||||
subs: &mut subs,
|
|
||||||
problems: &mut mono_problems,
|
|
||||||
home,
|
|
||||||
ident_ids: &mut ident_ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
|
|
||||||
|
|
||||||
let param_map = roc_mono::borrow::ParamMap::default();
|
|
||||||
let main_body = roc_mono::inc_dec::visit_declaration(
|
|
||||||
mono_env.arena,
|
|
||||||
mono_env.arena.alloc(param_map),
|
|
||||||
mono_env.arena.alloc(main_body),
|
|
||||||
);
|
|
||||||
let mut headers = {
|
|
||||||
let num_headers = match &procs.pending_specializations {
|
|
||||||
Some(map) => map.len(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Vec::with_capacity(num_headers)
|
|
||||||
};
|
|
||||||
let mut layout_cache = LayoutCache::default();
|
|
||||||
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
procs.runtime_errors,
|
|
||||||
roc_collections::all::MutMap::default()
|
|
||||||
);
|
|
||||||
|
|
||||||
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
|
|
||||||
let main_body = roc_mono::inc_dec::visit_declaration(
|
|
||||||
mono_env.arena,
|
|
||||||
param_map,
|
|
||||||
mono_env.arena.alloc(main_body),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Put this module's ident_ids back in the interns, so we can use them in env.
|
|
||||||
// This must happen *after* building the headers, because otherwise there's
|
|
||||||
// a conflicting mutable borrow on ident_ids.
|
|
||||||
env.interns.all_ident_ids.insert(home, ident_ids);
|
|
||||||
|
|
||||||
// Add all the Proc headers to the module.
|
// Add all the Proc headers to the module.
|
||||||
// We have to do this in a separate pass first,
|
// We have to do this in a separate pass first,
|
||||||
// because their bodies may reference each other.
|
// because their bodies may reference each other.
|
||||||
for ((symbol, layout), proc) in procs.drain() {
|
let mut scope = roc_gen::llvm::build::Scope::default();
|
||||||
|
for ((symbol, layout), proc) in procedures.drain() {
|
||||||
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||||
|
|
||||||
|
if proc.args.is_empty() {
|
||||||
|
// this is a 0-argument thunk, i.e. a top-level constant definition
|
||||||
|
// it must be in-scope everywhere in the module!
|
||||||
|
scope.insert_top_level_thunk(symbol, layout, fn_val);
|
||||||
|
}
|
||||||
|
|
||||||
headers.push((proc, fn_val));
|
headers.push((proc, fn_val));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build each proc using its header info.
|
// Build each proc using its header info.
|
||||||
for (proc, fn_val) in headers {
|
for (proc, fn_val) in headers {
|
||||||
// NOTE: This is here to be uncommented in case verification fails.
|
let mut current_scope = scope.clone();
|
||||||
// (This approach means we don't have to defensively clone name here.)
|
|
||||||
//
|
// only have top-level thunks for this proc's module in scope
|
||||||
// println!("\n\nBuilding and then verifying function {}\n\n", name);
|
// this retain is not needed for correctness, but will cause less confusion when debugging
|
||||||
build_proc(&env, &mut layout_ids, proc, fn_val);
|
let home = proc.name.module_id();
|
||||||
|
current_scope.retain_top_level_thunks_for_module(home);
|
||||||
|
|
||||||
|
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
|
||||||
|
|
||||||
if fn_val.verify(true) {
|
if fn_val.verify(true) {
|
||||||
function_pass.run_on(&fn_val);
|
function_pass.run_on(&fn_val);
|
||||||
} else {
|
} else {
|
||||||
|
use roc_builtins::std::Mode;
|
||||||
|
|
||||||
|
let mode = match stdlib_mode {
|
||||||
|
Mode::Uniqueness => "OPTIMIZED",
|
||||||
|
Mode::Standard => "NON-OPTIMIZED",
|
||||||
|
};
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"\n\nFunction {:?} failed LLVM verification in build. Its content was:\n",
|
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
|
||||||
fn_val.get_name().to_str().unwrap()
|
fn_val.get_name().to_str().unwrap(),
|
||||||
|
mode,
|
||||||
);
|
);
|
||||||
|
|
||||||
fn_val.print_to_stderr();
|
fn_val.print_to_stderr();
|
||||||
|
|
||||||
panic!(
|
panic!(
|
||||||
"The preceding code was from {:?}, which failed LLVM verification in build.",
|
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
|
||||||
fn_val.get_name().to_str().unwrap()
|
fn_val.get_name().to_str().unwrap(),
|
||||||
|
mode,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
|
||||||
let (main_fn_name, main_fn) = roc_gen::llvm::build::make_main_function(
|
|
||||||
&env,
|
&env,
|
||||||
&mut layout_ids,
|
&mut layout_ids,
|
||||||
&main_ret_layout,
|
main_fn_symbol,
|
||||||
&main_body,
|
&main_fn_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||||
|
@ -347,7 +391,7 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
|
||||||
&arena,
|
&arena,
|
||||||
execution_engine,
|
execution_engine,
|
||||||
main_fn_name,
|
main_fn_name,
|
||||||
&main_ret_layout,
|
&main_fn_layout,
|
||||||
&content,
|
&content,
|
||||||
&env.interns,
|
&env.interns,
|
||||||
home,
|
home,
|
||||||
|
@ -363,29 +407,6 @@ fn gen(src: &[u8], target: Triple, opt_level: OptLevel) -> Result<ReplOutput, Fa
|
||||||
expr: expr.into_bump_str().to_string(),
|
expr: expr.into_bump_str().to_string(),
|
||||||
expr_type: expr_type_str,
|
expr_type: expr_type_str,
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
// There were problems; report them and return.
|
|
||||||
let mut lines = Vec::with_capacity(total_problems);
|
|
||||||
|
|
||||||
for problem in can_problems.into_iter() {
|
|
||||||
let report = can_problem(&alloc, path.clone(), problem);
|
|
||||||
let mut buf = String::new();
|
|
||||||
|
|
||||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
|
||||||
|
|
||||||
lines.push(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
for problem in type_problems.into_iter() {
|
|
||||||
let report = type_problem(&alloc, path.clone(), problem);
|
|
||||||
let mut buf = String::new();
|
|
||||||
|
|
||||||
report.render_color_terminal(&mut buf, &alloc, &palette);
|
|
||||||
|
|
||||||
lines.push(buf);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ReplOutput::Problems(lines))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,36 +11,119 @@ mod helpers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod cli_run {
|
mod cli_run {
|
||||||
use crate::helpers::{example_file, run_roc};
|
use crate::helpers::{example_file, extract_valgrind_errors, run_roc, run_with_valgrind, Out};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_hello_world() {
|
fn run_hello_world() {
|
||||||
let out = run_roc(&[
|
fn check_hello_world_output(out: Out) {
|
||||||
"run",
|
if !out.stderr.is_empty() {
|
||||||
example_file("hello-world", "Hello.roc").to_str().unwrap(),
|
panic!(out.stderr);
|
||||||
]);
|
}
|
||||||
|
assert!(out.status.success());
|
||||||
|
|
||||||
if !out.stderr.is_empty() {
|
let valgrind_out =
|
||||||
panic!(out.stderr);
|
run_with_valgrind(&[example_file("hello-world", "app").to_str().unwrap()]);
|
||||||
|
assert!(valgrind_out.status.success());
|
||||||
|
let ending = "Hello, World!!!!!!!!!!!!!\n";
|
||||||
|
if !&valgrind_out.stdout.ends_with(ending) {
|
||||||
|
panic!(
|
||||||
|
"expected output to end with {:?} but instead got {:?}",
|
||||||
|
ending, &valgrind_out.stdout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let memory_errors = extract_valgrind_errors(&valgrind_out.stderr);
|
||||||
|
if !memory_errors.is_empty() {
|
||||||
|
panic!("{:?}", memory_errors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert!(&out.stdout.ends_with("Hello, World!!!!!!!!!!!!!\n"));
|
check_hello_world_output(run_roc(&[
|
||||||
assert!(out.status.success());
|
"build",
|
||||||
|
example_file("hello-world", "Hello.roc").to_str().unwrap(),
|
||||||
|
]));
|
||||||
|
check_hello_world_output(run_roc(&[
|
||||||
|
"build",
|
||||||
|
"--optimize",
|
||||||
|
example_file("hello-world", "Hello.roc").to_str().unwrap(),
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_quicksort() {
|
fn run_quicksort() {
|
||||||
let out = run_roc(&[
|
fn check_quicksort_output(out: Out) {
|
||||||
"run",
|
if !out.stderr.is_empty() {
|
||||||
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
|
panic!(out.stderr);
|
||||||
"--optimize",
|
}
|
||||||
]);
|
assert!(out.status.success());
|
||||||
|
|
||||||
if !out.stderr.is_empty() {
|
let valgrind_out =
|
||||||
panic!(out.stderr);
|
run_with_valgrind(&[example_file("quicksort", "app").to_str().unwrap()]);
|
||||||
|
assert!(valgrind_out.status.success());
|
||||||
|
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n";
|
||||||
|
if !&valgrind_out.stdout.ends_with(ending) {
|
||||||
|
panic!(
|
||||||
|
"expected output to end with {:?} but instead got {:?}",
|
||||||
|
ending, &valgrind_out.stdout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let memory_errors = extract_valgrind_errors(&valgrind_out.stderr);
|
||||||
|
if !memory_errors.is_empty() {
|
||||||
|
panic!("{:?}", memory_errors);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
assert!(&out
|
|
||||||
.stdout
|
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
|
||||||
.ends_with("[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n"));
|
/*
|
||||||
assert!(out.status.success());
|
check_quicksort_output(run_roc(&[
|
||||||
|
"build",
|
||||||
|
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
|
||||||
|
]));
|
||||||
|
*/
|
||||||
|
check_quicksort_output(run_roc(&[
|
||||||
|
"build",
|
||||||
|
"--optimize",
|
||||||
|
example_file("quicksort", "Quicksort.roc").to_str().unwrap(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn run_multi_module() {
|
||||||
|
fn check_muti_module_output(out: Out) {
|
||||||
|
if !out.stderr.is_empty() {
|
||||||
|
panic!(out.stderr);
|
||||||
|
}
|
||||||
|
assert!(out.status.success());
|
||||||
|
|
||||||
|
let valgrind_out =
|
||||||
|
run_with_valgrind(&[example_file("multi-module", "app").to_str().unwrap()]);
|
||||||
|
assert!(valgrind_out.status.success());
|
||||||
|
let ending = "[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\n";
|
||||||
|
if !&valgrind_out.stdout.ends_with(ending) {
|
||||||
|
panic!(
|
||||||
|
"expected output to end with {:?} but instead got {:?}",
|
||||||
|
ending, &valgrind_out.stdout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let memory_errors = extract_valgrind_errors(&valgrind_out.stderr);
|
||||||
|
if !memory_errors.is_empty() {
|
||||||
|
panic!("{:?}", memory_errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Uncomment this once we are correctly freeing the RocList even when in dev build.
|
||||||
|
/*
|
||||||
|
check_muti_module_output(run_roc(&[
|
||||||
|
"run",
|
||||||
|
example_file("multi-module", "Quicksort.roc")
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
]));
|
||||||
|
*/
|
||||||
|
check_muti_module_output(run_roc(&[
|
||||||
|
"run",
|
||||||
|
example_file("multi-module", "Quicksort.roc")
|
||||||
|
.to_str()
|
||||||
|
.unwrap(),
|
||||||
|
"--optimize",
|
||||||
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,8 @@ extern crate roc_load;
|
||||||
extern crate roc_module;
|
extern crate roc_module;
|
||||||
|
|
||||||
use roc_cli::repl::{INSTRUCTIONS, PROMPT, WELCOME_MESSAGE};
|
use roc_cli::repl::{INSTRUCTIONS, PROMPT, WELCOME_MESSAGE};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde_xml_rs::from_str;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -54,6 +56,86 @@ pub fn run_roc(args: &[&str]) -> Out {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn run_with_valgrind(args: &[&str]) -> Out {
|
||||||
|
//TODO: figure out if there is a better way to get the valgrind executable.
|
||||||
|
let mut cmd = Command::new("valgrind");
|
||||||
|
|
||||||
|
cmd.arg("--tool=memcheck");
|
||||||
|
cmd.arg("--xml=yes");
|
||||||
|
cmd.arg("--xml-fd=2");
|
||||||
|
for arg in args {
|
||||||
|
cmd.arg(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
let output = cmd
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute compiled `valgrind` binary in CLI test");
|
||||||
|
|
||||||
|
Out {
|
||||||
|
stdout: String::from_utf8(output.stdout).unwrap(),
|
||||||
|
stderr: String::from_utf8(output.stderr).unwrap(),
|
||||||
|
status: output.status,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ValgrindOutput {
|
||||||
|
#[serde(rename = "$value")]
|
||||||
|
pub fields: Vec<ValgrindField>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
#[serde(rename_all = "lowercase")]
|
||||||
|
enum ValgrindField {
|
||||||
|
ProtocolVersion(isize),
|
||||||
|
ProtocolTool(String),
|
||||||
|
Preamble(ValgrindDummyStruct),
|
||||||
|
Pid(isize),
|
||||||
|
PPid(isize),
|
||||||
|
Tool(String),
|
||||||
|
Args(ValgrindDummyStruct),
|
||||||
|
Error(ValgrindError),
|
||||||
|
Status(ValgrindDummyStruct),
|
||||||
|
ErrorCounts(ValgrindDummyStruct),
|
||||||
|
SuppCounts(ValgrindDummyStruct),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct ValgrindDummyStruct {}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct ValgrindError {
|
||||||
|
kind: String,
|
||||||
|
#[serde(default)]
|
||||||
|
what: Option<String>,
|
||||||
|
#[serde(default)]
|
||||||
|
xwhat: Option<ValgrindErrorXWhat>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
pub struct ValgrindErrorXWhat {
|
||||||
|
text: String,
|
||||||
|
#[serde(default)]
|
||||||
|
leakedbytes: Option<isize>,
|
||||||
|
#[serde(default)]
|
||||||
|
leakedblocks: Option<isize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn extract_valgrind_errors(xml: &str) -> Vec<ValgrindError> {
|
||||||
|
let parsed_xml: ValgrindOutput =
|
||||||
|
from_str(xml).expect("failed to parse the `valgrind` xml output");
|
||||||
|
parsed_xml
|
||||||
|
.fields
|
||||||
|
.iter()
|
||||||
|
.filter_map(|field| match field {
|
||||||
|
ValgrindField::Error(err) => Some(err.clone()),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn example_dir(dir_name: &str) -> PathBuf {
|
pub fn example_dir(dir_name: &str) -> PathBuf {
|
||||||
let mut path = env::current_exe().ok().unwrap();
|
let mut path = env::current_exe().ok().unwrap();
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pretty_assertions;
|
extern crate pretty_assertions;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate indoc;
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod repl_eval {
|
mod repl_eval {
|
||||||
use crate::helpers;
|
use crate::helpers;
|
||||||
|
|
||||||
|
const ERROR_MESSAGE_START: char = '─';
|
||||||
|
|
||||||
fn expect_success(input: &str, expected: &str) {
|
fn expect_success(input: &str, expected: &str) {
|
||||||
let out = helpers::repl_eval(input);
|
let out = helpers::repl_eval(input);
|
||||||
|
|
||||||
|
@ -15,6 +20,28 @@ mod repl_eval {
|
||||||
assert!(out.status.success());
|
assert!(out.status.success());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expect_failure(input: &str, expected: &str) {
|
||||||
|
let out = helpers::repl_eval(input);
|
||||||
|
|
||||||
|
// there may be some other stuff printed (e.g. unification errors)
|
||||||
|
// so skip till the header of the first error
|
||||||
|
match out.stdout.find(ERROR_MESSAGE_START) {
|
||||||
|
Some(index) => {
|
||||||
|
assert_eq!(&out.stderr, "");
|
||||||
|
assert_eq!(&out.stdout[index..], expected);
|
||||||
|
assert!(out.status.success());
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
assert_eq!(&out.stderr, "");
|
||||||
|
assert!(out.status.success());
|
||||||
|
panic!(
|
||||||
|
"I expected a failure, but there is no error message in stdout:\n\n{}",
|
||||||
|
&out.stdout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn literal_0() {
|
fn literal_0() {
|
||||||
expect_success("0", "0 : Num *");
|
expect_success("0", "0 : Num *");
|
||||||
|
@ -256,13 +283,46 @@ mod repl_eval {
|
||||||
// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\"");
|
// expect_success(r#""\n\nhi!\n\n""#, "\"\"\"\n\nhi!\n\n\"\"\"");
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// TODO uncomment this once https://github.com/rtfeldman/roc/issues/295 is done
|
#[test]
|
||||||
|
fn list_of_3_field_records() {
|
||||||
|
expect_success(
|
||||||
|
"[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
|
||||||
|
"[ { bar: 2, baz: 3, foo: 4.1 } ] : List { bar : Num *, baz : Int, foo : Float }",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn type_problem() {
|
||||||
|
expect_failure(
|
||||||
|
"1 + \"\"",
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
── TYPE MISMATCH ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
The 2nd argument to add is not what I expect:
|
||||||
|
|
||||||
|
4│ 1 + ""
|
||||||
|
^^
|
||||||
|
|
||||||
|
This argument is a string of type:
|
||||||
|
|
||||||
|
Str
|
||||||
|
|
||||||
|
But add needs the 2nd argument to be:
|
||||||
|
|
||||||
|
Num a
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[test]
|
||||||
|
// fn parse_problem() {
|
||||||
|
// // can't find something that won't parse currently
|
||||||
|
// }
|
||||||
//
|
//
|
||||||
// #[test]
|
// #[test]
|
||||||
// fn list_of_3_field_records() {
|
// fn mono_problem() {
|
||||||
// expect_success(
|
// // can't produce a mono error (non-exhaustive pattern) yet
|
||||||
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ]",
|
// }
|
||||||
// "[ { foo: 4.1, bar: 2, baz: 0x3 } ] : List { foo : Float, bar : Num *, baz : Int }",
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,4 +10,6 @@
|
||||||
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
|
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
|
||||||
// re-enable this when working on performance optimizations than have it block PRs.
|
// re-enable this when working on performance optimizations than have it block PRs.
|
||||||
#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
|
pub mod link;
|
||||||
pub mod program;
|
pub mod program;
|
||||||
|
pub mod target;
|
||||||
|
|
204
compiler/build/src/link.rs
Normal file
204
compiler/build/src/link.rs
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
use crate::target::arch_str;
|
||||||
|
use std::io;
|
||||||
|
use std::path::Path;
|
||||||
|
use std::process::{Child, Command};
|
||||||
|
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||||
|
|
||||||
|
pub fn link(
|
||||||
|
target: &Triple,
|
||||||
|
binary_path: &Path,
|
||||||
|
host_input_path: &Path,
|
||||||
|
dest_filename: &Path,
|
||||||
|
) -> io::Result<Child> {
|
||||||
|
// TODO we should no longer need to do this once we have platforms on
|
||||||
|
// a package repository, as we can then get precompiled hosts from there.
|
||||||
|
rebuild_host(host_input_path);
|
||||||
|
|
||||||
|
match target {
|
||||||
|
Triple {
|
||||||
|
architecture: Architecture::X86_64,
|
||||||
|
operating_system: OperatingSystem::Linux,
|
||||||
|
..
|
||||||
|
} => link_linux(target, binary_path, host_input_path, dest_filename),
|
||||||
|
Triple {
|
||||||
|
architecture: Architecture::X86_64,
|
||||||
|
operating_system: OperatingSystem::Darwin,
|
||||||
|
..
|
||||||
|
} => link_macos(target, binary_path, host_input_path, dest_filename),
|
||||||
|
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rebuild_host(host_input_path: &Path) {
|
||||||
|
let c_host_src = host_input_path.with_file_name("host.c");
|
||||||
|
let c_host_dest = host_input_path.with_file_name("c_host.o");
|
||||||
|
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 host_dest = host_input_path.with_file_name("host.o");
|
||||||
|
|
||||||
|
// Compile host.c
|
||||||
|
Command::new("clang")
|
||||||
|
.env_clear()
|
||||||
|
.args(&[
|
||||||
|
"-c",
|
||||||
|
c_host_src.to_str().unwrap(),
|
||||||
|
"-o",
|
||||||
|
c_host_dest.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if cargo_host_src.exists() {
|
||||||
|
// Compile and link Cargo.toml, if it exists
|
||||||
|
let cargo_dir = host_input_path.parent().unwrap();
|
||||||
|
let libhost_dir = cargo_dir.join("target").join("release");
|
||||||
|
|
||||||
|
Command::new("cargo")
|
||||||
|
.args(&["build", "--release"])
|
||||||
|
.current_dir(cargo_dir)
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Command::new("ld")
|
||||||
|
.env_clear()
|
||||||
|
.args(&[
|
||||||
|
"-r",
|
||||||
|
"-L",
|
||||||
|
libhost_dir.to_str().unwrap(),
|
||||||
|
c_host_dest.to_str().unwrap(),
|
||||||
|
"-lhost",
|
||||||
|
"-o",
|
||||||
|
host_dest.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
} else if rust_host_src.exists() {
|
||||||
|
// Compile and link host.rs, if it exists
|
||||||
|
Command::new("rustc")
|
||||||
|
.args(&[
|
||||||
|
rust_host_src.to_str().unwrap(),
|
||||||
|
"-o",
|
||||||
|
rust_host_dest.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
Command::new("ld")
|
||||||
|
.env_clear()
|
||||||
|
.args(&[
|
||||||
|
"-r",
|
||||||
|
c_host_dest.to_str().unwrap(),
|
||||||
|
rust_host_dest.to_str().unwrap(),
|
||||||
|
"-o",
|
||||||
|
host_dest.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
// Clean up rust_host.o
|
||||||
|
Command::new("rm")
|
||||||
|
.env_clear()
|
||||||
|
.args(&[
|
||||||
|
"-f",
|
||||||
|
rust_host_dest.to_str().unwrap(),
|
||||||
|
c_host_dest.to_str().unwrap(),
|
||||||
|
])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
// Clean up rust_host.o
|
||||||
|
Command::new("mv")
|
||||||
|
.env_clear()
|
||||||
|
.args(&[c_host_dest, host_dest])
|
||||||
|
.output()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link_linux(
|
||||||
|
target: &Triple,
|
||||||
|
binary_path: &Path,
|
||||||
|
host_input_path: &Path,
|
||||||
|
dest_filename: &Path,
|
||||||
|
) -> io::Result<Child> {
|
||||||
|
let libcrt_path = if Path::new("/usr/lib/x86_64-linux-gnu").exists() {
|
||||||
|
Path::new("/usr/lib/x86_64-linux-gnu")
|
||||||
|
} else {
|
||||||
|
Path::new("/usr/lib")
|
||||||
|
};
|
||||||
|
let libgcc_path = if Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
|
||||||
|
Path::new("/lib/x86_64-linux-gnu/libgcc_s.so.1")
|
||||||
|
} else if Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1").exists() {
|
||||||
|
Path::new("/usr/lib/x86_64-linux-gnu/libgcc_s.so.1")
|
||||||
|
} else {
|
||||||
|
Path::new("/usr/lib/libgcc_s.so.1")
|
||||||
|
};
|
||||||
|
// NOTE: order of arguments to `ld` matters here!
|
||||||
|
// The `-l` flags should go after the `.o` arguments
|
||||||
|
Command::new("ld")
|
||||||
|
// Don't allow LD_ env vars to affect this
|
||||||
|
.env_clear()
|
||||||
|
.args(&[
|
||||||
|
"-arch",
|
||||||
|
arch_str(target),
|
||||||
|
libcrt_path.join("crti.o").to_str().unwrap(),
|
||||||
|
libcrt_path.join("crtn.o").to_str().unwrap(),
|
||||||
|
libcrt_path.join("Scrt1.o").to_str().unwrap(),
|
||||||
|
"-dynamic-linker",
|
||||||
|
"/lib64/ld-linux-x86-64.so.2",
|
||||||
|
// Inputs
|
||||||
|
host_input_path.to_str().unwrap(), // host.o
|
||||||
|
dest_filename.to_str().unwrap(), // app.o
|
||||||
|
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496365925
|
||||||
|
// for discussion and further references
|
||||||
|
"-lc",
|
||||||
|
"-lm",
|
||||||
|
"-lpthread",
|
||||||
|
"-ldl",
|
||||||
|
"-lrt",
|
||||||
|
"-lutil",
|
||||||
|
"-lc_nonshared",
|
||||||
|
"-lc++",
|
||||||
|
"-lunwind",
|
||||||
|
libgcc_path.to_str().unwrap(),
|
||||||
|
// Output
|
||||||
|
"-o",
|
||||||
|
binary_path.to_str().unwrap(), // app
|
||||||
|
])
|
||||||
|
.spawn()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link_macos(
|
||||||
|
target: &Triple,
|
||||||
|
binary_path: &Path,
|
||||||
|
host_input_path: &Path,
|
||||||
|
dest_filename: &Path,
|
||||||
|
) -> io::Result<Child> {
|
||||||
|
// NOTE: order of arguments to `ld` matters here!
|
||||||
|
// The `-l` flags should go after the `.o` arguments
|
||||||
|
Command::new("ld")
|
||||||
|
// Don't allow LD_ env vars to affect this
|
||||||
|
.env_clear()
|
||||||
|
.args(&[
|
||||||
|
"-arch",
|
||||||
|
target.architecture.to_string().as_str(),
|
||||||
|
// Inputs
|
||||||
|
host_input_path.to_str().unwrap(), // host.o
|
||||||
|
dest_filename.to_str().unwrap(), // roc_app.o
|
||||||
|
// Libraries - see https://github.com/rtfeldman/roc/pull/554#discussion_r496392274
|
||||||
|
// for discussion and further references
|
||||||
|
"-lSystem",
|
||||||
|
"-lresolv",
|
||||||
|
"-lpthread",
|
||||||
|
// "-lrt", // TODO shouldn't we need this?
|
||||||
|
// "-lc_nonshared", // TODO shouldn't we need this?
|
||||||
|
// "-lgcc", // TODO will eventually need compiler_rt from gcc or something - see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
|
||||||
|
// "-lunwind", // TODO will eventually need this, see https://github.com/rtfeldman/roc/pull/554#discussion_r496370840
|
||||||
|
"-lc++", // TODO shouldn't we need this?
|
||||||
|
// Output
|
||||||
|
"-o",
|
||||||
|
binary_path.to_str().unwrap(), // app
|
||||||
|
])
|
||||||
|
.spawn()
|
||||||
|
}
|
|
@ -1,26 +1,21 @@
|
||||||
|
use crate::target;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use inkwell::context::Context;
|
use inkwell::context::Context;
|
||||||
use inkwell::targets::{
|
use inkwell::targets::{CodeModel, FileType, RelocMode};
|
||||||
CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple,
|
|
||||||
};
|
|
||||||
use inkwell::OptimizationLevel;
|
use inkwell::OptimizationLevel;
|
||||||
use roc_collections::all::default_hasher;
|
|
||||||
use roc_gen::layout_id::LayoutIds;
|
use roc_gen::layout_id::LayoutIds;
|
||||||
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel};
|
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
|
||||||
use roc_load::file::LoadedModule;
|
use roc_load::file::MonomorphizedModule;
|
||||||
use roc_mono::ir::{Env, PartialProc, Procs};
|
|
||||||
use roc_mono::layout::{Layout, LayoutCache};
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor};
|
use target_lexicon::Triple;
|
||||||
|
|
||||||
// TODO how should imported modules factor into this? What if those use builtins too?
|
// TODO how should imported modules factor into this? What if those use builtins too?
|
||||||
// TODO this should probably use more helper functions
|
// TODO this should probably use more helper functions
|
||||||
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
// TODO make this polymorphic in the llvm functions so it can be reused for another backend.
|
||||||
#[allow(clippy::cognitive_complexity)]
|
#[allow(clippy::cognitive_complexity)]
|
||||||
pub fn gen(
|
pub fn gen_from_mono_module(
|
||||||
arena: &Bump,
|
arena: &Bump,
|
||||||
mut loaded: LoadedModule,
|
loaded: MonomorphizedModule,
|
||||||
filename: PathBuf,
|
filename: PathBuf,
|
||||||
target: Triple,
|
target: Triple,
|
||||||
dest_filename: &Path,
|
dest_filename: &Path,
|
||||||
|
@ -54,14 +49,6 @@ pub fn gen(
|
||||||
println!("\n{}\n", buf);
|
println!("\n{}\n", buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look up the types and expressions of the `provided` values
|
|
||||||
let mut decls_by_id = loaded.declarations_by_id;
|
|
||||||
let home_decls = decls_by_id
|
|
||||||
.remove(&loaded.module_id)
|
|
||||||
.expect("Root module ID not found in loaded declarations_by_id");
|
|
||||||
|
|
||||||
let mut subs = loaded.solved.into_inner();
|
|
||||||
|
|
||||||
// Generate the binary
|
// Generate the binary
|
||||||
|
|
||||||
let context = Context::create();
|
let context = Context::create();
|
||||||
|
@ -71,157 +58,8 @@ pub fn gen(
|
||||||
|
|
||||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||||
|
|
||||||
let mut exposed_to_host =
|
|
||||||
HashSet::with_capacity_and_hasher(loaded.exposed_vars_by_symbol.len(), default_hasher());
|
|
||||||
|
|
||||||
for (symbol, _) in loaded.exposed_vars_by_symbol {
|
|
||||||
exposed_to_host.insert(symbol);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ident_ids = loaded.interns.all_ident_ids.remove(&home).unwrap();
|
|
||||||
let mut layout_ids = LayoutIds::default();
|
|
||||||
let mut procs = Procs::default();
|
|
||||||
let mut mono_problems = std::vec::Vec::new();
|
|
||||||
let mut layout_cache = LayoutCache::default();
|
|
||||||
let mut mono_env = Env {
|
|
||||||
arena,
|
|
||||||
subs: &mut subs,
|
|
||||||
problems: &mut mono_problems,
|
|
||||||
home,
|
|
||||||
ident_ids: &mut ident_ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add modules' decls to Procs
|
|
||||||
for (_, mut decls) in decls_by_id
|
|
||||||
.drain()
|
|
||||||
.chain(std::iter::once((loaded.module_id, home_decls)))
|
|
||||||
{
|
|
||||||
for decl in decls.drain(..) {
|
|
||||||
use roc_can::def::Declaration::*;
|
|
||||||
use roc_can::expr::Expr::*;
|
|
||||||
use roc_can::pattern::Pattern::*;
|
|
||||||
|
|
||||||
match decl {
|
|
||||||
Declare(def) | Builtin(def) => match def.loc_pattern.value {
|
|
||||||
Identifier(symbol) => {
|
|
||||||
match def.loc_expr.value {
|
|
||||||
Closure {
|
|
||||||
function_type: annotation,
|
|
||||||
return_type: ret_var,
|
|
||||||
recursive: recursivity,
|
|
||||||
arguments: loc_args,
|
|
||||||
loc_body: boxed_body,
|
|
||||||
..
|
|
||||||
} => {
|
|
||||||
let is_tail_recursive =
|
|
||||||
matches!(recursivity, roc_can::expr::Recursive::TailRecursive);
|
|
||||||
|
|
||||||
let loc_body = *boxed_body;
|
|
||||||
|
|
||||||
// If this is an exposed symbol, we need to
|
|
||||||
// register it as such. Otherwise, since it
|
|
||||||
// never gets called by Roc code, it will never
|
|
||||||
// get specialized!
|
|
||||||
if exposed_to_host.contains(&symbol) {
|
|
||||||
let mut pattern_vars =
|
|
||||||
bumpalo::collections::Vec::with_capacity_in(
|
|
||||||
loc_args.len(),
|
|
||||||
arena,
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var, _) in loc_args.iter() {
|
|
||||||
pattern_vars.push(*var);
|
|
||||||
}
|
|
||||||
|
|
||||||
let layout = layout_cache.from_var(mono_env.arena, annotation, mono_env.subs).unwrap_or_else(|err|
|
|
||||||
todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err)
|
|
||||||
);
|
|
||||||
|
|
||||||
procs.insert_exposed(
|
|
||||||
symbol,
|
|
||||||
layout,
|
|
||||||
pattern_vars, //: Vec<'a, Variable>,
|
|
||||||
annotation,
|
|
||||||
ret_var,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
procs.insert_named(
|
|
||||||
&mut mono_env,
|
|
||||||
&mut layout_cache,
|
|
||||||
symbol,
|
|
||||||
annotation,
|
|
||||||
loc_args,
|
|
||||||
loc_body,
|
|
||||||
is_tail_recursive,
|
|
||||||
ret_var,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
body => {
|
|
||||||
let annotation = def.expr_var;
|
|
||||||
let proc = PartialProc {
|
|
||||||
annotation,
|
|
||||||
// This is a 0-arity thunk, so it has no arguments.
|
|
||||||
pattern_symbols: bumpalo::collections::Vec::new_in(
|
|
||||||
mono_env.arena,
|
|
||||||
),
|
|
||||||
is_self_recursive: false,
|
|
||||||
body,
|
|
||||||
};
|
|
||||||
|
|
||||||
// If this is an exposed symbol, we need to
|
|
||||||
// register it as such. Otherwise, since it
|
|
||||||
// never gets called by Roc code, it will never
|
|
||||||
// get specialized!
|
|
||||||
if exposed_to_host.contains(&symbol) {
|
|
||||||
let pattern_vars = bumpalo::collections::Vec::new_in(arena);
|
|
||||||
let ret_layout = layout_cache.from_var(mono_env.arena, annotation, mono_env.subs).unwrap_or_else(|err|
|
|
||||||
todo!("TODO gracefully handle the situation where we expose a function to the host which doesn't have a valid layout (e.g. maybe the function wasn't monomorphic): {:?}", err)
|
|
||||||
);
|
|
||||||
let layout =
|
|
||||||
Layout::FunctionPointer(&[], arena.alloc(ret_layout));
|
|
||||||
|
|
||||||
procs.insert_exposed(
|
|
||||||
symbol,
|
|
||||||
layout,
|
|
||||||
pattern_vars,
|
|
||||||
// It seems brittle that we're passing
|
|
||||||
// annotation twice - especially since
|
|
||||||
// in both cases we're giving the
|
|
||||||
// annotation to the top-level value,
|
|
||||||
// not the thunk function it will code
|
|
||||||
// gen to. It seems to work, but that
|
|
||||||
// may only be because at present we
|
|
||||||
// only use the function annotation
|
|
||||||
// variable during specialization, and
|
|
||||||
// exposed values are never specialized
|
|
||||||
// because they must be monomorphic.
|
|
||||||
annotation,
|
|
||||||
annotation,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
procs.partial_procs.insert(symbol, proc);
|
|
||||||
procs.module_thunks.insert(symbol);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
other => {
|
|
||||||
todo!("TODO gracefully handle Declare({:?})", other);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
DeclareRec(_defs) => {
|
|
||||||
todo!("TODO support DeclareRec");
|
|
||||||
}
|
|
||||||
InvalidCycle(_loc_idents, _regions) => {
|
|
||||||
todo!("TODO handle InvalidCycle");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
let mut env = roc_gen::llvm::build::Env {
|
let env = roc_gen::llvm::build::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
builder: &builder,
|
builder: &builder,
|
||||||
context: &context,
|
context: &context,
|
||||||
|
@ -229,36 +67,27 @@ pub fn gen(
|
||||||
module,
|
module,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
leak: false,
|
leak: false,
|
||||||
exposed_to_host,
|
exposed_to_host: loaded.exposed_to_host.keys().copied().collect(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Populate Procs further and get the low-level Expr from the canonical Expr
|
// Populate Procs further and get the low-level Expr from the canonical Expr
|
||||||
let mut headers = {
|
let mut headers = Vec::with_capacity(loaded.procedures.len());
|
||||||
let num_headers = match &procs.pending_specializations {
|
|
||||||
Some(map) => map.len(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Vec::with_capacity(num_headers)
|
|
||||||
};
|
|
||||||
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
procs.runtime_errors,
|
|
||||||
roc_collections::all::MutMap::default()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Put this module's ident_ids back in the interns, so we can use them in env.
|
|
||||||
// This must happen *after* building the headers, because otherwise there's
|
|
||||||
// a conflicting mutable borrow on ident_ids.
|
|
||||||
env.interns.all_ident_ids.insert(home, ident_ids);
|
|
||||||
|
|
||||||
// Add all the Proc headers to the module.
|
// Add all the Proc headers to the module.
|
||||||
// We have to do this in a separate pass first,
|
// We have to do this in a separate pass first,
|
||||||
// because their bodies may reference each other.
|
// because their bodies may reference each other.
|
||||||
for ((symbol, layout), proc) in procs.get_specialized_procs(arena) {
|
let mut layout_ids = LayoutIds::default();
|
||||||
|
|
||||||
|
let mut scope = Scope::default();
|
||||||
|
for ((symbol, layout), proc) in loaded.procedures {
|
||||||
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||||
|
|
||||||
|
if proc.args.is_empty() {
|
||||||
|
// this is a 0-argument thunk, i.e. a top-level constant definition
|
||||||
|
// it must be in-scope everywhere in the module!
|
||||||
|
scope.insert_top_level_thunk(symbol, layout, fn_val);
|
||||||
|
}
|
||||||
|
|
||||||
headers.push((proc, fn_val));
|
headers.push((proc, fn_val));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,11 +97,13 @@ pub fn gen(
|
||||||
// (This approach means we don't have to defensively clone name here.)
|
// (This approach means we don't have to defensively clone name here.)
|
||||||
//
|
//
|
||||||
// println!("\n\nBuilding and then verifying function {:?}\n\n", proc);
|
// println!("\n\nBuilding and then verifying function {:?}\n\n", proc);
|
||||||
build_proc(&env, &mut layout_ids, proc, fn_val);
|
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
|
||||||
|
|
||||||
if fn_val.verify(true) {
|
if fn_val.verify(true) {
|
||||||
fpm.run_on(&fn_val);
|
fpm.run_on(&fn_val);
|
||||||
} else {
|
} else {
|
||||||
|
// fn_val.print_to_stderr();
|
||||||
|
// env.module.print_to_stderr();
|
||||||
// NOTE: If this fails, uncomment the above println to debug.
|
// NOTE: If this fails, uncomment the above println to debug.
|
||||||
panic!(
|
panic!(
|
||||||
"Non-main function failed LLVM verification. Uncomment the above println to debug!"
|
"Non-main function failed LLVM verification. Uncomment the above println to debug!"
|
||||||
|
@ -295,80 +126,10 @@ pub fn gen(
|
||||||
|
|
||||||
// Emit the .o file
|
// Emit the .o file
|
||||||
|
|
||||||
// NOTE: arch_str is *not* the same as the beginning of the magic target triple
|
|
||||||
// string! For example, if it's "x86-64" here, the magic target triple string
|
|
||||||
// will begin with "x86_64" (with an underscore) instead.
|
|
||||||
let arch_str = match target.architecture {
|
|
||||||
Architecture::X86_64 => {
|
|
||||||
Target::initialize_x86(&InitializationConfig::default());
|
|
||||||
|
|
||||||
"x86-64"
|
|
||||||
}
|
|
||||||
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
|
|
||||||
// NOTE: why not enable arm and wasm by default?
|
|
||||||
//
|
|
||||||
// We had some trouble getting them to link properly. This may be resolved in the
|
|
||||||
// future, or maybe it was just some weird configuration on one machine.
|
|
||||||
Target::initialize_arm(&InitializationConfig::default());
|
|
||||||
|
|
||||||
"arm"
|
|
||||||
}
|
|
||||||
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
|
|
||||||
Target::initialize_webassembly(&InitializationConfig::default());
|
|
||||||
|
|
||||||
"wasm32"
|
|
||||||
}
|
|
||||||
_ => panic!(
|
|
||||||
"TODO gracefully handle unsupported target architecture: {:?}",
|
|
||||||
target.architecture
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
let opt = OptimizationLevel::Aggressive;
|
let opt = OptimizationLevel::Aggressive;
|
||||||
let reloc = RelocMode::Default;
|
let reloc = RelocMode::Default;
|
||||||
let model = CodeModel::Default;
|
let model = CodeModel::Default;
|
||||||
|
let target_machine = target::target_machine(&target, opt, reloc, model).unwrap();
|
||||||
// Best guide I've found on how to determine these magic strings:
|
|
||||||
//
|
|
||||||
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
|
|
||||||
let target_triple_str = match target {
|
|
||||||
Triple {
|
|
||||||
architecture: Architecture::X86_64,
|
|
||||||
vendor: Vendor::Unknown,
|
|
||||||
operating_system: OperatingSystem::Linux,
|
|
||||||
..
|
|
||||||
} => "x86_64-unknown-linux-gnu",
|
|
||||||
Triple {
|
|
||||||
architecture: Architecture::X86_64,
|
|
||||||
vendor: Vendor::Pc,
|
|
||||||
operating_system: OperatingSystem::Linux,
|
|
||||||
..
|
|
||||||
} => "x86_64-pc-linux-gnu",
|
|
||||||
Triple {
|
|
||||||
architecture: Architecture::X86_64,
|
|
||||||
vendor: Vendor::Unknown,
|
|
||||||
operating_system: OperatingSystem::Darwin,
|
|
||||||
..
|
|
||||||
} => "x86_64-unknown-darwin10",
|
|
||||||
Triple {
|
|
||||||
architecture: Architecture::X86_64,
|
|
||||||
vendor: Vendor::Apple,
|
|
||||||
operating_system: OperatingSystem::Darwin,
|
|
||||||
..
|
|
||||||
} => "x86_64-apple-darwin10",
|
|
||||||
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
|
||||||
};
|
|
||||||
let target_machine = Target::from_name(arch_str)
|
|
||||||
.unwrap()
|
|
||||||
.create_target_machine(
|
|
||||||
&TargetTriple::create(target_triple_str),
|
|
||||||
arch_str,
|
|
||||||
"+avx2", // TODO this string was used uncritically from an example, and should be reexamined
|
|
||||||
opt,
|
|
||||||
reloc,
|
|
||||||
model,
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
target_machine
|
target_machine
|
||||||
.write_to_file(&env.module, FileType::Object, &dest_filename)
|
.write_to_file(&env.module, FileType::Object, &dest_filename)
|
||||||
|
|
76
compiler/build/src/target.rs
Normal file
76
compiler/build/src/target.rs
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
use inkwell::targets::{
|
||||||
|
CodeModel, InitializationConfig, RelocMode, Target, TargetMachine, TargetTriple,
|
||||||
|
};
|
||||||
|
use inkwell::OptimizationLevel;
|
||||||
|
use target_lexicon::{Architecture, OperatingSystem, Triple};
|
||||||
|
|
||||||
|
pub fn target_triple_str(target: &Triple) -> &'static str {
|
||||||
|
// Best guide I've found on how to determine these magic strings:
|
||||||
|
//
|
||||||
|
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
|
||||||
|
match target {
|
||||||
|
Triple {
|
||||||
|
architecture: Architecture::X86_64,
|
||||||
|
operating_system: OperatingSystem::Linux,
|
||||||
|
..
|
||||||
|
} => "x86_64-unknown-linux-gnu",
|
||||||
|
Triple {
|
||||||
|
architecture: Architecture::X86_64,
|
||||||
|
operating_system: OperatingSystem::Darwin,
|
||||||
|
..
|
||||||
|
} => "x86_64-unknown-darwin10",
|
||||||
|
_ => panic!("TODO gracefully handle unsupported target: {:?}", target),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// NOTE: arch_str is *not* the same as the beginning of the magic target triple
|
||||||
|
/// string! For example, if it's "x86-64" here, the magic target triple string
|
||||||
|
/// will begin with "x86_64" (with an underscore) instead.
|
||||||
|
pub fn arch_str(target: &Triple) -> &'static str {
|
||||||
|
// Best guide I've found on how to determine these magic strings:
|
||||||
|
//
|
||||||
|
// https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures
|
||||||
|
match target.architecture {
|
||||||
|
Architecture::X86_64 => {
|
||||||
|
Target::initialize_x86(&InitializationConfig::default());
|
||||||
|
|
||||||
|
"x86-64"
|
||||||
|
}
|
||||||
|
Architecture::Arm(_) if cfg!(feature = "target-arm") => {
|
||||||
|
// NOTE: why not enable arm and wasm by default?
|
||||||
|
//
|
||||||
|
// We had some trouble getting them to link properly. This may be resolved in the
|
||||||
|
// future, or maybe it was just some weird configuration on one machine.
|
||||||
|
Target::initialize_arm(&InitializationConfig::default());
|
||||||
|
|
||||||
|
"arm"
|
||||||
|
}
|
||||||
|
Architecture::Wasm32 if cfg!(feature = "target-webassembly") => {
|
||||||
|
Target::initialize_webassembly(&InitializationConfig::default());
|
||||||
|
|
||||||
|
"wasm32"
|
||||||
|
}
|
||||||
|
_ => panic!(
|
||||||
|
"TODO gracefully handle unsupported target architecture: {:?}",
|
||||||
|
target.architecture
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn target_machine(
|
||||||
|
target: &Triple,
|
||||||
|
opt: OptimizationLevel,
|
||||||
|
reloc: RelocMode,
|
||||||
|
model: CodeModel,
|
||||||
|
) -> Option<TargetMachine> {
|
||||||
|
let arch = arch_str(target);
|
||||||
|
|
||||||
|
Target::from_name(arch).unwrap().create_target_machine(
|
||||||
|
&TargetTriple::create(target_triple_str(target)),
|
||||||
|
arch,
|
||||||
|
"+avx2", // TODO this string was used uncritically from an example, and should be reexamined
|
||||||
|
opt,
|
||||||
|
reloc,
|
||||||
|
model,
|
||||||
|
)
|
||||||
|
}
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
mod libm;
|
mod libm;
|
||||||
|
|
||||||
/// TODO replace this with a normal Inkwell build_cast call - this was just
|
/// TODO this is no longer used. Feel free to delete it the next time
|
||||||
/// used as a proof of concept for getting bitcode importing working!
|
/// we need to rebuild builtins.bc!
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub fn i64_to_f64_(num: i64) -> f64 {
|
pub fn i64_to_f64_(num: i64) -> f64 {
|
||||||
num as f64
|
num as f64
|
||||||
|
|
|
@ -12,6 +12,7 @@ pub enum Mode {
|
||||||
Uniqueness,
|
Uniqueness,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct StdLib {
|
pub struct StdLib {
|
||||||
pub mode: Mode,
|
pub mode: Mode,
|
||||||
pub types: MutMap<Symbol, (SolvedType, Region)>,
|
pub types: MutMap<Symbol, (SolvedType, Region)>,
|
||||||
|
|
|
@ -25,7 +25,7 @@ pub struct Env<'a> {
|
||||||
pub tailcallable_symbol: Option<Symbol>,
|
pub tailcallable_symbol: Option<Symbol>,
|
||||||
|
|
||||||
/// Symbols which were referenced by qualified lookups.
|
/// Symbols which were referenced by qualified lookups.
|
||||||
pub referenced_symbols: MutSet<Symbol>,
|
pub qualified_lookups: MutSet<Symbol>,
|
||||||
|
|
||||||
pub ident_ids: IdentIds,
|
pub ident_ids: IdentIds,
|
||||||
pub exposed_ident_ids: IdentIds,
|
pub exposed_ident_ids: IdentIds,
|
||||||
|
@ -46,7 +46,7 @@ impl<'a> Env<'a> {
|
||||||
exposed_ident_ids,
|
exposed_ident_ids,
|
||||||
problems: Vec::new(),
|
problems: Vec::new(),
|
||||||
closures: MutMap::default(),
|
closures: MutMap::default(),
|
||||||
referenced_symbols: MutSet::default(),
|
qualified_lookups: MutSet::default(),
|
||||||
tailcallable_symbol: None,
|
tailcallable_symbol: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ impl<'a> Env<'a> {
|
||||||
Some(ident_id) => {
|
Some(ident_id) => {
|
||||||
let symbol = Symbol::new(module_id, *ident_id);
|
let symbol = Symbol::new(module_id, *ident_id);
|
||||||
|
|
||||||
self.referenced_symbols.insert(symbol);
|
self.qualified_lookups.insert(symbol);
|
||||||
|
|
||||||
Ok(symbol)
|
Ok(symbol)
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ impl<'a> Env<'a> {
|
||||||
Some(ident_id) => {
|
Some(ident_id) => {
|
||||||
let symbol = Symbol::new(module_id, *ident_id);
|
let symbol = Symbol::new(module_id, *ident_id);
|
||||||
|
|
||||||
self.referenced_symbols.insert(symbol);
|
self.qualified_lookups.insert(symbol);
|
||||||
|
|
||||||
Ok(symbol)
|
Ok(symbol)
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,6 +126,7 @@ pub enum Expr {
|
||||||
},
|
},
|
||||||
/// field accessor as a function, e.g. (.foo) expr
|
/// field accessor as a function, e.g. (.foo) expr
|
||||||
Accessor {
|
Accessor {
|
||||||
|
function_var: Variable,
|
||||||
record_var: Variable,
|
record_var: Variable,
|
||||||
closure_var: Variable,
|
closure_var: Variable,
|
||||||
ext_var: Variable,
|
ext_var: Variable,
|
||||||
|
@ -550,6 +551,7 @@ pub fn canonicalize_expr<'a>(
|
||||||
}
|
}
|
||||||
ast::Expr::AccessorFunction(field) => (
|
ast::Expr::AccessorFunction(field) => (
|
||||||
Accessor {
|
Accessor {
|
||||||
|
function_var: var_store.fresh(),
|
||||||
record_var: var_store.fresh(),
|
record_var: var_store.fresh(),
|
||||||
ext_var: var_store.fresh(),
|
ext_var: var_store.fresh(),
|
||||||
closure_var: var_store.fresh(),
|
closure_var: var_store.fresh(),
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
use crate::builtins::builtin_defs;
|
|
||||||
use crate::def::{canonicalize_defs, sort_can_defs, Declaration};
|
use crate::def::{canonicalize_defs, sort_can_defs, Declaration};
|
||||||
use crate::env::Env;
|
use crate::env::Env;
|
||||||
use crate::expr::Output;
|
use crate::expr::Output;
|
||||||
|
@ -115,7 +114,7 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (mut defs, _scope, output, symbols_introduced) = canonicalize_defs(
|
let (defs, _scope, output, symbols_introduced) = canonicalize_defs(
|
||||||
&mut env,
|
&mut env,
|
||||||
Output::default(),
|
Output::default(),
|
||||||
var_store,
|
var_store,
|
||||||
|
@ -149,17 +148,12 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather up all the symbols that were referenced from other modules.
|
// Gather up all the symbols that were referenced from other modules.
|
||||||
for symbol in env.referenced_symbols.iter() {
|
for symbol in env.qualified_lookups.iter() {
|
||||||
references.insert(*symbol);
|
references.insert(*symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add defs for any referenced builtins.
|
// NOTE previously we inserted builtin defs into the list of defs here
|
||||||
for (symbol, def) in builtin_defs(var_store) {
|
// this is now done later, in file.rs.
|
||||||
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
|
|
||||||
{
|
|
||||||
defs.can_defs_by_symbol.insert(symbol, def);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match sort_can_defs(&mut env, defs, Output::default()) {
|
match sort_can_defs(&mut env, defs, Output::default()) {
|
||||||
(Ok(declarations), output) => {
|
(Ok(declarations), output) => {
|
||||||
|
@ -250,6 +244,11 @@ pub fn canonicalize_module_defs<'a>(
|
||||||
references.insert(symbol);
|
references.insert(symbol);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gather up all the symbols that were referenced from other modules.
|
||||||
|
for symbol in env.qualified_lookups.iter() {
|
||||||
|
references.insert(*symbol);
|
||||||
|
}
|
||||||
|
|
||||||
Ok(ModuleOutput {
|
Ok(ModuleOutput {
|
||||||
aliases,
|
aliases,
|
||||||
rigid_variables,
|
rigid_variables,
|
||||||
|
|
|
@ -675,6 +675,7 @@ pub fn constrain_expr(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Accessor {
|
Accessor {
|
||||||
|
function_var,
|
||||||
field,
|
field,
|
||||||
record_var,
|
record_var,
|
||||||
closure_var,
|
closure_var,
|
||||||
|
@ -701,16 +702,19 @@ pub fn constrain_expr(
|
||||||
region,
|
region,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let function_type = Type::Function(
|
||||||
|
vec![record_type],
|
||||||
|
Box::new(Type::Variable(*closure_var)),
|
||||||
|
Box::new(field_type),
|
||||||
|
);
|
||||||
|
|
||||||
exists(
|
exists(
|
||||||
vec![*record_var, *closure_var, field_var, ext_var],
|
vec![*record_var, *function_var, *closure_var, field_var, ext_var],
|
||||||
And(vec![
|
And(vec![
|
||||||
|
Eq(function_type.clone(), expected, category.clone(), region),
|
||||||
Eq(
|
Eq(
|
||||||
Type::Function(
|
function_type,
|
||||||
vec![record_type],
|
NoExpectation(Variable(*function_var)),
|
||||||
Box::new(Type::Variable(*closure_var)),
|
|
||||||
Box::new(field_type),
|
|
||||||
),
|
|
||||||
expected,
|
|
||||||
category,
|
category,
|
||||||
region,
|
region,
|
||||||
),
|
),
|
||||||
|
|
|
@ -174,7 +174,11 @@ pub struct FreeVars {
|
||||||
pub wildcards: Vec<Variable>,
|
pub wildcards: Vec<Variable>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut VarStore) -> Type {
|
pub fn to_type(
|
||||||
|
solved_type: &SolvedType,
|
||||||
|
free_vars: &mut FreeVars,
|
||||||
|
var_store: &mut VarStore,
|
||||||
|
) -> Type {
|
||||||
use roc_types::solved_types::SolvedType::*;
|
use roc_types::solved_types::SolvedType::*;
|
||||||
|
|
||||||
match solved_type {
|
match solved_type {
|
||||||
|
|
|
@ -67,6 +67,7 @@ pub fn constrain_decls(
|
||||||
|
|
||||||
// perform usage analysis on the whole file
|
// perform usage analysis on the whole file
|
||||||
let mut var_usage = VarUsage::default();
|
let mut var_usage = VarUsage::default();
|
||||||
|
|
||||||
for decl in decls.iter().rev() {
|
for decl in decls.iter().rev() {
|
||||||
// NOTE: rigids are empty because they are not shared between top-level definitions
|
// NOTE: rigids are empty because they are not shared between top-level definitions
|
||||||
match decl {
|
match decl {
|
||||||
|
@ -1445,6 +1446,7 @@ pub fn constrain_expr(
|
||||||
}
|
}
|
||||||
|
|
||||||
Accessor {
|
Accessor {
|
||||||
|
function_var,
|
||||||
field,
|
field,
|
||||||
record_var,
|
record_var,
|
||||||
closure_var,
|
closure_var,
|
||||||
|
@ -1490,6 +1492,7 @@ pub fn constrain_expr(
|
||||||
exists(
|
exists(
|
||||||
vec![
|
vec![
|
||||||
*record_var,
|
*record_var,
|
||||||
|
*function_var,
|
||||||
*closure_var,
|
*closure_var,
|
||||||
*field_var,
|
*field_var,
|
||||||
*ext_var,
|
*ext_var,
|
||||||
|
@ -1497,7 +1500,16 @@ pub fn constrain_expr(
|
||||||
field_uniq_var,
|
field_uniq_var,
|
||||||
record_uniq_var,
|
record_uniq_var,
|
||||||
],
|
],
|
||||||
And(vec![Eq(fn_type, expected, category, region), record_con]),
|
And(vec![
|
||||||
|
Eq(fn_type.clone(), expected, category.clone(), region),
|
||||||
|
Eq(
|
||||||
|
fn_type,
|
||||||
|
Expected::NoExpectation(Variable(*function_var)),
|
||||||
|
category,
|
||||||
|
region,
|
||||||
|
),
|
||||||
|
record_con,
|
||||||
|
]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
RuntimeError(_) => True,
|
RuntimeError(_) => True,
|
||||||
|
|
|
@ -44,6 +44,7 @@ target-lexicon = "0.10"
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
roc_can = { path = "../can" }
|
roc_can = { path = "../can" }
|
||||||
roc_parse = { path = "../parse" }
|
roc_parse = { path = "../parse" }
|
||||||
|
roc_load = { path = "../load" }
|
||||||
pretty_assertions = "0.5.1"
|
pretty_assertions = "0.5.1"
|
||||||
maplit = "1.0.1"
|
maplit = "1.0.1"
|
||||||
indoc = "0.3.3"
|
indoc = "0.3.3"
|
||||||
|
|
|
@ -10,7 +10,7 @@ impl LayoutId {
|
||||||
// Returns something like "foo#1" when given a symbol that interns to "foo"
|
// Returns something like "foo#1" when given a symbol that interns to "foo"
|
||||||
// and a LayoutId of 1.
|
// and a LayoutId of 1.
|
||||||
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
|
pub fn to_symbol_string(self, symbol: Symbol, interns: &Interns) -> String {
|
||||||
format!("{}#{}", symbol.ident_string(interns), self.0)
|
format!("{}_{}", symbol.ident_string(interns), self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ use inkwell::OptimizationLevel;
|
||||||
use inkwell::{AddressSpace, IntPredicate};
|
use inkwell::{AddressSpace, IntPredicate};
|
||||||
use roc_collections::all::{ImMap, MutSet};
|
use roc_collections::all::{ImMap, MutSet};
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::symbol::{Interns, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
use roc_mono::ir::{JoinPointId, Wrapped};
|
use roc_mono::ir::{JoinPointId, Wrapped};
|
||||||
use roc_mono::layout::{Builtin, Layout, MemoryMode};
|
use roc_mono::layout::{Builtin, Layout, MemoryMode};
|
||||||
use target_lexicon::CallingConvention;
|
use target_lexicon::CallingConvention;
|
||||||
|
@ -53,6 +53,7 @@ pub enum OptLevel {
|
||||||
#[derive(Default, Debug, Clone, PartialEq)]
|
#[derive(Default, Debug, Clone, PartialEq)]
|
||||||
pub struct Scope<'a, 'ctx> {
|
pub struct Scope<'a, 'ctx> {
|
||||||
symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>,
|
symbols: ImMap<Symbol, (Layout<'a>, PointerValue<'ctx>)>,
|
||||||
|
pub top_level_thunks: ImMap<Symbol, (Layout<'a>, FunctionValue<'ctx>)>,
|
||||||
join_points: ImMap<JoinPointId, (BasicBlock<'ctx>, &'a [PointerValue<'ctx>])>,
|
join_points: ImMap<JoinPointId, (BasicBlock<'ctx>, &'a [PointerValue<'ctx>])>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,23 +64,23 @@ impl<'a, 'ctx> Scope<'a, 'ctx> {
|
||||||
pub fn insert(&mut self, symbol: Symbol, value: (Layout<'a>, PointerValue<'ctx>)) {
|
pub fn insert(&mut self, symbol: Symbol, value: (Layout<'a>, PointerValue<'ctx>)) {
|
||||||
self.symbols.insert(symbol, value);
|
self.symbols.insert(symbol, value);
|
||||||
}
|
}
|
||||||
|
pub fn insert_top_level_thunk(
|
||||||
|
&mut self,
|
||||||
|
symbol: Symbol,
|
||||||
|
layout: Layout<'a>,
|
||||||
|
function_value: FunctionValue<'ctx>,
|
||||||
|
) {
|
||||||
|
self.top_level_thunks
|
||||||
|
.insert(symbol, (layout, function_value));
|
||||||
|
}
|
||||||
fn remove(&mut self, symbol: &Symbol) {
|
fn remove(&mut self, symbol: &Symbol) {
|
||||||
self.symbols.remove(symbol);
|
self.symbols.remove(symbol);
|
||||||
}
|
}
|
||||||
/*
|
|
||||||
fn get_join_point(&self, symbol: &JoinPointId) -> Option<&PhiValue<'ctx>> {
|
pub fn retain_top_level_thunks_for_module(&mut self, module_id: ModuleId) {
|
||||||
self.join_points.get(symbol)
|
self.top_level_thunks
|
||||||
|
.retain(|s, _| s.module_id() == module_id);
|
||||||
}
|
}
|
||||||
fn remove_join_point(&mut self, symbol: &JoinPointId) {
|
|
||||||
self.join_points.remove(symbol);
|
|
||||||
}
|
|
||||||
fn get_mut_join_point(&mut self, symbol: &JoinPointId) -> Option<&mut PhiValue<'ctx>> {
|
|
||||||
self.join_points.get_mut(symbol)
|
|
||||||
}
|
|
||||||
fn insert_join_point(&mut self, symbol: JoinPointId, value: PhiValue<'ctx>) {
|
|
||||||
self.join_points.insert(symbol, value);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Env<'a, 'ctx, 'env> {
|
pub struct Env<'a, 'ctx, 'env> {
|
||||||
|
@ -321,6 +322,7 @@ pub fn construct_optimization_passes<'a>(
|
||||||
pmb.set_optimization_level(OptimizationLevel::None);
|
pmb.set_optimization_level(OptimizationLevel::None);
|
||||||
}
|
}
|
||||||
OptLevel::Optimize => {
|
OptLevel::Optimize => {
|
||||||
|
pmb.set_optimization_level(OptimizationLevel::Aggressive);
|
||||||
// this threshold seems to do what we want
|
// this threshold seems to do what we want
|
||||||
pmb.set_inliner_with_threshold(275);
|
pmb.set_inliner_with_threshold(275);
|
||||||
|
|
||||||
|
@ -416,26 +418,47 @@ pub fn build_roc_main<'a, 'ctx, 'env>(
|
||||||
env.arena.alloc(roc_main_fn)
|
env.arena.alloc(roc_main_fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn promote_to_main_function<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
symbol: Symbol,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
) -> (&'static str, &'a FunctionValue<'ctx>) {
|
||||||
|
let fn_name = layout_ids
|
||||||
|
.get(symbol, layout)
|
||||||
|
.to_symbol_string(symbol, &env.interns);
|
||||||
|
|
||||||
|
let wrapped = env.module.get_function(&fn_name).unwrap();
|
||||||
|
|
||||||
|
make_main_function_help(env, layout, wrapped)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn make_main_function<'a, 'ctx, 'env>(
|
pub fn make_main_function<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
layout: &Layout<'a>,
|
layout: &Layout<'a>,
|
||||||
main_body: &roc_mono::ir::Stmt<'a>,
|
main_body: &roc_mono::ir::Stmt<'a>,
|
||||||
) -> (&'static str, &'a FunctionValue<'ctx>) {
|
) -> (&'static str, &'a FunctionValue<'ctx>) {
|
||||||
|
// internal main function
|
||||||
|
let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body);
|
||||||
|
|
||||||
|
make_main_function_help(env, layout, roc_main_fn)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_main_function_help<'a, 'ctx, 'env>(
|
||||||
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
|
layout: &Layout<'a>,
|
||||||
|
roc_main_fn: FunctionValue<'ctx>,
|
||||||
|
) -> (&'static str, &'a FunctionValue<'ctx>) {
|
||||||
|
// build the C calling convention wrapper
|
||||||
use inkwell::types::BasicType;
|
use inkwell::types::BasicType;
|
||||||
use PassVia::*;
|
use PassVia::*;
|
||||||
|
|
||||||
let context = env.context;
|
let context = env.context;
|
||||||
let builder = env.builder;
|
let builder = env.builder;
|
||||||
|
|
||||||
let u8_ptr = context.i8_type().ptr_type(AddressSpace::Generic);
|
|
||||||
|
|
||||||
// internal main function
|
|
||||||
let roc_main_fn = *build_roc_main(env, layout_ids, layout, main_body);
|
|
||||||
|
|
||||||
// build the C calling convention wrapper
|
|
||||||
|
|
||||||
let main_fn_name = "$Test.main";
|
let main_fn_name = "$Test.main";
|
||||||
|
let u8_ptr = env.context.i8_type().ptr_type(AddressSpace::Generic);
|
||||||
|
|
||||||
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
|
let fields = [Layout::Builtin(Builtin::Int64), layout.clone()];
|
||||||
let main_return_layout = Layout::Struct(&fields);
|
let main_return_layout = Layout::Struct(&fields);
|
||||||
|
@ -1136,17 +1159,34 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
list_literal(env, inplace, scope, elem_layout, elems)
|
list_literal(env, inplace, scope, elem_layout, elems)
|
||||||
}
|
}
|
||||||
FunctionPointer(symbol, layout) => {
|
FunctionPointer(symbol, layout) => {
|
||||||
let fn_name = layout_ids
|
match scope.top_level_thunks.get(symbol) {
|
||||||
.get(*symbol, layout)
|
Some((_layout, function_value)) => {
|
||||||
.to_symbol_string(*symbol, &env.interns);
|
// this is a 0-argument thunk, evaluate it!
|
||||||
let ptr = env
|
let call =
|
||||||
.module
|
env.builder
|
||||||
.get_function(fn_name.as_str())
|
.build_call(*function_value, &[], "evaluate_top_level_thunk");
|
||||||
.unwrap_or_else(|| panic!("Could not get pointer to unknown function {:?}", symbol))
|
|
||||||
.as_global_value()
|
|
||||||
.as_pointer_value();
|
|
||||||
|
|
||||||
BasicValueEnum::PointerValue(ptr)
|
call.set_call_convention(FAST_CALL_CONV);
|
||||||
|
|
||||||
|
call.try_as_basic_value().left().unwrap()
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// this is a function pointer, store it
|
||||||
|
let fn_name = layout_ids
|
||||||
|
.get(*symbol, layout)
|
||||||
|
.to_symbol_string(*symbol, &env.interns);
|
||||||
|
let ptr = env
|
||||||
|
.module
|
||||||
|
.get_function(fn_name.as_str())
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!("Could not get pointer to unknown function {:?}", symbol)
|
||||||
|
})
|
||||||
|
.as_global_value()
|
||||||
|
.as_pointer_value();
|
||||||
|
|
||||||
|
BasicValueEnum::PointerValue(ptr)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
RuntimeErrorFunction(_) => todo!(),
|
RuntimeErrorFunction(_) => todo!(),
|
||||||
}
|
}
|
||||||
|
@ -1511,16 +1551,6 @@ pub fn build_exp_stmt<'a, 'ctx, 'env>(
|
||||||
increment_refcount_layout(env, parent, layout_ids, value, &layout);
|
increment_refcount_layout(env, parent, layout_ids, value, &layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
match layout {
|
|
||||||
Layout::Builtin(Builtin::List(MemoryMode::Refcounted, _)) => {
|
|
||||||
increment_refcount_list(env, parent, value.into_struct_value());
|
|
||||||
build_exp_stmt(env, layout_ids, scope, parent, cont)
|
|
||||||
}
|
|
||||||
_ => build_exp_stmt(env, layout_ids, scope, parent, cont),
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
build_exp_stmt(env, layout_ids, scope, parent, cont)
|
build_exp_stmt(env, layout_ids, scope, parent, cont)
|
||||||
}
|
}
|
||||||
Dec(symbol, cont) => {
|
Dec(symbol, cont) => {
|
||||||
|
@ -1836,6 +1866,7 @@ pub fn build_proc_header<'a, 'ctx, 'env>(
|
||||||
pub fn build_proc<'a, 'ctx, 'env>(
|
pub fn build_proc<'a, 'ctx, 'env>(
|
||||||
env: &'a Env<'a, 'ctx, 'env>,
|
env: &'a Env<'a, 'ctx, 'env>,
|
||||||
layout_ids: &mut LayoutIds<'a>,
|
layout_ids: &mut LayoutIds<'a>,
|
||||||
|
mut scope: Scope<'a, 'ctx>,
|
||||||
proc: roc_mono::ir::Proc<'a>,
|
proc: roc_mono::ir::Proc<'a>,
|
||||||
fn_val: FunctionValue<'ctx>,
|
fn_val: FunctionValue<'ctx>,
|
||||||
) {
|
) {
|
||||||
|
@ -1848,8 +1879,6 @@ pub fn build_proc<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
builder.position_at_end(entry);
|
builder.position_at_end(entry);
|
||||||
|
|
||||||
let mut scope = Scope::default();
|
|
||||||
|
|
||||||
// Add args to scope
|
// Add args to scope
|
||||||
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
|
for (arg_val, (layout, arg_symbol)) in fn_val.get_param_iter().zip(args) {
|
||||||
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
set_name(arg_val, arg_symbol.ident_string(&env.interns));
|
||||||
|
@ -1899,17 +1928,18 @@ fn call_with_args<'a, 'ctx, 'env>(
|
||||||
let fn_name = layout_ids
|
let fn_name = layout_ids
|
||||||
.get(symbol, layout)
|
.get(symbol, layout)
|
||||||
.to_symbol_string(symbol, &env.interns);
|
.to_symbol_string(symbol, &env.interns);
|
||||||
|
let fn_name = fn_name.as_str();
|
||||||
|
|
||||||
let fn_val = env
|
let fn_val = env.module.get_function(fn_name).unwrap_or_else(|| {
|
||||||
.module
|
if symbol.is_builtin() {
|
||||||
.get_function(fn_name.as_str())
|
panic!("Unrecognized builtin function: {:?}", fn_name)
|
||||||
.unwrap_or_else(|| {
|
} else {
|
||||||
if symbol.is_builtin() {
|
panic!(
|
||||||
panic!("Unrecognized builtin function: {:?}", symbol)
|
"Unrecognized non-builtin function: {:?} {:?}",
|
||||||
} else {
|
fn_name, layout
|
||||||
panic!("Unrecognized non-builtin function: {:?}", symbol)
|
)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let call = env.builder.build_call(fn_val, args, "call");
|
let call = env.builder.build_call(fn_val, args, "call");
|
||||||
|
|
||||||
|
@ -2626,8 +2656,13 @@ fn build_int_unary_op<'a, 'ctx, 'env>(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
NumToFloat => {
|
NumToFloat => {
|
||||||
// TODO specialize this to be not just for i64!
|
// This is an Int, so we need to convert it.
|
||||||
call_bitcode_fn(NumToFloat, env, &[arg.into()], "i64_to_f64_")
|
bd.build_cast(
|
||||||
|
InstructionOpcode::SIToFP,
|
||||||
|
arg,
|
||||||
|
env.context.f64_type(),
|
||||||
|
"i64_to_f64",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
unreachable!("Unrecognized int unary operation: {:?}", op);
|
unreachable!("Unrecognized int unary operation: {:?}", op);
|
||||||
|
|
|
@ -1019,8 +1019,7 @@ mod gen_list {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \shared ->
|
wrapper = \shared ->
|
||||||
|
|
||||||
# This should not mutate the original
|
# This should not mutate the original
|
||||||
x =
|
x =
|
||||||
when List.get (List.set shared 1 7.7) 1 is
|
when List.get (List.set shared 1 7.7) 1 is
|
||||||
|
@ -1034,7 +1033,7 @@ mod gen_list {
|
||||||
|
|
||||||
{ x, y }
|
{ x, y }
|
||||||
|
|
||||||
main [ 2.1, 4.3 ]
|
wrapper [ 2.1, 4.3 ]
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
(7.7, 4.3),
|
(7.7, 4.3),
|
||||||
|
@ -1047,23 +1046,20 @@ mod gen_list {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
shared = [ 2, 4 ]
|
||||||
shared = [ 2, 4 ]
|
|
||||||
|
|
||||||
# This List.set is out of bounds, and should have no effect
|
# This List.set is out of bounds, and should have no effect
|
||||||
x =
|
x =
|
||||||
when List.get (List.set shared 422 0) 1 is
|
when List.get (List.set shared 422 0) 1 is
|
||||||
Ok num -> num
|
Ok num -> num
|
||||||
Err _ -> 0
|
Err _ -> 0
|
||||||
|
|
||||||
y =
|
y =
|
||||||
when List.get shared 1 is
|
when List.get shared 1 is
|
||||||
Ok num -> num
|
Ok num -> num
|
||||||
Err _ -> 0
|
Err _ -> 0
|
||||||
|
|
||||||
{ x, y }
|
{ x, y }
|
||||||
|
|
||||||
main {}
|
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
(4, 4),
|
(4, 4),
|
||||||
|
@ -1149,16 +1145,21 @@ mod gen_list {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
swap : Int, Int, List a -> List a
|
app Quicksort provides [ main ] imports []
|
||||||
swap = \i, j, list ->
|
|
||||||
when Pair (List.get list i) (List.get list j) is
|
|
||||||
Pair (Ok atI) (Ok atJ) ->
|
|
||||||
list
|
|
||||||
|> List.set i atJ
|
|
||||||
|> List.set j atI
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
[]
|
swap : Int, Int, List a -> List a
|
||||||
|
swap = \i, j, list ->
|
||||||
|
when Pair (List.get list i) (List.get list j) is
|
||||||
|
Pair (Ok atI) (Ok atJ) ->
|
||||||
|
list
|
||||||
|
|> List.set i atJ
|
||||||
|
|> List.set j atI
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
main =
|
||||||
swap 0 1 [ 1, 2 ]
|
swap 0 1 [ 1, 2 ]
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
|
|
|
@ -482,12 +482,12 @@ mod gen_num {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
when 10 is
|
when 10 is
|
||||||
x if x == 5 -> 0
|
x if x == 5 -> 0
|
||||||
_ -> 42
|
_ -> 42
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
42,
|
42,
|
||||||
|
@ -500,12 +500,12 @@ mod gen_num {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
when 10 is
|
when 10 is
|
||||||
x if x == 10 -> 42
|
x if x == 10 -> 42
|
||||||
_ -> 0
|
_ -> 0
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
42,
|
42,
|
||||||
|
|
|
@ -276,10 +276,10 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
(\a -> a) 5
|
(\a -> a) 5
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
5,
|
5,
|
||||||
|
@ -292,14 +292,14 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
alwaysFloatIdentity : Int -> (Float -> Float)
|
alwaysFloatIdentity : Int -> (Float -> Float)
|
||||||
alwaysFloatIdentity = \num ->
|
alwaysFloatIdentity = \num ->
|
||||||
(\a -> a)
|
(\a -> a)
|
||||||
|
|
||||||
(alwaysFloatIdentity 2) 3.14
|
(alwaysFloatIdentity 2) 3.14
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
3.14,
|
3.14,
|
||||||
|
@ -402,8 +402,9 @@ mod gen_primitives {
|
||||||
i64
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn gen_nested_defs() {
|
fn gen_nested_defs_old() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -443,6 +444,28 @@ mod gen_primitives {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_x_in_x() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 5
|
||||||
|
|
||||||
|
answer =
|
||||||
|
1337
|
||||||
|
|
||||||
|
unused =
|
||||||
|
nested = 17
|
||||||
|
nested
|
||||||
|
|
||||||
|
answer
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
1337,
|
||||||
|
i64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn factorial() {
|
fn factorial() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
|
@ -469,15 +492,15 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Peano : [ S Peano, Z ]
|
Peano : [ S Peano, Z ]
|
||||||
|
|
||||||
three : Peano
|
three : Peano
|
||||||
three = S (S (S Z))
|
three = S (S (S Z))
|
||||||
|
|
||||||
when three is
|
when three is
|
||||||
Z -> 2
|
Z -> 2
|
||||||
S _ -> 1
|
S _ -> 1
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
i64
|
i64
|
||||||
|
@ -489,27 +512,47 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Peano : [ S Peano, Z ]
|
Peano : [ S Peano, Z ]
|
||||||
|
|
||||||
three : Peano
|
three : Peano
|
||||||
three = S (S (S Z))
|
three = S (S (S Z))
|
||||||
|
|
||||||
when three is
|
when three is
|
||||||
S (S _) -> 1
|
S (S _) -> 1
|
||||||
S (_) -> 0
|
S (_) -> 0
|
||||||
Z -> 0
|
Z -> 0
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
i64
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn top_level_constant() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app LinkedListLen0 provides [ main ] imports []
|
||||||
|
|
||||||
|
pi = 3.1415
|
||||||
|
|
||||||
|
main =
|
||||||
|
pi + pi
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
3.1415 + 3.1415,
|
||||||
|
f64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn linked_list_len_0() {
|
fn linked_list_len_0() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
app LinkedListLen0 provides [ main ] imports []
|
||||||
|
|
||||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
nil : LinkedList Int
|
nil : LinkedList Int
|
||||||
|
@ -522,13 +565,12 @@ mod gen_primitives {
|
||||||
Cons _ rest -> 1 + length rest
|
Cons _ rest -> 1 + length rest
|
||||||
|
|
||||||
|
|
||||||
length nil
|
main =
|
||||||
|
length nil
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
0,
|
0,
|
||||||
i64,
|
i64
|
||||||
|x| x,
|
|
||||||
false
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,6 +579,8 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
app LinkedListLenTwice0 provides [ main ] imports []
|
||||||
|
|
||||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
nil : LinkedList Int
|
nil : LinkedList Int
|
||||||
|
@ -548,13 +592,12 @@ mod gen_primitives {
|
||||||
Nil -> 0
|
Nil -> 0
|
||||||
Cons _ rest -> 1 + length rest
|
Cons _ rest -> 1 + length rest
|
||||||
|
|
||||||
length nil + length nil
|
main =
|
||||||
|
length nil + length nil
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
0,
|
0,
|
||||||
i64,
|
i64
|
||||||
|x| x,
|
|
||||||
false
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -563,6 +606,8 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
one : LinkedList Int
|
one : LinkedList Int
|
||||||
|
@ -574,14 +619,12 @@ mod gen_primitives {
|
||||||
Nil -> 0
|
Nil -> 0
|
||||||
Cons _ rest -> 1 + length rest
|
Cons _ rest -> 1 + length rest
|
||||||
|
|
||||||
|
main =
|
||||||
length one
|
length one
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
i64,
|
i64
|
||||||
|x| x,
|
|
||||||
false
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -590,6 +633,8 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
one : LinkedList Int
|
one : LinkedList Int
|
||||||
|
@ -601,14 +646,12 @@ mod gen_primitives {
|
||||||
Nil -> 0
|
Nil -> 0
|
||||||
Cons _ rest -> 1 + length rest
|
Cons _ rest -> 1 + length rest
|
||||||
|
|
||||||
|
main =
|
||||||
length one + length one
|
length one + length one
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
2,
|
2,
|
||||||
i64,
|
i64
|
||||||
|x| x,
|
|
||||||
false
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -617,6 +660,8 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
three : LinkedList Int
|
three : LinkedList Int
|
||||||
|
@ -629,52 +674,83 @@ mod gen_primitives {
|
||||||
Cons _ rest -> 1 + length rest
|
Cons _ rest -> 1 + length rest
|
||||||
|
|
||||||
|
|
||||||
length three
|
main =
|
||||||
|
length three
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
3,
|
3,
|
||||||
i64,
|
|
||||||
|x| x,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn linked_list_sum() {
|
|
||||||
assert_evals_to!(
|
|
||||||
indoc!(
|
|
||||||
r#"
|
|
||||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
|
||||||
|
|
||||||
three : LinkedList Int
|
|
||||||
three = Cons 3 (Cons 2 (Cons 1 Nil))
|
|
||||||
|
|
||||||
sum : LinkedList a -> Int
|
|
||||||
sum = \list ->
|
|
||||||
when list is
|
|
||||||
Nil -> 0
|
|
||||||
Cons x rest -> x + sum rest
|
|
||||||
|
|
||||||
sum three
|
|
||||||
"#
|
|
||||||
),
|
|
||||||
3 + 2 + 1,
|
|
||||||
i64
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn linked_list_map() {
|
fn linked_list_sum_num_a() {
|
||||||
// `f` is not actually a function, so the call to it fails currently
|
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
three : LinkedList Int
|
three : LinkedList Int
|
||||||
three = Cons 3 (Cons 2 (Cons 1 Nil))
|
three = Cons 3 (Cons 2 (Cons 1 Nil))
|
||||||
|
|
||||||
sum : LinkedList a -> Int
|
|
||||||
|
sum : LinkedList (Num a) -> Num a
|
||||||
|
sum = \list ->
|
||||||
|
when list is
|
||||||
|
Nil -> 0
|
||||||
|
Cons x rest -> x + sum rest
|
||||||
|
|
||||||
|
main =
|
||||||
|
sum three
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
3 + 2 + 1,
|
||||||
|
i64
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn linked_list_sum_int() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
|
zero : LinkedList Int
|
||||||
|
zero = Nil
|
||||||
|
|
||||||
|
sum : LinkedList Int -> Int
|
||||||
|
sum = \list ->
|
||||||
|
when list is
|
||||||
|
Nil -> 0
|
||||||
|
Cons x rest -> x + sum rest
|
||||||
|
|
||||||
|
main =
|
||||||
|
sum zero
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
0,
|
||||||
|
i64
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn linked_list_map() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app Test provides [ main ] imports []
|
||||||
|
|
||||||
|
LinkedList a : [ Nil, Cons a (LinkedList a) ]
|
||||||
|
|
||||||
|
three : LinkedList Int
|
||||||
|
three = Cons 3 (Cons 2 (Cons 1 Nil))
|
||||||
|
|
||||||
|
sum : LinkedList (Num a) -> Num a
|
||||||
sum = \list ->
|
sum = \list ->
|
||||||
when list is
|
when list is
|
||||||
Nil -> 0
|
Nil -> 0
|
||||||
|
@ -686,7 +762,8 @@ mod gen_primitives {
|
||||||
Nil -> Nil
|
Nil -> Nil
|
||||||
Cons x rest -> Cons (f x) (map f rest)
|
Cons x rest -> Cons (f x) (map f rest)
|
||||||
|
|
||||||
sum (map (\_ -> 1) three)
|
main =
|
||||||
|
sum (map (\_ -> 1) three)
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
3,
|
3,
|
||||||
|
@ -699,15 +776,15 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Maybe a : [ Nothing, Just a ]
|
Maybe a : [ Nothing, Just a ]
|
||||||
|
|
||||||
x : Maybe (Maybe Int)
|
x : Maybe (Maybe Int)
|
||||||
x = Just (Just 41)
|
x = Just (Just 41)
|
||||||
|
|
||||||
when x is
|
when x is
|
||||||
Just (Just v) -> v + 0x1
|
Just (Just v) -> v + 0x1
|
||||||
_ -> 0x1
|
_ -> 0x1
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
42,
|
42,
|
||||||
i64
|
i64
|
||||||
|
@ -716,16 +793,16 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Maybe a : [ Nothing, Just a ]
|
Maybe a : [ Nothing, Just a ]
|
||||||
|
|
||||||
x : Maybe (Maybe Int)
|
x : Maybe (Maybe Int)
|
||||||
x = Just Nothing
|
x = Just Nothing
|
||||||
|
|
||||||
when x is
|
when x is
|
||||||
Just (Just v) -> v + 0x1
|
Just (Just v) -> v + 0x1
|
||||||
Just Nothing -> 0x2
|
Just Nothing -> 0x2
|
||||||
Nothing -> 0x1
|
Nothing -> 0x1
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
2,
|
2,
|
||||||
i64
|
i64
|
||||||
|
@ -734,16 +811,16 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Maybe a : [ Nothing, Just a ]
|
Maybe a : [ Nothing, Just a ]
|
||||||
|
|
||||||
x : Maybe (Maybe Int)
|
x : Maybe (Maybe Int)
|
||||||
x = Nothing
|
x = Nothing
|
||||||
|
|
||||||
when x is
|
when x is
|
||||||
Just (Just v) -> v + 0x1
|
Just (Just v) -> v + 0x1
|
||||||
Just Nothing -> 0x2
|
Just Nothing -> 0x2
|
||||||
Nothing -> 0x1
|
Nothing -> 0x1
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
i64
|
i64
|
||||||
|
@ -755,16 +832,16 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Peano : [ S Peano, Z ]
|
Peano : [ S Peano, Z ]
|
||||||
|
|
||||||
three : Peano
|
three : Peano
|
||||||
three = S (S (S Z))
|
three = S (S (S Z))
|
||||||
|
|
||||||
when three is
|
when three is
|
||||||
S (S _) -> 1
|
S (S _) -> 1
|
||||||
S (_) -> 2
|
S (_) -> 2
|
||||||
Z -> 3
|
Z -> 3
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
1,
|
1,
|
||||||
i64
|
i64
|
||||||
|
@ -773,16 +850,16 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Peano : [ S Peano, Z ]
|
Peano : [ S Peano, Z ]
|
||||||
|
|
||||||
three : Peano
|
three : Peano
|
||||||
three = S Z
|
three = S Z
|
||||||
|
|
||||||
when three is
|
when three is
|
||||||
S (S _) -> 1
|
S (S _) -> 1
|
||||||
S (_) -> 2
|
S (_) -> 2
|
||||||
Z -> 3
|
Z -> 3
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
2,
|
2,
|
||||||
i64
|
i64
|
||||||
|
@ -791,16 +868,16 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Peano : [ S Peano, Z ]
|
Peano : [ S Peano, Z ]
|
||||||
|
|
||||||
three : Peano
|
three : Peano
|
||||||
three = Z
|
three = Z
|
||||||
|
|
||||||
when three is
|
when three is
|
||||||
S (S _) -> 1
|
S (S _) -> 1
|
||||||
S (_) -> 2
|
S (_) -> 2
|
||||||
Z -> 3
|
Z -> 3
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
3,
|
3,
|
||||||
i64
|
i64
|
||||||
|
@ -813,11 +890,11 @@ mod gen_primitives {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
if True then
|
if True then
|
||||||
x + z
|
x + z
|
||||||
else
|
else
|
||||||
y + z
|
y + z
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
3,
|
3,
|
||||||
i64
|
i64
|
||||||
|
|
|
@ -678,15 +678,58 @@ mod gen_records {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn just_to_be_sure() {
|
fn accessor() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
{ a: 1, b : 2, c : 3 }
|
.foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
[1, 2, 3],
|
7,
|
||||||
[i64; 3]
|
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.28 }
|
||||||
|
|
||||||
|
{ rec & foo: rec.foo + 1 }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
(6.28, 43),
|
||||||
|
(f64, i64)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn update_single_element_record() {
|
||||||
|
assert_evals_to!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
rec = { foo: 42}
|
||||||
|
|
||||||
|
{ rec & foo: rec.foo + 1 }
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
43,
|
||||||
|
i64
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -455,12 +455,12 @@ mod gen_tags {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
when 2 is
|
when 2 is
|
||||||
2 if False -> 0
|
2 if False -> 0
|
||||||
_ -> 42
|
_ -> 42
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
42,
|
42,
|
||||||
|
@ -473,12 +473,12 @@ mod gen_tags {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
when 2 is
|
when 2 is
|
||||||
2 if True -> 42
|
2 if True -> 42
|
||||||
_ -> 0
|
_ -> 0
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
42,
|
42,
|
||||||
|
@ -491,12 +491,12 @@ mod gen_tags {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
when 2 is
|
when 2 is
|
||||||
_ if False -> 0
|
_ if False -> 0
|
||||||
_ -> 42
|
_ -> 42
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
42,
|
42,
|
||||||
|
@ -674,7 +674,7 @@ mod gen_tags {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
x : [ Red, White, Blue ]
|
x : [ Red, White, Blue ]
|
||||||
x = Blue
|
x = Blue
|
||||||
|
|
||||||
|
@ -686,7 +686,7 @@ mod gen_tags {
|
||||||
|
|
||||||
y
|
y
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
3.1,
|
3.1,
|
||||||
|
@ -699,7 +699,7 @@ mod gen_tags {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
main = \{} ->
|
wrapper = \{} ->
|
||||||
y =
|
y =
|
||||||
when 1 + 2 is
|
when 1 + 2 is
|
||||||
3 -> 3
|
3 -> 3
|
||||||
|
@ -708,7 +708,7 @@ mod gen_tags {
|
||||||
|
|
||||||
y
|
y
|
||||||
|
|
||||||
main {}
|
wrapper {}
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
3,
|
3,
|
||||||
|
|
|
@ -1,9 +1,22 @@
|
||||||
use roc_collections::all::MutSet;
|
use roc_collections::all::{MutMap, MutSet};
|
||||||
use roc_types::subs::Subs;
|
|
||||||
|
|
||||||
pub fn helper_without_uniqueness<'a>(
|
fn promote_expr_to_module(src: &str) -> String {
|
||||||
|
let mut buffer = String::from("app Test provides [ main ] imports []\n\nmain =\n");
|
||||||
|
|
||||||
|
for line in src.lines() {
|
||||||
|
// indent the body!
|
||||||
|
buffer.push_str(" ");
|
||||||
|
buffer.push_str(line);
|
||||||
|
buffer.push('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn helper<'a>(
|
||||||
arena: &'a bumpalo::Bump,
|
arena: &'a bumpalo::Bump,
|
||||||
src: &str,
|
src: &str,
|
||||||
|
stdlib: roc_builtins::std::StdLib,
|
||||||
leak: bool,
|
leak: bool,
|
||||||
context: &'a inkwell::context::Context,
|
context: &'a inkwell::context::Context,
|
||||||
) -> (
|
) -> (
|
||||||
|
@ -11,26 +24,62 @@ pub fn helper_without_uniqueness<'a>(
|
||||||
Vec<roc_problem::can::Problem>,
|
Vec<roc_problem::can::Problem>,
|
||||||
inkwell::execution_engine::ExecutionEngine<'a>,
|
inkwell::execution_engine::ExecutionEngine<'a>,
|
||||||
) {
|
) {
|
||||||
use crate::helpers::{can_expr, infer_expr, CanExprOut};
|
|
||||||
use inkwell::OptimizationLevel;
|
use inkwell::OptimizationLevel;
|
||||||
use roc_gen::llvm::build::{build_proc, build_proc_header};
|
use roc_gen::llvm::build::{build_proc, build_proc_header, Scope};
|
||||||
use roc_mono::layout::Layout;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
let stdlib_mode = stdlib.mode;
|
||||||
|
let filename = PathBuf::from("Test.roc");
|
||||||
|
let src_dir = Path::new("fake/test/path");
|
||||||
|
|
||||||
|
let module_src;
|
||||||
|
let temp;
|
||||||
|
if src.starts_with("app") {
|
||||||
|
// this is already a module
|
||||||
|
module_src = src;
|
||||||
|
} else {
|
||||||
|
// this is an expression, promote it to a module
|
||||||
|
temp = promote_expr_to_module(src);
|
||||||
|
module_src = &temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
let exposed_types = MutMap::default();
|
||||||
|
let loaded = roc_load::file::load_and_monomorphize_from_str(
|
||||||
|
arena,
|
||||||
|
filename,
|
||||||
|
&module_src,
|
||||||
|
stdlib,
|
||||||
|
src_dir,
|
||||||
|
exposed_types,
|
||||||
|
);
|
||||||
|
|
||||||
|
let loaded = loaded.expect("failed to load module");
|
||||||
|
|
||||||
|
use roc_load::file::MonomorphizedModule;
|
||||||
|
let MonomorphizedModule {
|
||||||
|
can_problems,
|
||||||
|
type_problems,
|
||||||
|
mono_problems,
|
||||||
|
mut procedures,
|
||||||
|
interns,
|
||||||
|
exposed_to_host,
|
||||||
|
..
|
||||||
|
} = loaded;
|
||||||
|
|
||||||
|
debug_assert_eq!(exposed_to_host.len(), 1);
|
||||||
|
let main_fn_symbol = exposed_to_host.keys().copied().nth(0).unwrap();
|
||||||
|
|
||||||
|
let (_, main_fn_layout) = procedures
|
||||||
|
.keys()
|
||||||
|
.find(|(s, _)| *s == main_fn_symbol)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
let target = target_lexicon::Triple::host();
|
let target = target_lexicon::Triple::host();
|
||||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
||||||
let CanExprOut {
|
|
||||||
loc_expr,
|
|
||||||
var_store,
|
|
||||||
var,
|
|
||||||
constraint,
|
|
||||||
home,
|
|
||||||
interns,
|
|
||||||
problems,
|
|
||||||
..
|
|
||||||
} = can_expr(src);
|
|
||||||
|
|
||||||
// don't panic based on the errors here, so we can test that RuntimeError generates the correct code
|
// don't panic based on the errors here, so we can test that RuntimeError generates the correct code
|
||||||
let errors = problems
|
let errors = can_problems
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|problem| {
|
.filter(|problem| {
|
||||||
use roc_problem::can::Problem::*;
|
use roc_problem::can::Problem::*;
|
||||||
|
@ -43,15 +92,18 @@ pub fn helper_without_uniqueness<'a>(
|
||||||
})
|
})
|
||||||
.collect::<Vec<roc_problem::can::Problem>>();
|
.collect::<Vec<roc_problem::can::Problem>>();
|
||||||
|
|
||||||
let subs = Subs::new(var_store.into());
|
|
||||||
let mut unify_problems = Vec::new();
|
|
||||||
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
unify_problems,
|
type_problems,
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
"Encountered type mismatches: {:?}",
|
"Encountered type mismatches: {:?}",
|
||||||
unify_problems
|
type_problems,
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
mono_problems,
|
||||||
|
Vec::new(),
|
||||||
|
"Encountered monomorphization errors: {:?}",
|
||||||
|
mono_problems,
|
||||||
);
|
);
|
||||||
|
|
||||||
let module = roc_gen::llvm::build::module_from_builtins(context, "app");
|
let module = roc_gen::llvm::build::module_from_builtins(context, "app");
|
||||||
|
@ -66,102 +118,84 @@ pub fn helper_without_uniqueness<'a>(
|
||||||
let (module_pass, function_pass) =
|
let (module_pass, function_pass) =
|
||||||
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
||||||
|
|
||||||
// Compute main_fn_type before moving subs to Env
|
|
||||||
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
|
|
||||||
panic!(
|
|
||||||
"Code gen error in NON-OPTIMIZED test: could not convert to layout. Err was {:?}",
|
|
||||||
err
|
|
||||||
)
|
|
||||||
});
|
|
||||||
let execution_engine = module
|
let execution_engine = module
|
||||||
.create_jit_execution_engine(OptimizationLevel::None)
|
.create_jit_execution_engine(OptimizationLevel::None)
|
||||||
.expect("Error creating JIT execution engine for test");
|
.expect("Error creating JIT execution engine for test");
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
let mut env = roc_gen::llvm::build::Env {
|
let env = roc_gen::llvm::build::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
builder: &builder,
|
builder: &builder,
|
||||||
context: context,
|
context,
|
||||||
interns,
|
interns,
|
||||||
module,
|
module,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
leak: leak,
|
leak,
|
||||||
|
// important! we don't want any procedures to get the C calling convention
|
||||||
exposed_to_host: MutSet::default(),
|
exposed_to_host: MutSet::default(),
|
||||||
};
|
};
|
||||||
let mut procs = roc_mono::ir::Procs::default();
|
|
||||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
|
||||||
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
|
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
|
||||||
|
let mut headers = Vec::with_capacity(procedures.len());
|
||||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
|
||||||
let mut mono_problems = Vec::new();
|
|
||||||
let mut mono_env = roc_mono::ir::Env {
|
|
||||||
arena: &arena,
|
|
||||||
subs: &mut subs,
|
|
||||||
problems: &mut mono_problems,
|
|
||||||
home,
|
|
||||||
ident_ids: &mut ident_ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
|
|
||||||
|
|
||||||
let mut headers = {
|
|
||||||
let num_headers = match &procs.pending_specializations {
|
|
||||||
Some(map) => map.len(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Vec::with_capacity(num_headers)
|
|
||||||
};
|
|
||||||
let mut layout_cache = roc_mono::layout::LayoutCache::default();
|
|
||||||
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
procs.runtime_errors,
|
|
||||||
roc_collections::all::MutMap::default()
|
|
||||||
);
|
|
||||||
|
|
||||||
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
|
|
||||||
let main_body = roc_mono::inc_dec::visit_declaration(
|
|
||||||
mono_env.arena,
|
|
||||||
param_map,
|
|
||||||
mono_env.arena.alloc(main_body),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Put this module's ident_ids back in the interns, so we can use them in env.
|
|
||||||
// This must happen *after* building the headers, because otherwise there's
|
|
||||||
// a conflicting mutable borrow on ident_ids.
|
|
||||||
env.interns.all_ident_ids.insert(home, ident_ids);
|
|
||||||
|
|
||||||
// Add all the Proc headers to the module.
|
// Add all the Proc headers to the module.
|
||||||
// We have to do this in a separate pass first,
|
// We have to do this in a separate pass first,
|
||||||
// because their bodies may reference each other.
|
// because their bodies may reference each other.
|
||||||
for ((symbol, layout), proc) in procs.drain() {
|
let mut scope = Scope::default();
|
||||||
|
for ((symbol, layout), proc) in procedures.drain() {
|
||||||
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
||||||
|
|
||||||
|
if proc.args.is_empty() {
|
||||||
|
// this is a 0-argument thunk, i.e. a top-level constant definition
|
||||||
|
// it must be in-scope everywhere in the module!
|
||||||
|
scope.insert_top_level_thunk(symbol, layout, fn_val);
|
||||||
|
}
|
||||||
|
|
||||||
headers.push((proc, fn_val));
|
headers.push((proc, fn_val));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build each proc using its header info.
|
// Build each proc using its header info.
|
||||||
for (proc, fn_val) in headers {
|
for (proc, fn_val) in headers {
|
||||||
build_proc(&env, &mut layout_ids, proc, fn_val);
|
let mut current_scope = scope.clone();
|
||||||
|
|
||||||
|
// only have top-level thunks for this proc's module in scope
|
||||||
|
// this retain is not needed for correctness, but will cause less confusion when debugging
|
||||||
|
let home = proc.name.module_id();
|
||||||
|
current_scope.retain_top_level_thunks_for_module(home);
|
||||||
|
|
||||||
|
build_proc(&env, &mut layout_ids, scope.clone(), proc, fn_val);
|
||||||
|
|
||||||
if fn_val.verify(true) {
|
if fn_val.verify(true) {
|
||||||
function_pass.run_on(&fn_val);
|
function_pass.run_on(&fn_val);
|
||||||
} else {
|
} else {
|
||||||
|
use roc_builtins::std::Mode;
|
||||||
|
|
||||||
|
let mode = match stdlib_mode {
|
||||||
|
Mode::Uniqueness => "OPTIMIZED",
|
||||||
|
Mode::Standard => "NON-OPTIMIZED",
|
||||||
|
};
|
||||||
|
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"\n\nFunction {:?} failed LLVM verification in NON-OPTIMIZED build. Its content was:\n", fn_val.get_name().to_str().unwrap()
|
"\n\nFunction {:?} failed LLVM verification in {} build. Its content was:\n",
|
||||||
);
|
fn_val.get_name().to_str().unwrap(),
|
||||||
|
mode,
|
||||||
|
);
|
||||||
|
|
||||||
fn_val.print_to_stderr();
|
fn_val.print_to_stderr();
|
||||||
|
|
||||||
panic!(
|
panic!(
|
||||||
"The preceding code was from {:?}, which failed LLVM verification in NON-OPTIMIZED build.", fn_val.get_name().to_str().unwrap()
|
"The preceding code was from {:?}, which failed LLVM verification in {} build.",
|
||||||
);
|
fn_val.get_name().to_str().unwrap(),
|
||||||
|
mode,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let (main_fn_name, main_fn) = roc_gen::llvm::build::promote_to_main_function(
|
||||||
let (main_fn_name, main_fn) =
|
&env,
|
||||||
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
|
&mut layout_ids,
|
||||||
|
main_fn_symbol,
|
||||||
|
&main_fn_layout,
|
||||||
|
);
|
||||||
|
|
||||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
||||||
// env.module.print_to_stderr();
|
// env.module.print_to_stderr();
|
||||||
|
@ -185,177 +219,6 @@ pub fn helper_without_uniqueness<'a>(
|
||||||
(main_fn_name, errors, execution_engine.clone())
|
(main_fn_name, errors, execution_engine.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn helper_with_uniqueness<'a>(
|
|
||||||
arena: &'a bumpalo::Bump,
|
|
||||||
src: &str,
|
|
||||||
leak: bool,
|
|
||||||
context: &'a inkwell::context::Context,
|
|
||||||
) -> (&'static str, inkwell::execution_engine::ExecutionEngine<'a>) {
|
|
||||||
use crate::helpers::{infer_expr, uniq_expr};
|
|
||||||
use inkwell::OptimizationLevel;
|
|
||||||
use roc_gen::llvm::build::{build_proc, build_proc_header};
|
|
||||||
use roc_mono::layout::Layout;
|
|
||||||
|
|
||||||
let target = target_lexicon::Triple::host();
|
|
||||||
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
|
|
||||||
let (loc_expr, _output, problems, subs, var, constraint, home, interns) = uniq_expr(src);
|
|
||||||
|
|
||||||
let errors = problems
|
|
||||||
.into_iter()
|
|
||||||
.filter(|problem| {
|
|
||||||
use roc_problem::can::Problem::*;
|
|
||||||
|
|
||||||
// Ignore "unused" problems
|
|
||||||
match problem {
|
|
||||||
UnusedDef(_, _) | UnusedArgument(_, _, _) | UnusedImport(_, _) => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<roc_problem::can::Problem>>();
|
|
||||||
|
|
||||||
assert_eq!(errors, Vec::new(), "Encountered errors: {:?}", errors);
|
|
||||||
|
|
||||||
let mut unify_problems = Vec::new();
|
|
||||||
let (content, mut subs) = infer_expr(subs, &mut unify_problems, &constraint, var);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
unify_problems,
|
|
||||||
Vec::new(),
|
|
||||||
"Encountered one or more type mismatches: {:?}",
|
|
||||||
unify_problems
|
|
||||||
);
|
|
||||||
|
|
||||||
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(context, "app"));
|
|
||||||
let builder = context.create_builder();
|
|
||||||
let opt_level = if cfg!(debug_assertions) {
|
|
||||||
roc_gen::llvm::build::OptLevel::Normal
|
|
||||||
} else {
|
|
||||||
roc_gen::llvm::build::OptLevel::Optimize
|
|
||||||
};
|
|
||||||
let (mpm, fpm) = roc_gen::llvm::build::construct_optimization_passes(module, opt_level);
|
|
||||||
|
|
||||||
// Compute main_fn_type before moving subs to Env
|
|
||||||
let return_layout = Layout::new(&arena, content, &subs).unwrap_or_else(|err| {
|
|
||||||
panic!(
|
|
||||||
"Code gen error in OPTIMIZED test: could not convert to layout. Err was {:?}",
|
|
||||||
err
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let execution_engine = module
|
|
||||||
.create_jit_execution_engine(OptimizationLevel::None)
|
|
||||||
.expect("Error creating JIT execution engine for test");
|
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
|
||||||
let mut env = roc_gen::llvm::build::Env {
|
|
||||||
arena: &arena,
|
|
||||||
builder: &builder,
|
|
||||||
context: context,
|
|
||||||
interns,
|
|
||||||
module,
|
|
||||||
ptr_bytes,
|
|
||||||
leak: leak,
|
|
||||||
exposed_to_host: MutSet::default(),
|
|
||||||
};
|
|
||||||
let mut procs = roc_mono::ir::Procs::default();
|
|
||||||
let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap();
|
|
||||||
let mut layout_ids = roc_gen::layout_id::LayoutIds::default();
|
|
||||||
|
|
||||||
// Populate Procs and get the low-level Expr from the canonical Expr
|
|
||||||
let mut mono_problems = Vec::new();
|
|
||||||
let mut mono_env = roc_mono::ir::Env {
|
|
||||||
arena: &arena,
|
|
||||||
subs: &mut subs,
|
|
||||||
problems: &mut mono_problems,
|
|
||||||
home,
|
|
||||||
ident_ids: &mut ident_ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
let main_body = roc_mono::ir::Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
|
|
||||||
let mut headers = {
|
|
||||||
let num_headers = match &procs.pending_specializations {
|
|
||||||
Some(map) => map.len(),
|
|
||||||
None => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
Vec::with_capacity(num_headers)
|
|
||||||
};
|
|
||||||
let mut layout_cache = roc_mono::layout::LayoutCache::default();
|
|
||||||
let procs = roc_mono::ir::specialize_all(&mut mono_env, procs, &mut layout_cache);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
procs.runtime_errors,
|
|
||||||
roc_collections::all::MutMap::default()
|
|
||||||
);
|
|
||||||
|
|
||||||
let (mut procs, param_map) = procs.get_specialized_procs_help(mono_env.arena);
|
|
||||||
let main_body = roc_mono::inc_dec::visit_declaration(
|
|
||||||
mono_env.arena,
|
|
||||||
param_map,
|
|
||||||
mono_env.arena.alloc(main_body),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Put this module's ident_ids back in the interns, so we can use them in env.
|
|
||||||
// This must happen *after* building the headers, because otherwise there's
|
|
||||||
// a conflicting mutable borrow on ident_ids.
|
|
||||||
env.interns.all_ident_ids.insert(home, ident_ids);
|
|
||||||
|
|
||||||
// Add all the Proc headers to the module.
|
|
||||||
// We have to do this in a separate pass first,
|
|
||||||
// because their bodies may reference each other.
|
|
||||||
for ((symbol, layout), proc) in procs.drain() {
|
|
||||||
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
|
|
||||||
|
|
||||||
headers.push((proc, fn_val));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build each proc using its header info.
|
|
||||||
for (proc, fn_val) in headers {
|
|
||||||
build_proc(&env, &mut layout_ids, proc, fn_val);
|
|
||||||
|
|
||||||
if fn_val.verify(true) {
|
|
||||||
fpm.run_on(&fn_val);
|
|
||||||
} else {
|
|
||||||
eprintln!(
|
|
||||||
"\n\nFunction {:?} failed LLVM verification in OPTIMIZED build. Its content was:\n",
|
|
||||||
fn_val.get_name().to_str().unwrap()
|
|
||||||
);
|
|
||||||
|
|
||||||
fn_val.print_to_stderr();
|
|
||||||
|
|
||||||
panic!(
|
|
||||||
"The preceding code was from {:?}, which failed LLVM verification in OPTIMIZED build.", fn_val.get_name().to_str().unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let (main_fn_name, main_fn) =
|
|
||||||
roc_gen::llvm::build::make_main_function(&env, &mut layout_ids, &return_layout, &main_body);
|
|
||||||
|
|
||||||
// you're in the version with uniqueness!
|
|
||||||
|
|
||||||
// Uncomment this to see the module's un-optimized LLVM instruction output:
|
|
||||||
// env.module.print_to_stderr();
|
|
||||||
|
|
||||||
if main_fn.verify(true) {
|
|
||||||
fpm.run_on(&main_fn);
|
|
||||||
} else {
|
|
||||||
panic!("main function {} failed LLVM verification in OPTIMIZED build. Uncomment nearby statements to see more details.", main_fn_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
mpm.run_on(module);
|
|
||||||
|
|
||||||
// Verify the module
|
|
||||||
if let Err(errors) = env.module.verify() {
|
|
||||||
panic!("Errors defining module: {:?}", errors);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Uncomment this to see the module's optimized LLVM instruction output:
|
|
||||||
// env.module.print_to_stderr();
|
|
||||||
|
|
||||||
(main_fn_name, execution_engine)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO this is almost all code duplication with assert_llvm_evals_to
|
// TODO this is almost all code duplication with assert_llvm_evals_to
|
||||||
// the only difference is that this calls uniq_expr instead of can_expr.
|
// the only difference is that this calls uniq_expr instead of can_expr.
|
||||||
// Should extract the common logic into test helpers.
|
// Should extract the common logic into test helpers.
|
||||||
|
@ -370,11 +233,17 @@ macro_rules! assert_opt_evals_to {
|
||||||
|
|
||||||
let context = Context::create();
|
let context = Context::create();
|
||||||
|
|
||||||
let (main_fn_name, execution_engine) =
|
let stdlib = roc_builtins::unique::uniq_stdlib();
|
||||||
$crate::helpers::eval::helper_with_uniqueness(&arena, $src, $leak, &context);
|
|
||||||
|
|
||||||
let transform = |success| assert_eq!($transform(success), $expected);
|
let (main_fn_name, errors, execution_engine) =
|
||||||
run_jit_function!(execution_engine, main_fn_name, $ty, transform)
|
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
|
||||||
|
|
||||||
|
let transform = |success| {
|
||||||
|
let expected = $expected;
|
||||||
|
let given = $transform(success);
|
||||||
|
assert_eq!(&given, &expected);
|
||||||
|
};
|
||||||
|
run_jit_function!(execution_engine, main_fn_name, $ty, transform, errors)
|
||||||
};
|
};
|
||||||
|
|
||||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||||
|
@ -392,9 +261,10 @@ macro_rules! assert_llvm_evals_to {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
|
|
||||||
let context = Context::create();
|
let context = Context::create();
|
||||||
|
let stdlib = roc_builtins::std::standard_stdlib();
|
||||||
|
|
||||||
let (main_fn_name, errors, execution_engine) =
|
let (main_fn_name, errors, execution_engine) =
|
||||||
$crate::helpers::eval::helper_without_uniqueness(&arena, $src, $leak, &context);
|
$crate::helpers::eval::helper(&arena, $src, stdlib, $leak, &context);
|
||||||
|
|
||||||
let transform = |success| {
|
let transform = |success| {
|
||||||
let expected = $expected;
|
let expected = $expected;
|
||||||
|
@ -411,29 +281,20 @@ macro_rules! assert_llvm_evals_to {
|
||||||
|
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! assert_evals_to {
|
macro_rules! assert_evals_to {
|
||||||
($src:expr, $expected:expr, $ty:ty) => {
|
($src:expr, $expected:expr, $ty:ty) => {{
|
||||||
|
assert_evals_to!($src, $expected, $ty, (|val| val));
|
||||||
|
}};
|
||||||
|
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
||||||
|
// Same as above, except with an additional transformation argument.
|
||||||
|
{
|
||||||
|
assert_evals_to!($src, $expected, $ty, $transform, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
|
||||||
// Run un-optimized tests, and then optimized tests, in separate scopes.
|
// Run un-optimized tests, and then optimized tests, in separate scopes.
|
||||||
// These each rebuild everything from scratch, starting with
|
// These each rebuild everything from scratch, starting with
|
||||||
// parsing the source, so that there's no chance their passing
|
// parsing the source, so that there's no chance their passing
|
||||||
// or failing depends on leftover state from the previous one.
|
// or failing depends on leftover state from the previous one.
|
||||||
{
|
|
||||||
assert_llvm_evals_to!($src, $expected, $ty, (|val| val));
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assert_opt_evals_to!($src, $expected, $ty, (|val| val));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
|
|
||||||
// Same as above, except with an additional transformation argument.
|
|
||||||
{
|
|
||||||
assert_llvm_evals_to!($src, $expected, $ty, $transform);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
assert_opt_evals_to!($src, $expected, $ty, $transform);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
($src:expr, $expected:expr, $ty:ty, $transform:expr, $leak:expr) => {
|
|
||||||
// Same as above, except with an additional transformation argument.
|
|
||||||
{
|
{
|
||||||
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
|
assert_llvm_evals_to!($src, $expected, $ty, $transform, $leak);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,32 +3,6 @@ extern crate bumpalo;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
pub mod eval;
|
pub mod eval;
|
||||||
|
|
||||||
use self::bumpalo::Bump;
|
|
||||||
use roc_builtins::unique::uniq_stdlib;
|
|
||||||
use roc_can::constraint::Constraint;
|
|
||||||
use roc_can::env::Env;
|
|
||||||
use roc_can::expected::Expected;
|
|
||||||
use roc_can::expr::{canonicalize_expr, Expr, Output};
|
|
||||||
use roc_can::operator;
|
|
||||||
use roc_can::scope::Scope;
|
|
||||||
use roc_collections::all::{ImMap, MutMap, SendMap};
|
|
||||||
use roc_constrain::expr::constrain_expr;
|
|
||||||
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
|
|
||||||
use roc_module::ident::Ident;
|
|
||||||
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
|
|
||||||
use roc_parse::ast::{self, Attempting};
|
|
||||||
use roc_parse::blankspace::space0_before;
|
|
||||||
use roc_parse::parser::{loc, Fail, Parser, State};
|
|
||||||
use roc_problem::can::Problem;
|
|
||||||
use roc_region::all::{Located, Region};
|
|
||||||
use roc_solve::solve;
|
|
||||||
use roc_types::subs::{Content, Subs, VarStore, Variable};
|
|
||||||
use roc_types::types::Type;
|
|
||||||
|
|
||||||
pub fn test_home() -> ModuleId {
|
|
||||||
ModuleIds::default().get_or_insert(&"Test".into())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Used in the with_larger_debug_stack() function, for tests that otherwise
|
/// Used in the with_larger_debug_stack() function, for tests that otherwise
|
||||||
/// run out of stack space in debug builds (but don't in --release builds)
|
/// run out of stack space in debug builds (but don't in --release builds)
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -68,249 +42,3 @@ where
|
||||||
{
|
{
|
||||||
run_test()
|
run_test()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn infer_expr(
|
|
||||||
subs: Subs,
|
|
||||||
problems: &mut Vec<roc_solve::solve::TypeError>,
|
|
||||||
constraint: &Constraint,
|
|
||||||
expr_var: Variable,
|
|
||||||
) -> (Content, Subs) {
|
|
||||||
let env = solve::Env {
|
|
||||||
aliases: MutMap::default(),
|
|
||||||
vars_by_symbol: SendMap::default(),
|
|
||||||
};
|
|
||||||
let (solved, _) = solve::run(&env, problems, subs, constraint);
|
|
||||||
|
|
||||||
let content = solved.inner().get_without_compacting(expr_var).content;
|
|
||||||
|
|
||||||
(content, solved.into_inner())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
|
|
||||||
let state = State::new(input.trim().as_bytes(), Attempting::Module);
|
|
||||||
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
|
|
||||||
let answer = parser.parse(&arena, state);
|
|
||||||
|
|
||||||
answer
|
|
||||||
.map(|(loc_expr, _)| loc_expr)
|
|
||||||
.map_err(|(fail, _)| fail)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_expr(expr_str: &str) -> CanExprOut {
|
|
||||||
can_expr_with(&Bump::new(), test_home(), expr_str)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uniq_expr(
|
|
||||||
expr_str: &str,
|
|
||||||
) -> (
|
|
||||||
Located<Expr>,
|
|
||||||
Output,
|
|
||||||
Vec<Problem>,
|
|
||||||
Subs,
|
|
||||||
Variable,
|
|
||||||
Constraint,
|
|
||||||
ModuleId,
|
|
||||||
Interns,
|
|
||||||
) {
|
|
||||||
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
|
|
||||||
|
|
||||||
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn uniq_expr_with(
|
|
||||||
arena: &Bump,
|
|
||||||
expr_str: &str,
|
|
||||||
declared_idents: &ImMap<Ident, (Symbol, Region)>,
|
|
||||||
) -> (
|
|
||||||
Located<Expr>,
|
|
||||||
Output,
|
|
||||||
Vec<Problem>,
|
|
||||||
Subs,
|
|
||||||
Variable,
|
|
||||||
Constraint,
|
|
||||||
ModuleId,
|
|
||||||
Interns,
|
|
||||||
) {
|
|
||||||
let home = test_home();
|
|
||||||
let CanExprOut {
|
|
||||||
loc_expr,
|
|
||||||
output,
|
|
||||||
problems,
|
|
||||||
var_store: mut old_var_store,
|
|
||||||
var,
|
|
||||||
interns,
|
|
||||||
..
|
|
||||||
} = can_expr_with(arena, home, expr_str);
|
|
||||||
|
|
||||||
// double check
|
|
||||||
let mut var_store = VarStore::new(old_var_store.fresh());
|
|
||||||
|
|
||||||
let expected2 = Expected::NoExpectation(Type::Variable(var));
|
|
||||||
let constraint = roc_constrain::uniq::constrain_declaration(
|
|
||||||
home,
|
|
||||||
&mut var_store,
|
|
||||||
Region::zero(),
|
|
||||||
&loc_expr,
|
|
||||||
declared_idents,
|
|
||||||
expected2,
|
|
||||||
);
|
|
||||||
|
|
||||||
let stdlib = uniq_stdlib();
|
|
||||||
|
|
||||||
let types = stdlib.types;
|
|
||||||
let imports: Vec<_> = types
|
|
||||||
.into_iter()
|
|
||||||
.map(|(symbol, (solved_type, region))| Import {
|
|
||||||
loc_symbol: Located::at(region, symbol),
|
|
||||||
solved_type,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// load builtin values
|
|
||||||
|
|
||||||
// TODO what to do with those rigids?
|
|
||||||
let (_introduced_rigids, constraint) =
|
|
||||||
constrain_imported_values(imports, constraint, &mut var_store);
|
|
||||||
|
|
||||||
// load builtin types
|
|
||||||
let mut constraint = load_builtin_aliases(stdlib.aliases, constraint, &mut var_store);
|
|
||||||
|
|
||||||
constraint.instantiate_aliases(&mut var_store);
|
|
||||||
|
|
||||||
let subs2 = Subs::new(var_store.into());
|
|
||||||
|
|
||||||
(
|
|
||||||
loc_expr, output, problems, subs2, var, constraint, home, interns,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct CanExprOut {
|
|
||||||
pub loc_expr: Located<Expr>,
|
|
||||||
pub output: Output,
|
|
||||||
pub problems: Vec<Problem>,
|
|
||||||
pub home: ModuleId,
|
|
||||||
pub interns: Interns,
|
|
||||||
pub var_store: VarStore,
|
|
||||||
pub var: Variable,
|
|
||||||
pub constraint: Constraint,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
|
|
||||||
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
|
|
||||||
panic!(
|
|
||||||
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
|
|
||||||
expr_str, e
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
let mut var_store = VarStore::default();
|
|
||||||
let var = var_store.fresh();
|
|
||||||
let expected = Expected::NoExpectation(Type::Variable(var));
|
|
||||||
let module_ids = ModuleIds::default();
|
|
||||||
|
|
||||||
// Desugar operators (convert them to Apply calls, taking into account
|
|
||||||
// operator precedence and associativity rules), before doing other canonicalization.
|
|
||||||
//
|
|
||||||
// If we did this *during* canonicalization, then each time we
|
|
||||||
// visited a BinOp node we'd recursively try to apply this to each of its nested
|
|
||||||
// operators, and then again on *their* nested operators, ultimately applying the
|
|
||||||
// rules multiple times unnecessarily.
|
|
||||||
let loc_expr = operator::desugar_expr(arena, &loc_expr);
|
|
||||||
|
|
||||||
let mut scope = Scope::new(home);
|
|
||||||
let dep_idents = IdentIds::exposed_builtins(0);
|
|
||||||
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
|
|
||||||
let (loc_expr, output) = canonicalize_expr(
|
|
||||||
&mut env,
|
|
||||||
&mut var_store,
|
|
||||||
&mut scope,
|
|
||||||
Region::zero(),
|
|
||||||
&loc_expr.value,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add builtin defs (e.g. List.get) directly to the canonical Expr,
|
|
||||||
// since we aren't using modules here.
|
|
||||||
let mut with_builtins = loc_expr.value;
|
|
||||||
let builtin_defs = roc_can::builtins::builtin_defs(&mut var_store);
|
|
||||||
|
|
||||||
for (symbol, def) in builtin_defs {
|
|
||||||
if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol)
|
|
||||||
{
|
|
||||||
with_builtins = Expr::LetNonRec(
|
|
||||||
Box::new(def),
|
|
||||||
Box::new(Located {
|
|
||||||
region: Region::zero(),
|
|
||||||
value: with_builtins,
|
|
||||||
}),
|
|
||||||
var_store.fresh(),
|
|
||||||
SendMap::default(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let loc_expr = Located {
|
|
||||||
region: loc_expr.region,
|
|
||||||
value: with_builtins,
|
|
||||||
};
|
|
||||||
|
|
||||||
let constraint = constrain_expr(
|
|
||||||
&roc_constrain::expr::Env {
|
|
||||||
rigids: ImMap::default(),
|
|
||||||
home,
|
|
||||||
},
|
|
||||||
loc_expr.region,
|
|
||||||
&loc_expr.value,
|
|
||||||
expected,
|
|
||||||
);
|
|
||||||
|
|
||||||
let types = roc_builtins::std::types();
|
|
||||||
|
|
||||||
let imports: Vec<_> = types
|
|
||||||
.into_iter()
|
|
||||||
.map(|(symbol, (solved_type, region))| Import {
|
|
||||||
loc_symbol: Located::at(region, symbol),
|
|
||||||
solved_type,
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
// load builtin values
|
|
||||||
let (_introduced_rigids, constraint) =
|
|
||||||
constrain_imported_values(imports, constraint, &mut var_store);
|
|
||||||
|
|
||||||
// TODO determine what to do with those rigids
|
|
||||||
// for var in introduced_rigids {
|
|
||||||
// output.ftv.insert(var, format!("internal_{:?}", var).into());
|
|
||||||
// }
|
|
||||||
|
|
||||||
//load builtin types
|
|
||||||
let mut constraint =
|
|
||||||
load_builtin_aliases(roc_builtins::std::aliases(), constraint, &mut var_store);
|
|
||||||
|
|
||||||
constraint.instantiate_aliases(&mut var_store);
|
|
||||||
|
|
||||||
let mut all_ident_ids = MutMap::default();
|
|
||||||
|
|
||||||
// When pretty printing types, we may need the exposed builtins,
|
|
||||||
// so include them in the Interns we'll ultimately return.
|
|
||||||
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
|
|
||||||
all_ident_ids.insert(module_id, ident_ids);
|
|
||||||
}
|
|
||||||
|
|
||||||
all_ident_ids.insert(home, env.ident_ids);
|
|
||||||
|
|
||||||
let interns = Interns {
|
|
||||||
module_ids: env.module_ids.clone(),
|
|
||||||
all_ident_ids,
|
|
||||||
};
|
|
||||||
|
|
||||||
CanExprOut {
|
|
||||||
loc_expr,
|
|
||||||
output,
|
|
||||||
problems: env.problems,
|
|
||||||
home: env.home,
|
|
||||||
var_store,
|
|
||||||
interns,
|
|
||||||
var,
|
|
||||||
constraint,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -17,8 +17,10 @@ roc_problem = { path = "../problem" }
|
||||||
roc_unify = { path = "../unify" }
|
roc_unify = { path = "../unify" }
|
||||||
roc_parse = { path = "../parse" }
|
roc_parse = { path = "../parse" }
|
||||||
roc_solve = { path = "../solve" }
|
roc_solve = { path = "../solve" }
|
||||||
|
roc_mono = { path = "../mono" }
|
||||||
bumpalo = { version = "3.2", features = ["collections"] }
|
bumpalo = { version = "3.2", features = ["collections"] }
|
||||||
inlinable_string = "0.1"
|
inlinable_string = "0.1"
|
||||||
|
parking_lot = { version = "0.11", features = ["deadlock_detection"] }
|
||||||
crossbeam = "0.7"
|
crossbeam = "0.7"
|
||||||
num_cpus = "1"
|
num_cpus = "1"
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crossbeam::channel::{bounded, Sender};
|
||||||
use crossbeam::deque::{Injector, Stealer, Worker};
|
use crossbeam::deque::{Injector, Stealer, Worker};
|
||||||
use crossbeam::thread;
|
use crossbeam::thread;
|
||||||
use inlinable_string::InlinableString;
|
use inlinable_string::InlinableString;
|
||||||
|
use parking_lot::Mutex;
|
||||||
use roc_builtins::std::{Mode, StdLib};
|
use roc_builtins::std::{Mode, StdLib};
|
||||||
use roc_can::constraint::Constraint;
|
use roc_can::constraint::Constraint;
|
||||||
use roc_can::def::Declaration;
|
use roc_can::def::Declaration;
|
||||||
|
@ -32,7 +33,7 @@ use std::io;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::from_utf8_unchecked;
|
use std::str::from_utf8_unchecked;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::Arc;
|
||||||
use std::time::{Duration, SystemTime};
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
/// Filename extension for normal Roc modules
|
/// Filename extension for normal Roc modules
|
||||||
|
@ -349,8 +350,14 @@ pub fn load(
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
|
|
||||||
// Reserve one CPU for the main thread, and let all the others be eligible
|
// Reserve one CPU for the main thread, and let all the others be eligible
|
||||||
// to spawn workers.
|
// to spawn workers. We use .max(2) to enforce that we always
|
||||||
let num_workers = num_cpus::get() - 1;
|
// end up with at least 1 worker - since (.max(2) - 1) will
|
||||||
|
// always return a number that's at least 1. Using
|
||||||
|
// .max(2) on the initial number of CPUs instead of
|
||||||
|
// doing .max(1) on the entire expression guards against
|
||||||
|
// num_cpus returning 0, while also avoiding wrapping
|
||||||
|
// unsigned subtraction overflow.
|
||||||
|
let num_workers = num_cpus::get().max(2) - 1;
|
||||||
|
|
||||||
let mut worker_arenas = bumpalo::collections::Vec::with_capacity_in(num_workers, &arena);
|
let mut worker_arenas = bumpalo::collections::Vec::with_capacity_in(num_workers, &arena);
|
||||||
|
|
||||||
|
@ -878,8 +885,7 @@ fn finish<'a>(
|
||||||
|
|
||||||
let module_ids = Arc::try_unwrap(state.arc_modules)
|
let module_ids = Arc::try_unwrap(state.arc_modules)
|
||||||
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
|
.unwrap_or_else(|_| panic!("There were still outstanding Arc references to module_ids"))
|
||||||
.into_inner()
|
.into_inner();
|
||||||
.expect("Unwrapping mutex for module_ids");
|
|
||||||
|
|
||||||
let interns = Interns {
|
let interns = Interns {
|
||||||
module_ids,
|
module_ids,
|
||||||
|
@ -1079,10 +1085,8 @@ fn send_header<'a>(
|
||||||
|
|
||||||
let ident_ids = {
|
let ident_ids = {
|
||||||
// Lock just long enough to perform the minimal operations necessary.
|
// Lock just long enough to perform the minimal operations necessary.
|
||||||
let mut module_ids = (*module_ids).lock().expect("Failed to acquire lock for interning module IDs, presumably because a thread panicked.");
|
let mut module_ids = (*module_ids).lock();
|
||||||
let mut ident_ids_by_module = (*ident_ids_by_module).lock().expect(
|
let mut ident_ids_by_module = (*ident_ids_by_module).lock();
|
||||||
"Failed to acquire lock for interning ident IDs, presumably because a thread panicked.",
|
|
||||||
);
|
|
||||||
|
|
||||||
home = module_ids.get_or_insert(&declared_name.as_inline_str());
|
home = module_ids.get_or_insert(&declared_name.as_inline_str());
|
||||||
|
|
||||||
|
@ -1243,9 +1247,7 @@ impl<'a> BuildTask<'a> {
|
||||||
let mut dep_idents: IdentIdsByModule = IdentIds::exposed_builtins(num_deps);
|
let mut dep_idents: IdentIdsByModule = IdentIds::exposed_builtins(num_deps);
|
||||||
|
|
||||||
{
|
{
|
||||||
let ident_ids_by_module = (*ident_ids_by_module).lock().expect(
|
let ident_ids_by_module = (*ident_ids_by_module).lock();
|
||||||
"Failed to acquire lock for interning ident IDs, presumably because a thread panicked.",
|
|
||||||
);
|
|
||||||
|
|
||||||
// Populate dep_idents with each of their IdentIds,
|
// Populate dep_idents with each of their IdentIds,
|
||||||
// which we'll need during canonicalization to translate
|
// which we'll need during canonicalization to translate
|
||||||
|
@ -1277,9 +1279,11 @@ impl<'a> BuildTask<'a> {
|
||||||
|
|
||||||
waiting_for_solve.insert(module_id, solve_needed);
|
waiting_for_solve.insert(module_id, solve_needed);
|
||||||
|
|
||||||
let module_ids = {
|
// Clone the module_ids we'll need for canonicalization.
|
||||||
(*module_ids).lock().expect("Failed to acquire lock for obtaining module IDs, presumably because a thread panicked.").clone()
|
// This should be small, and cloning it should be quick.
|
||||||
};
|
// We release the lock as soon as we're done cloning, so we don't have
|
||||||
|
// to lock the global module_ids while canonicalizing any given module.
|
||||||
|
let module_ids = { (*module_ids).lock().clone() };
|
||||||
|
|
||||||
// Now that we have waiting_for_solve populated, continue parsing,
|
// Now that we have waiting_for_solve populated, continue parsing,
|
||||||
// canonicalizing, and constraining the module.
|
// canonicalizing, and constraining the module.
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -14,12 +14,13 @@ mod helpers;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_load {
|
mod test_load {
|
||||||
use crate::helpers::fixtures_dir;
|
use crate::helpers::fixtures_dir;
|
||||||
|
use bumpalo::Bump;
|
||||||
use inlinable_string::InlinableString;
|
use inlinable_string::InlinableString;
|
||||||
use roc_can::def::Declaration::*;
|
use roc_can::def::Declaration::*;
|
||||||
use roc_can::def::Def;
|
use roc_can::def::Def;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_constrain::module::SubsByModule;
|
use roc_constrain::module::SubsByModule;
|
||||||
use roc_load::file::{load, LoadedModule};
|
use roc_load::file::LoadedModule;
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
use roc_module::symbol::{Interns, ModuleId};
|
||||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||||
use roc_types::subs::Subs;
|
use roc_types::subs::Subs;
|
||||||
|
@ -34,9 +35,11 @@ mod test_load {
|
||||||
) -> LoadedModule {
|
) -> LoadedModule {
|
||||||
let src_dir = fixtures_dir().join(dir_name);
|
let src_dir = fixtures_dir().join(dir_name);
|
||||||
let filename = src_dir.join(format!("{}.roc", module_name));
|
let filename = src_dir.join(format!("{}.roc", module_name));
|
||||||
let loaded = load(
|
let arena = Bump::new();
|
||||||
|
let loaded = roc_load::file::load_and_typecheck(
|
||||||
|
&arena,
|
||||||
filename,
|
filename,
|
||||||
&roc_builtins::std::standard_stdlib(),
|
roc_builtins::std::standard_stdlib(),
|
||||||
src_dir.as_path(),
|
src_dir.as_path(),
|
||||||
subs_by_module,
|
subs_by_module,
|
||||||
);
|
);
|
||||||
|
@ -128,9 +131,11 @@ mod test_load {
|
||||||
let subs_by_module = MutMap::default();
|
let subs_by_module = MutMap::default();
|
||||||
let src_dir = fixtures_dir().join("interface_with_deps");
|
let src_dir = fixtures_dir().join("interface_with_deps");
|
||||||
let filename = src_dir.join("Primary.roc");
|
let filename = src_dir.join("Primary.roc");
|
||||||
let loaded = load(
|
let arena = Bump::new();
|
||||||
|
let loaded = roc_load::file::load_and_typecheck(
|
||||||
|
&arena,
|
||||||
filename,
|
filename,
|
||||||
&roc_builtins::std::standard_stdlib(),
|
roc_builtins::std::standard_stdlib(),
|
||||||
src_dir.as_path(),
|
src_dir.as_path(),
|
||||||
subs_by_module,
|
subs_by_module,
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,13 +14,14 @@ mod helpers;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_uniq_load {
|
mod test_uniq_load {
|
||||||
use crate::helpers::fixtures_dir;
|
use crate::helpers::fixtures_dir;
|
||||||
|
use bumpalo::Bump;
|
||||||
use inlinable_string::InlinableString;
|
use inlinable_string::InlinableString;
|
||||||
use roc_builtins::unique;
|
use roc_builtins::unique;
|
||||||
use roc_can::def::Declaration::*;
|
use roc_can::def::Declaration::*;
|
||||||
use roc_can::def::Def;
|
use roc_can::def::Def;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_constrain::module::SubsByModule;
|
use roc_constrain::module::SubsByModule;
|
||||||
use roc_load::file::{load, LoadedModule};
|
use roc_load::file::LoadedModule;
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
use roc_module::symbol::{Interns, ModuleId};
|
||||||
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
|
||||||
use roc_types::subs::Subs;
|
use roc_types::subs::Subs;
|
||||||
|
@ -33,11 +34,13 @@ mod test_uniq_load {
|
||||||
module_name: &str,
|
module_name: &str,
|
||||||
subs_by_module: SubsByModule,
|
subs_by_module: SubsByModule,
|
||||||
) -> LoadedModule {
|
) -> LoadedModule {
|
||||||
|
let arena = Bump::new();
|
||||||
let src_dir = fixtures_dir().join(dir_name);
|
let src_dir = fixtures_dir().join(dir_name);
|
||||||
let filename = src_dir.join(format!("{}.roc", module_name));
|
let filename = src_dir.join(format!("{}.roc", module_name));
|
||||||
let loaded = load(
|
let loaded = roc_load::file::load_and_typecheck(
|
||||||
|
&arena,
|
||||||
filename,
|
filename,
|
||||||
&unique::uniq_stdlib(),
|
unique::uniq_stdlib(),
|
||||||
src_dir.as_path(),
|
src_dir.as_path(),
|
||||||
subs_by_module,
|
subs_by_module,
|
||||||
);
|
);
|
||||||
|
@ -126,12 +129,14 @@ mod test_uniq_load {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn interface_with_deps() {
|
fn interface_with_deps() {
|
||||||
|
let arena = Bump::new();
|
||||||
let subs_by_module = MutMap::default();
|
let subs_by_module = MutMap::default();
|
||||||
let src_dir = fixtures_dir().join("interface_with_deps");
|
let src_dir = fixtures_dir().join("interface_with_deps");
|
||||||
let filename = src_dir.join("Primary.roc");
|
let filename = src_dir.join("Primary.roc");
|
||||||
let loaded = load(
|
let loaded = roc_load::file::load_and_typecheck(
|
||||||
|
&arena,
|
||||||
filename,
|
filename,
|
||||||
&roc_builtins::std::standard_stdlib(),
|
roc_builtins::std::standard_stdlib(),
|
||||||
src_dir.as_path(),
|
src_dir.as_path(),
|
||||||
subs_by_module,
|
subs_by_module,
|
||||||
);
|
);
|
||||||
|
|
|
@ -288,14 +288,18 @@ impl fmt::Debug for ModuleId {
|
||||||
.lock()
|
.lock()
|
||||||
.expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
|
.expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked.");
|
||||||
|
|
||||||
match names.get(&self.0) {
|
if PRETTY_PRINT_DEBUG_SYMBOLS {
|
||||||
Some(str_ref) => write!(f, "{}", str_ref.clone()),
|
match names.get(&self.0) {
|
||||||
None => {
|
Some(str_ref) => write!(f, "{}", str_ref.clone()),
|
||||||
panic!(
|
None => {
|
||||||
"Could not find a Debug name for module ID {} in {:?}",
|
panic!(
|
||||||
self.0, names,
|
"Could not find a Debug name for module ID {} in {:?}",
|
||||||
);
|
self.0, names,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
write!(f, "{}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,7 +380,7 @@ pub struct IdentId(u32);
|
||||||
///
|
///
|
||||||
/// Each module name is stored twice, for faster lookups.
|
/// Each module name is stored twice, for faster lookups.
|
||||||
/// Since these are interned strings, this shouldn't result in many total allocations in practice.
|
/// Since these are interned strings, this shouldn't result in many total allocations in practice.
|
||||||
#[derive(Clone, Debug, Default)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||||
pub struct IdentIds {
|
pub struct IdentIds {
|
||||||
by_ident: MutMap<InlinableString, IdentId>,
|
by_ident: MutMap<InlinableString, IdentId>,
|
||||||
|
|
||||||
|
|
|
@ -12,9 +12,12 @@ roc_module = { path = "../module" }
|
||||||
roc_types = { path = "../types" }
|
roc_types = { path = "../types" }
|
||||||
roc_can = { path = "../can" }
|
roc_can = { path = "../can" }
|
||||||
roc_unify = { path = "../unify" }
|
roc_unify = { path = "../unify" }
|
||||||
|
roc_constrain = { path = "../constrain" }
|
||||||
|
roc_solve = { path = "../solve" }
|
||||||
roc_problem = { path = "../problem" }
|
roc_problem = { path = "../problem" }
|
||||||
ven_pretty = { path = "../../vendor/pretty" }
|
ven_pretty = { path = "../../vendor/pretty" }
|
||||||
bumpalo = { version = "3.2", features = ["collections"] }
|
bumpalo = { version = "3.2", features = ["collections"] }
|
||||||
|
ven_ena = { path = "../../vendor/ena" }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
roc_constrain = { path = "../constrain" }
|
roc_constrain = { path = "../constrain" }
|
||||||
|
|
|
@ -337,6 +337,7 @@ impl<'a> BorrowInfState<'a> {
|
||||||
self.own_var(*x);
|
self.own_var(*x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionCall {
|
FunctionCall {
|
||||||
call_type,
|
call_type,
|
||||||
args,
|
args,
|
||||||
|
|
|
@ -1143,7 +1143,7 @@ fn test_to_equality<'a>(
|
||||||
// TODO procs and layout are currently unused, but potentially required
|
// TODO procs and layout are currently unused, but potentially required
|
||||||
// for defining optional fields?
|
// for defining optional fields?
|
||||||
// if not, do remove
|
// if not, do remove
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments, clippy::needless_collect)]
|
||||||
fn decide_to_branching<'a>(
|
fn decide_to_branching<'a>(
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
procs: &mut Procs<'a>,
|
procs: &mut Procs<'a>,
|
||||||
|
@ -1256,6 +1256,9 @@ fn decide_to_branching<'a>(
|
||||||
|
|
||||||
// TODO There must be some way to remove this iterator/loop
|
// TODO There must be some way to remove this iterator/loop
|
||||||
let nr = (tests.len() as i64) - 1 + (guard.is_some() as i64);
|
let nr = (tests.len() as i64) - 1 + (guard.is_some() as i64);
|
||||||
|
|
||||||
|
let arena = env.arena;
|
||||||
|
|
||||||
let accum_symbols = std::iter::once(true_symbol)
|
let accum_symbols = std::iter::once(true_symbol)
|
||||||
.chain((0..nr).map(|_| env.unique_symbol()))
|
.chain((0..nr).map(|_| env.unique_symbol()))
|
||||||
.rev()
|
.rev()
|
||||||
|
@ -1268,15 +1271,14 @@ fn decide_to_branching<'a>(
|
||||||
let accum = accum_it.next().unwrap();
|
let accum = accum_it.next().unwrap();
|
||||||
let test_symbol = env.unique_symbol();
|
let test_symbol = env.unique_symbol();
|
||||||
|
|
||||||
let and_expr =
|
let and_expr = Expr::RunLowLevel(LowLevel::And, arena.alloc([test_symbol, accum]));
|
||||||
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
|
|
||||||
|
|
||||||
// write to the branching symbol
|
// write to the branching symbol
|
||||||
cond = Stmt::Let(
|
cond = Stmt::Let(
|
||||||
current_symbol,
|
current_symbol,
|
||||||
and_expr,
|
and_expr,
|
||||||
Layout::Builtin(Builtin::Int1),
|
Layout::Builtin(Builtin::Int1),
|
||||||
env.arena.alloc(cond),
|
arena.alloc(cond),
|
||||||
);
|
);
|
||||||
|
|
||||||
// calculate the guard value
|
// calculate the guard value
|
||||||
|
@ -1287,9 +1289,9 @@ fn decide_to_branching<'a>(
|
||||||
};
|
};
|
||||||
cond = Stmt::Join {
|
cond = Stmt::Join {
|
||||||
id,
|
id,
|
||||||
parameters: env.arena.alloc([param]),
|
parameters: arena.alloc([param]),
|
||||||
remainder: env.arena.alloc(stmt),
|
remainder: arena.alloc(stmt),
|
||||||
continuation: env.arena.alloc(cond),
|
continuation: arena.alloc(cond),
|
||||||
};
|
};
|
||||||
|
|
||||||
// load all the variables (the guard might need them);
|
// load all the variables (the guard might need them);
|
||||||
|
@ -1301,18 +1303,17 @@ fn decide_to_branching<'a>(
|
||||||
let test_symbol = env.unique_symbol();
|
let test_symbol = env.unique_symbol();
|
||||||
let test = Expr::RunLowLevel(
|
let test = Expr::RunLowLevel(
|
||||||
LowLevel::Eq,
|
LowLevel::Eq,
|
||||||
bumpalo::vec![in env.arena; lhs, rhs].into_bump_slice(),
|
bumpalo::vec![in arena; lhs, rhs].into_bump_slice(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let and_expr =
|
let and_expr = Expr::RunLowLevel(LowLevel::And, arena.alloc([test_symbol, accum]));
|
||||||
Expr::RunLowLevel(LowLevel::And, env.arena.alloc([test_symbol, accum]));
|
|
||||||
|
|
||||||
// write to the branching symbol
|
// write to the branching symbol
|
||||||
cond = Stmt::Let(
|
cond = Stmt::Let(
|
||||||
current_symbol,
|
current_symbol,
|
||||||
and_expr,
|
and_expr,
|
||||||
Layout::Builtin(Builtin::Int1),
|
Layout::Builtin(Builtin::Int1),
|
||||||
env.arena.alloc(cond),
|
arena.alloc(cond),
|
||||||
);
|
);
|
||||||
|
|
||||||
// write to the test symbol
|
// write to the test symbol
|
||||||
|
@ -1320,11 +1321,11 @@ fn decide_to_branching<'a>(
|
||||||
test_symbol,
|
test_symbol,
|
||||||
test,
|
test,
|
||||||
Layout::Builtin(Builtin::Int1),
|
Layout::Builtin(Builtin::Int1),
|
||||||
env.arena.alloc(cond),
|
arena.alloc(cond),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (symbol, layout, expr) in new_stores.into_iter() {
|
for (symbol, layout, expr) in new_stores.into_iter() {
|
||||||
cond = Stmt::Let(symbol, expr, layout, env.arena.alloc(cond));
|
cond = Stmt::Let(symbol, expr, layout, arena.alloc(cond));
|
||||||
}
|
}
|
||||||
|
|
||||||
current_symbol = accum;
|
current_symbol = accum;
|
||||||
|
@ -1334,7 +1335,7 @@ fn decide_to_branching<'a>(
|
||||||
true_symbol,
|
true_symbol,
|
||||||
Expr::Literal(Literal::Bool(true)),
|
Expr::Literal(Literal::Bool(true)),
|
||||||
Layout::Builtin(Builtin::Int1),
|
Layout::Builtin(Builtin::Int1),
|
||||||
env.arena.alloc(cond),
|
arena.alloc(cond),
|
||||||
);
|
);
|
||||||
|
|
||||||
cond
|
cond
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -226,9 +226,51 @@ impl<'a> Layout<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Avoid recomputing Layout from Variable multiple times.
|
/// Avoid recomputing Layout from Variable multiple times.
|
||||||
#[derive(Default)]
|
/// We use `ena` for easy snapshots and rollbacks of the cache.
|
||||||
|
/// During specialization, a type variable `a` can be specialized to different layouts,
|
||||||
|
/// e.g. `identity : a -> a` could be specialized to `Bool -> Bool` or `Str -> Str`.
|
||||||
|
/// Therefore in general it's invalid to store a map from variables to layouts
|
||||||
|
/// But if we're careful when to invalidate certain keys, we still get some benefit
|
||||||
|
#[derive(Default, Debug)]
|
||||||
pub struct LayoutCache<'a> {
|
pub struct LayoutCache<'a> {
|
||||||
layouts: MutMap<Variable, Result<Layout<'a>, LayoutProblem>>,
|
layouts: ven_ena::unify::UnificationTable<ven_ena::unify::InPlace<CachedVariable<'a>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum CachedLayout<'a> {
|
||||||
|
Cached(Layout<'a>),
|
||||||
|
NotCached,
|
||||||
|
Problem(LayoutProblem),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Must wrap so we can define a specific UnifyKey instance
|
||||||
|
/// PhantomData so we can store the 'a lifetime, which is needed to implement the UnifyKey trait,
|
||||||
|
/// specifically so we can use `type Value = CachedLayout<'a>`
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
pub struct CachedVariable<'a>(Variable, std::marker::PhantomData<&'a ()>);
|
||||||
|
|
||||||
|
impl<'a> CachedVariable<'a> {
|
||||||
|
fn new(var: Variable) -> Self {
|
||||||
|
CachedVariable(var, std::marker::PhantomData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// use ven_ena::unify::{InPlace, Snapshot, UnificationTable, UnifyKey};
|
||||||
|
|
||||||
|
impl<'a> ven_ena::unify::UnifyKey for CachedVariable<'a> {
|
||||||
|
type Value = CachedLayout<'a>;
|
||||||
|
|
||||||
|
fn index(&self) -> u32 {
|
||||||
|
self.0.index()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_index(index: u32) -> Self {
|
||||||
|
CachedVariable(Variable::from_index(index), std::marker::PhantomData)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tag() -> &'static str {
|
||||||
|
"CachedVariable"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> LayoutCache<'a> {
|
impl<'a> LayoutCache<'a> {
|
||||||
|
@ -244,16 +286,60 @@ impl<'a> LayoutCache<'a> {
|
||||||
// Store things according to the root Variable, to avoid duplicate work.
|
// Store things according to the root Variable, to avoid duplicate work.
|
||||||
let var = subs.get_root_key_without_compacting(var);
|
let var = subs.get_root_key_without_compacting(var);
|
||||||
|
|
||||||
let mut env = Env {
|
let cached_var = CachedVariable::new(var);
|
||||||
arena,
|
|
||||||
subs,
|
|
||||||
seen: MutSet::default(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.layouts
|
self.expand_to_fit(cached_var);
|
||||||
.entry(var)
|
|
||||||
.or_insert_with(|| Layout::from_var(&mut env, var))
|
use CachedLayout::*;
|
||||||
.clone()
|
match self.layouts.probe_value(cached_var) {
|
||||||
|
Cached(result) => Ok(result),
|
||||||
|
Problem(problem) => Err(problem),
|
||||||
|
NotCached => {
|
||||||
|
let mut env = Env {
|
||||||
|
arena,
|
||||||
|
subs,
|
||||||
|
seen: MutSet::default(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = Layout::from_var(&mut env, var);
|
||||||
|
|
||||||
|
let cached_layout = match &result {
|
||||||
|
Ok(layout) => Cached(layout.clone()),
|
||||||
|
Err(problem) => Problem(problem.clone()),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.layouts
|
||||||
|
.update_value(cached_var, |existing| existing.value = cached_layout);
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expand_to_fit(&mut self, var: CachedVariable<'a>) {
|
||||||
|
use ven_ena::unify::UnifyKey;
|
||||||
|
|
||||||
|
let required = (var.index() as isize) - (self.layouts.len() as isize) + 1;
|
||||||
|
if required > 0 {
|
||||||
|
self.layouts.reserve(required as usize);
|
||||||
|
|
||||||
|
for _ in 0..required {
|
||||||
|
self.layouts.new_key(CachedLayout::NotCached);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn snapshot(
|
||||||
|
&mut self,
|
||||||
|
) -> ven_ena::unify::Snapshot<ven_ena::unify::InPlace<CachedVariable<'a>>> {
|
||||||
|
self.layouts.snapshot()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn rollback_to(
|
||||||
|
&mut self,
|
||||||
|
snapshot: ven_ena::unify::Snapshot<ven_ena::unify::InPlace<CachedVariable<'a>>>,
|
||||||
|
) {
|
||||||
|
self.layouts.rollback_to(snapshot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -370,7 +456,7 @@ fn layout_from_flat_type<'a>(
|
||||||
|
|
||||||
// correct the memory mode of unique lists
|
// correct the memory mode of unique lists
|
||||||
match Layout::from_var(env, wrapped_var)? {
|
match Layout::from_var(env, wrapped_var)? {
|
||||||
Layout::Builtin(Builtin::List(_, elem_layout)) => {
|
Layout::Builtin(Builtin::List(_ignored, elem_layout)) => {
|
||||||
let uniqueness_var = args[0];
|
let uniqueness_var = args[0];
|
||||||
let uniqueness_content =
|
let uniqueness_content =
|
||||||
subs.get_without_compacting(uniqueness_var).content;
|
subs.get_without_compacting(uniqueness_var).content;
|
||||||
|
@ -406,15 +492,19 @@ fn layout_from_flat_type<'a>(
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
Record(fields, ext_var) => {
|
Record(fields, ext_var) => {
|
||||||
debug_assert!(ext_var_is_empty_record(subs, ext_var));
|
|
||||||
|
|
||||||
// Sort the fields by label
|
// Sort the fields by label
|
||||||
let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena);
|
let mut sorted_fields = Vec::with_capacity_in(fields.len(), arena);
|
||||||
|
sorted_fields.extend(fields.into_iter());
|
||||||
|
|
||||||
for tuple in fields {
|
// extract any values from the ext_var
|
||||||
sorted_fields.push(tuple);
|
let mut fields_map = MutMap::default();
|
||||||
|
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut fields_map) {
|
||||||
|
Ok(()) | Err((_, Content::FlexVar(_))) => {}
|
||||||
|
Err(_) => unreachable!("this would have been a type error"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sorted_fields.extend(fields_map.into_iter());
|
||||||
|
|
||||||
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
||||||
|
|
||||||
// Determine the layouts of the fields, maintaining sort order
|
// Determine the layouts of the fields, maintaining sort order
|
||||||
|
@ -781,22 +871,6 @@ fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool {
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
|
||||||
fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
|
|
||||||
// the ext_var is empty
|
|
||||||
let mut ext_fields = MutMap::default();
|
|
||||||
match roc_types::pretty_print::chase_ext_record(subs, ext_var, &mut ext_fields) {
|
|
||||||
Ok(()) | Err((_, Content::FlexVar(_))) => ext_fields.is_empty(),
|
|
||||||
Err((_, content)) => panic!("invalid content in ext_var: {:?}", content),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
|
||||||
fn ext_var_is_empty_record(_: &Subs, _: Variable) -> bool {
|
|
||||||
// This should only ever be used in debug_assert! macros
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutProblem> {
|
fn layout_from_num_content<'a>(content: Content) -> Result<Layout<'a>, LayoutProblem> {
|
||||||
use roc_types::subs::Content::*;
|
use roc_types::subs::Content::*;
|
||||||
use roc_types::subs::FlatType::*;
|
use roc_types::subs::FlatType::*;
|
||||||
|
|
|
@ -875,7 +875,8 @@ mod test_mono {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
let Test.0 = 5i64;
|
let Test.0 = 5i64;
|
||||||
ret Test.0;
|
let Test.2 = 3i64;
|
||||||
|
ret Test.2;
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1920,4 +1921,149 @@ mod test_mono {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rigids() {
|
||||||
|
compiles_to_ir(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
swap : Int, Int, List a -> List a
|
||||||
|
swap = \i, j, list ->
|
||||||
|
when Pair (List.get list i) (List.get list j) is
|
||||||
|
Pair (Ok atI) (Ok atJ) ->
|
||||||
|
foo = atJ
|
||||||
|
|
||||||
|
list
|
||||||
|
|> List.set i foo
|
||||||
|
|> List.set j atI
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
||||||
|
|
||||||
|
swap 0 0 [0x1]
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
procedure List.3 (#Attr.2, #Attr.3):
|
||||||
|
let Test.43 = lowlevel ListLen #Attr.2;
|
||||||
|
let Test.39 = lowlevel NumLt #Attr.3 Test.43;
|
||||||
|
if Test.39 then
|
||||||
|
let Test.41 = 1i64;
|
||||||
|
let Test.42 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
|
||||||
|
let Test.40 = Ok Test.41 Test.42;
|
||||||
|
ret Test.40;
|
||||||
|
else
|
||||||
|
let Test.37 = 0i64;
|
||||||
|
let Test.38 = Struct {};
|
||||||
|
let Test.36 = Err Test.37 Test.38;
|
||||||
|
ret Test.36;
|
||||||
|
|
||||||
|
procedure List.4 (#Attr.2, #Attr.3, #Attr.4):
|
||||||
|
let Test.19 = lowlevel ListLen #Attr.2;
|
||||||
|
let Test.17 = lowlevel NumLt #Attr.3 Test.19;
|
||||||
|
if Test.17 then
|
||||||
|
let Test.18 = lowlevel ListSet #Attr.2 #Attr.3 #Attr.4;
|
||||||
|
ret Test.18;
|
||||||
|
else
|
||||||
|
ret #Attr.2;
|
||||||
|
|
||||||
|
procedure Test.0 (Test.2, Test.3, Test.4):
|
||||||
|
let Test.34 = CallByName List.3 Test.4 Test.2;
|
||||||
|
let Test.35 = CallByName List.3 Test.4 Test.3;
|
||||||
|
let Test.13 = Struct {Test.34, Test.35};
|
||||||
|
let Test.24 = true;
|
||||||
|
let Test.26 = 1i64;
|
||||||
|
let Test.25 = Index 0 Test.13;
|
||||||
|
let Test.27 = Index 0 Test.25;
|
||||||
|
let Test.33 = lowlevel Eq Test.26 Test.27;
|
||||||
|
let Test.31 = lowlevel And Test.33 Test.24;
|
||||||
|
let Test.29 = 1i64;
|
||||||
|
let Test.28 = Index 1 Test.13;
|
||||||
|
let Test.30 = Index 0 Test.28;
|
||||||
|
let Test.32 = lowlevel Eq Test.29 Test.30;
|
||||||
|
let Test.23 = lowlevel And Test.32 Test.31;
|
||||||
|
if Test.23 then
|
||||||
|
let Test.21 = Index 0 Test.13;
|
||||||
|
let Test.5 = Index 1 Test.21;
|
||||||
|
let Test.20 = Index 1 Test.13;
|
||||||
|
let Test.6 = Index 1 Test.20;
|
||||||
|
let Test.15 = CallByName List.4 Test.4 Test.2 Test.6;
|
||||||
|
let Test.14 = CallByName List.4 Test.15 Test.3 Test.5;
|
||||||
|
ret Test.14;
|
||||||
|
else
|
||||||
|
dec Test.4;
|
||||||
|
let Test.22 = Array [];
|
||||||
|
ret Test.22;
|
||||||
|
|
||||||
|
let Test.9 = 0i64;
|
||||||
|
let Test.10 = 0i64;
|
||||||
|
let Test.12 = 1i64;
|
||||||
|
let Test.11 = Array [Test.12];
|
||||||
|
let Test.8 = CallByName Test.0 Test.9 Test.10 Test.11;
|
||||||
|
ret Test.8;
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_x_in_x() {
|
||||||
|
compiles_to_ir(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 5
|
||||||
|
|
||||||
|
answer =
|
||||||
|
1337
|
||||||
|
|
||||||
|
unused =
|
||||||
|
nested = 17
|
||||||
|
nested
|
||||||
|
|
||||||
|
answer
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
let Test.1 = 1337i64;
|
||||||
|
let Test.0 = 5i64;
|
||||||
|
let Test.3 = 17i64;
|
||||||
|
ret Test.1;
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn let_x_in_x_indirect() {
|
||||||
|
compiles_to_ir(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
x = 5
|
||||||
|
|
||||||
|
answer =
|
||||||
|
1337
|
||||||
|
|
||||||
|
unused =
|
||||||
|
nested = 17
|
||||||
|
|
||||||
|
i = 1
|
||||||
|
|
||||||
|
nested
|
||||||
|
|
||||||
|
answer
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
let Test.1 = 1337i64;
|
||||||
|
let Test.0 = 5i64;
|
||||||
|
let Test.3 = 17i64;
|
||||||
|
let Test.4 = 1i64;
|
||||||
|
ret Test.1;
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,21 +10,21 @@ mod helpers;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_reporting {
|
mod test_reporting {
|
||||||
use crate::helpers::test_home;
|
use crate::helpers::test_home;
|
||||||
|
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
use roc_module::symbol::{Interns, ModuleId};
|
||||||
use roc_mono::ir::{Procs, Stmt};
|
use roc_mono::ir::{Procs, Stmt};
|
||||||
|
use roc_mono::layout::LayoutCache;
|
||||||
use roc_reporting::report::{
|
use roc_reporting::report::{
|
||||||
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
|
can_problem, mono_problem, parse_problem, type_problem, Report, BLUE_CODE, BOLD_CODE,
|
||||||
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
|
CYAN_CODE, DEFAULT_PALETTE, GREEN_CODE, MAGENTA_CODE, RED_CODE, RESET_CODE, UNDERLINE_CODE,
|
||||||
WHITE_CODE, YELLOW_CODE,
|
WHITE_CODE, YELLOW_CODE,
|
||||||
};
|
};
|
||||||
|
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
||||||
|
use roc_solve::solve;
|
||||||
use roc_types::pretty_print::name_all_type_vars;
|
use roc_types::pretty_print::name_all_type_vars;
|
||||||
use roc_types::subs::Subs;
|
use roc_types::subs::Subs;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
// use roc_region::all;
|
|
||||||
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
|
||||||
use roc_reporting::report::{RocDocAllocator, RocDocBuilder};
|
|
||||||
use roc_solve::solve;
|
|
||||||
|
|
||||||
fn filename_from_string(str: &str) -> PathBuf {
|
fn filename_from_string(str: &str) -> PathBuf {
|
||||||
let mut filename = PathBuf::new();
|
let mut filename = PathBuf::new();
|
||||||
|
@ -87,6 +87,7 @@ mod test_reporting {
|
||||||
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
|
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
|
||||||
|
|
||||||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||||||
|
let mut layout_cache = LayoutCache::default();
|
||||||
let mut mono_env = roc_mono::ir::Env {
|
let mut mono_env = roc_mono::ir::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
subs: &mut subs,
|
subs: &mut subs,
|
||||||
|
@ -94,7 +95,8 @@ mod test_reporting {
|
||||||
home,
|
home,
|
||||||
ident_ids: &mut ident_ids,
|
ident_ids: &mut ident_ids,
|
||||||
};
|
};
|
||||||
let _mono_expr = Stmt::new(&mut mono_env, loc_expr.value, &mut procs);
|
let _mono_expr =
|
||||||
|
Stmt::new(&mut mono_env, loc_expr.value, &mut procs, &mut layout_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((unify_problems, can_problems, mono_problems, home, interns))
|
Ok((unify_problems, can_problems, mono_problems, home, interns))
|
||||||
|
|
|
@ -89,13 +89,7 @@ impl Default for Pools {
|
||||||
|
|
||||||
impl Pools {
|
impl Pools {
|
||||||
pub fn new(num_pools: usize) -> Self {
|
pub fn new(num_pools: usize) -> Self {
|
||||||
let mut pools = Vec::with_capacity(num_pools);
|
Pools(vec![Vec::new(); num_pools])
|
||||||
|
|
||||||
for _ in 0..num_pools {
|
|
||||||
pools.push(Vec::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
Pools(pools)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
|
@ -469,22 +463,35 @@ fn solve(
|
||||||
let visit_mark = young_mark.next();
|
let visit_mark = young_mark.next();
|
||||||
let final_mark = visit_mark.next();
|
let final_mark = visit_mark.next();
|
||||||
|
|
||||||
debug_assert!({
|
debug_assert_eq!(
|
||||||
let offenders = next_pools
|
{
|
||||||
.get(next_rank)
|
let offenders = next_pools
|
||||||
.iter()
|
.get(next_rank)
|
||||||
.filter(|var| {
|
.iter()
|
||||||
subs.get_without_compacting(roc_types::subs::Variable::clone(
|
.filter(|var| {
|
||||||
var,
|
let current = subs.get_without_compacting(
|
||||||
))
|
roc_types::subs::Variable::clone(var),
|
||||||
.rank
|
);
|
||||||
.into_usize()
|
|
||||||
> next_rank.into_usize()
|
|
||||||
})
|
|
||||||
.collect::<Vec<&roc_types::subs::Variable>>();
|
|
||||||
|
|
||||||
offenders.is_empty()
|
current.rank.into_usize() > next_rank.into_usize()
|
||||||
});
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let result = offenders.len();
|
||||||
|
|
||||||
|
if result > 0 {
|
||||||
|
dbg!(
|
||||||
|
&subs,
|
||||||
|
&offenders,
|
||||||
|
&let_con.def_types,
|
||||||
|
&let_con.def_aliases
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
},
|
||||||
|
0
|
||||||
|
);
|
||||||
|
|
||||||
// pop pool
|
// pop pool
|
||||||
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
|
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
|
||||||
|
@ -568,6 +575,16 @@ fn type_to_var(
|
||||||
type_to_variable(subs, rank, pools, cached, typ)
|
type_to_variable(subs, rank, pools, cached, typ)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Abusing existing functions for our purposes
|
||||||
|
/// this is to put a solved type back into subs
|
||||||
|
pub fn insert_type_into_subs(subs: &mut Subs, typ: &Type) -> Variable {
|
||||||
|
let rank = Rank::NONE;
|
||||||
|
let mut pools = Pools::default();
|
||||||
|
let mut cached = MutMap::default();
|
||||||
|
|
||||||
|
type_to_variable(subs, rank, &mut pools, &mut cached, typ)
|
||||||
|
}
|
||||||
|
|
||||||
fn type_to_variable(
|
fn type_to_variable(
|
||||||
subs: &mut Subs,
|
subs: &mut Subs,
|
||||||
rank: Rank,
|
rank: Rank,
|
||||||
|
@ -764,10 +781,10 @@ fn check_for_infinite_type(
|
||||||
) {
|
) {
|
||||||
let var = loc_var.value;
|
let var = loc_var.value;
|
||||||
|
|
||||||
let is_uniq_infer = match subs.get(var).content {
|
let is_uniq_infer = matches!(
|
||||||
Content::Alias(Symbol::ATTR_ATTR, _, _) => true,
|
subs.get(var).content,
|
||||||
_ => false,
|
Content::Alias(Symbol::ATTR_ATTR, _, _)
|
||||||
};
|
);
|
||||||
|
|
||||||
while let Some((recursive, chain)) = subs.occurs(var) {
|
while let Some((recursive, chain)) = subs.occurs(var) {
|
||||||
let description = subs.get(recursive);
|
let description = subs.get(recursive);
|
||||||
|
@ -1182,6 +1199,184 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable])
|
||||||
pool.extend(vars);
|
pool.extend(vars);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Function that converts rigids variables to flex variables
|
||||||
|
/// this is used during the monomorphization process
|
||||||
|
pub fn instantiate_rigids(subs: &mut Subs, var: Variable) {
|
||||||
|
let rank = Rank::NONE;
|
||||||
|
let mut pools = Pools::default();
|
||||||
|
|
||||||
|
instantiate_rigids_help(subs, rank, &mut pools, var);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn instantiate_rigids_help(
|
||||||
|
subs: &mut Subs,
|
||||||
|
max_rank: Rank,
|
||||||
|
pools: &mut Pools,
|
||||||
|
var: Variable,
|
||||||
|
) -> Variable {
|
||||||
|
use roc_types::subs::Content::*;
|
||||||
|
use roc_types::subs::FlatType::*;
|
||||||
|
|
||||||
|
let desc = subs.get_without_compacting(var);
|
||||||
|
|
||||||
|
if let Some(copy) = desc.copy.into_variable() {
|
||||||
|
return copy;
|
||||||
|
}
|
||||||
|
|
||||||
|
let make_descriptor = |content| Descriptor {
|
||||||
|
content,
|
||||||
|
rank: max_rank,
|
||||||
|
mark: Mark::NONE,
|
||||||
|
copy: OptVariable::NONE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let content = desc.content;
|
||||||
|
let copy = var;
|
||||||
|
|
||||||
|
pools.get_mut(max_rank).push(copy);
|
||||||
|
|
||||||
|
// Link the original variable to the new variable. This lets us
|
||||||
|
// avoid making multiple copies of the variable we are instantiating.
|
||||||
|
//
|
||||||
|
// Need to do this before recursively copying to avoid looping.
|
||||||
|
subs.set(
|
||||||
|
var,
|
||||||
|
Descriptor {
|
||||||
|
content: content.clone(),
|
||||||
|
rank: desc.rank,
|
||||||
|
mark: Mark::NONE,
|
||||||
|
copy: copy.into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now we recursively copy the content of the variable.
|
||||||
|
// We have already marked the variable as copied, so we
|
||||||
|
// will not repeat this work or crawl this variable again.
|
||||||
|
match content {
|
||||||
|
Structure(flat_type) => {
|
||||||
|
let new_flat_type = match flat_type {
|
||||||
|
Apply(symbol, args) => {
|
||||||
|
let args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Apply(symbol, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
Func(arg_vars, closure_var, ret_var) => {
|
||||||
|
let new_ret_var = instantiate_rigids_help(subs, max_rank, pools, ret_var);
|
||||||
|
let new_closure_var =
|
||||||
|
instantiate_rigids_help(subs, max_rank, pools, closure_var);
|
||||||
|
let arg_vars = arg_vars
|
||||||
|
.into_iter()
|
||||||
|
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
Func(arg_vars, new_closure_var, new_ret_var)
|
||||||
|
}
|
||||||
|
|
||||||
|
same @ EmptyRecord | same @ EmptyTagUnion | same @ Erroneous(_) => same,
|
||||||
|
|
||||||
|
Record(fields, ext_var) => {
|
||||||
|
let mut new_fields = MutMap::default();
|
||||||
|
|
||||||
|
for (label, field) in fields {
|
||||||
|
use RecordField::*;
|
||||||
|
|
||||||
|
let new_field = match field {
|
||||||
|
Demanded(var) => {
|
||||||
|
Demanded(instantiate_rigids_help(subs, max_rank, pools, var))
|
||||||
|
}
|
||||||
|
Required(var) => {
|
||||||
|
Required(instantiate_rigids_help(subs, max_rank, pools, var))
|
||||||
|
}
|
||||||
|
Optional(var) => {
|
||||||
|
Optional(instantiate_rigids_help(subs, max_rank, pools, var))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
new_fields.insert(label, new_field);
|
||||||
|
}
|
||||||
|
|
||||||
|
Record(
|
||||||
|
new_fields,
|
||||||
|
instantiate_rigids_help(subs, max_rank, pools, ext_var),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TagUnion(tags, ext_var) => {
|
||||||
|
let mut new_tags = MutMap::default();
|
||||||
|
|
||||||
|
for (tag, vars) in tags {
|
||||||
|
let new_vars: Vec<Variable> = vars
|
||||||
|
.into_iter()
|
||||||
|
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
|
||||||
|
.collect();
|
||||||
|
new_tags.insert(tag, new_vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
TagUnion(
|
||||||
|
new_tags,
|
||||||
|
instantiate_rigids_help(subs, max_rank, pools, ext_var),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||||
|
let mut new_tags = MutMap::default();
|
||||||
|
|
||||||
|
let new_rec_var = instantiate_rigids_help(subs, max_rank, pools, rec_var);
|
||||||
|
|
||||||
|
for (tag, vars) in tags {
|
||||||
|
let new_vars: Vec<Variable> = vars
|
||||||
|
.into_iter()
|
||||||
|
.map(|var| instantiate_rigids_help(subs, max_rank, pools, var))
|
||||||
|
.collect();
|
||||||
|
new_tags.insert(tag, new_vars);
|
||||||
|
}
|
||||||
|
|
||||||
|
RecursiveTagUnion(
|
||||||
|
new_rec_var,
|
||||||
|
new_tags,
|
||||||
|
instantiate_rigids_help(subs, max_rank, pools, ext_var),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Boolean(b) => {
|
||||||
|
let mut mapper = |var| instantiate_rigids_help(subs, max_rank, pools, var);
|
||||||
|
|
||||||
|
Boolean(b.map_variables(&mut mapper))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
subs.set(copy, make_descriptor(Structure(new_flat_type)));
|
||||||
|
|
||||||
|
copy
|
||||||
|
}
|
||||||
|
|
||||||
|
FlexVar(_) | Error => copy,
|
||||||
|
|
||||||
|
RigidVar(name) => {
|
||||||
|
subs.set(copy, make_descriptor(FlexVar(Some(name))));
|
||||||
|
|
||||||
|
copy
|
||||||
|
}
|
||||||
|
|
||||||
|
Alias(symbol, args, real_type_var) => {
|
||||||
|
let new_args = args
|
||||||
|
.into_iter()
|
||||||
|
.map(|(name, var)| (name, instantiate_rigids_help(subs, max_rank, pools, var)))
|
||||||
|
.collect();
|
||||||
|
let new_real_type_var = instantiate_rigids_help(subs, max_rank, pools, real_type_var);
|
||||||
|
let new_content = Alias(symbol, new_args, new_real_type_var);
|
||||||
|
|
||||||
|
subs.set(copy, make_descriptor(new_content));
|
||||||
|
|
||||||
|
copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable {
|
fn deep_copy_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, var: Variable) -> Variable {
|
||||||
let copy = deep_copy_var_help(subs, rank, pools, var);
|
let copy = deep_copy_var_help(subs, rank, pools, var);
|
||||||
|
|
||||||
|
|
|
@ -52,10 +52,10 @@ pub enum Bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn var_is_shared(subs: &Subs, var: Variable) -> bool {
|
pub fn var_is_shared(subs: &Subs, var: Variable) -> bool {
|
||||||
match subs.get_without_compacting(var).content {
|
matches!(
|
||||||
Content::Structure(FlatType::Boolean(Bool::Shared)) => true,
|
subs.get_without_compacting(var).content,
|
||||||
_ => false,
|
Content::Structure(FlatType::Boolean(Bool::Shared))
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given the Subs
|
/// Given the Subs
|
||||||
|
@ -163,10 +163,7 @@ impl Bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_unique(&self, subs: &Subs) -> bool {
|
pub fn is_unique(&self, subs: &Subs) -> bool {
|
||||||
match self.simplify(subs) {
|
!matches!(self.simplify(subs), Shared)
|
||||||
Shared => false,
|
|
||||||
_ => true,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn variables(&self) -> SendSet<Variable> {
|
pub fn variables(&self) -> SendSet<Variable> {
|
||||||
|
|
|
@ -676,7 +676,7 @@ fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens
|
||||||
|
|
||||||
let combined = buffers.join(" | ");
|
let combined = buffers.join(" | ");
|
||||||
|
|
||||||
buf.push_str("(");
|
buf.push('(');
|
||||||
write_content(
|
write_content(
|
||||||
env,
|
env,
|
||||||
subs.get_without_compacting(cvar).content,
|
subs.get_without_compacting(cvar).content,
|
||||||
|
@ -686,7 +686,7 @@ fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens
|
||||||
);
|
);
|
||||||
buf.push_str(" | ");
|
buf.push_str(" | ");
|
||||||
buf.push_str(&combined);
|
buf.push_str(&combined);
|
||||||
buf.push_str(")");
|
buf.push(')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -716,7 +716,7 @@ fn write_apply(
|
||||||
|
|
||||||
let mut default_case = |subs, content| {
|
let mut default_case = |subs, content| {
|
||||||
if write_parens {
|
if write_parens {
|
||||||
buf.push_str("(");
|
buf.push('(');
|
||||||
}
|
}
|
||||||
|
|
||||||
write_content(env, content, subs, &mut arg_param, Parens::InTypeParam);
|
write_content(env, content, subs, &mut arg_param, Parens::InTypeParam);
|
||||||
|
@ -724,7 +724,7 @@ fn write_apply(
|
||||||
buf.push_str(&arg_param);
|
buf.push_str(&arg_param);
|
||||||
|
|
||||||
if write_parens {
|
if write_parens {
|
||||||
buf.push_str(")");
|
buf.push(')');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -762,13 +762,13 @@ fn write_apply(
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
if write_parens {
|
if write_parens {
|
||||||
buf.push_str("(");
|
buf.push('(');
|
||||||
}
|
}
|
||||||
|
|
||||||
write_symbol(env, symbol, buf);
|
write_symbol(env, symbol, buf);
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
buf.push_str(" ");
|
buf.push(' ');
|
||||||
write_content(
|
write_content(
|
||||||
env,
|
env,
|
||||||
subs.get_without_compacting(arg).content,
|
subs.get_without_compacting(arg).content,
|
||||||
|
@ -779,7 +779,7 @@ fn write_apply(
|
||||||
}
|
}
|
||||||
|
|
||||||
if write_parens {
|
if write_parens {
|
||||||
buf.push_str(")");
|
buf.push(')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -797,7 +797,7 @@ fn write_fn(
|
||||||
let use_parens = parens != Parens::Unnecessary;
|
let use_parens = parens != Parens::Unnecessary;
|
||||||
|
|
||||||
if use_parens {
|
if use_parens {
|
||||||
buf.push_str("(");
|
buf.push('(');
|
||||||
}
|
}
|
||||||
|
|
||||||
for arg in args {
|
for arg in args {
|
||||||
|
@ -826,7 +826,7 @@ fn write_fn(
|
||||||
);
|
);
|
||||||
|
|
||||||
if use_parens {
|
if use_parens {
|
||||||
buf.push_str(")");
|
buf.push(')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use crate::boolean_algebra;
|
use crate::boolean_algebra;
|
||||||
use crate::subs::{FlatType, Subs, VarId, Variable};
|
use crate::subs::{FlatType, Subs, VarId, Variable};
|
||||||
use crate::types::{Problem, RecordField, Type};
|
use crate::types::{Problem, RecordField, Type};
|
||||||
|
use roc_collections::all::MutSet;
|
||||||
use roc_module::ident::{Lowercase, TagName};
|
use roc_module::ident::{Lowercase, TagName};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
|
@ -15,13 +16,17 @@ impl<T> Solved<T> {
|
||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inner_mut(&mut self) -> &'_ mut T {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
|
||||||
pub fn into_inner(self) -> T {
|
pub fn into_inner(self) -> T {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is a fully solved type, with no Variables remaining in it.
|
/// This is a fully solved type, with no Variables remaining in it.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum SolvedType {
|
pub enum SolvedType {
|
||||||
/// A function. The types of its arguments, then the type of its return value.
|
/// A function. The types of its arguments, then the type of its return value.
|
||||||
Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>),
|
Func(Vec<SolvedType>, Box<SolvedType>, Box<SolvedType>),
|
||||||
|
@ -55,7 +60,7 @@ pub enum SolvedType {
|
||||||
Error,
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
pub enum SolvedBool {
|
pub enum SolvedBool {
|
||||||
SolvedShared,
|
SolvedShared,
|
||||||
SolvedContainer(VarId, Vec<VarId>),
|
SolvedContainer(VarId, Vec<VarId>),
|
||||||
|
@ -68,10 +73,13 @@ impl SolvedBool {
|
||||||
match boolean {
|
match boolean {
|
||||||
Bool::Shared => SolvedBool::SolvedShared,
|
Bool::Shared => SolvedBool::SolvedShared,
|
||||||
Bool::Container(cvar, mvars) => {
|
Bool::Container(cvar, mvars) => {
|
||||||
debug_assert!(matches!(
|
match subs.get_without_compacting(*cvar).content {
|
||||||
subs.get_without_compacting(*cvar).content,
|
crate::subs::Content::FlexVar(_) => {}
|
||||||
crate::subs::Content::FlexVar(_)
|
crate::subs::Content::Structure(FlatType::Boolean(Bool::Shared)) => {
|
||||||
));
|
return SolvedBool::SolvedShared;
|
||||||
|
}
|
||||||
|
other => panic!("Container var is not flex but {:?}", other),
|
||||||
|
}
|
||||||
|
|
||||||
SolvedBool::SolvedContainer(
|
SolvedBool::SolvedContainer(
|
||||||
VarId::from_var(*cvar, subs),
|
VarId::from_var(*cvar, subs),
|
||||||
|
@ -193,21 +201,32 @@ impl SolvedType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_var(subs: &Subs, var: Variable) -> Self {
|
pub fn from_var(subs: &Subs, var: Variable) -> Self {
|
||||||
|
let mut seen = RecursionVars::default();
|
||||||
|
Self::from_var_help(subs, &mut seen, var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_var_help(subs: &Subs, recursion_vars: &mut RecursionVars, var: Variable) -> Self {
|
||||||
use crate::subs::Content::*;
|
use crate::subs::Content::*;
|
||||||
|
|
||||||
|
// if this is a recursion var we've seen before, just generate a Flex
|
||||||
|
// (not doing so would have this function loop forever)
|
||||||
|
if recursion_vars.contains(subs, var) {
|
||||||
|
return SolvedType::Flex(VarId::from_var(var, subs));
|
||||||
|
}
|
||||||
|
|
||||||
match subs.get_without_compacting(var).content {
|
match subs.get_without_compacting(var).content {
|
||||||
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
|
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
|
||||||
RigidVar(name) => SolvedType::Rigid(name),
|
RigidVar(name) => SolvedType::Rigid(name),
|
||||||
Structure(flat_type) => Self::from_flat_type(subs, flat_type),
|
Structure(flat_type) => Self::from_flat_type(subs, recursion_vars, flat_type),
|
||||||
Alias(symbol, args, actual_var) => {
|
Alias(symbol, args, actual_var) => {
|
||||||
let mut new_args = Vec::with_capacity(args.len());
|
let mut new_args = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for (arg_name, arg_var) in args {
|
for (arg_name, arg_var) in args {
|
||||||
new_args.push((arg_name, Self::from_var(subs, arg_var)));
|
new_args.push((arg_name, Self::from_var_help(subs, recursion_vars, arg_var)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let aliased_to = Self::from_var(subs, actual_var);
|
let aliased_to = Self::from_var_help(subs, recursion_vars, actual_var);
|
||||||
|
|
||||||
SolvedType::Alias(symbol, new_args, Box::new(aliased_to))
|
SolvedType::Alias(symbol, new_args, Box::new(aliased_to))
|
||||||
}
|
}
|
||||||
|
@ -215,15 +234,19 @@ impl SolvedType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn from_flat_type(subs: &Subs, flat_type: FlatType) -> Self {
|
fn from_flat_type(
|
||||||
|
subs: &Subs,
|
||||||
|
recursion_vars: &mut RecursionVars,
|
||||||
|
flat_type: FlatType,
|
||||||
|
) -> Self {
|
||||||
use crate::subs::FlatType::*;
|
use crate::subs::FlatType::*;
|
||||||
|
|
||||||
match flat_type {
|
match flat_type {
|
||||||
Apply(symbol, args) => {
|
Apply(symbol, args) => {
|
||||||
let mut new_args = Vec::with_capacity(args.len());
|
let mut new_args = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for var in args {
|
for var in args.iter().copied() {
|
||||||
new_args.push(Self::from_var(subs, var));
|
new_args.push(Self::from_var_help(subs, recursion_vars, var));
|
||||||
}
|
}
|
||||||
|
|
||||||
SolvedType::Apply(symbol, new_args)
|
SolvedType::Apply(symbol, new_args)
|
||||||
|
@ -232,11 +255,11 @@ impl SolvedType {
|
||||||
let mut new_args = Vec::with_capacity(args.len());
|
let mut new_args = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for var in args {
|
for var in args {
|
||||||
new_args.push(Self::from_var(subs, var));
|
new_args.push(Self::from_var_help(subs, recursion_vars, var));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ret = Self::from_var(subs, ret);
|
let ret = Self::from_var_help(subs, recursion_vars, ret);
|
||||||
let closure = Self::from_var(subs, closure);
|
let closure = Self::from_var_help(subs, recursion_vars, closure);
|
||||||
|
|
||||||
SolvedType::Func(new_args, Box::new(closure), Box::new(ret))
|
SolvedType::Func(new_args, Box::new(closure), Box::new(ret))
|
||||||
}
|
}
|
||||||
|
@ -247,15 +270,15 @@ impl SolvedType {
|
||||||
use RecordField::*;
|
use RecordField::*;
|
||||||
|
|
||||||
let solved_type = match field {
|
let solved_type = match field {
|
||||||
Optional(var) => Optional(Self::from_var(subs, var)),
|
Optional(var) => Optional(Self::from_var_help(subs, recursion_vars, var)),
|
||||||
Required(var) => Required(Self::from_var(subs, var)),
|
Required(var) => Required(Self::from_var_help(subs, recursion_vars, var)),
|
||||||
Demanded(var) => Demanded(Self::from_var(subs, var)),
|
Demanded(var) => Demanded(Self::from_var_help(subs, recursion_vars, var)),
|
||||||
};
|
};
|
||||||
|
|
||||||
new_fields.push((label, solved_type));
|
new_fields.push((label, solved_type));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext = Self::from_var(subs, ext_var);
|
let ext = Self::from_var_help(subs, recursion_vars, ext_var);
|
||||||
|
|
||||||
SolvedType::Record {
|
SolvedType::Record {
|
||||||
fields: new_fields,
|
fields: new_fields,
|
||||||
|
@ -269,30 +292,32 @@ impl SolvedType {
|
||||||
let mut new_args = Vec::with_capacity(args.len());
|
let mut new_args = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for var in args {
|
for var in args {
|
||||||
new_args.push(Self::from_var(subs, var));
|
new_args.push(Self::from_var_help(subs, recursion_vars, var));
|
||||||
}
|
}
|
||||||
|
|
||||||
new_tags.push((tag_name, new_args));
|
new_tags.push((tag_name, new_args));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext = Self::from_var(subs, ext_var);
|
let ext = Self::from_var_help(subs, recursion_vars, ext_var);
|
||||||
|
|
||||||
SolvedType::TagUnion(new_tags, Box::new(ext))
|
SolvedType::TagUnion(new_tags, Box::new(ext))
|
||||||
}
|
}
|
||||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||||
|
recursion_vars.insert(subs, rec_var);
|
||||||
|
|
||||||
let mut new_tags = Vec::with_capacity(tags.len());
|
let mut new_tags = Vec::with_capacity(tags.len());
|
||||||
|
|
||||||
for (tag_name, args) in tags {
|
for (tag_name, args) in tags {
|
||||||
let mut new_args = Vec::with_capacity(args.len());
|
let mut new_args = Vec::with_capacity(args.len());
|
||||||
|
|
||||||
for var in args {
|
for var in args {
|
||||||
new_args.push(Self::from_var(subs, var));
|
new_args.push(Self::from_var_help(subs, recursion_vars, var));
|
||||||
}
|
}
|
||||||
|
|
||||||
new_tags.push((tag_name, new_args));
|
new_tags.push((tag_name, new_args));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ext = Self::from_var(subs, ext_var);
|
let ext = Self::from_var_help(subs, recursion_vars, ext_var);
|
||||||
|
|
||||||
SolvedType::RecursiveTagUnion(
|
SolvedType::RecursiveTagUnion(
|
||||||
VarId::from_var(rec_var, subs),
|
VarId::from_var(rec_var, subs),
|
||||||
|
@ -314,3 +339,20 @@ pub struct BuiltinAlias {
|
||||||
pub vars: Vec<Located<Lowercase>>,
|
pub vars: Vec<Located<Lowercase>>,
|
||||||
pub typ: SolvedType,
|
pub typ: SolvedType,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct RecursionVars(MutSet<Variable>);
|
||||||
|
|
||||||
|
impl RecursionVars {
|
||||||
|
fn contains(&self, subs: &Subs, var: Variable) -> bool {
|
||||||
|
let var = subs.get_root_key_without_compacting(var);
|
||||||
|
|
||||||
|
self.0.contains(&var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, subs: &Subs, var: Variable) {
|
||||||
|
let var = subs.get_root_key_without_compacting(var);
|
||||||
|
|
||||||
|
self.0.insert(var);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -72,6 +72,17 @@ impl VarStore {
|
||||||
VarStore { next: next_var.0 }
|
VarStore { next: next_var.0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_from_subs(subs: &Subs) -> Self {
|
||||||
|
let next_var = (subs.utable.len()) as u32;
|
||||||
|
debug_assert!(next_var >= Variable::FIRST_USER_SPACE_VAR.0);
|
||||||
|
|
||||||
|
VarStore { next: next_var }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn peek(&mut self) -> u32 {
|
||||||
|
self.next
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fresh(&mut self) -> Variable {
|
pub fn fresh(&mut self) -> Variable {
|
||||||
// Increment the counter and return the value it had before it was incremented.
|
// Increment the counter and return the value it had before it was incremented.
|
||||||
let answer = self.next;
|
let answer = self.next;
|
||||||
|
@ -163,6 +174,10 @@ impl Variable {
|
||||||
pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self {
|
pub unsafe fn unsafe_test_debug_variable(v: u32) -> Self {
|
||||||
Variable(v)
|
Variable(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn index(&self) -> u32 {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Into<OptVariable> for Variable {
|
impl Into<OptVariable> for Variable {
|
||||||
|
@ -255,6 +270,12 @@ impl Subs {
|
||||||
subs
|
subs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn extend_by(&mut self, entries: usize) {
|
||||||
|
for _ in 0..entries {
|
||||||
|
self.utable.new_key(flex_var_descriptor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn fresh(&mut self, value: Descriptor) -> Variable {
|
pub fn fresh(&mut self, value: Descriptor) -> Variable {
|
||||||
self.utable.new_key(value)
|
self.utable.new_key(value)
|
||||||
}
|
}
|
||||||
|
@ -531,10 +552,10 @@ pub enum Content {
|
||||||
impl Content {
|
impl Content {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn is_number(&self) -> bool {
|
pub fn is_number(&self) -> bool {
|
||||||
match &self {
|
matches!(
|
||||||
Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _)) => true,
|
&self,
|
||||||
_ => false,
|
Content::Structure(FlatType::Apply(Symbol::NUM_NUM, _))
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_unique(&self, subs: &Subs) -> bool {
|
pub fn is_unique(&self, subs: &Subs) -> bool {
|
||||||
|
|
|
@ -22,7 +22,7 @@ pub const TYPE_FLOATINGPOINT: &str = "FloatingPoint";
|
||||||
/// Can unify with Optional and Demanded
|
/// Can unify with Optional and Demanded
|
||||||
/// - Optional: introduced by pattern matches and annotations.
|
/// - Optional: introduced by pattern matches and annotations.
|
||||||
/// Can unify with Required, but not with Demanded
|
/// Can unify with Required, but not with Demanded
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone, Hash)]
|
||||||
pub enum RecordField<T> {
|
pub enum RecordField<T> {
|
||||||
Optional(T),
|
Optional(T),
|
||||||
Required(T),
|
Required(T),
|
||||||
|
@ -988,7 +988,7 @@ pub struct Alias {
|
||||||
pub typ: Type,
|
pub typ: Type,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
|
||||||
pub enum Problem {
|
pub enum Problem {
|
||||||
CanonicalizationProblem,
|
CanonicalizationProblem,
|
||||||
CircularType(Symbol, ErrorType, Region),
|
CircularType(Symbol, ErrorType, Region),
|
||||||
|
@ -1014,7 +1014,7 @@ pub enum Mismatch {
|
||||||
CanonicalizationProblem,
|
CanonicalizationProblem,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Clone)]
|
#[derive(PartialEq, Eq, Clone, Hash)]
|
||||||
pub enum ErrorType {
|
pub enum ErrorType {
|
||||||
Infinite,
|
Infinite,
|
||||||
Type(Symbol, Vec<ErrorType>),
|
Type(Symbol, Vec<ErrorType>),
|
||||||
|
@ -1063,7 +1063,7 @@ fn write_error_type_help(
|
||||||
|
|
||||||
match error_type {
|
match error_type {
|
||||||
Infinite => buf.push_str("∞"),
|
Infinite => buf.push_str("∞"),
|
||||||
Error => buf.push_str("?"),
|
Error => buf.push('?'),
|
||||||
FlexVar(name) => buf.push_str(name.as_str()),
|
FlexVar(name) => buf.push_str(name.as_str()),
|
||||||
RigidVar(name) => buf.push_str(name.as_str()),
|
RigidVar(name) => buf.push_str(name.as_str()),
|
||||||
Type(symbol, arguments) => {
|
Type(symbol, arguments) => {
|
||||||
|
@ -1181,7 +1181,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
||||||
|
|
||||||
match error_type {
|
match error_type {
|
||||||
Infinite => buf.push_str("∞"),
|
Infinite => buf.push_str("∞"),
|
||||||
Error => buf.push_str("?"),
|
Error => buf.push('?'),
|
||||||
FlexVar(name) => buf.push_str(name.as_str()),
|
FlexVar(name) => buf.push_str(name.as_str()),
|
||||||
RigidVar(name) => buf.push_str(name.as_str()),
|
RigidVar(name) => buf.push_str(name.as_str()),
|
||||||
Type(symbol, arguments) => {
|
Type(symbol, arguments) => {
|
||||||
|
@ -1316,7 +1316,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
||||||
while let Some((tag, args)) = it.next() {
|
while let Some((tag, args)) = it.next() {
|
||||||
buf.push_str(&format!("{:?}", tag));
|
buf.push_str(&format!("{:?}", tag));
|
||||||
for arg in args {
|
for arg in args {
|
||||||
buf.push_str(" ");
|
buf.push(' ');
|
||||||
write_debug_error_type_help(arg, buf, Parens::InTypeParam);
|
write_debug_error_type_help(arg, buf, Parens::InTypeParam);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1335,7 +1335,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
||||||
while let Some((tag, args)) = it.next() {
|
while let Some((tag, args)) = it.next() {
|
||||||
buf.push_str(&format!("{:?}", tag));
|
buf.push_str(&format!("{:?}", tag));
|
||||||
for arg in args {
|
for arg in args {
|
||||||
buf.push_str(" ");
|
buf.push(' ');
|
||||||
write_debug_error_type_help(arg, buf, Parens::Unnecessary);
|
write_debug_error_type_help(arg, buf, Parens::Unnecessary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1359,7 +1359,7 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Debug, Clone)]
|
#[derive(PartialEq, Eq, Debug, Clone, Hash)]
|
||||||
pub enum TypeExt {
|
pub enum TypeExt {
|
||||||
Closed,
|
Closed,
|
||||||
FlexOpen(Lowercase),
|
FlexOpen(Lowercase),
|
||||||
|
|
|
@ -31,21 +31,11 @@ macro_rules! mismatch {
|
||||||
println!("");
|
println!("");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
vec![Mismatch::TypeMismatch]
|
vec![Mismatch::TypeMismatch]
|
||||||
}};
|
}};
|
||||||
($msg:expr,) => {{
|
($msg:expr,) => {{
|
||||||
if cfg!(debug_assertions) {
|
mismatch!($msg)
|
||||||
println!(
|
|
||||||
"Mismatch in {} Line {} Column {}",
|
|
||||||
file!(),
|
|
||||||
line!(),
|
|
||||||
column!()
|
|
||||||
);
|
|
||||||
println!($msg);
|
|
||||||
println!("");
|
|
||||||
}
|
|
||||||
|
|
||||||
vec![Mismatch::TypeMismatch]
|
|
||||||
}};
|
}};
|
||||||
($msg:expr, $($arg:tt)*) => {{
|
($msg:expr, $($arg:tt)*) => {{
|
||||||
if cfg!(debug_assertions) {
|
if cfg!(debug_assertions) {
|
||||||
|
@ -133,12 +123,12 @@ fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
|
||||||
// NOTE: names are generated here (when creating an error type) and that modifies names
|
// NOTE: names are generated here (when creating an error type) and that modifies names
|
||||||
// generated by pretty_print.rs. So many test will fail with changes in variable names when
|
// generated by pretty_print.rs. So many test will fail with changes in variable names when
|
||||||
// this block runs.
|
// this block runs.
|
||||||
let (type1, _problems1) = subs.var_to_error_type(ctx.first);
|
// let (type1, _problems1) = subs.var_to_error_type(ctx.first);
|
||||||
let (type2, _problems2) = subs.var_to_error_type(ctx.second);
|
// let (type2, _problems2) = subs.var_to_error_type(ctx.second);
|
||||||
println!("\n --------------- \n");
|
// println!("\n --------------- \n");
|
||||||
dbg!(ctx.first, type1);
|
// dbg!(ctx.first, type1);
|
||||||
println!("\n --- \n");
|
// println!("\n --- \n");
|
||||||
dbg!(ctx.second, type2);
|
// dbg!(ctx.second, type2);
|
||||||
println!("\n --------------- \n");
|
println!("\n --------------- \n");
|
||||||
println!(
|
println!(
|
||||||
"{:?} {:?} ~ {:?} {:?}",
|
"{:?} {:?} ~ {:?} {:?}",
|
||||||
|
@ -681,7 +671,7 @@ fn unify_shared_tags(
|
||||||
|
|
||||||
merge(subs, ctx, Structure(flat_type))
|
merge(subs, ctx, Structure(flat_type))
|
||||||
} else {
|
} else {
|
||||||
mismatch!()
|
mismatch!("Problem with Tag Union")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -911,7 +901,7 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
|
||||||
RigidVar(_) | Structure(_) | Alias(_, _, _) => {
|
RigidVar(_) | Structure(_) | Alias(_, _, _) => {
|
||||||
// Type mismatch! Rigid can only unify with flex, even if the
|
// Type mismatch! Rigid can only unify with flex, even if the
|
||||||
// rigid names are the same.
|
// rigid names are the same.
|
||||||
mismatch!()
|
mismatch!("Rigid with {:?}", &other)
|
||||||
}
|
}
|
||||||
Error => {
|
Error => {
|
||||||
// Error propagates.
|
// Error propagates.
|
||||||
|
|
|
@ -47,6 +47,8 @@ These are potentially inspirational resources for the editor's design.
|
||||||
* [Unreal Engine 4](https://www.unrealengine.com/en-US/)
|
* [Unreal Engine 4](https://www.unrealengine.com/en-US/)
|
||||||
* [Blueprints](https://docs.unrealengine.com/en-US/Engine/Blueprints/index.html) visual scripting (not suggesting visual scripting for Roc)
|
* [Blueprints](https://docs.unrealengine.com/en-US/Engine/Blueprints/index.html) visual scripting (not suggesting visual scripting for Roc)
|
||||||
|
|
||||||
|
* [Live Programing](https://www.microsoft.com/en-us/research/project/live-programming/?from=http%3A%2F%2Fresearch.microsoft.com%2Fen-us%2Fprojects%2Fliveprogramming%2Ftypography.aspx#!publications) by [Microsoft Research] it contains many interesting research papers.
|
||||||
|
|
||||||
### Non-Code Related Inspiration
|
### Non-Code Related Inspiration
|
||||||
|
|
||||||
* [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more
|
* [Scrivner](https://www.literatureandlatte.com/scrivener/overview) writing app for novelists, screenwriters, and more
|
||||||
|
|
|
@ -13,42 +13,42 @@ pub fn handle_text_input(
|
||||||
}
|
}
|
||||||
|
|
||||||
match virtual_keycode {
|
match virtual_keycode {
|
||||||
Key1 | Numpad1 => text_state.push_str("1"),
|
Key1 | Numpad1 => text_state.push('1'),
|
||||||
Key2 | Numpad2 => text_state.push_str("2"),
|
Key2 | Numpad2 => text_state.push('2'),
|
||||||
Key3 | Numpad3 => text_state.push_str("3"),
|
Key3 | Numpad3 => text_state.push('3'),
|
||||||
Key4 | Numpad4 => text_state.push_str("4"),
|
Key4 | Numpad4 => text_state.push('4'),
|
||||||
Key5 | Numpad5 => text_state.push_str("5"),
|
Key5 | Numpad5 => text_state.push('5'),
|
||||||
Key6 | Numpad6 => text_state.push_str("6"),
|
Key6 | Numpad6 => text_state.push('6'),
|
||||||
Key7 | Numpad7 => text_state.push_str("7"),
|
Key7 | Numpad7 => text_state.push('7'),
|
||||||
Key8 | Numpad8 => text_state.push_str("8"),
|
Key8 | Numpad8 => text_state.push('8'),
|
||||||
Key9 | Numpad9 => text_state.push_str("9"),
|
Key9 | Numpad9 => text_state.push('9'),
|
||||||
Key0 | Numpad0 => text_state.push_str("0"),
|
Key0 | Numpad0 => text_state.push('0'),
|
||||||
A => text_state.push_str("a"),
|
A => text_state.push('a'),
|
||||||
B => text_state.push_str("b"),
|
B => text_state.push('b'),
|
||||||
C => text_state.push_str("c"),
|
C => text_state.push('c'),
|
||||||
D => text_state.push_str("d"),
|
D => text_state.push('d'),
|
||||||
E => text_state.push_str("e"),
|
E => text_state.push('e'),
|
||||||
F => text_state.push_str("f"),
|
F => text_state.push('f'),
|
||||||
G => text_state.push_str("g"),
|
G => text_state.push('g'),
|
||||||
H => text_state.push_str("h"),
|
H => text_state.push('h'),
|
||||||
I => text_state.push_str("i"),
|
I => text_state.push('i'),
|
||||||
J => text_state.push_str("j"),
|
J => text_state.push('j'),
|
||||||
K => text_state.push_str("k"),
|
K => text_state.push('k'),
|
||||||
L => text_state.push_str("l"),
|
L => text_state.push('l'),
|
||||||
M => text_state.push_str("m"),
|
M => text_state.push('m'),
|
||||||
N => text_state.push_str("n"),
|
N => text_state.push('n'),
|
||||||
O => text_state.push_str("o"),
|
O => text_state.push('o'),
|
||||||
P => text_state.push_str("p"),
|
P => text_state.push('p'),
|
||||||
Q => text_state.push_str("q"),
|
Q => text_state.push('q'),
|
||||||
R => text_state.push_str("r"),
|
R => text_state.push('r'),
|
||||||
S => text_state.push_str("s"),
|
S => text_state.push('s'),
|
||||||
T => text_state.push_str("t"),
|
T => text_state.push('t'),
|
||||||
U => text_state.push_str("u"),
|
U => text_state.push('u'),
|
||||||
V => text_state.push_str("v"),
|
V => text_state.push('v'),
|
||||||
W => text_state.push_str("w"),
|
W => text_state.push('w'),
|
||||||
X => text_state.push_str("x"),
|
X => text_state.push('x'),
|
||||||
Y => text_state.push_str("y"),
|
Y => text_state.push('y'),
|
||||||
Z => text_state.push_str("z"),
|
Z => text_state.push('z'),
|
||||||
Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
|
Escape | F1 | F2 | F3 | F4 | F5 | F6 | F7 | F8 | F9 | F10 | F11 | F12 | F13 | F14 | F15
|
||||||
| F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
|
| F16 | F17 | F18 | F19 | F20 | F21 | F22 | F23 | F24 | Snapshot | Scroll | Pause
|
||||||
| Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
|
| Insert | Home | Delete | End | PageDown | PageUp | Left | Up | Right | Down | Compose
|
||||||
|
@ -65,55 +65,55 @@ pub fn handle_text_input(
|
||||||
text_state.pop();
|
text_state.pop();
|
||||||
}
|
}
|
||||||
Return | NumpadEnter => {
|
Return | NumpadEnter => {
|
||||||
text_state.push_str("\n");
|
text_state.push('\n');
|
||||||
}
|
}
|
||||||
Space => {
|
Space => {
|
||||||
text_state.push_str(" ");
|
text_state.push(' ');
|
||||||
}
|
}
|
||||||
Comma | NumpadComma => {
|
Comma | NumpadComma => {
|
||||||
text_state.push_str(",");
|
text_state.push(',');
|
||||||
}
|
}
|
||||||
Add => {
|
Add => {
|
||||||
text_state.push_str("+");
|
text_state.push('+');
|
||||||
}
|
}
|
||||||
Apostrophe => {
|
Apostrophe => {
|
||||||
text_state.push_str("'");
|
text_state.push('\'');
|
||||||
}
|
}
|
||||||
At => {
|
At => {
|
||||||
text_state.push_str("@");
|
text_state.push('@');
|
||||||
}
|
}
|
||||||
Backslash => {
|
Backslash => {
|
||||||
text_state.push_str("\\");
|
text_state.push('\\');
|
||||||
}
|
}
|
||||||
Colon => {
|
Colon => {
|
||||||
text_state.push_str(":");
|
text_state.push(':');
|
||||||
}
|
}
|
||||||
Period | Decimal => {
|
Period | Decimal => {
|
||||||
text_state.push_str(".");
|
text_state.push('.');
|
||||||
}
|
}
|
||||||
Equals | NumpadEquals => {
|
Equals | NumpadEquals => {
|
||||||
text_state.push_str("=");
|
text_state.push('=');
|
||||||
}
|
}
|
||||||
Grave => {
|
Grave => {
|
||||||
text_state.push_str("`");
|
text_state.push('`');
|
||||||
}
|
}
|
||||||
Minus | Subtract => {
|
Minus | Subtract => {
|
||||||
text_state.push_str("-");
|
text_state.push('-');
|
||||||
}
|
}
|
||||||
Multiply => {
|
Multiply => {
|
||||||
text_state.push_str("*");
|
text_state.push('*');
|
||||||
}
|
}
|
||||||
Semicolon => {
|
Semicolon => {
|
||||||
text_state.push_str(";");
|
text_state.push(';');
|
||||||
}
|
}
|
||||||
Slash | Divide => {
|
Slash | Divide => {
|
||||||
text_state.push_str("/");
|
text_state.push('/');
|
||||||
}
|
}
|
||||||
Underline => {
|
Underline => {
|
||||||
text_state.push_str("_");
|
text_state.push('_');
|
||||||
}
|
}
|
||||||
Yen => {
|
Yen => {
|
||||||
text_state.push_str("¥");
|
text_state.push('¥');
|
||||||
}
|
}
|
||||||
Copy => {
|
Copy => {
|
||||||
todo!("copy");
|
todo!("copy");
|
||||||
|
|
4
examples/.gitignore
vendored
4
examples/.gitignore
vendored
|
@ -1,3 +1,3 @@
|
||||||
app
|
app
|
||||||
*.o
|
host.o
|
||||||
*.a
|
c_host.o
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
use std::ffi::CStr;
|
|
||||||
use std::os::raw::c_char;
|
|
||||||
|
|
||||||
#[link(name = "roc_app", kind = "static")]
|
|
||||||
extern "C" {
|
|
||||||
#[link_name = "main#1"]
|
|
||||||
fn str_from_roc() -> *const c_char;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let c_str = unsafe { CStr::from_ptr(str_from_roc()) };
|
|
||||||
|
|
||||||
println!("Roc says: {}", c_str.to_str().unwrap());
|
|
||||||
}
|
|
23
examples/hello-world/platform/Cargo.lock
generated
Normal file
23
examples/hello-world/platform/Cargo.lock
generated
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "host"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"roc_std 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roc_std"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
examples/hello-world/platform/Cargo.toml
Normal file
13
examples/hello-world/platform/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "host"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
roc_std = { path = "../../../roc_std" }
|
||||||
|
|
||||||
|
[workspace]
|
8
examples/hello-world/platform/README.md
Normal file
8
examples/hello-world/platform/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Rebuilding the host from source
|
||||||
|
|
||||||
|
Run `build.sh` to manually rebuild this platform's host.
|
||||||
|
|
||||||
|
Note that the compiler currently has its own logic for rebuilding these hosts
|
||||||
|
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
|
||||||
|
hosts will be precompiled by platform authors and distributed in packages,
|
||||||
|
at which point only package authors will need to think about rebuilding hosts.
|
12
examples/hello-world/platform/build.sh
Executable file
12
examples/hello-world/platform/build.sh
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# compile c_host.o and rust_host.o
|
||||||
|
clang -c host.c -o c_host.o
|
||||||
|
rustc host.rs -o rust_host.o
|
||||||
|
|
||||||
|
# link them together into host.o
|
||||||
|
ld -r c_host.o rust_host.o -o host.o
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
rm -f c_host.o
|
||||||
|
rm -f rust_host.o
|
7
examples/hello-world/platform/host.c
Normal file
7
examples/hello-world/platform/host.c
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
extern int rust_main();
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return rust_main();
|
||||||
|
}
|
18
examples/hello-world/platform/src/lib.rs
Normal file
18
examples/hello-world/platform/src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
use roc_std::RocStr;
|
||||||
|
use std::str;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#[link_name = "main_1"]
|
||||||
|
fn main() -> RocStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn rust_main() -> isize {
|
||||||
|
println!(
|
||||||
|
"Roc says: {}",
|
||||||
|
str::from_utf8(unsafe { main().as_slice() }).unwrap()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Exit code
|
||||||
|
0
|
||||||
|
}
|
49
examples/multi-module/Quicksort.roc
Normal file
49
examples/multi-module/Quicksort.roc
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
app Quicksort
|
||||||
|
provides [ quicksort ]
|
||||||
|
imports [ Utils.{swap} ]
|
||||||
|
|
||||||
|
|
||||||
|
quicksort : List Int -> List Int
|
||||||
|
quicksort = \originalList ->
|
||||||
|
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||||
|
quicksortHelp = \list, low, high ->
|
||||||
|
if low < high then
|
||||||
|
when partition low high list is
|
||||||
|
Pair partitionIndex partitioned ->
|
||||||
|
partitioned
|
||||||
|
|> quicksortHelp low (partitionIndex - 1)
|
||||||
|
|> quicksortHelp (partitionIndex + 1) high
|
||||||
|
else
|
||||||
|
list
|
||||||
|
|
||||||
|
partition : Int, Int, List (Num a) -> [ Pair Int (List (Num a)) ]
|
||||||
|
partition = \low, high, initialList ->
|
||||||
|
when List.get initialList high is
|
||||||
|
Ok pivot ->
|
||||||
|
when partitionHelp (low - 1) low initialList high pivot is
|
||||||
|
Pair newI newList ->
|
||||||
|
Pair (newI + 1) (Utils.swap (newI + 1) high newList)
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
Pair (low - 1) initialList
|
||||||
|
|
||||||
|
|
||||||
|
partitionHelp : Int, Int, List (Num a), Int, (Num a) -> [ Pair Int (List (Num a)) ]
|
||||||
|
partitionHelp = \i, j, list, high, pivot ->
|
||||||
|
if j < high then
|
||||||
|
when List.get list j is
|
||||||
|
Ok value ->
|
||||||
|
if value <= pivot then
|
||||||
|
partitionHelp (i + 1) (j + 1) (Utils.swap (i + 1) j list) high pivot
|
||||||
|
else
|
||||||
|
partitionHelp i (j + 1) list high pivot
|
||||||
|
|
||||||
|
Err _ ->
|
||||||
|
Pair i list
|
||||||
|
else
|
||||||
|
Pair i list
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
n = List.len originalList
|
||||||
|
quicksortHelp originalList 0 (n - 1)
|
12
examples/multi-module/Utils.roc
Normal file
12
examples/multi-module/Utils.roc
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
interface Utils exposes [ swap ] imports []
|
||||||
|
|
||||||
|
swap : Int, Int, List a -> List a
|
||||||
|
swap = \i, j, list ->
|
||||||
|
when Pair (List.get list i) (List.get list j) is
|
||||||
|
Pair (Ok atI) (Ok atJ) ->
|
||||||
|
list
|
||||||
|
|> List.set i atJ
|
||||||
|
|> List.set j atI
|
||||||
|
|
||||||
|
_ ->
|
||||||
|
[]
|
23
examples/multi-module/platform/Cargo.lock
generated
Normal file
23
examples/multi-module/platform/Cargo.lock
generated
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "host"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"roc_std 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roc_std"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
examples/multi-module/platform/Cargo.toml
Normal file
13
examples/multi-module/platform/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "host"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
roc_std = { path = "../../../roc_std" }
|
||||||
|
|
||||||
|
[workspace]
|
8
examples/multi-module/platform/README.md
Normal file
8
examples/multi-module/platform/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Rebuilding the host from source
|
||||||
|
|
||||||
|
Run `build.sh` to manually rebuild this platform's host.
|
||||||
|
|
||||||
|
Note that the compiler currently has its own logic for rebuilding these hosts
|
||||||
|
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
|
||||||
|
hosts will be precompiled by platform authors and distributed in packages,
|
||||||
|
at which point only package authors will need to think about rebuilding hosts.
|
12
examples/multi-module/platform/build.sh
Executable file
12
examples/multi-module/platform/build.sh
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# compile c_host.o and rust_host.o
|
||||||
|
clang -c host.c -o c_host.o
|
||||||
|
rustc host.rs -o rust_host.o
|
||||||
|
|
||||||
|
# link them together into host.o
|
||||||
|
ld -r c_host.o rust_host.o -o host.o
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
rm -f c_host.o
|
||||||
|
rm -f rust_host.o
|
7
examples/multi-module/platform/host.c
Normal file
7
examples/multi-module/platform/host.c
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
extern int rust_main();
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return rust_main();
|
||||||
|
}
|
40
examples/multi-module/platform/src/lib.rs
Normal file
40
examples/multi-module/platform/src/lib.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use roc_std::RocList;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#[link_name = "quicksort_1"]
|
||||||
|
fn quicksort(list: RocList<i64>) -> RocList<i64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUM_NUMS: usize = 10_000;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn rust_main() -> isize {
|
||||||
|
let nums: RocList<i64> = {
|
||||||
|
let mut nums = Vec::with_capacity(NUM_NUMS);
|
||||||
|
|
||||||
|
for index in 0..nums.capacity() {
|
||||||
|
let num = index as i64 % 123;
|
||||||
|
|
||||||
|
nums.push(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
RocList::from_slice(&nums)
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Running Roc quicksort on {} numbers...", nums.len());
|
||||||
|
let start_time = SystemTime::now();
|
||||||
|
let answer = unsafe { quicksort(nums) };
|
||||||
|
let end_time = SystemTime::now();
|
||||||
|
let duration = end_time.duration_since(start_time).unwrap();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
|
||||||
|
duration.as_secs_f64() * 1000.0,
|
||||||
|
// truncate the answer, so stdout is not swamped
|
||||||
|
&answer.as_slice()[0..20]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Exit code
|
||||||
|
0
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
app Quicksort provides [ quicksort ] imports []
|
app Quicksort provides [ quicksort ] imports []
|
||||||
|
|
||||||
quicksort = \originalList ->
|
quicksort = \originalList ->
|
||||||
|
|
||||||
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
quicksortHelp : List (Num a), Int, Int -> List (Num a)
|
||||||
quicksortHelp = \list, low, high ->
|
quicksortHelp = \list, low, high ->
|
||||||
if low < high then
|
if low < high then
|
||||||
|
|
23
examples/quicksort/platform/Cargo.lock
generated
Normal file
23
examples/quicksort/platform/Cargo.lock
generated
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
[[package]]
|
||||||
|
name = "host"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"roc_std 0.1.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.79"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roc_std"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
|
[metadata]
|
||||||
|
"checksum libc 0.2.79 (registry+https://github.com/rust-lang/crates.io-index)" = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743"
|
13
examples/quicksort/platform/Cargo.toml
Normal file
13
examples/quicksort/platform/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "host"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
roc_std = { path = "../../../roc_std" }
|
||||||
|
|
||||||
|
[workspace]
|
8
examples/quicksort/platform/README.md
Normal file
8
examples/quicksort/platform/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Rebuilding the host from source
|
||||||
|
|
||||||
|
Run `build.sh` to manually rebuild this platform's host.
|
||||||
|
|
||||||
|
Note that the compiler currently has its own logic for rebuilding these hosts
|
||||||
|
(in `link.rs`). It's hardcoded for now, but the long-term goal is that
|
||||||
|
hosts will be precompiled by platform authors and distributed in packages,
|
||||||
|
at which point only package authors will need to think about rebuilding hosts.
|
12
examples/quicksort/platform/build.sh
Executable file
12
examples/quicksort/platform/build.sh
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# compile c_host.o and rust_host.o
|
||||||
|
clang -c host.c -o c_host.o
|
||||||
|
rustc host.rs -o rust_host.o
|
||||||
|
|
||||||
|
# link them together into host.o
|
||||||
|
ld -r c_host.o rust_host.o -o host.o
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
rm -f c_host.o
|
||||||
|
rm -f rust_host.o
|
7
examples/quicksort/platform/host.c
Normal file
7
examples/quicksort/platform/host.c
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
extern int rust_main();
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return rust_main();
|
||||||
|
}
|
40
examples/quicksort/platform/src/lib.rs
Normal file
40
examples/quicksort/platform/src/lib.rs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
use roc_std::RocList;
|
||||||
|
use std::time::SystemTime;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#[link_name = "quicksort_1"]
|
||||||
|
fn quicksort(list: RocList<i64>) -> RocList<i64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NUM_NUMS: usize = 10_000;
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub fn rust_main() -> isize {
|
||||||
|
let nums: RocList<i64> = {
|
||||||
|
let mut nums = Vec::with_capacity(NUM_NUMS);
|
||||||
|
|
||||||
|
for index in 0..nums.capacity() {
|
||||||
|
let num = index as i64 % 123;
|
||||||
|
|
||||||
|
nums.push(num);
|
||||||
|
}
|
||||||
|
|
||||||
|
RocList::from_slice(&nums)
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Running Roc quicksort on {} numbers...", nums.len());
|
||||||
|
let start_time = SystemTime::now();
|
||||||
|
let answer = unsafe { quicksort(nums) };
|
||||||
|
let end_time = SystemTime::now();
|
||||||
|
let duration = end_time.duration_since(start_time).unwrap();
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
|
||||||
|
duration.as_secs_f64() * 1000.0,
|
||||||
|
// truncate the answer, so stdout is not swamped
|
||||||
|
&answer.as_slice()[0..20]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Exit code
|
||||||
|
0
|
||||||
|
}
|
|
@ -1,47 +0,0 @@
|
||||||
use std::time::SystemTime;
|
|
||||||
|
|
||||||
#[link(name = "roc_app", kind = "static")]
|
|
||||||
extern "C" {
|
|
||||||
#[allow(improper_ctypes)]
|
|
||||||
#[link_name = "quicksort#1"]
|
|
||||||
fn quicksort(list: &[i64]) -> Box<[i64]>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const NUM_NUMS: usize = 1_000_000;
|
|
||||||
|
|
||||||
pub fn main() {
|
|
||||||
let nums = {
|
|
||||||
let mut nums = Vec::with_capacity(NUM_NUMS + 1);
|
|
||||||
|
|
||||||
// give this list refcount 1
|
|
||||||
nums.push((std::usize::MAX - 1) as i64);
|
|
||||||
|
|
||||||
for index in 1..nums.capacity() {
|
|
||||||
let num = index as i64 % 12345;
|
|
||||||
|
|
||||||
nums.push(num);
|
|
||||||
}
|
|
||||||
|
|
||||||
nums
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Running Roc shared quicksort");
|
|
||||||
let start_time = SystemTime::now();
|
|
||||||
let answer = unsafe { quicksort(&nums[1..]) };
|
|
||||||
let end_time = SystemTime::now();
|
|
||||||
let duration = end_time.duration_since(start_time).unwrap();
|
|
||||||
|
|
||||||
println!(
|
|
||||||
"Roc quicksort took {:.4} ms to compute this answer: {:?}",
|
|
||||||
duration.as_secs_f64() * 1000.0,
|
|
||||||
// truncate the answer, so stdout is not swamped
|
|
||||||
// NOTE index 0 is the refcount!
|
|
||||||
&answer[1..20]
|
|
||||||
);
|
|
||||||
|
|
||||||
// the pointer is to the first _element_ of the list,
|
|
||||||
// but the refcount precedes it. Thus calling free() on
|
|
||||||
// this pointer would segfault/cause badness. Therefore, we
|
|
||||||
// leak it for now
|
|
||||||
Box::leak(answer);
|
|
||||||
}
|
|
49
examples/shared-quicksort/platform/README.md
Normal file
49
examples/shared-quicksort/platform/README.md
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Rebuilding the host from source
|
||||||
|
|
||||||
|
Here are the current steps to rebuild this host. These
|
||||||
|
steps can likely be moved into a `build.rs` script after
|
||||||
|
turning `host.rs` into a `cargo` project, but that hasn't
|
||||||
|
been attempted yet.
|
||||||
|
|
||||||
|
## Compile the Rust and C sources
|
||||||
|
|
||||||
|
Currently this host has both a `host.rs` and a `host.c`.
|
||||||
|
This is only because we haven't figured out a way to convince
|
||||||
|
Rust to emit a `.o` file that doesn't define a `main` entrypoint,
|
||||||
|
but which is capable of being linked into one later.
|
||||||
|
|
||||||
|
As a workaround, we have `host.rs` expose a function called
|
||||||
|
`rust_main` instead of `main`, and all `host.c` does is provide
|
||||||
|
an actual `main` which imports and then calls `rust_main` from
|
||||||
|
the compiled `host.rs`. It's not the most elegant workaround,
|
||||||
|
but [asking on `users.rust-lang.org`](https://users.rust-lang.org/t/error-when-compiling-linking-with-o-files/49635/4)
|
||||||
|
didn't turn up any nicer approaches. Maybe they're out there though!
|
||||||
|
|
||||||
|
To make this workaround happen, we need to compile both `host.rs`
|
||||||
|
and `host.c`. First, `cd` into `platform/host/src/` and then run:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ clang -c host.c -o c_host.o
|
||||||
|
$ rustc host.rs -o rust_host.o
|
||||||
|
```
|
||||||
|
|
||||||
|
Now we should have `c_host.o` and `rust_host.o` in the curent directory.
|
||||||
|
|
||||||
|
## Link together the `.o` files
|
||||||
|
|
||||||
|
Next, combine `c_host.o` and `rust_host.o` into `host.o` using `ld -r` like so:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ld -r c_host.o rust_host.o -o host.o
|
||||||
|
```
|
||||||
|
|
||||||
|
Move `host.o` into the appropriate `platform/` subdirectory
|
||||||
|
based on your architecture and operating system. For example,
|
||||||
|
on macOS, you'd move `host.o` into the `platform/host/x86_64-unknown-darwin10/` directory.
|
||||||
|
|
||||||
|
## All done!
|
||||||
|
|
||||||
|
Congratulations! You now have an updated host.
|
||||||
|
|
||||||
|
It's now fine to delete `c_host.o` and `rust_host.o`,
|
||||||
|
since they were only needed to produce `host.o`.
|
12
examples/shared-quicksort/platform/build.sh
Executable file
12
examples/shared-quicksort/platform/build.sh
Executable file
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# compile c_host.o and rust_host.o
|
||||||
|
clang -c host.c -o c_host.o
|
||||||
|
rustc host.rs -o rust_host.o
|
||||||
|
|
||||||
|
# link them together into host.o
|
||||||
|
ld -r c_host.o rust_host.o -o host.o
|
||||||
|
|
||||||
|
# clean up
|
||||||
|
rm -f c_host.o
|
||||||
|
rm -f rust_host.o
|
7
examples/shared-quicksort/platform/host.c
Normal file
7
examples/shared-quicksort/platform/host.c
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
extern int rust_main();
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
return rust_main();
|
||||||
|
}
|
|
@ -1,20 +1,22 @@
|
||||||
|
#![crate_type = "staticlib"]
|
||||||
|
|
||||||
use std::time::SystemTime;
|
use std::time::SystemTime;
|
||||||
|
|
||||||
#[link(name = "roc_app", kind = "static")]
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#[allow(improper_ctypes)]
|
#[allow(improper_ctypes)]
|
||||||
#[link_name = "quicksort#1"]
|
#[link_name = "quicksort_1"]
|
||||||
fn quicksort(list: Box<[i64]>) -> Box<[i64]>;
|
fn quicksort(list: Box<[i64]>) -> Box<[i64]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NUM_NUMS: usize = 1_000_000;
|
const NUM_NUMS: usize = 10_000;
|
||||||
|
|
||||||
pub fn main() {
|
#[no_mangle]
|
||||||
|
pub fn rust_main() -> isize {
|
||||||
let nums: Box<[i64]> = {
|
let nums: Box<[i64]> = {
|
||||||
let mut nums = Vec::with_capacity(NUM_NUMS);
|
let mut nums = Vec::with_capacity(NUM_NUMS);
|
||||||
|
|
||||||
for index in 0..nums.capacity() {
|
for index in 0..nums.capacity() {
|
||||||
let num = index as i64 % 12345;
|
let num = index as i64 % 123;
|
||||||
|
|
||||||
nums.push(num);
|
nums.push(num);
|
||||||
}
|
}
|
||||||
|
@ -39,4 +41,7 @@ pub fn main() {
|
||||||
// this pointer would segfault/cause badness. Therefore, we
|
// this pointer would segfault/cause badness. Therefore, we
|
||||||
// leak it for now
|
// leak it for now
|
||||||
Box::leak(answer);
|
Box::leak(answer);
|
||||||
|
|
||||||
|
// Exit code
|
||||||
|
0
|
||||||
}
|
}
|
|
@ -71,7 +71,7 @@ impl<T> RocList<T> {
|
||||||
let value = *self.get_storage_ptr();
|
let value = *self.get_storage_ptr();
|
||||||
|
|
||||||
// NOTE doesn't work with elements of 16 or more bytes
|
// NOTE doesn't work with elements of 16 or more bytes
|
||||||
match usize::cmp(&0, &value) {
|
match isize::cmp(&(value as isize), &0) {
|
||||||
Equal => Some(Storage::ReadOnly),
|
Equal => Some(Storage::ReadOnly),
|
||||||
Less => Some(Storage::Refcounted(value)),
|
Less => Some(Storage::Refcounted(value)),
|
||||||
Greater => Some(Storage::Capacity(value)),
|
Greater => Some(Storage::Capacity(value)),
|
||||||
|
@ -214,3 +214,209 @@ impl<T> Drop for RocList<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct RocStr {
|
||||||
|
elements: *mut u8,
|
||||||
|
length: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RocStr {
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
if self.is_small_str() {
|
||||||
|
let bytes = self.length.to_ne_bytes();
|
||||||
|
let last_byte = bytes[bytes.len() - 1];
|
||||||
|
(last_byte ^ 0b1000_0000) as usize
|
||||||
|
} else {
|
||||||
|
self.length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_small_str(&self) -> bool {
|
||||||
|
(self.length as isize) < 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn empty() -> Self {
|
||||||
|
RocStr {
|
||||||
|
// The first bit of length is 1 to specify small str.
|
||||||
|
length: 0,
|
||||||
|
elements: core::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, index: usize) -> Option<&u8> {
|
||||||
|
if index < self.len() {
|
||||||
|
Some(unsafe {
|
||||||
|
let raw = if self.is_small_str() {
|
||||||
|
self.get_small_str_ptr().add(index)
|
||||||
|
} else {
|
||||||
|
self.elements.add(index)
|
||||||
|
};
|
||||||
|
|
||||||
|
&*raw
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn storage(&self) -> Option<Storage> {
|
||||||
|
use core::cmp::Ordering::*;
|
||||||
|
|
||||||
|
if self.is_small_str() || self.length == 0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let value = *self.get_storage_ptr();
|
||||||
|
|
||||||
|
// NOTE doesn't work with elements of 16 or more bytes
|
||||||
|
match isize::cmp(&(value as isize), &0) {
|
||||||
|
Equal => Some(Storage::ReadOnly),
|
||||||
|
Less => Some(Storage::Refcounted(value)),
|
||||||
|
Greater => Some(Storage::Capacity(value)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage_ptr(&self) -> *const usize {
|
||||||
|
let ptr = self.elements as *const usize;
|
||||||
|
|
||||||
|
unsafe { ptr.offset(-1) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_storage_ptr_mut(&mut self) -> *mut usize {
|
||||||
|
self.get_storage_ptr() as *mut usize
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_element_ptr(elements: *const u8) -> *const usize {
|
||||||
|
let elem_alignment = core::mem::align_of::<u8>();
|
||||||
|
let ptr = elements as *const usize;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if elem_alignment <= core::mem::align_of::<usize>() {
|
||||||
|
ptr.offset(1)
|
||||||
|
} else {
|
||||||
|
// If elements have an alignment bigger than usize (e.g. an i128),
|
||||||
|
// we will have necessarily allocated two usize slots worth of
|
||||||
|
// space for the storage value (with the first usize slot being
|
||||||
|
// padding for alignment's sake), and we need to skip past both.
|
||||||
|
ptr.offset(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_small_str_ptr(&self) -> *const u8 {
|
||||||
|
(self as *const RocStr).cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_small_str_ptr_mut(&mut self) -> *mut u8 {
|
||||||
|
(self as *mut RocStr).cast()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_slice_with_capacity(slice: &[u8], capacity: usize) -> RocStr {
|
||||||
|
assert!(slice.len() <= capacity);
|
||||||
|
if capacity < core::mem::size_of::<RocStr>() {
|
||||||
|
let mut rocstr = RocStr::empty();
|
||||||
|
let target_ptr = rocstr.get_small_str_ptr_mut();
|
||||||
|
let source_ptr = slice.as_ptr() as *const u8;
|
||||||
|
for index in 0..(slice.len() as isize) {
|
||||||
|
unsafe {
|
||||||
|
*target_ptr.offset(index) = *source_ptr.offset(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Write length and small string bit to last byte of length.
|
||||||
|
let mut bytes = rocstr.length.to_ne_bytes();
|
||||||
|
bytes[bytes.len() - 1] = capacity as u8 ^ 0b1000_0000;
|
||||||
|
rocstr.length = usize::from_ne_bytes(bytes);
|
||||||
|
rocstr
|
||||||
|
} else {
|
||||||
|
let ptr = slice.as_ptr();
|
||||||
|
let element_bytes = capacity;
|
||||||
|
|
||||||
|
let num_bytes = core::mem::size_of::<usize>() + element_bytes;
|
||||||
|
|
||||||
|
let elements = unsafe {
|
||||||
|
let raw_ptr = libc::malloc(num_bytes);
|
||||||
|
|
||||||
|
// write the capacity
|
||||||
|
let capacity_ptr = raw_ptr as *mut usize;
|
||||||
|
*capacity_ptr = capacity;
|
||||||
|
|
||||||
|
let raw_ptr = Self::get_element_ptr(raw_ptr as *mut u8);
|
||||||
|
|
||||||
|
{
|
||||||
|
// NOTE: using a memcpy here causes weird issues
|
||||||
|
let target_ptr = raw_ptr as *mut u8;
|
||||||
|
let source_ptr = ptr as *const u8;
|
||||||
|
let length = slice.len() as isize;
|
||||||
|
for index in 0..length {
|
||||||
|
*target_ptr.offset(index) = *source_ptr.offset(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
raw_ptr as *mut u8
|
||||||
|
};
|
||||||
|
|
||||||
|
RocStr {
|
||||||
|
length: slice.len(),
|
||||||
|
elements,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_slice(slice: &[u8]) -> RocStr {
|
||||||
|
Self::from_slice_with_capacity(slice, slice.len())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_slice(&self) -> &[u8] {
|
||||||
|
if self.is_small_str() {
|
||||||
|
unsafe { core::slice::from_raw_parts(self.get_small_str_ptr(), self.len()) }
|
||||||
|
} else {
|
||||||
|
unsafe { core::slice::from_raw_parts(self.elements, self.length) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for RocStr {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
// RocStr { is_small_str: false, storage: Refcounted(3), elements: [ 1,2,3,4] }
|
||||||
|
f.debug_struct("RocStr")
|
||||||
|
.field("is_small_str", &self.is_small_str())
|
||||||
|
.field("storage", &self.storage())
|
||||||
|
.field("elements", &self.as_slice())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for RocStr {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.as_slice() == other.as_slice()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for RocStr {}
|
||||||
|
|
||||||
|
impl Drop for RocStr {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !self.is_small_str() {
|
||||||
|
use Storage::*;
|
||||||
|
match self.storage() {
|
||||||
|
None | Some(ReadOnly) => {}
|
||||||
|
Some(Capacity(_)) | Some(Refcounted(REFCOUNT_1)) => unsafe {
|
||||||
|
libc::free(self.get_storage_ptr() as *mut libc::c_void);
|
||||||
|
},
|
||||||
|
Some(Refcounted(rc)) => {
|
||||||
|
let sptr = self.get_storage_ptr_mut();
|
||||||
|
unsafe {
|
||||||
|
*sptr = rc - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue