Merge branch 'trunk' of github.com:rtfeldman/roc into benchmarks

This commit is contained in:
Anton-4 2021-05-28 15:09:42 +02:00
commit 222bee931e
147 changed files with 11253 additions and 5473 deletions

View File

@ -6,10 +6,10 @@ env:
RUST_BACKTRACE: 1
jobs:
prep-dependency-container:
build-fmt-clippy-test:
name: fmt, clippy, test --release
runs-on: [self-hosted]
timeout-minutes: 60
timeout-minutes: 90
env:
FORCE_COLOR: 1
steps:
@ -21,4 +21,5 @@ jobs:
run: earthly --version
- name: install dependencies, build, run zig tests, rustfmt, clippy, cargo test --release
run: earthly +test-all
run: ./ci/safe-earthly-test-all.sh

1
.gitignore vendored
View File

@ -6,6 +6,7 @@ zig-cache
# llvm human-readable output
*.ll
*.bc
#valgrind
vgcore.*

View File

@ -20,7 +20,7 @@ For debugging LLVM IR, we use [DebugIR](https://github.com/vaivaswatha/debugir).
### libunwind & libc++-dev
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be donw with `sudo apt-get install libunwind-dev`).
MacOS systems should already have `libunwind`, but other systems will need to install it (On Ubuntu, this can be done with `sudo apt-get install libunwind-dev`).
Some systems may already have `libc++-dev` on them, but if not, you may need to install it. (On Ubuntu, this can be done with `sudo apt-get install libc++-dev`.)
### libcxb libraries

345
Cargo.lock generated
View File

