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

This commit is contained in:
Anton-4 2021-10-05 11:58:45 +02:00
commit ab665b7380
128 changed files with 7689 additions and 2229 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:

2
.gitignore vendored
View file

@ -3,6 +3,8 @@ generated-docs
zig-cache zig-cache
.direnv .direnv
*.rs.bk *.rs.bk
*.o
*.tmp
# llvm human-readable output # llvm human-readable output
*.ll *.ll

View file

@ -74,6 +74,8 @@ There are also alternative installation options at http://releases.llvm.org/down
## Using Nix ## Using Nix
:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation:
### Install ### Install
Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command. Using [nix](https://nixos.org/download.html) is a quick way to get an environment bootstrapped with a single command.

387
Cargo.lock generated
View file

@ -131,10 +131,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",
] ]
@ -332,9 +338,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 = "cfg-if" name = "cfg-if"
@ -1074,15 +1077,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"
@ -1154,16 +1148,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"
@ -1182,6 +1166,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"
@ -1397,168 +1387,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"
@ -1589,9 +1417,9 @@ checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
[[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",
@ -1640,9 +1468,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",
@ -1659,9 +1487,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",
@ -1954,15 +1782,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"
@ -2285,13 +2104,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",
@ -2299,9 +2119,8 @@ dependencies = [
"fxhash", "fxhash",
"log", "log",
"num-traits", "num-traits",
"petgraph", "petgraph 0.6.0",
"rose_tree", "spirv",
"spirv_headers",
"thiserror", "thiserror",
] ]
@ -2523,7 +2342,9 @@ version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2" checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
dependencies = [ dependencies = [
"crc32fast",
"flate2", "flate2",
"indexmap",
"memchr", "memchr",
] ]
@ -2648,7 +2469,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",
@ -2710,7 +2531,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",
] ]
@ -3515,6 +3346,7 @@ dependencies = [
"roc_editor", "roc_editor",
"roc_fmt", "roc_fmt",
"roc_gen_llvm", "roc_gen_llvm",
"roc_linker",
"roc_load", "roc_load",
"roc_module", "roc_module",
"roc_mono", "roc_mono",
@ -3782,8 +3614,12 @@ dependencies = [
"iced-x86", "iced-x86",
"memmap2 0.3.1", "memmap2 0.3.1",
"object 0.26.2", "object 0.26.2",
"roc_build",
"roc_collections", "roc_collections",
"roc_mono",
"serde", "serde",
"target-lexicon",
"tempfile",
] ]
[[package]] [[package]]
@ -3997,15 +3833,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"
@ -4282,9 +4109,12 @@ checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
[[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"
@ -4361,21 +4191,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",
@ -4393,15 +4212,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "storage-map"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "418bb14643aa55a7841d5303f72cf512cfb323b8cc221d51580500a1ca75206c"
dependencies = [
"lock_api",
]
[[package]] [[package]]
name = "strip-ansi-escapes" name = "strip-ansi-escapes"
version = "0.1.1" version = "0.1.1"
@ -4620,12 +4430,6 @@ dependencies = [
"winapi 0.3.9", "winapi 0.3.9",
] ]
[[package]]
name = "thunderdome"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87b4947742c93ece24a0032141d9caa3d853752e694a57e35029dd2bd08673e0"
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@ -4845,7 +4649,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",
@ -4871,7 +4675,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",
] ]
@ -5288,9 +5092,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",
@ -5298,14 +5102,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",
@ -5313,29 +5116,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",
@ -5343,23 +5138,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",
@ -5452,15 +5282,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

@ -100,7 +100,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

View file

