diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d7842a72a..8f8a9d0246 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,7 +10,10 @@ jobs: - uses: actions/checkout@v1 - name: Install LLVM - run: sudo ./ci/install-llvm.sh 8 + run: sudo ./ci/install-llvm.sh 10 + + - name: Enable LLD + run: sudo ./ci/enable-lld.sh - uses: actions-rs/toolchain@v1 name: Install Rust Toolchain diff --git a/BUILDING_FROM_SOURCE.md b/BUILDING_FROM_SOURCE.md index a13d3b5ecf..f76f266202 100644 --- a/BUILDING_FROM_SOURCE.md +++ b/BUILDING_FROM_SOURCE.md @@ -9,16 +9,22 @@ To see which version of LLVM you need, take a look at `Cargo.toml`, in particula For Ubuntu, I used the `Automatic installation script` at [apt.llvm.org](https://apt.llvm.org) - but there are plenty of alternative options at http://releases.llvm.org/download.html -You may run into an error like this: +## Use LLD for the linker + +Using [`lld` for Rust's linker](https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306) +makes build times a lot faster, and I highly recommend it. + +Create `~/.config/cargo` and add this to it: + ``` - Updating git repository `https://github.com/TheDan64/inkwell` -error: failed to load source for a dependency on `inkwell` - -Caused by: - Unable to update https://github.com/TheDan64/inkwell?branch=llvm8-0#d0f5c1e1 - -Caused by: - revspec 'd0f5c1e198853bc06d8427fbafb7b068032d1d1a' not found; class=Reference (4); code=NotFound (-3) +[build] +# Link with lld, per https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306 +# Use target-cpu=native, per https://deterministic.space/high-performance-rust.html +rustflags = ["-C", "link-arg=-fuse-ld=lld", "-C", "target-cpu=native"] ``` -This seems to be caused by cargo being out of date (even if it's freshly installed), and can be fixed with `cargo update`. +Then install `lld` version 9 (e.g. with `$ sudo apt-get install lld-9`) +and add make sure there's a `ld.lld` executable on your `PATH` which +is symlinked to `lld-9`. + +That's it! Enjoy the faster builds. diff --git a/Cargo.lock b/Cargo.lock index 10f7fcda7e..fdeb2567b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,9 +2,9 @@ # It is not intended for manual editing. [[package]] name = "aho-corasick" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec" +checksum = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada" dependencies = [ "memchr", ] @@ -18,6 +18,23 @@ dependencies = [ "winapi", ] +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "0.1.7" @@ -38,18 +55,30 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bitmaps" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81e039a80914325b37fde728ef7693c212f0ac913d5599607d7b95a9484aae0b" +checksum = "031043d04099746d8db04daf1fa424b2bc8bd69d92b25962dcde24da39ab64a2" dependencies = [ "typenum", ] [[package]] -name = "bumpalo" -version = "3.2.0" +name = "bstr" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" +checksum = "2889e6d50f394968c8bf4240dc3f2a7eb4680844d27308f798229ac9d4725f41" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187" [[package]] name = "byteorder" @@ -63,6 +92,15 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1" +[[package]] +name = "cast" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b9434b9a5aa1450faa3f9cb14ea0e8c53bb5d2b3c1bfd1ab4fc03e9f33fbfb0" +dependencies = [ + "rustc_version", +] + [[package]] name = "cc" version = "1.0.50" @@ -75,6 +113,17 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" +[[package]] +name = "clap" +version = "2.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9" +dependencies = [ + "bitflags", + "textwrap", + "unicode-width", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -85,113 +134,107 @@ dependencies = [ ] [[package]] -name = "cranelift" -version = "0.59.0" +name = "criterion" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "177b8570ee9247af35938db95818634ee0b18dce71e7792bac1e4a9f516064ba" +checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1" dependencies = [ - "cranelift-codegen", - "cranelift-frontend", + "atty", + "cast", + "clap", + "criterion-plot", + "csv", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", ] [[package]] -name = "cranelift-bforest" -version = "0.59.0" +name = "criterion-plot" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45a9c21f8042b9857bda93f6c1910b9f9f24100187a3d3d52f214a34e3dc5818" +checksum = "a01e15e0ea58e8234f96146b1f91fa9d0e4dd7a38da93ff7a75d42c0b9d3a545" dependencies = [ - "cranelift-entity", + "cast", + "itertools", ] [[package]] -name = "cranelift-codegen" -version = "0.59.0" +name = "crossbeam-deque" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7853f77a6e4a33c67a69c40f5e1bb982bd2dc5c4a22e17e67b65bbccf9b33b2e" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" dependencies = [ - "byteorder", - "cranelift-bforest", - "cranelift-codegen-meta", - "cranelift-codegen-shared", - "cranelift-entity", - "gimli", - "log", - "smallvec", - "target-lexicon", - "thiserror", + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", ] [[package]] -name = "cranelift-codegen-meta" -version = "0.59.0" +name = "crossbeam-epoch" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "084cd6d5fb0d1da28acd72c199471bfb09acc703ec8f3bf07b1699584272a3b9" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ - "cranelift-codegen-shared", - "cranelift-entity", + "autocfg 1.0.0", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", ] [[package]] -name = "cranelift-codegen-shared" -version = "0.59.0" +name = "crossbeam-queue" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "701b599783305a58c25027a4d73f2d6b599b2d8ef3f26677275f480b4d51e05d" - -[[package]] -name = "cranelift-entity" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b88e792b28e1ebbc0187b72ba5ba880dad083abe9231a99d19604d10c9e73f38" - -[[package]] -name = "cranelift-frontend" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518344698fa6c976d853319218415fdfb4f1bc6b42d0b2e2df652e55dff1f778" +checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db" dependencies = [ - "cranelift-codegen", - "log", - "smallvec", - "target-lexicon", + "cfg-if", + "crossbeam-utils", ] [[package]] -name = "cranelift-module" -version = "0.59.0" +name = "crossbeam-utils" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0308925796ccf4f4facc2e6e782836474126851f7d3db74ee6138d644eea5da5" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "cranelift-codegen", - "cranelift-entity", - "log", - "thiserror", + "autocfg 1.0.0", + "cfg-if", + "lazy_static", ] [[package]] -name = "cranelift-native" -version = "0.59.0" +name = "csv" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32daf082da21c0c05d93394ff4842c2ab7c4991b1f3186a1d952f8ac660edd0b" +checksum = "00affe7f6ab566df61b4be3ce8cf16bc2576bca0963ceb0955e45d514bf9a279" dependencies = [ - "cranelift-codegen", - "raw-cpuid", - "target-lexicon", + "bstr", + "csv-core", + "itoa", + "ryu", + "serde", ] [[package]] -name = "cranelift-simplejit" -version = "0.59.0" +name = "csv-core" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accb62f74935a3182f2e32a4a9cf880f429122e10b69edcd81fe25ea09e35cdc" +checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" dependencies = [ - "cranelift-codegen", - "cranelift-module", - "cranelift-native", - "errno", - "libc", - "region", - "target-lexicon", - "winapi", + "memchr", ] [[package]] @@ -200,6 +243,12 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" +[[package]] +name = "distance" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d9d8664cf849d7d0f3114a3a387d2f5e4303176d746d5a951aaddc66dfe9240" + [[package]] name = "either" version = "1.5.3" @@ -216,27 +265,6 @@ dependencies = [ "regex", ] -[[package]] -name = "errno" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2a071601ed01b988f896ab14b95e67335d1eeb50190932a1320f7fe3cadc84e" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" -dependencies = [ - "gcc", - "libc", -] - [[package]] name = "fnv" version = "1.0.6" @@ -250,26 +278,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] -name = "gcc" -version = "0.3.55" +name = "getrandom" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" - -[[package]] -name = "gimli" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ - "byteorder", - "indexmap", + "cfg-if", + "libc", + "wasi", ] [[package]] name = "hermit-abi" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8" +checksum = "725cf19794cf90aa94e65050cb4191ff5d8fa87a498383774c47b332e3af952e" dependencies = [ "libc", ] @@ -302,20 +325,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "indexmap" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292" -dependencies = [ - "autocfg 1.0.0", -] - [[package]] name = "indoc" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9553c1e16c114b8b77ebeb329e5f2876eed62a8d51178c8bc6bff0d65f98f8" +checksum = "79255cf29f5711995ddf9ec261b4057b1deb34e66c90656c201e41376872c544" dependencies = [ "indoc-impl", "proc-macro-hack", @@ -323,21 +337,21 @@ dependencies = [ [[package]] name = "indoc-impl" -version = "0.3.4" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b714fc08d0961716390977cdff1536234415ac37b509e34e5a983def8340fb75" +checksum = "54554010aa3d17754e484005ea0022f1c93839aabc627c2c55f3d7b47206134c" dependencies = [ "proc-macro-hack", - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", - "syn 1.0.16", + "syn 1.0.17", "unindent", ] [[package]] name = "inkwell" version = "0.1.0" -source = "git+https://github.com/TheDan64/inkwell?branch=llvm8-0#647a7cbfc2a6be3b41b8de3b1c63ec1fe4e79267" +source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release1#005de45445d6ddae80f09ed012e6c3dfe674201a" dependencies = [ "either", "inkwell_internals", @@ -351,7 +365,7 @@ dependencies = [ [[package]] name = "inkwell_internals" version = "0.1.0" -source = "git+https://github.com/TheDan64/inkwell?branch=llvm8-0#647a7cbfc2a6be3b41b8de3b1c63ec1fe4e79267" +source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release1#005de45445d6ddae80f09ed012e6c3dfe674201a" dependencies = [ "proc-macro2 0.4.30", "quote 0.6.13", @@ -364,6 +378,30 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb6ee2a7da03bfc3b66ca47c92c2e392fcc053ea040a85561749b026f7aad09a" +[[package]] +name = "itertools" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" + +[[package]] +name = "js-sys" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -372,15 +410,15 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.67" +version = "0.2.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018" +checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" [[package]] name = "llvm-sys" -version = "80.1.2" +version = "100.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf2969773884a5701f0c255e2a14d48d4522a66db898ec1088cb21879a228377" +checksum = "df29b4f2f4e8a5f7871ccad859aa17a171ba251da96bfd55224d53f48a96d2a2" dependencies = [ "cc", "lazy_static", @@ -407,27 +445,42 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "mach" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" -dependencies = [ - "libc", -] - [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + [[package]] name = "memchr" version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" +[[package]] +name = "memoffset" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4fc2c02a7e374099d4ee95a193111f72d2110197fe200272371758f6c3643d8" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "num-traits" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096" +dependencies = [ + "autocfg 1.0.0", +] + [[package]] name = "num_cpus" version = "1.12.0" @@ -444,6 +497,12 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" +[[package]] +name = "oorandom" +version = "11.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebcec7c9c2a95cacc7cd0ecb89d8a8454eca13906f6deb55258ffff0adeb9405" + [[package]] name = "parking_lot" version = "0.10.0" @@ -474,6 +533,24 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae" +[[package]] +name = "plotters" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e3bb8da247d27ae212529352020f3e5ee16e83c0c258061d27b08ab92675eeb" +dependencies = [ + "js-sys", + "num-traits", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b" + [[package]] name = "pretty_assertions" version = "0.5.1" @@ -486,14 +563,9 @@ dependencies = [ [[package]] name = "proc-macro-hack" -version = "0.5.11" +version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5" -dependencies = [ - "proc-macro2 1.0.9", - "quote 1.0.3", - "syn 1.0.16", -] +checksum = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63" [[package]] name = "proc-macro2" @@ -506,9 +578,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.9" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435" +checksum = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3" dependencies = [ "unicode-xid 0.2.0", ] @@ -521,7 +593,7 @@ checksum = "9c35d9c36a562f37eca96e79f66d5fd56eefbc22560dacc4a864cabd2d277456" dependencies = [ "env_logger", "log", - "rand", + "rand 0.6.5", "rand_core 0.4.2", ] @@ -551,7 +623,7 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", ] [[package]] @@ -562,9 +634,9 @@ checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" dependencies = [ "autocfg 0.1.7", "libc", - "rand_chacha", + "rand_chacha 0.1.1", "rand_core 0.4.2", - "rand_hc", + "rand_hc 0.1.0", "rand_isaac", "rand_jitter", "rand_os", @@ -573,6 +645,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc 0.2.0", +] + [[package]] name = "rand_chacha" version = "0.1.1" @@ -583,6 +668,16 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + [[package]] name = "rand_core" version = "0.3.1" @@ -603,6 +698,9 @@ name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] [[package]] name = "rand_hc" @@ -613,6 +711,15 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + [[package]] name = "rand_isaac" version = "0.1.1" @@ -676,14 +783,27 @@ dependencies = [ ] [[package]] -name = "raw-cpuid" -version = "7.0.3" +name = "rayon" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf" +checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" dependencies = [ - "bitflags", - "cc", - "rustc_version", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "lazy_static", + "num_cpus", ] [[package]] @@ -703,9 +823,9 @@ checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" [[package]] name = "regex" -version = "1.3.4" +version = "1.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322cf97724bea3ee221b78fe25ac9c46114ebb51747ad5babd51a2fc6a8235a8" +checksum = "7f6946991529684867e47d86474e3a6d0c0ab9b82d5821e314b1ede31fa3a4b3" dependencies = [ "aho-corasick", "memchr", @@ -714,23 +834,56 @@ dependencies = [ ] [[package]] -name = "regex-syntax" -version = "0.6.16" +name = "regex-automata" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1132f845907680735a84409c3bebc64d1364a5683ffbce899550cd09d5eaefc1" +checksum = "ae1ded71d66a4a97f5e961fd0cb25a5f366a42a41570d16a763a69c092c26ae4" +dependencies = [ + "byteorder", +] [[package]] -name = "region" -version = "2.1.2" +name = "regex-syntax" +version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "448e868c6e4cfddfa49b6a72c95906c04e8547465e9536575b95c70a4044f856" +checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" + +[[package]] +name = "remove_dir_all" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e" dependencies = [ - "bitflags", - "libc", - "mach", "winapi", ] +[[package]] +name = "roc-cli" +version = "0.1.0" +dependencies = [ + "bumpalo", + "im", + "im-rc", + "inkwell", + "inlinable_string", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_constrain", + "roc_gen", + "roc_module", + "roc_mono", + "roc_parse", + "roc_problem", + "roc_region", + "roc_reporting", + "roc_solve", + "roc_types", + "roc_unify", + "roc_uniq", + "target-lexicon", +] + [[package]] name = "roc_builtins" version = "0.1.0" @@ -747,6 +900,10 @@ dependencies = [ "roc_types", ] +[[package]] +name = "roc_builtins_bitcode" +version = "0.1.0" + [[package]] name = "roc_can" version = "0.1.0" @@ -824,15 +981,12 @@ name = "roc_gen" version = "0.1.0" dependencies = [ "bumpalo", - "cranelift", - "cranelift-codegen", - "cranelift-module", - "cranelift-simplejit", "im", "im-rc", "indoc", "inkwell", "inlinable_string", + "libc", "maplit", "pretty_assertions", "quickcheck", @@ -955,16 +1109,29 @@ version = "0.1.0" name = "roc_reporting" version = "0.1.0" dependencies = [ + "bumpalo", + "distance", + "im", + "im-rc", "indoc", + "inlinable_string", "maplit", "pretty_assertions", "quickcheck", "quickcheck_macros", + "roc_builtins", + "roc_can", "roc_collections", + "roc_constrain", + "roc_load", "roc_module", + "roc_mono", + "roc_parse", "roc_problem", "roc_region", + "roc_solve", "roc_types", + "ven_pretty", ] [[package]] @@ -1055,6 +1222,21 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + [[package]] name = "scopeguard" version = "1.1.0" @@ -1077,10 +1259,38 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] -name = "sized-chunks" -version = "0.5.1" +name = "serde" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f59f81ec9833a580d2448e958d16bd872637798f3ab300b693c48f136fb76ff" +checksum = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399" + +[[package]] +name = "serde_derive" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + +[[package]] +name = "serde_json" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da07b57ee2623368351e9a0488bb0b261322a15a6e0ae53e243cbdc0f4208da9" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sized-chunks" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59044ea371ad781ff976f7b06480b9f0180e834eda94114f2afb4afc12b7718" dependencies = [ "bitmaps", "typenum", @@ -1105,11 +1315,11 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "123bd9499cfb380418d509322d7a6d52e5315f064fe4b3ad18a53d6b92c07859" +checksum = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03" dependencies = [ - "proc-macro2 1.0.9", + "proc-macro2 1.0.10", "quote 1.0.3", "unicode-xid 0.2.0", ] @@ -1121,23 +1331,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" [[package]] -name = "thiserror" -version = "1.0.11" +name = "tempfile" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee14bf8e6767ab4c687c9e8bc003879e042a96fd67a3ba5934eadb6536bef4db" +checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" dependencies = [ - "thiserror-impl", + "cfg-if", + "libc", + "rand 0.7.3", + "redox_syscall", + "remove_dir_all", + "winapi", ] [[package]] -name = "thiserror-impl" -version = "1.0.11" +name = "termcolor" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7b51e1fbc44b5a0840be594fbc0f960be09050f2617e61e6aa43bef97cd3ef4" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" dependencies = [ - "proc-macro2 1.0.9", - "quote 1.0.3", - "syn 1.0.16", + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", ] [[package]] @@ -1150,10 +1372,20 @@ dependencies = [ ] [[package]] -name = "tokio" -version = "0.2.13" +name = "tinytemplate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa5e81d6bc4e67fe889d5783bd2a128ab2e0cfa487e0be16b6a8d177b101616" +checksum = "57a3c6667d3e65eb1bc3aed6fd14011c6cbc3a0665218ab7f5daf040b9ec371a" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "tokio" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee5a0dd887e37d37390c13ff8ac830f992307fe30a1fff0ab8427af67211ba28" dependencies = [ "bytes", "fnv", @@ -1162,12 +1394,24 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "typed-arena" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae" + [[package]] name = "typenum" version = "1.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" +[[package]] +name = "unicode-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" + [[package]] name = "unicode-xid" version = "0.1.0" @@ -1200,12 +1444,105 @@ dependencies = [ "roc_collections", ] +[[package]] +name = "ven_pretty" +version = "0.9.1-alpha.0" +dependencies = [ + "arrayvec", + "criterion", + "difference", + "tempfile", + "termcolor", + "typed-arena", +] + [[package]] name = "version_check" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" +[[package]] +name = "walkdir" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasm-bindgen" +version = "0.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4" +dependencies = [ + "quote 1.0.3", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" + +[[package]] +name = "web-sys" +version = "0.3.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.8" @@ -1222,6 +1559,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 9aa94be014..f180dadbf0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "compiler/types", "compiler/uniq", "compiler/builtins", + "compiler/builtins/bitcode", "compiler/constrain", "compiler/unify", "compiler/solve", @@ -19,5 +20,12 @@ members = [ "compiler/load", "compiler/gen", "vendor/ena", - "vendor/pathfinding" + "vendor/pathfinding", + "vendor/pretty", + "cli" ] + +# Optimizations based on https://deterministic.space/high-performance-rust.html +[profile.release] +lto = "fat" +codegen-units = 1 diff --git a/ci/enable-lld.sh b/ci/enable-lld.sh new file mode 100755 index 0000000000..a70301f8dc --- /dev/null +++ b/ci/enable-lld.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +mkdir -p $HOME/.cargo +echo -e "[build]\nrustflags = [\"-C\", \"link-arg=-fuse-ld=lld\", \"-C\", \"target-cpu=native\"]" > $HOME/.cargo/config + +ln -s /usr/bin/lld-8 /usr/local/bin/ld.lld diff --git a/ci/install-llvm.sh b/ci/install-llvm.sh index 634f9e340c..0c327914ee 100755 --- a/ci/install-llvm.sh +++ b/ci/install-llvm.sh @@ -11,7 +11,7 @@ set -eux # read optional command line argument -LLVM_VERSION=9 +LLVM_VERSION=10 if [ "$#" -eq 1 ]; then LLVM_VERSION=$1 fi @@ -26,9 +26,9 @@ if [[ $EUID -ne 0 ]]; then fi declare -A LLVM_VERSION_PATTERNS -LLVM_VERSION_PATTERNS[8]="-8" LLVM_VERSION_PATTERNS[9]="-9" -LLVM_VERSION_PATTERNS[10]="" +LLVM_VERSION_PATTERNS[10]="-10" +LLVM_VERSION_PATTERNS[11]="" if [ ! ${LLVM_VERSION_PATTERNS[$LLVM_VERSION]+_} ]; then echo "This script does not support LLVM version $LLVM_VERSION" @@ -47,6 +47,8 @@ case "$DIST_VERSION" in Ubuntu_18.04 ) REPO_NAME="deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic$LLVM_VERSION_STRING main" ;; Ubuntu_18.10 ) REPO_NAME="deb http://apt.llvm.org/cosmic/ llvm-toolchain-cosmic$LLVM_VERSION_STRING main" ;; Ubuntu_19.04 ) REPO_NAME="deb http://apt.llvm.org/disco/ llvm-toolchain-disco$LLVM_VERSION_STRING main" ;; + Ubuntu_19.10 ) REPO_NAME="deb http://apt.llvm.org/eoan/ llvm-toolchain-eoan$LLVM_VERSION_STRING main" ;; + Ubuntu_20.04 ) REPO_NAME="deb http://apt.llvm.org/focal/ llvm-toolchain-focal$LLVM_VERSION_STRING main" ;; * ) echo "Distribution '$DISTRO' in version '$VERSION' is not supported by this script (${DIST_VERSION})." exit 2 diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 30c7aadbba..7a4e0b8912 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -8,6 +8,51 @@ keywords = ["roc", "gui"] edition = "2018" description = "A CLI for Roc" license = "Apache-2.0" +default-run = "roc" + +[[bin]] +name = "roc" +path = "src/main.rs" +test = false +bench = false + [dependencies] -roc = { path = "../", version = "0.1.0" } +roc_collections = { path = "../compiler/collections" } +roc_can = { path = "../compiler/can" } +roc_parse = { path = "../compiler/parse" } +roc_region = { path = "../compiler/region" } +roc_module = { path = "../compiler/module" } +roc_problem = { path = "../compiler/problem" } +roc_types = { path = "../compiler/types" } +roc_builtins = { path = "../compiler/builtins" } +roc_constrain = { path = "../compiler/constrain" } +roc_uniq = { path = "../compiler/uniq" } +roc_unify = { path = "../compiler/unify" } +roc_solve = { path = "../compiler/solve" } +roc_mono = { path = "../compiler/mono" } +roc_gen = { path = "../compiler/gen", version = "0.1.0" } +roc_reporting = { path = "../compiler/reporting", version = "0.1.0" } +im = "14" # im and im-rc should always have the same version! +im-rc = "14" # im and im-rc should always have the same version! +bumpalo = { version = "3.2", features = ["collections"] } +inlinable_string = "0.1.0" +# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. +# +# The reason for this fork is that the way Inkwell is designed, you have to use +# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that +# breaking changes get pushed directly to that branch, which breaks our build +# without warning. +# +# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch), +# but although that worked locally, it did not work on GitHub Actions. (After a few +# hours of investigation, gave up trying to figure out why.) So this is the workaround: +# having an immutable tag on the rtfeldman/inkwell fork which points to +# a particular "release" of Inkwell. +# +# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest +# commit of TheDan64/inkwell, push a new tag which points to the latest commit, +# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. +# This way, GitHub Actions works and nobody's builds get broken. +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } +target-lexicon = "0.10" diff --git a/cli/src/helpers.rs b/cli/src/helpers.rs new file mode 100644 index 0000000000..37f2cec34c --- /dev/null +++ b/cli/src/helpers.rs @@ -0,0 +1,396 @@ +use 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, ImSet, MutMap, SendMap, SendSet}; +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; +use std::hash::Hash; + +pub fn test_home() -> ModuleId { + ModuleIds::default().get_or_insert(&"Test".into()) +} + +pub fn infer_expr( + subs: Subs, + problems: &mut Vec, + 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_with<'a>(arena: &'a Bump, input: &'a str) -> Result, Fail> { + parse_loc_with(arena, input).map(|loc_expr| loc_expr.value) +} + +pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result>, Fail> { + let state = State::new(&input, 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, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let declared_idents: &ImMap = &ImMap::default(); + + uniq_expr_with(&Bump::new(), expr_str, declared_idents) +} + +pub fn uniq_expr_with( + arena: &Bump, + expr_str: &str, + declared_idents: &ImMap, +) -> ( + Located, + Output, + Vec, + Subs, + Variable, + Constraint, + ModuleId, + Interns, +) { + let home = test_home(); + let CanExprOut { + loc_expr, + output, + problems, + var_store: old_var_store, + var, + interns, + .. + } = can_expr_with(arena, home, expr_str); + + // double check + let var_store = VarStore::new(old_var_store.fresh()); + + let expected2 = Expected::NoExpectation(Type::Variable(var)); + let constraint = roc_constrain::uniq::constrain_declaration( + home, + &var_store, + Region::zero(), + &loc_expr, + declared_idents, + expected2, + ); + + let stdlib = uniq_stdlib(); + + let types = stdlib.types; + let imports: Vec<_> = types + .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, &var_store); + + // load builtin types + let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store); + + constraint.instantiate_aliases(&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, + pub output: Output, + pub problems: Vec, + 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 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, + &var_store, + &mut scope, + Region::zero(), + &loc_expr.value, + ); + + 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 + .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, &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, &var_store); + + constraint.instantiate_aliases(&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, + } +} + +pub fn mut_map_from_pairs(pairs: I) -> MutMap +where + I: IntoIterator, + K: Hash + Eq, +{ + let mut answer = MutMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +pub fn im_map_from_pairs(pairs: I) -> ImMap +where + I: IntoIterator, + K: Hash + Eq + Clone, + V: Clone, +{ + let mut answer = ImMap::default(); + + for (key, value) in pairs { + answer.insert(key, value); + } + + answer +} + +pub fn send_set_from(elems: I) -> SendSet +where + I: IntoIterator, + V: Hash + Eq + Clone, +{ + let mut answer = SendSet::default(); + + for elem in elems { + answer.insert(elem); + } + + answer +} + +// Check constraints +// +// Keep track of the used (in types or expectations) variables, and the declared variables (in +// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates +// and no variables that are used but not declared are allowed. +// +// There is one exception: the initial variable (that stores the type of the whole expression) is +// never declared, but is used. +pub fn assert_correct_variable_usage(constraint: &Constraint) { + // variables declared in constraint (flex_vars or rigid_vars) + // and variables actually used in constraints + let (declared, used) = variable_usage(constraint); + + let used: ImSet = used.into(); + let mut decl: ImSet = declared.rigid_vars.clone().into(); + + for var in declared.flex_vars.clone() { + decl.insert(var); + } + + let diff = used.clone().relative_complement(decl); + + // NOTE: this checks whether we're using variables that are not declared. For recursive type + // definitions, their rigid types are declared twice, which is correct! + if !diff.is_empty() { + println!("VARIABLE USAGE PROBLEM"); + + println!("used: {:?}", &used); + println!("rigids: {:?}", &declared.rigid_vars); + println!("flexs: {:?}", &declared.flex_vars); + + println!("difference: {:?}", &diff); + + panic!("variable usage problem (see stdout for details)"); + } +} + +#[derive(Default)] +pub struct SeenVariables { + pub rigid_vars: Vec, + pub flex_vars: Vec, +} + +pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec) { + let mut declared = SeenVariables::default(); + let mut used = ImSet::default(); + variable_usage_help(con, &mut declared, &mut used); + + used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) }); + + let mut used_vec: Vec = used.into_iter().collect(); + used_vec.sort(); + + declared.rigid_vars.sort(); + declared.flex_vars.sort(); + + (declared, used_vec) +} + +fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet) { + use Constraint::*; + + match con { + True | SaveTheEnvironment => (), + Eq(tipe, expectation, _, _) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Lookup(_, expectation, _) => { + for v in expectation.get_type_ref().variables() { + used.insert(v); + } + } + Pattern(_, _, tipe, pexpectation) => { + for v in tipe.variables() { + used.insert(v); + } + + for v in pexpectation.get_type_ref().variables() { + used.insert(v); + } + } + Let(letcon) => { + declared.rigid_vars.extend(letcon.rigid_vars.clone()); + declared.flex_vars.extend(letcon.flex_vars.clone()); + + variable_usage_help(&letcon.defs_constraint, declared, used); + variable_usage_help(&letcon.ret_constraint, declared, used); + } + And(constraints) => { + for sub in constraints { + variable_usage_help(sub, declared, used); + } + } + } +} diff --git a/cli/src/main.rs b/cli/src/main.rs index 1046a9c40a..c680b7ba02 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,27 +1,97 @@ -extern crate roc; +extern crate roc_gen; +extern crate roc_reporting; -use roc::eval::Evaluated::*; -use roc::eval::{call, eval, Evaluated}; -use roc::expr::Expr; -use roc::parse; -use roc::region::{Located, Region}; +use crate::helpers::{infer_expr, uniq_expr_with}; +use bumpalo::Bump; +use inkwell::context::Context; +use inkwell::module::Linkage; +use inkwell::passes::PassManager; +use inkwell::types::BasicType; +use inkwell::OptimizationLevel; +use roc_collections::all::ImMap; +use roc_gen::llvm::build::{ + build_proc, build_proc_header, get_call_conventions, module_from_builtins, +}; +use roc_gen::llvm::convert::basic_type_from_layout; +use roc_mono::expr::{Expr, Procs}; +use roc_mono::layout::Layout; +use std::time::SystemTime; + +use inkwell::targets::{ + CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, +}; use std::fs::File; use std::io; use std::io::prelude::*; +use std::path::{Path, PathBuf}; +use std::process::Command; +use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; -fn main() -> std::io::Result<()> { - let argv = std::env::args().into_iter().collect::>(); +pub mod helpers; + +fn main() -> io::Result<()> { + let now = SystemTime::now(); + let argv = std::env::args().collect::>(); match argv.get(1) { Some(filename) => { - let mut file = File::open(filename)?; + let mut path = Path::new(filename).canonicalize().unwrap(); + + if !path.is_absolute() { + path = std::env::current_dir()?.join(path).canonicalize().unwrap(); + } + + // Step 1: build the .o file for the app + let mut file = File::open(path.clone())?; let mut contents = String::new(); file.read_to_string(&mut contents)?; - let expr = parse::parse_string(contents.as_str()).unwrap(); + let dest_filename = path.with_extension("o"); - process_task(eval(expr)) + gen( + Path::new(filename).to_path_buf(), + contents.as_str(), + Triple::host(), + &dest_filename, + ); + + let end_time = now.elapsed().unwrap(); + + println!( + "Finished compilation and code gen in {} ms\n", + end_time.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() + .expect("`ar` failed to run"); + + // Step 3: have rustc compile the host and link in the .a file + Command::new("rustc") + .args(&["-L", ".", "host.rs", "-o", "app"]) + .current_dir(cwd) + .spawn() + .expect("rustc failed to run"); + + // Step 4: Run the compiled app + Command::new(cwd.join("app")).spawn().unwrap_or_else(|err| { + panic!( + "{} failed to run: {:?}", + cwd.join("app").to_str().unwrap(), + err + ) + }); + Ok(()) } None => { println!("Usage: roc FILENAME.roc"); @@ -31,109 +101,241 @@ fn main() -> std::io::Result<()> { } } -fn process_task(evaluated: Evaluated) -> std::io::Result<()> { - match evaluated { - EvalError(region, problem) => { - println!( - "\n\u{001B}[4mruntime error\u{001B}[24m\n\n{} at {}\n", - format!("{}", problem), - format!("line {}, column {}", region.start_line, region.start_col) - ); +fn gen(filename: PathBuf, src: &str, target: Triple, dest_filename: &Path) { + use roc_reporting::report::{can_problem, RocDocAllocator, DEFAULT_PALETTE}; + use roc_reporting::type_error::type_problem; - Ok(()) - } - ApplyVariant(name, Some(mut vals)) => { - match name.as_str() { - "Echo" => { - // Extract the string from the Echo variant. - let string_to_be_displayed = match vals.pop() { - Some(Str(payload)) => payload, - Some(EvalError(region, err)) => { - panic!( - "RUNTIME ERROR in Echo: {} at {}", - format!("{}", err), - format!("line {}, column {}", region.start_line, region.start_col) - ); - } - Some(val) => { - panic!("TYPE MISMATCH in Echo: {}", format!("{}", val)); - } - None => { - panic!("TYPE MISMATCH in Echo: None"); - } - }; + // Build the expr + let arena = Bump::new(); - // Print the string to the console, since that's what Echo does! - println!("{}", string_to_be_displayed); + let (loc_expr, _output, can_problems, subs, var, constraint, home, interns) = + uniq_expr_with(&arena, src, &ImMap::default()); - // Continue with the callback. - let callback = vals.pop().unwrap(); + let mut type_problems = Vec::new(); + let (content, mut subs) = infer_expr(subs, &mut type_problems, &constraint, var); - process_task(call( - Region { - start_line: 0, - start_col: 0, - end_line: 0, - end_col: 0, - }, - callback, - vec![with_zero_loc(Expr::EmptyRecord)], - )) - } - "Read" => { - // Read a line from from stdin, since that's what Read does! - let mut input = String::new(); + let src_lines: Vec<&str> = src.split('\n').collect(); + let palette = DEFAULT_PALETTE; - io::stdin().read_line(&mut input)?; + // Report parsing and canonicalization problems + let alloc = RocDocAllocator::new(&src_lines, home, &interns); - // Continue with the callback. - let callback = vals.pop().unwrap(); + for problem in can_problems.into_iter() { + let report = can_problem(&alloc, filename.clone(), problem); + let mut buf = String::new(); - process_task(call( - Region { - start_line: 0, - start_col: 0, - end_line: 0, - end_col: 0, - }, - callback, - vec![with_zero_loc(Expr::Str(input.trim().to_string()))], - )) - } - "Success" => { - // We finished all our tasks. Great! No need to print anything. - Ok(()) - } - _ => { - // We don't recognize this variant, so display it and exit. - display_val(ApplyVariant(name, Some(vals))); + report.render_color_terminal(&mut buf, &alloc, &palette); - Ok(()) - } - } - } - output => { - // We don't recognize this value, so display it and exit. - display_val(output); + println!("\n{}\n", buf); + } - Ok(()) + for problem in type_problems.into_iter() { + let report = type_problem(&alloc, filename.clone(), problem); + let mut buf = String::new(); + + report.render_color_terminal(&mut buf, &alloc, &palette); + + println!("\n{}\n", buf); + } + + // Generate the binary + + let context = Context::create(); + let module = module_from_builtins(&context, "app"); + let builder = context.create_builder(); + let fpm = PassManager::create(&module); + + roc_gen::llvm::build::add_passes(&fpm); + + fpm.initialize(); + + // Compute main_fn_type before moving subs to Env + let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let layout = Layout::from_content(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { + panic!( + "Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", + err, subs + ) + }); + + let main_fn_type = + basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false); + let main_fn_name = "$Test.main"; + + // 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: arena.alloc(module), + ptr_bytes, + }; + let mut procs = Procs::default(); + let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); + + // Populate Procs and get the low-level Expr from the canonical Expr + let mut mono_problems = std::vec::Vec::new(); + let main_body = Expr::new( + &arena, + &mut subs, + &mut mono_problems, + loc_expr.value, + &mut procs, + home, + &mut ident_ids, + ptr_bytes, + ); + + // Put this module's ident_ids back in the interns, so we can use them in env. + env.interns.all_ident_ids.insert(home, ident_ids); + + let mut headers = Vec::with_capacity(procs.len()); + + // 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, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { + let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); + + headers.push((proc, fn_val, arg_basic_types)); } } -} -fn with_zero_loc(val: T) -> Located { - Located::new( - val, - Region { - start_line: 0, - start_col: 0, + // Build each proc using its header info. + for (proc, fn_val, arg_basic_types) in headers { + // NOTE: This is here to be uncommented in case verification fails. + // (This approach means we don't have to defensively clone name here.) + // + // println!("\n\nBuilding and then verifying function {}\n\n", name); + build_proc(&env, proc, &procs, fn_val, arg_basic_types); - end_line: 0, - end_col: 0, - }, - ) -} + if fn_val.verify(true) { + fpm.run_on(&fn_val); + } else { + // NOTE: If this fails, uncomment the above println to debug. + panic!( + "Non-main function failed LLVM verification. Uncomment the above println to debug!" + ); + } + } -fn display_val(evaluated: Evaluated) { - println!("\n\u{001B}[4mroc out\u{001B}[24m\n\n{}\n", evaluated); + // Add main to the module. + let cc = get_call_conventions(target.default_calling_convention().unwrap()); + let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); + + main_fn.set_call_conventions(cc); + main_fn.set_linkage(Linkage::External); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(basic_block); + + let ret = roc_gen::llvm::build::build_expr( + &env, + &ImMap::default(), + main_fn, + &main_body, + &Procs::default(), + ); + + builder.build_return(Some(&ret)); + + // 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!("Function {} failed LLVM verification.", main_fn_name); + } + + // Verify the module + if let Err(errors) = env.module.verify() { + panic!("😱 LLVM errors when defining module: {:?}", errors); + } + + // Uncomment this to see the module's optimized LLVM instruction output: + // env.module.print_to_stderr(); + + // 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(_) => { + Target::initialize_arm(&InitializationConfig::default()); + + "arm" + } + Architecture::Wasm32 => { + Target::initialize_webassembly(&InitializationConfig::default()); + + "wasm32" + } + _ => panic!( + "TODO gracefully handle unsupported target architecture: {:?}", + target.architecture + ), + }; + + let opt = OptimizationLevel::Default; + let reloc = RelocMode::Default; + let model = CodeModel::Default; + + // 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 + .write_to_file(&env.module, FileType::Object, &dest_filename) + .expect("Writing .o file failed"); + + println!("\nSuccess! 🎉\n\n\t➡ {}\n", dest_filename.display()); } diff --git a/compiler/builtins/bitcode/Cargo.toml b/compiler/builtins/bitcode/Cargo.toml new file mode 100644 index 0000000000..375c4b9627 --- /dev/null +++ b/compiler/builtins/bitcode/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "roc_builtins_bitcode" +version = "0.1.0" +authors = ["Richard Feldman "] +repository = "https://github.com/rtfeldman/roc" +readme = "README.md" +edition = "2018" +description = "Generate LLVM bitcode for Roc builtins" +license = "Apache-2.0" diff --git a/compiler/builtins/bitcode/README.md b/compiler/builtins/bitcode/README.md new file mode 100644 index 0000000000..3b1216a311 --- /dev/null +++ b/compiler/builtins/bitcode/README.md @@ -0,0 +1,56 @@ +# Bitcode for Builtins + +Roc's builtins are implemented in the compiler using LLVM only. +When their implementations are simple enough (e.g. addition), they +can be implemented directly in Inkwell. + +When their implementations are complex enough, it's nicer to +implement them in a higher-level language like Rust, compile the +result to LLVM bitcode, and import that bitcode into the compiler. + +Here is the process for doing that. + +## Building the bitcode + +The source we'll use to generate the bitcode is in `src/lib.rs` in this directory. + +To generate the bitcode, `cd` into `compiler/builtins/bitcode/` and run: + +```bash +$ cargo rustc --release --lib -- --emit=llvm-bc +``` + +Then look in the root `roc` source directory under `target/release/deps/` for a file +with a name like `roc_builtins_bitcode-8da0901c58a73ebf.bc` - except +probably with a different hash before the `.bc`. If there's more than one +`*.bc` file in that directory, delete the whole `deps/` directory and re-run +the `cargo rustc` command above to regenerate it. + +> If you want to take a look at the human-readable LLVM IR rather than the +> bitcode, run this instead and look for a `.ll` file instead of a `.bc` file: +> +> ```bash +> $ cargo rustc --release --lib -- --emit=llvm-ir +> ``` + +## Importing the bitcode + +The bitcode is a bunch of bytes that aren't particularly human-readable. +Since Roc is designed to be distributed as a single binary, these bytes +need to be included in the raw source somewhere. + +The `llvm/src/build.rs` file statically imports these raw bytes +using the [`include_bytes!` macro](https://doc.rust-lang.org/std/macro.include_bytes.html), +so we just need to move the `.bc` file from the previous step to the correct +location. + +The current `.bc` file is located at: + +``` +compiler/gen/src/llvm/builtins.bc +``` + +...so you want to overwrite it with the new `.bc` file in `target/deps/` + +Once that's done, `git status` should show that the `builtins.bc` file +has been changed. Commit that change and you're done! diff --git a/compiler/builtins/bitcode/src/lib.rs b/compiler/builtins/bitcode/src/lib.rs new file mode 100644 index 0000000000..9ab761cc64 --- /dev/null +++ b/compiler/builtins/bitcode/src/lib.rs @@ -0,0 +1,12 @@ +// NOTE: Editing this file on its own does nothing! The procedure for +// incorporating changes here is in this crate' README. + +#![crate_type = "lib"] +#![no_std] + +/// TODO replace this with a normal Inkwell build_cast call - this was just +/// used as a proof of concept for getting bitcode importing working! +#[no_mangle] +pub fn i64_to_f64_(num: i64) -> f64 { + num as f64 +} diff --git a/compiler/builtins/docs/Float.roc b/compiler/builtins/docs/Float.roc index 6aabf81c5b..294550f3c4 100644 --- a/compiler/builtins/docs/Float.roc +++ b/compiler/builtins/docs/Float.roc @@ -48,7 +48,7 @@ interface Float ## See #Float.highest and #Float.lowest for the highest and ## lowest values that can be held in a #Float. ## -## Like #Int, it's possible for #Float operations to overflow and underflow +## Like #Int, it's possible for #Float operations to overflow. ## if they exceed the bounds of #Float.highest and #Float.lowest. When this happens: ## ## * In a development build, you'll get an assertion failure. @@ -151,12 +151,19 @@ div = \numerator, denominator -> ## ## >>> Float.pi ## >>> |> Float.mod 2.0 -#mod : Float, Float -> Result Float DivByZero +mod : Float a, Float a -> Result Float DivByZero -## Return the reciprocal of the #Float. -#recip : Float -> Result Float [ DivByZero ]* -#recip = \float -> -# 1.0 / float +tryMod : Float a, Float a -> Result (Float a) [ DivByZero ]* + +## Return the reciprocal of a #Float - that is, divides `1.0` by the given number. +## +## Crashes if given `0.0`, because division by zero is undefined in mathematics. +## +## For a version that does not crash, use #tryRecip +recip : Float a -> Result (Float a) [ DivByZero ]* + + +tryRecip : Float a -> Result (Float a) [ DivByZero ]* ## Return an approximation of the absolute value of the square root of the #Float. ## @@ -169,32 +176,39 @@ div = \numerator, denominator -> ## >>> Float.sqrt 0.0 ## ## >>> Float.sqrt -4.0 -#sqrt : Float -> Result Float InvalidSqrt +sqrt : Float a -> Result (Float a) [ InvalidSqrt ] ## Constants ## An approximation of e, specifically 2.718281828459045. -#e : Float -e = 2.718281828459045 +e : Float * ## An approximation of pi, specifically 3.141592653589793. -#pi : Float -pi = 3.141592653589793 +pi : Float * + +## Sort ascending - that is, with the lowest first, and the highest last. +## +## List.sort Float.asc [ 3.0, 6.0, 0.0 ] +## +asc : Float a, Float a -> [ Eq, Lt, Gt ] + +## Sort descending - that is, with the highest first, and the lowest last. +## +## List.sort Float.desc [ 3.0, 6.0, 0.0 ] +## +desc : Float a, Float a -> [ Eq, Lt, Gt ] ## Limits ## The highest supported #Float value you can have, which is approximately 1.8 × 10^308. ## ## If you go higher than this, your running Roc code will crash - so be careful not to! -#highest : Float -highest : Num.Num Float.FloatingPoint -highest = 1.0 +highest : Float * ## The lowest supported #Float value you can have, which is approximately -1.8 × 10^308. ## ## If you go lower than this, your running Roc code will crash - so be careful not to! -#lowest : Float -lowest = 1.0 +lowest : Float * ## The highest integer that can be represented as a #Float without # losing precision. ## It is equal to 2^53, which is approximately 9 × 10^15. @@ -206,8 +220,7 @@ lowest = 1.0 ## >>> Float.highestInt + 100 # Increasing may lose precision ## ## >>> Float.highestInt - 100 # Decreasing is fine - but watch out for lowestLosslessInt! -#highestInt : Float -highestInt = 1.0 +highestInt : Float * ## The lowest integer that can be represented as a #Float without losing precision. ## It is equal to -2^53, which is approximately -9 × 10^15. @@ -219,5 +232,4 @@ highestInt = 1.0 ## >>> Float.lowestIntVal - 100 # Decreasing may lose precision ## ## >>> Float.lowestIntVal + 100 # Increasing is fine - but watch out for highestInt! -#lowestInt : Float -lowestInt = 1.0 +lowestInt : Float * diff --git a/compiler/builtins/docs/Int.roc b/compiler/builtins/docs/Int.roc index 58ce030c39..2e0d54b87d 100644 --- a/compiler/builtins/docs/Int.roc +++ b/compiler/builtins/docs/Int.roc @@ -4,9 +4,72 @@ interface Int ## Types -# Integer := Integer +## A fixed-size integer - that is, a number with no fractional component. +## +## Integers come in two flavors: signed and unsigned. Signed integers can be +## negative ("signed" refers to how they can incorporate a minus sign), +## whereas unsigned integers cannot be negative. +## +## Since integers have a fixed size, the size you choose determines both the +## range of numbers it can represent, and also how much memory it takes up. +## +## #U8 is an an example of an integer. It is an unsigned #Int that takes up 8 bits +## (aka 1 byte) in memory. The `U` is for Unsigned and the 8 is for 8 bits. +## Because it has 8 bits to work with, it can store 256 numbers (2^8), +## and because it is unsigned, its lowest value is 0. This means the 256 numbers +## it can store range from 0 to 255. +## +## #I8 is a signed integer that takes up 8 bits. The `I` is for Integer, since +## integers in mathematics are signed by default. Because it has 8 bits just +## like #U8, it can store 256 numbers (still 2^16), but because it is signed, +## the range is different. Its 256 numbers range from -128 to 127. +## +## Here are some other examples: +## +## * #U16 is like #U8, except it takes up 16 bytes in memory. It can store 65,536 numbers (2^16), ranging from 0 to 65,536. +## * #I16 is like #U16, except it is signed. It can still store the same 65,536 numbers (2^16), ranging from -32,768 to 32,767. +## +## This pattern continues up to #U128 and #I128. +## +## ## Performance notes +## +## In general, using smaller numeric sizes means your program will use less memory. +## However, if a mathematical operation results in an answer that is too big +## or too small to fit in the size available for that answer (which is typically +## the same size as the inputs), then you'll get an overflow error. +## +## As such, minimizing memory usage without causing overflows involves choosing +## number sizes based on your knowledge of what numbers you expect your program +## to encounter at runtime. +## +## Minimizing memory usage does not imply maximum runtime speed! +## CPUs are typically fastest at performing integer operations on integers that +## are the same size as that CPU's native machine word size. That means a 64-bit +## CPU is typically fastest at executing instructions on #U64 and #I64 values, +## whereas a 32-bit CPU is typically fastest on #U32 and #I32 values. +## +## Putting these factors together, here are some reasonable guidelines for optimizing performance through integer size choice: +## +## * Start by deciding if this integer should allow negative numbers, and choose signed or unsigned accordingly. +## * Next, think about the range of numbers you expect this number to hold. Choose the smallest size you will never expect to overflow, no matter the inputs your program receives. (Validating inputs for size, and presenting the user with an error if they are too big, can help guard against overflow.) +## * Finally, if a particular operation is too slow at runtime, and you know the native machine word size on which it will be running (most often either 64-bit or 32-bit), try switching to an integer of that size and see if it makes a meaningful difference. (The difference is typically extremely small.) +Int size : Num (@Int size) -## A 64-bit signed integer. All number literals without decimal points are #Int values. +## A signed 8-bit integer, ranging from -128 to 127 +I8 : Int @I8 +U8 : Int @U8 +U16 : Int @U16 +I16 : Int @I16 +U32 : Int @U32 +I32 : Int @I32 +I64 : Int @I64 +U64 : Int @U64 +I128 : Int @I128 +U128 : Int @U128 +ILen : Int @ILen +ULen : Int @ULen + +## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values. ## ## >>> 1 ## @@ -17,22 +80,72 @@ interface Int ## ## >>> 1_000_000 ## -## See #Int.highest and #Int.lowest for the highest and -## lowest values that can be held in an #Int. +## Integers come in two flavors: *signed* and *unsigned*. +## +## * *Unsigned* integers can never be negative. The lowest value they can hold is zero. +## * *Signed* integers can be negative. +## +## Integers also come in different sizes. Choosing a size depends on your performance +## needs and the range of numbers you need to represent. At a high level, the +## general trade-offs are: +## +## * Larger integer sizes can represent a wider range of numbers. If you absolutely need to represent numbers in a certain range, make sure to pick an integer size that can hold them! +## * Smaller integer sizes take up less memory. This savings rarely matters in variables and function arguments, but the sizes of integers that you use in data structures can add up. This can also affect whether those data structures fit in [cache lines](https://en.wikipedia.org/wiki/CPU_cache#Cache_performance), which can be a performance bottleneck. +## * CPUs typically work fastest on their native [word size](https://en.wikipedia.org/wiki/Word_(computer_architecture)). For example, 64-bit CPUs tend to work fastest on 64-bit integers. Especially if your performance profiling shows that you are CPU bound rather than memory bound, consider #ILen or #ULen. +## +## Here are the different fixed size integer types: +## +## | Range | Type | Size | +## |--------------------------------------------------------|-------|----------| +## | ` -128` | #I8 | 1 Byte | +## | ` 127` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U8 | 1 Byte | +## | ` 255` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -32_768` | #I16 | 2 Bytes | +## | ` 32_767` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U16 | 2 Bytes | +## | ` 65_535` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -2_147_483_648` | #I32 | 4 Bytes | +## | ` 2_147_483_647` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U32 | 4 Bytes | +## | ` (over 4 billion) 4_294_967_295` | | | +## |--------------------------------------------------------|-------|----------| +## | ` -9_223_372_036_854_775_808` | #I64 | 8 Bytes | +## | ` 9_223_372_036_854_775_807` | | | +## |--------------------------------------------------------|-------|----------| +## | ` 0` | #U64 | 8 Bytes | +## | ` (over 18 quintillion) 18_446_744_073_709_551_615` | | | +## |--------------------------------------------------------|-------|----------| +## | `-170_141_183_460_469_231_731_687_303_715_884_105_728` | #I128 | 16 Bytes | +## | ` 170_141_183_460_469_231_731_687_303_715_884_105_727` | | | +## |--------------------------------------------------------|-------|----------| +## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes | +## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | +## +## There are also two variable-size integer types: #Iword and #Uword. +## Their sizes are determined by the machine word size for the system you're +## compiling for. For example, on a 64-bit system, #Iword is the same as #I64, +## and #Uword is the same as #U64. ## ## If any operation would result in an #Int that is either too big -## or too small to fit in that range (e.g. running `Int.highest + 1`), +## or too small to fit in that range (e.g. calling `Int.highest32 + 1`), ## then the operation will *overflow* or *underflow*, respectively. +## ## When this happens: ## ## * In a development build, you'll get an assertion failure. -## * In a release build, you'll get [wrapping overflow](https://en.wikipedia.org/wiki/Integer_overflow#Saturated_arithmetic), which is almost always a mathematically incorrect outcome for the requested operation. +## * In a release build, you'll get [wrapping overflow](https://en.wikipedia.org/wiki/Integer_overflow), which is almost always a mathematically incorrect outcome for the requested operation. (If you actually want wrapping, because you're writing something like a hash function, use functions like #Int.addWrapping.) ## ## As such, it's very important to design your code not to exceed these bounds! ## If you need to do math outside these bounds, consider using ## a different representation other than #Int. The reason #Int has these ## bounds is for performance reasons. -#Int : Num Integer +# Int size : Num [ @Int size ] ## Arithmetic @@ -90,21 +203,37 @@ interface Int #bitwiseNot : Int -> Int +## Sort ascending - that is, with the lowest first, and the highest last. +## +## List.sort Int.asc [ 3, 6, 0 ] +## +asc : Int a, Int a -> [ Eq, Lt, Gt ] + +## Sort descending - that is, with the highest first, and the lowest last. +## +## List.sort Int.desc [ 3, 6, 0 ] +## +desc : Int a, Int a -> [ Eq, Lt, Gt ] + +## TODO should we offer hash32 etc even if someday it has to do a hash64 and truncate? +## +## CAUTION: This function may give different answers in future releases of Roc, +## so be aware that if you rely on the exact answer this gives today, your +## code may break in a future Roc release. +hash64 : a -> U64 + ## Limits -## The highest number that can be stored in an #Int without overflowing its -## available memory (64 bits total) and crashing. +## The highest number that can be stored in an #I32 without overflowing its +## available memory and crashing. ## -## Note that this is smaller than the positive version of #Int.lowest, -## which means if you call #Num.abs on #Int.lowest, it will crash! -#highest : Int -highest = 0x7fff_ffff_ffff_ffff +## Note that this is smaller than the positive version of #Int.lowestI32 +## which means if you call #Num.abs on #Int.lowestI32, it will overflow and crash! +highestI32 : I32 -## The lowest number that can be stored in an #Int without overflowing its -## available memory (64 bits total) and crashing. +## The lowest number that can be stored in an #I32 without overflowing its +## available memory and crashing. ## ## Note that the positive version of this number is this is larger than -## #Int.highest, which means if you call #Num.abs on #Int.lowest, -## it will crash! -#lowest : Int -lowest = -0x8000_0000_0000_0000 +## #Int.highestI32, which means if you call #Num.abs on #Int.lowestI32, it will overflow and crash! +lowest : I32 diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index bdba0779bc..3af0244baf 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -27,6 +27,11 @@ interface List ## > applies to lists that take up 8 machine words in memory or fewer, so ## > for example on a 64-bit system, a list of 8 #Int values will be ## > stored as a flat array instead of as an RRBT. +## +## One #List can store up to 2,147,483,648 elements (just over 2 billion). If you need to store more +## elements than that, you can split them into smaller lists and operate +## on those instead of on one large #List. This often runs faster in practice, +## even for strings much smaller than 2 gigabytes. List elem : @List elem ## Initialize @@ -54,7 +59,8 @@ fromResult : Result elem * -> List elem reverse : List elem -> List elem -sort : List elem, Sorter elem -> List elem +sort : List elem, (elem, elem -> [ Eq, Lt, Gt ]) -> List elem +sortBy : List elem, (elem -> field), (field, field -> [ Eq, Lt, Gt ]) -> List elem ## Convert each element in the list to something new, by calling a conversion ## function on each of them. Then return a new list of the converted values. @@ -191,6 +197,13 @@ walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> s ## Check +## Returns the length of the list - the number of elements it contains. +## +## One #List can store up to 2,147,483,648 elements (just over 2 billion), which +## is exactly equal to the highest valid #I32 value. This means the #U32 this function +## returns can always be safely converted to an #I32 without losing any data. +len : List * -> U32 + isEmpty : List * -> Bool contains : List elem, elem -> Bool @@ -198,3 +211,4 @@ contains : List elem, elem -> Bool all : List elem, (elem -> Bool) -> Bool any : List elem, (elem -> Bool) -> Bool + diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 58121d80ce..7eb5ba24cb 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -1,4 +1,4 @@ -api Num provides Num, DivByZero..., negate, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero +api Num provides Num, DivByZero..., neg, abs, add, sub, mul, isOdd, isEven, isPositive, isNegative, isZero ## Types @@ -25,17 +25,21 @@ Num range : @Num range ## Return a negative number when given a positive one, and vice versa. ## -## Some languages have a unary `-` operator (for example, `-(a + b)`), but Roc does not. If you want to negate a number, calling this function is the way to do it! +## >>> Num.neg 5 ## -## > Num.neg 5 +## >>> Num.neg -2.5 ## -## > Num.neg -2.5 +## >>> Num.neg 0 ## -## > Num.neg 0 +## >>> Num.neg 0.0 ## -## > Num.neg 0.0 +## This is safe to use with any #Float, but it can cause overflow when used with certain #Int values. ## -## This will crash when given #Int.lowestValue, because doing so will result in a number higher than #Int.highestValue. +## For example, calling #Num.neg on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. +## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than +## the highest value it can represent. (For this reason, calling #Num.abs on the lowest signed value will also cause overflow.) +## +## Additionally, calling #Num.neg on any unsigned integer (such as any #U64 or #U32 value) other than 0 will cause overflow. ## ## (It will never crash when given a #Float, however, because of how floating point numbers represent positive and negative numbers.) neg : Num range -> Num range @@ -44,14 +48,23 @@ neg : Num range -> Num range ## ## * For a positive number, returns the same number. ## * For a negative number, returns the same number except positive. +## * For zero, returns zero. ## -## > Num.abs 4 +## >>> Num.abs 4 ## -## > Num.abs -2.5 +## >>> Num.abs -2.5 ## -## > Num.abs 0 +## >>> Num.abs 0 ## -## > Num.abs 0.0 +## >>> Num.abs 0.0 +## +## This is safe to use with any #Float, but it can cause overflow when used with certain #Int values. +## +## For example, calling #Num.abs on the lowest value of a signed integer (such as #Int.lowestI64 or #Int.lowestI32) will cause overflow. +## This is because, for any given size of signed integer (32-bit, 64-bit, etc.) its negated lowest value turns out to be 1 higher than +## the highest value it can represent. (For this reason, calling #Num.neg on the lowest signed value will also cause overflow.) +## +## Calling this on an unsigned integer (like #U32 or #U64) never does anything. abs : Num range -> Num range ## Check diff --git a/compiler/builtins/docs/Str.roc b/compiler/builtins/docs/Str.roc index bb208869f0..52460d485a 100644 --- a/compiler/builtins/docs/Str.roc +++ b/compiler/builtins/docs/Str.roc @@ -2,7 +2,59 @@ api Str provides Str, isEmpty, join ## Types -Str := Str +## A [Unicode](https://unicode.org) text value. +## +## Dealing with text is deep topic, so by design, Roc's `Str` module sticks +## to the basics. For more advanced use cases like working with raw [code points](https://unicode.org/glossary/#code_point), +## see the [roc/unicode](roc/unicode) package, and for locale-specific text +## functions (including capitalization, as capitalization rules vary by locale) +## see the [roc/locale](roc/locale) package. +## +## ### Unicode +## +## Unicode can represent text values which span multiple languages, symbols, and emoji. +## Here are some valid Roc strings: +## +## * "Roc" +## * "鹏" +## * "🐦" +## +## Every Unicode string is a sequence of [grapheme clusters](https://unicode.org/glossary/#grapheme_cluster). +## A grapheme cluster corresponds to what a person reading a string might call +## a "character", but because the term "character" is used to mean many different +## concepts across different programming languages, we intentionally avoid it in Roc. +## Instead, we use the term "clusters" as a shorthand for "grapheme clusters." +## +## You can get the number of grapheme clusters in a string by calling `Str.countClusters` on it: +## +## >>> Str.countClusters "Roc" +## +## >>> Str.countClusters "音乐" +## +## >>> Str.countClusters "👍" +## +## > The `countClusters` function traverses the entire string to calculate its answer, +## > so it's much better for performance to use `Str.isEmpty` instead of +## > calling `Str.countClusters` and checking whether the count was `0`. +## +## ### Escape characters +## +## ### String interpolation +## +## ### Encoding +## +## Roc strings are not coupled to any particular +## [encoding](https://en.wikipedia.org/wiki/Character_encoding). As it happens, +## they are currently encoded in UTF-8, but this module is intentionally designed +## not to rely on that implementation detail so that a future release of Roc can +## potentially change it without breaking existing Roc applications. +## +## This module has functions to can convert a #Str to a #List of raw [code unit](https://unicode.org/glossary/#code_unit) +## integers (not to be confused with the [code points](https://unicode.org/glossary/#code_point) +## mentioned earlier) in a particular encoding. If you need encoding-specific functions, +## you should take a look at the [roc/unicode](roc/unicode) package. +## It has many more tools than this module does! +Str : [ @Str ] ## Convert @@ -15,10 +67,22 @@ Str := Str ## but it's recommended to pass much smaller numbers instead. ## ## Passing a negative number for decimal places is equivalent to passing 0. -decimal : Int, Float -> Str +decimal : Float *, ULen -> Str ## Convert an #Int to a string. -int : Float -> Str +int : Int * -> Str + +## Split a string around a separator. +## +## >>> Str.splitClusters "1,2,3" "," +## +## Passing `""` for the separator is not useful; it returns the original string +## wrapped in a list. +## +## >>> Str.splitClusters "1,2,3" "" +## +## To split a string into its grapheme clusters, use #Str.clusters +split : Str, Str -> List Str ## Check @@ -52,4 +116,196 @@ padStart : Str, Int, Str -> Str padEnd : Str, Int, Str -> Str +## Grapheme Clusters + +## Split a string into its grapheme clusters. +## +## >>> Str.clusters "1,2,3" +## +## >>> Str.clusters "👍👍👍" +## +clusters : Str -> List Str + +reverseClusters : Str -> Str + +foldClusters : Str, { start: state, step: (state, Str -> state) } -> state + +## Returns #True if the string begins with a capital letter, and #False otherwise. +## +## >>> Str.isCapitalized "hi" +## +## >>> Str.isCapitalized "Hi" +## +## >>> Str.isCapitalized " Hi" +## +## >>> Str.isCapitalized "Česká" +## +## >>> Str.isCapitalized "Э" +## +## >>> Str.isCapitalized "東京" +## +## >>> Str.isCapitalized "🐦" +## +## >>> Str.isCapitalized "" +## +## Since the rules for how to capitalize an uncapitalized string vary by locale, +## see the [roc/locale](roc/locale) package for functions which do that. +isCapitalized : Str -> Bool + +## ## Code Units +## +## Besides grapheme clusters, another way to break down strings is into +## raw code unit integers. +## +## Code units are no substitute for grapheme clusters! +## These functions exist to support advanced use cases like those found in +## [roc/unicode](roc/unicode), and using code units when grapheme clusters would +## be more appropriate can very easily lead to bugs. +## +## For example, `Str.countGraphemes "👩‍👩‍👦‍👦"` returns `1`, +## whereas `Str.toUtf8 "👩‍👩‍👦‍👦"` returns a list with a length of 25, +## `Str.toUtf16 "👩‍👩‍👦‍👦"` returns a list with a length of 11. +## and `Str.toUtf32 "👩‍👩‍👦‍👦"` returns a list with a length of 7. + +## Return a #List of the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit). +## (To split the string into a #List of smaller #Str values instead of #U8 values, +## see #Str.split and #Str.clusters.) +## +## >>> Str.toUtf8 "👩‍👩‍👦‍👦" +## +## >>> Str.toUtf8 "Roc" +## +## >>> Str.toUtf8 "鹏" +## +## >>> Str.toUtf8 "🐦" +## +## For a more flexible function that walks through each of these #U8 code units +## without creating a #List, see #Str.foldUtf8 and #Str.foldRevUtf8. +toUtf8 : Str -> List U8 + +## Return a #List of the string's #U16 UTF-16 [code units](https://unicode.org/glossary/#code_unit). +## (To split the string into a #List of smaller #Str values instead of #U16 values, +## see #Str.split and #Str.clusters.) +## +## >>> Str.toUtf16 "👩‍👩‍👦‍👦" +## +## >>> Str.toUtf16 "Roc" +## +## >>> Str.toUtf16 "鹏" +## +## >>> Str.toUtf16 "🐦" +## +## For a more flexible function that walks through each of these #U16 code units +## without creating a #List, see #Str.foldUtf16 and #Str.foldRevUtf16. +toUtf16 : Str -> List U16 + +## Return a #List of the string's #U32 UTF-32 [code units](https://unicode.org/glossary/#code_unit). +## (To split the string into a #List of smaller #Str values instead of #U32 values, +## see #Str.split and #Str.clusters.) +## +## >>> Str.toUtf32 "👩‍👩‍👦‍👦" +## +## >>> Str.toUtf32 "Roc" +## +## >>> Str.toUtf32 "鹏" +## +## >>> Str.toUtf32 "🐦" +## +## For a more flexible function that walks through each of these #U32 code units +## without creating a #List, see #Str.foldUtf32 and #Str.foldRevUtf32. +toUtf32 : Str -> List U32 + + +## Walk through the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit) +## to build up a state. +## (If you want a `step` function which receives a #Str instead of an #U8, see #Str.foldClusters.) +## +## Here are the #U8 values that will be passed to `step` when this function is +## called on various strings: +## +## * `"👩‍👩‍👦‍👦"` passes 240, 159, 145, 169, 226, 128, 141, 240, 159, 145, 169, 226, 128, 141, 240, 159, 145, 166, 226, 128, 141, 240, 159, 145, 166 +## * `"Roc"` passes 82, 111, 99 +## * `"鹏"` passes 233, 185, 143 +## * `"🐦"` passes 240, 159, 144, 166 +## +## To convert a #Str into a plain `List U8` of UTF-8 code units, see #Str.toUtf8. +foldUtf8 : Str, { start: state, step: (state, U8 -> state) } -> state + +## Walk through the string's #U16 UTF-16 [code units](https://unicode.org/glossary/#code_unit) +## to build up a state. +## (If you want a `step` function which receives a #Str instead of an #U16, see #Str.foldClusters.) +## +## Here are the #U16 values that will be passed to `step` when this function is +## called on various strings: +## +## * `"👩‍👩‍👦‍👦"` passes 55357, 56425, 8205, 55357, 56425, 8205, 55357, 56422, 8205, 55357, 56422 +## * `"Roc"` passes 82, 111, 99 +## * `"鹏"` passes 40527 +## * `"🐦"` passes 55357, 56358 +## +## To convert a #Str into a plain `List U16` of UTF-16 code units, see #Str.toUtf16. +foldUtf16 : Str, { start: state, step: (state, U16 -> state) } -> state + +## Walk through the string's #U32 UTF-32 [code units](https://unicode.org/glossary/#code_unit) +## to build up a state. +## (If you want a `step` function which receives a #Str instead of an #U32, see #Str.foldClusters.) +## +## Here are the #U32 values that will be passed to `step` when this function is +## called on various strings: +## +## * `"👩‍👩‍👦‍👦"` passes 128105, 8205, 128105, 8205, 128102, 8205, 128102 +## * `"Roc"` passes 82, 111, 99 +## * `"鹏"` passes 40527 +## * `"🐦"` passes 128038 +## +## To convert a #Str into a plain `List U32` of UTF-32 code units, see #Str.toUtf32. +foldUtf32 : Str, { start: state, step: (state, U32 -> state) } -> state + + +## Walk backwards through the string's #U8 UTF-8 [code units](https://unicode.org/glossary/#code_unit) +## to build up a state. +## (If you want a `step` function which receives a #Str instead of an #U8, see #Str.foldClusters.) +## +## Here are the #U8 values that will be passed to `step` when this function is +## called on various strings: +## +## * `"👩‍👩‍👦‍👦"` passes 166, 145, 159, 240, 141, 128, 226, 166, 145, 159, 240, 141, 128, 226, 169, 145, 159, 240, 141, 128, 226, 169, 145, 159, 240 +## * `"Roc"` passes 99, 111, 82 +## * `"鹏"` passes 143, 185, 233 +## * `"🐦"` passes 166, 144, 159, 240 +## +## To convert a #Str into a plain `List U8` of UTF-8 code units, see #Str.toUtf8. +foldRevUtf8 : Str, { start: state, step: (state, U8 -> state) } -> state + +## Walk backwards through the string's #U16 UTF-16 [code units](https://unicode.org/glossary/#code_unit) +## to build up a state. +## (If you want a `step` function which receives a #Str instead of an #U16, see #Str.foldClusters.) +## +## Here are the #U16 values that will be passed to `step` when this function is +## called on various strings: +## +## * `"👩‍👩‍👦‍👦"` passes 56422, 55357, 8205, 56422, 55357, 8205, 56425, 55357, 8205, 56425, 55357 +## * `"Roc"` passes 99, 111, 82 +## * `"鹏"` passes 40527 +## * `"🐦"` passes 56358, 55357 +## +## To convert a #Str into a plain `List U16` of UTF-16 code units, see #Str.toUtf16. +foldRevUtf16 : Str, { start: state, step: (state, U16 -> state) } -> state + +## Walk backwards through the string's #U32 UTF-32 [code units](https://unicode.org/glossary/#code_unit) +## to build up a state. +## (If you want a `step` function which receives a #Str instead of an #U32, see #Str.foldClusters.) +## +## Here are the #U32 values that will be passed to `step` when this function is +## called on various strings: +## +## * `"👩‍👩‍👦‍👦"` passes 128102, 8205, 128102, 8205, 128105, 8205, 128105 +## * `"Roc"` passes 99, 111, 82 +## * `"鹏"` passes 40527 +## * `"🐦"` passes 128038 +## +## To convert a #Str into a plain `List U32` of UTF-32 code units, see #Str.toUtf32. +foldRevUtf32 : Str, { start: state, step: (state, U32 -> state) } -> state + + diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 26cb4074c5..7296872b3e 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -367,7 +367,7 @@ pub fn types() -> MutMap { // isEmpty : List * -> Bool add_type( - Symbol::LIST_ISEMPTY, + Symbol::LIST_IS_EMPTY, SolvedType::Func( vec![SolvedType::Apply( Symbol::LIST_LIST, @@ -408,6 +408,15 @@ pub fn types() -> MutMap { ), ); + // concat : List elem, List elem -> List elem + add_type( + Symbol::LIST_CONCAT, + SolvedType::Func( + vec![list_type(flex(TVAR1)), list_type(flex(TVAR1))], + Box::new(list_type(flex(TVAR1))), + ), + ); + // map : List before, (before -> after) -> List after add_type( Symbol::LIST_MAP, @@ -442,9 +451,9 @@ pub fn types() -> MutMap { ), ); - // length : List a -> Int + // len : List * -> Int add_type( - Symbol::LIST_LENGTH, + Symbol::LIST_LEN, SolvedType::Func(vec![list_type(flex(TVAR1))], Box::new(int_type())), ); diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index 199bd14eda..9c33318e66 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -394,16 +394,16 @@ pub fn types() -> MutMap { // Bool module - // isEq or (==) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool + // isEq or (==) : a, a -> Attr u Bool add_type( Symbol::BOOL_EQ, - unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)), + unique_function(vec![flex(TVAR1), flex(TVAR1)], bool_type(UVAR3)), ); - // isNeq or (!=) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool + // isNeq or (!=) : a, a -> Attr u Bool add_type( Symbol::BOOL_NEQ, - unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)), + unique_function(vec![flex(TVAR1), flex(TVAR1)], bool_type(UVAR3)), ); // and or (&&) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool @@ -434,13 +434,13 @@ pub fn types() -> MutMap { // isEmpty : Attr u (List *) -> Attr v Bool add_type( - Symbol::LIST_ISEMPTY, + Symbol::LIST_IS_EMPTY, unique_function(vec![list_type(UVAR1, TVAR1)], bool_type(UVAR2)), ); - // length : List a -> Int + // len : List * -> Int add_type( - Symbol::LIST_LENGTH, + Symbol::LIST_LEN, unique_function(vec![list_type(UVAR1, TVAR1)], int_type(UVAR2)), ); diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index c70e90fcd6..a5a1cf6d4f 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::all::{MutSet, SendMap}; +use roc_collections::all::{MutMap, MutSet, SendMap}; use roc_module::ident::Ident; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -292,9 +292,10 @@ fn can_annotation_help( Record { fields, ext } => { let mut field_types = SendMap::default(); + let mut seen = MutMap::default(); for field in fields.iter() { - can_assigned_field( + let opt_field_name = can_assigned_field( env, &field.value, region, @@ -305,6 +306,17 @@ fn can_annotation_help( &mut field_types, references, ); + + if let Some(added) = opt_field_name { + if let Some(replaced_region) = seen.insert(added.clone(), field.region) { + env.problem(roc_problem::can::Problem::DuplicateRecordFieldType { + field_name: added.clone(), + field_region: field.region, + record_region: region, + replaced_region, + }); + } + } } let ext_type = match ext { @@ -325,9 +337,10 @@ fn can_annotation_help( } TagUnion { tags, ext } => { let mut tag_types = Vec::with_capacity(tags.len()); + let mut seen = MutMap::default(); for tag in tags.iter() { - can_tag( + let opt_tag_name = can_tag( env, &tag.value, region, @@ -338,6 +351,17 @@ fn can_annotation_help( &mut tag_types, references, ); + + if let Some(added) = opt_tag_name { + if let Some(replaced_region) = seen.insert(added.clone(), tag.region) { + env.problem(roc_problem::can::Problem::DuplicateTag { + tag_name: added.clone(), + tag_region: tag.region, + tag_union_region: region, + replaced_region, + }); + } + } } let ext_type = match ext { @@ -388,7 +412,7 @@ fn can_assigned_field<'a>( local_aliases: &mut SendMap, field_types: &mut SendMap, references: &mut MutSet, -) { +) -> Option { use roc_parse::ast::AssignedField::*; match field { @@ -396,16 +420,18 @@ fn can_assigned_field<'a>( let field_type = can_annotation_help( env, &annotation.value, - region, + annotation.region, scope, var_store, introduced_variables, local_aliases, references, ); - let label = Lowercase::from(field_name.value); - field_types.insert(label, field_type); + let label = Lowercase::from(field_name.value); + field_types.insert(label.clone(), field_type); + + Some(label) } LabelOnly(loc_field_name) => { // Interpret { a, b } as { a : a, b : b } @@ -420,7 +446,9 @@ fn can_assigned_field<'a>( } }; - field_types.insert(field_name, field_type); + field_types.insert(field_name.clone(), field_type); + + Some(field_name) } SpaceBefore(nested, _) | SpaceAfter(nested, _) => can_assigned_field( env, @@ -433,7 +461,7 @@ fn can_assigned_field<'a>( field_types, references, ), - Malformed(_) => (), + Malformed(_) => None, } } @@ -449,7 +477,7 @@ fn can_tag<'a>( local_aliases: &mut SendMap, tag_types: &mut Vec<(TagName, Vec)>, references: &mut MutSet, -) { +) -> Option { match tag { Tag::Global { name, args } => { let name = name.value.into(); @@ -470,7 +498,10 @@ fn can_tag<'a>( arg_types.push(ann); } - tag_types.push((TagName::Global(name), arg_types)); + let tag_name = TagName::Global(name); + tag_types.push((tag_name.clone(), arg_types)); + + Some(tag_name) } Tag::Private { name, args } => { let ident_id = env.ident_ids.get_or_insert(&name.value.into()); @@ -492,7 +523,10 @@ fn can_tag<'a>( arg_types.push(ann); } - tag_types.push((TagName::Private(symbol), arg_types)); + let tag_name = TagName::Private(symbol); + tag_types.push((tag_name.clone(), arg_types)); + + Some(tag_name) } Tag::SpaceBefore(nested, _) | Tag::SpaceAfter(nested, _) => can_tag( env, @@ -505,6 +539,6 @@ fn can_tag<'a>( tag_types, references, ), - Tag::Malformed(_) => (), + Tag::Malformed(_) => None, } } diff --git a/compiler/can/src/constraint.rs b/compiler/can/src/constraint.rs index f3d7da1252..0d029c3b96 100644 --- a/compiler/can/src/constraint.rs +++ b/compiler/can/src/constraint.rs @@ -3,11 +3,11 @@ use roc_collections::all::{ImMap, ImSet, SendMap}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, PatternCategory, Type}; +use roc_types::types::{Alias, Category, PatternCategory, Type}; #[derive(Debug, Clone, PartialEq)] pub enum Constraint { - Eq(Type, Expected, Region), + Eq(Type, Expected, Category, Region), Lookup(Symbol, Expected, Region), Pattern(Region, PatternCategory, Type, PExpected), True, // Used for things that always unify, e.g. blanks and runtime errors @@ -32,24 +32,32 @@ impl Constraint { match self { True | SaveTheEnvironment => {} - Eq(typ, expected, _) => { - expected - .get_type_mut_ref() - .instantiate_aliases(aliases, var_store, introduced); - typ.instantiate_aliases(aliases, var_store, introduced); + Eq(typ, expected, _, region) => { + let expected_region = expected.get_annotation_region().unwrap_or(*region); + expected.get_type_mut_ref().instantiate_aliases( + expected_region, + aliases, + var_store, + introduced, + ); + typ.instantiate_aliases(*region, aliases, var_store, introduced); } - Lookup(_, expected, _) => { - expected - .get_type_mut_ref() - .instantiate_aliases(aliases, var_store, introduced); + Lookup(_, expected, region) => { + let expected_region = expected.get_annotation_region().unwrap_or(*region); + expected.get_type_mut_ref().instantiate_aliases( + expected_region, + aliases, + var_store, + introduced, + ); } - Pattern(_, _, typ, pexpected) => { + Pattern(region, _, typ, pexpected) => { pexpected .get_type_mut_ref() - .instantiate_aliases(aliases, var_store, introduced); - typ.instantiate_aliases(aliases, var_store, introduced); + .instantiate_aliases(*region, aliases, var_store, introduced); + typ.instantiate_aliases(*region, aliases, var_store, introduced); } And(nested) => { @@ -65,8 +73,8 @@ impl Constraint { } let mut introduced = ImSet::default(); - for Located { value: typ, .. } in letcon.def_types.iter_mut() { - typ.instantiate_aliases(&new_aliases, var_store, &mut introduced); + for Located { region, value: typ } in letcon.def_types.iter_mut() { + typ.instantiate_aliases(*region, &new_aliases, var_store, &mut introduced); } letcon.defs_constraint.instantiate_aliases_help( diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 86511b947f..d4835a78dc 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -22,14 +22,21 @@ use std::collections::HashMap; use std::fmt::Debug; use ven_graph::{strongly_connected_components, topological_sort_into_groups}; -#[allow(clippy::type_complexity)] #[derive(Clone, Debug, PartialEq)] pub struct Def { pub loc_pattern: Located, pub loc_expr: Located, pub expr_var: Variable, pub pattern_vars: SendMap, - pub annotation: Option<(Type, IntroducedVariables, SendMap)>, + pub annotation: Option, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Annotation { + pub signature: Type, + pub introduced_variables: IntroducedVariables, + pub aliases: SendMap, + pub region: Region, } #[derive(Debug)] @@ -71,6 +78,8 @@ enum PendingDef<'a> { vars: Vec>, ann: &'a Located>, }, + + ShadowedAlias, } #[derive(Clone, Debug, PartialEq)] @@ -118,7 +127,7 @@ pub fn canonicalize_defs<'a>( // This way, whenever any expr is doing lookups, it knows everything that's in scope - // even defs that appear after it in the source. // - // This naturally handles recursion too, because a given exper which refers + // This naturally handles recursion too, because a given expr which refers // to itself won't be processed until after its def has been added to scope. use roc_parse::ast::Def::*; @@ -192,6 +201,7 @@ pub fn canonicalize_defs<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); + let mut is_phantom = false; for loc_lowercase in vars { if let Some(var) = can_ann .introduced_variables @@ -203,12 +213,31 @@ pub fn canonicalize_defs<'a>( region: loc_lowercase.region, }); } else { - panic!("TODO handle phantom type variables, they are not allowed!\nThe {:?} variable in the definition of {:?} gives trouble", loc_lowercase, symbol); + is_phantom = true; + + env.problems.push(Problem::PhantomTypeArgument { + alias: symbol, + variable_region: loc_lowercase.region, + variable_name: loc_lowercase.value.clone(), + }); } } + if is_phantom { + // Bail out + continue; + } + if can_ann.typ.contains_symbol(symbol) { - make_tag_union_recursive(symbol, &mut can_ann.typ, var_store); + make_tag_union_recursive( + env, + symbol, + name.region, + vec![], + &mut can_ann.typ, + var_store, + &mut false, + ); } let alias = roc_types::types::Alias { @@ -222,7 +251,7 @@ pub fn canonicalize_defs<'a>( } } - correct_mutual_recursive_type_alias(&mut aliases, &var_store); + correct_mutual_recursive_type_alias(env, &mut aliases, &var_store); // Now that we have the scope completely assembled, and shadowing resolved, // we're ready to canonicalize any body exprs. @@ -662,19 +691,24 @@ fn pattern_to_vars_by_symbol( vars_by_symbol.insert(symbol.clone(), expr_var); } - AppliedTag(_, _, arguments) => { + AppliedTag { arguments, .. } => { for (var, nested) in arguments { pattern_to_vars_by_symbol(vars_by_symbol, &nested.value, *var); } } - RecordDestructure(_, destructs) => { + RecordDestructure { destructs, .. } => { for destruct in destructs { vars_by_symbol.insert(destruct.value.symbol.clone(), destruct.value.var); } } - IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore | UnsupportedPattern(_) => {} + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | UnsupportedPattern(_) => {} Shadowed(_, _) => {} } @@ -787,11 +821,12 @@ fn canonicalize_pending_def<'a>( value: loc_can_expr.value.clone(), }, pattern_vars: im::HashMap::clone(&vars_by_symbol), - annotation: Some(( - typ.clone(), - output.introduced_variables.clone(), - ann.aliases.clone(), - )), + annotation: Some(Annotation { + signature: typ.clone(), + introduced_variables: output.introduced_variables.clone(), + aliases: ann.aliases.clone(), + region: loc_ann.region, + }), }, ); } @@ -821,7 +856,11 @@ fn canonicalize_pending_def<'a>( region: loc_lowercase.region, }); } else { - panic!("TODO handle phantom type variables, they are not allowed!"); + env.problems.push(Problem::PhantomTypeArgument { + alias: symbol, + variable_region: loc_lowercase.region, + variable_name: loc_lowercase.value.clone(), + }); } } @@ -837,7 +876,9 @@ fn canonicalize_pending_def<'a>( scope.add_alias(symbol, name.region, can_vars, rec_type_union); } else { - panic!("recursion in type alias that is not behind a Tag"); + env.problems + .push(Problem::CyclicAlias(symbol, name.region, vec![])); + return output; } } @@ -848,6 +889,11 @@ fn canonicalize_pending_def<'a>( .introduced_variables .union(&can_ann.introduced_variables); } + + ShadowedAlias => { + // Since this alias was shadowed, it gets ignored and has no + // effect on the output. + } TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { let ann = canonicalize_annotation(env, scope, &loc_ann.value, loc_ann.region, var_store); @@ -880,6 +926,8 @@ fn canonicalize_pending_def<'a>( let (mut loc_can_expr, can_output) = canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); + output.references = output.references.union(can_output.references.clone()); + // reset the tailcallable_symbol env.tailcallable_symbol = outer_identifier; @@ -982,11 +1030,12 @@ fn canonicalize_pending_def<'a>( value: loc_can_expr.value.clone(), }, pattern_vars: im::HashMap::clone(&vars_by_symbol), - annotation: Some(( - typ.clone(), - output.introduced_variables.clone(), - ann.aliases.clone(), - )), + annotation: Some(Annotation { + signature: typ.clone(), + introduced_variables: output.introduced_variables.clone(), + aliases: ann.aliases.clone(), + region: loc_ann.region, + }), }, ); } @@ -1285,6 +1334,7 @@ fn to_pending_def<'a>( Alias { name, vars, ann } => { let region = Region::span_across(&name.region, &ann.region); + match scope.introduce( name.value.into(), &env.exposed_ident_ids, @@ -1321,7 +1371,14 @@ fn to_pending_def<'a>( } } - Err(_err) => panic!("TODO gracefully handle shadowing of type alias"), + Err((original_region, loc_shadowed_symbol)) => { + env.problem(Problem::ShadowingInAnnotation { + original_region, + shadow: loc_shadowed_symbol, + }); + + PendingDef::ShadowedAlias + } } } @@ -1354,7 +1411,11 @@ fn pending_typed_body<'a>( } /// Make aliases recursive -fn correct_mutual_recursive_type_alias(aliases: &mut SendMap, var_store: &VarStore) { +fn correct_mutual_recursive_type_alias<'a>( + env: &mut Env<'a>, + aliases: &mut SendMap, + var_store: &VarStore, +) { let mut symbols_introduced = ImSet::default(); for (key, _) in aliases.iter() { @@ -1403,11 +1464,17 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap, var &mutually_recursive_symbols, all_successors_without_self, ) { + // make sure we report only one error for the cycle, not an error for every + // alias in the cycle. + let mut can_still_report_error = true; + // TODO use itertools to be more efficient here for rec in &cycle { let mut to_instantiate = ImMap::default(); + let mut others = Vec::with_capacity(cycle.len() - 1); for other in &cycle { if rec != other { + others.push(*other); if let Some(alias) = originals.get(other) { to_instantiate.insert(*other, alias.clone()); } @@ -1416,11 +1483,20 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap, var if let Some(alias) = aliases.get_mut(rec) { alias.typ.instantiate_aliases( + alias.region, &to_instantiate, var_store, &mut ImSet::default(), ); - make_tag_union_recursive(*rec, &mut alias.typ, var_store); + make_tag_union_recursive( + env, + *rec, + alias.region, + others, + &mut alias.typ, + var_store, + &mut can_still_report_error, + ); } } } @@ -1428,14 +1504,41 @@ fn correct_mutual_recursive_type_alias(aliases: &mut SendMap, var } } -fn make_tag_union_recursive(symbol: Symbol, typ: &mut Type, var_store: &VarStore) { +fn make_tag_union_recursive<'a>( + env: &mut Env<'a>, + symbol: Symbol, + region: Region, + others: Vec, + typ: &mut Type, + var_store: &VarStore, + can_report_error: &mut bool, +) { match typ { Type::TagUnion(tags, ext) => { let rec_var = var_store.fresh(); *typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); typ.substitute_alias(symbol, &Type::Variable(rec_var)); } - Type::Alias(_, _, actual) => make_tag_union_recursive(symbol, actual, var_store), - _ => panic!("recursion in type alias is not behind a Tag"), + Type::Alias(_, _, actual) => make_tag_union_recursive( + env, + symbol, + region, + others, + actual, + var_store, + can_report_error, + ), + _ => { + let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone()); + *typ = Type::Erroneous(problem); + + // ensure cyclic error is only reported for one element of the cycle + if *can_report_error { + *can_report_error = false; + + let problem = Problem::CyclicAlias(symbol, region, others); + env.problems.push(problem); + } + } } } diff --git a/compiler/can/src/expected.rs b/compiler/can/src/expected.rs index d6c0b27afe..4075624dcc 100644 --- a/compiler/can/src/expected.rs +++ b/compiler/can/src/expected.rs @@ -37,6 +37,13 @@ impl PExpected { PExpected::ForReason(_, val, _) => val, } } + + pub fn replace(self, new: U) -> PExpected { + match self { + PExpected::NoExpectation(_val) => PExpected::NoExpectation(new), + PExpected::ForReason(reason, _val, region) => PExpected::ForReason(reason, new, region), + } + } } impl Expected { @@ -63,4 +70,23 @@ impl Expected { Expected::FromAnnotation(_, _, _, val) => val, } } + + pub fn get_annotation_region(&self) -> Option { + match self { + Expected::FromAnnotation(_, _, AnnotationSource::TypedBody { region }, _) => { + Some(*region) + } + _ => None, + } + } + + pub fn replace(self, new: U) -> Expected { + match self { + Expected::NoExpectation(_val) => Expected::NoExpectation(new), + Expected::ForReason(reason, _val, region) => Expected::ForReason(reason, new, region), + Expected::FromAnnotation(pattern, size, source, _val) => { + Expected::FromAnnotation(pattern, size, source, new) + } + } + } } diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 6e34a59aed..1289a61730 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -3,7 +3,7 @@ use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ finish_parsing_base, finish_parsing_float, finish_parsing_int, float_expr_from_result, - int_expr_from_result, + int_expr_from_result, num_expr_from_result, }; use crate::pattern::{canonicalize_pattern, Pattern}; use crate::procedure::References; @@ -14,7 +14,7 @@ use roc_module::symbol::Symbol; use roc_parse::ast; use roc_parse::operator::CalledVia; use roc_parse::pattern::PatternType::*; -use roc_problem::can::{Problem, RuntimeError}; +use roc_problem::can::{PrecedenceProblem, Problem, RuntimeError}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::Alias; @@ -30,9 +30,28 @@ pub struct Output { pub aliases: SendMap, } +impl Output { + pub fn union(&mut self, other: Self) { + self.references.union_mut(other.references); + + if let (None, Some(later)) = (self.tail_call, other.tail_call) { + self.tail_call = Some(later); + } + + self.introduced_variables.union(&other.introduced_variables); + self.aliases.extend(other.aliases); + } +} + #[derive(Clone, Debug, PartialEq)] pub enum Expr { // Literals + + // Num stores the `a` variable in `Num a`. Not the same as the variable + // stored in Int and Float below, which is strictly for better error messages + Num(Variable, i64), + + // Int and Float store a variable to generate better error messages Int(Variable, i64), Float(Variable, f64), Str(Box), @@ -48,8 +67,9 @@ pub enum Expr { When { cond_var: Variable, expr_var: Variable, + region: Region, loc_cond: Box>, - branches: Vec<(Located, Located)>, + branches: Vec, }, If { cond_var: Variable, @@ -79,13 +99,17 @@ pub enum Expr { ), // Product Types - Record(Variable, SendMap), + Record { + record_var: Variable, + fields: SendMap, + }, /// Empty record constant EmptyRecord, /// Look up exactly one field on a record, e.g. (expr).foo. Access { + record_var: Variable, ext_var: Variable, field_var: Variable, loc_expr: Box>, @@ -93,6 +117,7 @@ pub enum Expr { }, /// field accessor as a function, e.g. (.foo) expr Accessor { + record_var: Variable, ext_var: Variable, field_var: Variable, field: Lowercase, @@ -134,6 +159,13 @@ pub enum Recursive { NotRecursive, } +#[derive(Clone, Debug, PartialEq)] +pub struct WhenBranch { + pub patterns: Vec>, + pub value: Located, + pub guard: Option>, +} + pub fn canonicalize_expr<'a>( env: &mut Env<'a>, var_store: &VarStore, @@ -144,8 +176,8 @@ pub fn canonicalize_expr<'a>( use Expr::*; let (expr, output) = match expr { - ast::Expr::Int(string) => { - let answer = int_expr_from_result(var_store, finish_parsing_int(*string), env); + ast::Expr::Num(string) => { + let answer = num_expr_from_result(var_store, finish_parsing_int(*string), env); (answer, Output::default()) } @@ -161,7 +193,8 @@ pub fn canonicalize_expr<'a>( let (can_update, update_out) = canonicalize_expr(env, var_store, scope, loc_update.region, &loc_update.value); if let Var(symbol) = &can_update.value { - let (can_fields, mut output) = canonicalize_fields(env, var_store, scope, fields); + let (can_fields, mut output) = + canonicalize_fields(env, var_store, scope, region, fields); output.references = output.references.union(update_out.references); @@ -187,9 +220,16 @@ pub fn canonicalize_expr<'a>( if fields.is_empty() { (EmptyRecord, Output::default()) } else { - let (can_fields, output) = canonicalize_fields(env, var_store, scope, fields); + let (can_fields, output) = + canonicalize_fields(env, var_store, scope, region, fields); - (Record(var_store.fresh(), can_fields), output) + ( + Record { + record_var: var_store.fresh(), + fields: can_fields, + }, + output, + ) } } ast::Expr::Str(string) => (Str((*string).into()), Output::default()), @@ -394,20 +434,19 @@ pub fn canonicalize_expr<'a>( loc_body_expr.region, &loc_body_expr.value, ); - // Now that we've collected all the references, check to see if any of the args we defined // went unreferenced. If any did, report them as unused arguments. - for (symbol, region) in scope.symbols() { - if !original_scope.contains_symbol(*symbol) { - if !output.references.has_lookup(*symbol) { + for (sub_symbol, region) in scope.symbols() { + if !original_scope.contains_symbol(*sub_symbol) { + if !output.references.has_lookup(*sub_symbol) { // The body never referenced this argument we declared. It's an unused argument! - env.problem(Problem::UnusedArgument(*symbol, *region)); + env.problem(Problem::UnusedArgument(symbol, *sub_symbol, *region)); } // We shouldn't ultimately count arguments as referenced locals. Otherwise, // we end up with weird conclusions like the expression (\x -> x + 1) // references the (nonexistant) local variable x! - output.references.lookups.remove(symbol); + output.references.lookups.remove(sub_symbol); } } @@ -428,7 +467,7 @@ pub fn canonicalize_expr<'a>( // Infer the condition expression's type. let cond_var = var_store.fresh(); let (can_cond, mut output) = - canonicalize_expr(env, var_store, scope, region, &loc_cond.value); + canonicalize_expr(env, var_store, scope, loc_cond.region, &loc_cond.value); // the condition can never be a tail-call output.tail_call = None; @@ -436,19 +475,12 @@ pub fn canonicalize_expr<'a>( let mut can_branches = Vec::with_capacity(branches.len()); for branch in branches { - let (can_when_pattern, loc_can_expr, branch_references) = canonicalize_when_branch( - env, - var_store, - scope, - region, - branch.patterns.first().unwrap(), - &branch.value, - &mut output, - ); + let (can_when_branch, branch_references) = + canonicalize_when_branch(env, var_store, scope, region, *branch, &mut output); output.references = output.references.union(branch_references); - can_branches.push((can_when_pattern, loc_can_expr)); + can_branches.push(can_when_branch); } // A "when" with no branches is a runtime error, but it will mess things up @@ -462,6 +494,7 @@ pub fn canonicalize_expr<'a>( let expr = When { expr_var: var_store.fresh(), cond_var, + region, loc_cond: Box::new(can_cond), branches: can_branches, }; @@ -473,6 +506,7 @@ pub fn canonicalize_expr<'a>( ( Access { + record_var: var_store.fresh(), field_var: var_store.fresh(), ext_var: var_store.fresh(), loc_expr: Box::new(loc_expr), @@ -481,20 +515,15 @@ pub fn canonicalize_expr<'a>( output, ) } - ast::Expr::AccessorFunction(field) => { - let ext_var = var_store.fresh(); - let field_var = var_store.fresh(); - let field_name: Lowercase = (*field).into(); - - ( - Accessor { - field: field_name, - ext_var, - field_var, - }, - Output::default(), - ) - } + ast::Expr::AccessorFunction(field) => ( + Accessor { + record_var: var_store.fresh(), + ext_var: var_store.fresh(), + field_var: var_store.fresh(), + field: (*field).into(), + }, + Output::default(), + ), ast::Expr::GlobalTag(tag) => { let variant_var = var_store.fresh(); let ext_var = var_store.fresh(); @@ -557,14 +586,32 @@ pub fn canonicalize_expr<'a>( ) } - ast::Expr::MalformedIdent(_) - | ast::Expr::MalformedClosure - | ast::Expr::PrecedenceConflict(_, _, _) => { - panic!( - "TODO restore the rest of canonicalize()'s branches {:?} {:?}", - &expr, - local_successors(&References::new(), &env.closures) + ast::Expr::PrecedenceConflict(whole_region, binop1, binop2, _expr) => { + use roc_problem::can::RuntimeError::*; + + let problem = PrecedenceProblem::BothNonAssociative( + *whole_region, + binop1.clone(), + binop2.clone(), ); + + env.problem(Problem::PrecedenceProblem(problem.clone())); + + ( + RuntimeError(InvalidPrecedence(problem, region)), + Output::default(), + ) + } + ast::Expr::MalformedClosure => { + use roc_problem::can::RuntimeError::*; + (RuntimeError(MalformedClosure(region)), Output::default()) + } + ast::Expr::MalformedIdent(name) => { + use roc_problem::can::RuntimeError::*; + ( + RuntimeError(MalformedIdentifier((*name).into(), region)), + Output::default(), + ) } ast::Expr::Nested(sub_expr) => { let (answer, output) = canonicalize_expr(env, var_store, scope, region, sub_expr); @@ -644,34 +691,45 @@ pub fn canonicalize_expr<'a>( fn canonicalize_when_branch<'a>( env: &mut Env<'a>, var_store: &VarStore, - scope: &Scope, - region: Region, - loc_pattern: &Located>, - loc_expr: &'a Located>, + scope: &mut Scope, + _region: Region, + branch: &'a ast::WhenBranch<'a>, output: &mut Output, -) -> (Located, Located, References) { - // Each case branch gets a new scope for canonicalization. - // Shadow `scope` to make sure we don't accidentally use the original one for the - // rest of this block, but keep the original around for later diffing. +) -> (WhenBranch, References) { + let mut patterns = Vec::with_capacity(branch.patterns.len()); + let original_scope = scope; let mut scope = original_scope.clone(); - let loc_can_pattern = canonicalize_pattern( + // TODO report symbols not bound in all patterns + for loc_pattern in &branch.patterns { + patterns.push(canonicalize_pattern( + env, + var_store, + &mut scope, + WhenBranch, + &loc_pattern.value, + loc_pattern.region, + )); + } + + let (value, mut branch_output) = canonicalize_expr( env, var_store, &mut scope, - WhenBranch, - &loc_pattern.value, - loc_pattern.region, + branch.value.region, + &branch.value.value, ); - let (can_expr, branch_output) = - canonicalize_expr(env, var_store, &mut scope, region, &loc_expr.value); + let guard = match &branch.guard { + None => None, + Some(loc_expr) => { + let (can_guard, guard_branch_output) = + canonicalize_expr(env, var_store, &mut scope, loc_expr.region, &loc_expr.value); - // If we already recorded a tail call then keep it, else use this branch's tail call - match output.tail_call { - Some(_) => {} - None => output.tail_call = branch_output.tail_call, + branch_output.union(guard_branch_output); + Some(can_guard) + } }; // Now that we've collected all the references for this branch, check to see if @@ -687,7 +745,17 @@ fn canonicalize_when_branch<'a>( } } - (loc_can_pattern, can_expr, branch_output.references) + let references = branch_output.references.clone(); + output.union(branch_output); + + ( + WhenBranch { + patterns, + value, + guard, + }, + references, + ) } pub fn local_successors<'a>( @@ -819,6 +887,7 @@ fn canonicalize_fields<'a>( env: &mut Env<'a>, var_store: &VarStore, scope: &mut Scope, + region: Region, fields: &'a [Located>>], ) -> (SendMap, Output) { let mut can_fields = SendMap::default(); @@ -834,7 +903,16 @@ fn canonicalize_fields<'a>( loc_expr: Box::new(field_expr), }; - can_fields.insert(label, field); + let replaced = can_fields.insert(label.clone(), field); + + if let Some(old) = replaced { + env.problems.push(Problem::DuplicateRecordFieldValue { + field_name: label, + field_region: loc_field.region, + record_region: region, + replaced_region: old.region, + }); + } output.references = output.references.union(field_out.references); } diff --git a/compiler/can/src/num.rs b/compiler/can/src/num.rs index a06ad4242f..7c35d0a1a1 100644 --- a/compiler/can/src/num.rs +++ b/compiler/can/src/num.rs @@ -6,12 +6,34 @@ use roc_problem::can::RuntimeError::*; use roc_types::subs::VarStore; use std::i64; +#[inline(always)] +pub fn num_expr_from_result( + var_store: &VarStore, + result: Result, + env: &mut Env, +) -> Expr { + match result { + Ok(int) => Expr::Num(var_store.fresh(), int), + Err(raw) => { + // (Num *) compiles to Int if it doesn't + // get specialized to something else first, + // so use int's overflow bounds here. + let runtime_error = IntOutsideRange(raw.into()); + + env.problem(Problem::RuntimeError(runtime_error.clone())); + + Expr::RuntimeError(runtime_error) + } + } +} + #[inline(always)] pub fn int_expr_from_result( var_store: &VarStore, result: Result, env: &mut Env, ) -> Expr { + // Int stores a variable to generate better error messages match result { Ok(int) => Expr::Int(var_store.fresh(), int), Err(raw) => { @@ -30,6 +52,7 @@ pub fn float_expr_from_result( result: Result, env: &mut Env, ) -> Expr { + // Float stores a variable to generate better error messages match result { Ok(float) => Expr::Float(var_store.fresh(), float), Err(raw) => { diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 2b71344c9d..297b3d4bcf 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -62,8 +62,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a match &loc_expr.value { Float(_) | Nested(Float(_)) - | Int(_) - | Nested(Int(_)) + | Num(_) + | Nested(Num(_)) | NonBase10Int { .. } | Nested(NonBase10Int { .. }) | Str(_) @@ -78,8 +78,8 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a | Nested(MalformedIdent(_)) | MalformedClosure | Nested(MalformedClosure) - | PrecedenceConflict(_, _, _) - | Nested(PrecedenceConflict(_, _, _)) + | PrecedenceConflict(_, _, _, _) + | Nested(PrecedenceConflict(_, _, _, _)) | GlobalTag(_) | Nested(GlobalTag(_)) | PrivateTag(_) @@ -179,13 +179,19 @@ pub fn desugar_expr<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a }) } + let desugared_guard = if let Some(guard) = &branch.guard { + Some(desugar_expr(arena, guard).clone()) + } else { + None + }; + desugared_branches.push(&*arena.alloc(WhenBranch { patterns: alternatives, value: Located { region: desugared.region, value: Nested(&desugared.value), }, - guard: None, + guard: desugared_guard, })); } @@ -412,8 +418,9 @@ fn desugar_bin_op<'a>(arena: &'a Bump, loc_expr: &'a Located>) -> &'a L ); let region = broken_expr.region; let value = Expr::PrecedenceConflict( - bad_op, + loc_expr.region, stack_op, + bad_op, arena.alloc(broken_expr), ); diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 5c8af6318f..d068ce19cd 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -14,11 +14,21 @@ use roc_types::subs::{VarStore, Variable}; #[derive(Clone, Debug, PartialEq)] pub enum Pattern { Identifier(Symbol), - AppliedTag(Variable, TagName, Vec<(Variable, Located)>), + AppliedTag { + whole_var: Variable, + ext_var: Variable, + tag_name: TagName, + arguments: Vec<(Variable, Located)>, + }, + RecordDestructure { + whole_var: Variable, + ext_var: Variable, + destructs: Vec>, + }, IntLiteral(i64), + NumLiteral(Variable, i64), FloatLiteral(f64), StrLiteral(Box), - RecordDestructure(Variable, Vec>), Underscore, // Runtime Exceptions @@ -50,18 +60,23 @@ pub fn symbols_from_pattern_help(pattern: &Pattern, symbols: &mut Vec) { symbols.push(symbol.clone()); } - AppliedTag(_, _, arguments) => { + AppliedTag { arguments, .. } => { for (_, nested) in arguments { symbols_from_pattern_help(&nested.value, symbols); } } - RecordDestructure(_, destructs) => { + RecordDestructure { destructs, .. } => { for destruct in destructs { symbols.push(destruct.value.symbol.clone()); } } - IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore | UnsupportedPattern(_) => {} + NumLiteral(_, _) + | IntLiteral(_) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | UnsupportedPattern(_) => {} Shadowed(_, _) => {} } @@ -97,17 +112,23 @@ pub fn canonicalize_pattern<'a>( }, GlobalTag(name) => { // Canonicalize the tag's name. - Pattern::AppliedTag(var_store.fresh(), TagName::Global((*name).into()), vec![]) + Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name: TagName::Global((*name).into()), + arguments: vec![], + } } PrivateTag(name) => { let ident_id = env.ident_ids.get_or_insert(&(*name).into()); // Canonicalize the tag's name. - Pattern::AppliedTag( - var_store.fresh(), - TagName::Private(Symbol::new(env.home, ident_id)), - vec![], - ) + Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name: TagName::Private(Symbol::new(env.home, ident_id)), + arguments: vec![], + } } Apply(tag, patterns) => { let tag_name = match tag.value { @@ -135,7 +156,12 @@ pub fn canonicalize_pattern<'a>( )); } - Pattern::AppliedTag(var_store.fresh(), tag_name, can_patterns) + Pattern::AppliedTag { + whole_var: var_store.fresh(), + ext_var: var_store.fresh(), + tag_name, + arguments: can_patterns, + } } FloatLiteral(ref string) => match pattern_type { @@ -155,12 +181,12 @@ pub fn canonicalize_pattern<'a>( ptype @ DefExpr | ptype @ TopLevelDef => unsupported_pattern(env, ptype, region), }, - IntLiteral(string) => match pattern_type { + NumLiteral(string) => match pattern_type { WhenBranch => { let int = finish_parsing_int(string) .unwrap_or_else(|_| panic!("TODO handle malformed int pattern")); - Pattern::IntLiteral(int) + Pattern::NumLiteral(var_store.fresh(), int) } ptype @ DefExpr | ptype @ TopLevelDef | ptype @ FunctionArg => { unsupported_pattern(env, ptype, region) @@ -202,7 +228,8 @@ pub fn canonicalize_pattern<'a>( } RecordDestructure(patterns) => { let ext_var = var_store.fresh(); - let mut fields = Vec::with_capacity(patterns.len()); + let whole_var = var_store.fresh(); + let mut destructs = Vec::with_capacity(patterns.len()); let mut opt_erroneous = None; for loc_pattern in *patterns { @@ -215,7 +242,7 @@ pub fn canonicalize_pattern<'a>( region, ) { Ok(symbol) => { - fields.push(Located { + destructs.push(Located { region: loc_pattern.region, value: RecordDestruct { var: var_store.fresh(), @@ -240,45 +267,26 @@ pub fn canonicalize_pattern<'a>( }; } RecordField(label, loc_guard) => { - match scope.introduce( - label.into(), - &env.exposed_ident_ids, - &mut env.ident_ids, - region, - ) { - Ok(symbol) => { - let can_guard = canonicalize_pattern( - env, - var_store, - scope, - pattern_type, - &loc_guard.value, - loc_guard.region, - ); + // a guard does not introduce the label into scope! + let symbol = scope.ignore(label.into(), &mut env.ident_ids); + let can_guard = canonicalize_pattern( + env, + var_store, + scope, + pattern_type, + &loc_guard.value, + loc_guard.region, + ); - fields.push(Located { - region: loc_pattern.region, - value: RecordDestruct { - var: var_store.fresh(), - label: Lowercase::from(label), - symbol, - guard: Some((var_store.fresh(), can_guard)), - }, - }); - } - Err((original_region, shadow)) => { - env.problem(Problem::RuntimeError(RuntimeError::Shadowing { - original_region, - shadow: shadow.clone(), - })); - - // No matter what the other patterns - // are, we're definitely shadowed and will - // get a runtime exception as soon as we - // encounter the first bad pattern. - opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); - } - }; + destructs.push(Located { + region: loc_pattern.region, + value: RecordDestruct { + var: var_store.fresh(), + label: Lowercase::from(label), + symbol, + guard: Some((var_store.fresh(), can_guard)), + }, + }); } _ => panic!("invalid pattern in record"), } @@ -286,7 +294,11 @@ pub fn canonicalize_pattern<'a>( // If we encountered an erroneous pattern (e.g. one with shadowing), // use the resulting RuntimeError. Otherwise, return a successful record destructure. - opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure(ext_var, fields)) + opt_erroneous.unwrap_or_else(|| Pattern::RecordDestructure { + whole_var, + ext_var, + destructs, + }) } RecordField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); @@ -339,12 +351,15 @@ fn add_bindings_from_patterns( Identifier(symbol) => { answer.push((*symbol, *region)); } - AppliedTag(_, _, loc_args) => { + AppliedTag { + arguments: loc_args, + .. + } => { for (_, loc_arg) in loc_args { add_bindings_from_patterns(&loc_arg.region, &loc_arg.value, scope, answer); } } - RecordDestructure(_, destructs) => { + RecordDestructure { destructs, .. } => { for Located { region, value: RecordDestruct { symbol, .. }, @@ -353,7 +368,8 @@ fn add_bindings_from_patterns( answer.push((*symbol, *region)); } } - IntLiteral(_) + NumLiteral(_, _) + | IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) | Underscore diff --git a/compiler/can/src/procedure.rs b/compiler/can/src/procedure.rs index 0483cf591b..ca2ca15cf7 100644 --- a/compiler/can/src/procedure.rs +++ b/compiler/can/src/procedure.rs @@ -63,6 +63,11 @@ impl References { self } + pub fn union_mut(&mut self, other: References) { + self.lookups.extend(other.lookups); + self.calls.extend(other.calls); + } + pub fn has_lookup(&self, symbol: Symbol) -> bool { self.lookups.contains(&symbol) } diff --git a/compiler/can/src/scope.rs b/compiler/can/src/scope.rs index 06c5f636f8..32d5624710 100644 --- a/compiler/can/src/scope.rs +++ b/compiler/can/src/scope.rs @@ -57,10 +57,13 @@ impl Scope { pub fn lookup(&mut self, ident: &Ident, region: Region) -> Result { match self.idents.get(ident) { Some((symbol, _)) => Ok(*symbol), - None => Err(RuntimeError::LookupNotInScope(Located { - region, - value: ident.clone().into(), - })), + None => Err(RuntimeError::LookupNotInScope( + Located { + region, + value: ident.clone().into(), + }, + self.idents.keys().map(|v| v.as_ref().into()).collect(), + )), } } @@ -107,6 +110,14 @@ impl Scope { } } + /// Ignore an identifier. + /// + /// Used for record guards like { x: Just _ } + pub fn ignore(&mut self, ident: Ident, all_ident_ids: &mut IdentIds) -> Symbol { + let ident_id = all_ident_ids.add(ident.into()); + Symbol::new(self.home, ident_id) + } + /// Import a Symbol from another module into this module's top-level scope. /// /// Returns Err if this would shadow an existing ident, including the diff --git a/compiler/can/tests/test_canonicalize.rs b/compiler/can/tests/test_can.rs similarity index 96% rename from compiler/can/tests/test_canonicalize.rs rename to compiler/can/tests/test_can.rs index bf9f11b37e..2f5e89176e 100644 --- a/compiler/can/tests/test_canonicalize.rs +++ b/compiler/can/tests/test_can.rs @@ -11,7 +11,7 @@ extern crate roc_region; mod helpers; #[cfg(test)] -mod test_canonicalize { +mod test_can { use crate::helpers::{can_expr_with, test_home, CanExprOut}; use bumpalo::Bump; use roc_can::expr::Expr::{self, *}; @@ -40,6 +40,7 @@ mod test_canonicalize { } } } + fn assert_can_int(input: &str, expected: i64) { let arena = Bump::new(); let actual_out = can_expr_with(&arena, test_home(), input); @@ -54,6 +55,20 @@ mod test_canonicalize { } } + fn assert_can_num(input: &str, expected: i64) { + let arena = Bump::new(); + let actual_out = can_expr_with(&arena, test_home(), input); + + match actual_out.loc_expr.value { + Expr::Num(_, actual) => { + assert_eq!(expected, actual); + } + actual => { + panic!("Expected a Num, but got: {:?}", actual); + } + } + } + // NUMBER LITERALS #[test] @@ -98,12 +113,12 @@ mod test_canonicalize { #[test] fn zero() { - assert_can_int("0", 0); + assert_can_num("0", 0); } #[test] fn minus_zero() { - assert_can_int("-0", 0); + assert_can_num("-0", 0); } #[test] @@ -551,6 +566,31 @@ mod test_canonicalize { } } + #[test] + fn unused_def_regression() { + let src = indoc!( + r#" + Booly : [ Yes, No, Maybe ] + + y : Booly + y = No + + # There was a bug where annotating a def meant that its + # references no longer got reported. + # + # https://github.com/rtfeldman/roc/issues/298 + x : List Booly + x = [ y ] + + x + "# + ); + let arena = Bump::new(); + let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); + + assert_eq!(problems, Vec::new()); + } + //#[test] //fn closing_over_locals() { // // "local" should be used, because the closure used it. diff --git a/compiler/collections/src/all.rs b/compiler/collections/src/all.rs index de92d6cf08..91e2717f10 100644 --- a/compiler/collections/src/all.rs +++ b/compiler/collections/src/all.rs @@ -93,3 +93,44 @@ where map } + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub struct Index(usize); + +impl Index { + pub const FIRST: Self = Index(0); + + pub fn zero_based(i: usize) -> Self { + Index(i) + } + + pub fn to_zero_based(self) -> usize { + self.0 + } + + pub fn one_based(i: usize) -> Self { + Index(i - 1) + } + + pub fn ordinal(self) -> std::string::String { + int_to_ordinal(self.0 + 1) + } +} + +fn int_to_ordinal(number: usize) -> std::string::String { + // NOTE: one-based + let remainder10 = number % 10; + let remainder100 = number % 100; + + let ending = match remainder100 { + 11..=13 => "th", + _ => match remainder10 { + 1 => "st", + 2 => "nd", + 3 => "rd", + _ => "th", + }, + }; + + format!("{}{}", number, ending) +} diff --git a/compiler/constrain/src/builtins.rs b/compiler/constrain/src/builtins.rs index f4e093339f..8a4f2731df 100644 --- a/compiler/constrain/src/builtins.rs +++ b/compiler/constrain/src/builtins.rs @@ -5,6 +5,7 @@ use roc_collections::all::SendMap; use roc_module::symbol::Symbol; use roc_region::all::Region; use roc_types::subs::Variable; +use roc_types::types::Category; use roc_types::types::Reason; use roc_types::types::Type::{self, *}; @@ -17,8 +18,8 @@ pub fn int_literal(num_var: Variable, expected: Expected, region: Region) exists( vec![num_var], And(vec![ - Eq(num_type.clone(), expected_literal, region), - Eq(num_type, expected, region), + Eq(num_type.clone(), expected_literal, Category::Int, region), + Eq(num_type, expected, Category::Int, region), ]), ) } @@ -32,8 +33,8 @@ pub fn float_literal(num_var: Variable, expected: Expected, region: Region exists( vec![num_var], And(vec![ - Eq(num_type.clone(), expected_literal, region), - Eq(num_type, expected, region), + Eq(num_type.clone(), expected_literal, Category::Float, region), + Eq(num_type, expected, Category::Float, region), ]), ) } diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index c0fd8a6263..3626bb9775 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -7,16 +7,16 @@ use roc_can::def::{Declaration, Def}; use roc_can::expected::Expected::{self, *}; use roc_can::expected::PExpected; use roc_can::expr::Expr::{self, *}; -use roc_can::expr::Field; +use roc_can::expr::{Field, WhenBranch}; use roc_can::pattern::Pattern; -use roc_collections::all::{ImMap, SendMap}; -use roc_module::ident::{Lowercase, TagName}; +use roc_collections::all::{ImMap, Index, SendMap}; +use roc_module::ident::Lowercase; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; use roc_types::subs::Variable; use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::Type::{self, *}; -use roc_types::types::{Alias, PReason, Reason}; +use roc_types::types::{Alias, Category, PReason, Reason}; /// This is for constraining Defs #[derive(Default, Debug)] @@ -80,9 +80,18 @@ pub fn constrain_expr( ) -> Constraint { match expr { Int(var, _) => int_literal(*var, expected, region), + Num(var, _) => exists( + vec![*var], + Eq( + Type::Apply(Symbol::NUM_NUM, vec![Type::Variable(*var)]), + expected, + Category::Num, + region, + ), + ), Float(var, _) => float_literal(*var, expected, region), EmptyRecord => constrain_empty_record(region, expected), - Expr::Record(stored_var, fields) => { + Expr::Record { record_var, fields } => { if fields.is_empty() { constrain_empty_record(region, expected) } else { @@ -113,13 +122,18 @@ pub fn constrain_expr( // could all share? Box::new(Type::EmptyRec), ); - let record_con = Eq(record_type, expected.clone(), region); + let record_con = Eq(record_type, expected.clone(), Category::Record, region); constraints.push(record_con); // variable to store in the AST - let stored_con = Eq(Type::Variable(*stored_var), expected, region); + let stored_con = Eq( + Type::Variable(*record_var), + expected, + Category::Storage, + region, + ); - field_vars.push(*stored_var); + field_vars.push(*record_var); constraints.push(stored_con); exists(field_vars, And(constraints)) @@ -135,19 +149,29 @@ pub fn constrain_expr( let mut vars = Vec::with_capacity(updates.len() + 2); let mut cons = Vec::with_capacity(updates.len() + 1); for (field_name, Field { var, loc_expr, .. }) in updates.clone() { - let (var, tipe, con) = - constrain_field_update(env, var, region, field_name.clone(), &loc_expr); + let (var, tipe, con) = constrain_field_update( + env, + var, + loc_expr.region, + field_name.clone(), + &loc_expr, + ); fields.insert(field_name, tipe); vars.push(var); cons.push(con); } - let fields_type = Type::Record(fields.clone(), Box::new(Type::Variable(*ext_var))); + let fields_type = Type::Record(fields, Box::new(Type::Variable(*ext_var))); let record_type = Type::Variable(*record_var); // NOTE from elm compiler: fields_type is separate so that Error propagates better - let fields_con = Eq(record_type.clone(), NoExpectation(fields_type), region); - let record_con = Eq(record_type.clone(), expected, region); + let fields_con = Eq( + record_type.clone(), + NoExpectation(fields_type), + Category::Record, + region, + ); + let record_con = Eq(record_type.clone(), expected, Category::Record, region); vars.push(*record_var); vars.push(*ext_var); @@ -155,20 +179,27 @@ pub fn constrain_expr( let con = Lookup( *symbol, ForReason( - Reason::RecordUpdateKeys(*symbol, fields), + Reason::RecordUpdateKeys( + *symbol, + updates + .iter() + .map(|(key, field)| (key.clone(), field.region)) + .collect(), + ), record_type, region, ), region, ); - cons.push(con); - cons.push(fields_con); - cons.push(record_con); + // ensure constraints are solved in this order, gives better errors + cons.insert(0, fields_con); + cons.insert(1, con); + cons.insert(2, record_con); exists(vars, And(cons)) } - Str(_) | BlockStr(_) => Eq(str_type(), expected, region), + Str(_) | BlockStr(_) => Eq(str_type(), expected, Category::Str, region), List { elem_var, loc_elems, @@ -177,22 +208,32 @@ pub fn constrain_expr( if loc_elems.is_empty() { exists( vec![*elem_var], - Eq(empty_list_type(*elem_var), expected, region), + Eq(empty_list_type(*elem_var), expected, Category::List, region), ) } else { let list_elem_type = Type::Variable(*elem_var); let mut constraints = Vec::with_capacity(1 + loc_elems.len()); - for loc_elem in loc_elems { - let elem_expected = - ForReason(Reason::ElemInList, list_elem_type.clone(), region); + for (index, loc_elem) in loc_elems.iter().enumerate() { + let elem_expected = ForReason( + Reason::ElemInList { + index: Index::zero_based(index), + }, + list_elem_type.clone(), + loc_elem.region, + ); let constraint = constrain_expr(env, loc_elem.region, &loc_elem.value, elem_expected); constraints.push(constraint); } - constraints.push(Eq(list_type(list_elem_type), expected, region)); + constraints.push(Eq( + list_type(list_elem_type), + expected, + Category::List, + region, + )); exists(vec![*elem_var], And(constraints)) } @@ -201,11 +242,18 @@ pub fn constrain_expr( let (fn_var, loc_fn, ret_var) = &**boxed; // The expression that evaluates to the function being called, e.g. `foo` in // (foo) bar baz + let opt_symbol = if let Var(symbol) = loc_fn.value { + Some(symbol) + } else { + None + }; + let fn_type = Variable(*fn_var); let fn_region = loc_fn.region; let fn_expected = NoExpectation(fn_type.clone()); - // TODO look up the name and use NamedFnArg if possible. - let fn_reason = Reason::AnonymousFnCall { + + let fn_reason = Reason::FnCall { + name: opt_symbol, arity: loc_args.len() as u8, }; @@ -226,9 +274,10 @@ pub fn constrain_expr( for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() { let region = loc_arg.region; let arg_type = Variable(*arg_var); - // TODO look up the name and use NamedFnArg if possible. - let reason = Reason::AnonymousFnArg { - arg_index: index as u8, + + let reason = Reason::FnArg { + name: opt_symbol, + arg_index: Index::zero_based(index), }; let expected_arg = ForReason(reason, arg_type.clone(), region); let arg_con = constrain_expr(env, loc_arg.region, &loc_arg.value, expected_arg); @@ -244,13 +293,15 @@ pub fn constrain_expr( region, ); + let category = Category::CallResult(opt_symbol); + exists( vars, And(vec![ fn_con, - Eq(fn_type, expected_fn_type, fn_region), + Eq(fn_type, expected_fn_type, category.clone(), fn_region), And(arg_cons), - Eq(ret_type, expected, region), + Eq(ret_type, expected, category, region), ]), ) } @@ -305,9 +356,14 @@ pub fn constrain_expr( ret_constraint, })), // "the closure's type is equal to expected type" - Eq(fn_type.clone(), expected, region), + Eq(fn_type.clone(), expected, Category::Lambda, region), // "fn_var is equal to the closure's type" - fn_var is used in code gen - Eq(Type::Variable(*fn_var), NoExpectation(fn_type), region), + Eq( + Type::Variable(*fn_var), + NoExpectation(fn_type), + Category::Storage, + region, + ), ]), ) } @@ -318,25 +374,33 @@ pub fn constrain_expr( branches, final_else, } => { - // TODO use Bool alias here, so we don't allocate this type every time - let bool_type = Type::TagUnion( - vec![ - (TagName::Global("True".into()), vec![]), - (TagName::Global("False".into()), vec![]), - ], - Box::new(Type::EmptyTagUnion), + let expect_bool = |region| { + let bool_type = Type::Variable(Variable::BOOL); + Expected::ForReason(Reason::IfCondition, bool_type, region) + }; + let mut branch_cons = Vec::with_capacity(2 * branches.len() + 3); + + // TODO why does this cond var exist? is it for error messages? + let first_cond_region = branches[0].0.region; + let cond_var_is_bool_con = Eq( + Type::Variable(*cond_var), + expect_bool(first_cond_region), + Category::If, + first_cond_region, ); - let expect_bool = Expected::ForReason(Reason::IfCondition, bool_type, region); - let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); + + branch_cons.push(cond_var_is_bool_con); match expected { FromAnnotation(name, arity, _, tipe) => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, loc_cond.region, + &loc_cond.value, + expect_bool(loc_cond.region), ); + let then_con = constrain_expr( env, loc_body.region, @@ -344,7 +408,10 @@ pub fn constrain_expr( FromAnnotation( name.clone(), arity, - AnnotationSource::TypedIfBranch(index + 1), + AnnotationSource::TypedIfBranch { + index: Index::zero_based(index), + num_branches: branches.len(), + }, tipe.clone(), ), ); @@ -359,12 +426,20 @@ pub fn constrain_expr( FromAnnotation( name, arity, - AnnotationSource::TypedIfBranch(branches.len() + 1), + AnnotationSource::TypedIfBranch { + index: Index::zero_based(branches.len()), + num_branches: branches.len(), + }, tipe.clone(), ), ); - let ast_con = Eq(Type::Variable(*branch_var), NoExpectation(tipe), region); + let ast_con = Eq( + Type::Variable(*branch_var), + NoExpectation(tipe), + Category::Storage, + region, + ); branch_cons.push(ast_con); branch_cons.push(else_con); @@ -373,19 +448,24 @@ pub fn constrain_expr( } _ => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, loc_cond.region, + &loc_cond.value, + expect_bool(loc_cond.region), ); + let then_con = constrain_expr( env, loc_body.region, &loc_body.value, ForReason( - Reason::IfBranch { index: index + 1 }, + Reason::IfBranch { + index: Index::zero_based(index), + total_branches: branches.len(), + }, Type::Variable(*branch_var), - region, + loc_body.region, ), ); @@ -398,14 +478,20 @@ pub fn constrain_expr( &final_else.value, ForReason( Reason::IfBranch { - index: branches.len() + 1, + index: Index::zero_based(branches.len()), + total_branches: branches.len() + 1, }, Type::Variable(*branch_var), - region, + final_else.region, ), ); - branch_cons.push(Eq(Type::Variable(*branch_var), expected, region)); + branch_cons.push(Eq( + Type::Variable(*branch_var), + expected, + Category::Storage, + region, + )); branch_cons.push(else_con); exists(vec![*cond_var, *branch_var], And(branch_cons)) @@ -417,6 +503,7 @@ pub fn constrain_expr( expr_var, loc_cond, branches, + .. } => { // Infer the condition expression's type. let cond_var = *cond_var; @@ -434,24 +521,35 @@ pub fn constrain_expr( match &expected { FromAnnotation(name, arity, _, typ) => { // record the type of the whole expression in the AST - let ast_con = Eq(Type::Variable(*expr_var), expected.clone(), region); + let ast_con = Eq( + Type::Variable(*expr_var), + expected.clone(), + Category::Storage, + region, + ); constraints.push(ast_con); - for (index, (loc_when_pattern, loc_expr)) in branches.iter().enumerate() { + for (index, when_branch) in branches.iter().enumerate() { + let pattern_region = + Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); + let branch_con = constrain_when_branch( env, - region, - &loc_when_pattern, - loc_expr, + when_branch.value.region, + when_branch, PExpected::ForReason( - PReason::WhenMatch { index }, + PReason::WhenMatch { + index: Index::zero_based(index), + }, cond_type.clone(), - region, + pattern_region, ), FromAnnotation( name.clone(), *arity, - TypedWhenBranch(index), + TypedWhenBranch { + index: Index::zero_based(index), + }, typ.clone(), ), ); @@ -464,18 +562,27 @@ pub fn constrain_expr( let branch_type = Variable(*expr_var); let mut branch_cons = Vec::with_capacity(branches.len()); - for (index, (loc_when_pattern, loc_expr)) in branches.iter().enumerate() { + for (index, when_branch) in branches.iter().enumerate() { + let pattern_region = + Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( env, region, - &loc_when_pattern, - loc_expr, + when_branch, PExpected::ForReason( - PReason::WhenMatch { index }, + PReason::WhenMatch { + index: Index::zero_based(index), + }, cond_type.clone(), - region, + pattern_region, + ), + ForReason( + Reason::WhenBranch { + index: Index::zero_based(index), + }, + branch_type.clone(), + when_branch.value.region, ), - ForReason(Reason::WhenBranch { index }, branch_type.clone(), region), ); branch_cons.push(branch_con); @@ -488,7 +595,7 @@ pub fn constrain_expr( And(branch_cons), // The return type of each branch must equal // the return type of the entire when-expression. - Eq(branch_type, expected, region), + Eq(branch_type, expected, Category::When, region), ])); } } @@ -501,6 +608,7 @@ pub fn constrain_expr( exists(vec![cond_var, *expr_var], And(constraints)) } Access { + record_var, ext_var, field_var, loc_expr, @@ -519,6 +627,15 @@ pub fn constrain_expr( let record_type = Type::Record(rec_field_types, Box::new(ext_type)); let record_expected = Expected::NoExpectation(record_type); + let category = Category::Access(field.clone()); + + let record_con = Eq( + Type::Variable(*record_var), + record_expected.clone(), + category.clone(), + region, + ); + let constraint = constrain_expr( &Env { home: env.home, @@ -530,12 +647,17 @@ pub fn constrain_expr( ); exists( - vec![field_var, ext_var], - And(vec![constraint, Eq(field_type, expected, region)]), + vec![*record_var, field_var, ext_var], + And(vec![ + constraint, + Eq(field_type, expected, category, region), + record_con, + ]), ) } Accessor { field, + record_var, ext_var, field_var, } => { @@ -549,13 +671,27 @@ pub fn constrain_expr( field_types.insert(label, field_type.clone()); let record_type = Type::Record(field_types, Box::new(ext_type)); + let category = Category::Accessor(field.clone()); + + let record_expected = Expected::NoExpectation(record_type.clone()); + let record_con = Eq( + Type::Variable(*record_var), + record_expected, + category.clone(), + region, + ); + exists( - vec![field_var, ext_var], - Eq( - Type::Function(vec![record_type], Box::new(field_type)), - expected, - region, - ), + vec![*record_var, field_var, ext_var], + And(vec![ + Eq( + Type::Function(vec![record_type], Box::new(field_type)), + expected, + category, + region, + ), + record_con, + ]), ) } LetRec(defs, loc_ret, var, aliases) => { @@ -568,7 +704,12 @@ pub fn constrain_expr( constrain_recursive_defs(env, defs, body_con), // Record the type of tne entire def-expression in the variable. // Code gen will need that later! - Eq(Type::Variable(*var), expected, loc_ret.region), + Eq( + Type::Variable(*var), + expected, + Category::Storage, + loc_ret.region, + ), ]), ) } @@ -582,7 +723,12 @@ pub fn constrain_expr( constrain_def(env, def, body_con), // Record the type of tne entire def-expression in the variable. // Code gen will need that later! - Eq(Type::Variable(*var), expected, loc_ret.region), + Eq( + Type::Variable(*var), + expected, + Category::Storage, + loc_ret.region, + ), ]), ) } @@ -615,9 +761,15 @@ pub fn constrain_expr( Box::new(Type::Variable(*ext_var)), ), expected.clone(), + Category::TagApply(name.clone()), + region, + ); + let ast_con = Eq( + Type::Variable(*variant_var), + expected, + Category::Storage, region, ); - let ast_con = Eq(Type::Variable(*variant_var), expected, region); vars.push(*variant_var); vars.push(*ext_var); @@ -634,12 +786,11 @@ pub fn constrain_expr( fn constrain_when_branch( env: &Env, region: Region, - loc_pattern: &Located, - loc_expr: &Located, + when_branch: &WhenBranch, pattern_expected: PExpected, expr_expected: Expected, ) -> Constraint { - let ret_constraint = constrain_expr(env, region, &loc_expr.value, expr_expected); + let ret_constraint = constrain_expr(env, region, &when_branch.value.value, expr_expected); let mut state = PatternState { headers: SendMap::default(), @@ -647,21 +798,56 @@ fn constrain_when_branch( constraints: Vec::with_capacity(1), }; - constrain_pattern( - &loc_pattern.value, - loc_pattern.region, - pattern_expected, - &mut state, - ); + // TODO ensure this is correct + // TODO investigate for error messages, is it better to unify all branches with a variable, + // then unify that variable with the expectation? + for loc_pattern in &when_branch.patterns { + constrain_pattern( + &loc_pattern.value, + loc_pattern.region, + pattern_expected.clone(), + &mut state, + ); + } - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - def_aliases: SendMap::default(), - defs_constraint: Constraint::And(state.constraints), - ret_constraint, - })) + if let Some(loc_guard) = &when_branch.guard { + let guard_constraint = constrain_expr( + env, + region, + &loc_guard.value, + Expected::ForReason( + Reason::WhenGuard, + Type::Variable(Variable::BOOL), + loc_guard.region, + ), + ); + + // must introduce the headers from the pattern before constraining the guard + Constraint::Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: state.vars, + def_types: state.headers, + def_aliases: SendMap::default(), + defs_constraint: Constraint::And(state.constraints), + ret_constraint: Constraint::Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: Vec::new(), + def_types: SendMap::default(), + def_aliases: SendMap::default(), + defs_constraint: guard_constraint, + ret_constraint, + })), + })) + } else { + Constraint::Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: state.vars, + def_types: state.headers, + def_aliases: SendMap::default(), + defs_constraint: Constraint::And(state.constraints), + ret_constraint, + })) + } } fn constrain_field(env: &Env, field_var: Variable, loc_expr: &Located) -> (Type, Constraint) { @@ -674,7 +860,7 @@ fn constrain_field(env: &Env, field_var: Variable, loc_expr: &Located) -> #[inline(always)] fn constrain_empty_record(region: Region, expected: Expected) -> Constraint { - Eq(EmptyRec, expected, region) + Eq(EmptyRec, expected, Category::Record, region) } /// Constrain top-level module declarations @@ -755,16 +941,16 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let mut new_rigids = Vec::new(); let expr_con = match &def.annotation { - Some((annotation, introduced_vars, ann_def_aliases)) => { - def_aliases = ann_def_aliases.clone(); + Some(annotation) => { + def_aliases = annotation.aliases.clone(); - let arity = annotation.arity(); + let arity = annotation.signature.arity(); let rigids = &env.rigids; let mut ftv = rigids.clone(); - let annotation = instantiate_rigids( - annotation, - &introduced_vars, + let signature = instantiate_rigids( + &annotation.signature, + &annotation.introduced_variables, &mut new_rigids, &mut ftv, &def.loc_pattern, @@ -774,13 +960,16 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let annotation_expected = FromAnnotation( def.loc_pattern.clone(), arity, - AnnotationSource::TypedBody, - annotation, + AnnotationSource::TypedBody { + region: annotation.region, + }, + signature, ); pattern_state.constraints.push(Eq( expr_type, annotation_expected.clone(), + Category::Storage, // TODO proper region Region::zero(), )); @@ -925,17 +1114,17 @@ pub fn rec_defs_help( flex_info.def_types.extend(pattern_state.headers); } - Some((annotation, introduced_vars, ann_def_aliases)) => { - for (symbol, alias) in ann_def_aliases.clone() { + Some(annotation) => { + for (symbol, alias) in annotation.aliases.clone() { def_aliases.insert(symbol, alias); } - let arity = annotation.arity(); + let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); - let annotation = instantiate_rigids( - annotation, - &introduced_vars, + let signature = instantiate_rigids( + &annotation.signature, + &annotation.introduced_variables, &mut new_rigids, &mut ftv, &def.loc_pattern, @@ -945,8 +1134,10 @@ pub fn rec_defs_help( let annotation_expected = FromAnnotation( def.loc_pattern.clone(), arity, - AnnotationSource::TypedBody, - annotation.clone(), + AnnotationSource::TypedBody { + region: annotation.region, + }, + signature.clone(), ); let expr_con = constrain_expr( &Env { @@ -962,6 +1153,7 @@ pub fn rec_defs_help( rigid_info.constraints.push(Eq( expr_type, annotation_expected.clone(), + Category::Storage, def.loc_expr.region, )); diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 5b6991bc9e..863abff1d6 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -277,9 +277,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &VarSt Type::Alias(*symbol, type_variables, Box::new(actual)) } - Error => { - panic!("TODO convert from SolvedType::Error to Type somehow"); - } + Error => Type::Erroneous(roc_types::types::Problem::SolvedTypeError), Erroneous(problem) => Type::Erroneous(problem.clone()), } } diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 5008871644..332104561e 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -3,12 +3,12 @@ use roc_can::constraint::Constraint; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; use roc_can::pattern::RecordDestruct; -use roc_collections::all::SendMap; +use roc_collections::all::{Index, SendMap}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::subs::Variable; -use roc_types::types::{PatternCategory, Type}; +use roc_types::types::{Category, PReason, PatternCategory, Type}; pub struct PatternState { pub headers: SendMap>, @@ -53,11 +53,12 @@ fn headers_from_annotation_help( Underscore | Shadowed(_, _) | UnsupportedPattern(_) + | NumLiteral(_, _) | IntLiteral(_) | FloatLiteral(_) | StrLiteral(_) => true, - RecordDestructure(_, destructs) => match annotation.value.shallow_dealias() { + RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { Type::Record(fields, _) => { for destruct in destructs { // NOTE ignores the .guard field. @@ -76,7 +77,11 @@ fn headers_from_annotation_help( _ => false, }, - AppliedTag(_, tag_name, arguments) => match annotation.value.shallow_dealias() { + AppliedTag { + tag_name, + arguments, + .. + } => match annotation.value.shallow_dealias() { Type::TagUnion(tags, _) => { if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) { if !arguments.len() == arg_types.len() { @@ -112,9 +117,10 @@ pub fn constrain_pattern( state: &mut PatternState, ) { match pattern { - Underscore | UnsupportedPattern(_) => { + Underscore | UnsupportedPattern(_) | Shadowed(_, _) => { // Neither the _ pattern nor erroneous ones add any constraints. } + Identifier(symbol) => { state.headers.insert( symbol.clone(), @@ -124,10 +130,22 @@ pub fn constrain_pattern( }, ); } + + NumLiteral(var, _) => { + state.vars.push(*var); + + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::Num, + builtins::builtin_type(Symbol::NUM_NUM, vec![Type::Variable(*var)]), + expected, + )); + } + IntLiteral(_) => { state.constraints.push(Constraint::Pattern( region, - PatternCategory::Int, + PatternCategory::Float, builtins::builtin_type(Symbol::INT_INT, vec![]), expected, )); @@ -151,7 +169,12 @@ pub fn constrain_pattern( )); } - RecordDestructure(ext_var, patterns) => { + RecordDestructure { + whole_var, + ext_var, + destructs, + } => { + state.vars.push(*whole_var); state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); @@ -166,7 +189,7 @@ pub fn constrain_pattern( guard, }, .. - } in patterns + } in destructs { let pat_type = Type::Variable(*var); let expected = PExpected::NoExpectation(pat_type.clone()); @@ -180,10 +203,15 @@ pub fn constrain_pattern( field_types.insert(label.clone(), pat_type.clone()); if let Some((guard_var, loc_guard)) = guard { - state.constraints.push(Constraint::Eq( - Type::Variable(*guard_var), - Expected::NoExpectation(pat_type.clone()), + state.constraints.push(Constraint::Pattern( region, + PatternCategory::PatternGuard, + Type::Variable(*guard_var), + PExpected::ForReason( + PReason::PatternGuard, + pat_type.clone(), + loc_guard.region, + ), )); state.vars.push(*guard_var); @@ -194,38 +222,69 @@ pub fn constrain_pattern( } let record_type = Type::Record(field_types, Box::new(ext_type)); - let record_con = - Constraint::Pattern(region, PatternCategory::Record, record_type, expected); + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(record_type), + Category::Storage, + region, + ); + + let record_con = Constraint::Pattern( + region, + PatternCategory::Record, + Type::Variable(*whole_var), + expected, + ); + + state.constraints.push(whole_con); state.constraints.push(record_con); } - AppliedTag(ext_var, tag_name, patterns) => { - let mut argument_types = Vec::with_capacity(patterns.len()); - for (pattern_var, loc_pattern) in patterns { + AppliedTag { + whole_var, + ext_var, + tag_name, + arguments, + } => { + let mut argument_types = Vec::with_capacity(arguments.len()); + for (index, (pattern_var, loc_pattern)) in arguments.iter().enumerate() { state.vars.push(*pattern_var); let pattern_type = Type::Variable(*pattern_var); argument_types.push(pattern_type.clone()); - let expected = PExpected::NoExpectation(pattern_type); + let expected = PExpected::ForReason( + PReason::TagArg { + tag_name: tag_name.clone(), + index: Index::zero_based(index), + }, + pattern_type, + region, + ); constrain_pattern(&loc_pattern.value, loc_pattern.region, expected, state); } + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(Type::TagUnion( + vec![(tag_name.clone(), argument_types)], + Box::new(Type::Variable(*ext_var)), + )), + Category::Storage, + region, + ); + let tag_con = Constraint::Pattern( region, PatternCategory::Ctor(tag_name.clone()), - Type::TagUnion( - vec![(tag_name.clone(), argument_types)], - Box::new(Type::Variable(*ext_var)), - ), + Type::Variable(*whole_var), expected, ); + state.vars.push(*whole_var); state.vars.push(*ext_var); + state.constraints.push(whole_con); state.constraints.push(tag_con); } - Shadowed(_, _) => { - panic!("TODO constrain Shadowed pattern"); - } } } diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index e0d3556d65..0f023a1363 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -4,17 +4,17 @@ use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::def::{Declaration, Def}; use roc_can::expected::{Expected, PExpected}; -use roc_can::expr::{Expr, Field}; +use roc_can::expr::{Expr, Field, WhenBranch}; use roc_can::pattern::{Pattern, RecordDestruct}; -use roc_collections::all::{ImMap, ImSet, SendMap}; -use roc_module::ident::{Ident, Lowercase, TagName}; +use roc_collections::all::{ImMap, ImSet, Index, SendMap}; +use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; use roc_types::boolean_algebra::{Atom, Bool}; use roc_types::subs::{VarStore, Variable}; use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::Type::{self, *}; -use roc_types::types::{Alias, PReason, Reason}; +use roc_types::types::{Alias, Category, PReason, Reason}; use roc_uniq::builtins::{attr_type, empty_list_type, list_type, str_type}; use roc_uniq::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage}; @@ -143,6 +143,8 @@ fn constrain_pattern( use roc_can::pattern::Pattern::*; use roc_types::types::PatternCategory; + let region = pattern.region; + match &pattern.value { Identifier(symbol) => { state.headers.insert( @@ -154,6 +156,14 @@ fn constrain_pattern( ); } + NumLiteral(inner_var, _) => { + let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); + state.constraints.push(exists( + vec![val_uvar, num_uvar, num_var, *inner_var], + Constraint::Pattern(pattern.region, PatternCategory::Num, num_type, expected), + )); + } + IntLiteral(_) => { let (num_uvar, int_uvar, num_type) = unique_int(var_store); state.constraints.push(exists( @@ -182,10 +192,15 @@ fn constrain_pattern( )); } - RecordDestructure(ext_var, patterns) => { + RecordDestructure { + whole_var, + ext_var, + destructs, + } => { // TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness - let mut pattern_uniq_vars = Vec::with_capacity(patterns.len()); + let mut pattern_uniq_vars = Vec::with_capacity(destructs.len()); + state.vars.push(*whole_var); state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); @@ -199,7 +214,7 @@ fn constrain_pattern( guard, }, .. - } in patterns + } in destructs { let pat_uniq_var = var_store.fresh(); pattern_uniq_vars.push(pat_uniq_var); @@ -217,10 +232,11 @@ fn constrain_pattern( field_types.insert(label.clone(), pat_type.clone()); if let Some((guard_var, loc_guard)) = guard { - state.constraints.push(Eq( - Type::Variable(*guard_var), - Expected::NoExpectation(pat_type.clone()), + state.constraints.push(Constraint::Pattern( pattern.region, + PatternCategory::PatternGuard, + Type::Variable(*guard_var), + PExpected::NoExpectation(pat_type.clone()), )); state.vars.push(*guard_var); constrain_pattern(var_store, state, loc_guard, expected); @@ -243,22 +259,36 @@ fn constrain_pattern( record_uniq_type, Type::Record(field_types, Box::new(ext_type)), ); + + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(record_type), + Category::Storage, + region, + ); + let record_con = Constraint::Pattern( - pattern.region, + region, PatternCategory::Record, - record_type, + Type::Variable(*whole_var), expected, ); + state.constraints.push(whole_con); state.constraints.push(record_con); } - AppliedTag(ext_var, symbol, patterns) => { + AppliedTag { + whole_var, + ext_var, + tag_name, + arguments, + } => { // TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness - let mut argument_types = Vec::with_capacity(patterns.len()); - let mut pattern_uniq_vars = Vec::with_capacity(patterns.len()); + let mut argument_types = Vec::with_capacity(arguments.len()); + let mut pattern_uniq_vars = Vec::with_capacity(arguments.len()); - for (pattern_var, loc_pattern) in patterns { + for (pattern_var, loc_pattern) in arguments { state.vars.push(*pattern_var); let pat_uniq_var = var_store.fresh(); @@ -284,19 +314,29 @@ fn constrain_pattern( let union_type = attr_type( tag_union_uniq_type, Type::TagUnion( - vec![(symbol.clone(), argument_types)], + vec![(tag_name.clone(), argument_types)], Box::new(Type::Variable(*ext_var)), ), ); + let whole_con = Constraint::Eq( + Type::Variable(*whole_var), + Expected::NoExpectation(union_type), + Category::Storage, + region, + ); + let tag_con = Constraint::Pattern( - pattern.region, - PatternCategory::Ctor(symbol.clone()), - union_type, + region, + PatternCategory::Ctor(tag_name.clone()), + Type::Variable(*whole_var), expected, ); + state.vars.push(*whole_var); state.vars.push(*ext_var); + + state.constraints.push(whole_con); state.constraints.push(tag_con); } @@ -306,6 +346,23 @@ fn constrain_pattern( } } +fn unique_unbound_num( + inner_var: Variable, + var_store: &VarStore, +) -> (Variable, Variable, Type, Variable) { + let num_var = var_store.fresh(); + let num_uvar = var_store.fresh(); + let val_uvar = var_store.fresh(); + + let val_type = Type::Variable(inner_var); + let val_utype = attr_type(Bool::variable(val_uvar), val_type); + + let num_utype = Type::Apply(Symbol::NUM_NUM, vec![val_utype]); + let num_type = attr_type(Bool::variable(num_uvar), num_utype); + + (num_uvar, val_uvar, num_type, num_var) +} + fn unique_num(var_store: &VarStore, symbol: Symbol) -> (Variable, Variable, Type) { let num_uvar = var_store.fresh(); let val_uvar = var_store.fresh(); @@ -339,6 +396,23 @@ pub fn constrain_expr( pub use roc_can::expr::Expr::*; match expr { + Num(inner_var, _) => { + let var = var_store.fresh(); + let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); + + exists( + vec![var, *inner_var, val_uvar, num_uvar, num_var], + And(vec![ + Eq( + Type::Variable(var), + Expected::ForReason(Reason::NumLiteral, num_type, region), + Category::Num, + region, + ), + Eq(Type::Variable(var), expected, Category::Num, region), + ]), + ) + } Int(var, _) => { let (num_uvar, int_uvar, num_type) = unique_int(var_store); @@ -348,9 +422,10 @@ pub fn constrain_expr( Eq( Type::Variable(*var), Expected::ForReason(Reason::IntLiteral, num_type, region), + Category::Int, region, ), - Eq(Type::Variable(*var), expected, region), + Eq(Type::Variable(*var), expected, Category::Int, region), ]), ) } @@ -363,9 +438,10 @@ pub fn constrain_expr( Eq( Type::Variable(*var), Expected::ForReason(Reason::FloatLiteral, num_type, region), + Category::Float, region, ), - Eq(Type::Variable(*var), expected, region), + Eq(Type::Variable(*var), expected, Category::Float, region), ]), ) } @@ -373,7 +449,10 @@ pub fn constrain_expr( let uniq_type = var_store.fresh(); let inferred = str_type(Bool::variable(uniq_type)); - exists(vec![uniq_type], Eq(inferred, expected, region)) + exists( + vec![uniq_type], + Eq(inferred, expected, Category::Str, region), + ) } EmptyRecord => { let uniq_type = var_store.fresh(); @@ -383,16 +462,17 @@ pub fn constrain_expr( Eq( attr_type(Bool::variable(uniq_type), EmptyRec), expected, + Category::Record, region, ), ) } - Record(variable, fields) => { + Record { record_var, fields } => { // NOTE: canonicalization guarantees at least one field // zero fields generates an EmptyRecord let mut field_types = SendMap::default(); let mut field_vars = Vec::with_capacity(fields.len()); - field_vars.push(*variable); + field_vars.push(*record_var); // Constraints need capacity for each field + 1 for the record itself + 1 for ext let mut constraints = Vec::with_capacity(2 + fields.len()); @@ -430,8 +510,13 @@ pub fn constrain_expr( Box::new(Type::EmptyRec), ), ); - let record_con = Eq(record_type, expected.clone(), region); - let ext_con = Eq(Type::Variable(*variable), expected, region); + let record_con = Eq(record_type, expected.clone(), Category::Record, region); + let ext_con = Eq( + Type::Variable(*record_var), + expected, + Category::Record, + region, + ); constraints.push(record_con); constraints.push(ext_con); @@ -474,8 +559,18 @@ pub fn constrain_expr( ), ); - let union_con = Eq(union_type, expected.clone(), region); - let ast_con = Eq(Type::Variable(*variant_var), expected, region); + let union_con = Eq( + union_type, + expected.clone(), + Category::TagApply(name.clone()), + region, + ); + let ast_con = Eq( + Type::Variable(*variant_var), + expected, + Category::TagApply(name.clone()), + region, + ); vars.push(uniq_var); vars.push(*variant_var); @@ -492,15 +587,23 @@ pub fn constrain_expr( let uniq_var = var_store.fresh(); if loc_elems.is_empty() { let inferred = empty_list_type(Bool::variable(uniq_var), *elem_var); - exists(vec![*elem_var, uniq_var], Eq(inferred, expected, region)) + exists( + vec![*elem_var, uniq_var], + Eq(inferred, expected, Category::List, region), + ) } else { // constrain `expected ~ List a` and that all elements `~ a`. let entry_type = Type::Variable(*elem_var); let mut constraints = Vec::with_capacity(1 + loc_elems.len()); - for loc_elem in loc_elems.iter() { - let elem_expected = - Expected::ForReason(Reason::ElemInList, entry_type.clone(), region); + for (index, loc_elem) in loc_elems.iter().enumerate() { + let elem_expected = Expected::ForReason( + Reason::ElemInList { + index: Index::zero_based(index), + }, + entry_type.clone(), + region, + ); let constraint = constrain_expr( env, var_store, @@ -515,7 +618,7 @@ pub fn constrain_expr( } let inferred = list_type(Bool::variable(uniq_var), entry_type); - constraints.push(Eq(inferred, expected, region)); + constraints.push(Eq(inferred, expected, Category::List, region)); exists(vec![*elem_var, uniq_var], And(constraints)) } @@ -599,11 +702,12 @@ pub fn constrain_expr( ret_constraint, })), // "the closure's type is equal to expected type" - Eq(fn_type.clone(), expected, region), + Eq(fn_type.clone(), expected, Category::Lambda, region), // "fn_var is equal to the closure's type" - fn_var is used in code gen Eq( Type::Variable(*fn_var), Expected::NoExpectation(fn_type), + Category::Lambda, region, ), ]), @@ -617,6 +721,12 @@ pub fn constrain_expr( let fn_expected = Expected::NoExpectation(fn_type.clone()); let fn_region = fn_expr.region; + let opt_symbol = if let Var(symbol) = fn_expr.value { + Some(symbol) + } else { + None + }; + let mut vars = Vec::with_capacity(2 + loc_args.len()); vars.push(*fn_var); @@ -633,8 +743,8 @@ pub fn constrain_expr( fn_expected, ); - // TODO look up the name and use NamedFnArg if possible. - let fn_reason = Reason::AnonymousFnCall { + let fn_reason = Reason::FnCall { + name: opt_symbol, arity: loc_args.len() as u8, }; @@ -645,8 +755,9 @@ pub fn constrain_expr( let region = loc_arg.region; let arg_type = Variable(*arg_var); - let reason = Reason::AnonymousFnArg { - arg_index: index as u8, + let reason = Reason::FnArg { + name: opt_symbol, + arg_index: Index::zero_based(index), }; let expected_arg = Expected::ForReason(reason, arg_type.clone(), region); @@ -680,9 +791,14 @@ pub fn constrain_expr( vars, And(vec![ fn_con, - Eq(fn_type, expected_fn_type, fn_region), + Eq( + fn_type, + expected_fn_type, + Category::CallResult(opt_symbol), + fn_region, + ), And(arg_cons), - Eq(ret_type, expected, region), + Eq(ret_type, expected, Category::CallResult(opt_symbol), region), ]), ) } @@ -716,7 +832,12 @@ pub fn constrain_expr( ), // Record the type of tne entire def-expression in the variable. // Code gen will need that later! - Eq(Type::Variable(*var), expected, loc_ret.region), + Eq( + Type::Variable(*var), + expected, + Category::Storage, + loc_ret.region, + ), ]), ) } @@ -750,7 +871,12 @@ pub fn constrain_expr( ), // Record the type of tne entire def-expression in the variable. // Code gen will need that later! - Eq(Type::Variable(*var), expected, loc_ret.region), + Eq( + Type::Variable(*var), + expected, + Category::Storage, + loc_ret.region, + ), ]), ) } @@ -761,17 +887,26 @@ pub fn constrain_expr( final_else, } => { // TODO use Bool alias here, so we don't allocate this type every time - let bool_type = Type::TagUnion( - vec![ - (TagName::Global("True".into()), vec![]), - (TagName::Global("False".into()), vec![]), - ], - Box::new(Type::EmptyTagUnion), - ); - + let bool_type = Type::Variable(Variable::BOOL); let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2); + // TODO why does this cond var exist? is it for error messages? + let cond_uniq_var = var_store.fresh(); + cond_uniq_vars.push(cond_uniq_var); + let cond_var_is_bool_con = Eq( + Type::Variable(*cond_var), + Expected::ForReason( + Reason::IfCondition, + attr_type(Bool::variable(cond_uniq_var), bool_type.clone()), + region, + ), + Category::If, + Region::zero(), + ); + + branch_cons.push(cond_var_is_bool_con); + match expected { Expected::FromAnnotation(name, arity, _, tipe) => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { @@ -783,11 +918,16 @@ pub fn constrain_expr( ); cond_uniq_vars.push(cond_uniq_var); - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, + var_store, + var_usage, + applied_usage_constraint, loc_cond.region, + &loc_cond.value, + expect_bool, ); + let then_con = constrain_expr( env, var_store, @@ -798,7 +938,10 @@ pub fn constrain_expr( Expected::FromAnnotation( name.clone(), arity, - AnnotationSource::TypedIfBranch(index + 1), + AnnotationSource::TypedIfBranch { + index: Index::zero_based(index), + num_branches: branches.len(), + }, tipe.clone(), ), ); @@ -816,7 +959,10 @@ pub fn constrain_expr( Expected::FromAnnotation( name, arity, - AnnotationSource::TypedIfBranch(branches.len() + 1), + AnnotationSource::TypedIfBranch { + index: Index::zero_based(branches.len()), + num_branches: branches.len(), + }, tipe.clone(), ), ); @@ -824,6 +970,7 @@ pub fn constrain_expr( let ast_con = Eq( Type::Variable(*branch_var), Expected::NoExpectation(tipe), + Category::Storage, region, ); @@ -845,11 +992,16 @@ pub fn constrain_expr( ); cond_uniq_vars.push(cond_uniq_var); - let cond_con = Eq( - Type::Variable(*cond_var), - expect_bool.clone(), + let cond_con = constrain_expr( + env, + var_store, + var_usage, + applied_usage_constraint, loc_cond.region, + &loc_cond.value, + expect_bool, ); + let then_con = constrain_expr( env, var_store, @@ -858,7 +1010,10 @@ pub fn constrain_expr( loc_body.region, &loc_body.value, Expected::ForReason( - Reason::IfBranch { index: index + 1 }, + Reason::IfBranch { + index: Index::zero_based(index), + total_branches: branches.len(), + }, Type::Variable(*branch_var), region, ), @@ -876,14 +1031,20 @@ pub fn constrain_expr( &final_else.value, Expected::ForReason( Reason::IfBranch { - index: branches.len() + 1, + index: Index::zero_based(branches.len()), + total_branches: branches.len(), }, Type::Variable(*branch_var), region, ), ); - branch_cons.push(Eq(Type::Variable(*branch_var), expected, region)); + branch_cons.push(Eq( + Type::Variable(*branch_var), + expected, + Category::If, + region, + )); branch_cons.push(else_con); cond_uniq_vars.push(*cond_var); @@ -898,6 +1059,7 @@ pub fn constrain_expr( expr_var, loc_cond, branches, + .. } => { let cond_var = *cond_var; let cond_type = Variable(cond_var); @@ -916,26 +1078,37 @@ pub fn constrain_expr( match &expected { Expected::FromAnnotation(name, arity, _, typ) => { - constraints.push(Eq(Type::Variable(*expr_var), expected.clone(), region)); + constraints.push(Eq( + Type::Variable(*expr_var), + expected.clone(), + Category::When, + region, + )); + + for (index, when_branch) in branches.iter().enumerate() { + let pattern_region = + Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); - for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() { let branch_con = constrain_when_branch( var_store, var_usage, applied_usage_constraint, env, region, - &loc_pattern, - loc_expr, + when_branch, PExpected::ForReason( - PReason::WhenMatch { index }, + PReason::WhenMatch { + index: Index::zero_based(index), + }, cond_type.clone(), - region, + pattern_region, ), Expected::FromAnnotation( name.clone(), *arity, - TypedWhenBranch(index), + TypedWhenBranch { + index: Index::zero_based(index), + }, typ.clone(), ), ); @@ -952,22 +1125,27 @@ pub fn constrain_expr( let branch_type = Variable(*expr_var); let mut branch_cons = Vec::with_capacity(branches.len()); - for (index, (loc_pattern, loc_expr)) in branches.iter().enumerate() { + for (index, when_branch) in branches.iter().enumerate() { + let pattern_region = + Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( var_store, var_usage, applied_usage_constraint, env, region, - &loc_pattern, - loc_expr, + when_branch, PExpected::ForReason( - PReason::WhenMatch { index }, + PReason::WhenMatch { + index: Index::zero_based(index), + }, cond_type.clone(), - region, + pattern_region, ), Expected::ForReason( - Reason::WhenBranch { index }, + Reason::WhenBranch { + index: Index::zero_based(index), + }, branch_type.clone(), region, ), @@ -982,7 +1160,7 @@ pub fn constrain_expr( And(branch_cons), // The return type of each branch must equal // the return type of the entire case-expression. - Eq(branch_type, expected, region), + Eq(branch_type, expected, Category::When, region), ])) } } @@ -1020,7 +1198,7 @@ pub fn constrain_expr( let fields_type = attr_type( Bool::variable(uniq_var), - Type::Record(fields.clone(), Box::new(Type::Variable(*ext_var))), + Type::Record(fields, Box::new(Type::Variable(*ext_var))), ); let record_type = Type::Variable(*record_var); @@ -1028,9 +1206,10 @@ pub fn constrain_expr( let fields_con = Eq( record_type.clone(), Expected::NoExpectation(fields_type), + Category::Record, region, ); - let record_con = Eq(record_type.clone(), expected, region); + let record_con = Eq(record_type.clone(), expected, Category::Record, region); vars.push(*record_var); vars.push(*ext_var); @@ -1038,7 +1217,13 @@ pub fn constrain_expr( let con = Lookup( *symbol, Expected::ForReason( - Reason::RecordUpdateKeys(*symbol, fields), + Reason::RecordUpdateKeys( + *symbol, + updates + .iter() + .map(|(key, field)| (key.clone(), field.region)) + .collect(), + ), record_type, region, ), @@ -1053,6 +1238,7 @@ pub fn constrain_expr( } Access { + record_var, ext_var, field_var, loc_expr, @@ -1074,7 +1260,16 @@ pub fn constrain_expr( Type::Record(field_types, Box::new(Type::Variable(*ext_var))), ); + let category = Category::Access(field.clone()); + let record_expected = Expected::NoExpectation(record_type); + let record_con = Eq( + Type::Variable(*record_var), + record_expected.clone(), + category.clone(), + region, + ); + let inner_constraint = constrain_expr( env, var_store, @@ -1086,13 +1281,24 @@ pub fn constrain_expr( ); exists( - vec![*field_var, *ext_var, field_uniq_var, record_uniq_var], - And(vec![Eq(field_type, expected, region), inner_constraint]), + vec![ + *record_var, + *field_var, + *ext_var, + field_uniq_var, + record_uniq_var, + ], + And(vec![ + Eq(field_type, expected, category, region), + inner_constraint, + record_con, + ]), ) } Accessor { field, + record_var, field_var, ext_var, } => { @@ -1112,6 +1318,16 @@ pub fn constrain_expr( Type::Record(field_types, Box::new(Type::Variable(*ext_var))), ); + let category = Category::Accessor(field.clone()); + + let record_expected = Expected::NoExpectation(record_type.clone()); + let record_con = Eq( + Type::Variable(*record_var), + record_expected, + category.clone(), + region, + ); + let fn_uniq_var = var_store.fresh(); let fn_type = attr_type( Bool::variable(fn_uniq_var), @@ -1120,13 +1336,14 @@ pub fn constrain_expr( exists( vec![ + *record_var, *field_var, *ext_var, fn_uniq_var, field_uniq_var, record_uniq_var, ], - And(vec![Eq(fn_type, expected, region)]), + And(vec![Eq(fn_type, expected, category, region), record_con]), ) } RuntimeError(_) => True, @@ -1159,10 +1376,11 @@ fn constrain_var( vec![val_var, uniq_var], And(vec![ Lookup(symbol_for_lookup, expected.clone(), region), - Eq(attr_type, expected, region), + Eq(attr_type, expected, Category::Uniqueness, region), Eq( Type::Boolean(uniq_type), Expected::NoExpectation(Type::Boolean(Bool::shared())), + Category::Uniqueness, region, ), ]), @@ -1187,7 +1405,7 @@ fn constrain_var( variables, And(vec![ Lookup(symbol_for_lookup, new_expected, region), - Eq(record_type, expected, region), + Eq(record_type, expected, Category::Uniqueness, region), ]), ) } @@ -1327,15 +1545,15 @@ fn constrain_by_usage_record( // TODO trim down these arguments #[allow(clippy::too_many_arguments)] -#[inline(always)] +// NOTE enabling the inline pragma can blow the stack in debug mode +// #[inline(always)] fn constrain_when_branch( var_store: &VarStore, var_usage: &VarUsage, applied_usage_constraint: &mut ImSet, env: &Env, region: Region, - loc_pattern: &Located, - loc_expr: &Located, + when_branch: &WhenBranch, pattern_expected: PExpected, expr_expected: Expected, ) -> Constraint { @@ -1345,7 +1563,7 @@ fn constrain_when_branch( var_usage, applied_usage_constraint, region, - &loc_expr.value, + &when_branch.value.value, expr_expected, ); @@ -1355,17 +1573,59 @@ fn constrain_when_branch( constraints: Vec::with_capacity(1), }; - // mutates the state, so return value is not used - constrain_pattern(var_store, &mut state, &loc_pattern, pattern_expected); + for loc_pattern in &when_branch.patterns { + // mutates the state, so return value is not used + constrain_pattern( + var_store, + &mut state, + &loc_pattern, + pattern_expected.clone(), + ); + } - Constraint::Let(Box::new(LetConstraint { - rigid_vars: Vec::new(), - flex_vars: state.vars, - def_types: state.headers, - def_aliases: SendMap::default(), - defs_constraint: Constraint::And(state.constraints), - ret_constraint, - })) + if let Some(loc_guard) = &when_branch.guard { + let guard_uniq_var = var_store.fresh(); + + let bool_type = attr_type( + Bool::variable(guard_uniq_var), + Type::Variable(Variable::BOOL), + ); + + let guard_constraint = constrain_expr( + env, + var_store, + var_usage, + applied_usage_constraint, + loc_guard.region, + &loc_guard.value, + Expected::ForReason(Reason::WhenGuard, bool_type, loc_guard.region), + ); + + Constraint::Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: state.vars, + def_types: state.headers, + def_aliases: SendMap::default(), + defs_constraint: Constraint::And(state.constraints), + ret_constraint: Constraint::Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: vec![guard_uniq_var], + def_types: SendMap::default(), + def_aliases: SendMap::default(), + defs_constraint: guard_constraint, + ret_constraint, + })), + })) + } else { + Constraint::Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: state.vars, + def_types: state.headers, + def_aliases: SendMap::default(), + defs_constraint: Constraint::And(state.constraints), + ret_constraint, + })) + } } fn constrain_def_pattern( @@ -1638,15 +1898,15 @@ fn constrain_def( let mut new_rigids = Vec::new(); let expr_con = match &def.annotation { - Some((annotation, introduced_vars, ann_def_aliases)) => { - def_aliases = ann_def_aliases.clone(); - let arity = annotation.arity(); + Some(annotation) => { + def_aliases = annotation.aliases.clone(); + let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); - let annotation = instantiate_rigids( + let signature = instantiate_rigids( var_store, - annotation, - &introduced_vars, + &annotation.signature, + &annotation.introduced_variables, &mut new_rigids, &mut ftv, &def.loc_pattern, @@ -1656,13 +1916,16 @@ fn constrain_def( let annotation_expected = Expected::FromAnnotation( def.loc_pattern.clone(), arity, - AnnotationSource::TypedBody, - annotation, + AnnotationSource::TypedBody { + region: annotation.region, + }, + signature, ); pattern_state.constraints.push(Eq( expr_type, annotation_expected.clone(), + Category::Storage, Region::zero(), )); @@ -1859,16 +2122,16 @@ pub fn rec_defs_help( flex_info.def_types.extend(pattern_state.headers); } - Some((annotation, introduced_vars, ann_def_aliases)) => { - for (symbol, alias) in ann_def_aliases.clone() { + Some(annotation) => { + for (symbol, alias) in annotation.aliases.clone() { def_aliases.insert(symbol, alias); } - let arity = annotation.arity(); + let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); - let annotation = instantiate_rigids( + let signature = instantiate_rigids( var_store, - annotation, - &introduced_vars, + &annotation.signature, + &annotation.introduced_variables, &mut new_rigids, &mut ftv, &def.loc_pattern, @@ -1877,8 +2140,10 @@ pub fn rec_defs_help( let annotation_expected = Expected::FromAnnotation( def.loc_pattern.clone(), arity, - AnnotationSource::TypedBody, - annotation.clone(), + AnnotationSource::TypedBody { + region: annotation.region, + }, + signature.clone(), ); let expr_con = constrain_expr( &Env { @@ -1897,6 +2162,7 @@ pub fn rec_defs_help( rigid_info.constraints.push(Eq( expr_type, annotation_expected.clone(), + Category::Storage, def.loc_expr.region, )); diff --git a/compiler/fmt/src/expr.rs b/compiler/fmt/src/expr.rs index dd5a3d0a71..75b3e44768 100644 --- a/compiler/fmt/src/expr.rs +++ b/compiler/fmt/src/expr.rs @@ -89,7 +89,7 @@ pub fn fmt_expr<'a>( } buf.push_str("\"\"\""); } - Int(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { + Num(string) | Float(string) | GlobalTag(string) | PrivateTag(string) => { buf.push_str(string) } NonBase10Int { @@ -432,7 +432,7 @@ pub fn is_multiline_pattern<'a>(pattern: &'a Pattern<'a>) -> bool { | Pattern::Apply(_, _) | Pattern::RecordDestructure(_) | Pattern::RecordField(_, _) - | Pattern::IntLiteral(_) + | Pattern::NumLiteral(_) | Pattern::NonBase10Literal { .. } | Pattern::FloatLiteral(_) | Pattern::StrLiteral(_) @@ -456,7 +456,7 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool { // These expressions never have newlines Float(_) - | Int(_) + | Num(_) | NonBase10Int { .. } | Str(_) | Access(_, _) @@ -497,7 +497,7 @@ pub fn is_multiline_expr<'a>(expr: &'a Expr<'a>) -> bool { || next_is_multiline_bin_op } - UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, loc_subexpr) => { + UnaryOp(loc_subexpr, _) | PrecedenceConflict(_, _, _, loc_subexpr) => { is_multiline_expr(&loc_subexpr.value) } diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index b7f06d2ce4..44a8cca814 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -56,7 +56,7 @@ pub fn fmt_pattern<'a>( fmt_pattern(buf, &loc_pattern.value, indent, true, only_comments); } - IntLiteral(string) => buf.push_str(string), + NumLiteral(string) => buf.push_str(string), NonBase10Literal { base, string, diff --git a/compiler/gen/Cargo.toml b/compiler/gen/Cargo.toml index d4a4e158cb..0b74a834a2 100644 --- a/compiler/gen/Cargo.toml +++ b/compiler/gen/Cargo.toml @@ -20,18 +20,25 @@ im = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version! bumpalo = { version = "3.2", features = ["collections"] } inlinable_string = "0.1.0" -# NOTE: Breaking API changes get pushed directly to this Inkwell branch, so be -# very careful when running `cargo update` to get a new revision into Cargo.lock. +# NOTE: rtfeldman/inkwell is a fork of TheDan64/inkwell which does not change anything. # -# We have to depend on `branch` instead of a specific `rev` here because, although -# `rev` works locally, it causes an error on GitHub Actions. (It's unclear why, -# but after several hours of trying unsuccessfully to fix it, `branch` is it.) -inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" } -target-lexicon = "0.10" # NOTE: we must use the same version of target-lexicon as cranelift! -cranelift = "0.59" # All cranelift crates should have the same version! -cranelift-simplejit = "0.59" # All cranelift crates should have the same version! -cranelift-module = "0.59" # All cranelift crates should have the same version! -cranelift-codegen = "0.59" # All cranelift crates should have the same version! +# The reason for this fork is that the way Inkwell is designed, you have to use +# a particular branch (e.g. "llvm8-0") in Cargo.toml. That would be fine, except that +# breaking changes get pushed directly to that branch, which breaks our build +# without warning. +# +# We tried referencing a specific rev on TheDan64/inkwell directly (instead of branch), +# but although that worked locally, it did not work on GitHub Actions. (After a few +# hours of investigation, gave up trying to figure out why.) So this is the workaround: +# having an immutable tag on the rtfeldman/inkwell fork which points to +# a particular "release" of Inkwell. +# +# When we want to update Inkwell, we can sync up rtfeldman/inkwell to the latest +# commit of TheDan64/inkwell, push a new tag which points to the latest commit, +# change the tag value in this Cargo.toml to point to that tag, and `cargo update`. +# This way, GitHub Actions works and nobody's builds get broken. +inkwell = { git = "https://github.com/rtfeldman/inkwell", tag = "llvm10-0.release1" } +target-lexicon = "0.10" [dev-dependencies] roc_can = { path = "../can" } @@ -43,3 +50,4 @@ quickcheck = "0.8" quickcheck_macros = "0.8" tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] } bumpalo = { version = "3.2", features = ["collections"] } +libc = "0.2" diff --git a/compiler/gen/src/crane/build.rs b/compiler/gen/src/crane/build.rs deleted file mode 100644 index a4abd0baf7..0000000000 --- a/compiler/gen/src/crane/build.rs +++ /dev/null @@ -1,746 +0,0 @@ -use std::convert::TryFrom; - -use bumpalo::collections::Vec; -use bumpalo::Bump; -use cranelift::frontend::Switch; -use cranelift::prelude::{ - AbiParam, ExternalName, FloatCC, FunctionBuilder, FunctionBuilderContext, IntCC, MemFlags, -}; -use cranelift_codegen::ir::entities::{StackSlot, Value}; -use cranelift_codegen::ir::stackslot::{StackSlotData, StackSlotKind}; -use cranelift_codegen::ir::{immediates::Offset32, types, InstBuilder, Signature, Type}; -use cranelift_codegen::isa::TargetFrontendConfig; -use cranelift_codegen::Context; -use cranelift_module::{Backend, FuncId, Linkage, Module}; - -use crate::crane::convert::{sig_from_layout, type_from_layout}; -use roc_collections::all::ImMap; -use roc_module::symbol::{Interns, Symbol}; -use roc_mono::expr::{Expr, Proc, Procs}; -use roc_mono::layout::{Builtin, Layout}; - -type Scope = ImMap; - -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum ScopeEntry { - Stack { expr_type: Type, slot: StackSlot }, - Heap { expr_type: Type, ptr: Value }, - Arg { expr_type: Type, param: Value }, - Func { sig: Signature, func_id: FuncId }, -} - -pub struct Env<'a> { - pub arena: &'a Bump, - pub cfg: TargetFrontendConfig, - pub interns: Interns, - pub malloc: FuncId, -} - -pub fn build_expr<'a, B: Backend>( - env: &Env<'a>, - scope: &Scope, - module: &mut Module, - builder: &mut FunctionBuilder, - expr: &Expr<'a>, - procs: &Procs<'a>, -) -> Value { - use roc_mono::expr::Expr::*; - - match expr { - Int(num) => builder.ins().iconst(types::I64, *num), - Float(num) => builder.ins().f64const(*num), - Bool(val) => builder.ins().bconst(types::B1, *val), - Byte(val) => builder.ins().iconst(types::I8, *val as i64), - Cond { - cond_lhs, - cond_rhs, - pass, - fail, - cond_layout, - ret_layout, - } => { - let branch = Branch2 { - cond_lhs, - cond_rhs, - pass, - fail, - cond_layout, - ret_layout, - }; - - build_branch2(env, scope, module, builder, branch, procs) - } - Switch { - cond, - branches, - default_branch, - ret_layout, - cond_layout, - } => { - let ret_type = type_from_layout(env.cfg, &ret_layout); - let switch_args = SwitchArgs { - cond_layout, - cond_expr: cond, - branches, - default_branch, - ret_type, - }; - - build_switch(env, scope, module, builder, switch_args, procs) - } - Store(stores, ret) => { - let mut scope = im_rc::HashMap::clone(scope); - let cfg = env.cfg; - - for (name, layout, expr) in stores.iter() { - let val = build_expr(env, &scope, module, builder, &expr, procs); - let expr_type = type_from_layout(cfg, &layout); - - let slot = builder.create_stack_slot(StackSlotData::new( - StackSlotKind::ExplicitSlot, - layout.stack_size(cfg.pointer_bytes() as u32), - )); - - builder.ins().stack_store(val, slot, Offset32::new(0)); - - // Make a new scope which includes the binding we just encountered. - // This should be done *after* compiling the bound expr, since any - // recursive (in the LetRec sense) bindings should already have - // been extracted as procedures. Nothing in here should need to - // access itself! - scope = im_rc::HashMap::clone(&scope); - - scope.insert(*name, ScopeEntry::Stack { expr_type, slot }); - } - - build_expr(env, &scope, module, builder, ret, procs) - } - CallByName(symbol, args) => call_by_name(env, *symbol, args, scope, module, builder, procs), - FunctionPointer(name) => { - let fn_id = match scope.get(name) { - Some(ScopeEntry::Func{ func_id, .. }) => *func_id, - other => panic!( - "FunctionPointer could not find function named {:?} in scope; instead, found {:?} in scope {:?}", - name, other, scope - ), - }; - - let func_ref = module.declare_func_in_func(fn_id, &mut builder.func); - - builder.ins().func_addr(env.cfg.pointer_type(), func_ref) - } - CallByPointer(sub_expr, args, layout) => { - let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena); - - for arg in args.iter() { - arg_vals.push(build_expr(env, scope, module, builder, arg, procs)); - } - - let sig = sig_from_layout(env.cfg, module, layout); - let callee = build_expr(env, scope, module, builder, sub_expr, procs); - let sig_ref = builder.import_signature(sig); - let call = builder.ins().call_indirect(sig_ref, callee, &arg_vals); - let results = builder.inst_results(call); - - debug_assert!(results.len() == 1); - - results[0] - } - Load(name) => match scope.get(name) { - Some(ScopeEntry::Stack { expr_type, slot }) => { - builder - .ins() - .stack_load(*expr_type, *slot, Offset32::new(0)) - } - Some(ScopeEntry::Arg { param, .. }) => *param, - Some(ScopeEntry::Heap { expr_type, ptr }) => { - builder - .ins() - .load(*expr_type, MemFlags::new(), *ptr, Offset32::new(0)) - } - Some(ScopeEntry::Func { .. }) => { - panic!("TODO I don't yet know how to return fn pointers") - } - None => panic!("Could not find a var for {:?} in scope {:?}", name, scope), - }, - Struct { layout, fields } => { - let cfg = env.cfg; - - // Sort the fields - let mut sorted_fields = Vec::with_capacity_in(fields.len(), env.arena); - for field in fields.iter() { - sorted_fields.push(field); - } - sorted_fields.sort_by_key(|k| &k.0); - - // Create a slot - let slot = builder.create_stack_slot(StackSlotData::new( - StackSlotKind::ExplicitSlot, - layout.stack_size(cfg.pointer_bytes() as u32), - )); - - // Create instructions for storing each field's expression - for (index, (_, ref inner_expr)) in sorted_fields.iter().enumerate() { - let val = build_expr(env, &scope, module, builder, inner_expr, procs); - - // Is there an existing function for this? - let field_size = match inner_expr { - Int(_) => std::mem::size_of::(), - _ => panic!("I don't yet know how to calculate the offset for {:?} when building a cranelift struct", val), - }; - let offset = i32::try_from(index * field_size) - .expect("TODO handle field size conversion to i32"); - - builder.ins().stack_store(val, slot, Offset32::new(offset)); - } - - let ir_type = type_from_layout(cfg, layout); - builder.ins().stack_addr(ir_type, slot, Offset32::new(0)) - } - // Access { - // label, - // field_layout, - // struct_layout, - // } => { - // panic!("I don't yet know how to crane build {:?}", expr); - // } - Str(str_literal) => { - if str_literal.is_empty() { - panic!("TODO build an empty string in Crane"); - } else { - let bytes_len = str_literal.len() + 1/* TODO drop the +1 when we have structs and this is no longer a NUL-terminated CString.*/; - let ptr = call_malloc(env, module, builder, bytes_len); - let mem_flags = MemFlags::new(); - - // Copy the bytes from the string literal into the array - for (index, byte) in str_literal.bytes().enumerate() { - let val = builder.ins().iconst(types::I8, byte as i64); - let offset = Offset32::new(index as i32); - - builder.ins().store(mem_flags, val, ptr, offset); - } - - // Add a NUL terminator at the end. - // TODO: Instead of NUL-terminating, return a struct - // with the pointer and also the length and capacity. - let nul_terminator = builder.ins().iconst(types::I8, 0); - let index = bytes_len as i32 - 1; - let offset = Offset32::new(index); - - builder.ins().store(mem_flags, nul_terminator, ptr, offset); - - ptr - } - } - Array { elem_layout, elems } => { - if elems.is_empty() { - panic!("TODO build an empty Array in Crane"); - } else { - let elem_bytes = elem_layout.stack_size(env.cfg.pointer_bytes() as u32) as usize; - let bytes_len = (elem_bytes * elems.len()) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */; - let ptr = call_malloc(env, module, builder, bytes_len); - let mem_flags = MemFlags::new(); - - // Copy the elements from the literal into the array - for (index, elem) in elems.iter().enumerate() { - let offset = Offset32::new(elem_bytes as i32 * index as i32); - let val = build_expr(env, scope, module, builder, elem, procs); - - builder.ins().store(mem_flags, val, ptr, offset); - } - - // Add a NUL terminator at the end. - // TODO: Instead of NUL-terminating, return a struct - // with the pointer and also the length and capacity. - let nul_terminator = builder.ins().iconst(types::I8, 0); - let index = bytes_len as i32 - 1; - let offset = Offset32::new(index); - - builder.ins().store(mem_flags, nul_terminator, ptr, offset); - - ptr - } - } - _ => { - panic!("I don't yet know how to crane build {:?}", expr); - } - } -} - -struct Branch2<'a> { - cond_lhs: &'a Expr<'a>, - cond_rhs: &'a Expr<'a>, - cond_layout: &'a Layout<'a>, - pass: &'a Expr<'a>, - fail: &'a Expr<'a>, - ret_layout: &'a Layout<'a>, -} - -fn build_branch2<'a, B: Backend>( - env: &Env<'a>, - scope: &Scope, - module: &mut Module, - builder: &mut FunctionBuilder, - branch: Branch2<'a>, - procs: &Procs<'a>, -) -> Value { - let ret_layout = branch.ret_layout; - let ret_type = type_from_layout(env.cfg, &ret_layout); - // Declare a variable which each branch will mutate to be the value of that branch. - // At the end of the expression, we will evaluate to this. - let ret = cranelift::frontend::Variable::with_u32(0); - - // The block we'll jump to once the switch has completed. - let ret_block = builder.create_block(); - - builder.declare_var(ret, ret_type); - - let lhs = build_expr(env, scope, module, builder, branch.cond_lhs, procs); - let rhs = build_expr(env, scope, module, builder, branch.cond_rhs, procs); - let pass_block = builder.create_block(); - let fail_block = builder.create_block(); - - match branch.cond_layout { - Layout::Builtin(Builtin::Float64) => { - // For floats, first do a `fcmp` comparison to get a bool answer about equality, - // then use `brnz` to branch if that bool equality answer was nonzero (aka true). - let is_eq = builder.ins().fcmp(FloatCC::Equal, lhs, rhs); - - builder.ins().brnz(is_eq, pass_block, &[]); - } - Layout::Builtin(Builtin::Int64) => { - // For ints, we can compare and branch in the same instruction: `icmp` - builder - .ins() - .br_icmp(IntCC::Equal, lhs, rhs, pass_block, &[]); - } - other => panic!("I don't know how to build a conditional for {:?}", other), - } - - // Unconditionally jump to fail_block (if we didn't just jump to pass_block). - builder.ins().jump(fail_block, &[]); - - let mut build_branch = |expr, block| { - builder.switch_to_block(block); - - // TODO re-enable this once Switch stops making unsealed blocks, e.g. - // https://docs.rs/cranelift-frontend/0.59.0/src/cranelift_frontend/switch.rs.html#152 - // builder.seal_block(block); - - // Mutate the ret variable to be the outcome of this branch. - let value = build_expr(env, scope, module, builder, expr, procs); - - builder.def_var(ret, value); - - // Unconditionally jump to ret_block, making the whole expression evaluate to ret. - builder.ins().jump(ret_block, &[]); - }; - - build_branch(branch.pass, pass_block); - build_branch(branch.fail, fail_block); - - // Finally, build ret_block - which contains our terminator instruction. - { - builder.switch_to_block(ret_block); - // TODO re-enable this once Switch stops making unsealed blocks, e.g. - // https://docs.rs/cranelift-frontend/0.59.0/src/cranelift_frontend/switch.rs.html#152 - // builder.seal_block(block); - - // Now that ret has been mutated by the switch statement, evaluate to it. - builder.use_var(ret) - } -} -struct SwitchArgs<'a> { - pub cond_expr: &'a Expr<'a>, - pub cond_layout: &'a Layout<'a>, - pub branches: &'a [(u64, Expr<'a>)], - pub default_branch: &'a Expr<'a>, - pub ret_type: Type, -} - -fn build_switch<'a, B: Backend>( - env: &Env<'a>, - scope: &Scope, - module: &mut Module, - builder: &mut FunctionBuilder, - switch_args: SwitchArgs<'a>, - procs: &Procs<'a>, -) -> Value { - let mut switch = Switch::new(); - let SwitchArgs { - branches, - cond_expr, - default_branch, - ret_type, - .. - } = switch_args; - let mut blocks = Vec::with_capacity_in(branches.len(), env.arena); - - // Declare a variable which each branch will mutate to be the value of that branch. - // At the end of the expression, we will evaluate to this. - let ret = cranelift::frontend::Variable::with_u32(0); - - builder.declare_var(ret, ret_type); - - // The block for the conditional's default branch. - let default_block = builder.create_block(); - - // The block we'll jump to once the switch has completed. - let ret_block = builder.create_block(); - - // Build the blocks for each branch, and register them in the switch. - // Do this before emitting the switch, because it needs to be emitted at the front. - for (int, _) in branches { - let block = builder.create_block(); - - blocks.push(block); - - switch.set_entry(*int, block); - } - - // Run the switch. Each branch will mutate ret and then jump to ret_block. - let cond = build_expr(env, scope, module, builder, cond_expr, procs); - - switch.emit(builder, cond, default_block); - - let mut build_branch = |block, expr| { - builder.switch_to_block(block); - // TODO re-enable this once Switch stops making unsealed blocks, e.g. - // https://docs.rs/cranelift-frontend/0.59.0/src/cranelift_frontend/switch.rs.html#152 - // builder.seal_block(block); - - // Mutate the ret variable to be the outcome of this branch. - let value = build_expr(env, scope, module, builder, expr, procs); - - builder.def_var(ret, value); - - // Unconditionally jump to ret_block, making the whole expression evaluate to ret. - builder.ins().jump(ret_block, &[]); - }; - - // Build the blocks for each branch - for ((_, expr), block) in branches.iter().zip(blocks) { - build_branch(block, expr); - } - - // Build the block for the default branch - build_branch(default_block, default_branch); - - // Finally, build ret_block - which contains our terminator instruction. - { - builder.switch_to_block(ret_block); - // TODO re-enable this once Switch stops making unsealed blocks, e.g. - // https://docs.rs/cranelift-frontend/0.59.0/src/cranelift_frontend/switch.rs.html#152 - // builder.seal_block(block); - - // Now that ret has been mutated by the switch statement, evaluate to it. - builder.use_var(ret) - } -} - -pub fn declare_proc<'a, B: Backend>( - env: &Env<'a>, - module: &mut Module, - symbol: Symbol, - proc: &Proc<'a>, -) -> (FuncId, Signature) { - let args = proc.args; - let cfg = env.cfg; - // TODO this Layout::from_content is duplicated when building this Proc - let ret_type = type_from_layout(cfg, &proc.ret_layout); - - // Create a signature for the function - let mut sig = module.make_signature(); - - // Add return type to the signature - sig.returns.push(AbiParam::new(ret_type)); - - // Add params to the signature - for (layout, _name) in args.iter() { - let arg_type = type_from_layout(cfg, &layout); - - sig.params.push(AbiParam::new(arg_type)); - } - - // Declare the function in the module - let fn_id = module - .declare_function(symbol.ident_string(&env.interns), Linkage::Local, &sig) - .unwrap_or_else(|err| panic!("Error when building function {:?} - {:?}", symbol, err)); - - (fn_id, sig) -} - -// TODO trim down these arguments -#[allow(clippy::too_many_arguments)] -pub fn define_proc_body<'a, B: Backend>( - env: &Env<'a>, - ctx: &mut Context, - module: &mut Module, - fn_id: FuncId, - scope: &Scope, - sig: Signature, - proc: Proc<'a>, - procs: &Procs<'a>, -) { - let args = proc.args; - let cfg = env.cfg; - - // Build the body of the function - { - let mut scope = scope.clone(); - - ctx.func.signature = sig; - ctx.func.name = ExternalName::user(0, fn_id.as_u32()); - - let mut func_ctx = FunctionBuilderContext::new(); - let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); - - let block = builder.create_block(); - - builder.switch_to_block(block); - builder.append_block_params_for_function_params(block); - - // Add args to scope - for (¶m, (layout, arg_symbol)) in builder.block_params(block).iter().zip(args) { - let expr_type = type_from_layout(cfg, &layout); - - scope.insert(*arg_symbol, ScopeEntry::Arg { expr_type, param }); - } - - let body = build_expr(env, &scope, module, &mut builder, &proc.body, procs); - - builder.ins().return_(&[body]); - // TODO re-enable this once Switch stops making unsealed blocks, e.g. - // https://docs.rs/cranelift-frontend/0.59.0/src/cranelift_frontend/switch.rs.html#152 - // builder.seal_block(block); - builder.seal_all_blocks(); - - builder.finalize(); - } - - module - .define_function(fn_id, ctx) - .expect("Defining Cranelift function failed"); - - module.clear_context(ctx); -} - -fn build_arg<'a, B: Backend>( - (arg, _): &'a (Expr<'a>, Layout<'a>), - env: &Env<'a>, - scope: &Scope, - module: &mut Module, - builder: &mut FunctionBuilder, - procs: &Procs<'a>, -) -> Value { - build_expr(env, scope, module, builder, arg, procs) -} - -#[inline(always)] -fn call_by_name<'a, B: Backend>( - env: &Env<'a>, - symbol: Symbol, - args: &'a [(Expr<'a>, Layout<'a>)], - scope: &Scope, - module: &mut Module, - builder: &mut FunctionBuilder, - procs: &Procs<'a>, -) -> Value { - match symbol { - Symbol::NUM_ADD => { - debug_assert!(args.len() == 2); - let a = build_arg(&args[0], env, scope, module, builder, procs); - let b = build_arg(&args[1], env, scope, module, builder, procs); - - builder.ins().iadd(a, b) - } - Symbol::NUM_SUB => { - debug_assert!(args.len() == 2); - let a = build_arg(&args[0], env, scope, module, builder, procs); - let b = build_arg(&args[1], env, scope, module, builder, procs); - - builder.ins().isub(a, b) - } - Symbol::NUM_MUL => { - debug_assert!(args.len() == 2); - let a = build_arg(&args[0], env, scope, module, builder, procs); - let b = build_arg(&args[1], env, scope, module, builder, procs); - - builder.ins().imul(a, b) - } - Symbol::NUM_NEG => { - debug_assert!(args.len() == 1); - let num = build_arg(&args[0], env, scope, module, builder, procs); - - builder.ins().ineg(num) - } - Symbol::FLOAT_DIV => { - debug_assert!(args.len() == 2); - let a = build_arg(&args[0], env, scope, module, builder, procs); - let b = build_arg(&args[1], env, scope, module, builder, procs); - - builder.ins().fdiv(a, b) - } - // Symbol::INT_DIV => { - // debug_assert!(args.len() == 2); - // let a = build_arg(&args[0], env, scope, module, builder, procs); - // let b = build_arg(&args[1], env, scope, module, builder, procs); - // - // builder.ins().sdiv(a, b) - // } - Symbol::LIST_GET_UNSAFE => { - debug_assert!(args.len() == 2); - - let list_ptr = build_arg(&args[0], env, scope, module, builder, procs); - let elem_index = build_arg(&args[1], env, scope, module, builder, procs); - - let elem_type = Type::int(64).unwrap(); // TODO Look this up instead of hardcoding it! - let elem_bytes = 8; // TODO Look this up instead of hardcoding it! - let elem_size = builder.ins().iconst(types::I64, elem_bytes); - - // Multiply the requested index by the size of each element. - let offset = builder.ins().imul(elem_index, elem_size); - - builder.ins().load_complex( - elem_type, - MemFlags::new(), - &[list_ptr, offset], - Offset32::new(0), - ) - } - Symbol::LIST_SET => { - let (_list_expr, list_layout) = &args[0]; - - match list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => { - // TODO try memcpy for shallow clones; it's probably faster - // let list_val = build_expr(env, scope, module, builder, list_expr, procs); - - let num_elems = 10; // TODO FIXME read from List.len - let elem_bytes = - elem_layout.stack_size(env.cfg.pointer_bytes() as u32) as usize; - let bytes_len = (elem_bytes * num_elems) + 1/* TODO drop the +1 when we have structs and this is no longer NUL-terminated. */; - let ptr = call_malloc(env, module, builder, bytes_len); - // let mem_flags = MemFlags::new(); - - // Copy the elements from the literal into the array - // for (index, elem) in elems.iter().enumerate() { - // let offset = Offset32::new(elem_bytes as i32 * index as i32); - // let val = build_expr(env, scope, module, builder, elem, procs); - - // builder.ins().store(mem_flags, val, ptr, offset); - // } - - // Add a NUL terminator at the end. - // TODO: Instead of NUL-terminating, return a struct - // with the pointer and also the length and capacity. - // let nul_terminator = builder.ins().iconst(types::I8, 0); - // let index = bytes_len as i32 - 1; - // let offset = Offset32::new(index); - - // builder.ins().store(mem_flags, nul_terminator, ptr, offset); - - list_set_in_place( - env, - ptr, - build_arg(&args[1], env, scope, module, builder, procs), - build_arg(&args[2], env, scope, module, builder, procs), - elem_layout, - builder, - ); - - ptr - } - _ => { - unreachable!("Invalid List layout for List.set: {:?}", list_layout); - } - } - } - Symbol::LIST_SET_IN_PLACE => { - // set : List elem, Int, elem -> List elem - debug_assert!(args.len() == 3); - - let (list_expr, list_layout) = &args[0]; - let list_val = build_expr(env, scope, module, builder, list_expr, procs); - - match list_layout { - Layout::Builtin(Builtin::List(elem_layout)) => list_set_in_place( - env, - list_val, - build_arg(&args[1], env, scope, module, builder, procs), - build_arg(&args[2], env, scope, module, builder, procs), - elem_layout, - builder, - ), - _ => { - unreachable!("Invalid List layout for List.set: {:?}", list_layout); - } - } - } - _ => { - let fn_id = match scope.get(&symbol) { - Some(ScopeEntry::Func{ func_id, .. }) => *func_id, - other => panic!( - "CallByName could not find function named {:?} in scope; instead, found {:?} in scope {:?}", - symbol, other, scope - ), - }; - let local_func = module.declare_func_in_func(fn_id, &mut builder.func); - let mut arg_vals = Vec::with_capacity_in(args.len(), env.arena); - - for (arg, _layout) in args { - arg_vals.push(build_expr(env, scope, module, builder, arg, procs)); - } - - let call = builder.ins().call(local_func, arg_vals.into_bump_slice()); - let results = builder.inst_results(call); - - debug_assert!(results.len() == 1); - - results[0] - } - } -} - -fn call_malloc( - env: &Env<'_>, - module: &mut Module, - builder: &mut FunctionBuilder, - size: usize, -) -> Value { - // Declare malloc inside this function - let local_func = module.declare_func_in_func(env.malloc, &mut builder.func); - - // Convert the size argument to a Value - let ptr_size_type = module.target_config().pointer_type(); - let size_arg = builder.ins().iconst(ptr_size_type, size as i64); - - // Call malloc and return the resulting pointer - let call = builder.ins().call(local_func, &[size_arg]); - let results = builder.inst_results(call); - - debug_assert!(results.len() == 1); - - results[0] -} - -fn list_set_in_place<'a>( - env: &Env<'a>, - list_ptr: Value, - elem_index: Value, - elem: Value, - elem_layout: &Layout<'a>, - builder: &mut FunctionBuilder, -) -> Value { - let elem_bytes = elem_layout.stack_size(env.cfg.pointer_bytes() as u32); - let elem_size = builder.ins().iconst(types::I64, elem_bytes as i64); - - // Multiply the requested index by the size of each element. - let offset = builder.ins().imul(elem_index, elem_size); - - builder - .ins() - .store_complex(MemFlags::new(), elem, &[list_ptr, offset], Offset32::new(0)); - - list_ptr -} diff --git a/compiler/gen/src/crane/convert.rs b/compiler/gen/src/crane/convert.rs deleted file mode 100644 index 8f0d1451f5..0000000000 --- a/compiler/gen/src/crane/convert.rs +++ /dev/null @@ -1,61 +0,0 @@ -use cranelift::prelude::AbiParam; -use cranelift_codegen::ir::{types, Signature, Type}; -use cranelift_codegen::isa::TargetFrontendConfig; -use cranelift_module::{Backend, Module}; - -use roc_mono::layout::Layout; - -pub fn type_from_layout(cfg: TargetFrontendConfig, layout: &Layout<'_>) -> Type { - use roc_mono::layout::Builtin::*; - use roc_mono::layout::Layout::*; - - match layout { - Pointer(_) | FunctionPointer(_, _) => cfg.pointer_type(), - Struct(fields) => { - // This will change as we add more fields and field types to the tests - let naive_all_ints = fields.iter().all(|ref field| match field.1 { - Builtin(Int64) => true, - _ => false, - }); - - if naive_all_ints && fields.len() == 3 { - types::I64.by(4).unwrap() - } else { - panic!("TODO layout_to_crane_type for Struct"); - } - } - Builtin(builtin) => match builtin { - Int64 => types::I64, - Float64 => types::F64, - Str | Map(_, _) | Set(_) | List(_) => cfg.pointer_type(), - }, - } -} - -pub fn sig_from_layout( - cfg: TargetFrontendConfig, - module: &mut Module, - layout: &Layout<'_>, -) -> Signature { - match layout { - Layout::FunctionPointer(args, ret) => { - let ret_type = type_from_layout(cfg, &ret); - let mut sig = module.make_signature(); - - // Add return type to the signature - sig.returns.push(AbiParam::new(ret_type)); - - // Add params to the signature - for layout in args.iter() { - let arg_type = type_from_layout(cfg, &layout); - - sig.params.push(AbiParam::new(arg_type)); - } - - sig - } - _ => { - panic!("Could not make Signature from Layout {:?}", layout); - } - } -} diff --git a/compiler/gen/src/crane/imports.rs b/compiler/gen/src/crane/imports.rs deleted file mode 100644 index ab57529790..0000000000 --- a/compiler/gen/src/crane/imports.rs +++ /dev/null @@ -1,117 +0,0 @@ -use cranelift::prelude::{AbiParam, FunctionBuilder, FunctionBuilderContext}; -use cranelift_codegen::ir::{ExternalName, Function, InstBuilder, Signature}; -use cranelift_codegen::isa::CallConv; -use cranelift_codegen::Context; -use cranelift_module::{Backend, FuncId, Linkage, Module}; - -pub fn declare_malloc_header(module: &mut Module) -> (FuncId, Signature) { - let ptr_size_type = module.target_config().pointer_type(); - let sig = Signature { - params: vec![AbiParam::new(ptr_size_type)], - returns: vec![AbiParam::new(ptr_size_type)], - call_conv: CallConv::SystemV, // TODO is this the calling convention we actually want? - }; - - // Declare the wrapper around malloc - let func_id = module - .declare_function("roc_malloc", Linkage::Local, &sig) - .unwrap(); - - (func_id, sig) -} - -pub fn define_malloc_body( - module: &mut Module, - ctx: &mut Context, - sig: Signature, - func_id: FuncId, -) { - let ptr_size_type = module.target_config().pointer_type(); - - ctx.func = Function::with_name_signature(ExternalName::user(0, func_id.as_u32()), sig); - - let mut func_ctx = FunctionBuilderContext::new(); - - { - let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); - let block = builder.create_block(); - - builder.switch_to_block(block); - builder.append_block_params_for_function_params(block); - - let mut malloc_sig = module.make_signature(); - - malloc_sig.params.push(AbiParam::new(ptr_size_type)); - malloc_sig.returns.push(AbiParam::new(ptr_size_type)); - - let callee = module - .declare_function("malloc", Linkage::Import, &malloc_sig) - .expect("declare malloc"); - let malloc = module.declare_func_in_func(callee, &mut builder.func); - let size = builder.block_params(block)[0]; - let call = builder.ins().call(malloc, &[size]); - let ptr = builder.inst_results(call)[0]; - - builder.ins().return_(&[ptr]); - - // TODO re-enable this once Switch stops making unsealed blocks, e.g. - // https://docs.rs/cranelift-frontend/0.59.0/src/cranelift_frontend/switch.rs.html#152 - // builder.seal_block(block); - } - - module.define_function(func_id, ctx).unwrap(); -} - -pub fn define_malloc(module: &mut Module, ctx: &mut Context) -> FuncId { - // TODO investigate whether we can remove this wrapper function somehow. - // It may get inlined away, but it seems like it shouldn't be - // necessary, and we should be able to return the FuncId of the imported malloc. - let ptr_size_type = module.target_config().pointer_type(); - let sig = Signature { - params: vec![AbiParam::new(ptr_size_type)], - returns: vec![AbiParam::new(ptr_size_type)], - call_conv: CallConv::SystemV, // TODO is this the calling convention we actually want? - }; - - // Declare the wrapper around malloc - let func_id = module - .declare_function("roc_malloc", Linkage::Local, &sig) - .unwrap(); - - let ptr_size_type = module.target_config().pointer_type(); - - ctx.func = Function::with_name_signature(ExternalName::user(0, func_id.as_u32()), sig); - - let mut func_ctx = FunctionBuilderContext::new(); - - { - let mut builder: FunctionBuilder = FunctionBuilder::new(&mut ctx.func, &mut func_ctx); - let block = builder.create_block(); - - builder.switch_to_block(block); - builder.append_block_params_for_function_params(block); - - let mut malloc_sig = module.make_signature(); - - malloc_sig.params.push(AbiParam::new(ptr_size_type)); - malloc_sig.returns.push(AbiParam::new(ptr_size_type)); - - let callee = module - .declare_function("malloc", Linkage::Import, &malloc_sig) - .expect("declare malloc"); - let malloc = module.declare_func_in_func(callee, &mut builder.func); - let size = builder.block_params(block)[0]; - let call = builder.ins().call(malloc, &[size]); - let ptr = builder.inst_results(call)[0]; - - builder.ins().return_(&[ptr]); - - builder.seal_block(block); - builder.finalize(); - } - - module.define_function(func_id, ctx).unwrap(); - module.clear_context(ctx); - - func_id -} diff --git a/compiler/gen/src/crane/mod.rs b/compiler/gen/src/crane/mod.rs deleted file mode 100644 index ab260d47f3..0000000000 --- a/compiler/gen/src/crane/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod build; -pub mod convert; -pub mod imports; diff --git a/compiler/gen/src/lib.rs b/compiler/gen/src/lib.rs index 797ca6351c..89bd8eb2fd 100644 --- a/compiler/gen/src/lib.rs +++ b/compiler/gen/src/lib.rs @@ -10,5 +10,5 @@ // and encouraging shortcuts here creates bad incentives. I would rather temporarily // re-enable this when working on performance optimizations than have it block PRs. #![allow(clippy::large_enum_variant)] -pub mod crane; + pub mod llvm; diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 0e703d15db..b9d2cab93e 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2,17 +2,22 @@ use bumpalo::collections::Vec; use bumpalo::Bump; use inkwell::builder::Builder; use inkwell::context::Context; +use inkwell::memory_buffer::MemoryBuffer; use inkwell::module::{Linkage, Module}; -use inkwell::types::BasicTypeEnum; +use inkwell::passes::PassManager; +use inkwell::types::{BasicTypeEnum, IntType, StructType}; use inkwell::values::BasicValueEnum::{self, *}; -use inkwell::values::{FunctionValue, IntValue, PointerValue}; +use inkwell::values::{FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::{FloatPredicate, IntPredicate}; -use crate::llvm::convert::{basic_type_from_layout, get_fn_type}; +use crate::llvm::convert::{ + basic_type_from_layout, collection_wrapper, empty_collection, get_fn_type, ptr_int, +}; use roc_collections::all::ImMap; use roc_module::symbol::{Interns, Symbol}; use roc_mono::expr::{Expr, Proc, Procs}; -use roc_mono::layout::Layout; +use roc_mono::layout::{Builtin, Layout}; +use target_lexicon::CallingConvention; /// This is for Inkwell's FunctionValue::verify - we want to know the verification /// output in debug builds, but we don't want it to print to stdout in release builds! @@ -22,6 +27,10 @@ const PRINT_FN_VERIFICATION_OUTPUT: bool = true; #[cfg(not(debug_assertions))] const PRINT_FN_VERIFICATION_OUTPUT: bool = false; +// 0 is the C calling convention - see https://llvm.org/doxygen/namespacellvm_1_1CallingConv.html +// TODO: experiment with different internal calling conventions, e.g. "fast" +const DEFAULT_CALLING_CONVENTION: u32 = 0; + type Scope<'a, 'ctx> = ImMap, PointerValue<'ctx>)>; pub struct Env<'a, 'ctx, 'env> { @@ -30,14 +39,48 @@ pub struct Env<'a, 'ctx, 'env> { pub builder: &'env Builder<'ctx>, pub module: &'ctx Module<'ctx>, pub interns: Interns, - pub pointer_bytes: u32, + pub ptr_bytes: u32, } +impl<'a, 'ctx, 'env> Env<'a, 'ctx, 'env> { + pub fn ptr_int(&self) -> IntType<'ctx> { + ptr_int(self.context, self.ptr_bytes) + } +} + +pub fn module_from_builtins<'ctx>(ctx: &'ctx Context, module_name: &str) -> Module<'ctx> { + let memory_buffer = + MemoryBuffer::create_from_memory_range(include_bytes!("builtins.bc"), module_name); + + Module::parse_bitcode_from_buffer(&memory_buffer, ctx) + .unwrap_or_else(|err| panic!("Unable to import builtins bitcode. LLVM error: {:?}", err)) +} + +pub fn add_passes(fpm: &PassManager>) { + // tail-call elimination is always on + fpm.add_instruction_combining_pass(); + fpm.add_tail_call_elimination_pass(); + + // Enable more optimizations when running cargo test --release + if !cfg!(debug_assertions) { + fpm.add_reassociate_pass(); + fpm.add_basic_alias_analysis_pass(); + fpm.add_promote_memory_to_register_pass(); + fpm.add_cfg_simplification_pass(); + fpm.add_gvn_pass(); + // TODO figure out why enabling any of these (even alone) causes LLVM to segfault + // fpm.add_strip_dead_prototypes_pass(); + // fpm.add_dead_arg_elimination_pass(); + // fpm.add_function_inlining_pass(); + } +} + +#[allow(clippy::cognitive_complexity)] pub fn build_expr<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, - expr: &Expr<'a>, + expr: &'a Expr<'a>, procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { use roc_mono::expr::Expr::*; @@ -45,39 +88,49 @@ pub fn build_expr<'a, 'ctx, 'env>( match expr { Int(num) => env.context.i64_type().const_int(*num as u64, true).into(), Float(num) => env.context.f64_type().const_float(*num).into(), + Bool(b) => env.context.bool_type().const_int(*b as u64, false).into(), + Byte(b) => env.context.i8_type().const_int(*b as u64, false).into(), Cond { - cond_lhs, - cond_rhs, - pass, - fail, + branch_symbol, + pass: (pass_stores, pass_expr), + fail: (fail_stores, fail_expr), ret_layout, .. } => { - let cond = Branch2 { - cond_lhs, - cond_rhs, + let pass = env.arena.alloc(Expr::Store(pass_stores, pass_expr)); + let fail = env.arena.alloc(Expr::Store(fail_stores, fail_expr)); + + let conditional = Branch2 { + cond: branch_symbol, pass, fail, ret_layout: ret_layout.clone(), }; - build_branch2(env, scope, parent, cond, procs) - } - Branches { .. } => { - panic!("TODO build_branches(env, scope, parent, cond_lhs, branches, procs)"); + build_branch2(env, scope, parent, conditional, procs) } Switch { cond, branches, - default_branch, + default_branch: (default_stores, default_expr), ret_layout, cond_layout, } => { - let ret_type = basic_type_from_layout(env.context, &ret_layout); + let ret_type = + basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); + + let default_branch = env.arena.alloc(Expr::Store(default_stores, default_expr)); + + let mut combined = Vec::with_capacity_in(branches.len(), env.arena); + + for (int, stores, expr) in branches.iter() { + combined.push((*int, Expr::Store(stores, expr))); + } + let switch_args = SwitchArgs { cond_layout: cond_layout.clone(), cond_expr: cond, - branches, + branches: combined.into_bump_slice(), default_branch, ret_type, }; @@ -90,7 +143,7 @@ pub fn build_expr<'a, 'ctx, 'env>( for (symbol, layout, expr) in stores.iter() { let val = build_expr(env, &scope, parent, &expr, procs); - let expr_bt = basic_type_from_layout(context, &layout); + let expr_bt = basic_type_from_layout(env.arena, context, &layout, env.ptr_bytes); let alloca = create_entry_block_alloca( env, parent, @@ -114,20 +167,53 @@ pub fn build_expr<'a, 'ctx, 'env>( } CallByName(symbol, args) => match *symbol { Symbol::BOOL_OR => { - panic!("TODO create a phi node for ||"); + // The (||) operator + debug_assert!(args.len() == 2); + + let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value(); + let build_then = || env.context.bool_type().const_int(true as u64, false).into(); + let build_else = || build_expr(env, scope, parent, &args[1].0, procs); + + let ret_type = env.context.bool_type().into(); + + build_basic_phi2(env, parent, comparison, build_then, build_else, ret_type) } Symbol::BOOL_AND => { - panic!("TODO create a phi node for &&"); + // The (&&) operator + debug_assert!(args.len() == 2); + + let comparison = build_expr(env, scope, parent, &args[0].0, procs).into_int_value(); + let build_then = || build_expr(env, scope, parent, &args[1].0, procs); + let build_else = || { + env.context + .bool_type() + .const_int(false as u64, false) + .into() + }; + + let ret_type = env.context.bool_type().into(); + + build_basic_phi2(env, parent, comparison, build_then, build_else, ret_type) + } + Symbol::BOOL_NOT => { + // The (!) operator + debug_assert!(args.len() == 1); + + let arg = build_expr(env, scope, parent, &args[0].0, procs); + + let int_val = env.builder.build_not(arg.into_int_value(), "bool_not"); + + BasicValueEnum::IntValue(int_val) } _ => { - let mut arg_vals: Vec = + let mut arg_tuples: Vec<(BasicValueEnum, &'a Layout<'a>)> = Vec::with_capacity_in(args.len(), env.arena); - for (arg, _layout) in args.iter() { - arg_vals.push(build_expr(env, scope, parent, arg, procs)); + for (arg, layout) in args.iter() { + arg_tuples.push((build_expr(env, scope, parent, arg, procs), layout)); } - call_with_args(*symbol, arg_vals.into_bump_slice(), env) + call_with_args(*symbol, parent, arg_tuples.into_bump_slice(), env) } }, FunctionPointer(symbol) => { @@ -159,37 +245,36 @@ pub fn build_expr<'a, 'ctx, 'env>( } }; + call.set_call_convention(DEFAULT_CALLING_CONVENTION); + call.try_as_basic_value() .left() .unwrap_or_else(|| panic!("LLVM error: Invalid call by pointer.")) } - - Load(symbol) => match scope.get(symbol) { - Some((_, ptr)) => env - .builder - .build_load(*ptr, symbol.ident_string(&env.interns)), - None => panic!("Could not find a var for {:?} in scope {:?}", symbol, scope), - }, + Load(symbol) => load_symbol(env, scope, symbol), Str(str_literal) => { if str_literal.is_empty() { panic!("TODO build an empty string in LLVM"); } else { let ctx = env.context; let builder = env.builder; - let bytes_len = str_literal.len() + 1/* TODO drop the +1 when we have structs and this is no longer a NUL-terminated CString.*/; + let str_len = str_literal.len() + 1/* TODO drop the +1 when we have structs and this is no longer a NUL-terminated CString.*/; let byte_type = ctx.i8_type(); let nul_terminator = byte_type.const_zero(); - let len = ctx.i32_type().const_int(bytes_len as u64, false); + let len_val = ctx.i32_type().const_int(str_len as u64, false); let ptr = env .builder - .build_array_malloc(ctx.i8_type(), len, "str_ptr") + .build_array_malloc(ctx.i8_type(), len_val, "str_ptr") .unwrap(); + // TODO check if malloc returned null; if so, runtime error for OOM! + // Copy the bytes from the string literal into the array for (index, byte) in str_literal.bytes().enumerate() { - let index = ctx.i32_type().const_int(index as u64, false); - let elem_ptr = unsafe { builder.build_gep(ptr, &[index], "byte") }; + let index_val = ctx.i32_type().const_int(index as u64, false); + let elem_ptr = + unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "byte") }; builder.build_store(elem_ptr, byte_type.const_int(byte as u64, false)); } @@ -197,8 +282,9 @@ pub fn build_expr<'a, 'ctx, 'env>( // Add a NUL terminator at the end. // TODO: Instead of NUL-terminating, return a struct // with the pointer and also the length and capacity. - let index = ctx.i32_type().const_int(bytes_len as u64 - 1, false); - let elem_ptr = unsafe { builder.build_gep(ptr, &[index], "nul_terminator") }; + let index_val = ctx.i32_type().const_int(str_len as u64 - 1, false); + let elem_ptr = + unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "nul_terminator") }; builder.build_store(elem_ptr, nul_terminator); @@ -206,53 +292,344 @@ pub fn build_expr<'a, 'ctx, 'env>( } } Array { elem_layout, elems } => { + let ctx = env.context; + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let builder = env.builder; + if elems.is_empty() { - panic!("TODO build an empty string in LLVM"); + let struct_type = empty_collection(ctx, env.ptr_bytes); + + // THe pointer should be null (aka zero) and the length should be zero, + // so the whole struct should be a const_zero + BasicValueEnum::StructValue(struct_type.const_zero()) } else { - let elem_bytes = elem_layout.stack_size(env.pointer_bytes) as u64; - let bytes_len = elem_bytes * (elems.len() + 1) as u64/* TODO drop the +1 when we have structs and this is no longer a NUL-terminated CString.*/; + let len_u64 = elems.len() as u64; + let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; - let ctx = env.context; - let builder = env.builder; + let ptr = { + let bytes_len = elem_bytes * len_u64; + let len_type = env.ptr_int(); + let len = len_type.const_int(bytes_len, false); - let elem_type = basic_type_from_layout(ctx, elem_layout); - let nul_terminator = elem_type.into_int_type().const_zero(); - let len = ctx.i32_type().const_int(bytes_len, false); - let ptr = env - .builder - .build_array_malloc(elem_type, len, "str_ptr") - .unwrap(); + env.builder + .build_array_malloc(elem_type, len, "create_list_ptr") + .unwrap() - // Copy the bytes from the string literal into the array + // TODO check if malloc returned null; if so, runtime error for OOM! + }; + + // Copy the elements from the list literal into the array for (index, elem) in elems.iter().enumerate() { - let offset = ctx.i32_type().const_int(elem_bytes * index as u64, false); - let elem_ptr = unsafe { builder.build_gep(ptr, &[offset], "elem") }; - + let index_val = ctx.i32_type().const_int(index as u64, false); + let elem_ptr = + unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; let val = build_expr(env, &scope, parent, &elem, procs); builder.build_store(elem_ptr, val); } - // Add a NUL terminator at the end. - // TODO: Instead of NUL-terminating, return a struct - // with the pointer and also the length and capacity. - let index = ctx.i32_type().const_int(bytes_len as u64 - 1, false); - let elem_ptr = unsafe { builder.build_gep(ptr, &[index], "nul_terminator") }; + let ptr_val = BasicValueEnum::PointerValue(ptr); + let struct_type = collection_wrapper(ctx, ptr.get_type(), env.ptr_bytes); + let len = BasicValueEnum::IntValue(env.ptr_int().const_int(len_u64, false)); + let mut struct_val; - builder.build_store(elem_ptr, nul_terminator); + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_val, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); - BasicValueEnum::PointerValue(ptr) + // Store the length + struct_val = builder + .build_insert_value(struct_val, len, Builtin::WRAPPER_LEN, "insert_len") + .unwrap(); + + BasicValueEnum::StructValue(struct_val.into_struct_value()) } } + + Struct(sorted_fields) => { + let ctx = env.context; + let builder = env.builder; + + // Determine types + let num_fields = sorted_fields.len(); + let mut field_types = Vec::with_capacity_in(num_fields, env.arena); + let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); + + for (field_expr, field_layout) in sorted_fields.iter() { + let val = build_expr(env, &scope, parent, field_expr, procs); + let field_type = + basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes); + + field_types.push(field_type); + field_vals.push(val); + } + + // Create the struct_type + let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); + let mut struct_val = struct_type.const_zero().into(); + + // Insert field exprs into struct_val + for (index, field_val) in field_vals.into_iter().enumerate() { + struct_val = builder + .build_insert_value(struct_val, field_val, index as u32, "insert_field") + .unwrap(); + } + + BasicValueEnum::StructValue(struct_val.into_struct_value()) + } + Tag { + union_size, + arguments, + .. + } if *union_size == 1 => { + let it = arguments.iter(); + + let ctx = env.context; + let builder = env.builder; + + // Determine types + let num_fields = arguments.len() + 1; + let mut field_types = Vec::with_capacity_in(num_fields, env.arena); + let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); + + for (field_expr, field_layout) in it { + let val = build_expr(env, &scope, parent, field_expr, procs); + let field_type = + basic_type_from_layout(env.arena, env.context, &field_layout, env.ptr_bytes); + + field_types.push(field_type); + field_vals.push(val); + } + + // Create the struct_type + let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); + let mut struct_val = struct_type.const_zero().into(); + + // Insert field exprs into struct_val + for (index, field_val) in field_vals.into_iter().enumerate() { + struct_val = builder + .build_insert_value(struct_val, field_val, index as u32, "insert_field") + .unwrap(); + } + + BasicValueEnum::StructValue(struct_val.into_struct_value()) + } + Tag { + arguments, + tag_layout, + .. + } => { + let ptr_size = env.ptr_bytes; + + let whole_size = tag_layout.stack_size(ptr_size); + let mut filler = tag_layout.stack_size(ptr_size); + + let ctx = env.context; + let builder = env.builder; + + // Determine types + let num_fields = arguments.len() + 1; + let mut field_types = Vec::with_capacity_in(num_fields, env.arena); + let mut field_vals = Vec::with_capacity_in(num_fields, env.arena); + + for (field_expr, field_layout) in arguments.iter() { + let val = build_expr(env, &scope, parent, field_expr, procs); + let field_type = + basic_type_from_layout(env.arena, env.context, &field_layout, ptr_size); + + field_types.push(field_type); + field_vals.push(val); + + let field_size = field_layout.stack_size(ptr_size); + filler -= field_size; + } + + // TODO verify that this is required (better safe than sorry) + if filler > 0 { + field_types.push(env.context.i8_type().array_type(filler).into()); + } + + // Create the struct_type + let struct_type = ctx.struct_type(field_types.into_bump_slice(), false); + let mut struct_val = struct_type.const_zero().into(); + + // Insert field exprs into struct_val + for (index, field_val) in field_vals.into_iter().enumerate() { + struct_val = builder + .build_insert_value(struct_val, field_val, index as u32, "insert_field") + .unwrap(); + } + + // How we create tag values + // + // The memory layout of tags can be different. e.g. in + // + // [ Ok Int, Err Str ] + // + // the `Ok` tag stores a 64-bit integer, the `Err` tag stores a struct. + // All tags of a union must have the same length, for easy addressing (e.g. array lookups). + // So we need to ask for the maximum of all tag's sizes, even if most tags won't use + // all that memory, and certainly won't use it in the same way (the tags have fields of + // different types/sizes) + // + // In llvm, we must be explicit about the type of value we're creating: we can't just + // make a unspecified block of memory. So what we do is create a byte array of the + // desired size. Then when we know which tag we have (which is here, in this function), + // we need to cast that down to the array of bytes that llvm expects + // + // There is the bitcast instruction, but it doesn't work for arrays. So we need to jump + // through some hoops using store and load to get this to work: the array is put into a + // one-element struct, which can be cast to the desired type. + // + // This tricks comes from + // https://github.com/raviqqe/ssf/blob/bc32aae68940d5bddf5984128e85af75ca4f4686/ssf-llvm/src/expression_compiler.rs#L116 + + let array_type = ctx.i8_type().array_type(whole_size); + + let result = cast_basic_basic( + builder, + struct_val.into_struct_value().into(), + array_type.into(), + ); + + // For unclear reasons, we can't cast an array to a struct on the other side. + // the solution is to wrap the array in a struct (yea...) + let wrapper_type = ctx.struct_type(&[array_type.into()], false); + let mut wrapper_val = wrapper_type.const_zero().into(); + wrapper_val = builder + .build_insert_value(wrapper_val, result, 0, "insert_field") + .unwrap(); + wrapper_val.into_struct_value().into() + } + AccessAtIndex { + index, + expr, + is_unwrapped, + .. + } if *is_unwrapped => { + let builder = env.builder; + + // Get Struct val + // Since this is a one-element tag union, we get the correct struct immediately + let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value(); + + builder + .build_extract_value( + argument, + *index as u32, + env.arena.alloc(format!("tag_field_access_{}_", index)), + ) + .unwrap() + } + + AccessAtIndex { + index, + expr, + field_layouts, + .. + } => { + let builder = env.builder; + + // Determine types, assumes the descriminant is in the field layouts + let num_fields = field_layouts.len(); + let mut field_types = Vec::with_capacity_in(num_fields, env.arena); + let ptr_bytes = env.ptr_bytes; + + for field_layout in field_layouts.iter() { + let field_type = + basic_type_from_layout(env.arena, env.context, &field_layout, ptr_bytes); + field_types.push(field_type); + } + + // Create the struct_type + let struct_type = env + .context + .struct_type(field_types.into_bump_slice(), false); + + // cast the argument bytes into the desired shape for this tag + let argument = build_expr(env, &scope, parent, expr, procs).into_struct_value(); + + let struct_value = cast_struct_struct(builder, argument, struct_type); + + builder + .build_extract_value(struct_value, *index as u32, "") + .expect("desired field did not decode") + } _ => { panic!("I don't yet know how to LLVM build {:?}", expr); } } } +fn load_symbol<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + scope: &Scope<'a, 'ctx>, + symbol: &Symbol, +) -> BasicValueEnum<'ctx> { + match scope.get(symbol) { + Some((_, ptr)) => env + .builder + .build_load(*ptr, symbol.ident_string(&env.interns)), + None => panic!("Could not find a var for {:?} in scope {:?}", symbol, scope), + } +} + +/// Cast a struct to another struct of the same (or smaller?) size +fn cast_struct_struct<'ctx>( + builder: &Builder<'ctx>, + from_value: StructValue<'ctx>, + to_type: StructType<'ctx>, +) -> StructValue<'ctx> { + cast_basic_basic(builder, from_value.into(), to_type.into()).into_struct_value() +} + +/// Cast a value to another value of the same (or smaller?) size +fn cast_basic_basic<'ctx>( + builder: &Builder<'ctx>, + from_value: BasicValueEnum<'ctx>, + to_type: BasicTypeEnum<'ctx>, +) -> BasicValueEnum<'ctx> { + use inkwell::types::BasicType; + // store the value in memory + let argument_pointer = builder.build_alloca(from_value.get_type(), ""); + builder.build_store(argument_pointer, from_value); + + // then read it back as a different type + let to_type_pointer = builder + .build_bitcast( + argument_pointer, + to_type.ptr_type(inkwell::AddressSpace::Generic), + "", + ) + .into_pointer_value(); + + builder.build_load(to_type_pointer, "") +} + +fn extract_tag_discriminant<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + from_value: StructValue<'ctx>, +) -> IntValue<'ctx> { + let struct_type = env + .context + .struct_type(&[env.context.i64_type().into()], false); + + let struct_value = cast_struct_struct(env.builder, from_value, struct_type); + + env.builder + .build_extract_value(struct_value, 0, "") + .expect("desired field did not decode") + .into_int_value() +} + struct Branch2<'a> { - cond_lhs: &'a Expr<'a>, - cond_rhs: &'a Expr<'a>, + cond: &'a Symbol, pass: &'a Expr<'a>, fail: &'a Expr<'a>, ret_layout: Layout<'a>, @@ -265,33 +642,23 @@ fn build_branch2<'a, 'ctx, 'env>( cond: Branch2<'a>, procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { - let builder = env.builder; let ret_layout = cond.ret_layout; - let ret_type = basic_type_from_layout(env.context, &ret_layout); + let pass = cond.pass; + let fail = cond.fail; + let ret_type = basic_type_from_layout(env.arena, env.context, &ret_layout, env.ptr_bytes); - let lhs = build_expr(env, scope, parent, cond.cond_lhs, procs); - let rhs = build_expr(env, scope, parent, cond.cond_rhs, procs); + let cond_expr = load_symbol(env, scope, cond.cond); - match (lhs, rhs) { - (FloatValue(lhs_float), FloatValue(rhs_float)) => { - let comparison = - builder.build_float_compare(FloatPredicate::OEQ, lhs_float, rhs_float, "cond"); + match cond_expr { + IntValue(value) => { + let build_then = || build_expr(env, scope, parent, pass, procs); + let build_else = || build_expr(env, scope, parent, fail, procs); - build_phi2( - env, scope, parent, comparison, cond.pass, cond.fail, ret_type, procs, - ) - } - - (IntValue(lhs_int), IntValue(rhs_int)) => { - let comparison = builder.build_int_compare(IntPredicate::EQ, lhs_int, rhs_int, "cond"); - - build_phi2( - env, scope, parent, comparison, cond.pass, cond.fail, ret_type, procs, - ) + build_basic_phi2(env, parent, value, build_then, build_else, ret_type) } _ => panic!( - "Tried to make a branch out of incompatible conditions: lhs = {:?} and rhs = {:?}", - cond.cond_lhs, cond.cond_rhs + "Tried to make a branch out of an invalid condition: cond_expr = {:?}", + cond_expr, ), } } @@ -317,6 +684,7 @@ fn build_switch<'a, 'ctx, 'env>( let SwitchArgs { branches, cond_expr, + mut cond_layout, default_branch, ret_type, .. @@ -325,14 +693,48 @@ fn build_switch<'a, 'ctx, 'env>( let cont_block = context.append_basic_block(parent, "cont"); // Build the condition - let cond = build_expr(env, scope, parent, cond_expr, procs).into_int_value(); + let cond = match cond_layout { + Layout::Builtin(Builtin::Float64) => { + // float matches are done on the bit pattern + cond_layout = Layout::Builtin(Builtin::Int64); + let full_cond = build_expr(env, scope, parent, cond_expr, procs); + + builder + .build_bitcast(full_cond, env.context.i64_type(), "") + .into_int_value() + } + Layout::Union(_) => { + // we match on the discriminant, not the whole Tag + cond_layout = Layout::Builtin(Builtin::Int64); + let full_cond = build_expr(env, scope, parent, cond_expr, procs).into_struct_value(); + + extract_tag_discriminant(env, full_cond) + } + Layout::Builtin(_) => build_expr(env, scope, parent, cond_expr, procs).into_int_value(), + other => todo!("Build switch value from layout: {:?}", other), + }; // Build the cases let mut incoming = Vec::with_capacity_in(branches.len(), arena); let mut cases = Vec::with_capacity_in(branches.len(), arena); for (int, _) in branches.iter() { - let int_val = context.i64_type().const_int(*int as u64, false); + // Switch constants must all be same type as switch value! + // e.g. this is incorrect, and will trigger a LLVM warning: + // + // switch i8 %apple1, label %default [ + // i64 2, label %branch2 + // i64 0, label %branch0 + // i64 1, label %branch1 + // ] + // + // they either need to all be i8, or i64 + let int_val = match cond_layout { + Layout::Builtin(Builtin::Int64) => context.i64_type().const_int(*int as u64, false), + Layout::Builtin(Builtin::Bool) => context.bool_type().const_int(*int as u64, false), + Layout::Builtin(Builtin::Byte) => context.i8_type().const_int(*int as u64, false), + _ => panic!("Can't cast to cond_layout = {:?}", cond_layout), + }; let block = context.append_basic_block(parent, format!("branch{}", int).as_str()); cases.push((int_val, block)); @@ -374,8 +776,9 @@ fn build_switch<'a, 'ctx, 'env>( } // TODO trim down these arguments +#[allow(dead_code)] #[allow(clippy::too_many_arguments)] -fn build_phi2<'a, 'ctx, 'env>( +fn build_expr_phi2<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, scope: &Scope<'a, 'ctx>, parent: FunctionValue<'ctx>, @@ -385,6 +788,28 @@ fn build_phi2<'a, 'ctx, 'env>( ret_type: BasicTypeEnum<'ctx>, procs: &Procs<'a>, ) -> BasicValueEnum<'ctx> { + build_basic_phi2( + env, + parent, + comparison, + || build_expr(env, scope, parent, pass, procs), + || build_expr(env, scope, parent, fail, procs), + ret_type, + ) +} + +fn build_basic_phi2<'a, 'ctx, 'env, PassFn, FailFn>( + env: &Env<'a, 'ctx, 'env>, + parent: FunctionValue<'ctx>, + comparison: IntValue<'ctx>, + build_pass: PassFn, + build_fail: FailFn, + ret_type: BasicTypeEnum<'ctx>, +) -> BasicValueEnum<'ctx> +where + PassFn: Fn() -> BasicValueEnum<'ctx>, + FailFn: Fn() -> BasicValueEnum<'ctx>, +{ let builder = env.builder; let context = env.context; @@ -397,14 +822,14 @@ fn build_phi2<'a, 'ctx, 'env>( // build then block builder.position_at_end(then_block); - let then_val = build_expr(env, scope, parent, pass, procs); + let then_val = build_pass(); builder.build_unconditional_branch(cont_block); let then_block = builder.get_insert_block().unwrap(); // build else block builder.position_at_end(else_block); - let else_val = build_expr(env, scope, parent, fail, procs); + let else_val = build_fail(); builder.build_unconditional_branch(cont_block); let else_block = builder.get_insert_block().unwrap(); @@ -414,10 +839,7 @@ fn build_phi2<'a, 'ctx, 'env>( let phi = builder.build_phi(ret_type, "branch"); - phi.add_incoming(&[ - (&Into::::into(then_val), then_block), - (&Into::::into(else_val), else_block), - ]); + phi.add_incoming(&[(&then_val, then_block), (&else_val, else_block)]); phi.as_basic_value() } @@ -460,12 +882,12 @@ pub fn build_proc_header<'a, 'ctx, 'env>( let args = proc.args; let arena = env.arena; let context = &env.context; - let ret_type = basic_type_from_layout(context, &proc.ret_layout); + let ret_type = basic_type_from_layout(arena, context, &proc.ret_layout, env.ptr_bytes); let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); let mut arg_symbols = Vec::new_in(arena); for (layout, arg_symbol) in args.iter() { - let arg_type = basic_type_from_layout(env.context, &layout); + let arg_type = basic_type_from_layout(arena, env.context, &layout, env.ptr_bytes); arg_basic_types.push(arg_type); arg_symbols.push(arg_symbol); @@ -479,6 +901,8 @@ pub fn build_proc_header<'a, 'ctx, 'env>( Some(Linkage::Private), ); + fn_val.set_call_conventions(DEFAULT_CALLING_CONVENTION); + (fn_val, arg_basic_types) } @@ -530,41 +954,76 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) { } #[inline(always)] +#[allow(clippy::cognitive_complexity)] fn call_with_args<'a, 'ctx, 'env>( symbol: Symbol, - args: &[BasicValueEnum<'ctx>], + parent: FunctionValue<'ctx>, + args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], env: &Env<'a, 'ctx, 'env>, ) -> BasicValueEnum<'ctx> { match symbol { - Symbol::NUM_ADD => { + Symbol::INT_ADD | Symbol::NUM_ADD => { debug_assert!(args.len() == 2); let int_val = env.builder.build_int_add( - args[0].into_int_value(), - args[1].into_int_value(), - "ADD_I64", + args[0].0.into_int_value(), + args[1].0.into_int_value(), + "add_i64", ); BasicValueEnum::IntValue(int_val) } - Symbol::NUM_SUB => { + Symbol::FLOAT_ADD => { + debug_assert!(args.len() == 2); + + let float_val = env.builder.build_float_add( + args[0].0.into_float_value(), + args[1].0.into_float_value(), + "add_f64", + ); + + BasicValueEnum::FloatValue(float_val) + } + Symbol::INT_SUB | Symbol::NUM_SUB => { debug_assert!(args.len() == 2); let int_val = env.builder.build_int_sub( - args[0].into_int_value(), - args[1].into_int_value(), - "SUB_I64", + args[0].0.into_int_value(), + args[1].0.into_int_value(), + "sub_I64", ); BasicValueEnum::IntValue(int_val) } + Symbol::FLOAT_SUB => { + debug_assert!(args.len() == 2); + + let float_val = env.builder.build_float_sub( + args[0].0.into_float_value(), + args[1].0.into_float_value(), + "sub_f64", + ); + + BasicValueEnum::FloatValue(float_val) + } + Symbol::FLOAT_DIV => { + debug_assert!(args.len() == 2); + + let float_val = env.builder.build_float_div( + args[0].0.into_float_value(), + args[1].0.into_float_value(), + "div_f64", + ); + + BasicValueEnum::FloatValue(float_val) + } Symbol::NUM_MUL => { debug_assert!(args.len() == 2); let int_val = env.builder.build_int_mul( - args[0].into_int_value(), - args[1].into_int_value(), - "MUL_I64", + args[0].0.into_int_value(), + args[1].0.into_int_value(), + "mul_i64", ); BasicValueEnum::IntValue(int_val) @@ -574,72 +1033,151 @@ fn call_with_args<'a, 'ctx, 'env>( let int_val = env .builder - .build_int_neg(args[0].into_int_value(), "NEGATE_I64"); + .build_int_neg(args[0].0.into_int_value(), "negate_i64"); BasicValueEnum::IntValue(int_val) } - Symbol::FLOAT_DIV => { + Symbol::LIST_LEN => { + debug_assert!(args.len() == 1); + + BasicValueEnum::IntValue(load_list_len(env.builder, args[0].0.into_struct_value())) + } + Symbol::LIST_IS_EMPTY => { + debug_assert!(args.len() == 1); + + let list_struct = args[0].0.into_struct_value(); + let builder = env.builder; + let list_len = load_list_len(builder, list_struct); + let zero = env.ptr_int().const_zero(); + let answer = builder.build_int_compare(IntPredicate::EQ, list_len, zero, "is_zero"); + + BasicValueEnum::IntValue(answer) + } + Symbol::INT_EQ_I64 => { debug_assert!(args.len() == 2); - let float_val = env.builder.build_float_div( - args[0].into_float_value(), - args[1].into_float_value(), - "DIV_F64", + let int_val = env.builder.build_int_compare( + IntPredicate::EQ, + args[0].0.into_int_value(), + args[1].0.into_int_value(), + "cmp_i64", ); - BasicValueEnum::FloatValue(float_val) + BasicValueEnum::IntValue(int_val) } - // Symbol::INT_DIV => { - // debug_assert!(args.len() == 2); - // - // let int_val = env.builder.build_int_signed_div( - // args[0].into_int_value(), - // args[1].into_int_value(), - // "DIV_I64", - // ); - // - // BasicValueEnum::IntValue(int_val) - // } - Symbol::LIST_GET_UNSAFE => { + Symbol::INT_EQ_I1 => { debug_assert!(args.len() == 2); - let list_ptr = args[0].into_pointer_value(); - let elem_index = args[1].into_int_value(); + let int_val = env.builder.build_int_compare( + IntPredicate::EQ, + args[0].0.into_int_value(), + args[1].0.into_int_value(), + "cmp_i1", + ); - let builder = env.builder; - let elem_bytes = 8; // TODO Look this up instead of hardcoding it! - let elem_size = env.context.i64_type().const_int(elem_bytes, false); - let offset = builder.build_int_mul(elem_index, elem_size, "MUL_OFFSET"); - - let elem_ptr = unsafe { builder.build_gep(list_ptr, &[offset], "elem") }; - - builder.build_load(elem_ptr, "List.get") + BasicValueEnum::IntValue(int_val) } - Symbol::LIST_SET /* TODO clone first for LIST_SET! */ | Symbol::LIST_SET_IN_PLACE => { - debug_assert!(args.len() == 3); + Symbol::INT_EQ_I8 => { + debug_assert!(args.len() == 2); - let list_ptr = args[0].into_pointer_value(); - let elem_index = args[1].into_int_value(); - let elem = args[2]; + let int_val = env.builder.build_int_compare( + IntPredicate::EQ, + args[0].0.into_int_value(), + args[1].0.into_int_value(), + "cmp_i8", + ); - let builder = env.builder; - let elem_bytes = 8; // TODO Look this up instead of hardcoding it! - let elem_size = env.context.i64_type().const_int(elem_bytes, false); - let offset = builder.build_int_mul(elem_index, elem_size, "MUL_OFFSET"); - - let elem_ptr = unsafe { builder.build_gep(list_ptr, &[offset], "elem") }; - - builder.build_store(elem_ptr, elem); - - list_ptr.into() + BasicValueEnum::IntValue(int_val) } + Symbol::NUM_TO_FLOAT => { + // TODO specialize this to be not just for i64! + let builtin_fn_name = "i64_to_f64_"; + + let fn_val = env + .module + .get_function(builtin_fn_name) + .unwrap_or_else(|| panic!("Unrecognized builtin function: {:?} - if you're working on the Roc compiler, do you need to rebuild the bitcode? See compiler/builtins/bitcode/README.md", builtin_fn_name)); + + let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); + + for (arg, _layout) in args.iter() { + arg_vals.push(*arg); + } + + let call = env + .builder + .build_call(fn_val, arg_vals.into_bump_slice(), "call_builtin"); + + call.set_call_convention(DEFAULT_CALLING_CONVENTION); + + call.try_as_basic_value() + .left() + .unwrap_or_else(|| panic!("LLVM error: Invalid call for builtin {:?}", symbol)) + } + Symbol::FLOAT_EQ => { + debug_assert!(args.len() == 2); + + let int_val = env.builder.build_float_compare( + FloatPredicate::OEQ, + args[0].0.into_float_value(), + args[1].0.into_float_value(), + "cmp_f64", + ); + + BasicValueEnum::IntValue(int_val) + } + Symbol::LIST_GET_UNSAFE => { + let builder = env.builder; + + // List.get : List elem, Int -> [ Ok elem, OutOfBounds ]* + debug_assert!(args.len() == 2); + + let (_, list_layout) = &args[0]; + + let wrapper_struct = args[0].0.into_struct_value(); + let elem_index = args[1].0.into_int_value(); + + // Get the usize length from the wrapper struct + let _list_len = load_list_len(builder, wrapper_struct); + + // TODO here, check to see if the requested index exceeds the length of the array. + + match list_layout { + Layout::Builtin(Builtin::List(_)) => { + // Load the pointer to the array data + let array_data_ptr = load_list_ptr(builder, wrapper_struct); + + // We already checked the bounds earlier. + let elem_ptr = unsafe { + builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "elem") + }; + + builder.build_load(elem_ptr, "List.get") + } + _ => { + unreachable!("Invalid List layout for List.get: {:?}", list_layout); + } + } + } + Symbol::LIST_SET => list_set(parent, args, env, InPlace::Clone), + Symbol::LIST_SET_IN_PLACE => list_set(parent, args, env, InPlace::InPlace), _ => { let fn_val = env .module .get_function(symbol.ident_string(&env.interns)) .unwrap_or_else(|| panic!("Unrecognized function: {:?}", symbol)); - let call = env.builder.build_call(fn_val, args, "tmp"); + let mut arg_vals: Vec = Vec::with_capacity_in(args.len(), env.arena); + + for (arg, _layout) in args.iter() { + arg_vals.push(*arg); + } + + let call = env + .builder + .build_call(fn_val, arg_vals.into_bump_slice(), "call"); + + call.set_call_convention(DEFAULT_CALLING_CONVENTION); call.try_as_basic_value() .left() @@ -647,3 +1185,171 @@ fn call_with_args<'a, 'ctx, 'env>( } } } + +fn load_list_len<'ctx>( + builder: &Builder<'ctx>, + wrapper_struct: StructValue<'ctx>, +) -> IntValue<'ctx> { + builder + .build_extract_value(wrapper_struct, Builtin::WRAPPER_LEN, "list_len") + .unwrap() + .into_int_value() +} + +fn load_list_ptr<'ctx>( + builder: &Builder<'ctx>, + wrapper_struct: StructValue<'ctx>, +) -> PointerValue<'ctx> { + builder + .build_extract_value(wrapper_struct, Builtin::WRAPPER_PTR, "list_ptr") + .unwrap() + .into_pointer_value() +} + +fn clone_list<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + list_len: IntValue<'ctx>, + elems_ptr: PointerValue<'ctx>, + elem_layout: &Layout<'_>, +) -> (StructValue<'ctx>, PointerValue<'ctx>) { + let builder = env.builder; + let ctx = env.context; + let ptr_bytes = env.ptr_bytes; + + // Calculate the number of bytes we'll need to allocate. + let elem_bytes = env + .ptr_int() + .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); + let size = env + .builder + .build_int_mul(elem_bytes, list_len, "mul_len_by_elem_bytes"); + + // Allocate space for the new array that we'll copy into. + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let clone_ptr = builder + .build_array_malloc(elem_type, list_len, "list_ptr") + .unwrap(); + + // TODO check if malloc returned null; if so, runtime error for OOM! + + // Either memcpy or deep clone the array elements + if elem_layout.safe_to_memcpy() { + // Copy the bytes from the original array into the new + // one we just malloc'd. + // + // TODO how do we decide when to do the small memcpy vs the normal one? + builder.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, size); + } else { + panic!("TODO Cranelift currently only knows how to clone list elements that are Copy."); + } + + // Create a fresh wrapper struct for the newly populated array + let struct_type = collection_wrapper(ctx, clone_ptr.get_type(), env.ptr_bytes); + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + clone_ptr, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value(struct_val, list_len, Builtin::WRAPPER_LEN, "insert_len") + .unwrap(); + + (struct_val.into_struct_value(), clone_ptr) +} + +enum InPlace { + InPlace, + Clone, +} + +fn bounds_check_comparison<'ctx>( + builder: &Builder<'ctx>, + elem_index: IntValue<'ctx>, + len: IntValue<'ctx>, +) -> IntValue<'ctx> { + // Note: Check for index < length as the "true" condition, + // to avoid misprediction. (In practice this should usually pass, + // and CPUs generally default to predicting that a forward jump + // shouldn't be taken; that is, they predict "else" won't be taken.) + builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check") +} + +fn list_set<'a, 'ctx, 'env>( + parent: FunctionValue<'ctx>, + args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], + env: &Env<'a, 'ctx, 'env>, + in_place: InPlace, +) -> BasicValueEnum<'ctx> { + // List.set : List elem, Int, elem -> List elem + let builder = env.builder; + + debug_assert!(args.len() == 3); + + let original_wrapper = args[0].0.into_struct_value(); + let elem_index = args[1].0.into_int_value(); + + // Load the usize length from the wrapper. We need it for bounds checking. + let list_len = load_list_len(builder, original_wrapper); + + // Bounds check: only proceed if index < length. + // Otherwise, return the list unaltered. + let comparison = bounds_check_comparison(builder, elem_index, list_len); + + // If the index is in bounds, clone and mutate in place. + let build_then = || { + let (elem, elem_layout) = args[2]; + let (new_wrapper, array_data_ptr) = match in_place { + InPlace::InPlace => (original_wrapper, load_list_ptr(builder, original_wrapper)), + InPlace::Clone => clone_list( + env, + list_len, + load_list_ptr(builder, original_wrapper), + elem_layout, + ), + }; + + // If we got here, we passed the bounds check, so this is an in-bounds GEP + let elem_ptr = + unsafe { builder.build_in_bounds_gep(array_data_ptr, &[elem_index], "load_index") }; + + // Mutate the new array in-place to change the element. + builder.build_store(elem_ptr, elem); + + BasicValueEnum::StructValue(new_wrapper) + }; + + // If the index was out of bounds, return the original list unaltered. + let build_else = || BasicValueEnum::StructValue(original_wrapper); + let ret_type = original_wrapper.get_type(); + + build_basic_phi2( + env, + parent, + comparison, + build_then, + build_else, + ret_type.into(), + ) +} + +/// Translates a target_lexicon::Triple to a LLVM calling convention u32 +/// as described in https://llvm.org/doxygen/namespacellvm_1_1CallingConv.html +pub fn get_call_conventions(cc: CallingConvention) -> u32 { + use CallingConvention::*; + + // For now, we're returning 0 for the C calling convention on all of these. + // Not sure if we should be picking something more specific! + match cc { + SystemV => 0, + WasmBasicCAbi => 0, + WindowsFastcall => 0, + } +} diff --git a/compiler/gen/src/llvm/builtins.bc b/compiler/gen/src/llvm/builtins.bc new file mode 100644 index 0000000000..40bf79bc49 Binary files /dev/null and b/compiler/gen/src/llvm/builtins.bc differ diff --git a/compiler/gen/src/llvm/convert.rs b/compiler/gen/src/llvm/convert.rs index 00b1bd703c..250fbe765d 100644 --- a/compiler/gen/src/llvm/convert.rs +++ b/compiler/gen/src/llvm/convert.rs @@ -1,9 +1,11 @@ +use bumpalo::collections::Vec; +use bumpalo::Bump; use inkwell::context::Context; use inkwell::types::BasicTypeEnum::{self, *}; -use inkwell::types::{BasicType, FunctionType}; +use inkwell::types::{ArrayType, BasicType, FunctionType, IntType, PointerType, StructType}; use inkwell::AddressSpace; -use roc_mono::layout::Layout; +use roc_mono::layout::{Builtin, Layout}; /// TODO could this be added to Inkwell itself as a method on BasicValueEnum? pub fn get_fn_type<'ctx>( @@ -20,45 +22,155 @@ pub fn get_fn_type<'ctx>( } } +/// TODO could this be added to Inkwell itself as a method on BasicValueEnum? +pub fn get_array_type<'ctx>(bt_enum: &BasicTypeEnum<'ctx>, size: u32) -> ArrayType<'ctx> { + match bt_enum { + ArrayType(typ) => typ.array_type(size), + IntType(typ) => typ.array_type(size), + FloatType(typ) => typ.array_type(size), + PointerType(typ) => typ.array_type(size), + StructType(typ) => typ.array_type(size), + VectorType(typ) => typ.array_type(size), + } +} + pub fn basic_type_from_layout<'ctx>( + arena: &Bump, context: &'ctx Context, layout: &Layout<'_>, + ptr_bytes: u32, ) -> BasicTypeEnum<'ctx> { use roc_mono::layout::Builtin::*; use roc_mono::layout::Layout::*; match layout { FunctionPointer(args, ret_layout) => { - let ret_type = basic_type_from_layout(context, &ret_layout); - let mut arg_basic_types = Vec::with_capacity(args.len()); + let ret_type = basic_type_from_layout(arena, context, &ret_layout, ptr_bytes); + let mut arg_basic_types = Vec::with_capacity_in(args.len(), arena); for arg_layout in args.iter() { - arg_basic_types.push(basic_type_from_layout(context, arg_layout)); + arg_basic_types.push(basic_type_from_layout( + arena, context, arg_layout, ptr_bytes, + )); } - let fn_type = get_fn_type(&ret_type, arg_basic_types.as_slice()); + let fn_type = get_fn_type(&ret_type, arg_basic_types.into_bump_slice()); let ptr_type = fn_type.ptr_type(AddressSpace::Generic); ptr_type.as_basic_type_enum() } - Struct(_fields) => { - panic!("TODO layout_to_basic_type for Struct"); + Pointer(layout) => basic_type_from_layout(arena, context, &layout, ptr_bytes) + .ptr_type(AddressSpace::Generic) + .into(), + Struct(sorted_fields) => { + // Determine types + let mut field_types = Vec::with_capacity_in(sorted_fields.len(), arena); + + for field_layout in sorted_fields.iter() { + field_types.push(basic_type_from_layout( + arena, + context, + field_layout, + ptr_bytes, + )); + } + + context + .struct_type(field_types.into_bump_slice(), false) + .as_basic_type_enum() } - Pointer(_layout) => { - panic!("TODO layout_to_basic_type for Pointer"); + Union(tags) if tags.len() == 1 => { + let layouts = tags.iter().next().unwrap(); + + // Determine types + let mut field_types = Vec::with_capacity_in(layouts.len(), arena); + + for layout in layouts.iter() { + field_types.push(basic_type_from_layout(arena, context, layout, ptr_bytes)); + } + + context + .struct_type(field_types.into_bump_slice(), false) + .as_basic_type_enum() } + Union(_) => { + // TODO make this dynamic + let ptr_size = std::mem::size_of::(); + let union_size = layout.stack_size(ptr_size as u32); + + let array_type = context + .i8_type() + .array_type(union_size) + .as_basic_type_enum(); + + context.struct_type(&[array_type], false).into() + } + Builtin(builtin) => match builtin { Int64 => context.i64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(), - Str => context + Bool => context.bool_type().as_basic_type_enum(), + Byte => context.i8_type().as_basic_type_enum(), + Str | EmptyStr => context .i8_type() .ptr_type(AddressSpace::Generic) .as_basic_type_enum(), - Map(_, _) => panic!("TODO layout_to_basic_type for Builtin::Map"), - Set(_) => panic!("TODO layout_to_basic_type for Builtin::Set"), - List(elem_layout) => basic_type_from_layout(context, elem_layout) - .ptr_type(AddressSpace::Generic) - .as_basic_type_enum(), + Map(_, _) | EmptyMap => panic!("TODO layout_to_basic_type for Builtin::Map"), + Set(_) | EmptySet => panic!("TODO layout_to_basic_type for Builtin::Set"), + List(elem_layout) => { + let ptr_type = basic_type_from_layout(arena, context, elem_layout, ptr_bytes) + .ptr_type(AddressSpace::Generic); + + collection_wrapper(context, ptr_type, ptr_bytes).into() + } + EmptyList => BasicTypeEnum::StructType(empty_collection(context, ptr_bytes)), }, } } + +/// A length usize and a pointer to some elements. +/// Could be a wrapper for a List or a Str. +/// +/// The order of these doesn't matter, since they should be initialized +/// to zero anyway for an empty collection; as such, we return a +/// (usize, usize) struct layout no matter what. +pub fn empty_collection(ctx: &Context, ptr_bytes: u32) -> StructType<'_> { + let usize_type = BasicTypeEnum::IntType(ptr_int(ctx, ptr_bytes)); + + ctx.struct_type(&[usize_type, usize_type], false) +} + +/// A length usize and a pointer to some elements. +/// +/// Could be a wrapper for a List or a Str. +pub fn collection_wrapper<'ctx>( + ctx: &'ctx Context, + ptr_type: PointerType<'ctx>, + ptr_bytes: u32, +) -> StructType<'ctx> { + let ptr_type_enum = BasicTypeEnum::PointerType(ptr_type); + let len_type = BasicTypeEnum::IntType(ptr_int(ctx, ptr_bytes)); + + // This conditional is based on a constant, so the branch should be optimized away. + // The reason for keeping the conditional here is so we can flip the order + // of the fields (by changing the constants) without breaking this code. + if Builtin::WRAPPER_PTR == 0 { + ctx.struct_type(&[ptr_type_enum, len_type], false) + } else { + ctx.struct_type(&[len_type, ptr_type_enum], false) + } +} + +pub fn ptr_int(ctx: &Context, ptr_bytes: u32) -> IntType<'_> { + match ptr_bytes { + 1 => ctx.i8_type(), + 2 => ctx.i16_type(), + 4 => ctx.i32_type(), + 8 => ctx.i64_type(), + 16 => ctx.i128_type(), + _ => panic!( + "Invalid target: Roc does't support compiling to {}-bit systems.", + ptr_bytes * 8 + ), + } +} diff --git a/compiler/gen/test.asm b/compiler/gen/test.asm new file mode 100644 index 0000000000..7c703ef496 --- /dev/null +++ b/compiler/gen/test.asm @@ -0,0 +1,4 @@ + .text + .file "my_module" + + .section ".note.GNU-stack","",@progbits diff --git a/compiler/gen/tests/gen_builtins.rs b/compiler/gen/tests/gen_builtins.rs new file mode 100644 index 0000000000..095f27c693 --- /dev/null +++ b/compiler/gen/tests/gen_builtins.rs @@ -0,0 +1,387 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate inkwell; +extern crate libc; +extern crate roc_gen; + +#[macro_use] +mod helpers; + +#[cfg(test)] +mod gen_builtins { + use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; + use bumpalo::Bump; + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use inkwell::passes::PassManager; + use inkwell::types::BasicType; + use inkwell::OptimizationLevel; + use roc_collections::all::ImMap; + use roc_gen::llvm::build::{build_proc, build_proc_header}; + use roc_gen::llvm::convert::basic_type_from_layout; + use roc_mono::expr::{Expr, Procs}; + use roc_mono::layout::Layout; + use roc_types::subs::Subs; + + #[test] + fn empty_list_literal() { + assert_evals_to!("[]", &[], &'static [i64]); + } + + #[test] + fn int_list_literal() { + assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]); + } + + #[test] + fn gen_if_fn() { + assert_evals_to!( + indoc!( + r#" + limitedNegate = \num -> + if num == 1 then + -1 + else if num == -1 then + 1 + else + num + + limitedNegate 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn gen_float_eq() { + assert_evals_to!( + indoc!( + r#" + 1.0 == 1.0 + "# + ), + true, + bool + ); + } + + #[test] + fn gen_add_f64() { + assert_evals_to!( + indoc!( + r#" + 1.1 + 2.4 + 3 + "# + ), + 6.5, + f64 + ); + } + + #[test] + fn gen_div_f64() { + assert_evals_to!( + indoc!( + r#" + 48 / 2 + "# + ), + 24.0, + f64 + ); + } + + #[test] + fn gen_add_i64() { + assert_evals_to!( + indoc!( + r#" + 1 + 2 + 3 + "# + ), + 6, + i64 + ); + } + + #[test] + fn gen_sub_f64() { + assert_evals_to!( + indoc!( + r#" + 1.5 - 2.4 - 3 + "# + ), + -3.9, + f64 + ); + } + + #[test] + fn gen_sub_i64() { + assert_evals_to!( + indoc!( + r#" + 1 - 2 - 3 + "# + ), + -4, + i64 + ); + } + + #[test] + fn gen_mul_i64() { + assert_evals_to!( + indoc!( + r#" + 2 * 4 * 6 + "# + ), + 48, + i64 + ); + } + + #[test] + fn gen_order_of_arithmetic_ops() { + assert_evals_to!( + indoc!( + r#" + 1 + 3 * 7 - 2 + "# + ), + 20, + i64 + ); + } + + #[test] + fn gen_order_of_arithmetic_ops_complex_float() { + assert_evals_to!( + indoc!( + r#" + 48 / 2 + 3 + "# + ), + 27.0, + f64 + ); + } + + #[test] + fn if_guard_bind_variable() { + assert_evals_to!( + indoc!( + r#" + when 10 is + x if x == 5 -> 0 + _ -> 42 + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when 10 is + x if x == 10 -> 42 + _ -> 0 + "# + ), + 42, + i64 + ); + } + #[test] + fn tail_call_elimination() { + assert_evals_to!( + indoc!( + r#" + sum = \n, accum -> + when n is + 0 -> accum + _ -> sum (n - 1) (n + accum) + + sum 1_000_000 0 + "# + ), + 500000500000, + i64 + ); + } + #[test] + fn int_negate() { + assert_evals_to!("Num.neg 123", -123, i64); + } + + #[test] + fn gen_basic_fn() { + assert_evals_to!( + indoc!( + r#" + always42 : Num.Num Int.Integer -> Num.Num Int.Integer + always42 = \num -> 42 + + always42 5 + "# + ), + 42, + i64 + ); + } + + #[test] + fn empty_list_len() { + assert_evals_to!("List.len []", 0, usize); + } + + #[test] + fn basic_int_list_len() { + assert_evals_to!("List.len [ 12, 9, 6, 3 ]", 4, usize); + } + + // #[test] + // fn loaded_int_list_len() { + // assert_evals_to!( + // indoc!( + // r#" + // nums = [ 2, 4, 6 ] + + // List.len nums + // "# + // ), + // 3, + // usize + // ); + // } + + // #[test] + // fn fn_int_list_len() { + // assert_evals_to!( + // indoc!( + // r#" + // # TODO remove this annotation once monomorphization works! + // getLen = \list -> List.len list + + // nums = [ 2, 4, 6 ] + + // getLen nums + // "# + // ), + // 3, + // usize + // ); + // } + + // #[test] + // fn int_list_is_empty() { + // assert_evals_to!("List.isEmpty [ 12, 9, 6, 3 ]", 0, u8, |x| x); + // } + // + + #[test] + fn head_int_list() { + assert_evals_to!("List.getUnsafe [ 12, 9, 6, 3 ] 0", 12, i64); + } + + #[test] + fn get_int_list() { + assert_evals_to!("List.getUnsafe [ 12, 9, 6 ] 1", 9, i64); + } + + #[test] + fn get_set_unique_int_list() { + assert_evals_to!("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", 42, i64); + } + + #[test] + fn set_unique_int_list() { + assert_evals_to!( + "List.set [ 12, 9, 7, 1, 5 ] 2 33", + &[12, 9, 33, 1, 5], + &'static [i64] + ); + } + + #[test] + fn set_unique_list_oob() { + assert_evals_to!( + "List.set [ 3, 17, 4.1 ] 1337 9.25", + &[3.0, 17.0, 4.1], + &'static [f64] + ); + } + + #[test] + fn set_shared_int_list() { + assert_evals_to!( + indoc!( + r#" + shared = [ 2.1, 4.3 ] + + # This should not mutate the original + x = List.getUnsafe (List.set shared 1 7.7) 1 + + { x, y: List.getUnsafe shared 1 } + "# + ), + (7.7, 4.3), + (f64, f64) + ); + } + + #[test] + fn set_shared_list_oob() { + assert_evals_to!( + indoc!( + r#" + shared = [ 2, 4 ] + + # This List.set is out of bounds, and should have no effect + x = List.getUnsafe (List.set shared 422 0) 1 + + { x, y: List.getUnsafe shared 1 } + "# + ), + (4, 4), + (i64, i64) + ); + } + + #[test] + fn get_unique_int_list() { + assert_evals_to!( + indoc!( + r#" + shared = [ 2, 4 ] + + List.getUnsafe shared 1 + "# + ), + 4, + i64 + ); + } + + #[test] + fn int_to_float() { + assert_evals_to!( + indoc!( + r#" + Num.toFloat 0x9 + "# + ), + 9.0, + f64 + ); + } +} diff --git a/compiler/gen/tests/gen_primitives.rs b/compiler/gen/tests/gen_primitives.rs new file mode 100644 index 0000000000..6279c0a2fe --- /dev/null +++ b/compiler/gen/tests/gen_primitives.rs @@ -0,0 +1,429 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate inkwell; +extern crate libc; +extern crate roc_gen; + +#[macro_use] +mod helpers; + +#[cfg(test)] +mod gen_primitives { + use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; + use bumpalo::Bump; + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use inkwell::passes::PassManager; + use inkwell::types::BasicType; + use inkwell::OptimizationLevel; + use roc_collections::all::ImMap; + use roc_gen::llvm::build::{build_proc, build_proc_header}; + use roc_gen::llvm::convert::basic_type_from_layout; + use roc_mono::expr::{Expr, Procs}; + use roc_mono::layout::Layout; + use roc_types::subs::Subs; + use std::ffi::{CStr, CString}; + use std::os::raw::c_char; + + #[test] + fn basic_str() { + assert_evals_to!( + "\"shirt and hat\"", + CString::new("shirt and hat").unwrap().as_c_str(), + *const c_char, + CStr::from_ptr + ); + } + + #[test] + fn basic_int() { + assert_evals_to!("123", 123, i64); + } + + #[test] + fn basic_float() { + assert_evals_to!("1234.0", 1234.0, f64); + } + + #[test] + fn branch_first_float() { + assert_evals_to!( + indoc!( + r#" + when 1.23 is + 1.23 -> 12 + _ -> 34 + "# + ), + 12, + i64 + ); + } + + #[test] + fn branch_second_float() { + assert_evals_to!( + indoc!( + r#" + when 2.34 is + 1.23 -> 63 + _ -> 48 + "# + ), + 48, + i64 + ); + } + + #[test] + fn branch_third_float() { + assert_evals_to!( + indoc!( + r#" + when 10.0 is + 1.0 -> 63 + 2.0 -> 48 + _ -> 112 + "# + ), + 112, + i64 + ); + } + + #[test] + fn branch_first_int() { + assert_evals_to!( + indoc!( + r#" + when 1 is + 1 -> 12 + _ -> 34 + "# + ), + 12, + i64 + ); + } + + #[test] + fn branch_second_int() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 1 -> 63 + _ -> 48 + "# + ), + 48, + i64 + ); + } + + #[test] + fn branch_third_int() { + assert_evals_to!( + indoc!( + r#" + when 10 is + 1 -> 63 + 2 -> 48 + _ -> 112 + "# + ), + 112, + i64 + ); + } + + #[test] + fn branch_store_variable() { + assert_evals_to!( + indoc!( + r#" + when 0 is + 1 -> 12 + a -> a + "# + ), + 0, + i64 + ); + } + + #[test] + fn when_one_element_tag() { + assert_evals_to!( + indoc!( + r#" + x : [ Pair Int Int ] + x = Pair 0x2 0x3 + + when x is + Pair l r -> l + r + "# + ), + 5, + i64 + ); + } + + #[test] + fn gen_when_one_branch() { + assert_evals_to!( + indoc!( + r#" + when 3.14 is + _ -> 23 + "# + ), + 23, + i64 + ); + } + + #[test] + fn gen_large_when_int() { + assert_evals_to!( + indoc!( + r#" + foo = \num -> + when num is + 0 -> 200 + -3 -> 111 # TODO adding more negative numbers reproduces parsing bugs here + 3 -> 789 + 1 -> 123 + 2 -> 456 + _ -> 1000 + + foo -3 + "# + ), + 111, + i64 + ); + } + + // #[test] + // fn gen_large_when_float() { + // assert_evals_to!( + // indoc!( + // r#" + // foo = \num -> + // when num is + // 0.5 -> 200.1 + // -3.6 -> 111.2 # TODO adding more negative numbers reproduces parsing bugs here + // 3.6 -> 789.5 + // 1.7 -> 123.3 + // 2.8 -> 456.4 + // _ -> 1000.6 + + // foo -3.6 + // "# + // ), + // 111.2, + // f64 + // ); + // } + + #[test] + fn or_pattern() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 1 | 2 -> 42 + _ -> 1 + "# + ), + 42, + i64 + ); + } + + #[test] + fn apply_identity() { + assert_evals_to!( + indoc!( + r#" + identity = \a -> a + + identity 5 + "# + ), + 5, + i64 + ); + } + + #[test] + fn apply_unnamed_fn() { + assert_evals_to!( + indoc!( + r#" + (\a -> a) 5 + "# + ), + 5, + i64 + ); + } + + #[test] + fn return_unnamed_fn() { + assert_evals_to!( + indoc!( + r#" + alwaysFloatIdentity : Int -> (Float -> Float) + alwaysFloatIdentity = \num -> + (\a -> a) + + (alwaysFloatIdentity 2) 3.14 + "# + ), + 3.14, + f64 + ); + } + + #[test] + fn gen_when_fn() { + assert_evals_to!( + indoc!( + r#" + limitedNegate = \num -> + when num is + 1 -> -1 + -1 -> 1 + _ -> num + + limitedNegate 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn gen_basic_def() { + assert_evals_to!( + indoc!( + r#" + answer = 42 + + answer + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + pi = 3.14 + + pi + "# + ), + 3.14, + f64 + ); + } + + #[test] + fn gen_multiple_defs() { + assert_evals_to!( + indoc!( + r#" + answer = 42 + + pi = 3.14 + + answer + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + answer = 42 + + pi = 3.14 + + pi + "# + ), + 3.14, + f64 + ); + } + + #[test] + fn gen_chained_defs() { + assert_evals_to!( + indoc!( + r#" + x = i1 + i3 = i2 + i1 = 1337 + i2 = i1 + y = 12.4 + + i3 + "# + ), + 1337, + i64 + ); + } + #[test] + fn gen_nested_defs() { + assert_evals_to!( + indoc!( + r#" + x = 5 + + answer = + i3 = i2 + + nested = + a = 1.0 + b = 5 + + i1 + + i1 = 1337 + i2 = i1 + + + nested + + # None of this should affect anything, even though names + # overlap with the previous nested defs + unused = + nested = 17 + + i1 = 84.2 + + nested + + y = 12.4 + + answer + "# + ), + 1337, + i64 + ); + } +} diff --git a/compiler/gen/tests/gen_records.rs b/compiler/gen/tests/gen_records.rs new file mode 100644 index 0000000000..7066594b44 --- /dev/null +++ b/compiler/gen/tests/gen_records.rs @@ -0,0 +1,394 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate inkwell; +extern crate libc; +extern crate roc_gen; + +#[macro_use] +mod helpers; + +#[cfg(test)] +mod gen_records { + use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; + use bumpalo::Bump; + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use inkwell::passes::PassManager; + use inkwell::types::BasicType; + use inkwell::OptimizationLevel; + use roc_collections::all::ImMap; + use roc_gen::llvm::build::{build_proc, build_proc_header}; + use roc_gen::llvm::convert::basic_type_from_layout; + use roc_mono::expr::{Expr, Procs}; + use roc_mono::layout::Layout; + use roc_types::subs::Subs; + + #[test] + fn basic_record() { + assert_evals_to!( + indoc!( + r#" + { y: 17, x: 15, z: 19 }.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x: 15, y: 17, z: 19 }.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn f64_record() { + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.x + "# + ), + 15.1, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.y + "# + ), + 17.2, + f64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { y: 17.2, x: 15.1, z: 19.3 } + + rec.z + "# + ), + 19.3, + f64 + ); + } + + #[test] + fn fn_record() { + assert_evals_to!( + indoc!( + r#" + getRec = \x -> { y: 17, x, z: 19 } + + (getRec 15).x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.z + "# + ), + 19, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.z + rec.x + "# + ), + 34, + i64 + ); + } + + #[test] + fn def_record() { + assert_evals_to!( + indoc!( + r#" + rec = { y: 17, x: 15, z: 19 } + + rec.x + "# + ), + 15, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.y + "# + ), + 17, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + rec = { x: 15, y: 17, z: 19 } + + rec.z + "# + ), + 19, + i64 + ); + } + + #[test] + fn when_on_record() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2 } is + { x } -> x + 3 + "# + ), + 5, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when { x: 0x2, y: 3.14 } is + { x: var } -> var + 3 + "# + ), + 5, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + { x } = { x: 0x2, y: 3.14 } + + x + "# + ), + 2, + i64 + ); + } + + #[test] + fn record_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when { x: 0x2, y: 3.14 } is + { x: 0x4 } -> 5 + { x } -> x + 3 + "# + ), + 5, + i64 + ); + } + + #[test] + fn twice_record_access() { + assert_evals_to!( + indoc!( + r#" + x = {a: 0x2, b: 0x3 } + + x.a + x.b + "# + ), + 5, + i64 + ); + } + #[test] + fn empty_record() { + assert_evals_to!( + indoc!( + r#" + v = {} + + 1 + "# + ), + 1, + i64 + ); + } + #[test] + fn i64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3, y: 5 } + "# + ), + (3, 5), + (i64, i64) + ); + } + + // #[test] + // fn i64_record3_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3, y: 5, z: 17 } + // "# + // ), + // (3, 5, 17), + // (i64, i64, i64) + // ); + // } + + #[test] + fn f64_record2_literal() { + assert_evals_to!( + indoc!( + r#" + { x: 3.1, y: 5.1 } + "# + ), + (3.1, 5.1), + (f64, f64) + ); + } + + // #[test] + // fn f64_record3_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3.1, y: 5.1, z: 17.1 } + // "# + // ), + // (3.1, 5.1, 17.1), + // (f64, f64, f64) + // ); + // } + + // #[test] + // fn bool_record4_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // record : { a : Bool, b : Bool, c : Bool, d : Bool } + // record = { a: True, b: True, c : True, d : Bool } + + // record + // "# + // ), + // (true, false, false, true), + // (bool, bool, bool, bool) + // ); + // } + + #[test] + fn i64_record1_literal() { + assert_evals_to!( + indoc!( + r#" + { a: 3 } + "# + ), + 3, + i64 + ); + } + + // #[test] + // fn i64_record9_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } + // "# + // ), + // (3, 5, 17, 1, 9, 12, 13, 14, 15), + // (i64, i64, i64, i64, i64, i64, i64, i64, i64) + // ); + // } + + // #[test] + // fn f64_record3_literal() { + // assert_evals_to!( + // indoc!( + // r#" + // { x: 3.1, y: 5.1, z: 17.1 } + // "# + // ), + // (3.1, 5.1, 17.1), + // (f64, f64, f64) + // ); + // } + + #[test] + fn bool_literal() { + assert_evals_to!( + indoc!( + r#" + x : Bool + x = True + + x + "# + ), + true, + bool + ); + } +} diff --git a/compiler/gen/tests/gen_tags.rs b/compiler/gen/tests/gen_tags.rs new file mode 100644 index 0000000000..96ae1a3477 --- /dev/null +++ b/compiler/gen/tests/gen_tags.rs @@ -0,0 +1,624 @@ +#[macro_use] +extern crate pretty_assertions; +#[macro_use] +extern crate indoc; + +extern crate bumpalo; +extern crate inkwell; +extern crate libc; +extern crate roc_gen; + +#[macro_use] +mod helpers; + +#[cfg(test)] +mod gen_tags { + use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; + use bumpalo::Bump; + use inkwell::context::Context; + use inkwell::execution_engine::JitFunction; + use inkwell::passes::PassManager; + use inkwell::types::BasicType; + use inkwell::OptimizationLevel; + use roc_collections::all::ImMap; + use roc_gen::llvm::build::{build_proc, build_proc_header}; + use roc_gen::llvm::convert::basic_type_from_layout; + use roc_mono::expr::{Expr, Procs}; + use roc_mono::layout::Layout; + use roc_types::subs::Subs; + + #[test] + fn applied_tag_nothing() { + assert_evals_to!( + indoc!( + r#" + Maybe a : [ Just a, Nothing ] + + x : Maybe Int + x = Nothing + + 0x1 + "# + ), + 1, + i64 + ); + } + + #[test] + fn applied_tag_just() { + assert_evals_to!( + indoc!( + r#" + Maybe a : [ Just a, Nothing ] + + y : Maybe Int + y = Just 0x4 + + 0x1 + "# + ), + 1, + i64 + ); + } + + #[test] + fn applied_tag_just_unit() { + assert_evals_to!( + indoc!( + r#" + Fruit : [ Orange, Apple, Banana ] + Maybe a : [ Just a, Nothing ] + + orange : Fruit + orange = Orange + + y : Maybe Fruit + y = Just orange + + 0x1 + "# + ), + 1, + i64 + ); + } + + // #[test] + // fn raw_result() { + // assert_evals_to!( + // indoc!( + // r#" + // x : Result Int Int + // x = Err 41 + + // x + // "# + // ), + // 0, + // i8 + // ); + // } + + #[test] + fn true_is_true() { + assert_evals_to!( + indoc!( + r#" + bool : [True, False] + bool = True + + bool + "# + ), + true, + bool + ); + } + + #[test] + fn false_is_false() { + assert_evals_to!( + indoc!( + r#" + bool : [True, False] + bool = False + + bool + "# + ), + false, + bool + ); + } + + #[test] + fn basic_enum() { + assert_evals_to!( + indoc!( + r#" + Fruit : [ Apple, Orange, Banana ] + + apple : Fruit + apple = Apple + + orange : Fruit + orange = Orange + + apple == orange + "# + ), + false, + bool + ); + } + + // #[test] + // fn linked_list_empty() { + // assert_evals_to!( + // indoc!( + // r#" + // LinkedList a : [ Cons a (LinkedList a), Nil ] + // + // empty : LinkedList Int + // empty = Nil + // + // 1 + // "# + // ), + // 1, + // i64 + // ); + // } + // + // #[test] + // fn linked_list_singleton() { + // assert_evals_to!( + // indoc!( + // r#" + // LinkedList a : [ Cons a (LinkedList a), Nil ] + // + // singleton : LinkedList Int + // singleton = Cons 0x1 Nil + // + // 1 + // "# + // ), + // 1, + // i64 + // ); + // } + // + // #[test] + // fn linked_list_is_empty() { + // assert_evals_to!( + // indoc!( + // r#" + // LinkedList a : [ Cons a (LinkedList a), Nil ] + // + // isEmpty : LinkedList a -> Bool + // isEmpty = \list -> + // when list is + // Nil -> True + // Cons _ _ -> False + // + // isEmpty (Cons 4 Nil) + // "# + // ), + // false, + // bool + // ); + // } + + #[test] + fn even_odd() { + assert_evals_to!( + indoc!( + r#" + even = \n -> + when n is + 0 -> True + 1 -> False + _ -> odd (n - 1) + + odd = \n -> + when n is + 0 -> False + 1 -> True + _ -> even (n - 1) + + odd 5 && even 42 + "# + ), + true, + bool + ); + } + + #[test] + fn gen_literal_true() { + assert_evals_to!( + indoc!( + r#" + if True then -1 else 1 + "# + ), + -1, + i64 + ); + } + + #[test] + fn gen_if_float() { + assert_evals_to!( + indoc!( + r#" + if True then -1.0 else 1.0 + "# + ), + -1.0, + f64 + ); + } + #[test] + fn when_on_nothing() { + assert_evals_to!( + indoc!( + r#" + x : [ Nothing, Just Int ] + x = Nothing + + when x is + Nothing -> 0x2 + Just _ -> 0x1 + "# + ), + 2, + i64 + ); + } + + #[test] + fn when_on_just() { + assert_evals_to!( + indoc!( + r#" + x : [ Nothing, Just Int ] + x = Just 41 + + when x is + Just v -> v + 0x1 + Nothing -> 0x1 + "# + ), + 42, + i64 + ); + } + + #[test] + fn when_on_result() { + assert_evals_to!( + indoc!( + r#" + x : Result Int Int + x = Err 41 + + when x is + Err v -> v + 1 + Ok _ -> 1 + "# + ), + 42, + i64 + ); + } + + #[test] + fn when_on_these() { + assert_evals_to!( + indoc!( + r#" + These a b : [ This a, That b, These a b ] + + x : These Int Int + x = These 0x3 0x2 + + when x is + These a b -> a + b + That v -> 8 + This v -> v + "# + ), + 5, + i64 + ); + } + + #[test] + fn match_on_two_values() { + // this will produce a Chain internally + assert_evals_to!( + indoc!( + r#" + when Pair 2 3 is + Pair 4 3 -> 9 + Pair a b -> a + b + "# + ), + 5, + i64 + ); + } + + #[test] + fn pair_with_guard_pattern() { + assert_evals_to!( + indoc!( + r#" + when Pair 2 3 is + Pair 4 _ -> 1 + Pair 3 _ -> 2 + Pair a b -> a + b + "# + ), + 5, + i64 + ); + } + + #[test] + fn result_with_guard_pattern() { + // This test revealed an issue with hashing Test values + assert_evals_to!( + indoc!( + r#" + x : Result Int Int + x = Ok 2 + + when x is + Ok 3 -> 1 + Ok _ -> 2 + Err _ -> 3 + "# + ), + 2, + i64 + ); + } + + #[test] + fn maybe_is_just() { + assert_evals_to!( + indoc!( + r#" + Maybe a : [ Just a, Nothing ] + + isJust : Maybe a -> Bool + isJust = \list -> + when list is + Nothing -> False + Just _ -> True + + isJust (Just 42) + "# + ), + true, + bool + ); + } + + #[test] + fn nested_pattern_match() { + assert_evals_to!( + indoc!( + r#" + Maybe a : [ Nothing, Just a ] + + x : Maybe (Maybe Int) + x = Just (Just 41) + + when x is + Just (Just v) -> v + 0x1 + _ -> 0x1 + "# + ), + 42, + i64 + ); + } + #[test] + fn if_guard_pattern_false() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 2 if False -> 0 + _ -> 42 + "# + ), + 42, + i64 + ); + } + + #[test] + fn if_guard_pattern_true() { + assert_evals_to!( + indoc!( + r#" + when 2 is + 2 if True -> 42 + _ -> 0 + "# + ), + 42, + i64 + ); + } + + #[test] + fn if_guard_exhaustiveness() { + assert_evals_to!( + indoc!( + r#" + when 2 is + _ if False -> 0 + _ -> 42 + "# + ), + 42, + i64 + ); + } + + #[test] + fn when_on_enum() { + assert_evals_to!( + indoc!( + r#" + Fruit : [ Apple, Orange, Banana ] + + apple : Fruit + apple = Apple + + when apple is + Apple -> 1 + Banana -> 2 + Orange -> 3 + "# + ), + 1, + i64 + ); + } + + #[test] + fn pattern_matching_unit() { + assert_evals_to!( + indoc!( + r#" + Unit : [ Unit ] + + f : Unit -> Int + f = \Unit -> 42 + + f Unit + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + Unit : [ Unit ] + + x : Unit + x = Unit + + when x is + Unit -> 42 + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + f : {} -> Int + f = \{} -> 42 + + f {} + "# + ), + 42, + i64 + ); + + assert_evals_to!( + indoc!( + r#" + when {} is + {} -> 42 + "# + ), + 42, + i64 + ); + } + + #[test] + fn one_element_tag() { + assert_evals_to!( + indoc!( + r#" + x : [ Pair Int ] + x = Pair 2 + + 0x3 + "# + ), + 3, + i64 + ); + } + + #[test] + fn nested_tag_union() { + assert_evals_to!( + indoc!( + r#" + Maybe a : [ Nothing, Just a ] + + x : Maybe (Maybe a) + x = Just (Just 41) + + 5 + "# + ), + 5, + i64 + ); + } + #[test] + fn unit_type() { + assert_evals_to!( + indoc!( + r#" + Unit : [ Unit ] + + v : Unit + v = Unit + + 1 + "# + ), + 1, + i64 + ); + } + + #[test] + fn nested_record_load() { + assert_evals_to!( + indoc!( + r#" + Maybe a : [ Nothing, Just a ] + + x = { a : { b : 0x5 } } + + y = x.a + + y.b + "# + ), + 5, + i64 + ); + } +} diff --git a/compiler/gen/tests/helpers/eval.rs b/compiler/gen/tests/helpers/eval.rs new file mode 100644 index 0000000000..972e8c70bf --- /dev/null +++ b/compiler/gen/tests/helpers/eval.rs @@ -0,0 +1,419 @@ +#[macro_export] +macro_rules! assert_llvm_evals_to { + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + let target = target_lexicon::Triple::host(); + let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; + let arena = Bump::new(); + let CanExprOut { loc_expr, var_store, var, constraint, home, interns, .. } = can_expr($src); + 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); + + let context = Context::create(); + let module = roc_gen::llvm::build::module_from_builtins(&context, "app"); + let builder = context.create_builder(); + let fpm = inkwell::passes::PassManager::create(&module); + + roc_gen::llvm::build::add_passes(&fpm); + + fpm.initialize(); + + // Compute main_fn_type before moving subs to Env + let layout = Layout::from_content(&arena, content, &subs, ptr_bytes) + .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); + let execution_engine = + module + .create_jit_execution_engine(OptimizationLevel::None) + .expect("Error creating JIT execution engine for test"); + + let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes) + .fn_type(&[], false); + let main_fn_name = "$Test.main"; + + // 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: arena.alloc(module), + ptr_bytes + }; + let mut procs = Procs::default(); + let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); + + // Populate Procs and get the low-level Expr from the canonical Expr + let mut mono_problems = Vec::new(); + let main_body = Expr::new(&arena, &mut subs, &mut mono_problems, loc_expr.value, &mut procs, home, &mut ident_ids, ptr_bytes); + + // Put this module's ident_ids back in the interns, so we can use them in Env. + env.interns.all_ident_ids.insert(home, ident_ids); + + let mut headers = Vec::with_capacity(procs.len()); + + // 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, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { + let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); + + headers.push((proc, fn_val, arg_basic_types)); + } + } + + // Build each proc using its header info. + for (proc, fn_val, arg_basic_types) in headers { + // NOTE: This is here to be uncommented in case verification fails. + // (This approach means we don't have to defensively clone name here.) + // + // println!("\n\nBuilding and then verifying function {}\n\n", name); + build_proc(&env, proc, &procs, fn_val, arg_basic_types); + + if fn_val.verify(true) { + fpm.run_on(&fn_val); + } else { + // NOTE: If this fails, uncomment the above println to debug. + panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); + } + } + + // Add main to the module. + let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); + let cc = roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap()); + + main_fn.set_call_conventions(cc); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(basic_block); + + let ret = roc_gen::llvm::build::build_expr( + &env, + &ImMap::default(), + main_fn, + &main_body, + &mut Procs::default(), + ); + + builder.build_return(Some(&ret)); + + // 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!("Function {} failed LLVM verification.", main_fn_name); + } + + // 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(); + + unsafe { + let main: JitFunction $ty> = execution_engine + .get_function(main_fn_name) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + assert_eq!($transform(main.call()), $expected); + } + }; +} + +// 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. +// Should extract the common logic into test helpers. +#[macro_export] +macro_rules! assert_opt_evals_to { + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + let arena = Bump::new(); + 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 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 context = Context::create(); + let module = roc_gen::llvm::build::module_from_builtins(&context, "app"); + let builder = context.create_builder(); + let fpm = PassManager::create(&module); + + roc_gen::llvm::build::add_passes(&fpm); + + fpm.initialize(); + + // Compute main_fn_type before moving subs to Env + let layout = Layout::from_content(&arena, content, &subs, ptr_bytes) + .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); + + let execution_engine = + module + .create_jit_execution_engine(OptimizationLevel::None) + .expect("Error creating JIT execution engine for test"); + + let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes) + .fn_type(&[], false); + let main_fn_name = "$Test.main"; + + // 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: arena.alloc(module), + ptr_bytes + }; + let mut procs = Procs::default(); + let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); + + // Populate Procs and get the low-level Expr from the canonical Expr + let mut mono_problems = Vec::new(); + let main_body = Expr::new(&arena, &mut subs, &mut mono_problems, loc_expr.value, &mut procs, home, &mut ident_ids, ptr_bytes); + + // Put this module's ident_ids back in the interns, so we can use them in Env. + env.interns.all_ident_ids.insert(home, ident_ids); + + let mut headers = Vec::with_capacity(procs.len()); + + // 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, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { + let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); + + headers.push((proc, fn_val, arg_basic_types)); + } + } + + // Build each proc using its header info. + for (proc, fn_val, arg_basic_types) in headers { + // NOTE: This is here to be uncommented in case verification fails. + // (This approach means we don't have to defensively clone name here.) + // + // println!("\n\nBuilding and then verifying function {}\n\n", name); + build_proc(&env, proc, &procs, fn_val, arg_basic_types); + + if fn_val.verify(true) { + fpm.run_on(&fn_val); + } else { + // NOTE: If this fails, uncomment the above println to debug. + panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); + } + } + + // Add main to the module. + let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); + let cc = roc_gen::llvm::build::get_call_conventions(target.default_calling_convention().unwrap()); + + main_fn.set_call_conventions(cc); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(basic_block); + + let ret = roc_gen::llvm::build::build_expr( + &env, + &ImMap::default(), + main_fn, + &main_body, + &mut Procs::default(), + ); + + builder.build_return(Some(&ret)); + + // 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!("Function {} failed LLVM verification.", main_fn_name); + } + + // 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(); + + unsafe { + let main: JitFunction $ty> = execution_engine + .get_function(main_fn_name) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + assert_eq!($transform(main.call()), $expected); + } + }; +} + +#[macro_export] +macro_rules! emit_expr { + ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { + let arena = Bump::new(); + let (loc_expr, _output, _problems, subs, var, constraint, home, interns) = uniq_expr($src); + + 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 context = Context::create(); + let module = context.create_module("app"); + let builder = context.create_builder(); + let fpm = PassManager::create(&module); + + roc_gen::llvm::build::add_passes(&fpm); + + fpm.initialize(); + + // Compute main_fn_type before moving subs to Env + let layout = Layout::from_content(&arena, content, &subs, ptr_bytes) + .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); + + let execution_engine = + module + .create_jit_execution_engine(OptimizationLevel::None) + .expect("Error creating JIT execution engine for test"); + + let main_fn_type = basic_type_from_layout(&arena, &context, &layout, ptr_bytes) + .fn_type(&[], false); + let main_fn_name = "$Test.main"; + + // 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: arena.alloc(module), + ptr_bytes + }; + let mut procs = Procs::default(); + let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); + + // Populate Procs and get the low-level Expr from the canonical Expr + let main_body = Expr::new(&arena, &mut subs, loc_expr.value, &mut procs, home, &mut ident_ids, $crate::helpers::eval::POINTER_SIZE); + + // Put this module's ident_ids back in the interns, so we can use them in Env. + env.interns.all_ident_ids.insert(home, ident_ids); + + let mut headers = Vec::with_capacity(procs.len()); + + // 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, opt_proc) in procs.as_map().into_iter() { + if let Some(proc) = opt_proc { + let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); + + headers.push((proc, fn_val, arg_basic_types)); + } + } + + // Build each proc using its header info. + for (proc, fn_val, arg_basic_types) in headers { + // NOTE: This is here to be uncommented in case verification fails. + // (This approach means we don't have to defensively clone name here.) + // + // println!("\n\nBuilding and then verifying function {}\n\n", name); + build_proc(&env, proc, &procs, fn_val, arg_basic_types); + + if fn_val.verify(true) { + fpm.run_on(&fn_val); + } else { + // NOTE: If this fails, uncomment the above println to debug. + panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); + } + } + + // Add main to the module. + let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); + + main_fn.set_call_conventions($crate::helpers::eval::MAIN_CALLING_CONVENTION); + + // Add main's body + let basic_block = context.append_basic_block(main_fn, "entry"); + + builder.position_at_end(basic_block); + + let ret = roc_gen::llvm::build::build_expr( + &env, + &ImMap::default(), + main_fn, + &main_body, + &mut Procs::default(), + ); + + builder.build_return(Some(&ret)); + + // 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!("Function {} failed LLVM verification.", main_fn_name); + } + + // 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(); + + unsafe { + let main: JitFunction $ty> = execution_engine + .get_function(main_fn_name) + .ok() + .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) + .expect("errored"); + + assert_eq!($transform(main.call()), $expected); + } + }; +} + +#[macro_export] +macro_rules! assert_evals_to { + ($src:expr, $expected:expr, $ty:ty) => { + // Run un-optimized tests, and then optimized tests, in separate scopes. + // These each rebuild everything from scratch, starting with + // parsing the source, so that there's no chance their passing + // 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); + } + }; +} diff --git a/compiler/gen/tests/helpers/mod.rs b/compiler/gen/tests/helpers/mod.rs index 0f2042472f..d4eb1c1986 100644 --- a/compiler/gen/tests/helpers/mod.rs +++ b/compiler/gen/tests/helpers/mod.rs @@ -1,5 +1,8 @@ extern crate bumpalo; +#[macro_use] +pub mod eval; + use self::bumpalo::Bump; use roc_builtins::unique::uniq_stdlib; use roc_can::constraint::Constraint; @@ -31,7 +34,7 @@ pub fn test_home() -> ModuleId { #[allow(dead_code)] pub fn infer_expr( subs: Subs, - problems: &mut Vec, + problems: &mut Vec, constraint: &Constraint, expr_var: Variable, ) -> (Content, Subs) { @@ -421,7 +424,7 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu match con { True | SaveTheEnvironment => (), - Eq(tipe, expectation, _) => { + Eq(tipe, expectation, _, _) => { for v in tipe.variables() { used.insert(v); } diff --git a/compiler/gen/tests/test_gen.rs b/compiler/gen/tests/test_gen.rs deleted file mode 100644 index 044da1892a..0000000000 --- a/compiler/gen/tests/test_gen.rs +++ /dev/null @@ -1,948 +0,0 @@ -#[macro_use] -extern crate pretty_assertions; -#[macro_use] -extern crate indoc; - -extern crate bumpalo; -extern crate inkwell; -extern crate roc_gen; - -mod helpers; - -#[cfg(test)] -mod test_gen { - use crate::helpers::{can_expr, infer_expr, uniq_expr, CanExprOut}; - use bumpalo::Bump; - use cranelift::prelude::{AbiParam, ExternalName, FunctionBuilder, FunctionBuilderContext}; - use cranelift_codegen::ir::InstBuilder; - use cranelift_codegen::settings; - use cranelift_codegen::verifier::verify_function; - use cranelift_module::{default_libcall_names, Linkage, Module}; - use cranelift_simplejit::{SimpleJITBackend, SimpleJITBuilder}; - use inkwell::context::Context; - use inkwell::execution_engine::JitFunction; - use inkwell::passes::PassManager; - use inkwell::types::BasicType; - use inkwell::OptimizationLevel; - use roc_collections::all::{ImMap, MutMap}; - use roc_gen::crane::build::{declare_proc, define_proc_body, ScopeEntry}; - use roc_gen::crane::convert::type_from_layout; - use roc_gen::crane::imports::define_malloc; - use roc_gen::llvm::build::{build_proc, build_proc_header}; - use roc_gen::llvm::convert::basic_type_from_layout; - use roc_mono::expr::Expr; - use roc_mono::layout::Layout; - use roc_types::subs::Subs; - use std::ffi::{CStr, CString}; - use std::mem; - use std::os::raw::c_char; - - macro_rules! assert_crane_evals_to { - ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { - let arena = Bump::new(); - let CanExprOut { loc_expr, var_store, var, constraint, home, interns, .. } = can_expr($src); - let subs = Subs::new(var_store.into()); - let mut unify_problems = Vec::new(); - let (content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); - - assert_eq!(unify_problems, Vec::new(), "Encountered one or more type mismatches: {:?}", unify_problems); - - let shared_builder = settings::builder(); - let shared_flags = settings::Flags::new(shared_builder); - let mut module: Module = - Module::new(SimpleJITBuilder::new(default_libcall_names())); - - let cfg = module.target_config(); - let mut ctx = module.make_context(); - let malloc = define_malloc(&mut module, &mut ctx); - let mut func_ctx = FunctionBuilderContext::new(); - - let main_fn_name = "$Test.main"; - - // Compute main_fn_ret_type before moving subs to Env - let layout = Layout::from_content(&arena, content, &subs) - .unwrap_or_else(|err| panic!("Code gen error in test: could not convert content to layout. Err was {:?} and Subs were {:?}", err, subs)); - let main_ret_type = type_from_layout(cfg, &layout); - - // Compile and add all the Procs before adding main - let mut procs = MutMap::default(); - let mut env = roc_gen::crane::build::Env { - arena: &arena, - interns, - cfg, - malloc - }; - let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - - // Populate Procs and Subs, and get the low-level Expr from the canonical Expr - let mono_expr = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); - - // Put this module's ident_ids back in the interns - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut scope = ImMap::default(); - let mut declared = Vec::with_capacity(procs.len()); - - // Declare all the Procs, then insert them into scope so their bodies - // can look up their Funcs in scope later when calling each other by value. - for (name, opt_proc) in procs.iter() { - if let Some(proc) = opt_proc { - let (func_id, sig) = declare_proc(&env, &mut module, name.clone(), proc); - - declared.push((proc.clone(), sig.clone(), func_id)); - - scope.insert(name.clone(), ScopeEntry::Func { func_id, sig }); - } - } - - for (proc, sig, fn_id) in declared { - define_proc_body( - &env, - &mut ctx, - &mut module, - fn_id, - &scope, - sig, - proc, - &procs, - ); - - // Verify the function we just defined - if let Err(errors) = verify_function(&ctx.func, &shared_flags) { - // NOTE: We don't include proc here because it's already - // been moved. If you need to know which proc failed, go back - // and add some logging. - panic!("Errors defining proc: {}", errors); - } - } - - // Add main itself - let mut sig = module.make_signature(); - sig.returns.push(AbiParam::new(main_ret_type)); - - let main_fn = module - .declare_function(main_fn_name, Linkage::Local, &sig) - .unwrap(); - - ctx.func.signature = sig; - ctx.func.name = ExternalName::user(0, main_fn.as_u32()); - - { - let mut builder: FunctionBuilder = - FunctionBuilder::new(&mut ctx.func, &mut func_ctx); - let block = builder.create_block(); - - builder.switch_to_block(block); - // TODO try deleting this line and seeing if everything still works. - builder.append_block_params_for_function_params(block); - - let main_body = - roc_gen::crane::build::build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs); - - builder.ins().return_(&[main_body]); - // TODO re-enable this once Switch stops making unsealed blocks, e.g. - // https://docs.rs/cranelift-frontend/0.59.0/src/cranelift_frontend/switch.rs.html#152 - // builder.seal_block(block); - builder.seal_all_blocks(); - builder.finalize(); - } - - module.define_function(main_fn, &mut ctx).expect("declare main"); - module.clear_context(&mut ctx); - - // Perform linking - module.finalize_definitions(); - - // Verify the main function - if let Err(errors) = verify_function(&ctx.func, &shared_flags) { - panic!("Errors defining {} - {}", main_fn_name, errors); - } - - let main_ptr = module.get_finalized_function(main_fn); - - unsafe { - let run_main = mem::transmute::<_, fn() -> $ty>(main_ptr) ; - - assert_eq!($transform(run_main()), $expected); - } - }; - } - - macro_rules! assert_llvm_evals_to { - ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { - let arena = Bump::new(); - let CanExprOut { loc_expr, var_store, var, constraint, home, interns, .. } = can_expr($src); - let subs = Subs::new(var_store.into()); - let mut unify_problems = Vec::new(); - let (content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); - - let context = Context::create(); - let module = context.create_module("app"); - let builder = context.create_builder(); - let fpm = PassManager::create(&module); - - // Enable optimizations when running cargo test --release - if !cfg!(debug_assertions) { - fpm.add_instruction_combining_pass(); - fpm.add_reassociate_pass(); - fpm.add_basic_alias_analysis_pass(); - fpm.add_promote_memory_to_register_pass(); - fpm.add_cfg_simplification_pass(); - fpm.add_gvn_pass(); - // TODO figure out why enabling any of these (even alone) causes LLVM to segfault - // fpm.add_strip_dead_prototypes_pass(); - // fpm.add_dead_arg_elimination_pass(); - // fpm.add_function_inlining_pass(); - } - - fpm.initialize(); - - // Compute main_fn_type before moving subs to Env - let layout = Layout::from_content(&arena, content, &subs) - .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); - let main_fn_type = basic_type_from_layout(&context, &layout) - .fn_type(&[], false); - let main_fn_name = "$Test.main"; - - let execution_engine = - module - .create_jit_execution_engine(OptimizationLevel::None) - .expect("Error creating JIT execution engine for test"); - - let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None); - - // 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: arena.alloc(module), - pointer_bytes - }; - let mut procs = MutMap::default(); - let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - - // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); - - // Put this module's ident_ids back in the interns, so we can use them in Env. - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut headers = Vec::with_capacity(procs.len()); - - // 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, opt_proc) in procs.clone().into_iter() { - if let Some(proc) = opt_proc { - let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); - - headers.push((proc, fn_val, arg_basic_types)); - } - } - - // Build each proc using its header info. - for (proc, fn_val, arg_basic_types) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {}\n\n", name); - build_proc(&env, proc, &procs, fn_val, arg_basic_types); - - if fn_val.verify(true) { - fpm.run_on(&fn_val); - } else { - // NOTE: If this fails, uncomment the above println to debug. - panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); - } - } - - // Add main to the module. - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(basic_block); - - let ret = roc_gen::llvm::build::build_expr( - &env, - &ImMap::default(), - main_fn, - &main_body, - &mut MutMap::default(), - ); - - builder.build_return(Some(&ret)); - - // 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!("Function {} failed LLVM verification.", main_fn_name); - } - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - unsafe { - let main: JitFunction $ty> = execution_engine - .get_function(main_fn_name) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - assert_eq!($transform(main.call()), $expected); - } - }; - } - - // TODO this is almost all code duplication with the one in test_gen; - // the only difference is that this calls uniq_expr instead of can_expr. - // Should extract the common logic into test helpers. - macro_rules! assert_opt_evals_to { - ($src:expr, $expected:expr, $ty:ty, $transform:expr) => { - let arena = Bump::new(); - let (loc_expr, _output, _problems, subs, var, constraint, home, interns) = uniq_expr($src); - - let mut unify_problems = Vec::new(); - let (content, subs) = infer_expr(subs, &mut unify_problems, &constraint, var); - - let context = Context::create(); - let module = context.create_module("app"); - let builder = context.create_builder(); - let fpm = PassManager::create(&module); - - // Enable optimizations when running cargo test --release - if !cfg!(debug_assertions) { - fpm.add_instruction_combining_pass(); - fpm.add_reassociate_pass(); - fpm.add_basic_alias_analysis_pass(); - fpm.add_promote_memory_to_register_pass(); - fpm.add_cfg_simplification_pass(); - fpm.add_gvn_pass(); - // TODO figure out why enabling any of these (even alone) causes LLVM to segfault - // fpm.add_strip_dead_prototypes_pass(); - // fpm.add_dead_arg_elimination_pass(); - // fpm.add_function_inlining_pass(); - } - - fpm.initialize(); - - // Compute main_fn_type before moving subs to Env - let layout = Layout::from_content(&arena, content, &subs) - .unwrap_or_else(|err| panic!("Code gen error in test: could not convert to layout. Err was {:?} and Subs were {:?}", err, subs)); - let main_fn_type = basic_type_from_layout(&context, &layout) - .fn_type(&[], false); - let main_fn_name = "$Test.main"; - - let execution_engine = - module - .create_jit_execution_engine(OptimizationLevel::None) - .expect("Error creating JIT execution engine for test"); - - let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None); - - // 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: arena.alloc(module), - pointer_bytes - }; - let mut procs = MutMap::default(); - let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - - // Populate Procs and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&arena, &subs, loc_expr.value, &mut procs, home, &mut ident_ids); - - // Put this module's ident_ids back in the interns, so we can use them in Env. - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut headers = Vec::with_capacity(procs.len()); - - // 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, opt_proc) in procs.clone().into_iter() { - if let Some(proc) = opt_proc { - let (fn_val, arg_basic_types) = build_proc_header(&env, symbol, &proc); - - headers.push((proc, fn_val, arg_basic_types)); - } - } - - // Build each proc using its header info. - for (proc, fn_val, arg_basic_types) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {}\n\n", name); - build_proc(&env, proc, &procs, fn_val, arg_basic_types); - - if fn_val.verify(true) { - fpm.run_on(&fn_val); - } else { - // NOTE: If this fails, uncomment the above println to debug. - panic!("Non-main function failed LLVM verification. Uncomment the above println to debug!"); - } - } - - // Add main to the module. - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(basic_block); - - let ret = roc_gen::llvm::build::build_expr( - &env, - &ImMap::default(), - main_fn, - &main_body, - &mut MutMap::default(), - ); - - builder.build_return(Some(&ret)); - - // 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!("Function {} failed LLVM verification.", main_fn_name); - } - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - unsafe { - let main: JitFunction $ty> = execution_engine - .get_function(main_fn_name) - .ok() - .ok_or(format!("Unable to JIT compile `{}`", main_fn_name)) - .expect("errored"); - - assert_eq!($transform(main.call()), $expected); - } - }; - } - - macro_rules! assert_evals_to { - ($src:expr, $expected:expr, $ty:ty) => { - // Run Cranelift tests, then LLVM tests, in separate scopes. - // These each rebuild everything from scratch, starting with - // parsing the source, so that there's no chance their passing - // or failing depends on leftover state from the previous one. - { - assert_crane_evals_to!($src, $expected, $ty, (|val| val)); - } - { - 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_crane_evals_to!($src, $expected, $ty, $transform); - } - { - assert_llvm_evals_to!($src, $expected, $ty, $transform); - } - { - assert_opt_evals_to!($src, $expected, $ty, $transform); - } - }; - } - - #[test] - fn basic_str() { - assert_evals_to!( - "\"shirt and hat\"", - CString::new("shirt and hat").unwrap().as_c_str(), - *const c_char, - CStr::from_ptr - ); - } - - #[test] - fn basic_int() { - assert_evals_to!("123", 123, i64); - } - - #[test] - fn basic_float() { - assert_evals_to!("1234.0", 1234.0, f64); - } - - #[test] - fn get_int_list() { - assert_evals_to!("List.getUnsafe [ 12, 9, 6, 3 ] 1", 9, i64); - } - - #[test] - fn set_unique_int_list() { - assert_evals_to!("List.getUnsafe (List.set [ 12, 9, 7, 3 ] 1 42) 1", 42, i64); - } - - #[test] - fn set_shared_int_list() { - assert_evals_to!( - indoc!( - r#" - shared = [ 2, 4 ] - - List.getUnsafe shared 1 - "# - ), - 4, - i64 - ); - } - - #[test] - fn branch_first_float() { - assert_evals_to!( - indoc!( - r#" - when 1.23 is - 1.23 -> 12 - _ -> 34 - "# - ), - 12, - i64 - ); - } - - #[test] - fn branch_second_float() { - assert_evals_to!( - indoc!( - r#" - when 2.34 is - 1.23 -> 63 - _ -> 48 - "# - ), - 48, - i64 - ); - } - - #[test] - fn branch_first_int() { - assert_evals_to!( - indoc!( - r#" - when 1 is - 1 -> 12 - _ -> 34 - "# - ), - 12, - i64 - ); - } - - #[test] - fn branch_second_int() { - assert_evals_to!( - indoc!( - r#" - when 2 is - 1 -> 63 - _ -> 48 - "# - ), - 48, - i64 - ); - } - - #[test] - fn gen_when_one_branch() { - assert_evals_to!( - indoc!( - r#" - when 3.14 is - _ -> 23 - "# - ), - 23, - i64 - ); - } - - #[test] - fn gen_large_when_int() { - assert_evals_to!( - indoc!( - r#" - foo = \num -> - when num is - 0 -> 200 - -3 -> 111 # TODO adding more negative numbers reproduces parsing bugs here - 3 -> 789 - 1 -> 123 - 2 -> 456 - _ -> 1000 - - foo -3 - "# - ), - 111, - i64 - ); - } - - #[test] - fn int_negate() { - assert_evals_to!("Num.neg 123", -123, i64); - } - - // #[test] - // fn gen_large_when_float() { - // assert_evals_to!( - // indoc!( - // r#" - // foo = \num -> - // when num is - // 0.5 -> 200.1 - // -3.6 -> 111.2 # TODO adding more negative numbers reproduces parsing bugs here - // 3.6 -> 789.5 - // 1.7 -> 123.3 - // 2.8 -> 456.4 - // _ -> 1000.6 - - // foo -3.6 - // "# - // ), - // 111.2, - // f64 - // ); - // } - - #[test] - fn gen_basic_def() { - assert_evals_to!( - indoc!( - r#" - answer = 42 - - answer - "# - ), - 42, - i64 - ); - - assert_evals_to!( - indoc!( - r#" - pi = 3.14 - - pi - "# - ), - 3.14, - f64 - ); - } - - #[test] - fn gen_multiple_defs() { - assert_evals_to!( - indoc!( - r#" - answer = 42 - - pi = 3.14 - - answer - "# - ), - 42, - i64 - ); - - assert_evals_to!( - indoc!( - r#" - answer = 42 - - pi = 3.14 - - pi - "# - ), - 3.14, - f64 - ); - } - - #[test] - fn gen_chained_defs() { - assert_evals_to!( - indoc!( - r#" - x = i1 - i3 = i2 - i1 = 1337 - i2 = i1 - y = 12.4 - - i3 - "# - ), - 1337, - i64 - ); - } - - #[test] - fn gen_nested_defs() { - assert_evals_to!( - indoc!( - r#" - x = 5 - - answer = - i3 = i2 - - nested = - a = 1.0 - b = 5 - - i1 - - i1 = 1337 - i2 = i1 - - - nested - - # None of this should affect anything, even though names - # overlap with the previous nested defs - unused = - nested = 17 - - i1 = 84.2 - - nested - - y = 12.4 - - answer - "# - ), - 1337, - i64 - ); - } - - #[test] - fn gen_basic_fn() { - assert_evals_to!( - indoc!( - r#" - always42 : Num.Num Int.Integer -> Num.Num Int.Integer - always42 = \num -> 42 - - always42 5 - "# - ), - 42, - i64 - ); - } - - #[test] - fn gen_when_fn() { - assert_evals_to!( - indoc!( - r#" - limitedNegate = \num -> - when num is - 1 -> -1 - -1 -> 1 - _ -> num - - limitedNegate 1 - "# - ), - -1, - i64 - ); - } - - #[test] - fn apply_unnamed_fn() { - assert_evals_to!( - indoc!( - r#" - (\a -> a) 5 - "# - ), - 5, - i64 - ); - } - - #[test] - fn gen_add_i64() { - assert_evals_to!( - indoc!( - r#" - 1 + 2 + 3 - "# - ), - 6, - i64 - ); - } - - #[test] - fn gen_sub_i64() { - assert_evals_to!( - indoc!( - r#" - 1 - 2 - 3 - "# - ), - -4, - i64 - ); - } - - #[test] - fn gen_mul_i64() { - assert_evals_to!( - indoc!( - r#" - 2 * 4 * 6 - "# - ), - 48, - i64 - ); - } - - #[test] - fn gen_div_f64() { - assert_evals_to!( - indoc!( - r#" - 48.0 / 2.0 - "# - ), - 24.0, - f64 - ); - } - - #[test] - fn gen_order_of_arithmetic_ops() { - assert_evals_to!( - indoc!( - r#" - 1 + 3 * 7 - 2 - "# - ), - 20, - i64 - ); - } - - #[test] - // https://math.berkeley.edu/~gbergman/misc/numbers/ord_ops.html - // fn gen_order_of_arithmetic_ops_complex() { - // assert_evals_to!( - // indoc!( - // r#" - // 48 / 2 * (9 + 3) - // "# - // ), - // 288, - // i64 - // ); - // } - - #[test] - fn gen_order_of_arithmetic_ops_complex_float() { - assert_evals_to!( - indoc!( - r#" - 48.0 / 2.0 + 3.0 - "# - ), - 24.0, - f64 - ); - } - - #[test] - fn return_unnamed_fn() { - assert_evals_to!( - indoc!( - r#" - alwaysFloatIdentity : Int -> (Float -> Float) - alwaysFloatIdentity = \num -> - (\a -> a) - - (alwaysFloatIdentity 2) 3.14 - "# - ), - 3.14, - f64 - ); - } - - // #[test] - // fn basic_record() { - // assert_evals_to!( - // indoc!( - // r#" - // point = { x: 15, y: 17, z: 19 } - - // point.x - // "# - // ), - // 15, - // i64 - // ); - - // assert_evals_to!( - // indoc!( - // r#" - // point = { x: 15, y: 17, z: 19 } - - // point.y - // "# - // ), - // 17, - // i64 - // ); - // assert_evals_to!( - // indoc!( - // r#" - // point = { x: 15, y: 17, z: 19 } - - // point.z - // "# - // ), - // 19, - // i64 - // ); - // } -} diff --git a/compiler/load/src/file.rs b/compiler/load/src/file.rs index 15e1051029..b5c8bf0eba 100644 --- a/compiler/load/src/file.rs +++ b/compiler/load/src/file.rs @@ -52,7 +52,7 @@ pub struct LoadedModule { pub interns: Interns, pub solved: Solved, pub can_problems: Vec, - pub type_problems: Vec, + pub type_problems: Vec, pub declarations: Vec, } @@ -93,7 +93,7 @@ enum Msg { solved_types: MutMap, aliases: MutMap, subs: Arc>, - problems: Vec, + problems: Vec, }, } @@ -1048,8 +1048,11 @@ fn parse_and_constrain( (module, ident_ids, constraint, problems) } - Err(_runtime_error) => { - panic!("TODO gracefully handle module canonicalization error"); + Err(runtime_error) => { + panic!( + "TODO gracefully handle module canonicalization error {:?}", + runtime_error + ); } }; diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc b/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc index be06fef3c2..0901f80fe9 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/AStar.roc @@ -63,7 +63,7 @@ updateCost = \current, neighbour, model -> newCosts = Map.insert model.costs neighbour distanceTo distanceTo = reconstructPath newCameFrom neighbour - |> List.length + |> List.len |> Num.toFloat newModel = { model & costs : newCosts , cameFrom : newCameFrom } diff --git a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc index fb37ddaf1a..37300efb8c 100644 --- a/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc +++ b/compiler/load/tests/fixtures/build/interface_with_deps/WithBuiltins.roc @@ -1,5 +1,5 @@ interface WithBuiltins - exposes [ floatTest, divisionFn, divisionTest, intTest, constantInt, fromDep2, divDep1ByDep2 ] + exposes [ floatTest, divisionFn, divisionTest, intTest, constantNum, fromDep2, divDep1ByDep2 ] imports [ Dep1, Dep2.{ two } ] floatTest = Float.highest @@ -12,7 +12,7 @@ divisionTest = Float.highest / x intTest = Int.highest -constantInt = 5 +constantNum = 5 fromDep2 = Dep2.two diff --git a/compiler/load/tests/helpers/mod.rs b/compiler/load/tests/helpers/mod.rs index f9ee25deb2..13eba5065a 100644 --- a/compiler/load/tests/helpers/mod.rs +++ b/compiler/load/tests/helpers/mod.rs @@ -28,10 +28,33 @@ pub fn test_home() -> ModuleId { ModuleIds::default().get_or_insert(&"Test".into()) } +/// Without a larger-than-default stack size, some tests +/// run out of stack space in debug builds (but don't in --release builds) +#[allow(dead_code)] +const THREAD_STACK_SIZE: usize = 4 * 1024 * 1024; + +pub fn test_async(future: F) -> F::Output { + use tokio::runtime::Builder; + + // Create the runtime + let mut rt = Builder::new() + .thread_name("tokio-thread-for-tests") + .thread_stack_size(THREAD_STACK_SIZE) + // DEBUG: Replace this with .basic_scheduler() to make tests run single-threaded on the main thread. + // Doing this makes assertion failures easier to read, but means + // the tests can't reveal concurrency bugs, so leave this off by default! + .threaded_scheduler() + .build() + .expect("Error initializing Tokio runtime."); + + // Spawn the root task + rt.block_on(future) +} + #[allow(dead_code)] pub fn infer_expr( subs: Subs, - problems: &mut Vec, + problems: &mut Vec, constraint: &Constraint, expr_var: Variable, ) -> (Content, Subs) { @@ -391,7 +414,7 @@ fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mu match con { True | SaveTheEnvironment => (), - Eq(tipe, expectation, _) => { + Eq(tipe, expectation, _, _) => { for v in tipe.variables() { used.insert(v); } diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index 7cc30a048a..58923ad6ce 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -13,7 +13,7 @@ mod helpers; #[cfg(test)] mod test_load { - use crate::helpers::fixtures_dir; + use crate::helpers::{fixtures_dir, test_async}; use inlinable_string::InlinableString; use roc_can::def::Declaration::*; use roc_can::def::Def; @@ -27,16 +27,6 @@ mod test_load { // HELPERS - fn test_async(future: F) -> F::Output { - use tokio::runtime::Runtime; - - // Create the runtime - let mut rt = Runtime::new().expect("Error initializing Tokio runtime."); - - // Spawn the root task - rt.block_on(future) - } - async fn load_fixture( dir_name: &str, module_name: &str, @@ -213,7 +203,7 @@ mod test_load { "divisionTest" => "Float", "intTest" => "Int", "x" => "Float", - "constantInt" => "Int", + "constantNum" => "Num *", "divDep1ByDep2" => "Float", "fromDep2" => "Float", }, diff --git a/compiler/load/tests/test_uniq_load.rs b/compiler/load/tests/test_uniq_load.rs index a7f8981ad9..e0e39db172 100644 --- a/compiler/load/tests/test_uniq_load.rs +++ b/compiler/load/tests/test_uniq_load.rs @@ -13,7 +13,7 @@ mod helpers; #[cfg(test)] mod test_uniq_load { - use crate::helpers::fixtures_dir; + use crate::helpers::{fixtures_dir, test_async}; use inlinable_string::InlinableString; use roc_builtins::unique; use roc_can::def::Declaration::*; @@ -28,16 +28,6 @@ mod test_uniq_load { // HELPERS - fn test_async(future: F) -> F::Output { - use tokio::runtime::Runtime; - - // Create the runtime - let mut rt = Runtime::new().expect("Error initializing Tokio runtime."); - - // Spawn the root task - rt.block_on(future) - } - async fn load_fixture( dir_name: &str, module_name: &str, @@ -208,7 +198,7 @@ mod test_uniq_load { "divisionTest" => "Attr * Float", "intTest" => "Attr * Int", "x" => "Attr * Float", - "constantInt" => "Attr * Int", + "constantNum" => "Attr * (Num (Attr * *))", "divDep1ByDep2" => "Attr * Float", "fromDep2" => "Attr * Float", }, diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index dea7b585d6..dc8bdd1084 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -9,6 +9,14 @@ use std::{fmt, u32}; #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Symbol(u64); +// When this is `true` (which it normally should be), Symbol's Debug::fmt implementation +// attempts to pretty print debug symbols using interns recorded using +// register_debug_idents calls (which should be made in debug mode). +// Set it to false if you want to see the raw ModuleId and IdentId ints, +// but please set it back to true before checking in the result! +#[cfg(debug_assertions)] +const PRETTY_PRINT_DEBUG_SYMBOLS: bool = true; + /// In Debug builds only, Symbol has a name() method that lets /// you look up its name in a global intern table. This table is /// behind a mutex, so it is neither populated nor available in release builds. @@ -101,26 +109,30 @@ impl Symbol { impl fmt::Debug for Symbol { #[cfg(debug_assertions)] fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let module_id = self.module_id(); - let ident_id = self.ident_id(); + if PRETTY_PRINT_DEBUG_SYMBOLS { + let module_id = self.module_id(); + let ident_id = self.ident_id(); - match DEBUG_IDENT_IDS_BY_MODULE_ID.lock() { - Ok(names) => match &names.get(&module_id.0) { - Some(ident_ids) => match ident_ids.get_name(ident_id) { - Some(ident_str) => write!(f, "`{:?}.{}`", module_id, ident_str), + match DEBUG_IDENT_IDS_BY_MODULE_ID.lock() { + Ok(names) => match &names.get(&module_id.0) { + Some(ident_ids) => match ident_ids.get_name(ident_id) { + Some(ident_str) => write!(f, "`{:?}.{}`", module_id, ident_str), + None => fallback_debug_fmt(*self, f), + }, None => fallback_debug_fmt(*self, f), }, - None => fallback_debug_fmt(*self, f), - }, - Err(err) => { - // Print and return Err rather than panicking, because this - // might be used in a panic error message, and if we panick - // while we're already panicking it'll kill the process - // without printing any of the errors! - println!("DEBUG INFO: Failed to acquire lock for Debug reading from DEBUG_IDENT_IDS_BY_MODULE_ID, presumably because a thread panicked: {:?}", err); + Err(err) => { + // Print and return Err rather than panicking, because this + // might be used in a panic error message, and if we panick + // while we're already panicking it'll kill the process + // without printing any of the errors! + println!("DEBUG INFO: Failed to acquire lock for Debug reading from DEBUG_IDENT_IDS_BY_MODULE_ID, presumably because a thread panicked: {:?}", err); - fallback_debug_fmt(*self, f) + fallback_debug_fmt(*self, f) + } } + } else { + fallback_debug_fmt(*self, f) } } @@ -168,6 +180,19 @@ pub struct Interns { } impl Interns { + pub fn module_id(&mut self, name: &InlinableString) -> ModuleId { + self.module_ids.get_or_insert(name) + } + + pub fn module_name(&self, module_id: ModuleId) -> &InlinableString { + self.module_ids.get_name(module_id).unwrap_or_else(|| { + panic!( + "Unable to find interns entry for module_id {:?} in Interns {:?}", + module_id, self + ) + }) + } + pub fn symbol(&self, module_id: ModuleId, ident: InlinableString) -> Symbol { match self.all_ident_ids.get(&module_id) { Some(ident_ids) => match ident_ids.get_id(&ident) { @@ -207,32 +232,11 @@ lazy_static! { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ModuleId(u32); -/// In Debug builds only, ModuleId has a name() method that lets -/// you look up its name in a global intern table. This table is -/// behind a mutex, so it is neither populated nor available in release builds. impl ModuleId { // NOTE: the define_builtins! macro adds a bunch of constants to this impl, // // e.g. pub const NUM: ModuleId = … - #[cfg(debug_assertions)] - pub fn name(self) -> Box { - let names = - DEBUG_MODULE_ID_NAMES - .lock() - .expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); - - match names.get(&self.0) { - Some(str_ref) => str_ref.clone(), - None => { - panic!( - "Could not find a Debug name for module ID {} in {:?}", - self.0, names, - ); - } - } - } - #[cfg(debug_assertions)] pub fn register_debug_idents(self, ident_ids: &IdentIds) { let mut all = DEBUG_IDENT_IDS_BY_MODULE_ID.lock().expect("Failed to acquire lock for Debug interning into DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); @@ -264,7 +268,20 @@ impl fmt::Debug for ModuleId { // Originally, this printed both name and numeric ID, but the numeric ID // didn't seem to add anything useful. Feel free to temporarily re-add it // if it's helpful in debugging! - write!(f, "{}", self.name()) + let names = + DEBUG_MODULE_ID_NAMES + .lock() + .expect("Failed to acquire lock for Debug reading from DEBUG_MODULE_ID_NAMES, presumably because a thread panicked."); + + match names.get(&self.0) { + Some(str_ref) => write!(f, "{}", str_ref.clone()), + None => { + panic!( + "Could not find a Debug name for module ID {} in {:?}", + self.0, names, + ); + } + } } /// In relese builds, all we have access to is the number, so only display that. @@ -547,11 +564,13 @@ macro_rules! define_builtins { }; } +// NOTE: Some of these builtins have a # at the beginning of their names. +// This is because they are for compiler use only, and should not cause +// namespace conflicts with userspace! define_builtins! { - 0 ATTR: "Attr" => { + 0 ATTR: "#Attr" => { 0 UNDERSCORE: "_" // the _ used in pattern matches. This is Symbol 0. - 1 ATTR_ATTR: "Attr" // the Attr.Attr type alias, used in uniqueness types - 2 ATTR_AT_ATTR: "@Attr" // the Attr.@Attr private tag + 1 ATTR_ATTR: "Attr" // the #Attr.Attr type alias, used in uniqueness types. } 1 NUM: "Num" => { 0 NUM_NUM: "Num" imported // the Num.Num type alias @@ -575,6 +594,11 @@ define_builtins! { 4 INT_MOD: "mod" 5 INT_HIGHEST: "highest" 6 INT_LOWEST: "lowest" + 7 INT_ADD: "#add" + 8 INT_SUB: "#sub" + 9 INT_EQ_I64: "#eqi64" // Equality on 64-bit integers, the standard in Roc + 10 INT_EQ_I1: "#eqi1" // Equality on boolean (theoretically i1) values + 11 INT_EQ_I8: "#eqi8" // Equality on byte (theoretically i8) values } 3 FLOAT: "Float" => { 0 FLOAT_FLOAT: "Float" imported // the Float.Float type alias @@ -585,6 +609,10 @@ define_builtins! { 5 FLOAT_SQRT: "sqrt" 6 FLOAT_HIGHEST: "highest" 7 FLOAT_LOWEST: "lowest" + 8 FLOAT_ADD: "#add" + 9 FLOAT_SUB: "#sub" + 10 FLOAT_EQ: "#eq" + 11 FLOAT_ROUND: "round" } 4 BOOL: "Bool" => { 0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias @@ -603,16 +631,17 @@ define_builtins! { 6 LIST: "List" => { 0 LIST_LIST: "List" imported // the List.List type alias 1 LIST_AT_LIST: "@List" // the List.@List private tag - 2 LIST_ISEMPTY: "isEmpty" + 2 LIST_IS_EMPTY: "isEmpty" 3 LIST_GET: "get" 4 LIST_SET: "set" - 5 LIST_SET_IN_PLACE: "set_in_place" + 5 LIST_SET_IN_PLACE: "#setInPlace" 6 LIST_PUSH: "push" 7 LIST_MAP: "map" - 8 LIST_LENGTH: "length" + 8 LIST_LEN: "len" 9 LIST_FOLDL: "foldl" 10 LIST_FOLDR: "foldr" 11 LIST_GET_UNSAFE: "getUnsafe" // TODO remove once we can code gen Result + 12 LIST_CONCAT: "concat" } 7 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/Cargo.toml b/compiler/mono/Cargo.toml index 67eae0c3aa..f2e3bb270b 100644 --- a/compiler/mono/Cargo.toml +++ b/compiler/mono/Cargo.toml @@ -11,12 +11,12 @@ roc_module = { path = "../module" } roc_types = { path = "../types" } roc_can = { path = "../can" } roc_unify = { path = "../unify" } +roc_problem = { path = "../problem" } bumpalo = { version = "3.2", features = ["collections"] } [dev-dependencies] roc_constrain = { path = "../constrain" } roc_builtins = { path = "../builtins" } -roc_problem = { path = "../problem" } roc_parse = { path = "../parse" } roc_solve = { path = "../solve" } pretty_assertions = "0.5.1 " diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs new file mode 100644 index 0000000000..af00476c12 --- /dev/null +++ b/compiler/mono/src/decision_tree.rs @@ -0,0 +1,1444 @@ +use crate::expr::Env; +use crate::expr::Expr; +use crate::expr::Pattern; +use bumpalo::Bump; +use roc_collections::all::{MutMap, MutSet}; +use roc_module::ident::TagName; +use roc_module::symbol::Symbol; + +use crate::expr::specialize_equality; +use crate::layout::Builtin; +use crate::layout::Layout; +use crate::pattern::{Ctor, Union}; + +/// COMPILE CASES + +type Label = u64; + +/// Users of this module will mainly interact with this function. It takes +/// some normal branches and gives out a decision tree that has "labels" at all +/// the leafs and a dictionary that maps these "labels" to the code that should +/// run. +pub fn compile<'a>(raw_branches: Vec<(Guard<'a>, Pattern<'a>, u64)>) -> DecisionTree<'a> { + let formatted = raw_branches + .into_iter() + .map(|(guard, pattern, index)| Branch { + goal: index, + patterns: vec![(Path::Empty, guard, pattern)], + }) + .collect(); + + to_decision_tree(formatted) +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Guard<'a> { + NoGuard, + Guard { + stores: &'a [(Symbol, Layout<'a>, Expr<'a>)], + expr: Expr<'a>, + }, +} + +impl<'a> Guard<'a> { + fn is_none(&self) -> bool { + self == &Guard::NoGuard + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DecisionTree<'a> { + Match(Label), + Decision { + path: Path, + edges: Vec<(Test<'a>, DecisionTree<'a>)>, + default: Option>>, + }, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Test<'a> { + IsCtor { + tag_id: u8, + tag_name: TagName, + union: crate::pattern::Union, + arguments: Vec<(Pattern<'a>, Layout<'a>)>, + }, + IsInt(i64), + // float patterns are stored as u64 so they are comparable/hashable + IsFloat(u64), + IsStr(Box), + IsBit(bool), + IsByte { + tag_id: u8, + num_alts: usize, + }, + // A pattern that always succeeds (like `_`) can still have a guard + Guarded { + opt_test: Option>>, + stores: &'a [(Symbol, Layout<'a>, Expr<'a>)], + expr: Expr<'a>, + }, +} +use std::hash::{Hash, Hasher}; +impl<'a> Hash for Test<'a> { + fn hash(&self, state: &mut H) { + use Test::*; + + match self { + IsCtor { tag_id, .. } => { + state.write_u8(0); + tag_id.hash(state); + // The point of this custom implementation is to not hash the tag arguments + } + IsInt(v) => { + state.write_u8(1); + v.hash(state); + } + IsFloat(v) => { + state.write_u8(2); + v.hash(state); + } + IsStr(v) => { + state.write_u8(3); + v.hash(state); + } + IsBit(v) => { + state.write_u8(4); + v.hash(state); + } + IsByte { tag_id, num_alts } => { + state.write_u8(5); + tag_id.hash(state); + num_alts.hash(state); + } + Guarded { opt_test: None, .. } => { + state.write_u8(6); + } + Guarded { + opt_test: Some(nested), + .. + } => { + state.write_u8(7); + nested.hash(state); + } + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Path { + Index { + index: u64, + tag_id: u8, + path: Box, + }, + Unbox(Box), + Empty, +} + +// ACTUALLY BUILD DECISION TREES + +#[derive(Clone, Debug, PartialEq)] +struct Branch<'a> { + goal: Label, + patterns: Vec<(Path, Guard<'a>, Pattern<'a>)>, +} + +fn to_decision_tree(raw_branches: Vec) -> DecisionTree { + let branches: Vec<_> = raw_branches.into_iter().map(flatten_patterns).collect(); + + match check_for_match(&branches) { + Some(goal) => DecisionTree::Match(goal), + None => { + // TODO remove clone + let path = pick_path(branches.clone()); + + let (edges, fallback) = gather_edges(branches, &path); + + let mut decision_edges: Vec<_> = edges + .into_iter() + .map(|(a, b)| (a, to_decision_tree(b))) + .collect(); + + match (decision_edges.split_last_mut(), fallback.split_last()) { + (Some(((_tag, decision_tree), rest)), None) if rest.is_empty() => { + // TODO remove clone + decision_tree.clone() + } + (_, None) => DecisionTree::Decision { + path, + edges: decision_edges, + default: None, + }, + (None, Some(_)) => to_decision_tree(fallback), + _ => DecisionTree::Decision { + path, + edges: decision_edges, + default: Some(Box::new(to_decision_tree(fallback))), + }, + } + } + } +} + +fn is_complete(tests: &[Test]) -> bool { + let length = tests.len(); + debug_assert!(length > 0); + match tests.get(length - 1) { + None => unreachable!("should never happen"), + Some(v) => match v { + Test::IsCtor { union, .. } => length == union.alternatives.len(), + Test::IsByte { num_alts, .. } => length == *num_alts, + Test::IsBit(_) => length == 2, + Test::IsInt(_) => false, + Test::IsFloat(_) => false, + Test::IsStr(_) => false, + Test::Guarded { .. } => false, + }, + } +} + +fn flatten_patterns(branch: Branch) -> Branch { + let mut result = Vec::with_capacity(branch.patterns.len()); + + for path_pattern in branch.patterns { + flatten(path_pattern, &mut result); + } + Branch { + goal: branch.goal, + patterns: result, + } +} + +fn flatten<'a>( + path_pattern: (Path, Guard<'a>, Pattern<'a>), + path_patterns: &mut Vec<(Path, Guard<'a>, Pattern<'a>)>, +) { + match &path_pattern.2 { + Pattern::AppliedTag { + union, + arguments, + tag_id, + .. + } => { + // TODO do we need to check that guard.is_none() here? + if union.alternatives.len() == 1 { + let path = path_pattern.0; + // Theory: unbox doesn't have any value for us, because one-element tag unions + // don't store the tag anyway. + if arguments.len() == 1 { + path_patterns.push(( + Path::Unbox(Box::new(path)), + path_pattern.1.clone(), + path_pattern.2.clone(), + )); + } else { + for (index, (arg_pattern, _)) in arguments.iter().enumerate() { + flatten( + ( + Path::Index { + index: index as u64, + tag_id: *tag_id, + path: Box::new(path.clone()), + }, + // same guard here? + path_pattern.1.clone(), + arg_pattern.clone(), + ), + path_patterns, + ); + } + } + } else { + path_patterns.push(path_pattern); + } + } + + _ => { + path_patterns.push(path_pattern); + } + } +} + +/// SUCCESSFULLY MATCH + +/// If the first branch has no more "decision points" we can finally take that +/// path. If that is the case we give the resulting label and a mapping from free +/// variables to "how to get their value". So a pattern like (Just (x,_)) will give +/// us something like ("x" => value.0.0) +fn check_for_match<'a>(branches: &Vec>) -> Option