@ -123,11 +123,11 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "ash"
version = "0.31.0"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c69a8137596e84c22d57f3da1b5de1d4230b1742a710091c85f4d7ce50f00f38"
checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112"
dependencies = [
"libloading 0.6.7",
"libloading 0.7.0",
]
[[package]]
@ -212,7 +212,16 @@ dependencies = [
"block-padding",
"byte-tools",
"byteorder",
"generic-array",
"generic-array 0.12.4",
]
[[package]]
name = "block-buffer"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
@ -265,7 +274,7 @@ checksum = "8e215f8c2f9f79cb53c8335e687ffd07d5bfcb6fe5fc80723762d0be46e7cc54"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -373,7 +382,7 @@ dependencies = [
"proc-macro-error",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -443,6 +452,16 @@ dependencies = [
"objc",
]
[[package]]
name = "codespan-reporting"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e"
dependencies = [
"termcolor",
"unicode-width",
]
[[package]]
name = "colored"
version = "2.0.0"
@ -586,6 +605,15 @@ dependencies = [
"objc",
]
[[package]]
name = "cpufeatures"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dec1028182c380cc45a2e2c5ec841134f2dfd0f8f5f0a5bcd68004f81b5efdf4"
dependencies = [
"libc",
]
[[package]]
name = "crc32fast"
version = "1.2.1"
@ -777,17 +805,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e98e2ad1a782e33928b96fc3948e7c355e5af34ba4de7670fe8bac2a3b2006d"
dependencies = [
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
name = "d3d12"
version = "0.3.2"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a60cceb22c7c53035f8980524fdc7f17cf49681a3c154e6757d30afbec6ec4"
checksum = "091ed1b25fe47c7ff129fc440c23650b6114f36aa00bc7212cc8041879294428"
dependencies = [
"bitflags",
"libloading 0.6.7",
"libloading 0.7.0",
"winapi 0.3.9",
]
@ -812,7 +840,7 @@ dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"strsim",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -823,7 +851,7 @@ checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
"darling_core",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -834,7 +862,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -849,7 +877,16 @@ version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
"generic-array",
"generic-array 0.12.4",
]
[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
dependencies = [
"generic-array 0.14.4",
]
[[package]]
@ -1090,7 +1127,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -1143,6 +1180,16 @@ dependencies = [
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "getrandom"
version = "0.1.16"
@ -1167,9 +1214,9 @@ dependencies = [
[[package]]
name = "gfx-auxil"
version = "0.8.0"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7b33ecf067f2117668d91c9b0f2e5f223ebd1ffec314caa2f3de27bb580186d"
checksum = "9ccf8711c9994dfa34337466bee3ae1462e172874c432ce4eb120ab2e98d39cf"
dependencies = [
"fxhash",
"gfx-hal",
@ -1178,15 +1225,15 @@ dependencies = [
[[package]]
name = "gfx-backend-dx11"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f851d03c2e8f117e3702bf41201a4fafa447d5cb1276d5375870ae7573d069dd"
checksum = "6f839f27f8c8a6dc553ccca7f5b35a42009432bc25db9688bba7061cd394161f"
dependencies = [
"arrayvec",
"bitflags",
"gfx-auxil",
"gfx-hal",
"libloading 0.6.7",
"libloading 0.7.0",
"log",
"parking_lot",
"range-alloc",
@ -1200,9 +1247,9 @@ dependencies = [
[[package]]
name = "gfx-backend-dx12"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36dc6ba2b7647e2c2b27b8f74ff5ccdd53c703776588eee5b1de515fdcbd6bc9"
checksum = "3937738b0da5839bba4e33980d29f9a06dbce184d04a3a08c9a949e7953700e3"
dependencies = [
"arrayvec",
"bit-set",
@ -1216,14 +1263,15 @@ dependencies = [
"raw-window-handle",
"smallvec",
"spirv_cross",
"thunderdome",
"winapi 0.3.9",
]
[[package]]
name = "gfx-backend-empty"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f07ef26a65954cfdd7b4c587f485100d1bb3b0bd6a51b02d817d6c87cca7a91"
checksum = "2ac55ada4bfcd35479b3421eea324d36d7da5f724e2f66ecb36d4efdb7041a5e"
dependencies = [
"gfx-hal",
"log",
@ -1232,32 +1280,31 @@ dependencies = [
[[package]]
name = "gfx-backend-gl"
version = "0.7.1"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c6717c50ab601efe4a669bfb44db615e3888695ac8263222aeaa702642b9fbc2"
checksum = "0caa03d6e0b7b4f202aea1f20c3f3288cfa06d92d24cea9d69c9a7627967244a"
dependencies = [
"arrayvec",
"bitflags",
"gfx-auxil",
"fxhash",
"gfx-hal",
"glow",
"js-sys",
"khronos-egl",
"libloading 0.6.7",
"libloading 0.7.0",
"log",
"naga",
"parking_lot",
"raw-window-handle",
"spirv_cross",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "gfx-backend-metal"
version = "0.7.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8dc54b456ece69ef49f8893269ebf24ac70969ed34ba2719c3f3abcc8fbff14e"
checksum = "340895ad544ba46433acb3bdabece0ef16f2dbedc030adbd7c9eaf2839fbed41"
dependencies = [
"arrayvec",
"bitflags",
@ -1265,24 +1312,24 @@ dependencies = [
"cocoa-foundation",
"copyless",
"foreign-types",
"gfx-auxil",
"fxhash",
"gfx-hal",
"log",
"metal",
"naga",
"objc",
"parking_lot",
"profiling",
"range-alloc",
"raw-window-handle",
"spirv_cross",
"storage-map",
]
[[package]]
name = "gfx-backend-vulkan"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dabe88b1a5c91e0f969b441cc57e70364858066e4ba937deeb62065654ef9bd9"
checksum = "a353fc6fdb42ec646de49bbb74e4870e37a7e680caf33f3ac0615c30b1146d94"
dependencies = [
"arrayvec",
"ash",
@ -1301,9 +1348,9 @@ dependencies = [
[[package]]
name = "gfx-hal"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1d9cc8d3b573dda62d0baca4f02e0209786e22c562caff001d77c389008781d"
checksum = "6d285bfd566f6b9134af908446ca350c0a1047495dfb9bbd826e701e8ee1d259"
dependencies = [
"bitflags",
"naga",
@ -1319,9 +1366,9 @@ checksum = "f6503fe142514ca4799d4c26297c4248239fe8838d827db6bd6065c6ed29a6ce"
[[package]]
name = "glow"
version = "0.7.2"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "072136d2c3783f3a92f131acb227bc806d3886278e2a4dc1e9990ec89ef9e70b"
checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108"
dependencies = [
"js-sys",
"slotmap",
@ -1370,13 +1417,12 @@ dependencies = [
[[package]]
name = "gpu-alloc"
version = "0.3.0"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7724b9aef57ea36d70faf54e0ee6265f86e41de16bed8333efdeab5b00e16b"
checksum = "bc76088804bb65a6f3b880bea9306fdaeffb25ebb453105fafa691282ee9fdba"
dependencies = [
"bitflags",
"gpu-alloc-types",
"tracing",
]
[[package]]
@ -1397,7 +1443,6 @@ dependencies = [
"bitflags",
"gpu-descriptor-types",
"hashbrown 0.9.1",
"tracing",
]
[[package]]
@ -1558,7 +1603,7 @@ dependencies = [
"proc-macro-hack",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
"unindent",
]
@ -1583,7 +1628,7 @@ source = "git+https://github.com/rtfeldman/inkwell?tag=llvm10-0.release4#9ae45f0
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -1657,9 +1702,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.46"
version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175"
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
dependencies = [
"wasm-bindgen",
]
@ -1676,12 +1721,12 @@ dependencies = [
[[package]]
name = "khronos-egl"
version = "3.0.2"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b19cc4a81304db2a0ad69740e83cdc3a9364e3f9bd6d88a87288a4c2deec927b"
checksum = "8c2352bd1d0bceb871cb9d40f24360c8133c11d7486b68b5381c1dd1a32015e3"
dependencies = [
"libc",
"libloading 0.6.7",
"libloading 0.7.0",
]
[[package]]
@ -1821,9 +1866,9 @@ dependencies = [
[[package]]
name = "metal"
version = "0.21.0"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4598d719460ade24c7d91f335daf055bf2a7eec030728ce751814c50cdd6a26c"
checksum = "1c12e48c737ee9a55e8bb2352bcde588f79ae308d3529ee888f7cc0f469b5777"
dependencies = [
"bitflags",
"block",
@ -1886,14 +1931,23 @@ dependencies = [
"ws2_32-sys",
]
[[package]]
name = "morphic_lib"
version = "0.1.0"
dependencies = [
"sha2",
"thiserror",
]
[[package]]
name = "naga"
version = "0.3.2"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05089b2acdf0e6a962cdbf5e328402345a27f59fcde1a59fe97a73e8149d416f"
checksum = "f470a97eafcdd0dbea43d5e1a8ef3557aa31f49ba643d9430dbbf911c162b24c"
dependencies = [
"bit-set",
"bitflags",
"codespan-reporting",
"fxhash",
"log",
"num-traits",
@ -1938,7 +1992,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -2049,7 +2103,7 @@ dependencies = [
"proc-macro-crate",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -2093,22 +2147,21 @@ dependencies = [
[[package]]
name = "object"
version = "0.22.0"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d3b63360ec3cb337817c2dbd47ab4a0f170d285d8e5a2064600f3def1402397"
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
[[package]]
name = "object"
version = "0.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
dependencies = [
"crc32fast",
"flate2",
"indexmap",
"wasmparser",
]
[[package]]
name = "object"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9a7ab5d64814df0fe4a4b5ead45ed6c5f181ee3ff04ba344313a6c80446c5d4"
[[package]]
name = "once_cell"
version = "1.7.2"
@ -2127,6 +2180,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "ordered-float"
version = "2.1.1"
@ -2194,7 +2253,7 @@ checksum = "0b4b5f600e60dd3a147fb57b4547033d382d1979eb087af310e91cb45a63b1f4"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -2260,7 +2319,7 @@ dependencies = [
"pest_meta",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -2420,7 +2479,7 @@ dependencies = [
"proc-macro-error-attr",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
"version_check",
]
@ -2432,7 +2491,7 @@ checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
"syn-mid",
"version_check",
]
@ -2467,6 +2526,12 @@ dependencies = [
"unicode-xid 0.2.1",
]
[[package]]
name = "profiling"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3a66d5e88679f2720126c11ee29da07a08f094eac52306b066edd7d393752d6"
[[package]]
name = "pulldown-cmark"
version = "0.8.0"
@ -2520,7 +2585,7 @@ checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -3019,6 +3084,8 @@ dependencies = [
"roc_can",
"roc_collections",
"roc_load",
"roc_module",
"roc_region",
]
[[package]]
@ -3140,7 +3207,7 @@ dependencies = [
"libc",
"libloading 0.6.7",
"maplit",
"object 0.22.0",
"object 0.24.0",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3219,6 +3286,7 @@ dependencies = [
"indoc 0.3.6",
"linked-hash-map",
"maplit",
"morphic_lib",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3426,7 +3494,7 @@ version = "0.3.1"
source = "git+https://github.com/rtfeldman/rustyline?tag=prompt-fix#a6b8a20d2bf5c3793d7367848be2f4afec2f0d99"
dependencies = [
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -3510,7 +3578,7 @@ checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -3555,7 +3623,7 @@ checksum = "b2acd6defeddb41eb60bb468f8825d0cfd0c2a76bc03bfd235b6a1dc4f6a1ad5"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -3564,10 +3632,23 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
"block-buffer",
"digest",
"block-buffer 0.7.3",
"digest 0.8.1",
"fake-simd",
"opaque-debug",
"opaque-debug 0.2.3",
]
[[package]]
name = "sha2"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8f6b75b17576b792bef0db1bcc4b8b8bcdf9506744cf34b974195487af6cff2"
dependencies = [
"block-buffer 0.9.0",
"cfg-if 1.0.0",
"cpufeatures",
"digest 0.9.0",
"opaque-debug 0.3.0",
]
[[package]]
@ -3662,7 +3743,7 @@ checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -3729,9 +3810,9 @@ dependencies = [
[[package]]
name = "syn"
version = "1.0.65"
version = "1.0.72"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3a1d708c221c5a612956ef9f75b37e454e88d1f7b899fbd3a18d4252012d663"
checksum = "a1e8cdbefb79a9a5a65e0db8b47b723ee907b7c7f8496c76a1770b5c310bab82"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
@ -3746,7 +3827,7 @@ checksum = "baa8e7560a164edb1621a55d18a0c59abf49d360f47aa7b821061dd7eea7fac9"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -3757,7 +3838,7 @@ checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
"unicode-xid 0.2.1",
]
@ -3853,7 +3934,7 @@ checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
]
[[package]]
@ -3869,9 +3950,9 @@ dependencies = [
[[package]]
name = "thunderdome"
version = "0.3.0"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7572415bd688d401c52f6e36f4c8e805b9ae1622619303b9fa835d531db0acae"
checksum = "87b4947742c93ece24a0032141d9caa3d853752e694a57e35029dd2bd08673e0"
[[package]]
name = "tinytemplate"
@ -3906,38 +3987,6 @@ dependencies = [
"serde",
]
[[package]]
name = "tracing"
version = "0.1.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01ebdc2bb4498ab1ab5f5b73c5803825e60199229ccba0698170e3be0e7f959f"
dependencies = [
"cfg-if 1.0.0",
"pin-project-lite 0.2.6",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42e6fa53307c8a17e4ccd4dc81cf5ec38db9209f59b222210375b54ee40d1e2"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
]
[[package]]
name = "tracing-core"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f50de3927f93d202783f4513cda820ab47ef17f624b03c096e86ef00c67e6b5f"
dependencies = [
"lazy_static",
]
[[package]]
name = "ttf-parser"
version = "0.6.2"
@ -4108,9 +4157,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.69"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e"
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
dependencies = [
"cfg-if 1.0.0",
"wasm-bindgen-macro",
@ -4118,24 +4167,24 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.69"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62"
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
dependencies = [
"bumpalo",
"lazy_static",
"log",
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.19"
version = "0.4.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35"
checksum = "5fba7978c679d53ce2d0ac80c8c175840feb849a161664365d1287b41f2e67f1"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@ -4145,9 +4194,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.69"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084"
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
dependencies = [
"quote 1.0.9",
"wasm-bindgen-macro-support",
@ -4155,28 +4204,22 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.69"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549"
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
dependencies = [
"proc-macro2 1.0.26",
"quote 1.0.9",
"syn 1.0.65",
"syn 1.0.72",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.69"
version = "0.2.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158"
[[package]]
name = "wasmparser"
version = "0.57.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6"
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
[[package]]
name = "wayland-client"
@ -4253,9 +4296,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.46"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
dependencies = [
"js-sys",
"wasm-bindgen",
@ -4263,18 +4306,17 @@ dependencies = [
[[package]]
name = "wgpu"
version = "0.7.1"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79a0a0a63fac9492cfaf6e7e4bdf9729c128f1e94124b9e4cbc4004b8cb6d1d8"
checksum = "215fd50e66f794bd16683e7e0e0b9b53be265eb10fdf02276caf5de3e5743fcf"
dependencies = [
"arrayvec",
"js-sys",
"log",
"naga",
"parking_lot",
"raw-window-handle",
"smallvec",
"syn 1.0.65",
"tracing",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
@ -4284,9 +4326,9 @@ dependencies = [
[[package]]
name = "wgpu-core"
version = "0.7.1"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c89fa2cc5d72236461ac09c5be967012663e29cb62f1a972654cbf35e49dffa8"
checksum = "1d56c368fc0e6f3927c711d2b55a51ad4321218efc0239c4acf69e456ab70399"
dependencies = [
"arrayvec",
"bitflags",
@ -4302,29 +4344,30 @@ dependencies = [
"gfx-hal",
"gpu-alloc",
"gpu-descriptor",
"log",
"naga",
"parking_lot",
"profiling",
"raw-window-handle",
"smallvec",
"thiserror",
"tracing",
"wgpu-types",
]
[[package]]
name = "wgpu-types"
version = "0.7.0"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72fa9ba80626278fd87351555c363378d08122d7601e58319be3d6fa85a87747"
checksum = "aa248d90c8e6832269b8955bf800e8241f942c25e18a235b7752226804d21556"
dependencies = [
"bitflags",
]
[[package]]
name = "wgpu_glyph"
version = "0.11.0"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "354c1f79e09923724a6006a6953c3946522b84f7a9ec202b7e8001f274fd4bd5"
checksum = "634570b440f4c24c2e6049ed01ec832c23d338dea3eca654d5760838017a1c8b"
dependencies = [
"bytemuck",
"glyph_brush",
@ -4518,6 +4561,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
dependencies = [
"proc-macro2 1.0.26",
"syn 1.0.65",
"syn 1.0.72",
"synstructure",
]

View File

@ -496,3 +496,22 @@ of the authors and should not be interpreted as representing official policies,
either expressed or implied, of the FreeBSD Project.
===========================================================
* morphic_lib - https://github.com/morphic-lang/morphic_lib
This source code can be found in vendor/morphic_lib and is licensed under the following terms:
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
===========================================================

2
ci/earthly-conf.yml Normal file
View File

@ -0,0 +1,2 @@
global:
cache_size_mb: 25000

20
ci/safe-earthly-test-all.sh Executable file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env bash
LOG_FILE="earthly_log.txt"
touch $LOG_FILE
script -efq $LOG_FILE -c "earthly --config ci/earthly-conf.yml +test-all"
EXIT_CODE=$?
if grep -q "failed to mount" "$LOG_FILE"; then
echo ""
echo ""
echo "------<<<<<<!!!!!!>>>>>>------"
echo "DETECTED FAILURE TO MOUNT ERROR: running without cache"
echo "------<<<<<<!!!!!!>>>>>>------"
echo ""
echo ""
earthly --config ci/earthly-conf.yml --no-cache +test-all
else
exit $EXIT_CODE
fi

View File

@ -52,7 +52,13 @@ pub unsafe fn jit_to_ast<'a>(
home,
};
jit_to_ast_help(&env, lib, main_fn_name, layout, content)
match layout {
Layout::FunctionPointer(&[], result) => {
// this is a thunk
jit_to_ast_help(&env, lib, main_fn_name, result, content)
}
_ => jit_to_ast_help(&env, lib, main_fn_name, layout, content),
}
}
fn jit_to_ast_help<'a>(
@ -110,7 +116,7 @@ fn jit_to_ast_help<'a>(
}
}))
}
Layout::Builtin(Builtin::List(_, elem_layout)) => Ok(run_jit_function!(
Layout::Builtin(Builtin::List(elem_layout)) => Ok(run_jit_function!(
lib,
main_fn_name,
(*const u8, usize),
@ -119,12 +125,6 @@ fn jit_to_ast_help<'a>(
Layout::Builtin(other) => {
todo!("add support for rendering builtin {:?} to the REPL", other)
}
Layout::PhantomEmptyStruct => Ok(run_jit_function!(lib, main_fn_name, &u8, |_| {
Expr::Record {
fields: &[],
final_comments: env.arena.alloc([]),
}
})),
Layout::Struct(field_layouts) => {
let ptr_to_ast = |ptr: *const u8| match content {
Content::Structure(FlatType::Record(fields, _)) => {
@ -140,6 +140,9 @@ fn jit_to_ast_help<'a>(
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
}
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => {
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[])
}
other => {
unreachable!(
"Something had a Struct layout, but instead of a Record or TagUnion type, it had: {:?}",
@ -236,10 +239,8 @@ fn jit_to_ast_help<'a>(
todo!("add support for rendering recursive tag unions in the REPL")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
Err(ToAstProblem::FunctionLayout)
}
Layout::Pointer(_) => todo!("add support for rendering pointers in the REPL"),
Layout::Closure(_, _, _) => Err(ToAstProblem::FunctionLayout),
Layout::FunctionPointer(_, _) => Err(ToAstProblem::FunctionLayout),
}
}
@ -290,7 +291,7 @@ fn ptr_to_ast<'a>(
items: &[],
final_comments: &[],
},
Layout::Builtin(Builtin::List(_, elem_layout)) => {
Layout::Builtin(Builtin::List(elem_layout)) => {
// Turn the (ptr, len) wrapper struct into actual ptr and len values.
let len = unsafe { *(ptr.offset(env.ptr_bytes as isize) as *const usize) };
let ptr = unsafe { *(ptr as *const *const u8) };
@ -313,6 +314,9 @@ fn ptr_to_ast<'a>(
let (tag_name, payload_vars) = tags.iter().next().unwrap();
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), payload_vars)
}
Content::Structure(FlatType::FunctionOrTagUnion(tag_name, _, _)) => {
single_tag_union_to_ast(env, ptr, field_layouts, tag_name.clone(), &[])
}
Content::Structure(FlatType::EmptyRecord) => {
struct_to_ast(env, ptr, &[], &MutMap::default())
}

View File

@ -1,6 +1,7 @@
use crate::repl::eval;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::module::Linkage;
use roc_build::link::module_to_dylib;
use roc_build::program::FunctionIterator;
use roc_can::builtins::builtin_defs_map;
@ -8,6 +9,7 @@ use roc_collections::all::{MutMap, MutSet};
use roc_fmt::annotation::Formattable;
use roc_fmt::annotation::{Newlines, Parens};
use roc_gen::llvm::build::{build_proc, build_proc_header, OptLevel};
use roc_gen::llvm::externs::add_default_roc_externs;
use roc_load::file::LoadingProblem;
use roc_parse::parser::SyntaxError;
use roc_types::pretty_print::{content_to_string, name_all_type_vars};
@ -126,14 +128,15 @@ pub fn gen_and_eval<'a>(
Ok(ReplOutput::Problems(lines))
} else {
let context = Context::create();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, ""));
let builder = context.create_builder();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
let module = arena.alloc(roc_gen::llvm::build::module_from_builtins(&context, ""));
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
add_default_roc_externs(&context, module, &builder, ptr_bytes);
// mark our zig-defined builtins as internal
use inkwell::module::Linkage;
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
if name.starts_with("roc_builtins") {
@ -190,12 +193,12 @@ pub fn gen_and_eval<'a>(
// because their bodies may reference each other.
let mut scope = roc_gen::llvm::build::Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val);
}
headers.push((proc, fn_val));
@ -240,7 +243,7 @@ pub fn gen_and_eval<'a>(
&env,
&mut layout_ids,
main_fn_symbol,
&main_fn_layout,
main_fn_layout,
);
env.dibuilder.finalize();
@ -256,14 +259,17 @@ pub fn gen_and_eval<'a>(
module_pass.run_on(env.module);
// Verify the module
if let Err(errors) = env.module.verify() {
panic!("Errors defining module: {:?}", errors);
}
// Uncomment this to see the module's optimized LLVM instruction output:
// env.module.print_to_stderr();
// Verify the module
if let Err(errors) = env.module.verify() {
panic!(
"Errors defining module: {}\n\nUncomment things nearby to see more details.",
errors
);
}
let lib = module_to_dylib(&env.module, &target, opt_level)
.expect("Error loading compiled dylib for test");
let res_answer = unsafe {
@ -271,7 +277,7 @@ pub fn gen_and_eval<'a>(
&arena,
lib,
main_fn_name,
&main_fn_layout,
&arena.alloc(main_fn_layout).full(),
&content,
&env.interns,
home,

View File

@ -14,7 +14,6 @@ comptime {
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
@ -25,6 +24,22 @@ const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocCallResult) void;
extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
}
const RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {};
@ -46,7 +61,7 @@ pub export fn main() i32 {
// stdout the result
stdout.print("{}\n", .{callresult.content.asSlice()}) catch unreachable;
callresult.content.deinit(std.heap.c_allocator);
callresult.content.deinit();
// end time
var ts2: std.os.timespec = undefined;

View File

@ -24,6 +24,22 @@ const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocCallResult) void;
extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
return realloc(@alignCast(16, @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
free(@alignCast(16, @ptrCast([*]u8, c_ptr)));
}
const RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {};
@ -45,7 +61,7 @@ pub export fn main() i32 {
// stdout the result
stdout.print("{}\n", .{callresult.content.asSlice()}) catch unreachable;
callresult.content.deinit(std.heap.c_allocator);
callresult.content.deinit();
// end time
var ts2: std.os.timespec = undefined;

View File

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

View File

@ -2,7 +2,7 @@ use crate::target;
use bumpalo::Bump;
use inkwell::context::Context;
use inkwell::targets::{CodeModel, FileType, RelocMode};
use inkwell::values::FunctionValue;
pub use roc_gen::llvm::build::FunctionIterator;
use roc_gen::llvm::build::{build_proc, build_proc_header, module_from_builtins, OptLevel, Scope};
use roc_load::file::MonomorphizedModule;
use roc_mono::layout::LayoutIds;
@ -100,20 +100,22 @@ pub fn gen_from_mono_module(
let kind_id = Attribute::get_named_enum_kind_id("alwaysinline");
debug_assert!(kind_id > 0);
let attr = context.create_enum_attribute(kind_id, 1);
let enum_attr = context.create_enum_attribute(kind_id, 1);
for function in FunctionIterator::from_module(module) {
let name = function.get_name().to_str().unwrap();
// mark our zig-defined builtins as internal
if name.starts_with("roc_builtins") {
function.set_linkage(Linkage::Internal);
}
if name.starts_with("roc_builtins.dict") || name.starts_with("dict.RocDict") {
function.add_attribute(AttributeLoc::Function, attr);
}
if name.starts_with("roc_builtins.list") || name.starts_with("list.RocList") {
function.add_attribute(AttributeLoc::Function, attr);
if name.starts_with("roc_builtins.dict")
|| name.starts_with("dict.RocDict")
|| name.starts_with("roc_builtins.list")
|| name.starts_with("list.RocList")
{
function.add_attribute(AttributeLoc::Function, enum_attr);
}
}
@ -146,12 +148,12 @@ pub fn gen_from_mono_module(
let mut scope = Scope::default();
for ((symbol, layout), proc) in loaded.procedures {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val);
}
headers.push((proc, fn_val));
@ -287,30 +289,3 @@ pub fn gen_from_mono_module(
emit_o_file,
}
}
pub struct FunctionIterator<'ctx> {
next: Option<FunctionValue<'ctx>>,
}
impl<'ctx> FunctionIterator<'ctx> {
pub fn from_module(module: &inkwell::module::Module<'ctx>) -> Self {
Self {
next: module.get_first_function(),
}
}
}
impl<'ctx> Iterator for FunctionIterator<'ctx> {
type Item = FunctionValue<'ctx>;
fn next(&mut self) -> Option<Self::Item> {
match self.next {
Some(function) => {
self.next = function.get_next_function();
Some(function)
}
None => None,
}
}
}

View File

@ -0,0 +1,233 @@
const std = @import("std");
const math = std.math;
pub const RocDec = struct {
num: i128,
pub const decimal_places: u32 = 18;
pub const min: RocDec = .{ .num = math.minInt(i128) };
pub const max: RocDec = .{ .num = math.maxInt(i128) };
pub const one_point_zero: RocDec = .{ .num = comptime math.pow(i128, 10, RocDec.decimal_places) };
pub fn add(self: RocDec, other: RocDec) RocDec {
var answer: i128 = undefined;
const overflowed = @addWithOverflow(i128, self.num, other.num, &answer);
if (!overflowed) {
return RocDec{ .num = answer };
} else {
std.debug.panic("TODO runtime exception for overflow!", .{});
}
}
pub fn mul(self: RocDec, other: RocDec) RocDec {
const self_i128 = self.num;
const other_i128 = other.num;
// const answer = 0; //self_i256 * other_i256;
const is_answer_negative = (self_i128 < 0) != (other_i128 < 0);
const self_u128 = @intCast(u128, math.absInt(self_i128) catch {
if (other_i128 == 0) {
return .{ .num = 0 };
} else if (other_i128 == RocDec.one_point_zero.num) {
return self;
} else {
std.debug.panic("TODO runtime exception for overflow!", .{});
}
});
const other_u128 = @intCast(u128, math.absInt(other_i128) catch {
if (self_i128 == 0) {
return .{ .num = 0 };
} else if (self_i128 == RocDec.one_point_zero.num) {
return other;
} else {
std.debug.panic("TODO runtime exception for overflow!", .{});
}
});
const unsigned_answer: i128 = mul_and_decimalize(self_u128, other_u128);
if (is_answer_negative) {
return .{ .num = -unsigned_answer };
} else {
return .{ .num = unsigned_answer };
}
}
};
const U256 = struct {
hi: u128,
lo: u128,
};
fn mul_and_decimalize(a: u128, b: u128) i128 {
const answer_u256 = mul_u128(a, b);
var lhs_hi = answer_u256.hi;
var lhs_lo = answer_u256.lo;
// Divide - or just add 1, multiply by floor(2^315/10^18), then right shift 315 times.
// floor(2^315/10^18) is 66749594872528440074844428317798503581334516323645399060845050244444366430645
// Add 1.
// This can't overflow because the intial numbers are only 127bit due to removing the sign bit.
var overflowed = @addWithOverflow(u128, lhs_lo, 1, &lhs_lo);
lhs_hi = blk: {
if (overflowed) {
break :blk lhs_hi + 1;
} else {
break :blk lhs_hi + 0;
}
};
// This needs to do multiplication in a way that expands,
// since we throw away 315 bits we care only about the higher end, not lower.
// So like need to do high low mult with 2 U256's and then bitshift.
// I bet this has a lot of room for multiplication optimization.
const rhs_hi: u128 = 0x9392ee8e921d5d073aff322e62439fcf;
const rhs_lo: u128 = 0x32d7f344649470f90cac0c573bf9e1b5;
const ea = mul_u128(lhs_lo, rhs_lo);
const gf = mul_u128(lhs_hi, rhs_lo);
const jh = mul_u128(lhs_lo, rhs_hi);
const lk = mul_u128(lhs_hi, rhs_hi);
const e = ea.hi;
const _a = ea.lo;
const g = gf.hi;
const f = gf.lo;
const j = jh.hi;
const h = jh.lo;
const l = lk.hi;
const k = lk.lo;
// b = e + f + h
var e_plus_f: u128 = undefined;
overflowed = @addWithOverflow(u128, e, f, &e_plus_f);
var b_carry1: u128 = undefined;
if (overflowed) {
b_carry1 = 1;
} else {
b_carry1 = 0;
}
var idk: u128 = undefined;
overflowed = @addWithOverflow(u128, e_plus_f, h, &idk);
var b_carry2: u128 = undefined;
if (overflowed) {
b_carry2 = 1;
} else {
b_carry2 = 0;
}
// c = carry + g + j + k // it doesn't say +k but I think it should be?
var g_plus_j: u128 = undefined;
overflowed = @addWithOverflow(u128, g, j, &g_plus_j);
var c_carry1: u128 = undefined;
if (overflowed) {
c_carry1 = 1;
} else {
c_carry1 = 0;
}
var g_plus_j_plus_k: u128 = undefined;
overflowed = @addWithOverflow(u128, g_plus_j, k, &g_plus_j_plus_k);
var c_carry2: u128 = undefined;
if (overflowed) {
c_carry2 = 1;
} else {
c_carry2 = 0;
}
var c_without_bcarry2: u128 = undefined;
overflowed = @addWithOverflow(u128, g_plus_j_plus_k, b_carry1, &c_without_bcarry2);
var c_carry3: u128 = undefined;
if (overflowed) {
c_carry3 = 1;
} else {
c_carry3 = 0;
}
var c: u128 = undefined;
overflowed = @addWithOverflow(u128, c_without_bcarry2, b_carry2, &c);
var c_carry4: u128 = undefined;
if (overflowed) {
c_carry4 = 1;
} else {
c_carry4 = 0;
}
// d = carry + l
var d: u128 = undefined;
overflowed = @addWithOverflow(u128, l, c_carry1, &d);
overflowed = overflowed or @addWithOverflow(u128, d, c_carry2, &d);
overflowed = overflowed or @addWithOverflow(u128, d, c_carry3, &d);
overflowed = overflowed or @addWithOverflow(u128, d, c_carry4, &d);
if (overflowed) {
std.debug.panic("TODO runtime exception for overflow!", .{});
}
// Final 512bit value is d, c, b, a
// need to left shift 321 times
// 315 - 256 is 59. So left shift d, c 59 times.
return @intCast(i128, c >> 59 | (d << (128 - 59)));
}
fn mul_u128(a: u128, b: u128) U256 {
var hi: u128 = undefined;
var lo: u128 = undefined;
const bits_in_dword_2: u32 = 64;
const lower_mask: u128 = math.maxInt(u128) >> bits_in_dword_2;
lo = (a & lower_mask) * (b & lower_mask);
var t = lo >> bits_in_dword_2;
lo &= lower_mask;
t += (a >> bits_in_dword_2) * (b & lower_mask);
lo += (t & lower_mask) << bits_in_dword_2;
hi = t >> bits_in_dword_2;
t = lo >> bits_in_dword_2;
lo &= lower_mask;
t += (b >> bits_in_dword_2) * (a & lower_mask);
lo += (t & lower_mask) << bits_in_dword_2;
hi += t >> bits_in_dword_2;
hi += (a >> bits_in_dword_2) * (b >> bits_in_dword_2);
return .{ .hi = hi, .lo = lo };
}
const one_e20: i256 = 100000000000000000000;
const expectEqual = std.testing.expectEqual;
test "add" {
const dec: RocDec = .{ .num = 0 };
expectEqual(RocDec{ .num = 0 }, dec.add(.{ .num = 0 }));
}
test "mul" {
var dec1: RocDec = .{ .num = 0 };
expectEqual(RocDec{ .num = 0 }, dec1.mul(.{ .num = 0 }));
}

View File

@ -2,7 +2,6 @@ const std = @import("std");
const testing = std.testing;
const expectEqual = testing.expectEqual;
const mem = std.mem;
const Allocator = mem.Allocator;
const assert = std.debug.assert;
const utils = @import("utils.zig");
@ -74,7 +73,7 @@ const Alignment = packed enum(u8) {
Align8KeyFirst,
Align8ValueFirst,
fn toUsize(self: Alignment) usize {
fn toU32(self: Alignment) u32 {
switch (self) {
.Align16KeyFirst => return 16,
.Align16ValueFirst => return 16,
@ -94,20 +93,18 @@ const Alignment = packed enum(u8) {
};
pub fn decref(
allocator: *Allocator,
alignment: Alignment,
bytes_or_null: ?[*]u8,
data_bytes: usize,
alignment: Alignment,
) void {
return utils.decref(allocator, alignment.toUsize(), bytes_or_null, data_bytes);
return utils.decref(bytes_or_null, data_bytes, alignment.toU32());
}
pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: Alignment,
data_bytes: usize,
alignment: Alignment,
) [*]u8 {
return utils.allocateWithRefcount(allocator, alignment.toUsize(), data_bytes);
return utils.allocateWithRefcount(data_bytes, alignment.toU32());
}
pub const RocDict = extern struct {
@ -124,7 +121,6 @@ pub const RocDict = extern struct {
}
pub fn allocate(
allocator: *Allocator,
number_of_levels: usize,
number_of_entries: usize,
alignment: Alignment,
@ -136,7 +132,7 @@ pub const RocDict = extern struct {
const data_bytes = number_of_slots * slot_size;
return RocDict{
.dict_bytes = allocateWithRefcount(allocator, alignment, data_bytes),
.dict_bytes = allocateWithRefcount(data_bytes, alignment),
.number_of_levels = number_of_levels,
.dict_entries_len = number_of_entries,
};
@ -144,7 +140,6 @@ pub const RocDict = extern struct {
pub fn reallocate(
self: RocDict,
allocator: *Allocator,
alignment: Alignment,
key_width: usize,
value_width: usize,
@ -157,7 +152,7 @@ pub const RocDict = extern struct {
const delta_capacity = new_capacity - old_capacity;
const data_bytes = new_capacity * slot_size;
const first_slot = allocateWithRefcount(allocator, alignment, data_bytes);
const first_slot = allocateWithRefcount(data_bytes, alignment);
// transfer the memory
@ -204,7 +199,7 @@ pub const RocDict = extern struct {
};
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
decref(allocator, alignment, self.dict_bytes, self.capacity() * slotSize(key_width, value_width));
decref(self.dict_bytes, self.capacity() * slotSize(key_width, value_width), alignment);
return result;
}
@ -236,7 +231,7 @@ pub const RocDict = extern struct {
return totalCapacityAtLevel(self.number_of_levels);
}
pub fn makeUnique(self: RocDict, allocator: *Allocator, alignment: Alignment, key_width: usize, value_width: usize) RocDict {
pub fn makeUnique(self: RocDict, alignment: Alignment, key_width: usize, value_width: usize) RocDict {
if (self.isEmpty()) {
return self;
}
@ -246,7 +241,7 @@ pub const RocDict = extern struct {
}
// unfortunately, we have to clone
var new_dict = RocDict.allocate(allocator, self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width);
var new_dict = RocDict.allocate(self.number_of_levels, self.dict_entries_len, alignment, key_width, value_width);
var old_bytes: [*]u8 = @ptrCast([*]u8, self.dict_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_dict.dict_bytes);
@ -256,7 +251,7 @@ pub const RocDict = extern struct {
// NOTE we fuse an increment of all keys/values with a decrement of the input dict
const data_bytes = self.capacity() * slotSize(key_width, value_width);
decref(allocator, alignment, self.dict_bytes, data_bytes);
decref(self.dict_bytes, data_bytes, alignment);
return new_dict;
}
@ -414,13 +409,16 @@ const HashFn = fn (u64, ?[*]u8) callconv(.C) u64;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
const Inc = fn (?[*]u8) callconv(.C) void;
const IncN = fn (?[*]u8, usize) callconv(.C) void;
const Dec = fn (?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
// Dict.insert : Dict k v, k, v -> Dict k v
pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width: usize, value: Opaque, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
var seed: u64 = INITIAL_SEED;
var result = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var result = input.makeUnique(alignment, key_width, value_width);
var current_level: usize = 1;
var current_level_size: usize = 8;
@ -428,7 +426,7 @@ pub fn dictInsert(input: RocDict, alignment: Alignment, key: Opaque, key_width:
while (true) {
if (current_level > result.number_of_levels) {
result = result.reallocate(std.heap.c_allocator, alignment, key_width, value_width);
result = result.reallocate(alignment, key_width, value_width);
}
const hash = hash_fn(seed, key);
@ -484,7 +482,7 @@ pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width:
return;
},
MaybeIndex.index => |index| {
var dict = input.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
var dict = input.makeUnique(alignment, key_width, value_width);
assert(index < dict.capacity());
@ -499,7 +497,7 @@ pub fn dictRemove(input: RocDict, alignment: Alignment, key: Opaque, key_width:
// if the dict is now completely empty, free its allocation
if (dict.dict_entries_len == 0) {
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
decref(dict.dict_bytes, data_bytes, alignment);
output.* = RocDict.empty();
return;
}
@ -572,7 +570,7 @@ pub fn dictKeys(dict: RocDict, alignment: Alignment, key_width: usize, value_wid
}
const data_bytes = length * key_width;
var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes);
var ptr = allocateWithRefcount(data_bytes, alignment);
var offset = blk: {
if (alignment.keyFirst()) {
@ -621,7 +619,7 @@ pub fn dictValues(dict: RocDict, alignment: Alignment, key_width: usize, value_w
}
const data_bytes = length * value_width;
var ptr = allocateWithRefcount(std.heap.c_allocator, alignment, data_bytes);
var ptr = allocateWithRefcount(data_bytes, alignment);
var offset = blk: {
if (alignment.keyFirst()) {
@ -655,7 +653,7 @@ fn doNothing(ptr: Opaque) callconv(.C) void {
}
pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, inc_key: Inc, inc_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
while (i < dict2.capacity()) : (i += 1) {
@ -690,7 +688,7 @@ pub fn dictUnion(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width
}
pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Inc, dec_value: Inc, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
@ -715,7 +713,7 @@ pub fn dictIntersection(dict1: RocDict, dict2: RocDict, alignment: Alignment, ke
}
pub fn dictDifference(dict1: RocDict, dict2: RocDict, alignment: Alignment, key_width: usize, value_width: usize, hash_fn: HashFn, is_eq: EqFn, dec_key: Dec, dec_value: Dec, output: *RocDict) callconv(.C) void {
output.* = dict1.makeUnique(std.heap.c_allocator, alignment, key_width, value_width);
output.* = dict1.makeUnique(alignment, key_width, value_width);
var i: usize = 0;
const size = dict1.capacity();
@ -756,16 +754,34 @@ pub fn setFromList(list: RocList, alignment: Alignment, key_width: usize, value_
// NOTE: decref checks for the empty case
const data_bytes = size * key_width;
decref(std.heap.c_allocator, alignment, list.bytes, data_bytes);
decref(list.bytes, data_bytes, alignment);
}
const StepperCaller = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, accum: Opaque, alignment: Alignment, key_width: usize, value_width: usize, accum_width: usize, inc_key: Inc, inc_value: Inc, output: Opaque) callconv(.C) void {
pub fn dictWalk(
dict: RocDict,
caller: Caller3,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
accum: Opaque,
alignment: Alignment,
key_width: usize,
value_width: usize,
accum_width: usize,
inc_key: Inc,
inc_value: Inc,
output: Opaque,
) callconv(.C) void {
const alignment_u32 = alignment.toU32();
// allocate space to write the result of the stepper into
// experimentally aliasing the accum and output pointers is not a good idea
const alloc: [*]u8 = @ptrCast([*]u8, std.heap.c_allocator.alloc(u8, accum_width) catch unreachable);
const bytes_ptr: [*]u8 = utils.alloc(accum_width, alignment_u32);
var b1 = output orelse unreachable;
var b2 = alloc;
var b2 = bytes_ptr;
if (data_is_owned) {
inc_n_data(data, dict.len());
}
@memcpy(b2, accum orelse unreachable, accum_width);
@ -777,7 +793,7 @@ pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, a
const key = dict.getKey(i, alignment, key_width, value_width);
const value = dict.getValue(i, alignment, key_width, value_width);
stepper_caller(stepper, key, value, b2, b1);
caller(data, key, value, b2, b1);
const temp = b1;
b2 = b1;
@ -788,8 +804,5 @@ pub fn dictWalk(dict: RocDict, stepper: Opaque, stepper_caller: StepperCaller, a
}
@memcpy(output orelse unreachable, b2, accum_width);
std.heap.c_allocator.free(alloc[0..accum_width]);
const data_bytes = dict.capacity() * slotSize(key_width, value_width);
decref(std.heap.c_allocator, alignment, dict.dict_bytes, data_bytes);
utils.dealloc(bytes_ptr, alignment_u32);
}

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,9 @@ const builtin = @import("builtin");
const std = @import("std");
const testing = std.testing;
// Dec Module
const dec = @import("dec.zig");
// List Module
const list = @import("list.zig");
@ -25,6 +28,8 @@ comptime {
exportListFn(list.listReverse, "reverse");
exportListFn(list.listSortWith, "sort_with");
exportListFn(list.listConcat, "concat");
exportListFn(list.listDrop, "drop");
exportListFn(list.listSet, "set");
}
// Dict Module

View File

@ -1,9 +1,9 @@
const utils = @import("utils.zig");
const roc_mem = @import("mem.zig");
const RocList = @import("list.zig").RocList;
const std = @import("std");
const mem = std.mem;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
const Allocator = mem.Allocator;
const unicode = std.unicode;
const testing = std.testing;
const expectEqual = testing.expectEqual;
@ -34,6 +34,8 @@ pub const RocStr = extern struct {
str_bytes: ?[*]u8,
str_len: usize,
pub const alignment = @alignOf(usize);
pub inline fn empty() RocStr {
return RocStr{
.str_len = 0,
@ -43,15 +45,15 @@ pub const RocStr = extern struct {
// This clones the pointed-to bytes if they won't fit in a
// small string, and returns a (pointer, len) tuple which points to them.
pub fn init(allocator: *Allocator, bytes_ptr: [*]const u8, length: usize) RocStr {
var result = RocStr.allocate(allocator, InPlace.Clone, length);
pub fn init(bytes_ptr: [*]const u8, length: usize) RocStr {
var result = RocStr.allocate(InPlace.Clone, length);
@memcpy(result.asU8ptr(), bytes_ptr, length);
return result;
}
pub fn initBig(allocator: *Allocator, in_place: InPlace, number_of_chars: u64) RocStr {
const first_element = utils.allocateWithRefcount(allocator, @sizeOf(usize), number_of_chars);
pub fn initBig(in_place: InPlace, number_of_chars: u64) RocStr {
const first_element = utils.allocateWithRefcount(number_of_chars, @sizeOf(usize));
return RocStr{
.str_bytes = first_element,
@ -60,11 +62,11 @@ pub const RocStr = extern struct {
}
// allocate space for a (big or small) RocStr, but put nothing in it yet
pub fn allocate(allocator: *Allocator, result_in_place: InPlace, number_of_chars: usize) RocStr {
pub fn allocate(result_in_place: InPlace, number_of_chars: usize) RocStr {
const result_is_big = number_of_chars >= small_string_size;
if (result_is_big) {
return RocStr.initBig(allocator, result_in_place, number_of_chars);
return RocStr.initBig(result_in_place, number_of_chars);
} else {
var t = blank_small_string;
@ -77,10 +79,9 @@ pub const RocStr = extern struct {
}
}
pub fn deinit(self: RocStr, allocator: *Allocator) void {
pub fn deinit(self: RocStr) void {
if (!self.isSmallStr() and !self.isEmpty()) {
const alignment = @alignOf(usize);
utils.decref(allocator, alignment, self.str_bytes, self.str_len);
utils.decref(self.str_bytes, self.str_len, RocStr.alignment);
}
}
@ -98,7 +99,7 @@ pub const RocStr = extern struct {
if (length < roc_str_size) {
return RocStr.empty();
} else {
var new_bytes: []T = allocator.alloc(u8, length) catch unreachable;
var new_bytes: []T = utils.alloc(length, RocStr.alignment) catch unreachable;
var new_bytes_ptr: [*]u8 = @ptrCast([*]u8, &new_bytes);
@ -146,12 +147,12 @@ pub const RocStr = extern struct {
return true;
}
pub fn clone(allocator: *Allocator, in_place: InPlace, str: RocStr) RocStr {
pub fn clone(in_place: InPlace, str: RocStr) RocStr {
if (str.isSmallStr() or str.isEmpty()) {
// just return the bytes
return str;
} else {
var new_str = RocStr.initBig(allocator, in_place, str.str_len);
var new_str = RocStr.initBig(in_place, str.str_len);
var old_bytes: [*]u8 = @ptrCast([*]u8, str.str_bytes);
var new_bytes: [*]u8 = @ptrCast([*]u8, new_str.str_bytes);
@ -164,33 +165,30 @@ pub const RocStr = extern struct {
pub fn reallocate(
self: RocStr,
allocator: *Allocator,
new_length: usize,
) RocStr {
const alignment = 1;
const element_width = 1;
if (self.bytes) |source_ptr| {
if (self.isUnique()) {
const new_source = utils.unsafeReallocate(source_ptr, allocator, alignment, self.len(), new_length, element_width);
const new_source = utils.unsafeReallocate(source_ptr, RocStr.alignment, self.len(), new_length, element_width);
return RocStr{ .str_bytes = new_source, .str_len = new_length };
}
}
return self.reallocateFresh(allocator, alignment, new_length, element_width);
return self.reallocateFresh(RocStr.alignment, new_length, element_width);
}
/// reallocate by explicitly making a new allocation and copying elements over
pub fn reallocateFresh(
self: RocStr,
allocator: *Allocator,
new_length: usize,
) RocStr {
const old_length = self.len();
const delta_length = new_length - old_length;
const result = RocStr.allocate(allocator, InPlace.Clone, new_length);
const result = RocStr.allocate(InPlace.Clone, new_length);
// transfer the memory
@ -200,7 +198,7 @@ pub const RocStr = extern struct {
@memcpy(dest_ptr, source_ptr, old_length);
@memset(dest_ptr + old_length, 0, delta_length);
self.deinit(allocator);
self.deinit();
return result;
}
@ -268,33 +266,33 @@ pub const RocStr = extern struct {
const str1_len = 3;
var str1: [str1_len]u8 = "abc".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
var roc_str2 = RocStr.init(str2_ptr, str2_len);
expect(roc_str1.eq(roc_str2));
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
roc_str1.deinit();
roc_str2.deinit();
}
test "RocStr.eq: not equal different length" {
const str1_len = 4;
var str1: [str1_len]u8 = "abcd".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
var roc_str2 = RocStr.init(str2_ptr, str2_len);
defer {
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
roc_str1.deinit();
roc_str2.deinit();
}
expect(!roc_str1.eq(roc_str2));
@ -304,16 +302,16 @@ pub const RocStr = extern struct {
const str1_len = 3;
var str1: [str1_len]u8 = "acb".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
var roc_str2 = RocStr.init(str2_ptr, str2_len);
defer {
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
roc_str1.deinit();
roc_str2.deinit();
}
expect(!roc_str1.eq(roc_str2));
@ -321,7 +319,7 @@ pub const RocStr = extern struct {
};
pub fn init(bytes_ptr: [*]const u8, length: usize) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, RocStr.init, .{ std.heap.c_allocator, bytes_ptr, length });
return @call(.{ .modifier = always_inline }, RocStr.init, .{ bytes_ptr, length });
}
// Str.equal
@ -335,17 +333,12 @@ pub fn strNumberOfBytes(string: RocStr) callconv(.C) usize {
}
// Str.fromInt
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strFromIntC(int: i64) callconv(.C) RocStr {
return strFromInt(std.heap.c_allocator, int);
}
fn strFromInt(allocator: *Allocator, int: i64) RocStr {
// prepare for having multiple integer types in the future
return @call(.{ .modifier = always_inline }, strFromIntHelp, .{ allocator, i64, int });
return @call(.{ .modifier = always_inline }, strFromIntHelp, .{ i64, int });
}
fn strFromIntHelp(allocator: *Allocator, comptime T: type, int: T) RocStr {
fn strFromIntHelp(comptime T: type, int: T) RocStr {
// determine maximum size for this T
comptime const size = comptime blk: {
// the string representation of the minimum i128 value uses at most 40 characters
@ -357,11 +350,10 @@ fn strFromIntHelp(allocator: *Allocator, comptime T: type, int: T) RocStr {
var buf: [size]u8 = undefined;
const result = std.fmt.bufPrint(&buf, "{}", .{int}) catch unreachable;
return RocStr.init(allocator, &buf, result.len);
return RocStr.init(&buf, result.len);
}
// Str.fromFloat
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
// NOTE the compiled zig for float formatting seems to use LLVM11-specific features
// hopefully we can use zig instead of snprintf in the future when we upgrade
@ -374,16 +366,15 @@ pub fn strFromFloatC(float: f64) callconv(.C) RocStr {
const result = c.snprintf(&buf, 100, "%f", float);
return RocStr.init(std.heap.c_allocator, &buf, @intCast(usize, result));
return RocStr.init(&buf, @intCast(usize, result));
}
// Str.split
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strSplitInPlaceC(array: [*]RocStr, string: RocStr, delimiter: RocStr) callconv(.C) void {
return @call(.{ .modifier = always_inline }, strSplitInPlace, .{ std.heap.c_allocator, array, string, delimiter });
return @call(.{ .modifier = always_inline }, strSplitInPlace, .{ array, string, delimiter });
}
fn strSplitInPlace(allocator: *Allocator, array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
fn strSplitInPlace(array: [*]RocStr, string: RocStr, delimiter: RocStr) void {
var ret_array_index: usize = 0;
var slice_start_index: usize = 0;
var str_index: usize = 0;
@ -415,7 +406,7 @@ fn strSplitInPlace(allocator: *Allocator, array: [*]RocStr, string: RocStr, deli
if (matches_delimiter) {
const segment_len: usize = str_index - slice_start_index;
array[ret_array_index] = RocStr.init(allocator, str_bytes + slice_start_index, segment_len);
array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, segment_len);
slice_start_index = str_index + delimiter_len;
ret_array_index += 1;
str_index += delimiter_len;
@ -425,21 +416,21 @@ fn strSplitInPlace(allocator: *Allocator, array: [*]RocStr, string: RocStr, deli
}
}
array[ret_array_index] = RocStr.init(allocator, str_bytes + slice_start_index, str_len - slice_start_index);
array[ret_array_index] = RocStr.init(str_bytes + slice_start_index, str_len - slice_start_index);
}
test "strSplitInPlace: no delimiter" {
// Str.split "abc" "!" == [ "abc" ]
const str_arr = "abc";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "!";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
var array: [1]RocStr = undefined;
const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
strSplitInPlace(array_ptr, str, delimiter);
var expected = [1]RocStr{
str,
@ -447,15 +438,15 @@ test "strSplitInPlace: no delimiter" {
defer {
for (array) |roc_str| {
roc_str.deinit(testing.allocator);
roc_str.deinit();
}
for (expected) |roc_str| {
roc_str.deinit(testing.allocator);
roc_str.deinit();
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
str.deinit();
delimiter.deinit();
}
expectEqual(array.len, expected.len);
@ -464,10 +455,10 @@ test "strSplitInPlace: no delimiter" {
test "strSplitInPlace: empty end" {
const str_arr = "1---- ---- ---- ---- ----2---- ---- ---- ---- ----";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "---- ---- ---- ---- ----";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const array_len: usize = 3;
var array: [array_len]RocStr = [_]RocStr{
@ -477,10 +468,10 @@ test "strSplitInPlace: empty end" {
};
const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
strSplitInPlace(array_ptr, str, delimiter);
const one = RocStr.init(testing.allocator, "1", 1);
const two = RocStr.init(testing.allocator, "2", 1);
const one = RocStr.init("1", 1);
const two = RocStr.init("2", 1);
var expected = [3]RocStr{
one, two, RocStr.empty(),
@ -488,15 +479,15 @@ test "strSplitInPlace: empty end" {
defer {
for (array) |rocStr| {
rocStr.deinit(testing.allocator);
rocStr.deinit();
}
for (expected) |rocStr| {
rocStr.deinit(testing.allocator);
rocStr.deinit();
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
str.deinit();
delimiter.deinit();
}
expectEqual(array.len, expected.len);
@ -507,10 +498,10 @@ test "strSplitInPlace: empty end" {
test "strSplitInPlace: delimiter on sides" {
const str_arr = "tttghittt";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "ttt";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const array_len: usize = 3;
var array: [array_len]RocStr = [_]RocStr{
@ -519,10 +510,10 @@ test "strSplitInPlace: delimiter on sides" {
undefined,
};
const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
strSplitInPlace(array_ptr, str, delimiter);
const ghi_arr = "ghi";
const ghi = RocStr.init(testing.allocator, ghi_arr, ghi_arr.len);
const ghi = RocStr.init(ghi_arr, ghi_arr.len);
var expected = [3]RocStr{
RocStr.empty(), ghi, RocStr.empty(),
@ -530,15 +521,15 @@ test "strSplitInPlace: delimiter on sides" {
defer {
for (array) |rocStr| {
rocStr.deinit(testing.allocator);
rocStr.deinit();
}
for (expected) |rocStr| {
rocStr.deinit(testing.allocator);
rocStr.deinit();
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
str.deinit();
delimiter.deinit();
}
expectEqual(array.len, expected.len);
@ -550,20 +541,20 @@ test "strSplitInPlace: delimiter on sides" {
test "strSplitInPlace: three pieces" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ]
const str_arr = "a!b!c";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "!";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
const array_len: usize = 3;
var array: [array_len]RocStr = undefined;
const array_ptr: [*]RocStr = &array;
strSplitInPlace(testing.allocator, array_ptr, str, delimiter);
strSplitInPlace(array_ptr, str, delimiter);
const a = RocStr.init(testing.allocator, "a", 1);
const b = RocStr.init(testing.allocator, "b", 1);
const c = RocStr.init(testing.allocator, "c", 1);
const a = RocStr.init("a", 1);
const b = RocStr.init("b", 1);
const c = RocStr.init("c", 1);
var expected_array = [array_len]RocStr{
a, b, c,
@ -571,15 +562,15 @@ test "strSplitInPlace: three pieces" {
defer {
for (array) |roc_str| {
roc_str.deinit(testing.allocator);
roc_str.deinit();
}
for (expected_array) |roc_str| {
roc_str.deinit(testing.allocator);
roc_str.deinit();
}
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
str.deinit();
delimiter.deinit();
}
expectEqual(expected_array.len, array.len);
@ -637,14 +628,14 @@ test "countSegments: long delimiter" {
// Str.split "str" "delimiter" == [ "str" ]
// 1 segment
const str_arr = "str";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "delimiter";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer {
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
str.deinit();
delimiter.deinit();
}
const segments_count = countSegments(str, delimiter);
@ -655,14 +646,14 @@ test "countSegments: delimiter at start" {
// Str.split "hello there" "hello" == [ "", " there" ]
// 2 segments
const str_arr = "hello there";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "hello";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer {
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
str.deinit();
delimiter.deinit();
}
const segments_count = countSegments(str, delimiter);
@ -674,14 +665,14 @@ test "countSegments: delimiter interspered" {
// Str.split "a!b!c" "!" == [ "a", "b", "c" ]
// 3 segments
const str_arr = "a!b!c";
const str = RocStr.init(testing.allocator, str_arr, str_arr.len);
const str = RocStr.init(str_arr, str_arr.len);
const delimiter_arr = "!";
const delimiter = RocStr.init(testing.allocator, delimiter_arr, delimiter_arr.len);
const delimiter = RocStr.init(delimiter_arr, delimiter_arr.len);
defer {
str.deinit(testing.allocator);
delimiter.deinit(testing.allocator);
str.deinit();
delimiter.deinit();
}
const segments_count = countSegments(str, delimiter);
@ -736,8 +727,8 @@ test "countGraphemeClusters: empty string" {
test "countGraphemeClusters: ascii characters" {
const bytes_arr = "abcd";
const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 4);
@ -746,8 +737,8 @@ test "countGraphemeClusters: ascii characters" {
test "countGraphemeClusters: utf8 characters" {
const bytes_arr = "ãxā";
const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 3);
@ -756,8 +747,8 @@ test "countGraphemeClusters: utf8 characters" {
test "countGraphemeClusters: emojis" {
const bytes_arr = "🤔🤔🤔";
const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 3);
@ -766,8 +757,8 @@ test "countGraphemeClusters: emojis" {
test "countGraphemeClusters: emojis and ut8 characters" {
const bytes_arr = "🤔å🤔¥🤔ç";
const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 6);
@ -776,8 +767,8 @@ test "countGraphemeClusters: emojis and ut8 characters" {
test "countGraphemeClusters: emojis, ut8, and ascii characters" {
const bytes_arr = "6🤔å🤔e¥🤔çpp";
const bytes_len = bytes_arr.len;
const str = RocStr.init(testing.allocator, bytes_arr, bytes_len);
defer str.deinit(testing.allocator);
const str = RocStr.init(bytes_arr, bytes_len);
defer str.deinit();
const count = countGraphemeClusters(str);
expectEqual(count, 10);
@ -828,36 +819,36 @@ pub fn startsWithCodePoint(string: RocStr, prefix: u32) callconv(.C) bool {
}
test "startsWithCodePoint: ascii char" {
const whole = RocStr.init(testing.allocator, "foobar", 6);
const whole = RocStr.init("foobar", 6);
const prefix = 'f';
expect(startsWithCodePoint(whole, prefix));
}
test "startsWithCodePoint: emoji" {
const yes = RocStr.init(testing.allocator, "💖foobar", 10);
const no = RocStr.init(testing.allocator, "foobar", 6);
const yes = RocStr.init("💖foobar", 10);
const no = RocStr.init("foobar", 6);
const prefix = '💖';
expect(startsWithCodePoint(yes, prefix));
expect(!startsWithCodePoint(no, prefix));
}
test "startsWith: foo starts with fo" {
const foo = RocStr.init(testing.allocator, "foo", 3);
const fo = RocStr.init(testing.allocator, "fo", 2);
const foo = RocStr.init("foo", 3);
const fo = RocStr.init("fo", 2);
expect(startsWith(foo, fo));
}
test "startsWith: 123456789123456789 starts with 123456789123456789" {
const str = RocStr.init(testing.allocator, "123456789123456789", 18);
defer str.deinit(testing.allocator);
const str = RocStr.init("123456789123456789", 18);
defer str.deinit();
expect(startsWith(str, str));
}
test "startsWith: 12345678912345678910 starts with 123456789123456789" {
const str = RocStr.init(testing.allocator, "12345678912345678910", 20);
defer str.deinit(testing.allocator);
const prefix = RocStr.init(testing.allocator, "123456789123456789", 18);
defer prefix.deinit(testing.allocator);
const str = RocStr.init("12345678912345678910", 20);
defer str.deinit();
const prefix = RocStr.init("123456789123456789", 18);
defer prefix.deinit();
expect(startsWith(str, prefix));
}
@ -886,63 +877,60 @@ pub fn endsWith(string: RocStr, suffix: RocStr) callconv(.C) bool {
}
test "endsWith: foo ends with oo" {
const foo = RocStr.init(testing.allocator, "foo", 3);
const oo = RocStr.init(testing.allocator, "oo", 2);
defer foo.deinit(testing.allocator);
defer oo.deinit(testing.allocator);
const foo = RocStr.init("foo", 3);
const oo = RocStr.init("oo", 2);
defer foo.deinit();
defer oo.deinit();
expect(endsWith(foo, oo));
}
test "endsWith: 123456789123456789 ends with 123456789123456789" {
const str = RocStr.init(testing.allocator, "123456789123456789", 18);
defer str.deinit(testing.allocator);
const str = RocStr.init("123456789123456789", 18);
defer str.deinit();
expect(endsWith(str, str));
}
test "endsWith: 12345678912345678910 ends with 345678912345678910" {
const str = RocStr.init(testing.allocator, "12345678912345678910", 20);
const suffix = RocStr.init(testing.allocator, "345678912345678910", 18);
defer str.deinit(testing.allocator);
defer suffix.deinit(testing.allocator);
const str = RocStr.init("12345678912345678910", 20);
const suffix = RocStr.init("345678912345678910", 18);
defer str.deinit();
defer suffix.deinit();
expect(endsWith(str, suffix));
}
test "endsWith: hello world ends with world" {
const str = RocStr.init(testing.allocator, "hello world", 11);
const suffix = RocStr.init(testing.allocator, "world", 5);
defer str.deinit(testing.allocator);
defer suffix.deinit(testing.allocator);
const str = RocStr.init("hello world", 11);
const suffix = RocStr.init("world", 5);
defer str.deinit();
defer suffix.deinit();
expect(endsWith(str, suffix));
}
// Str.concat
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strConcatC(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strConcat, .{ std.heap.c_allocator, result_in_place, arg1, arg2 });
return @call(.{ .modifier = always_inline }, strConcat, .{ result_in_place, arg1, arg2 });
}
fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
fn strConcat(result_in_place: InPlace, arg1: RocStr, arg2: RocStr) RocStr {
if (arg1.isEmpty()) {
// the second argument is borrowed, so we must increment its refcount before returning
return RocStr.clone(allocator, result_in_place, arg2);
return RocStr.clone(result_in_place, arg2);
} else if (arg2.isEmpty()) {
// the first argument is owned, so we can return it without cloning
return RocStr.clone(allocator, result_in_place, arg1);
return arg1;
} else {
const combined_length = arg1.len() + arg2.len();
const alignment = 1;
const element_width = 1;
if (!arg1.isSmallStr() and arg1.isUnique()) {
if (arg1.str_bytes) |source_ptr| {
const new_source = utils.unsafeReallocate(
source_ptr,
allocator,
alignment,
RocStr.alignment,
arg1.len(),
combined_length,
element_width,
@ -954,17 +942,12 @@ fn strConcat(allocator: *Allocator, result_in_place: InPlace, arg1: RocStr, arg2
}
}
var result = arg1.reallocateFresh(
allocator,
combined_length,
);
var result = arg1.reallocateFresh(combined_length);
var result_ptr = result.asU8ptr();
arg1.memcpy(result_ptr);
arg2.memcpy(result_ptr + arg1.len());
arg1.deinit(allocator);
return result;
}
}
@ -973,27 +956,27 @@ test "RocStr.concat: small concat small" {
const str1_len = 3;
var str1: [str1_len]u8 = "foo".*;
const str1_ptr: [*]u8 = &str1;
var roc_str1 = RocStr.init(testing.allocator, str1_ptr, str1_len);
var roc_str1 = RocStr.init(str1_ptr, str1_len);
const str2_len = 3;
var str2: [str2_len]u8 = "abc".*;
const str2_ptr: [*]u8 = &str2;
var roc_str2 = RocStr.init(testing.allocator, str2_ptr, str2_len);
var roc_str2 = RocStr.init(str2_ptr, str2_len);
const str3_len = 6;
var str3: [str3_len]u8 = "fooabc".*;
const str3_ptr: [*]u8 = &str3;
var roc_str3 = RocStr.init(testing.allocator, str3_ptr, str3_len);
var roc_str3 = RocStr.init(str3_ptr, str3_len);
defer {
roc_str1.deinit(testing.allocator);
roc_str2.deinit(testing.allocator);
roc_str3.deinit(testing.allocator);
roc_str1.deinit();
roc_str2.deinit();
roc_str3.deinit();
}
const result = strConcat(testing.allocator, InPlace.Clone, roc_str1, roc_str2);
const result = strConcat(InPlace.Clone, roc_str1, roc_str2);
defer result.deinit(testing.allocator);
defer result.deinit();
expect(roc_str3.eq(result));
}
@ -1004,12 +987,11 @@ pub const RocListStr = extern struct {
};
// Str.joinWith
// When we actually use this in Roc, libc will be linked so we have access to std.heap.c_allocator
pub fn strJoinWithC(list: RocListStr, separator: RocStr) callconv(.C) RocStr {
return @call(.{ .modifier = always_inline }, strJoinWith, .{ std.heap.c_allocator, list, separator });
return @call(.{ .modifier = always_inline }, strJoinWith, .{ list, separator });
}
fn strJoinWith(allocator: *Allocator, list: RocListStr, separator: RocStr) RocStr {
fn strJoinWith(list: RocListStr, separator: RocStr) RocStr {
const len = list.list_length;
if (len == 0) {
@ -1027,7 +1009,7 @@ fn strJoinWith(allocator: *Allocator, list: RocListStr, separator: RocStr) RocSt
// include size of the separator
total_size += separator.len() * (len - 1);
var result = RocStr.allocate(allocator, InPlace.Clone, total_size);
var result = RocStr.allocate(InPlace.Clone, total_size);
var result_ptr = result.asU8ptr();
var offset: usize = 0;
@ -1050,45 +1032,45 @@ test "RocStr.joinWith: result is big" {
const sep_len = 2;
var sep: [sep_len]u8 = ", ".*;
const sep_ptr: [*]u8 = &sep;
var roc_sep = RocStr.init(testing.allocator, sep_ptr, sep_len);
var roc_sep = RocStr.init(sep_ptr, sep_len);
const elem_len = 13;
var elem: [elem_len]u8 = "foobarbazspam".*;
const elem_ptr: [*]u8 = &elem;
var roc_elem = RocStr.init(testing.allocator, elem_ptr, elem_len);
var roc_elem = RocStr.init(elem_ptr, elem_len);
const result_len = 43;
var xresult: [result_len]u8 = "foobarbazspam, foobarbazspam, foobarbazspam".*;
const result_ptr: [*]u8 = &xresult;
var roc_result = RocStr.init(testing.allocator, result_ptr, result_len);
var roc_result = RocStr.init(result_ptr, result_len);
var elements: [3]RocStr = .{ roc_elem, roc_elem, roc_elem };
const list = RocListStr{ .list_length = 3, .list_elements = @ptrCast([*]RocStr, &elements) };
defer {
roc_sep.deinit(testing.allocator);
roc_elem.deinit(testing.allocator);
roc_result.deinit(testing.allocator);
roc_sep.deinit();
roc_elem.deinit();
roc_result.deinit();
}
const result = strJoinWith(testing.allocator, list, roc_sep);
const result = strJoinWith(list, roc_sep);
defer result.deinit(testing.allocator);
defer result.deinit();
expect(roc_result.eq(result));
}
// Str.toBytes
pub fn strToBytesC(arg: RocStr) callconv(.C) RocList {
return @call(.{ .modifier = always_inline }, strToBytes, .{ std.heap.c_allocator, arg });
return @call(.{ .modifier = always_inline }, strToBytes, .{arg});
}
fn strToBytes(allocator: *Allocator, arg: RocStr) RocList {
fn strToBytes(arg: RocStr) RocList {
if (arg.isEmpty()) {
return RocList.empty();
} else if (arg.isSmallStr()) {
const length = arg.len();
const ptr = utils.allocateWithRefcount(allocator, @alignOf(usize), length);
const ptr = utils.allocateWithRefcount(length, RocStr.alignment);
@memcpy(ptr, arg.asU8ptr(), length);
@ -1106,25 +1088,25 @@ const FromUtf8Result = extern struct {
};
pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{ std.heap.c_allocator, arg });
output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg});
}
fn fromUtf8(allocator: *Allocator, arg: RocList) FromUtf8Result {
fn fromUtf8(arg: RocList) FromUtf8Result {
const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length];
if (unicode.utf8ValidateSlice(bytes)) {
// the output will be correct. Now we need to take ownership of the input
if (arg.len() <= SMALL_STR_MAX_LENGTH) {
// turn the bytes into a small string
const string = RocStr.init(allocator, @ptrCast([*]u8, arg.bytes), arg.len());
const string = RocStr.init(@ptrCast([*]u8, arg.bytes), arg.len());
// then decrement the input list
const data_bytes = arg.len();
utils.decref(allocator, @alignOf(usize), arg.bytes, data_bytes);
utils.decref(arg.bytes, data_bytes, RocStr.alignment);
return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte };
} else {
const byte_list = arg.makeUnique(allocator, @alignOf(usize), @sizeOf(u8));
const byte_list = arg.makeUnique(RocStr.alignment, @sizeOf(u8));
const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length };
@ -1135,7 +1117,7 @@ fn fromUtf8(allocator: *Allocator, arg: RocList) FromUtf8Result {
// consume the input list
const data_bytes = arg.len();
utils.decref(allocator, @alignOf(usize), arg.bytes, data_bytes);
utils.decref(arg.bytes, data_bytes, RocStr.alignment);
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem };
}
@ -1202,11 +1184,11 @@ pub const Utf8ByteProblem = packed enum(u8) {
};
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
return fromUtf8(std.testing.allocator, RocList{ .bytes = bytes, .length = length });
return fromUtf8(RocList{ .bytes = bytes, .length = length });
}
fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8(std.testing.allocator, str);
return fromUtf8(str);
}
fn expectOk(result: FromUtf8Result) void {
@ -1214,7 +1196,7 @@ fn expectOk(result: FromUtf8Result) void {
}
fn sliceHelp(bytes: [*]const u8, length: usize) RocList {
var list = RocList.allocate(testing.allocator, @alignOf(usize), length, @sizeOf(u8));
var list = RocList.allocate(RocStr.alignment, length, @sizeOf(u8));
@memcpy(list.bytes orelse unreachable, bytes, length);
list.length = length;

View File

@ -1,12 +1,60 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const always_inline = std.builtin.CallOptions.Modifier.always_inline;
// If allocation fails, this must cxa_throw - it must not return a null pointer!
extern fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void;
// This should never be passed a null pointer.
// If allocation fails, this must cxa_throw - it must not return a null pointer!
extern fn roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void;
// This should never be passed a null pointer.
extern fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void;
comptime {
// During tetsts, use the testing allocators to satisfy these functions.
if (std.builtin.is_test) {
@export(testing_roc_alloc, .{ .name = "roc_alloc", .linkage = .Strong });
@export(testing_roc_realloc, .{ .name = "roc_realloc", .linkage = .Strong });
@export(testing_roc_dealloc, .{ .name = "roc_dealloc", .linkage = .Strong });
}
}
fn testing_roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
return @ptrCast(?*c_void, std.testing.allocator.alloc(u8, size) catch unreachable);
}
fn testing_roc_realloc(c_ptr: *c_void, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*c_void {
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
const slice = ptr[0..old_size];
return @ptrCast(?*c_void, std.testing.allocator.realloc(slice, new_size) catch unreachable);
}
fn testing_roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
const ptr = @ptrCast([*]u8, @alignCast(16, c_ptr));
std.testing.allocator.destroy(ptr);
}
pub fn alloc(size: usize, alignment: u32) [*]u8 {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_alloc, .{ size, alignment }));
}
pub fn realloc(c_ptr: [*]u8, new_size: usize, old_size: usize, alignment: u32) [*]u8 {
return @ptrCast([*]u8, @call(.{ .modifier = always_inline }, roc_realloc, .{ c_ptr, new_size, old_size, alignment }));
}
pub fn dealloc(c_ptr: [*]u8, alignment: u32) void {
return @call(.{ .modifier = always_inline }, roc_dealloc, .{ c_ptr, alignment });
}
pub const Inc = fn (?[*]u8) callconv(.C) void;
pub const IncN = fn (?[*]u8, u64) callconv(.C) void;
pub const Dec = fn (?[*]u8) callconv(.C) void;
const REFCOUNT_MAX_ISIZE: comptime isize = 0;
const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
pub const REFCOUNT_ONE_ISIZE: comptime isize = std.math.minInt(isize);
pub const REFCOUNT_ONE: usize = @bitCast(usize, REFCOUNT_ONE_ISIZE);
pub const IntWidth = enum(u8) {
@ -62,10 +110,9 @@ pub fn intWidth(width: IntWidth) anytype {
}
pub fn decref(
allocator: *Allocator,
alignment: usize,
bytes_or_null: ?[*]u8,
data_bytes: usize,
alignment: u32,
) void {
if (data_bytes == 0) {
return;
@ -81,7 +128,7 @@ pub fn decref(
switch (alignment) {
16 => {
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 16)[0 .. 16 + data_bytes]);
dealloc(bytes - 16, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
@ -89,7 +136,7 @@ pub fn decref(
else => {
// NOTE enums can currently have an alignment of < 8
if (refcount == REFCOUNT_ONE_ISIZE) {
allocator.free((bytes - 8)[0 .. 8 + data_bytes]);
dealloc(bytes - 8, alignment);
} else if (refcount_isize < 0) {
(isizes - 1)[0] = refcount - 1;
}
@ -98,9 +145,8 @@ pub fn decref(
}
pub fn allocateWithRefcount(
allocator: *Allocator,
alignment: usize,
data_bytes: usize,
alignment: u32,
) [*]u8 {
comptime const result_in_place = false;
@ -108,7 +154,7 @@ pub fn allocateWithRefcount(
16 => {
const length = 2 * @sizeOf(usize) + data_bytes;
var new_bytes: []align(16) u8 = allocator.alignedAlloc(u8, 16, length) catch unreachable;
var new_bytes: [*]align(16) u8 = @alignCast(16, alloc(length, alignment));
var as_usize_array = @ptrCast([*]usize, new_bytes);
if (result_in_place) {
@ -127,13 +173,13 @@ pub fn allocateWithRefcount(
else => {
const length = @sizeOf(usize) + data_bytes;
var new_bytes: []align(8) u8 = allocator.alignedAlloc(u8, 8, length) catch unreachable;
var new_bytes: [*]align(8) u8 = @alignCast(8, alloc(length, alignment));
var as_usize_array = @ptrCast([*]isize, new_bytes);
var as_isize_array = @ptrCast([*]isize, new_bytes);
if (result_in_place) {
as_usize_array[0] = @intCast(isize, number_of_slots);
as_isize_array[0] = @intCast(isize, number_of_slots);
} else {
as_usize_array[0] = REFCOUNT_ONE_ISIZE;
as_isize_array[0] = REFCOUNT_ONE_ISIZE;
}
var as_u8_array = @ptrCast([*]u8, new_bytes);
@ -146,8 +192,7 @@ pub fn allocateWithRefcount(
pub fn unsafeReallocate(
source_ptr: [*]u8,
allocator: *Allocator,
alignment: usize,
alignment: u32,
old_length: usize,
new_length: usize,
element_width: usize,
@ -165,8 +210,8 @@ pub fn unsafeReallocate(
// TODO handle out of memory
// NOTE realloc will dealloc the original allocation
const old_allocation = (source_ptr - align_width)[0..old_width];
const new_allocation = allocator.realloc(old_allocation, new_width) catch unreachable;
const old_allocation = source_ptr - align_width;
const new_allocation = realloc(old_allocation, new_width, old_width, alignment);
const new_source = @ptrCast([*]u8, new_allocation) + align_width;
return new_source;

View File

@ -32,7 +32,7 @@ fn main() {
run_command(&bitcode_path, "zig", &["build", "ir", "-Drelease=true"]);
let dest_bc_path = Path::new(&out_dir).join("builtins.bc");
let dest_bc_path = bitcode_path.join("builtins.bc");
let dest_bc = dest_bc_path.to_str().expect("Invalid dest bc path");
println!("Compiling bitcode to: {}", dest_bc);
@ -43,7 +43,6 @@ fn main() {
);
println!("cargo:rerun-if-changed=build.rs");
println!("cargo:rustc-env=BUILTINS_BC={}", dest_bc);
println!("cargo:rustc-env=BUILTINS_O={}", dest_obj);
get_zig_files(bitcode_path.as_path(), &|path| {
let path: &Path = path;

View File

@ -4,7 +4,7 @@ interface Num2
## Types
## Represents a number that could be either an #Int or a #Float.
## Represents a number that could be either an [Int] or a [Frac].
##
## This is useful for functions that can work on either, for example #Num.add, whose type is:
##
@ -53,6 +53,85 @@ interface Num2
## number literals without any suffix.
Num range : [ @Num range ]
## A decimal number.
##
## [Dec] is the best default choice for representing base-10 decimal numbers
## like currency, because it is base-10 under the hood. In contrast,
## [F64] and [F32] are base-2 under the hood, which can lead to decimal
## precision loss even when doing addition and subtraction. For example, when
## using [F64], running 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125,
## whereas when using [Dec], 0.1 + 0.2 returns 0.3.
##
## Under the hood, a [Dec] is an [I128], and operations on it perform
## [base-10 fixed-point arithmetic](https://en.wikipedia.org/wiki/Fixed-point_arithmetic)
## with 18 decimal places of precision.
##
## This means a [Dec] can represent whole numbers up to slightly over 170
## quintillion, along with 18 decimal places. (To be precise, it can store
## numbers betwween `-170_141_183_460_469_231_731.687303715884105728`
## and `170_141_183_460_469_231_731.687303715884105727`.) Why 18
## decimal places? It's the highest number of decimal places where you can still
## convert any [U64] to a [Dec] without losing information.
##
## There are some use cases where [F64] and [F32] can be better choices than [Dec]
## despite their precision issues. For example, in graphical applications they
## can be a better choice for representing coordinates because they take up
## less memory, certain relevant calculations run faster (see performance
## details, below), and decimal precision loss isn't as big a concern when
## dealing with screen coordinates as it is when dealing with currency.
##
## ## Performance
##
## [Dec] typically takes slightly less time than [F64] to perform addition and
## subtraction, but 10-20 times longer to perform multiplication and division.
## [sqrt] and trigonometry are massively slower with [Dec] than with [F64].
Dec : Frac [ @Decimal128 ]
## A fixed-size number with a fractional component.
##
## Roc fractions come in two flavors: fixed-point base-10 and floating-point base-2.
##
## * [Dec] is a 128-bit [fixed-point](https://en.wikipedia.org/wiki/Fixed-point_arithmetic) base-10 number. It's a great default choice, especially when precision is important - for example when representing currency. With [Dec], 0.1 + 0.2 returns 0.3.
## * [F64] and [F32] are [floating-point](https://en.wikipedia.org/wiki/Floating-point_arithmetic) base-2 numbers. They sacrifice precision for lower memory usage and improved performance on some operations. This makes them a good fit for representing graphical coordinates. With [F64], 0.1 + 0.2 returns 0.3000000000000000444089209850062616169452667236328125.
##
## If you don't specify a type, Roc will default to using [Dec] because it's
## the least error-prone overall. For example, suppose you write this:
##
## wasItPrecise = 0.1 + 0.2 == 0.3
##
## The value of `wasItPrecise` here will be `True`, because Roc uses [Dec]
## by default when there are no types specified.
##
## In contrast, suppose we use `f32` or `f64` for one of these numbers:
##
## wasItPrecise = 0.1f64 + 0.2 == 0.3
##
## Here, `wasItPrecise` will be `False` because the entire calculation will have
## been done in a base-2 floating point calculation, which causes noticeable
## precision loss in this case.
##
## ## Performance Notes
##
## On typical modern CPUs, performance is similar between [Dec], [F64], and [F32]
## for addition and subtraction. For example, [F32] and [F64] do addition using
## a single CPU floating-point addition instruction, which typically takes a
## few clock cycles to complete. In contrast, [Dec] does addition using a few
## CPU integer arithmetic instructions, each of which typically takes only one
## clock cycle to complete. Exact numbers will vary by CPU, but they should be
## similar overall.
##
## [Dec] is significantly slower for multiplication and division. It not only
## needs to do more arithmetic instructions than [F32] and [F64] do, but also
## those instructions typically take more clock cycles to complete.
##
## With [Num.sqrt] and trigonometry functions like [Num.cos], there is
## an even bigger performance difference. [F32] and [F64] can do these in a
## single instruction, whereas [Dec] needs entire custom procedures - which use
## loops and conditionals. If you need to do performance-critical trigonometry
## or square roots, either [F32] or [F64] is probably a better choice than the
## usual default choice of [Dec], despite the precision problems they bring.
Frac a : Num [ @Fraction a ]
## A fixed-size integer - that is, a number with no fractional component.
##
## Integers come in two flavors: signed and unsigned. Signed integers can be
@ -102,21 +181,20 @@ Num range : [ @Num range ]
## * 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 numeric calculation is running too slowly, you can try experimenting with other number sizes. This rarely makes a meaningful difference, but some processors can operate on different number sizes at different speeds.
Int size : Num [ @Int size ]
Int size : Num [ @Integer size ]
## 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 ]
Nat : Int [ @Nat ]
I8 : Int [ @Signed8 ]
U8 : Int [ @Unsigned8 ]
I16 : Int [ @Signed16 ]
U16 : Int [ @Unsigned16 ]
I32 : Int [ @Signed32 ]
U32 : Int [ @Unsigned32 ]
I64 : Int [ @Signed64 ]
U64 : Int [ @Unsigned64 ]
I128 : Int [ @Signed128 ]
U128 : Int [ @Unsigned128 ]
Nat : Int [ @Natural ]
## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values.
##
@ -455,7 +533,10 @@ mul : Num range, Num range -> Num range
## Convert
## Convert a number to a string, formatted as the traditional base 10 (decimal).
## Convert a number to a [Str].
##
## This is the same as calling `Num.format {}` - so for more details on
## exact formatting, see [Num.format].
##
## >>> Num.toStr 42
##
@ -468,6 +549,68 @@ mul : Num range, Num range -> Num range
## For other bases see #toHexStr, #toOctalStr, and #toBinaryStr.
toStr : Num * -> Str
## Convert a number into a [Str], formatted with the given options.
##
## Default options:
## * `base: Decimal`
## * `notation: Standard`
## * `decimalMark: HideForIntegers "."`
## * `decimalDigits: { min: 0, max: All }`
## * `minIntDigits: 1`
## * `wholeSep: { mark: ",", places: 3 }`
##
## ## Options
##
##
## ### decimalMark
##
## * `AlwaysShow` always shows the decimal mark, no matter what.
## * `HideForIntegers` hides the decimal mark if all the numbers after the decimal mark are 0.
##
## The [Str] included in either of these represents the mark itself.
##
## ### `decimalDigits
##
## With 0 decimal digits, the decimal mark will still be rendered if
## `decimalMark` is set to `AlwaysShow`.
##
## If `max` is less than `min`, then first the number will be truncated to `max`
## digits, and then zeroes will be added afterwards until it reaches `min` digits.
##
## >>> Num.format 1.23 { decPlaces: 0, decPointVis: AlwaysShow }
##
## ### minIntDigits
##
## If the integer portion of number is fewer than this many digits, zeroes will
## be added in front of it until there are at least `minWholeDigits` digits.
##
## If this is set to zero, then numbers less than 1 will begin with `"."`
## rather than `"0."`.
##
## ### wholeSep
##
## Examples:
##
## In some countries (e.g. USA and UK), a comma is used to separate thousands:
## >>> Num.format 1_000_000 { base: Decimal, wholeSep: { mark: ",", places: 3 } }
##
## Sometimes when rendering bits, it's nice to group them into groups of 4:
## >>> Num.format 1_000_000 { base: Binary, wholeSep: { mark: " ", places: 4 } }
##
## It's also common to render hexadecimal in groups of 2:
## >>> Num.format 1_000_000 { base: Hexadecimal, wholeSep: { mark: " ", places: 2 } }
format :
Num *,
{
base ? [ Decimal, Hexadecimal, Octal, Binary ],
notation ? [ Standard, Scientific ],
decimalMark ? [ AlwaysShow Str, HideForIntegers ],
decimalDigits ? { min : U16, max : [ All, Trunc U16, Round U16, Floor U16, Ceil U16 ] },
minWholeDigits ? U16,
wholeSep ? { mark : Str, places : U64 }
}
-> Str
## Round off the given float to the nearest integer.
round : Float * -> Int *
ceil : Float * -> Int *

View File

@ -429,16 +429,22 @@ chompCodePoint : Str, U32 -> Result Str [ Expected [ ExactCodePoint U32 ]* Str ]
## If the string begins with digits which can represent a valid #U8, return
## that number along with the rest of the string after the digits.
parseU8 : Str -> Result { val : U8, rest : Str } [ Expected [ NumU8 ]* Str ]*
parseI8 : Str -> Result { val : I8, rest : Str } [ Expected [ NumI8 ]* Str ]*
parseU16 : Str -> Result { val : U16, rest : Str } [ Expected [ NumU16 ]* Str ]*
parseI16 : Str -> Result { val : I16, rest : Str } [ Expected [ NumI16 ]* Str ]*
parseU32 : Str -> Result { val : U32, rest : Str } [ Expected [ NumU32 ]* Str ]*
parseI32 : Str -> Result { val : I32, rest : Str } [ Expected [ NumI32 ]* Str ]*
parseU64 : Str -> Result { val : U64, rest : Str } [ Expected [ NumU64 ]* Str ]*
parseI64 : Str -> Result { val : I64, rest : Str } [ Expected [ NumI64 ]* Str ]*
parseU128 : Str -> Result { val : U128, rest : Str } [ Expected [ NumU128 ]* Str ]*
parseI128 : Str -> Result { val : I128, rest : Str } [ Expected [ NumI128 ]* Str ]*
parseU8 : Str, NumFormat -> Result { val : U8, rest : Str } [ Expected [ NumU8 ]* Str ]*
parseI8 : Str, NumFormat -> Result { val : I8, rest : Str } [ Expected [ NumI8 ]* Str ]*
parseU16 : Str, NumFormat -> Result { val : U16, rest : Str } [ Expected [ NumU16 ]* Str ]*
parseI16 : Str, NumFormat -> Result { val : I16, rest : Str } [ Expected [ NumI16 ]* Str ]*
parseU32 : Str, NumFormat -> Result { val : U32, rest : Str } [ Expected [ NumU32 ]* Str ]*
parseI32 : Str, NumFormat -> Result { val : I32, rest : Str } [ Expected [ NumI32 ]* Str ]*
parseU64 : Str, NumFormat -> Result { val : U64, rest : Str } [ Expected [ NumU64 ]* Str ]*
parseI64 : Str, NumFormat -> Result { val : I64, rest : Str } [ Expected [ NumI64 ]* Str ]*
parseU128 : Str, NumFormat -> Result { val : U128, rest : Str } [ Expected [ NumU128 ]* Str ]*
parseI128 : Str, NumFormat -> Result { val : I128, rest : Str } [ Expected [ NumI128 ]* Str ]*
parseF64 : Str -> Result { val : U128, rest : Str } [ Expected [ NumF64 ]* Str ]*
parseF32 : Str -> Result { val : I128, rest : Str } [ Expected [ NumF32 ]* Str ]*
parseF64 : Str, NumFormat -> Result { val : U128, rest : Str } [ Expected [ NumF64 ]* Str ]*
parseF32 : Str, NumFormat -> Result { val : I128, rest : Str } [ Expected [ NumF32 ]* Str ]*
## TODO make this similar to the Num.format argument
## except more flexible - e.g. the policy for whole number separators
## might be to allow them, to require them, or to allow them only every N digits
## (e.g. 3 for thousands, 4 for bits, 2 for hex)
NumFormat : { }

View File

@ -1,26 +1,12 @@
use std::fs::File;
use std::io::prelude::Read;
use std::vec::Vec;
const BC_PATH: &str = env!(
"BUILTINS_BC",
"Env var BUILTINS_BC not found. Is there a problem with the build script?"
);
pub const OBJ_PATH: &str = env!(
"BUILTINS_O",
"Env var BUILTINS_O not found. Is there a problem with the build script?"
);
pub fn get_bytes() -> Vec<u8> {
// In the build script for the builtins module, we compile the builtins bitcode and set
// BUILTINS_BC to the path to the compiled output.
let mut builtins_bitcode = File::open(BC_PATH).expect("Unable to find builtins bitcode source");
let mut buffer = Vec::new();
builtins_bitcode
.read_to_end(&mut buffer)
.expect("Unable to read builtins bitcode");
buffer
pub fn as_bytes() -> &'static [u8] {
// In the build script for the builtins module,
// we compile the builtins into LLVM bitcode
include_bytes!("../bitcode/builtins.bc")
}
pub const NUM_ASIN: &str = "roc_builtins.num.asin";
@ -76,9 +62,11 @@ pub const LIST_WALK_BACKWARDS: &str = "roc_builtins.list.walk_backwards";
pub const LIST_CONTAINS: &str = "roc_builtins.list.contains";
pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append";
pub const LIST_DROP: &str = "roc_builtins.list.drop";
pub const LIST_SINGLE: &str = "roc_builtins.list.single";
pub const LIST_JOIN: &str = "roc_builtins.list.join";
pub const LIST_RANGE: &str = "roc_builtins.list.range";
pub const LIST_REVERSE: &str = "roc_builtins.list.reverse";
pub const LIST_SORT_WITH: &str = "roc_builtins.list.sort_with";
pub const LIST_CONCAT: &str = "roc_builtins.list.concat";
pub const LIST_SET: &str = "roc_builtins.list.set";

View File

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

View File

@ -92,14 +92,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
);
let ext = Box::new(SolvedType::Flex(TOP_LEVEL_CLOSURE_VAR));
// in the future, we will enable the line below
// let closure_var = Box::new(SolvedType::TagUnion(
// vec![(TagName::Closure($symbol), vec![])],
// ext,
// ));
let closure_var = ext;
let typ = SolvedType::Func($arguments, closure_var, $result);
let typ = SolvedType::Func(
$arguments,
Box::new(SolvedType::TagUnion(
vec![(TagName::Closure($symbol), vec![])],
ext,
)),
$result,
);
// TODO instead of using Region::zero for all of these,
// instead use the Region where they were defined in their
@ -848,6 +849,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))),
);
// drop : List elem, Nat -> List elem
add_top_level_function_type!(
Symbol::LIST_DROP,
vec![list_type(flex(TVAR1)), nat_type()],
Box::new(list_type(flex(TVAR1))),
);
// prepend : List elem, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_PREPEND,

View File

@ -84,6 +84,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP => list_map,
LIST_MAP2 => list_map2,
LIST_MAP3 => list_map3,
LIST_DROP => list_drop,
LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if,
LIST_KEEP_OKS => list_keep_oks,
@ -1881,6 +1882,28 @@ fn list_set(symbol: Symbol, var_store: &mut VarStore) -> Def {
list_ret_var,
)
}
/// List.drop : List elem, Nat -> List elem
fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListDrop,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(index_var, Var(Symbol::ARG_2)),
],
ret_var: list_var,
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1), (index_var, Symbol::ARG_2)],
var_store,
body,
list_var,
)
}
/// List.append : List elem, elem -> List elem
fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def {

View File

@ -135,9 +135,12 @@ pub enum Expr {
},
/// field accessor as a function, e.g. (.foo) expr
Accessor {
/// accessors are desugared to closures; they need to have a name
/// so the closure can have a correct lambda set
name: Symbol,
function_var: Variable,
record_var: Variable,
closure_var: Variable,
closure_ext_var: Variable,
ext_var: Variable,
field_var: Variable,
field: Lowercase,
@ -158,6 +161,14 @@ pub enum Expr {
arguments: Vec<(Variable, Located<Expr>)>,
},
ZeroArgumentTag {
closure_name: Symbol,
variant_var: Variable,
ext_var: Variable,
name: TagName,
arguments: Vec<(Variable, Located<Expr>)>,
},
// Test
Expect(Box<Located<Expr>>, Box<Located<Expr>>),
@ -392,6 +403,17 @@ pub fn canonicalize_expr<'a>(
name,
arguments: args,
},
ZeroArgumentTag {
variant_var,
ext_var,
name,
..
} => Tag {
variant_var,
ext_var,
name,
arguments: args,
},
_ => {
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
Call(
@ -613,10 +635,11 @@ pub fn canonicalize_expr<'a>(
}
ast::Expr::AccessorFunction(field) => (
Accessor {
name: env.gen_unique_symbol(),
function_var: var_store.fresh(),
record_var: var_store.fresh(),
ext_var: var_store.fresh(),
closure_var: var_store.fresh(),
closure_ext_var: var_store.fresh(),
field_var: var_store.fresh(),
field: (*field).into(),
},
@ -626,11 +649,14 @@ pub fn canonicalize_expr<'a>(
let variant_var = var_store.fresh();
let ext_var = var_store.fresh();
let symbol = env.gen_unique_symbol();
(
Tag {
ZeroArgumentTag {
name: TagName::Global((*tag).into()),
arguments: vec![],
variant_var,
closure_name: symbol,
ext_var,
},
Output::default(),
@ -641,13 +667,15 @@ pub fn canonicalize_expr<'a>(
let ext_var = var_store.fresh();
let tag_ident = env.ident_ids.get_or_insert(&(*tag).into());
let symbol = Symbol::new(env.home, tag_ident);
let lambda_set_symbol = env.gen_unique_symbol();
(
Tag {
ZeroArgumentTag {
name: TagName::Private(symbol),
arguments: vec![],
variant_var,
ext_var,
closure_name: lambda_set_symbol,
},
Output::default(),
)
@ -1427,6 +1455,23 @@ pub fn inline_calls(var_store: &mut VarStore, scope: &mut Scope, expr: Expr) ->
);
}
ZeroArgumentTag {
closure_name,
variant_var,
ext_var,
name,
arguments,
} => {
todo!(
"Inlining for ZeroArgumentTag with closure_name {:?}, variant_var {:?}, ext_var {:?}, name {:?}, arguments {:?}",
closure_name,
variant_var,
ext_var,
name,
arguments
);
}
Call(boxed_tuple, args, called_via) => {
let (fn_var, loc_expr, closure_var, expr_var) = *boxed_tuple;

View File

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

View File

@ -36,6 +36,7 @@ pub struct ModuleOutput {
pub problems: Vec<Problem>,
pub ident_ids: IdentIds,
pub references: MutSet<Symbol>,
pub scope: Scope,
}
// TODO trim these down
@ -309,6 +310,7 @@ where
}
Ok(ModuleOutput {
scope,
aliases,
rigid_variables,
declarations,
@ -509,7 +511,7 @@ fn fix_values_captured_in_closure_expr(
fix_values_captured_in_closure_expr(&mut loc_expr.value, no_capture_symbols);
}
Tag { arguments, .. } => {
Tag { arguments, .. } | ZeroArgumentTag { arguments, .. } => {
for (_, loc_arg) in arguments.iter_mut() {
fix_values_captured_in_closure_expr(&mut loc_arg.value, no_capture_symbols);
}

View File

@ -1,4 +1,4 @@
use roc_collections::all::{ImMap, MutSet};
use roc_collections::all::{MutSet, SendMap};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
use roc_problem::can::RuntimeError;
@ -10,14 +10,14 @@ use roc_types::types::{Alias, Type};
pub struct Scope {
/// All the identifiers in scope, mapped to were they were defined and
/// the Symbol they resolve to.
idents: ImMap<Ident, (Symbol, Region)>,
idents: SendMap<Ident, (Symbol, Region)>,
/// A cache of all the symbols in scope. This makes lookups much
/// faster when checking for unused defs and unused arguments.
symbols: ImMap<Symbol, Region>,
symbols: SendMap<Symbol, Region>,
/// The type aliases currently in scope
aliases: ImMap<Symbol, Alias>,
aliases: SendMap<Symbol, Alias>,
/// The current module being processed. This will be used to turn
/// unqualified idents into Symbols.
@ -28,7 +28,7 @@ impl Scope {
pub fn new(home: ModuleId, var_store: &mut VarStore) -> Scope {
use roc_types::solved_types::{BuiltinAlias, FreeVars};
let solved_aliases = roc_types::builtin_aliases::aliases();
let mut aliases = ImMap::default();
let mut aliases = SendMap::default();
for (symbol, builtin_alias) in solved_aliases {
let BuiltinAlias { region, vars, typ } = builtin_alias;
@ -58,7 +58,7 @@ impl Scope {
Scope {
home,
idents: Symbol::default_in_scope(),
symbols: ImMap::default(),
symbols: SendMap::default(),
aliases,
}
}

View File

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

View File

@ -10,7 +10,7 @@ use roc_can::expr::Expr::{self, *};
use roc_can::expr::{Field, WhenBranch};
use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, SendMap};
use roc_module::ident::Lowercase;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
@ -712,10 +712,11 @@ pub fn constrain_expr(
)
}
Accessor {
name: closure_name,
function_var,
field,
record_var,
closure_var,
closure_ext_var: closure_var,
ext_var,
field_var,
} => {
@ -739,9 +740,15 @@ pub fn constrain_expr(
region,
);
let ext = Type::Variable(*closure_var);
let lambda_set = Type::TagUnion(
vec![(TagName::Closure(*closure_name), vec![])],
Box::new(ext),
);
let function_type = Type::Function(
vec![record_type],
Box::new(Type::Variable(*closure_var)),
Box::new(lambda_set),
Box::new(field_type),
);
@ -860,6 +867,58 @@ pub fn constrain_expr(
exists(vars, And(arg_cons))
}
ZeroArgumentTag {
variant_var,
ext_var,
name,
arguments,
closure_name,
} => {
let mut vars = Vec::with_capacity(arguments.len());
let mut types = Vec::with_capacity(arguments.len());
let mut arg_cons = Vec::with_capacity(arguments.len());
for (var, loc_expr) in arguments {
let arg_con = constrain_expr(
env,
loc_expr.region,
&loc_expr.value,
Expected::NoExpectation(Type::Variable(*var)),
);
arg_cons.push(arg_con);
vars.push(*var);
types.push(Type::Variable(*var));
}
let union_con = Eq(
Type::FunctionOrTagUnion(
name.clone(),
*closure_name,
Box::new(Type::Variable(*ext_var)),
),
expected.clone(),
Category::TagApply {
tag_name: name.clone(),
args_count: arguments.len(),
},
region,
);
let ast_con = Eq(
Type::Variable(*variant_var),
expected,
Category::Storage(std::file!(), std::line!()),
region,
);
vars.push(*variant_var);
vars.push(*ext_var);
arg_cons.push(union_con);
arg_cons.push(ast_con);
exists(vars, And(arg_cons))
}
RunLowLevel { args, ret_var, op } => {
// This is a modified version of what we do for function calls.
@ -1156,7 +1215,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
name,
..
},
Type::Function(arg_types, _closure_type, ret_type),
Type::Function(arg_types, signature_closure_type, ret_type),
) => {
// NOTE if we ever have problems with the closure, the ignored `_closure_type`
// is probably a good place to start the investigation!
@ -1261,6 +1320,19 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint {
defs_constraint,
ret_constraint,
})),
Eq(
Type::Variable(closure_var),
Expected::FromAnnotation(
def.loc_pattern.clone(),
arity,
AnnotationSource::TypedBody {
region: annotation.region,
},
*signature_closure_type.clone(),
),
Category::ClosureSize,
region,
),
Store(signature.clone(), *fn_var, std::file!(), std::line!()),
Store(signature, expr_var, std::file!(), std::line!()),
Store(ret_type, ret_var, std::file!(), std::line!()),

View File

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

View File

@ -1,7 +1,7 @@
use crate::expr::constrain_decls;
use roc_builtins::std::StdLib;
use roc_can::constraint::{Constraint, LetConstraint};
use roc_can::module::ModuleOutput;
use roc_can::def::Declaration;
use roc_collections::all::{MutMap, MutSet, SendMap};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
@ -22,16 +22,18 @@ pub struct ConstrainedModule {
pub constraint: Constraint,
}
pub fn constrain_module(module: &ModuleOutput, home: ModuleId) -> Constraint {
pub fn constrain_module(
aliases: &MutMap<Symbol, Alias>,
declarations: &[Declaration],
home: ModuleId,
) -> Constraint {
let mut send_aliases = SendMap::default();
for (symbol, alias) in module.aliases.iter() {
for (symbol, alias) in aliases.iter() {
send_aliases.insert(*symbol, alias.clone());
}
let decls = &module.declarations;
constrain_decls(home, decls)
constrain_decls(home, declarations)
}
#[derive(Debug, Clone)]

View File

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

View File

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

View File

@ -68,24 +68,31 @@ const ARGUMENT_SYMBOLS: [Symbol; 8] = [
pub fn build_transform_caller<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
function_layout: &Layout<'a>,
function: FunctionValue<'ctx>,
closure_data_layout: Layout<'a>,
argument_layouts: &[Layout<'a>],
) -> FunctionValue<'ctx> {
let symbol = Symbol::ZIG_FUNCTION_CALLER;
let fn_name = layout_ids
.get(symbol, &function_layout)
.to_symbol_string(symbol, &env.interns);
let fn_name: &str = &format!(
"{}_zig_function_caller",
function.get_name().to_string_lossy()
);
match env.module.get_function(fn_name.as_str()) {
match env.module.get_function(fn_name) {
Some(function_value) => function_value,
None => build_transform_caller_help(env, function_layout, argument_layouts, &fn_name),
None => build_transform_caller_help(
env,
function,
closure_data_layout,
argument_layouts,
&fn_name,
),
}
}
fn build_transform_caller_help<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
function_layout: &Layout<'a>,
roc_function: FunctionValue<'ctx>,
closure_data_layout: Layout<'a>,
argument_layouts: &[Layout<'a>],
fn_name: &str,
) -> FunctionValue<'ctx> {
@ -124,8 +131,6 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
set_name(*argument, name.ident_string(&env.interns));
}
let closure_type = basic_type_from_layout(env, function_layout).ptr_type(AddressSpace::Generic);
let mut arguments_cast =
bumpalo::collections::Vec::with_capacity_in(arguments.len(), env.arena);
@ -142,41 +147,61 @@ fn build_transform_caller_help<'a, 'ctx, 'env>(
arguments_cast.push(argument);
}
let closure_cast = env
.builder
.build_bitcast(closure_ptr, closure_type, "load_opaque")
.into_pointer_value();
match closure_data_layout {
Layout::FunctionPointer(_, _) => {
// do nothing
}
Layout::Closure(_, lambda_set, _) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
// do nothing
} else {
let closure_type =
basic_type_from_layout(env, &lambda_set.runtime_representation())
.ptr_type(AddressSpace::Generic);
let fpointer = env.builder.build_load(closure_cast, "load_opaque");
let closure_cast = env
.builder
.build_bitcast(closure_ptr, closure_type, "load_opaque")
.into_pointer_value();
let call = match function_layout {
Layout::FunctionPointer(_, _) => env.builder.build_call(
fpointer.into_pointer_value(),
arguments_cast.as_slice(),
"tmp",
),
Layout::Closure(_, _, _) | Layout::Struct(_) => {
let pair = fpointer.into_struct_value();
let closure_data = env.builder.build_load(closure_cast, "load_opaque");
let fpointer = env
arguments_cast.push(closure_data);
}
}
Layout::Struct([Layout::Closure(_, lambda_set, _)]) => {
// a case required for Set.walk; may be able to remove when we can define builtins in
// terms of other builtins in the right way (using their function symbols instead of
// hacking with lowlevel ops).
let closure_type = basic_type_from_layout(
env,
&Layout::Struct(&[lambda_set.runtime_representation()]),
)
.ptr_type(AddressSpace::Generic);
let closure_cast = env
.builder
.build_extract_value(pair, 0, "get_fpointer")
.unwrap();
.build_bitcast(closure_ptr, closure_type, "load_opaque")
.into_pointer_value();
let closure_data = env
.builder
.build_extract_value(pair, 1, "get_closure_data")
.unwrap();
let closure_data = env.builder.build_load(closure_cast, "load_opaque");
arguments_cast.push(closure_data);
env.builder.build_call(
fpointer.into_pointer_value(),
arguments_cast.as_slice(),
"tmp",
)
}
_ => unreachable!("layout is not callable {:?}", function_layout),
Layout::Struct([]) => {
// do nothing, should try to remove this case later
}
Layout::Struct(_) => {
// do nothing, should try to remove this case later
}
other => unreachable!("layout is not valid for a closure: {:?}", other),
}
let call = {
env.builder
.build_call(roc_function, arguments_cast.as_slice(), "tmp")
};
call.set_call_convention(FAST_CALL_CONV);
let result = call
@ -406,18 +431,19 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
pub fn build_compare_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function: FunctionValue<'ctx>,
closure_data_layout: Layout<'a>,
layout: &Layout<'a>,
) -> FunctionValue<'ctx> {
let block = env.builder.get_insert_block().expect("to be in a function");
let di_location = env.builder.get_current_debug_location().unwrap();
let symbol = Symbol::GENERIC_COMPARE_REF;
let fn_name = layout_ids
.get(symbol, &layout)
.to_symbol_string(symbol, &env.interns);
let fn_name: &str = &format!(
"{}_compare_wrapper",
roc_function.get_name().to_string_lossy()
);
let function_value = match env.module.get_function(fn_name.as_str()) {
let function_value = match env.module.get_function(fn_name) {
Some(function_value) => function_value,
None => {
let arg_type = env.context.i8_type().ptr_type(AddressSpace::Generic);
@ -443,28 +469,17 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
debug_info_init!(env, function_value);
let mut it = function_value.get_param_iter();
let function_ptr = it.next().unwrap().into_pointer_value();
let closure_ptr = it.next().unwrap().into_pointer_value();
let value_ptr1 = it.next().unwrap().into_pointer_value();
let value_ptr2 = it.next().unwrap().into_pointer_value();
set_name(
function_ptr.into(),
Symbol::ARG_1.ident_string(&env.interns),
);
set_name(closure_ptr.into(), Symbol::ARG_1.ident_string(&env.interns));
set_name(value_ptr1.into(), Symbol::ARG_2.ident_string(&env.interns));
set_name(value_ptr2.into(), Symbol::ARG_3.ident_string(&env.interns));
let value_type = basic_type_from_layout(env, layout);
let function_type = env
.context
.i8_type()
.fn_type(&[value_type, value_type], false)
.ptr_type(AddressSpace::Generic);
let value_ptr_type = value_type.ptr_type(AddressSpace::Generic);
let function_cast =
env.builder
.build_bitcast(function_ptr, function_type, "load_opaque");
let value_cast1 = env
.builder
.build_bitcast(value_ptr1, value_ptr_type, "load_opaque")
@ -478,10 +493,36 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
let value1 = env.builder.build_load(value_cast1, "load_opaque");
let value2 = env.builder.build_load(value_cast2, "load_opaque");
let default = [value1, value2];
let arguments_cast = match closure_data_layout {
Layout::FunctionPointer(_, _) => &default,
Layout::Closure(_, lambda_set, _) => {
if let Layout::Struct(&[]) = lambda_set.runtime_representation() {
&default
} else {
let closure_type =
basic_type_from_layout(env, &lambda_set.runtime_representation())
.ptr_type(AddressSpace::Generic);
let closure_cast = env
.builder
.build_bitcast(closure_ptr, closure_type, "load_opaque")
.into_pointer_value();
let closure_data = env.builder.build_load(closure_cast, "load_opaque");
env.arena.alloc([value1, value2, closure_data]) as &[_]
}
}
Layout::Struct([]) => &default,
other => unreachable!("layout is not valid for a closure: {:?}", other),
};
let call = env.builder.build_call(
function_cast.into_pointer_value(),
&[value1, value2],
"call_user_defined_function",
roc_function,
arguments_cast,
"call_user_defined_compare_function",
);
let result = call.try_as_basic_value().left().unwrap();

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,11 @@
use crate::debug_info_init;
use crate::llvm::bitcode::{
build_dec_wrapper, build_eq_wrapper, build_inc_wrapper, build_transform_caller,
call_bitcode_fn, call_void_bitcode_fn,
build_dec_wrapper, build_eq_wrapper, build_inc_wrapper, call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
complex_bitcast, load_symbol, load_symbol_and_layout, set_name, Env, Scope,
complex_bitcast, load_symbol, load_symbol_and_layout, set_name, Env, RocFunctionCall, Scope,
};
use crate::llvm::build_list::{layout_width, pass_as_opaque};
use crate::llvm::convert::{as_const_zero, basic_type_from_layout};
use crate::llvm::refcounting::Mode;
use inkwell::attributes::{Attribute, AttributeLoc};
@ -71,10 +71,7 @@ pub fn dict_len<'a, 'ctx, 'env>(
}
}
pub fn dict_empty<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
_scope: &Scope<'a, 'ctx>,
) -> BasicValueEnum<'ctx> {
pub fn dict_empty<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
// get the RocDict type defined by zig
let roc_dict_type = env.module.get_struct_type("dict.RocDict").unwrap();
@ -639,10 +636,9 @@ fn dict_intersect_or_difference<'a, 'ctx, 'env>(
pub fn dict_walk<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
dict: BasicValueEnum<'ctx>,
stepper: BasicValueEnum<'ctx>,
accum: BasicValueEnum<'ctx>,
stepper_layout: &Layout<'a>,
key_layout: &Layout<'a>,
value_layout: &Layout<'a>,
accum_layout: &Layout<'a>,
@ -655,34 +651,10 @@ pub fn dict_walk<'a, 'ctx, 'env>(
let dict_ptr = builder.build_alloca(zig_dict_type, "dict_ptr");
env.builder.build_store(dict_ptr, dict);
let stepper_ptr = builder.build_alloca(stepper.get_type(), "stepper_ptr");
env.builder.build_store(stepper_ptr, stepper);
let stepper_caller = build_transform_caller(
env,
layout_ids,
stepper_layout,
&[*key_layout, *value_layout, *accum_layout],
)
.as_global_value()
.as_pointer_value();
let accum_bt = basic_type_from_layout(env, accum_layout);
let accum_ptr = builder.build_alloca(accum_bt, "accum_ptr");
env.builder.build_store(accum_ptr, accum);
let key_width = env
.ptr_int()
.const_int(key_layout.stack_size(env.ptr_bytes) as u64, false);
let value_width = env
.ptr_int()
.const_int(value_layout.stack_size(env.ptr_bytes) as u64, false);
let accum_width = env
.ptr_int()
.const_int(accum_layout.stack_size(env.ptr_bytes) as u64, false);
let alignment = Alignment::from_key_value_layout(key_layout, value_layout, env.ptr_bytes);
let alignment_iv = env.context.i8_type().const_int(alignment as u64, false);
@ -695,13 +667,15 @@ pub fn dict_walk<'a, 'ctx, 'env>(
env,
&[
dict_ptr.into(),
env.builder.build_bitcast(stepper_ptr, u8_ptr, "to_opaque"),
stepper_caller.into(),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.builder.build_bitcast(accum_ptr, u8_ptr, "to_opaque"),
alignment_iv.into(),
key_width.into(),
value_width.into(),
accum_width.into(),
layout_width(env, key_layout),
layout_width(env, value_layout),
layout_width(env, accum_layout),
inc_key_fn.as_global_value().as_pointer_value().into(),
inc_value_fn.as_global_value().as_pointer_value().into(),
env.builder.build_bitcast(output_ptr, u8_ptr, "to_opaque"),

View File

@ -56,11 +56,6 @@ fn build_hash_layout<'a, 'ctx, 'env>(
val.into_struct_value(),
),
Layout::PhantomEmptyStruct => {
// just does nothing and returns the seed
seed
}
Layout::Union(union_layout) => {
build_hash_tag(env, layout_ids, layout, union_layout, seed, val)
}
@ -91,10 +86,6 @@ fn build_hash_layout<'a, 'ctx, 'env>(
}
},
Layout::Pointer(_) => {
unreachable!("unused")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
unreachable!("the type system will guarantee these are never hashed")
}
@ -157,7 +148,7 @@ fn hash_builtin<'a, 'ctx, 'env>(
Builtin::Set(_) => {
todo!("Implement Hash for Set")
}
Builtin::List(_, element_layout) => build_hash_list(
Builtin::List(element_layout) => build_hash_list(
env,
layout_ids,
layout,

View File

@ -1,32 +1,20 @@
#![allow(clippy::too_many_arguments)]
use crate::llvm::bitcode::{
build_compare_wrapper, build_dec_wrapper, build_eq_wrapper, build_inc_n_wrapper,
build_inc_wrapper, build_transform_caller, call_bitcode_fn, call_void_bitcode_fn,
build_dec_wrapper, build_eq_wrapper, build_inc_n_wrapper, build_inc_wrapper,
build_transform_caller, call_bitcode_fn, call_void_bitcode_fn,
};
use crate::llvm::build::{
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, InPlace,
allocate_with_refcount_help, cast_basic_basic, complex_bitcast, Env, RocFunctionCall,
};
use crate::llvm::convert::{basic_type_from_layout, get_ptr_type};
use crate::llvm::refcounting::{
increment_refcount_layout, refcount_is_one_comparison, PointerToRefcount,
};
use crate::llvm::refcounting::increment_refcount_layout;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::types::{BasicTypeEnum, PointerType};
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::{AddressSpace, IntPredicate};
use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode};
fn alignment_intvalue<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let alignment = element_layout.alignment_bytes(env.ptr_bytes);
let alignment_iv = env.ptr_int().const_int(alignment as u64, false);
alignment_iv.into()
}
use roc_mono::layout::{Builtin, InPlace, Layout, LayoutIds};
fn list_returned_from_zig<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -72,7 +60,7 @@ fn pass_list_as_i128<'a, 'ctx, 'env>(
complex_bitcast(env.builder, list, env.context.i128_type().into(), "to_i128")
}
fn layout_width<'a, 'ctx, 'env>(
pub fn layout_width<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
@ -81,7 +69,7 @@ fn layout_width<'a, 'ctx, 'env>(
.into()
}
fn pass_as_opaque<'a, 'ctx, 'env>(
pub fn pass_as_opaque<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
ptr: PointerValue<'ctx>,
) -> BasicValueEnum<'ctx> {
@ -102,7 +90,7 @@ pub fn list_single<'a, 'ctx, 'env>(
call_bitcode_fn_returns_list(
env,
&[
alignment_intvalue(env, element_layout),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
],
@ -124,7 +112,7 @@ pub fn list_repeat<'a, 'ctx, 'env>(
env,
&[
list_len.into(),
alignment_intvalue(env, element_layout),
env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
@ -185,7 +173,7 @@ pub fn list_prepend<'a, 'ctx, 'env>(
if elem_layout.safe_to_memcpy() {
// Copy the bytes from the original array into the new
// one we just malloc'd.
// one we just allocated
//
// TODO how do we decide when to do the small memcpy vs the normal one?
builder
@ -208,17 +196,17 @@ pub fn list_join<'a, 'ctx, 'env>(
) -> BasicValueEnum<'ctx> {
match outer_list_layout {
Layout::Builtin(Builtin::EmptyList)
| Layout::Builtin(Builtin::List(_, Layout::Builtin(Builtin::EmptyList))) => {
| Layout::Builtin(Builtin::List(Layout::Builtin(Builtin::EmptyList))) => {
// If the input list is empty, or if it is a list of empty lists
// then simply return an empty list
empty_list(env)
}
Layout::Builtin(Builtin::List(_, Layout::Builtin(Builtin::List(_, element_layout)))) => {
Layout::Builtin(Builtin::List(Layout::Builtin(Builtin::List(element_layout)))) => {
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, outer_list),
alignment_intvalue(env, element_layout),
env.alignment_intvalue(element_layout),
layout_width(env, element_layout),
],
&bitcode::LIST_JOIN,
@ -243,13 +231,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
// this pointer will never actually be dereferenced
Layout::Builtin(Builtin::Int64),
),
Layout::Builtin(Builtin::List(memory_mode, elem_layout)) => (
match memory_mode {
MemoryMode::Unique => InPlace::InPlace,
MemoryMode::Refcounted => InPlace::Clone,
},
*elem_layout,
),
Layout::Builtin(Builtin::List(elem_layout)) => (InPlace::Clone, *elem_layout),
_ => unreachable!("Invalid layout {:?} in List.reverse", list_layout),
};
@ -258,7 +240,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
env,
&[
pass_list_as_i128(env, list),
alignment_intvalue(env, &element_layout),
env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout),
],
&bitcode::LIST_REVERSE,
@ -276,7 +258,7 @@ pub fn list_get_unsafe<'a, 'ctx, 'env>(
let builder = env.builder;
match list_layout {
Layout::Builtin(Builtin::List(_, elem_layout)) => {
Layout::Builtin(Builtin::List(elem_layout)) => {
let elem_type = basic_type_from_layout(env, elem_layout);
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
// Load the pointer to the array data
@ -314,7 +296,7 @@ pub fn list_append<'a, 'ctx, 'env>(
env,
&[
pass_list_as_i128(env, original_wrapper.into()),
alignment_intvalue(env, &element_layout),
env.alignment_intvalue(&element_layout),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
],
@ -322,98 +304,62 @@ pub fn list_append<'a, 'ctx, 'env>(
)
}
/// List.set : List elem, Int, elem -> List elem
pub fn list_set<'a, 'ctx, 'env>(
parent: FunctionValue<'ctx>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
/// List.drop : List elem, Nat -> List elem
pub fn list_drop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
input_inplace: InPlace,
output_inplace: InPlace,
layout_ids: &mut LayoutIds<'a>,
original_wrapper: StructValue<'ctx>,
count: IntValue<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
debug_assert_eq!(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 = 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 ctx = env.context;
let elem_type = basic_type_from_layout(env, elem_layout);
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
let (new_wrapper, array_data_ptr) = match input_inplace {
InPlace::InPlace => (
original_wrapper,
load_list_ptr(builder, original_wrapper, ptr_type),
),
InPlace::Clone => {
let list_ptr = load_list_ptr(builder, original_wrapper, ptr_type);
let refcount_ptr = PointerToRefcount::from_ptr_to_data(env, list_ptr);
let refcount = refcount_ptr.get_refcount(env);
let rc_is_one = refcount_is_one_comparison(env, refcount);
let source_block = env.builder.get_insert_block().unwrap();
let clone_block = ctx.append_basic_block(parent, "clone");
let done_block = ctx.append_basic_block(parent, "done");
env.builder
.build_conditional_branch(rc_is_one, done_block, clone_block);
env.builder.position_at_end(clone_block);
let cloned =
clone_nonempty_list(env, output_inplace, list_len, list_ptr, elem_layout).0;
env.builder.build_unconditional_branch(done_block);
env.builder.position_at_end(done_block);
let list_type = original_wrapper.get_type();
let merged = env.builder.build_phi(list_type, "writable_list");
merged.add_incoming(&[(&original_wrapper, source_block), (&cloned, clone_block)]);
let result = merged.as_basic_value().into_struct_value();
(result, load_list_ptr(builder, result, ptr_type))
}
};
// 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(
let dec_element_fn = build_dec_wrapper(env, layout_ids, &element_layout);
call_bitcode_fn_returns_list(
env,
parent,
comparison,
build_then,
build_else,
ret_type.into(),
&[
pass_list_as_i128(env, original_wrapper.into()),
env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout),
count.into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
&bitcode::LIST_DROP,
)
}
/// List.set : List elem, Nat, elem -> List elem
pub fn list_set<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
list: BasicValueEnum<'ctx>,
index: IntValue<'ctx>,
element: BasicValueEnum<'ctx>,
element_layout: &'a Layout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
let (length, bytes) = load_list(
env.builder,
list.into_struct_value(),
env.context.i8_type().ptr_type(AddressSpace::Generic),
);
let new_bytes = call_bitcode_fn(
env,
&[
bytes.into(),
length.into(),
env.alignment_intvalue(&element_layout),
index.into(),
pass_element_as_opaque(env, element),
layout_width(env, element_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
],
&bitcode::LIST_SET,
);
store_list(env, new_bytes.into_pointer_value(), length)
}
fn bounds_check_comparison<'ctx>(
builder: &Builder<'ctx>,
elem_index: IntValue<'ctx>,
@ -444,50 +390,12 @@ pub enum ListWalk {
WalkBackwardsUntil,
}
pub fn list_walk_help<'a, 'ctx, 'env>(
pub fn list_walk_generic<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
scope: &crate::llvm::build::Scope<'a, 'ctx>,
parent: FunctionValue<'ctx>,
args: &[roc_module::symbol::Symbol],
variant: ListWalk,
) -> BasicValueEnum<'ctx> {
use crate::llvm::build::load_symbol_and_layout;
debug_assert_eq!(args.len(), 3);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let (func, func_layout) = load_symbol_and_layout(scope, &args[1]);
let (default, default_layout) = load_symbol_and_layout(scope, &args[2]);
match list_layout {
Layout::Builtin(Builtin::EmptyList) => default,
Layout::Builtin(Builtin::List(_, element_layout)) => list_walk_generic(
env,
layout_ids,
parent,
list,
element_layout,
func,
func_layout,
default,
default_layout,
variant,
),
_ => unreachable!("invalid list layout"),
}
}
fn list_walk_generic<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
_parent: FunctionValue<'ctx>,
roc_function_call: RocFunctionCall<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
func: BasicValueEnum<'ctx>,
func_layout: &Layout<'a>,
default: BasicValueEnum<'ctx>,
default_layout: &Layout<'a>,
variant: ListWalk,
@ -501,21 +409,9 @@ fn list_walk_generic<'a, 'ctx, 'env>(
ListWalk::WalkBackwardsUntil => todo!(),
};
let transform_ptr = builder.build_alloca(func.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, func);
let default_ptr = builder.build_alloca(default.get_type(), "default_ptr");
env.builder.build_store(default_ptr, default);
let stepper_caller = build_transform_caller(
env,
layout_ids,
func_layout,
&[*element_layout, *default_layout],
)
.as_global_value()
.as_pointer_value();
let result_ptr = env.builder.build_alloca(default.get_type(), "result");
match variant {
@ -524,10 +420,12 @@ fn list_walk_generic<'a, 'ctx, 'env>(
env,
&[
pass_list_as_i128(env, list),
pass_as_opaque(env, transform_ptr),
stepper_caller.into(),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
pass_as_opaque(env, default_ptr),
alignment_intvalue(env, &element_layout),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
layout_width(env, default_layout),
pass_as_opaque(env, result_ptr),
@ -541,10 +439,12 @@ fn list_walk_generic<'a, 'ctx, 'env>(
env,
&[
pass_list_as_i128(env, list),
pass_as_opaque(env, transform_ptr),
stepper_caller.into(),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
pass_as_opaque(env, default_ptr),
alignment_intvalue(env, &element_layout),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
layout_width(env, default_layout),
dec_element_fn.as_global_value().as_pointer_value().into(),
@ -651,21 +551,10 @@ pub fn list_contains<'a, 'ctx, 'env>(
pub fn list_keep_if<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform);
let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &[*element_layout])
.as_global_value()
.as_pointer_value();
let inc_element_fn = build_inc_wrapper(env, layout_ids, element_layout);
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
@ -673,9 +562,11 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
env,
&[
pass_list_as_i128(env, list),
pass_as_opaque(env, transform_ptr),
stepper_caller.into(),
alignment_intvalue(env, &element_layout),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
inc_element_fn.as_global_value().as_pointer_value().into(),
dec_element_fn.as_global_value().as_pointer_value().into(),
@ -688,20 +579,35 @@ pub fn list_keep_if<'a, 'ctx, 'env>(
pub fn list_keep_oks<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
roc_function_call: RocFunctionCall<'ctx>,
function_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>,
after_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
list_keep_result(
// Layout of the `Result after *`
let result_layout = match function_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
call_bitcode_fn(
env,
layout_ids,
transform,
transform_layout,
list,
before_layout,
after_layout,
&[
pass_list_as_i128(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&before_layout),
layout_width(env, before_layout),
layout_width(env, result_layout),
layout_width(env, after_layout),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_KEEP_OKS,
)
}
@ -710,20 +616,35 @@ pub fn list_keep_oks<'a, 'ctx, 'env>(
pub fn list_keep_errs<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
roc_function_call: RocFunctionCall<'ctx>,
function_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>,
after_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
list_keep_result(
// Layout of the `Result after *`
let result_layout = match function_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
call_bitcode_fn(
env,
layout_ids,
transform,
transform_layout,
list,
before_layout,
after_layout,
&[
pass_list_as_i128(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&before_layout),
layout_width(env, before_layout),
layout_width(env, result_layout),
layout_width(env, after_layout),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_KEEP_ERRS,
)
}
@ -731,8 +652,10 @@ pub fn list_keep_errs<'a, 'ctx, 'env>(
pub fn list_keep_result<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
transform: FunctionValue<'ctx>,
transform_layout: Layout<'a>,
closure_data: BasicValueEnum<'ctx>,
closure_data_layout: Layout<'a>,
list: BasicValueEnum<'ctx>,
before_layout: &Layout<'a>,
after_layout: &Layout<'a>,
@ -746,39 +669,27 @@ pub fn list_keep_result<'a, 'ctx, 'env>(
_ => unreachable!("not a callable layout"),
};
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform);
let closure_data_ptr = builder.build_alloca(closure_data.get_type(), "closure_data_ptr");
env.builder.build_store(closure_data_ptr, closure_data);
let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &[*before_layout])
build_transform_caller(env, transform, closure_data_layout, &[*before_layout])
.as_global_value()
.as_pointer_value();
let before_width = env
.ptr_int()
.const_int(before_layout.stack_size(env.ptr_bytes) as u64, false);
let after_width = env
.ptr_int()
.const_int(after_layout.stack_size(env.ptr_bytes) as u64, false);
let result_width = env
.ptr_int()
.const_int(result_layout.stack_size(env.ptr_bytes) as u64, false);
let inc_closure = build_inc_wrapper(env, layout_ids, transform_layout);
let inc_closure = build_inc_wrapper(env, layout_ids, &transform_layout);
let dec_result_fn = build_dec_wrapper(env, layout_ids, result_layout);
call_bitcode_fn(
env,
&[
pass_list_as_i128(env, list),
pass_as_opaque(env, transform_ptr),
pass_as_opaque(env, closure_data_ptr),
stepper_caller.into(),
alignment_intvalue(env, &before_layout),
before_width.into(),
result_width.into(),
after_width.into(),
env.alignment_intvalue(&before_layout),
layout_width(env, before_layout),
layout_width(env, after_layout),
layout_width(env, result_layout),
inc_closure.as_global_value().as_pointer_value().into(),
dec_result_fn.as_global_value().as_pointer_value().into(),
],
@ -789,151 +700,84 @@ pub fn list_keep_result<'a, 'ctx, 'env>(
/// List.sortWith : List a, (a, a -> Ordering) -> List a
pub fn list_sort_with<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
roc_function_call: RocFunctionCall<'ctx>,
compare_wrapper: PointerValue<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let transform_ptr = transform.into_pointer_value();
let compare_wrapper = build_compare_wrapper(env, layout_ids, element_layout)
.as_global_value()
.as_pointer_value();
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
pass_as_opaque(env, transform_ptr),
compare_wrapper.into(),
alignment_intvalue(env, &element_layout),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
],
bitcode::LIST_SORT_WITH,
)
}
/// List.map : List before, (before -> after) -> List after
pub fn list_map<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
list_map_generic(
env,
layout_ids,
transform,
transform_layout,
list,
element_layout,
bitcode::LIST_MAP,
&[*element_layout],
)
}
/// List.mapWithIndex : List before, (Nat, before -> after) -> List after
pub fn list_map_with_index<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
return_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
list_map_generic(
env,
layout_ids,
transform,
transform_layout,
list,
element_layout,
bitcode::LIST_MAP_WITH_INDEX,
&[Layout::Builtin(Builtin::Usize), *element_layout],
)
}
fn list_map_generic<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
op: &str,
argument_layouts: &[Layout<'a>],
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let return_layout = match transform_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform);
let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, argument_layouts)
.as_global_value()
.as_pointer_value();
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
pass_as_opaque(env, transform_ptr),
stepper_caller.into(),
alignment_intvalue(env, &element_layout),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
layout_width(env, return_layout),
],
op,
bitcode::LIST_MAP_WITH_INDEX,
)
}
/// List.map : List before, (before -> after) -> List after
pub fn list_map<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
roc_function_call: RocFunctionCall<'ctx>,
list: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>,
return_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, list),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(&element_layout),
layout_width(env, element_layout),
layout_width(env, return_layout),
],
bitcode::LIST_MAP,
)
}
pub fn list_map2<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
element1_layout: &Layout<'a>,
element2_layout: &Layout<'a>,
return_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let return_layout = match transform_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform);
let argument_layouts = [*element1_layout, *element2_layout];
let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
.as_global_value()
.as_pointer_value();
let a_width = env
.ptr_int()
.const_int(element1_layout.stack_size(env.ptr_bytes) as u64, false);
let b_width = env
.ptr_int()
.const_int(element2_layout.stack_size(env.ptr_bytes) as u64, false);
let c_width = env
.ptr_int()
.const_int(return_layout.stack_size(env.ptr_bytes) as u64, false);
let dec_a = build_dec_wrapper(env, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
@ -942,12 +786,14 @@ pub fn list_map2<'a, 'ctx, 'env>(
&[
pass_list_as_i128(env, list1),
pass_list_as_i128(env, list2),
pass_as_opaque(env, transform_ptr),
stepper_caller.into(),
alignment_intvalue(env, &transform_layout),
a_width.into(),
b_width.into(),
c_width.into(),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(return_layout),
layout_width(env, element1_layout),
layout_width(env, element2_layout),
layout_width(env, return_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
],
@ -958,48 +804,15 @@ pub fn list_map2<'a, 'ctx, 'env>(
pub fn list_map3<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
transform: BasicValueEnum<'ctx>,
transform_layout: &Layout<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
list3: BasicValueEnum<'ctx>,
element1_layout: &Layout<'a>,
element2_layout: &Layout<'a>,
element3_layout: &Layout<'a>,
result_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let return_layout = match transform_layout {
Layout::FunctionPointer(_, ret) => ret,
Layout::Closure(_, _, ret) => ret,
_ => unreachable!("not a callable layout"),
};
let transform_ptr = builder.build_alloca(transform.get_type(), "transform_ptr");
env.builder.build_store(transform_ptr, transform);
let argument_layouts = [*element1_layout, *element2_layout, *element3_layout];
let stepper_caller =
build_transform_caller(env, layout_ids, transform_layout, &argument_layouts)
.as_global_value()
.as_pointer_value();
let a_width = env
.ptr_int()
.const_int(element1_layout.stack_size(env.ptr_bytes) as u64, false);
let b_width = env
.ptr_int()
.const_int(element2_layout.stack_size(env.ptr_bytes) as u64, false);
let c_width = env
.ptr_int()
.const_int(element3_layout.stack_size(env.ptr_bytes) as u64, false);
let d_width = env
.ptr_int()
.const_int(return_layout.stack_size(env.ptr_bytes) as u64, false);
let dec_a = build_dec_wrapper(env, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
let dec_c = build_dec_wrapper(env, layout_ids, element3_layout);
@ -1010,13 +823,15 @@ pub fn list_map3<'a, 'ctx, 'env>(
pass_list_as_i128(env, list1),
pass_list_as_i128(env, list2),
pass_list_as_i128(env, list3),
pass_as_opaque(env, transform_ptr),
stepper_caller.into(),
alignment_intvalue(env, transform_layout),
a_width.into(),
b_width.into(),
c_width.into(),
d_width.into(),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(result_layout),
layout_width(env, element1_layout),
layout_width(env, element2_layout),
layout_width(env, element3_layout),
layout_width(env, result_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
dec_c.as_global_value().as_pointer_value().into(),
@ -1040,12 +855,12 @@ pub fn list_concat<'a, 'ctx, 'env>(
// then simply return an empty list
empty_list(env)
}
Layout::Builtin(Builtin::List(_, elem_layout)) => call_bitcode_fn_returns_list(
Layout::Builtin(Builtin::List(elem_layout)) => call_bitcode_fn_returns_list(
env,
&[
pass_list_as_i128(env, first_list),
pass_list_as_i128(env, second_list),
alignment_intvalue(env, elem_layout),
env.alignment_intvalue(elem_layout),
layout_width(env, elem_layout),
],
&bitcode::LIST_CONCAT,
@ -1292,71 +1107,6 @@ pub fn load_list_ptr<'ctx>(
cast_basic_basic(builder, generic_ptr.into(), ptr_type.into()).into_pointer_value()
}
fn clone_nonempty_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,
list_len: IntValue<'ctx>,
elems_ptr: PointerValue<'ctx>,
elem_layout: &Layout<'_>,
) -> (StructValue<'ctx>, PointerValue<'ctx>) {
let builder = env.builder;
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, "clone_mul_len_by_elem_bytes");
// Allocate space for the new array that we'll copy into.
let clone_ptr = allocate_list(env, inplace, elem_layout, list_len);
// 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)
.unwrap();
} else {
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
}
let struct_type = super::convert::zig_list_type(env);
let mut struct_val;
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
pass_as_opaque(env, clone_ptr),
Builtin::WRAPPER_PTR,
"insert_ptr_clone_nonempty_list",
)
.unwrap();
// Store the length
struct_val = builder
.build_insert_value(struct_val, list_len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
let answer = builder
.build_bitcast(
struct_val.into_struct_value(),
super::convert::zig_list_type(env),
"cast_collection",
)
.into_struct_value();
(answer, clone_ptr)
}
pub fn allocate_list<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
inplace: InPlace,

View File

@ -1,12 +1,12 @@
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build::{complex_bitcast, Env, InPlace, Scope};
use crate::llvm::build::{complex_bitcast, Env, Scope};
use crate::llvm::build_list::{allocate_list, call_bitcode_fn_returns_list, store_list};
use inkwell::builder::Builder;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace;
use roc_builtins::bitcode;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
use roc_mono::layout::{Builtin, InPlace, Layout};
use super::build::load_symbol;

View File

@ -99,7 +99,7 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::Float16 => float_cmp(FloatPredicate::OEQ, "eq_f16"),
Builtin::Str => str_equal(env, lhs_val, rhs_val),
Builtin::List(_, elem) => build_list_eq(
Builtin::List(elem) => build_list_eq(
env,
layout_ids,
&Layout::Builtin(*builtin),
@ -159,11 +159,6 @@ fn build_eq<'a, 'ctx, 'env>(
rhs_val,
),
Layout::PhantomEmptyStruct => {
// always equal to itself
env.context.bool_type().const_int(1, false).into()
}
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be compared directly")
@ -197,10 +192,6 @@ fn build_eq<'a, 'ctx, 'env>(
}
},
Layout::Pointer(_) => {
unreachable!("unused")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
unreachable!("the type system will guarantee these are never compared")
}
@ -258,7 +249,7 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
result.into()
}
Builtin::List(_, elem) => {
Builtin::List(elem) => {
let is_equal = build_list_eq(
env,
layout_ids,
@ -338,19 +329,10 @@ fn build_neq<'a, 'ctx, 'env>(
result.into()
}
Layout::PhantomEmptyStruct => {
// always equal to itself
env.context.bool_type().const_int(1, false).into()
}
Layout::RecursivePointer => {
unreachable!("recursion pointers should never be compared directly")
}
Layout::Pointer(_) => {
unreachable!("unused")
}
Layout::FunctionPointer(_, _) | Layout::Closure(_, _, _) => {
unreachable!("the type system will guarantee these are never compared")
}

View File

@ -108,21 +108,10 @@ pub fn basic_type_from_layout<'a, 'ctx, 'env>(
FunctionPointer(args, ret_layout) => {
basic_type_from_function_layout(env, args, None, ret_layout)
}
Closure(args, closure_layout, ret_layout) => {
let closure_data_layout = closure_layout.as_block_of_memory_layout();
let closure_data = basic_type_from_layout(env, &closure_data_layout);
let function_pointer =
basic_type_from_function_layout(env, args, Some(closure_data), ret_layout);
env.context
.struct_type(&[function_pointer, closure_data], false)
.as_basic_type_enum()
Closure(_args, closure_layout, _ret_layout) => {
let closure_data_layout = closure_layout.runtime_representation();
basic_type_from_layout(env, &closure_data_layout)
}
Pointer(layout) => basic_type_from_layout(env, &layout)
.ptr_type(AddressSpace::Generic)
.into(),
PhantomEmptyStruct => env.context.struct_type(&[], false).into(),
Struct(sorted_fields) => basic_type_from_record(env, sorted_fields),
Union(variant) => {
use UnionLayout::*;
@ -181,7 +170,7 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
Float16 => context.f16_type().as_basic_type_enum(),
Dict(_, _) | EmptyDict => zig_dict_type(env).into(),
Set(_) | EmptySet => zig_dict_type(env).into(),
List(_, _) | EmptyList => zig_list_type(env).into(),
List(_) | EmptyList => zig_list_type(env).into(),
Str | EmptyStr => zig_str_type(env).into(),
}
}

View File

@ -0,0 +1,141 @@
use crate::llvm::build::{add_func, set_name, C_CALL_CONV};
use crate::llvm::convert::ptr_int;
use inkwell::builder::Builder;
use inkwell::context::Context;
use inkwell::module::{Linkage, Module};
use inkwell::AddressSpace;
/// Define functions for roc_alloc, roc_realloc, and roc_dealloc
/// which use libc implementations (malloc, realloc, and free)
pub fn add_default_roc_externs<'ctx>(
ctx: &'ctx Context,
module: &Module<'ctx>,
builder: &Builder<'ctx>,
ptr_bytes: u32,
) {
let usize_type = ptr_int(ctx, ptr_bytes);
let i8_ptr_type = ctx.i8_type().ptr_type(AddressSpace::Generic);
// roc_alloc
{
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_alloc").unwrap();
let mut params = fn_val.get_param_iter();
let size_arg = params.next().unwrap();
let _alignment_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
// Add a basic block for the entry point
let entry = ctx.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
// Call libc malloc()
let retval = builder
.build_array_malloc(ctx.i8_type(), size_arg.into_int_value(), "call_malloc")
.unwrap();
builder.build_return(Some(&retval));
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);
}
}
// roc_realloc
{
let libc_realloc_val = {
let fn_val = add_func(
module,
"realloc",
i8_ptr_type.fn_type(
&[
// ptr: *void
i8_ptr_type.into(),
// size: usize
usize_type.into(),
],
false,
),
Linkage::External,
C_CALL_CONV,
);
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
let size_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
set_name(ptr_arg, "ptr");
set_name(size_arg, "size");
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);
}
fn_val
};
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_realloc").unwrap();
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
let new_size_arg = params.next().unwrap();
let _old_size_arg = params.next().unwrap();
let _alignment_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
// Add a basic block for the entry point
let entry = ctx.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
// Call libc realloc()
let call = builder.build_call(
libc_realloc_val,
&[ptr_arg, new_size_arg],
"call_libc_realloc",
);
call.set_call_convention(C_CALL_CONV);
let retval = call.try_as_basic_value().left().unwrap();
builder.build_return(Some(&retval));
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);
}
}
// roc_dealloc
{
// The type of this function (but not the implementation) should have
// already been defined by the builtins, which rely on it.
let fn_val = module.get_function("roc_dealloc").unwrap();
let mut params = fn_val.get_param_iter();
let ptr_arg = params.next().unwrap();
let _alignment_arg = params.next().unwrap();
debug_assert!(params.next().is_none());
// Add a basic block for the entry point
let entry = ctx.append_basic_block(fn_val, "entry");
builder.position_at_end(entry);
// Call libc free()
builder.build_free(ptr_arg.into_pointer_value());
builder.build_return(None);
if cfg!(debug_assertions) {
crate::llvm::build::verify_fn(fn_val);
}
}
}

View File

@ -6,4 +6,5 @@ pub mod build_list;
pub mod build_str;
pub mod compare;
pub mod convert;
pub mod externs;
pub mod refcounting;

View File

@ -1,6 +1,6 @@
use crate::debug_info_init;
use crate::llvm::build::{
cast_basic_basic, cast_block_of_memory_to_tag, set_name, Env, FAST_CALL_CONV,
add_func, cast_basic_basic, cast_block_of_memory_to_tag, set_name, Env, FAST_CALL_CONV,
LLVM_SADD_WITH_OVERFLOW_I64,
};
use crate::llvm::build_list::{incrementing_elem_loop, list_len, load_list};
@ -15,7 +15,7 @@ use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, Str
use inkwell::{AddressSpace, IntPredicate};
use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout, LayoutIds, MemoryMode, UnionLayout};
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
pub const REFCOUNT_MAX: usize = 0_usize;
@ -40,8 +40,8 @@ impl<'ctx> PointerToRefcount<'ctx> {
/// # Safety
///
/// the invariant is that the given pointer really points to the refcount,
/// not the data, and only is the start of the malloced buffer if the alignment
/// works out that way.
/// not the data, and only is the start of the allocated buffer if the
/// alignment works out that way.
pub unsafe fn from_ptr<'a, 'env>(env: &Env<'a, 'ctx, 'env>, ptr: PointerValue<'ctx>) -> Self {
// must make sure it's a pointer to usize
let refcount_type = ptr_int(env.context, env.ptr_bytes);
@ -165,17 +165,18 @@ impl<'ctx> PointerToRefcount<'ctx> {
false,
);
let function_value =
env.module
.add_function(fn_name, fn_type, Some(Linkage::Private));
// Because it's an internal-only function, it should use the fast calling convention.
function_value.set_call_conventions(FAST_CALL_CONV);
let function_value = add_func(
env.module,
fn_name,
fn_type,
Linkage::Private,
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
);
let subprogram = env.new_subprogram(fn_name);
function_value.set_subprogram(subprogram);
Self::_build_decrement_function_body(env, function_value, alignment);
Self::build_decrement_function_body(env, function_value, alignment);
function_value
}
@ -194,10 +195,10 @@ impl<'ctx> PointerToRefcount<'ctx> {
call.set_call_convention(FAST_CALL_CONV);
}
fn _build_decrement_function_body<'a, 'env>(
fn build_decrement_function_body<'a, 'env>(
env: &Env<'a, 'ctx, 'env>,
parent: FunctionValue<'ctx>,
extra_bytes: u32,
alignment: u32,
) {
let builder = env.builder;
let ctx = env.context;
@ -269,15 +270,21 @@ impl<'ctx> PointerToRefcount<'ctx> {
{
builder.position_at_end(then_block);
if !env.leak {
match extra_bytes {
let ptr = builder.build_pointer_cast(
refcount_ptr.value,
ctx.i8_type().ptr_type(AddressSpace::Generic),
"cast_to_i8_ptr",
);
match alignment {
n if env.ptr_bytes == n => {
// the refcount ptr is also the ptr to the malloced region
builder.build_free(refcount_ptr.value);
// the refcount ptr is also the ptr to the allocated region
env.call_dealloc(ptr, alignment);
}
n if 2 * env.ptr_bytes == n => {
// we need to step back another ptr_bytes to get the malloced ptr
let malloced = Self::from_ptr_to_data(env, refcount_ptr.value);
builder.build_free(malloced.value);
// we need to step back another ptr_bytes to get the allocated ptr
let allocated = Self::from_ptr_to_data(env, ptr);
env.call_dealloc(allocated.value, alignment);
}
n => unreachable!("invalid extra_bytes {:?}", n),
}
@ -369,12 +376,6 @@ fn modify_refcount_struct_help<'a, 'ctx, 'env>(
layouts: &[Layout<'a>],
fn_val: FunctionValue<'ctx>,
) {
debug_assert_eq!(
when_recursive,
&WhenRecursive::Unreachable,
"TODO pipe when_recursive through the dict key/value inc/dec"
);
let builder = env.builder;
let ctx = env.context;
@ -468,21 +469,17 @@ fn modify_refcount_builtin<'a, 'ctx, 'env>(
use Builtin::*;
match builtin {
List(memory_mode, element_layout) => {
if let MemoryMode::Refcounted = memory_mode {
let function = modify_refcount_list(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
);
List(element_layout) => {
let function = modify_refcount_list(
env,
layout_ids,
mode,
when_recursive,
layout,
element_layout,
);
Some(function)
} else {
None
}
Some(function)
}
Set(element_layout) => {
let key_layout = &Layout::Struct(&[]);
@ -699,26 +696,16 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
}
}
Closure(argument_layouts, closure_layout, return_layout) => {
if closure_layout.contains_refcounted() {
// Temporary hack to make this work for now. With defunctionalization, none of this
// will matter
let p2 = closure_layout.as_block_of_memory_layout();
let mut argument_layouts =
Vec::from_iter_in(argument_layouts.iter().copied(), env.arena);
argument_layouts.push(p2);
let argument_layouts = argument_layouts.into_bump_slice();
let p1 = Layout::FunctionPointer(argument_layouts, return_layout);
let actual_layout = Layout::Struct(env.arena.alloc([p1, p2]));
Closure(_, lambda_set, _) => {
if lambda_set.contains_refcounted() {
let function = modify_refcount_layout_build_function(
env,
parent,
layout_ids,
mode,
when_recursive,
&actual_layout,
&lambda_set.runtime_representation(),
)?;
Some(function)
@ -733,8 +720,6 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
Some(function)
}
PhantomEmptyStruct => None,
Layout::RecursivePointer => match when_recursive {
WhenRecursive::Unreachable => {
unreachable!("recursion pointers should never be hashed directly")
@ -755,7 +740,7 @@ fn modify_refcount_layout_build_function<'a, 'ctx, 'env>(
}
},
FunctionPointer(_, _) | Pointer(_) => None,
FunctionPointer(_, _) => None,
}
}
@ -1165,12 +1150,13 @@ pub fn build_header_help<'a, 'ctx, 'env>(
VoidType(t) => t.fn_type(arguments, false),
};
let fn_val = env
.module
.add_function(fn_name, fn_type, Some(Linkage::Private));
// Because it's an internal-only function, it should use the fast calling convention.
fn_val.set_call_conventions(FAST_CALL_CONV);
let fn_val = add_func(
env.module,
fn_name,
fn_type,
Linkage::Private,
FAST_CALL_CONV, // Because it's an internal-only function, it should use the fast calling convention.
);
let subprogram = env.new_subprogram(&fn_name);
fn_val.set_subprogram(subprogram);
@ -1697,7 +1683,7 @@ pub fn refcount_offset<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>, layout: &Layou
let value_bytes = layout.stack_size(env.ptr_bytes) as u64;
match layout {
Layout::Builtin(Builtin::List(_, _)) => env.ptr_bytes as u64,
Layout::Builtin(Builtin::List(_)) => env.ptr_bytes as u64,
Layout::Builtin(Builtin::Str) => env.ptr_bytes as u64,
Layout::RecursivePointer | Layout::Union(_) => env.ptr_bytes as u64,
_ => (env.ptr_bytes as u64).max(value_bytes),

View File

@ -23,7 +23,7 @@ bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1"
target-lexicon = "0.10"
libloading = "0.6"
object = { version = "0.22", features = ["write"] }
object = { version = "0.24", features = ["write"] }
[dev-dependencies]
roc_can = { path = "../can" }

View File

@ -315,6 +315,13 @@ impl Assembler<AArch64GeneralReg, AArch64FloatReg> for AArch64Assembler {
fn jmp_imm32(_buf: &mut Vec<'_, u8>, _offset: i32) -> usize {
unimplemented!("jump instructions not yet implemented for AArch64");
}
#[inline(always)]
fn tail_call(buf: &mut Vec<'_, u8>) -> u64 {
Self::jmp_imm32(buf, 0);
buf.len() as u64 - 4 // TODO is 4 the correct offset in ARM?
}
#[inline(always)]
fn jne_reg64_imm64_imm32(
_buf: &mut Vec<'_, u8>,

View File

@ -99,6 +99,8 @@ pub trait Assembler<GeneralReg: RegTrait, FloatReg: RegTrait> {
// It returns the base offset to calculate the jump from (generally the instruction after the jump).
fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize;
fn tail_call(buf: &mut Vec<'_, u8>) -> u64;
// Jumps by an offset of offset bytes if reg is not equal to imm.
// It should always generate the same number of bytes to enable replacement if offset changes.
// It returns the base offset to calculate the jump from (generally the instruction after the jump).
@ -341,6 +343,14 @@ impl<
Ok(())
}
/// Used for generating wrappers for malloc/realloc/free
fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String> {
let mut out = bumpalo::vec![in self.env.arena];
let offset = ASM::tail_call(&mut out);
Ok((out.into_bump_slice(), offset))
}
fn build_fn_call(
&mut self,
dst: &Symbol,

View File

@ -834,6 +834,13 @@ impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
jmp_imm32(buf, offset);
buf.len()
}
#[inline(always)]
fn tail_call(buf: &mut Vec<'_, u8>) -> u64 {
Self::jmp_imm32(buf, 0);
buf.len() as u64 - 4
}
#[inline(always)]
fn jne_reg64_imm64_imm32(
buf: &mut Vec<'_, u8>,

View File

@ -1,4 +1,4 @@
#![warn(clippy::all, clippy::dbg_macro)]
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)]
@ -22,6 +22,7 @@ pub struct Env<'a> {
pub interns: Interns,
pub exposed_to_host: MutSet<Symbol>,
pub lazy_literals: bool,
pub generate_allocators: bool,
}
// These relocations likely will need a length.
@ -67,6 +68,9 @@ where
// The backend should track these args so it can use them as needed.
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)]) -> Result<(), String>;
/// Used for generating wrappers for malloc/realloc/free
fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>;
/// build_proc creates a procedure and outputs it to the wrapped object writer.
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset();
@ -104,9 +108,9 @@ where
fail: _,
} => {
// for now, treat invoke as a normal call
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
self.build_stmt(&stmt)
self.build_expr(symbol, &Expr::Call(call.clone()), layout)?;
self.free_symbols(stmt);
self.build_stmt(pass)
}
Stmt::Switch {
cond_symbol,
@ -214,7 +218,7 @@ where
}
}
CallType::LowLevel { op: lowlevel } => {
CallType::LowLevel { op: lowlevel, .. } => {
self.build_run_low_level(sym, lowlevel, arguments, layout)
}
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
@ -426,7 +430,6 @@ where
self.set_last_seen(*sym, stmt);
match expr {
Expr::Literal(_) => {}
Expr::FunctionPointer(sym, _) => self.set_last_seen(*sym, stmt),
Expr::Call(call) => self.scan_ast_call(call, stmt),
@ -479,15 +482,15 @@ where
Stmt::Invoke {
symbol,
layout,
layout: _,
call,
pass,
fail: _,
} => {
// for now, treat invoke as a normal call
let stmt = Stmt::Let(*symbol, Expr::Call(call.clone()), *layout, pass);
self.scan_ast(&stmt);
self.set_last_seen(*symbol, stmt);
self.scan_ast_call(call, stmt);
self.scan_ast(pass);
}
Stmt::Switch {
@ -546,10 +549,8 @@ where
match call_type {
CallType::ByName { .. } => {}
CallType::ByPointer { name: sym, .. } => {
self.set_last_seen(*sym, stmt);
}
CallType::LowLevel { .. } => {}
CallType::HigherOrderLowLevel { .. } => {}
CallType::Foreign { .. } => {}
}
}

View File

@ -13,7 +13,9 @@ use roc_mono::ir::Proc;
use roc_mono::layout::Layout;
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
const VERSION: &str = env!("CARGO_PKG_VERSION");
// This is used by some code below which is currently commented out.
// See that code for more details!
// const VERSION: &str = env!("CARGO_PKG_VERSION");
/// build_module is the high level builder/delegator.
/// It takes the request to build a module and output the object file for the module.
@ -41,6 +43,28 @@ pub fn build_module<'a>(
Object::new(BinaryFormat::Elf, Architecture::X86_64, Endianness::Little),
)
}
Triple {
architecture: TargetArch::X86_64,
binary_format: TargetBF::Macho,
..
} => {
let backend: Backend64Bit<
x86_64::X86_64GeneralReg,
x86_64::X86_64FloatReg,
x86_64::X86_64Assembler,
x86_64::X86_64SystemV,
> = Backend::new(env, target)?;
build_object(
env,
procedures,
backend,
Object::new(
BinaryFormat::MachO,
Architecture::X86_64,
Endianness::Little,
),
)
}
Triple {
architecture: TargetArch::Aarch64(_),
binary_format: TargetBF::Elf,
@ -65,6 +89,60 @@ pub fn build_module<'a>(
}
}
fn generate_wrapper<'a, B: Backend<'a>>(
backend: &mut B,
output: &mut Object,
wrapper_name: String,
wraps: String,
) -> Result<(), String> {
let text_section = output.section_id(StandardSection::Text);
let proc_symbol = Symbol {
name: wrapper_name.as_bytes().to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Dynamic,
weak: false,
section: SymbolSection::Section(text_section),
flags: SymbolFlags::None,
};
let proc_id = output.add_symbol(proc_symbol);
let (proc_data, offset) = backend.build_wrapped_jmp()?;
let proc_offset = output.add_symbol_data(proc_id, text_section, proc_data, 16);
let name = wraps.as_str().as_bytes();
// If the symbol is an undefined zig builtin, we need to add it here.
let symbol = Symbol {
name: name.to_vec(),
value: 0,
size: 0,
kind: SymbolKind::Text,
scope: SymbolScope::Dynamic,
weak: true,
section: SymbolSection::Undefined,
flags: SymbolFlags::None,
};
output.add_symbol(symbol);
if let Some(sym_id) = output.symbol_id(name) {
let reloc = write::Relocation {
offset: offset + proc_offset,
size: 32,
kind: RelocationKind::PltRelative,
encoding: RelocationEncoding::X86Branch,
symbol: sym_id,
addend: -4,
};
output
.add_relocation(text_section, reloc)
.map_err(|e| format!("{:?}", e))?;
Ok(())
} else {
Err(format!("failed to find fn symbol for {:?}", wraps))
}
}
fn build_object<'a, B: Backend<'a>>(
env: &'a Env,
procedures: MutMap<(symbol::Symbol, Layout<'a>), Proc<'a>>,
@ -72,12 +150,37 @@ fn build_object<'a, B: Backend<'a>>(
mut output: Object,
) -> Result<Object, String> {
let data_section = output.section_id(StandardSection::Data);
let comment = output.add_section(vec![], b"comment".to_vec(), SectionKind::OtherString);
/*
// Commented out because we couldn't figure out how to get it to work on mac - see https://github.com/rtfeldman/roc/pull/1323
let comment = output.add_section(vec![], b".comment".to_vec(), SectionKind::OtherString);
output.append_section_data(
comment,
format!("\0roc dev backend version {} \0", VERSION).as_bytes(),
1,
);
*/
if env.generate_allocators {
generate_wrapper(
&mut backend,
&mut output,
"roc_alloc".into(),
"malloc".into(),
)?;
generate_wrapper(
&mut backend,
&mut output,
"roc_realloc".into(),
"realloc".into(),
)?;
generate_wrapper(
&mut backend,
&mut output,
"roc_dealloc".into(),
"free".into(),
)?;
}
// Setup layout_ids for procedure calls.
let mut layout_ids = roc_mono::layout::LayoutIds::default();
@ -89,7 +192,7 @@ fn build_object<'a, B: Backend<'a>>(
let section_id = output.add_section(
output.segment_name(StandardSegment::Text).to_vec(),
format!(".text.{}", fn_name).as_bytes().to_vec(),
format!(".text.{:x}", sym.as_u64()).as_bytes().to_vec(),
SectionKind::Text,
);
@ -182,7 +285,7 @@ fn build_object<'a, B: Backend<'a>>(
offset: offset + proc_offset,
size: 32,
kind: RelocationKind::PltRelative,
encoding: RelocationEncoding::Generic,
encoding: RelocationEncoding::X86Branch,
symbol: sym_id,
addend: -4,
}

View File

@ -10,8 +10,8 @@ extern crate libc;
#[macro_use]
mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod gen_num {
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_num {
#[test]
fn i64_values() {
assert_evals_to!("0", 0, i64);
@ -128,9 +128,7 @@ mod gen_num {
af = 31
ag = 32
# This can't be one line because it causes a stack overflow in the frontend :(
tmp = a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q
tmp + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag
a + b + c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r + s + t + u + v + w + x + y + z + aa + ab + ac + ad + ae + af + ag
"#
),
528,

View File

@ -61,12 +61,18 @@ pub fn helper<'a>(
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
procedures,
procedures: top_procedures,
interns,
exposed_to_host,
..
} = loaded;
let mut procedures = MutMap::default();
for ((symbol, top_level), proc) in top_procedures {
procedures.insert((symbol, arena.alloc(top_level).full()), proc);
}
/*
println!("=========== Procedures ==========");
println!("{:?}", procedures);
@ -83,11 +89,12 @@ pub fn helper<'a>(
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = exposed_to_host.keys().copied().next().unwrap();
let (_, main_fn_layout) = procedures
let main_fn_layout = procedures
.keys()
.find(|(s, _)| *s == main_fn_symbol)
.unwrap()
.clone();
.map(|t| t.1)
.unwrap();
let mut layout_ids = roc_mono::layout::LayoutIds::default();
let main_fn_name = layout_ids
.get(main_fn_symbol, &main_fn_layout)
@ -166,6 +173,7 @@ pub fn helper<'a>(
interns,
exposed_to_host: exposed_to_host.keys().copied().collect(),
lazy_literals,
generate_allocators: true, // Needed for testing, since we don't have a platform
};
let target = target_lexicon::Triple::host();

View File

@ -1,8 +1,10 @@
use crate::docs::DocEntry::DetatchedDoc;
use crate::docs::TypeAnnotation::{Apply, BoundVariable, Record, TagUnion};
use inlinable_string::InlinableString;
use roc_can::scope::Scope;
use roc_collections::all::MutMap;
use roc_module::ident::ModuleName;
use roc_module::symbol::IdentIds;
use roc_module::symbol::{IdentIds, Interns, ModuleId};
use roc_parse::ast;
use roc_parse::ast::CommentOrNewline;
use roc_parse::ast::{AssignedField, Def};
@ -10,18 +12,19 @@ use roc_region::all::Located;
// Documentation generation requirements
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Documentation {
pub name: String,
pub version: String,
pub docs: String,
pub modules: Vec<ModuleDocumentation>,
pub modules: Vec<(MutMap<ModuleId, ModuleDocumentation>, Interns)>,
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct ModuleDocumentation {
pub name: String,
pub entries: Vec<DocEntry>,
pub scope: Scope,
}
#[derive(Debug, Clone)]
@ -75,19 +78,21 @@ pub struct Tag {
}
pub fn generate_module_docs<'a>(
scope: Scope,
module_name: ModuleName,
exposed_ident_ids: &'a IdentIds,
ident_ids: &'a IdentIds,
parsed_defs: &'a [Located<ast::Def<'a>>],
) -> ModuleDocumentation {
let (entries, _) =
parsed_defs
.iter()
.fold((vec![], None), |(acc, maybe_comments_after), def| {
generate_entry_doc(exposed_ident_ids, acc, maybe_comments_after, &def.value)
generate_entry_doc(ident_ids, acc, maybe_comments_after, &def.value)
});
ModuleDocumentation {
name: module_name.as_str().to_string(),
scope,
entries,
}
}
@ -117,7 +122,7 @@ fn detatched_docs_from_comments_and_new_lines<'a>(
}
fn generate_entry_doc<'a>(
exposed_ident_ids: &'a IdentIds,
ident_ids: &'a IdentIds,
mut acc: Vec<DocEntry>,
before_comments_or_new_lines: Option<&'a [roc_parse::ast::CommentOrNewline<'a>]>,
def: &'a ast::Def<'a>,
@ -135,13 +140,13 @@ fn generate_entry_doc<'a>(
acc.push(DetatchedDoc(detatched_doc));
}
generate_entry_doc(exposed_ident_ids, acc, Some(comments_or_new_lines), sub_def)
generate_entry_doc(ident_ids, acc, Some(comments_or_new_lines), sub_def)
}
Def::SpaceAfter(sub_def, comments_or_new_lines) => {
let (new_acc, _) =
// If there are comments before, attach to this definition
generate_entry_doc(exposed_ident_ids, acc, before_comments_or_new_lines, sub_def);
generate_entry_doc(ident_ids, acc, before_comments_or_new_lines, sub_def);
// Comments after a definition are attached to the next definition
(new_acc, Some(comments_or_new_lines))
@ -150,7 +155,7 @@ fn generate_entry_doc<'a>(
Def::Annotation(loc_pattern, _loc_ann) => match loc_pattern.value {
Pattern::Identifier(identifier) => {
// Check if the definition is exposed
if exposed_ident_ids
if ident_ids
.get_id(&InlinableString::from(identifier))
.is_some()
{
@ -170,7 +175,7 @@ fn generate_entry_doc<'a>(
Def::AnnotatedBody { ann_pattern, .. } => match ann_pattern.value {
Pattern::Identifier(identifier) => {
// Check if the definition is exposed
if exposed_ident_ids
if ident_ids
.get_id(&InlinableString::from(identifier))
.is_some()
{

View File

@ -20,6 +20,7 @@ use roc_module::symbol::{
};
use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, PartialProc, PendingSpecialization, Proc, Procs,
TopLevelFunctionLayout,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
@ -51,7 +52,7 @@ const DEFAULT_APP_OUTPUT_PATH: &str = "app";
const ROC_FILE_EXTENSION: &str = "roc";
/// Roc-Config file name
const PKG_CONFIG_FILE_NAME: &str = "Pkg-Config";
const PKG_CONFIG_FILE_NAME: &str = "Package-Config";
/// The . in between module names like Foo.Bar.Baz
const MODULE_SEPARATOR: char = '.';
@ -704,7 +705,7 @@ pub struct MonomorphizedModule<'a> {
pub can_problems: MutMap<ModuleId, Vec<roc_problem::can::Problem>>,
pub type_problems: MutMap<ModuleId, Vec<solve::TypeError>>,
pub mono_problems: MutMap<ModuleId, Vec<roc_mono::ir::MonoProblem>>,
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
pub procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
pub exposed_to_host: MutMap<Symbol, Variable>,
pub header_sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
pub sources: MutMap<ModuleId, (PathBuf, Box<str>)>,
@ -775,7 +776,7 @@ enum Msg<'a> {
ident_ids: IdentIds,
layout_cache: LayoutCache<'a>,
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations<'a>>,
procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
problems: Vec<roc_mono::ir::MonoProblem>,
module_timing: ModuleTiming,
subs: Subs,
@ -817,7 +818,7 @@ struct State<'a> {
pub module_cache: ModuleCache<'a>,
pub dependencies: Dependencies<'a>,
pub procedures: MutMap<(Symbol, Layout<'a>), Proc<'a>>,
pub procedures: MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
pub exposed_to_host: MutMap<Symbol, Variable>,
/// This is the "final" list of IdentIds, after canonicalization and constraint gen
@ -847,7 +848,8 @@ struct State<'a> {
/// pending specializations in the same thread.
pub needs_specialization: MutSet<ModuleId>,
pub all_pending_specializations: MutMap<Symbol, MutMap<Layout<'a>, PendingSpecialization<'a>>>,
pub all_pending_specializations:
MutMap<Symbol, MutMap<TopLevelFunctionLayout<'a>, PendingSpecialization<'a>>>,
pub specializations_in_flight: u32,
@ -1892,7 +1894,7 @@ fn update<'a>(
let work = state.dependencies.notify(module_id, Phase::SolveTypes);
// if there is a platform, the Pkg-Config module provides host-exposed,
// if there is a platform, the Package-Config module provides host-exposed,
// otherwise the App module exposes host-exposed
let is_host_exposed = match state.platform_id {
None => module_id == state.root_id,
@ -2046,6 +2048,27 @@ fn update<'a>(
&& state.dependencies.solved_all()
&& state.goal_phase == Phase::MakeSpecializations
{
// display the mono IR of the module, for debug purposes
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
let procs_string = state
.procedures
.values()
.map(|proc| proc.to_pretty(200))
.collect::<Vec<_>>();
let result = procs_string.join("\n");
println!("{}", result);
}
if false {
let it = state.procedures.iter().map(|x| x.1);
if let Err(e) = roc_mono::alias_analysis::spec_program(it) {
println!("Error in alias analysis: {:?}", e)
}
}
Proc::insert_refcount_operations(arena, &mut state.procedures);
Proc::optimize_refcount_operations(
@ -2070,19 +2093,6 @@ fn update<'a>(
existing.extend(requested);
}
// display the mono IR of the module, for debug purposes
if roc_mono::ir::PRETTY_PRINT_IR_SYMBOLS {
let procs_string = state
.procedures
.values()
.map(|proc| proc.to_pretty(200))
.collect::<Vec<_>>();
let result = procs_string.join("\n");
println!("{}", result);
}
msg_tx
.send(Msg::FinishedAllSpecialization {
subs,
@ -2312,7 +2322,7 @@ fn load_pkg_config<'a>(
let chomped = &bytes[..delta];
let header_src = unsafe { std::str::from_utf8_unchecked(chomped) };
// make a Pkg-Config module that ultimately exposes `main` to the host
// make a Package-Config module that ultimately exposes `main` to the host
let pkg_config_module_msg = fabricate_pkg_config_module(
arena,
shorthand,
@ -2551,7 +2561,7 @@ fn parse_header<'a>(
}) => {
match package_or_path {
PackageOrPath::Path(StrLiteral::PlainLine(package)) => {
// check whether we can find a Pkg-Config.roc file
// check whether we can find a Package-Config.roc file
let mut pkg_config_roc = pkg_config_dir;
pkg_config_roc.push(package);
pkg_config_roc.push(PKG_CONFIG_FILE_NAME);
@ -3502,9 +3512,14 @@ fn fabricate_effects_module<'a>(
problems: can_env.problems,
ident_ids: can_env.ident_ids,
references: MutSet::default(),
scope,
};
let constraint = constrain_module(&module_output, module_id);
let constraint = constrain_module(
&module_output.aliases,
&module_output.declarations,
module_id,
);
let module = Module {
module_id,
@ -3521,6 +3536,7 @@ fn fabricate_effects_module<'a>(
let module_docs = ModuleDocumentation {
name: String::from(name),
entries: Vec::new(),
scope: module_output.scope,
};
let constrained_module = ConstrainedModule {
@ -3602,18 +3618,6 @@ where
..
} = parsed;
// Generate documentation information
// TODO: store timing information?
let module_docs = match module_name {
ModuleNameEnum::PkgConfig => None,
ModuleNameEnum::App(_) => None,
ModuleNameEnum::Interface(name) => Some(crate::docs::generate_module_docs(
name.as_str().into(),
&exposed_ident_ids,
&parsed_defs,
)),
};
let mut var_store = VarStore::default();
let canonicalized = canonicalize_module_defs(
&arena,
@ -3634,7 +3638,24 @@ where
match canonicalized {
Ok(module_output) => {
let constraint = constrain_module(&module_output, module_id);
// Generate documentation information
// TODO: store timing information?
let module_docs = match module_name {
ModuleNameEnum::PkgConfig => None,
ModuleNameEnum::App(_) => None,
ModuleNameEnum::Interface(name) => Some(crate::docs::generate_module_docs(
module_output.scope,
name.as_str().into(),
&module_output.ident_ids,
&parsed_defs,
)),
};
let constraint = constrain_module(
&module_output.aliases,
&module_output.declarations,
module_id,
);
let module = Module {
module_id,
@ -3799,6 +3820,8 @@ fn make_specializations<'a>(
home,
ident_ids: &mut ident_ids,
ptr_bytes,
update_mode_counter: 0,
call_specialization_counter: 0,
};
// TODO: for now this final specialization pass is sequential,
@ -3860,6 +3883,8 @@ fn build_pending_specializations<'a>(
home,
ident_ids: &mut ident_ids,
ptr_bytes,
update_mode_counter: 0,
call_specialization_counter: 0,
};
// Add modules' decls to Procs
@ -3978,7 +4003,7 @@ fn add_def_to_module<'a>(
procs.insert_exposed(
symbol,
layout,
TopLevelFunctionLayout::from_layout(mono_env.arena, layout),
mono_env.arena,
mono_env.subs,
def.annotation,
@ -3999,6 +4024,9 @@ fn add_def_to_module<'a>(
);
}
body => {
// mark this symbols as a top-level thunk before any other work on the procs
procs.module_thunks.insert(symbol);
// If this is an exposed symbol, we need to
// register it as such. Otherwise, since it
// never gets called by Roc code, it will never
@ -4011,7 +4039,10 @@ fn add_def_to_module<'a>(
annotation,
mono_env.subs,
) {
Ok(l) => l,
Ok(l) => {
// remember, this is a 0-argument thunk
Layout::FunctionPointer(&[], mono_env.arena.alloc(l))
}
Err(LayoutProblem::Erroneous) => {
let message = "top level function has erroneous type";
procs.runtime_errors.insert(symbol, message);
@ -4031,7 +4062,7 @@ fn add_def_to_module<'a>(
procs.insert_exposed(
symbol,
layout,
TopLevelFunctionLayout::from_layout(mono_env.arena, layout),
mono_env.arena,
mono_env.subs,
def.annotation,
@ -4051,7 +4082,6 @@ fn add_def_to_module<'a>(
};
procs.partial_procs.insert(symbol, proc);
procs.module_thunks.insert(symbol);
}
};
}

View File

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

View File

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

View File

@ -39,6 +39,7 @@ pub enum LowLevel {
ListKeepOks,
ListKeepErrs,
ListSortWith,
ListDrop,
DictSize,
DictEmpty,
DictInsert,
@ -100,3 +101,103 @@ pub enum LowLevel {
Hash,
ExpectTrue,
}
impl LowLevel {
/// is one of the arguments always a function?
/// An example is List.map.
pub fn is_higher_order(&self) -> bool {
use LowLevel::*;
match self {
StrConcat
| StrJoinWith
| StrIsEmpty
| StrStartsWith
| StrStartsWithCodePoint
| StrEndsWith
| StrSplit
| StrCountGraphemes
| StrFromInt
| StrFromUtf8
| StrToBytes
| StrFromFloat
| ListLen
| ListGetUnsafe
| ListSet
| ListSetInPlace
| ListDrop
| ListSingle
| ListRepeat
| ListReverse
| ListConcat
| ListContains
| ListAppend
| ListPrepend
| ListJoin
| ListRange
| DictSize
| DictEmpty
| DictInsert
| DictRemove
| DictContains
| DictGetUnsafe
| DictKeys
| DictValues
| DictUnion
| DictIntersection
| DictDifference
| SetFromList
| NumAdd
| NumAddWrap
| NumAddChecked
| NumSub
| NumSubWrap
| NumSubChecked
| NumMul
| NumMulWrap
| NumMulChecked
| NumGt
| NumGte
| NumLt
| NumLte
| NumCompare
| NumDivUnchecked
| NumRemUnchecked
| NumIsMultipleOf
| NumAbs
| NumNeg
| NumSin
| NumCos
| NumSqrtUnchecked
| NumLogUnchecked
| NumRound
| NumToFloat
| NumPow
| NumCeiling
| NumPowInt
| NumFloor
| NumIsFinite
| NumAtan
| NumAcos
| NumAsin
| NumBitwiseAnd
| NumBitwiseXor
| NumBitwiseOr
| NumShiftLeftBy
| NumShiftRightBy
| NumShiftRightZfBy
| NumIntCast
| Eq
| NotEq
| And
| Or
| Not
| Hash
| ExpectTrue => false,
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| DictWalk => true,
}
}
}

View File

@ -1,6 +1,6 @@
use crate::ident::Ident;
use inlinable_string::InlinableString;
use roc_collections::all::{default_hasher, ImMap, MutMap};
use roc_collections::all::{default_hasher, MutMap, SendMap};
use roc_region::all::Region;
use std::collections::HashMap;
use std::{fmt, u32};
@ -86,6 +86,10 @@ impl Symbol {
})
}
pub fn as_u64(self) -> u64 {
self.0
}
pub fn fully_qualified(self, interns: &Interns, home: ModuleId) -> InlinableString {
let module_id = self.module_id();
@ -101,6 +105,10 @@ impl Symbol {
.into()
}
}
pub const fn to_ne_bytes(self) -> [u8; 8] {
self.0.to_ne_bytes()
}
}
/// Rather than displaying as this:
@ -157,6 +165,12 @@ impl fmt::Display for Symbol {
}
}
impl From<Symbol> for u64 {
fn from(symbol: Symbol) -> Self {
symbol.0
}
}
fn fallback_debug_fmt(symbol: Symbol, f: &mut fmt::Formatter) -> fmt::Result {
let module_id = symbol.module_id();
let ident_id = symbol.ident_id();
@ -697,8 +711,8 @@ macro_rules! define_builtins {
/// and what symbols they should resolve to.
///
/// This is for type aliases like `Int` and `Str` and such.
pub fn default_in_scope() -> ImMap<Ident, (Symbol, Region)> {
let mut scope = ImMap::default();
pub fn default_in_scope() -> SendMap<Ident, (Symbol, Region)> {
let mut scope = SendMap::default();
$(
$(
@ -924,6 +938,7 @@ define_builtins! {
29 LIST_WALK_UNTIL: "walkUntil"
30 LIST_RANGE: "range"
31 LIST_SORT_WITH: "sortWith"
32 LIST_DROP: "drop"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View File

@ -15,6 +15,7 @@ roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_problem = { path = "../problem" }
ven_pretty = { path = "../../vendor/pretty" }
morphic_lib = { path = "../../vendor/morphic_lib" }
bumpalo = { version = "3.6.1", features = ["collections"] }
hashbrown = { version = "0.11.2", features = [ "bumpalo" ] }
ven_ena = { path = "../../vendor/ena" }

View File

@ -0,0 +1,602 @@
use morphic_lib::TypeContext;
use morphic_lib::{
BlockExpr, BlockId, CalleeSpecVar, EntryPointName, ExprContext, FuncDef, FuncDefBuilder,
FuncName, ModDefBuilder, ModName, ProgramBuilder, Result, TypeId, TypeName, UpdateModeVar,
ValueId,
};
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
use std::convert::TryFrom;
use crate::ir::{Call, CallType, Expr, Literal, ModifyRc, Proc, Stmt};
use crate::layout::{Builtin, Layout, ListLayout, UnionLayout};
// just using one module for now
const MOD_LIST: ModName = ModName(b"UserApp");
const MOD_APP: ModName = ModName(b"UserApp");
pub fn spec_program<'a, I>(procs: I) -> Result<morphic_lib::Solutions>
where
I: Iterator<Item = &'a Proc<'a>>,
{
let mut main_function = None;
let main_module = {
let mut m = ModDefBuilder::new();
for proc in procs {
let spec = proc_spec(proc)?;
m.add_func(FuncName(&proc.name.to_ne_bytes()), spec)?;
if format!("{:?}", proc.name).contains("mainForHost") {
main_function = Some(proc.name);
}
}
m.build()?
};
let program = {
let mut p = ProgramBuilder::new();
p.add_mod(MOD_APP, main_module)?;
p.add_entry_point(
EntryPointName(b"mainForHost"),
MOD_APP,
FuncName(&main_function.unwrap().to_ne_bytes()),
)?;
p.build()?
};
morphic_lib::solve(program)
}
fn proc_spec(proc: &Proc) -> Result<FuncDef> {
let mut builder = FuncDefBuilder::new();
let mut env = Env::default();
let block = builder.add_block();
// introduce the arguments
let mut argument_layouts = Vec::new();
for (i, (layout, symbol)) in proc.args.iter().enumerate() {
let value_id = builder.add_get_tuple_field(block, builder.get_argument(), i as u32)?;
env.symbols.insert(*symbol, value_id);
argument_layouts.push(*layout);
}
let value_id = stmt_spec(&mut builder, &mut env, block, &proc.ret_layout, &proc.body)?;
let root = BlockExpr(block, value_id);
let arg_type_id = layout_spec(&mut builder, &Layout::Struct(&argument_layouts))?;
let ret_type_id = layout_spec(&mut builder, &proc.ret_layout)?;
builder.build(arg_type_id, ret_type_id, root)
}
#[derive(Default)]
struct Env {
symbols: MutMap<Symbol, ValueId>,
join_points: MutMap<crate::ir::JoinPointId, morphic_lib::JoinPointId>,
}
fn stmt_spec(
builder: &mut FuncDefBuilder,
env: &mut Env,
block: BlockId,
layout: &Layout,
stmt: &Stmt,
) -> Result<ValueId> {
use Stmt::*;
match stmt {
Let(symbol, expr, layout, continuation) => {
let value_id = expr_spec(builder, env, block, layout, expr)?;
env.symbols.insert(*symbol, value_id);
let result = stmt_spec(builder, env, block, layout, continuation)?;
env.symbols.remove(symbol);
Ok(result)
}
Invoke {
symbol,
call,
layout: call_layout,
pass,
fail,
} => {
// a call that might throw an exception
let value_id = call_spec(builder, env, block, call_layout, call)?;
let pass_block = builder.add_block();
env.symbols.insert(*symbol, value_id);
let pass_value_id = stmt_spec(builder, env, pass_block, layout, pass)?;
env.symbols.remove(symbol);
let pass_block_expr = BlockExpr(pass_block, pass_value_id);
let fail_block = builder.add_block();
let fail_value_id = stmt_spec(builder, env, fail_block, layout, fail)?;
let fail_block_expr = BlockExpr(fail_block, fail_value_id);
builder.add_choice(block, &[pass_block_expr, fail_block_expr])
}
Switch {
cond_symbol: _,
cond_layout: _,
branches,
default_branch,
ret_layout,
} => {
let mut cases = Vec::with_capacity(branches.len() + 1);
let it = branches
.iter()
.map(|(_, _, body)| body)
.chain(std::iter::once(default_branch.1));
for branch in it {
let block = builder.add_block();
let value_id = stmt_spec(builder, env, block, ret_layout, branch)?;
cases.push(BlockExpr(block, value_id));
}
builder.add_choice(block, &cases)
}
Ret(symbol) => Ok(env.symbols[symbol]),
Refcounting(modify_rc, continuation) => match modify_rc {
ModifyRc::Inc(symbol, _) | ModifyRc::Dec(symbol) | ModifyRc::DecRef(symbol) => {
let result_type = builder.add_tuple_type(&[])?;
let argument = env.symbols[symbol];
// this is how RC is modelled; it recursively touches all heap cells
builder.add_unknown_with(block, &[argument], result_type)?;
stmt_spec(builder, env, block, layout, continuation)
}
},
Join {
id,
parameters,
continuation,
remainder,
} => {
let mut type_ids = Vec::new();
for p in parameters.iter() {
type_ids.push(layout_spec(builder, &p.layout)?);
}
let ret_type_id = layout_spec(builder, layout)?;
let jp_arg_type_id = builder.add_tuple_type(&type_ids)?;
let (jpid, jp_argument) =
builder.declare_join_point(block, jp_arg_type_id, ret_type_id)?;
let join_body_sub_block = {
env.join_points.insert(*id, jpid);
let jp_body_block = builder.add_block();
// unpack the argument
for (i, p) in parameters.iter().enumerate() {
let value_id =
builder.add_get_tuple_field(jp_body_block, jp_argument, i as u32)?;
env.symbols.insert(p.symbol, value_id);
}
let jp_body_value_id = stmt_spec(builder, env, jp_body_block, layout, remainder)?;
BlockExpr(jp_body_block, jp_body_value_id)
};
// NOTE the symbols bound by the join point can shadow the argument symbols of the
// surrounding function, so we don't remove them from the env here
let cont_block = builder.add_block();
let cont_value_id = stmt_spec(builder, env, cont_block, layout, continuation)?;
env.join_points.remove(id);
builder.define_join_point(jpid, join_body_sub_block)?;
builder.add_sub_block(block, BlockExpr(cont_block, cont_value_id))
}
Jump(id, symbols) => {
let ret_type_id = layout_spec(builder, layout)?;
let argument = build_tuple_value(builder, env, block, symbols)?;
let jpid = env.join_points[id];
builder.add_jump(block, jpid, argument, ret_type_id)
}
Rethrow | RuntimeError(_) => {
let type_id = layout_spec(builder, layout)?;
builder.add_terminate(block, type_id)
}
}
}
fn build_tuple_value(
builder: &mut FuncDefBuilder,
env: &Env,
block: BlockId,
symbols: &[Symbol],
) -> Result<ValueId> {
let mut value_ids = Vec::new();
for field in symbols.iter() {
let value_id = match env.symbols.get(field) {
None => panic!(
"Symbol {:?} is not defined in environment {:?}",
field, &env.symbols
),
Some(x) => *x,
};
value_ids.push(value_id);
}
builder.add_make_tuple(block, &value_ids)
}
fn build_tuple_type(builder: &mut FuncDefBuilder, layouts: &[Layout]) -> Result<TypeId> {
let mut field_types = Vec::new();
for field in layouts.iter() {
field_types.push(layout_spec(builder, field)?);
}
builder.add_tuple_type(&field_types)
}
fn call_spec(
builder: &mut FuncDefBuilder,
env: &Env,
block: BlockId,
layout: &Layout,
call: &Call,
) -> Result<ValueId> {
use CallType::*;
match &call.call_type {
ByName {
name: symbol,
full_layout: _,
ret_layout: _,
arg_layouts: _,
specialization_id,
} => {
let array = specialization_id.to_bytes();
let spec_var = CalleeSpecVar(&array);
let arg_value_id = build_tuple_value(builder, env, block, call.arguments)?;
let slice = &symbol.to_ne_bytes();
let name = FuncName(slice);
let module = MOD_APP;
builder.add_call(block, spec_var, module, name, arg_value_id)
}
Foreign {
foreign_symbol: _,
ret_layout,
} => {
let arguments: Vec<_> = call
.arguments
.iter()
.map(|symbol| env.symbols[symbol])
.collect();
let result_type = layout_spec(builder, ret_layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
LowLevel { op, update_mode } => lowlevel_spec(
builder,
env,
block,
layout,
op,
*update_mode,
call.arguments,
),
HigherOrderLowLevel { .. } => todo!(),
}
}
fn lowlevel_spec(
builder: &mut FuncDefBuilder,
env: &Env,
block: BlockId,
layout: &Layout,
op: &LowLevel,
update_mode: crate::ir::UpdateModeId,
arguments: &[Symbol],
) -> Result<ValueId> {
use LowLevel::*;
let type_id = layout_spec(builder, layout)?;
let mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
match op {
NumAdd | NumSub => {
// NOTE these numeric operations panic (e.g. on overflow)
let pass_block = {
let block = builder.add_block();
let value = new_num(builder, block)?;
BlockExpr(block, value)
};
let fail_block = {
let block = builder.add_block();
let value = builder.add_terminate(block, type_id)?;
BlockExpr(block, value)
};
let sub_block = {
let block = builder.add_block();
let choice = builder.add_choice(block, &[pass_block, fail_block])?;
BlockExpr(block, choice)
};
builder.add_sub_block(block, sub_block)
}
Eq | NotEq => new_bool(builder, block),
NumLte | NumLt | NumGt | NumGte => new_order(builder, block),
ListLen => {
let list = env.symbols[&arguments[0]];
builder.add_get_tuple_field(block, list, LIST_LEN_INDEX)
}
ListGetUnsafe => {
// NOTE the ListGet lowlevel op is only evaluated if the index is in-bounds
let list = env.symbols[&arguments[0]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_touch(block, cell)?;
builder.add_bag_get(block, bag)
}
ListSet => {
let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[2]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
builder.add_bag_insert(block, bag, to_insert)?;
Ok(list)
}
other => todo!("lowlevel op not implemented: {:?}", other),
}
}
fn build_variant_types(
builder: &mut FuncDefBuilder,
union_layout: &UnionLayout,
) -> Result<Vec<TypeId>> {
use UnionLayout::*;
let mut result = Vec::new();
match union_layout {
NonRecursive(tags) => {
for tag in tags.iter() {
result.push(build_tuple_type(builder, tag)?);
}
}
Recursive(_) => todo!(),
NonNullableUnwrapped(_) => todo!(),
NullableWrapped {
nullable_id: _,
other_tags: _,
} => todo!(),
NullableUnwrapped {
nullable_id: _,
other_fields: _,
} => todo!(),
}
Ok(result)
}
fn expr_spec(
builder: &mut FuncDefBuilder,
env: &Env,
block: BlockId,
layout: &Layout,
expr: &Expr,
) -> Result<ValueId> {
use Expr::*;
match expr {
Literal(literal) => literal_spec(builder, block, literal),
Call(call) => call_spec(builder, env, block, layout, call),
Tag {
tag_layout,
tag_name: _,
tag_id,
union_size: _,
arguments,
} => {
let value_id = build_tuple_value(builder, env, block, arguments)?;
let variant_types = build_variant_types(builder, tag_layout)?;
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)
}
Struct(fields) => build_tuple_value(builder, env, block, fields),
AccessAtIndex {
index,
field_layouts: _,
structure,
wrapped,
} => {
use crate::ir::Wrapped;
let value_id = env.symbols[structure];
match wrapped {
Wrapped::EmptyRecord => {
// this is a unit value
builder.add_make_tuple(block, &[])
}
Wrapped::SingleElementRecord => {
todo!("do we unwrap single-element records still?")
}
Wrapped::RecordOrSingleTagUnion => {
builder.add_get_tuple_field(block, value_id, *index as u32)
}
Wrapped::MultiTagUnion => {
builder.add_get_tuple_field(block, value_id, *index as u32)
}
}
}
Array { elem_layout, elems } => {
let type_id = layout_spec(builder, elem_layout)?;
let list = new_list(builder, block, type_id)?;
let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
for symbol in elems.iter() {
let value_id = env.symbols[symbol];
bag = builder.add_bag_insert(block, bag, value_id)?;
}
Ok(bag)
}
EmptyArray => {
use ListLayout::*;
match ListLayout::try_from(layout) {
Ok(EmptyList) => {
// just make up an element type
let type_id = builder.add_tuple_type(&[])?;
new_list(builder, block, type_id)
}
Ok(List(element_layout)) => {
let type_id = layout_spec(builder, element_layout)?;
new_list(builder, block, type_id)
}
Err(()) => unreachable!("empty array does not have a list layout"),
}
}
Reuse { .. } => todo!("currently unused"),
Reset(_) => todo!("currently unused"),
RuntimeErrorFunction(_) => {
let type_id = layout_spec(builder, layout)?;
builder.add_terminate(block, type_id)
}
}
}
fn literal_spec(
builder: &mut FuncDefBuilder,
block: BlockId,
literal: &Literal,
) -> Result<ValueId> {
use Literal::*;
match literal {
Str(_) => new_static_string(builder, block),
Int(_) | Float(_) | Bool(_) | Byte(_) => builder.add_make_tuple(block, &[]),
}
}
fn layout_spec(builder: &mut FuncDefBuilder, layout: &Layout) -> Result<TypeId> {
use Layout::*;
match layout {
Builtin(builtin) => builtin_spec(builder, builtin),
Struct(fields) => build_tuple_type(builder, fields),
Union(union_layout) => {
let variant_types = build_variant_types(builder, union_layout)?;
builder.add_union_type(&variant_types)
}
RecursivePointer => todo!(),
FunctionPointer(_, _) => todo!(),
Closure(_, _, _) => todo!(),
}
}
fn builtin_spec(builder: &mut FuncDefBuilder, builtin: &Builtin) -> Result<TypeId> {
use Builtin::*;
match builtin {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]),
Float128 => todo!(),
Float64 => todo!(),
Float32 => todo!(),
Float16 => todo!(),
Str => todo!(),
Dict(_, _) => todo!(),
Set(_) => todo!(),
List(_) => {
// TODO should incorporate the element type into the name
Ok(builder.add_named_type(MOD_LIST, TypeName(b"List")))
}
EmptyStr => todo!(),
EmptyList => todo!(),
EmptyDict => todo!(),
EmptySet => todo!(),
}
}
// const OK_TAG_ID: u8 = 1u8;
// const ERR_TAG_ID: u8 = 0u8;
const LIST_CELL_INDEX: u32 = 0;
const LIST_BAG_INDEX: u32 = 1;
const LIST_LEN_INDEX: u32 = 2;
fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result<ValueId> {
let cell = builder.add_new_heap_cell(block)?;
let bag = builder.add_empty_bag(block, element_type)?;
let length = new_usize(builder, block)?;
builder.add_make_tuple(block, &[cell, bag, length])
}
fn new_usize(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
new_num(builder, block)
}
fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
let cell = builder.add_new_heap_cell(block)?;
// immediately mutate the cell, so any future updates on this value are invalid
// updating a static string would cause a crash at runtime
let _ = builder.add_update(block, UpdateModeVar(&[]), cell)?;
let length = new_usize(builder, block)?;
builder.add_make_tuple(block, &[cell, length])
}
fn new_order(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// always generats EQ
let tag_id = 0;
let unit = builder.add_tuple_type(&[])?;
let unit_value = builder.add_make_tuple(block, &[])?;
builder.add_make_union(block, &[unit, unit, unit], tag_id, unit_value)
}
fn new_bool(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// always generats False
let tag_id = 0;
let unit = builder.add_tuple_type(&[])?;
let unit_value = builder.add_make_tuple(block, &[])?;
builder.add_make_union(block, &[unit, unit], tag_id, unit_value)
}
fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// we model all our numbers as unit values
builder.add_make_tuple(block, &[])
}

View File

@ -1,4 +1,4 @@
use crate::ir::{Expr, JoinPointId, Param, Proc, Stmt};
use crate::ir::{Expr, JoinPointId, Param, Proc, Stmt, TopLevelFunctionLayout};
use crate::layout::Layout;
use bumpalo::collections::Vec;
use bumpalo::Bump;
@ -6,6 +6,9 @@ use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
pub const OWNED: bool = false;
pub const BORROWED: bool = true;
fn should_borrow_layout(layout: &Layout) -> bool {
match layout {
Layout::Closure(_, _, _) => false,
@ -15,14 +18,15 @@ fn should_borrow_layout(layout: &Layout) -> bool {
pub fn infer_borrow<'a>(
arena: &'a Bump,
procs: &MutMap<(Symbol, Layout<'a>), Proc<'a>>,
procs: &MutMap<(Symbol, TopLevelFunctionLayout<'a>), Proc<'a>>,
) -> ParamMap<'a> {
let mut param_map = ParamMap {
items: MutMap::default(),
};
for (key, proc) in procs {
param_map.visit_proc(arena, proc, *key);
for ((s, top_level), proc) in procs {
let key = (*s, arena.alloc(*top_level).full());
param_map.visit_proc(arena, proc, key);
}
let mut env = BorrowInfState {
@ -47,7 +51,8 @@ pub fn infer_borrow<'a>(
// mutually recursive functions (or just make all their arguments owned)
for (key, proc) in procs {
env.collect_proc(proc, key.1);
let layout = arena.alloc(key.1).full();
env.collect_proc(proc, layout);
}
if !env.modified {
@ -306,16 +311,6 @@ impl<'a> BorrowInfState<'a> {
}
}
fn own_arg(&mut self, x: Symbol) {
self.own_var(x);
}
fn own_args(&mut self, xs: &[Symbol]) {
for x in xs.iter() {
self.own_arg(*x);
}
}
/// For each xs[i], if xs[i] is owned, then mark ps[i] as owned.
/// We use this action to preserve tail calls. That is, if we have
/// a tail call `f xs`, if the i-th parameter is borrowed, but `xs[i]` is owned
@ -367,43 +362,29 @@ impl<'a> BorrowInfState<'a> {
name, full_layout, ..
} => {
// get the borrow signature of the applied function
match self.param_map.get_symbol(*name, *full_layout) {
Some(ps) => {
// the return value will be owned
self.own_var(z);
let ps = self
.param_map
.get_symbol(*name, *full_layout)
.expect("function is defined");
// if the function exects an owned argument (ps), the argument must be owned (args)
debug_assert_eq!(
arguments.len(),
ps.len(),
"{:?} has {} parameters, but was applied to {} arguments",
name,
ps.len(),
arguments.len()
);
self.own_args_using_params(arguments, ps);
}
None => {
// this is really an indirect call, but the function was bound to a symbol
// the return value will be owned
self.own_var(z);
// if the function exects an owned argument (ps), the argument must be owned (args)
self.own_args(arguments);
}
}
}
ByPointer { .. } => {
// the return value will be owned
self.own_var(z);
// if the function exects an owned argument (ps), the argument must be owned (args)
self.own_args(arguments);
debug_assert_eq!(
arguments.len(),
ps.len(),
"{:?} has {} parameters, but was applied to {} arguments",
name,
ps.len(),
arguments.len()
);
self.own_args_using_params(arguments, ps);
}
LowLevel { op } => {
// very unsure what demand RunLowLevel should place upon its arguments
LowLevel { op, .. } => {
debug_assert!(!op.is_higher_order());
self.own_var(z);
let ps = lowlevel_borrow_signature(self.arena, *op);
@ -411,6 +392,130 @@ impl<'a> BorrowInfState<'a> {
self.own_args_using_bools(arguments, ps);
}
HigherOrderLowLevel {
op, closure_layout, ..
} => {
use roc_module::low_level::LowLevel::*;
debug_assert!(op.is_higher_order());
match op {
ListMap | ListKeepIf | ListKeepOks | ListKeepErrs => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
Some(function_ps) => {
// own the list if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(arguments[0]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(1).map(|p| p.borrow) {
self.own_var(arguments[2]);
}
}
None => unreachable!(),
}
}
ListMapWithIndex => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
Some(function_ps) => {
// own the list if the function wants to own the element
if !function_ps[1].borrow {
self.own_var(arguments[0]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[2]);
}
}
None => unreachable!(),
}
}
ListMap2 => match self.param_map.get_symbol(arguments[2], *closure_layout) {
Some(function_ps) => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(arguments[0]);
}
if !function_ps[1].borrow {
self.own_var(arguments[1]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[3]);
}
}
None => unreachable!(),
},
ListMap3 => match self.param_map.get_symbol(arguments[3], *closure_layout) {
Some(function_ps) => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(arguments[0]);
}
if !function_ps[1].borrow {
self.own_var(arguments[1]);
}
if !function_ps[2].borrow {
self.own_var(arguments[2]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(3).map(|p| p.borrow) {
self.own_var(arguments[4]);
}
}
None => unreachable!(),
},
ListSortWith => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
Some(function_ps) => {
// always own the input list
self.own_var(arguments[0]);
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[2]);
}
}
None => unreachable!(),
}
}
ListWalk | ListWalkUntil | ListWalkBackwards | DictWalk => {
match self.param_map.get_symbol(arguments[2], *closure_layout) {
Some(function_ps) => {
// own the data structure if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(arguments[0]);
}
// own the default value if the function wants to own it
if !function_ps[1].borrow {
self.own_var(arguments[1]);
}
// own the closure environment if the function needs to own it
if let Some(false) = function_ps.get(2).map(|p| p.borrow) {
self.own_var(arguments[3]);
}
}
None => unreachable!(),
}
}
_ => {
// very unsure what demand RunLowLevel should place upon its arguments
self.own_var(z);
let ps = lowlevel_borrow_signature(self.arena, *op);
self.own_args_using_bools(arguments, ps);
}
}
}
Foreign { .. } => {
// very unsure what demand ForeignCall should place upon its arguments
self.own_var(z);
@ -463,48 +568,33 @@ impl<'a> BorrowInfState<'a> {
Call(call) => self.collect_call(z, call),
Literal(_) | FunctionPointer(_, _) | RuntimeErrorFunction(_) => {}
Literal(_) | RuntimeErrorFunction(_) => {}
}
}
#[allow(clippy::many_single_char_names)]
fn preserve_tail_call(&mut self, x: Symbol, v: &Expr<'a>, b: &Stmt<'a>) {
match (v, b) {
(
Expr::Call(crate::ir::Call {
call_type:
crate::ir::CallType::ByName {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
Stmt::Ret(z),
)
| (
Expr::Call(crate::ir::Call {
call_type:
crate::ir::CallType::ByPointer {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
Stmt::Ret(z),
) => {
if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g, *full_layout) {
self.own_params_using_args(ys, ps)
}
if let (
Expr::Call(crate::ir::Call {
call_type:
crate::ir::CallType::ByName {
name: g,
full_layout,
..
},
arguments: ys,
..
}),
Stmt::Ret(z),
) = (v, b)
{
if self.current_proc == *g && x == *z {
// anonymous functions (for which the ps may not be known)
// can never be tail-recursive, so this is fine
if let Some(ps) = self.param_map.get_symbol(*g, *full_layout) {
self.own_params_using_args(ys, ps)
}
}
_ => {}
}
}
@ -539,18 +629,6 @@ impl<'a> BorrowInfState<'a> {
self.collect_stmt(b);
}
Let(x, Expr::FunctionPointer(fsymbol, layout), _, b) => {
// ensure that the function pointed to is in the param map
if let Some(params) = self.param_map.get_symbol(*fsymbol, *layout) {
self.param_map
.items
.insert(Key::Declaration(*x, *layout), params);
}
self.collect_stmt(b);
self.preserve_tail_call(*x, &Expr::FunctionPointer(*fsymbol, *layout), b);
}
Let(x, v, _, b) => {
self.collect_stmt(b);
self.collect_expr(*x, v);
@ -620,7 +698,7 @@ impl<'a> BorrowInfState<'a> {
pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[bool] {
// NOTE this means that Roc is responsible for cleaning up resources;
// the host cannot (currently) take ownership
let all = bumpalo::vec![in arena; true; arity];
let all = bumpalo::vec![in arena; BORROWED; arity];
all.into_bump_slice()
}
@ -628,9 +706,11 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
use LowLevel::*;
// TODO is true or false more efficient for non-refcounted layouts?
let irrelevant = false;
let owned = false;
let borrowed = true;
let irrelevant = OWNED;
let function = irrelevant;
let closure_data = irrelevant;
let owned = OWNED;
let borrowed = BORROWED;
// Here we define the borrow signature of low-level operations
//
@ -651,20 +731,23 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListPrepend => arena.alloc_slice_copy(&[owned, owned]),
StrJoinWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListJoin => arena.alloc_slice_copy(&[irrelevant]),
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, irrelevant]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, irrelevant]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, irrelevant]),
ListKeepIf | ListKeepOks | ListKeepErrs => arena.alloc_slice_copy(&[owned, borrowed]),
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, function, closure_data]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
ListKeepIf | ListKeepOks | ListKeepErrs => {
arena.alloc_slice_copy(&[owned, function, closure_data])
}
ListContains => arena.alloc_slice_copy(&[borrowed, irrelevant]),
ListRange => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
ListWalk | ListWalkUntil | ListWalkBackwards => {
arena.alloc_slice_copy(&[owned, irrelevant, owned])
arena.alloc_slice_copy(&[owned, owned, function, closure_data])
}
ListSortWith => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSortWith => arena.alloc_slice_copy(&[owned, function, closure_data]),
// TODO when we have lists with capacity (if ever)
// List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[owned, owned]),
ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),
@ -693,7 +776,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
DictUnion | DictDifference | DictIntersection => arena.alloc_slice_copy(&[owned, borrowed]),
// borrow function argument so we don't have to worry about RC of the closure
DictWalk => arena.alloc_slice_copy(&[owned, borrowed, owned]),
DictWalk => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
SetFromList => arena.alloc_slice_copy(&[owned]),

View File

@ -1101,7 +1101,13 @@ fn test_to_equality<'a>(
cond_layout: &Layout<'a>,
path: &[PathInstruction],
test: Test<'a>,
) -> (StoresVec<'a>, Symbol, Symbol, Layout<'a>) {
) -> (
StoresVec<'a>,
Symbol,
Symbol,
Layout<'a>,
Option<ConstructorKnown<'a>>,
) {
let (rhs_symbol, mut stores, _layout) =
path_to_expr_help(env, cond_symbol, &path, *cond_layout);
@ -1148,6 +1154,11 @@ fn test_to_equality<'a>(
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int64),
Some(ConstructorKnown::OnlyPass {
scrutinee: path_symbol,
layout: *cond_layout,
tag_id,
}),
)
}
Test::IsInt(test_int) => {
@ -1162,6 +1173,7 @@ fn test_to_equality<'a>(
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int64),
None,
)
}
@ -1177,6 +1189,7 @@ fn test_to_equality<'a>(
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Float64),
None,
)
}
@ -1192,6 +1205,7 @@ fn test_to_equality<'a>(
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int8),
None,
)
}
@ -1205,6 +1219,7 @@ fn test_to_equality<'a>(
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Int1),
None,
)
}
@ -1219,6 +1234,7 @@ fn test_to_equality<'a>(
lhs_symbol,
rhs_symbol,
Layout::Builtin(Builtin::Str),
None,
)
}
@ -1231,6 +1247,7 @@ type Tests<'a> = std::vec::Vec<(
Symbol,
Symbol,
Layout<'a>,
Option<ConstructorKnown<'a>>,
)>;
fn stores_and_condition<'a>(
@ -1239,7 +1256,7 @@ fn stores_and_condition<'a>(
cond_layout: &Layout<'a>,
test_chain: Vec<(Vec<PathInstruction>, Test<'a>)>,
) -> (Tests<'a>, Option<(Symbol, JoinPointId, Stmt<'a>)>) {
let mut tests = Vec::with_capacity(test_chain.len());
let mut tests: Tests = Vec::with_capacity(test_chain.len());
let mut guard = None;
@ -1404,8 +1421,12 @@ fn compile_test_help<'a>(
default_branch,
};
let op = LowLevel::Eq;
let test = Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::LowLevel { op: LowLevel::Eq },
call_type: crate::ir::CallType::LowLevel {
op,
update_mode: env.next_update_mode_id(),
},
arguments: arena.alloc([lhs, rhs]),
});
@ -1440,12 +1461,20 @@ fn compile_tests<'a>(
cond = compile_guard(env, ret_layout, id, arena.alloc(stmt), fail, cond);
}
for (new_stores, lhs, rhs, _layout) in tests.into_iter() {
cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond);
for (new_stores, lhs, rhs, _layout, opt_constructor_info) in tests.into_iter() {
match opt_constructor_info {
None => {
cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond);
}
Some(cinfo) => {
cond = compile_test_help(env, cinfo, ret_layout, new_stores, lhs, rhs, fail, cond);
}
}
}
cond
}
#[derive(Debug)]
enum ConstructorKnown<'a> {
Both {
scrutinee: Symbol,
@ -1567,7 +1596,7 @@ fn decide_to_branching<'a>(
// use knowledge about constructors for optimization
debug_assert_eq!(tests.len(), 1);
let (new_stores, lhs, rhs, _layout) = tests.into_iter().next().unwrap();
let (new_stores, lhs, rhs, _layout, _cinfo) = tests.into_iter().next().unwrap();
compile_test_help(
env,
@ -1677,12 +1706,33 @@ fn decide_to_branching<'a>(
// We have learned more about the exact layout of the cond (based on the path)
// but tests are still relative to the original cond symbol
let mut switch = Stmt::Switch {
cond_layout: inner_cond_layout,
cond_symbol: inner_cond_symbol,
branches: branches.into_bump_slice(),
default_branch: (default_branch_info, env.arena.alloc(default_branch)),
ret_layout,
let mut switch = if let Layout::Union(_) = inner_cond_layout {
let tag_id_symbol = env.unique_symbol();
let temp = Stmt::Switch {
cond_layout: Layout::TAG_SIZE,
cond_symbol: tag_id_symbol,
branches: branches.into_bump_slice(),
default_branch: (default_branch_info, env.arena.alloc(default_branch)),
ret_layout,
};
let expr = Expr::AccessAtIndex {
index: 0,
field_layouts: &[Layout::TAG_SIZE],
structure: inner_cond_symbol,
wrapped: Wrapped::MultiTagUnion,
};
Stmt::Let(tag_id_symbol, expr, Layout::TAG_SIZE, env.arena.alloc(temp))
} else {
Stmt::Switch {
cond_layout: inner_cond_layout,
cond_symbol: inner_cond_symbol,
branches: branches.into_bump_slice(),
default_branch: (default_branch_info, env.arena.alloc(default_branch)),
ret_layout,
}
};
for (symbol, layout, expr) in cond_stores_vec.into_iter().rev() {

View File

@ -165,11 +165,10 @@ impl<'a, 'i> Env<'a, 'i> {
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
}
Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = self.arena.alloc([fpointer, *closure_layout.layout]);
Closure(_, lambda_set, _) => {
self.constructor_map.insert(symbol, 0);
self.layout_map.insert(symbol, Layout::Struct(fields));
self.layout_map
.insert(symbol, lambda_set.runtime_representation());
}
_ => {}
}
@ -203,7 +202,7 @@ impl<'a, 'i> Env<'a, 'i> {
}
fn layout_for_constructor<'a>(
arena: &'a Bump,
_arena: &'a Bump,
layout: &Layout<'a>,
constructor: u64,
) -> ConstructorLayout<&'a [Layout<'a>]> {
@ -245,10 +244,12 @@ fn layout_for_constructor<'a>(
debug_assert_eq!(constructor, 0);
HasFields(fields)
}
Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = arena.alloc([fpointer, *closure_layout.layout]);
HasFields(fields)
Closure(_arguments, _lambda_set, _result) => {
// TODO can this be improved again?
// let fpointer = Layout::FunctionPointer(arguments, result);
// let fields = arena.alloc([fpointer, *lambda_set.layout]);
// HasFields(fields)
ConstructorLayout::Unknown
}
other => unreachable!("weird layout {:?}", other),
}
@ -373,11 +374,12 @@ pub fn expand_and_cancel_proc<'a>(
introduced.push(*symbol);
}
Layout::Closure(arguments, closure_layout, result) => {
let fpointer = Layout::FunctionPointer(arguments, result);
let fields = env.arena.alloc([fpointer, *closure_layout.layout]);
env.insert_struct_info(*symbol, fields);
introduced.push(*symbol);
Layout::Closure(_arguments, _lambda_set, _result) => {
// TODO can this be improved again?
// let fpointer = Layout::FunctionPointer(arguments, result);
// let fields = env.arena.alloc([fpointer, *closure_layout.layout]);
// env.insert_struct_info(*symbol, fields);
// introduced.push(*symbol);
}
_ => {}
}
@ -421,7 +423,10 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
// prevent long chains of `Let`s from blowing the stack
let mut literal_stack = Vec::new_in(env.arena);
while !matches!(&expr, Expr::AccessAtIndex { .. } | Expr::Struct(_)) {
while !matches!(
&expr,
Expr::AccessAtIndex { .. } | Expr::Struct(_) | Expr::Call(_)
) {
if let Stmt::Let(symbol1, expr1, layout1, cont1) = cont {
literal_stack.push((symbol, expr.clone(), *layout));
@ -483,6 +488,17 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
new_cont = expand_and_cancel(env, cont);
}
}
Expr::Call(_) => {
if let Layout::Struct(fields) = layout {
env.insert_struct_info(symbol, fields);
new_cont = expand_and_cancel(env, cont);
env.remove_struct_info(symbol);
} else {
new_cont = expand_and_cancel(env, cont);
}
}
_ => {
new_cont = expand_and_cancel(env, cont);
}
@ -536,7 +552,12 @@ fn expand_and_cancel<'a>(env: &mut Env<'a, '_>, stmt: &'a Stmt<'a>) -> &'a Stmt<
&*env.arena.alloc(stmt)
}
Refcounting(ModifyRc::DecRef(_symbol), _cont) => unreachable!("not introduced yet"),
Refcounting(ModifyRc::DecRef(symbol), cont) => {
// decref the current cell
env.deferred.decrefs.push(*symbol);
expand_and_cancel(env, cont)
}
Refcounting(ModifyRc::Dec(symbol), cont) => {
use ConstructorLayout::*;

View File

@ -1,4 +1,4 @@
use crate::borrow::ParamMap;
use crate::borrow::{ParamMap, BORROWED, OWNED};
use crate::ir::{Expr, JoinPointId, ModifyRc, Param, Proc, Stmt};
use crate::layout::Layout;
use bumpalo::collections::Vec;
@ -103,8 +103,7 @@ pub fn occuring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
use Expr::*;
match expr {
FunctionPointer(symbol, _)
| AccessAtIndex {
AccessAtIndex {
structure: symbol, ..
} => {
result.insert(*symbol);
@ -398,7 +397,7 @@ impl<'a> Context<'a> {
&& is_borrow_param(*x, xs, ps)
&& !b_live_vars.contains(x)
{
b = self.add_dec(*x, b)
b = self.add_dec(*x, b);
}
}
@ -442,7 +441,7 @@ impl<'a> Context<'a> {
use crate::ir::CallType::*;
match &call_type {
LowLevel { op } => {
LowLevel { op, .. } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
@ -454,6 +453,209 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
HigherOrderLowLevel {
op, closure_layout, ..
} => {
macro_rules! create_call {
($borrows:expr) => {
Expr::Call(crate::ir::Call {
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
HigherOrderLowLevel {
op: *op,
closure_layout: *closure_layout,
function_owns_closure_data: true,
}
} else {
call_type
},
arguments,
})
};
}
macro_rules! decref_if_owned {
($borrows:expr, $argument:expr, $stmt:expr) => {
if !$borrows {
self.arena.alloc(Stmt::Refcounting(
ModifyRc::DecRef($argument),
self.arena.alloc($stmt),
))
} else {
$stmt
}
};
}
const FUNCTION: bool = BORROWED;
const CLOSURE_DATA: bool = BORROWED;
match op {
roc_module::low_level::LowLevel::ListMap
| roc_module::low_level::LowLevel::ListKeepIf
| roc_module::low_level::LowLevel::ListKeepOks
| roc_module::low_level::LowLevel::ListKeepErrs => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
Some(function_ps) => {
let borrows = [function_ps[0].borrow, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(
arguments,
&borrows,
b,
b_live_vars,
);
// if the list is owned, then all elements have been consumed, but not the list itself
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b);
let v = create_call!(function_ps.get(1));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
}
roc_module::low_level::LowLevel::ListMapWithIndex => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
Some(function_ps) => {
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[1].borrow, arguments[0], b);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
}
roc_module::low_level::LowLevel::ListMap2 => {
match self.param_map.get_symbol(arguments[2], *closure_layout) {
Some(function_ps) => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b);
let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
}
roc_module::low_level::LowLevel::ListMap3 => {
match self.param_map.get_symbol(arguments[3], *closure_layout) {
Some(function_ps) => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
function_ps[2].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b);
let b = decref_if_owned!(function_ps[1].borrow, arguments[1], b);
let b = decref_if_owned!(function_ps[2].borrow, arguments[2], b);
let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
}
roc_module::low_level::LowLevel::ListSortWith => {
match self.param_map.get_symbol(arguments[1], *closure_layout) {
Some(function_ps) => {
let borrows = [OWNED, FUNCTION, CLOSURE_DATA];
let b = self.add_dec_after_lowlevel(
arguments,
&borrows,
b,
b_live_vars,
);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
}
roc_module::low_level::LowLevel::ListWalk
| roc_module::low_level::LowLevel::ListWalkUntil
| roc_module::low_level::LowLevel::ListWalkBackwards
| roc_module::low_level::LowLevel::DictWalk => {
match self.param_map.get_symbol(arguments[2], *closure_layout) {
Some(function_ps) => {
// borrow data structure based on first argument of the folded function
// borrow the default based on second argument of the folded function
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(
arguments,
&borrows,
b,
b_live_vars,
);
let b = decref_if_owned!(function_ps[0].borrow, arguments[0], b);
let v = create_call!(function_ps.get(2));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
None => unreachable!(),
}
}
_ => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
}
}
Foreign { .. } => {
let ps = crate::borrow::foreign_borrow_signature(self.arena, arguments.len());
let b = self.add_dec_after_lowlevel(arguments, ps, b, b_live_vars);
@ -470,44 +672,20 @@ impl<'a> Context<'a> {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name, *full_layout) {
Some(ps) => {
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
let ps = self
.param_map
.get_symbol(*name, *full_layout)
.expect("function is defined");
let b = self.add_dec_after_application(arguments, ps, b, b_live_vars);
let b = self.arena.alloc(Stmt::Let(z, v, l, b));
self.add_inc_before(arguments, ps, b, b_live_vars)
}
None => {
// an indirect call that was bound to a name
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
self.add_inc_before_consume_all(
arguments,
self.arena.alloc(Stmt::Let(z, v, l, b)),
b_live_vars,
)
}
}
}
ByPointer { .. } => {
let v = Expr::Call(crate::ir::Call {
call_type,
arguments,
});
self.add_inc_before_consume_all(
arguments,
self.arena.alloc(Stmt::Let(z, v, l, b)),
b_live_vars,
)
let b = self.add_dec_after_application(arguments, ps, b, b_live_vars);
let b = self.arena.alloc(Stmt::Let(z, v, l, b));
self.add_inc_before(arguments, ps, b, b_live_vars)
}
}
}
@ -552,11 +730,7 @@ impl<'a> Context<'a> {
arguments,
}) => self.visit_call(z, call_type, arguments, l, b, b_live_vars),
EmptyArray
| FunctionPointer(_, _)
| Literal(_)
| Reset(_)
| RuntimeErrorFunction(_) => {
EmptyArray | Literal(_) | Reset(_) | RuntimeErrorFunction(_) => {
// EmptyArray is always stack-allocated
// function pointers are persistent
self.arena.alloc(Stmt::Let(z, v, l, b))
@ -585,10 +759,7 @@ impl<'a> Context<'a> {
fn update_var_info(&self, symbol: Symbol, layout: &Layout<'a>, expr: &Expr<'a>) -> Self {
// is this value a constant?
// TODO do function pointers also fall into this category?
let persistent = match expr {
Expr::Call(crate::ir::Call { arguments, .. }) => arguments.is_empty(),
_ => false,
};
let persistent = false;
// must this value be consumed?
let consume = consume_expr(&self.vars, expr);
@ -722,38 +893,31 @@ impl<'a> Context<'a> {
fail,
layout,
} => {
// TODO this combines parts of Let and Switch. Did this happen correctly?
let mut case_live_vars = collect_stmt(pass, &self.jp_live_vars, MutSet::default());
case_live_vars.extend(collect_stmt(fail, &self.jp_live_vars, MutSet::default()));
// live vars of the whole expression
let invoke_live_vars = collect_stmt(stmt, &self.jp_live_vars, MutSet::default());
// the result of an invoke should not be touched in the fail branch
// but it should be present in the pass branch (otherwise it would be dead)
// NOTE: we cheat a bit here to allow `invoke` when generating code for `expect`
let is_dead = !case_live_vars.contains(symbol);
let is_dead = !invoke_live_vars.contains(symbol);
if is_dead && layout.is_refcounted() {
panic!("A variable of a reference-counted layout is dead; that's a bug!");
}
case_live_vars.remove(symbol);
let fail = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let (b, alt_live_vars) = ctx.visit_stmt(fail);
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
ctx.add_dec_for_alt(&invoke_live_vars, &alt_live_vars, b)
};
if !is_dead {
case_live_vars.insert(*symbol);
}
let pass = {
// TODO should we use ctor info like Lean?
let ctx = self.clone();
let ctx = ctx.update_var_info_invoke(*symbol, layout, call);
let (b, alt_live_vars) = ctx.visit_stmt(pass);
ctx.add_dec_for_alt(&case_live_vars, &alt_live_vars, b)
ctx.add_dec_for_alt(&invoke_live_vars, &alt_live_vars, b)
};
let invoke = Invoke {
@ -768,9 +932,13 @@ impl<'a> Context<'a> {
use crate::ir::CallType;
let stmt = match &call.call_type {
CallType::LowLevel { op } => {
CallType::LowLevel { op, .. } => {
let ps = crate::borrow::lowlevel_borrow_signature(self.arena, *op);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
self.add_dec_after_lowlevel(call.arguments, ps, cont, &invoke_live_vars)
}
CallType::HigherOrderLowLevel { .. } => {
todo!("copy the code for normal calls over to here");
}
CallType::Foreign { .. } => {
@ -778,35 +946,22 @@ impl<'a> Context<'a> {
self.arena,
call.arguments.len(),
);
self.add_dec_after_lowlevel(call.arguments, ps, cont, &case_live_vars)
self.add_dec_after_lowlevel(call.arguments, ps, cont, &invoke_live_vars)
}
CallType::ByName {
name, full_layout, ..
} => {
// get the borrow signature
match self.param_map.get_symbol(*name, *full_layout) {
Some(ps) => self.add_dec_after_application(
call.arguments,
ps,
cont,
&case_live_vars,
),
None => self.add_inc_before_consume_all(
call.arguments,
cont,
&case_live_vars,
),
}
}
CallType::ByPointer { .. } => {
self.add_inc_before_consume_all(call.arguments, cont, &case_live_vars)
let ps = self
.param_map
.get_symbol(*name, *full_layout)
.expect("function is defined");
self.add_dec_after_application(call.arguments, ps, cont, &invoke_live_vars)
}
};
let mut invoke_live_vars = case_live_vars;
occuring_variables_call(call, &mut invoke_live_vars);
(stmt, invoke_live_vars)
}
Join {

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,17 @@ const GENERATE_NULLABLE: bool = true;
const DEFAULT_NUM_BUILTIN: Builtin<'_> = Builtin::Int64;
pub const TAG_SIZE: Builtin<'_> = Builtin::Int64;
impl Layout<'_> {
pub const TAG_SIZE: Layout<'static> = Layout::Builtin(Builtin::Int64);
}
#[derive(Copy, Clone)]
#[repr(u8)]
pub enum InPlace {
InPlace,
Clone,
}
#[derive(Debug, Clone)]
pub enum LayoutProblem {
UnresolvedTypeVar(Variable),
@ -29,14 +40,25 @@ pub enum Layout<'a> {
/// A layout that is empty (turns into the empty struct in LLVM IR
/// but for our purposes, not zero-sized, so it does not get dropped from data structures
/// this is important for closures that capture zero-sized values
PhantomEmptyStruct,
Struct(&'a [Layout<'a>]),
Union(UnionLayout<'a>),
RecursivePointer,
/// A function. The types of its arguments, then the type of its return value.
FunctionPointer(&'a [Layout<'a>], &'a Layout<'a>),
Closure(&'a [Layout<'a>], ClosureLayout<'a>, &'a Layout<'a>),
Pointer(&'a Layout<'a>),
Closure(&'a [Layout<'a>], LambdaSet<'a>, &'a Layout<'a>),
}
impl<'a> Layout<'a> {
pub fn in_place(&self) -> InPlace {
match self {
Layout::Builtin(Builtin::EmptyList) => InPlace::InPlace,
Layout::Builtin(Builtin::List(_)) => InPlace::Clone,
Layout::Builtin(Builtin::EmptyStr) => InPlace::InPlace,
Layout::Builtin(Builtin::Str) => InPlace::Clone,
Layout::Builtin(Builtin::Int1) => InPlace::Clone,
_ => unreachable!("Layout {:?} does not have an inplace", self),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -94,82 +116,101 @@ impl<'a> UnionLayout<'a> {
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct ClosureLayout<'a> {
/// the layout that this specific closure captures
/// uses a Vec instead of a MutMap because it's Hash
/// the vec is likely to be small, so linear search is fine
captured: &'a [(TagName, &'a [Layout<'a>])],
/// use with care; there is some stuff happening here re. unwrapping
/// one-element records that might cause issues
pub layout: &'a Layout<'a>,
pub struct LambdaSet<'a> {
/// collection of function names and their closure arguments
pub set: &'a [(Symbol, &'a [Layout<'a>])],
/// how the closure will be represented at runtime
representation: &'a Layout<'a>,
}
impl<'a> ClosureLayout<'a> {
fn from_unit(arena: &'a Bump) -> Self {
let layout = Layout::PhantomEmptyStruct;
let layouts = arena.alloc(layout);
ClosureLayout {
captured: &[],
layout: layouts,
}
}
fn from_bool(arena: &'a Bump) -> Self {
let layout = Layout::Builtin(Builtin::Int1);
let layouts = arena.alloc(layout);
ClosureLayout {
captured: &[],
layout: layouts,
}
}
fn from_byte(arena: &'a Bump) -> Self {
let layout = Layout::Builtin(Builtin::Int8);
let layouts = arena.alloc(layout);
ClosureLayout {
captured: &[],
layout: layouts,
}
}
fn from_unwrapped(arena: &'a Bump, layouts: &'a [Layout<'a>]) -> Self {
debug_assert!(!layouts.is_empty());
/// representation of the closure *for a particular function*
#[derive(Debug)]
pub enum ClosureRepresentation<'a> {
/// the closure is represented as a union. Includes the tag ID!
Union {
tag_layout: &'a [Layout<'a>],
tag_name: TagName,
tag_id: u8,
union_size: u8,
},
/// the representation is anything but a union
Other(Layout<'a>),
}
// DO NOT unwrap 1-element records here!
let layout = arena.alloc(Layout::Struct(layouts));
impl<'a> LambdaSet<'a> {
pub fn runtime_representation(&self) -> Layout<'a> {
*self.representation
}
ClosureLayout {
captured: &[],
layout,
pub fn layout_for_member(&self, function_symbol: Symbol) -> ClosureRepresentation<'a> {
debug_assert!(
self.set.iter().any(|(s, _)| *s == function_symbol),
"function symbol not in set"
);
match self.representation {
Layout::Union(union) => {
// here we rely on the fact that a union in a closure would be stored in a one-element record.
// a closure representation that is itself union must be a of the shape `Closure1 ... | Closure2 ...`
match union {
UnionLayout::NonRecursive(tags) => {
let index = self
.set
.iter()
.position(|(s, _)| *s == function_symbol)
.unwrap();
ClosureRepresentation::Union {
union_size: self.set.len() as u8,
tag_id: index as u8,
tag_layout: tags[index],
tag_name: TagName::Closure(function_symbol),
}
}
UnionLayout::Recursive(_) => todo!("recursive closures"),
UnionLayout::NonNullableUnwrapped(_) => todo!("recursive closures"),
UnionLayout::NullableWrapped {
nullable_id: _,
other_tags: _,
} => todo!("recursive closures"),
UnionLayout::NullableUnwrapped {
nullable_id: _,
other_fields: _,
} => todo!("recursive closures"),
}
}
_ => ClosureRepresentation::Other(*self.representation),
}
}
fn from_tag_union(arena: &'a Bump, tags: &'a [(TagName, &'a [Layout<'a>])]) -> Self {
debug_assert!(tags.len() > 1);
pub fn extend_function_layout(
&self,
arena: &'a Bump,
argument_layouts: &'a [Layout<'a>],
ret_layout: &'a Layout<'a>,
) -> Layout<'a> {
if let [] = self.set {
// TERRIBLE HACK for builting functions
Layout::FunctionPointer(argument_layouts, ret_layout)
} else {
match self.representation {
Layout::Struct(&[]) => {
// this function does not have anything in its closure, and the lambda set is a
// singleton, so we pass no extra argument
Layout::FunctionPointer(argument_layouts, ret_layout)
}
Layout::Builtin(Builtin::Int1) | Layout::Builtin(Builtin::Int8) => {
// we don't pass this along either
Layout::FunctionPointer(argument_layouts, ret_layout)
}
_ => {
let mut arguments = Vec::with_capacity_in(argument_layouts.len() + 1, arena);
arguments.extend(argument_layouts);
arguments.push(self.runtime_representation());
// if the closed-over value is actually a layout, it should be wrapped in a 1-element record
debug_assert!(matches!(tags[0].0, TagName::Closure(_)));
let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena);
for (_, tag_args) in tags.iter() {
tag_arguments.push(&tag_args[0..]);
}
ClosureLayout {
captured: tags,
layout: arena.alloc(Layout::Union(UnionLayout::NonRecursive(
tag_arguments.into_bump_slice(),
))),
}
}
pub fn get_wrapped(&self) -> crate::ir::Wrapped {
use crate::ir::Wrapped;
match self.layout {
Layout::Struct(fields) if fields.len() == 1 => Wrapped::SingleElementRecord,
Layout::Struct(_) => Wrapped::RecordOrSingleTagUnion,
Layout::Union(_) => Wrapped::MultiTagUnion,
_ => Wrapped::SingleElementRecord,
Layout::FunctionPointer(arguments.into_bump_slice(), ret_layout)
}
}
}
}
@ -177,209 +218,118 @@ impl<'a> ClosureLayout<'a> {
arena: &'a Bump,
subs: &Subs,
closure_var: Variable,
) -> Result<Option<Self>, LayoutProblem> {
) -> Result<Self, LayoutProblem> {
let mut tags = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) {
Ok(()) | Err((_, Content::FlexVar(_))) if !tags.is_empty() => {
// special-case the `[ Closure1, Closure2, Closure3 ]` case, where none of
// the tags have a payload
let all_no_payload = tags.iter().all(|(_, arguments)| arguments.is_empty());
// sort the tags; make sure ordering stays intact!
tags.sort();
if all_no_payload {
return Ok(None);
}
let mut set = Vec::with_capacity_in(tags.len(), arena);
// otherwise, this is a closure with a payload
let variant = union_sorted_tags_help(arena, tags, None, subs);
let mut env = Env {
arena,
subs,
seen: MutSet::default(),
};
use UnionVariant::*;
match variant {
Never => Ok(None),
Unit => Ok(None),
UnitWithArguments => {
// the closure layout is zero-sized, but there is something in it (e.g. `{}`)
for (tag_name, variables) in tags.iter() {
if let TagName::Closure(function_symbol) = tag_name {
let mut arguments = Vec::with_capacity_in(variables.len(), arena);
let closure_layout = ClosureLayout::from_unit(arena);
Ok(Some(closure_layout))
}
BoolUnion { .. } => {
let closure_layout = ClosureLayout::from_bool(arena);
Ok(Some(closure_layout))
}
ByteUnion(_) => {
let closure_layout = ClosureLayout::from_byte(arena);
Ok(Some(closure_layout))
}
Unwrapped(layouts) => {
let closure_layout =
ClosureLayout::from_unwrapped(arena, layouts.into_bump_slice());
Ok(Some(closure_layout))
}
Wrapped(variant) => {
use WrappedVariant::*;
match variant {
NonRecursive {
sorted_tag_layouts: tags,
} => {
let closure_layout =
ClosureLayout::from_tag_union(arena, tags.into_bump_slice());
Ok(Some(closure_layout))
}
_ => panic!("handle recursive layouts"),
for var in variables {
arguments.push(Layout::from_var(&mut env, *var)?);
}
set.push((*function_symbol, arguments.into_bump_slice()));
} else {
unreachable!("non-closure tag name in lambda set");
}
}
let representation = arena.alloc(Self::make_representation(arena, subs, tags));
Ok(LambdaSet {
set: set.into_bump_slice(),
representation,
})
}
Ok(()) | Err((_, Content::FlexVar(_))) => {
// a max closure size of 0 means this is a standart top-level function
Ok(None)
// TODO hack for builting functions.
Ok(LambdaSet {
set: &[],
representation: arena.alloc(Layout::Struct(&[])),
})
}
_ => panic!("called ClosureLayout.from_var on invalid input"),
_ => panic!("called LambdaSet.from_var on invalid input"),
}
}
pub fn extend_function_layout(
fn make_representation(
arena: &'a Bump,
argument_layouts: &'a [Layout<'a>],
closure_layout: Self,
ret_layout: &'a Layout<'a>,
subs: &Subs,
tags: std::vec::Vec<(TagName, std::vec::Vec<Variable>)>,
) -> Layout<'a> {
let closure_data_layout = match closure_layout.layout {
Layout::Struct(fields) if fields.len() == 1 => &fields[0],
other => other,
};
// otherwise, this is a closure with a payload
let variant = union_sorted_tags_help(arena, tags, None, subs);
// define the function pointer
let function_ptr_layout = {
let mut temp = Vec::from_iter_in(argument_layouts.iter().cloned(), arena);
temp.push(*closure_data_layout);
Layout::FunctionPointer(temp.into_bump_slice(), ret_layout)
};
use UnionVariant::*;
match variant {
Never | Unit | UnitWithArguments => Layout::Struct(&[]),
BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
ByteUnion(_) => Layout::Builtin(Builtin::Int8),
Unwrapped(_tag_name, layouts) => Layout::Struct(layouts.into_bump_slice()),
Wrapped(variant) => {
use WrappedVariant::*;
function_ptr_layout
match variant {
NonRecursive {
sorted_tag_layouts: tags,
} => {
debug_assert!(tags.len() > 1);
// if the closed-over value is actually a layout, it should be wrapped in a 1-element record
debug_assert!(matches!(tags[0].0, TagName::Closure(_)));
let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena);
for (_, tag_args) in tags.iter() {
tag_arguments.push(&tag_args[0..]);
}
Layout::Union(UnionLayout::NonRecursive(tag_arguments.into_bump_slice()))
}
_ => panic!("handle recursive layouts"),
}
}
}
}
pub fn get_wrapped(&self) -> crate::ir::Wrapped {
use crate::ir::Wrapped;
match self.representation {
Layout::Struct(fields) if fields.len() == 1 => Wrapped::SingleElementRecord,
Layout::Struct(_) => Wrapped::RecordOrSingleTagUnion,
Layout::Union(_) => Wrapped::MultiTagUnion,
_ => Wrapped::SingleElementRecord,
}
}
pub fn stack_size(&self, pointer_size: u32) -> u32 {
self.layout.stack_size(pointer_size)
self.representation.stack_size(pointer_size)
}
pub fn contains_refcounted(&self) -> bool {
self.layout.contains_refcounted()
self.representation.contains_refcounted()
}
pub fn safe_to_memcpy(&self) -> bool {
self.layout.safe_to_memcpy()
self.representation.safe_to_memcpy()
}
pub fn as_named_layout(&self, symbol: Symbol) -> Layout<'a> {
let layouts = if self.captured.is_empty() {
match self.layout {
Layout::Struct(fields) if fields.len() == 1 => fields[0],
other => *other,
}
} else if let Some((_, tag_args)) = self
.captured
.iter()
.find(|(tn, _)| *tn == TagName::Closure(symbol))
{
if tag_args.len() == 1 {
tag_args[0]
} else {
Layout::Struct(tag_args)
}
} else {
unreachable!(
"invariant broken, TagName::Closure({:?}) is not in {:?}",
symbol, &self.captured
);
};
layouts
pub fn alignment_bytes(&self, pointer_size: u32) -> u32 {
self.representation.alignment_bytes(pointer_size)
}
pub fn as_block_of_memory_layout(&self) -> Layout<'a> {
match self.layout {
Layout::Struct(fields) if fields.len() == 1 => fields[0],
other => *other,
}
}
pub fn internal_layout(&self) -> Layout<'a> {
*self.layout
}
pub fn build_closure_data(
&self,
original: Symbol,
symbols: &'a [Symbol],
) -> BuildClosureData<'a> {
use crate::ir::Expr;
match self.layout {
Layout::Struct(fields) if fields.len() == 1 => BuildClosureData::Alias(symbols[0]),
Layout::Struct(fields) => {
debug_assert!(fields.len() > 1);
debug_assert_eq!(fields.len(), symbols.len());
BuildClosureData::Struct(Expr::Struct(symbols))
}
Layout::Union(UnionLayout::NonRecursive(tags)) => {
// NOTE it's very important that this Union consists of Closure tags
// and is not an unpacked 1-element record
let tag_id = self
.captured
.iter()
.position(|(tn, _)| *tn == TagName::Closure(original))
.unwrap() as _;
BuildClosureData::Union {
tag_layout: Layout::Union(UnionLayout::NonRecursive(tags)),
tag_name: TagName::Closure(original),
tag_id,
union_size: tags.len() as u8,
}
}
Layout::PhantomEmptyStruct => {
debug_assert_eq!(symbols.len(), 1);
BuildClosureData::Struct(Expr::Struct(&[]))
}
_ => {
debug_assert_eq!(
symbols.len(),
1,
"symbols {:?} for layout {:?}",
&symbols,
&self.layout
);
BuildClosureData::Alias(symbols[0])
}
}
}
}
pub enum BuildClosureData<'a> {
Alias(Symbol),
Struct(crate::ir::Expr<'a>),
Union {
tag_layout: Layout<'a>,
tag_name: TagName,
tag_id: u8,
union_size: u8,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Hash, Copy)]
pub enum MemoryMode {
Unique,
Refcounted,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
@ -398,7 +348,7 @@ pub enum Builtin<'a> {
Str,
Dict(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>),
List(MemoryMode, &'a Layout<'a>),
List(&'a Layout<'a>),
EmptyStr,
EmptyList,
EmptyDict,
@ -423,6 +373,12 @@ impl<'a, 'b> Env<'a, 'b> {
self.seen.insert(var)
}
fn remove_seen(&mut self, var: Variable) -> bool {
let var = self.subs.get_root_key_without_compacting(var);
self.seen.remove(&var)
}
}
impl<'a> Layout<'a> {
@ -516,7 +472,6 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.safe_to_memcpy(),
PhantomEmptyStruct => true,
Struct(fields) => fields
.iter()
.all(|field_layout| field_layout.safe_to_memcpy()),
@ -541,10 +496,6 @@ impl<'a> Layout<'a> {
true
}
Closure(_, closure_layout, _) => closure_layout.safe_to_memcpy(),
Pointer(_) => {
// We cannot memcpy pointers, because then we would have the same pointer in multiple places!
false
}
RecursivePointer => {
// We cannot memcpy pointers, because then we would have the same pointer in multiple places!
false
@ -556,12 +507,7 @@ impl<'a> Layout<'a> {
// For this calculation, we don't need an accurate
// stack size, we just need to know whether it's zero,
// so it's fine to use a pointer size of 1.
if let Layout::PhantomEmptyStruct = self {
false
} else {
// self.stack_size(1) == 0
false
}
false
}
pub fn stack_size(&self, pointer_size: u32) -> u32 {
@ -569,7 +515,6 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.stack_size(pointer_size),
PhantomEmptyStruct => 0,
Struct(fields) => {
let mut sum = 0;
@ -600,10 +545,9 @@ impl<'a> Layout<'a> {
| NonNullableUnwrapped(_) => pointer_size,
}
}
Closure(_, closure_layout, _) => pointer_size + closure_layout.stack_size(pointer_size),
Closure(_, lambda_set, _) => lambda_set.stack_size(pointer_size),
FunctionPointer(_, _) => pointer_size,
RecursivePointer => pointer_size,
Pointer(_) => pointer_size,
}
}
@ -633,12 +577,10 @@ impl<'a> Layout<'a> {
}
}
Layout::Builtin(builtin) => builtin.alignment_bytes(pointer_size),
Layout::PhantomEmptyStruct => 0,
Layout::RecursivePointer => pointer_size,
Layout::FunctionPointer(_, _) => pointer_size,
Layout::Pointer(_) => pointer_size,
Layout::Closure(_, captured, _) => {
pointer_size.max(captured.layout.alignment_bytes(pointer_size))
pointer_size.max(captured.alignment_bytes(pointer_size))
}
}
}
@ -659,7 +601,7 @@ impl<'a> Layout<'a> {
RecursivePointer => true,
Builtin(List(MemoryMode::Refcounted, _)) | Builtin(Str) => true,
Builtin(List(_)) | Builtin(Str) => true,
_ => false,
}
@ -673,7 +615,6 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.is_refcounted(),
PhantomEmptyStruct => false,
Struct(fields) => fields.iter().any(|f| f.contains_refcounted()),
Union(variant) => {
use UnionLayout::*;
@ -692,7 +633,7 @@ impl<'a> Layout<'a> {
}
RecursivePointer => true,
Closure(_, closure_layout, _) => closure_layout.contains_refcounted(),
FunctionPointer(_, _) | Pointer(_) => false,
FunctionPointer(_, _) => false,
}
}
@ -706,7 +647,6 @@ impl<'a> Layout<'a> {
match self {
Builtin(builtin) => builtin.to_doc(alloc, parens),
PhantomEmptyStruct => alloc.text("{}"),
Struct(fields) => {
let fields_doc = fields.iter().map(|x| x.to_doc(alloc, parens));
@ -728,7 +668,9 @@ impl<'a> Layout<'a> {
Closure(args, closure_layout, result) => {
let args_doc = args.iter().map(|x| x.to_doc(alloc, Parens::InFunction));
let bom = closure_layout.layout.to_doc(alloc, Parens::NotNeeded);
let bom = closure_layout
.representation
.to_doc(alloc, Parens::NotNeeded);
alloc
.intersperse(args_doc, ", ")
@ -737,7 +679,6 @@ impl<'a> Layout<'a> {
.append(" |} -> ")
.append(result.to_doc(alloc, Parens::InFunction))
}
Pointer(_) => todo!(),
}
}
}
@ -909,7 +850,7 @@ impl<'a> Builtin<'a> {
Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size,
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
List(_, _) | EmptyList => Builtin::LIST_WORDS * pointer_size,
List(_) | EmptyList => Builtin::LIST_WORDS * pointer_size,
}
}
@ -935,7 +876,7 @@ impl<'a> Builtin<'a> {
Str | EmptyStr => pointer_size,
Dict(_, _) | EmptyDict => pointer_size,
Set(_) | EmptySet => pointer_size,
List(_, _) | EmptyList => pointer_size,
List(_) | EmptyList => pointer_size,
}
}
@ -945,7 +886,7 @@ impl<'a> Builtin<'a> {
match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
Str | Dict(_, _) | Set(_) | List(_, _) => false,
Str | Dict(_, _) | Set(_) | List(_) => false,
}
}
@ -956,10 +897,7 @@ impl<'a> Builtin<'a> {
match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Float128 | Float64 | Float32
| Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
List(mode, element_layout) => match mode {
MemoryMode::Refcounted => true,
MemoryMode::Unique => element_layout.contains_refcounted(),
},
List(_) => true,
Str | Dict(_, _) | Set(_) => true,
}
@ -992,7 +930,7 @@ impl<'a> Builtin<'a> {
EmptySet => alloc.text("EmptySet"),
Str => alloc.text("Str"),
List(_, layout) => alloc
List(layout) => alloc
.text("List ")
.append(layout.to_doc(alloc, Parens::InTypeParam)),
Set(layout) => alloc
@ -1108,10 +1046,9 @@ fn layout_from_flat_type<'a>(
let fn_args = fn_args.into_bump_slice();
let ret = arena.alloc(ret);
match ClosureLayout::from_var(env.arena, env.subs, closure_var)? {
Some(closure_layout) => Ok(Layout::Closure(fn_args, closure_layout, ret)),
None => Ok(Layout::FunctionPointer(fn_args, ret)),
}
let lambda_set = LambdaSet::from_var(env.arena, env.subs, closure_var)?;
Ok(Layout::Closure(fn_args, lambda_set, ret))
}
Record(fields, ext_var) => {
// extract any values from the ext_var
@ -1155,6 +1092,14 @@ fn layout_from_flat_type<'a>(
Ok(layout_from_tag_union(arena, tags, subs))
}
FunctionOrTagUnion(tag_name, _, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
let mut tags = MutMap::default();
tags.insert(tag_name, vec![]);
Ok(layout_from_tag_union(arena, tags, subs))
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
debug_assert!(ext_var_is_empty_tag_union(subs, ext_var));
@ -1240,6 +1185,8 @@ fn layout_from_flat_type<'a>(
UnionLayout::Recursive(tag_layouts.into_bump_slice())
};
env.remove_seen(rec_var);
Ok(Layout::Union(union_layout))
}
EmptyTagUnion => {
@ -1320,7 +1267,7 @@ pub enum UnionVariant<'a> {
UnitWithArguments,
BoolUnion { ttrue: TagName, ffalse: TagName },
ByteUnion(Vec<'a, TagName>),
Unwrapped(Vec<'a, Layout<'a>>),
Unwrapped(TagName, Vec<'a, Layout<'a>>),
Wrapped(WrappedVariant<'a>),
}
@ -1542,7 +1489,7 @@ pub fn union_sorted_tags_help<'a>(
fields: layouts.into_bump_slice(),
})
} else {
UnionVariant::Unwrapped(layouts)
UnionVariant::Unwrapped(tag_name, layouts)
}
}
num_tags => {
@ -1694,7 +1641,7 @@ pub fn layout_from_tag_union<'a>(
Unit | UnitWithArguments => Layout::Struct(&[]),
BoolUnion { .. } => Layout::Builtin(Builtin::Int1),
ByteUnion(_) => Layout::Builtin(Builtin::Int8),
Unwrapped(mut field_layouts) => {
Unwrapped(_, mut field_layouts) => {
if field_layouts.len() == 1 {
field_layouts.pop().unwrap()
} else {
@ -1923,11 +1870,7 @@ pub fn list_layout_from_elem<'a>(
_ => {
let elem_layout = Layout::from_var(env, elem_var)?;
// This is a normal list.
Ok(Layout::Builtin(Builtin::List(
MemoryMode::Refcounted,
env.arena.alloc(elem_layout),
)))
Ok(Layout::Builtin(Builtin::List(env.arena.alloc(elem_layout))))
}
}
}
@ -1958,7 +1901,7 @@ pub struct LayoutIds<'a> {
impl<'a> LayoutIds<'a> {
/// Returns a LayoutId which is unique for the given symbol and layout.
/// If given the same symbol and same layout, returns the same LayoutId.
pub fn get(&mut self, symbol: Symbol, layout: &Layout<'a>) -> LayoutId {
pub fn get<'b>(&mut self, symbol: Symbol, layout: &'b Layout<'a>) -> LayoutId {
// Note: this function does some weird stuff to satisfy the borrow checker.
// There's probably a nicer way to write it that still works.
let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout {
@ -1967,7 +1910,7 @@ impl<'a> LayoutIds<'a> {
});
// Get the id associated with this layout, or default to next_id.
let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id);
let answer = ids.by_id.get(&layout).copied().unwrap_or(ids.next_id);
// If we had to default to next_id, it must not have been found;
// store the ID we're going to return and increment next_id.
@ -1980,3 +1923,21 @@ impl<'a> LayoutIds<'a> {
LayoutId(answer)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum ListLayout<'a> {
EmptyList,
List(&'a Layout<'a>),
}
impl<'a> std::convert::TryFrom<&Layout<'a>> for ListLayout<'a> {
type Error = ();
fn try_from(value: &Layout<'a>) -> Result<Self, Self::Error> {
match value {
Layout::Builtin(Builtin::EmptyList) => Ok(ListLayout::EmptyList),
Layout::Builtin(Builtin::List(element)) => Ok(ListLayout::List(element)),
_ => Err(()),
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -100,6 +100,8 @@ mod test_reporting {
home,
ident_ids: &mut ident_ids,
ptr_bytes: 8,
update_mode_counter: 0,
call_specialization_counter: 0,
};
let _mono_expr = Stmt::new(
&mut mono_env,

View File

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

View File

@ -728,6 +728,27 @@ fn type_to_variable(
register(subs, rank, pools, content)
}
FunctionOrTagUnion(tag_name, symbol, ext) => {
let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext);
let mut ext_tag_vec = Vec::new();
let new_ext_var = match roc_types::pretty_print::chase_ext_tag_union(
subs,
temp_ext_var,
&mut ext_tag_vec,
) {
Ok(()) => Variable::EMPTY_TAG_UNION,
Err((new, _)) => new,
};
debug_assert!(ext_tag_vec.is_empty());
let content = Content::Structure(FlatType::FunctionOrTagUnion(
tag_name.clone(),
*symbol,
new_ext_var,
));
register(subs, rank, pools, content)
}
RecursiveTagUnion(rec_var, tags, ext) => {
let mut tag_vars = MutMap::default();
@ -1134,6 +1155,10 @@ fn adjust_rank_content(
rank
}
FunctionOrTagUnion(_, _, ext_var) => {
adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var)
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, *ext_var);
@ -1309,6 +1334,12 @@ fn instantiate_rigids_help(
)
}
FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion(
tag_name,
symbol,
instantiate_rigids_help(subs, max_rank, pools, ext_var),
),
RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut new_tags = MutMap::default();
@ -1495,6 +1526,12 @@ fn deep_copy_var_help(
TagUnion(new_tags, deep_copy_var_help(subs, max_rank, pools, ext_var))
}
FunctionOrTagUnion(tag_name, symbol, ext_var) => FunctionOrTagUnion(
tag_name,
symbol,
deep_copy_var_help(subs, max_rank, pools, ext_var),
),
RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut new_tags = MutMap::default();

View File

@ -1,18 +1,8 @@
#[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_num {
mod gen_compare {
use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use indoc::indoc;
#[test]
fn eq_i64() {

View File

@ -3,9 +3,30 @@
use crate::assert_evals_to;
use crate::assert_llvm_evals_to;
use crate::helpers::with_larger_debug_stack;
use core::ffi::c_void;
use indoc::indoc;
use roc_std::{RocList, RocStr};
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
#[no_mangle]
pub unsafe fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
_alignment: u32,
) -> *mut c_void {
libc::realloc(c_ptr, new_size)
}
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
#[test]
fn roc_list_construction() {
let list = RocList::from_slice(&[1i64; 23]);
@ -147,6 +168,17 @@ fn list_append() {
);
}
#[test]
fn list_drop() {
assert_evals_to!(
"List.drop [1,2,3] 2",
RocList::from_slice(&[3]),
RocList<i64>
);
assert_evals_to!("List.drop [] 1", RocList::from_slice(&[]), RocList<i64>);
assert_evals_to!("List.drop [1,2] 5", RocList::from_slice(&[]), RocList<i64>);
}
#[test]
fn list_append_to_empty_list() {
assert_evals_to!("List.append [] 3", RocList::from_slice(&[3]), RocList<i64>);
@ -629,7 +661,8 @@ fn list_map2_pair() {
assert_evals_to!(
indoc!(
r#"
List.map2 [1,2,3] [3,2,1] (\a,b -> Pair a b)
f = (\a,b -> Pair a b)
List.map2 [1,2,3] [3,2,1] f
"#
),
RocList::from_slice(&[(1, 3), (2, 2), (3, 1)]),
@ -645,7 +678,7 @@ fn list_map2_different_lengths() {
List.map2
["a", "b", "lllllllllllllongnggg" ]
["b"]
Str.concat
(\a, b -> Str.concat a b)
"#
),
RocList::from_slice(&[RocStr::from_slice("ab".as_bytes()),]),
@ -1806,10 +1839,15 @@ fn list_keep_errs() {
assert_evals_to!("List.keepErrs [] (\\x -> x)", 0, i64);
assert_evals_to!("List.keepErrs [1,2] (\\x -> Err x)", &[1, 2], &[i64]);
assert_evals_to!(
"List.keepErrs [0,1,2] (\\x -> x % 0 |> Result.mapErr (\\_ -> 32))",
indoc!(
r#"
List.keepErrs [0,1,2] (\x -> x % 0 |> Result.mapErr (\_ -> 32))
"#
),
&[32, 32, 32],
&[i64]
);
assert_evals_to!("List.keepErrs [Ok 1, Err 2] (\\x -> x)", &[2], &[i64]);
}

View File

@ -913,7 +913,7 @@ fn annotation_without_body() {
}
#[test]
fn closure() {
fn simple_closure() {
assert_evals_to!(
indoc!(
r#"
@ -992,18 +992,19 @@ fn specialize_closure() {
foo = \{} ->
x = 41
y = 1
y = [1]
f = \{} -> x
g = \{} -> x + y
g = \{} -> x + List.len y
[ f, g ]
apply = \f -> f {}
main =
items = foo {}
# List.len items
List.map items (\f -> f {})
List.map items apply
"#
),
RocList::from_slice(&[41, 42]),
@ -1026,7 +1027,7 @@ fn io_poc_effect() {
runEffect : Effect a -> a
runEffect = \@Effect thunk -> thunk {}
foo : Effect F64
foo : Effect F64
foo =
succeed 3.14
@ -1049,16 +1050,16 @@ fn io_poc_desugared() {
app "test" provides [ main ] to "./platform"
# succeed : a -> ({} -> a)
succeed = \x -> \{} -> x
succeed = \x -> \_ -> x
foo : {} -> F64
foo : Str -> F64
foo =
succeed 3.14
# runEffect : ({} -> a) -> a
runEffect = \thunk -> thunk {}
runEffect = \thunk -> thunk ""
main : F64
main : F64
main =
runEffect foo
"#
@ -1090,6 +1091,27 @@ fn return_wrapped_function_pointer() {
);
}
#[test]
fn return_wrapped_function_pointer_b() {
assert_non_opt_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
foo : { x: (I64 -> Str) }
foo = { x: (\_ -> "foobar") }
main : { x: (I64 -> Str) }
main = foo
"#
),
1,
i64,
|_| 1
);
}
#[test]
fn return_wrapped_closure() {
assert_non_opt_evals_to!(
@ -1243,7 +1265,7 @@ fn recursive_functon_with_rigid() {
State a : { count : I64, x : a }
foo : State a -> Int *
foo : State a -> Int *
foo = \state ->
if state.count == 0 then
0
@ -1634,13 +1656,13 @@ fn binary_tree_double_pattern_match() {
BTree : [ Node BTree BTree, Leaf I64 ]
foo : BTree -> I64
foo : BTree -> I64
foo = \btree ->
when btree is
Node (Node (Leaf x) _) _ -> x
_ -> 0
main : I64
main : I64
main =
foo (Node (Node (Leaf 32) (Leaf 0)) (Leaf 0))
"#
@ -1651,7 +1673,7 @@ fn binary_tree_double_pattern_match() {
}
#[test]
fn unified_empty_closure() {
fn unified_empty_closure_bool() {
// none of the Closure tags will have a payload
// this was not handled correctly in the past
assert_non_opt_evals_to!(
@ -1674,6 +1696,31 @@ fn unified_empty_closure() {
);
}
#[test]
fn unified_empty_closure_byte() {
// none of the Closure tags will have a payload
// this was not handled correctly in the past
assert_non_opt_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
foo = \{} ->
when A is
A -> (\_ -> 3.14)
B -> (\_ -> 3.14)
C -> (\_ -> 3.14)
main : Float *
main =
(foo {}) 0
"#
),
3.14,
f64
);
}
#[test]
fn task_always_twice() {
assert_non_opt_evals_to!(
@ -2227,6 +2274,37 @@ fn build_then_apply_closure() {
}
#[test]
fn expanded_result() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
a : Result I64 Str
a = Ok 4
after = \x, f ->
when x is
Ok v -> f v
Err e -> Err e
main : I64
main =
helper = after a (\x -> Ok x)
when helper is
Ok v -> v
Err _ -> 0
"#
),
4,
i64
);
}
#[test]
#[ignore]
fn backpassing_result() {
assert_evals_to!(
indoc!(
@ -2242,7 +2320,7 @@ fn backpassing_result() {
main : I64
main =
helper =
x <- Result.after a
x <- Result.after a
y <- Result.after (f x)
z <- Result.after (g y)
@ -2250,7 +2328,7 @@ fn backpassing_result() {
helper
|> Result.withDefault 0
"#
),
4,
@ -2311,3 +2389,79 @@ fn expect_fail() {
i64
);
}
#[test]
fn increment_or_double_closure() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
apply : (a -> a), a -> a
apply = \f, x -> f x
main =
one : I64
one = 1
two : I64
two = 2
b : Bool
b = True
increment : I64 -> I64
increment = \x -> x + one
double : I64 -> I64
double = \x -> if b then x * two else x
f = (if True then increment else double)
apply f 42
"#
),
43,
i64
);
}
#[test]
fn module_thunk_is_function() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main = helper "foo" "bar"
helper = Str.concat
"#
),
RocStr::from_slice(b"foobar"),
RocStr
);
}
#[test]
#[should_panic(expected = "Roc failed with message: ")]
fn hit_unresolved_type_variable() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main : Str
main =
(accept Bool.isEq) "B"
accept : * -> (b -> b)
accept = \_ ->
\input -> input
"#
),
RocStr::from_slice(b"B"),
RocStr
);
}

View File

@ -4,6 +4,7 @@ use roc_build::program::FunctionIterator;
use roc_can::builtins::builtin_defs_map;
use roc_can::def::Def;
use roc_collections::all::{MutMap, MutSet};
use roc_gen::llvm::externs::add_default_roc_externs;
use roc_module::symbol::Symbol;
use roc_types::subs::VarStore;
@ -179,12 +180,16 @@ pub fn helper<'a>(
),
};
let builder = context.create_builder();
let module = roc_gen::llvm::build::module_from_builtins(context, "app");
// Add roc_alloc, roc_realloc, and roc_dealloc, since the repl has no
// platform to provide them.
add_default_roc_externs(context, &module, &builder, ptr_bytes);
// strip Zig debug stuff
module.strip_debug_info();
let builder = context.create_builder();
let opt_level = if cfg!(debug_assertions) {
roc_gen::llvm::build::OptLevel::Normal
} else {
@ -239,12 +244,12 @@ pub fn helper<'a>(
// because their bodies may reference each other.
let mut scope = Scope::default();
for ((symbol, layout), proc) in procedures.drain() {
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc);
let fn_val = build_proc_header(&env, &mut layout_ids, symbol, layout, &proc);
if proc.args.is_empty() {
// this is a 0-argument thunk, i.e. a top-level constant definition
// it must be in-scope everywhere in the module!
scope.insert_top_level_thunk(symbol, layout, fn_val);
scope.insert_top_level_thunk(symbol, arena.alloc(layout), fn_val);
}
headers.push((proc, fn_val));
@ -275,7 +280,7 @@ pub fn helper<'a>(
mode,
);
// fn_val.print_to_stderr();
fn_val.print_to_stderr();
// module.print_to_stderr();
panic!(
@ -289,7 +294,7 @@ pub fn helper<'a>(
&env,
&mut layout_ids,
main_fn_symbol,
&main_fn_layout,
main_fn_layout,
);
env.dibuilder.finalize();

View File

@ -1,9 +1,10 @@
#![warn(clippy::all, clippy::dbg_macro)]
#![warn(clippy::dbg_macro)]
// See github.com/rtfeldman/roc/issues/800 for discussion of the large_enum_variant check.
#![allow(clippy::large_enum_variant)]
// we actually want to compare against the literal float bits
#![allow(clippy::clippy::float_cmp)]
pub mod gen_compare;
pub mod gen_dict;
pub mod gen_hash;
pub mod gen_list;

View File

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

View File

@ -179,6 +179,9 @@ fn find_names_needed(
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
}
Structure(FunctionOrTagUnion(_, _, ext_var)) => {
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
}
Structure(RecursiveTagUnion(rec_var, tags, ext_var)) => {
let mut sorted_tags: Vec<_> = tags.iter().collect();
sorted_tags.sort();
@ -487,6 +490,28 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String
}
}
FunctionOrTagUnion(tag_name, _, ext_var) => {
let interns = &env.interns;
let home = env.home;
buf.push_str("[ ");
buf.push_str(&tag_name.as_string(&interns, home));
buf.push_str(" ]");
let mut sorted_fields = vec![(tag_name, vec![])];
let ext_content = chase_ext_tag_union(subs, ext_var, &mut sorted_fields);
if let Err((_, content)) = ext_content {
// This is an open tag union, so print the variable
// right after the ']'
//
// e.g. the "*" at the end of `{ x: I64 }*`
// or the "r" at the end of `{ x: I64 }r`
write_content(env, content, subs, buf, parens)
}
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let interns = &env.interns;
let home = env.home;
@ -570,6 +595,11 @@ pub fn chase_ext_tag_union(
chase_ext_tag_union(subs, ext_var, fields)
}
Content::Structure(FunctionOrTagUnion(tag_name, _, ext_var)) => {
fields.push((tag_name, vec![]));
chase_ext_tag_union(subs, ext_var, fields)
}
Content::Alias(_, _, var) => chase_ext_tag_union(subs, var, fields),
content => Err((var, content)),

View File

@ -104,6 +104,10 @@ fn hash_solved_type_help<H: Hasher>(
hash_solved_type_help(ext, flex_vars, state);
}
FunctionOrTagUnion(_, _, ext) => {
hash_solved_type_help(ext, flex_vars, state);
}
RecursiveTagUnion(rec, tags, ext) => {
var_id_hash_help(*rec, flex_vars, state);
for (name, arguments) in tags {
@ -172,6 +176,7 @@ pub enum SolvedType {
},
EmptyRecord,
TagUnion(Vec<(TagName, Vec<SolvedType>)>, Box<SolvedType>),
FunctionOrTagUnion(TagName, Symbol, Box<SolvedType>),
RecursiveTagUnion(VarId, Vec<(TagName, Vec<SolvedType>)>, Box<SolvedType>),
EmptyTagUnion,
/// A type from an Invalid module
@ -263,6 +268,10 @@ impl SolvedType {
SolvedType::TagUnion(solved_tags, Box::new(solved_ext))
}
FunctionOrTagUnion(tag_name, symbol, box_ext) => {
let solved_ext = Self::from_type(solved_subs, box_ext);
SolvedType::FunctionOrTagUnion(tag_name.clone(), *symbol, Box::new(solved_ext))
}
RecursiveTagUnion(rec_var, tags, box_ext) => {
let solved_ext = Self::from_type(solved_subs, box_ext);
let mut solved_tags = Vec::with_capacity(tags.len());
@ -423,6 +432,11 @@ impl SolvedType {
SolvedType::TagUnion(new_tags, Box::new(ext))
}
FunctionOrTagUnion(tag_name, symbol, ext_var) => {
let ext = Self::from_var_help(subs, recursion_vars, ext_var);
SolvedType::FunctionOrTagUnion(tag_name, symbol, Box::new(ext))
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
recursion_vars.insert(subs, rec_var);
@ -562,6 +576,11 @@ pub fn to_type(
Type::TagUnion(new_tags, Box::new(to_type(ext, free_vars, var_store)))
}
FunctionOrTagUnion(tag_name, symbol, ext) => Type::FunctionOrTagUnion(
tag_name.clone(),
*symbol,
Box::new(to_type(ext, free_vars, var_store)),
),
RecursiveTagUnion(rec_var_id, tags, ext) => {
let mut new_tags = Vec::with_capacity(tags.len());

View File

@ -606,6 +606,7 @@ pub enum FlatType {
Func(Vec<Variable>, Variable, Variable),
Record(MutMap<Lowercase, RecordField<Variable>>, Variable),
TagUnion(MutMap<TagName, Vec<Variable>>, Variable),
FunctionOrTagUnion(TagName, Symbol, Variable),
RecursiveTagUnion(Variable, MutMap<TagName, Vec<Variable>>, Variable),
Erroneous(Problem),
EmptyRecord,
@ -662,6 +663,10 @@ fn occurs(
let it = once(&ext_var).chain(tags.values().flatten());
short_circuit(subs, root_var, &new_seen, it)
}
FunctionOrTagUnion(_, _, ext_var) => {
let it = once(&ext_var);
short_circuit(subs, root_var, &new_seen, it)
}
RecursiveTagUnion(_rec_var, tags, ext_var) => {
// TODO rec_var is excluded here, verify that this is correct
let it = once(&ext_var).chain(tags.values().flatten());
@ -752,6 +757,13 @@ fn explicit_substitute(
}
subs.set_content(in_var, Structure(TagUnion(tags, new_ext_var)));
}
FunctionOrTagUnion(tag_name, symbol, ext_var) => {
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
subs.set_content(
in_var,
Structure(FunctionOrTagUnion(tag_name, symbol, new_ext_var)),
);
}
RecursiveTagUnion(rec_var, mut tags, ext_var) => {
// NOTE rec_var is not substituted, verify that this is correct!
let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen);
@ -891,6 +903,10 @@ fn get_var_names(
taken_names
}
FlatType::FunctionOrTagUnion(_, _, ext_var) => {
get_var_names(subs, ext_var, taken_names)
}
FlatType::RecursiveTagUnion(rec_var, tags, ext_var) => {
let taken_names = get_var_names(subs, ext_var, taken_names);
let mut taken_names = get_var_names(subs, rec_var, taken_names);
@ -1142,6 +1158,32 @@ fn flat_type_to_err_type(
}
}
FunctionOrTagUnion(tag_name, _, ext_var) => {
let mut err_tags = SendMap::default();
err_tags.insert(tag_name, vec![]);
match var_to_err_type(subs, state, ext_var).unwrap_alias() {
ErrorType::TagUnion(sub_tags, sub_ext) => {
ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext)
}
ErrorType::RecursiveTagUnion(_, sub_tags, sub_ext) => {
ErrorType::TagUnion(sub_tags.union(err_tags), sub_ext)
}
ErrorType::FlexVar(var) => {
ErrorType::TagUnion(err_tags, TypeExt::FlexOpen(var))
}
ErrorType::RigidVar(var) => {
ErrorType::TagUnion(err_tags, TypeExt::RigidOpen(var))
}
other =>
panic!("Tried to convert a tag union extension to an error, but the tag union extension had the ErrorType of {:?}", other)
}
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut err_tags = SendMap::default();
@ -1236,6 +1278,9 @@ fn restore_content(subs: &mut Subs, content: &Content) {
subs.restore(*ext_var);
}
FunctionOrTagUnion(_, _, ext_var) => {
subs.restore(*ext_var);
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
for var in tags.values().flatten() {

View File

@ -141,6 +141,7 @@ pub enum Type {
Function(Vec<Type>, Box<Type>, Box<Type>),
Record(SendMap<Lowercase, RecordField<Type>>, Box<Type>),
TagUnion(Vec<(TagName, Vec<Type>)>, Box<Type>),
FunctionOrTagUnion(TagName, Symbol, Box<Type>),
Alias(Symbol, Vec<(Lowercase, Type)>, Box<Type>),
HostExposedAlias {
name: Symbol,
@ -308,6 +309,26 @@ impl fmt::Debug for Type {
}
}
}
Type::FunctionOrTagUnion(tag_name, _, ext) => {
write!(f, "[")?;
write!(f, "{:?}", tag_name)?;
write!(f, "]")?;
match *ext.clone() {
Type::EmptyTagUnion => {
// This is a closed variant. We're done!
Ok(())
}
other => {
// This is an open tag union, so print the variable
// right after the ']'
//
// e.g. the "*" at the end of `[ Foo ]*`
// or the "r" at the end of `[ DivByZero ]r`
other.fmt(f)
}
}
}
Type::RecursiveTagUnion(rec, tags, ext) => {
write!(f, "[")?;
@ -404,6 +425,9 @@ impl Type {
}
ext.substitute(substitutions);
}
FunctionOrTagUnion(_, _, ext) => {
ext.substitute(substitutions);
}
RecursiveTagUnion(_, tags, ext) => {
for (_, args) in tags {
for x in args {
@ -456,6 +480,9 @@ impl Type {
closure.substitute_alias(rep_symbol, actual);
ret.substitute_alias(rep_symbol, actual);
}
FunctionOrTagUnion(_, _, ext) => {
ext.substitute_alias(rep_symbol, actual);
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags {
for x in args {
@ -506,6 +533,7 @@ impl Type {
|| closure.contains_symbol(rep_symbol)
|| args.iter().any(|arg| arg.contains_symbol(rep_symbol))
}
FunctionOrTagUnion(_, _, ext) => ext.contains_symbol(rep_symbol),
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
ext.contains_symbol(rep_symbol)
|| tags
@ -541,6 +569,7 @@ impl Type {
|| closure.contains_variable(rep_variable)
|| args.iter().any(|arg| arg.contains_variable(rep_variable))
}
FunctionOrTagUnion(_, _, ext) => ext.contains_variable(rep_variable),
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
ext.contains_variable(rep_variable)
|| tags
@ -595,6 +624,9 @@ impl Type {
closure.instantiate_aliases(region, aliases, var_store, introduced);
ret.instantiate_aliases(region, aliases, var_store, introduced);
}
FunctionOrTagUnion(_, _, ext) => {
ext.instantiate_aliases(region, aliases, var_store, introduced);
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags {
for x in args {
@ -734,6 +766,9 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
symbols_help(&closure, accum);
args.iter().for_each(|arg| symbols_help(arg, accum));
}
FunctionOrTagUnion(_, _, ext) => {
symbols_help(&ext, accum);
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
symbols_help(&ext, accum);
tags.iter()
@ -807,6 +842,9 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
}
variables_help(ext, accum);
}
FunctionOrTagUnion(_, _, ext) => {
variables_help(ext, accum);
}
RecursiveTagUnion(rec, tags, ext) => {
for (_, args) in tags {
for x in args {
@ -900,6 +938,9 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
}
variables_help_detailed(ext, accum);
}
FunctionOrTagUnion(_, _, ext) => {
variables_help_detailed(ext, accum);
}
RecursiveTagUnion(rec, tags, ext) => {
for (_, args) in tags {
for x in args {

View File

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

View File

@ -237,6 +237,10 @@ fn unify_structure(
// unify the structure with this recursive tag union
unify_pool(subs, pool, ctx.first, *structure)
}
FlatType::FunctionOrTagUnion(_, _, _) => {
// unify the structure with this unrecursive tag union
unify_pool(subs, pool, ctx.first, *structure)
}
_ => todo!("rec structure {:?}", &flat_type),
},
@ -978,12 +982,106 @@ fn unify_flat_type(
problems
}
}
(TagUnion(tags, ext), Func(args, closure, ret)) if tags.len() == 1 => {
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, true)
(FunctionOrTagUnion(tag_name, tag_symbol, ext), Func(args, closure, ret)) => {
unify_function_or_tag_union_and_func(
subs,
pool,
ctx,
tag_name,
*tag_symbol,
*ext,
args,
*ret,
*closure,
true,
)
}
(Func(args, closure, ret), TagUnion(tags, ext)) if tags.len() == 1 => {
unify_tag_union_and_func(tags, args, subs, pool, ctx, ext, ret, closure, false)
(Func(args, closure, ret), FunctionOrTagUnion(tag_name, tag_symbol, ext)) => {
unify_function_or_tag_union_and_func(
subs,
pool,
ctx,
tag_name,
*tag_symbol,
*ext,
args,
*ret,
*closure,
false,
)
}
(FunctionOrTagUnion(tag_name_1, _, ext_1), FunctionOrTagUnion(tag_name_2, _, ext_2)) => {
if tag_name_1 == tag_name_2 {
let problems = unify_pool(subs, pool, *ext_1, *ext_2);
if problems.is_empty() {
let desc = subs.get(ctx.second);
merge(subs, ctx, desc.content)
} else {
problems
}
} else {
let mut tags1 = MutMap::default();
tags1.insert(tag_name_1.clone(), vec![]);
let union1 = gather_tags(subs, tags1, *ext_1);
let mut tags2 = MutMap::default();
tags2.insert(tag_name_2.clone(), vec![]);
let union2 = gather_tags(subs, tags2, *ext_2);
unify_tag_union(subs, pool, ctx, union1, union2, (None, None))
}
}
(TagUnion(tags1, ext1), FunctionOrTagUnion(tag_name, _, ext2)) => {
let union1 = gather_tags(subs, tags1.clone(), *ext1);
let mut tags2 = MutMap::default();
tags2.insert(tag_name.clone(), vec![]);
let union2 = gather_tags(subs, tags2, *ext2);
unify_tag_union(subs, pool, ctx, union1, union2, (None, None))
}
(FunctionOrTagUnion(tag_name, _, ext1), TagUnion(tags2, ext2)) => {
let mut tags1 = MutMap::default();
tags1.insert(tag_name.clone(), vec![]);
let union1 = gather_tags(subs, tags1, *ext1);
let union2 = gather_tags(subs, tags2.clone(), *ext2);
unify_tag_union(subs, pool, ctx, union1, union2, (None, None))
}
(RecursiveTagUnion(recursion_var, tags1, ext1), FunctionOrTagUnion(tag_name, _, ext2)) => {
// this never happens in type-correct programs, but may happen if there is a type error
debug_assert!(is_recursion_var(subs, *recursion_var));
let mut tags2 = MutMap::default();
tags2.insert(tag_name.clone(), vec![]);
let union1 = gather_tags(subs, tags1.clone(), *ext1);
let union2 = gather_tags(subs, tags2, *ext2);
unify_tag_union(
subs,
pool,
ctx,
union1,
union2,
(Some(*recursion_var), None),
)
}
(FunctionOrTagUnion(tag_name, _, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
debug_assert!(is_recursion_var(subs, *recursion_var));
let mut tags1 = MutMap::default();
tags1.insert(tag_name.clone(), vec![]);
let union1 = gather_tags(subs, tags1, *ext1);
let union2 = gather_tags(subs, tags2.clone(), *ext2);
unify_tag_union_not_recursive_recursive(subs, pool, ctx, union1, union2, *recursion_var)
}
(other1, other2) => mismatch!(
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
other1,
@ -1166,53 +1264,72 @@ fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
)
}
#[allow(clippy::too_many_arguments, clippy::ptr_arg)]
fn unify_tag_union_and_func(
tags: &MutMap<TagName, Vec<Variable>>,
args: &Vec<Variable>,
#[allow(clippy::too_many_arguments)]
fn unify_function_or_tag_union_and_func(
subs: &mut Subs,
pool: &mut Pool,
ctx: &Context,
ext: &Variable,
ret: &Variable,
closure: &Variable,
tag_name: &TagName,
tag_symbol: Symbol,
tag_ext: Variable,
function_arguments: &[Variable],
function_return: Variable,
function_lambda_set: Variable,
left: bool,
) -> Outcome {
use FlatType::*;
let (tag_name, payload) = tags.iter().next().unwrap();
let mut new_tags = MutMap::with_capacity_and_hasher(1, default_hasher());
if payload.is_empty() {
let mut new_tags = MutMap::with_capacity_and_hasher(1, default_hasher());
new_tags.insert(tag_name.clone(), function_arguments.to_owned());
new_tags.insert(tag_name.clone(), args.to_owned());
let content = Structure(TagUnion(new_tags, tag_ext));
let content = Structure(TagUnion(new_tags, *ext));
let new_tag_union_var = fresh(subs, pool, ctx, content);
let new_tag_union_var = fresh(subs, pool, ctx, content);
let mut problems = if left {
unify_pool(subs, pool, new_tag_union_var, function_return)
} else {
unify_pool(subs, pool, function_return, new_tag_union_var)
};
let problems = if left {
unify_pool(subs, pool, new_tag_union_var, *ret)
{
let lambda_set_ext = subs.fresh_unnamed_flex_var();
let mut closure_tags = MutMap::with_capacity_and_hasher(1, default_hasher());
closure_tags.insert(TagName::Closure(tag_symbol), vec![]);
let lambda_set_content = Structure(TagUnion(closure_tags, lambda_set_ext));
let tag_lambda_set = register(
subs,
Descriptor {
content: lambda_set_content,
rank: ctx.first_desc.rank.min(ctx.second_desc.rank),
mark: Mark::NONE,
copy: OptVariable::NONE,
},
pool,
);
let closure_problems = if left {
unify_pool(subs, pool, tag_lambda_set, function_lambda_set)
} else {
unify_pool(subs, pool, *ret, new_tag_union_var)
unify_pool(subs, pool, function_lambda_set, tag_lambda_set)
};
if problems.is_empty() {
let desc = if left {
subs.get(ctx.second)
} else {
subs.get(ctx.first)
};
subs.union(ctx.first, ctx.second, desc);
}
problems
} else {
mismatch!(
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
TagUnion(tags.clone(), *ext),
Func(args.to_owned(), *closure, *ret)
)
problems.extend(closure_problems);
}
if problems.is_empty() {
let desc = if left {
subs.get(ctx.second)
} else {
subs.get(ctx.first)
};
subs.union(ctx.first, ctx.second, desc);
}
problems
}

View File

@ -12,6 +12,8 @@ pulldown-cmark = { version = "0.8", default-features = false }
roc_load = { path = "../compiler/load" }
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
roc_module = { path = "../compiler/module" }
roc_region = { path = "../compiler/region" }
roc_collections = { path = "../compiler/collections" }
bumpalo = { version = "3.2", features = ["collections"] }

View File

@ -4,18 +4,22 @@ use roc_can::builtins::builtin_defs_map;
use roc_load::docs::{DocEntry, TypeAnnotation};
use roc_load::docs::{ModuleDocumentation, RecordField};
use roc_load::file::LoadingProblem;
use roc_module::symbol::{Interns, ModuleId};
use std::fs;
extern crate roc_load;
use bumpalo::Bump;
use roc_can::scope::Scope;
use roc_collections::all::MutMap;
use roc_region::all::Region;
use std::path::{Path, PathBuf};
pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
let files_docs = files_to_documentations(filenames, std_lib);
//
// TODO: get info from a file like "elm.json"
let package = roc_load::docs::Documentation {
let mut package = roc_load::docs::Documentation {
name: "roc/builtins".to_string(),
version: "1.0.0".to_string(),
docs: "Package introduction or README.".to_string(),
@ -45,36 +49,44 @@ pub fn generate(filenames: Vec<PathBuf>, std_lib: StdLib, build_dir: &Path) {
)
.expect("TODO gracefully handle failing to make the favicon");
let template_html = include_str!("./static/index.html");
let template_html = include_str!("./static/index.html").replace(
"<!-- Module links -->",
render_sidebar(
package
.modules
.iter()
.flat_map(|(docs_by_id, _)| docs_by_id.values()),
)
.as_str(),
);
// Write each package's module docs html file
for module in &package.modules {
let mut filename = String::new();
filename.push_str(module.name.as_str());
filename.push_str(".html");
for (docs_by_id, interns) in package.modules.iter_mut() {
for module in docs_by_id.values_mut() {
let mut filename = String::new();
filename.push_str(module.name.as_str());
filename.push_str(".html");
let rendered_module = template_html
.replace(
"<!-- Module links -->",
render_module_links(&package.modules).as_str(),
)
.replace(
"<!-- Package Name and Version -->",
render_name_and_version(package.name.as_str(), package.version.as_str()).as_str(),
)
.replace(
"<!-- Module Docs -->",
render_main_content(&module).as_str(),
);
let rendered_module = template_html
.replace(
"<!-- Package Name and Version -->",
render_name_and_version(package.name.as_str(), package.version.as_str())
.as_str(),
)
.replace(
"<!-- Module Docs -->",
render_main_content(interns, module).as_str(),
);
fs::write(build_dir.join(filename), rendered_module)
.expect("TODO gracefully handle failing to write html");
fs::write(build_dir.join(filename), rendered_module)
.expect("TODO gracefully handle failing to write html");
}
}
println!("🎉 Docs generated in {}", build_dir.display());
}
fn render_main_content(module: &ModuleDocumentation) -> String {
fn render_main_content(interns: &Interns, module: &mut ModuleDocumentation) -> String {
let mut buf = String::new();
buf.push_str(
@ -86,8 +98,6 @@ fn render_main_content(module: &ModuleDocumentation) -> String {
.as_str(),
);
// buf.push_str(markdown_to_html(module.docs.clone()).as_str());
for entry in &module.entries {
match entry {
DocEntry::DocDef(doc_def) => {
@ -121,11 +131,15 @@ fn render_main_content(module: &ModuleDocumentation) -> String {
);
if let Some(docs) = &doc_def.docs {
buf.push_str(markdown_to_html(docs.to_string()).as_str());
buf.push_str(
markdown_to_html(&mut module.scope, interns, docs.to_string()).as_str(),
);
}
}
DocEntry::DetatchedDoc(docs) => {
buf.push_str(markdown_to_html(docs.to_string()).as_str());
buf.push_str(
markdown_to_html(&mut module.scope, interns, docs.to_string()).as_str(),
);
}
};
}
@ -197,7 +211,7 @@ fn render_name_and_version(name: &str, version: &str) -> String {
buf
}
fn render_module_links(modules: &[ModuleDocumentation]) -> String {
fn render_sidebar<'a, I: Iterator<Item = &'a ModuleDocumentation>>(modules: I) -> String {
let mut buf = String::new();
for module in modules {
@ -271,7 +285,7 @@ fn render_module_links(modules: &[ModuleDocumentation]) -> String {
pub fn files_to_documentations(
filenames: Vec<PathBuf>,
std_lib: StdLib,
) -> Vec<ModuleDocumentation> {
) -> Vec<(MutMap<ModuleId, ModuleDocumentation>, Interns)> {
let arena = Bump::new();
let mut files_docs = vec![];
@ -288,7 +302,7 @@ pub fn files_to_documentations(
8, // TODO: Is it okay to hardcode ptr_bytes here? I think it should be fine since we'er only type checking (also, 8 => 32bit system)
builtin_defs_map,
) {
Ok(mut loaded) => files_docs.extend(loaded.documentation.drain().map(|x| x.1)),
Ok(loaded) => files_docs.push((loaded.documentation, loaded.interns)),
Err(LoadingProblem::FormattedReport(report)) => {
println!("{}", report);
panic!();
@ -296,6 +310,7 @@ pub fn files_to_documentations(
Err(e) => panic!("{:?}", e),
}
}
files_docs
}
@ -412,15 +427,98 @@ fn type_annotation_to_html(indent_level: usize, buf: &mut String, type_ann: &Typ
}
}
fn markdown_to_html(markdown: String) -> String {
fn insert_doc_links(scope: &mut Scope, interns: &Interns, markdown: String) -> String {
let buf = &markdown;
let mut result = String::new();
let mut chomping_from: Option<usize> = None;
let mut chars = buf.chars().enumerate().peekable();
while let Some((index, char)) = chars.next() {
match chomping_from {
None => {
let next_is_alphabetic = match chars.peek() {
None => false,
Some((_, next_char)) => next_char.is_alphabetic(),
};
if char == '#' && next_is_alphabetic {
chomping_from = Some(index);
}
}
Some(from) => {
if !(char.is_alphabetic() || char == '.') {
let after_link = buf.chars().skip(from + buf.len());
result = buf.chars().take(from).collect();
let doc_link = make_doc_link(
scope,
interns,
&buf.chars()
.skip(from + 1)
.take(index - from)
.collect::<String>(),
);
result.insert_str(from, doc_link.as_str());
let remainder = insert_doc_links(scope, interns, after_link.collect());
result.push_str(remainder.as_str());
break;
}
}
}
}
result
}
fn make_doc_link(scope: &mut Scope, interns: &Interns, doc_item: &str) -> String {
match scope.lookup(&doc_item.into(), Region::zero()) {
Ok(symbol) => {
let module_str = symbol.module_string(interns);
let ident_str = symbol.ident_string(interns);
let mut link = String::new();
link.push_str(module_str);
link.push_str(".html#");
link.push_str(ident_str);
let mut buf = String::new();
buf.push('[');
buf.push_str(doc_item);
buf.push_str("](");
buf.push_str(link.as_str());
buf.push(')');
buf
}
Err(_) => {
panic!(
"Could not find symbol in scope for module link : {}",
doc_item
)
}
}
}
fn markdown_to_html(scope: &mut Scope, interns: &Interns, markdown: String) -> String {
use pulldown_cmark::CodeBlockKind;
use pulldown_cmark::CowStr;
use pulldown_cmark::Event;
use pulldown_cmark::Tag::*;
let markdown_with_links = insert_doc_links(scope, interns, markdown);
let markdown_options = pulldown_cmark::Options::empty();
let mut docs_parser = vec![];
let (_, _) = pulldown_cmark::Parser::new_ext(&markdown, markdown_options).fold(
let (_, _) = pulldown_cmark::Parser::new_ext(&markdown_with_links, markdown_options).fold(
(0, 0),
|(start_quote_count, end_quote_count), event| match event {
// Replace this sequence (`>>>` syntax):

View File

@ -28,13 +28,13 @@ arraystring = "0.3.0"
libc = "0.2"
page_size = "0.4"
winit = "0.24"
wgpu = "0.7"
wgpu = "0.8"
glyph_brush = "0.7"
log = "0.4"
zerocopy = "0.3"
env_logger = "0.8"
futures = "0.3"
wgpu_glyph = "0.11"
wgpu_glyph = "0.12"
cgmath = "0.18.0"
snafu = { version = "0.6", features = ["backtraces"] }
colored = "2"
@ -62,11 +62,3 @@ quickcheck = "1.0"
quickcheck_macros = "1.0"
criterion = "0.3"
rand = "0.8.2"
[[bench]]
name = "file_benchmark"
harness = false
[[bench]]
name = "edit_benchmark"
harness = false

View File

@ -1,216 +0,0 @@
use bumpalo::Bump;
use criterion::{criterion_group, criterion_main, Criterion};
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use roc_editor::mvc::app_model::AppModel;
use roc_editor::mvc::ed_model::{EdModel, Position, RawSelection};
use roc_editor::mvc::update::handle_new_char;
use roc_editor::text_buffer;
use roc_editor::text_buffer::TextBuffer;
use ropey::Rope;
use std::cmp::min;
use std::path::Path;
// duplicate inside mvc::update
fn mock_app_model(
text_buf: TextBuffer,
caret_pos: Position,
selection_opt: Option<RawSelection>,
) -> AppModel {
AppModel {
ed_model_opt: Some(EdModel {
text_buf,
caret_pos,
selection_opt,
glyph_dim_rect_opt: None,
has_focus: true,
}),
}
}
fn text_buffer_from_str(lines_str: &str) -> TextBuffer {
TextBuffer {
text_rope: Rope::from_str(lines_str),
path_str: "".to_owned(),
arena: Bump::new(),
}
}
pub fn char_insert_bench(c: &mut Criterion) {
let text_buf = text_buffer_from_str("");
let caret_pos = Position { line: 0, column: 0 };
let selection_opt: Option<RawSelection> = None;
let mut app_model = mock_app_model(text_buf, caret_pos, selection_opt);
c.bench_function("single char insert, small buffer", |b| {
b.iter(|| handle_new_char(&mut app_model, &'a'))
});
}
fn bench_resource_path(nr_lines: usize) -> String {
let resource_path_res = std::env::var("BENCH_RESOURCE_PATH");
let resource_path_str = if let Ok(resource_path) = resource_path_res {
resource_path
} else {
"benches/resources/".to_owned()
};
format!("{}{}_lines.roc", resource_path_str, nr_lines)
}
pub fn char_pop_bench(c: &mut Criterion) {
let nr_lines = 50000;
let mut text_buf = buf_from_dummy_file(nr_lines);
let mut rand_gen_pos = StdRng::seed_from_u64(44);
c.bench_function(
&format!("single char pop, {} lines", text_buf.nr_of_lines()),
|b| {
b.iter(|| {
let max_line_nr = text_buf.nr_of_lines();
let rand_line_nr = rand_gen_pos.gen_range(0..max_line_nr);
let max_col = text_buf
.line_len(rand_line_nr)
.expect("Failed to retrieve line length.");
let caret_pos = Position {
line: rand_line_nr,
column: rand_gen_pos.gen_range(0..max_col),
};
text_buf.pop_char(caret_pos);
})
},
);
}
fn get_all_lines_helper(nr_lines: usize, c: &mut Criterion) {
let text_buf = buf_from_dummy_file(nr_lines);
let arena = Bump::new();
c.bench_function(
&format!("get all {:?} lines from textbuffer", nr_lines),
|b| b.iter(|| text_buf.all_lines(&arena)),
);
}
fn get_all_lines_bench(c: &mut Criterion) {
get_all_lines_helper(10000, c)
}
fn get_line_len_helper(nr_lines: usize, c: &mut Criterion) {
let text_buf = buf_from_dummy_file(nr_lines);
let mut rand_gen = StdRng::seed_from_u64(45);
c.bench_function(
&format!("get random line len from {:?}-line textbuffer", nr_lines),
|b| b.iter(|| text_buf.line_len(rand_gen.gen_range(0..nr_lines)).unwrap()),
);
}
fn get_line_len_bench(c: &mut Criterion) {
get_line_len_helper(10000, c)
}
fn get_line_helper(nr_lines: usize, c: &mut Criterion) {
let text_buf = buf_from_dummy_file(nr_lines);
let mut rand_gen = StdRng::seed_from_u64(46);
c.bench_function(
&format!("get random line from {:?}-line textbuffer", nr_lines),
|b| {
b.iter(|| {
let rand_indx = rand_gen.gen_range(0..nr_lines);
text_buf.line(rand_indx).unwrap()
})
},
);
}
fn get_line_bench(c: &mut Criterion) {
get_line_helper(10000, c)
}
pub fn del_select_bench(c: &mut Criterion) {
let nr_lines = 25000000;
let mut text_buf = buf_from_dummy_file(nr_lines);
let mut rand_gen = StdRng::seed_from_u64(47);
c.bench_function(
&format!(
"delete rand selection, {}-line file",
text_buf.nr_of_lines()
),
|b| {
b.iter(|| {
let rand_sel = gen_rand_selection(&mut rand_gen, &text_buf);
text_buf.del_selection(rand_sel).unwrap();
})
},
);
}
fn gen_rand_selection(rand_gen: &mut StdRng, text_buf: &TextBuffer) -> RawSelection {
let max_line_nr = text_buf.nr_of_lines();
let rand_line_nr_a = rand_gen.gen_range(0..max_line_nr - 3);
let max_col_a = text_buf.line_len(rand_line_nr_a).expect(&format!(
"Failed to retrieve line length. For line {}, with {} lines in buffer",
rand_line_nr_a,
text_buf.nr_of_lines()
));
let rand_col_a = if max_col_a > 0 {
rand_gen.gen_range(0..max_col_a)
} else {
0
};
let max_sel_end = min(rand_line_nr_a + 5, max_line_nr);
let rand_line_nr_b = rand_gen.gen_range((rand_line_nr_a + 1)..max_sel_end);
let max_col_b = text_buf.line_len(rand_line_nr_b).expect(&format!(
"Failed to retrieve line length. For line {}, with {} lines in buffer",
rand_line_nr_b,
text_buf.nr_of_lines()
));
let rand_col_b = if max_col_b > 0 {
rand_gen.gen_range(0..max_col_b)
} else {
0
};
RawSelection {
start_pos: Position {
line: rand_line_nr_a,
column: rand_col_a,
},
end_pos: Position {
line: rand_line_nr_b,
column: rand_col_b,
},
}
}
fn buf_from_dummy_file(nr_lines: usize) -> TextBuffer {
let path_str = bench_resource_path(nr_lines);
text_buffer::from_path(Path::new(&path_str)).expect("Failed to read file at given path.")
}
//TODO remove all random generation from inside measured execution block
//criterion_group!(benches, del_select_bench);
criterion_group!(
benches,
char_pop_bench,
char_insert_bench,
get_all_lines_bench,
get_line_len_bench,
get_line_bench,
del_select_bench
);
criterion_main!(benches);

View File

@ -1,163 +0,0 @@
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use rand::distributions::Alphanumeric;
use rand::rngs::StdRng;
use rand::{Rng, SeedableRng};
use roc_editor::mvc::app_model::AppModel;
use roc_editor::mvc::ed_model::{EdModel, Position, RawSelection};
use roc_editor::mvc::update::handle_new_char;
use roc_editor::text_buffer;
use roc_editor::text_buffer::TextBuffer;
use ropey::Rope;
use std::fs::File;
use std::io::Write;
use std::path::Path;
// duplicate inside mvc::update
fn mock_app_model(
text_buf: TextBuffer,
caret_pos: Position,
selection_opt: Option<RawSelection>,
) -> AppModel {
AppModel {
ed_model_opt: Some(EdModel {
text_buf,
caret_pos,
selection_opt,
glyph_dim_rect_opt: None,
has_focus: true,
}),
}
}
fn text_buffer_from_str(lines_str: &str) -> TextBuffer {
TextBuffer {
text_rope: Rope::from_str(lines_str),
path_str: "".to_owned(),
arena: bumpalo::Bump::new(),
}
}
pub fn char_insert_benchmark(c: &mut Criterion) {
let text_buf = text_buffer_from_str("");
let caret_pos = Position { line: 0, column: 0 };
let selection_opt: Option<RawSelection> = None;
let mut app_model = mock_app_model(text_buf, caret_pos, selection_opt);
c.bench_function("single char insert, small buffer", |b| {
b.iter(|| handle_new_char(&mut app_model, &'a'))
});
}
static ROC_SOURCE_START: &str = "interface LongStrProvider
exposes [ longStr ]
imports []
longStr : Str
longStr =
\"\"\"";
static ROC_SOURCE_END: &str = "\"\"\"";
fn line_count(lines: &str) -> usize {
lines.matches("\n").count()
}
pub fn gen_file(nr_lines: usize) {
let nr_of_str_lines = nr_lines - line_count(ROC_SOURCE_START);
let path_str = bench_resource_path(nr_lines);
let path = Path::new(&path_str);
let display = path.display();
// Open a file in write-only mode, returns `io::Result<File>`
let mut file = match File::create(&path) {
Err(why) => panic!("couldn't create {}: {}", display, why),
Ok(file) => file,
};
file.write(ROC_SOURCE_START.as_bytes())
.expect("Failed to write String to file.");
let mut rand_gen_line = StdRng::seed_from_u64(42);
for _ in 0..nr_of_str_lines {
let line_len = rand_gen_line.gen_range(1..90);
let char_seed = rand_gen_line.gen_range(0..1000);
let mut rand_string: String = StdRng::seed_from_u64(char_seed)
.sample_iter(&Alphanumeric)
.take(line_len)
.map(char::from)
.collect();
rand_string.push('\n');
file.write(rand_string.as_bytes())
.expect("Failed to write String to file.");
}
file.write(ROC_SOURCE_END.as_bytes())
.expect("Failed to write String to file.");
}
fn bench_resource_path(nr_lines: usize) -> String {
let resource_path_res = std::env::var("BENCH_RESOURCE_PATH");
let resource_path_str = if let Ok(resource_path) = resource_path_res {
resource_path
} else {
"benches/resources/".to_owned()
};
format!("{}{}_lines.roc", resource_path_str, nr_lines)
}
fn file_read_bench_helper(nr_lines: usize, c: &mut Criterion) {
let path_str = bench_resource_path(nr_lines);
text_buffer::from_path(Path::new(&path_str)).expect("Failed to read file at given path.");
c.bench_function(
&format!("read {:?} line file into textbuffer", nr_lines),
|b| b.iter(|| text_buffer::from_path(black_box(Path::new(&path_str)))),
);
}
fn file_read_bench_10(c: &mut Criterion) {
// generate dummy files
/*let lines_vec = vec![100, 500, 1000, 10000, 50000, 100000, 25000000];
for nr_lines in lines_vec.iter(){
gen_file(*nr_lines);
}*/
file_read_bench_helper(10, c)
}
fn file_read_bench_100(c: &mut Criterion) {
file_read_bench_helper(100, c)
}
fn file_read_bench_500(c: &mut Criterion) {
file_read_bench_helper(500, c)
}
fn file_read_bench_1k(c: &mut Criterion) {
file_read_bench_helper(1000, c)
}
fn file_read_bench_10k(c: &mut Criterion) {
file_read_bench_helper(10000, c)
}
fn file_read_bench_100k(c: &mut Criterion) {
file_read_bench_helper(100000, c)
}
fn file_read_bench_25m(c: &mut Criterion) {
file_read_bench_helper(25000000, c)
}
criterion_group!(
benches,
file_read_bench_10,
file_read_bench_100,
file_read_bench_500,
file_read_bench_1k,
file_read_bench_10k,
file_read_bench_100k,
file_read_bench_25m
);
criterion_main!(benches);

View File

@ -1,11 +0,0 @@
interface LongStrProvider
exposes [ longStr ]
imports []
longStr : Str
longStr =
"""7vntt4wlBKiVkNss19DZlOfmSAyIzO5Ph8eckYgnctYDersOFs3AWOPHcONxI58DoTEwGKNLGkhrxwCD
gWxYsX9hlEuQ0vI4twHMqgj8F
Ox4pVYIxku15v1KaWahgjkJ8EBXMWhe5m2519wpEtP
HtaqU0XzVu1ix3jGAZ66UugNKJrVP8RVQm
"""

View File

@ -1,26 +0,0 @@
System info:
- CPU: Intel i7 4770k
- SSD: Samsung 970 EVO PLUS M.2 1TB
- OS: Ubuntu 20.04
c.bench_function(
"read file into textbuffer",
|b| b.iter(|| text_buffer::from_path(black_box(Path::new(path_str))))
);
10 lines, 285 B time: [3.2343 us]
100 lines, 4.2 KiB time: [6.1810 us]
500 lines, 22.2 KiB time: [15.689 us]
1000 lines, 44.6 KiB time: [29.591 us]
10000 lines, 448 KiB time: [376.22 us]
50000 lines, 2.2 MiB time: [2.0329 ms]
100000 lines, 4.4 MiB time: [4.4221 ms]
25000000 lines, 1.1 GiB time: [1.1333 s]

View File

@ -396,8 +396,8 @@ fn begin_render_pass<'a>(
let bg_color = to_wgpu_color(ed_theme.background);
encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
color_attachments: &[wgpu::RenderPassColorAttachmentDescriptor {
attachment: texture_view,
color_attachments: &[wgpu::RenderPassColorAttachment {
view: texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(bg_color),

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