@ -57,6 +57,7 @@ roc_build = { path = "../compiler/build", default-features = false }
roc_fmt = { path = "../compiler/fmt" } roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" } roc_reporting = { path = "../compiler/reporting" }
roc_editor = { path = "../editor", optional = true } roc_editor = { path = "../editor", optional = true }
roc_linker = { path = "../linker" }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
const_format = "0.2" const_format = "0.2"

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) {
@ -53,6 +56,8 @@ pub fn build_file<'a>(
emit_debug_info: bool, emit_debug_info: bool,
emit_timings: bool, emit_timings: bool,
link_type: LinkType, link_type: LinkType,
surgically_link: bool,
precompiled: bool,
) -> Result<BuiltFile, LoadingProblem<'a>> { ) -> Result<BuiltFile, LoadingProblem<'a>> {
let compilation_start = SystemTime::now(); let compilation_start = SystemTime::now();
let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; let ptr_bytes = target.pointer_width().unwrap().bytes() as u32;
@ -85,7 +90,39 @@ pub fn build_file<'a>(
let host_extension = if emit_wasm { "zig" } else { "o" }; let host_extension = if emit_wasm { "zig" } else { "o" };
let app_extension = if emit_wasm { "bc" } else { "o" }; let app_extension = if emit_wasm { "bc" } else { "o" };
let cwd = roc_file_path.parent().unwrap();
let mut binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
if emit_wasm {
binary_path.set_extension("wasm");
}
let mut host_input_path = PathBuf::from(cwd);
let path_to_platform = loaded.platform_path.clone(); let path_to_platform = loaded.platform_path.clone();
host_input_path.push(&*path_to_platform);
host_input_path.push("host");
host_input_path.set_extension(host_extension);
// TODO this should probably be moved before load_and_monomorphize.
// To do this we will need to preprocess files just for their exported symbols.
// Also, we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.
let rebuild_thread = spawn_rebuild_thread(
opt_level,
surgically_link,
precompiled,
host_input_path.clone(),
binary_path.clone(),
target,
loaded
.exposed_to_host
.keys()
.map(|x| x.as_str(&loaded.interns).to_string())
.collect(),
);
// TODO try to move as much of this linking as possible to the precompiled
// host, to minimize the amount of host-application linking required.
let app_o_file = Builder::new() let app_o_file = Builder::new()
.prefix("roc_app") .prefix("roc_app")
.suffix(&format!(".{}", app_extension)) .suffix(&format!(".{}", app_extension))
@ -142,9 +179,6 @@ pub fn build_file<'a>(
program::report_problems(&mut loaded); program::report_problems(&mut loaded);
let loaded = loaded; let loaded = loaded;
let cwd = roc_file_path.parent().unwrap();
let binary_path = cwd.join(&*loaded.output_path); // TODO should join ".exe" on Windows
let code_gen_timing = match opt_level { let code_gen_timing = match opt_level {
OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm( OptLevel::Normal | OptLevel::Optimize => program::gen_from_mono_module_llvm(
arena, arena,
@ -192,44 +226,53 @@ pub fn build_file<'a>(
); );
} }
// Step 2: link the precompiled host and compiled app let rebuild_duration = rebuild_thread.join().unwrap();
let mut host_input_path = PathBuf::from(cwd); if emit_timings && !precompiled {
host_input_path.push(&*path_to_platform);
host_input_path.push("host");
host_input_path.set_extension(host_extension);
// TODO we should no longer need to do this once we have platforms on
// a package repository, as we can then get precompiled hosts from there.
let rebuild_host_start = SystemTime::now();
rebuild_host(target, host_input_path.as_path());
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
if emit_timings {
println!( println!(
"Finished rebuilding the host in {} ms\n", "Finished rebuilding and preprocessing the host in {} ms\n",
rebuild_host_end.as_millis() rebuild_duration
); );
} }
// TODO try to move as much of this linking as possible to the precompiled // Step 2: link the precompiled host and compiled app
// host, to minimize the amount of host-application linking required.
let link_start = SystemTime::now(); let link_start = SystemTime::now();
let (mut child, binary_path) = // TODO use lld let outcome = if surgically_link {
link( roc_linker::link_preprocessed_host(target, &host_input_path, app_o_file, &binary_path)
target, .map_err(|_| {
binary_path, todo!("gracefully handle failing to surgically link");
&[host_input_path.as_path().to_str().unwrap(), app_o_file.to_str().unwrap()], })?;
link_type BuildOutcome::NoProblems
) } else {
.map_err(|_| { let mut inputs = vec![
todo!("gracefully handle `rustc` failing to spawn."); 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
link(
target,
binary_path.clone(),
&inputs,
link_type
)
.map_err(|_| {
todo!("gracefully handle `ld` failing to spawn.");
})?;
let exit_status = child.wait().map_err(|_| {
todo!("gracefully handle error after `ld` spawned");
})?; })?;
let cmd_result = child.wait().map_err(|_| { // TODO change this to report whether there were errors or warnings!
todo!("gracefully handle error after `rustc` spawned"); if exit_status.success() {
}); BuildOutcome::NoProblems
} else {
BuildOutcome::Errors
}
};
let linking_time = link_start.elapsed().unwrap(); let linking_time = link_start.elapsed().unwrap();
if emit_timings { if emit_timings {
@ -238,16 +281,6 @@ pub fn build_file<'a>(
let total_time = compilation_start.elapsed().unwrap(); let total_time = compilation_start.elapsed().unwrap();
// If the cmd errored out, return the Err.
let exit_status = cmd_result?;
// TODO change this to report whether there were errors or warnings!
let outcome = if exit_status.success() {
BuildOutcome::NoProblems
} else {
BuildOutcome::Errors
};
Ok(BuiltFile { Ok(BuiltFile {
binary_path, binary_path,
outcome, outcome,
@ -255,6 +288,46 @@ pub fn build_file<'a>(
}) })
} }
fn spawn_rebuild_thread(
opt_level: OptLevel,
surgically_link: bool,
precompiled: bool,
host_input_path: PathBuf,
binary_path: PathBuf,
target: &Triple,
exported_symbols: Vec<String>,
) -> std::thread::JoinHandle<u128> {
let thread_local_target = target.clone();
std::thread::spawn(move || {
let rebuild_host_start = SystemTime::now();
if !precompiled {
if surgically_link {
roc_linker::build_and_preprocess_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
exported_symbols,
)
.unwrap();
} else {
rebuild_host(
opt_level,
&thread_local_target,
host_input_path.as_path(),
None,
);
}
}
if surgically_link {
// Copy preprocessed host to executable location.
let prehost = host_input_path.with_file_name("preprocessedhost");
std::fs::copy(prehost, binary_path.as_path()).unwrap();
}
let rebuild_host_end = rebuild_host_start.elapsed().unwrap();
rebuild_host_end.as_millis()
})
}
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn check_file( pub fn check_file(
arena: &Bump, arena: &Bump,

View file

@ -34,6 +34,8 @@ pub const FLAG_OPTIMIZE: &str = "optimize";
pub const FLAG_LIB: &str = "lib"; pub const FLAG_LIB: &str = "lib";
pub const FLAG_BACKEND: &str = "backend"; pub const FLAG_BACKEND: &str = "backend";
pub const FLAG_TIME: &str = "time"; pub const FLAG_TIME: &str = "time";
pub const FLAG_LINK: &str = "roc-linker";
pub const FLAG_PRECOMPILED: &str = "precompiled-host";
pub const ROC_FILE: &str = "ROC_FILE"; pub const ROC_FILE: &str = "ROC_FILE";
pub const BACKEND: &str = "BACKEND"; pub const BACKEND: &str = "BACKEND";
pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES"; pub const DIRECTORY_OR_FILES: &str = "DIRECTORY_OR_FILES";
@ -89,6 +91,18 @@ pub fn build_app<'a>() -> App<'a> {
.help("Prints detailed compilation time information.") .help("Prints detailed compilation time information.")
.required(false), .required(false),
) )
.arg(
Arg::with_name(FLAG_LINK)
.long(FLAG_LINK)
.help("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.help("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
) )
.subcommand(App::new(CMD_RUN) .subcommand(App::new(CMD_RUN)
.about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`") .about("DEPRECATED - now use `roc [FILE]` instead of `roc run [FILE]`")
@ -177,6 +191,18 @@ pub fn build_app<'a>() -> App<'a> {
.help("Prints detailed compilation time information.") .help("Prints detailed compilation time information.")
.required(false), .required(false),
) )
.arg(
Arg::with_name(FLAG_LINK)
.long(FLAG_LINK)
.help("Uses the roc linker instead of the system linker.")
.required(false),
)
.arg(
Arg::with_name(FLAG_PRECOMPILED)
.long(FLAG_PRECOMPILED)
.help("Assumes the host has been precompiled and skips recompiling the host.")
.required(false),
)
.arg( .arg(
Arg::with_name(FLAG_BACKEND) Arg::with_name(FLAG_BACKEND)
.long(FLAG_BACKEND) .long(FLAG_BACKEND)
@ -261,6 +287,14 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
} else { } else {
LinkType::Executable LinkType::Executable
}; };
let surgically_link = matches.is_present(FLAG_LINK);
let precompiled = matches.is_present(FLAG_PRECOMPILED);
if surgically_link && !roc_linker::supported(&link_type, &target) {
panic!(
"Link type, {:?}, with target, {}, not supported by roc linker",
link_type, target
);
}
let path = Path::new(filename); let path = Path::new(filename);
@ -293,6 +327,8 @@ pub fn build(matches: &ArgMatches, config: BuildConfig) -> io::Result<i32> {
emit_debug_info, emit_debug_info,
emit_timings, emit_timings,
link_type, link_type,
surgically_link,
precompiled,
); );
match res_binary_path { match res_binary_path {

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
@ -157,10 +163,9 @@ mod cli_run {
let example = $example; let example = $example;
let file_name = example_file(dir_name, example.filename); let file_name = example_file(dir_name, example.filename);
match example.filename { match example.executable_filename {
"Fib.roc" => { "hello-web" => {
// it is broken because the dev and normal backend don't generate the // this is a web webassembly example, but we don't test with JS at the moment
// same name for main. The dev version is expected here.
eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename); eprintln!("WARNING: skipping testing example {} because the test is broken right now!", example.filename);
return; return;
} }
@ -185,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,
);
}
} }
)* )*
@ -229,7 +247,14 @@ 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: &[],
expected_ending:"Hello, World!\n",
use_valgrind: true,
},
hello_web:"hello-web" => Example {
filename: "Hello.roc",
executable_filename: "hello-web",
stdin: &[], stdin: &[],
expected_ending:"Hello, World!\n", expected_ending:"Hello, World!\n",
use_valgrind: true, use_valgrind: true,
@ -429,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<'_>>) {
@ -556,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

@ -22,7 +22,8 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocCallResult) void; extern fn roc__mainForHost_1_exposed() RocStr;
extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
@ -47,28 +48,23 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); std.process.exit(0);
} }
const RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() i32 { pub export fn main() i32 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // actually call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult); const callresult = roc__mainForHost_1_exposed();
// stdout the result // stdout the result
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.content.deinit(); callresult.deinit();
// end time // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;

View file

@ -22,7 +22,7 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocCallResult) void; extern fn roc__mainForHost_1_exposed() RocStr;
extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
@ -47,28 +47,23 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); std.process.exit(0);
} }
const RocCallResult = extern struct { flag: usize, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() i32 { pub export fn main() i32 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // actually call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult); const callresult = roc__mainForHost_1_exposed();
// stdout the result // stdout the result
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.content.deinit(); callresult.deinit();
// end time // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;

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");

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;
@ -56,7 +57,7 @@ fn find_zig_str_path() -> PathBuf {
return zig_str_path; return zig_str_path;
} }
panic!("cannot find `str.zig`") panic!("cannot find `str.zig`. Launch me from either the root of the roc repo or one level down(roc/examples, roc/cli...)")
} }
fn find_wasi_libc_path() -> PathBuf { fn find_wasi_libc_path() -> PathBuf {
@ -76,6 +77,7 @@ fn find_wasi_libc_path() -> PathBuf {
} }
#[cfg(not(target_os = "macos"))] #[cfg(not(target_os = "macos"))]
#[allow(clippy::too_many_arguments)]
pub fn build_zig_host_native( pub fn build_zig_host_native(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
@ -83,36 +85,48 @@ pub fn build_zig_host_native(
zig_host_src: &str, zig_host_src: &str,
zig_str_path: &str, zig_str_path: &str,
target: &str, target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Output { ) -> Output {
Command::new("zig") let mut command = Command::new("zig");
command
.env_clear() .env_clear()
.env("PATH", env_path) .env("PATH", env_path)
.env("HOME", env_home) .env("HOME", env_home);
.args(&[ if let Some(shared_lib_path) = shared_lib_path {
"build-obj", command.args(&[
zig_host_src, "build-exe",
emit_bin, "-fPIE",
"--pkg-begin", shared_lib_path.to_str().unwrap(),
"str", bitcode::OBJ_PATH,
zig_str_path, ]);
"--pkg-end", } else {
// include the zig runtime command.args(&["build-obj", "-fPIC"]);
"-fcompiler-rt", }
// include libc command.args(&[
"--library", zig_host_src,
"c", emit_bin,
"-fPIC", "--pkg-begin",
"-O", "str",
"ReleaseSafe", zig_str_path,
// cross-compile? "--pkg-end",
"-target", // include the zig runtime
target, "-fcompiler-rt",
]) // include libc
.output() "--library",
.unwrap() "c",
// cross-compile?
"-target",
target,
]);
if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
}
command.output().unwrap()
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
#[allow(clippy::too_many_arguments)]
pub fn build_zig_host_native( pub fn build_zig_host_native(
env_path: &str, env_path: &str,
env_home: &str, env_home: &str,
@ -120,6 +134,8 @@ pub fn build_zig_host_native(
zig_host_src: &str, zig_host_src: &str,
zig_str_path: &str, zig_str_path: &str,
_target: &str, _target: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Output { ) -> Output {
use serde_json::Value; use serde_json::Value;
@ -161,32 +177,41 @@ pub fn build_zig_host_native(
zig_compiler_rt_path.push("special"); zig_compiler_rt_path.push("special");
zig_compiler_rt_path.push("compiler_rt.zig"); zig_compiler_rt_path.push("compiler_rt.zig");
Command::new("zig") let mut command = Command::new("zig");
command
.env_clear() .env_clear()
.env("PATH", &env_path) .env("PATH", &env_path)
.env("HOME", &env_home) .env("HOME", &env_home);
.args(&[ if let Some(shared_lib_path) = shared_lib_path {
"build-obj", command.args(&[
zig_host_src, "build-exe",
emit_bin, "-fPIE",
"--pkg-begin", shared_lib_path.to_str().unwrap(),
"str", bitcode::OBJ_PATH,
zig_str_path, ]);
"--pkg-end", } else {
// include the zig runtime command.args(&["build-obj", "-fPIC"]);
"--pkg-begin", }
"compiler_rt", command.args(&[
zig_compiler_rt_path.to_str().unwrap(), zig_host_src,
"--pkg-end", emit_bin,
// include libc "--pkg-begin",
"--library", "str",
"c", zig_str_path,
"-fPIC", "--pkg-end",
"-O", // include the zig runtime
"ReleaseSafe", "--pkg-begin",
]) "compiler_rt",
.output() zig_compiler_rt_path.to_str().unwrap(),
.unwrap() "--pkg-end",
// include libc
"--library",
"c",
]);
if matches!(opt_level, OptLevel::Optimize) {
command.args(&["-O", "ReleaseSafe"]);
}
command.output().unwrap()
} }
pub fn build_zig_host_wasm32( pub fn build_zig_host_wasm32(
@ -195,7 +220,12 @@ pub fn build_zig_host_wasm32(
emit_bin: &str, emit_bin: &str,
zig_host_src: &str, zig_host_src: &str,
zig_str_path: &str, zig_str_path: &str,
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Output { ) -> Output {
if shared_lib_path.is_some() {
unimplemented!("Linking a shared library to wasm not yet implemented");
}
// NOTE currently just to get compiler warnings if the host code is invalid. // NOTE currently just to get compiler warnings if the host code is invalid.
// the produced artifact is not used // the produced artifact is not used
// //
@ -204,7 +234,8 @@ pub fn build_zig_host_wasm32(
// we'd like to compile with `-target wasm32-wasi` but that is blocked on // we'd like to compile with `-target wasm32-wasi` but that is blocked on
// //
// https://github.com/ziglang/zig/issues/9414 // https://github.com/ziglang/zig/issues/9414
Command::new("zig") let mut command = Command::new("zig");
command
.env_clear() .env_clear()
.env("PATH", env_path) .env("PATH", env_path)
.env("HOME", env_home) .env("HOME", env_home)
@ -226,21 +257,67 @@ pub fn build_zig_host_wasm32(
// "wasm32-wasi", // "wasm32-wasi",
// "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll", // "-femit-llvm-ir=/home/folkertdev/roc/roc/examples/benchmarks/platform/host.ll",
"-fPIC", "-fPIC",
"-O", "--strip",
"ReleaseSafe", ]);
]) if matches!(opt_level, OptLevel::Optimize) {
.output() command.args(&["-O", "ReleaseSafe"]);
.unwrap() }
command.output().unwrap()
} }
pub fn rebuild_host(target: &Triple, host_input_path: &Path) { pub fn build_c_host_native(
env_path: &str,
env_home: &str,
dest: &str,
sources: &[&str],
opt_level: OptLevel,
shared_lib_path: Option<&Path>,
) -> Output {
let mut command = Command::new("clang");
command
.env_clear()
.env("PATH", &env_path)
.env("HOME", &env_home)
.args(sources)
.args(&["-o", dest]);
if let Some(shared_lib_path) = shared_lib_path {
command.args(&[
shared_lib_path.to_str().unwrap(),
bitcode::OBJ_PATH,
"-fPIE",
"-pie",
"-lm",
"-lpthread",
"-ldl",
"-lrt",
"-lutil",
]);
} else {
command.args(&["-fPIC", "-c"]);
}
if matches!(opt_level, OptLevel::Optimize) {
command.arg("-O2");
}
command.output().unwrap()
}
pub fn rebuild_host(
opt_level: OptLevel,
target: &Triple,
host_input_path: &Path,
shared_lib_path: Option<&Path>,
) {
let c_host_src = host_input_path.with_file_name("host.c"); let c_host_src = host_input_path.with_file_name("host.c");
let c_host_dest = host_input_path.with_file_name("c_host.o"); let c_host_dest = host_input_path.with_file_name("c_host.o");
let zig_host_src = host_input_path.with_file_name("host.zig"); let zig_host_src = host_input_path.with_file_name("host.zig");
let rust_host_src = host_input_path.with_file_name("host.rs"); let rust_host_src = host_input_path.with_file_name("host.rs");
let rust_host_dest = host_input_path.with_file_name("rust_host.o"); let rust_host_dest = host_input_path.with_file_name("rust_host.o");
let cargo_host_src = host_input_path.with_file_name("Cargo.toml"); let cargo_host_src = host_input_path.with_file_name("Cargo.toml");
let host_dest_native = host_input_path.with_file_name("host.o"); let host_dest_native = host_input_path.with_file_name(if shared_lib_path.is_some() {
"dynhost"
} else {
"host.o"
});
let host_dest_wasm = host_input_path.with_file_name("host.bc"); let host_dest_wasm = host_input_path.with_file_name("host.bc");
let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string()); let env_path = env::var("PATH").unwrap_or_else(|_| "".to_string());
@ -266,6 +343,8 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
&emit_bin, &emit_bin,
zig_host_src.to_str().unwrap(), zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(), zig_str_path.to_str().unwrap(),
opt_level,
shared_lib_path,
) )
} }
Architecture::X86_64 => { Architecture::X86_64 => {
@ -277,6 +356,8 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
zig_host_src.to_str().unwrap(), zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(), zig_str_path.to_str().unwrap(),
"native", "native",
opt_level,
shared_lib_path,
) )
} }
Architecture::X86_32(_) => { Architecture::X86_32(_) => {
@ -288,89 +369,142 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
zig_host_src.to_str().unwrap(), zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(), zig_str_path.to_str().unwrap(),
"i386-linux-musl", "i386-linux-musl",
opt_level,
shared_lib_path,
) )
} }
_ => panic!("Unsupported architecture {:?}", target.architecture), _ => panic!("Unsupported architecture {:?}", target.architecture),
}; };
validate_output("host.zig", "zig", output) validate_output("host.zig", "zig", output)
} else { } else if cargo_host_src.exists() {
// Compile host.c
let output = Command::new("clang")
.env_clear()
.env("PATH", &env_path)
.args(&[
"-O2",
"-fPIC",
"-c",
c_host_src.to_str().unwrap(),
"-o",
c_host_dest.to_str().unwrap(),
])
.output()
.unwrap();
validate_output("host.c", "clang", output);
}
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 = cargo_dir.join("target").join("release"); let cargo_out_dir =
cargo_dir
.join("target")
.join(if matches!(opt_level, OptLevel::Optimize) {
"release"
} else {
"debug"
});
let output = Command::new("cargo") let mut command = Command::new("cargo");
.args(&["build", "--release"]) command.arg("build").current_dir(cargo_dir);
.current_dir(cargo_dir) if matches!(opt_level, OptLevel::Optimize) {
.output() command.arg("--release");
.unwrap(); }
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();
validate_output("src/lib.rs", "cargo build --release", output); validate_output(source_file, "cargo build", output);
let output = Command::new("ld") if shared_lib_path.is_some() {
.env_clear() // For surgical linking, just copy the dynamically linked rust app.
.env("PATH", &env_path) std::fs::copy(cargo_out_dir.join("host"), host_dest_native).unwrap();
.args(&[ } else {
"-r", // Cargo hosts depend on a c wrapper for the api. Compile host.c as well.
"-L",
libhost_dir.to_str().unwrap(), let output = build_c_host_native(
&env_path,
&env_home,
c_host_dest.to_str().unwrap(), c_host_dest.to_str().unwrap(),
"-lhost", &[c_host_src.to_str().unwrap()],
"-o", opt_level,
host_dest_native.to_str().unwrap(), shared_lib_path,
]) );
.output() validate_output("host.c", "clang", output);
.unwrap();
validate_output("c_host.o", "ld", output); let output = Command::new("ld")
.env_clear()
.env("PATH", &env_path)
.args(&[
"-r",
"-L",
cargo_out_dir.to_str().unwrap(),
c_host_dest.to_str().unwrap(),
"-lhost",
"-o",
host_dest_native.to_str().unwrap(),
])
.output()
.unwrap();
validate_output("c_host.o", "ld", output);
// Clean up c_host.o
let output = Command::new("rm")
.env_clear()
.args(&["-f", c_host_dest.to_str().unwrap()])
.output()
.unwrap();
validate_output("rust_host.o", "rm", output);
}
} else if rust_host_src.exists() { } else if rust_host_src.exists() {
// Compile and link host.rs, if it exists // Compile and link host.rs, if it exists
let output = Command::new("rustc") let mut command = Command::new("rustc");
.args(&[ command.args(&[
rust_host_src.to_str().unwrap(), rust_host_src.to_str().unwrap(),
"-o", "-o",
rust_host_dest.to_str().unwrap(), rust_host_dest.to_str().unwrap(),
]) ]);
.output() if matches!(opt_level, OptLevel::Optimize) {
.unwrap(); command.arg("-O");
}
let output = command.output().unwrap();
validate_output("host.rs", "rustc", output); validate_output("host.rs", "rustc", output);
let output = Command::new("ld") // Rust hosts depend on a c wrapper for the api. Compile host.c as well.
.env_clear() if shared_lib_path.is_some() {
.env("PATH", &env_path) // If compiling to executable, let c deal with linking as well.
.args(&[ let output = build_c_host_native(
"-r", &env_path,
c_host_dest.to_str().unwrap(), &env_home,
rust_host_dest.to_str().unwrap(),
"-o",
host_dest_native.to_str().unwrap(), host_dest_native.to_str().unwrap(),
]) &[
.output() c_host_src.to_str().unwrap(),
.unwrap(); rust_host_dest.to_str().unwrap(),
],
opt_level,
shared_lib_path,
);
validate_output("host.c", "clang", output);
} else {
let output = build_c_host_native(
&env_path,
&env_home,
c_host_dest.to_str().unwrap(),
&[c_host_src.to_str().unwrap()],
opt_level,
shared_lib_path,
);
validate_output("rust_host.o", "ld", output); validate_output("host.c", "clang", output);
let output = Command::new("ld")
.env_clear()
.env("PATH", &env_path)
.args(&[
"-r",
c_host_dest.to_str().unwrap(),
rust_host_dest.to_str().unwrap(),
"-o",
host_dest_native.to_str().unwrap(),
])
.output()
.unwrap();
// Clean up rust_host.o validate_output("rust_host.o", "ld", output);
}
// Clean up rust_host.o and c_host.o
let output = Command::new("rm") let output = Command::new("rm")
.env_clear() .env_clear()
.args(&[ .args(&[
@ -382,15 +516,17 @@ pub fn rebuild_host(target: &Triple, host_input_path: &Path) {
.unwrap(); .unwrap();
validate_output("rust_host.o", "rm", output); validate_output("rust_host.o", "rm", output);
} else if c_host_dest.exists() { } else if c_host_src.exists() {
// Clean up c_host.o // Compile host.c, if it exists
let output = Command::new("mv") let output = build_c_host_native(
.env_clear() &env_path,
.args(&[c_host_dest, host_dest_native]) &env_home,
.output() host_dest_native.to_str().unwrap(),
.unwrap(); &[c_host_src.to_str().unwrap()],
opt_level,
validate_output("c_host.o", "mv", output); shared_lib_path,
);
validate_output("host.c", "clang", output);
} }
} }

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

@ -6,7 +6,6 @@ interface List
get, get,
set, set,
append, append,
map,
len, len,
walkBackwards, walkBackwards,
concat, concat,
@ -23,14 +22,18 @@ interface List
last, last,
keepOks, keepOks,
keepErrs, keepErrs,
mapWithIndex, map,
map2, map2,
map3, map3,
mapWithIndex,
mapOrDrop,
mapJoin,
product, product,
walkUntil, walkUntil,
range, range,
sortWith, sortWith,
drop, drop,
dropAt,
swap swap
] ]
imports [] imports []
@ -250,6 +253,21 @@ sortDesc : List elem, (elem -> Num *) -> List elem
## See for example `Set.map`, `Dict.map`, and [Result.map]. ## See for example `Set.map`, `Dict.map`, and [Result.map].
map : List before, (before -> after) -> List after map : List before, (before -> after) -> List after
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
##
## Some languages have a function named `zip`, which does something similar to
## calling [List.map2] passing two lists and `Pair`:
##
## >>> zipped = List.map2 [ "a", "b", "c" ] [ 1, 2, 3 ] Pair
map2 : List a, List b, (a, b -> c) -> List c
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## This works like [List.map], except it also passes the index ## This works like [List.map], except it also passes the index
## of the element to the conversion function. ## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after mapWithIndex : List before, (before, Nat -> after) -> List after
@ -258,6 +276,18 @@ mapWithIndex : List before, (before, Nat -> after) -> List after
## cancel the entire operation immediately, and return that #Err. ## cancel the entire operation immediately, and return that #Err.
mapOrCancel : List before, (before -> Result after err) -> Result (List after) err mapOrCancel : List before, (before -> Result after err) -> Result (List after) err
## Like [List.map], except the transformation function specifies whether to
## `Keep` or `Drop` each element from the final [List].
##
## You may know a similar function named `filterMap` in other languages.
mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
##
## You may know a similar function named `concatMap` in other languages.
mapJoin : List before, (before -> List after) -> List after
## This works like [List.map], except only the transformed values that are ## This works like [List.map], except only the transformed values that are
## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. ## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped.
## ##
@ -322,10 +352,6 @@ concat : List elem, List elem -> List elem
## >>> List.join [] ## >>> List.join []
join : List (List elem) -> List elem join : List (List elem) -> List elem
## Like [List.map], except the transformation function wraps the return value
## in a list. At the end, all the lists get joined together into one list.
joinMap : List before, (before -> List after) -> List after
## Like [List.join], but only keeps elements tagged with `Ok`. Elements ## Like [List.join], but only keeps elements tagged with `Ok`. Elements
## tagged with `Err` are dropped. ## tagged with `Err` are dropped.
## ##
@ -341,28 +367,6 @@ joinMap : List before, (before -> List after) -> List after
## so we're sticking with `Result` for now. ## so we're sticking with `Result` for now.
oks : List (Result elem *) -> List elem oks : List (Result elem *) -> List elem
## Iterates over the shortest of the given lists and returns a list of `Pair`
## tags, each wrapping one of the elements in that list, along with the elements
## in the same index in # the other lists.
##
## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ]
##
## Accepts up to 8 lists.
##
## > For a generalized version that returns whatever you like, instead of a `Pair`,
## > see `zipMap`.
zip : List a, List b -> List [ Pair a b ]*
## Like `zip` but you can specify what to do with each element.
##
## More specifically, [repeat what zip's docs say here]
##
## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3
##
## Accepts up to 8 lists.
zipMap : List a, List b, (a, b -> c) -> List c
## Filter ## Filter
## Run the given function on each element of a list, and return all the ## Run the given function on each element of a list, and return all the
@ -422,15 +426,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

@ -82,6 +82,7 @@ pub fn canonicalize_annotation(
let mut introduced_variables = IntroducedVariables::default(); let mut introduced_variables = IntroducedVariables::default();
let mut references = MutSet::default(); let mut references = MutSet::default();
let mut aliases = SendMap::default(); let mut aliases = SendMap::default();
let typ = can_annotation_help( let typ = can_annotation_help(
env, env,
annotation, annotation,
@ -249,28 +250,7 @@ fn can_annotation_help(
actual: Box::new(actual), actual: Box::new(actual),
} }
} }
None => { None => Type::Apply(symbol, args),
let mut args = Vec::new();
references.insert(symbol);
for arg in *type_arguments {
let arg_ann = can_annotation_help(
env,
&arg.value,
region,
scope,
var_store,
introduced_variables,
local_aliases,
references,
);
args.push(arg_ann);
}
Type::Apply(symbol, args)
}
} }
} }
BoundVariable(v) => { BoundVariable(v) => {

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

@ -2,15 +2,14 @@ use crate::{Backend, Env, Relocation};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_collections::all::{MutMap, MutSet}; use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_mono::ir::{BranchInfo, Literal, Stmt}; use roc_mono::ir::{BranchInfo, JoinPointId, Literal, Param, SelfRecursive, Stmt};
use roc_mono::layout::{Builtin, Layout}; use roc_mono::layout::{Builtin, Layout};
use std::marker::PhantomData; use std::marker::PhantomData;
use target_lexicon::Triple;
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];
@ -49,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.
@ -211,12 +211,16 @@ pub struct Backend64Bit<
env: &'a Env<'a>, env: &'a Env<'a>,
buf: Vec<'a, u8>, buf: Vec<'a, u8>,
relocs: Vec<'a, Relocation>, relocs: Vec<'a, Relocation>,
proc_name: Option<String>,
is_self_recursive: Option<SelfRecursive>,
last_seen_map: MutMap<Symbol, *const Stmt<'a>>, last_seen_map: MutMap<Symbol, *const Stmt<'a>>,
layout_map: MutMap<Symbol, *const Layout<'a>>, layout_map: MutMap<Symbol, Layout<'a>>,
free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>, free_map: MutMap<*const Stmt<'a>, Vec<'a, Symbol>>,
symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>, symbol_storage_map: MutMap<Symbol, SymbolStorage<GeneralReg, FloatReg>>,
literal_map: MutMap<Symbol, Literal<'a>>, literal_map: MutMap<Symbol, Literal<'a>>,
join_map: MutMap<JoinPointId, u64>,
// This should probably be smarter than a vec. // This should probably be smarter than a vec.
// There are certain registers we should always use first. With pushing and popping, this could get mixed. // There are certain registers we should always use first. With pushing and popping, this could get mixed.
@ -247,11 +251,13 @@ impl<
CC: CallConv<GeneralReg, FloatReg>, CC: CallConv<GeneralReg, FloatReg>,
> Backend<'a> for Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC> > Backend<'a> for Backend64Bit<'a, GeneralReg, FloatReg, ASM, CC>
{ {
fn new(env: &'a Env, _target: &Triple) -> Result<Self, String> { fn new(env: &'a Env) -> Result<Self, String> {
Ok(Backend64Bit { Ok(Backend64Bit {
phantom_asm: PhantomData, phantom_asm: PhantomData,
phantom_cc: PhantomData, phantom_cc: PhantomData,
env, env,
proc_name: None,
is_self_recursive: None,
buf: bumpalo::vec![in env.arena], buf: bumpalo::vec![in env.arena],
relocs: bumpalo::vec![in env.arena], relocs: bumpalo::vec![in env.arena],
last_seen_map: MutMap::default(), last_seen_map: MutMap::default(),
@ -259,6 +265,7 @@ impl<
free_map: MutMap::default(), free_map: MutMap::default(),
symbol_storage_map: MutMap::default(), symbol_storage_map: MutMap::default(),
literal_map: MutMap::default(), literal_map: MutMap::default(),
join_map: MutMap::default(),
general_free_regs: bumpalo::vec![in env.arena], general_free_regs: bumpalo::vec![in env.arena],
general_used_regs: bumpalo::vec![in env.arena], general_used_regs: bumpalo::vec![in env.arena],
general_used_callee_saved_regs: MutSet::default(), general_used_callee_saved_regs: MutSet::default(),
@ -275,12 +282,15 @@ impl<
self.env self.env
} }
fn reset(&mut self) { fn reset(&mut self, name: String, is_self_recursive: SelfRecursive) {
self.proc_name = Some(name);
self.is_self_recursive = Some(is_self_recursive);
self.stack_size = 0; self.stack_size = 0;
self.free_stack_chunks.clear(); self.free_stack_chunks.clear();
self.fn_call_stack_size = 0; self.fn_call_stack_size = 0;
self.last_seen_map.clear(); self.last_seen_map.clear();
self.layout_map.clear(); self.layout_map.clear();
self.join_map.clear();
self.free_map.clear(); self.free_map.clear();
self.symbol_storage_map.clear(); self.symbol_storage_map.clear();
self.buf.clear(); self.buf.clear();
@ -304,7 +314,7 @@ impl<
&mut self.last_seen_map &mut self.last_seen_map
} }
fn layout_map(&mut self) -> &mut MutMap<Symbol, *const Layout<'a>> { fn layout_map(&mut self) -> &mut MutMap<Symbol, Layout<'a>> {
&mut self.layout_map &mut self.layout_map
} }
@ -330,8 +340,49 @@ impl<
)?; )?;
let setup_offset = out.len(); let setup_offset = out.len();
// Deal with jumps to the return address.
let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]);
// Check if their is an unnessary jump to return right at the end of the function.
let mut end_jmp_size = 0;
for reloc in old_relocs
.iter()
.filter(|reloc| matches!(reloc, Relocation::JmpToReturn { .. }))
{
if let Relocation::JmpToReturn {
inst_loc,
inst_size,
..
} = reloc
{
if *inst_loc as usize + *inst_size as usize == self.buf.len() {
end_jmp_size = *inst_size as usize;
break;
}
}
}
// Update jumps to returns.
let ret_offset = self.buf.len() - end_jmp_size;
let mut tmp = bumpalo::vec![in self.env.arena];
for reloc in old_relocs
.iter()
.filter(|reloc| matches!(reloc, Relocation::JmpToReturn { .. }))
{
if let Relocation::JmpToReturn {
inst_loc,
inst_size,
offset,
} = reloc
{
if *inst_loc as usize + *inst_size as usize != self.buf.len() {
self.update_jmp_imm32_offset(&mut tmp, *inst_loc, *offset, ret_offset as u64);
}
}
}
// Add function body. // Add function body.
out.extend(&self.buf); out.extend(&self.buf[..self.buf.len() - end_jmp_size]);
// Cleanup stack. // Cleanup stack.
CC::cleanup_stack( CC::cleanup_stack(
@ -342,23 +393,28 @@ impl<
)?; )?;
ASM::ret(&mut out); ASM::ret(&mut out);
// Update relocs to include stack setup offset. // Update other relocs to include stack setup offset.
let mut out_relocs = bumpalo::vec![in self.env.arena]; let mut out_relocs = bumpalo::vec![in self.env.arena];
let old_relocs = std::mem::replace(&mut self.relocs, bumpalo::vec![in self.env.arena]); out_relocs.extend(
out_relocs.extend(old_relocs.into_iter().map(|reloc| match reloc { old_relocs
Relocation::LocalData { offset, data } => Relocation::LocalData { .into_iter()
offset: offset + setup_offset as u64, .filter(|reloc| !matches!(reloc, Relocation::JmpToReturn { .. }))
data, .map(|reloc| match reloc {
}, Relocation::LocalData { offset, data } => Relocation::LocalData {
Relocation::LinkedData { offset, name } => Relocation::LinkedData { offset: offset + setup_offset as u64,
offset: offset + setup_offset as u64, data,
name, },
}, Relocation::LinkedData { offset, name } => Relocation::LinkedData {
Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction { offset: offset + setup_offset as u64,
offset: offset + setup_offset as u64, name,
name, },
}, Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction {
})); offset: offset + setup_offset as u64,
name,
},
Relocation::JmpToReturn { .. } => unreachable!(),
}),
);
Ok((out.into_bump_slice(), out_relocs.into_bump_slice())) Ok((out.into_bump_slice(), out_relocs.into_bump_slice()))
} }
@ -367,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 {
@ -401,29 +462,13 @@ impl<
arg_layouts: &[Layout<'a>], arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String> { ) -> Result<(), String> {
if let Some(SelfRecursive::SelfRecursive(id)) = self.is_self_recursive {
if &fn_name == self.proc_name.as_ref().unwrap() && self.join_map.contains_key(&id) {
return self.build_jump(&id, args, arg_layouts, ret_layout);
}
}
// Save used caller saved regs. // Save used caller saved regs.
let old_general_used_regs = std::mem::replace( self.push_used_caller_saved_regs_to_stack()?;
&mut self.general_used_regs,
bumpalo::vec![in self.env.arena],
);
for (reg, saved_sym) in old_general_used_regs.into_iter() {
if CC::general_caller_saved(&reg) {
self.general_free_regs.push(reg);
self.free_to_stack(&saved_sym)?;
} else {
self.general_used_regs.push((reg, saved_sym));
}
}
let old_float_used_regs =
std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]);
for (reg, saved_sym) in old_float_used_regs.into_iter() {
if CC::float_caller_saved(&reg) {
self.float_free_regs.push(reg);
self.free_to_stack(&saved_sym)?;
} else {
self.float_used_regs.push((reg, saved_sym));
}
}
// Put values in param regs or on top of the stack. // Put values in param regs or on top of the stack.
let tmp_stack_size = CC::store_args( let tmp_stack_size = CC::store_args(
@ -450,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
@ -486,7 +550,7 @@ impl<
// Build unconditional jump to the end of this switch. // Build unconditional jump to the end of this switch.
// Since we don't know the offset yet, set it to 0 and overwrite later. // Since we don't know the offset yet, set it to 0 and overwrite later.
let jmp_location = self.buf.len(); let jmp_location = self.buf.len();
let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0); let jmp_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
ret_jumps.push((jmp_location, jmp_offset)); ret_jumps.push((jmp_location, jmp_offset));
// Overwite the original jne with the correct offset. // Overwite the original jne with the correct offset.
@ -510,12 +574,12 @@ impl<
// Update all return jumps to jump past the default case. // Update all return jumps to jump past the default case.
let ret_offset = self.buf.len(); let ret_offset = self.buf.len();
for (jmp_location, start_offset) in ret_jumps.into_iter() { for (jmp_location, start_offset) in ret_jumps.into_iter() {
tmp.clear(); self.update_jmp_imm32_offset(
let jmp_offset = ret_offset - start_offset; &mut tmp,
ASM::jmp_imm32(&mut tmp, jmp_offset as i32); jmp_location as u64,
for (i, byte) in tmp.iter().enumerate() { start_offset as u64,
self.buf[jmp_location + i] = *byte; ret_offset as u64,
} );
} }
Ok(()) Ok(())
} else { } else {
@ -526,6 +590,134 @@ impl<
} }
} }
fn build_join(
&mut self,
id: &JoinPointId,
parameters: &'a [Param<'a>],
body: &'a Stmt<'a>,
remainder: &'a Stmt<'a>,
ret_layout: &Layout<'a>,
) -> Result<(), String> {
// Create jump to remaining.
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
// This section can essentially be seen as a sub function within the main function.
// Thus we build using a new backend with some minor extra synchronization.
let mut sub_backend = Self::new(self.env)?;
sub_backend.reset(
self.proc_name.as_ref().unwrap().clone(),
self.is_self_recursive.as_ref().unwrap().clone(),
);
// Sync static maps of important information.
sub_backend.last_seen_map = self.last_seen_map.clone();
sub_backend.layout_map = self.layout_map.clone();
sub_backend.free_map = self.free_map.clone();
// Setup join point.
sub_backend.join_map.insert(*id, 0);
self.join_map.insert(*id, self.buf.len() as u64);
// Sync stack size so the "sub function" doesn't mess up our stack.
sub_backend.stack_size = self.stack_size;
sub_backend.fn_call_stack_size = self.fn_call_stack_size;
// Load params as if they were args.
let mut args = bumpalo::vec![in self.env.arena];
for param in parameters {
args.push((param.layout, param.symbol));
}
sub_backend.load_args(args.into_bump_slice(), ret_layout)?;
// Build all statements in body.
sub_backend.build_stmt(body, ret_layout)?;
// Merge the "sub function" into the main function.
let sub_func_offset = self.buf.len() as u64;
self.buf.extend_from_slice(&sub_backend.buf);
// Update stack based on how much was used by the sub function.
self.stack_size = sub_backend.stack_size;
self.fn_call_stack_size = sub_backend.fn_call_stack_size;
// Relocations must be shifted to be merged correctly.
self.relocs
.extend(sub_backend.relocs.into_iter().map(|reloc| match reloc {
Relocation::LocalData { offset, data } => Relocation::LocalData {
offset: offset + sub_func_offset,
data,
},
Relocation::LinkedData { offset, name } => Relocation::LinkedData {
offset: offset + sub_func_offset,
name,
},
Relocation::LinkedFunction { offset, name } => Relocation::LinkedFunction {
offset: offset + sub_func_offset,
name,
},
Relocation::JmpToReturn {
inst_loc,
inst_size,
offset,
} => Relocation::JmpToReturn {
inst_loc: inst_loc + sub_func_offset,
inst_size,
offset: offset + sub_func_offset,
},
}));
// Overwite the original jump with the correct offset.
let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset(
&mut tmp,
jmp_location as u64,
start_offset as u64,
self.buf.len() as u64,
);
// Build remainder of function.
self.build_stmt(remainder, ret_layout)
}
fn build_jump(
&mut self,
id: &JoinPointId,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
// Treat this like a function call, but with a jump instead of a call instruction at the end.
self.push_used_caller_saved_regs_to_stack()?;
let tmp_stack_size = CC::store_args(
&mut self.buf,
&self.symbol_storage_map,
args,
arg_layouts,
ret_layout,
)?;
self.fn_call_stack_size = std::cmp::max(self.fn_call_stack_size, tmp_stack_size);
let jmp_location = self.buf.len();
let start_offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678);
if let Some(offset) = self.join_map.get(id) {
let offset = *offset;
let mut tmp = bumpalo::vec![in self.env.arena];
self.update_jmp_imm32_offset(
&mut tmp,
jmp_location as u64,
start_offset as u64,
offset,
);
Ok(())
} else {
Err(format!(
"Jump: unknown point specified to jump to: {:?}",
id
))
}
}
fn build_num_abs( fn build_num_abs(
&mut self, &mut self,
dst: &Symbol, dst: &Symbol,
@ -726,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)),
} }
} }
@ -828,29 +1049,39 @@ impl<
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> { fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String> {
let val = self.symbol_storage_map.get(sym); let val = self.symbol_storage_map.get(sym);
match val { match val {
Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => Ok(()), Some(SymbolStorage::GeneralReg(reg)) if *reg == CC::GENERAL_RETURN_REGS[0] => {}
Some(SymbolStorage::GeneralReg(reg)) => { Some(SymbolStorage::GeneralReg(reg)) => {
// If it fits in a general purpose register, just copy it over to. // If it fits in a general purpose register, just copy it over to.
// Technically this can be optimized to produce shorter instructions if less than 64bits. // Technically this can be optimized to produce shorter instructions if less than 64bits.
ASM::mov_reg64_reg64(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *reg); ASM::mov_reg64_reg64(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *reg);
Ok(())
} }
Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => Ok(()), Some(SymbolStorage::FloatReg(reg)) if *reg == CC::FLOAT_RETURN_REGS[0] => {}
Some(SymbolStorage::FloatReg(reg)) => { Some(SymbolStorage::FloatReg(reg)) => {
ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg); ASM::mov_freg64_freg64(&mut self.buf, CC::FLOAT_RETURN_REGS[0], *reg);
Ok(())
} }
Some(SymbolStorage::Base { offset, size, .. }) => match layout { Some(SymbolStorage::Base { offset, size, .. }) => match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(Builtin::Int64) => {
ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset); ASM::mov_reg64_base32(&mut self.buf, CC::GENERAL_RETURN_REGS[0], *offset);
Ok(())
} }
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);
Ok(()) }
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
if size > 0 { if size > 0 {
let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER) let ret_reg = if self.symbol_storage_map.contains_key(&Symbol::RET_POINTER)
{ {
@ -858,23 +1089,34 @@ impl<
} else { } else {
None None
}; };
CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg) CC::return_struct(&mut self.buf, offset, size, field_layouts, ret_reg)?;
} else {
// Nothing to do for empty struct
Ok(())
} }
} }
x => Err(format!( x => {
"returning symbol with layout, {:?}, is not yet implemented", return Err(format!(
x "returning symbol with layout, {:?}, is not yet implemented",
)), x
));
}
}, },
Some(x) => Err(format!( Some(x) => {
"returning symbol storage, {:?}, is not yet implemented", return Err(format!(
x "returning symbol storage, {:?}, is not yet implemented",
)), x
None => Err(format!("Unknown return symbol: {}", sym)), ));
}
None => {
return Err(format!("Unknown return symbol: {}", sym));
}
} }
let inst_loc = self.buf.len() as u64;
let offset = ASM::jmp_imm32(&mut self.buf, 0x1234_5678) as u64;
self.relocs.push(Relocation::JmpToReturn {
inst_loc,
inst_size: self.buf.len() as u64 - inst_loc,
offset,
});
Ok(())
} }
} }
@ -1212,4 +1454,72 @@ impl<
)), )),
} }
} }
fn push_used_caller_saved_regs_to_stack(&mut self) -> Result<(), String> {
let old_general_used_regs = std::mem::replace(
&mut self.general_used_regs,
bumpalo::vec![in self.env.arena],
);
for (reg, saved_sym) in old_general_used_regs.into_iter() {
if CC::general_caller_saved(&reg) {
self.general_free_regs.push(reg);
self.free_to_stack(&saved_sym)?;
} else {
self.general_used_regs.push((reg, saved_sym));
}
}
let old_float_used_regs =
std::mem::replace(&mut self.float_used_regs, bumpalo::vec![in self.env.arena]);
for (reg, saved_sym) in old_float_used_regs.into_iter() {
if CC::float_caller_saved(&reg) {
self.float_free_regs.push(reg);
self.free_to_stack(&saved_sym)?;
} else {
self.float_used_regs.push((reg, saved_sym));
}
}
Ok(())
}
// Updates a jump instruction to a new offset and returns the number of bytes written.
fn update_jmp_imm32_offset(
&mut self,
tmp: &mut Vec<'a, u8>,
jmp_location: u64,
base_offset: u64,
target_offset: u64,
) {
tmp.clear();
let jmp_offset = target_offset as i32 - base_offset as i32;
ASM::jmp_imm32(tmp, jmp_offset);
for (i, byte) in tmp.iter().enumerate() {
self.buf[jmp_location as usize + i] = *byte;
}
}
}
#[macro_export]
macro_rules! single_register_integers {
() => {
Builtin::Int1
| Builtin::Int8
| Builtin::Int16
| Builtin::Int32
| Builtin::Int64
| Builtin::Usize
};
}
#[macro_export]
macro_rules! single_register_floats {
() => {
Builtin::Float32 | Builtin::Float64
};
}
#[macro_export]
macro_rules! single_register_builtins {
() => {
single_register_integers!() | single_register_floats!()
};
} }

View file

@ -1,5 +1,7 @@
use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE}; use crate::generic64::{Assembler, CallConv, RegTrait, SymbolStorage, PTR_SIZE};
use crate::Relocation; use crate::{
single_register_builtins, single_register_floats, single_register_integers, Relocation,
};
use bumpalo::collections::Vec; use bumpalo::collections::Vec;
use roc_collections::all::MutMap; use roc_collections::all::MutMap;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
@ -175,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>,
@ -191,7 +194,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
} }
for (layout, sym) in args.iter() { for (layout, sym) in args.iter() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(single_register_integers!()) => {
if general_i < Self::GENERAL_PARAM_REGS.len() { if general_i < Self::GENERAL_PARAM_REGS.len() {
symbol_map.insert( symbol_map.insert(
*sym, *sym,
@ -210,7 +213,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
); );
} }
} }
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(single_register_floats!()) => {
if float_i < Self::FLOAT_PARAM_REGS.len() { if float_i < Self::FLOAT_PARAM_REGS.len() {
symbol_map.insert( symbol_map.insert(
*sym, *sym,
@ -229,6 +232,30 @@ 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(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"Loading args with layout {:?} not yet implementd", "Loading args with layout {:?} not yet implementd",
@ -254,8 +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(Builtin::Int64) => {} Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
Layout::Builtin(Builtin::Float64) => {}
x => { x => {
return Err(format!( return Err(format!(
"receiving return type, {:?}, is not yet implemented", "receiving return type, {:?}, is not yet implemented",
@ -265,7 +291,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
} }
for (i, layout) in arg_layouts.iter().enumerate() { for (i, layout) in arg_layouts.iter().enumerate() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { Layout::Builtin(single_register_integers!()) => {
if general_i < Self::GENERAL_PARAM_REGS.len() { if general_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[general_i]; let dst = Self::GENERAL_PARAM_REGS[general_i];
@ -319,7 +345,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Float64) => { Layout::Builtin(single_register_floats!()) => {
if float_i < Self::FLOAT_PARAM_REGS.len() { if float_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[float_i]; let dst = Self::FLOAT_PARAM_REGS[float_i];
@ -371,6 +397,33 @@ 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(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"calling with arg type, {:?}, is not yet implemented", "calling with arg type, {:?}, is not yet implemented",
@ -513,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>,
@ -529,13 +583,23 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
for (layout, sym) in args.iter() { for (layout, sym) in args.iter() {
if i < Self::GENERAL_PARAM_REGS.len() { if i < Self::GENERAL_PARAM_REGS.len() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { 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(Builtin::Float64) => { 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(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"Loading args with layout {:?} not yet implementd", "Loading args with layout {:?} not yet implementd",
@ -543,11 +607,9 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
)); ));
} }
} }
i += 1;
} else { } else {
base_offset += match layout { base_offset += match layout {
Layout::Builtin(Builtin::Int64) => 8, Layout::Builtin(single_register_builtins!()) => 8,
Layout::Builtin(Builtin::Float64) => 8,
x => { x => {
return Err(format!( return Err(format!(
"Loading args with layout {:?} not yet implemented", "Loading args with layout {:?} not yet implemented",
@ -577,12 +639,10 @@ 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 {
Layout::Builtin(Builtin::Int64) => {} Layout::Builtin(single_register_builtins!()) => {}
Layout::Builtin(Builtin::Float64) => {}
x => { x => {
return Err(format!( return Err(format!(
"receiving return type, {:?}, is not yet implemented", "receiving return type, {:?}, is not yet implemented",
@ -592,10 +652,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
} }
for (i, layout) in arg_layouts.iter().enumerate() { for (i, layout) in arg_layouts.iter().enumerate() {
match layout { match layout {
Layout::Builtin(Builtin::Int64) => { 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")?
@ -613,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
@ -646,10 +705,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
stack_offset += 8; stack_offset += 8;
} }
} }
Layout::Builtin(Builtin::Float64) => { 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")?
@ -666,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
@ -698,6 +756,13 @@ 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(&[]) => {}
x => { x => {
return Err(format!( return Err(format!(
"calling with arg type, {:?}, is not yet implemented", "calling with arg type, {:?}, is not yet implemented",

View file

@ -9,10 +9,10 @@ use roc_module::ident::{ModuleName, TagName};
use roc_module::low_level::LowLevel; use roc_module::low_level::LowLevel;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_mono::ir::{ use roc_mono::ir::{
BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Proc, Stmt, BranchInfo, CallType, Expr, JoinPointId, ListLiteralElement, Literal, Param, Proc,
SelfRecursive, Stmt,
}; };
use roc_mono::layout::{Builtin, Layout, LayoutIds}; use roc_mono::layout::{Builtin, Layout, LayoutIds};
use target_lexicon::Triple;
mod generic64; mod generic64;
mod object_builder; mod object_builder;
@ -46,6 +46,11 @@ pub enum Relocation {
offset: u64, offset: u64,
name: String, name: String,
}, },
JmpToReturn {
inst_loc: u64,
inst_size: u64,
offset: u64,
},
} }
trait Backend<'a> trait Backend<'a>
@ -53,12 +58,13 @@ where
Self: Sized, Self: Sized,
{ {
/// new creates a new backend that will output to the specific Object. /// new creates a new backend that will output to the specific Object.
fn new(env: &'a Env, target: &Triple) -> Result<Self, String>; fn new(env: &'a Env) -> Result<Self, String>;
fn env(&self) -> &'a Env<'a>; fn env(&self) -> &'a Env<'a>;
/// reset resets any registers or other values that may be occupied at the end of a procedure. /// reset resets any registers or other values that may be occupied at the end of a procedure.
fn reset(&mut self); /// It also passes basic procedure information to the builder for setup of the next function.
fn reset(&mut self, name: String, is_self_recursive: SelfRecursive);
/// finalize does any setup and cleanup that should happen around the procedure. /// finalize does any setup and cleanup that should happen around the procedure.
/// finalize does setup because things like stack size and jump locations are not know until the function is written. /// finalize does setup because things like stack size and jump locations are not know until the function is written.
@ -79,17 +85,16 @@ where
/// build_proc creates a procedure and outputs it to the wrapped object writer. /// build_proc creates a procedure and outputs it to the wrapped object writer.
fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> { fn build_proc(&mut self, proc: Proc<'a>) -> Result<(&'a [u8], &[Relocation]), String> {
self.reset(); let proc_name = LayoutIds::default()
.get(proc.name, &proc.ret_layout)
.to_symbol_string(proc.name, &self.env().interns);
self.reset(proc_name, proc.is_self_recursive);
self.load_args(proc.args, &proc.ret_layout)?; self.load_args(proc.args, &proc.ret_layout)?;
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()
} }
@ -110,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,
@ -128,6 +138,35 @@ where
self.free_symbols(stmt)?; self.free_symbols(stmt)?;
Ok(()) Ok(())
} }
Stmt::Join {
id,
parameters,
body,
remainder,
} => {
for param in parameters.iter() {
self.set_layout_map(param.symbol, &param.layout)?;
}
self.build_join(id, parameters, body, remainder, ret_layout)?;
self.free_symbols(stmt)?;
Ok(())
}
Stmt::Jump(id, args) => {
let mut arg_layouts: bumpalo::collections::Vec<Layout<'a>> =
bumpalo::vec![in self.env().arena];
arg_layouts.reserve(args.len());
let layout_map = self.layout_map();
for arg in *args {
if let Some(layout) = layout_map.get(arg) {
arg_layouts.push(*layout);
} else {
return Err(format!("the argument, {:?}, has no know layout", arg));
}
}
self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout)?;
self.free_symbols(stmt)?;
Ok(())
}
x => Err(format!("the statement, {:?}, is not yet implemented", x)), x => Err(format!("the statement, {:?}, is not yet implemented", x)),
} }
} }
@ -141,6 +180,25 @@ where
ret_layout: &Layout<'a>, ret_layout: &Layout<'a>,
) -> Result<(), String>; ) -> Result<(), String>;
// build_join generates a instructions for a join statement.
fn build_join(
&mut self,
id: &JoinPointId,
parameters: &'a [Param<'a>],
body: &'a Stmt<'a>,
remainder: &'a Stmt<'a>,
ret_layout: &Layout<'a>,
) -> Result<(), String>;
// build_jump generates a instructions for a jump statement.
fn build_jump(
&mut self,
id: &JoinPointId,
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String>;
/// build_expr builds the expressions for the specified symbol. /// build_expr builds the expressions for the specified symbol.
/// The builder must keep track of the symbol because it may be referred to later. /// The builder must keep track of the symbol because it may be referred to later.
fn build_expr( fn build_expr(
@ -241,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) =>
@ -263,8 +328,7 @@ where
let layout_map = self.layout_map(); let layout_map = self.layout_map();
for arg in *arguments { for arg in *arguments {
if let Some(layout) = layout_map.get(arg) { if let Some(layout) = layout_map.get(arg) {
// This is safe because every value in the map is always set with a valid layout and cannot be null. arg_layouts.push(*layout);
arg_layouts.push(unsafe { *(*layout) });
} else { } else {
return Err(format!("the argument, {:?}, has no know layout", arg)); return Err(format!("the argument, {:?}, has no know layout", arg));
} }
@ -414,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)),
} }
} }
@ -507,7 +578,7 @@ where
/// load_literal sets a symbol to be equal to a literal. /// load_literal sets a symbol to be equal to a literal.
fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>; fn load_literal(&mut self, sym: &Symbol, lit: &Literal<'a>) -> Result<(), String>;
/// return_symbol moves a symbol to the correct return location for the backend. /// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>; fn return_symbol(&mut self, sym: &Symbol, layout: &Layout<'a>) -> Result<(), String>;
/// free_symbols will free all symbols for the given statement. /// free_symbols will free all symbols for the given statement.
@ -542,12 +613,10 @@ where
/// set_layout_map sets the layout for a specific symbol. /// set_layout_map sets the layout for a specific symbol.
fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) -> Result<(), String> { fn set_layout_map(&mut self, sym: Symbol, layout: &Layout<'a>) -> Result<(), String> {
if let Some(x) = self.layout_map().insert(sym, layout) { if let Some(old_layout) = self.layout_map().insert(sym, *layout) {
// Layout map already contains the symbol. We should never need to overwrite. // Layout map already contains the symbol. We should never need to overwrite.
// If the layout is not the same, that is a bug. // If the layout is not the same, that is a bug.
// There is always an old layout value and this dereference is safe. if &old_layout != layout {
let old_layout = unsafe { *x };
if old_layout != *layout {
Err(format!( Err(format!(
"Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}", "Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}",
sym, layout, old_layout sym, layout, old_layout
@ -561,7 +630,7 @@ where
} }
/// layout_map gets the map from symbol to layout. /// layout_map gets the map from symbol to layout.
fn layout_map(&mut self) -> &mut MutMap<Symbol, *const Layout<'a>>; fn layout_map(&mut self) -> &mut MutMap<Symbol, Layout<'a>>;
fn create_free_map(&mut self) { fn create_free_map(&mut self) {
let mut free_map = MutMap::default(); let mut free_map = MutMap::default();

View file

@ -34,7 +34,7 @@ pub fn build_module<'a>(
x86_64::X86_64FloatReg, x86_64::X86_64FloatReg,
x86_64::X86_64Assembler, x86_64::X86_64Assembler,
x86_64::X86_64SystemV, x86_64::X86_64SystemV,
> = Backend::new(env, target)?; > = Backend::new(env)?;
build_object( build_object(
env, env,
procedures, procedures,
@ -52,7 +52,7 @@ pub fn build_module<'a>(
x86_64::X86_64FloatReg, x86_64::X86_64FloatReg,
x86_64::X86_64Assembler, x86_64::X86_64Assembler,
x86_64::X86_64SystemV, x86_64::X86_64SystemV,
> = Backend::new(env, target)?; > = Backend::new(env)?;
build_object( build_object(
env, env,
procedures, procedures,
@ -74,7 +74,7 @@ pub fn build_module<'a>(
aarch64::AArch64FloatReg, aarch64::AArch64FloatReg,
aarch64::AArch64Assembler, aarch64::AArch64Assembler,
aarch64::AArch64Call, aarch64::AArch64Call,
> = Backend::new(env, target)?; > = Backend::new(env)?;
build_object( build_object(
env, env,
procedures, procedures,
@ -304,6 +304,7 @@ fn build_object<'a, B: Backend<'a>>(
return Err(format!("failed to find fn symbol for {:?}", name)); return Err(format!("failed to find fn symbol for {:?}", name));
} }
} }
Relocation::JmpToReturn { .. } => unreachable!(),
}; };
relocations.push((section_id, elfreloc)); relocations.push((section_id, elfreloc));
} }

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;
@ -281,6 +275,24 @@ mod dev_num {
); );
} }
#[test]
fn gen_fast_fib_fn() {
assert_evals_to!(
indoc!(
r#"
fib = \n, a, b ->
if n == 0 then
a
else
fib (n - 1) b (a + b)
fib 10 0 1
"#
),
55,
i64
);
}
#[test] #[test]
fn f64_abs() { fn f64_abs() {
assert_evals_to!("Num.abs -4.7", 4.7, f64); assert_evals_to!("Num.abs -4.7", 4.7, f64);
@ -580,18 +592,18 @@ mod dev_num {
// assert_evals_to!("0.0 >= 0.0", true, bool); // assert_evals_to!("0.0 >= 0.0", true, bool);
// } // }
// #[test] #[test]
// fn gen_order_of_arithmetic_ops() { fn gen_order_of_arithmetic_ops() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// 1 + 3 * 7 - 2 1 + 3 * 7 - 2
// "# "#
// ), ),
// 20, 20,
// i64 i64
// ); );
// } }
// #[test] // #[test]
// fn gen_order_of_arithmetic_ops_complex_float() { // fn gen_order_of_arithmetic_ops_complex_float() {
@ -606,59 +618,59 @@ mod dev_num {
// ); // );
// } // }
// #[test] #[test]
// fn if_guard_bind_variable_false() { fn if_guard_bind_variable_false() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// wrapper = \{} -> wrapper = \{} ->
// when 10 is when 10 is
// x if x == 5 -> 0 x if x == 5 -> 0
// _ -> 42 _ -> 42
// wrapper {} wrapper {}
// "# "#
// ), ),
// 42, 42,
// i64 i64
// ); );
// } }
// #[test] #[test]
// fn if_guard_bind_variable_true() { fn if_guard_bind_variable_true() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// wrapper = \{} -> wrapper = \{} ->
// when 10 is when 10 is
// x if x == 10 -> 42 x if x == 10 -> 42
// _ -> 0 _ -> 0
// wrapper {} wrapper {}
// "# "#
// ), ),
// 42, 42,
// i64 i64
// ); );
// } }
// #[test] #[test]
// fn tail_call_elimination() { fn tail_call_elimination() {
// assert_evals_to!( assert_evals_to!(
// indoc!( indoc!(
// r#" r#"
// sum = \n, accum -> sum = \n, accum ->
// when n is when n is
// 0 -> accum 0 -> accum
// _ -> sum (n - 1) (n + accum) _ -> sum (n - 1) (n + accum)
// sum 1_000_000 0 sum 1_000_000 0
// "# "#
// ), ),
// 500000500000, 500000500000,
// i64 i64
// ); );
// } }
// #[test] // #[test]
// fn int_negate() { // fn int_negate() {

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};
@ -704,8 +705,14 @@ fn promote_to_main_function<'a, 'ctx, 'env>(
let main_fn_name = "$Test.main"; let main_fn_name = "$Test.main";
// Add main to the module. // Add main to the module.
let main_fn = let main_fn = expose_function_to_host_help_c_abi(
expose_function_to_host_help_c_abi(env, main_fn_name, roc_main_fn, &[], main_fn_name); env,
main_fn_name,
roc_main_fn,
top_level.arguments,
top_level.result,
main_fn_name,
);
(main_fn_name, main_fn) (main_fn_name, main_fn)
} }
@ -928,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,
@ -2179,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;
@ -2220,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());
} }
}; };
} }
@ -3075,6 +3088,7 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
symbol: Symbol, symbol: Symbol,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>], arguments: &[Layout<'a>],
return_layout: Layout<'a>,
) { ) {
// Assumption: there is only one specialization of a host-exposed function // Assumption: there is only one specialization of a host-exposed function
let ident_string = symbol.as_str(&env.interns); let ident_string = symbol.as_str(&env.interns);
@ -3085,26 +3099,19 @@ fn expose_function_to_host<'a, 'ctx, 'env>(
ident_string, ident_string,
roc_function, roc_function,
arguments, arguments,
return_layout,
&c_function_name, &c_function_name,
); );
} }
fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>( fn expose_function_to_host_help_c_abi_generic<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>, env: &Env<'a, 'ctx, 'env>,
ident_string: &str,
roc_function: FunctionValue<'ctx>, roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>], arguments: &[Layout<'a>],
c_function_name: &str, c_function_name: &str,
) -> FunctionValue<'ctx> { ) -> FunctionValue<'ctx> {
let context = env.context; // NOTE we ingore env.is_gen_test here
let wrapper_return_type = roc_function.get_type().get_return_type().unwrap();
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); let mut cc_argument_types = Vec::with_capacity_in(arguments.len(), env.arena);
for layout in arguments { for layout in arguments {
@ -3115,6 +3122,7 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
// let mut argument_types = roc_function.get_type().get_param_types(); // let mut argument_types = roc_function.get_type().get_param_types();
let mut argument_types = cc_argument_types; let mut argument_types = cc_argument_types;
let return_type = wrapper_return_type; let return_type = wrapper_return_type;
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());
@ -3142,9 +3150,11 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
debug_info_init!(env, c_function); debug_info_init!(env, c_function);
// drop the final argument, which is the pointer we write the result into // drop the final argument, which is the pointer we write the result into
let args = c_function.get_params(); let args_vector = c_function.get_params();
let output_arg_index = args.len() - 1; let mut args = args_vector.as_slice();
let args = &args[..args.len() - 1]; let args_length = args.len();
args = &args[..args.len() - 1];
let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena); let mut arguments_for_call = Vec::with_capacity_in(args.len(), env.arena);
@ -3189,19 +3199,309 @@ fn expose_function_to_host_help_c_abi<'a, 'ctx, 'env>(
let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap(); let call_unwrapped_result = call_unwrapped.try_as_basic_value().left().unwrap();
make_good_roc_result(env, call_unwrapped_result) // make_good_roc_result(env, call_unwrapped_result)
call_unwrapped_result
} }
}; };
let output_arg_index = args_length - 1;
let output_arg = c_function let output_arg = c_function
.get_nth_param(output_arg_index as u32) .get_nth_param(output_arg_index as u32)
.unwrap() .unwrap()
.into_pointer_value(); .into_pointer_value();
builder.build_store(output_arg, call_result); builder.build_store(output_arg, call_result);
builder.build_return(None); builder.build_return(None);
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>(
env: &Env<'a, 'ctx, 'env>,
ident_string: &str,
roc_function: FunctionValue<'ctx>,
arguments: &[Layout<'a>],
return_layout: Layout<'a>,
c_function_name: &str,
) -> FunctionValue<'ctx> {
let context = env.context;
if env.is_gen_test {
return expose_function_to_host_help_c_abi_gen_test(
env,
ident_string,
roc_function,
arguments,
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 {
context
.struct_type(
&[
context.i64_type().into(),
roc_function.get_type().get_return_type().unwrap(),
],
false,
)
.into()
} else {
roc_function.get_type().get_return_type().unwrap()
};
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 cc_return = to_cc_return(env, &return_layout);
let c_function_type = match cc_return {
CCReturn::Void => env.context.void_type().fn_type(&argument_types, false),
CCReturn::Return => return_type.fn_type(&argument_types, false),
CCReturn::ByPointer => {
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();
match cc_return {
CCReturn::Return => {
debug_assert_eq!(args.len(), roc_function.get_params().len());
}
CCReturn::Void => {
debug_assert_eq!(args.len(), roc_function.get_params().len());
}
CCReturn::ByPointer => {
args = &args[..args.len() - 1];
debug_assert_eq!(args.len(), roc_function.get_params().len());
}
}
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 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 {
CCReturn::Void => {
// TODO return empty struct here?
builder.build_return(None);
}
CCReturn::Return => {
builder.build_return(Some(&call_result));
}
CCReturn::ByPointer => {
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 // 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_type = env.context.i64_type().fn_type(&[], false);
let size_function_name: String = format!("roc__{}_size", ident_string); let size_function_name: String = format!("roc__{}_size", ident_string);
@ -3715,7 +4015,13 @@ fn build_proc_header<'a, 'ctx, 'env>(
if env.exposed_to_host.contains(&symbol) { if env.exposed_to_host.contains(&symbol) {
let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena); let arguments = Vec::from_iter_in(proc.args.iter().map(|(layout, _)| *layout), env.arena);
expose_function_to_host(env, symbol, fn_val, arguments.into_bump_slice()); expose_function_to_host(
env,
symbol,
fn_val,
arguments.into_bump_slice(),
proc.ret_layout,
);
} }
fn_val fn_val
@ -3782,23 +4088,17 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
builder.position_at_end(entry); builder.position_at_end(entry);
let mut parameters = function_value.get_params(); let mut evaluator_arguments = function_value.get_params();
let output = parameters.pop().unwrap().into_pointer_value();
let closure_data = if let Some(closure_data_ptr) = parameters.pop() { // the final parameter is the output pointer, pop it
let closure_data = let output = evaluator_arguments.pop().unwrap().into_pointer_value();
builder.build_load(closure_data_ptr.into_pointer_value(), "load_closure_data");
env.arena.alloc([closure_data]) as &[_] // NOTE this may be incorrect in the long run
} else { // here we load any argument that is a pointer
&[] for param in evaluator_arguments.iter_mut() {
}; if param.is_pointer_value() {
*param = builder.build_load(param.into_pointer_value(), "load_param");
let mut parameters = parameters; }
for param in parameters.iter_mut() {
debug_assert!(param.is_pointer_value());
*param = builder.build_load(param.into_pointer_value(), "load_param");
} }
let call_result = if env.is_gen_test { let call_result = if env.is_gen_test {
@ -3807,13 +4107,13 @@ pub fn build_closure_caller<'a, 'ctx, 'env>(
function_value, function_value,
evaluator, evaluator,
evaluator.get_call_conventions(), evaluator.get_call_conventions(),
closure_data, &evaluator_arguments,
result_type, result_type,
) )
} else { } else {
let call = env let call = env
.builder .builder
.build_call(evaluator, closure_data, "call_function"); .build_call(evaluator, &evaluator_arguments, "call_function");
call.set_call_convention(evaluator.get_call_conventions()); call.set_call_convention(evaluator.get_call_conventions());
@ -3934,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);
@ -4606,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::*;
@ -4662,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);
@ -4682,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);
@ -4738,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);
@ -4780,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
@ -4800,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),
} }
@ -4825,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);
@ -4856,7 +5193,7 @@ fn run_low_level<'a, 'ctx, 'env>(
Usize | Int128 | Int64 | Int32 | Int16 | Int8 => { Usize | Int128 | Int64 | Int32 | Int16 | Int8 => {
build_int_unary_op(env, arg.into_int_value(), arg_builtin, op) build_int_unary_op(env, arg.into_int_value(), arg_builtin, op)
} }
Float128 | Float64 | Float32 | Float16 => { Float128 | Float64 | Float32 => {
build_float_unary_op(env, arg.into_float_value(), op) build_float_unary_op(env, arg.into_float_value(), op)
} }
_ => { _ => {
@ -4952,7 +5289,7 @@ fn run_low_level<'a, 'ctx, 'env>(
"lt_or_gt", "lt_or_gt",
) )
} }
Float128 | Float64 | Float32 | Float16 => { Float128 | Float64 | Float32 => {
let are_equal = env.builder.build_float_compare( let are_equal = env.builder.build_float_compare(
FloatPredicate::OEQ, FloatPredicate::OEQ,
lhs_arg.into_float_value(), lhs_arg.into_float_value(),
@ -5127,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"),
} }
@ -5398,8 +5735,7 @@ fn to_cc_type_builtin<'a, 'ctx, 'env>(
| Builtin::Decimal | Builtin::Decimal
| Builtin::Float128 | Builtin::Float128
| Builtin::Float64 | Builtin::Float64
| Builtin::Float32 | Builtin::Float32 => basic_type_from_builtin(env, builtin),
| Builtin::Float16 => basic_type_from_builtin(env, builtin),
Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => { Builtin::Str | Builtin::EmptyStr | Builtin::List(_) | Builtin::EmptyList => {
env.str_list_c_abi().into() env.str_list_c_abi().into()
} }
@ -5445,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();
@ -5762,7 +6124,7 @@ pub fn build_num_binop<'a, 'ctx, 'env>(
rhs_layout, rhs_layout,
op, op,
), ),
Float128 | Float64 | Float32 | Float16 => build_float_binop( Float128 | Float64 | Float32 => build_float_binop(
env, env,
parent, parent,
lhs_arg.into_float_value(), lhs_arg.into_float_value(),

View file

@ -132,7 +132,6 @@ fn hash_builtin<'a, 'ctx, 'env>(
| Builtin::Float64 | Builtin::Float64
| Builtin::Float32 | Builtin::Float32
| Builtin::Float128 | Builtin::Float128
| Builtin::Float16
| Builtin::Decimal | Builtin::Decimal
| Builtin::Usize => { | Builtin::Usize => {
let hash_bytes = store_and_use_as_u8_ptr(env, val, layout); let hash_bytes = store_and_use_as_u8_ptr(env, val, layout);

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

@ -103,7 +103,6 @@ fn build_eq_builtin<'a, 'ctx, 'env>(
Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"), Builtin::Float128 => float_cmp(FloatPredicate::OEQ, "eq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::OEQ, "eq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"), Builtin::Float32 => float_cmp(FloatPredicate::OEQ, "eq_f32"),
Builtin::Float16 => float_cmp(FloatPredicate::OEQ, "eq_f16"),
Builtin::Str => str_equal(env, lhs_val, rhs_val), Builtin::Str => str_equal(env, lhs_val, rhs_val),
Builtin::List(elem) => build_list_eq( Builtin::List(elem) => build_list_eq(
@ -247,7 +246,6 @@ fn build_neq_builtin<'a, 'ctx, 'env>(
Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"), Builtin::Float128 => float_cmp(FloatPredicate::ONE, "neq_f128"),
Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"), Builtin::Float64 => float_cmp(FloatPredicate::ONE, "neq_f64"),
Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"), Builtin::Float32 => float_cmp(FloatPredicate::ONE, "neq_f32"),
Builtin::Float16 => float_cmp(FloatPredicate::ONE, "neq_f16"),
Builtin::Str => { Builtin::Str => {
let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value(); let is_equal = str_equal(env, lhs_val, rhs_val).into_int_value();

View file

@ -97,7 +97,6 @@ pub fn basic_type_from_builtin<'a, 'ctx, 'env>(
Float128 => context.f128_type().as_basic_type_enum(), Float128 => context.f128_type().as_basic_type_enum(),
Float64 => context.f64_type().as_basic_type_enum(), Float64 => context.f64_type().as_basic_type_enum(),
Float32 => context.f32_type().as_basic_type_enum(), Float32 => context.f32_type().as_basic_type_enum(),
Float16 => context.f16_type().as_basic_type_enum(),
Dict(_, _) | EmptyDict => zig_dict_type(env).into(), Dict(_, _) | EmptyDict => zig_dict_type(env).into(),
Set(_) | EmptySet => zig_dict_type(env).into(), Set(_) | EmptySet => zig_dict_type(env).into(),
List(_) | EmptyList => zig_list_type(env).into(), List(_) | EmptyList => zig_list_type(env).into(),

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,148 +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::Float16) => 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,
@ -161,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,
@ -186,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(),
@ -206,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);
@ -256,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) {
@ -307,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 {
@ -352,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));
@ -399,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));
@ -430,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));
} }
@ -464,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 {:?}",
@ -480,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(
@ -529,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())?;
@ -547,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 => {
@ -1241,7 +1379,7 @@ fn builtin_spec(
match builtin { match builtin {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]), Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize => builder.add_tuple_type(&[]),
Decimal | Float128 | Float64 | Float32 | Float16 => builder.add_tuple_type(&[]), Decimal | Float128 | Float64 | Float32 => builder.add_tuple_type(&[]),
Str | EmptyStr => str_type(builder), Str | EmptyStr => str_type(builder),
Dict(key_layout, value_layout) => { Dict(key_layout, value_layout) => {
let value_type = layout_spec_help(builder, value_layout, when_recursive)?; let value_type = layout_spec_help(builder, value_layout, when_recursive)?;
@ -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,
@ -1933,7 +1939,29 @@ fn specialize_external<'a>(
match layout { match layout {
RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => { RawFunctionLayout::Function(argument_layouts, lambda_set, return_layout) => {
let assigned = env.unique_symbol(); let assigned = env.unique_symbol();
let unit = env.unique_symbol();
let mut argument_symbols =
Vec::with_capacity_in(argument_layouts.len(), env.arena);
let mut proc_arguments =
Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
let mut top_level_arguments =
Vec::with_capacity_in(argument_layouts.len() + 1, env.arena);
for layout in argument_layouts {
let symbol = env.unique_symbol();
proc_arguments.push((*layout, symbol));
argument_symbols.push(symbol);
top_level_arguments.push(*layout);
}
// the proc needs to take an extra closure argument
let lambda_set_layout = Layout::LambdaSet(lambda_set);
proc_arguments.push((lambda_set_layout, Symbol::ARG_CLOSURE));
// this should also be reflected in the TopLevel signature
top_level_arguments.push(lambda_set_layout);
let hole = env.arena.alloc(Stmt::Ret(assigned)); let hole = env.arena.alloc(Stmt::Ret(assigned));
@ -1941,19 +1969,16 @@ fn specialize_external<'a>(
env, env,
lambda_set, lambda_set,
Symbol::ARG_CLOSURE, Symbol::ARG_CLOSURE,
env.arena.alloc([unit]), argument_symbols.into_bump_slice(),
argument_layouts, argument_layouts,
*return_layout, *return_layout,
assigned, assigned,
hole, hole,
); );
let body = let_empty_struct(unit, env.arena.alloc(body));
let lambda_set_layout = Layout::LambdaSet(lambda_set);
let proc = Proc { let proc = Proc {
name, name,
args: env.arena.alloc([(lambda_set_layout, Symbol::ARG_CLOSURE)]), args: proc_arguments.into_bump_slice(),
body, body,
closure_data_layout: None, closure_data_layout: None,
ret_layout: *return_layout, ret_layout: *return_layout,
@ -1964,7 +1989,7 @@ fn specialize_external<'a>(
let top_level = ProcLayout::new( let top_level = ProcLayout::new(
env.arena, env.arena,
env.arena.alloc([lambda_set_layout]), top_level_arguments.into_bump_slice(),
*return_layout, *return_layout,
); );
@ -6189,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,
@ -6418,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,
@ -6427,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,
@ -6475,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(
@ -6487,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);
@ -6516,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,
@ -6554,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,
@ -6606,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,
@ -6624,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()?
@ -6638,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: _,
@ -6665,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,
@ -6814,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>)>,
@ -6852,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(_) => {
@ -6859,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"),
@ -681,7 +681,6 @@ pub enum Builtin<'a> {
Float128, Float128,
Float64, Float64,
Float32, Float32,
Float16,
Str, Str,
Dict(&'a Layout<'a>, &'a Layout<'a>), Dict(&'a Layout<'a>, &'a Layout<'a>),
Set(&'a Layout<'a>), Set(&'a Layout<'a>),
@ -1119,7 +1118,6 @@ impl<'a> Builtin<'a> {
const F128_SIZE: u32 = 16; const F128_SIZE: u32 = 16;
const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32; const F64_SIZE: u32 = std::mem::size_of::<f64>() as u32;
const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32; const F32_SIZE: u32 = std::mem::size_of::<f32>() as u32;
const F16_SIZE: u32 = 2;
/// Number of machine words in an empty one of these /// Number of machine words in an empty one of these
pub const STR_WORDS: u32 = 2; pub const STR_WORDS: u32 = 2;
@ -1149,7 +1147,6 @@ impl<'a> Builtin<'a> {
Float128 => Builtin::F128_SIZE, Float128 => Builtin::F128_SIZE,
Float64 => Builtin::F64_SIZE, Float64 => Builtin::F64_SIZE,
Float32 => Builtin::F32_SIZE, Float32 => Builtin::F32_SIZE,
Float16 => Builtin::F16_SIZE,
Str | EmptyStr => Builtin::STR_WORDS * pointer_size, Str | EmptyStr => Builtin::STR_WORDS * pointer_size,
Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size, Dict(_, _) | EmptyDict => Builtin::DICT_WORDS * pointer_size,
Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size, Set(_) | EmptySet => Builtin::SET_WORDS * pointer_size,
@ -1176,7 +1173,6 @@ impl<'a> Builtin<'a> {
Float128 => align_of::<i128>() as u32, Float128 => align_of::<i128>() as u32,
Float64 => align_of::<f64>() as u32, Float64 => align_of::<f64>() as u32,
Float32 => align_of::<f32>() as u32, Float32 => align_of::<f32>() as u32,
Float16 => align_of::<i16>() as u32,
Dict(_, _) | EmptyDict => pointer_size, Dict(_, _) | EmptyDict => pointer_size,
Set(_) | EmptySet => pointer_size, Set(_) | EmptySet => pointer_size,
// we often treat these as i128 (64-bit systems) // we often treat these as i128 (64-bit systems)
@ -1194,7 +1190,7 @@ impl<'a> Builtin<'a> {
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
| Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => true, | Float32 | EmptyStr | EmptyDict | EmptyList | EmptySet => true,
Str | Dict(_, _) | Set(_) | List(_) => false, Str | Dict(_, _) | Set(_) | List(_) => false,
} }
} }
@ -1205,7 +1201,7 @@ impl<'a> Builtin<'a> {
match self { match self {
Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64 Int128 | Int64 | Int32 | Int16 | Int8 | Int1 | Usize | Decimal | Float128 | Float64
| Float32 | Float16 | EmptyStr | EmptyDict | EmptyList | EmptySet => false, | Float32 | EmptyStr | EmptyDict | EmptyList | EmptySet => false,
List(_) => true, List(_) => true,
Str | Dict(_, _) | Set(_) => true, Str | Dict(_, _) | Set(_) => true,
@ -1232,7 +1228,6 @@ impl<'a> Builtin<'a> {
Float128 => alloc.text("Float128"), Float128 => alloc.text("Float128"),
Float64 => alloc.text("Float64"), Float64 => alloc.text("Float64"),
Float32 => alloc.text("Float32"), Float32 => alloc.text("Float32"),
Float16 => alloc.text("Float16"),
EmptyStr => alloc.text("EmptyStr"), EmptyStr => alloc.text("EmptyStr"),
EmptyList => alloc.text("EmptyList"), EmptyList => alloc.text("EmptyList"),
@ -1266,8 +1261,7 @@ impl<'a> Builtin<'a> {
| Builtin::Decimal | Builtin::Decimal
| Builtin::Float128 | Builtin::Float128
| Builtin::Float64 | Builtin::Float64
| Builtin::Float32 | Builtin::Float32 => unreachable!("not heap-allocated"),
| Builtin::Float16 => unreachable!("not heap-allocated"),
Builtin::Str => pointer_size, Builtin::Str => pointer_size,
Builtin::Dict(k, v) => k Builtin::Dict(k, v) => k
.alignment_bytes(pointer_size) .alignment_bytes(pointer_size)

View file

@ -1,7 +1,8 @@
use roc_can::expected::{Expected, PExpected}; use roc_can::expected::{Expected, PExpected};
use roc_collections::all::{Index, MutSet, SendMap}; use roc_collections::all::{Index, MutSet, SendMap};
use roc_module::ident::{IdentStr, Lowercase, TagName}; use roc_module::ident::{Ident, IdentStr, Lowercase, TagName};
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_solve::solve; use roc_solve::solve;
use roc_types::pretty_print::Parens; use roc_types::pretty_print::Parens;
use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt};
@ -10,6 +11,7 @@ use std::path::PathBuf;
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity}; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder, Severity};
use ven_pretty::DocAllocator; use ven_pretty::DocAllocator;
const DUPLICATE_NAME: &str = "DUPLICATE NAME";
const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#; const ADD_ANNOTATIONS: &str = r#"Can more type annotations be added? Type annotations always help me give more specific messages, and I think they could help a lot in this case"#;
pub fn type_problem<'b>( pub fn type_problem<'b>(
@ -103,12 +105,38 @@ pub fn type_problem<'b>(
SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711
Shadowed(original_region, shadow) => {
let doc = report_shadowing(alloc, original_region, shadow);
let title = DUPLICATE_NAME.to_string();
report(title, doc, filename)
}
other => panic!("unhandled bad type: {:?}", other), other => panic!("unhandled bad type: {:?}", other),
} }
} }
} }
} }
fn report_shadowing<'b>(
alloc: &'b RocDocAllocator<'b>,
original_region: Region,
shadow: Located<Ident>,
) -> RocDocBuilder<'b> {
let line = r#"Since these types have the same name, it's easy to use the wrong one on accident. Give one of them a new name."#;
alloc.stack(vec![
alloc
.text("The ")
.append(alloc.ident(shadow.value))
.append(alloc.reflow(" name is first defined here:")),
alloc.region(original_region),
alloc.reflow("But then it's defined a second time here:"),
alloc.region(shadow.region),
alloc.reflow(line),
])
}
pub fn cyclic_alias<'b>( pub fn cyclic_alias<'b>(
alloc: &'b RocDocAllocator<'b>, alloc: &'b RocDocAllocator<'b>,
symbol: Symbol, symbol: Symbol,

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
);
}

View file

@ -2906,3 +2906,104 @@ fn do_pass_bool_byte_closure_layout() {
RocStr RocStr
); );
} }
#[test]
fn nested_rigid_list() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
foo : List a -> List a
foo = \list ->
p2 : List a
p2 = list
p2
main =
when foo [] is
_ -> "hello world"
"#
),
RocStr::from_slice(b"hello world"),
RocStr
);
}
#[test]
fn nested_rigid_alias() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
Identity a : [ @Identity a ]
foo : Identity a -> Identity a
foo = \list ->
p2 : Identity a
p2 = list
p2
main =
when foo (@Identity "foo") is
_ -> "hello world"
"#
),
RocStr::from_slice(b"hello world"),
RocStr
);
}
#[test]
fn nested_rigid_tag_union() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
foo : [ @Identity a ] -> [ @Identity a ]
foo = \list ->
p2 : [ @Identity a ]
p2 = list
p2
main =
when foo (@Identity "foo") is
_ -> "hello world"
"#
),
RocStr::from_slice(b"hello world"),
RocStr
);
}
#[test]
fn call_that_needs_closure_parameter() {
// here both p2 is lifted to the top-level, which means that `list` must be
// passed to it from `manyAux`.
assert_evals_to!(
indoc!(
r#"
Step state a : [ Loop state, Done a ]
manyAux : List a -> [ Pair (Step (List a) (List a))]
manyAux = \list ->
p2 = \_ -> Pair (Done list)
p2 "foo"
manyAuxTest = (manyAux [ ]) == Pair (Loop [97])
runTest = \t -> if t then "PASS" else "FAIL"
runTest manyAuxTest
"#
),
RocStr::from_slice(b"FAIL"),
RocStr
);
}

View file

@ -949,3 +949,31 @@ fn str_from_utf8_range_count_too_high_for_start() {
RocStr RocStr
); );
} }
#[test]
fn str_repeat_small() {
assert_evals_to!(
indoc!(r#"Str.repeat "Roc" 3"#),
RocStr::from("RocRocRoc"),
RocStr
);
}
#[test]
fn str_repeat_big() {
assert_evals_to!(
indoc!(r#"Str.repeat "more than 16 characters" 2"#),
RocStr::from("more than 16 charactersmore than 16 characters"),
RocStr
);
}
#[test]
fn str_repeat_empty_string() {
assert_evals_to!(indoc!(r#"Str.repeat "" 3"#), RocStr::from(""), RocStr);
}
#[test]
fn str_repeat_zero_times() {
assert_evals_to!(indoc!(r#"Str.repeat "Roc" 0"#), RocStr::from(""), RocStr);
}

View file

@ -0,0 +1,43 @@
procedure List.3 (#Attr.2, #Attr.3):
let Test.20 = lowlevel ListLen #Attr.2;
let Test.17 = lowlevel NumLt #Attr.3 Test.20;
if Test.17 then
let Test.19 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
let Test.18 = Ok Test.19;
ret Test.18;
else
let Test.16 = Struct {};
let Test.15 = Err Test.16;
ret Test.15;
procedure Test.2 (Test.6):
let Test.24 = "bar";
ret Test.24;
procedure Test.0 ():
let Test.1 = Array [];
joinpoint Test.22 Test.3:
let Test.14 = 0i64;
let Test.7 = CallByName List.3 Test.3 Test.14;
dec Test.3;
let Test.11 = 1i64;
let Test.12 = GetTagId Test.7;
let Test.13 = lowlevel Eq Test.11 Test.12;
if Test.13 then
let Test.5 = UnionAtIndex (Id 1) (Index 0) Test.7;
let Test.9 = "foo";
let Test.8 = CallByName Test.2 Test.9;
dec Test.9;
ret Test.8;
else
let Test.10 = "bad!";
ret Test.10;
in
let Test.25 = false;
if Test.25 then
jump Test.22 Test.1;
else
dec Test.1;
let Test.23 = Struct {};
let Test.21 = Array [Test.23];
jump Test.22 Test.21;

View file

@ -10,7 +10,7 @@ procedure Num.27 (#Attr.2, #Attr.3):
let Test.26 = lowlevel NumLt #Attr.2 #Attr.3; let Test.26 = lowlevel NumLt #Attr.2 #Attr.3;
ret Test.26; ret Test.26;
procedure Test.1 (Test.29, Test.30, Test.31): procedure Test.1 (Test.27, Test.28, Test.29):
joinpoint Test.12 Test.2 Test.3 Test.4: joinpoint Test.12 Test.2 Test.3 Test.4:
let Test.14 = CallByName Num.27 Test.3 Test.4; let Test.14 = CallByName Num.27 Test.3 Test.4;
if Test.14 then if Test.14 then
@ -29,7 +29,7 @@ procedure Test.1 (Test.29, Test.30, Test.31):
else else
ret Test.2; ret Test.2;
in in
jump Test.12 Test.29 Test.30 Test.31; jump Test.12 Test.27 Test.28 Test.29;
procedure Test.0 (): procedure Test.0 ():
let Test.9 = Array []; let Test.9 = Array [];

View file

@ -1084,6 +1084,33 @@ fn specialize_lowlevel() {
) )
} }
#[mono_test]
fn empty_list_of_function_type() {
// see https://github.com/rtfeldman/roc/issues/1732
indoc!(
r#"
app "test" provides [ main ] to "./platform"
main =
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!"
"#
)
}
// #[ignore] // #[ignore]
// #[mono_test] // #[mono_test]
// fn static_str_closure() { // fn static_str_closure() {

View file

@ -30,13 +30,13 @@ arraystring = "0.3.0"
libc = "0.2" libc = "0.2"
page_size = "0.4" page_size = "0.4"
winit = "0.24" winit = "0.24"
wgpu = "0.9" wgpu = "0.10"
glyph_brush = "0.7" glyph_brush = "0.7"
log = "0.4" log = "0.4"
zerocopy = "0.3" zerocopy = "0.3"
env_logger = "0.8" env_logger = "0.8"
futures = "0.3" futures = "0.3"
wgpu_glyph = "0.13" wgpu_glyph = "0.14"
cgmath = "0.18.0" cgmath = "0.18.0"
snafu = { version = "0.6", features = ["backtraces"] } snafu = { version = "0.6", features = ["backtraces"] }
colored = "2" colored = "2"

View file

@ -67,7 +67,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
.build(&event_loop) .build(&event_loop)
.unwrap(); .unwrap();
let instance = wgpu::Instance::new(wgpu::BackendBit::all()); let instance = wgpu::Instance::new(wgpu::Backends::all());
let surface = unsafe { instance.create_surface(&window) }; let surface = unsafe { instance.create_surface(&window) };
@ -103,17 +103,17 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
let render_format = wgpu::TextureFormat::Bgra8Unorm; let render_format = wgpu::TextureFormat::Bgra8Unorm;
let mut size = window.inner_size(); let mut size = window.inner_size();
let swap_chain_descr = wgpu::SwapChainDescriptor { let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format, format: render_format,
width: size.width, width: size.width,
height: size.height, height: size.height,
present_mode: wgpu::PresentMode::Mailbox, present_mode: wgpu::PresentMode::Mailbox,
}; };
let mut swap_chain = gpu_device.create_swap_chain(&surface, &swap_chain_descr); surface.configure(&gpu_device, &surface_config);
let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &swap_chain_descr); let rect_resources = pipelines::make_rect_pipeline(&gpu_device, &surface_config);
let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?; let mut glyph_brush = build_glyph_brush(&gpu_device, render_format)?;
@ -202,10 +202,10 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
} => { } => {
size = new_size; size = new_size;
swap_chain = gpu_device.create_swap_chain( surface.configure(
&surface, &gpu_device,
&wgpu::SwapChainDescriptor { &wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsage::RENDER_ATTACHMENT, usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format: render_format, format: render_format,
width: size.width, width: size.width,
height: size.height, height: size.height,
@ -271,11 +271,15 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
label: Some("Redraw"), label: Some("Redraw"),
}); });
let frame = swap_chain let frame = surface
.get_current_frame() .get_current_frame()
.expect("Failed to acquire next SwapChainFrame") .expect("Failed to acquire next SwapChainFrame")
.output; .output;
let view = frame
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
if let Some(ref mut ed_model) = app_model.ed_model_opt { if let Some(ref mut ed_model) = app_model.ed_model_opt {
if rendered_wgpu_opt.is_none() || ed_model.dirty { if rendered_wgpu_opt.is_none() || ed_model.dirty {
let rendered_wgpu_res = let rendered_wgpu_res =
@ -293,7 +297,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
draw_rects( draw_rects(
&rendered_wgpu.rects_behind, &rendered_wgpu.rects_behind,
&mut encoder, &mut encoder,
&frame.view, &view,
&gpu_device, &gpu_device,
&rect_resources, &rect_resources,
wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)), wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)),
@ -311,7 +315,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
&gpu_device, &gpu_device,
&mut staging_belt, &mut staging_belt,
&mut encoder, &mut encoder,
&frame.view, &view,
size.width, size.width,
size.height, size.height,
) )
@ -321,7 +325,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
draw_rects( draw_rects(
&rendered_wgpu.rects_front, &rendered_wgpu.rects_front,
&mut encoder, &mut encoder,
&frame.view, &view,
&gpu_device, &gpu_device,
&rect_resources, &rect_resources,
wgpu::LoadOp::Load, wgpu::LoadOp::Load,
@ -336,7 +340,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
} else { } else {
begin_render_pass( begin_render_pass(
&mut encoder, &mut encoder,
&frame.view, &view,
wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)), wgpu::LoadOp::Clear(to_wgpu_color(ed_theme.background)),
); );
@ -355,7 +359,7 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
&gpu_device, &gpu_device,
&mut staging_belt, &mut staging_belt,
&mut encoder, &mut encoder,
&frame.view, &view,
size.width, size.width,
size.height, size.height,
) )

View file

@ -4,7 +4,6 @@ use std::process::Command;
use std::process::Stdio; use std::process::Stdio;
use crate::editor::code_lines::CodeLines; use crate::editor::code_lines::CodeLines;
//use crate::editor::ed_error::from_ui_res;
use crate::editor::ed_error::EdResult; use crate::editor::ed_error::EdResult;
use crate::editor::ed_error::MissingSelection; use crate::editor::ed_error::MissingSelection;
use crate::editor::grid_node_map::GridNodeMap; use crate::editor::grid_node_map::GridNodeMap;

View file

@ -105,7 +105,7 @@ pub fn create_rect_buffers(
let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { let vertex_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None, label: None,
size: Vertex::SIZE * 4 * nr_of_rects, size: Vertex::SIZE * 4 * nr_of_rects,
usage: wgpu::BufferUsage::VERTEX | wgpu::BufferUsage::COPY_DST, usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
@ -114,7 +114,7 @@ pub fn create_rect_buffers(
let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor { let index_buffer = gpu_device.create_buffer(&wgpu::BufferDescriptor {
label: None, label: None,
size: u32_size * 6 * nr_of_rects, size: u32_size * 6 * nr_of_rects,
usage: wgpu::BufferUsage::INDEX | wgpu::BufferUsage::COPY_DST, usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false, mapped_at_creation: false,
}); });
@ -148,7 +148,7 @@ impl StagingBuffer {
StagingBuffer { StagingBuffer {
buffer: device.create_buffer_init(&BufferInitDescriptor { buffer: device.create_buffer_init(&BufferInitDescriptor {
contents: bytemuck::cast_slice(data), contents: bytemuck::cast_slice(data),
usage: wgpu::BufferUsage::COPY_SRC, usage: wgpu::BufferUsages::COPY_SRC,
label: Some("Staging Buffer"), label: Some("Staging Buffer"),
}), }),
size: size_of_slice(data) as wgpu::BufferAddress, size: size_of_slice(data) as wgpu::BufferAddress,

View file

@ -2,7 +2,7 @@ use cgmath::{Matrix4, Ortho};
use wgpu::util::DeviceExt; use wgpu::util::DeviceExt;
use wgpu::{ use wgpu::{
BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer, BindGroup, BindGroupLayout, BindGroupLayoutDescriptor, BindGroupLayoutEntry, Buffer,
ShaderStage, ShaderStages,
}; };
// orthographic projection is used to transform pixel coords to the coordinate system used by wgpu // orthographic projection is used to transform pixel coords to the coordinate system used by wgpu
@ -45,7 +45,7 @@ pub fn update_ortho_buffer(
let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let new_ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"), label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[new_uniforms]), contents: bytemuck::cast_slice(&[new_uniforms]),
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_SRC, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_SRC,
}); });
// get a command encoder for the current frame // get a command encoder for the current frame
@ -83,14 +83,14 @@ pub fn init_ortho(
let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor { let ortho_buffer = gpu_device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Ortho uniform buffer"), label: Some("Ortho uniform buffer"),
contents: bytemuck::cast_slice(&[uniforms]), contents: bytemuck::cast_slice(&[uniforms]),
usage: wgpu::BufferUsage::UNIFORM | wgpu::BufferUsage::COPY_DST, usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
}); });
// bind groups consist of extra resources that are provided to the shaders // bind groups consist of extra resources that are provided to the shaders
let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor { let ortho_bind_group_layout = gpu_device.create_bind_group_layout(&BindGroupLayoutDescriptor {
entries: &[BindGroupLayoutEntry { entries: &[BindGroupLayoutEntry {
binding: 0, binding: 0,
visibility: ShaderStage::VERTEX, visibility: ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer { ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform, ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: false, has_dynamic_offset: false,

View file

@ -9,9 +9,9 @@ pub struct RectResources {
pub fn make_rect_pipeline( pub fn make_rect_pipeline(
gpu_device: &wgpu::Device, gpu_device: &wgpu::Device,
swap_chain_descr: &wgpu::SwapChainDescriptor, surface_config: &wgpu::SurfaceConfiguration,
) -> RectResources { ) -> RectResources {
let ortho = init_ortho(swap_chain_descr.width, swap_chain_descr.height, gpu_device); let ortho = init_ortho(surface_config.width, surface_config.height, gpu_device);
let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = gpu_device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
bind_group_layouts: &[&ortho.bind_group_layout], bind_group_layouts: &[&ortho.bind_group_layout],
@ -21,11 +21,10 @@ pub fn make_rect_pipeline(
let pipeline = create_render_pipeline( let pipeline = create_render_pipeline(
gpu_device, gpu_device,
&pipeline_layout, &pipeline_layout,
swap_chain_descr.format, surface_config.format,
&wgpu::ShaderModuleDescriptor { &wgpu::ShaderModuleDescriptor {
label: None, label: None,
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))), source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(include_str!("../shaders/shader.wgsl"))),
flags: wgpu::ShaderFlags::all(),
}, },
); );
@ -61,7 +60,7 @@ pub fn create_render_pipeline(
}, },
alpha: wgpu::BlendComponent::REPLACE, alpha: wgpu::BlendComponent::REPLACE,
}), }),
write_mask: wgpu::ColorWrite::ALL, write_mask: wgpu::ColorWrites::ALL,
}], }],
}), }),
primitive: wgpu::PrimitiveState::default(), primitive: wgpu::PrimitiveState::default(),

View file

@ -18,7 +18,7 @@ impl Vertex {
pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress; pub const SIZE: wgpu::BufferAddress = std::mem::size_of::<Self>() as wgpu::BufferAddress;
pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout { pub const DESC: wgpu::VertexBufferLayout<'static> = wgpu::VertexBufferLayout {
array_stride: Self::SIZE, array_stride: Self::SIZE,
step_mode: wgpu::InputStepMode::Vertex, step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[ attributes: &[
// position // position
wgpu::VertexAttribute { wgpu::VertexAttribute {

4
examples/.gitignore vendored
View file

@ -4,3 +4,7 @@ app
libhost.a libhost.a
roc_app.ll roc_app.ll
roc_app.bc roc_app.bc
dynhost
preprocessedhost
metadata
libapp.so

View file

@ -23,16 +23,19 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed([*]u8) void; extern fn roc__mainForHost_1_exposed_generic([*]u8) void;
extern fn roc__mainForHost_size() i64; extern fn roc__mainForHost_size() i64;
extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void; extern fn roc__mainForHost_1_Fx_caller(*const u8, [*]u8, [*]u8) void;
extern fn roc__mainForHost_1_Fx_size() i64; extern fn roc__mainForHost_1_Fx_size() i64;
extern fn roc__mainForHost_1_Fx_result_size() i64; extern fn roc__mainForHost_1_Fx_result_size() i64;
const Align = 2 * @alignOf(usize); const Align = 2 * @alignOf(usize);
extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void; extern fn malloc(size: usize) callconv(.C) ?*align(Align) c_void;
extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(Align) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void; extern fn free(c_ptr: [*]align(Align) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
const DEBUG: bool = false; const DEBUG: bool = false;
@ -74,6 +77,14 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); std.process.exit(0);
} }
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
return memset(dst, value, size);
}
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() callconv(.C) u8 { pub export fn main() callconv(.C) u8 {
@ -90,23 +101,11 @@ pub export fn main() callconv(.C) u8 {
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
roc__mainForHost_1_exposed(output); roc__mainForHost_1_exposed_generic(output);
const flag = @ptrCast(*u64, @alignCast(@alignOf(u64), output)).*; const closure_data_pointer = @ptrCast([*]u8, output);
if (flag == 0) { call_the_closure(closure_data_pointer);
// all is well
const closure_data_pointer = @ptrCast([*]u8, output[@sizeOf(u64)..size]);
call_the_closure(closure_data_pointer);
} else {
const ptr = @ptrCast(*u32, output + @sizeOf(u64));
const msg = @intToPtr([*:0]const u8, ptr.*);
const stderr = std.io.getStdErr().writer();
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
return 0;
}
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;

View file

@ -5,8 +5,16 @@ authors = ["The Roc Contributors"]
license = "UPL-1.0" license = "UPL-1.0"
edition = "2018" edition = "2018"
links = "app"
[lib] [lib]
crate-type = ["staticlib"] name = "host"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]
[[bin]]
name = "host"
path = "src/main.rs"
[dependencies] [dependencies]
roc_std = { path = "../../../roc_std" } roc_std = { path = "../../../roc_std" }

View file

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View file

@ -1,7 +1,3 @@
#include <stdio.h>
extern int rust_main(); extern int rust_main();
int main() { int main() { return rust_main(); }
return rust_main();
}

View file

@ -4,7 +4,7 @@ use core::alloc::Layout;
use core::ffi::c_void; use core::ffi::c_void;
use core::mem::MaybeUninit; use core::mem::MaybeUninit;
use libc; use libc;
use roc_std::{RocCallResult, RocStr}; use roc_std::RocStr;
use std::ffi::CStr; use std::ffi::CStr;
use std::os::raw::c_char; use std::os::raw::c_char;
@ -27,12 +27,12 @@ extern "C" {
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size) libc::malloc(size)
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_realloc( pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void, c_ptr: *mut c_void,
new_size: usize, new_size: usize,
_old_size: usize, _old_size: usize,
@ -42,12 +42,12 @@ pub unsafe fn roc_realloc(
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr) libc::free(c_ptr)
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id { match tag_id {
0 => { 0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char); let slice = CStr::from_ptr(c_ptr as *const c_char);
@ -60,7 +60,17 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
} }
#[no_mangle] #[no_mangle]
pub fn rust_main() -> isize { pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
let size = unsafe { roc_main_size() } as usize; let size = unsafe { roc_main_size() } as usize;
let layout = Layout::array::<u8>(size).unwrap(); let layout = Layout::array::<u8>(size).unwrap();
@ -70,30 +80,18 @@ pub fn rust_main() -> isize {
roc_main(buffer); roc_main(buffer);
let output = buffer as *mut RocCallResult<()>; let result = call_the_closure(buffer);
match (&*output).into() { std::alloc::dealloc(buffer, layout);
Ok(()) => {
let closure_data_ptr = buffer.offset(8);
let result = call_the_closure(closure_data_ptr as *const u8);
std::alloc::dealloc(buffer, layout); result
result
}
Err(msg) => {
std::alloc::dealloc(buffer, layout);
panic!("Roc failed with message: {}", msg);
}
}
}; };
// Exit code // Exit code
0 0
} }
unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 { unsafe extern "C" fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
let size = size_Fx_result() as usize; let size = size_Fx_result() as usize;
let layout = Layout::array::<u8>(size).unwrap(); let layout = Layout::array::<u8>(size).unwrap();
let buffer = std::alloc::alloc(layout) as *mut u8; let buffer = std::alloc::alloc(layout) as *mut u8;
@ -105,19 +103,13 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
buffer as *mut u8, buffer as *mut u8,
); );
let output = &*(buffer as *mut RocCallResult<()>); std::alloc::dealloc(buffer, layout);
match output.into() { 0
Ok(_) => {
std::alloc::dealloc(buffer, layout);
0
}
Err(e) => panic!("failed with {}", e),
}
} }
#[no_mangle] #[no_mangle]
pub fn roc_fx_getLine() -> RocStr { pub extern "C" fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead}; use std::io::{self, BufRead};
let stdin = io::stdin(); let stdin = io::stdin();
@ -127,7 +119,7 @@ pub fn roc_fx_getLine() -> RocStr {
} }
#[no_mangle] #[no_mangle]
pub fn roc_fx_putLine(line: RocStr) -> () { pub extern "C" fn roc_fx_putLine(line: RocStr) -> () {
let bytes = line.as_slice(); let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) }; let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string); println!("{}", string);

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main());
}

View file

@ -32,6 +32,8 @@ extern fn roc__mainForHost_1_Fx_result_size() i64;
extern fn malloc(size: usize) callconv(.C) ?*c_void; extern fn malloc(size: usize) callconv(.C) ?*c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(u128)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void; extern fn free(c_ptr: [*]align(@alignOf(u128)) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
return malloc(size); return malloc(size);
@ -52,18 +54,29 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); std.process.exit(0);
} }
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
return memset(dst, value, size);
}
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() u8 { pub export fn main() u8 {
const allocator = std.heap.page_allocator;
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
const size = @intCast(usize, roc__mainForHost_size()); // NOTE the return size can be zero, which will segfault. Always allocate at least 8 bytes
const raw_output = std.heap.c_allocator.alloc(u8, size) catch unreachable; const size = std.math.max(8, @intCast(usize, roc__mainForHost_size()));
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);
defer { defer {
std.heap.c_allocator.free(raw_output); allocator.free(raw_output);
} }
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
@ -71,21 +84,7 @@ pub export fn main() u8 {
roc__mainForHost_1_exposed(output); roc__mainForHost_1_exposed(output);
const elements = @ptrCast([*]u64, @alignCast(8, output)); call_the_closure(output);
var flag = elements[0];
if (flag == 0) {
// all is well
const closure_data_pointer = @ptrCast([*]u8, output[8..size]);
call_the_closure(closure_data_pointer);
} else {
const msg = @intToPtr([*:0]const u8, elements[1]);
stderr.print("Application crashed with message\n\n {s}\n\nShutting down\n", .{msg}) catch unreachable;
return 0;
}
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts2) catch unreachable;
@ -102,27 +101,20 @@ fn to_seconds(tms: std.os.timespec) f64 {
} }
fn call_the_closure(closure_data_pointer: [*]u8) void { fn call_the_closure(closure_data_pointer: [*]u8) void {
const allocator = std.heap.page_allocator;
const size = roc__mainForHost_1_Fx_result_size(); const size = roc__mainForHost_1_Fx_result_size();
const raw_output = std.heap.c_allocator.alloc(u8, @intCast(usize, size)) catch unreachable; const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
var output = @ptrCast([*]u8, raw_output); var output = @ptrCast([*]u8, raw_output);
defer { defer {
std.heap.c_allocator.free(raw_output); allocator.free(raw_output);
} }
const flags: u8 = 0; const flags: u8 = 0;
roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output); roc__mainForHost_1_Fx_caller(&flags, closure_data_pointer, output);
const elements = @ptrCast([*]u64, @alignCast(8, output)); return;
var flag = elements[0];
if (flag == 0) {
return;
} else {
unreachable;
}
} }
pub export fn roc_fx_getLine() str.RocStr { pub export fn roc_fx_getLine() str.RocStr {

View file

@ -1 +1,2 @@
add add
fib

View file

@ -3,25 +3,13 @@ app "fib"
imports [] imports []
provides [ main ] to base provides [ main ] to base
main = \n -> fib n main = \n -> fib n 0 1
fib = \n ->
if n == 0 then
0
else if n == 1 then
1
else
(fib (n - 1)) + (fib (n - 2))
# the clever implementation requires join points # the clever implementation requires join points
# fib = \n, a, b -> fib = \n, a, b ->
# if n == 0 then if n == 0 then
# a a
#
# else else
# fib (n - 1) b (a + b) fib (n - 1) b (a + b)
#
# fib n 0 1

View file

@ -20,6 +20,8 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
// NOTE the LLVM backend expects this signature
// extern fn roc__mainForHost_1_exposed(i64, *i64) void;
extern fn roc__mainForHost_1_exposed(i64) i64; extern fn roc__mainForHost_1_exposed(i64) i64;
const Align = extern struct { a: usize, b: usize }; const Align = extern struct { a: usize, b: usize };

View file

@ -1 +1 @@
hello-world hello-rust

View file

@ -1,4 +1,4 @@
app "hello-world" app "hello-rust"
packages { base: "platform" } packages { base: "platform" }
imports [] imports []
provides [ main ] to base provides [ main ] to base

View file

@ -4,9 +4,16 @@ version = "0.1.0"
authors = ["The Roc Contributors"] authors = ["The Roc Contributors"]
license = "UPL-1.0" license = "UPL-1.0"
edition = "2018" edition = "2018"
links = "app"
[lib] [lib]
crate-type = ["staticlib"] name = "host"
path = "src/lib.rs"
crate-type = ["staticlib", "rlib"]
[[bin]]
name = "host"
path = "src/main.rs"
[dependencies] [dependencies]
roc_std = { path = "../../../roc_std" } roc_std = { path = "../../../roc_std" }

View file

@ -0,0 +1,4 @@
fn main() {
println!("cargo:rustc-link-lib=dylib=app");
println!("cargo:rustc-link-search=.");
}

View file

@ -1,7 +1,3 @@
#include <stdio.h>
extern int rust_main(); extern int rust_main();
int main() { int main() { return rust_main(); }
return rust_main();
}

View file

@ -1,23 +1,22 @@
#![allow(non_snake_case)] #![allow(non_snake_case)]
use core::ffi::c_void; use core::ffi::c_void;
use core::mem::MaybeUninit;
use libc::c_char; use libc::c_char;
use roc_std::{RocCallResult, RocStr}; use roc_std::RocStr;
use std::ffi::CStr; use std::ffi::CStr;
extern "C" { extern "C" {
#[link_name = "roc__mainForHost_1_exposed"] #[link_name = "roc__mainForHost_1_exposed"]
fn roc_main(output: *mut RocCallResult<RocStr>) -> (); fn roc_main() -> RocStr;
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
return libc::malloc(size); return libc::malloc(size);
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_realloc( pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void, c_ptr: *mut c_void,
new_size: usize, new_size: usize,
_old_size: usize, _old_size: usize,
@ -27,12 +26,12 @@ pub unsafe fn roc_realloc(
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
return libc::free(c_ptr); return libc::free(c_ptr);
} }
#[no_mangle] #[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id { match tag_id {
0 => { 0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char); let slice = CStr::from_ptr(c_ptr as *const c_char);
@ -45,26 +44,25 @@ pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
} }
#[no_mangle] #[no_mangle]
pub fn rust_main() -> isize { pub unsafe extern "C" fn roc_memcpy(dst: *mut c_void, src: *mut c_void, n: usize) -> *mut c_void {
let mut call_result: MaybeUninit<RocCallResult<RocStr>> = MaybeUninit::uninit(); libc::memcpy(dst, src, n)
}
#[no_mangle]
pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut c_void {
libc::memset(dst, c, n)
}
#[no_mangle]
pub extern "C" fn rust_main() -> i32 {
unsafe { unsafe {
roc_main(call_result.as_mut_ptr()); let roc_str = roc_main();
let output = call_result.assume_init(); let len = roc_str.len();
let str_bytes = roc_str.get_bytes() as *const libc::c_void;
match output.into() { if libc::write(1, str_bytes, len) < 0 {
Ok(roc_str) => { panic!("Writing to stdout failed!");
let len = roc_str.len();
let str_bytes = roc_str.get_bytes() as *const libc::c_void;
if libc::write(1, str_bytes, len) < 0 {
panic!("Writing to stdout failed!");
}
}
Err(msg) => {
panic!("Roc failed with message: {}", msg);
}
} }
} }

View file

@ -0,0 +1,3 @@
fn main() {
std::process::exit(host::rust_main());
}

2
examples/hello-web/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
hello-web
*.wat

View file

@ -0,0 +1,12 @@
app "hello-web"
packages { base: "platform" }
imports []
provides [ main ] to base
greeting =
hi = "Hello"
name = "World"
"\(hi), \(name)!"
main = greeting

View file

@ -1,34 +1,35 @@
# Hello, World! # Hello, World!
To run, `cd` into this directory and run: To run, go to the project home directory and run:
```bash ```bash
$ cargo run Hello.roc $ cargo run -- build --backend=wasm32 examples/hello-web/Hello.roc
``` ```
To run in release mode instead, do: Then `cd` into the example directory and run any web server that can handle WebAssembly.
For example with `http-server`:
```bash ```bash
$ cargo run --release Hello.roc cd examples/hello-web
npm install -g http-server
http-server
``` ```
## Troubleshooting Now open your browser at http://localhost:8080
If you encounter `cannot find -lc++`, run the following for ubuntu `sudo apt install libc++-dev`.
## Design Notes ## Design Notes
This demonstrates the basic design of hosts: Roc code gets compiled into a pure This demonstrates the basic design of hosts: Roc code gets compiled into a pure
function (in this case, a thunk that always returns `"Hello, World!"`) and function (in this case, a thunk that always returns `"Hello, World!"`) and
then the host calls that function. Fundamentally, that's the whole idea! The host then the host calls that function. Fundamentally, that's the whole idea! The host
might not even have a `main` - it could be a library, a plugin, anything. might not even have a `main` - it could be a library, a plugin, anything.
Everything else is built on this basic "hosts calling linked pure functions" design. Everything else is built on this basic "hosts calling linked pure functions" design.
For example, things get more interesting when the compiled Roc function returns For example, things get more interesting when the compiled Roc function returns
a `Task` - that is, a tagged union data structure containing function pointers a `Task` - that is, a tagged union data structure containing function pointers
to callback closures. This lets the Roc pure function describe arbitrary to callback closures. This lets the Roc pure function describe arbitrary
chainable effects, which the host can interpret to perform I/O as requested by chainable effects, which the host can interpret to perform I/O as requested by
the Roc program. (The tagged union `Task` would have a variant for each supported the Roc program. (The tagged union `Task` would have a variant for each supported
I/O operation.) I/O operation.)
In this trivial example, it's very easy to line up the API between the host and In this trivial example, it's very easy to line up the API between the host and
@ -43,6 +44,6 @@ Roc application authors only care about the Roc-host/Roc-app portion, and the
host author only cares about the Roc-host/C boundary when implementing the host. host author only cares about the Roc-host/C boundary when implementing the host.
Using this glue code, the Roc compiler can generate C header files describing the Using this glue code, the Roc compiler can generate C header files describing the
boundary. This not only gets us host compatibility with C compilers, but also boundary. This not only gets us host compatibility with C compilers, but also
Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen)
generates correct Rust FFI bindings from C headers. generates correct Rust FFI bindings from C headers.

View file

@ -0,0 +1,12 @@
<html>
<body>
<div id="output"></div>
<script src="platform/host.js"></script>
<script>
const elem = document.getElementById("output");
roc_web_platform_run("./hello-web.wasm", (string_from_roc) => {
elem.textContent = string_from_roc;
});
</script>
</body>
</html>

View file

@ -0,0 +1,10 @@
platform examples/hello-world
requires {}{ main : Str }
exposes []
packages {}
imports []
provides [ mainForHost ]
effects fx.Effect {}
mainForHost : Str
mainForHost = main

View file

@ -0,0 +1,57 @@
async function roc_web_platform_run(wasm_filename, callback) {
const decoder = new TextDecoder();
let memory_bytes;
let exit_code;
function js_display_roc_string(str_bytes, str_len) {
const utf8_bytes = memory_bytes.subarray(str_bytes, str_bytes + str_len);
const js_string = decoder.decode(utf8_bytes);
callback(js_string);
}
const importObj = {
wasi_snapshot_preview1: {
proc_exit: (code) => {
if (code !== 0) {
console.error(`Exited with code ${code}`);
}
exit_code = code;
},
roc_panic: (_pointer, _tag_id) => {
throw 'Roc panicked!';
}
},
env: {
js_display_roc_string,
},
};
let wasm;
const response = await fetch(wasm_filename);
if (WebAssembly.instantiateStreaming) {
// streaming API has better performance if available
wasm = await WebAssembly.instantiateStreaming(response, importObj);
} else {
const module_bytes = await response.arrayBuffer();
wasm = await WebAssembly.instantiate(module_bytes, importObj);
}
memory_bytes = new Uint8Array(wasm.instance.exports.memory.buffer);
try {
wasm.instance.exports._start();
} catch (e) {
const is_ok = e.message === "unreachable" && exit_code === 0;
if (!is_ok) {
console.error(e);
}
}
}
if (typeof module !== 'undefined') {
module.exports = {
roc_web_platform_run,
};
}

View file

@ -0,0 +1,68 @@
const std = @import("std");
const str = @import("str");
const RocStr = str.RocStr;
const testing = std.testing;
const expectEqual = testing.expectEqual;
const expect = testing.expect;
comptime {
// This is a workaround for https://github.com/ziglang/zig/issues/8218
// which is only necessary on macOS.
//
// Once that issue is fixed, we can undo the changes in
// 177cf12e0555147faa4d436e52fc15175c2c4ff0 and go back to passing
// -fcompiler-rt in link.rs instead of doing this. Note that this
// workaround is present in many host.zig files, so make sure to undo
// it everywhere!
if (std.builtin.os.tag == .macos) {
_ = @import("compiler_rt");
}
}
const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *c_void, old_size: usize, new_size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = old_size;
_ = alignment;
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *c_void, alignment: u32) callconv(.C) void {
_ = alignment;
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
}
// NOTE roc_panic is provided in the JS file, so it can throw an exception
const mem = std.mem;
const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocStr) void;
const Unit = extern struct {};
extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void;
pub fn main() u8 {
// actually call roc to populate the callresult
var callresult = RocStr.empty();
roc__mainForHost_1_exposed(&callresult);
// display the result using JavaScript
js_display_roc_string(callresult.asU8ptr(), callresult.len());
callresult.deinit();
return 0;
}

View file

@ -0,0 +1,25 @@
/**
* Node.js test file for hello-web example
* We are not running this in CI currently, and Node.js is not a Roc dependency.
* But if you happen to have it, you can run this.
*/
// Node doesn't have the fetch API
const fs = require("fs/promises");
global.fetch = (filename) =>
fs.readFile(filename).then((buffer) => ({
arrayBuffer() {
return buffer;
},
}));
const { roc_web_platform_run } = require("./platform/host");
roc_web_platform_run("./hello-world.wasm", (string_from_roc) => {
const expected = "Hello, World!";
if (string_from_roc !== expected) {
console.error(`Expected "${expected}", but got "${string_from_roc}"`);
process.exit(1);
}
console.log("OK");
});

View file

@ -1,89 +1,81 @@
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h> #include <errno.h>
#include <string.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
void* roc_alloc(size_t size, unsigned int alignment) { void* roc_alloc(size_t size, unsigned int alignment) { return malloc(size); }
return malloc(size);
void* roc_realloc(void* ptr, size_t old_size, size_t new_size,
unsigned int alignment) {
return realloc(ptr, new_size);
} }
void* roc_realloc(void* ptr, size_t old_size, size_t new_size, unsigned int alignment) { void roc_dealloc(void* ptr, unsigned int alignment) { free(ptr); }
return realloc(ptr, new_size);
}
void roc_dealloc(void* ptr, unsigned int alignment) {
free(ptr);
}
void roc_panic(void* ptr, unsigned int alignment) { void roc_panic(void* ptr, unsigned int alignment) {
char* msg = (char *)ptr; char* msg = (char*)ptr;
fprintf(stderr, "Application crashed with message\n\n %s\n\nShutting down\n", msg); fprintf(stderr,
exit(0); "Application crashed with message\n\n %s\n\nShutting down\n", msg);
exit(0);
} }
void* roc_memcpy(void* dest, const void* src, size_t n) {
return memcpy(dest, src, n);
}
void* roc_memset(void* str, int c, size_t n) { return memset(str, c, n); }
struct RocStr { struct RocStr {
char* bytes; char* bytes;
size_t len; size_t len;
}; };
bool is_small_str(struct RocStr str) { bool is_small_str(struct RocStr str) { return ((ssize_t)str.len) < 0; }
return ((ssize_t)str.len) < 0;
}
// Determine the length of the string, taking into // Determine the length of the string, taking into
// account the small string optimization // account the small string optimization
size_t roc_str_len(struct RocStr str) { size_t roc_str_len(struct RocStr str) {
char* bytes = (char*)&str; char* bytes = (char*)&str;
char last_byte = bytes[sizeof(str) - 1]; char last_byte = bytes[sizeof(str) - 1];
char last_byte_xored = last_byte ^ 0b10000000; char last_byte_xored = last_byte ^ 0b10000000;
size_t small_len = (size_t)(last_byte_xored); size_t small_len = (size_t)(last_byte_xored);
size_t big_len = str.len; size_t big_len = str.len;
// Avoid branch misprediction costs by always // Avoid branch misprediction costs by always
// determining both small_len and big_len, // determining both small_len and big_len,
// so this compiles to a cmov instruction. // so this compiles to a cmov instruction.
if (is_small_str(str)) { if (is_small_str(str)) {
return small_len; return small_len;
} else { } else {
return big_len; return big_len;
} }
} }
struct RocCallResult { extern struct RocStr roc__mainForHost_1_exposed();
size_t flag;
struct RocStr content;
};
extern void roc__mainForHost_1_exposed(struct RocCallResult *re);
int main() { int main() {
// Make space for the Roc call result struct RocStr str = roc__mainForHost_1_exposed();
struct RocCallResult call_result;
// Call Roc to populate call_result // Determine str_len and the str_bytes pointer,
roc__mainForHost_1_exposed(&call_result); // taking into account the small string optimization.
size_t str_len = roc_str_len(str);
char* str_bytes;
// Determine str_len and the str_bytes pointer, if (is_small_str(str)) {
// taking into account the small string optimization. str_bytes = (char*)&str;
struct RocStr str = call_result.content; } else {
size_t str_len = roc_str_len(str); str_bytes = str.bytes;
char* str_bytes; }
if (is_small_str(str)) { // Write to stdout
str_bytes = (char*)&str; if (write(1, str_bytes, str_len) >= 0) {
} else { // Writing succeeded!
str_bytes = str.bytes; return 0;
} } else {
printf("Error writing to stdout: %s\n", strerror(errno));
// Write to stdout return 1;
if (write(1, str_bytes, str_len) >= 0) { }
// Writing succeeded!
return 0;
} else {
printf("Error writing to stdout: %s\n", strerror(errno));
return 1;
}
} }

View file

@ -23,6 +23,8 @@ const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void { export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*c_void {
_ = alignment; _ = alignment;
@ -51,12 +53,18 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); std.process.exit(0);
} }
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
return memset(dst, value, size);
}
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(*RocCallResult) void; extern fn roc__mainForHost_1_exposed() RocStr;
const RocCallResult = extern struct { flag: u64, content: RocStr };
const Unit = extern struct {}; const Unit = extern struct {};
@ -64,20 +72,17 @@ pub fn main() u8 {
const stdout = std.io.getStdOut().writer(); const stdout = std.io.getStdOut().writer();
const stderr = std.io.getStdErr().writer(); const stderr = std.io.getStdErr().writer();
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = RocStr.empty() };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // actually call roc to populate the callresult
roc__mainForHost_1_exposed(&callresult); var callresult = roc__mainForHost_1_exposed();
// stdout the result // stdout the result
stdout.print("{s}\n", .{callresult.content.asSlice()}) catch unreachable; stdout.print("{s}\n", .{callresult.asSlice()}) catch unreachable;
callresult.content.deinit(); callresult.deinit();
// end time // end time
var ts2: std.os.timespec = undefined; var ts2: std.os.timespec = undefined;

View file

@ -20,12 +20,14 @@ comptime {
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
extern fn roc__mainForHost_1_exposed(RocList, *RocCallResult) void; extern fn roc__mainForHost_1_exposed(RocList) RocList;
const Align = extern struct { a: usize, b: usize }; const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void; extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) c_void;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void; extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*c_void;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void; extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
extern fn memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void;
extern fn memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void;
const DEBUG: bool = false; const DEBUG: bool = false;
@ -67,13 +69,19 @@ export fn roc_panic(c_ptr: *c_void, tag_id: u32) callconv(.C) void {
std.process.exit(0); std.process.exit(0);
} }
export fn roc_memcpy(dst: [*]u8, src: [*]u8, size: usize) callconv(.C) void{
return memcpy(dst, src, size);
}
export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void{
return memset(dst, value, size);
}
// warning! the array is currently stack-allocated so don't make this too big // warning! the array is currently stack-allocated so don't make this too big
const NUM_NUMS = 100; const NUM_NUMS = 100;
const RocList = extern struct { elements: [*]i64, length: usize }; const RocList = extern struct { elements: [*]i64, length: usize };
const RocCallResult = extern struct { flag: u64, content: RocList };
const Unit = extern struct {}; const Unit = extern struct {};
pub export fn main() u8 { pub export fn main() u8 {
@ -93,19 +101,16 @@ pub export fn main() u8 {
const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS }; const roc_list = RocList{ .elements = numbers, .length = NUM_NUMS };
// make space for the result
var callresult = RocCallResult{ .flag = 0, .content = undefined };
// start time // start time
var ts1: std.os.timespec = undefined; var ts1: std.os.timespec = undefined;
std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable; std.os.clock_gettime(std.os.CLOCK_REALTIME, &ts1) catch unreachable;
// actually call roc to populate the callresult // actually call roc to populate the callresult
roc__mainForHost_1_exposed(roc_list, &callresult); var callresult = roc__mainForHost_1_exposed(roc_list);
// stdout the result // stdout the result
const length = std.math.min(20, callresult.content.length); const length = std.math.min(20, callresult.length);
var result = callresult.content.elements[0..length]; var result = callresult.elements[0..length];
for (result) |x, i| { for (result) |x, i| {
if (i == 0) { if (i == 0) {

View file

@ -18,12 +18,16 @@ test = false
bench = false bench = false
[dependencies] [dependencies]
roc_mono = { path = "../compiler/mono" }
roc_build = { path = "../compiler/build", default-features = false }
roc_collections = { path = "../compiler/collections" } roc_collections = { path = "../compiler/collections" }
bumpalo = { version = "3.6", features = ["collections"] } bumpalo = { version = "3.6", features = ["collections"] }
# TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it # TODO switch to clap 3.0.0 once it's out. Tried adding clap = "~3.0.0-beta.1" and cargo wouldn't accept it
clap = { git = "https://github.com/rtfeldman/clap", branch = "master" } clap = { git = "https://github.com/rtfeldman/clap", branch = "master" }
iced-x86 = "1.14" iced-x86 = "1.14"
memmap2 = "0.3" memmap2 = "0.3"
object = { version = "0.26", features = ["read"] } object = { version = "0.26", features = ["read", "write"] }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
bincode = "1.3" bincode = "1.3"
target-lexicon = "0.12.2"
tempfile = "3.1.0"

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