Merge branch 'trunk' into editor_sound

This commit is contained in:
Anton-4 2021-10-04 16:30:32 +02:00 committed by GitHub
commit 7ad4a63f26
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
179 changed files with 10326 additions and 5260 deletions

View file

@ -7,6 +7,7 @@ name: Benchmarks
env: env:
RUST_BACKTRACE: 1 RUST_BACKTRACE: 1
ROC_NUM_WORKERS: 1
jobs: jobs:
prep-dependency-container: prep-dependency-container:
@ -42,4 +43,4 @@ jobs:
run: cd ci/bench-runner && cargo build --release && cd ../.. run: cd ci/bench-runner && cargo build --release && cd ../..
- name: run benchmarks with regression check - name: run benchmarks with regression check
run: echo "TODO re-enable benchmarks once race condition is fixed"#./ci/bench-runner/target/release/bench-runner --check-executables-changed run: ./ci/bench-runner/target/release/bench-runner --check-executables-changed

1
.gitignore vendored
View file

@ -4,6 +4,7 @@ zig-cache
.direnv .direnv
*.rs.bk *.rs.bk
*.o *.o
*.tmp
# llvm human-readable output # llvm human-readable output
*.ll *.ll

409
Cargo.lock generated
View file

@ -153,10 +153,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "ash" name = "arrayvec"
version = "0.32.1" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06063a002a77d2734631db74e8f4ce7148b77fe522e6bca46f2ae7774fd48112" checksum = "be4dc07131ffa69b8072d35f5007352af944213cde02545e2103680baed38fcd"
[[package]]
name = "ash"
version = "0.33.3+1.2.191"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc4f1d82f164f838ae413296d1131aa6fa79b917d25bebaa7033d25620c09219"
dependencies = [ dependencies = [
"libloading 0.7.0", "libloading 0.7.0",
] ]
@ -379,9 +385,6 @@ name = "cc"
version = "1.0.70" version = "1.0.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
dependencies = [
"jobserver",
]
[[package]] [[package]]
name = "cesu8" name = "cesu8"
@ -1207,15 +1210,6 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650"
[[package]]
name = "drm-fourcc"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4"
dependencies = [
"serde",
]
[[package]] [[package]]
name = "dtoa" name = "dtoa"
version = "0.4.8" version = "0.4.8"
@ -1287,16 +1281,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "external-memory"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4dfe8d292b014422776a8c516862d2bff8a81b223a4461dfdc45f3862dc9d39"
dependencies = [
"bitflags",
"drm-fourcc",
]
[[package]] [[package]]
name = "fake-simd" name = "fake-simd"
version = "0.1.2" version = "0.1.2"
@ -1315,6 +1299,12 @@ version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d" checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
[[package]]
name = "fixedbitset"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "398ea4fabe40b9b0d885340a2a991a44c8a645624075ad966d21f88688e2b69e"
[[package]] [[package]]
name = "flate2" name = "flate2"
version = "1.0.21" version = "1.0.21"
@ -1530,168 +1520,6 @@ dependencies = [
"wasi 0.10.2+wasi-snapshot-preview1", "wasi 0.10.2+wasi-snapshot-preview1",
] ]
[[package]]
name = "gfx-auxil"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1694991b11d642680e82075a75c7c2bd75556b805efa7660b705689f05b1ab1c"
dependencies = [
"fxhash",
"gfx-hal",
"spirv_cross",
]
[[package]]
name = "gfx-backend-dx11"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f9e453baf3aaef2b0c354ce0b3d63d76402e406a59b64b7182d123cfa6635ae"
dependencies = [
"arrayvec",
"bitflags",
"gfx-auxil",
"gfx-hal",
"gfx-renderdoc",
"libloading 0.7.0",
"log",
"parking_lot",
"range-alloc",
"raw-window-handle",
"smallvec",
"spirv_cross",
"thunderdome",
"winapi 0.3.9",
"wio",
]
[[package]]
name = "gfx-backend-dx12"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21506399f64a3c4d389182a89a30073856ae33eb712315456b4fd8f39ee7682a"
dependencies = [
"arrayvec",
"bit-set",
"bitflags",
"d3d12",
"gfx-auxil",
"gfx-hal",
"gfx-renderdoc",
"log",
"parking_lot",
"range-alloc",
"raw-window-handle",
"smallvec",
"spirv_cross",
"thunderdome",
"winapi 0.3.9",
]
[[package]]
name = "gfx-backend-empty"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c8f813c47791918aa00dc9c9ddf961d23fa8c2a5d869e6cb8ea84f944820f4"
dependencies = [
"gfx-hal",
"log",
"raw-window-handle",
]
[[package]]
name = "gfx-backend-gl"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bae057fc3a0ab23ecf97ae51d4017d27d5ddf0aab16ee6dcb58981af88c3152"
dependencies = [
"arrayvec",
"bitflags",
"fxhash",
"gfx-hal",
"glow",
"js-sys",
"khronos-egl",
"libloading 0.7.0",
"log",
"naga",
"parking_lot",
"raw-window-handle",
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "gfx-backend-metal"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0de85808e2a98994c6af925253f8a9593bc57180ef1ea137deab6d35cc949517"
dependencies = [
"arrayvec",
"bitflags",
"block",
"cocoa-foundation",
"copyless",
"core-graphics-types",
"foreign-types",
"fxhash",
"gfx-hal",
"log",
"metal",
"naga",
"objc",
"parking_lot",
"profiling",
"range-alloc",
"raw-window-handle",
"storage-map",
]
[[package]]
name = "gfx-backend-vulkan"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9861ec855acbbc65c0e4f966d761224886e811dc2c6d413a4776e9293d0e5c0"
dependencies = [
"arrayvec",
"ash",
"byteorder",
"core-graphics-types",
"gfx-hal",
"gfx-renderdoc",
"inplace_it",
"log",
"naga",
"objc",
"parking_lot",
"raw-window-handle",
"smallvec",
"winapi 0.3.9",
]
[[package]]
name = "gfx-hal"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fbb575ea793dd0507b3082f4f2cde62dc9f3cebd98f5cd49ba2a4da97a976fd"
dependencies = [
"bitflags",
"external-memory",
"naga",
"raw-window-handle",
"thiserror",
]
[[package]]
name = "gfx-renderdoc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8027995e247e2426d3a00d13f5191dd56c314bff02dc4b54cbf727f1ba9c40a"
dependencies = [
"libloading 0.7.0",
"log",
"renderdoc-sys",
]
[[package]] [[package]]
name = "ghost" name = "ghost"
version = "0.1.2" version = "0.1.2"
@ -1728,9 +1556,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]] [[package]]
name = "glow" name = "glow"
version = "0.9.0" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b80b98efaa8a34fce11d60dd2ce2760d5d83c373cbcc73bb87c2a3a84a54108" checksum = "4f04649123493bc2483cbef4daddb45d40bbdae5adb221a63a23efdb0cc99520"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"slotmap", "slotmap",
@ -1779,9 +1607,9 @@ dependencies = [
[[package]] [[package]]
name = "gpu-alloc" name = "gpu-alloc"
version = "0.4.7" version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbc1b6ca374e81862526786d9cb42357ce03706ed1b8761730caafd02ab91f3a" checksum = "ab8524eac5fc9d05625c891adf78fcf64dc0ee9f8d0882874b9f220f42b442bf"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"gpu-alloc-types", "gpu-alloc-types",
@ -1798,9 +1626,9 @@ dependencies = [
[[package]] [[package]]
name = "gpu-descriptor" name = "gpu-descriptor"
version = "0.1.1" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8a70f1e87a3840ed6a3e99e02c2b861e4dbdf26f0d07e38f42ea5aff46cfce2" checksum = "d7a237f0419ab10d17006d55c62ac4f689a6bf52c75d3f38b8361d249e8d4b0b"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"gpu-descriptor-types", "gpu-descriptor-types",
@ -2113,15 +1941,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.54" version = "0.3.54"
@ -2475,13 +2294,14 @@ dependencies = [
"sha2", "sha2",
"smallvec", "smallvec",
"thiserror", "thiserror",
"typed-arena",
] ]
[[package]] [[package]]
name = "naga" name = "naga"
version = "0.5.0" version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef670817eef03d356d5a509ea275e7dd3a78ea9e24261ea3cb2dfed1abb08f64" checksum = "8c5859e55c51da10b98e7a73068e0a0c5da7bbcae4fc38f86043d0c6d1b917cf"
dependencies = [ dependencies = [
"bit-set", "bit-set",
"bitflags", "bitflags",
@ -2489,9 +2309,8 @@ dependencies = [
"fxhash", "fxhash",
"log", "log",
"num-traits", "num-traits",
"petgraph", "petgraph 0.6.0",
"rose_tree", "spirv",
"spirv_headers",
"thiserror", "thiserror",
] ]
@ -2968,7 +2787,7 @@ dependencies = [
"cfg-if 1.0.0", "cfg-if 1.0.0",
"instant", "instant",
"libc", "libc",
"petgraph", "petgraph 0.5.1",
"redox_syscall", "redox_syscall",
"smallvec", "smallvec",
"thread-id", "thread-id",
@ -3036,7 +2855,17 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7" checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
dependencies = [ dependencies = [
"fixedbitset", "fixedbitset 0.2.0",
"indexmap",
]
[[package]]
name = "petgraph"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a13a2fa9d0b63e5f22328828741e523766fff0ee9e779316902290dff3f824f"
dependencies = [
"fixedbitset 0.4.0",
"indexmap", "indexmap",
] ]
@ -3723,6 +3552,28 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "roc_ast"
version = "0.1.0"
dependencies = [
"arraystring",
"bumpalo",
"indoc 1.0.3",
"libc",
"page_size",
"pretty_assertions 0.6.1",
"roc_can",
"roc_collections",
"roc_module",
"roc_parse",
"roc_problem",
"roc_region",
"roc_types",
"roc_unify",
"snafu",
"ven_graph",
]
[[package]] [[package]]
name = "roc_build" name = "roc_build"
version = "0.1.0" version = "0.1.0"
@ -3847,6 +3698,19 @@ dependencies = [
"wasmer-wasi", "wasmer-wasi",
] ]
[[package]]
name = "roc_code_markup"
version = "0.1.0"
dependencies = [
"bumpalo",
"palette",
"roc_ast",
"roc_module",
"roc_utils",
"serde",
"snafu",
]
[[package]] [[package]]
name = "roc_collections" name = "roc_collections"
version = "0.1.0" version = "0.1.0"
@ -3924,8 +3788,10 @@ dependencies = [
"quickcheck 1.0.3", "quickcheck 1.0.3",
"quickcheck_macros 1.0.0", "quickcheck_macros 1.0.0",
"rand 0.8.4", "rand 0.8.4",
"roc_ast",
"roc_builtins", "roc_builtins",
"roc_can", "roc_can",
"roc_code_markup",
"roc_collections", "roc_collections",
"roc_fmt", "roc_fmt",
"roc_load", "roc_load",
@ -4293,6 +4159,13 @@ dependencies = [
"minimp3", "minimp3",
] ]
[[package]]
name = "roc_utils"
version = "0.1.0"
dependencies = [
"snafu",
]
[[package]] [[package]]
name = "ropey" name = "ropey"
version = "1.3.1" version = "1.3.1"
@ -4302,15 +4175,6 @@ dependencies = [
"smallvec", "smallvec",
] ]
[[package]]
name = "rose_tree"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "284de9dae38774e2813aaabd7e947b4a6fe9b8c58c2309f754a487cdd50de1c2"
dependencies = [
"petgraph",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.21" version = "0.1.21"
@ -4604,9 +4468,12 @@ dependencies = [
[[package]] [[package]]
name = "slotmap" name = "slotmap"
version = "0.4.3" version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6bf34684c5767b87de9119790e92e9a1d60056be2ceeaf16a8e6ef13082aeab1" checksum = "e1e08e261d0e8f5c43123b7adf3e4ca1690d655377ac93a03b2c9d3e98de1342"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@ -4683,21 +4550,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "spirv_cross" name = "spirv"
version = "0.23.1" version = "0.2.0+1.5.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60647fadbf83c4a72f0d7ea67a7ca3a81835cf442b8deae5c134c3e0055b2e14" checksum = "246bfa38fe3db3f1dfc8ca5a2cdeb7348c78be2112740cc0ec8ef18b6d94f830"
dependencies = [
"cc",
"js-sys",
"wasm-bindgen",
]
[[package]]
name = "spirv_headers"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f5b132530b1ac069df335577e3581765995cba5a13995cdbbdbc8fb057c532c"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"num-traits", "num-traits",
@ -5197,7 +5053,7 @@ dependencies = [
name = "ven_pretty" name = "ven_pretty"
version = "0.9.1-alpha.0" version = "0.9.1-alpha.0"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.5.2",
"criterion", "criterion",
"difference", "difference",
"tempfile", "tempfile",
@ -5223,7 +5079,7 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983" checksum = "6cbce692ab4ca2f1f3047fcf732430249c0e971bfdd2b234cf2c47ad93af5983"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.5.2",
"utf8parse", "utf8parse",
"vte_generate_state_changes", "vte_generate_state_changes",
] ]
@ -5640,9 +5496,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.50" version = "0.3.51"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be" checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@ -5650,14 +5506,13 @@ dependencies = [
[[package]] [[package]]
name = "wgpu" name = "wgpu"
version = "0.9.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bd247f8b26fd3d42ef2f320d378025cd6e84d782ef749fab45cc3b981fbe3275" checksum = "3d92a4fe73b1e7d7ef99938dacd49258cbf1ad87cdb5bf6efa20c27447442b45"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.7.1",
"js-sys", "js-sys",
"log", "log",
"naga",
"parking_lot", "parking_lot",
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
@ -5665,29 +5520,21 @@ dependencies = [
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"wgpu-core", "wgpu-core",
"wgpu-hal",
"wgpu-types", "wgpu-types",
] ]
[[package]] [[package]]
name = "wgpu-core" name = "wgpu-core"
version = "0.9.2" version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "958a8a5e418492723ab4e7933bf6dbdf06f5dc87274ba2ae0e4f9c891aac579c" checksum = "5f1b4d918c970526cbc83b72ccb72dbefd38aec45f07b2310de4ffcd7f4bd8c5"
dependencies = [ dependencies = [
"arrayvec", "arrayvec 0.7.1",
"bitflags", "bitflags",
"cfg_aliases", "cfg_aliases",
"copyless", "copyless",
"fxhash", "fxhash",
"gfx-backend-dx11",
"gfx-backend-dx12",
"gfx-backend-empty",
"gfx-backend-gl",
"gfx-backend-metal",
"gfx-backend-vulkan",
"gfx-hal",
"gpu-alloc",
"gpu-descriptor",
"log", "log",
"naga", "naga",
"parking_lot", "parking_lot",
@ -5695,23 +5542,58 @@ dependencies = [
"raw-window-handle", "raw-window-handle",
"smallvec", "smallvec",
"thiserror", "thiserror",
"wgpu-hal",
"wgpu-types", "wgpu-types",
] ]
[[package]] [[package]]
name = "wgpu-types" name = "wgpu-hal"
version = "0.9.0" version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5c9678cd533558e28b416d66947b099742df1939307478db54f867137f1b60" checksum = "27cd894b17bff1958ee93da1cc991fd64bf99667746d4bd2a7403855f4d37fe2"
dependencies = [
"arrayvec 0.7.1",
"ash",
"bit-set",
"bitflags",
"block",
"core-graphics-types",
"d3d12",
"foreign-types",
"fxhash",
"glow",
"gpu-alloc",
"gpu-descriptor",
"inplace_it",
"khronos-egl",
"libloading 0.7.0",
"log",
"metal",
"naga",
"objc",
"parking_lot",
"range-alloc",
"raw-window-handle",
"renderdoc-sys",
"thiserror",
"wgpu-types",
"winapi 0.3.9",
]
[[package]]
name = "wgpu-types"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25feb2fbf24ab3219a9f10890ceb8e1ef02b13314ed89d64a9ae99dcad883e18"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
[[package]] [[package]]
name = "wgpu_glyph" name = "wgpu_glyph"
version = "0.13.0" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fee8c96eda18195a7ad9989737183e0a357f14b15e98838c76abbcf56a5f970" checksum = "cbf11aebbcf20806535bee127367bcb393c83d77c60c4f7917184d839716cf41"
dependencies = [ dependencies = [
"bytemuck", "bytemuck",
"glyph_brush", "glyph_brush",
@ -5804,15 +5686,6 @@ dependencies = [
"x11-dl", "x11-dl",
] ]
[[package]]
name = "wio"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d129932f4644ac2396cb456385cbf9e63b5b30c6e8dc4820bdca4eb082037a5"
dependencies = [
"winapi 0.3.9",
]
[[package]] [[package]]
name = "ws2_32-sys" name = "ws2_32-sys"
version = "0.2.1" version = "0.2.1"

View file

@ -29,9 +29,12 @@ members = [
"vendor/pathfinding", "vendor/pathfinding",
"vendor/pretty", "vendor/pretty",
"editor", "editor",
"ast",
"cli", "cli",
"cli/cli_utils", "cli/cli_utils",
"code_markup",
"roc_std", "roc_std",
"utils",
"docs", "docs",
"linker", "linker",
] ]

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli compiler docs editor roc_std vendor examples linker Cargo.toml Cargo.lock ./ COPY --dir cli compiler docs editor ast code_markup utils roc_std vendor examples linker Cargo.toml Cargo.lock ./
test-zig: test-zig:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
@ -67,7 +67,7 @@ check-rustfmt:
check-typos: check-typos:
RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically RUN cargo install typos-cli --version 1.0.11 # version set to prevent confusion if the version is updated automatically
COPY --dir .github ci cli compiler docs editor examples linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./ COPY --dir .github ci cli compiler docs editor examples ast code_markup utils linker nightly_benches packages roc_std www *.md LEGAL_DETAILS shell.nix ./
RUN typos RUN typos
test-rust: test-rust:
@ -101,7 +101,7 @@ test-all:
BUILD +test-rust BUILD +test-rust
BUILD +verify-no-git-changes BUILD +verify-no-git-changes
# compile everything needed for benchmarks and output a self-contained folder # compile everything needed for benchmarks and output a self-contained dir from which benchmarks can be run.
prep-bench-folder: prep-bench-folder:
FROM +copy-dirs FROM +copy-dirs
ARG BENCH_SUFFIX=branch ARG BENCH_SUFFIX=branch

27
ast/Cargo.toml Normal file
View file

@ -0,0 +1,27 @@
[package]
name = "roc_ast"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
description = "AST as used by the editor and (soon) docs. In contrast to the compiler, these types do not keep track of a location in a file."
[dependencies]
roc_can = { path = "../compiler/can" }
roc_collections = { path = "../compiler/collections" }
roc_region = { path = "../compiler/region" }
roc_module = { path = "../compiler/module" }
roc_parse = { path = "../compiler/parse" }
roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify"}
arraystring = "0.3.0"
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"
page_size = "0.4"
snafu = { version = "0.6", features = ["backtraces"] }
ven_graph = { path = "../vendor/pathfinding" }
indoc = "1.0"
[dev-dependencies]
pretty_assertions = "0.6"

38
ast/src/ast_error.rs Normal file
View file

@ -0,0 +1,38 @@
use snafu::{Backtrace, Snafu};
use crate::lang::core::ast::ASTNodeId;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum ASTError {
#[snafu(display(
"ASTNodeIdWithoutExprId: The expr_id_opt in ASTNode({:?}) was `None` but I was expexting `Some(ExprId)` .",
ast_node_id
))]
ASTNodeIdWithoutExprId {
ast_node_id: ASTNodeId,
backtrace: Backtrace,
},
#[snafu(display(
"UnexpectedASTNode: required a {} at this position, node was a {}.",
required_node_type,
encountered_node_type
))]
UnexpectedASTNode {
required_node_type: String,
encountered_node_type: String,
backtrace: Backtrace,
},
#[snafu(display(
"UnexpectedPattern2Variant: required a {} at this position, Pattern2 was a {}.",
required_pattern2,
encountered_pattern2,
))]
UnexpectedPattern2Variant {
required_pattern2: String,
encountered_pattern2: String,
backtrace: Backtrace,
},
}
pub type ASTResult<T, E = ASTError> = std::result::Result<T, E>;

View file

@ -0,0 +1,305 @@
use roc_collections::all::MutMap;
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_types::subs::Variable;
use crate::{
lang::{
core::{
def::def::References,
expr::{
expr2::{Expr2, ExprId, WhenBranch},
expr_to_expr2::to_expr2,
output::Output,
record_field::RecordField,
},
pattern::to_pattern2,
},
env::Env,
scope::Scope,
},
mem_pool::{pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
pub(crate) enum CanonicalizeRecordProblem {
#[allow(dead_code)]
InvalidOptionalValue {
field_name: PoolStr,
field_region: Region,
record_region: Region,
},
}
enum FieldVar {
VarAndExprId(Variable, ExprId),
OnlyVar(Variable),
}
pub(crate) fn canonicalize_fields<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
fields: &'a [Located<roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>>],
) -> Result<(PoolVec<RecordField>, Output), CanonicalizeRecordProblem> {
let mut can_fields: MutMap<&'a str, FieldVar> = MutMap::default();
let mut output = Output::default();
for loc_field in fields.iter() {
match canonicalize_field(env, scope, &loc_field.value) {
Ok(can_field) => {
match can_field {
CanonicalField::LabelAndValue {
label,
value_expr,
value_output,
var,
} => {
let expr_id = env.pool.add(value_expr);
let replaced =
can_fields.insert(label, FieldVar::VarAndExprId(var, expr_id));
if let Some(_old) = replaced {
// env.problems.push(Problem::DuplicateRecordFieldValue {
// field_name: label,
// field_region: loc_field.region,
// record_region: region,
// replaced_region: old.region,
// });
todo!()
}
output.references.union_mut(value_output.references);
}
CanonicalField::InvalidLabelOnly { label, var } => {
let replaced = can_fields.insert(label, FieldVar::OnlyVar(var));
if let Some(_old) = replaced {
todo!()
}
}
}
}
Err(CanonicalizeFieldProblem::InvalidOptionalValue {
field_name: _,
field_region: _,
}) => {
// env.problem(Problem::InvalidOptionalValue {
// field_name: field_name.clone(),
// field_region,
// record_region: region,
// });
// return Err(CanonicalizeRecordProblem::InvalidOptionalValue {
// field_name,
// field_region,
// record_region: region,
// });
todo!()
}
}
}
let pool_vec = PoolVec::with_capacity(can_fields.len() as u32, env.pool);
for (node_id, (string, field_var)) in pool_vec.iter_node_ids().zip(can_fields.into_iter()) {
let name = PoolStr::new(string, env.pool);
match field_var {
FieldVar::VarAndExprId(var, expr_id) => {
env.pool[node_id] = RecordField::LabeledValue(name, var, expr_id);
}
FieldVar::OnlyVar(var) => {
env.pool[node_id] = RecordField::InvalidLabelOnly(name, var);
} // TODO RecordField::LabelOnly
}
}
Ok((pool_vec, output))
}
#[allow(dead_code)]
enum CanonicalizeFieldProblem {
InvalidOptionalValue {
field_name: PoolStr,
field_region: Region,
},
}
enum CanonicalField<'a> {
LabelAndValue {
label: &'a str,
value_expr: Expr2,
value_output: Output,
var: Variable,
},
InvalidLabelOnly {
label: &'a str,
var: Variable,
}, // TODO make ValidLabelOnly
}
fn canonicalize_field<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
field: &'a roc_parse::ast::AssignedField<'a, roc_parse::ast::Expr<'a>>,
) -> Result<CanonicalField<'a>, CanonicalizeFieldProblem> {
use roc_parse::ast::AssignedField::*;
match field {
// Both a label and a value, e.g. `{ name: "blah" }`
RequiredValue(label, _, loc_expr) => {
let field_var = env.var_store.fresh();
let (loc_can_expr, output) = to_expr2(env, scope, &loc_expr.value, loc_expr.region);
Ok(CanonicalField::LabelAndValue {
label: label.value,
value_expr: loc_can_expr,
value_output: output,
var: field_var,
})
}
OptionalValue(label, _, loc_expr) => Err(CanonicalizeFieldProblem::InvalidOptionalValue {
field_name: PoolStr::new(label.value, env.pool),
field_region: Region::span_across(&label.region, &loc_expr.region),
}),
// A label with no value, e.g. `{ name }` (this is sugar for { name: name })
LabelOnly(label) => {
let field_var = env.var_store.fresh();
// TODO return ValidLabel if label points to in scope variable
Ok(CanonicalField::InvalidLabelOnly {
label: label.value,
var: field_var,
})
}
SpaceBefore(sub_field, _) | SpaceAfter(sub_field, _) => {
canonicalize_field(env, scope, sub_field)
}
Malformed(_string) => {
panic!("TODO canonicalize malformed record field");
}
}
}
#[inline(always)]
pub(crate) fn canonicalize_when_branch<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
branch: &'a roc_parse::ast::WhenBranch<'a>,
output: &mut Output,
) -> (WhenBranch, References) {
let patterns = PoolVec::with_capacity(branch.patterns.len() as u32, env.pool);
let original_scope = scope;
let mut scope = original_scope.shallow_clone();
// TODO report symbols not bound in all patterns
for (node_id, loc_pattern) in patterns.iter_node_ids().zip(branch.patterns.iter()) {
let (new_output, can_pattern) = to_pattern2(
env,
&mut scope,
roc_parse::pattern::PatternType::WhenBranch,
&loc_pattern.value,
loc_pattern.region,
);
output.union(new_output);
env.set_region(node_id, loc_pattern.region);
env.pool[node_id] = can_pattern;
}
let (value, mut branch_output) =
to_expr2(env, &mut scope, &branch.value.value, branch.value.region);
let value_id = env.pool.add(value);
env.set_region(value_id, branch.value.region);
let guard = match &branch.guard {
None => None,
Some(loc_expr) => {
let (can_guard, guard_branch_output) =
to_expr2(env, &mut scope, &loc_expr.value, loc_expr.region);
let expr_id = env.pool.add(can_guard);
env.set_region(expr_id, loc_expr.region);
branch_output.union(guard_branch_output);
Some(expr_id)
}
};
// Now that we've collected all the references for this branch, check to see if
// any of the new idents it defined were unused. If any were, report it.
for (symbol, region) in scope.symbols() {
let symbol = symbol;
if !output.references.has_lookup(symbol)
&& !branch_output.references.has_lookup(symbol)
&& !original_scope.contains_symbol(symbol)
{
env.problem(Problem::UnusedDef(symbol, region));
}
}
let references = branch_output.references.clone();
output.union(branch_output);
(
WhenBranch {
patterns,
body: value_id,
guard,
},
references,
)
}
pub(crate) fn canonicalize_lookup(
env: &mut Env<'_>,
scope: &mut Scope,
module_name: &str,
ident: &str,
region: Region,
) -> (Expr2, Output) {
use Expr2::*;
let mut output = Output::default();
let can_expr = if module_name.is_empty() {
// Since module_name was empty, this is an unqualified var.
// Look it up in scope!
match scope.lookup(&(*ident).into(), region) {
Ok(symbol) => {
output.references.lookups.insert(symbol);
Var(symbol)
}
Err(problem) => {
env.problem(Problem::RuntimeError(problem));
RuntimeError()
}
}
} else {
// Since module_name was nonempty, this is a qualified var.
// Look it up in the env!
match env.qualified_lookup(module_name, ident, region) {
Ok(symbol) => {
output.references.lookups.insert(symbol);
Var(symbol)
}
Err(problem) => {
// Either the module wasn't imported, or
// it was imported but it doesn't expose this ident.
env.problem(Problem::RuntimeError(problem));
RuntimeError()
}
}
};
// If it's valid, this ident should be in scope already.
(can_expr, output)
}

View file

@ -0,0 +1,2 @@
pub mod canonicalize;
pub mod module;

View file

@ -2,14 +2,6 @@
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_imports)] #![allow(unused_imports)]
#![allow(unused_variables)] #![allow(unused_variables)]
use crate::lang::ast::{Expr2, FunctionDef, ValueDef};
use crate::lang::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
use crate::lang::expr::Env;
use crate::lang::expr::Output;
use crate::lang::pattern::Pattern2;
use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone};
use crate::lang::scope::Scope;
use crate::lang::types::Alias;
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::operator::desugar_def; use roc_can::operator::desugar_def;
use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_collections::all::{default_hasher, ImMap, ImSet, MutMap, MutSet, SendMap};
@ -22,6 +14,21 @@ use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::subs::{VarStore, Variable}; use roc_types::subs::{VarStore, Variable};
use crate::lang::core::def::def::canonicalize_defs;
use crate::lang::core::def::def::Def;
use crate::lang::core::def::def::{sort_can_defs, Declaration};
use crate::lang::core::expr::expr2::Expr2;
use crate::lang::core::expr::output::Output;
use crate::lang::core::pattern::Pattern2;
use crate::lang::core::types::Alias;
use crate::lang::core::val_def::ValueDef;
use crate::lang::env::Env;
use crate::lang::scope::Scope;
use crate::mem_pool::pool::NodeId;
use crate::mem_pool::pool::Pool;
use crate::mem_pool::pool_vec::PoolVec;
use crate::mem_pool::shallow_clone::ShallowClone;
pub struct ModuleOutput { pub struct ModuleOutput {
pub aliases: MutMap<Symbol, NodeId<Alias>>, pub aliases: MutMap<Symbol, NodeId<Alias>>,
pub rigid_variables: MutMap<Variable, Lowercase>, pub rigid_variables: MutMap<Variable, Lowercase>,

View file

@ -1,13 +1,5 @@
use bumpalo::{collections::Vec as BumpVec, Bump}; use bumpalo::{collections::Vec as BumpVec, Bump};
use crate::lang::{
ast::{ClosureExtra, Expr2, ExprId, RecordField, ValueDef, WhenBranch},
expr::Env,
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
pool::{Pool, PoolStr, PoolVec, ShallowClone},
types::{Type2, TypeId},
};
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap}; use roc_collections::all::{BumpMap, BumpMapDefault, Index, SendMap};
use roc_module::{ use roc_module::{
@ -21,6 +13,22 @@ use roc_types::{
types::{Category, Reason}, types::{Category, Reason},
}; };
use crate::{
lang::{
core::{
expr::{
expr2::{ClosureExtra, Expr2, ExprId, WhenBranch},
record_field::RecordField,
},
pattern::{DestructType, Pattern2, PatternId, PatternState2, RecordDestruct},
types::{Type2, TypeId},
val_def::ValueDef,
},
env::Env,
},
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
#[derive(Debug)] #[derive(Debug)]
pub enum Constraint<'a> { pub enum Constraint<'a> {
Eq(Type2, Expected<Type2>, Category, Region), Eq(Type2, Expected<Type2>, Category, Region),
@ -1744,3 +1752,397 @@ fn num_num(pool: &mut Pool, type_id: TypeId) -> Type2 {
pool.add(alias_content), pool.add(alias_content),
) )
} }
#[cfg(test)]
pub mod test_constrain {
use bumpalo::Bump;
use roc_can::expected::Expected;
use roc_collections::all::MutMap;
use roc_module::{
ident::Lowercase,
symbol::{IdentIds, Interns, ModuleIds, Symbol},
};
use roc_parse::parser::SyntaxError;
use roc_region::all::Region;
use roc_types::{
pretty_print::content_to_string,
solved_types::Solved,
subs::{Subs, VarStore, Variable},
};
use super::Constraint;
use crate::{
constrain::constrain_expr,
lang::{
core::{
expr::{expr2::Expr2, expr_to_expr2::loc_expr_to_expr2, output::Output},
types::Type2,
},
env::Env,
scope::Scope,
},
mem_pool::pool::Pool,
solve_type,
};
use indoc::indoc;
fn run_solve<'a>(
arena: &'a Bump,
mempool: &mut Pool,
aliases: MutMap<Symbol, roc_types::types::Alias>,
rigid_variables: MutMap<Variable, Lowercase>,
constraint: Constraint,
var_store: VarStore,
) -> (Solved<Subs>, solve_type::Env, Vec<solve_type::TypeError>) {
let env = solve_type::Env {
vars_by_symbol: MutMap::default(),
aliases,
};
let mut subs = Subs::new(var_store);
for (var, name) in rigid_variables {
subs.rigid_var(var, name);
}
// Now that the module is parsed, canonicalized, and constrained,
// we need to type check it.
let mut problems = Vec::new();
// Run the solver to populate Subs.
let (solved_subs, solved_env) =
solve_type::run(arena, mempool, &env, &mut problems, subs, &constraint);
(solved_subs, solved_env, problems)
}
fn infer_eq(actual: &str, expected_str: &str) {
let mut env_pool = Pool::with_capacity(1024);
let env_arena = Bump::new();
let code_arena = Bump::new();
let mut var_store = VarStore::default();
let var = var_store.fresh();
let dep_idents = IdentIds::exposed_builtins(8);
let exposed_ident_ids = IdentIds::default();
let mut module_ids = ModuleIds::default();
let mod_id = module_ids.get_or_insert(&"ModId123".into());
let mut env = Env::new(
mod_id,
&env_arena,
&mut env_pool,
&mut var_store,
dep_idents,
&module_ids,
exposed_ident_ids,
);
let mut scope = Scope::new(env.home, env.pool, env.var_store);
let region = Region::zero();
let expr2_result = str_to_expr2(&code_arena, actual, &mut env, &mut scope, region);
match expr2_result {
Ok((expr, _)) => {
let constraint = constrain_expr(
&code_arena,
&mut env,
&expr,
Expected::NoExpectation(Type2::Variable(var)),
Region::zero(),
);
let Env {
pool,
var_store: ref_var_store,
mut dep_idents,
..
} = env;
// extract the var_store out of the env again
let mut var_store = VarStore::default();
std::mem::swap(ref_var_store, &mut var_store);
let (mut solved, _, _) = run_solve(
&code_arena,
pool,
Default::default(),
Default::default(),
constraint,
var_store,
);
let subs = solved.inner_mut();
let content = subs.get_content_without_compacting(var);
// Connect the ModuleId to it's IdentIds
dep_idents.insert(mod_id, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids: dep_idents,
};
let actual_str = content_to_string(content, subs, mod_id, &interns);
assert_eq!(actual_str, expected_str);
}
Err(e) => panic!("syntax error {:?}", e),
}
}
pub fn str_to_expr2<'a>(
arena: &'a Bump,
input: &'a str,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> Result<(Expr2, Output), SyntaxError<'a>> {
match roc_parse::test_helpers::parse_loc_with(arena, input.trim()) {
Ok(loc_expr) => Ok(loc_expr_to_expr2(arena, loc_expr, env, scope, region)),
Err(fail) => Err(fail),
}
}
#[test]
fn constrain_str() {
infer_eq(
indoc!(
r#"
"type inference!"
"#
),
"Str",
)
}
// This will be more useful once we actually map
// strings less than 15 chars to SmallStr
#[test]
fn constrain_small_str() {
infer_eq(
indoc!(
r#"
"a"
"#
),
"Str",
)
}
#[test]
fn constrain_empty_record() {
infer_eq(
indoc!(
r#"
{}
"#
),
"{}",
)
}
#[test]
fn constrain_small_int() {
infer_eq(
indoc!(
r#"
12
"#
),
"Num *",
)
}
#[test]
fn constrain_float() {
infer_eq(
indoc!(
r#"
3.14
"#
),
"Float *",
)
}
#[test]
fn constrain_record() {
infer_eq(
indoc!(
r#"
{ x : 1, y : "hi" }
"#
),
"{ x : Num *, y : Str }",
)
}
#[test]
fn constrain_empty_list() {
infer_eq(
indoc!(
r#"
[]
"#
),
"List *",
)
}
#[test]
fn constrain_list() {
infer_eq(
indoc!(
r#"
[ 1, 2 ]
"#
),
"List (Num *)",
)
}
#[test]
fn constrain_list_of_records() {
infer_eq(
indoc!(
r#"
[ { x: 1 }, { x: 3 } ]
"#
),
"List { x : Num * }",
)
}
#[test]
fn constrain_global_tag() {
infer_eq(
indoc!(
r#"
Foo
"#
),
"[ Foo ]*",
)
}
#[test]
fn constrain_private_tag() {
infer_eq(
indoc!(
r#"
@Foo
"#
),
"[ @Foo ]*",
)
}
#[test]
fn constrain_call_and_accessor() {
infer_eq(
indoc!(
r#"
.foo { foo: "bar" }
"#
),
"Str",
)
}
#[test]
fn constrain_access() {
infer_eq(
indoc!(
r#"
{ foo: "bar" }.foo
"#
),
"Str",
)
}
#[test]
fn constrain_if() {
infer_eq(
indoc!(
r#"
if True then Green else Red
"#
),
"[ Green, Red ]*",
)
}
#[test]
fn constrain_when() {
infer_eq(
indoc!(
r#"
when if True then Green else Red is
Green -> Blue
Red -> Purple
"#
),
"[ Blue, Purple ]*",
)
}
#[test]
fn constrain_let_value() {
infer_eq(
indoc!(
r#"
person = { name: "roc" }
person
"#
),
"{ name : Str }",
)
}
#[test]
fn constrain_update() {
infer_eq(
indoc!(
r#"
person = { name: "roc" }
{ person & name: "bird" }
"#
),
"{ name : Str }",
)
}
#[ignore = "TODO: implement builtins in the editor"]
#[test]
fn constrain_run_low_level() {
infer_eq(
indoc!(
r#"
List.map [ { name: "roc" }, { name: "bird" } ] .name
"#
),
"List Str",
)
}
#[test]
fn constrain_closure() {
infer_eq(
indoc!(
r#"
x = 1
\{} -> x
"#
),
"{}* -> Num *",
)
}
}

45
ast/src/lang/core/ast.rs Normal file
View file

@ -0,0 +1,45 @@
use crate::{
ast_error::{ASTNodeIdWithoutExprId, ASTResult},
mem_pool::pool::Pool,
};
use super::{
def::def2::{def2_to_string, DefId},
expr::{expr2::ExprId, expr2_to_string::expr2_to_string},
header::AppHeader,
};
#[derive(Debug)]
pub struct AST {
pub header: AppHeader,
pub def_ids: Vec<DefId>,
}
#[derive(Debug, PartialEq, Copy, Clone)]
pub enum ASTNodeId {
ADefId(DefId),
AExprId(ExprId),
}
impl ASTNodeId {
pub fn to_expr_id(&self) -> ASTResult<ExprId> {
match self {
ASTNodeId::AExprId(expr_id) => Ok(*expr_id),
_ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?,
}
}
pub fn to_def_id(&self) -> ASTResult<DefId> {
match self {
ASTNodeId::ADefId(def_id) => Ok(*def_id),
_ => ASTNodeIdWithoutExprId { ast_node_id: *self }.fail()?,
}
}
}
pub fn ast_node_to_string(node_id: ASTNodeId, pool: &Pool) -> String {
match node_id {
ASTNodeId::ADefId(def_id) => def2_to_string(def_id, pool),
ASTNodeId::AExprId(expr_id) => expr2_to_string(expr_id, pool),
}
}

View file

@ -0,0 +1,70 @@
use roc_types::subs::VarStore;
use crate::{
lang::core::{def::def::Def, expr::expr2::Expr2},
mem_pool::{pool::Pool, pool_vec::PoolVec},
};
use super::def::def::Declaration;
pub(crate) fn decl_to_let(
pool: &mut Pool,
var_store: &mut VarStore,
decl: Declaration,
ret: Expr2,
) -> Expr2 {
match decl {
Declaration::Declare(def) => match def {
Def::AnnotationOnly { .. } => todo!(),
Def::Value(value_def) => {
let def_id = pool.add(value_def);
let body_id = pool.add(ret);
Expr2::LetValue {
def_id,
body_id,
body_var: var_store.fresh(),
}
}
Def::Function(function_def) => {
let def_id = pool.add(function_def);
let body_id = pool.add(ret);
Expr2::LetFunction {
def_id,
body_id,
body_var: var_store.fresh(),
}
}
},
Declaration::DeclareRec(defs) => {
let mut function_defs = vec![];
for def in defs {
match def {
Def::AnnotationOnly { .. } => todo!(),
Def::Function(function_def) => function_defs.push(function_def),
Def::Value(_) => unreachable!(),
}
}
let body_id = pool.add(ret);
Expr2::LetRec {
defs: PoolVec::new(function_defs.into_iter(), pool),
body_var: var_store.fresh(),
body_id,
}
}
Declaration::InvalidCycle(_entries, _) => {
// TODO: replace with something from Expr2
// Expr::RuntimeError(RuntimeError::CircularDef(entries))
todo!()
}
Declaration::Builtin(_) => {
// Builtins should only be added to top-level decls, not to let-exprs!
unreachable!()
}
}
}

View file

@ -12,15 +12,6 @@
// }; // };
// use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern}; // use crate::pattern::{bindings_from_patterns, canonicalize_pattern, Pattern};
// use crate::procedure::References; // use crate::procedure::References;
use crate::lang::ast::{Expr2, FunctionDef, Rigids, ValueDef};
use crate::lang::expr::Output;
use crate::lang::expr::{to_expr2, to_expr_id, Env};
use crate::lang::pattern::{
symbols_and_variables_from_pattern, symbols_from_pattern, to_pattern_id, Pattern2, PatternId,
};
use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone};
use crate::lang::scope::Scope;
use crate::lang::types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId};
use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap}; use roc_collections::all::{default_hasher, ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -33,6 +24,22 @@ use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use ven_graph::{strongly_connected_components, topological_sort_into_groups}; use ven_graph::{strongly_connected_components, topological_sort_into_groups};
use crate::{
lang::{
core::{
expr::{expr2::Expr2, expr_to_expr2::to_expr2, output::Output},
fun_def::FunctionDef,
pattern::{self, symbols_from_pattern, to_pattern_id, Pattern2, PatternId},
types::{to_annotation2, Alias, Annotation2, Signature, Type2, TypeId},
val_def::ValueDef,
},
env::Env,
rigids::Rigids,
scope::Scope,
},
mem_pool::{pool::Pool, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
#[derive(Debug)] #[derive(Debug)]
pub enum Def { pub enum Def {
AnnotationOnly { rigids: Rigids, annotation: TypeId }, AnnotationOnly { rigids: Rigids, annotation: TypeId },
@ -127,7 +134,7 @@ fn to_pending_def<'a>(
match def { match def {
Annotation(loc_pattern, loc_ann) => { Annotation(loc_pattern, loc_ann) => {
// This takes care of checking for shadowing and adding idents to scope. // This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( let (output, loc_can_pattern) = pattern::to_pattern_id(
env, env,
scope, scope,
pattern_type, pattern_type,
@ -142,7 +149,7 @@ fn to_pending_def<'a>(
} }
Body(loc_pattern, loc_expr) => { Body(loc_pattern, loc_expr) => {
// This takes care of checking for shadowing and adding idents to scope. // This takes care of checking for shadowing and adding idents to scope.
let (output, loc_can_pattern) = crate::lang::pattern::to_pattern_id( let (output, loc_can_pattern) = pattern::to_pattern_id(
env, env,
scope, scope,
pattern_type, pattern_type,

View file

@ -0,0 +1,43 @@
use crate::{
lang::core::{
expr::{expr2::Expr2, expr2_to_string::expr2_to_string},
pattern::Pattern2,
},
mem_pool::pool::{NodeId, Pool},
};
// A top level definition, not inside a function. For example: `main = "Hello, world!"`
#[derive(Debug)]
pub enum Def2 {
// ValueDef example: `main = "Hello, world!"`. identifier -> `main`, expr -> "Hello, world!"
ValueDef {
identifier_id: NodeId<Pattern2>,
expr_id: NodeId<Expr2>,
},
Blank,
}
pub type DefId = NodeId<Def2>;
pub fn def2_to_string(node_id: DefId, pool: &Pool) -> String {
let mut full_string = String::new();
let def2 = pool.get(node_id);
match def2 {
Def2::ValueDef {
identifier_id,
expr_id,
} => {
full_string.push_str(&format!(
"Def2::ValueDef(identifier_id: >>{:?}), expr_id: >>{:?})",
pool.get(*identifier_id),
expr2_to_string(*expr_id, pool)
));
}
Def2::Blank => {
full_string.push_str("Def2::Blank");
}
}
full_string
}

View file

@ -0,0 +1,97 @@
use bumpalo::collections::Vec as BumpVec;
use bumpalo::Bump;
use roc_parse::{parser::SyntaxError, pattern::PatternType};
use roc_region::all::Region;
use crate::lang::{
core::{expr::expr_to_expr2::loc_expr_to_expr2, pattern::to_pattern2},
env::Env,
scope::Scope,
};
use super::def2::Def2;
pub fn defs_to_defs2<'a>(
arena: &'a Bump,
env: &mut Env<'a>,
scope: &mut Scope,
parsed_defs: &'a BumpVec<roc_region::all::Loc<roc_parse::ast::Def<'a>>>,
region: Region,
) -> Vec<Def2> {
parsed_defs
.iter()
.map(|loc| to_def2_from_def(arena, env, scope, &loc.value, region))
.collect()
}
pub fn to_def2_from_def<'a>(
arena: &'a Bump,
env: &mut Env<'a>,
scope: &mut Scope,
parsed_def: &'a roc_parse::ast::Def<'a>,
region: Region,
) -> Def2 {
use roc_parse::ast::Def::*;
match parsed_def {
SpaceBefore(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region),
SpaceAfter(inner_def, _) => to_def2_from_def(arena, env, scope, inner_def, region),
Body(&loc_pattern, &loc_expr) => {
// TODO loc_pattern use identifier
let expr2 = loc_expr_to_expr2(arena, loc_expr, env, scope, region).0;
let expr_id = env.pool.add(expr2);
use roc_parse::ast::Pattern::*;
match loc_pattern.value {
Identifier(_) => {
let (_, pattern2) = to_pattern2(
env,
scope,
PatternType::TopLevelDef,
&loc_pattern.value,
region,
);
let pattern_id = env.pool.add(pattern2);
// TODO support with annotation
Def2::ValueDef {
identifier_id: pattern_id,
expr_id,
}
}
other => {
unimplemented!(
"I don't yet know how to convert the pattern {:?} into an expr2",
other
)
}
}
}
other => {
unimplemented!(
"I don't know how to make an expr2 from this def yet: {:?}",
other
)
}
}
}
pub fn str_to_def2<'a>(
arena: &'a Bump,
input: &'a str,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> Result<Vec<Def2>, SyntaxError<'a>> {
match roc_parse::test_helpers::parse_defs_with(arena, input.trim()) {
Ok(vec_loc_def) => Ok(defs_to_defs2(
arena,
env,
scope,
arena.alloc(vec_loc_def),
region,
)),
Err(fail) => Err(fail),
}
}

View file

@ -0,0 +1,3 @@
pub mod def;
pub mod def2;
pub mod def_to_def2;

View file

@ -0,0 +1,233 @@
use arraystring::{typenum::U30, ArrayString};
use roc_types::subs::Variable;
use crate::{
lang::core::{fun_def::FunctionDef, pattern::Pattern2, val_def::ValueDef},
mem_pool::{pool::NodeId, pool_str::PoolStr, pool_vec::PoolVec},
};
use roc_can::expr::Recursive;
use roc_module::low_level::LowLevel;
use roc_module::operator::CalledVia;
use roc_module::symbol::Symbol;
use super::record_field::RecordField;
pub type ArrString = ArrayString<U30>;
// TODO make the inner types private?
pub type ExprId = NodeId<Expr2>;
/// An Expr that fits in 32B.
/// It has a 1B discriminant and variants which hold payloads of at most 31B.
#[derive(Debug)]
pub enum Expr2 {
/// A negative number literal without a dot
SmallInt {
number: IntVal, // 16B
var: Variable, // 4B
style: IntStyle, // 1B
text: PoolStr, // 8B
},
// TODO(rvcas): rename this eventually
/// A large (over 64-bit) negative number literal without a dot.
/// This variant can't use IntVal because if IntVal stored 128-bit
/// integers, it would be 32B on its own because of alignment.
I128 {
number: i128, // 16B
var: Variable, // 4B
style: IntStyle, // 1B
text: PoolStr, // 8B
},
// TODO(rvcas): rename this eventually
/// A large (over 64-bit) nonnegative number literal without a dot
/// This variant can't use IntVal because if IntVal stored 128-bit
/// integers, it would be 32B on its own because of alignment.
U128 {
number: u128, // 16B
var: Variable, // 4B
style: IntStyle, // 1B
text: PoolStr, // 8B
},
/// A floating-point literal (with a dot)
Float {
number: FloatVal, // 16B
var: Variable, // 4B
text: PoolStr, // 8B
},
/// string literals of length up to 30B
SmallStr(ArrString), // 31B
/// string literals of length 31B or more
Str(PoolStr), // 8B
// Lookups
Var(Symbol), // 8B
InvalidLookup(PoolStr), // 8B
List {
elem_var: Variable, // 4B
elems: PoolVec<ExprId>, // 8B
},
If {
cond_var: Variable, // 4B
expr_var: Variable, // 4B
branches: PoolVec<(ExprId, ExprId)>, // 8B
final_else: ExprId, // 4B
},
When {
cond_var: Variable, // 4B
expr_var: Variable, // 4B
branches: PoolVec<WhenBranch>, // 8B
cond: ExprId, // 4B
},
LetRec {
defs: PoolVec<FunctionDef>, // 8B
body_var: Variable, // 8B
body_id: ExprId, // 4B
},
LetFunction {
def_id: NodeId<FunctionDef>, // 4B
body_var: Variable, // 8B
body_id: ExprId, // 4B
},
LetValue {
def_id: NodeId<ValueDef>, // 4B
body_id: ExprId, // 4B
body_var: Variable, // 4B
},
Call {
args: PoolVec<(Variable, ExprId)>, // 8B
expr: ExprId, // 4B
expr_var: Variable, // 4B
fn_var: Variable, // 4B
closure_var: Variable, // 4B
called_via: CalledVia, // 2B
},
RunLowLevel {
op: LowLevel, // 1B
args: PoolVec<(Variable, ExprId)>, // 8B
ret_var: Variable, // 4B
},
Closure {
args: PoolVec<(Variable, NodeId<Pattern2>)>, // 8B
name: Symbol, // 8B
body: ExprId, // 4B
function_type: Variable, // 4B
recursive: Recursive, // 1B
extra: NodeId<ClosureExtra>, // 4B
},
// Product Types
Record {
record_var: Variable, // 4B
fields: PoolVec<RecordField>, // 8B
},
/// Empty record constant
EmptyRecord,
/// Look up exactly one field on a record, e.g. (expr).foo.
Access {
field: PoolStr, // 4B
expr: ExprId, // 4B
record_var: Variable, // 4B
ext_var: Variable, // 4B
field_var: Variable, // 4B
},
/// field accessor as a function, e.g. (.foo) expr
Accessor {
function_var: Variable, // 4B
closure_var: Variable, // 4B
field: PoolStr, // 4B
record_var: Variable, // 4B
ext_var: Variable, // 4B
field_var: Variable, // 4B
},
Update {
symbol: Symbol, // 8B
updates: PoolVec<RecordField>, // 8B
record_var: Variable, // 4B
ext_var: Variable, // 4B
},
// Sum Types
GlobalTag {
name: PoolStr, // 4B
variant_var: Variable, // 4B
ext_var: Variable, // 4B
arguments: PoolVec<(Variable, ExprId)>, // 8B
},
PrivateTag {
name: Symbol, // 8B
variant_var: Variable, // 4B
ext_var: Variable, // 4B
arguments: PoolVec<(Variable, ExprId)>, // 8B
},
Blank, // Rendered as empty box in editor
// Compiles, but will crash if reached
RuntimeError(/* TODO make a version of RuntimeError that fits in 15B */),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Problem {
RanOutOfNodeIds,
}
pub type Res<T> = Result<T, Problem>;
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum IntStyle {
Decimal,
Octal,
Hex,
Binary,
}
impl IntStyle {
pub fn from_base(base: roc_parse::ast::Base) -> Self {
use roc_parse::ast::Base;
match base {
Base::Decimal => Self::Decimal,
Base::Octal => Self::Octal,
Base::Hex => Self::Hex,
Base::Binary => Self::Binary,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum IntVal {
I64(i64),
U64(u64),
I32(i32),
U32(u32),
I16(i16),
U16(u16),
I8(i8),
U8(u8),
}
#[test]
fn size_of_intval() {
assert_eq!(std::mem::size_of::<IntVal>(), 16);
}
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum FloatVal {
F64(f64),
F32(f32),
}
#[derive(Debug)]
pub struct WhenBranch {
pub patterns: PoolVec<Pattern2>, // 4B
pub body: ExprId, // 3B
pub guard: Option<ExprId>, // 4B
}
/// This is overflow data from a Closure variant, which needs to store
/// more than 32B of total data
#[derive(Debug)]
pub struct ClosureExtra {
pub return_type: Variable, // 4B
pub captured_symbols: PoolVec<(Symbol, Variable)>, // 8B
pub closure_type: Variable, // 4B
pub closure_ext_var: Variable, // 4B
}

View file

@ -0,0 +1,139 @@
use crate::{
lang::core::{expr::record_field::RecordField, val_def::value_def_to_string},
mem_pool::pool::Pool,
};
use super::expr2::{Expr2, ExprId};
use roc_types::subs::Variable;
pub fn expr2_to_string(node_id: ExprId, pool: &Pool) -> String {
let mut full_string = String::new();
let expr2 = pool.get(node_id);
expr2_to_string_helper(expr2, 0, pool, &mut full_string);
full_string
}
fn get_spacing(indent_level: usize) -> String {
std::iter::repeat(" ")
.take(indent_level)
.collect::<Vec<&str>>()
.join("")
}
fn expr2_to_string_helper(
expr2: &Expr2,
indent_level: usize,
pool: &Pool,
out_string: &mut String,
) {
out_string.push_str(&get_spacing(indent_level));
match expr2 {
Expr2::SmallStr(arr_string) => out_string.push_str(&format!(
"{}{}{}",
"SmallStr(\"",
arr_string.as_str(),
"\")",
)),
Expr2::Str(pool_str) => {
out_string.push_str(&format!("{}{}{}", "Str(\"", pool_str.as_str(pool), "\")",))
}
Expr2::Blank => out_string.push_str("Blank"),
Expr2::EmptyRecord => out_string.push_str("EmptyRecord"),
Expr2::Record { record_var, fields } => {
out_string.push_str("Record:\n");
out_string.push_str(&var_to_string(record_var, indent_level + 1));
out_string.push_str(&format!("{}fields: [\n", get_spacing(indent_level + 1)));
let mut first_child = true;
for field in fields.iter(pool) {
if !first_child {
out_string.push_str(", ")
} else {
first_child = false;
}
match field {
RecordField::InvalidLabelOnly(pool_str, var) => {
out_string.push_str(&format!(
"{}({}, Var({:?})",
get_spacing(indent_level + 2),
pool_str.as_str(pool),
var,
));
}
RecordField::LabelOnly(pool_str, var, symbol) => {
out_string.push_str(&format!(
"{}({}, Var({:?}), Symbol({:?})",
get_spacing(indent_level + 2),
pool_str.as_str(pool),
var,
symbol
));
}
RecordField::LabeledValue(pool_str, var, val_node_id) => {
out_string.push_str(&format!(
"{}({}, Var({:?}), Expr2(\n",
get_spacing(indent_level + 2),
pool_str.as_str(pool),
var,
));
let val_expr2 = pool.get(*val_node_id);
expr2_to_string_helper(val_expr2, indent_level + 3, pool, out_string);
out_string.push_str(&format!("{})\n", get_spacing(indent_level + 2)));
}
}
}
out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1)));
}
Expr2::List { elem_var, elems } => {
out_string.push_str("List:\n");
out_string.push_str(&var_to_string(elem_var, indent_level + 1));
out_string.push_str(&format!("{}elems: [\n", get_spacing(indent_level + 1)));
let mut first_elt = true;
for elem_expr2_id in elems.iter(pool) {
if !first_elt {
out_string.push_str(", ")
} else {
first_elt = false;
}
let elem_expr2 = pool.get(*elem_expr2_id);
expr2_to_string_helper(elem_expr2, indent_level + 2, pool, out_string)
}
out_string.push_str(&format!("{}]\n", get_spacing(indent_level + 1)));
}
Expr2::InvalidLookup(pool_str) => {
out_string.push_str(&format!("InvalidLookup({})", pool_str.as_str(pool)));
}
Expr2::SmallInt { text, .. } => {
out_string.push_str(&format!("SmallInt({})", text.as_str(pool)));
}
Expr2::LetValue {
def_id, body_id, ..
} => {
out_string.push_str(&format!(
"LetValue(def_id: >>{:?}), body_id: >>{:?})",
value_def_to_string(pool.get(*def_id), pool),
pool.get(*body_id)
));
}
other => todo!("Implement for {:?}", other),
}
out_string.push('\n');
}
fn var_to_string(some_var: &Variable, indent_level: usize) -> String {
format!("{}Var({:?})\n", get_spacing(indent_level + 1), some_var)
}

View file

@ -0,0 +1,710 @@
use bumpalo::Bump;
use roc_can::expr::Recursive;
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
use roc_can::operator::desugar_expr;
use roc_collections::all::MutSet;
use roc_module::symbol::Symbol;
use roc_parse::{ast::Expr, pattern::PatternType};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use super::{expr2::Expr2, output::Output};
use crate::canonicalization::canonicalize::{
canonicalize_fields, canonicalize_lookup, canonicalize_when_branch, CanonicalizeRecordProblem,
};
use crate::lang::core::declaration::decl_to_let;
use crate::lang::core::def::def::{canonicalize_defs, sort_can_defs};
use crate::lang::core::expr::expr2::ClosureExtra;
use crate::lang::core::pattern::to_pattern2;
use crate::lang::core::str::flatten_str_literal;
use crate::mem_pool::shallow_clone::ShallowClone;
use crate::{
lang::{
core::expr::expr2::{ExprId, FloatVal, IntStyle, IntVal},
env::Env,
scope::Scope,
},
mem_pool::{pool_str::PoolStr, pool_vec::PoolVec},
};
pub fn loc_expr_to_expr2<'a>(
arena: &'a Bump,
loc_expr: Located<Expr<'a>>,
env: &mut Env<'a>,
scope: &mut Scope,
region: Region,
) -> (Expr2, Output) {
let desugared_loc_expr = desugar_expr(arena, arena.alloc(loc_expr));
to_expr2(env, scope, arena.alloc(desugared_loc_expr.value), region)
}
const ZERO: Region = Region::zero();
pub fn to_expr2<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
parse_expr: &'a roc_parse::ast::Expr<'a>,
region: Region,
) -> (Expr2, self::Output) {
use roc_parse::ast::Expr::*;
match parse_expr {
Float(string) => {
match finish_parsing_float(string) {
Ok(float) => {
let expr = Expr2::Float {
number: FloatVal::F64(float),
var: env.var_store.fresh(),
text: PoolStr::new(string, &mut env.pool),
};
(expr, Output::default())
}
Err((raw, error)) => {
// emit runtime error
let runtime_error = RuntimeError::InvalidFloat(error, ZERO, raw.into());
env.problem(Problem::RuntimeError(runtime_error));
//
// Expr::RuntimeError(runtime_error)
todo!()
}
}
}
Num(string) => {
match finish_parsing_int(string) {
Ok(int) => {
let expr = Expr2::SmallInt {
number: IntVal::I64(int),
var: env.var_store.fresh(),
// TODO non-hardcode
style: IntStyle::Decimal,
text: PoolStr::new(string, &mut env.pool),
};
(expr, Output::default())
}
Err((raw, error)) => {
// emit runtime error
let runtime_error = RuntimeError::InvalidInt(
error,
roc_parse::ast::Base::Decimal,
ZERO,
raw.into(),
);
env.problem(Problem::RuntimeError(runtime_error));
//
// Expr::RuntimeError(runtime_error)
todo!()
}
}
}
NonBase10Int {
string,
base,
is_negative,
} => {
match finish_parsing_base(string, *base, *is_negative) {
Ok(int) => {
let expr = Expr2::SmallInt {
number: IntVal::I64(int),
var: env.var_store.fresh(),
// TODO non-hardcode
style: IntStyle::from_base(*base),
text: PoolStr::new(string, &mut env.pool),
};
(expr, Output::default())
}
Err((raw, error)) => {
// emit runtime error
let runtime_error = RuntimeError::InvalidInt(error, *base, ZERO, raw.into());
env.problem(Problem::RuntimeError(runtime_error));
//
// Expr::RuntimeError(runtime_error)
todo!()
}
}
}
Str(literal) => flatten_str_literal(env, scope, literal),
List { items, .. } => {
let mut output = Output::default();
let output_ref = &mut output;
let elems: PoolVec<ExprId> = PoolVec::with_capacity(items.len() as u32, env.pool);
for (node_id, item) in elems.iter_node_ids().zip(items.iter()) {
let (expr, sub_output) = to_expr2(env, scope, &item.value, item.region);
output_ref.union(sub_output);
let expr_id = env.pool.add(expr);
env.pool[node_id] = expr_id;
}
let expr = Expr2::List {
elem_var: env.var_store.fresh(),
elems,
};
(expr, output)
}
GlobalTag(tag) => {
// a global tag without any arguments
(
Expr2::GlobalTag {
name: PoolStr::new(tag, env.pool),
variant_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
arguments: PoolVec::empty(env.pool),
},
Output::default(),
)
}
PrivateTag(name) => {
// a private tag without any arguments
let ident_id = env.ident_ids.get_or_insert(&(*name).into());
let name = Symbol::new(env.home, ident_id);
(
Expr2::PrivateTag {
name,
variant_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
arguments: PoolVec::empty(env.pool),
},
Output::default(),
)
}
RecordUpdate {
fields,
update: loc_update,
final_comments: _,
} => {
let (can_update, update_out) =
to_expr2(env, scope, &loc_update.value, loc_update.region);
if let Expr2::Var(symbol) = &can_update {
match canonicalize_fields(env, scope, fields) {
Ok((can_fields, mut output)) => {
output.references.union_mut(update_out.references);
let answer = Expr2::Update {
record_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
symbol: *symbol,
updates: can_fields,
};
(answer, output)
}
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
field_name: _,
field_region: _,
record_region: _,
}) => {
// let runtime_error = roc_problem::can::RuntimeError::InvalidOptionalValue {
// field_name,
// field_region,
// record_region,
// };
//
// env.problem(Problem::RuntimeError(runtime_error));
todo!()
}
}
} else {
// only (optionally qualified) variables can be updated, not arbitrary expressions
// let error = roc_problem::can::RuntimeError::InvalidRecordUpdate {
// region: can_update.region,
// };
//
// let answer = Expr::RuntimeError(error.clone());
//
// env.problems.push(Problem::RuntimeError(error));
//
// (answer, Output::default())
todo!()
}
}
Record {
fields,
final_comments: _,
} => {
if fields.is_empty() {
(Expr2::EmptyRecord, Output::default())
} else {
match canonicalize_fields(env, scope, fields) {
Ok((can_fields, output)) => (
Expr2::Record {
record_var: env.var_store.fresh(),
fields: can_fields,
},
output,
),
Err(CanonicalizeRecordProblem::InvalidOptionalValue {
field_name: _,
field_region: _,
record_region: _,
}) => {
// let runtime_error = RuntimeError::InvalidOptionalValue {
// field_name,
// field_region,
// record_region,
// };
//
// env.problem(runtime_error);
// (
// Expr::RuntimeError(
// ),
// Output::default(),
//
// )
todo!()
}
}
}
}
Access(record_expr, field) => {
// TODO
let region = ZERO;
let (record_expr_id, output) = to_expr_id(env, scope, record_expr, region);
(
Expr2::Access {
record_var: env.var_store.fresh(),
field_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
expr: record_expr_id,
field: PoolStr::new(field, env.pool),
},
output,
)
}
AccessorFunction(field) => (
Expr2::Accessor {
function_var: env.var_store.fresh(),
record_var: env.var_store.fresh(),
ext_var: env.var_store.fresh(),
closure_var: env.var_store.fresh(),
field_var: env.var_store.fresh(),
field: PoolStr::new(field, env.pool),
},
Output::default(),
),
If(branches, final_else) => {
let mut new_branches = Vec::with_capacity(branches.len());
let mut output = Output::default();
for (condition, then_branch) in branches.iter() {
let (cond, cond_output) = to_expr2(env, scope, &condition.value, condition.region);
let (then_expr, then_output) =
to_expr2(env, scope, &then_branch.value, then_branch.region);
output.references.union_mut(cond_output.references);
output.references.union_mut(then_output.references);
new_branches.push((env.pool.add(cond), env.pool.add(then_expr)));
}
let (else_expr, else_output) =
to_expr2(env, scope, &final_else.value, final_else.region);
output.references.union_mut(else_output.references);
let expr = Expr2::If {
cond_var: env.var_store.fresh(),
expr_var: env.var_store.fresh(),
branches: PoolVec::new(new_branches.into_iter(), env.pool),
final_else: env.pool.add(else_expr),
};
(expr, output)
}
When(loc_cond, branches) => {
// Infer the condition expression's type.
let cond_var = env.var_store.fresh();
let (can_cond, mut output) = to_expr2(env, scope, &loc_cond.value, loc_cond.region);
// the condition can never be a tail-call
output.tail_call = None;
let can_branches = PoolVec::with_capacity(branches.len() as u32, env.pool);
for (node_id, branch) in can_branches.iter_node_ids().zip(branches.iter()) {
let (can_when_branch, branch_references) =
canonicalize_when_branch(env, scope, *branch, &mut output);
output.references.union_mut(branch_references);
env.pool[node_id] = can_when_branch;
}
// A "when" with no branches is a runtime error, but it will mess things up
// if code gen mistakenly thinks this is a tail call just because its condition
// happened to be one. (The condition gave us our initial output value.)
if branches.is_empty() {
output.tail_call = None;
}
// Incorporate all three expressions into a combined Output value.
let expr = Expr2::When {
expr_var: env.var_store.fresh(),
cond_var,
cond: env.pool.add(can_cond),
branches: can_branches,
};
(expr, output)
}
Closure(loc_arg_patterns, loc_body_expr) => {
// The globally unique symbol that will refer to this closure once it gets converted
// into a top-level procedure for code gen.
//
// In the Foo module, this will look something like Foo.$1 or Foo.$2.
let symbol = env
.closure_name_symbol
.unwrap_or_else(|| env.gen_unique_symbol());
env.closure_name_symbol = None;
// The body expression gets a new scope for canonicalization.
// Shadow `scope` to make sure we don't accidentally use the original one for the
// rest of this block, but keep the original around for later diffing.
let original_scope = scope;
let mut scope = original_scope.shallow_clone();
let can_args = PoolVec::with_capacity(loc_arg_patterns.len() as u32, env.pool);
let mut output = Output::default();
let mut bound_by_argument_patterns = MutSet::default();
for (node_id, loc_pattern) in can_args.iter_node_ids().zip(loc_arg_patterns.iter()) {
let (new_output, can_arg) = to_pattern2(
env,
&mut scope,
roc_parse::pattern::PatternType::FunctionArg,
&loc_pattern.value,
loc_pattern.region,
);
bound_by_argument_patterns
.extend(new_output.references.bound_symbols.iter().copied());
output.union(new_output);
let pattern_id = env.add(can_arg, loc_pattern.region);
env.pool[node_id] = (env.var_store.fresh(), pattern_id);
}
let (body_expr, new_output) =
to_expr2(env, &mut scope, &loc_body_expr.value, loc_body_expr.region);
let mut captured_symbols: MutSet<Symbol> =
new_output.references.lookups.iter().copied().collect();
// filter out the closure's name itself
captured_symbols.remove(&symbol);
// symbols bound either in this pattern or deeper down are not captured!
captured_symbols.retain(|s| !new_output.references.bound_symbols.contains(s));
captured_symbols.retain(|s| !bound_by_argument_patterns.contains(s));
// filter out top-level symbols
// those will be globally available, and don't need to be captured
captured_symbols.retain(|s| !env.top_level_symbols.contains(s));
// filter out imported symbols
// those will be globally available, and don't need to be captured
captured_symbols.retain(|s| s.module_id() == env.home);
// TODO any Closure that has an empty `captured_symbols` list could be excluded!
output.union(new_output);
// filter out aliases
captured_symbols.retain(|s| !output.references.referenced_aliases.contains(s));
// filter out functions that don't close over anything
captured_symbols.retain(|s| !output.non_closures.contains(s));
// Now that we've collected all the references, check to see if any of the args we defined
// went unreferenced. If any did, report them as unused arguments.
for (sub_symbol, region) in scope.symbols() {
if !original_scope.contains_symbol(sub_symbol) {
if !output.references.has_lookup(sub_symbol) {
// The body never referenced this argument we declared. It's an unused argument!
env.problem(Problem::UnusedArgument(symbol, sub_symbol, region));
}
// We shouldn't ultimately count arguments as referenced locals. Otherwise,
// we end up with weird conclusions like the expression (\x -> x + 1)
// references the (nonexistant) local variable x!
output.references.lookups.remove(&sub_symbol);
}
}
env.register_closure(symbol, output.references.clone());
let mut captured_symbols: Vec<_> = captured_symbols
.into_iter()
.map(|s| (s, env.var_store.fresh()))
.collect();
// sort symbols, so we know the order in which they're stored in the closure record
captured_symbols.sort();
// store that this function doesn't capture anything. It will be promoted to a
// top-level function, and does not need to be captured by other surrounding functions.
if captured_symbols.is_empty() {
output.non_closures.insert(symbol);
}
let captured_symbols = PoolVec::new(captured_symbols.into_iter(), env.pool);
let extra = ClosureExtra {
return_type: env.var_store.fresh(), // 4B
captured_symbols, // 8B
closure_type: env.var_store.fresh(), // 4B
closure_ext_var: env.var_store.fresh(), // 4B
};
(
Expr2::Closure {
function_type: env.var_store.fresh(),
name: symbol,
recursive: Recursive::NotRecursive,
args: can_args,
body: env.add(body_expr, loc_body_expr.region),
extra: env.pool.add(extra),
},
output,
)
}
Apply(loc_fn, loc_args, application_style) => {
// The expression that evaluates to the function being called, e.g. `foo` in
// (foo) bar baz
let fn_region = loc_fn.region;
// Canonicalize the function expression and its arguments
let (fn_expr, mut output) = to_expr2(env, scope, &loc_fn.value, fn_region);
// The function's return type
let args = PoolVec::with_capacity(loc_args.len() as u32, env.pool);
for (node_id, loc_arg) in args.iter_node_ids().zip(loc_args.iter()) {
let (arg_expr_id, arg_out) = to_expr_id(env, scope, &loc_arg.value, loc_arg.region);
env.pool[node_id] = (env.var_store.fresh(), arg_expr_id);
output.references.union_mut(arg_out.references);
}
// Default: We're not tail-calling a symbol (by name), we're tail-calling a function value.
output.tail_call = None;
let expr = match fn_expr {
Expr2::Var(ref symbol) => {
output.references.calls.insert(*symbol);
// we're tail-calling a symbol by name, check if it's the tail-callable symbol
output.tail_call = match &env.tailcallable_symbol {
Some(tc_sym) if *tc_sym == *symbol => Some(*symbol),
Some(_) | None => None,
};
// IDEA: Expr2::CallByName?
let fn_expr_id = env.add(fn_expr, fn_region);
Expr2::Call {
args,
expr: fn_expr_id,
expr_var: env.var_store.fresh(),
fn_var: env.var_store.fresh(),
closure_var: env.var_store.fresh(),
called_via: *application_style,
}
}
Expr2::RuntimeError() => {
// We can't call a runtime error; bail out by propagating it!
return (fn_expr, output);
}
Expr2::GlobalTag {
variant_var,
ext_var,
name,
..
} => Expr2::GlobalTag {
variant_var,
ext_var,
name,
arguments: args,
},
Expr2::PrivateTag {
variant_var,
ext_var,
name,
..
} => Expr2::PrivateTag {
variant_var,
ext_var,
name,
arguments: args,
},
_ => {
// This could be something like ((if True then fn1 else fn2) arg1 arg2).
let fn_expr_id = env.add(fn_expr, fn_region);
Expr2::Call {
args,
expr: fn_expr_id,
expr_var: env.var_store.fresh(),
fn_var: env.var_store.fresh(),
closure_var: env.var_store.fresh(),
called_via: *application_style,
}
}
};
(expr, output)
}
Defs(loc_defs, loc_ret) => {
let (unsorted, mut scope, defs_output, symbols_introduced) = canonicalize_defs(
env,
Output::default(),
scope,
loc_defs,
PatternType::DefExpr,
);
// The def as a whole is a tail call iff its return expression is a tail call.
// Use its output as a starting point because its tail_call already has the right answer!
let (ret_expr, mut output) = to_expr2(env, &mut scope, &loc_ret.value, loc_ret.region);
output
.introduced_variables
.union(&defs_output.introduced_variables);
output.references.union_mut(defs_output.references);
// Now that we've collected all the references, check to see if any of the new idents
// we defined went unused by the return expression. If any were unused, report it.
for (symbol, region) in symbols_introduced {
if !output.references.has_lookup(symbol) {
env.problem(Problem::UnusedDef(symbol, region));
}
}
let (can_defs, output) = sort_can_defs(env, unsorted, output);
match can_defs {
Ok(decls) => {
let mut expr = ret_expr;
for declaration in decls.into_iter().rev() {
expr = decl_to_let(env.pool, env.var_store, declaration, expr);
}
(expr, output)
}
Err(_err) => {
// TODO: fix this to be something from Expr2
// (RuntimeError(err), output)
todo!()
}
}
}
PrecedenceConflict { .. } => {
// use roc_problem::can::RuntimeError::*;
//
// let problem = PrecedenceProblem::BothNonAssociative(
// *whole_region,
// binop1.clone(),
// binop2.clone(),
// );
//
// env.problem(Problem::PrecedenceProblem(problem.clone()));
//
// (
// RuntimeError(InvalidPrecedence(problem, region)),
// Output::default(),
// )
todo!()
}
MalformedClosure => {
// use roc_problem::can::RuntimeError::*;
// (RuntimeError(MalformedClosure(region)), Output::default())
todo!()
}
MalformedIdent(_name, _problem) => {
// use roc_problem::can::RuntimeError::*;
//
// let problem = MalformedIdentifier((*name).into(), region);
// env.problem(Problem::RuntimeError(problem.clone()));
//
// (RuntimeError(problem), Output::default())
todo!()
}
Var { module_name, ident } => canonicalize_lookup(env, scope, module_name, ident, region),
// Below this point, we shouln't see any of these nodes anymore because
// operator desugaring should have removed them!
bad_expr @ ParensAround(_) => {
panic!(
"A ParensAround did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
bad_expr @ SpaceBefore(_, _) => {
panic!(
"A SpaceBefore did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
bad_expr @ SpaceAfter(_, _) => {
panic!(
"A SpaceAfter did not get removed during operator desugaring somehow: {:#?}",
bad_expr
);
}
bad_expr @ BinOps { .. } => {
panic!(
"A binary operator chain did not get desugared somehow: {:#?}",
bad_expr
);
}
bad_expr @ UnaryOp(_, _) => {
panic!(
"A unary operator did not get desugared somehow: {:#?}",
bad_expr
);
}
rest => todo!("not yet implemented {:?}", rest),
}
}
pub fn to_expr_id<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
parse_expr: &'a roc_parse::ast::Expr<'a>,
region: Region,
) -> (ExprId, Output) {
let (expr, output) = to_expr2(env, scope, parse_expr, region);
(env.add(expr, region), output)
}

View file

@ -0,0 +1,51 @@
use roc_collections::all::MutMap;
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_types::subs::Variable;
#[derive(Clone, Debug, PartialEq, Default)]
pub struct IntroducedVariables {
// Rigids must be unique within a type annoation.
// E.g. in `identity : a -> a`, there should only be one
// variable (a rigid one, with name "a").
// Hence `rigids : Map<Lowercase, Variable>`
//
// But then between annotations, the same name can occur multiple times,
// but a variable can only have one name. Therefore
// `ftv : Map<Variable, Lowercase>`.
pub wildcards: Vec<Variable>,
pub var_by_name: MutMap<Lowercase, Variable>,
pub name_by_var: MutMap<Variable, Lowercase>,
pub host_exposed_aliases: MutMap<Symbol, Variable>,
}
impl IntroducedVariables {
pub fn insert_named(&mut self, name: Lowercase, var: Variable) {
self.var_by_name.insert(name.clone(), var);
self.name_by_var.insert(var, name);
}
pub fn insert_wildcard(&mut self, var: Variable) {
self.wildcards.push(var);
}
pub fn insert_host_exposed_alias(&mut self, symbol: Symbol, var: Variable) {
self.host_exposed_aliases.insert(symbol, var);
}
pub fn union(&mut self, other: &Self) {
self.wildcards.extend(other.wildcards.iter().cloned());
self.var_by_name.extend(other.var_by_name.clone());
self.name_by_var.extend(other.name_by_var.clone());
self.host_exposed_aliases
.extend(other.host_exposed_aliases.clone());
}
pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> {
self.var_by_name.get(name)
}
pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> {
self.name_by_var.get(&var)
}
}

View file

@ -0,0 +1,6 @@
pub mod expr2;
pub mod expr2_to_string;
pub(crate) mod expr_to_expr2;
mod introduced_vars;
pub(crate) mod output;
pub mod record_field;

View file

@ -0,0 +1,30 @@
use crate::{
lang::core::{def::def::References, types::Alias},
mem_pool::pool::NodeId,
};
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol;
use super::introduced_vars::IntroducedVariables;
#[derive(Clone, Default, Debug, PartialEq)]
pub struct Output {
pub references: References,
pub tail_call: Option<Symbol>,
pub introduced_variables: IntroducedVariables,
pub aliases: MutMap<Symbol, NodeId<Alias>>,
pub non_closures: MutSet<Symbol>,
}
impl Output {
pub fn union(&mut self, other: Self) {
self.references.union_mut(other.references);
if let (None, Some(later)) = (self.tail_call, other.tail_call) {
self.tail_call = Some(later);
}
self.aliases.extend(other.aliases);
self.non_closures.extend(other.non_closures);
}
}

View file

@ -0,0 +1,49 @@
use roc_types::subs::Variable;
use crate::mem_pool::pool_str::PoolStr;
use roc_module::symbol::Symbol;
use super::expr2::ExprId;
#[derive(Debug)]
pub enum RecordField {
InvalidLabelOnly(PoolStr, Variable),
LabelOnly(PoolStr, Variable, Symbol),
LabeledValue(PoolStr, Variable, ExprId),
}
use RecordField::*;
impl RecordField {
pub fn get_record_field_var(&self) -> &Variable {
match self {
InvalidLabelOnly(_, var) => var,
LabelOnly(_, var, _) => var,
LabeledValue(_, var, _) => var,
}
}
pub fn get_record_field_pool_str(&self) -> &PoolStr {
match self {
InvalidLabelOnly(pool_str, _) => pool_str,
LabelOnly(pool_str, _, _) => pool_str,
LabeledValue(pool_str, _, _) => pool_str,
}
}
pub fn get_record_field_pool_str_mut(&mut self) -> &mut PoolStr {
match self {
InvalidLabelOnly(pool_str, _) => pool_str,
LabelOnly(pool_str, _, _) => pool_str,
LabeledValue(pool_str, _, _) => pool_str,
}
}
pub fn get_record_field_val_node_id(&self) -> Option<ExprId> {
match self {
InvalidLabelOnly(_, _) => None,
LabelOnly(_, _, _) => None,
LabeledValue(_, _, field_val_id) => Some(*field_val_id),
}
}
}

View file

@ -0,0 +1,61 @@
use crate::{
lang::rigids::Rigids,
mem_pool::{pool::NodeId, pool_vec::PoolVec, shallow_clone::ShallowClone},
};
use roc_module::symbol::Symbol;
use roc_types::subs::Variable;
use super::{
expr::expr2::ExprId,
pattern::PatternId,
types::{Type2, TypeId},
};
#[derive(Debug)]
pub enum FunctionDef {
WithAnnotation {
name: Symbol, // 8B
arguments: PoolVec<(PatternId, Type2)>, // 8B
rigids: NodeId<Rigids>, // 4B
return_type: TypeId, // 4B
body: ExprId, // 4B
},
NoAnnotation {
name: Symbol, // 8B
arguments: PoolVec<(PatternId, Variable)>, // 8B
return_var: Variable, // 4B
body: ExprId, // 4B
},
}
impl ShallowClone for FunctionDef {
fn shallow_clone(&self) -> Self {
match self {
Self::WithAnnotation {
name,
arguments,
rigids,
return_type,
body,
} => Self::WithAnnotation {
name: *name,
arguments: arguments.shallow_clone(),
rigids: *rigids,
return_type: *return_type,
body: *body,
},
Self::NoAnnotation {
name,
arguments,
return_var,
body,
} => Self::NoAnnotation {
name: *name,
arguments: arguments.shallow_clone(),
return_var: *return_var,
body: *body,
},
}
}
}

View file

@ -0,0 +1,10 @@
use super::expr::expr2::ExprId;
#[derive(Debug)]
pub struct AppHeader {
pub app_name: String,
pub packages_base: String,
pub imports: Vec<String>,
pub provides: Vec<String>,
pub ast_node_id: ExprId, // TODO probably want to create and use HeaderId
}

10
ast/src/lang/core/mod.rs Normal file
View file

@ -0,0 +1,10 @@
pub mod ast;
mod declaration;
pub mod def;
pub mod expr;
mod fun_def;
pub mod header;
pub mod pattern;
pub mod str;
pub mod types;
pub mod val_def;

View file

@ -1,11 +1,7 @@
#![allow(clippy::all)] #![allow(clippy::all)]
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_imports)] #![allow(unused_imports)]
use crate::editor::ed_error::{EdResult, UnexpectedPattern2Variant};
use crate::lang::ast::{ExprId, FloatVal, IntVal};
use crate::lang::expr::{to_expr_id, Env, Output};
use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone};
use crate::lang::scope::Scope;
use bumpalo::collections::Vec as BumpVec; use bumpalo::collections::Vec as BumpVec;
use roc_can::expr::unescape_char; use roc_can::expr::unescape_char;
use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use roc_can::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int};
@ -17,7 +13,18 @@ use roc_problem::can::{MalformedPatternProblem, Problem, RuntimeError};
use roc_region::all::Region; use roc_region::all::Region;
use roc_types::subs::Variable; use roc_types::subs::Variable;
use super::constrain::Constraint; use crate::ast_error::{ASTResult, UnexpectedPattern2Variant};
use crate::constrain::Constraint;
use crate::lang::core::expr::expr_to_expr2::to_expr_id;
use crate::lang::env::Env;
use crate::lang::scope::Scope;
use crate::mem_pool::pool::{NodeId, Pool};
use crate::mem_pool::pool_str::PoolStr;
use crate::mem_pool::pool_vec::PoolVec;
use crate::mem_pool::shallow_clone::ShallowClone;
use super::expr::expr2::{ExprId, FloatVal, IntVal};
use super::expr::output::Output;
use super::types::Type2; use super::types::Type2;
pub type PatternId = NodeId<Pattern2>; pub type PatternId = NodeId<Pattern2>;
@ -483,7 +490,7 @@ pub fn symbols_from_pattern(pool: &Pool, initial: &Pattern2) -> Vec<Symbol> {
symbols symbols
} }
pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> EdResult<String> { pub fn get_identifier_string(pattern: &Pattern2, interns: &Interns) -> ASTResult<String> {
match pattern { match pattern {
Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()), Pattern2::Identifier(symbol) => Ok(symbol.ident_str(interns).to_string()),
other => UnexpectedPattern2Variant { other => UnexpectedPattern2Variant {
@ -569,7 +576,7 @@ fn underscore_in_def<'a>(env: &mut Env<'a>, region: Region) -> Pattern2 {
Pattern2::UnsupportedPattern(region) Pattern2::UnsupportedPattern(region)
} }
fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 { pub(crate) fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 {
use roc_parse::ast::StrLiteral::*; use roc_parse::ast::StrLiteral::*;
match literal { match literal {
@ -579,7 +586,7 @@ fn flatten_str_literal(pool: &mut Pool, literal: &StrLiteral<'_>) -> Pattern2 {
} }
} }
fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 { pub(crate) fn flatten_str_lines(pool: &mut Pool, lines: &[&[StrSegment<'_>]]) -> Pattern2 {
use StrSegment::*; use StrSegment::*;
let mut buf = String::new(); let mut buf = String::new();

228
ast/src/lang/core/str.rs Normal file
View file

@ -0,0 +1,228 @@
use roc_module::{operator::CalledVia, symbol::Symbol};
use roc_parse::ast::StrLiteral;
use crate::{
ast_error::{ASTResult, UnexpectedASTNode},
lang::{core::expr::expr_to_expr2::to_expr2, env::Env, scope::Scope},
mem_pool::{pool::Pool, pool_str::PoolStr, pool_vec::PoolVec},
};
use super::expr::{
expr2::{Expr2, ExprId},
output::Output,
};
pub(crate) fn flatten_str_literal<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
literal: &StrLiteral<'a>,
) -> (Expr2, Output) {
use roc_parse::ast::StrLiteral::*;
match literal {
PlainLine(str_slice) => {
// TODO use smallstr
let expr = Expr2::Str(PoolStr::new(str_slice, &mut env.pool));
(expr, Output::default())
}
Line(segments) => flatten_str_lines(env, scope, &[segments]),
Block(lines) => flatten_str_lines(env, scope, lines),
}
}
enum StrSegment {
Interpolation(Expr2),
Plaintext(PoolStr),
}
fn flatten_str_lines<'a>(
env: &mut Env<'a>,
scope: &mut Scope,
lines: &[&[roc_parse::ast::StrSegment<'a>]],
) -> (Expr2, Output) {
use roc_parse::ast::StrSegment::*;
let mut buf = String::new();
let mut segments = Vec::new();
let mut output = Output::default();
for line in lines {
for segment in line.iter() {
match segment {
Plaintext(string) => {
buf.push_str(string);
}
Unicode(loc_hex_digits) => match u32::from_str_radix(loc_hex_digits.value, 16) {
Ok(code_pt) => match std::char::from_u32(code_pt) {
Some(ch) => {
buf.push(ch);
}
None => {
// env.problem(Problem::InvalidUnicodeCodePt(loc_hex_digits.region));
//
// return (
// Expr::RuntimeError(RuntimeError::InvalidUnicodeCodePt(
// loc_hex_digits.region,
// )),
// output,
// );
todo!()
}
},
Err(_) => {
// env.problem(Problem::InvalidHexadecimal(loc_hex_digits.region));
//
// return (
// Expr::RuntimeError(RuntimeError::InvalidHexadecimal(
// loc_hex_digits.region,
// )),
// output,
// );
todo!()
}
},
Interpolated(loc_expr) => {
if roc_can::expr::is_valid_interpolation(loc_expr.value) {
// Interpolations desugar to Str.concat calls
output.references.calls.insert(Symbol::STR_CONCAT);
if !buf.is_empty() {
segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool)));
buf = String::new();
}
let (loc_expr, new_output) =
to_expr2(env, scope, loc_expr.value, loc_expr.region);
output.union(new_output);
segments.push(StrSegment::Interpolation(loc_expr));
} else {
// env.problem(Problem::InvalidInterpolation(loc_expr.region));
//
// return (
// Expr::RuntimeError(RuntimeError::InvalidInterpolation(loc_expr.region)),
// output,
// );
todo!()
}
}
EscapedChar(escaped) => buf.push(roc_can::expr::unescape_char(escaped)),
}
}
}
if !buf.is_empty() {
segments.push(StrSegment::Plaintext(PoolStr::new(&buf, &mut env.pool)));
}
(desugar_str_segments(env, segments), output)
}
/// Resolve string interpolations by desugaring a sequence of StrSegments
/// into nested calls to Str.concat
fn desugar_str_segments(env: &mut Env, segments: Vec<StrSegment>) -> Expr2 {
use StrSegment::*;
let pool = &mut env.pool;
let var_store = &mut env.var_store;
let mut iter = segments.into_iter().rev();
let mut expr = match iter.next() {
Some(Plaintext(pool_str)) => Expr2::Str(pool_str),
Some(Interpolation(expr_id)) => expr_id,
None => {
// No segments? Empty string!
let pool_str = PoolStr::new("", pool);
Expr2::Str(pool_str)
}
};
for seg in iter {
let new_expr = match seg {
Plaintext(string) => Expr2::Str(string),
Interpolation(expr_id) => expr_id,
};
let concat_expr_id = pool.add(Expr2::Var(Symbol::STR_CONCAT));
let args = vec![
(var_store.fresh(), pool.add(new_expr)),
(var_store.fresh(), pool.add(expr)),
];
let args = PoolVec::new(args.into_iter(), pool);
let new_call = Expr2::Call {
args,
expr: concat_expr_id,
expr_var: var_store.fresh(),
fn_var: var_store.fresh(),
closure_var: var_store.fresh(),
called_via: CalledVia::Space,
};
expr = new_call
}
expr
}
pub fn update_str_expr(
node_id: ExprId,
new_char: char,
insert_index: usize,
pool: &mut Pool,
) -> ASTResult<()> {
let str_expr = pool.get_mut(node_id);
enum Either {
MyString(String),
MyPoolStr(PoolStr),
Done,
}
let insert_either = match str_expr {
Expr2::SmallStr(arr_string) => {
let insert_res = arr_string.try_insert(insert_index as u8, new_char);
match insert_res {
Ok(_) => Either::Done,
_ => {
let mut new_string = arr_string.as_str().to_string();
new_string.insert(insert_index, new_char);
Either::MyString(new_string)
}
}
}
Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str),
other => UnexpectedASTNode {
required_node_type: "SmallStr or Str",
encountered_node_type: format!("{:?}", other),
}
.fail()?,
};
match insert_either {
Either::MyString(new_string) => {
let new_pool_str = PoolStr::new(&new_string, pool);
pool.set(node_id, Expr2::Str(new_pool_str))
}
Either::MyPoolStr(old_pool_str) => {
let mut new_string = old_pool_str.as_str(pool).to_owned();
new_string.insert(insert_index, new_char);
let new_pool_str = PoolStr::new(&new_string, pool);
pool.set(node_id, Expr2::Str(new_pool_str))
}
Either::Done => (),
}
Ok(())
}

View file

@ -1,9 +1,6 @@
#![allow(clippy::all)] #![allow(clippy::all)]
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_imports)] #![allow(unused_imports)]
use crate::lang::expr::Env;
use crate::lang::pool::{NodeId, Pool, PoolStr, PoolVec, ShallowClone};
use crate::lang::scope::Scope;
// use roc_can::expr::Output; // use roc_can::expr::Output;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, TagName}; use roc_module::ident::{Ident, TagName};
@ -12,6 +9,13 @@ use roc_region::all::{Located, Region};
use roc_types::types::{Problem, RecordField}; use roc_types::types::{Problem, RecordField};
use roc_types::{subs::Variable, types::ErrorType}; use roc_types::{subs::Variable, types::ErrorType};
use crate::lang::env::Env;
use crate::lang::scope::Scope;
use crate::mem_pool::pool::{NodeId, Pool};
use crate::mem_pool::pool_str::PoolStr;
use crate::mem_pool::pool_vec::PoolVec;
use crate::mem_pool::shallow_clone::ShallowClone;
pub type TypeId = NodeId<Type2>; pub type TypeId = NodeId<Type2>;
#[derive(Debug)] #[derive(Debug)]

View file

@ -0,0 +1,101 @@
use crate::{
lang::{core::expr::expr2_to_string::expr2_to_string, rigids::Rigids},
mem_pool::{
pool::{NodeId, Pool},
shallow_clone::ShallowClone,
},
};
use roc_types::subs::Variable;
use super::{
expr::expr2::ExprId,
pattern::{Pattern2, PatternId},
types::TypeId,
};
#[derive(Debug)]
pub enum ValueDef {
WithAnnotation {
pattern_id: PatternId, // 4B
expr_id: ExprId, // 4B
type_id: TypeId,
rigids: Rigids,
expr_var: Variable, // 4B
},
NoAnnotation {
pattern_id: PatternId, // 4B
expr_id: ExprId, // 4B
expr_var: Variable, // 4B
},
}
impl ShallowClone for ValueDef {
fn shallow_clone(&self) -> Self {
match self {
Self::WithAnnotation {
pattern_id,
expr_id,
type_id,
rigids,
expr_var,
} => Self::WithAnnotation {
pattern_id: *pattern_id,
expr_id: *expr_id,
type_id: *type_id,
rigids: rigids.shallow_clone(),
expr_var: *expr_var,
},
Self::NoAnnotation {
pattern_id,
expr_id,
expr_var,
} => Self::NoAnnotation {
pattern_id: *pattern_id,
expr_id: *expr_id,
expr_var: *expr_var,
},
}
}
}
impl ValueDef {
pub fn get_expr_id(&self) -> ExprId {
match self {
ValueDef::WithAnnotation { expr_id, .. } => *expr_id,
ValueDef::NoAnnotation { expr_id, .. } => *expr_id,
}
}
pub fn get_pattern_id(&self) -> NodeId<Pattern2> {
match self {
ValueDef::WithAnnotation { pattern_id, .. } => *pattern_id,
ValueDef::NoAnnotation { pattern_id, .. } => *pattern_id,
}
}
}
pub fn value_def_to_string(val_def: &ValueDef, pool: &Pool) -> String {
match val_def {
ValueDef::WithAnnotation {
pattern_id,
expr_id,
type_id,
rigids,
expr_var,
} => {
format!("WithAnnotation {{ pattern_id: {:?}, expr_id: {:?}, type_id: {:?}, rigids: {:?}, expr_var: {:?}}}", pool.get(*pattern_id), expr2_to_string(*expr_id, pool), pool.get(*type_id), rigids, expr_var)
}
ValueDef::NoAnnotation {
pattern_id,
expr_id,
expr_var,
} => {
format!(
"NoAnnotation {{ pattern_id: {:?}, expr_id: {:?}, expr_var: {:?}}}",
pool.get(*pattern_id),
expr2_to_string(*expr_id, pool),
expr_var
)
}
}
}

168
ast/src/lang/env.rs Normal file
View file

@ -0,0 +1,168 @@
use bumpalo::{collections::Vec as BumpVec, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, ModuleName};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::{Located, Region};
use roc_types::subs::VarStore;
use crate::mem_pool::pool::{NodeId, Pool};
use super::core::def::def::References;
#[derive(Debug)]
pub struct Env<'a> {
pub home: ModuleId,
pub var_store: &'a mut VarStore,
pub pool: &'a mut Pool,
pub arena: &'a Bump,
pub problems: BumpVec<'a, Problem>,
pub dep_idents: MutMap<ModuleId, IdentIds>,
pub module_ids: &'a ModuleIds,
pub ident_ids: IdentIds,
pub exposed_ident_ids: IdentIds,
pub closures: MutMap<Symbol, References>,
/// Symbols which were referenced by qualified lookups.
pub qualified_lookups: MutSet<Symbol>,
pub top_level_symbols: MutSet<Symbol>,
pub closure_name_symbol: Option<Symbol>,
pub tailcallable_symbol: Option<Symbol>,
}
impl<'a> Env<'a> {
pub fn new(
home: ModuleId,
arena: &'a Bump,
pool: &'a mut Pool,
var_store: &'a mut VarStore,
dep_idents: MutMap<ModuleId, IdentIds>,
module_ids: &'a ModuleIds,
exposed_ident_ids: IdentIds,
) -> Env<'a> {
Env {
home,
arena,
pool,
problems: BumpVec::new_in(arena),
var_store,
dep_idents,
module_ids,
ident_ids: exposed_ident_ids.clone(), // we start with these, but will add more later
exposed_ident_ids,
closures: MutMap::default(),
qualified_lookups: MutSet::default(),
tailcallable_symbol: None,
closure_name_symbol: None,
top_level_symbols: MutSet::default(),
}
}
pub fn add<T>(&mut self, item: T, region: Region) -> NodeId<T> {
let id = self.pool.add(item);
self.set_region(id, region);
id
}
pub fn problem(&mut self, problem: Problem) {
self.problems.push(problem);
}
pub fn set_region<T>(&mut self, _node_id: NodeId<T>, _region: Region) {
dbg!("Don't Forget to set the region eventually");
}
pub fn register_closure(&mut self, symbol: Symbol, references: References) {
self.closures.insert(symbol, references);
}
/// Generates a unique, new symbol like "$1" or "$5",
/// using the home module as the module_id.
///
/// This is used, for example, during canonicalization of an Expr::Closure
/// to generate a unique symbol to refer to that closure.
pub fn gen_unique_symbol(&mut self) -> Symbol {
let ident_id = self.ident_ids.gen_unique();
Symbol::new(self.home, ident_id)
}
/// Returns Err if the symbol resolved, but it was not exposed by the given module
pub fn qualified_lookup(
&mut self,
module_name: &str,
ident: &str,
region: Region,
) -> Result<Symbol, RuntimeError> {
debug_assert!(
!module_name.is_empty(),
"Called env.qualified_lookup with an unqualified ident: {:?}",
ident
);
let module_name: ModuleName = module_name.into();
match self.module_ids.get_id(&module_name) {
Some(&module_id) => {
let ident: Ident = ident.into();
// You can do qualified lookups on your own module, e.g.
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
if module_id == self.home {
match self.ident_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol);
Ok(symbol)
}
None => Err(RuntimeError::LookupNotInScope(
Located {
value: ident,
region,
},
self.ident_ids
.idents()
.map(|(_, string)| string.as_ref().into())
.collect(),
)),
}
} else {
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.qualified_lookups.insert(symbol);
Ok(symbol)
}
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
}
}
}
None => Err(RuntimeError::ModuleNotImported {
module_name,
imported_modules: self
.module_ids
.available_modules()
.map(|string| string.as_ref().into())
.collect(),
region,
}),
}
}
}

4
ast/src/lang/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod core;
pub mod env;
mod rigids;
pub mod scope;

81
ast/src/lang/rigids.rs Normal file
View file

@ -0,0 +1,81 @@
use std::{
collections::{HashMap, HashSet},
hash::BuildHasherDefault,
};
use crate::mem_pool::{
pool::Pool, pool_str::PoolStr, pool_vec::PoolVec, shallow_clone::ShallowClone,
};
use roc_collections::all::WyHash;
use roc_types::subs::Variable;
#[derive(Debug)]
pub struct Rigids {
pub names: PoolVec<(Option<PoolStr>, Variable)>, // 8B
padding: [u8; 1],
}
#[allow(clippy::needless_collect)]
impl Rigids {
pub fn new(
named: HashMap<&str, Variable, BuildHasherDefault<WyHash>>,
unnamed: HashSet<Variable, BuildHasherDefault<WyHash>>,
pool: &mut Pool,
) -> Self {
let names = PoolVec::with_capacity((named.len() + unnamed.len()) as u32, pool);
let mut temp_names = Vec::new();
temp_names.extend(named.iter().map(|(name, var)| (Some(*name), *var)));
temp_names.extend(unnamed.iter().map(|var| (None, *var)));
for (node_id, (opt_name, variable)) in names.iter_node_ids().zip(temp_names) {
let poolstr = opt_name.map(|name| PoolStr::new(name, pool));
pool[node_id] = (poolstr, variable);
}
Self {
names,
padding: Default::default(),
}
}
pub fn named(&self, pool: &mut Pool) -> PoolVec<(PoolStr, Variable)> {
let named = self
.names
.iter(pool)
.filter_map(|(opt_pool_str, var)| {
opt_pool_str.as_ref().map(|pool_str| (*pool_str, *var))
})
.collect::<Vec<(PoolStr, Variable)>>();
PoolVec::new(named.into_iter(), pool)
}
pub fn unnamed(&self, pool: &mut Pool) -> PoolVec<Variable> {
let unnamed = self
.names
.iter(pool)
.filter_map(|(opt_pool_str, var)| {
if opt_pool_str.is_none() {
Some(*var)
} else {
None
}
})
.collect::<Vec<Variable>>();
PoolVec::new(unnamed.into_iter(), pool)
}
}
impl ShallowClone for Rigids {
fn shallow_clone(&self) -> Self {
Self {
names: self.names.shallow_clone(),
padding: self.padding,
}
}
}

View file

@ -1,8 +1,11 @@
#![allow(clippy::all)] #![allow(clippy::all)]
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_imports)] #![allow(unused_imports)]
use crate::lang::pool::{Pool, PoolStr, PoolVec, ShallowClone};
use crate::lang::types::{Alias, Type2, TypeId}; use crate::mem_pool::pool::Pool;
use crate::mem_pool::pool_str::PoolStr;
use crate::mem_pool::pool_vec::PoolVec;
use crate::mem_pool::shallow_clone::ShallowClone;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Ident, Lowercase}; use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{IdentIds, ModuleId, Symbol}; use roc_module::symbol::{IdentIds, ModuleId, Symbol};
@ -14,6 +17,8 @@ use roc_types::{
subs::{VarId, VarStore, Variable}, subs::{VarId, VarStore, Variable},
}; };
use super::core::types::{Alias, Type2, TypeId};
fn solved_type_to_type_id( fn solved_type_to_type_id(
pool: &mut Pool, pool: &mut Pool,
solved_type: &SolvedType, solved_type: &SolvedType,

7
ast/src/lib.rs Normal file
View file

@ -0,0 +1,7 @@
pub mod ast_error;
mod canonicalization;
pub mod constrain;
pub mod lang;
pub mod mem_pool;
pub mod parse;
pub mod solve_type;

4
ast/src/mem_pool/mod.rs Normal file
View file

@ -0,0 +1,4 @@
pub mod pool;
pub mod pool_str;
pub mod pool_vec;
pub mod shallow_clone;

228
ast/src/mem_pool/pool.rs Normal file
View file

@ -0,0 +1,228 @@
/// A memory pool of 32-byte nodes. The node value 0 is reserved for the pool's
/// use, and valid nodes may never have that value.
///
/// Internally, the pool is divided into pages of 4096 bytes. It stores nodes
/// into one page at a time, and when it runs out, it uses mmap to reserve an
/// anonymous memory page in which to store nodes.
///
/// Since nodes are 32 bytes, one page can store 128 nodes; you can access a
/// particular node by its NodeId, which is an opaque wrapper around a pointer.
///
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
/// This is important for performance.
use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use std::any::type_name;
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::null;
pub const NODE_BYTES: usize = 32;
// Each page has 128 slots. Each slot holds one 32B node
// This means each page is 4096B, which is the size of a memory page
// on typical systems where the compiler will be run.
//
// Nice things about this system include:
// * Allocating a new page is as simple as asking the OS for a memory page.
// * Since each node is 32B, each node's memory address will be a multiple of 16.
// * Thanks to the free lists and our consistent chunk sizes, we should
// end up with very little fragmentation.
// * Finding a slot for a given node should be very fast: see if the relevant
// free list has any openings; if not, try the next size up.
//
// Less nice things include:
// * This system makes it very hard to ever give a page back to the OS.
// We could try doing the Mesh Allocator strategy: whenever we allocate
// something, assign it to a random slot in the page, and then periodically
// try to merge two pages into one (by locking and remapping them in the OS)
// and then returning the redundant physical page back to the OS. This should
// work in theory, but is pretty complicated, and we'd need to schedule it.
// Keep in mind that we can't use the Mesh Allocator itself because it returns
// usize pointers, which would be too big for us to have 16B nodes.
// On the plus side, we could be okay with higher memory usage early on,
// and then later use the Mesh strategy to reduce long-running memory usage.
//
// With this system, we can allocate up to 4B nodes. If we wanted to keep
// a generational index in there, like https://crates.io/crates/sharded-slab
// does, we could use some of the 32 bits for that. For example, if we wanted
// to have a 5-bit generational index (supporting up to 32 generations), then
// we would have 27 bits remaining, meaning we could only support at most
// 134M nodes. Since the editor has a separate Pool for each module, is that
// enough for any single module we'll encounter in practice? Probably, and
// especially if we allocate super large collection literals on the heap instead
// of in the pool.
//
// Another possible design is to try to catch reuse bugs using an "ASan" like
// approach: in development builds, whenever we "free" a particular slot, we
// can add it to a dev-build-only "freed nodes" list and don't hand it back
// out (so, we leak the memory.) Then we can (again, in development builds only)
// check to see if we're about to store something in zeroed-out memory; if so, check
// to see if it was
#[derive(Debug, Eq)]
pub struct NodeId<T> {
pub(super) index: u32,
pub(super) _phantom: PhantomData<T>,
}
impl<T> Clone for NodeId<T> {
fn clone(&self) -> Self {
NodeId {
index: self.index,
_phantom: PhantomData::default(),
}
}
}
impl<T> PartialEq for NodeId<T> {
fn eq(&self, other: &Self) -> bool {
self.index == other.index
}
}
impl<T> Copy for NodeId<T> {}
#[derive(Debug)]
pub struct Pool {
pub(super) nodes: *mut [u8; NODE_BYTES],
num_nodes: u32,
capacity: u32,
// free_1node_slots: Vec<NodeId<T>>,
}
impl Pool {
pub fn with_capacity(nodes: u32) -> Self {
// round up number of nodes requested to nearest page size in bytes
let bytes_per_page = page_size::get();
let node_bytes = NODE_BYTES * nodes as usize;
let leftover = node_bytes % bytes_per_page;
let bytes_to_mmap = if leftover == 0 {
node_bytes
} else {
node_bytes + bytes_per_page - leftover
};
let nodes = unsafe {
// mmap anonymous memory pages - that is, contiguous virtual memory
// addresses from the OS which will be lazily translated into
// physical memory one 4096-byte page at a time, once we actually
// try to read or write in that page's address range.
libc::mmap(
null::<c_void>() as *mut c_void,
bytes_to_mmap,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
0,
0,
)
} as *mut [u8; NODE_BYTES];
// This is our actual capacity, in nodes.
// It might be higher than the requested capacity due to rounding up
// to nearest page size.
let capacity = (bytes_to_mmap / NODE_BYTES) as u32;
Pool {
nodes,
num_nodes: 0,
capacity,
}
}
pub fn add<T>(&mut self, node: T) -> NodeId<T> {
// It's only safe to store this if T fits in S.
debug_assert!(
size_of::<T>() <= NODE_BYTES,
"{} has a size of {}, but it needs to be at most {}",
type_name::<T>(),
size_of::<T>(),
NODE_BYTES
);
let node_id = self.reserve(1);
let node_ptr = unsafe { self.nodes.offset(node_id.index as isize) } as *mut T;
unsafe { *node_ptr = node };
node_id
}
/// Reserves the given number of contiguous node slots, and returns
/// the NodeId of the first one. We only allow reserving 2^32 in a row.
pub(super) fn reserve<T>(&mut self, nodes: u32) -> NodeId<T> {
// TODO once we have a free list, look in there for an open slot first!
let index = self.num_nodes;
if index < self.capacity {
self.num_nodes = index + nodes;
NodeId {
index,
_phantom: PhantomData::default(),
}
} else {
todo!("pool ran out of capacity. TODO reallocate the nodes pointer to map to a bigger space. Can use mremap on Linux, but must memcpy lots of bytes on macOS and Windows.");
}
}
pub fn get<'a, 'b, T>(&'a self, node_id: NodeId<T>) -> &'b T {
unsafe {
let node_ptr = self.nodes.offset(node_id.index as isize) as *const T;
&*node_ptr
}
}
pub fn get_mut<T>(&mut self, node_id: NodeId<T>) -> &mut T {
unsafe {
let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T;
&mut *node_ptr
}
}
pub fn set<T>(&mut self, node_id: NodeId<T>, element: T) {
unsafe {
let node_ptr = self.nodes.offset(node_id.index as isize) as *mut T;
*node_ptr = element;
}
}
// A node is available iff its bytes are all zeroes
#[allow(dead_code)]
fn is_available<T>(&self, node_id: NodeId<T>) -> bool {
debug_assert_eq!(size_of::<T>(), NODE_BYTES);
unsafe {
let node_ptr = self.nodes.offset(node_id.index as isize) as *const [u8; NODE_BYTES];
*node_ptr == [0; NODE_BYTES]
}
}
}
impl<T> std::ops::Index<NodeId<T>> for Pool {
type Output = T;
fn index(&self, node_id: NodeId<T>) -> &Self::Output {
self.get(node_id)
}
}
impl<T> std::ops::IndexMut<NodeId<T>> for Pool {
fn index_mut(&mut self, node_id: NodeId<T>) -> &mut Self::Output {
self.get_mut(node_id)
}
}
impl Drop for Pool {
fn drop(&mut self) {
unsafe {
libc::munmap(
self.nodes as *mut c_void,
NODE_BYTES * self.capacity as usize,
);
}
}
}

View file

@ -0,0 +1,86 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::marker::PhantomData;
use std::mem::size_of;
/// A string containing at most 2^32 pool-allocated bytes.
#[derive(Debug, Copy, Clone)]
pub struct PoolStr {
first_node_id: NodeId<()>,
len: u32,
}
#[test]
fn pool_str_size() {
assert_eq!(size_of::<PoolStr>(), 8);
}
impl PoolStr {
pub fn new(string: &str, pool: &mut Pool) -> Self {
debug_assert!(string.len() <= u32::MAX as usize);
let chars_per_node = NODE_BYTES / size_of::<char>();
let number_of_nodes = f64::ceil(string.len() as f64 / chars_per_node as f64) as u32;
if number_of_nodes > 0 {
let first_node_id = pool.reserve(number_of_nodes);
let index = first_node_id.index as isize;
let next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut c_void;
unsafe {
libc::memcpy(
next_node_ptr,
string.as_ptr() as *const c_void,
string.len(),
);
}
PoolStr {
first_node_id,
len: string.len() as u32,
}
} else {
PoolStr {
first_node_id: NodeId {
index: 0,
_phantom: PhantomData::default(),
},
len: 0,
}
}
}
pub fn as_str(&self, pool: &Pool) -> &str {
unsafe {
let node_ptr = pool.nodes.offset(self.first_node_id.index as isize) as *const u8;
let node_slice: &[u8] = std::slice::from_raw_parts(node_ptr, self.len as usize);
std::str::from_utf8_unchecked(&node_slice[0..self.len as usize])
}
}
#[allow(clippy::len_without_is_empty)]
pub fn len(&self, pool: &Pool) -> usize {
let contents = self.as_str(pool);
contents.len()
}
pub fn is_empty(&self, pool: &Pool) -> bool {
self.len(pool) == 0
}
}
impl ShallowClone for PoolStr {
fn shallow_clone(&self) -> Self {
// Question: should this fully clone, or is a shallow copy
// (and the aliasing it entails) OK?
Self {
first_node_id: self.first_node_id,
len: self.len,
}
}
}

View file

@ -0,0 +1,323 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::any::type_name;
use std::cmp::Ordering;
use std::marker::PhantomData;
use std::mem::size_of;
/// An array of at most 2^32 pool-allocated nodes.
#[derive(Debug)]
pub struct PoolVec<T> {
first_node_id: NodeId<T>,
len: u32,
}
#[test]
fn pool_vec_size() {
assert_eq!(size_of::<PoolVec<()>>(), 8);
}
impl<'a, T: 'a + Sized> PoolVec<T> {
pub fn empty(pool: &mut Pool) -> Self {
Self::new(std::iter::empty(), pool)
}
pub fn with_capacity(len: u32, pool: &mut Pool) -> Self {
debug_assert!(
size_of::<T>() <= NODE_BYTES,
"{} has a size of {}",
type_name::<T>(),
size_of::<T>()
);
if len == 0 {
Self::empty(pool)
} else {
let first_node_id = pool.reserve(len);
PoolVec { first_node_id, len }
}
}
pub fn len(&self) -> usize {
self.len as usize
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
pub fn new<I: ExactSizeIterator<Item = T>>(nodes: I, pool: &mut Pool) -> Self {
debug_assert!(nodes.len() <= u32::MAX as usize);
debug_assert!(size_of::<T>() <= NODE_BYTES);
let len = nodes.len() as u32;
if len > 0 {
let first_node_id = pool.reserve(len);
let index = first_node_id.index as isize;
let mut next_node_ptr = unsafe { pool.nodes.offset(index) } as *mut T;
for (indx_inc, node) in nodes.enumerate() {
unsafe {
*next_node_ptr = node;
next_node_ptr = pool.nodes.offset(index + (indx_inc as isize) + 1) as *mut T;
}
}
PoolVec { first_node_id, len }
} else {
PoolVec {
first_node_id: NodeId {
index: 0,
_phantom: PhantomData::default(),
},
len: 0,
}
}
}
pub fn iter(&self, pool: &'a Pool) -> impl ExactSizeIterator<Item = &'a T> {
self.pool_list_iter(pool)
}
pub fn iter_mut(&self, pool: &'a mut Pool) -> impl ExactSizeIterator<Item = &'a mut T> {
self.pool_list_iter_mut(pool)
}
pub fn iter_node_ids(&self) -> impl ExactSizeIterator<Item = NodeId<T>> {
self.pool_list_iter_node_ids()
}
/// Private version of into_iter which exposes the implementation detail
/// of PoolVecIter. We don't want that struct to be public, but we
/// actually do want to have this separate function for code reuse
/// in the iterator's next() method.
#[inline(always)]
fn pool_list_iter(&self, pool: &'a Pool) -> PoolVecIter<'a, T> {
PoolVecIter {
pool,
current_node_id: self.first_node_id,
len_remaining: self.len,
}
}
#[inline(always)]
fn pool_list_iter_mut(&self, pool: &'a Pool) -> PoolVecIterMut<'a, T> {
PoolVecIterMut {
pool,
current_node_id: self.first_node_id,
len_remaining: self.len,
}
}
#[inline(always)]
fn pool_list_iter_node_ids(&self) -> PoolVecIterNodeIds<T> {
PoolVecIterNodeIds {
current_node_id: self.first_node_id,
len_remaining: self.len,
}
}
pub fn free<S>(self, pool: &'a mut Pool) {
// zero out the memory
unsafe {
let index = self.first_node_id.index as isize;
let node_ptr = pool.nodes.offset(index) as *mut c_void;
let bytes = self.len as usize * NODE_BYTES;
libc::memset(node_ptr, 0, bytes);
}
// TODO insert it into the pool's free list
}
}
impl<T> ShallowClone for PoolVec<T> {
fn shallow_clone(&self) -> Self {
// Question: should this fully clone, or is a shallow copy
// (and the aliasing it entails) OK?
Self {
first_node_id: self.first_node_id,
len: self.len,
}
}
}
struct PoolVecIter<'a, T> {
pool: &'a Pool,
current_node_id: NodeId<T>,
len_remaining: u32,
}
impl<'a, T> ExactSizeIterator for PoolVecIter<'a, T>
where
T: 'a,
{
fn len(&self) -> usize {
self.len_remaining as usize
}
}
impl<'a, T> Iterator for PoolVecIter<'a, T>
where
T: 'a,
{
type Item = &'a T;
fn next(&mut self) -> Option<Self::Item> {
let len_remaining = self.len_remaining;
match len_remaining.cmp(&1) {
Ordering::Greater => {
// Get the current node
let index = self.current_node_id.index;
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T;
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
};
self.len_remaining = len_remaining - 1;
Some(unsafe { &*node_ptr })
}
Ordering::Equal => {
self.len_remaining = 0;
// Don't advance the node pointer's node, because that might
// advance past the end of the page!
let index = self.current_node_id.index;
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *const T;
Some(unsafe { &*node_ptr })
}
Ordering::Less => {
// len_remaining was 0
None
}
}
}
}
struct PoolVecIterMut<'a, T> {
pool: &'a Pool,
current_node_id: NodeId<T>,
len_remaining: u32,
}
impl<'a, T> ExactSizeIterator for PoolVecIterMut<'a, T>
where
T: 'a,
{
fn len(&self) -> usize {
self.len_remaining as usize
}
}
impl<'a, T> Iterator for PoolVecIterMut<'a, T>
where
T: 'a,
{
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
let len_remaining = self.len_remaining;
match len_remaining.cmp(&1) {
Ordering::Greater => {
// Get the current node
let index = self.current_node_id.index;
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T;
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
};
self.len_remaining = len_remaining - 1;
Some(unsafe { &mut *node_ptr })
}
Ordering::Equal => {
self.len_remaining = 0;
// Don't advance the node pointer's node, because that might
// advance past the end of the page!
let index = self.current_node_id.index;
let node_ptr = unsafe { self.pool.nodes.offset(index as isize) } as *mut T;
Some(unsafe { &mut *node_ptr })
}
Ordering::Less => {
// len_remaining was 0
None
}
}
}
}
struct PoolVecIterNodeIds<T> {
current_node_id: NodeId<T>,
len_remaining: u32,
}
impl<T> ExactSizeIterator for PoolVecIterNodeIds<T> {
fn len(&self) -> usize {
self.len_remaining as usize
}
}
impl<T> Iterator for PoolVecIterNodeIds<T> {
type Item = NodeId<T>;
fn next(&mut self) -> Option<Self::Item> {
let len_remaining = self.len_remaining;
match len_remaining.cmp(&1) {
Ordering::Greater => {
// Get the current node
let current = self.current_node_id;
let index = current.index;
// Advance the node pointer to the next node in the current page
self.current_node_id = NodeId {
index: index + 1,
_phantom: PhantomData::default(),
};
self.len_remaining = len_remaining - 1;
Some(current)
}
Ordering::Equal => {
self.len_remaining = 0;
// Don't advance the node pointer's node, because that might
// advance past the end of the page!
Some(self.current_node_id)
}
Ordering::Less => {
// len_remaining was 0
None
}
}
}
}
#[test]
fn pool_vec_iter_test() {
let expected_vec: Vec<usize> = vec![2, 4, 8, 16];
let mut test_pool = Pool::with_capacity(1024);
let pool_vec = PoolVec::new(expected_vec.clone().into_iter(), &mut test_pool);
let current_vec: Vec<usize> = pool_vec.iter(&test_pool).copied().collect();
assert_eq!(current_vec, expected_vec);
}

View file

@ -0,0 +1,32 @@
use roc_can::expected::Expected;
use roc_can::expected::PExpected;
/// Clones the outer node, but does not clone any nodeids
pub trait ShallowClone {
fn shallow_clone(&self) -> Self;
}
impl<T: ShallowClone> ShallowClone for Expected<T> {
fn shallow_clone(&self) -> Self {
use Expected::*;
match self {
NoExpectation(t) => NoExpectation(t.shallow_clone()),
ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region),
FromAnnotation(loc_pat, n, source, t) => {
FromAnnotation(loc_pat.clone(), *n, *source, t.shallow_clone())
}
}
}
}
impl<T: ShallowClone> ShallowClone for PExpected<T> {
fn shallow_clone(&self) -> Self {
use PExpected::*;
match self {
NoExpectation(t) => NoExpectation(t.shallow_clone()),
ForReason(reason, t, region) => ForReason(reason.clone(), t.shallow_clone(), *region),
}
}
}

2
ast/src/parse/mod.rs Normal file
View file

@ -0,0 +1,2 @@
pub mod parse_ast;
pub mod parse_header;

View file

@ -0,0 +1,48 @@
use bumpalo::Bump;
use roc_parse::parser::SyntaxError;
use roc_region::all::Region;
use crate::lang::{
core::{
ast::AST,
def::{def2::DefId, def_to_def2::str_to_def2},
expr::expr2::Expr2,
},
env::Env,
scope::Scope,
};
use super::parse_header;
pub fn parse_from_string<'a>(
code_str: &'a str,
env: &mut Env<'a>,
ast_arena: &'a Bump,
) -> Result<AST, SyntaxError<'a>> {
let blank_line_indx = code_str
.find("\n\n")
.expect("I was expecting a double newline to split header and rest of code.");
let header_str = &code_str[0..blank_line_indx];
let tail_str = &code_str[blank_line_indx..];
let mut scope = Scope::new(env.home, env.pool, env.var_store);
let region = Region::new(0, 0, 0, 0);
let mut def_ids = Vec::<DefId>::new();
let def2_vec = str_to_def2(ast_arena, tail_str, env, &mut scope, region)?;
for def2 in def2_vec {
let def_id = env.pool.add(def2);
def_ids.push(def_id);
}
let ast_node_id = env.pool.add(Expr2::Blank);
Ok(AST {
header: parse_header::parse_from_string(header_str, ast_node_id),
def_ids,
})
}

View file

@ -0,0 +1,12 @@
use crate::lang::core::{expr::expr2::ExprId, header::AppHeader};
// TODO don't use mock struct and actually parse string
pub fn parse_from_string(_header_str: &str, ast_node_id: ExprId) -> AppHeader {
AppHeader {
app_name: "\"untitled-app\"".to_owned(),
packages_base: "\"platform\"".to_owned(),
imports: vec![],
provides: vec!["main".to_owned()],
ast_node_id,
}
}

View file

@ -1,8 +1,5 @@
#![allow(clippy::all)] #![allow(clippy::all)]
#![allow(dead_code)] #![allow(dead_code)]
use crate::lang::constrain::Constraint::{self, *};
use crate::lang::pool::{Pool, PoolVec, ShallowClone};
use crate::lang::types::Type2;
use bumpalo::Bump; use bumpalo::Bump;
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{BumpMap, BumpMapDefault, MutMap}; use roc_collections::all::{BumpMap, BumpMapDefault, MutMap};
@ -20,6 +17,12 @@ use roc_types::types::{
use roc_unify::unify::unify; use roc_unify::unify::unify;
use roc_unify::unify::Unified::*; use roc_unify::unify::Unified::*;
use crate::constrain::Constraint;
use crate::lang::core::types::Type2;
use crate::mem_pool::pool::Pool;
use crate::mem_pool::pool_vec::PoolVec;
use crate::mem_pool::shallow_clone::ShallowClone;
// Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed
// https://github.com/elm/compiler // https://github.com/elm/compiler
// Thank you, Evan! // Thank you, Evan!
@ -197,6 +200,8 @@ fn solve<'a>(
subs: &mut Subs, subs: &mut Subs,
constraint: &Constraint, constraint: &Constraint,
) -> State { ) -> State {
use crate::solve_type::Constraint::*;
match constraint { match constraint {
True => state, True => state,
// SaveTheEnvironment => { // SaveTheEnvironment => {

View file

@ -25,12 +25,12 @@ fn bench_group_wall_time(c: &mut Criterion) {
group.sample_size(nr_of_runs); group.sample_size(nr_of_runs);
let bench_funcs: Vec<fn(Option<&mut BenchmarkGroup<WallTime>>) -> ()> = vec![ let bench_funcs: Vec<fn(Option<&mut BenchmarkGroup<WallTime>>) -> ()> = vec![
bench_nqueens, // queens 11 bench_nqueens, // queens 11
bench_cfold, // e = mkExpr 17 1 bench_cfold, // e = mkExpr 17 1
bench_deriv, // nest deriv 8 f bench_deriv, // nest deriv 8 f
bench_rbtree_ck, // ms = makeMap 5 80000 bench_rbtree_ck, // ms = makeMap 5 80000
bench_rbtree_delete, // m = makeMap 100000 // bench_rbtree_delete, // m = makeMap 100000
bench_quicksort, // list size 10000 bench_quicksort, // list size 10000
]; ];
for bench_func in bench_funcs.iter() { for bench_func in bench_funcs.iter() {

View file

@ -131,6 +131,7 @@ pub fn bench_rbtree_ck<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGro
); );
} }
#[allow(dead_code)]
pub fn bench_rbtree_delete<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) { pub fn bench_rbtree_delete<T: Measurement>(bench_group_opt: Option<&mut BenchmarkGroup<T>>) {
exec_bench_w_input( exec_bench_w_input(
&example_file("benchmarks", "RBTreeDel.roc"), &example_file("benchmarks", "RBTreeDel.roc"),

View file

@ -3,6 +3,8 @@ use roc_build::{
link::{link, rebuild_host, LinkType}, link::{link, rebuild_host, LinkType},
program, program,
}; };
#[cfg(feature = "llvm")]
use roc_builtins::bitcode;
use roc_can::builtins::builtin_defs_map; use roc_can::builtins::builtin_defs_map;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_load::file::LoadingProblem; use roc_load::file::LoadingProblem;
@ -10,6 +12,7 @@ use roc_mono::ir::OptLevel;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use target_lexicon::Triple; use target_lexicon::Triple;
#[cfg(feature = "llvm")]
use tempfile::Builder; use tempfile::Builder;
fn report_timing(buf: &mut String, label: &str, duration: Duration) { fn report_timing(buf: &mut String, label: &str, duration: Duration) {
@ -240,11 +243,19 @@ pub fn build_file<'a>(
})?; })?;
BuildOutcome::NoProblems BuildOutcome::NoProblems
} else { } else {
let mut inputs = vec![
host_input_path.as_path().to_str().unwrap(),
app_o_file.to_str().unwrap(),
];
if matches!(opt_level, OptLevel::Development) {
inputs.push(bitcode::OBJ_PATH);
}
let (mut child, _) = // TODO use lld let (mut child, _) = // TODO use lld
link( link(
target, target,
binary_path.clone(), binary_path.clone(),
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], &inputs,
link_type link_type
) )
.map_err(|_| { .map_err(|_| {

View file

@ -11,12 +11,13 @@ use std::path::{Path, PathBuf};
#[global_allocator] #[global_allocator]
static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[cfg(feature = "llvm")]
use roc_cli::build;
use std::ffi::{OsStr, OsString}; use std::ffi::{OsStr, OsString};
#[cfg(feature = "llvm")]
use roc_cli::build;
#[cfg(not(feature = "llvm"))] #[cfg(not(feature = "llvm"))]
fn build(_target: &Triple, _matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> { fn build(_matches: &clap::ArgMatches, _config: BuildConfig) -> io::Result<i32> {
panic!("Building without LLVM is not currently supported."); panic!("Building without LLVM is not currently supported.");
} }

View file

@ -18,6 +18,13 @@ mod cli_run {
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
#[cfg(target_os = "linux")]
const TEST_SURGICAL_LINKER: bool = true;
// Surgical linker currently only supports linux.
#[cfg(not(target_os = "linux"))]
const TEST_SURGICAL_LINKER: bool = false;
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
const ALLOW_VALGRIND: bool = true; const ALLOW_VALGRIND: bool = true;
@ -136,7 +143,6 @@ mod cli_run {
); );
} }
} }
/// This macro does two things. /// This macro does two things.
/// ///
/// First, it generates and runs a separate test for each of the given /// First, it generates and runs a separate test for each of the given
@ -184,6 +190,19 @@ mod cli_run {
example.expected_ending, example.expected_ending,
example.use_valgrind, example.use_valgrind,
); );
// Also check with the surgical linker.
if TEST_SURGICAL_LINKER {
check_output_with_stdin(
&file_name,
example.stdin,
example.executable_filename,
&["--roc-linker"],
example.expected_ending,
example.use_valgrind,
);
}
} }
)* )*
@ -228,7 +247,7 @@ mod cli_run {
}, },
hello_rust:"hello-rust" => Example { hello_rust:"hello-rust" => Example {
filename: "Hello.roc", filename: "Hello.roc",
executable_filename: "hello-world", executable_filename: "hello-rust",
stdin: &[], stdin: &[],
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, use_valgrind: true,
@ -435,77 +454,77 @@ mod cli_run {
} }
benchmarks! { benchmarks! {
nqueens => Example { nqueens => Example {
filename: "NQueens.roc", filename: "NQueens.roc",
executable_filename: "nqueens", executable_filename: "nqueens",
stdin: &["6"], stdin: &["6"],
expected_ending: "4\n", expected_ending: "4\n",
use_valgrind: true, use_valgrind: true,
}, },
cfold => Example { cfold => Example {
filename: "CFold.roc", filename: "CFold.roc",
executable_filename: "cfold", executable_filename: "cfold",
stdin: &["3"], stdin: &["3"],
expected_ending: "11 & 11\n", expected_ending: "11 & 11\n",
use_valgrind: true, use_valgrind: true,
}, },
deriv => Example { deriv => Example {
filename: "Deriv.roc", filename: "Deriv.roc",
executable_filename: "deriv", executable_filename: "deriv",
stdin: &["2"], stdin: &["2"],
expected_ending: "1 count: 6\n2 count: 22\n", expected_ending: "1 count: 6\n2 count: 22\n",
use_valgrind: true, use_valgrind: true,
}, },
rbtree_ck => Example { rbtree_ck => Example {
filename: "RBTreeCk.roc", filename: "RBTreeCk.roc",
executable_filename: "rbtree-ck", executable_filename: "rbtree-ck",
stdin: &["100"], stdin: &["100"],
expected_ending: "10\n", expected_ending: "10\n",
use_valgrind: true, use_valgrind: true,
}, },
rbtree_insert => Example { rbtree_insert => Example {
filename: "RBTreeInsert.roc", filename: "RBTreeInsert.roc",
executable_filename: "rbtree-insert", executable_filename: "rbtree-insert",
stdin: &[], stdin: &[],
expected_ending: "Node Black 0 {} Empty Empty\n", expected_ending: "Node Black 0 {} Empty Empty\n",
use_valgrind: true, use_valgrind: true,
}, },
rbtree_del => Example { // rbtree_del => Example {
filename: "RBTreeDel.roc", // filename: "RBTreeDel.roc",
executable_filename: "rbtree-del", // executable_filename: "rbtree-del",
stdin: &["420"], // stdin: &["420"],
expected_ending: "30\n", // expected_ending: "30\n",
use_valgrind: true, // use_valgrind: true,
}, // },
astar => Example { astar => Example {
filename: "TestAStar.roc", filename: "TestAStar.roc",
executable_filename: "test-astar", executable_filename: "test-astar",
stdin: &[], stdin: &[],
expected_ending: "True\n", expected_ending: "True\n",
use_valgrind: false, use_valgrind: false,
}, },
base64 => Example { base64 => Example {
filename: "TestBase64.roc", filename: "TestBase64.roc",
executable_filename: "test-base64", executable_filename: "test-base64",
stdin: &[], stdin: &[],
expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n", expected_ending: "encoded: SGVsbG8gV29ybGQ=\ndecoded: Hello World\n",
use_valgrind: true, use_valgrind: true,
}, },
closure => Example { closure => Example {
filename: "Closure.roc", filename: "Closure.roc",
executable_filename: "closure", executable_filename: "closure",
stdin: &[], stdin: &[],
expected_ending: "", expected_ending: "",
use_valgrind: true, use_valgrind: true,
}, },
quicksort_app => Example { quicksort_app => Example {
filename: "QuicksortApp.roc", filename: "QuicksortApp.roc",
executable_filename: "quicksortapp", executable_filename: "quicksortapp",
stdin: &[], stdin: &[],
expected_ending: "todo put the correct quicksort answer here", expected_ending: "todo put the correct quicksort answer here",
use_valgrind: true, use_valgrind: true,
}, },
} }
#[cfg(not(debug_assertions))] #[cfg(not(debug_assertions))]
fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) { fn check_for_tests(examples_dir: &str, all_examples: &mut MutMap<&str, Example<'_>>) {
@ -562,10 +581,10 @@ mod cli_run {
file.read_exact(buf).unwrap(); file.read_exact(buf).unwrap();
// Only app modules in this directory are considered benchmarks. // Only app modules in this directory are considered benchmarks.
if "app".as_bytes() == buf { if "app".as_bytes() == buf && !benchmark_file_name.contains("RBTreeDel") {
all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| { all_benchmarks.remove(benchmark_file_name.as_str()).unwrap_or_else(|| {
panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name); panic!("The benchmark {}/{} does not have any corresponding tests in cli_run. Please add one, so if it ever stops working, we'll know about it right away!", benchmarks_dir, benchmark_file_name);
}); });
} }
} }
} }

View file

@ -502,6 +502,11 @@ mod repl_eval {
expect_success("\\x -> x", "<function> : a -> a"); expect_success("\\x -> x", "<function> : a -> a");
} }
#[test]
fn sum_lambda() {
expect_success("\\x, y -> x + y", "<function> : Num a, Num a -> Num a");
}
#[test] #[test]
fn stdlib_function() { fn stdlib_function() {
expect_success("Num.abs", "<function> : Num a -> Num a"); expect_success("Num.abs", "<function> : Num a -> Num a");

18
code_markup/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
name = "roc_code_markup"
version = "0.1.0"
authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
description = "Our own markup language for Roc code. Used by the editor and (soon) the docs."
[dependencies]
roc_ast = { path = "../ast" }
roc_module = { path = "../compiler/module" }
roc_utils = { path = "../utils" }
serde = { version = "1.0.123", features = ["derive"] }
palette = "0.5"
snafu = { version = "0.6", features = ["backtraces"] }
bumpalo = { version = "3.2", features = ["collections"] }
[dev-dependencies]

22
code_markup/src/colors.rs Normal file
View file

@ -0,0 +1,22 @@
use palette::{Hsv, LinSrgb};
pub type RgbaTup = (f32, f32, f32, f32);
pub const WHITE: RgbaTup = (1.0, 1.0, 1.0, 1.0);
pub fn to_slice((r, g, b, a): RgbaTup) -> [f32; 4] {
[r, g, b, a]
}
pub fn from_hsb(hue: usize, saturation: usize, brightness: usize) -> RgbaTup {
from_hsba(hue, saturation, brightness, 1.0)
}
pub fn from_hsba(hue: usize, saturation: usize, brightness: usize, alpha: f32) -> RgbaTup {
let rgb = LinSrgb::from(Hsv::new(
hue as f32,
(saturation as f32) / 100.0,
(brightness as f32) / 100.0,
));
(rgb.red, rgb.green, rgb.blue, alpha)
}

5
code_markup/src/lib.rs Normal file
View file

@ -0,0 +1,5 @@
pub mod colors;
pub mod markup;
pub mod markup_error;
pub mod slow_pool;
pub mod syntax_highlight;

View file

@ -1,8 +1,8 @@
#![allow(dead_code)] #![allow(dead_code)]
use crate::editor::ed_error::{CaretNotFound, EdResult};
use snafu::ensure; use snafu::ensure;
use crate::markup_error::{CaretNotFound, MarkResult};
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub struct Caret { pub struct Caret {
pub offset_col: usize, pub offset_col: usize,
@ -65,10 +65,6 @@ pub struct Attributes {
} }
impl Attributes { impl Attributes {
pub fn new() -> Attributes {
Attributes { all: Vec::new() }
}
pub fn add(&mut self, attr: Attribute) { pub fn add(&mut self, attr: Attribute) {
self.all.push(attr); self.all.push(attr);
} }
@ -103,7 +99,7 @@ impl Attributes {
carets carets
} }
pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> EdResult<()> { pub fn delete_caret(&mut self, offset_col: usize, node_id: usize) -> MarkResult<()> {
let old_len = self.all.len(); let old_len = self.all.len();
self.all.retain(|attr| { self.all.retain(|attr| {
@ -121,3 +117,9 @@ impl Attributes {
Ok(()) Ok(())
} }
} }
impl Default for Attributes {
fn default() -> Self {
Attributes { all: Vec::new() }
}
}

View file

@ -1,7 +1,6 @@
use crate::{ use roc_ast::lang::core::{ast::ASTNodeId, expr::expr2::ExprId};
editor::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle},
lang::{ast::ExprId, parse::ASTNodeId}, use crate::{slow_pool::MarkNodeId, syntax_highlight::HighlightStyle};
};
use super::{attribute::Attributes, nodes, nodes::MarkupNode}; use super::{attribute::Attributes, nodes, nodes::MarkupNode};
@ -10,7 +9,7 @@ pub fn new_equals_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>)
content: nodes::EQUALS.to_owned(), content: nodes::EQUALS.to_owned(),
ast_node_id, ast_node_id,
syn_high_style: HighlightStyle::Operator, syn_high_style: HighlightStyle::Operator,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }
@ -21,7 +20,7 @@ pub fn new_comma_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> Marku
content: nodes::COMMA.to_owned(), content: nodes::COMMA.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Blank, syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }
@ -31,7 +30,7 @@ pub fn new_blank_mn(ast_node_id: ASTNodeId, parent_id_opt: Option<MarkNodeId>) -
MarkupNode::Blank { MarkupNode::Blank {
ast_node_id, ast_node_id,
syn_high_style: HighlightStyle::Blank, syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }
@ -45,7 +44,7 @@ pub fn new_blank_mn_w_nls(
MarkupNode::Blank { MarkupNode::Blank {
ast_node_id, ast_node_id,
syn_high_style: HighlightStyle::Blank, syn_high_style: HighlightStyle::Blank,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: nr_of_newlines, newlines_at_end: nr_of_newlines,
} }
@ -56,7 +55,7 @@ pub fn new_colon_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -> Marku
content: nodes::COLON.to_owned(), content: nodes::COLON.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Operator, syn_high_style: HighlightStyle::Operator,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }
@ -67,7 +66,7 @@ pub fn new_left_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>)
content: nodes::LEFT_ACCOLADE.to_owned(), content: nodes::LEFT_ACCOLADE.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket, syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }
@ -78,7 +77,7 @@ pub fn new_right_accolade_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>)
content: nodes::RIGHT_ACCOLADE.to_owned(), content: nodes::RIGHT_ACCOLADE.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket, syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }
@ -89,7 +88,7 @@ pub fn new_left_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) ->
content: nodes::LEFT_SQUARE_BR.to_owned(), content: nodes::LEFT_SQUARE_BR.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket, syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }
@ -100,7 +99,7 @@ pub fn new_right_square_mn(expr_id: ExprId, parent_id_opt: Option<MarkNodeId>) -
content: nodes::RIGHT_SQUARE_BR.to_owned(), content: nodes::RIGHT_SQUARE_BR.to_owned(),
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::Bracket, syn_high_style: HighlightStyle::Bracket,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt, parent_id_opt,
newlines_at_end: 0, newlines_at_end: 0,
} }

View file

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

View file

@ -1,33 +1,39 @@
use super::attribute::Attributes; use crate::{
use crate::editor::ed_error::EdResult; markup::common_nodes::{
use crate::editor::ed_error::ExpectedTextNode; new_blank_mn, new_colon_mn, new_comma_mn, new_equals_mn, new_left_accolade_mn,
use crate::editor::ed_error::{NestedNodeMissingChild, NestedNodeRequired}; new_left_square_mn, new_right_accolade_mn, new_right_square_mn,
use crate::editor::markup::common_nodes::new_blank_mn; },
use crate::editor::markup::common_nodes::new_blank_mn_w_nls; markup_error::MarkResult,
use crate::editor::markup::common_nodes::new_colon_mn; slow_pool::{MarkNodeId, SlowPool},
use crate::editor::markup::common_nodes::new_comma_mn; syntax_highlight::HighlightStyle,
use crate::editor::markup::common_nodes::new_equals_mn; };
use crate::editor::markup::common_nodes::new_left_accolade_mn;
use crate::editor::markup::common_nodes::new_left_square_mn; use super::{
use crate::editor::markup::common_nodes::new_right_accolade_mn; attribute::Attributes, common_nodes::new_blank_mn_w_nls, top_level_def::tld_mark_node,
use crate::editor::markup::common_nodes::new_right_square_mn; };
use crate::editor::mvc::tld_value_update::tld_mark_node;
use crate::editor::slow_pool::MarkNodeId; use crate::markup_error::{ExpectedTextNode, NestedNodeMissingChild, NestedNodeRequired};
use crate::editor::slow_pool::SlowPool;
use crate::editor::syntax_highlight::HighlightStyle;
use crate::editor::util::index_of;
use crate::lang::ast::Def2;
use crate::lang::ast::DefId;
use crate::lang::ast::ExprId;
use crate::lang::ast::RecordField;
use crate::lang::ast::ValueDef;
use crate::lang::parse::ASTNodeId;
use crate::lang::parse::{AppHeader, AST};
use crate::lang::pattern::get_identifier_string;
use crate::lang::{ast::Expr2, expr::Env, pool::PoolStr};
use crate::ui::util::slice_get;
use bumpalo::Bump; use bumpalo::Bump;
use roc_ast::{
ast_error::ASTResult,
lang::{
core::{
ast::{ASTNodeId, AST},
def::def2::{Def2, DefId},
expr::{
expr2::{Expr2, ExprId},
record_field::RecordField,
},
header::AppHeader,
pattern::get_identifier_string,
val_def::ValueDef,
},
env::Env,
},
mem_pool::pool_str::PoolStr,
};
use roc_module::symbol::Interns; use roc_module::symbol::Interns;
use roc_utils::{index_of, slice_get};
use std::fmt; use std::fmt;
#[derive(Debug)] #[derive(Debug)]
@ -95,7 +101,7 @@ impl MarkupNode {
&self, &self,
child_id: MarkNodeId, child_id: MarkNodeId,
mark_node_pool: &SlowPool, mark_node_pool: &SlowPool,
) -> EdResult<(usize, usize)> { ) -> MarkResult<(usize, usize)> {
match self { match self {
MarkupNode::Nested { children_ids, .. } => { MarkupNode::Nested { children_ids, .. } => {
let mut mark_child_index_opt: Option<usize> = None; let mut mark_child_index_opt: Option<usize> = None;
@ -122,12 +128,11 @@ impl MarkupNode {
Ok((child_index, ast_child_index)) Ok((child_index, ast_child_index))
} else { } else {
// we want to find the index of the closest ast mark node to child_index // we want to find the index of the closest ast mark node to child_index
let indices_in_mark_res: EdResult<Vec<usize>> = child_ids_with_ast let mut indices_in_mark = vec![];
.iter()
.map(|c_id| index_of(*c_id, children_ids))
.collect();
let indices_in_mark = indices_in_mark_res?; for &c_id in child_ids_with_ast.iter() {
indices_in_mark.push(index_of(c_id, children_ids)?);
}
let mut last_diff = usize::MAX; let mut last_diff = usize::MAX;
let mut best_index = 0; let mut best_index = 0;
@ -186,7 +191,7 @@ impl MarkupNode {
full_content full_content
} }
pub fn get_content_mut(&mut self) -> EdResult<&mut String> { pub fn get_content_mut(&mut self) -> MarkResult<&mut String> {
match self { match self {
MarkupNode::Nested { .. } => ExpectedTextNode { MarkupNode::Nested { .. } => ExpectedTextNode {
function_name: "set_content".to_owned(), function_name: "set_content".to_owned(),
@ -208,7 +213,7 @@ impl MarkupNode {
.all(|chr| chr.is_ascii_alphanumeric()) .all(|chr| chr.is_ascii_alphanumeric())
} }
pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> EdResult<()> { pub fn add_child_at_index(&mut self, index: usize, child_id: MarkNodeId) -> MarkResult<()> {
if let MarkupNode::Nested { children_ids, .. } = self { if let MarkupNode::Nested { children_ids, .. } = self {
children_ids.splice(index..index, vec![child_id]); children_ids.splice(index..index, vec![child_id]);
} else { } else {
@ -292,7 +297,7 @@ fn new_markup_node(
content: text, content: text,
ast_node_id: node_id, ast_node_id: node_id,
syn_high_style: highlight_style, syn_high_style: highlight_style,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0, newlines_at_end: 0,
}; };
@ -307,7 +312,7 @@ pub fn def2_to_markup<'a, 'b>(
def2_node_id: DefId, def2_node_id: DefId,
mark_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
interns: &Interns, interns: &Interns,
) -> EdResult<MarkNodeId> { ) -> ASTResult<MarkNodeId> {
let ast_node_id = ASTNodeId::ADefId(def2_node_id); let ast_node_id = ASTNodeId::ADefId(def2_node_id);
let mark_node_id = match def2 { let mark_node_id = match def2 {
@ -349,7 +354,7 @@ pub fn expr2_to_markup<'a, 'b>(
expr2_node_id: ExprId, expr2_node_id: ExprId,
mark_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
interns: &Interns, interns: &Interns,
) -> EdResult<MarkNodeId> { ) -> ASTResult<MarkNodeId> {
let ast_node_id = ASTNodeId::AExprId(expr2_node_id); let ast_node_id = ASTNodeId::AExprId(expr2_node_id);
let mark_node_id = match expr2 { let mark_node_id = match expr2 {
@ -497,7 +502,7 @@ pub fn expr2_to_markup<'a, 'b>(
content: val_name, content: val_name,
ast_node_id, ast_node_id,
syn_high_style: HighlightStyle::Variable, syn_high_style: HighlightStyle::Variable,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0, newlines_at_end: 0,
}; };
@ -606,7 +611,7 @@ fn header_mn(content: String, expr_id: ExprId, mark_node_pool: &mut SlowPool) ->
content, content,
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: HighlightStyle::PackageRelated, syn_high_style: HighlightStyle::PackageRelated,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0, newlines_at_end: 0,
}; };
@ -624,7 +629,7 @@ fn header_val_mn(
content, content,
ast_node_id: ASTNodeId::AExprId(expr_id), ast_node_id: ASTNodeId::AExprId(expr_id),
syn_high_style: highlight_style, syn_high_style: highlight_style,
attributes: Attributes::new(), attributes: Attributes::default(),
parent_id_opt: None, parent_id_opt: None,
newlines_at_end: 0, newlines_at_end: 0,
}; };
@ -798,7 +803,7 @@ pub fn ast_to_mark_nodes<'a, 'b>(
ast: &AST, ast: &AST,
mark_node_pool: &mut SlowPool, mark_node_pool: &mut SlowPool,
interns: &Interns, interns: &Interns,
) -> EdResult<Vec<MarkNodeId>> { ) -> ASTResult<Vec<MarkNodeId>> {
let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)]; let mut all_mark_node_ids = vec![header_to_markup(&ast.header, mark_node_pool)];
for &def_id in ast.def_ids.iter() { for &def_id in ast.def_ids.iter() {

View file

@ -0,0 +1,51 @@
use roc_ast::{
ast_error::ASTResult,
lang::{
core::{
ast::ASTNodeId,
pattern::{get_identifier_string, PatternId},
},
env::Env,
},
};
use roc_module::symbol::Interns;
use crate::{
markup::{attribute::Attributes, common_nodes::new_equals_mn, nodes::MarkupNode},
slow_pool::{MarkNodeId, SlowPool},
syntax_highlight::HighlightStyle,
};
pub fn tld_mark_node<'a>(
identifier_id: PatternId,
expr_mark_node_id: MarkNodeId,
ast_node_id: ASTNodeId,
mark_node_pool: &mut SlowPool,
env: &Env<'a>,
interns: &Interns,
) -> ASTResult<MarkupNode> {
let pattern2 = env.pool.get(identifier_id);
let val_name = get_identifier_string(pattern2, interns)?;
let val_name_mn = MarkupNode::Text {
content: val_name,
ast_node_id,
syn_high_style: HighlightStyle::Variable,
attributes: Attributes::default(),
parent_id_opt: None,
newlines_at_end: 0,
};
let val_name_mn_id = mark_node_pool.add(val_name_mn);
let equals_mn_id = mark_node_pool.add(new_equals_mn(ast_node_id, None));
let full_let_node = MarkupNode::Nested {
ast_node_id,
children_ids: vec![val_name_mn_id, equals_mn_id, expr_mark_node_id],
parent_id_opt: None,
newlines_at_end: 2,
};
Ok(full_let_node)
}

View file

@ -0,0 +1,55 @@
use roc_utils::util_error::UtilError;
use snafu::{Backtrace, NoneError, ResultExt, Snafu};
use crate::slow_pool::MarkNodeId;
#[derive(Debug, Snafu)]
#[snafu(visibility(pub))]
pub enum MarkError {
#[snafu(display(
"CaretNotFound: No carets were found in the expected node with id {}",
node_id
))]
CaretNotFound {
node_id: MarkNodeId,
backtrace: Backtrace,
},
#[snafu(display(
"ExpectedTextNode: the function {} expected a Text node, got {} instead.",
function_name,
node_type
))]
ExpectedTextNode {
function_name: String,
node_type: String,
backtrace: Backtrace,
},
#[snafu(display("NestedNodeMissingChild: expected to find child with id {} in Nested MarkupNode, but it was missing. Id's of the children are {:?}.", node_id, children_ids))]
NestedNodeMissingChild {
node_id: MarkNodeId,
children_ids: Vec<MarkNodeId>,
backtrace: Backtrace,
},
#[snafu(display(
"NestedNodeRequired: required a Nested node at this position, node was a {}.",
node_type
))]
NestedNodeRequired {
node_type: String,
backtrace: Backtrace,
},
#[snafu(display("UIError: {}", msg))]
UtilErrorBacktrace { msg: String, backtrace: Backtrace },
}
pub type MarkResult<T, E = MarkError> = std::result::Result<T, E>;
impl From<UtilError> for MarkError {
fn from(util_err: UtilError) -> Self {
let msg = format!("{}", util_err);
// hack to handle MarkError derive
let dummy_res: Result<(), NoneError> = Err(NoneError {});
dummy_res.context(UtilErrorBacktrace { msg }).unwrap_err()
}
}

View file

@ -1,6 +1,7 @@
use crate::editor::markup::nodes::MarkupNode;
use std::fmt; use std::fmt;
use crate::markup::nodes::MarkupNode;
pub type MarkNodeId = usize; pub type MarkNodeId = usize;
#[derive(Debug)] #[derive(Debug)]
@ -9,10 +10,6 @@ pub struct SlowPool {
} }
impl SlowPool { impl SlowPool {
pub fn new() -> SlowPool {
SlowPool { nodes: Vec::new() }
}
pub fn add(&mut self, node: MarkupNode) -> MarkNodeId { pub fn add(&mut self, node: MarkupNode) -> MarkNodeId {
let id = self.nodes.len(); let id = self.nodes.len();
@ -72,3 +69,9 @@ impl fmt::Display for SlowPool {
Ok(()) Ok(())
} }
} }
impl Default for SlowPool {
fn default() -> Self {
SlowPool { nodes: Vec::new() }
}
}

View file

@ -1,8 +1,8 @@
use crate::graphics::colors as gr_colors;
use gr_colors::{from_hsb, RgbaTup};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use crate::colors::{self, from_hsb, RgbaTup};
#[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)] #[derive(Hash, Eq, PartialEq, Copy, Clone, Debug, Deserialize, Serialize)]
pub enum HighlightStyle { pub enum HighlightStyle {
Operator, // =+-<>... Operator, // =+-<>...
@ -24,14 +24,14 @@ pub fn default_highlight_map() -> HashMap<HighlightStyle, RgbaTup> {
let mut highlight_map = HashMap::new(); let mut highlight_map = HashMap::new();
[ [
(Operator, gr_colors::WHITE), (Operator, colors::WHITE),
(String, from_hsb(346, 65, 97)), (String, from_hsb(346, 65, 97)),
(FunctionName, gr_colors::WHITE), (FunctionName, colors::WHITE),
(Type, gr_colors::WHITE), (Type, colors::WHITE),
(Bracket, from_hsb(347, 80, 100)), (Bracket, from_hsb(347, 80, 100)),
(Number, from_hsb(185, 50, 75)), (Number, from_hsb(185, 50, 75)),
(PackageRelated, gr_colors::WHITE), (PackageRelated, colors::WHITE),
(Variable, gr_colors::WHITE), (Variable, colors::WHITE),
(RecordField, from_hsb(258, 50, 90)), (RecordField, from_hsb(258, 50, 90)),
(Import, from_hsb(185, 50, 75)), (Import, from_hsb(185, 50, 75)),
(Provides, from_hsb(185, 50, 75)), (Provides, from_hsb(185, 50, 75)),

View file

@ -1,7 +1,8 @@
use crate::target::arch_str; use crate::target::arch_str;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use libloading::{Error, Library}; use libloading::{Error, Library};
#[cfg(feature = "llvm")] use roc_builtins::bitcode;
// #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
use std::collections::HashMap; use std::collections::HashMap;
use std::env; use std::env;
@ -93,7 +94,12 @@ pub fn build_zig_host_native(
.env("PATH", env_path) .env("PATH", env_path)
.env("HOME", env_home); .env("HOME", env_home);
if let Some(shared_lib_path) = shared_lib_path { if let Some(shared_lib_path) = shared_lib_path {
command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); command.args(&[
"build-exe",
"-fPIE",
shared_lib_path.to_str().unwrap(),
bitcode::OBJ_PATH,
]);
} else { } else {
command.args(&["build-obj", "-fPIC"]); command.args(&["build-obj", "-fPIC"]);
} }
@ -109,7 +115,6 @@ pub fn build_zig_host_native(
// include libc // include libc
"--library", "--library",
"c", "c",
"--strip",
// cross-compile? // cross-compile?
"-target", "-target",
target, target,
@ -178,7 +183,12 @@ pub fn build_zig_host_native(
.env("PATH", &env_path) .env("PATH", &env_path)
.env("HOME", &env_home); .env("HOME", &env_home);
if let Some(shared_lib_path) = shared_lib_path { if let Some(shared_lib_path) = shared_lib_path {
command.args(&["build-exe", "-fPIE", shared_lib_path.to_str().unwrap()]); command.args(&[
"build-exe",
"-fPIE",
shared_lib_path.to_str().unwrap(),
bitcode::OBJ_PATH,
]);
} else { } else {
command.args(&["build-obj", "-fPIC"]); command.args(&["build-obj", "-fPIC"]);
} }
@ -197,7 +207,6 @@ pub fn build_zig_host_native(
// include libc // include libc
"--library", "--library",
"c", "c",
"--strip",
]); ]);
if matches!(opt_level, OptLevel::Optimize) { if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]); command.args(&["-O", "ReleaseSafe"]);
@ -274,6 +283,7 @@ pub fn build_c_host_native(
if let Some(shared_lib_path) = shared_lib_path { if let Some(shared_lib_path) = shared_lib_path {
command.args(&[ command.args(&[
shared_lib_path.to_str().unwrap(), shared_lib_path.to_str().unwrap(),
bitcode::OBJ_PATH,
"-fPIE", "-fPIE",
"-pie", "-pie",
"-lm", "-lm",
@ -370,7 +380,7 @@ pub fn rebuild_host(
} else if cargo_host_src.exists() { } else if cargo_host_src.exists() {
// Compile and link Cargo.toml, if it exists // Compile and link Cargo.toml, if it exists
let cargo_dir = host_input_path.parent().unwrap(); let cargo_dir = host_input_path.parent().unwrap();
let libhost_dir = let cargo_out_dir =
cargo_dir cargo_dir
.join("target") .join("target")
.join(if matches!(opt_level, OptLevel::Optimize) { .join(if matches!(opt_level, OptLevel::Optimize) {
@ -378,30 +388,30 @@ pub fn rebuild_host(
} else { } else {
"debug" "debug"
}); });
let libhost = libhost_dir.join("libhost.a");
let mut command = Command::new("cargo"); let mut command = Command::new("cargo");
command.arg("build").current_dir(cargo_dir); command.arg("build").current_dir(cargo_dir);
if matches!(opt_level, OptLevel::Optimize) { if matches!(opt_level, OptLevel::Optimize) {
command.arg("--release"); command.arg("--release");
} }
let source_file = if shared_lib_path.is_some() {
command.env("RUSTFLAGS", "-C link-dead-code");
command.args(&["--bin", "host"]);
"src/main.rs"
} else {
command.arg("--lib");
"src/lib.rs"
};
let output = command.output().unwrap(); let output = command.output().unwrap();
validate_output("src/lib.rs", "cargo build", output); validate_output(source_file, "cargo build", output);
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
if shared_lib_path.is_some() { if shared_lib_path.is_some() {
// If compiling to executable, let c deal with linking as well. // For surgical linking, just copy the dynamically linked rust app.
let output = build_c_host_native( std::fs::copy(cargo_out_dir.join("host"), host_dest_native).unwrap();
&env_path,
&env_home,
host_dest_native.to_str().unwrap(),
&[c_host_src.to_str().unwrap(), libhost.to_str().unwrap()],
opt_level,
shared_lib_path,
);
validate_output("host.c", "clang", output);
} else { } else {
// Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
let output = build_c_host_native( let output = build_c_host_native(
&env_path, &env_path,
&env_home, &env_home,
@ -418,7 +428,7 @@ pub fn rebuild_host(
.args(&[ .args(&[
"-r", "-r",
"-L", "-L",
libhost_dir.to_str().unwrap(), cargo_out_dir.to_str().unwrap(),
c_host_dest.to_str().unwrap(), c_host_dest.to_str().unwrap(),
"-lhost", "-lhost",
"-o", "-o",

View file

@ -2,11 +2,9 @@
use roc_gen_llvm::llvm::build::module_from_builtins; use roc_gen_llvm::llvm::build::module_from_builtins;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
pub use roc_gen_llvm::llvm::build::FunctionIterator; pub use roc_gen_llvm::llvm::build::FunctionIterator;
#[cfg(feature = "llvm")]
use roc_load::file::MonomorphizedModule; use roc_load::file::MonomorphizedModule;
#[cfg(feature = "llvm")] #[cfg(feature = "llvm")]
use roc_mono::ir::OptLevel; use roc_mono::ir::OptLevel;
#[cfg(feature = "llvm")]
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::time::Duration; use std::time::Duration;

View file

@ -62,12 +62,6 @@ Its one thing to actually write these functions, its _another_ thing to let the
### builtins/mono/src/borrow.rs ### builtins/mono/src/borrow.rs
After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail. After we have all of this, we need to specify if the arguments we're passing are owned, borrowed or irrelvant. Towards the bottom of this file, add a new case for you builtin and specify each arg. Be sure to read the comment, as it explains this in more detail.
## Specifying the uniqueness of a function
### builtins/src/unique.rs
One of the cool things about Roc is that it evaluates if a value in memory is shared between scopes or if it is used in just one place. If the value is used in one place then it is 'unique', and it therefore can be mutated in place. For a value created by a function, the uniqueness of the output is determined in part by the uniqueness of the input arguments. For example `List.single : elem -> List elem` can return a unique list if the `elem` is also unique.
We have to define the uniqueness constraints of a function just like we have to define a type signature. That is what happens in `unique.rs`. This can be tricky so it would be a good step to ask for help on if it is confusing.
## Testing it ## Testing it
### solve/tests/solve_expr.rs ### solve/tests/solve_expr.rs
To make sure that Roc is properly inferring the type of the new builtin, add a test to this file simlar to: To make sure that Roc is properly inferring the type of the new builtin, add a test to this file simlar to:

View file

@ -1,6 +1,7 @@
const std = @import("std"); const std = @import("std");
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const RocResult = utils.RocResult; const RocResult = utils.RocResult;
const UpdateMode = utils.UpdateMode;
const mem = std.mem; const mem = std.mem;
const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool; const EqFn = fn (?[*]u8, ?[*]u8) callconv(.C) bool;
@ -52,6 +53,14 @@ pub const RocList = extern struct {
}; };
} }
pub fn makeUniqueExtra(self: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) RocList {
if (update_mode == .InPlace) {
return self;
} else {
return self.makeUnique(alignment, element_width);
}
}
pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList { pub fn makeUnique(self: RocList, alignment: u32, element_width: usize) RocList {
if (self.isEmpty()) { if (self.isEmpty()) {
return self; return self;
@ -132,14 +141,14 @@ const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void; const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listReverse(list: RocList, alignment: u32, element_width: usize) callconv(.C) RocList { pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList {
if (list.bytes) |source_ptr| { if (list.bytes) |source_ptr| {
const size = list.len(); const size = list.len();
var i: usize = 0; var i: usize = 0;
const end: usize = size - 1; const end: usize = size - 1;
if (list.isUnique()) { if (update_mode == .InPlace or list.isUnique()) {
// Working from the front and back so // Working from the front and back so
// we only need to go ~(n / 2) iterations. // we only need to go ~(n / 2) iterations.
@ -720,10 +729,13 @@ pub fn listSingle(alignment: u32, element: Opaque, element_width: usize) callcon
return output; return output;
} }
pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize) callconv(.C) RocList { pub fn listAppend(list: RocList, alignment: u32, element: Opaque, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList {
const old_length = list.len(); const old_length = list.len();
var output = list.reallocate(alignment, old_length + 1, element_width); var output = list.reallocate(alignment, old_length + 1, element_width);
// we'd need capacity to use update_mode here
_ = update_mode;
if (output.bytes) |target| { if (output.bytes) |target| {
if (element) |source| { if (element) |source| {
@memcpy(target + old_length * element_width, source, element_width); @memcpy(target + old_length * element_width, source, element_width);
@ -763,18 +775,24 @@ pub fn listSwap(
element_width: usize, element_width: usize,
index_1: usize, index_1: usize,
index_2: usize, index_2: usize,
update_mode: UpdateMode,
) callconv(.C) RocList { ) callconv(.C) RocList {
const size = list.len(); const size = list.len();
if (index_1 >= size or index_2 >= size) { if (index_1 == index_2 or index_1 >= size or index_2 >= size) {
// Either index out of bounds so we just return // Either index out of bounds so we just return
return list; return list;
} }
const newList = list.makeUnique(alignment, element_width); const newList = blk: {
if (update_mode == .InPlace) {
break :blk list;
} else {
break :blk list.makeUnique(alignment, element_width);
}
};
if (newList.bytes) |source_ptr| { const source_ptr = @ptrCast([*]u8, newList.bytes);
swapElements(source_ptr, element_width, index_1, index_2); swapElements(source_ptr, element_width, index_1, index_2);
}
return newList; return newList;
} }
@ -815,6 +833,67 @@ pub fn listDrop(
} }
} }
pub fn listDropAt(
list: RocList,
alignment: u32,
element_width: usize,
drop_index: usize,
dec: Dec,
) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
const size = list.len();
if (drop_index >= size) {
return list;
}
if (drop_index < size) {
const element = source_ptr + drop_index * element_width;
dec(element);
}
// NOTE
// we need to return an empty list explicitly,
// because we rely on the pointer field being null if the list is empty
// which also requires duplicating the utils.decref call to spend the RC token
if (size < 2) {
utils.decref(list.bytes, size * element_width, alignment);
return RocList.empty();
}
if (list.isUnique()) {
var i = drop_index;
while (i < size) : (i += 1) {
const copy_target = source_ptr + i * element_width;
const copy_source = copy_target + element_width;
@memcpy(copy_target, copy_source, element_width);
}
var new_list = list;
new_list.length -= 1;
return new_list;
}
const output = RocList.allocate(alignment, size - 1, element_width);
const target_ptr = output.bytes orelse unreachable;
const head_size = drop_index * element_width;
@memcpy(target_ptr, source_ptr, head_size);
const tail_target = target_ptr + drop_index * element_width;
const tail_source = source_ptr + (drop_index + 1) * element_width;
const tail_size = (size - drop_index - 1) * element_width;
@memcpy(tail_target, tail_source, tail_size);
utils.decref(list.bytes, size * element_width, alignment);
return output;
} else {
return RocList.empty();
}
}
pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList { pub fn listRange(width: utils.IntWidth, low: Opaque, high: Opaque) callconv(.C) RocList {
return switch (width) { return switch (width) {
.U8 => helper1(u8, low, high), .U8 => helper1(u8, low, high),

View file

@ -39,6 +39,7 @@ comptime {
exportListFn(list.listSortWith, "sort_with"); exportListFn(list.listSortWith, "sort_with");
exportListFn(list.listConcat, "concat"); exportListFn(list.listConcat, "concat");
exportListFn(list.listDrop, "drop"); exportListFn(list.listDrop, "drop");
exportListFn(list.listDropAt, "drop_at");
exportListFn(list.listSet, "set"); exportListFn(list.listSet, "set");
exportListFn(list.listSetInPlace, "set_in_place"); exportListFn(list.listSetInPlace, "set_in_place");
exportListFn(list.listSwap, "swap"); exportListFn(list.listSwap, "swap");
@ -101,6 +102,7 @@ comptime {
exportStrFn(str.strToUtf8C, "to_utf8"); exportStrFn(str.strToUtf8C, "to_utf8");
exportStrFn(str.fromUtf8C, "from_utf8"); exportStrFn(str.fromUtf8C, "from_utf8");
exportStrFn(str.fromUtf8RangeC, "from_utf8_range"); exportStrFn(str.fromUtf8RangeC, "from_utf8_range");
exportStrFn(str.repeat, "repeat");
} }
// Utils // Utils
@ -164,7 +166,12 @@ test "" {
// https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig // https://github.com/ziglang/zig/blob/85755c51d529e7d9b406c6bdf69ce0a0f33f3353/lib/std/special/compiler_rt/muloti4.zig
// //
// Thank you Zig Contributors! // Thank you Zig Contributors!
export fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
// Export it as weak incase it is alreadly linked in by something else.
comptime {
@export(__muloti4, .{ .name = "__muloti4", .linkage = .Weak });
}
fn __muloti4(a: i128, b: i128, overflow: *c_int) callconv(.C) i128 {
// @setRuntimeSafety(std.builtin.is_test); // @setRuntimeSafety(std.builtin.is_test);
const min = @bitCast(i128, @as(u128, 1 << (128 - 1))); const min = @bitCast(i128, @as(u128, 1 << (128 - 1)));

View file

@ -1,5 +1,6 @@
const utils = @import("utils.zig"); const utils = @import("utils.zig");
const RocList = @import("list.zig").RocList; const RocList = @import("list.zig").RocList;
const UpdateMode = utils.UpdateMode;
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const always_inline = std.builtin.CallOptions.Modifier.always_inline; const always_inline = std.builtin.CallOptions.Modifier.always_inline;
@ -866,6 +867,22 @@ pub fn startsWith(string: RocStr, prefix: RocStr) callconv(.C) bool {
return true; return true;
} }
// Str.repeat
pub fn repeat(string: RocStr, count: usize) callconv(.C) RocStr {
const bytes_len = string.len();
const bytes_ptr = string.asU8ptr();
var ret_string = RocStr.allocate(.Clone, count * bytes_len);
var ret_string_ptr = ret_string.asU8ptr();
var i: usize = 0;
while (i < count) : (i += 1) {
@memcpy(ret_string_ptr + (i * bytes_len), bytes_ptr, bytes_len);
}
return ret_string;
}
// Str.startsWithCodePt // Str.startsWithCodePt
pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool { pub fn startsWithCodePt(string: RocStr, prefix: u32) callconv(.C) bool {
const bytes_ptr = string.asU8ptr(); const bytes_ptr = string.asU8ptr();
@ -1131,10 +1148,10 @@ test "RocStr.joinWith: result is big" {
// Str.toUtf8 // Str.toUtf8
pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList { pub fn strToUtf8C(arg: RocStr) callconv(.C) RocList {
return @call(.{ .modifier = always_inline }, strToBytes, .{arg}); return strToBytes(arg);
} }
fn strToBytes(arg: RocStr) RocList { inline fn strToBytes(arg: RocStr) RocList {
if (arg.isEmpty()) { if (arg.isEmpty()) {
return RocList.empty(); return RocList.empty();
} else if (arg.isSmallStr()) { } else if (arg.isSmallStr()) {
@ -1161,11 +1178,11 @@ const CountAndStart = extern struct {
start: usize, start: usize,
}; };
pub fn fromUtf8C(arg: RocList, output: *FromUtf8Result) callconv(.C) void { pub fn fromUtf8C(arg: RocList, update_mode: UpdateMode, output: *FromUtf8Result) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8, .{arg}); output.* = fromUtf8(arg, update_mode);
} }
fn fromUtf8(arg: RocList) FromUtf8Result { inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result {
const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length]; const bytes = @ptrCast([*]const u8, arg.bytes)[0..arg.length];
if (unicode.utf8ValidateSlice(bytes)) { if (unicode.utf8ValidateSlice(bytes)) {
@ -1178,13 +1195,23 @@ fn fromUtf8(arg: RocList) FromUtf8Result {
const data_bytes = arg.len(); const data_bytes = arg.len();
utils.decref(arg.bytes, data_bytes, RocStr.alignment); utils.decref(arg.bytes, data_bytes, RocStr.alignment);
return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; return FromUtf8Result{
.is_ok = true,
.string = string,
.byte_index = 0,
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
} else { } else {
const byte_list = arg.makeUnique(RocStr.alignment, @sizeOf(u8)); const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode);
const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length }; const string = RocStr{ .str_bytes = byte_list.bytes, .str_len = byte_list.length };
return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte }; return FromUtf8Result{
.is_ok = true,
.string = string,
.byte_index = 0,
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
} }
} else { } else {
const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length); const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length);
@ -1193,7 +1220,12 @@ fn fromUtf8(arg: RocList) FromUtf8Result {
const data_bytes = arg.len(); const data_bytes = arg.len();
utils.decref(arg.bytes, data_bytes, RocStr.alignment); utils.decref(arg.bytes, data_bytes, RocStr.alignment);
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem }; return FromUtf8Result{
.is_ok = false,
.string = RocStr.empty(),
.byte_index = temp.index,
.problem_code = temp.problem,
};
} }
} }
@ -1276,11 +1308,11 @@ pub const Utf8ByteProblem = enum(u8) {
}; };
fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result { fn validateUtf8Bytes(bytes: [*]u8, length: usize) FromUtf8Result {
return fromUtf8(RocList{ .bytes = bytes, .length = length }); return fromUtf8(RocList{ .bytes = bytes, .length = length }, .Immutable);
} }
fn validateUtf8BytesX(str: RocList) FromUtf8Result { fn validateUtf8BytesX(str: RocList) FromUtf8Result {
return fromUtf8(str); return fromUtf8(str, .Immutable);
} }
fn expectOk(result: FromUtf8Result) !void { fn expectOk(result: FromUtf8Result) !void {

View file

@ -256,3 +256,8 @@ pub const Ordering = enum(u8) {
GT = 1, GT = 1,
LT = 2, LT = 2,
}; };
pub const UpdateMode = extern enum(u8) {
Immutable = 0,
InPlace = 1,
};

View file

@ -31,6 +31,7 @@ interface List
range, range,
sortWith, sortWith,
drop, drop,
dropAt,
swap swap
] ]
imports [] imports []
@ -422,15 +423,18 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
## If the given index is outside the bounds of the list, returns the original ## If the given index is outside the bounds of the list, returns the original
## list unmodified. ## list unmodified.
## ##
## To drop the element at a given index, instead of replacing it, see [List.drop]. ## To drop the element at a given index, instead of replacing it, see [List.dropAt].
set : List elem, Nat, elem -> List elem set : List elem, Nat, elem -> List elem
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
## Drops the element at the given index from the list. ## Drops the element at the given index from the list.
## ##
## This has no effect if the given index is outside the bounds of the list. ## This has no effect if the given index is outside the bounds of the list.
## ##
## To replace the element at a given index, instead of dropping it, see [List.set]. ## To replace the element at a given index, instead of dropping it, see [List.set].
drop : List elem, Nat -> List elem dropAt : List elem, Nat -> List elem
## Adds a new element to the end of the list. ## Adds a new element to the end of the list.
## ##

View file

@ -28,6 +28,7 @@ pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8"; pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8"; pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range"; pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const DICT_HASH: &str = "roc_builtins.dict.hash"; pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str"; pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";
@ -62,6 +63,7 @@ pub const LIST_REPEAT: &str = "roc_builtins.list.repeat";
pub const LIST_APPEND: &str = "roc_builtins.list.append"; pub const LIST_APPEND: &str = "roc_builtins.list.append";
pub const LIST_PREPEND: &str = "roc_builtins.list.prepend"; pub const LIST_PREPEND: &str = "roc_builtins.list.prepend";
pub const LIST_DROP: &str = "roc_builtins.list.drop"; pub const LIST_DROP: &str = "roc_builtins.list.drop";
pub const LIST_DROP_AT: &str = "roc_builtins.list.drop_at";
pub const LIST_SWAP: &str = "roc_builtins.list.swap"; pub const LIST_SWAP: &str = "roc_builtins.list.swap";
pub const LIST_SINGLE: &str = "roc_builtins.list.single"; pub const LIST_SINGLE: &str = "roc_builtins.list.single";
pub const LIST_JOIN: &str = "roc_builtins.list.join"; pub const LIST_JOIN: &str = "roc_builtins.list.join";

View file

@ -618,6 +618,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(str_type()) Box::new(str_type())
); );
// repeat : Str, Nat -> Str
add_top_level_function_type!(
Symbol::STR_REPEAT,
vec![str_type(), nat_type()],
Box::new(str_type())
);
// fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]* // fromUtf8 : List U8 -> Result Str [ BadUtf8 Utf8Problem ]*
{ {
let bad_utf8 = SolvedType::TagUnion( let bad_utf8 = SolvedType::TagUnion(
@ -913,6 +920,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(list_type(flex(TVAR1))), Box::new(list_type(flex(TVAR1))),
); );
// dropAt : List elem, Nat -> List elem
add_top_level_function_type!(
Symbol::LIST_DROP_AT,
vec![list_type(flex(TVAR1)), nat_type()],
Box::new(list_type(flex(TVAR1))),
);
// swap : List elem, Nat, Nat -> List elem // swap : List elem, Nat, Nat -> List elem
add_top_level_function_type!( add_top_level_function_type!(
Symbol::LIST_SWAP, Symbol::LIST_SWAP,

View file

@ -66,6 +66,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_FROM_UTF8_RANGE => str_from_utf8_range, STR_FROM_UTF8_RANGE => str_from_utf8_range,
STR_TO_UTF8 => str_to_utf8, STR_TO_UTF8 => str_to_utf8,
STR_FROM_FLOAT=> str_from_float, STR_FROM_FLOAT=> str_from_float,
STR_REPEAT => str_repeat,
LIST_LEN => list_len, LIST_LEN => list_len,
LIST_GET => list_get, LIST_GET => list_get,
LIST_SET => list_set, LIST_SET => list_set,
@ -86,6 +87,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP2 => list_map2, LIST_MAP2 => list_map2,
LIST_MAP3 => list_map3, LIST_MAP3 => list_map3,
LIST_DROP => list_drop, LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
LIST_SWAP => list_swap, LIST_SWAP => list_swap,
LIST_MAP_WITH_INDEX => list_map_with_index, LIST_MAP_WITH_INDEX => list_map_with_index,
LIST_KEEP_IF => list_keep_if, LIST_KEEP_IF => list_keep_if,
@ -1233,6 +1235,26 @@ fn str_split(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// Str.repeat : Str, Nat -> Str
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();
let nat_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::StrRepeat,
args: vec![(str_var, Var(Symbol::ARG_1)), (nat_var, Var(Symbol::ARG_2))],
ret_var: str_var,
};
defn(
symbol,
vec![(str_var, Symbol::ARG_1), (nat_var, Symbol::ARG_2)],
var_store,
body,
str_var,
)
}
/// Str.concat : Str, Str -> Str /// Str.concat : Str, Str -> Str
fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def { fn str_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh(); let str_var = var_store.fresh();
@ -1958,6 +1980,29 @@ fn list_drop(symbol: Symbol, var_store: &mut VarStore) -> Def {
) )
} }
/// List.dropAt : List elem, Nat -> List elem
fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();
let index_var = var_store.fresh();
let body = RunLowLevel {
op: LowLevel::ListDropAt,
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 /// List.append : List elem, elem -> List elem
fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def { fn list_append(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh(); let list_var = var_store.fresh();

View file

@ -9,7 +9,7 @@ use roc_can::expected::PExpected;
use roc_can::expr::Expr::{self, *}; use roc_can::expr::Expr::{self, *};
use roc_can::expr::{Field, WhenBranch}; use roc_can::expr::{Field, WhenBranch};
use roc_can::pattern::Pattern; use roc_can::pattern::Pattern;
use roc_collections::all::{ImMap, Index, SendMap}; use roc_collections::all::{ImMap, Index, MutSet, SendMap};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
@ -1438,13 +1438,15 @@ fn instantiate_rigids(
annotation: &Type, annotation: &Type,
introduced_vars: &IntroducedVariables, introduced_vars: &IntroducedVariables,
new_rigids: &mut Vec<Variable>, new_rigids: &mut Vec<Variable>,
ftv: &mut ImMap<Lowercase, Variable>, ftv: &mut ImMap<Lowercase, Variable>, // rigids defined before the current annotation
loc_pattern: &Located<Pattern>, loc_pattern: &Located<Pattern>,
headers: &mut SendMap<Symbol, Located<Type>>, headers: &mut SendMap<Symbol, Located<Type>>,
) -> Type { ) -> Type {
let mut annotation = annotation.clone(); let mut annotation = annotation.clone();
let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default(); let mut rigid_substitution: ImMap<Variable, Type> = ImMap::default();
let outside_rigids: MutSet<Variable> = ftv.values().copied().collect();
for (name, var) in introduced_vars.var_by_name.iter() { for (name, var) in introduced_vars.var_by_name.iter() {
if let Some(existing_rigid) = ftv.get(name) { if let Some(existing_rigid) = ftv.get(name) {
rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); rigid_substitution.insert(*var, Type::Variable(*existing_rigid));
@ -1464,7 +1466,12 @@ fn instantiate_rigids(
&Located::at(loc_pattern.region, annotation.clone()), &Located::at(loc_pattern.region, annotation.clone()),
) { ) {
for (symbol, loc_type) in new_headers { for (symbol, loc_type) in new_headers {
new_rigids.extend(loc_type.value.variables()); for var in loc_type.value.variables() {
// a rigid is only new if this annotation is the first occurrence of this rigid
if !outside_rigids.contains(&var) {
new_rigids.push(var);
}
}
headers.insert(symbol, loc_type); headers.insert(symbol, loc_type);
} }
} }

View file

@ -226,6 +226,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>, _symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)], _args: &'a [(Layout<'a>, Symbol)],
_ret_layout: &Layout<'a>, _ret_layout: &Layout<'a>,

View file

@ -9,7 +9,7 @@ use std::marker::PhantomData;
pub mod aarch64; pub mod aarch64;
pub mod x86_64; pub mod x86_64;
const PTR_SIZE: u32 = 64; const PTR_SIZE: u32 = 8;
pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> { pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
const GENERAL_PARAM_REGS: &'static [GeneralReg]; const GENERAL_PARAM_REGS: &'static [GeneralReg];
@ -48,6 +48,7 @@ pub trait CallConv<GeneralReg: RegTrait, FloatReg: RegTrait> {
// load_args updates the symbol map to know where every arg is stored. // load_args updates the symbol map to know where every arg is stored.
fn load_args<'a>( fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
// ret_layout is needed because if it is a complex type, we pass a pointer as the first arg. // ret_layout is needed because if it is a complex type, we pass a pointer as the first arg.
@ -422,7 +423,12 @@ impl<
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
CC::load_args(&mut self.symbol_storage_map, args, ret_layout)?; CC::load_args(
&mut self.buf,
&mut self.symbol_storage_map,
args,
ret_layout,
)?;
// Update used and free regs. // Update used and free regs.
for (sym, storage) in &self.symbol_storage_map { for (sym, storage) in &self.symbol_storage_map {
match storage { match storage {
@ -489,6 +495,25 @@ impl<
ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]);
Ok(()) Ok(())
} }
Layout::Builtin(Builtin::Str) => {
if CC::returns_via_arg_pointer(ret_layout)? {
// This will happen on windows, return via pointer here.
Err("FnCall: Returning strings via pointer not yet implemented".to_string())
} else {
let offset = self.claim_stack_size(16)?;
self.symbol_storage_map.insert(
*dst,
SymbolStorage::Base {
offset,
size: 16,
owned: true,
},
);
ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]);
ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]);
Ok(())
}
}
x => Err(format!( x => Err(format!(
"FnCall: receiving return type, {:?}, is not yet implemented", "FnCall: receiving return type, {:?}, is not yet implemented",
x x
@ -893,6 +918,35 @@ impl<
ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val); ASM::mov_freg64_imm64(&mut self.buf, &mut self.relocs, reg, val);
Ok(()) Ok(())
} }
Literal::Str(x) if x.len() < 16 => {
// Load small string.
let reg = self.get_tmp_general_reg()?;
let offset = self.claim_stack_size(16)?;
self.symbol_storage_map.insert(
*sym,
SymbolStorage::Base {
offset,
size: 16,
owned: true,
},
);
let mut bytes = [0; 16];
bytes[..x.len()].copy_from_slice(x.as_bytes());
bytes[15] = (x.len() as u8) | 0b1000_0000;
let mut num_bytes = [0; 8];
num_bytes.copy_from_slice(&bytes[..8]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(&mut self.buf, reg, num);
ASM::mov_base32_reg64(&mut self.buf, offset, reg);
num_bytes.copy_from_slice(&bytes[8..]);
let num = i64::from_ne_bytes(num_bytes);
ASM::mov_reg64_imm64(&mut self.buf, reg, num);
ASM::mov_base32_reg64(&mut self.buf, offset + 8, reg);
Ok(())
}
x => Err(format!("loading literal, {:?}, is not yet implemented", x)), x => Err(format!("loading literal, {:?}, is not yet implemented", x)),
} }
} }
@ -1012,6 +1066,19 @@ impl<
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(Builtin::Float64) => {
ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset); ASM::mov_freg64_base32(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *offset);
} }
Layout::Builtin(Builtin::Str) => {
if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) {
// This will happen on windows, return via pointer here.
return Err("Returning strings via pointer not yet implemented".to_string());
} else {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
ASM::mov_reg64_base32(
&mut self.buf,
CC::GENERAL_RETURN_REGS[1],
*offset + 8,
);
}
}
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
let (offset, size) = (*offset, *size); let (offset, size) = (*offset, *size);
// Nothing to do for empty struct // Nothing to do for empty struct
@ -1446,8 +1513,6 @@ macro_rules! single_register_integers {
#[macro_export] #[macro_export]
macro_rules! single_register_floats { macro_rules! single_register_floats {
() => { () => {
// Float16 is explicitly ignored because it is not supported by must hardware and may require special exceptions.
// Builtin::Float16 |
Builtin::Float32 | Builtin::Float64 Builtin::Float32 | Builtin::Float64
}; };
} }

View file

@ -177,6 +177,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
@ -231,6 +232,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
); );
} }
} }
Layout::Builtin(Builtin::Str) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg.
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
base_offset += 16;
X86_64Assembler::mov_reg64_base32(buf, dst1, base_offset - 8);
X86_64Assembler::mov_reg64_base32(buf, dst2, base_offset);
symbol_map.insert(
*sym,
SymbolStorage::Base {
offset: base_offset,
size: 16,
owned: true,
},
);
general_i += 2;
} else {
return Err(
"loading strings args on the stack is not yet implemented".to_string()
);
}
}
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
@ -257,7 +281,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
// For most return layouts we will do nothing. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
Layout::Builtin(single_register_builtins!()) => {} Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
x => { x => {
return Err(format!( return Err(format!(
"receiving return type, {:?}, is not yet implemented", "receiving return type, {:?}, is not yet implemented",
@ -373,6 +397,32 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Str) => {
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg.
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
SymbolStorage::Base { offset, .. } => {
X86_64Assembler::mov_reg64_base32(buf, dst1, *offset);
X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8);
}
_ => {
return Err("Strings only support being loaded from base offsets"
.to_string());
}
}
general_i += 2;
} else {
return Err(
"calling functions with strings on the stack is not yet implemented"
.to_string(),
);
}
}
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
@ -516,6 +566,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
#[inline(always)] #[inline(always)]
fn load_args<'a>( fn load_args<'a>(
_buf: &mut Vec<'a, u8>,
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>, symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)], args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
@ -535,9 +586,18 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => { Layout::Builtin(single_register_integers!()) => {
symbol_map symbol_map
.insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i])); .insert(*sym, SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]));
i += 1;
} }
Layout::Builtin(single_register_floats!()) => { Layout::Builtin(single_register_floats!()) => {
symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i])); symbol_map.insert(*sym, SymbolStorage::FloatReg(Self::FLOAT_PARAM_REGS[i]));
i += 1;
}
Layout::Builtin(Builtin::Str) => {
// I think this just needs to be passed on the stack, so not a huge deal.
return Err(
"Passing str args with Windows fast call not yet implemented."
.to_string(),
);
} }
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
@ -547,7 +607,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
)); ));
} }
} }
i += 1;
} else { } else {
base_offset += match layout { base_offset += match layout {
Layout::Builtin(single_register_builtins!()) => 8, Layout::Builtin(single_register_builtins!()) => 8,
@ -580,7 +639,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<u32, String> { ) -> Result<u32, String> {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32; let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut reg_i = 0;
// For most return layouts we will do nothing. // For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg. // In some cases, we need to put the return address as the first arg.
match ret_layout { match ret_layout {
@ -597,7 +655,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_integers!()) => { Layout::Builtin(single_register_integers!()) => {
if i < Self::GENERAL_PARAM_REGS.len() { if i < Self::GENERAL_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[reg_i]; let dst = Self::GENERAL_PARAM_REGS[i];
match symbol_map match symbol_map
.get(&args[i]) .get(&args[i])
.ok_or("function argument does not reference any symbol")? .ok_or("function argument does not reference any symbol")?
@ -615,7 +673,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
) )
} }
} }
reg_i += 1;
} else { } else {
// Load the value to the stack. // Load the value to the stack.
match symbol_map match symbol_map
@ -651,7 +708,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
Layout::Builtin(single_register_floats!()) => { Layout::Builtin(single_register_floats!()) => {
if i < Self::FLOAT_PARAM_REGS.len() { if i < Self::FLOAT_PARAM_REGS.len() {
// Load the value to the param reg. // Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[reg_i]; let dst = Self::FLOAT_PARAM_REGS[i];
match symbol_map match symbol_map
.get(&args[i]) .get(&args[i])
.ok_or("function argument does not reference any symbol")? .ok_or("function argument does not reference any symbol")?
@ -668,7 +725,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
return Err("Cannot load general symbol into FloatReg".to_string()) return Err("Cannot load general symbol into FloatReg".to_string())
} }
} }
reg_i += 1;
} else { } else {
// Load the value to the stack. // Load the value to the stack.
match symbol_map match symbol_map
@ -700,6 +756,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Str) => {
// I think this just needs to be passed on the stack, so not a huge deal.
return Err(
"Passing str args with Windows fast call not yet implemented.".to_string(),
);
}
Layout::Struct(&[]) => {} Layout::Struct(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(

View file

@ -93,12 +93,8 @@ where
for (layout, sym) in proc.args { for (layout, sym) in proc.args {
self.set_layout_map(*sym, layout)?; self.set_layout_map(*sym, layout)?;
} }
// let start = std::time::Instant::now();
self.scan_ast(&proc.body); self.scan_ast(&proc.body);
self.create_free_map(); self.create_free_map();
// let duration = start.elapsed();
// println!("Time to calculate lifetimes: {:?}", duration);
// println!("{:?}", self.last_seen_map());
self.build_stmt(&proc.body, &proc.ret_layout)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize() self.finalize()
} }
@ -119,6 +115,11 @@ where
self.free_symbols(stmt)?; self.free_symbols(stmt)?;
Ok(()) Ok(())
} }
Stmt::Refcounting(_modify, following) => {
// TODO: actually deal with refcounting. For hello world, we just skipped it.
self.build_stmt(following, ret_layout)?;
Ok(())
}
Stmt::Switch { Stmt::Switch {
cond_symbol, cond_symbol,
cond_layout, cond_layout,
@ -298,6 +299,13 @@ where
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
Symbol::STR_CONCAT => self.build_run_low_level(
sym,
&LowLevel::StrConcat,
arguments,
arg_layouts,
ret_layout,
),
x if x x if x
.module_string(&self.env().interns) .module_string(&self.env().interns)
.starts_with(ModuleName::APP) => .starts_with(ModuleName::APP) =>
@ -470,6 +478,13 @@ where
arg_layouts, arg_layouts,
ret_layout, ret_layout,
), ),
LowLevel::StrConcat => self.build_fn_call(
sym,
bitcode::STR_CONCAT.to_string(),
args,
arg_layouts,
ret_layout,
),
x => Err(format!("low level, {:?}. is not yet implemented", x)), x => Err(format!("low level, {:?}. is not yet implemented", x)),
} }
} }

View file

@ -1,12 +1,6 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use] #[macro_use]
extern crate indoc; extern crate indoc;
extern crate bumpalo;
extern crate libc;
#[macro_use] #[macro_use]
mod helpers; mod helpers;

View file

@ -0,0 +1,954 @@
// #[macro_use]
// extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod dev_str {
// use roc_std::{RocList, RocStr};
// #[test]
// fn str_split_bigger_delimiter_small_str() {
// assert_evals_to!(
// indoc!(
// r#"
// List.len (Str.split "hello" "JJJJ there")
// "#
// ),
// 1,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// when List.first (Str.split "JJJ" "JJJJ there") is
// Ok str ->
// Str.countGraphemes str
// _ ->
// -1
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn str_split_str_concat_repeated() {
// assert_evals_to!(
// indoc!(
// r#"
// when List.first (Str.split "JJJJJ" "JJJJ there") is
// Ok str ->
// str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
// |> Str.concat str
// _ ->
// "Not Str!"
// "#
// ),
// RocStr::from_slice(b"JJJJJJJJJJJJJJJJJJJJJJJJJ"),
// RocStr
// );
// }
// #[test]
// fn str_split_small_str_bigger_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// when
// List.first
// (Str.split "JJJ" "0123456789abcdefghi")
// is
// Ok str -> str
// _ -> ""
// "#
// ),
// RocStr::from_slice(b"JJJ"),
// RocStr
// );
// }
// #[test]
// fn str_split_big_str_small_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "01234567789abcdefghi?01234567789abcdefghi" "?"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"01234567789abcdefghi"),
// RocStr::from_slice(b"01234567789abcdefghi")
// ]),
// RocList<RocStr>
// );
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "01234567789abcdefghi 3ch 01234567789abcdefghi" "3ch"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"01234567789abcdefghi "),
// RocStr::from_slice(b" 01234567789abcdefghi")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_small_str_small_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "J!J!J" "!"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"J"),
// RocStr::from_slice(b"J"),
// RocStr::from_slice(b"J")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_bigger_delimiter_big_strs() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "string to split is shorter"
// "than the delimiter which happens to be very very long"
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"string to split is shorter")]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_empty_strs() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "" ""
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"")]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_minimal_example() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split "a," ","
// "#
// ),
// RocList::from_slice(&[RocStr::from_slice(b"a"), RocStr::from_slice(b"")]),
// RocList<RocStr>
// )
// }
// #[test]
// fn str_split_small_str_big_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
// "---- ---- ---- ---- ----"
// |> List.len
// "#
// ),
// 3,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "1---- ---- ---- ---- ----2---- ---- ---- ---- ----"
// "---- ---- ---- ---- ----"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"1"),
// RocStr::from_slice(b"2"),
// RocStr::from_slice(b"")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_split_small_str_20_char_delimiter() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.split
// "3|-- -- -- -- -- -- |4|-- -- -- -- -- -- |"
// "|-- -- -- -- -- -- |"
// "#
// ),
// RocList::from_slice(&[
// RocStr::from_slice(b"3"),
// RocStr::from_slice(b"4"),
// RocStr::from_slice(b"")
// ]),
// RocList<RocStr>
// );
// }
// #[test]
// fn str_concat_big_to_big() {
// assert_evals_to!(
// indoc!(
// r#"
// Str.concat
// "First string that is fairly long. Longer strings make for different errors. "
// "Second string that is also fairly long. Two long strings test things that might not appear with short strings."
// "#
// ),
// RocStr::from_slice(b"First string that is fairly long. Longer strings make for different errors. Second string that is also fairly long. Two long strings test things that might not appear with short strings."),
// RocStr
// );
// }
#[test]
fn small_str_literal() {
assert_evals_to!(
"\"JJJJJJJJJJJJJJJ\"",
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_zeroed_literal() {
// // Verifies that we zero out unused bytes in the string.
// // This is important so that string equality tests don't randomly
// // fail due to unused memory being there!
// assert_evals_to!(
// "\"J\"",
// [
// 0x4a,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0x00,
// 0b1000_0001
// ],
// [u8; 16]
// );
// }
#[test]
fn small_str_concat_empty_first_arg() {
assert_evals_to!(
r#"Str.concat "" "JJJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
#[test]
fn small_str_concat_empty_second_arg() {
assert_evals_to!(
r#"Str.concat "JJJJJJJJJJJJJJJ" """#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_concat_small_to_big() {
// assert_evals_to!(
// r#"Str.concat "abc" " this is longer than 15 chars""#,
// RocStr::from_slice(b"abc this is longer than 15 chars"),
// RocStr
// );
// }
#[test]
fn small_str_concat_small_to_small_staying_small() {
assert_evals_to!(
r#"Str.concat "J" "JJJJJJJJJJJJJJ""#,
[
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0x4a,
0b1000_1111
],
[u8; 16]
);
}
// #[test]
// fn small_str_concat_small_to_small_overflow_to_big() {
// assert_evals_to!(
// r#"Str.concat "abcdefghijklm" "nopqrstuvwxyz""#,
// RocStr::from_slice(b"abcdefghijklmnopqrstuvwxyz"),
// RocStr
// );
// }
// #[test]
// fn str_concat_empty() {
// assert_evals_to!(r#"Str.concat "" """#, RocStr::default(), RocStr);
// }
// #[test]
// fn small_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty "abc""#, false, bool);
// }
// #[test]
// fn big_str_is_empty() {
// assert_evals_to!(
// r#"Str.isEmpty "this is more than 15 chars long""#,
// false,
// bool
// );
// }
// #[test]
// fn empty_str_is_empty() {
// assert_evals_to!(r#"Str.isEmpty """#, true, bool);
// }
// #[test]
// fn str_starts_with() {
// assert_evals_to!(r#"Str.startsWith "hello world" "hell""#, true, bool);
// assert_evals_to!(r#"Str.startsWith "hello world" """#, true, bool);
// assert_evals_to!(r#"Str.startsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "hell" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.startsWith "" "hello world""#, false, bool);
// }
// #[test]
// fn str_starts_with_code_point() {
// assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "foobar" {}"#, 'f' as u32),
// true,
// bool
// );
// assert_evals_to!(
// &format!(r#"Str.startsWithCodePt "zoobar" {}"#, 'f' as u32),
// false,
// bool
// );
// }
// #[test]
// fn str_ends_with() {
// assert_evals_to!(r#"Str.endsWith "hello world" "world""#, true, bool);
// assert_evals_to!(r#"Str.endsWith "nope" "hello world""#, false, bool);
// assert_evals_to!(r#"Str.endsWith "" "hello world""#, false, bool);
// }
// #[test]
// fn str_count_graphemes_small_str() {
// assert_evals_to!(r#"Str.countGraphemes "å🤔""#, 2, usize);
// }
// #[test]
// fn str_count_graphemes_three_js() {
// assert_evals_to!(r#"Str.countGraphemes "JJJ""#, 3, usize);
// }
// #[test]
// fn str_count_graphemes_big_str() {
// assert_evals_to!(
// r#"Str.countGraphemes "6🤔å🤔e¥🤔çppkd🙃1jdal🦯asdfa∆ltråø˚waia8918.,🏅jjc""#,
// 45,
// usize
// );
// }
// #[test]
// fn str_starts_with_same_big_str() {
// assert_evals_to!(
// r#"Str.startsWith "123456789123456789" "123456789123456789""#,
// true,
// bool
// );
// }
// #[test]
// fn str_starts_with_different_big_str() {
// assert_evals_to!(
// r#"Str.startsWith "12345678912345678910" "123456789123456789""#,
// true,
// bool
// );
// }
// #[test]
// fn str_starts_with_same_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "1234""#, true, bool);
// }
// #[test]
// fn str_starts_with_different_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "12""#, true, bool);
// }
// #[test]
// fn str_starts_with_false_small_str() {
// assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
// }
// #[test]
// fn str_from_int() {
// assert_evals_to!(
// r#"Str.fromInt 1234"#,
// roc_std::RocStr::from_slice("1234".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt 0"#,
// roc_std::RocStr::from_slice("0".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt -1"#,
// roc_std::RocStr::from_slice("-1".as_bytes()),
// roc_std::RocStr
// );
// let max = format!("{}", i64::MAX);
// assert_evals_to!(
// r#"Str.fromInt Num.maxInt"#,
// RocStr::from_slice(max.as_bytes()),
// RocStr
// );
// let min = format!("{}", i64::MIN);
// assert_evals_to!(
// r#"Str.fromInt Num.minInt"#,
// RocStr::from_slice(min.as_bytes()),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_ascii() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_ascii() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0x7E ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("abc~".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_unicode() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("∆".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_unicode() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xE2, 0x88, 0x86, 0xC5, 0x93, 0xC2, 0xAC ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("∆œ¬".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_grapheme() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_many_grapheme() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 0xF0, 0x9F, 0xA4, 0xA0, 0xF0, 0x9F, 0x9A, 0x80 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖🤠🚀".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_all() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 0xF0, 0x9F, 0x92, 0x96, 98, 0xE2, 0x88, 0x86 ] is
// Ok val -> val
// Err _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("💖b∆".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_invalid_start_byte() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 0x80, 99 ] is
// Err (BadUtf8 InvalidStartByte byteIndex) ->
// if byteIndex == 2 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_unexpected_end_of_sequence() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0xC2 ] is
// Err (BadUtf8 UnexpectedEndOfSequence byteIndex) ->
// if byteIndex == 3 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_expected_continuation() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 99, 0xC2, 0x00 ] is
// Err (BadUtf8 ExpectedContinuation byteIndex) ->
// if byteIndex == 3 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_overlong_encoding() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 0xF0, 0x80, 0x80, 0x80 ] is
// Err (BadUtf8 OverlongEncoding byteIndex) ->
// if byteIndex == 1 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_codepoint_too_large() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 0xF4, 0x90, 0x80, 0x80 ] is
// Err (BadUtf8 CodepointTooLarge byteIndex) ->
// if byteIndex == 1 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_from_utf8_fail_surrogate_half() {
// assert_evals_to!(
// indoc!(
// r#"
// when Str.fromUtf8 [ 97, 98, 0xED, 0xA0, 0x80 ] is
// Err (BadUtf8 EncodesSurrogateHalf byteIndex) ->
// if byteIndex == 2 then
// "a"
// else
// "b"
// _ -> ""
// "#
// ),
// roc_std::RocStr::from_slice("a".as_bytes()),
// roc_std::RocStr
// );
// }
// #[test]
// fn str_equality() {
// assert_evals_to!(r#""a" == "a""#, true, bool);
// assert_evals_to!(
// r#""loremipsumdolarsitamet" == "loremipsumdolarsitamet""#,
// true,
// bool
// );
// assert_evals_to!(r#""a" != "b""#, true, bool);
// assert_evals_to!(r#""a" == "b""#, false, bool);
// }
// #[test]
// fn str_clone() {
// use roc_std::RocStr;
// let long = RocStr::from_slice("loremipsumdolarsitamet".as_bytes());
// let short = RocStr::from_slice("x".as_bytes());
// let empty = RocStr::from_slice("".as_bytes());
// debug_assert_eq!(long.clone(), long);
// debug_assert_eq!(short.clone(), short);
// debug_assert_eq!(empty.clone(), empty);
// }
// #[test]
// fn nested_recursive_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// Expr : [ Add Expr Expr, Val I64, Var I64 ]
// expr : Expr
// expr = Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))
// printExpr : Expr -> Str
// printExpr = \e ->
// when e is
// Add a b ->
// "Add ("
// |> Str.concat (printExpr a)
// |> Str.concat ") ("
// |> Str.concat (printExpr b)
// |> Str.concat ")"
// Val v -> "Val " |> Str.concat (Str.fromInt v)
// Var v -> "Var " |> Str.concat (Str.fromInt v)
// printExpr expr
// "#
// ),
// RocStr::from_slice(b"Add (Add (Val 3) (Val 1)) (Add (Val 1) (Var 1))"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_small() {
// assert_evals_to!(
// r#"Str.joinWith ["1", "2"] ", " "#,
// RocStr::from("1, 2"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_big() {
// assert_evals_to!(
// r#"Str.joinWith ["10000000", "2000000", "30000000"] ", " "#,
// RocStr::from("10000000, 2000000, 30000000"),
// RocStr
// );
// }
// #[test]
// fn str_join_comma_single() {
// assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
// }
// #[test]
// fn str_from_float() {
// assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
// }
// #[test]
// fn str_to_utf8() {
// assert_evals_to!(
// r#"Str.toUtf8 "hello""#,
// RocList::from_slice(&[104, 101, 108, 108, 111]),
// RocList<u8>
// );
// assert_evals_to!(
// r#"Str.toUtf8 "this is a long string""#,
// RocList::from_slice(&[
// 116, 104, 105, 115, 32, 105, 115, 32, 97, 32, 108, 111, 110, 103, 32, 115, 116,
// 114, 105, 110, 103
// ]),
// RocList<u8>
// );
// }
// #[test]
// fn str_from_utf8_range() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 5, start: 0 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("hello"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_slice() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 4, start: 1 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ello"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_slice_not_end() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { count: 3, start: 1 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ell"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_order_does_not_matter() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 1, count: 3 } is
// Ok utf8String -> utf8String
// _ -> ""
// "#
// ),
// RocStr::from("ell"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_out_of_bounds_start_value() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 7, count: 3 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_count_too_high() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 0, count: 6 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_range_count_too_high_for_start() {
// assert_evals_to!(
// indoc!(
// r#"
// bytes = Str.toUtf8 "hello"
// when Str.fromUtf8Range bytes { start: 4, count: 3 } is
// Ok _ -> ""
// Err (BadUtf8 _ _) -> ""
// Err OutOfBounds -> "out of bounds"
// "#
// ),
// RocStr::from("out of bounds"),
// RocStr
// );
// }
}

View file

@ -9,13 +9,14 @@ use crate::llvm::build_dict::{
use crate::llvm::build_hash::generic_hash; use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{ use crate::llvm::build_list::{
self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat, self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
list_contains, list_drop, list_get_unsafe, list_join, list_keep_errs, list_keep_if, list_contains, list_drop, list_drop_at, list_get_unsafe, list_join, list_keep_errs,
list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index, list_prepend, list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index,
list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with, list_swap, list_prepend, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with,
list_swap,
}; };
use crate::llvm::build_str::{ use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int, empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_split, str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split,
str_starts_with, str_starts_with_code_point, str_to_utf8, str_starts_with, str_starts_with_code_point, str_to_utf8,
}; };
use crate::llvm::compare::{generic_eq, generic_neq}; use crate::llvm::compare::{generic_eq, generic_neq};
@ -708,7 +709,7 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
env, env,
main_fn_name, main_fn_name,
roc_main_fn, roc_main_fn,
&[], top_level.arguments,
top_level.result, top_level.result,
main_fn_name, main_fn_name,
); );
@ -934,7 +935,9 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
CallType::LowLevel { op, update_mode } => { CallType::LowLevel { op, update_mode } => {
let bytes = update_mode.to_bytes(); let bytes = update_mode.to_bytes();
let update_var = UpdateModeVar(&bytes); let update_var = UpdateModeVar(&bytes);
let update_mode = func_spec_solutions.update_mode(update_var).ok(); let update_mode = func_spec_solutions
.update_mode(update_var)
.unwrap_or(UpdateMode::Immutable);
run_low_level( run_low_level(
env, env,
@ -2185,7 +2188,10 @@ fn list_literal<'a, 'ctx, 'env>(
let list_length = elems.len(); let list_length = elems.len();
let list_length_intval = env.ptr_int().const_int(list_length as _, false); let list_length_intval = env.ptr_int().const_int(list_length as _, false);
if element_type.is_int_type() { // TODO re-enable, currently causes morphic segfaults because it tries to update
// constants in-place...
// if element_type.is_int_type() {
if false {
let element_type = element_type.into_int_type(); let element_type = element_type.into_int_type();
let element_width = elem_layout.stack_size(env.ptr_bytes); let element_width = elem_layout.stack_size(env.ptr_bytes);
let size = list_length * element_width as usize; let size = list_length * element_width as usize;
@ -2226,17 +2232,18 @@ fn list_literal<'a, 'ctx, 'env>(
} }
ListLiteralElement::Symbol(symbol) => { ListLiteralElement::Symbol(symbol) => {
let val = load_symbol(scope, symbol); let val = load_symbol(scope, symbol);
let intval = val.into_int_value();
if intval.is_const() { // here we'd like to furthermore check for intval.is_const().
global_elements.push(intval); // if all elements are const for LLVM, we could make the array a constant.
} else { // BUT morphic does not know about this, and could allow us to modify that
is_all_constant = false; // array in-place. That would cause a segfault. So, we'll have to find
// constants ourselves and cannot lean on LLVM here.
runtime_evaluated_elements.push((index, val)); is_all_constant = false;
global_elements.push(element_type.get_undef()); runtime_evaluated_elements.push((index, val));
}
global_elements.push(element_type.get_undef());
} }
}; };
} }
@ -3210,6 +3217,141 @@ fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
c_function c_function
} }
fn expose_function_to_host_help_c_abi_gen_test<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
ident_string: &str,
roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>],
c_function_name: &str,
) -> FunctionValue<'ctx> {
let context = env.context;
// a tagged union to indicate to the test loader that a panic occurred.
// especially when running 32-bit binaries on a 64-bit machine, there
// does not seem to be a smarter solution
let wrapper_return_type = context.struct_type(
&[
context.i64_type().into(),
roc_function.get_type().get_return_type().unwrap(),
],
false,
);
let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
for layout in arguments {
cc_argument_types.push(to_cc_type(env, layout));
}
// STEP 1: turn `f : a,b,c -> d` into `f : a,b,c, &d -> {}` if the C abi demands it
let mut argument_types = cc_argument_types;
let return_type = wrapper_return_type;
let c_function_type = {
let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into());
env.context.void_type().fn_type(&argument_types, false)
};
let c_function = add_func(
env.module,
c_function_name,
c_function_type,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(c_function_name);
c_function.set_subprogram(subprogram);
// STEP 2: build the exposed function's body
let builder = env.builder;
let context = env.context;
let entry = context.append_basic_block(c_function, "entry");
builder.position_at_end(entry);
debug_info_init!(env, c_function);
// drop the final argument, which is the pointer we write the result into
let args_vector = c_function.get_params();
let mut args = args_vector.as_slice();
let args_length = args.len();
args = &args[..args.len() - 1];
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
let it = args.iter().zip(roc_function.get_type().get_param_types());
for (arg, fastcc_type) in it {
let arg_type = arg.get_type();
if arg_type == fastcc_type {
// the C and Fast calling conventions agree
arguments_for_call.push(*arg);
} else {
let cast = complex_bitcast_check_size(env, *arg, fastcc_type, "to_fastcc_type");
arguments_for_call.push(cast);
}
}
let arguments_for_call = &arguments_for_call.into_bump_slice();
let call_result = {
let roc_wrapper_function = make_exception_catcher(env, roc_function);
debug_assert_eq!(
arguments_for_call.len(),
roc_wrapper_function.get_params().len()
);
builder.position_at_end(entry);
let call_wrapped = builder.build_call(
roc_wrapper_function,
arguments_for_call,
"call_wrapped_function",
);
call_wrapped.set_call_convention(FAST_CALL_CONV);
call_wrapped.try_as_basic_value().left().unwrap()
};
let output_arg_index = args_length - 1;
let output_arg = c_function
.get_nth_param(output_arg_index as u32)
.unwrap()
.into_pointer_value();
builder.build_store(output_arg, call_result);
builder.build_return(None);
// STEP 3: build a {} -> u64 function that gives the size of the return type
let size_function_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!("roc__{}_size", ident_string);
let size_function = add_func(
env.module,
size_function_name.as_str(),
size_function_type,
Linkage::External,
C_CALL_CONV,
);
let subprogram = env.new_subprogram(&size_function_name);
size_function.set_subprogram(subprogram);
let entry = context.append_basic_block(size_function, "entry");
builder.position_at_end(entry);
debug_info_init!(env, size_function);
let size: BasicValueEnum = return_type.size_of().unwrap().into();
builder.build_return(Some(&size));
c_function
}
fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
ident_string: &str, ident_string: &str,
@ -3220,16 +3362,24 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let context = env.context; let context = env.context;
// a generic version that writes the result into a passed *u8 pointer if env.is_gen_test {
if !env.is_gen_test { return expose_function_to_host_help_c_abi_gen_test(
expose_function_to_host_help_c_abi_generic(
env, env,
ident_string,
roc_function, roc_function,
arguments, arguments,
&format!("{}_generic", c_function_name), c_function_name,
); );
} }
// a generic version that writes the result into a passed *u8 pointer
expose_function_to_host_help_c_abi_generic(
env,
roc_function,
arguments,
&format!("{}_generic", c_function_name),
);
let wrapper_return_type = if env.is_gen_test { let wrapper_return_type = if env.is_gen_test {
context context
.struct_type( .struct_type(
@ -3256,11 +3406,9 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
let cc_return = to_cc_return(env, &return_layout); let cc_return = to_cc_return(env, &return_layout);
let c_function_type = match cc_return { let c_function_type = match cc_return {
CCReturn::Void if !env.is_gen_test => { CCReturn::Void => env.context.void_type().fn_type(&argument_types, false),
env.context.void_type().fn_type(&argument_types, false) CCReturn::Return => return_type.fn_type(&argument_types, false),
} CCReturn::ByPointer => {
CCReturn::Return if !env.is_gen_test => return_type.fn_type(&argument_types, false),
_ => {
let output_type = return_type.ptr_type(AddressSpace::Generic); let output_type = return_type.ptr_type(AddressSpace::Generic);
argument_types.push(output_type.into()); argument_types.push(output_type.into());
env.context.void_type().fn_type(&argument_types, false) env.context.void_type().fn_type(&argument_types, false)
@ -3294,13 +3442,13 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
let args_length = args.len(); let args_length = args.len();
match cc_return { match cc_return {
CCReturn::Return if !env.is_gen_test => { CCReturn::Return => {
debug_assert_eq!(args.len(), roc_function.get_params().len()); debug_assert_eq!(args.len(), roc_function.get_params().len());
} }
CCReturn::Void if !env.is_gen_test => { CCReturn::Void => {
debug_assert_eq!(args.len(), roc_function.get_params().len()); debug_assert_eq!(args.len(), roc_function.get_params().len());
} }
_ => { CCReturn::ByPointer => {
args = &args[..args.len() - 1]; args = &args[..args.len() - 1];
debug_assert_eq!(args.len(), roc_function.get_params().len()); debug_assert_eq!(args.len(), roc_function.get_params().len());
} }
@ -3323,44 +3471,25 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
let arguments_for_call = &arguments_for_call.into_bump_slice(); let arguments_for_call = &arguments_for_call.into_bump_slice();
let call_result = { let call_result = {
if env.is_gen_test { let call_unwrapped =
let roc_wrapper_function = make_exception_catcher(env, roc_function); builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function");
debug_assert_eq!( call_unwrapped.set_call_convention(FAST_CALL_CONV);
arguments_for_call.len(),
roc_wrapper_function.get_params().len()
);
builder.position_at_end(entry); let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
let call_wrapped = builder.build_call( // make_good_roc_result(env, call_unwrapped_result)
roc_wrapper_function, call_unwrapped_result
arguments_for_call,
"call_wrapped_function",
);
call_wrapped.set_call_convention(FAST_CALL_CONV);
call_wrapped.try_as_basic_value().left().unwrap()
} else {
let call_unwrapped =
builder.build_call(roc_function, arguments_for_call, "call_unwrapped_function");
call_unwrapped.set_call_convention(FAST_CALL_CONV);
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
// make_good_roc_result(env, call_unwrapped_result)
call_unwrapped_result
}
}; };
match cc_return { match cc_return {
CCReturn::Void if !env.is_gen_test => { CCReturn::Void => {
// TODO return empty struct here? // TODO return empty struct here?
builder.build_return(None); builder.build_return(None);
} }
CCReturn::Return if !env.is_gen_test => { CCReturn::Return => {
builder.build_return(Some(&call_result)); builder.build_return(Some(&call_result));
} }
_ => { CCReturn::ByPointer => {
let output_arg_index = args_length - 1; let output_arg_index = args_length - 1;
let output_arg = c_function let output_arg = c_function
@ -4105,19 +4234,28 @@ pub fn build_proc<'a, 'ctx, 'env>(
let func_solutions = mod_solutions.func_solutions(func_name).unwrap(); let func_solutions = mod_solutions.func_solutions(func_name).unwrap();
let mut it = func_solutions.specs(); let mut it = func_solutions.specs();
let func_spec = it.next().unwrap(); let evaluator = match it.next() {
debug_assert!( Some(func_spec) => {
it.next().is_none(), debug_assert!(
"we expect only one specialization of this symbol" it.next().is_none(),
); "we expect only one specialization of this symbol"
);
let evaluator = function_value_by_func_spec( function_value_by_func_spec(
env, env,
*func_spec, *func_spec,
symbol, symbol,
top_level.arguments, top_level.arguments,
&top_level.result, &top_level.result,
); )
}
None => {
// morphic did not generate a specialization for this function,
// therefore it must actually be unused.
// An example is our closure callers
panic!("morphic did not specialize {:?}", symbol);
}
};
let ident_string = proc.name.as_str(&env.interns); let ident_string = proc.name.as_str(&env.interns);
let fn_name: String = format!("{}_1", ident_string); let fn_name: String = format!("{}_1", ident_string);
@ -4777,7 +4915,7 @@ fn run_low_level<'a, 'ctx, 'env>(
layout: &Layout<'a>, layout: &Layout<'a>,
op: LowLevel, op: LowLevel,
args: &[Symbol], args: &[Symbol],
update_mode: Option<UpdateMode>, update_mode: UpdateMode,
// expect_failed: *const (), // expect_failed: *const (),
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
use LowLevel::*; use LowLevel::*;
@ -4833,7 +4971,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); let original_wrapper = load_symbol(scope, &args[0]).into_struct_value();
str_from_utf8(env, parent, original_wrapper) str_from_utf8(env, parent, original_wrapper, update_mode)
} }
StrFromUtf8Range => { StrFromUtf8Range => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -4853,6 +4991,12 @@ fn run_low_level<'a, 'ctx, 'env>(
str_to_utf8(env, string.into_struct_value()) str_to_utf8(env, string.into_struct_value())
} }
StrRepeat => {
// Str.repeat : Str, Nat -> Str
debug_assert_eq!(args.len(), 2);
str_repeat(env, scope, args[0], args[1])
}
StrSplit => { StrSplit => {
// Str.split : Str, Str -> List Str // Str.split : Str, Str -> List Str
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -4909,7 +5053,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]); let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
list_reverse(env, list, list_layout) list_reverse(env, list, list_layout, update_mode)
} }
ListConcat => { ListConcat => {
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -4951,7 +5095,7 @@ fn run_low_level<'a, 'ctx, 'env>(
let original_wrapper = load_symbol(scope, &args[0]).into_struct_value(); let original_wrapper = load_symbol(scope, &args[0]).into_struct_value();
let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]); let (elem, elem_layout) = load_symbol_and_layout(scope, &args[1]);
list_append(env, original_wrapper, elem, elem_layout) list_append(env, original_wrapper, elem, elem_layout, update_mode)
} }
ListSwap => { ListSwap => {
// List.swap : List elem, Nat, Nat -> List elem // List.swap : List elem, Nat, Nat -> List elem
@ -4971,6 +5115,7 @@ fn run_low_level<'a, 'ctx, 'env>(
index_1.into_int_value(), index_1.into_int_value(),
index_2.into_int_value(), index_2.into_int_value(),
element_layout, element_layout,
update_mode,
), ),
_ => unreachable!("Invalid layout {:?} in List.swap", list_layout), _ => unreachable!("Invalid layout {:?} in List.swap", list_layout),
} }
@ -4996,6 +5141,27 @@ fn run_low_level<'a, 'ctx, 'env>(
_ => unreachable!("Invalid layout {:?} in List.drop", list_layout), _ => unreachable!("Invalid layout {:?} in List.drop", list_layout),
} }
} }
ListDropAt => {
// List.dropAt : List elem, Nat -> List elem
debug_assert_eq!(args.len(), 2);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);
let original_wrapper = list.into_struct_value();
let count = load_symbol(scope, &args[1]);
match list_layout {
Layout::Builtin(Builtin::EmptyList) => empty_list(env),
Layout::Builtin(Builtin::List(element_layout)) => list_drop_at(
env,
layout_ids,
original_wrapper,
count.into_int_value(),
element_layout,
),
_ => unreachable!("Invalid layout {:?} in List.dropAt", list_layout),
}
}
ListPrepend => { ListPrepend => {
// List.prepend : List elem, elem -> List elem // List.prepend : List elem, elem -> List elem
debug_assert_eq!(args.len(), 2); debug_assert_eq!(args.len(), 2);
@ -5298,7 +5464,7 @@ fn run_low_level<'a, 'ctx, 'env>(
index.into_int_value(), index.into_int_value(),
element, element,
element_layout, element_layout,
update_mode.unwrap(), update_mode,
), ),
_ => unreachable!("invalid dict layout"), _ => unreachable!("invalid dict layout"),
} }
@ -5615,89 +5781,115 @@ fn build_foreign_symbol<'a, 'ctx, 'env>(
let builder = env.builder; let builder = env.builder;
let context = env.context; let context = env.context;
// Here we build two functions: let fastcc_function_name = format!("{}_fastcc_wrapper", foreign.as_str());
//
// - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine`
// This is just a type signature that we make available to the linker,
// and can use in the wrapper
// - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper`
let return_type = basic_type_from_layout(env, ret_layout); let (fastcc_function, arguments) = match env.module.get_function(fastcc_function_name.as_str())
let cc_return = to_cc_return(env, ret_layout); {
Some(function_value) => {
let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena);
let mut cc_argument_types = Vec::with_capacity_in(argument_symbols.len() + 1, env.arena); for symbol in argument_symbols {
let mut fastcc_argument_types = Vec::with_capacity_in(argument_symbols.len(), env.arena); let (value, _) = load_symbol_and_layout(scope, symbol);
let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena);
for symbol in argument_symbols { arguments.push(value);
let (value, layout) = load_symbol_and_layout(scope, symbol); }
cc_argument_types.push(to_cc_type(env, layout)); (function_value, arguments)
}
let basic_type = basic_type_from_layout(env, layout); None => {
fastcc_argument_types.push(basic_type); // Here we build two functions:
//
arguments.push(value); // - an C_CALL_CONV extern that will be provided by the host, e.g. `roc_fx_putLine`
} // This is just a type signature that we make available to the linker,
// and can use in the wrapper
let cc_type = match cc_return { // - a FAST_CALL_CONV wrapper that we make here, e.g. `roc_fx_putLine_fastcc_wrapper`
CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false),
CCReturn::ByPointer => { let return_type = basic_type_from_layout(env, ret_layout);
cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into()); let cc_return = to_cc_return(env, ret_layout);
env.context.void_type().fn_type(&cc_argument_types, false)
let mut cc_argument_types =
Vec::with_capacity_in(argument_symbols.len() + 1, env.arena);
let mut fastcc_argument_types =
Vec::with_capacity_in(argument_symbols.len(), env.arena);
let mut arguments = Vec::with_capacity_in(argument_symbols.len(), env.arena);
for symbol in argument_symbols {
let (value, layout) = load_symbol_and_layout(scope, symbol);
cc_argument_types.push(to_cc_type(env, layout));
let basic_type = basic_type_from_layout(env, layout);
fastcc_argument_types.push(basic_type);
arguments.push(value);
}
let cc_type = match cc_return {
CCReturn::Void => env.context.void_type().fn_type(&cc_argument_types, false),
CCReturn::ByPointer => {
cc_argument_types.push(return_type.ptr_type(AddressSpace::Generic).into());
env.context.void_type().fn_type(&cc_argument_types, false)
}
CCReturn::Return => return_type.fn_type(&cc_argument_types, false),
};
let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type);
let fastcc_type = return_type.fn_type(&fastcc_argument_types, false);
let fastcc_function = add_func(
env.module,
&fastcc_function_name,
fastcc_type,
Linkage::Private,
FAST_CALL_CONV,
);
let old = builder.get_insert_block().unwrap();
let entry = context.append_basic_block(fastcc_function, "entry");
{
builder.position_at_end(entry);
let return_pointer = env.builder.build_alloca(return_type, "return_value");
let fastcc_parameters = fastcc_function.get_params();
let mut cc_arguments =
Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena);
for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter())
{
if param.get_type() == *cc_type {
cc_arguments.push(param);
} else {
let as_cc_type =
complex_bitcast(env.builder, param, *cc_type, "to_cc_type");
cc_arguments.push(as_cc_type);
}
}
if let CCReturn::ByPointer = cc_return {
cc_arguments.push(return_pointer.into());
}
let call = env.builder.build_call(cc_function, &cc_arguments, "tmp");
call.set_call_convention(C_CALL_CONV);
let return_value = match cc_return {
CCReturn::Return => call.try_as_basic_value().left().unwrap(),
CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"),
CCReturn::Void => return_type.const_zero(),
};
builder.build_return(Some(&return_value));
}
builder.position_at_end(old);
(fastcc_function, arguments)
} }
CCReturn::Return => return_type.fn_type(&cc_argument_types, false),
}; };
let cc_function = get_foreign_symbol(env, foreign.clone(), cc_type);
let fastcc_type = return_type.fn_type(&fastcc_argument_types, false);
let fastcc_function = add_func(
env.module,
&format!("{}_fastcc_wrapper", foreign.as_str()),
fastcc_type,
Linkage::Private,
FAST_CALL_CONV,
);
let old = builder.get_insert_block().unwrap();
let entry = context.append_basic_block(fastcc_function, "entry");
{
builder.position_at_end(entry);
let return_pointer = env.builder.build_alloca(return_type, "return_value");
let fastcc_parameters = fastcc_function.get_params();
let mut cc_arguments = Vec::with_capacity_in(fastcc_parameters.len() + 1, env.arena);
for (param, cc_type) in fastcc_parameters.into_iter().zip(cc_argument_types.iter()) {
if param.get_type() == *cc_type {
cc_arguments.push(param);
} else {
let as_cc_type = complex_bitcast(env.builder, param, *cc_type, "to_cc_type");
cc_arguments.push(as_cc_type);
}
}
if let CCReturn::ByPointer = cc_return {
cc_arguments.push(return_pointer.into());
}
let call = env.builder.build_call(cc_function, &cc_arguments, "tmp");
call.set_call_convention(C_CALL_CONV);
let return_value = match cc_return {
CCReturn::Return => call.try_as_basic_value().left().unwrap(),
CCReturn::ByPointer => env.builder.build_load(return_pointer, "read_result"),
CCReturn::Void => return_type.const_zero(),
};
builder.build_return(Some(&return_value));
}
builder.position_at_end(old);
let call = env.builder.build_call(fastcc_function, &arguments, "tmp"); let call = env.builder.build_call(fastcc_function, &arguments, "tmp");
call.set_call_convention(FAST_CALL_CONV); call.set_call_convention(FAST_CALL_CONV);
return call.try_as_basic_value().left().unwrap(); return call.try_as_basic_value().left().unwrap();

View file

@ -17,6 +17,16 @@ use morphic_lib::UpdateMode;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_mono::layout::{Builtin, Layout, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutIds};
pub fn pass_update_mode<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
match update_mode {
UpdateMode::Immutable => env.context.i8_type().const_zero().into(),
UpdateMode::InPlace => env.context.i8_type().const_int(1, false).into(),
}
}
fn list_returned_from_zig<'a, 'ctx, 'env>( fn list_returned_from_zig<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
output: BasicValueEnum<'ctx>, output: BasicValueEnum<'ctx>,
@ -162,6 +172,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
list: BasicValueEnum<'ctx>, list: BasicValueEnum<'ctx>,
list_layout: &Layout<'a>, list_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let element_layout = match *list_layout { let element_layout = match *list_layout {
Layout::Builtin(Builtin::EmptyList) => { Layout::Builtin(Builtin::EmptyList) => {
@ -180,6 +191,7 @@ pub fn list_reverse<'a, 'ctx, 'env>(
pass_list_cc(env, list), pass_list_cc(env, list),
env.alignment_intvalue(&element_layout), env.alignment_intvalue(&element_layout),
layout_width(env, &element_layout), layout_width(env, &element_layout),
pass_update_mode(env, update_mode),
], ],
bitcode::LIST_REVERSE, bitcode::LIST_REVERSE,
) )
@ -228,6 +240,7 @@ pub fn list_append<'a, 'ctx, 'env>(
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
element: BasicValueEnum<'ctx>, element: BasicValueEnum<'ctx>,
element_layout: &Layout<'a>, element_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
call_bitcode_fn_returns_list( call_bitcode_fn_returns_list(
env, env,
@ -236,6 +249,7 @@ pub fn list_append<'a, 'ctx, 'env>(
env.alignment_intvalue(element_layout), env.alignment_intvalue(element_layout),
pass_element_as_opaque(env, element), pass_element_as_opaque(env, element),
layout_width(env, element_layout), layout_width(env, element_layout),
pass_update_mode(env, update_mode),
], ],
bitcode::LIST_APPEND, bitcode::LIST_APPEND,
) )
@ -267,6 +281,7 @@ pub fn list_swap<'a, 'ctx, 'env>(
index_1: IntValue<'ctx>, index_1: IntValue<'ctx>,
index_2: IntValue<'ctx>, index_2: IntValue<'ctx>,
element_layout: &Layout<'a>, element_layout: &Layout<'a>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
call_bitcode_fn_returns_list( call_bitcode_fn_returns_list(
env, env,
@ -276,12 +291,13 @@ pub fn list_swap<'a, 'ctx, 'env>(
layout_width(env, element_layout), layout_width(env, element_layout),
index_1.into(), index_1.into(),
index_2.into(), index_2.into(),
pass_update_mode(env, update_mode),
], ],
bitcode::LIST_SWAP, bitcode::LIST_SWAP,
) )
} }
/// List.drop : List elem, Nat, Nat -> List elem /// List.drop : List elem, Nat -> List elem
pub fn list_drop<'a, 'ctx, 'env>( pub fn list_drop<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>, layout_ids: &mut LayoutIds<'a>,
@ -303,6 +319,28 @@ pub fn list_drop<'a, 'ctx, 'env>(
) )
} }
/// List.dropAt : List elem, Nat -> List elem
pub fn list_drop_at<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
original_wrapper: StructValue<'ctx>,
count: IntValue<'ctx>,
element_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_element_fn = build_dec_wrapper(env, layout_ids, element_layout);
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(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_AT,
)
}
/// List.set : List elem, Nat, elem -> List elem /// List.set : List elem, Nat, elem -> List elem
pub fn list_set<'a, 'ctx, 'env>( pub fn list_set<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,

View file

@ -1,9 +1,12 @@
use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn}; use crate::llvm::bitcode::{call_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::build::{complex_bitcast, Env, Scope}; use crate::llvm::build::{complex_bitcast, Env, Scope};
use crate::llvm::build_list::{allocate_list, call_bitcode_fn_returns_list, store_list}; use crate::llvm::build_list::{
allocate_list, call_bitcode_fn_returns_list, pass_update_mode, store_list,
};
use inkwell::builder::Builder; use inkwell::builder::Builder;
use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue}; use inkwell::values::{BasicValueEnum, FunctionValue, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace; use inkwell::AddressSpace;
use morphic_lib::UpdateMode;
use roc_builtins::bitcode; use roc_builtins::bitcode;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
@ -12,6 +15,18 @@ use super::build::load_symbol;
pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8); pub static CHAR_LAYOUT: Layout = Layout::Builtin(Builtin::Int8);
/// Str.repeat : Str, Nat -> Str
pub fn str_repeat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
count_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_c_abi = str_symbol_to_c_abi(env, scope, str_symbol);
let count = load_symbol(scope, &count_symbol);
call_bitcode_fn(env, &[str_c_abi.into(), count], bitcode::STR_REPEAT)
}
/// Str.split : Str, Str -> List Str /// Str.split : Str, Str -> List Str
pub fn str_split<'a, 'ctx, 'env>( pub fn str_split<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
@ -338,6 +353,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
_parent: FunctionValue<'ctx>, _parent: FunctionValue<'ctx>,
original_wrapper: StructValue<'ctx>, original_wrapper: StructValue<'ctx>,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> { ) -> BasicValueEnum<'ctx> {
let builder = env.builder; let builder = env.builder;
@ -353,6 +369,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>(
env.str_list_c_abi().into(), env.str_list_c_abi().into(),
"to_i128", "to_i128",
), ),
pass_update_mode(env, update_mode),
result_ptr.into(), result_ptr.into(),
], ],
bitcode::STR_FROM_UTF8, bitcode::STR_FROM_UTF8,

View file

@ -3,48 +3,57 @@
## Plan ## Plan
- Initial bringup - Initial bringup
- Get a wasm backend working for some of the number tests. - [x] Get a wasm backend working for some of the number tests.
- Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time. - [x] Use a separate `gen_wasm` directory for now, to avoid trying to do bringup and integration at the same time.
- Improve the fundamentals - Get the fundamentals working
- [x] Come up with a way to do control flow - [x] Come up with a way to do control flow
- [x] Flesh out the details of value representations between local variables and stack memory - [x] Flesh out the details of value representations between local variables and stack memory
- [ ] Set up a way to write tests with any return value rather than just i64 and f64 - [x] Set up a way to write tests with any return value rather than just i64 and f64
- [ ] Figure out relocations for linking object files - [x] Implement stack memory
- [ ] Think about the Wasm module builder library we're using, are we happy with it? - [x] Push and pop stack frames
- [x] Deal with returning structs
- [x] Distinguish which variables go in locals, own stack frame, caller stack frame, etc.
- [ ] Ensure early Return statements don't skip stack cleanup
- [ ] Vendor-in parity_wasm library so that we can use `bumpalo::Vec`
- [ ] Implement relocations
- Requires knowing the _byte_ offset of each call site. This is awkward as the backend builds a `Vec<Instruction>` rather than a `Vec<u8>`. It may be worth serialising each instruction as it is inserted.
- Refactor for code sharing with CPU backends
- [ ] Implement a `scan_ast` pre-pass like `Backend` does, but for reusing Wasm locals rather than CPU registers
- [ ] Extract a trait from `WasmBackend` that looks as similar as possible to `Backend`, to prepare for code sharing
- [ ] Refactor to actually share code between `WasmBackend` and `Backend` if it seems feasible
- Integration - Integration
- Move wasm files to `gen_dev/src/wasm` - Move wasm files to `gen_dev/src/wasm`
- Share tests between wasm and x64, with some way of saying which tests work on which backends, and dispatching to different eval helpers based on that. - Share tests between wasm and x64, with some way of saying which tests work on which backends, and dispatching to different eval helpers based on that.
- Get `build_module` in object_builder.rs to dispatch to the wasm generator (adding some Wasm options to the `Triple` struct) - Get `build_module` in object_builder.rs to dispatch to the wasm generator (adding some Wasm options to the `Triple` struct)
- Get `build_module` to write to a file, or maybe return `Vec<u8>`, instead of returning an Object structure - Get `build_module` to write to a file, or maybe return `Vec<u8>`, instead of returning an Object structure
- Code sharing
- Try to ensure that both Wasm and x64 use the same `Backend` trait so that we can share code.
- We need to work towards this after we've progressed a bit more with Wasm and gained more understanding and experience of the differences.
- We will have to think about how to deal with the `Backend` code that doesn't apply to Wasm. Perhaps we will end up with more traits like `RegisterBackend` / `StackBackend` or `NativeBackend` / `WasmBackend`, and perhaps even some traits to do with backends that support jumps and those that don't.
## Structured control flow ## Structured control flow
🚨 **This is an area that could be tricky** 🚨
One of the security features of WebAssembly is that it does not allow unrestricted "jumps" to anywhere you like. It does not have an instruction for that. All of the [control instructions][control-inst] can only implement "structured" control flow, and have names like `if`, `loop`, `block` that you'd normally associate with high-level languages. There are branch (`br`) instructions that can jump to labelled blocks within the same function, but the blocks have to be nested in sensible ways. One of the security features of WebAssembly is that it does not allow unrestricted "jumps" to anywhere you like. It does not have an instruction for that. All of the [control instructions][control-inst] can only implement "structured" control flow, and have names like `if`, `loop`, `block` that you'd normally associate with high-level languages. There are branch (`br`) instructions that can jump to labelled blocks within the same function, but the blocks have to be nested in sensible ways.
[control-inst]: https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions [control-inst]: https://webassembly.github.io/spec/core/syntax/instructions.html#control-instructions
Implications: This way of representing control flow is similar to parts of the Roc AST like `When`, `If` and `LetRec`. But Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. We need to map back from graph to a tree again in the Wasm backend.
Roc, like most modern languages, is already enforcing structured control flow in the source program. Constructs from the Roc AST like `When`, `If` and `LetRec` can all be converted straightforwardly to Wasm constructs. Our solution is to wrap all joinpoint/jump graphs in an outer `loop`, with nested `block`s inside it.
However the Mono IR converts this to jumps and join points, which are more of a Control Flow Graph than a tree. That doesn't map so directly to the Wasm structures. This is such a common issue for compiler back-ends that the WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. We should probably investigate this area sooner rather than later. If relooping turns out to be necessary or difficult, we might need to switch from parity_wasm to binaryen. ### Possible future optimisations
> By the way, it's not obvious how to pronounce "binaryen" but apparently it rhymes with "Targaryen", the family name from the "Game of Thrones" TV series There are other algorithms available that may result in more optimised control flow. We are not focusing on that for our development backend, but here are some notes for future reference.
The WebAssembly compiler toolkit `binaryen` has an [API for control-flow graphs][cfg-api]. We're not using `binaryen` right now. It's a C++ library, though it does have a (very thin and somewhat hard-to-use) [Rust wrapper][binaryen-rs]. Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf).
> By the way, apparently "binaryen" rhymes with "Targaryen", the family name from the "Game of Thrones" TV series
There is also an improvement on Relooper called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2). It can reorder the joinpoints and jumps to make code more efficient. (It is also has things Roc wouldn't need but C++ does, like support for "irreducible" graphs that include `goto`).
[cfg-api]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api [cfg-api]: https://github.com/WebAssembly/binaryen/wiki/Compiling-to-WebAssembly-with-Binaryen#cfg-api
[binaryen-rs]: https://crates.io/crates/binaryen [binaryen-rs]: https://crates.io/crates/binaryen
Binaryen's control-flow graph API implements the "Relooper" algorithm developed by the Emscripten project and described in [this paper](https://github.com/emscripten-core/emscripten/blob/main/docs/paper.pdf).
There is an alternative algorithm that is supposed to be an improvement on Relooper, called ["Stackifier"](https://medium.com/leaningtech/solving-the-structured-control-flow-problem-once-and-for-all-5123117b1ee2).
## Stack machine vs register machine ## Stack machine vs register machine
Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack. Wasm's instruction set is based on a stack-machine VM. Whereas CPU instructions have named registers that they operate on, Wasm has no named registers at all. The instructions don't contain register names. Instructions can oly operate on whatever data is at the top of the stack.
@ -90,29 +99,30 @@ The Mono IR contains two functions, `Num.add` and `main`, so we generate two cor
(func (;1;) (result i64) ; declare function index 1 (main) with no parameters and an i64 result (func (;1;) (result i64) ; declare function index 1 (main) with no parameters and an i64 result
(local i64 i64 i64 i64) ; declare 4 local variables, all with type i64, one for each symbol in the Mono IR (local i64 i64 i64 i64) ; declare 4 local variables, all with type i64, one for each symbol in the Mono IR
i64.const 1 ; load constant of type i64 and value 1 stack=[1] i64.const 1 ; stack=[1]
local.set 0 ; store top of stack to local0 stack=[] local0=1 local.set 0 ; stack=[] local0=1
i64.const 2 ; load constant of type i64 and value 2 stack=[2] local0=1 i64.const 2 ; stack=[2] local0=1
local.set 1 ; store top of stack to local1 stack=[] local0=1 local1=2 local.set 1 ; stack=[] local0=1 local1=2
local.get 0 ; load local0 to top of stack stack=[1] local0=1 local1=2 local.get 0 ; stack=[1] local0=1 local1=2
local.get 1 ; load local1 to top of stack stack=[1,2] local0=1 local1=2 local.get 1 ; stack=[1,2] local0=1 local1=2
call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[3] local0=1 local1=2 call 0 ; stack=[3] local0=1 local1=2
local.set 2 ; store top of stack to local2 stack=[] local0=1 local1=2 local2=3 local.set 2 ; stack=[] local0=1 local1=2 local2=3
i64.const 4 ; load constant of type i64 and value 4 stack=[4] local0=1 local1=2 local2=3 i64.const 4 ; stack=[4] local0=1 local1=2 local2=3
local.set 3 ; store top of stack to local3 stack=[] local0=1 local1=2 local2=3 local3=4 local.set 3 ; stack=[] local0=1 local1=2 local2=3 local3=4
local.get 2 ; load local2 to top of stack stack=[3] local0=1 local1=2 local2=3 local3=4 local.get 2 ; stack=[3] local0=1 local1=2 local2=3 local3=4
local.get 3 ; load local3 to top of stack stack=[3,4] local0=1 local1=2 local2=3 local3=4 local.get 3 ; stack=[3,4] local0=1 local1=2 local2=3 local3=4
call 0 ; call function index 0 (which pops 2 and pushes 1) stack=[7] local0=1 local1=2 local2=3 local3=4 call 0 ; stack=[7] local0=1 local1=2 local2=3 local3=4
return) ; return the value at the top of the stack return) ; return the value at the top of the stack
``` ```
If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away. The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first). If we run this code through the `wasm-opt` tool from the [binaryen toolkit](https://github.com/WebAssembly/binaryen#tools), the unnecessary locals get optimised away (which is all of them in this example!). The command line below runs the minimum number of passes to achieve this (`--simplify-locals` must come first).
``` ```
$ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm $ wasm-opt --simplify-locals --reorder-locals --vacuum example.wasm > opt.wasm
``` ```
The optimised functions have no local variables, and the code shrinks to about 60% of its original size. The optimised functions have no local variables at all for this example. (Of course, this is an oversimplified toy example! It might not be so extreme in a real program.)
``` ```
(func (;0;) (param i64 i64) (result i64) (func (;0;) (param i64 i64) (result i64)
local.get 0 local.get 0
@ -122,9 +132,20 @@ The optimised functions have no local variables, and the code shrinks to about 6
i64.const 1 i64.const 1
i64.const 2 i64.const 2
call 0 call 0
i64.const 4) i64.const 4
call 0)
``` ```
### Reducing sets and gets
It would be nice to find some cheap optimisation to reduce the number of `local.set` and `local.get` instructions.
We don't need a `local` if the value we want is already at the top of the VM stack. In fact, for our example above, it just so happens that if we simply skip generating the `local.set` instructions, everything _does_ appear on the VM stack in the right order, which means we can skip the `local.get` too. It ends up being very close to the fully optimised version! I assume this is because the Mono IR within the function is in dependency order, but I'm not sure...
Of course the trick is to do this reliably for more complex dependency graphs. I am investigating whether we can do it by optimistically assuming it's OK not to create a local, and then keeping track of which symbols are at which positions in the VM stack after every instruction. Then when we need to use a symbol we can first check if it's on the VM stack and only create a local if it's not. In cases where we _do_ need to create a local, we need to go back and insert a `local.set` instruction at an earlier point in the program. We can make this fast by waiting to do all of the insertions in one batch when we're finalising the procedure.
For a while we thought it would be very helpful to reuse the same local for multiple symbols at different points in the program. And we already have similar code in the CPU backends for register allocation. But on further examination, it doesn't actually buy us much! In our example above, we would still have the same number of `local.set` and `local.get` instructions - they'd just be operating on two locals instead of four! That doesn't shrink much code. Only the declaration at the top of the function would shrink from `(local i64 i64 i64 i64)` to `(local i64 i64)`... and in fact that's only smaller in the text format, it's the same size in the binary format! So the `scan_ast` pass doesn't seem worthwhile for Wasm.
## Memory ## Memory
WebAssembly programs have a "linear memory" for storing data, which is a block of memory assigned to it by the host. You can assign a min and max size to the memory, and the WebAssembly program can request 64kB pages from the host, just like a "normal" program would request pages from the OS. Addresses start at zero and go up to whatever the current size is. Zero is a perfectly normal address like any other, and dereferencing it is not a segfault. But addresses beyond the current memory size are out of bounds and dereferencing them will cause a panic. WebAssembly programs have a "linear memory" for storing data, which is a block of memory assigned to it by the host. You can assign a min and max size to the memory, and the WebAssembly program can request 64kB pages from the host, just like a "normal" program would request pages from the OS. Addresses start at zero and go up to whatever the current size is. Zero is a perfectly normal address like any other, and dereferencing it is not a segfault. But addresses beyond the current memory size are out of bounds and dereferencing them will cause a panic.
@ -143,7 +164,7 @@ When we are talking about how we store values in _memory_, I'll use the term _st
Of course our program can use another area of memory as a heap as well. WebAssembly doesn't mind how you divide up your memory. It just gives you some memory and some instructions for loading and storing. Of course our program can use another area of memory as a heap as well. WebAssembly doesn't mind how you divide up your memory. It just gives you some memory and some instructions for loading and storing.
## Function calls ## Calling conventions & stack memory
In WebAssembly you call a function by pushing arguments to the stack and then issuing a `call` instruction, which specifies a function index. The VM knows how many values to pop off the stack by examining the _type_ of the function. In our example earlier, `Num.add` had the type `[i64 i64] → [i64]` so it expects to find two i64's on the stack and pushes one i64 back as the result. Remember, the runtime engine will validate the module before running it, and if your generated code is trying to call a function at a point in the program where the wrong value types are on the stack, it will fail validation. In WebAssembly you call a function by pushing arguments to the stack and then issuing a `call` instruction, which specifies a function index. The VM knows how many values to pop off the stack by examining the _type_ of the function. In our example earlier, `Num.add` had the type `[i64 i64] → [i64]` so it expects to find two i64's on the stack and pushes one i64 back as the result. Remember, the runtime engine will validate the module before running it, and if your generated code is trying to call a function at a point in the program where the wrong value types are on the stack, it will fail validation.
@ -151,11 +172,17 @@ Function arguments are restricted to the four value types, `i32`, `i64`, `f32` a
That's all great for primitive values but what happens when we want to pass more complex data structures between functions? That's all great for primitive values but what happens when we want to pass more complex data structures between functions?
Well, remember, "stack memory" is not a special kind of memory in WebAssembly, it's just an area of our memory where we _decide_ that we want to implement a stack data structure. So we can implement it however we want. A good choice would be to make our stack frame look the same as it would when we're targeting a CPU, except without the return address (since there's no need for one). We can also decide to pass numbers through the machine stack rather than in stack memory, since that takes fewer instructions. Well, remember, "stack memory" is not a special kind of memory in WebAssembly, and is separate from the VM stack. It's just an area of our memory where we implement a stack data structure. But there are some conventions that it makes sense to follow so that we can easily link to Wasm code generated from Zig or other languages.
The only other thing we need is a stack pointer. On CPU targets, there's often have a specific "stack pointer" register. WebAssembly has no equivalent to that, but we can use a `global` variable. ### Observations from compiled C code
The system I've outlined above is based on my experience of compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals). - `global 0` is used as the stack pointer, and its value is normally copied to a `local` as well (presumably because locals tend to be assigned to CPU registers)
- Stack memory grows downwards
- If a C function returns a struct, the compiled WebAssembly function has no return value, but instead has an extra _argument_. The argument is an `i32` pointer to space allocated in the caller's stack, that the called function can write to.
- There is no maximum number of arguments for a WebAssembly function, and arguments are not passed via _stack memory_. This makes sense because the _VM stack_ has no size limit. It's like having a CPU with an unlimited number of registers.
- Stack memory is only used for allocating local variables, not for passing arguments. And it's only used for values that cannot be stored in one of WebAssembly's primitive values (`i32`, `i64`, `f32`, `f64`).
These observations are based on experiments compiling C to WebAssembly via the Emscripten toolchain (which is built on top of clang). It's also in line with what the WebAssembly project describes [here](https://github.com/WebAssembly/design/blob/main/Rationale.md#locals).
## Modules vs Instances ## Modules vs Instances

View file

@ -1,5 +1,5 @@
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, ModuleBuilder}; use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder, SignatureBuilder};
use parity_wasm::elements::{ use parity_wasm::elements::{
BlockType, Instruction, Instruction::*, Instructions, Local, ValueType, BlockType, Instruction, Instruction::*, Instructions, Local, ValueType,
}; };
@ -8,147 +8,28 @@ use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt}; use roc_mono::ir::{CallType, Expr, JoinPointId, Literal, Proc, Stmt};
use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::layout::{Builtin, Layout};
use crate::*; use crate::layout::WasmLayout;
use crate::storage::{StackMemoryLocation, SymbolStorage};
use crate::{
copy_memory, pop_stack_frame, push_stack_frame, round_up_to_alignment, CopyMemoryConfig,
LocalId, PTR_SIZE, PTR_TYPE,
};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone. // Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
// Follow Emscripten's example by using 1kB (4 bytes would probably do) // Follow Emscripten's example by using 1kB (4 bytes would probably do)
const UNUSED_DATA_SECTION_BYTES: u32 = 1024; const UNUSED_DATA_SECTION_BYTES: u32 = 1024;
#[derive(Clone, Copy, Debug)]
struct LocalId(u32);
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
struct LabelId(u32); struct LabelId(u32);
#[derive(Debug)] enum LocalKind {
struct SymbolStorage(LocalId, WasmLayout); Parameter,
Variable,
// See README for background information on Wasm locals, memory and function calls
#[derive(Debug)]
pub enum WasmLayout {
// Most number types can fit in a Wasm local without any stack memory.
// Roc i8 is represented as an i32 local. Store the type and the original size.
LocalOnly(ValueType, u32),
// A `local` pointing to stack memory
StackMemory(u32),
// A `local` pointing to heap memory
HeapMemory,
}
impl WasmLayout {
fn new(layout: &Layout) -> Self {
use ValueType::*;
let size = layout.stack_size(PTR_SIZE);
match layout {
Layout::Builtin(Builtin::Int128) => Self::StackMemory(size),
Layout::Builtin(Builtin::Int64) => Self::LocalOnly(I64, size),
Layout::Builtin(Builtin::Int32) => Self::LocalOnly(I32, size),
Layout::Builtin(Builtin::Int16) => Self::LocalOnly(I32, size),
Layout::Builtin(Builtin::Int8) => Self::LocalOnly(I32, size),
Layout::Builtin(Builtin::Int1) => Self::LocalOnly(I32, size),
Layout::Builtin(Builtin::Usize) => Self::LocalOnly(I32, size),
Layout::Builtin(Builtin::Decimal) => Self::StackMemory(size),
Layout::Builtin(Builtin::Float128) => Self::StackMemory(size),
Layout::Builtin(Builtin::Float64) => Self::LocalOnly(F64, size),
Layout::Builtin(Builtin::Float32) => Self::LocalOnly(F32, size),
Layout::Builtin(Builtin::Str) => Self::StackMemory(size),
Layout::Builtin(Builtin::Dict(_, _)) => Self::StackMemory(size),
Layout::Builtin(Builtin::Set(_)) => Self::StackMemory(size),
Layout::Builtin(Builtin::List(_)) => Self::StackMemory(size),
Layout::Builtin(Builtin::EmptyStr) => Self::StackMemory(size),
Layout::Builtin(Builtin::EmptyList) => Self::StackMemory(size),
Layout::Builtin(Builtin::EmptyDict) => Self::StackMemory(size),
Layout::Builtin(Builtin::EmptySet) => Self::StackMemory(size),
Layout::LambdaSet(lambda_set) => WasmLayout::new(&lambda_set.runtime_representation()),
Layout::Struct(_) => Self::StackMemory(size),
Layout::Union(UnionLayout::NonRecursive(_)) => Self::StackMemory(size),
Layout::Union(UnionLayout::Recursive(_)) => Self::HeapMemory,
Layout::Union(UnionLayout::NonNullableUnwrapped(_)) => Self::HeapMemory,
Layout::Union(UnionLayout::NullableWrapped { .. }) => Self::HeapMemory,
Layout::Union(UnionLayout::NullableUnwrapped { .. }) => Self::HeapMemory,
Layout::RecursivePointer => Self::HeapMemory,
}
}
fn value_type(&self) -> ValueType {
match self {
Self::LocalOnly(type_, _) => *type_,
_ => PTR_TYPE,
}
}
fn stack_memory(&self) -> u32 {
match self {
Self::StackMemory(size) => *size,
_ => 0,
}
}
#[allow(dead_code)]
fn load(&self, offset: u32) -> Result<Instruction, String> {
use crate::backend::WasmLayout::*;
use ValueType::*;
match self {
LocalOnly(I32, 4) => Ok(I32Load(ALIGN_4, offset)),
LocalOnly(I32, 2) => Ok(I32Load16S(ALIGN_2, offset)),
LocalOnly(I32, 1) => Ok(I32Load8S(ALIGN_1, offset)),
LocalOnly(I64, 8) => Ok(I64Load(ALIGN_8, offset)),
LocalOnly(F64, 8) => Ok(F64Load(ALIGN_8, offset)),
LocalOnly(F32, 4) => Ok(F32Load(ALIGN_4, offset)),
// LocalOnly(F32, 2) => Ok(), // convert F16 to F32 (lowlevel function? Wasm-only?)
// StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR?
HeapMemory => {
if PTR_TYPE == I64 {
Ok(I64Load(ALIGN_8, offset))
} else {
Ok(I32Load(ALIGN_4, offset))
}
}
_ => Err(format!(
"Failed to generate load instruction for WasmLayout {:?}",
self
)),
}
}
#[allow(dead_code)]
fn store(&self, offset: u32) -> Result<Instruction, String> {
use crate::backend::WasmLayout::*;
use ValueType::*;
match self {
LocalOnly(I32, 4) => Ok(I32Store(ALIGN_4, offset)),
LocalOnly(I32, 2) => Ok(I32Store16(ALIGN_2, offset)),
LocalOnly(I32, 1) => Ok(I32Store8(ALIGN_1, offset)),
LocalOnly(I64, 8) => Ok(I64Store(ALIGN_8, offset)),
LocalOnly(F64, 8) => Ok(F64Store(ALIGN_8, offset)),
LocalOnly(F32, 4) => Ok(F32Store(ALIGN_4, offset)),
// LocalOnly(F32, 2) => Ok(), // convert F32 to F16 (lowlevel function? Wasm-only?)
// StackMemory(size) => Ok(), // would this be some kind of memcpy in the IR?
HeapMemory => {
if PTR_TYPE == I64 {
Ok(I64Store(ALIGN_8, offset))
} else {
Ok(I32Store(ALIGN_4, offset))
}
}
_ => Err(format!(
"Failed to generate store instruction for WasmLayout {:?}",
self
)),
}
}
} }
// TODO: use Bumpalo Vec once parity_wasm supports general iterators (>=0.43)
pub struct WasmBackend<'a> { pub struct WasmBackend<'a> {
// Module: Wasm AST // Module: Wasm AST
pub builder: ModuleBuilder, pub builder: ModuleBuilder,
@ -160,12 +41,12 @@ pub struct WasmBackend<'a> {
// Functions: Wasm AST // Functions: Wasm AST
instructions: std::vec::Vec<Instruction>, instructions: std::vec::Vec<Instruction>,
ret_type: ValueType,
arg_types: std::vec::Vec<ValueType>, arg_types: std::vec::Vec<ValueType>,
locals: std::vec::Vec<Local>, locals: std::vec::Vec<Local>,
// Functions: internal state & IR mappings // Functions: internal state & IR mappings
stack_memory: u32, stack_memory: i32,
stack_frame_pointer: Option<LocalId>,
symbol_storage_map: MutMap<Symbol, SymbolStorage>, symbol_storage_map: MutMap<Symbol, SymbolStorage>,
/// how many blocks deep are we (used for jumps) /// how many blocks deep are we (used for jumps)
block_depth: u32, block_depth: u32,
@ -185,12 +66,12 @@ impl<'a> WasmBackend<'a> {
// Functions: Wasm AST // Functions: Wasm AST
instructions: std::vec::Vec::with_capacity(256), instructions: std::vec::Vec::with_capacity(256),
ret_type: ValueType::I32,
arg_types: std::vec::Vec::with_capacity(8), arg_types: std::vec::Vec::with_capacity(8),
locals: std::vec::Vec::with_capacity(32), locals: std::vec::Vec::with_capacity(32),
// Functions: internal state & IR mappings // Functions: internal state & IR mappings
stack_memory: 0, stack_memory: 0,
stack_frame_pointer: None,
symbol_storage_map: MutMap::default(), symbol_storage_map: MutMap::default(),
block_depth: 0, block_depth: 0,
joinpoint_label_map: MutMap::default(), joinpoint_label_map: MutMap::default(),
@ -205,48 +86,18 @@ impl<'a> WasmBackend<'a> {
// Functions: internal state & IR mappings // Functions: internal state & IR mappings
self.stack_memory = 0; self.stack_memory = 0;
self.stack_frame_pointer = None;
self.symbol_storage_map.clear(); self.symbol_storage_map.clear();
// joinpoint_label_map.clear(); self.joinpoint_label_map.clear();
assert_eq!(self.block_depth, 0);
} }
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> { pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
let ret_layout = WasmLayout::new(&proc.ret_layout); let signature_builder = self.start_proc(&proc);
if let WasmLayout::StackMemory { .. } = ret_layout {
return Err(format!(
"Not yet implemented: Returning values to callee stack memory {:?} {:?}",
proc.name, sym
));
}
self.ret_type = ret_layout.value_type();
self.arg_types.reserve(proc.args.len());
for (layout, symbol) in proc.args {
let wasm_layout = WasmLayout::new(layout);
self.arg_types.push(wasm_layout.value_type());
self.insert_local(wasm_layout, *symbol);
}
self.build_stmt(&proc.body, &proc.ret_layout)?; self.build_stmt(&proc.body, &proc.ret_layout)?;
let signature = builder::signature() let function_def = self.finalize_proc(signature_builder);
.with_params(self.arg_types.clone()) // requires std::Vec, not Bumpalo
.with_result(self.ret_type)
.build_sig();
// functions must end with an End instruction/opcode
let mut instructions = self.instructions.clone();
instructions.push(Instruction::End);
let function_def = builder::function()
.with_signature(signature)
.body()
.with_locals(self.locals.clone())
.with_instructions(Instructions::new(instructions))
.build() // body
.build(); // function
let location = self.builder.push_function(function_def); let location = self.builder.push_function(function_def);
let function_index = location.body; let function_index = location.body;
self.proc_symbol_map.insert(sym, location); self.proc_symbol_map.insert(sym, location);
@ -255,48 +106,188 @@ impl<'a> WasmBackend<'a> {
Ok(function_index) Ok(function_index)
} }
fn insert_local(&mut self, layout: WasmLayout, symbol: Symbol) -> LocalId { fn start_proc(&mut self, proc: &Proc<'a>) -> SignatureBuilder {
self.stack_memory += layout.stack_memory(); let ret_layout = WasmLayout::new(&proc.ret_layout);
let index = self.symbol_storage_map.len();
if index >= self.arg_types.len() { let signature_builder = if let WasmLayout::StackMemory { .. } = ret_layout {
self.locals.push(Local::new(1, layout.value_type())); self.arg_types.push(PTR_TYPE);
self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
builder::signature()
} else {
let ret_type = ret_layout.value_type();
self.start_block(BlockType::Value(ret_type)); // block to ensure all paths pop stack memory (if any)
builder::signature().with_result(ret_type)
};
for (layout, symbol) in proc.args {
self.insert_local(WasmLayout::new(layout), *symbol, LocalKind::Parameter);
} }
let local_id = LocalId(index as u32);
let storage = SymbolStorage(local_id, layout); signature_builder.with_params(self.arg_types.clone())
self.symbol_storage_map.insert(symbol, storage);
local_id
} }
fn get_symbol_storage(&self, sym: &Symbol) -> Result<&SymbolStorage, String> { fn finalize_proc(&mut self, signature_builder: SignatureBuilder) -> FunctionDefinition {
self.symbol_storage_map.get(sym).ok_or_else(|| { self.end_block(); // end the block from start_proc, to ensure all paths pop stack memory (if any)
format!(
let mut final_instructions = Vec::with_capacity(self.instructions.len() + 10);
if self.stack_memory > 0 {
push_stack_frame(
&mut final_instructions,
self.stack_memory,
self.stack_frame_pointer.unwrap(),
);
}
final_instructions.extend(self.instructions.drain(0..));
if self.stack_memory > 0 {
pop_stack_frame(
&mut final_instructions,
self.stack_memory,
self.stack_frame_pointer.unwrap(),
);
}
final_instructions.push(End);
builder::function()
.with_signature(signature_builder.build_sig())
.body()
.with_locals(self.locals.clone())
.with_instructions(Instructions::new(final_instructions))
.build() // body
.build() // function
}
fn insert_local(
&mut self,
wasm_layout: WasmLayout,
symbol: Symbol,
kind: LocalKind,
) -> Option<LocalId> {
let next_local_id = LocalId((self.arg_types.len() + self.locals.len()) as u32);
match kind {
LocalKind::Parameter => {
self.arg_types.push(wasm_layout.value_type());
}
LocalKind::Variable => {
self.locals.push(Local::new(1, wasm_layout.value_type()));
}
}
let (maybe_local_id, storage) = match wasm_layout {
WasmLayout::LocalOnly(value_type, size) => (
Some(next_local_id),
SymbolStorage::Local {
local_id: next_local_id,
value_type,
size,
},
),
WasmLayout::HeapMemory => (
Some(next_local_id),
SymbolStorage::Local {
local_id: next_local_id,
value_type: PTR_TYPE,
size: PTR_SIZE,
},
),
WasmLayout::StackMemory {
size,
alignment_bytes,
} => {
let location = match kind {
LocalKind::Parameter => StackMemoryLocation::PointerArg(next_local_id),
LocalKind::Variable => {
match self.stack_frame_pointer {
Some(_) => {}
None => {
self.stack_frame_pointer = Some(next_local_id);
}
};
let offset =
round_up_to_alignment(self.stack_memory, alignment_bytes as i32);
self.stack_memory = offset + size as i32;
StackMemoryLocation::FrameOffset(offset as u32)
}
};
(
None,
SymbolStorage::StackMemory {
location,
size,
alignment_bytes,
},
)
}
};
self.symbol_storage_map.insert(symbol, storage);
maybe_local_id
}
fn get_symbol_storage(&self, sym: &Symbol) -> &SymbolStorage {
self.symbol_storage_map.get(sym).unwrap_or_else(|| {
panic!(
"Symbol {:?} not found in function scope:\n{:?}", "Symbol {:?} not found in function scope:\n{:?}",
sym, self.symbol_storage_map sym, self.symbol_storage_map
) )
}) })
} }
fn load_from_symbol(&mut self, sym: &Symbol) -> Result<(), String> { fn local_id_from_symbol(&self, sym: &Symbol) -> LocalId {
let SymbolStorage(LocalId(local_id), _) = self.get_symbol_storage(sym)?; let storage = self.get_symbol_storage(sym);
let id: u32 = *local_id; match storage {
self.instructions.push(GetLocal(id)); SymbolStorage::Local { local_id, .. } => *local_id,
Ok(()) _ => {
panic!("{:?} does not have a local_id", sym);
}
}
}
/// Load a symbol, e.g. for passing to a function call
fn load_symbol(&mut self, sym: &Symbol) {
let storage = self.get_symbol_storage(sym).to_owned();
match storage {
SymbolStorage::Local { local_id, .. }
| SymbolStorage::StackMemory {
location: StackMemoryLocation::PointerArg(local_id),
..
} => {
self.instructions.push(GetLocal(local_id.0));
}
SymbolStorage::StackMemory {
location: StackMemoryLocation::FrameOffset(offset),
..
} => {
self.instructions.extend([
GetLocal(self.stack_frame_pointer.unwrap().0),
I32Const(offset as i32),
I32Add,
]);
}
}
} }
/// start a loop that leaves a value on the stack /// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) { fn start_loop_with_return(&mut self, value_type: ValueType) {
self.block_depth += 1; self.block_depth += 1;
// self.instructions.push(Loop(BlockType::NoResult));
self.instructions.push(Loop(BlockType::Value(value_type))); self.instructions.push(Loop(BlockType::Value(value_type)));
} }
fn start_block(&mut self) { fn start_block(&mut self, block_type: BlockType) {
self.block_depth += 1; self.block_depth += 1;
self.instructions.push(Block(block_type));
// Our blocks always end with a `return` or `br`,
// so they never leave extra values on the stack
self.instructions.push(Block(BlockType::NoResult));
} }
fn end_block(&mut self) { fn end_block(&mut self) {
@ -306,36 +297,80 @@ impl<'a> WasmBackend<'a> {
fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> { fn build_stmt(&mut self, stmt: &Stmt<'a>, ret_layout: &Layout<'a>) -> Result<(), String> {
match stmt { match stmt {
// This pattern is a simple optimisation to get rid of one local and two instructions per proc. // Simple optimisation: if we are just returning the expression, we don't need a local
// If we are just returning the expression result, then don't SetLocal and immediately GetLocal
Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => { Stmt::Let(let_sym, expr, layout, Stmt::Ret(ret_sym)) if let_sym == ret_sym => {
let wasm_layout = WasmLayout::new(layout);
if let WasmLayout::StackMemory {
size,
alignment_bytes,
} = wasm_layout
{
// Map this symbol to the first argument (pointer into caller's stack)
// Saves us from having to copy it later
let storage = SymbolStorage::StackMemory {
location: StackMemoryLocation::PointerArg(LocalId(0)),
size,
alignment_bytes,
};
self.symbol_storage_map.insert(*let_sym, storage);
}
self.build_expr(let_sym, expr, layout)?; self.build_expr(let_sym, expr, layout)?;
self.instructions.push(Return); self.instructions.push(Br(self.block_depth)); // jump to end of function (stack frame pop)
Ok(()) Ok(())
} }
Stmt::Let(sym, expr, layout, following) => { Stmt::Let(sym, expr, layout, following) => {
let wasm_layout = WasmLayout::new(layout); let wasm_layout = WasmLayout::new(layout);
let local_id = self.insert_local(wasm_layout, *sym); let maybe_local_id = self.insert_local(wasm_layout, *sym, LocalKind::Variable);
self.build_expr(sym, expr, layout)?; self.build_expr(sym, expr, layout)?;
self.instructions.push(SetLocal(local_id.0));
if let Some(local_id) = maybe_local_id {
self.instructions.push(SetLocal(local_id.0));
}
self.build_stmt(following, ret_layout)?; self.build_stmt(following, ret_layout)?;
Ok(()) Ok(())
} }
Stmt::Ret(sym) => { Stmt::Ret(sym) => {
if let Some(SymbolStorage(local_id, _)) = self.symbol_storage_map.get(sym) { use crate::storage::SymbolStorage::*;
self.instructions.push(GetLocal(local_id.0));
self.instructions.push(Return); let storage = self.symbol_storage_map.get(sym).unwrap();
Ok(())
} else { match storage {
Err(format!( StackMemory {
"Not yet implemented: returning values with layout {:?}", location,
ret_layout size,
)) alignment_bytes,
} => {
let (from_ptr, from_offset) = match location {
StackMemoryLocation::PointerArg(local_id) => (*local_id, 0),
StackMemoryLocation::FrameOffset(offset) => {
(self.stack_frame_pointer.unwrap(), *offset)
}
};
copy_memory(
&mut self.instructions,
CopyMemoryConfig {
from_ptr,
from_offset,
to_ptr: LocalId(0),
to_offset: 0,
size: *size,
alignment_bytes: *alignment_bytes,
},
);
}
Local { local_id, .. } => {
self.instructions.push(GetLocal(local_id.0));
self.instructions.push(Br(self.block_depth)); // jump to end of function (for stack frame pop)
}
} }
Ok(())
} }
Stmt::Switch { Stmt::Switch {
@ -351,19 +386,16 @@ impl<'a> WasmBackend<'a> {
// create (number_of_branches - 1) new blocks. // create (number_of_branches - 1) new blocks.
for _ in 0..branches.len() { for _ in 0..branches.len() {
self.start_block() self.start_block(BlockType::NoResult)
} }
// the LocalId of the symbol that we match on // the LocalId of the symbol that we match on
let matched_on = match self.symbol_storage_map.get(cond_symbol) { let matched_on = self.local_id_from_symbol(cond_symbol);
Some(SymbolStorage(local_id, _)) => local_id.0,
None => unreachable!("symbol not defined: {:?}", cond_symbol),
};
// then, we jump whenever the value under scrutiny is equal to the value of a branch // then, we jump whenever the value under scrutiny is equal to the value of a branch
for (i, (value, _, _)) in branches.iter().enumerate() { for (i, (value, _, _)) in branches.iter().enumerate() {
// put the cond_symbol on the top of the stack // put the cond_symbol on the top of the stack
self.instructions.push(GetLocal(matched_on)); self.instructions.push(GetLocal(matched_on.0));
self.instructions.push(I32Const(*value as i32)); self.instructions.push(I32Const(*value as i32));
@ -398,12 +430,13 @@ impl<'a> WasmBackend<'a> {
let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len()); let mut jp_parameter_local_ids = std::vec::Vec::with_capacity(parameters.len());
for parameter in parameters.iter() { for parameter in parameters.iter() {
let wasm_layout = WasmLayout::new(&parameter.layout); let wasm_layout = WasmLayout::new(&parameter.layout);
let local_id = self.insert_local(wasm_layout, parameter.symbol); let maybe_local_id =
self.insert_local(wasm_layout, parameter.symbol, LocalKind::Variable);
jp_parameter_local_ids.push(local_id); let jp_param_id = maybe_local_id.unwrap();
jp_parameter_local_ids.push(jp_param_id);
} }
self.start_block(); self.start_block(BlockType::NoResult);
self.joinpoint_label_map self.joinpoint_label_map
.insert(*id, (self.block_depth, jp_parameter_local_ids)); .insert(*id, (self.block_depth, jp_parameter_local_ids));
@ -429,12 +462,8 @@ impl<'a> WasmBackend<'a> {
// put the arguments on the stack // put the arguments on the stack
for (symbol, local_id) in arguments.iter().zip(locals.iter()) { for (symbol, local_id) in arguments.iter().zip(locals.iter()) {
let argument = match self.symbol_storage_map.get(symbol) { let argument = self.local_id_from_symbol(symbol);
Some(SymbolStorage(local_id, _)) => local_id.0, self.instructions.push(GetLocal(argument.0));
None => unreachable!("symbol not defined: {:?}", symbol),
};
self.instructions.push(GetLocal(argument));
self.instructions.push(SetLocal(local_id.0)); self.instructions.push(SetLocal(local_id.0));
} }
@ -463,7 +492,7 @@ impl<'a> WasmBackend<'a> {
}) => match call_type { }) => match call_type {
CallType::ByName { name: func_sym, .. } => { CallType::ByName { name: func_sym, .. } => {
for arg in *arguments { for arg in *arguments {
self.load_from_symbol(arg)?; self.load_symbol(arg);
} }
let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!( let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!(
"Cannot find function {:?} called from {:?}", "Cannot find function {:?} called from {:?}",
@ -479,46 +508,102 @@ impl<'a> WasmBackend<'a> {
x => Err(format!("the call type, {:?}, is not yet implemented", x)), x => Err(format!("the call type, {:?}, is not yet implemented", x)),
}, },
Expr::Struct(fields) => self.create_struct(sym, layout, fields),
x => Err(format!("Expression is not yet implemented {:?}", x)), x => Err(format!("Expression is not yet implemented {:?}", x)),
} }
} }
fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> { fn load_literal(&mut self, lit: &Literal<'a>, layout: &Layout<'a>) -> Result<(), String> {
match lit { let instruction = match lit {
Literal::Bool(x) => { Literal::Bool(x) => I32Const(*x as i32),
self.instructions.push(I32Const(*x as i32)); Literal::Byte(x) => I32Const(*x as i32),
Ok(()) Literal::Int(x) => match layout {
Layout::Builtin(Builtin::Int64) => I64Const(*x as i64),
Layout::Builtin(
Builtin::Int32
| Builtin::Int16
| Builtin::Int8
| Builtin::Int1
| Builtin::Usize,
) => I32Const(*x as i32),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
},
Literal::Float(x) => match layout {
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()),
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()),
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
}
},
x => {
return Err(format!("loading literal, {:?}, is not yet implemented", x));
} }
Literal::Byte(x) => { };
self.instructions.push(I32Const(*x as i32)); self.instructions.push(instruction);
Ok(()) Ok(())
} }
Literal::Int(x) => {
let instruction = match layout { fn create_struct(
Layout::Builtin(Builtin::Int64) => I64Const(*x as i64), &mut self,
Layout::Builtin( sym: &Symbol,
Builtin::Int32 layout: &Layout<'a>,
| Builtin::Int16 fields: &'a [Symbol],
| Builtin::Int8 ) -> Result<(), String> {
| Builtin::Int1 let storage = self.get_symbol_storage(sym).to_owned();
| Builtin::Usize,
) => I32Const(*x as i32), if let Layout::Struct(field_layouts) = layout {
x => panic!("loading literal, {:?}, is not yet implemented", x), match storage {
}; SymbolStorage::StackMemory { location, size, .. } => {
self.instructions.push(instruction); if size > 0 {
Ok(()) let (local_id, struct_offset) =
} location.local_and_offset(self.stack_frame_pointer);
Literal::Float(x) => { let mut field_offset = struct_offset;
let instruction = match layout { for (field, _) in fields.iter().zip(field_layouts.iter()) {
Layout::Builtin(Builtin::Float64) => F64Const((*x as f64).to_bits()), field_offset += self.copy_symbol_to_pointer_at_offset(
Layout::Builtin(Builtin::Float32) => F32Const((*x as f32).to_bits()), local_id,
x => panic!("loading literal, {:?}, is not yet implemented", x), field_offset,
}; field,
self.instructions.push(instruction); );
Ok(()) }
} } else {
x => Err(format!("loading literal, {:?}, is not yet implemented", x)), return Err(format!("Not supported yet: zero-size struct at {:?}", sym));
}
}
_ => {
return Err(format!(
"Cannot create struct {:?} with storage {:?}",
sym, storage
));
}
};
} else {
// Struct expression but not Struct layout => single element. Copy it.
let field_storage = self.get_symbol_storage(&fields[0]).to_owned();
storage.copy_from(
&field_storage,
&mut self.instructions,
self.stack_frame_pointer,
);
} }
Ok(())
}
fn copy_symbol_to_pointer_at_offset(
&mut self,
to_ptr: LocalId,
to_offset: u32,
from_symbol: &Symbol,
) -> u32 {
let from_storage = self.get_symbol_storage(from_symbol).to_owned();
from_storage.copy_to_memory(
&mut self.instructions,
to_ptr,
to_offset,
self.stack_frame_pointer,
)
} }
fn build_call_low_level( fn build_call_low_level(
@ -528,7 +613,7 @@ impl<'a> WasmBackend<'a> {
return_layout: &Layout<'a>, return_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
for arg in args { for arg in args {
self.load_from_symbol(arg)?; self.load_symbol(arg);
} }
let wasm_layout = WasmLayout::new(return_layout); let wasm_layout = WasmLayout::new(return_layout);
self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?; self.build_instructions_lowlevel(lowlevel, wasm_layout.value_type())?;
@ -546,7 +631,7 @@ impl<'a> WasmBackend<'a> {
// For those, we'll need to pre-process each argument before the main op, // For those, we'll need to pre-process each argument before the main op,
// so simple arrays of instructions won't work. But there are common patterns. // so simple arrays of instructions won't work. But there are common patterns.
let instructions: &[Instruction] = match lowlevel { let instructions: &[Instruction] = match lowlevel {
// Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_from_symbol? // Wasm type might not be enough, may need to sign-extend i8 etc. Maybe in load_symbol?
LowLevel::NumAdd => match return_value_type { LowLevel::NumAdd => match return_value_type {
ValueType::I32 => &[I32Add], ValueType::I32 => &[I32Add],
ValueType::I64 => &[I64Add], ValueType::I64 => &[I64Add],

View file

@ -0,0 +1,82 @@
use parity_wasm::elements::ValueType;
use roc_mono::layout::{Layout, UnionLayout};
use crate::{PTR_SIZE, PTR_TYPE};
// See README for background information on Wasm locals, memory and function calls
#[derive(Debug, Clone)]
pub enum WasmLayout {
// Primitive number value. Just a Wasm local, without any stack memory.
// For example, Roc i8 is represented as Wasm i32. Store the type and the original size.
LocalOnly(ValueType, u32),
// Local pointer to stack memory
StackMemory { size: u32, alignment_bytes: u32 },
// Local pointer to heap memory
HeapMemory,
}
impl WasmLayout {
pub fn new(layout: &Layout) -> Self {
use roc_mono::layout::Builtin::*;
use UnionLayout::*;
use ValueType::*;
let size = layout.stack_size(PTR_SIZE);
let alignment_bytes = layout.alignment_bytes(PTR_SIZE);
match layout {
Layout::Builtin(Int32 | Int16 | Int8 | Int1 | Usize) => Self::LocalOnly(I32, size),
Layout::Builtin(Int64) => Self::LocalOnly(I64, size),
Layout::Builtin(Float32) => Self::LocalOnly(F32, size),
Layout::Builtin(Float64) => Self::LocalOnly(F64, size),
Layout::Builtin(
Int128
| Decimal
| Float128
| Str
| Dict(_, _)
| Set(_)
| List(_)
| EmptyStr
| EmptyList
| EmptyDict
| EmptySet,
)
| Layout::Struct(_)
| Layout::LambdaSet(_)
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
size,
alignment_bytes,
},
Layout::Union(
Recursive(_)
| NonNullableUnwrapped(_)
| NullableWrapped { .. }
| NullableUnwrapped { .. },
)
| Layout::RecursivePointer => Self::HeapMemory,
}
}
pub fn value_type(&self) -> ValueType {
match self {
Self::LocalOnly(type_, _) => *type_,
_ => PTR_TYPE,
}
}
#[allow(dead_code)]
pub fn stack_memory(&self) -> u32 {
match self {
Self::StackMemory { size, .. } => *size,
_ => 0,
}
}
}

View file

@ -1,9 +1,11 @@
mod backend; mod backend;
pub mod from_wasm32_memory; pub mod from_wasm32_memory;
mod layout;
mod storage;
use bumpalo::Bump; use bumpalo::Bump;
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::elements::{Instruction, Internal, ValueType}; use parity_wasm::elements::{Instruction, Instruction::*, Internal, ValueType};
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
@ -22,6 +24,10 @@ pub const ALIGN_4: u32 = 2;
pub const ALIGN_8: u32 = 3; pub const ALIGN_8: u32 = 3;
pub const STACK_POINTER_GLOBAL_ID: u32 = 0; pub const STACK_POINTER_GLOBAL_ID: u32 = 0;
pub const STACK_ALIGNMENT_BYTES: i32 = 16;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct LocalId(pub u32);
pub struct Env<'a> { pub struct Env<'a> {
pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot pub arena: &'a Bump, // not really using this much, parity_wasm works with std::vec a lot
@ -104,3 +110,85 @@ pub fn build_module_help<'a>(
Ok((backend.builder, main_function_index)) Ok((backend.builder, main_function_index))
} }
fn encode_alignment(bytes: u32) -> u32 {
match bytes {
1 => ALIGN_1,
2 => ALIGN_2,
4 => ALIGN_4,
8 => ALIGN_8,
_ => panic!("{:?}-byte alignment is not supported", bytes),
}
}
pub struct CopyMemoryConfig {
from_ptr: LocalId,
from_offset: u32,
to_ptr: LocalId,
to_offset: u32,
size: u32,
alignment_bytes: u32,
}
pub fn copy_memory(instructions: &mut Vec<Instruction>, config: CopyMemoryConfig) {
let alignment_flag = encode_alignment(config.alignment_bytes);
let mut i = 0;
while config.size - i >= 8 {
instructions.push(GetLocal(config.to_ptr.0));
instructions.push(GetLocal(config.from_ptr.0));
instructions.push(I64Load(alignment_flag, i + config.from_offset));
instructions.push(I64Store(alignment_flag, i + config.to_offset));
i += 8;
}
if config.size - i >= 4 {
instructions.push(GetLocal(config.to_ptr.0));
instructions.push(GetLocal(config.from_ptr.0));
instructions.push(I32Load(alignment_flag, i + config.from_offset));
instructions.push(I32Store(alignment_flag, i + config.to_offset));
i += 4;
}
while config.size - i > 0 {
instructions.push(GetLocal(config.to_ptr.0));
instructions.push(GetLocal(config.from_ptr.0));
instructions.push(I32Load8U(alignment_flag, i + config.from_offset));
instructions.push(I32Store8(alignment_flag, i + config.to_offset));
i += 1;
}
}
/// Round up to alignment_bytes (assumed to be a power of 2)
pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
let mut aligned = unaligned;
aligned += alignment_bytes - 1; // if lower bits are non-zero, push it over the next boundary
aligned &= -alignment_bytes; // mask with a flag that has upper bits 1, lower bits 0
aligned
}
pub fn push_stack_frame(
instructions: &mut Vec<Instruction>,
size: i32,
local_frame_pointer: LocalId,
) {
let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES);
instructions.extend([
GetGlobal(STACK_POINTER_GLOBAL_ID),
I32Const(aligned_size),
I32Sub,
TeeLocal(local_frame_pointer.0),
SetGlobal(STACK_POINTER_GLOBAL_ID),
]);
}
pub fn pop_stack_frame(
instructions: &mut Vec<Instruction>,
size: i32,
local_frame_pointer: LocalId,
) {
let aligned_size = round_up_to_alignment(size, STACK_ALIGNMENT_BYTES);
instructions.extend([
GetLocal(local_frame_pointer.0),
I32Const(aligned_size),
I32Add,
SetGlobal(STACK_POINTER_GLOBAL_ID),
]);
}

View file

@ -0,0 +1,151 @@
use crate::{copy_memory, CopyMemoryConfig, LocalId, ALIGN_1, ALIGN_2, ALIGN_4, ALIGN_8};
use parity_wasm::elements::{Instruction, Instruction::*, ValueType};
#[derive(Debug, Clone)]
pub enum StackMemoryLocation {
FrameOffset(u32),
PointerArg(LocalId),
}
impl StackMemoryLocation {
pub fn local_and_offset(&self, stack_frame_pointer: Option<LocalId>) -> (LocalId, u32) {
match self {
Self::PointerArg(local_id) => (*local_id, 0),
Self::FrameOffset(offset) => (stack_frame_pointer.unwrap(), *offset),
}
}
}
#[derive(Debug, Clone)]
pub enum SymbolStorage {
// TODO: implicit storage in the VM stack
// TODO: const data storage
Local {
local_id: LocalId,
value_type: ValueType,
size: u32,
},
StackMemory {
location: StackMemoryLocation,
size: u32,
alignment_bytes: u32,
},
}
impl SymbolStorage {
/// generate code to copy from another storage of the same type
pub fn copy_from(
&self,
from: &Self,
instructions: &mut Vec<Instruction>,
stack_frame_pointer: Option<LocalId>,
) {
match (self, from) {
(
Self::Local {
local_id: to_local_id,
value_type: to_value_type,
size: to_size,
},
Self::Local {
local_id: from_local_id,
value_type: from_value_type,
size: from_size,
},
) => {
debug_assert!(to_value_type == from_value_type);
debug_assert!(to_size == from_size);
instructions.push(GetLocal(from_local_id.0));
instructions.push(SetLocal(to_local_id.0));
}
(
Self::StackMemory {
location: to_location,
size: to_size,
alignment_bytes: to_alignment_bytes,
},
Self::StackMemory {
location: from_location,
size: from_size,
alignment_bytes: from_alignment_bytes,
},
) => {
let (from_ptr, from_offset) = from_location.local_and_offset(stack_frame_pointer);
let (to_ptr, to_offset) = to_location.local_and_offset(stack_frame_pointer);
debug_assert!(*to_size == *from_size);
debug_assert!(*to_alignment_bytes == *from_alignment_bytes);
copy_memory(
instructions,
CopyMemoryConfig {
from_ptr,
from_offset,
to_ptr,
to_offset,
size: *from_size,
alignment_bytes: *from_alignment_bytes,
},
);
}
_ => {
panic!(
"Cannot copy different storage types {:?} to {:?}",
from, self
);
}
}
}
/// Generate code to copy to a memory address (such as a struct index)
pub fn copy_to_memory(
&self,
instructions: &mut Vec<Instruction>,
to_ptr: LocalId,
to_offset: u32,
stack_frame_pointer: Option<LocalId>,
) -> u32 {
match self {
Self::Local {
local_id,
value_type,
size,
..
} => {
let store_instruction = match (value_type, size) {
(ValueType::I64, 8) => I64Store(ALIGN_8, to_offset),
(ValueType::I32, 4) => I32Store(ALIGN_4, to_offset),
(ValueType::I32, 2) => I32Store16(ALIGN_2, to_offset),
(ValueType::I32, 1) => I32Store8(ALIGN_1, to_offset),
(ValueType::F32, 4) => F32Store(ALIGN_4, to_offset),
(ValueType::F64, 8) => F64Store(ALIGN_8, to_offset),
_ => {
panic!("Cannot store {:?} with alignment of {:?}", value_type, size);
}
};
instructions.push(GetLocal(to_ptr.0));
instructions.push(GetLocal(local_id.0));
instructions.push(store_instruction);
*size
}
Self::StackMemory {
location,
size,
alignment_bytes,
} => {
let (from_ptr, from_offset) = location.local_and_offset(stack_frame_pointer);
copy_memory(
instructions,
CopyMemoryConfig {
from_ptr,
from_offset,
to_ptr,
to_offset,
size: *size,
alignment_bytes: *alignment_bytes,
},
);
*size
}
}
}
}

View file

@ -1,11 +1,15 @@
use parity_wasm::builder; use parity_wasm::builder;
use parity_wasm::builder::ModuleBuilder; use parity_wasm::builder::ModuleBuilder;
use parity_wasm::elements::{Instruction, Instruction::*, Instructions, Internal, ValueType}; use parity_wasm::elements::{
Instruction, Instruction::*, Instructions, Internal, Local, ValueType,
};
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory; use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
use roc_gen_wasm::*; use roc_gen_wasm::*;
use roc_std::{RocDec, RocList, RocOrder, RocStr}; use roc_std::{RocDec, RocList, RocOrder, RocStr};
const STACK_POINTER_LOCAL_ID: u32 = 0;
pub trait Wasm32TestResult { pub trait Wasm32TestResult {
fn insert_test_wrapper( fn insert_test_wrapper(
module_builder: &mut ModuleBuilder, module_builder: &mut ModuleBuilder,
@ -16,9 +20,11 @@ pub trait Wasm32TestResult {
let signature = builder::signature().with_result(ValueType::I32).build_sig(); let signature = builder::signature().with_result(ValueType::I32).build_sig();
let stack_frame_pointer = Local::new(1, ValueType::I32);
let function_def = builder::function() let function_def = builder::function()
.with_signature(signature) .with_signature(signature)
.body() .body()
.with_locals(vec![stack_frame_pointer])
.with_instructions(Instructions::new(instructions)) .with_instructions(Instructions::new(instructions))
.build() // body .build() // body
.build(); // function .build(); // function
@ -35,22 +41,15 @@ pub trait Wasm32TestResult {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>; fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction>;
} }
fn build_wrapper_body_prelude(stack_memory_size: usize) -> Vec<Instruction> {
vec![
GetGlobal(STACK_POINTER_GLOBAL_ID),
I32Const(stack_memory_size as i32),
I32Sub,
SetGlobal(STACK_POINTER_GLOBAL_ID),
]
}
macro_rules! build_wrapper_body_primitive { macro_rules! build_wrapper_body_primitive {
($store_instruction: expr, $align: expr) => { ($store_instruction: expr, $align: expr) => {
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> { fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
const MAX_ALIGNED_SIZE: usize = 16; let size: i32 = 8;
let mut instructions = build_wrapper_body_prelude(MAX_ALIGNED_SIZE); let mut instructions = Vec::with_capacity(16);
push_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.extend([ instructions.extend([
GetGlobal(STACK_POINTER_GLOBAL_ID), // load result address to prepare for the store instruction later
GetLocal(STACK_POINTER_LOCAL_ID),
// //
// Call the main function with no arguments. Get primitive back. // Call the main function with no arguments. Get primitive back.
Call(main_function_index), Call(main_function_index),
@ -59,9 +58,10 @@ macro_rules! build_wrapper_body_primitive {
$store_instruction($align, 0), $store_instruction($align, 0),
// //
// Return the result pointer // Return the result pointer
GetGlobal(STACK_POINTER_GLOBAL_ID), GetLocal(STACK_POINTER_LOCAL_ID),
End,
]); ]);
pop_stack_frame(&mut instructions, size, LocalId(STACK_POINTER_LOCAL_ID));
instructions.push(End);
instructions instructions
} }
}; };
@ -76,18 +76,28 @@ macro_rules! wasm_test_result_primitive {
} }
fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> { fn build_wrapper_body_stack_memory(main_function_index: u32, size: usize) -> Vec<Instruction> {
let mut instructions = build_wrapper_body_prelude(size); let mut instructions = Vec::with_capacity(16);
push_stack_frame(
&mut instructions,
size as i32,
LocalId(STACK_POINTER_LOCAL_ID),
);
instructions.extend([ instructions.extend([
// //
// Call the main function with the allocated address to write the result. // Call the main function with the allocated address to write the result.
// No value is returned to the VM stack. This is the same as in compiled C. // No value is returned to the VM stack. This is the same as in compiled C.
GetGlobal(STACK_POINTER_GLOBAL_ID), GetLocal(STACK_POINTER_LOCAL_ID),
Call(main_function_index), Call(main_function_index),
// //
// Return the result address // Return the result address
GetGlobal(STACK_POINTER_GLOBAL_ID), GetLocal(STACK_POINTER_LOCAL_ID),
End,
]); ]);
pop_stack_frame(
&mut instructions,
size as i32,
LocalId(STACK_POINTER_LOCAL_ID),
);
instructions.push(End);
instructions instructions
} }
@ -163,3 +173,106 @@ where
) )
} }
} }
impl<T, U, V, W> Wasm32TestResult for (T, U, V, W)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X> Wasm32TestResult for (T, U, V, W, X)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH + U::ACTUAL_WIDTH + V::ACTUAL_WIDTH + W::ACTUAL_WIDTH + X::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X, Y> Wasm32TestResult for (T, U, V, W, X, Y)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
Y: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH
+ V::ACTUAL_WIDTH
+ W::ACTUAL_WIDTH
+ X::ACTUAL_WIDTH
+ Y::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X, Y, Z> Wasm32TestResult for (T, U, V, W, X, Y, Z)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
Y: Wasm32TestResult + FromWasm32Memory,
Z: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH
+ V::ACTUAL_WIDTH
+ W::ACTUAL_WIDTH
+ X::ACTUAL_WIDTH
+ Y::ACTUAL_WIDTH
+ Z::ACTUAL_WIDTH,
)
}
}
impl<T, U, V, W, X, Y, Z, A> Wasm32TestResult for (T, U, V, W, X, Y, Z, A)
where
T: Wasm32TestResult + FromWasm32Memory,
U: Wasm32TestResult + FromWasm32Memory,
V: Wasm32TestResult + FromWasm32Memory,
W: Wasm32TestResult + FromWasm32Memory,
X: Wasm32TestResult + FromWasm32Memory,
Y: Wasm32TestResult + FromWasm32Memory,
Z: Wasm32TestResult + FromWasm32Memory,
A: Wasm32TestResult + FromWasm32Memory,
{
fn build_wrapper_body(main_function_index: u32) -> Vec<Instruction> {
build_wrapper_body_stack_memory(
main_function_index,
T::ACTUAL_WIDTH
+ U::ACTUAL_WIDTH
+ V::ACTUAL_WIDTH
+ W::ACTUAL_WIDTH
+ X::ACTUAL_WIDTH
+ Y::ACTUAL_WIDTH
+ Z::ACTUAL_WIDTH
+ A::ACTUAL_WIDTH,
)
}
}

View file

@ -307,94 +307,13 @@ mod wasm_records {
// () // ()
// ); // );
// } // }
//
// #[test]
// fn i64_record1_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3 }
// "#
// ),
// 3,
// i64
// );
// }
// #[test]
// fn i64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3, y: 5 }
// "#
// ),
// (3, 5),
// (i64, i64)
// );
// }
// // #[test]
// // fn i64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3, y: 5, z: 17 }
// // "#
// // ),
// // (3, 5, 17),
// // (i64, i64, i64)
// // );
// // }
// #[test]
// fn f64_record2_literal() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 3.1, y: 5.1 }
// "#
// ),
// (3.1, 5.1),
// (f64, f64)
// );
// }
// // #[test]
// // fn f64_record3_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // { x: 3.1, y: 5.1, z: 17.1 }
// // "#
// // ),
// // (3.1, 5.1, 17.1),
// // (f64, f64, f64)
// // );
// // }
// // #[test]
// // fn bool_record4_literal() {
// // assert_evals_to!(
// // indoc!(
// // r#"
// // record : { a : Bool, b : Bool, c : Bool, d : Bool }
// // record = { a: True, b: True, c : True, d : Bool }
// // record
// // "#
// // ),
// // (true, false, false, true),
// // (bool, bool, bool, bool)
// // );
// // }
#[test] #[test]
fn i64_record1_literal() { fn i64_record1_literal() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(
r#" r#"
{ a: 3 } { x: 3 }
"# "#
), ),
3, 3,
@ -402,31 +321,86 @@ mod wasm_records {
); );
} }
// // #[test] #[test]
// // fn i64_record9_literal() { fn i64_record2_literal() {
// // assert_evals_to!( assert_evals_to!(
// // indoc!( indoc!(
// // r#" r#"
// // { a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 } { x: 3, y: 5 }
// // "# "#
// // ), ),
// // (3, 5, 17, 1, 9, 12, 13, 14, 15), (3, 5),
// // (i64, i64, i64, i64, i64, i64, i64, i64, i64) (i64, i64)
// // ); );
// // } }
// // #[test] #[test]
// // fn f64_record3_literal() { fn i64_record3_literal() {
// // assert_evals_to!( assert_evals_to!(
// // indoc!( indoc!(
// // r#" r#"
// // { x: 3.1, y: 5.1, z: 17.1 } { x: 3, y: 5, z: 17 }
// // "# "#
// // ), ),
// // (3.1, 5.1, 17.1), (3, 5, 17),
// // (f64, f64, f64) (i64, i64, i64)
// // ); );
// // } }
#[test]
fn f64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1 }
"#
),
(3.1, 5.1),
(f64, f64)
);
}
#[test]
fn f64_record3_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1, z: 17.1 }
"#
),
(3.1, 5.1, 17.1),
(f64, f64, f64)
);
}
#[test]
fn bool_record4_literal() {
assert_evals_to!(
indoc!(
r#"
record : { a : Bool, b : Bool, c : Bool, d : Bool }
record = { a: True, b: False, c : False, d : True }
record
"#
),
[true, false, false, true],
[bool; 4]
);
}
#[test]
fn i64_record9_literal() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
"#
),
[3, 5, 17, 1, 9, 12, 13, 14, 15],
[i64; 9]
);
}
#[test] #[test]
fn bool_literal() { fn bool_literal() {
@ -667,135 +641,135 @@ mod wasm_records {
// ); // );
// } // }
// #[test] #[test]
// fn return_record_2() { fn return_record_2() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { x: 3, y: 5 } { x: 3, y: 5 }
// "# "#
// ), ),
// [3, 5], [3, 5],
// [i64; 2] [i64; 2]
// ); );
// } }
// #[test] #[test]
// fn return_record_3() { fn return_record_3() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { x: 3, y: 5, z: 4 } { x: 3, y: 5, z: 4 }
// "# "#
// ), ),
// (3, 5, 4), (3, 5, 4),
// (i64, i64, i64) (i64, i64, i64)
// ); );
// } }
// #[test] #[test]
// fn return_record_4() { fn return_record_4() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2 } { a: 3, b: 5, c: 4, d: 2 }
// "# "#
// ), ),
// [3, 5, 4, 2], [3, 5, 4, 2],
// [i64; 4] [i64; 4]
// ); );
// } }
// #[test] #[test]
// fn return_record_5() { fn return_record_5() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1 } { a: 3, b: 5, c: 4, d: 2, e: 1 }
// "# "#
// ), ),
// [3, 5, 4, 2, 1], [3, 5, 4, 2, 1],
// [i64; 5] [i64; 5]
// ); );
// } }
// #[test] #[test]
// fn return_record_6() { fn return_record_6() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 } { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
// "# "#
// ), ),
// [3, 5, 4, 2, 1, 7], [3, 5, 4, 2, 1, 7],
// [i64; 6] [i64; 6]
// ); );
// } }
// #[test] #[test]
// fn return_record_7() { fn return_record_7() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 } { a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
// "# "#
// ), ),
// [3, 5, 4, 2, 1, 7, 8], [3, 5, 4, 2, 1, 7, 8],
// [i64; 7] [i64; 7]
// ); );
// } }
// #[test] #[test]
// fn return_record_float_int() { fn return_record_float_int() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 3.14, b: 0x1 } { a: 3.14, b: 0x1 }
// "# "#
// ), ),
// (3.14, 0x1), (3.14, 0x1),
// (f64, i64) (f64, i64)
// ); );
// } }
// #[test] #[test]
// fn return_record_int_float() { fn return_record_int_float() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 0x1, b: 3.14 } { a: 0x1, b: 3.14 }
// "# "#
// ), ),
// (0x1, 3.14), (0x1, 3.14),
// (i64, f64) (i64, f64)
// ); );
// } }
// #[test] #[test]
// fn return_record_float_float() { fn return_record_float_float() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 6.28, b: 3.14 } { a: 6.28, b: 3.14 }
// "# "#
// ), ),
// (6.28, 3.14), (6.28, 3.14),
// (f64, f64) (f64, f64)
// ); );
// } }
// #[test] #[test]
// fn return_record_float_float_float() { fn return_record_float_float_float() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// { a: 6.28, b: 3.14, c: 0.1 } { a: 6.28, b: 3.14, c: 0.1 }
// "# "#
// ), ),
// (6.28, 3.14, 0.1), (6.28, 3.14, 0.1),
// (f64, f64, f64) (f64, f64, f64)
// ); );
// } }
// #[test] // #[test]
// fn return_nested_record() { // fn return_nested_record() {
@ -851,20 +825,20 @@ mod wasm_records {
// ); // );
// } // }
#[test] // #[test]
fn update_single_element_record() { // fn update_single_element_record() {
assert_evals_to!( // assert_evals_to!(
indoc!( // indoc!(
r#" // r#"
rec = { foo: 42} // rec = { foo: 42}
{ rec & foo: rec.foo + 1 } // { rec & foo: rec.foo + 1 }
"# // "#
), // ),
43, // 43,
i64 // i64
); // );
} // }
// #[test] // #[test]
// fn booleans_in_record() { // fn booleans_in_record() {
@ -899,6 +873,24 @@ mod wasm_records {
// ); // );
// } // }
#[test]
fn stack_memory_return_from_branch() {
// stack memory pointer should end up in the right place after returning from a branch
assert_evals_to!(
indoc!(
r#"
stackMemoryJunk = { x: 999, y: 111 }
if True then
{ x: 123, y: 321 }
else
stackMemoryJunk
"#
),
(123, 321),
(i64, i64)
);
}
// #[test] // #[test]
// fn blue_and_present() { // fn blue_and_present() {
// assert_evals_to!( // assert_evals_to!(

View file

@ -37,13 +37,13 @@ use roc_types::subs::{Subs, VarStore, Variable};
use roc_types::types::{Alias, Type}; use roc_types::types::{Alias, Type};
use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::Entry::{Occupied, Vacant};
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use std::fs;
use std::io; use std::io;
use std::iter; use std::iter;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::from_utf8_unchecked; use std::str::from_utf8_unchecked;
use std::sync::Arc; use std::sync::Arc;
use std::time::{Duration, SystemTime}; use std::time::{Duration, SystemTime};
use std::{env, fs};
/// Default name for the binary generated for an app, if an invalid one was specified. /// Default name for the binary generated for an app, if an invalid one was specified.
const DEFAULT_APP_OUTPUT_PATH: &str = "app"; const DEFAULT_APP_OUTPUT_PATH: &str = "app";
@ -1351,7 +1351,12 @@ where
// doing .max(1) on the entire expression guards against // doing .max(1) on the entire expression guards against
// num_cpus returning 0, while also avoiding wrapping // num_cpus returning 0, while also avoiding wrapping
// unsigned subtraction overflow. // unsigned subtraction overflow.
let num_workers = num_cpus::get().max(2) - 1; let default_num_workers = num_cpus::get().max(2) - 1;
let num_workers = match env::var("ROC_NUM_WORKERS") {
Ok(env_str) => env_str.parse::<usize>().unwrap_or(default_num_workers),
Err(_) => default_num_workers,
};
let worker_arenas = arena.alloc(bumpalo::collections::Vec::with_capacity_in( let worker_arenas = arena.alloc(bumpalo::collections::Vec::with_capacity_in(
num_workers, num_workers,

View file

@ -15,6 +15,7 @@ pub enum LowLevel {
StrFromUtf8, StrFromUtf8,
StrFromUtf8Range, StrFromUtf8Range,
StrToUtf8, StrToUtf8,
StrRepeat,
StrFromFloat, StrFromFloat,
ListLen, ListLen,
ListGetUnsafe, ListGetUnsafe,
@ -40,6 +41,7 @@ pub enum LowLevel {
ListKeepErrs, ListKeepErrs,
ListSortWith, ListSortWith,
ListDrop, ListDrop,
ListDropAt,
ListSwap, ListSwap,
DictSize, DictSize,
DictEmpty, DictEmpty,
@ -114,19 +116,19 @@ impl LowLevel {
match self { match self {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8
| StrFromUtf8Range | StrToUtf8 | StrFromFloat | ListLen | ListGetUnsafe | ListSet | StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | ListLen | ListGetUnsafe
| ListDrop | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains | ListSet | ListDrop | ListDropAt | ListSingle | ListRepeat | ListReverse
| ListAppend | ListPrepend | ListJoin | ListRange | ListSwap | DictSize | DictEmpty | ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange
| DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | ListSwap | DictSize | DictEmpty | DictInsert | DictRemove | DictContains
| DictUnion | DictIntersection | DictDifference | SetFromList | NumAdd | NumAddWrap | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection
| NumAddChecked | NumSub | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | DictDifference | SetFromList | NumAdd | NumAddWrap | NumAddChecked | NumSub
| NumMulChecked | NumGt | NumGte | NumLt | NumLte | NumCompare | NumDivUnchecked | NumSubWrap | NumSubChecked | NumMul | NumMulWrap | NumMulChecked | NumGt | NumGte
| NumRemUnchecked | NumIsMultipleOf | NumAbs | NumNeg | NumSin | NumCos | NumLt | NumLte | NumCompare | NumDivUnchecked | NumRemUnchecked | NumIsMultipleOf
| NumSqrtUnchecked | NumLogUnchecked | NumRound | NumToFloat | NumPow | NumCeiling | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
| NumPowInt | NumFloor | NumIsFinite | NumAtan | NumAcos | NumAsin | NumBitwiseAnd | NumToFloat | NumPow | NumCeiling | NumPowInt | NumFloor | NumIsFinite | NumAtan
| NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy | NumShiftRightBy | NumBytesToU16 | NumAcos | NumAsin | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumBytesToU32 | NumShiftRightZfBy | NumIntCast | Eq | NotEq | And | Or | Not | NumShiftRightBy | NumBytesToU16 | NumBytesToU32 | NumShiftRightZfBy | NumIntCast
| Hash | ExpectTrue => false, | Eq | NotEq | And | Or | Not | Hash | ExpectTrue => false,
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith

View file

@ -973,6 +973,7 @@ define_builtins! {
16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt" 16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt"
17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime 17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
18 STR_FROM_UTF8_RANGE: "fromUtf8Range" 18 STR_FROM_UTF8_RANGE: "fromUtf8Range"
19 STR_REPEAT: "repeat"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias
@ -1009,6 +1010,7 @@ define_builtins! {
31 LIST_SORT_WITH: "sortWith" 31 LIST_SORT_WITH: "sortWith"
32 LIST_DROP: "drop" 32 LIST_DROP: "drop"
33 LIST_SWAP: "swap" 33 LIST_SWAP: "swap"
34 LIST_DROP_AT: "dropAt"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -9,13 +9,16 @@ use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use std::convert::TryFrom; use std::convert::TryFrom;
use crate::ir::{Call, CallType, Expr, ListLiteralElement, Literal, ModifyRc, Proc, Stmt}; use crate::ir::{
use crate::layout::{Builtin, Layout, ListLayout, UnionLayout}; Call, CallType, Expr, HostExposedLayouts, ListLiteralElement, Literal, ModifyRc, Proc, Stmt,
};
use crate::layout::{Builtin, Layout, ListLayout, RawFunctionLayout, UnionLayout};
// just using one module for now // just using one module for now
pub const MOD_APP: ModName = ModName(b"UserApp"); pub const MOD_APP: ModName = ModName(b"UserApp");
pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes()); pub const STATIC_STR_NAME: ConstName = ConstName(&Symbol::STR_ALIAS_ANALYSIS_STATIC.to_ne_bytes());
pub const STATIC_LIST_NAME: ConstName = ConstName(b"THIS IS A STATIC LIST");
const ENTRY_POINT_NAME: &[u8] = b"mainForHost"; const ENTRY_POINT_NAME: &[u8] = b"mainForHost";
@ -128,25 +131,49 @@ where
}; };
m.add_const(STATIC_STR_NAME, static_str_def)?; m.add_const(STATIC_STR_NAME, static_str_def)?;
// the entry point wrapper // a const that models all static lists
let roc_main_bytes = func_name_bytes_help( let static_list_def = {
entry_point.symbol, let mut cbuilder = ConstDefBuilder::new();
entry_point.layout.arguments.iter().copied(), let block = cbuilder.add_block();
entry_point.layout.result, let cell = cbuilder.add_new_heap_cell(block)?;
);
let roc_main = FuncName(&roc_main_bytes);
let entry_point_function = build_entry_point(entry_point.layout, roc_main)?; let unit_type = cbuilder.add_tuple_type(&[])?;
let entry_point_name = FuncName(ENTRY_POINT_NAME); let bag = cbuilder.add_empty_bag(block, unit_type)?;
m.add_func(entry_point_name, entry_point_function)?; let value_id = cbuilder.add_make_tuple(block, &[cell, bag])?;
let root = BlockExpr(block, value_id);
let list_type_id = static_list_type(&mut cbuilder)?;
cbuilder.build(list_type_id, root)?
};
m.add_const(STATIC_LIST_NAME, static_list_def)?;
let mut type_definitions = MutSet::default(); let mut type_definitions = MutSet::default();
let mut host_exposed_functions = Vec::new();
// all other functions // all other functions
for proc in procs { for proc in procs {
let bytes = func_name_bytes(proc); let bytes = func_name_bytes(proc);
let func_name = FuncName(&bytes); let func_name = FuncName(&bytes);
if let HostExposedLayouts::HostExposed { aliases, .. } = &proc.host_exposed_layouts {
for (_, (symbol, top_level, layout)) in aliases {
match layout {
RawFunctionLayout::Function(_, _, _) => {
let it = top_level.arguments.iter().copied();
let bytes = func_name_bytes_help(*symbol, it, top_level.result);
host_exposed_functions.push((bytes, top_level.arguments));
}
RawFunctionLayout::ZeroArgumentThunk(_) => {
let it = std::iter::once(Layout::Struct(&[]));
let bytes = func_name_bytes_help(*symbol, it, top_level.result);
host_exposed_functions.push((bytes, top_level.arguments));
}
}
}
}
if DEBUG { if DEBUG {
eprintln!( eprintln!(
"{:?}: {:?} with {:?} args", "{:?}: {:?} with {:?} args",
@ -163,6 +190,19 @@ where
m.add_func(func_name, spec)?; m.add_func(func_name, spec)?;
} }
// the entry point wrapper
let roc_main_bytes = func_name_bytes_help(
entry_point.symbol,
entry_point.layout.arguments.iter().copied(),
entry_point.layout.result,
);
let roc_main = FuncName(&roc_main_bytes);
let entry_point_function =
build_entry_point(entry_point.layout, roc_main, &host_exposed_functions)?;
let entry_point_name = FuncName(ENTRY_POINT_NAME);
m.add_func(entry_point_name, entry_point_function)?;
for union_layout in type_definitions { for union_layout in type_definitions {
let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes(); let type_name_bytes = recursive_tag_union_name_bytes(&union_layout).as_bytes();
let type_name = TypeName(&type_name_bytes); let type_name = TypeName(&type_name_bytes);
@ -202,23 +242,83 @@ where
morphic_lib::solve(program) morphic_lib::solve(program)
} }
fn build_entry_point(layout: crate::ir::ProcLayout, func_name: FuncName) -> Result<FuncDef> { /// if you want an "escape hatch" which allows you construct "best-case scenario" values
/// of an arbitrary type in much the same way that 'unknown_with' allows you to construct
/// "worst-case scenario" values of an arbitrary type, you can use the following terrible hack:
/// use 'add_make_union' to construct an instance of variant 0 of a union type 'union {(), your_type}',
/// and then use 'add_unwrap_union' to extract variant 1 from the value you just constructed.
/// In the current implementation (but not necessarily in future versions),
/// I can promise this will effectively give you a value of type 'your_type'
/// all of whose heap cells are considered unique and mutable.
fn terrible_hack(builder: &mut FuncDefBuilder, block: BlockId, type_id: TypeId) -> Result<ValueId> {
let variant_types = vec![builder.add_tuple_type(&[])?, type_id];
let unit = builder.add_make_tuple(block, &[])?;
let value = builder.add_make_union(block, &variant_types, 0, unit)?;
builder.add_unwrap_union(block, value, 1)
}
fn build_entry_point(
layout: crate::ir::ProcLayout,
func_name: FuncName,
host_exposed_functions: &[([u8; SIZE], &[Layout])],
) -> Result<FuncDef> {
let mut builder = FuncDefBuilder::new(); let mut builder = FuncDefBuilder::new();
let block = builder.add_block(); let outer_block = builder.add_block();
// to the modelling language, the arguments appear out of thin air let mut cases = Vec::new();
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
let argument = builder.add_unknown_with(block, &[], argument_type)?;
let name_bytes = [0; 16]; {
let spec_var = CalleeSpecVar(&name_bytes); let block = builder.add_block();
let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?;
// to the modelling language, the arguments appear out of thin air
let argument_type = build_tuple_type(&mut builder, layout.arguments)?;
// does not make any assumptions about the input
// let argument = builder.add_unknown_with(block, &[], argument_type)?;
// assumes the input can be updated in-place
let argument = terrible_hack(&mut builder, block, argument_type)?;
let name_bytes = [0; 16];
let spec_var = CalleeSpecVar(&name_bytes);
let result = builder.add_call(block, spec_var, MOD_APP, func_name, argument)?;
// to the modelling language, the result disappears into the void
let unit_type = builder.add_tuple_type(&[])?;
let unit_value = builder.add_unknown_with(block, &[result], unit_type)?;
cases.push(BlockExpr(block, unit_value));
}
// add fake calls to host-exposed functions so they are specialized
for (name_bytes, layouts) in host_exposed_functions {
let host_exposed_func_name = FuncName(name_bytes);
if host_exposed_func_name == func_name {
continue;
}
let block = builder.add_block();
let type_id = layout_spec(&mut builder, &Layout::Struct(layouts))?;
let argument = builder.add_unknown_with(block, &[], type_id)?;
let spec_var = CalleeSpecVar(name_bytes);
let result =
builder.add_call(block, spec_var, MOD_APP, host_exposed_func_name, argument)?;
let unit_type = builder.add_tuple_type(&[])?;
let unit_value = builder.add_unknown_with(block, &[result], unit_type)?;
cases.push(BlockExpr(block, unit_value));
}
// to the modelling language, the result disappears into the void
let unit_type = builder.add_tuple_type(&[])?; let unit_type = builder.add_tuple_type(&[])?;
let unit_value = builder.add_unknown_with(block, &[result], unit_type)?; let unit_value = builder.add_choice(outer_block, &cases)?;
let root = BlockExpr(block, unit_value); let root = BlockExpr(outer_block, unit_value);
let spec = builder.build(unit_type, unit_type, root)?; let spec = builder.build(unit_type, unit_type, root)?;
Ok(spec) Ok(spec)
@ -818,6 +918,17 @@ fn lowlevel_spec(
let new_cell = builder.add_new_heap_cell(block)?; let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag]) builder.add_make_tuple(block, &[new_cell, bag])
} }
ListReverse => {
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_update(block, update_mode_var, cell)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
}
ListAppend => { ListAppend => {
let list = env.symbols[&arguments[0]]; let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[1]]; let to_insert = env.symbols[&arguments[1]];
@ -833,6 +944,27 @@ fn lowlevel_spec(
let new_cell = builder.add_new_heap_cell(block)?; let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag]) builder.add_make_tuple(block, &[new_cell, bag])
} }
StrToUtf8 => {
let string = env.symbols[&arguments[0]];
let u8_type = builder.add_tuple_type(&[])?;
let bag = builder.add_empty_bag(block, u8_type)?;
let cell = builder.add_get_tuple_field(block, string, LIST_CELL_INDEX)?;
builder.add_make_tuple(block, &[cell, bag])
}
StrFromUtf8 => {
let list = env.symbols[&arguments[0]];
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let string = builder.add_make_tuple(block, &[cell])?;
let byte_index = builder.add_make_tuple(block, &[])?;
let is_ok = builder.add_make_tuple(block, &[])?;
let problem_code = builder.add_make_tuple(block, &[])?;
builder.add_make_tuple(block, &[byte_index, string, is_ok, problem_code])
}
DictEmpty => { DictEmpty => {
match layout { match layout {
Layout::Builtin(Builtin::EmptyDict) => { Layout::Builtin(Builtin::EmptyDict) => {
@ -1117,9 +1249,11 @@ fn expr_spec<'a>(
let list = new_list(builder, block, type_id)?; let list = new_list(builder, block, type_id)?;
let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?; let mut bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let mut all_constants = true;
for element in elems.iter() { for element in elems.iter() {
let value_id = if let ListLiteralElement::Symbol(symbol) = element { let value_id = if let ListLiteralElement::Symbol(symbol) = element {
all_constants = false;
env.symbols[symbol] env.symbols[symbol]
} else { } else {
builder.add_make_tuple(block, &[]).unwrap() builder.add_make_tuple(block, &[]).unwrap()
@ -1128,9 +1262,13 @@ fn expr_spec<'a>(
bag = builder.add_bag_insert(block, bag, value_id)?; bag = builder.add_bag_insert(block, bag, value_id)?;
} }
let cell = builder.add_new_heap_cell(block)?; if all_constants {
new_static_list(builder, block)
} else {
let cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[cell, bag]) builder.add_make_tuple(block, &[cell, bag])
}
} }
EmptyArray => { EmptyArray => {
@ -1296,6 +1434,14 @@ fn str_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
builder.add_tuple_type(&[cell_id]) builder.add_tuple_type(&[cell_id])
} }
fn static_list_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
let unit_type = builder.add_tuple_type(&[])?;
let cell = builder.add_heap_cell_type();
let bag = builder.add_bag_type(unit_type)?;
builder.add_tuple_type(&[cell, bag])
}
// const OK_TAG_ID: u8 = 1u8; // const OK_TAG_ID: u8 = 1u8;
// const ERR_TAG_ID: u8 = 0u8; // const ERR_TAG_ID: u8 = 0u8;
@ -1329,6 +1475,12 @@ fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<Val
builder.add_const_ref(block, module, STATIC_STR_NAME) builder.add_const_ref(block, module, STATIC_STR_NAME)
} }
fn new_static_list(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
let module = MOD_APP;
builder.add_const_ref(block, module, STATIC_LIST_NAME)
}
fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> { fn new_num(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
// we model all our numbers as unit values // we model all our numbers as unit values
builder.add_make_tuple(block, &[]) builder.add_make_tuple(block, &[])

View file

@ -993,6 +993,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
// List.append should own its first argument // List.append should own its first argument
ListAppend => arena.alloc_slice_copy(&[owned, owned]), ListAppend => arena.alloc_slice_copy(&[owned, owned]),
ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]), ListDrop => arena.alloc_slice_copy(&[owned, irrelevant]),
ListDropAt => arena.alloc_slice_copy(&[owned, irrelevant]),
ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]), ListSwap => arena.alloc_slice_copy(&[owned, irrelevant, irrelevant]),
Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]), Eq | NotEq => arena.alloc_slice_copy(&[borrowed, borrowed]),
@ -1013,6 +1014,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
StrFromUtf8 => arena.alloc_slice_copy(&[owned]), StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]), StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrToUtf8 => arena.alloc_slice_copy(&[owned]), StrToUtf8 => arena.alloc_slice_copy(&[owned]),
StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]), StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),
Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]), Hash => arena.alloc_slice_copy(&[borrowed, irrelevant]),
DictSize => arena.alloc_slice_copy(&[borrowed]), DictSize => arena.alloc_slice_copy(&[borrowed]),

View file

@ -94,6 +94,12 @@ impl<'a> CapturedSymbols<'a> {
} }
} }
impl<'a> Default for CapturedSymbols<'a> {
fn default() -> Self {
CapturedSymbols::None
}
}
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct PendingSpecialization<'a> { pub struct PendingSpecialization<'a> {
solved_type: SolvedType, solved_type: SolvedType,
@ -6208,6 +6214,8 @@ fn reuse_function_symbol<'a>(
layout_cache, layout_cache,
); );
// even though this function may not itself capture,
// unification may still cause it to have an extra argument
construct_closure_data( construct_closure_data(
env, env,
lambda_set, lambda_set,
@ -6437,8 +6445,6 @@ fn call_by_name<'a>(
assign_to_symbols(env, procs, layout_cache, iter, result) assign_to_symbols(env, procs, layout_cache, iter, result)
} }
} else { } else {
let argument_layouts = lambda_set.extend_argument_list(env.arena, arg_layouts);
call_by_name_help( call_by_name_help(
env, env,
procs, procs,
@ -6446,7 +6452,7 @@ fn call_by_name<'a>(
proc_name, proc_name,
loc_args, loc_args,
lambda_set, lambda_set,
argument_layouts, arg_layouts,
ret_layout, ret_layout,
layout_cache, layout_cache,
assigned, assigned,
@ -6494,10 +6500,6 @@ fn call_by_name_help<'a>(
let original_fn_var = fn_var; let original_fn_var = fn_var;
let arena = env.arena; let arena = env.arena;
// debug_assert!(!procs.module_thunks.contains(&proc_name), "{:?}", proc_name);
let top_level_layout = ProcLayout::new(env.arena, argument_layouts, *ret_layout);
// the arguments given to the function, stored in symbols // the arguments given to the function, stored in symbols
let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena); let mut field_symbols = Vec::with_capacity_in(loc_args.len(), arena);
field_symbols.extend( field_symbols.extend(
@ -6506,7 +6508,13 @@ fn call_by_name_help<'a>(
.map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)), .map(|(_, arg_expr)| possible_reuse_symbol(env, procs, &arg_expr.value)),
); );
let field_symbols = field_symbols.into_bump_slice(); // If required, add an extra argument to the layout that is the captured environment
// afterwards, we MUST make sure the number of arguments in the layout matches the
// number of arguments actually passed.
let top_level_layout = {
let argument_layouts = lambda_set.extend_argument_list(env.arena, argument_layouts);
ProcLayout::new(env.arena, argument_layouts, *ret_layout)
};
// the variables of the given arguments // the variables of the given arguments
let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena); let mut pattern_vars = Vec::with_capacity_in(loc_args.len(), arena);
@ -6535,6 +6543,8 @@ fn call_by_name_help<'a>(
proc_name, proc_name,
); );
let field_symbols = field_symbols.into_bump_slice();
let call = self::Call { let call = self::Call {
call_type: CallType::ByName { call_type: CallType::ByName {
name: proc_name, name: proc_name,
@ -6573,6 +6583,9 @@ fn call_by_name_help<'a>(
"see call_by_name for background (scroll down a bit), function is {:?}", "see call_by_name for background (scroll down a bit), function is {:?}",
proc_name, proc_name,
); );
let field_symbols = field_symbols.into_bump_slice();
let call = self::Call { let call = self::Call {
call_type: CallType::ByName { call_type: CallType::ByName {
name: proc_name, name: proc_name,
@ -6625,6 +6638,8 @@ fn call_by_name_help<'a>(
proc_name, proc_name,
); );
let field_symbols = field_symbols.into_bump_slice();
let call = self::Call { let call = self::Call {
call_type: CallType::ByName { call_type: CallType::ByName {
name: proc_name, name: proc_name,
@ -6643,6 +6658,19 @@ fn call_by_name_help<'a>(
None => { None => {
let opt_partial_proc = procs.partial_procs.get(&proc_name); let opt_partial_proc = procs.partial_procs.get(&proc_name);
/*
debug_assert_eq!(
argument_layouts.len(),
field_symbols.len(),
"Function {:?} is called with {} arguments, but the layout expects {}",
proc_name,
field_symbols.len(),
argument_layouts.len(),
);
*/
let field_symbols = field_symbols.into_bump_slice();
match opt_partial_proc { match opt_partial_proc {
Some(partial_proc) => { Some(partial_proc) => {
// TODO should pending_procs hold a Rc<Proc> to avoid this .clone()? // TODO should pending_procs hold a Rc<Proc> to avoid this .clone()?
@ -6657,18 +6685,22 @@ fn call_by_name_help<'a>(
match specialize(env, procs, proc_name, layout_cache, pending, partial_proc) match specialize(env, procs, proc_name, layout_cache, pending, partial_proc)
{ {
Ok((proc, layout)) => call_specialized_proc( Ok((proc, layout)) => {
env, // now we just call our freshly-specialized function
procs, call_specialized_proc(
proc_name, env,
proc, procs,
layout, proc_name,
field_symbols, proc,
loc_args, lambda_set,
layout_cache, layout,
assigned, field_symbols,
hole, loc_args,
), layout_cache,
assigned,
hole,
)
}
Err(SpecializeFailure { Err(SpecializeFailure {
attempted_layout, attempted_layout,
problem: _, problem: _,
@ -6684,6 +6716,7 @@ fn call_by_name_help<'a>(
procs, procs,
proc_name, proc_name,
proc, proc,
lambda_set,
attempted_layout, attempted_layout,
field_symbols, field_symbols,
loc_args, loc_args,
@ -6833,6 +6866,7 @@ fn call_specialized_proc<'a>(
procs: &mut Procs<'a>, procs: &mut Procs<'a>,
proc_name: Symbol, proc_name: Symbol,
proc: Proc<'a>, proc: Proc<'a>,
lambda_set: LambdaSet<'a>,
layout: RawFunctionLayout<'a>, layout: RawFunctionLayout<'a>,
field_symbols: &'a [Symbol], field_symbols: &'a [Symbol],
loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>, loc_args: std::vec::Vec<(Variable, Located<roc_can::expr::Expr>)>,
@ -6871,6 +6905,8 @@ fn call_specialized_proc<'a>(
arguments: field_symbols, arguments: field_symbols,
}; };
// the closure argument is already added here (to get the right specialization)
// but now we need to remove it because the `match_on_lambda_set` will add it again
build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole) build_call(env, call, assigned, Layout::LambdaSet(lambda_set), hole)
} }
RawFunctionLayout::ZeroArgumentThunk(_) => { RawFunctionLayout::ZeroArgumentThunk(_) => {
@ -6878,30 +6914,75 @@ fn call_specialized_proc<'a>(
} }
} }
} else { } else {
debug_assert_eq!(
function_layout.arguments.len(),
field_symbols.len(),
"function {:?} with layout {:?} expects {:?} arguments, but is applied to {:?}",
proc_name,
function_layout,
function_layout.arguments.len(),
field_symbols.len(),
);
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
arg_layouts: function_layout.arguments,
specialization_id: env.next_call_specialization_id(),
},
arguments: field_symbols,
};
let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev()); let iter = loc_args.into_iter().rev().zip(field_symbols.iter().rev());
let result = build_call(env, call, assigned, function_layout.result, hole); match procs
.partial_procs
.get(&proc_name)
.map(|pp| &pp.captured_symbols)
{
Some(&CapturedSymbols::Captured(captured_symbols)) => {
let symbols = Vec::from_iter_in(captured_symbols.iter().map(|x| x.0), env.arena)
.into_bump_slice();
assign_to_symbols(env, procs, layout_cache, iter, result) let closure_data_symbol = env.unique_symbol();
// the closure argument is already added here (to get the right specialization)
// but now we need to remove it because the `match_on_lambda_set` will add it again
let mut argument_layouts =
Vec::from_iter_in(function_layout.arguments.iter().copied(), env.arena);
argument_layouts.pop().unwrap();
debug_assert_eq!(argument_layouts.len(), field_symbols.len(),);
let new_hole = match_on_lambda_set(
env,
lambda_set,
closure_data_symbol,
field_symbols,
argument_layouts.into_bump_slice(),
function_layout.result,
assigned,
hole,
);
let result = construct_closure_data(
env,
lambda_set,
proc_name,
symbols,
closure_data_symbol,
env.arena.alloc(new_hole),
);
assign_to_symbols(env, procs, layout_cache, iter, result)
}
_ => {
debug_assert_eq!(
function_layout.arguments.len(),
field_symbols.len(),
"function {:?} with layout {:#?} expects {:?} arguments, but is applied to {:?}",
proc_name,
function_layout,
function_layout.arguments.len(),
field_symbols.len(),
);
let call = self::Call {
call_type: CallType::ByName {
name: proc_name,
ret_layout: function_layout.result,
arg_layouts: function_layout.arguments,
specialization_id: env.next_call_specialization_id(),
},
arguments: field_symbols,
};
let result = build_call(env, call, assigned, function_layout.result, hole);
assign_to_symbols(env, procs, layout_cache, iter, result)
}
}
} }
} }

View file

@ -599,7 +599,7 @@ impl<'a> LambdaSet<'a> {
// this can happen when there is a type error somewhere // this can happen when there is a type error somewhere
Ok(LambdaSet { Ok(LambdaSet {
set: &[], set: &[],
representation: arena.alloc(Layout::Union(UnionLayout::NonRecursive(&[]))), representation: arena.alloc(Layout::Struct(&[])),
}) })
} }
_ => panic!("called LambdaSet.from_var on invalid input"), _ => panic!("called LambdaSet.from_var on invalid input"),

View file

@ -3709,6 +3709,18 @@ mod solve_expr {
); );
} }
#[test]
fn list_drop_at() {
infer_eq_without_problem(
indoc!(
r#"
List.dropAt
"#
),
"List a, Nat -> List a",
);
}
#[test] #[test]
fn function_that_captures_nothing_is_not_captured() { fn function_that_captures_nothing_is_not_captured() {
// we should make sure that a function that doesn't capture anything it not itself captured // we should make sure that a function that doesn't capture anything it not itself captured

View file

@ -198,6 +198,43 @@ fn list_drop() {
assert_evals_to!("List.drop [1,2] 5", RocList::from_slice(&[]), RocList<i64>); assert_evals_to!("List.drop [1,2] 5", RocList::from_slice(&[]), RocList<i64>);
} }
#[test]
fn list_drop_at() {
assert_evals_to!(
"List.dropAt [1, 2, 3] 0",
RocList::from_slice(&[2, 3]),
RocList<i64>
);
assert_evals_to!(
"List.dropAt [0, 0, 0] 3",
RocList::from_slice(&[0, 0, 0]),
RocList<i64>
);
assert_evals_to!("List.dropAt [] 1", RocList::from_slice(&[]), RocList<i64>);
assert_evals_to!("List.dropAt [0] 0", RocList::from_slice(&[]), RocList<i64>);
}
#[test]
fn list_drop_at_mutable() {
assert_evals_to!(
indoc!(
r#"
list : List I64
list = [ if True then 4 else 4, 5, 6 ]
{ newList: List.dropAt list 0, original: list }
"#
),
(
// new_list
RocList::from_slice(&[5, 6]),
// original
RocList::from_slice(&[4, 5, 6]),
),
(RocList<i64>, RocList<i64>,)
);
}
#[test] #[test]
fn list_swap() { fn list_swap() {
assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList<i64>); assert_evals_to!("List.swap [] 0 1", RocList::from_slice(&[]), RocList<i64>);
@ -2031,3 +2068,31 @@ fn map_with_index_multi_record() {
RocList<((), ())> RocList<((), ())>
); );
} }
#[test]
fn empty_list_of_function_type() {
// see https://github.com/rtfeldman/roc/issues/1732
assert_evals_to!(
indoc!(
r#"
myList : List (Str -> Str)
myList = []
myClosure : Str -> Str
myClosure = \_ -> "bar"
choose =
if False then
myList
else
[ myClosure ]
when List.get choose 0 is
Ok f -> f "foo"
Err _ -> "bad!"
"#
),
RocStr::from_slice(b"bar"),
RocStr
);
}

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