mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 08:11:12 +00:00
Mov gen to its own crate
This commit is contained in:
parent
a18e023326
commit
363a7a0abd
16 changed files with 101 additions and 552 deletions
207
Cargo.lock
generated
207
Cargo.lock
generated
|
@ -237,12 +237,6 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "fixedbitset"
|
|
||||||
version = "0.1.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "86d4de0081402f5e88cdac65c8dcdcc73118c1a7a465e2a05f0da05843a8ea33"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fnv"
|
name = "fnv"
|
||||||
version = "1.0.6"
|
version = "1.0.6"
|
||||||
|
@ -255,97 +249,6 @@ version = "0.1.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-executor",
|
|
||||||
"futures-io",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-channel"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-sink",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-core"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-executor"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"futures-task",
|
|
||||||
"futures-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-io"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-macro"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro-hack",
|
|
||||||
"proc-macro2 1.0.9",
|
|
||||||
"quote 1.0.3",
|
|
||||||
"syn 1.0.16",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-sink"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-task"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "futures-util"
|
|
||||||
version = "0.3.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
|
|
||||||
dependencies = [
|
|
||||||
"futures-channel",
|
|
||||||
"futures-core",
|
|
||||||
"futures-io",
|
|
||||||
"futures-macro",
|
|
||||||
"futures-sink",
|
|
||||||
"futures-task",
|
|
||||||
"memchr",
|
|
||||||
"pin-utils",
|
|
||||||
"proc-macro-hack",
|
|
||||||
"proc-macro-nested",
|
|
||||||
"slab",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gcc"
|
name = "gcc"
|
||||||
version = "0.3.55"
|
version = "0.3.55"
|
||||||
|
@ -541,12 +444,6 @@ version = "1.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
|
checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ordermap"
|
|
||||||
version = "0.3.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a86ed3f5f244b372d6b1a00b72ef7f8876d0bc6a78a4c9985c53614041512063"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.10.0"
|
version = "0.10.0"
|
||||||
|
@ -571,28 +468,12 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "petgraph"
|
|
||||||
version = "0.4.13"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "9c3659d1ee90221741f65dd128d9998311b0e40c5d3c23a62445938214abce4f"
|
|
||||||
dependencies = [
|
|
||||||
"fixedbitset",
|
|
||||||
"ordermap",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
|
checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-utils"
|
|
||||||
version = "0.1.0-alpha.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pretty_assertions"
|
name = "pretty_assertions"
|
||||||
version = "0.5.1"
|
version = "0.5.1"
|
||||||
|
@ -614,12 +495,6 @@ dependencies = [
|
||||||
"syn 1.0.16",
|
"syn 1.0.16",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "proc-macro-nested"
|
|
||||||
version = "0.1.3"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "0.4.30"
|
version = "0.4.30"
|
||||||
|
@ -856,47 +731,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "roc"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bumpalo",
|
|
||||||
"cranelift",
|
|
||||||
"cranelift-codegen",
|
|
||||||
"cranelift-module",
|
|
||||||
"cranelift-simplejit",
|
|
||||||
"futures",
|
|
||||||
"im",
|
|
||||||
"im-rc",
|
|
||||||
"indoc",
|
|
||||||
"inkwell",
|
|
||||||
"inlinable_string",
|
|
||||||
"lazy_static",
|
|
||||||
"log",
|
|
||||||
"maplit",
|
|
||||||
"petgraph",
|
|
||||||
"pretty_assertions",
|
|
||||||
"quickcheck",
|
|
||||||
"quickcheck_macros",
|
|
||||||
"roc_builtins",
|
|
||||||
"roc_can",
|
|
||||||
"roc_collections",
|
|
||||||
"roc_constrain",
|
|
||||||
"roc_fmt",
|
|
||||||
"roc_module",
|
|
||||||
"roc_mono",
|
|
||||||
"roc_parse",
|
|
||||||
"roc_problem",
|
|
||||||
"roc_region",
|
|
||||||
"roc_solve",
|
|
||||||
"roc_types",
|
|
||||||
"roc_unify",
|
|
||||||
"roc_uniq",
|
|
||||||
"target-lexicon",
|
|
||||||
"tokio",
|
|
||||||
"wyhash",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roc_builtins"
|
name = "roc_builtins"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -985,6 +819,41 @@ dependencies = [
|
||||||
"roc_types",
|
"roc_types",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roc_gen"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"cranelift",
|
||||||
|
"cranelift-codegen",
|
||||||
|
"cranelift-module",
|
||||||
|
"cranelift-simplejit",
|
||||||
|
"im",
|
||||||
|
"im-rc",
|
||||||
|
"indoc",
|
||||||
|
"inkwell",
|
||||||
|
"inlinable_string",
|
||||||
|
"maplit",
|
||||||
|
"pretty_assertions",
|
||||||
|
"quickcheck",
|
||||||
|
"quickcheck_macros",
|
||||||
|
"roc_builtins",
|
||||||
|
"roc_can",
|
||||||
|
"roc_collections",
|
||||||
|
"roc_constrain",
|
||||||
|
"roc_module",
|
||||||
|
"roc_mono",
|
||||||
|
"roc_parse",
|
||||||
|
"roc_problem",
|
||||||
|
"roc_region",
|
||||||
|
"roc_solve",
|
||||||
|
"roc_types",
|
||||||
|
"roc_unify",
|
||||||
|
"roc_uniq",
|
||||||
|
"target-lexicon",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roc_load"
|
name = "roc_load"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -1217,12 +1086,6 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "slab"
|
|
||||||
version = "0.4.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"compiler",
|
|
||||||
"compiler/region",
|
"compiler/region",
|
||||||
"compiler/collections",
|
"compiler/collections",
|
||||||
"compiler/module",
|
"compiler/module",
|
||||||
|
@ -18,6 +17,7 @@ members = [
|
||||||
"compiler/fmt",
|
"compiler/fmt",
|
||||||
"compiler/mono",
|
"compiler/mono",
|
||||||
"compiler/load",
|
"compiler/load",
|
||||||
|
"compiler/gen",
|
||||||
"vendor/ena",
|
"vendor/ena",
|
||||||
"vendor/pathfinding"
|
"vendor/pathfinding"
|
||||||
]
|
]
|
||||||
|
|
|
@ -105,3 +105,40 @@ That concludes our original recursive call to `eval`, after which point we'll be
|
||||||
|
|
||||||
This will work the same way as `Minus` did, and will reduce down to `Int(6)`.
|
This will work the same way as `Minus` did, and will reduce down to `Int(6)`.
|
||||||
|
|
||||||
|
|
||||||
|
## Optimization philosophy
|
||||||
|
|
||||||
|
Focus on optimizations which are only safe in the absence of side effects, and leave the rest to LLVM.
|
||||||
|
|
||||||
|
This focus may lead to some optimizations becoming transitively in scope. For example, some deforestation
|
||||||
|
examples in the MSR paper benefit from multiple rounds of interleaved deforestation, beta-reduction, and inlining.
|
||||||
|
To get those benefits, we'd have to do some inlining and beta-reduction that we could otherwise leave to LLVM's
|
||||||
|
inlining and constant propagation/folding.
|
||||||
|
|
||||||
|
Even if we're doing those things, it may still make sense to have LLVM do a pass for them as well, since
|
||||||
|
early LLVM optimization passes may unlock later opportunities for inlining and constant propagation/folding.
|
||||||
|
|
||||||
|
## Inlining
|
||||||
|
|
||||||
|
If a function is called exactly once (it's a helper function), presumably we always want to inline those.
|
||||||
|
If a function is "small enough" it's probably worth inlining too.
|
||||||
|
|
||||||
|
## Fusion
|
||||||
|
|
||||||
|
https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/deforestation-short-cut.pdf
|
||||||
|
|
||||||
|
Basic approach:
|
||||||
|
|
||||||
|
Do list stuff using `build` passing Cons Nil (like a cons list) and then do foldr/build substitution/reduction.
|
||||||
|
Afterwards, we can do a separate pass to flatten nested Cons structures into properly initialized RRBTs.
|
||||||
|
This way we get both deforestation and efficient RRBT construction. Should work for the other collection types too.
|
||||||
|
|
||||||
|
It looks like we need to do some amount of inlining and beta reductions on the Roc side, rather than
|
||||||
|
leaving all of those to LLVM.
|
||||||
|
|
||||||
|
Advanced approach:
|
||||||
|
|
||||||
|
Express operations like map and filter in terms of toStream and fromStream, to unlock more deforestation.
|
||||||
|
More info on here:
|
||||||
|
|
||||||
|
https://wiki.haskell.org/GHC_optimisations#Fusion
|
|
@ -1,30 +1,23 @@
|
||||||
[package]
|
[package]
|
||||||
name = "roc"
|
name = "roc_gen"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
authors = ["Richard Feldman <oss@rtfeldman.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
roc_collections = { path = "./collections" }
|
roc_collections = { path = "../collections" }
|
||||||
roc_region = { path = "./region" }
|
roc_region = { path = "../region" }
|
||||||
roc_module = { path = "./module" }
|
roc_module = { path = "../module" }
|
||||||
roc_parse = { path = "./parse" }
|
roc_problem = { path = "../problem" }
|
||||||
roc_problem = { path = "./problem" }
|
roc_types = { path = "../types" }
|
||||||
roc_types = { path = "./types" }
|
roc_builtins = { path = "../builtins" }
|
||||||
roc_can = { path = "./can" }
|
roc_constrain = { path = "../constrain" }
|
||||||
roc_builtins = { path = "./builtins" }
|
roc_uniq = { path = "../uniq" }
|
||||||
roc_constrain = { path = "./constrain" }
|
roc_unify = { path = "../unify" }
|
||||||
roc_uniq = { path = "./uniq" }
|
roc_solve = { path = "../solve" }
|
||||||
roc_unify = { path = "./unify" }
|
roc_mono = { path = "../mono" }
|
||||||
roc_solve = { path = "./solve" }
|
|
||||||
roc_fmt = { path = "./fmt" }
|
|
||||||
roc_mono = { path = "./mono" }
|
|
||||||
log = "0.4.8"
|
|
||||||
petgraph = { version = "0.4.5", optional = true }
|
|
||||||
im = "14" # im and im-rc should always have the same version!
|
im = "14" # im and im-rc should always have the same version!
|
||||||
im-rc = "14" # im and im-rc should always have the same version!
|
im-rc = "14" # im and im-rc should always have the same version!
|
||||||
wyhash = "0.3"
|
|
||||||
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
|
|
||||||
bumpalo = "2.6"
|
bumpalo = "2.6"
|
||||||
inlinable_string = "0.1.0"
|
inlinable_string = "0.1.0"
|
||||||
# NOTE: Breaking API changes get pushed directly to this Inkwell branch, so be
|
# NOTE: Breaking API changes get pushed directly to this Inkwell branch, so be
|
||||||
|
@ -34,8 +27,6 @@ inlinable_string = "0.1.0"
|
||||||
# `rev` works locally, it causes an error on GitHub Actions. (It's unclear why,
|
# `rev` works locally, it causes an error on GitHub Actions. (It's unclear why,
|
||||||
# but after several hours of trying unsuccessfully to fix it, `branch` is it.)
|
# but after several hours of trying unsuccessfully to fix it, `branch` is it.)
|
||||||
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" }
|
inkwell = { git = "https://github.com/TheDan64/inkwell", branch = "llvm8-0" }
|
||||||
futures = "0.3"
|
|
||||||
lazy_static = "1.4"
|
|
||||||
target-lexicon = "0.10" # NOTE: we must use the same version of target-lexicon as cranelift!
|
target-lexicon = "0.10" # NOTE: we must use the same version of target-lexicon as cranelift!
|
||||||
cranelift = "0.59" # All cranelift crates should have the same version!
|
cranelift = "0.59" # All cranelift crates should have the same version!
|
||||||
cranelift-simplejit = "0.59" # All cranelift crates should have the same version!
|
cranelift-simplejit = "0.59" # All cranelift crates should have the same version!
|
||||||
|
@ -43,8 +34,12 @@ cranelift-module = "0.59" # All cranelift crates should have the same version
|
||||||
cranelift-codegen = "0.59" # All cranelift crates should have the same version!
|
cranelift-codegen = "0.59" # All cranelift crates should have the same version!
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
|
roc_can = { path = "../can" }
|
||||||
|
roc_parse = { path = "../parse" }
|
||||||
pretty_assertions = "0.5.1 "
|
pretty_assertions = "0.5.1 "
|
||||||
maplit = "1.0.1"
|
maplit = "1.0.1"
|
||||||
indoc = "0.3.3"
|
indoc = "0.3.3"
|
||||||
quickcheck = "0.8"
|
quickcheck = "0.8"
|
||||||
quickcheck_macros = "0.8"
|
quickcheck_macros = "0.8"
|
||||||
|
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
|
||||||
|
bumpalo = "2.6"
|
|
@ -10,6 +10,5 @@
|
||||||
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
|
// and encouraging shortcuts here creates bad incentives. I would rather temporarily
|
||||||
// re-enable this when working on performance optimizations than have it block PRs.
|
// re-enable this when working on performance optimizations than have it block PRs.
|
||||||
#![allow(clippy::large_enum_variant)]
|
#![allow(clippy::large_enum_variant)]
|
||||||
|
|
||||||
pub mod crane;
|
pub mod crane;
|
||||||
pub mod llvm;
|
pub mod llvm;
|
|
@ -5,7 +5,7 @@ extern crate indoc;
|
||||||
|
|
||||||
extern crate bumpalo;
|
extern crate bumpalo;
|
||||||
extern crate inkwell;
|
extern crate inkwell;
|
||||||
extern crate roc;
|
extern crate roc_gen;
|
||||||
|
|
||||||
mod helpers;
|
mod helpers;
|
||||||
|
|
||||||
|
@ -24,12 +24,12 @@ mod test_gen {
|
||||||
use inkwell::passes::PassManager;
|
use inkwell::passes::PassManager;
|
||||||
use inkwell::types::BasicType;
|
use inkwell::types::BasicType;
|
||||||
use inkwell::OptimizationLevel;
|
use inkwell::OptimizationLevel;
|
||||||
use roc::crane::build::{declare_proc, define_proc_body, ScopeEntry};
|
|
||||||
use roc::crane::convert::type_from_layout;
|
|
||||||
use roc::crane::imports::define_malloc;
|
|
||||||
use roc::llvm::build::{build_proc, build_proc_header};
|
|
||||||
use roc::llvm::convert::basic_type_from_layout;
|
|
||||||
use roc_collections::all::{ImMap, MutMap};
|
use roc_collections::all::{ImMap, MutMap};
|
||||||
|
use roc_gen::crane::build::{declare_proc, define_proc_body, ScopeEntry};
|
||||||
|
use roc_gen::crane::convert::type_from_layout;
|
||||||
|
use roc_gen::crane::imports::define_malloc;
|
||||||
|
use roc_gen::llvm::build::{build_proc, build_proc_header};
|
||||||
|
use roc_gen::llvm::convert::basic_type_from_layout;
|
||||||
use roc_mono::expr::Expr;
|
use roc_mono::expr::Expr;
|
||||||
use roc_mono::layout::Layout;
|
use roc_mono::layout::Layout;
|
||||||
use roc_types::subs::Subs;
|
use roc_types::subs::Subs;
|
||||||
|
@ -63,7 +63,7 @@ mod test_gen {
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
let mut procs = MutMap::default();
|
let mut procs = MutMap::default();
|
||||||
let mut env = roc::crane::build::Env {
|
let mut env = roc_gen::crane::build::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
subs,
|
subs,
|
||||||
interns,
|
interns,
|
||||||
|
@ -135,7 +135,7 @@ mod test_gen {
|
||||||
builder.append_block_params_for_function_params(block);
|
builder.append_block_params_for_function_params(block);
|
||||||
|
|
||||||
let main_body =
|
let main_body =
|
||||||
roc::crane::build::build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs);
|
roc_gen::crane::build::build_expr(&env, &scope, &mut module, &mut builder, &mono_expr, &procs);
|
||||||
|
|
||||||
builder.ins().return_(&[main_body]);
|
builder.ins().return_(&[main_body]);
|
||||||
// TODO re-enable this once Switch stops making unsealed blocks, e.g.
|
// TODO re-enable this once Switch stops making unsealed blocks, e.g.
|
||||||
|
@ -210,7 +210,7 @@ mod test_gen {
|
||||||
let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
|
let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
let mut env = roc::llvm::build::Env {
|
let mut env = roc_gen::llvm::build::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
subs,
|
subs,
|
||||||
builder: &builder,
|
builder: &builder,
|
||||||
|
@ -265,7 +265,7 @@ mod test_gen {
|
||||||
|
|
||||||
builder.position_at_end(basic_block);
|
builder.position_at_end(basic_block);
|
||||||
|
|
||||||
let ret = roc::llvm::build::build_expr(
|
let ret = roc_gen::llvm::build::build_expr(
|
||||||
&env,
|
&env,
|
||||||
&ImMap::default(),
|
&ImMap::default(),
|
||||||
main_fn,
|
main_fn,
|
||||||
|
@ -346,7 +346,7 @@ mod test_gen {
|
||||||
let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
|
let pointer_bytes = execution_engine.get_target_data().get_pointer_byte_size(None);
|
||||||
|
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
let mut env = roc::llvm::build::Env {
|
let mut env = roc_gen::llvm::build::Env {
|
||||||
arena: &arena,
|
arena: &arena,
|
||||||
subs,
|
subs,
|
||||||
builder: &builder,
|
builder: &builder,
|
||||||
|
@ -401,7 +401,7 @@ mod test_gen {
|
||||||
|
|
||||||
builder.position_at_end(basic_block);
|
builder.position_at_end(basic_block);
|
||||||
|
|
||||||
let ret = roc::llvm::build::build_expr(
|
let ret = roc_gen::llvm::build::build_expr(
|
||||||
&env,
|
&env,
|
||||||
&ImMap::default(),
|
&ImMap::default(),
|
||||||
main_fn,
|
main_fn,
|
|
@ -1,36 +0,0 @@
|
||||||
// PHILOSOPHY
|
|
||||||
//
|
|
||||||
// Focus on optimizations which are only safe in the absence of side effects, and leave the rest to LLVM.
|
|
||||||
//
|
|
||||||
// This focus may lead to some optimizations becoming transitively in scope. For example, some deforestation
|
|
||||||
// examples in the MSR paper benefit from multiple rounds of interleaved deforestation, beta-reduction, and inlining.
|
|
||||||
// To get those benefits, we'd have to do some inlining and beta-reduction that we could otherwise leave to LLVM's
|
|
||||||
// inlining and constant propagation/folding.
|
|
||||||
//
|
|
||||||
// Even if we're doing those things, it may still make sense to have LLVM do a pass for them as well, since
|
|
||||||
// early LLVM optimization passes may unlock later opportunities for inlining and constant propagation/folding.
|
|
||||||
//
|
|
||||||
// INLINING
|
|
||||||
//
|
|
||||||
// If a function is called exactly once (it's a helper function), presumably we always want to inline those.
|
|
||||||
// If a function is "small enough" it's probably worth inlining too.
|
|
||||||
//
|
|
||||||
// FUSION
|
|
||||||
//
|
|
||||||
// https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/deforestation-short-cut.pdf
|
|
||||||
//
|
|
||||||
// Basic approach:
|
|
||||||
//
|
|
||||||
// Do list stuff using `build` passing Cons Nil (like a cons list) and then do foldr/build substitution/reduction.
|
|
||||||
// Afterwards, we can do a separate pass to flatten nested Cons structures into properly initialized RRBTs.
|
|
||||||
// This way we get both deforestation and efficient RRBT construction. Should work for the other collection types too.
|
|
||||||
//
|
|
||||||
// It looks like we need to do some amount of inlining and beta reductions on the Roc side, rather than
|
|
||||||
// leaving all of those to LLVM.
|
|
||||||
//
|
|
||||||
// Advanced approach:
|
|
||||||
//
|
|
||||||
// Express operations like map and filter in terms of toStream and fromStream, to unlock more deforestation.
|
|
||||||
// More info on here:
|
|
||||||
//
|
|
||||||
// https://wiki.haskell.org/GHC_optimisations#Fusion
|
|
|
@ -1,309 +0,0 @@
|
||||||
use std::alloc::{self, Layout};
|
|
||||||
use std::fmt;
|
|
||||||
use std::mem::{self, MaybeUninit};
|
|
||||||
use std::ptr;
|
|
||||||
use std::slice;
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
/// An immutable string whose maximum length is `isize::MAX`. (For convenience,
|
|
||||||
/// it still returns its length as `usize` since it can't be negative.)
|
|
||||||
///
|
|
||||||
/// For larger strings, under the hood this is a struct which stores a
|
|
||||||
/// pointer and a usize for length (so 16 bytes on a 64-bit system).
|
|
||||||
///
|
|
||||||
/// For smaller strings (lengths 0-15 on 64-bit systems, and 0-7 on 32-bit),
|
|
||||||
/// this uses a "short string optimization" where it stores the entire string
|
|
||||||
/// in this struct and does not bother allocating on the heap at all.
|
|
||||||
pub struct RocStr(InnerStr);
|
|
||||||
|
|
||||||
/// Roc strings are optimized not to do heap allocations when they are between
|
|
||||||
/// 0-15 bytes in length on 64-bit little endian systems,
|
|
||||||
/// and 0-7 bytes on systems that are 32-bit, big endian, or both.
|
|
||||||
///
|
|
||||||
/// This optimization relies on the assumption that string lengths are always
|
|
||||||
/// less than isize::MAX as opposed to usize::MAX. It relies on this because
|
|
||||||
/// it uses the most significant bit in the most significant byte in the length
|
|
||||||
/// as a flag for whether it is a short string or a long string. This bit is
|
|
||||||
/// unused if lengths are below isize::MAX.
|
|
||||||
///
|
|
||||||
/// Roc integers are i64, so on 64-bit systems this guarantee necessarily holds
|
|
||||||
/// from the roc side. On a 32-bit system it might not though. Rust historically
|
|
||||||
/// had this guarantee, but it might get relaxed. For more on the Rust side, see
|
|
||||||
/// https://github.com/rust-lang/unsafe-code-guidelines/issues/102
|
|
||||||
///
|
|
||||||
/// Since Roc will interpret them as i64, it's important that on 64-bit systems,
|
|
||||||
/// Rust never sends Roc any length values outsize isize::MAX because they'll
|
|
||||||
/// be interpreted as negative i64s!
|
|
||||||
///
|
|
||||||
/// Anyway, this "is this a short string?" bit is in a convenient location on
|
|
||||||
/// 64-bit little endian systems. This is because of how Rust's &str is
|
|
||||||
/// laid out, and memory alignment.
|
|
||||||
///
|
|
||||||
/// Rust's &str is laid out as a slice, namely:
|
|
||||||
///
|
|
||||||
/// struct RustStr { ptr: *const [u8], length: usize }
|
|
||||||
///
|
|
||||||
/// In little endian systems, the bit for detecting short vs long length is
|
|
||||||
/// the most significant bit of the length field, which is the very last byte
|
|
||||||
/// in the struct.
|
|
||||||
///
|
|
||||||
/// This means if we detect that we are a short string, we can pass a pointer
|
|
||||||
/// to the entire struct (which is necessarily aligned already), and its first
|
|
||||||
/// contiguous N bytes represent the bytes in the string, where N is 15 on
|
|
||||||
/// 64-bit systems and 7 on 32-bit ones. The final byte is the msbyte where
|
|
||||||
/// we stored the flag, but it doesn't matter what's in that memory because the
|
|
||||||
/// str's length will be too low to encounter that anyway.
|
|
||||||
union InnerStr {
|
|
||||||
raw: [u8; 16],
|
|
||||||
long: LongStr,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Copy)]
|
|
||||||
#[repr(C)]
|
|
||||||
struct LongStr {
|
|
||||||
/// It is *crucial* that we have exactly this memory layout!
|
|
||||||
/// This is the same layout that Rust uses for string slices in memory,
|
|
||||||
/// which lets us mem::transmute long strings directly into them.
|
|
||||||
///
|
|
||||||
/// https://pramode.in/2016/09/13/using-unsafe-tricks-in-rust/
|
|
||||||
bytes: MaybeUninit<*const u8>,
|
|
||||||
length: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
// The bit pattern for an empty string. (1 and then all 0s.)
|
|
||||||
// Any other bit pattern means this is not an empty string!
|
|
||||||
#[cfg(target_pointer_width = "64")]
|
|
||||||
const EMPTY_STRING: usize =
|
|
||||||
0b1000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
|
|
||||||
|
|
||||||
#[cfg(target_pointer_width = "32")]
|
|
||||||
const EMPTY_STRING: usize = 0b1000_0000_0000_0000;
|
|
||||||
|
|
||||||
impl RocStr {
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn is_empty(&self) -> bool {
|
|
||||||
unsafe { self.0.long.length == EMPTY_STRING }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn empty() -> RocStr {
|
|
||||||
RocStr(InnerStr {
|
|
||||||
long: LongStr {
|
|
||||||
length: EMPTY_STRING,
|
|
||||||
// empty strings only ever have length set.
|
|
||||||
bytes: MaybeUninit::uninit(),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
let len_msbyte = self.len_msbyte();
|
|
||||||
|
|
||||||
if flagged_as_short_string(len_msbyte) {
|
|
||||||
// Drop the "is this a short string?" flag
|
|
||||||
let length: u8 = len_msbyte & 0b0111_1111;
|
|
||||||
|
|
||||||
length as usize
|
|
||||||
} else {
|
|
||||||
unsafe { self.0.long.length }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The most significant byte in the length. We use the last bit of this
|
|
||||||
/// byte to determine if we are a short string or a long string.
|
|
||||||
/// If this is a short string, we intentionally set that bit to 1.
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
|
|
||||||
fn len_msbyte(&self) -> u8 {
|
|
||||||
(unsafe { mem::transmute::<usize, [u8; 8]>(self.0.long.length) })[7]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(all(target_pointer_width = "32", target_endian = "little"))]
|
|
||||||
fn len_msbyte(&self) -> u8 {
|
|
||||||
(unsafe { mem::transmute::<usize, [u8; 4]>(self.long.length) })[3]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(all(target_pointer_width = "64", target_endian = "big"))]
|
|
||||||
fn len_msbyte(&self) -> u8 {
|
|
||||||
(unsafe { mem::transmute::<usize, [u8; 8]>(self.long.length) })[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
#[cfg(all(target_pointer_width = "32", target_endian = "big"))]
|
|
||||||
fn len_msbyte(&self) -> u8 {
|
|
||||||
(unsafe { mem::transmute::<usize, [u8; 4]>(self.long.length) })[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn flagged_as_short_string(len_msbyte: u8) -> bool {
|
|
||||||
// It's a short string iff the first bit of len_msbyte is 1.
|
|
||||||
len_msbyte & 0b1000_0000 == 0b1000_0000
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
fn with_short_string_flag_enabled(len_msbyte: u8) -> u8 {
|
|
||||||
// It's a short string iff the first bit of len_msbyte is 1.
|
|
||||||
len_msbyte | 0b1000_0000
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for RocStr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
// TODO do this without getting a cloned String involved
|
|
||||||
let string: String = self.clone().into();
|
|
||||||
|
|
||||||
string.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for RocStr {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
// TODO do this without getting a cloned String involved
|
|
||||||
let string: String = self.clone().into();
|
|
||||||
|
|
||||||
string.fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for LongStr {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
let length = self.length;
|
|
||||||
let layout = unsafe { Layout::from_size_align_unchecked(length, 8) };
|
|
||||||
let old_bytes_ptr = unsafe { self.bytes.assume_init() };
|
|
||||||
|
|
||||||
// Allocate memory for the new bytes. (We'll manually drop them later.)
|
|
||||||
let new_bytes_ptr = unsafe { alloc::alloc(layout) };
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
ptr::copy_nonoverlapping(old_bytes_ptr, new_bytes_ptr, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
LongStr {
|
|
||||||
bytes: MaybeUninit::new(new_bytes_ptr),
|
|
||||||
length,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Into<String> for RocStr {
|
|
||||||
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
|
|
||||||
fn into(self) -> String {
|
|
||||||
let len_msbyte = self.len_msbyte();
|
|
||||||
|
|
||||||
// TODO I'm not sure this works the way we want it to. Need to review.
|
|
||||||
|
|
||||||
if flagged_as_short_string(len_msbyte) {
|
|
||||||
// Drop the "is this a short string?" flag
|
|
||||||
let length: u8 = len_msbyte & 0b0111_1111;
|
|
||||||
let bytes_ptr = unsafe { &self.0.raw } as *const u8;
|
|
||||||
|
|
||||||
// These bytes are already aligned, so we can use them directly.
|
|
||||||
let bytes_slice: &[u8] = unsafe { slice::from_raw_parts(bytes_ptr, length as usize) };
|
|
||||||
|
|
||||||
(unsafe { str::from_utf8_unchecked(bytes_slice) }).to_string()
|
|
||||||
} else {
|
|
||||||
// If it's a long string, we already have the exact
|
|
||||||
// same memory layout as a Rust &str slice.
|
|
||||||
let str_slice = unsafe { mem::transmute::<[u8; 16], &str>(self.0.raw) };
|
|
||||||
let string = str_slice.to_string();
|
|
||||||
|
|
||||||
// Drop will deallocate the bytes, which we don't want in this case.
|
|
||||||
// String is using those bytes now!
|
|
||||||
mem::forget(self);
|
|
||||||
|
|
||||||
string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<String> for RocStr {
|
|
||||||
#[cfg(all(target_pointer_width = "64", target_endian = "little"))]
|
|
||||||
fn from(string: String) -> RocStr {
|
|
||||||
if string.is_empty() {
|
|
||||||
RocStr::empty()
|
|
||||||
} else {
|
|
||||||
let str_len = string.len();
|
|
||||||
|
|
||||||
if str_len <= 15 {
|
|
||||||
let mut buffer: [u8; 16] = [0; 16];
|
|
||||||
|
|
||||||
// Copy the raw bytes from the string into the buffer.
|
|
||||||
unsafe {
|
|
||||||
// Write into the buffer's bytes
|
|
||||||
ptr::copy_nonoverlapping(string.as_ptr(), buffer.as_ptr() as *mut u8, str_len);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the last byte in the buffer to be the length (with flag).
|
|
||||||
buffer[15] = with_short_string_flag_enabled(string.len() as u8);
|
|
||||||
|
|
||||||
RocStr(InnerStr { raw: buffer })
|
|
||||||
} else {
|
|
||||||
panic!("TODO: use mem::forget on the string and steal its bytes!");
|
|
||||||
// let bytes_ptr = string.as_bytes().clone().as_ptr();
|
|
||||||
// let long = LongStr {
|
|
||||||
// bytes: MaybeUninit::new(bytes_ptr),
|
|
||||||
// length: str_len,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// RocStr(InnerStr { long })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Clone for RocStr {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
let inner = if flagged_as_short_string(self.len_msbyte()) {
|
|
||||||
InnerStr {
|
|
||||||
raw: (unsafe { self.0.raw }),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
InnerStr {
|
|
||||||
long: (unsafe { self.0.long }),
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
RocStr(inner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Drop for RocStr {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
// If this is a LongStr, we need to deallocate its bytes.
|
|
||||||
// Otherwise we would have a memory leak!
|
|
||||||
if !flagged_as_short_string(self.len_msbyte()) {
|
|
||||||
let bytes_ptr = unsafe { self.0.long.bytes.assume_init() };
|
|
||||||
|
|
||||||
// If this was already dropped previously (most likely because the
|
|
||||||
// bytes were moved into a String), we shouldn't deallocate them.
|
|
||||||
if !bytes_ptr.is_null() {
|
|
||||||
let length = unsafe { self.0.long.length };
|
|
||||||
let layout = unsafe { Layout::from_size_align_unchecked(length, 8) };
|
|
||||||
|
|
||||||
// We don't need to call drop_in_place. We know bytes_ptr points to
|
|
||||||
// a plain u8 array, so there will for sure be no destructor to run.
|
|
||||||
unsafe {
|
|
||||||
alloc::dealloc(bytes_ptr as *mut u8, layout);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod test_roc_str {
|
|
||||||
use super::RocStr;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_str() {
|
|
||||||
assert!(RocStr::empty().is_empty());
|
|
||||||
assert_eq!(RocStr::empty().len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn fmt() {
|
|
||||||
assert_eq!("".to_string(), format!("{}", RocStr::empty()));
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue