mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Merge branch 'trunk' into dev-backend-num-to-float
This commit is contained in:
commit
9ef80444f1
90 changed files with 4872 additions and 1734 deletions
4
AUTHORS
4
AUTHORS
|
@ -56,3 +56,7 @@ Callum Dunster <cdunster@users.noreply.github.com>
|
||||||
Martin Stewart <MartinSStewart@gmail.com>
|
Martin Stewart <MartinSStewart@gmail.com>
|
||||||
James Hegedus <jthegedus@hey.com>
|
James Hegedus <jthegedus@hey.com>
|
||||||
Cristiano Piemontese <cristiano.piemontese@vidiemme.it>
|
Cristiano Piemontese <cristiano.piemontese@vidiemme.it>
|
||||||
|
Yann Simon <yann.simon.fr@gmail.com>
|
||||||
|
Shahn Hogan <shahnhogan@hotmail.com>
|
||||||
|
Tankor Smash <tankorsmash+github@gmail.com>
|
||||||
|
Matthias Devlamynck <matthias.devlamynck@mailoo.org>
|
||||||
|
|
|
@ -49,7 +49,7 @@ If you want to install it manually, you can also download Zig directly [here](ht
|
||||||
**version: 12.0.x**
|
**version: 12.0.x**
|
||||||
|
|
||||||
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
|
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
|
||||||
`/usr/local/opt/llvm@12/bin` to your `PATH`. You can confirm this worked by
|
`$(brew --prefix llvm@12)/bin` to your `PATH`. You can confirm this worked by
|
||||||
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
|
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
|
||||||
You may also need to manually specify a prefix env var like so:
|
You may also need to manually specify a prefix env var like so:
|
||||||
```
|
```
|
||||||
|
|
25
Cargo.lock
generated
25
Cargo.lock
generated
|
@ -125,15 +125,6 @@ dependencies = [
|
||||||
name = "arena-pool"
|
name = "arena-pool"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arraystring"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4d517c467117e1d8ca795bc8cc90857ff7f79790cca0e26f6e9462694ece0185"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -3144,7 +3135,7 @@ dependencies = [
|
||||||
name = "roc_ast"
|
name = "roc_ast"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arraystring",
|
"arrayvec 0.7.2",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"indoc",
|
"indoc",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -3327,7 +3318,7 @@ dependencies = [
|
||||||
name = "roc_editor"
|
name = "roc_editor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arraystring",
|
"arrayvec 0.7.2",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cgmath",
|
"cgmath",
|
||||||
|
@ -3383,6 +3374,7 @@ dependencies = [
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
"roc_test_utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3400,6 +3392,7 @@ dependencies = [
|
||||||
"roc_parse",
|
"roc_parse",
|
||||||
"roc_problem",
|
"roc_problem",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
"roc_reporting",
|
||||||
"roc_solve",
|
"roc_solve",
|
||||||
"roc_std",
|
"roc_std",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
|
@ -3524,9 +3517,7 @@ dependencies = [
|
||||||
name = "roc_parse"
|
name = "roc_parse"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"diff",
|
|
||||||
"encode_unicode",
|
"encode_unicode",
|
||||||
"indoc",
|
"indoc",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
@ -3535,6 +3526,7 @@ dependencies = [
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
"roc_test_utils",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -3599,6 +3591,13 @@ dependencies = [
|
||||||
name = "roc_std"
|
name = "roc_std"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "roc_test_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"pretty_assertions",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "roc_types"
|
name = "roc_types"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
|
@ -32,6 +32,7 @@ members = [
|
||||||
"code_markup",
|
"code_markup",
|
||||||
"reporting",
|
"reporting",
|
||||||
"roc_std",
|
"roc_std",
|
||||||
|
"test_utils",
|
||||||
"utils",
|
"utils",
|
||||||
"docs",
|
"docs",
|
||||||
"linker",
|
"linker",
|
||||||
|
|
|
@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
|
||||||
|
|
||||||
copy-dirs:
|
copy-dirs:
|
||||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||||
COPY --dir cli cli_utils compiler docs editor ast code_markup utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
COPY --dir cli cli_utils compiler docs editor ast code_markup utils test_utils reporting roc_std vendor examples linker Cargo.toml Cargo.lock version.txt ./
|
||||||
|
|
||||||
test-zig:
|
test-zig:
|
||||||
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
FROM +install-zig-llvm-valgrind-clippy-rustfmt
|
||||||
|
@ -117,7 +117,7 @@ build-nightly-release:
|
||||||
RUN git log --pretty=format:'%h' -n 1 >> version.txt
|
RUN git log --pretty=format:'%h' -n 1 >> version.txt
|
||||||
RUN printf " on: " >> version.txt
|
RUN printf " on: " >> version.txt
|
||||||
RUN date >> version.txt
|
RUN date >> version.txt
|
||||||
RUN cargo build --features with_sound --release
|
RUN RUSTFLAGS="-C target-cpu=x86-64-v2" cargo build --features with_sound --release
|
||||||
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc ../../LICENSE ../../LEGAL_DETAILS ../../examples/hello-world ../../examples/hello-rust ../../examples/hello-zig ../../compiler/builtins/bitcode/src/ ../../roc_std
|
RUN cd ./target/release && tar -czvf roc_linux_x86_64.tar.gz ./roc ../../LICENSE ../../LEGAL_DETAILS ../../examples/hello-world ../../examples/hello-rust ../../examples/hello-zig ../../compiler/builtins/bitcode/src/ ../../roc_std
|
||||||
SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
|
SAVE ARTIFACT ./target/release/roc_linux_x86_64.tar.gz AS LOCAL roc_linux_x86_64.tar.gz
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
Roc is a language for making delightful software.
|
Roc is a language for making delightful software.
|
||||||
|
|
||||||
If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md) may be of interest.
|
The [tutorial](TUTORIAL.md) is the best place to learn about how to use the language - it assumes no prior knowledge of Roc or similar languages. (If you already know [Elm](https://elm-lang.org/), then [Roc for Elm Programmers](https://github.com/rtfeldman/roc/blob/trunk/roc-for-elm-programmers.md) may be of interest.)
|
||||||
|
|
||||||
You can get help and discuss with other people on the [Roc Zulip chat](https://roc.zulipchat.com).
|
There's also a folder of [examples](https://github.com/rtfeldman/roc/tree/trunk/examples) - the [CLI example](https://github.com/rtfeldman/roc/tree/trunk/examples/cli) in particular is a reasonable starting point to build on.
|
||||||
|
|
||||||
|
[Roc Zulip chat](https://roc.zulipchat.com) is the best place to ask questions and get help! It's also where we discuss [ideas](https://roc.zulipchat.com/#narrow/stream/304641-ideas) for the language. If you want to get involved in contributing to the language, Zulip is also a great place to ask about good first projects.
|
||||||
|
|
||||||
## State of Roc
|
## State of Roc
|
||||||
|
|
||||||
|
|
1358
TUTORIAL.md
Normal file
1358
TUTORIAL.md
Normal file
File diff suppressed because it is too large
Load diff
|
@ -17,7 +17,7 @@ roc_problem = { path = "../compiler/problem" }
|
||||||
roc_types = { path = "../compiler/types" }
|
roc_types = { path = "../compiler/types" }
|
||||||
roc_unify = { path = "../compiler/unify"}
|
roc_unify = { path = "../compiler/unify"}
|
||||||
roc_load = { path = "../compiler/load" }
|
roc_load = { path = "../compiler/load" }
|
||||||
arraystring = "0.3.0"
|
arrayvec = "0.7.2"
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
libc = "0.2.106"
|
libc = "0.2.106"
|
||||||
page_size = "0.4.2"
|
page_size = "0.4.2"
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use arraystring::{typenum::U30, ArrayString};
|
use arrayvec::ArrayString;
|
||||||
use roc_types::subs::Variable;
|
use roc_types::subs::Variable;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -12,7 +12,7 @@ use roc_module::symbol::Symbol;
|
||||||
|
|
||||||
use super::record_field::RecordField;
|
use super::record_field::RecordField;
|
||||||
|
|
||||||
pub type ArrString = ArrayString<U30>;
|
pub type ArrString = ArrayString<24>;
|
||||||
|
|
||||||
// TODO make the inner types private?
|
// TODO make the inner types private?
|
||||||
pub type ExprId = NodeId<Expr2>;
|
pub type ExprId = NodeId<Expr2>;
|
||||||
|
|
|
@ -181,24 +181,16 @@ pub fn update_str_expr(
|
||||||
enum Either {
|
enum Either {
|
||||||
MyString(String),
|
MyString(String),
|
||||||
MyPoolStr(PoolStr),
|
MyPoolStr(PoolStr),
|
||||||
Done,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let insert_either = match str_expr {
|
let insert_either = match str_expr {
|
||||||
Expr2::SmallStr(arr_string) => {
|
Expr2::SmallStr(arr_string) => {
|
||||||
// TODO make sure this works for unicode "characters"
|
let mut new_string = arr_string.as_str().to_owned();
|
||||||
let insert_res = arr_string.try_insert(insert_index as u8, new_char);
|
|
||||||
|
|
||||||
match insert_res {
|
|
||||||
Ok(_) => Either::Done,
|
|
||||||
_ => {
|
|
||||||
let mut new_string = arr_string.as_str().to_string();
|
|
||||||
new_string.insert(insert_index, new_char);
|
new_string.insert(insert_index, new_char);
|
||||||
|
|
||||||
Either::MyString(new_string)
|
Either::MyString(new_string)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str),
|
Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str),
|
||||||
other => UnexpectedASTNode {
|
other => UnexpectedASTNode {
|
||||||
required_node_type: "SmallStr or Str",
|
required_node_type: "SmallStr or Str",
|
||||||
|
@ -222,7 +214,6 @@ pub fn update_str_expr(
|
||||||
|
|
||||||
pool.set(node_id, Expr2::Str(new_pool_str))
|
pool.set(node_id, Expr2::Str(new_pool_str))
|
||||||
}
|
}
|
||||||
Either::Done => (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
@ -46,15 +46,15 @@ pub fn format(files: std::vec::Vec<PathBuf>) {
|
||||||
);
|
);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let ast = ast.remove_spaces(&arena);
|
let ast_normalized = ast.remove_spaces(&arena);
|
||||||
let reparsed_ast = reparsed_ast.remove_spaces(&arena);
|
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
|
||||||
|
|
||||||
// HACK!
|
// HACK!
|
||||||
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
// We compare the debug format strings of the ASTs, because I'm finding in practice that _somewhere_ deep inside the ast,
|
||||||
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
// the PartialEq implementation is returning `false` even when the Debug-formatted impl is exactly the same.
|
||||||
// I don't have the patience to debug this right now, so let's leave it for another day...
|
// I don't have the patience to debug this right now, so let's leave it for another day...
|
||||||
// TODO: fix PartialEq impl on ast types
|
// TODO: fix PartialEq impl on ast types
|
||||||
if format!("{:?}", ast) != format!("{:?}", reparsed_ast) {
|
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
|
||||||
let mut fail_file = file.clone();
|
let mut fail_file = file.clone();
|
||||||
fail_file.set_extension("roc-format-failed");
|
fail_file.set_extension("roc-format-failed");
|
||||||
std::fs::write(&fail_file, &buf).unwrap();
|
std::fs::write(&fail_file, &buf).unwrap();
|
||||||
|
@ -76,6 +76,27 @@ pub fn format(files: std::vec::Vec<PathBuf>) {
|
||||||
after_file.display());
|
after_file.display());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now verify that the resultant formatting is _stable_ - i.e. that it doesn't change again if re-formatted
|
||||||
|
let mut reformatted_buf = String::new_in(&arena);
|
||||||
|
fmt_all(&arena, &mut reformatted_buf, reparsed_ast);
|
||||||
|
if buf.as_str() != reformatted_buf.as_str() {
|
||||||
|
let mut unstable_1_file = file.clone();
|
||||||
|
unstable_1_file.set_extension("roc-format-unstable-1");
|
||||||
|
std::fs::write(&unstable_1_file, &buf).unwrap();
|
||||||
|
|
||||||
|
let mut unstable_2_file = file.clone();
|
||||||
|
unstable_2_file.set_extension("roc-format-unstable-2");
|
||||||
|
std::fs::write(&unstable_2_file, &reformatted_buf).unwrap();
|
||||||
|
|
||||||
|
internal_error!(
|
||||||
|
"Formatting bug; formatting is not stable. Reformatting the formatted file changed it again.\n\n\
|
||||||
|
I wrote the result of formatting to this file for debugging purposes:\n{}\n\n\
|
||||||
|
I wrote the result of double-formatting here:\n{}\n\n",
|
||||||
|
unstable_1_file.display(),
|
||||||
|
unstable_2_file.display());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If all the checks above passed, actually write out the new file.
|
||||||
std::fs::write(&file, &buf).unwrap();
|
std::fs::write(&file, &buf).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ use roc_cli::build;
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let matches = build_app().get_matches();
|
let matches = build_app().get_matches();
|
||||||
|
|
||||||
let exit_code = match matches.subcommand_name() {
|
let exit_code = match matches.subcommand() {
|
||||||
None => {
|
None => {
|
||||||
match matches.index_of(ROC_FILE) {
|
match matches.index_of(ROC_FILE) {
|
||||||
Some(arg_index) => {
|
Some(arg_index) => {
|
||||||
|
@ -37,14 +37,10 @@ fn main() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(CMD_BUILD) => Ok(build(
|
Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?),
|
||||||
matches.subcommand_matches(CMD_BUILD).unwrap(),
|
Some((CMD_CHECK, matches)) => {
|
||||||
BuildConfig::BuildOnly,
|
|
||||||
)?),
|
|
||||||
Some(CMD_CHECK) => {
|
|
||||||
let arena = bumpalo::Bump::new();
|
let arena = bumpalo::Bump::new();
|
||||||
|
|
||||||
let matches = matches.subcommand_matches(CMD_CHECK).unwrap();
|
|
||||||
let emit_timings = matches.is_present(FLAG_TIME);
|
let emit_timings = matches.is_present(FLAG_TIME);
|
||||||
let filename = matches.value_of(ROC_FILE).unwrap();
|
let filename = matches.value_of(ROC_FILE).unwrap();
|
||||||
let roc_file_path = PathBuf::from(filename);
|
let roc_file_path = PathBuf::from(filename);
|
||||||
|
@ -66,16 +62,14 @@ fn main() -> io::Result<()> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(CMD_REPL) => {
|
Some((CMD_REPL, _)) => {
|
||||||
repl::main()?;
|
repl::main()?;
|
||||||
|
|
||||||
// Exit 0 if the repl exited normally
|
// Exit 0 if the repl exited normally
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
Some(CMD_EDIT) => {
|
Some((CMD_EDIT, matches)) => {
|
||||||
match matches
|
match matches
|
||||||
.subcommand_matches(CMD_EDIT)
|
|
||||||
.unwrap()
|
|
||||||
.values_of_os(DIRECTORY_OR_FILES)
|
.values_of_os(DIRECTORY_OR_FILES)
|
||||||
.map(|mut values| values.next())
|
.map(|mut values| values.next())
|
||||||
{
|
{
|
||||||
|
@ -90,11 +84,8 @@ fn main() -> io::Result<()> {
|
||||||
// Exit 0 if the editor exited normally
|
// Exit 0 if the editor exited normally
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
Some(CMD_DOCS) => {
|
Some((CMD_DOCS, matches)) => {
|
||||||
let maybe_values = matches
|
let maybe_values = matches.values_of_os(DIRECTORY_OR_FILES);
|
||||||
.subcommand_matches(CMD_DOCS)
|
|
||||||
.unwrap()
|
|
||||||
.values_of_os(DIRECTORY_OR_FILES);
|
|
||||||
|
|
||||||
let mut values: Vec<OsString> = Vec::new();
|
let mut values: Vec<OsString> = Vec::new();
|
||||||
|
|
||||||
|
@ -128,11 +119,8 @@ fn main() -> io::Result<()> {
|
||||||
|
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
Some(CMD_FORMAT) => {
|
Some((CMD_FORMAT, matches)) => {
|
||||||
let maybe_values = matches
|
let maybe_values = matches.values_of_os(DIRECTORY_OR_FILES);
|
||||||
.subcommand_matches(CMD_FORMAT)
|
|
||||||
.unwrap()
|
|
||||||
.values_of_os(DIRECTORY_OR_FILES);
|
|
||||||
|
|
||||||
let mut values: Vec<OsString> = Vec::new();
|
let mut values: Vec<OsString> = Vec::new();
|
||||||
|
|
||||||
|
@ -166,7 +154,7 @@ fn main() -> io::Result<()> {
|
||||||
|
|
||||||
Ok(0)
|
Ok(0)
|
||||||
}
|
}
|
||||||
Some(CMD_VERSION) => {
|
Some((CMD_VERSION, _)) => {
|
||||||
println!("roc {}", concatcp!(include_str!("../../version.txt"), "\n"));
|
println!("roc {}", concatcp!(include_str!("../../version.txt"), "\n"));
|
||||||
|
|
||||||
Ok(0)
|
Ok(0)
|
||||||
|
|
79
cli_utils/Cargo.lock
generated
79
cli_utils/Cargo.lock
generated
|
@ -90,15 +90,6 @@ dependencies = [
|
||||||
"num-traits",
|
"num-traits",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "arraystring"
|
|
||||||
version = "0.3.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4d517c467117e1d8ca795bc8cc90857ff7f79790cca0e26f6e9462694ece0185"
|
|
||||||
dependencies = [
|
|
||||||
"typenum",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "arrayvec"
|
name = "arrayvec"
|
||||||
version = "0.5.2"
|
version = "0.5.2"
|
||||||
|
@ -191,6 +182,18 @@ dependencies = [
|
||||||
"typenum",
|
"typenum",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitvec"
|
||||||
|
version = "0.22.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5237f00a8c86130a0cc317830e558b966dd7850d48a953d998c813f01a41b527"
|
||||||
|
dependencies = [
|
||||||
|
"funty",
|
||||||
|
"radium",
|
||||||
|
"tap",
|
||||||
|
"wyz",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "block"
|
name = "block"
|
||||||
version = "0.1.6"
|
version = "0.1.6"
|
||||||
|
@ -968,6 +971,12 @@ 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 = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "funty"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures"
|
name = "futures"
|
||||||
version = "0.3.17"
|
version = "0.3.17"
|
||||||
|
@ -1919,6 +1928,28 @@ dependencies = [
|
||||||
"ttf-parser 0.13.2",
|
"ttf-parser 0.13.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packed_struct"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c48e482b9a59ad6c2cdb06f7725e7bd33fe3525baaf4699fde7bfea6a5b77b1"
|
||||||
|
dependencies = [
|
||||||
|
"bitvec",
|
||||||
|
"packed_struct_codegen",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packed_struct_codegen"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56e3692b867ec1d48ccb441e951637a2cc3130d0912c0059e48319e1c83e44bc"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "page_size"
|
name = "page_size"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -2210,6 +2241,12 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "radium"
|
||||||
|
version = "0.6.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rand"
|
name = "rand"
|
||||||
version = "0.8.4"
|
version = "0.8.4"
|
||||||
|
@ -2375,7 +2412,7 @@ dependencies = [
|
||||||
name = "roc_ast"
|
name = "roc_ast"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arraystring",
|
"arrayvec 0.7.2",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"libc",
|
"libc",
|
||||||
"page_size",
|
"page_size",
|
||||||
|
@ -2545,7 +2582,7 @@ dependencies = [
|
||||||
name = "roc_editor"
|
name = "roc_editor"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arraystring",
|
"arrayvec 0.7.2",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"cgmath",
|
"cgmath",
|
||||||
|
@ -2603,12 +2640,14 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"object 0.26.2",
|
"object 0.26.2",
|
||||||
|
"packed_struct",
|
||||||
"roc_builtins",
|
"roc_builtins",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_mono",
|
"roc_mono",
|
||||||
"roc_problem",
|
"roc_problem",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
"roc_reporting",
|
||||||
"roc_solve",
|
"roc_solve",
|
||||||
"roc_types",
|
"roc_types",
|
||||||
"roc_unify",
|
"roc_unify",
|
||||||
|
@ -2771,6 +2810,8 @@ dependencies = [
|
||||||
name = "roc_solve"
|
name = "roc_solve"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"arrayvec 0.7.2",
|
||||||
|
"bumpalo",
|
||||||
"roc_can",
|
"roc_can",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
|
@ -2787,6 +2828,7 @@ version = "0.1.0"
|
||||||
name = "roc_types"
|
name = "roc_types"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
"roc_collections",
|
"roc_collections",
|
||||||
"roc_module",
|
"roc_module",
|
||||||
"roc_region",
|
"roc_region",
|
||||||
|
@ -3163,6 +3205,12 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tap"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "target-lexicon"
|
name = "target-lexicon"
|
||||||
version = "0.12.2"
|
version = "0.12.2"
|
||||||
|
@ -3807,6 +3855,15 @@ dependencies = [
|
||||||
"rand_core 0.6.3",
|
"rand_core 0.6.3",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wyz"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "129e027ad65ce1453680623c3fb5163cbf7107bfe1aa32257e7d0e63f9ced188"
|
||||||
|
dependencies = [
|
||||||
|
"tap",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "x11-clipboard"
|
name = "x11-clipboard"
|
||||||
version = "0.5.3"
|
version = "0.5.3"
|
||||||
|
|
|
@ -53,14 +53,14 @@ The process of evaluation is basically to transform an `Expr` into the simplest
|
||||||
|
|
||||||
For example, let's say we had this code:
|
For example, let's say we had this code:
|
||||||
|
|
||||||
"1 + 2 - 3"
|
"1 + 8 - 3"
|
||||||
|
|
||||||
The parser will translate this into the following `Expr`:
|
The parser will translate this into the following `Expr`:
|
||||||
|
|
||||||
BinOp(
|
BinOp(
|
||||||
Int(1),
|
Int(1),
|
||||||
Plus,
|
Plus,
|
||||||
BinOp(Int(2), Minus, Int(3))
|
BinOp(Int(8), Minus, Int(3))
|
||||||
)
|
)
|
||||||
|
|
||||||
The `eval` function will take this `Expr` and translate it into this much simpler `Expr`:
|
The `eval` function will take this `Expr` and translate it into this much simpler `Expr`:
|
||||||
|
|
|
@ -491,11 +491,12 @@ fn gen_from_mono_module_dev_wasm32(
|
||||||
loaded: MonomorphizedModule,
|
loaded: MonomorphizedModule,
|
||||||
app_o_file: &Path,
|
app_o_file: &Path,
|
||||||
) -> CodeGenTiming {
|
) -> CodeGenTiming {
|
||||||
let mut procedures = MutMap::default();
|
let MonomorphizedModule {
|
||||||
|
module_id,
|
||||||
for (key, proc) in loaded.procedures {
|
procedures,
|
||||||
procedures.insert(key, proc);
|
mut interns,
|
||||||
}
|
..
|
||||||
|
} = loaded;
|
||||||
|
|
||||||
let exposed_to_host = loaded
|
let exposed_to_host = loaded
|
||||||
.exposed_to_host
|
.exposed_to_host
|
||||||
|
@ -505,11 +506,11 @@ fn gen_from_mono_module_dev_wasm32(
|
||||||
|
|
||||||
let env = roc_gen_wasm::Env {
|
let env = roc_gen_wasm::Env {
|
||||||
arena,
|
arena,
|
||||||
interns: loaded.interns,
|
module_id,
|
||||||
exposed_to_host,
|
exposed_to_host,
|
||||||
};
|
};
|
||||||
|
|
||||||
let bytes = roc_gen_wasm::build_module(&env, procedures).unwrap();
|
let bytes = roc_gen_wasm::build_module(&env, &mut interns, procedures).unwrap();
|
||||||
|
|
||||||
std::fs::write(&app_o_file, &bytes).expect("failed to write object to file");
|
std::fs::write(&app_o_file, &bytes).expect("failed to write object to file");
|
||||||
|
|
||||||
|
@ -533,8 +534,7 @@ fn gen_from_mono_module_dev_assembly(
|
||||||
generate_allocators,
|
generate_allocators,
|
||||||
};
|
};
|
||||||
|
|
||||||
let module_object = roc_gen_dev::build_module(&env, target, loaded.procedures)
|
let module_object = roc_gen_dev::build_module(&env, target, loaded.procedures);
|
||||||
.expect("failed to compile module");
|
|
||||||
|
|
||||||
let module_out = module_object
|
let module_out = module_object
|
||||||
.write()
|
.write()
|
||||||
|
|
|
@ -2,6 +2,7 @@ const std = @import("std");
|
||||||
const mem = std.mem;
|
const mem = std.mem;
|
||||||
const Builder = std.build.Builder;
|
const Builder = std.build.Builder;
|
||||||
const CrossTarget = std.zig.CrossTarget;
|
const CrossTarget = std.zig.CrossTarget;
|
||||||
|
const Arch = std.Target.Cpu.Arch;
|
||||||
|
|
||||||
pub fn build(b: *Builder) void {
|
pub fn build(b: *Builder) void {
|
||||||
// b.setPreferredReleaseMode(builtin.Mode.Debug
|
// b.setPreferredReleaseMode(builtin.Mode.Debug
|
||||||
|
@ -21,7 +22,12 @@ pub fn build(b: *Builder) void {
|
||||||
test_step.dependOn(&main_tests.step);
|
test_step.dependOn(&main_tests.step);
|
||||||
|
|
||||||
// Targets
|
// Targets
|
||||||
const host_target = b.standardTargetOptions(.{});
|
const host_target = b.standardTargetOptions(.{
|
||||||
|
.default_target = CrossTarget{
|
||||||
|
.cpu_model = .baseline,
|
||||||
|
// TODO allow for native target for maximum speed
|
||||||
|
}
|
||||||
|
});
|
||||||
const i386_target = makeI386Target();
|
const i386_target = makeI386Target();
|
||||||
const wasm32_target = makeWasm32Target();
|
const wasm32_target = makeWasm32Target();
|
||||||
|
|
||||||
|
|
|
@ -138,6 +138,7 @@ comptime {
|
||||||
|
|
||||||
comptime {
|
comptime {
|
||||||
exportUtilsFn(utils.test_panic, "test_panic");
|
exportUtilsFn(utils.test_panic, "test_panic");
|
||||||
|
exportUtilsFn(utils.increfC, "incref");
|
||||||
exportUtilsFn(utils.decrefC, "decref");
|
exportUtilsFn(utils.decrefC, "decref");
|
||||||
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");
|
||||||
|
|
||||||
|
|
|
@ -104,6 +104,12 @@ pub const IntWidth = enum(u8) {
|
||||||
I128 = 9,
|
I128 = 9,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn increfC(ptr_to_refcount: *isize, amount: isize) callconv(.C) void {
|
||||||
|
var refcount = ptr_to_refcount.*;
|
||||||
|
var masked_amount = if (refcount == REFCOUNT_MAX_ISIZE) 0 else amount;
|
||||||
|
ptr_to_refcount.* = refcount + masked_amount;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn decrefC(
|
pub fn decrefC(
|
||||||
bytes_or_null: ?[*]isize,
|
bytes_or_null: ?[*]isize,
|
||||||
alignment: u32,
|
alignment: u32,
|
||||||
|
@ -261,3 +267,17 @@ pub const UpdateMode = enum(u8) {
|
||||||
Immutable = 0,
|
Immutable = 0,
|
||||||
InPlace = 1,
|
InPlace = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
test "increfC, refcounted data" {
|
||||||
|
var mock_rc: isize = REFCOUNT_ONE_ISIZE + 17;
|
||||||
|
var ptr_to_refcount: *isize = &mock_rc;
|
||||||
|
increfC(ptr_to_refcount, 2);
|
||||||
|
try std.testing.expectEqual(mock_rc, REFCOUNT_ONE_ISIZE + 19);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "increfC, static data" {
|
||||||
|
var mock_rc: isize = REFCOUNT_MAX_ISIZE;
|
||||||
|
var ptr_to_refcount: *isize = &mock_rc;
|
||||||
|
increfC(ptr_to_refcount, 2);
|
||||||
|
try std.testing.expectEqual(mock_rc, REFCOUNT_MAX_ISIZE);
|
||||||
|
}
|
||||||
|
|
|
@ -61,6 +61,7 @@ interface Num
|
||||||
addChecked,
|
addChecked,
|
||||||
atan,
|
atan,
|
||||||
acos,
|
acos,
|
||||||
|
toStr,
|
||||||
Signed128,
|
Signed128,
|
||||||
Signed64,
|
Signed64,
|
||||||
Signed32,
|
Signed32,
|
||||||
|
|
|
@ -10,8 +10,6 @@ interface Str
|
||||||
countGraphemes,
|
countGraphemes,
|
||||||
startsWith,
|
startsWith,
|
||||||
endsWith,
|
endsWith,
|
||||||
fromInt,
|
|
||||||
fromFloat,
|
|
||||||
fromUtf8,
|
fromUtf8,
|
||||||
Utf8Problem,
|
Utf8Problem,
|
||||||
Utf8ByteProblem,
|
Utf8ByteProblem,
|
||||||
|
|
|
@ -309,5 +309,6 @@ pub const DEC_MUL_WITH_OVERFLOW: &str = "roc_builtins.dec.mul_with_overflow";
|
||||||
pub const DEC_DIV: &str = "roc_builtins.dec.div";
|
pub const DEC_DIV: &str = "roc_builtins.dec.div";
|
||||||
|
|
||||||
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
pub const UTILS_TEST_PANIC: &str = "roc_builtins.utils.test_panic";
|
||||||
|
pub const UTILS_INCREF: &str = "roc_builtins.utils.incref";
|
||||||
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
pub const UTILS_DECREF: &str = "roc_builtins.utils.decref";
|
||||||
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
pub const UTILS_DECREF_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";
|
||||||
|
|
|
@ -385,6 +385,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
// maxI128 : I128
|
// maxI128 : I128
|
||||||
add_type!(Symbol::NUM_MAX_I128, i128_type());
|
add_type!(Symbol::NUM_MAX_I128, i128_type());
|
||||||
|
|
||||||
|
// toStr : Num a -> Str
|
||||||
|
add_top_level_function_type!(
|
||||||
|
Symbol::NUM_TO_STR,
|
||||||
|
vec![num_type(flex(TVAR1))],
|
||||||
|
Box::new(str_type())
|
||||||
|
);
|
||||||
|
|
||||||
// Float module
|
// Float module
|
||||||
|
|
||||||
// div : Float a, Float a -> Float a
|
// div : Float a, Float a -> Float a
|
||||||
|
@ -618,13 +625,6 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
Box::new(nat_type())
|
Box::new(nat_type())
|
||||||
);
|
);
|
||||||
|
|
||||||
// fromInt : Int a -> Str
|
|
||||||
add_top_level_function_type!(
|
|
||||||
Symbol::STR_FROM_INT,
|
|
||||||
vec![int_type(flex(TVAR1))],
|
|
||||||
Box::new(str_type())
|
|
||||||
);
|
|
||||||
|
|
||||||
// repeat : Str, Nat -> Str
|
// repeat : Str, Nat -> Str
|
||||||
add_top_level_function_type!(
|
add_top_level_function_type!(
|
||||||
Symbol::STR_REPEAT,
|
Symbol::STR_REPEAT,
|
||||||
|
@ -702,13 +702,6 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
|
||||||
Box::new(list_type(u8_type()))
|
Box::new(list_type(u8_type()))
|
||||||
);
|
);
|
||||||
|
|
||||||
// fromFloat : Float a -> Str
|
|
||||||
add_top_level_function_type!(
|
|
||||||
Symbol::STR_FROM_FLOAT,
|
|
||||||
vec![float_type(flex(TVAR1))],
|
|
||||||
Box::new(str_type())
|
|
||||||
);
|
|
||||||
|
|
||||||
// List module
|
// List module
|
||||||
|
|
||||||
// get : List elem, Nat -> Result elem [ OutOfBounds ]*
|
// get : List elem, Nat -> Result elem [ OutOfBounds ]*
|
||||||
|
|
|
@ -61,11 +61,9 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||||
STR_STARTS_WITH_CODE_PT => str_starts_with_code_point,
|
STR_STARTS_WITH_CODE_PT => str_starts_with_code_point,
|
||||||
STR_ENDS_WITH => str_ends_with,
|
STR_ENDS_WITH => str_ends_with,
|
||||||
STR_COUNT_GRAPHEMES => str_count_graphemes,
|
STR_COUNT_GRAPHEMES => str_count_graphemes,
|
||||||
STR_FROM_INT => str_from_int,
|
|
||||||
STR_FROM_UTF8 => str_from_utf8,
|
STR_FROM_UTF8 => str_from_utf8,
|
||||||
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_REPEAT => str_repeat,
|
STR_REPEAT => str_repeat,
|
||||||
STR_TRIM => str_trim,
|
STR_TRIM => str_trim,
|
||||||
STR_TRIM_LEFT => str_trim_left,
|
STR_TRIM_LEFT => str_trim_left,
|
||||||
|
@ -192,6 +190,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
|
||||||
NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
|
NUM_SHIFT_RIGHT_ZERO_FILL => num_shift_right_zf_by,
|
||||||
NUM_INT_CAST=> num_int_cast,
|
NUM_INT_CAST=> num_int_cast,
|
||||||
NUM_MAX_I128=> num_max_i128,
|
NUM_MAX_I128=> num_max_i128,
|
||||||
|
NUM_TO_STR => num_to_str,
|
||||||
RESULT_MAP => result_map,
|
RESULT_MAP => result_map,
|
||||||
RESULT_MAP_ERR => result_map_err,
|
RESULT_MAP_ERR => result_map_err,
|
||||||
RESULT_AFTER => result_after,
|
RESULT_AFTER => result_after,
|
||||||
|
@ -369,6 +368,26 @@ fn num_min_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Num.toStr : Num a -> Str
|
||||||
|
fn num_to_str(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
|
let num_var = var_store.fresh();
|
||||||
|
let str_var = var_store.fresh();
|
||||||
|
|
||||||
|
let body = RunLowLevel {
|
||||||
|
op: LowLevel::NumToStr,
|
||||||
|
args: vec![(num_var, Var(Symbol::ARG_1))],
|
||||||
|
ret_var: str_var,
|
||||||
|
};
|
||||||
|
|
||||||
|
defn(
|
||||||
|
symbol,
|
||||||
|
vec![(num_var, Symbol::ARG_1)],
|
||||||
|
var_store,
|
||||||
|
body,
|
||||||
|
str_var,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Bool.isEq : val, val -> Bool
|
/// Bool.isEq : val, val -> Bool
|
||||||
fn bool_eq(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn bool_eq(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let arg_var = var_store.fresh();
|
let arg_var = var_store.fresh();
|
||||||
|
@ -1436,26 +1455,6 @@ fn str_count_graphemes(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Str.fromInt : Int * -> Str
|
|
||||||
fn str_from_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|
||||||
let int_var = var_store.fresh();
|
|
||||||
let str_var = var_store.fresh();
|
|
||||||
|
|
||||||
let body = RunLowLevel {
|
|
||||||
op: LowLevel::StrFromInt,
|
|
||||||
args: vec![(int_var, Var(Symbol::ARG_1))],
|
|
||||||
ret_var: str_var,
|
|
||||||
};
|
|
||||||
|
|
||||||
defn(
|
|
||||||
symbol,
|
|
||||||
vec![(int_var, Symbol::ARG_1)],
|
|
||||||
var_store,
|
|
||||||
body,
|
|
||||||
str_var,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 { byteIndex : Nat, problem : Utf8Problem } } ]*
|
/// Str.fromUtf8 : List U8 -> Result Str [ BadUtf8 { byteIndex : Nat, problem : Utf8Problem } } ]*
|
||||||
fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let bytes_var = var_store.fresh();
|
let bytes_var = var_store.fresh();
|
||||||
|
@ -1738,26 +1737,6 @@ fn str_to_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
lowlevel_1(symbol, LowLevel::StrToUtf8, var_store)
|
lowlevel_1(symbol, LowLevel::StrToUtf8, var_store)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Str.fromFloat : Float * -> Str
|
|
||||||
fn str_from_float(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
|
||||||
let float_var = var_store.fresh();
|
|
||||||
let str_var = var_store.fresh();
|
|
||||||
|
|
||||||
let body = RunLowLevel {
|
|
||||||
op: LowLevel::StrFromFloat,
|
|
||||||
args: vec![(float_var, Var(Symbol::ARG_1))],
|
|
||||||
ret_var: str_var,
|
|
||||||
};
|
|
||||||
|
|
||||||
defn(
|
|
||||||
symbol,
|
|
||||||
vec![(float_var, Symbol::ARG_1)],
|
|
||||||
var_store,
|
|
||||||
body,
|
|
||||||
str_var,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// List.concat : List elem, List elem -> List elem
|
/// List.concat : List elem, List elem -> List elem
|
||||||
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
|
||||||
let list_var = var_store.fresh();
|
let list_var = var_store.fresh();
|
||||||
|
|
|
@ -15,3 +15,4 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions = "1.0.0"
|
pretty_assertions = "1.0.0"
|
||||||
indoc = "1.0.3"
|
indoc = "1.0.3"
|
||||||
|
roc_test_utils = { path = "../../test_utils" }
|
||||||
|
|
|
@ -139,7 +139,12 @@ impl<'a> Formattable<'a> for Expr<'a> {
|
||||||
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||||
} else {
|
} else {
|
||||||
buf.push('(');
|
buf.push('(');
|
||||||
sub_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
sub_expr.format_with_options(
|
||||||
|
buf,
|
||||||
|
Parens::NotNeeded,
|
||||||
|
Newlines::Yes,
|
||||||
|
indent + INDENT,
|
||||||
|
);
|
||||||
buf.push(')');
|
buf.push(')');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -576,8 +581,10 @@ fn fmt_when<'a>(
|
||||||
while let Some(branch) = it.next() {
|
while let Some(branch) = it.next() {
|
||||||
let patterns = &branch.patterns;
|
let patterns = &branch.patterns;
|
||||||
let expr = &branch.value;
|
let expr = &branch.value;
|
||||||
add_spaces(buf, indent + INDENT);
|
|
||||||
let (first_pattern, rest) = patterns.split_first().unwrap();
|
let (first_pattern, rest) = patterns.split_first().unwrap();
|
||||||
|
if !has_newline_before(&first_pattern.value) {
|
||||||
|
add_spaces(buf, indent + INDENT);
|
||||||
|
}
|
||||||
let is_multiline = match rest.last() {
|
let is_multiline = match rest.last() {
|
||||||
None => false,
|
None => false,
|
||||||
Some(last_pattern) => first_pattern.region.start_line != last_pattern.region.end_line,
|
Some(last_pattern) => first_pattern.region.start_line != last_pattern.region.end_line,
|
||||||
|
@ -591,12 +598,9 @@ fn fmt_when<'a>(
|
||||||
);
|
);
|
||||||
for when_pattern in rest {
|
for when_pattern in rest {
|
||||||
if is_multiline {
|
if is_multiline {
|
||||||
buf.push_str("\n");
|
newline(buf, indent + INDENT);
|
||||||
add_spaces(buf, indent + INDENT);
|
|
||||||
buf.push_str("| ");
|
|
||||||
} else {
|
|
||||||
buf.push_str(" | ");
|
|
||||||
}
|
}
|
||||||
|
buf.push_str(" | ");
|
||||||
fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded);
|
fmt_pattern(buf, &when_pattern.value, indent + INDENT, Parens::NotNeeded);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -605,9 +609,9 @@ fn fmt_when<'a>(
|
||||||
guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
guard_expr.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent + INDENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.push_str(" ->\n");
|
buf.push_str(" ->");
|
||||||
|
newline(buf, indent + INDENT * 2);
|
||||||
|
|
||||||
add_spaces(buf, indent + (INDENT * 2));
|
|
||||||
match expr.value {
|
match expr.value {
|
||||||
Expr::SpaceBefore(nested, spaces) => {
|
Expr::SpaceBefore(nested, spaces) => {
|
||||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + (INDENT * 2));
|
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent + (INDENT * 2));
|
||||||
|
@ -635,6 +639,16 @@ fn fmt_when<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn has_newline_before(value: &Pattern) -> bool {
|
||||||
|
match value {
|
||||||
|
Pattern::SpaceAfter(v, _) => has_newline_before(v),
|
||||||
|
Pattern::SpaceBefore(v, spaces) => {
|
||||||
|
v.is_multiline() && spaces.last().map(|s| s.is_newline()).unwrap_or(false)
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fmt_expect<'a>(
|
fn fmt_expect<'a>(
|
||||||
buf: &mut String<'a>,
|
buf: &mut String<'a>,
|
||||||
condition: &'a Located<Expr<'a>>,
|
condition: &'a Located<Expr<'a>>,
|
||||||
|
@ -1098,6 +1112,7 @@ fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
||||||
BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false,
|
BinOp::Pizza | BinOp::Assignment | BinOp::HasType | BinOp::Backpassing => false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Expr::If(_, _) => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ where
|
||||||
match space {
|
match space {
|
||||||
Newline => {
|
Newline => {
|
||||||
if !encountered_comment && (consecutive_newlines < 2) {
|
if !encountered_comment && (consecutive_newlines < 2) {
|
||||||
if iter.peek() == Some(&&Newline) {
|
if iter.peek() == Some(&&Newline) && consecutive_newlines < 1 {
|
||||||
buf.push('\n');
|
buf.push('\n');
|
||||||
} else {
|
} else {
|
||||||
newline(buf, indent);
|
newline(buf, indent);
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate pretty_assertions;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate indoc;
|
extern crate indoc;
|
||||||
extern crate bumpalo;
|
extern crate bumpalo;
|
||||||
extern crate roc_fmt;
|
extern crate roc_fmt;
|
||||||
|
@ -14,24 +12,35 @@ mod test_fmt {
|
||||||
use roc_fmt::module::fmt_module;
|
use roc_fmt::module::fmt_module;
|
||||||
use roc_parse::module::{self, module_defs};
|
use roc_parse::module::{self, module_defs};
|
||||||
use roc_parse::parser::{Parser, State};
|
use roc_parse::parser::{Parser, State};
|
||||||
|
use roc_test_utils::assert_multiline_str_eq;
|
||||||
|
|
||||||
fn expr_formats_to(input: &str, expected: &str) {
|
// Not intended to be used directly in tests; please use expr_formats_to or expr_formats_same
|
||||||
|
fn expect_format_helper(input: &str, expected: &str) {
|
||||||
let arena = Bump::new();
|
let arena = Bump::new();
|
||||||
let input = input.trim_end();
|
|
||||||
let expected = expected.trim_end();
|
|
||||||
|
|
||||||
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
|
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
|
||||||
Ok(actual) => {
|
Ok(actual) => {
|
||||||
let mut buf = String::new_in(&arena);
|
let mut buf = String::new_in(&arena);
|
||||||
|
|
||||||
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
|
actual.format_with_options(&mut buf, Parens::NotNeeded, Newlines::Yes, 0);
|
||||||
|
|
||||||
assert_eq!(buf, expected)
|
assert_multiline_str_eq!(expected, buf.as_str());
|
||||||
}
|
}
|
||||||
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
|
Err(error) => panic!("Unexpected parse failure when parsing this for formatting:\n\n{}\n\nParse error was:\n\n{:?}\n\n", input, error)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn expr_formats_to(input: &str, expected: &str) {
|
||||||
|
let input = input.trim_end();
|
||||||
|
let expected = expected.trim_end();
|
||||||
|
|
||||||
|
// First check that input formats to the expected version
|
||||||
|
expect_format_helper(input, expected);
|
||||||
|
|
||||||
|
// Parse the expected result format it, asserting that it doesn't change
|
||||||
|
// It's important that formatting be stable / idempotent
|
||||||
|
expect_format_helper(expected, expected);
|
||||||
|
}
|
||||||
|
|
||||||
fn expr_formats_same(input: &str) {
|
fn expr_formats_same(input: &str) {
|
||||||
expr_formats_to(input, input);
|
expr_formats_to(input, input);
|
||||||
}
|
}
|
||||||
|
@ -56,7 +65,7 @@ mod test_fmt {
|
||||||
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
Err(error) => panic!("Unexpected parse failure when parsing this for defs formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
assert_eq!(buf, expected)
|
assert_multiline_str_eq!(expected, buf.as_str())
|
||||||
}
|
}
|
||||||
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
Err(error) => panic!("Unexpected parse failure when parsing this for module header formatting:\n\n{:?}\n\nParse error was:\n\n{:?}\n\n", src, error)
|
||||||
};
|
};
|
||||||
|
@ -89,6 +98,32 @@ mod test_fmt {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore]
|
||||||
|
fn def_with_comment_on_same_line() {
|
||||||
|
// TODO(joshuawarner32): make trailing comments format stabily
|
||||||
|
// This test currently fails because the comment ends up as SpaceBefore for the following `a`
|
||||||
|
// This works fine when formatted _once_ - but if you format again, the formatter wants to
|
||||||
|
// insert a newline between `a = "Hello"` and the comment, further muddying the waters.
|
||||||
|
// Clearly the formatter shouldn't be allowed to migrate a comment around like that.
|
||||||
|
expr_formats_to(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
a = "Hello" # This variable is for greeting
|
||||||
|
|
||||||
|
a
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
a = "Hello"
|
||||||
|
# This variable is for greeting
|
||||||
|
a
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn def_with_comment_and_extra_space() {
|
fn def_with_comment_and_extra_space() {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
|
@ -1835,7 +1870,7 @@ mod test_fmt {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_with_alternatives() {
|
fn when_with_alternatives_1() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
when b is
|
when b is
|
||||||
|
@ -1848,6 +1883,10 @@ mod test_fmt {
|
||||||
5
|
5
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_with_alternatives_2() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
r#"
|
r#"
|
||||||
when b is
|
when b is
|
||||||
|
@ -1857,6 +1896,10 @@ mod test_fmt {
|
||||||
1
|
1
|
||||||
"#
|
"#
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_with_alternatives_3() {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -1874,6 +1917,10 @@ mod test_fmt {
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_with_alternatives_4() {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
|
@ -1929,6 +1976,30 @@ mod test_fmt {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn with_multiline_pattern_indentation() {
|
||||||
|
expr_formats_to(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when b is 3->4
|
||||||
|
9
|
||||||
|
|8->9
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
when b is
|
||||||
|
3 ->
|
||||||
|
4
|
||||||
|
|
||||||
|
9
|
||||||
|
| 8 ->
|
||||||
|
9
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_with_moving_comments() {
|
fn when_with_moving_comments() {
|
||||||
expr_formats_to(
|
expr_formats_to(
|
||||||
|
@ -2111,6 +2182,35 @@ mod test_fmt {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inner_def_with_triple_newline_before() {
|
||||||
|
// The triple newline used to cause the code in add_spaces to not indent the next line,
|
||||||
|
// which of course is not the same tree (and nor does it parse)
|
||||||
|
expr_formats_to(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\x ->
|
||||||
|
m = 2
|
||||||
|
|
||||||
|
|
||||||
|
m1 = insert m n powerOf10
|
||||||
|
|
||||||
|
42
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
\x ->
|
||||||
|
m = 2
|
||||||
|
|
||||||
|
m1 = insert m n powerOf10
|
||||||
|
|
||||||
|
42
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn when_guard() {
|
fn when_guard() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
|
@ -2311,6 +2411,15 @@ mod test_fmt {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn binop_if() {
|
||||||
|
expr_formats_same(indoc!(
|
||||||
|
r#"
|
||||||
|
5 * (if x > 0 then 1 else 2)
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// UNARY OP
|
// UNARY OP
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2759,6 +2868,21 @@ mod test_fmt {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn backpassing_parens_body() {
|
||||||
|
expr_formats_same(indoc!(
|
||||||
|
r#"
|
||||||
|
Task.fromResult
|
||||||
|
(a, b <- binaryOp ctx
|
||||||
|
if a == b then
|
||||||
|
-1
|
||||||
|
else
|
||||||
|
0
|
||||||
|
)
|
||||||
|
"#
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn backpassing_body_on_newline() {
|
fn backpassing_body_on_newline() {
|
||||||
expr_formats_same(indoc!(
|
expr_formats_same(indoc!(
|
||||||
|
|
|
@ -16,6 +16,7 @@ roc_builtins = { path = "../builtins" }
|
||||||
roc_unify = { path = "../unify" }
|
roc_unify = { path = "../unify" }
|
||||||
roc_solve = { path = "../solve" }
|
roc_solve = { path = "../solve" }
|
||||||
roc_mono = { path = "../mono" }
|
roc_mono = { path = "../mono" }
|
||||||
|
roc_reporting = { path = "../../reporting" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
target-lexicon = "0.12.2"
|
target-lexicon = "0.12.2"
|
||||||
# TODO: Deal with the update of object to 0.27.
|
# TODO: Deal with the update of object to 0.27.
|
||||||
|
|
|
@ -5,6 +5,7 @@ use packed_struct::prelude::*;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::layout::Layout;
|
use roc_mono::layout::Layout;
|
||||||
|
use roc_reporting::internal_error;
|
||||||
|
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
@ -151,13 +152,15 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||||
saved_regs: &[AArch64GeneralReg],
|
saved_regs: &[AArch64GeneralReg],
|
||||||
requested_stack_size: i32,
|
requested_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<i32, String> {
|
) -> i32 {
|
||||||
// Full size is upcast to i64 to make sure we don't overflow here.
|
// Full size is upcast to i64 to make sure we don't overflow here.
|
||||||
let full_stack_size = requested_stack_size
|
let full_stack_size = match requested_stack_size
|
||||||
.checked_add(8 * saved_regs.len() as i32 + 8) // The extra 8 is space to store the frame pointer.
|
.checked_add(8 * saved_regs.len() as i32 + 8) // The extra 8 is space to store the frame pointer.
|
||||||
.ok_or("Ran out of stack space")?
|
.and_then(|size| size.checked_add(fn_call_stack_size))
|
||||||
.checked_add(fn_call_stack_size)
|
{
|
||||||
.ok_or("Ran out of stack space")?;
|
Some(size) => size,
|
||||||
|
_ => internal_error!("Ran out of stack space"),
|
||||||
|
};
|
||||||
let alignment = if full_stack_size <= 0 {
|
let alignment = if full_stack_size <= 0 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
|
@ -194,12 +197,12 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||||
offset -= 8;
|
offset -= 8;
|
||||||
AArch64Assembler::mov_base32_reg64(buf, offset, *reg);
|
AArch64Assembler::mov_base32_reg64(buf, offset, *reg);
|
||||||
}
|
}
|
||||||
Ok(aligned_stack_size)
|
aligned_stack_size
|
||||||
} else {
|
} else {
|
||||||
Ok(0)
|
0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err("Ran out of stack space".to_string())
|
internal_error!("Ran out of stack space");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,7 +212,7 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||||
saved_regs: &[AArch64GeneralReg],
|
saved_regs: &[AArch64GeneralReg],
|
||||||
aligned_stack_size: i32,
|
aligned_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
if aligned_stack_size > 0 {
|
if aligned_stack_size > 0 {
|
||||||
// All the following stores could be optimized by using `STP` to store pairs.
|
// All the following stores could be optimized by using `STP` to store pairs.
|
||||||
let mut offset = aligned_stack_size;
|
let mut offset = aligned_stack_size;
|
||||||
|
@ -230,7 +233,6 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||||
aligned_stack_size,
|
aligned_stack_size,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -239,8 +241,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||||
_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>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
Err("Loading args not yet implemented for AArch64".to_string())
|
unimplemented!("Loading args not yet implemented for AArch64");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -250,8 +252,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||||
_args: &'a [Symbol],
|
_args: &'a [Symbol],
|
||||||
_arg_layouts: &[Layout<'a>],
|
_arg_layouts: &[Layout<'a>],
|
||||||
_ret_layout: &Layout<'a>,
|
_ret_layout: &Layout<'a>,
|
||||||
) -> Result<u32, String> {
|
) -> u32 {
|
||||||
Err("Storing args not yet implemented for AArch64".to_string())
|
unimplemented!("Storing args not yet implemented for AArch64");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_struct<'a>(
|
fn return_struct<'a>(
|
||||||
|
@ -260,12 +262,12 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
|
||||||
_struct_size: u32,
|
_struct_size: u32,
|
||||||
_field_layouts: &[Layout<'a>],
|
_field_layouts: &[Layout<'a>],
|
||||||
_ret_reg: Option<AArch64GeneralReg>,
|
_ret_reg: Option<AArch64GeneralReg>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
Err("Returning structs not yet implemented for AArch64".to_string())
|
unimplemented!("Returning structs not yet implemented for AArch64");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn returns_via_arg_pointer(_ret_layout: &Layout) -> Result<bool, String> {
|
fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool {
|
||||||
Err("Returning via arg pointer not yet implemented for AArch64".to_string())
|
unimplemented!("Returning via arg pointer not yet implemented for AArch64");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -7,6 +7,7 @@ use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::layout::{Builtin, Layout};
|
use roc_mono::layout::{Builtin, Layout};
|
||||||
|
use roc_reporting::internal_error;
|
||||||
|
|
||||||
// Not sure exactly how I want to represent registers.
|
// Not sure exactly how I want to represent registers.
|
||||||
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
|
// If we want max speed, we would likely make them structs that impl the same trait to avoid ifs.
|
||||||
|
@ -152,7 +153,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
general_saved_regs: &[X86_64GeneralReg],
|
general_saved_regs: &[X86_64GeneralReg],
|
||||||
requested_stack_size: i32,
|
requested_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<i32, String> {
|
) -> i32 {
|
||||||
x86_64_generic_setup_stack(
|
x86_64_generic_setup_stack(
|
||||||
buf,
|
buf,
|
||||||
general_saved_regs,
|
general_saved_regs,
|
||||||
|
@ -167,7 +168,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
general_saved_regs: &[X86_64GeneralReg],
|
general_saved_regs: &[X86_64GeneralReg],
|
||||||
aligned_stack_size: i32,
|
aligned_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
x86_64_generic_cleanup_stack(
|
x86_64_generic_cleanup_stack(
|
||||||
buf,
|
buf,
|
||||||
general_saved_regs,
|
general_saved_regs,
|
||||||
|
@ -182,11 +183,11 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
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>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
||||||
let mut general_i = 0;
|
let mut general_i = 0;
|
||||||
let mut float_i = 0;
|
let mut float_i = 0;
|
||||||
if X86_64SystemV::returns_via_arg_pointer(ret_layout)? {
|
if X86_64SystemV::returns_via_arg_pointer(ret_layout) {
|
||||||
symbol_map.insert(
|
symbol_map.insert(
|
||||||
Symbol::RET_POINTER,
|
Symbol::RET_POINTER,
|
||||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
|
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
|
||||||
|
@ -251,21 +252,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
);
|
);
|
||||||
general_i += 2;
|
general_i += 2;
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
unimplemented!("loading strings args on the stack is not yet implemented");
|
||||||
"loading strings args on the stack is not yet implemented".to_string()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout::Struct(&[]) => {}
|
Layout::Struct(&[]) => {}
|
||||||
x => {
|
x => {
|
||||||
return Err(format!(
|
unimplemented!("Loading args with layout {:?} not yet implemented", x);
|
||||||
"Loading args with layout {:?} not yet implemented",
|
|
||||||
x
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -275,7 +270,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
args: &'a [Symbol],
|
args: &'a [Symbol],
|
||||||
arg_layouts: &[Layout<'a>],
|
arg_layouts: &[Layout<'a>],
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<u32, String> {
|
) -> u32 {
|
||||||
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||||
let mut general_i = 0;
|
let mut general_i = 0;
|
||||||
let mut float_i = 0;
|
let mut float_i = 0;
|
||||||
|
@ -284,22 +279,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
match ret_layout {
|
match ret_layout {
|
||||||
Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
|
Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
|
||||||
x => {
|
x => {
|
||||||
return Err(format!(
|
unimplemented!("receiving return type, {:?}, is not yet implemented", x);
|
||||||
"receiving return type, {:?}, is not yet implemented",
|
|
||||||
x
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||||
match layout {
|
match layout {
|
||||||
Layout::Builtin(single_register_integers!()) => {
|
Layout::Builtin(single_register_integers!()) => {
|
||||||
|
let storage = match symbol_map.get(&args[i]) {
|
||||||
|
Some(storage) => storage,
|
||||||
|
None => {
|
||||||
|
internal_error!("function argument does not reference any symbol")
|
||||||
|
}
|
||||||
|
};
|
||||||
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];
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::GeneralReg(reg)
|
SymbolStorage::GeneralReg(reg)
|
||||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||||
|
@ -308,18 +303,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||||
}
|
}
|
||||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||||
return Err(
|
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
general_i += 1;
|
general_i += 1;
|
||||||
} else {
|
} else {
|
||||||
// Load the value to the stack.
|
// Load the value to the stack.
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::GeneralReg(reg)
|
SymbolStorage::GeneralReg(reg)
|
||||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||||
|
@ -338,22 +328,23 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||||
return Err(
|
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stack_offset += 8;
|
stack_offset += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout::Builtin(single_register_floats!()) => {
|
Layout::Builtin(single_register_floats!()) => {
|
||||||
|
let storage = match symbol_map.get(&args[i]) {
|
||||||
|
Some(storage) => storage,
|
||||||
|
None => {
|
||||||
|
internal_error!("function argument does not reference any symbol")
|
||||||
|
}
|
||||||
|
};
|
||||||
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];
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::FloatReg(reg)
|
SymbolStorage::FloatReg(reg)
|
||||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||||
|
@ -363,16 +354,13 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
}
|
}
|
||||||
SymbolStorage::GeneralReg(_)
|
SymbolStorage::GeneralReg(_)
|
||||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
internal_error!("Cannot load general symbol into FloatReg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float_i += 1;
|
float_i += 1;
|
||||||
} else {
|
} else {
|
||||||
// Load the value to the stack.
|
// Load the value to the stack.
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::FloatReg(reg)
|
SymbolStorage::FloatReg(reg)
|
||||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||||
|
@ -392,48 +380,48 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
}
|
}
|
||||||
SymbolStorage::GeneralReg(_)
|
SymbolStorage::GeneralReg(_)
|
||||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
internal_error!("Cannot load general symbol into FloatReg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stack_offset += 8;
|
stack_offset += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout::Builtin(Builtin::Str) => {
|
Layout::Builtin(Builtin::Str) => {
|
||||||
|
let storage = match symbol_map.get(&args[i]) {
|
||||||
|
Some(storage) => storage,
|
||||||
|
None => {
|
||||||
|
internal_error!("function argument does not reference any symbol")
|
||||||
|
}
|
||||||
|
};
|
||||||
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
|
if general_i + 1 < Self::GENERAL_PARAM_REGS.len() {
|
||||||
// Load the value to the param reg.
|
// Load the value to the param reg.
|
||||||
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
|
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
|
||||||
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
|
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::Base { offset, .. } => {
|
SymbolStorage::Base { offset, .. } => {
|
||||||
X86_64Assembler::mov_reg64_base32(buf, dst1, *offset);
|
X86_64Assembler::mov_reg64_base32(buf, dst1, *offset);
|
||||||
X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8);
|
X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err("Strings only support being loaded from base offsets"
|
internal_error!(
|
||||||
.to_string());
|
"Strings only support being loaded from base offsets"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
general_i += 2;
|
general_i += 2;
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
unimplemented!(
|
||||||
"calling functions with strings on the stack is not yet implemented"
|
"calling functions with strings on the stack is not yet implemented"
|
||||||
.to_string(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout::Struct(&[]) => {}
|
Layout::Struct(&[]) => {}
|
||||||
x => {
|
x => {
|
||||||
return Err(format!(
|
unimplemented!("calling with arg type, {:?}, is not yet implemented", x);
|
||||||
"calling with arg type, {:?}, is not yet implemented",
|
|
||||||
x
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(stack_offset as u32)
|
stack_offset as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_struct<'a>(
|
fn return_struct<'a>(
|
||||||
|
@ -442,14 +430,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
|
||||||
_struct_size: u32,
|
_struct_size: u32,
|
||||||
_field_layouts: &[Layout<'a>],
|
_field_layouts: &[Layout<'a>],
|
||||||
_ret_reg: Option<X86_64GeneralReg>,
|
_ret_reg: Option<X86_64GeneralReg>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
Err("Returning structs not yet implemented for X86_64".to_string())
|
unimplemented!("Returning structs not yet implemented for X86_64");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String> {
|
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
|
||||||
// TODO: This may need to be more complex/extended to fully support the calling convention.
|
// TODO: This may need to be more complex/extended to fully support the calling convention.
|
||||||
// details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
|
// details here: https://github.com/hjl-tools/x86-psABI/wiki/x86-64-psABI-1.0.pdf
|
||||||
Ok(ret_layout.stack_size(PTR_SIZE) > 16)
|
ret_layout.stack_size(PTR_SIZE) > 16
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -551,7 +539,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
saved_regs: &[X86_64GeneralReg],
|
saved_regs: &[X86_64GeneralReg],
|
||||||
requested_stack_size: i32,
|
requested_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<i32, String> {
|
) -> i32 {
|
||||||
x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size, fn_call_stack_size)
|
x86_64_generic_setup_stack(buf, saved_regs, requested_stack_size, fn_call_stack_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,7 +549,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
saved_regs: &[X86_64GeneralReg],
|
saved_regs: &[X86_64GeneralReg],
|
||||||
aligned_stack_size: i32,
|
aligned_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size, fn_call_stack_size)
|
x86_64_generic_cleanup_stack(buf, saved_regs, aligned_stack_size, fn_call_stack_size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,10 +559,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
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>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
let mut base_offset = Self::SHADOW_SPACE_SIZE as i32 + 8; // 8 is the size of the pushed base pointer.
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout)? {
|
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) {
|
||||||
symbol_map.insert(
|
symbol_map.insert(
|
||||||
Symbol::RET_POINTER,
|
Symbol::RET_POINTER,
|
||||||
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]),
|
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]),
|
||||||
|
@ -595,27 +583,20 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
}
|
}
|
||||||
Layout::Builtin(Builtin::Str) => {
|
Layout::Builtin(Builtin::Str) => {
|
||||||
// I think this just needs to be passed on the stack, so not a huge deal.
|
// I think this just needs to be passed on the stack, so not a huge deal.
|
||||||
return Err(
|
unimplemented!(
|
||||||
"Passing str args with Windows fast call not yet implemented."
|
"Passing str args with Windows fast call not yet implemented."
|
||||||
.to_string(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Layout::Struct(&[]) => {}
|
Layout::Struct(&[]) => {}
|
||||||
x => {
|
x => {
|
||||||
return Err(format!(
|
unimplemented!("Loading args with layout {:?} not yet implemented", x);
|
||||||
"Loading args with layout {:?} not yet implemented",
|
|
||||||
x
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
base_offset += match layout {
|
base_offset += match layout {
|
||||||
Layout::Builtin(single_register_builtins!()) => 8,
|
Layout::Builtin(single_register_builtins!()) => 8,
|
||||||
x => {
|
x => {
|
||||||
return Err(format!(
|
unimplemented!("Loading args with layout {:?} not yet implemented", x);
|
||||||
"Loading args with layout {:?} not yet implemented",
|
|
||||||
x
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
symbol_map.insert(
|
symbol_map.insert(
|
||||||
|
@ -628,7 +609,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -638,29 +618,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
args: &'a [Symbol],
|
args: &'a [Symbol],
|
||||||
arg_layouts: &[Layout<'a>],
|
arg_layouts: &[Layout<'a>],
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<u32, String> {
|
) -> u32 {
|
||||||
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
|
||||||
// For most return layouts we will do nothing.
|
// For most return layouts we will do nothing.
|
||||||
// In some cases, we need to put the return address as the first arg.
|
// In some cases, we need to put the return address as the first arg.
|
||||||
match ret_layout {
|
match ret_layout {
|
||||||
Layout::Builtin(single_register_builtins!()) => {}
|
Layout::Builtin(single_register_builtins!()) => {}
|
||||||
x => {
|
x => {
|
||||||
return Err(format!(
|
unimplemented!("receiving return type, {:?}, is not yet implemented", x);
|
||||||
"receiving return type, {:?}, is not yet implemented",
|
|
||||||
x
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (i, layout) in arg_layouts.iter().enumerate() {
|
for (i, layout) in arg_layouts.iter().enumerate() {
|
||||||
match layout {
|
match layout {
|
||||||
Layout::Builtin(single_register_integers!()) => {
|
Layout::Builtin(single_register_integers!()) => {
|
||||||
|
let storage = match symbol_map.get(&args[i]) {
|
||||||
|
Some(storage) => storage,
|
||||||
|
None => {
|
||||||
|
internal_error!("function argument does not reference any symbol")
|
||||||
|
}
|
||||||
|
};
|
||||||
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[i];
|
let dst = Self::GENERAL_PARAM_REGS[i];
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::GeneralReg(reg)
|
SymbolStorage::GeneralReg(reg)
|
||||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
X86_64Assembler::mov_reg64_reg64(buf, dst, *reg);
|
||||||
|
@ -669,17 +649,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
X86_64Assembler::mov_reg64_base32(buf, dst, *offset);
|
||||||
}
|
}
|
||||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||||
return Err(
|
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Load the value to the stack.
|
// Load the value to the stack.
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::GeneralReg(reg)
|
SymbolStorage::GeneralReg(reg)
|
||||||
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
| SymbolStorage::BaseAndGeneralReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
X86_64Assembler::mov_stack32_reg64(buf, stack_offset, *reg);
|
||||||
|
@ -698,22 +673,23 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
|
||||||
return Err(
|
internal_error!("Cannot load floating point symbol into GeneralReg")
|
||||||
"Cannot load floating point symbol into GeneralReg".to_string()
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stack_offset += 8;
|
stack_offset += 8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout::Builtin(single_register_floats!()) => {
|
Layout::Builtin(single_register_floats!()) => {
|
||||||
|
let storage = match symbol_map.get(&args[i]) {
|
||||||
|
Some(storage) => storage,
|
||||||
|
None => {
|
||||||
|
internal_error!("function argument does not reference any symbol")
|
||||||
|
}
|
||||||
|
};
|
||||||
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[i];
|
let dst = Self::FLOAT_PARAM_REGS[i];
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::FloatReg(reg)
|
SymbolStorage::FloatReg(reg)
|
||||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
X86_64Assembler::mov_freg64_freg64(buf, dst, *reg);
|
||||||
|
@ -723,15 +699,12 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
}
|
}
|
||||||
SymbolStorage::GeneralReg(_)
|
SymbolStorage::GeneralReg(_)
|
||||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
unimplemented!("Cannot load general symbol into FloatReg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Load the value to the stack.
|
// Load the value to the stack.
|
||||||
match symbol_map
|
match storage {
|
||||||
.get(&args[i])
|
|
||||||
.ok_or("function argument does not reference any symbol")?
|
|
||||||
{
|
|
||||||
SymbolStorage::FloatReg(reg)
|
SymbolStorage::FloatReg(reg)
|
||||||
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
| SymbolStorage::BaseAndFloatReg { reg, .. } => {
|
||||||
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
X86_64Assembler::mov_stack32_freg64(buf, stack_offset, *reg);
|
||||||
|
@ -751,7 +724,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
}
|
}
|
||||||
SymbolStorage::GeneralReg(_)
|
SymbolStorage::GeneralReg(_)
|
||||||
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
| SymbolStorage::BaseAndGeneralReg { .. } => {
|
||||||
return Err("Cannot load general symbol into FloatReg".to_string())
|
unimplemented!("Cannot load general symbol into FloatReg")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stack_offset += 8;
|
stack_offset += 8;
|
||||||
|
@ -759,20 +732,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
}
|
}
|
||||||
Layout::Builtin(Builtin::Str) => {
|
Layout::Builtin(Builtin::Str) => {
|
||||||
// I think this just needs to be passed on the stack, so not a huge deal.
|
// I think this just needs to be passed on the stack, so not a huge deal.
|
||||||
return Err(
|
unimplemented!("Passing str args with Windows fast call not yet implemented.");
|
||||||
"Passing str args with Windows fast call not yet implemented.".to_string(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Layout::Struct(&[]) => {}
|
Layout::Struct(&[]) => {}
|
||||||
x => {
|
x => {
|
||||||
return Err(format!(
|
unimplemented!("calling with arg type, {:?}, is not yet implemented", x);
|
||||||
"calling with arg type, {:?}, is not yet implemented",
|
|
||||||
x
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(stack_offset as u32)
|
stack_offset as u32
|
||||||
}
|
}
|
||||||
|
|
||||||
fn return_struct<'a>(
|
fn return_struct<'a>(
|
||||||
|
@ -781,14 +749,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
|
||||||
_struct_size: u32,
|
_struct_size: u32,
|
||||||
_field_layouts: &[Layout<'a>],
|
_field_layouts: &[Layout<'a>],
|
||||||
_ret_reg: Option<X86_64GeneralReg>,
|
_ret_reg: Option<X86_64GeneralReg>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
Err("Returning structs not yet implemented for X86_64WindowsFastCall".to_string())
|
unimplemented!("Returning structs not yet implemented for X86_64WindowsFastCall");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn returns_via_arg_pointer(ret_layout: &Layout) -> Result<bool, String> {
|
fn returns_via_arg_pointer(ret_layout: &Layout) -> bool {
|
||||||
// TODO: This is not fully correct there are some exceptions for "vector" types.
|
// TODO: This is not fully correct there are some exceptions for "vector" types.
|
||||||
// details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values
|
// details here: https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=msvc-160#return-values
|
||||||
Ok(ret_layout.stack_size(PTR_SIZE) > 8)
|
ret_layout.stack_size(PTR_SIZE) > 8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -798,15 +766,17 @@ fn x86_64_generic_setup_stack<'a>(
|
||||||
saved_regs: &[X86_64GeneralReg],
|
saved_regs: &[X86_64GeneralReg],
|
||||||
requested_stack_size: i32,
|
requested_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<i32, String> {
|
) -> i32 {
|
||||||
X86_64Assembler::push_reg64(buf, X86_64GeneralReg::RBP);
|
X86_64Assembler::push_reg64(buf, X86_64GeneralReg::RBP);
|
||||||
X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP);
|
X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP);
|
||||||
|
|
||||||
let full_stack_size = requested_stack_size
|
let full_stack_size = match requested_stack_size
|
||||||
.checked_add(8 * saved_regs.len() as i32)
|
.checked_add(8 * saved_regs.len() as i32)
|
||||||
.ok_or("Ran out of stack space")?
|
.and_then(|size| size.checked_add(fn_call_stack_size))
|
||||||
.checked_add(fn_call_stack_size)
|
{
|
||||||
.ok_or("Ran out of stack space")?;
|
Some(size) => size,
|
||||||
|
_ => internal_error!("Ran out of stack space"),
|
||||||
|
};
|
||||||
let alignment = if full_stack_size <= 0 {
|
let alignment = if full_stack_size <= 0 {
|
||||||
0
|
0
|
||||||
} else {
|
} else {
|
||||||
|
@ -832,12 +802,12 @@ fn x86_64_generic_setup_stack<'a>(
|
||||||
X86_64Assembler::mov_base32_reg64(buf, -offset, *reg);
|
X86_64Assembler::mov_base32_reg64(buf, -offset, *reg);
|
||||||
offset -= 8;
|
offset -= 8;
|
||||||
}
|
}
|
||||||
Ok(aligned_stack_size)
|
aligned_stack_size
|
||||||
} else {
|
} else {
|
||||||
Ok(0)
|
0
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err("Ran out of stack space".to_string())
|
internal_error!("Ran out of stack space");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -848,7 +818,7 @@ fn x86_64_generic_cleanup_stack<'a>(
|
||||||
saved_regs: &[X86_64GeneralReg],
|
saved_regs: &[X86_64GeneralReg],
|
||||||
aligned_stack_size: i32,
|
aligned_stack_size: i32,
|
||||||
fn_call_stack_size: i32,
|
fn_call_stack_size: i32,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
if aligned_stack_size > 0 {
|
if aligned_stack_size > 0 {
|
||||||
let mut offset = aligned_stack_size - fn_call_stack_size;
|
let mut offset = aligned_stack_size - fn_call_stack_size;
|
||||||
for reg in saved_regs {
|
for reg in saved_regs {
|
||||||
|
@ -864,7 +834,6 @@ fn x86_64_generic_cleanup_stack<'a>(
|
||||||
}
|
}
|
||||||
//X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RSP, X86_64GeneralReg::RBP);
|
//X86_64Assembler::mov_reg64_reg64(buf, X86_64GeneralReg::RSP, X86_64GeneralReg::RBP);
|
||||||
X86_64Assembler::pop_reg64(buf, X86_64GeneralReg::RBP);
|
X86_64Assembler::pop_reg64(buf, X86_64GeneralReg::RBP);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {
|
||||||
|
|
|
@ -13,6 +13,7 @@ use roc_mono::ir::{
|
||||||
SelfRecursive, Stmt,
|
SelfRecursive, Stmt,
|
||||||
};
|
};
|
||||||
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
||||||
|
use roc_reporting::internal_error;
|
||||||
|
|
||||||
mod generic64;
|
mod generic64;
|
||||||
mod object_builder;
|
mod object_builder;
|
||||||
|
@ -58,7 +59,7 @@ 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) -> Result<Self, String>;
|
fn new(env: &'a Env) -> Self;
|
||||||
|
|
||||||
fn env(&self) -> &'a Env<'a>;
|
fn env(&self) -> &'a Env<'a>;
|
||||||
|
|
||||||
|
@ -70,55 +71,48 @@ where
|
||||||
/// 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.
|
||||||
/// For example, this can store the frame pointer and setup stack space.
|
/// For example, this can store the frame pointer and setup stack space.
|
||||||
/// finalize is run at the end of build_proc when all internal code is finalized.
|
/// finalize is run at the end of build_proc when all internal code is finalized.
|
||||||
fn finalize(&mut self) -> Result<(&'a [u8], &[Relocation]), String>;
|
fn finalize(&mut self) -> (&'a [u8], &[Relocation]);
|
||||||
|
|
||||||
// load_args is used to let the backend know what the args are.
|
// load_args is used to let the backend know what the args are.
|
||||||
// The backend should track these args so it can use them as needed.
|
// The backend should track these args so it can use them as needed.
|
||||||
fn load_args(
|
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
args: &'a [(Layout<'a>, Symbol)],
|
|
||||||
ret_layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// Used for generating wrappers for malloc/realloc/free
|
/// Used for generating wrappers for malloc/realloc/free
|
||||||
fn build_wrapped_jmp(&mut self) -> Result<(&'a [u8], u64), String>;
|
fn build_wrapped_jmp(&mut self) -> (&'a [u8], u64);
|
||||||
|
|
||||||
/// 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>) -> (&'a [u8], &[Relocation]) {
|
||||||
let proc_name = LayoutIds::default()
|
let proc_name = LayoutIds::default()
|
||||||
.get(proc.name, &proc.ret_layout)
|
.get(proc.name, &proc.ret_layout)
|
||||||
.to_symbol_string(proc.name, &self.env().interns);
|
.to_symbol_string(proc.name, &self.env().interns);
|
||||||
self.reset(proc_name, proc.is_self_recursive);
|
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);
|
||||||
}
|
}
|
||||||
self.scan_ast(&proc.body);
|
self.scan_ast(&proc.body);
|
||||||
self.create_free_map();
|
self.create_free_map();
|
||||||
self.build_stmt(&proc.body, &proc.ret_layout)?;
|
self.build_stmt(&proc.body, &proc.ret_layout);
|
||||||
self.finalize()
|
self.finalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// build_stmt builds a statement and outputs at the end of the buffer.
|
/// build_stmt builds a statement and outputs at the end of the buffer.
|
||||||
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>) {
|
||||||
match stmt {
|
match stmt {
|
||||||
Stmt::Let(sym, expr, layout, following) => {
|
Stmt::Let(sym, expr, layout, following) => {
|
||||||
self.build_expr(sym, expr, layout)?;
|
self.build_expr(sym, expr, layout);
|
||||||
self.set_layout_map(*sym, layout)?;
|
self.set_layout_map(*sym, layout);
|
||||||
self.free_symbols(stmt)?;
|
self.free_symbols(stmt);
|
||||||
self.build_stmt(following, ret_layout)?;
|
self.build_stmt(following, ret_layout);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Stmt::Ret(sym) => {
|
Stmt::Ret(sym) => {
|
||||||
self.load_literal_symbols(&[*sym])?;
|
self.load_literal_symbols(&[*sym]);
|
||||||
self.return_symbol(sym, ret_layout)?;
|
self.return_symbol(sym, ret_layout);
|
||||||
self.free_symbols(stmt)?;
|
self.free_symbols(stmt);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Stmt::Refcounting(_modify, following) => {
|
Stmt::Refcounting(_modify, following) => {
|
||||||
// TODO: actually deal with refcounting. For hello world, we just skipped it.
|
// TODO: actually deal with refcounting. For hello world, we just skipped it.
|
||||||
self.build_stmt(following, ret_layout)?;
|
self.build_stmt(following, ret_layout);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Stmt::Switch {
|
Stmt::Switch {
|
||||||
cond_symbol,
|
cond_symbol,
|
||||||
|
@ -127,16 +121,15 @@ where
|
||||||
default_branch,
|
default_branch,
|
||||||
ret_layout,
|
ret_layout,
|
||||||
} => {
|
} => {
|
||||||
self.load_literal_symbols(&[*cond_symbol])?;
|
self.load_literal_symbols(&[*cond_symbol]);
|
||||||
self.build_switch(
|
self.build_switch(
|
||||||
cond_symbol,
|
cond_symbol,
|
||||||
cond_layout,
|
cond_layout,
|
||||||
branches,
|
branches,
|
||||||
default_branch,
|
default_branch,
|
||||||
ret_layout,
|
ret_layout,
|
||||||
)?;
|
);
|
||||||
self.free_symbols(stmt)?;
|
self.free_symbols(stmt);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Stmt::Join {
|
Stmt::Join {
|
||||||
id,
|
id,
|
||||||
|
@ -145,11 +138,10 @@ where
|
||||||
remainder,
|
remainder,
|
||||||
} => {
|
} => {
|
||||||
for param in parameters.iter() {
|
for param in parameters.iter() {
|
||||||
self.set_layout_map(param.symbol, ¶m.layout)?;
|
self.set_layout_map(param.symbol, ¶m.layout);
|
||||||
}
|
}
|
||||||
self.build_join(id, parameters, body, remainder, ret_layout)?;
|
self.build_join(id, parameters, body, remainder, ret_layout);
|
||||||
self.free_symbols(stmt)?;
|
self.free_symbols(stmt);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Stmt::Jump(id, args) => {
|
Stmt::Jump(id, args) => {
|
||||||
let mut arg_layouts: bumpalo::collections::Vec<Layout<'a>> =
|
let mut arg_layouts: bumpalo::collections::Vec<Layout<'a>> =
|
||||||
|
@ -160,14 +152,13 @@ where
|
||||||
if let Some(layout) = layout_map.get(arg) {
|
if let Some(layout) = layout_map.get(arg) {
|
||||||
arg_layouts.push(*layout);
|
arg_layouts.push(*layout);
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("the argument, {:?}, has no know layout", arg));
|
internal_error!("the argument, {:?}, has no know layout", arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout)?;
|
self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout);
|
||||||
self.free_symbols(stmt)?;
|
self.free_symbols(stmt);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
x => Err(format!("the statement, {:?}, is not yet implemented", x)),
|
x => unimplemented!("the statement, {:?}, is not yet implemented", x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// build_switch generates a instructions for a switch statement.
|
// build_switch generates a instructions for a switch statement.
|
||||||
|
@ -178,7 +169,7 @@ where
|
||||||
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
|
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
|
||||||
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
|
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<(), String>;
|
);
|
||||||
|
|
||||||
// build_join generates a instructions for a join statement.
|
// build_join generates a instructions for a join statement.
|
||||||
fn build_join(
|
fn build_join(
|
||||||
|
@ -188,7 +179,7 @@ where
|
||||||
body: &'a Stmt<'a>,
|
body: &'a Stmt<'a>,
|
||||||
remainder: &'a Stmt<'a>,
|
remainder: &'a Stmt<'a>,
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<(), String>;
|
);
|
||||||
|
|
||||||
// build_jump generates a instructions for a jump statement.
|
// build_jump generates a instructions for a jump statement.
|
||||||
fn build_jump(
|
fn build_jump(
|
||||||
|
@ -197,24 +188,18 @@ where
|
||||||
args: &'a [Symbol],
|
args: &'a [Symbol],
|
||||||
arg_layouts: &[Layout<'a>],
|
arg_layouts: &[Layout<'a>],
|
||||||
ret_layout: &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(&mut self, sym: &Symbol, expr: &Expr<'a>, layout: &Layout<'a>) {
|
||||||
&mut self,
|
|
||||||
sym: &Symbol,
|
|
||||||
expr: &Expr<'a>,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String> {
|
|
||||||
match expr {
|
match expr {
|
||||||
Expr::Literal(lit) => {
|
Expr::Literal(lit) => {
|
||||||
if self.env().lazy_literals {
|
if self.env().lazy_literals {
|
||||||
self.literal_map().insert(*sym, *lit);
|
self.literal_map().insert(*sym, *lit);
|
||||||
} else {
|
} else {
|
||||||
self.load_literal(sym, lit)?;
|
self.load_literal(sym, lit);
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
Expr::Call(roc_mono::ir::Call {
|
Expr::Call(roc_mono::ir::Call {
|
||||||
call_type,
|
call_type,
|
||||||
|
@ -244,7 +229,7 @@ where
|
||||||
.get(*func_sym, layout)
|
.get(*func_sym, layout)
|
||||||
.to_symbol_string(*func_sym, &self.env().interns);
|
.to_symbol_string(*func_sym, &self.env().interns);
|
||||||
// Now that the arguments are needed, load them if they are literals.
|
// Now that the arguments are needed, load them if they are literals.
|
||||||
self.load_literal_symbols(arguments)?;
|
self.load_literal_symbols(arguments);
|
||||||
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
|
self.build_fn_call(sym, fn_name, arguments, arg_layouts, ret_layout)
|
||||||
} else {
|
} else {
|
||||||
self.build_inline_builtin(
|
self.build_inline_builtin(
|
||||||
|
@ -266,7 +251,7 @@ where
|
||||||
if let Some(layout) = layout_map.get(arg) {
|
if let Some(layout) = layout_map.get(arg) {
|
||||||
arg_layouts.push(*layout);
|
arg_layouts.push(*layout);
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("the argument, {:?}, has no know layout", arg));
|
internal_error!("the argument, {:?}, has no know layout", arg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.build_run_low_level(
|
self.build_run_low_level(
|
||||||
|
@ -277,19 +262,21 @@ where
|
||||||
layout,
|
layout,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
x => Err(format!("the call type, {:?}, is not yet implemented", x)),
|
x => unimplemented!("the call type, {:?}, is not yet implemented", x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Struct(fields) => {
|
Expr::Struct(fields) => {
|
||||||
self.load_literal_symbols(fields)?;
|
self.load_literal_symbols(fields);
|
||||||
self.create_struct(sym, layout, fields)
|
self.create_struct(sym, layout, fields);
|
||||||
}
|
}
|
||||||
Expr::StructAtIndex {
|
Expr::StructAtIndex {
|
||||||
index,
|
index,
|
||||||
field_layouts,
|
field_layouts,
|
||||||
structure,
|
structure,
|
||||||
} => self.load_struct_at_index(sym, structure, *index, field_layouts),
|
} => {
|
||||||
x => Err(format!("the expression, {:?}, is not yet implemented", x)),
|
self.load_struct_at_index(sym, structure, *index, field_layouts);
|
||||||
|
}
|
||||||
|
x => unimplemented!("the expression, {:?}, is not yet implemented", x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -302,9 +289,9 @@ where
|
||||||
args: &'a [Symbol],
|
args: &'a [Symbol],
|
||||||
arg_layouts: &[Layout<'a>],
|
arg_layouts: &[Layout<'a>],
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
// Now that the arguments are needed, load them if they are literals.
|
// Now that the arguments are needed, load them if they are literals.
|
||||||
self.load_literal_symbols(args)?;
|
self.load_literal_symbols(args);
|
||||||
match lowlevel {
|
match lowlevel {
|
||||||
LowLevel::NumAbs => {
|
LowLevel::NumAbs => {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
|
@ -480,7 +467,7 @@ where
|
||||||
arg_layouts,
|
arg_layouts,
|
||||||
ret_layout,
|
ret_layout,
|
||||||
),
|
),
|
||||||
x => Err(format!("low level, {:?}. is not yet implemented", x)),
|
x => unimplemented!("low level, {:?}. is not yet implemented", x),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -492,8 +479,8 @@ where
|
||||||
args: &'a [Symbol],
|
args: &'a [Symbol],
|
||||||
arg_layouts: &[Layout<'a>],
|
arg_layouts: &[Layout<'a>],
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
self.load_literal_symbols(args)?;
|
self.load_literal_symbols(args);
|
||||||
match func_sym {
|
match func_sym {
|
||||||
Symbol::NUM_IS_ZERO => {
|
Symbol::NUM_IS_ZERO => {
|
||||||
debug_assert_eq!(
|
debug_assert_eq!(
|
||||||
|
@ -507,14 +494,11 @@ where
|
||||||
"NumIsZero: expected to have return layout of type Bool"
|
"NumIsZero: expected to have return layout of type Bool"
|
||||||
);
|
);
|
||||||
|
|
||||||
self.load_literal(&Symbol::DEV_TMP, &Literal::Int(0))?;
|
self.load_literal(&Symbol::DEV_TMP, &Literal::Int(0));
|
||||||
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0])?;
|
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
|
||||||
self.free_symbol(&Symbol::DEV_TMP)
|
self.free_symbol(&Symbol::DEV_TMP)
|
||||||
}
|
}
|
||||||
_ => Err(format!(
|
_ => unimplemented!("the function, {:?}, is not yet implemented", func_sym),
|
||||||
"the function, {:?}, is not yet implemented",
|
|
||||||
func_sym
|
|
||||||
)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -527,77 +511,31 @@ where
|
||||||
args: &'a [Symbol],
|
args: &'a [Symbol],
|
||||||
arg_layouts: &[Layout<'a>],
|
arg_layouts: &[Layout<'a>],
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<(), String>;
|
);
|
||||||
|
|
||||||
/// build_num_abs stores the absolute value of src into dst.
|
/// build_num_abs stores the absolute value of src into dst.
|
||||||
fn build_num_abs(
|
fn build_num_abs(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src: &Symbol,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_num_add stores the sum of src1 and src2 into dst.
|
/// build_num_add stores the sum of src1 and src2 into dst.
|
||||||
fn build_num_add(
|
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src1: &Symbol,
|
|
||||||
src2: &Symbol,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_num_mul stores `src1 * src2` into dst.
|
/// build_num_mul stores `src1 * src2` into dst.
|
||||||
fn build_num_mul(
|
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src1: &Symbol,
|
|
||||||
src2: &Symbol,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_num_neg stores the negated value of src into dst.
|
/// build_num_neg stores the negated value of src into dst.
|
||||||
fn build_num_neg(
|
fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src: &Symbol,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_num_sub stores the `src1 - src2` difference into dst.
|
/// build_num_sub stores the `src1 - src2` difference into dst.
|
||||||
fn build_num_sub(
|
fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src1: &Symbol,
|
|
||||||
src2: &Symbol,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_eq stores the result of `src1 == src2` into dst.
|
/// build_eq stores the result of `src1 == src2` into dst.
|
||||||
fn build_eq(
|
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src1: &Symbol,
|
|
||||||
src2: &Symbol,
|
|
||||||
arg_layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_neq stores the result of `src1 != src2` into dst.
|
/// build_neq stores the result of `src1 != src2` into dst.
|
||||||
fn build_neq(
|
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src1: &Symbol,
|
|
||||||
src2: &Symbol,
|
|
||||||
arg_layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_num_lt stores the result of `src1 < src2` into dst.
|
/// build_num_lt stores the result of `src1 < src2` into dst.
|
||||||
fn build_num_lt(
|
fn build_num_lt(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);
|
||||||
&mut self,
|
|
||||||
dst: &Symbol,
|
|
||||||
src1: &Symbol,
|
|
||||||
src2: &Symbol,
|
|
||||||
arg_layout: &Layout<'a>,
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// build_num_to_float convert Number to Float
|
/// build_num_to_float convert Number to Float
|
||||||
fn build_num_to_float(
|
fn build_num_to_float(
|
||||||
|
@ -606,29 +544,23 @@ where
|
||||||
src: &Symbol,
|
src: &Symbol,
|
||||||
arg_layout: &Layout<'a>,
|
arg_layout: &Layout<'a>,
|
||||||
ret_layout: &Layout<'a>,
|
ret_layout: &Layout<'a>,
|
||||||
) -> Result<(), String>;
|
);
|
||||||
|
|
||||||
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
|
/// literal_map gets the map from symbol to literal, used for lazy loading and literal folding.
|
||||||
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
|
fn literal_map(&mut self) -> &mut MutMap<Symbol, Literal<'a>>;
|
||||||
|
|
||||||
fn load_literal_symbols(&mut self, syms: &[Symbol]) -> Result<(), String> {
|
fn load_literal_symbols(&mut self, syms: &[Symbol]) {
|
||||||
if self.env().lazy_literals {
|
if self.env().lazy_literals {
|
||||||
for sym in syms {
|
for sym in syms {
|
||||||
if let Some(lit) = self.literal_map().remove(sym) {
|
if let Some(lit) = self.literal_map().remove(sym) {
|
||||||
self.load_literal(sym, &lit)?;
|
self.load_literal(sym, &lit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// create_struct creates a struct with the elements specified loaded into it as data.
|
/// create_struct creates a struct with the elements specified loaded into it as data.
|
||||||
fn create_struct(
|
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]);
|
||||||
&mut self,
|
|
||||||
sym: &Symbol,
|
|
||||||
layout: &Layout<'a>,
|
|
||||||
fields: &'a [Symbol],
|
|
||||||
) -> Result<(), String>;
|
|
||||||
|
|
||||||
/// load_struct_at_index loads into `sym` the value at `index` in `structure`.
|
/// load_struct_at_index loads into `sym` the value at `index` in `structure`.
|
||||||
fn load_struct_at_index(
|
fn load_struct_at_index(
|
||||||
|
@ -637,27 +569,26 @@ where
|
||||||
structure: &Symbol,
|
structure: &Symbol,
|
||||||
index: u64,
|
index: u64,
|
||||||
field_layouts: &'a [Layout<'a>],
|
field_layouts: &'a [Layout<'a>],
|
||||||
) -> Result<(), String>;
|
);
|
||||||
|
|
||||||
/// 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>);
|
||||||
|
|
||||||
/// return_symbol moves a symbol to the correct return location for the backend and adds a jump to the end of the function.
|
/// 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>);
|
||||||
|
|
||||||
/// free_symbols will free all symbols for the given statement.
|
/// free_symbols will free all symbols for the given statement.
|
||||||
fn free_symbols(&mut self, stmt: &Stmt<'a>) -> Result<(), String> {
|
fn free_symbols(&mut self, stmt: &Stmt<'a>) {
|
||||||
if let Some(syms) = self.free_map().remove(&(stmt as *const Stmt<'a>)) {
|
if let Some(syms) = self.free_map().remove(&(stmt as *const Stmt<'a>)) {
|
||||||
for sym in syms {
|
for sym in syms {
|
||||||
// println!("Freeing symbol: {:?}", sym);
|
// println!("Freeing symbol: {:?}", sym);
|
||||||
self.free_symbol(&sym)?;
|
self.free_symbol(&sym);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// free_symbol frees any registers or stack space used to hold a symbol.
|
/// free_symbol frees any registers or stack space used to hold a symbol.
|
||||||
fn free_symbol(&mut self, sym: &Symbol) -> Result<(), String>;
|
fn free_symbol(&mut self, sym: &Symbol);
|
||||||
|
|
||||||
/// set_last_seen sets the statement a symbol was last seen in.
|
/// set_last_seen sets the statement a symbol was last seen in.
|
||||||
fn set_last_seen(
|
fn set_last_seen(
|
||||||
|
@ -676,20 +607,18 @@ where
|
||||||
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>>;
|
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>>;
|
||||||
|
|
||||||
/// 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>) {
|
||||||
if let Some(old_layout) = 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.
|
||||||
if &old_layout != layout {
|
if &old_layout != layout {
|
||||||
Err(format!(
|
internal_error!(
|
||||||
"Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}",
|
"Overwriting layout for symbol, {:?}: got {:?}, want {:?}",
|
||||||
sym, layout, old_layout
|
sym,
|
||||||
))
|
layout,
|
||||||
} else {
|
old_layout
|
||||||
Ok(())
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -779,8 +708,8 @@ where
|
||||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
self.set_last_seen(*sym, stmt, &owning_symbol);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Reset(sym) => {
|
Expr::Reset { symbol, .. } => {
|
||||||
self.set_last_seen(*sym, stmt, &owning_symbol);
|
self.set_last_seen(*symbol, stmt, &owning_symbol);
|
||||||
}
|
}
|
||||||
Expr::EmptyArray => {}
|
Expr::EmptyArray => {}
|
||||||
Expr::RuntimeErrorFunction(_) => {}
|
Expr::RuntimeErrorFunction(_) => {}
|
||||||
|
|
|
@ -10,6 +10,7 @@ use object::{
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::symbol;
|
use roc_module::symbol;
|
||||||
use roc_mono::ir::{Proc, ProcLayout};
|
use roc_mono::ir::{Proc, ProcLayout};
|
||||||
|
use roc_reporting::internal_error;
|
||||||
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
|
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
|
||||||
|
|
||||||
// This is used by some code below which is currently commented out.
|
// This is used by some code below which is currently commented out.
|
||||||
|
@ -22,7 +23,7 @@ pub fn build_module<'a>(
|
||||||
env: &'a Env,
|
env: &'a Env,
|
||||||
target: &Triple,
|
target: &Triple,
|
||||||
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
|
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
) -> Result<Object, String> {
|
) -> Object {
|
||||||
match target {
|
match target {
|
||||||
Triple {
|
Triple {
|
||||||
architecture: TargetArch::X86_64,
|
architecture: TargetArch::X86_64,
|
||||||
|
@ -34,7 +35,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)?;
|
> = Backend::new(env);
|
||||||
build_object(
|
build_object(
|
||||||
env,
|
env,
|
||||||
procedures,
|
procedures,
|
||||||
|
@ -52,7 +53,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)?;
|
> = Backend::new(env);
|
||||||
build_object(
|
build_object(
|
||||||
env,
|
env,
|
||||||
procedures,
|
procedures,
|
||||||
|
@ -74,7 +75,7 @@ pub fn build_module<'a>(
|
||||||
aarch64::AArch64FloatReg,
|
aarch64::AArch64FloatReg,
|
||||||
aarch64::AArch64Assembler,
|
aarch64::AArch64Assembler,
|
||||||
aarch64::AArch64Call,
|
aarch64::AArch64Call,
|
||||||
> = Backend::new(env)?;
|
> = Backend::new(env);
|
||||||
build_object(
|
build_object(
|
||||||
env,
|
env,
|
||||||
procedures,
|
procedures,
|
||||||
|
@ -92,7 +93,7 @@ pub fn build_module<'a>(
|
||||||
aarch64::AArch64FloatReg,
|
aarch64::AArch64FloatReg,
|
||||||
aarch64::AArch64Assembler,
|
aarch64::AArch64Assembler,
|
||||||
aarch64::AArch64Call,
|
aarch64::AArch64Call,
|
||||||
> = Backend::new(env)?;
|
> = Backend::new(env);
|
||||||
build_object(
|
build_object(
|
||||||
env,
|
env,
|
||||||
procedures,
|
procedures,
|
||||||
|
@ -104,9 +105,7 @@ pub fn build_module<'a>(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
x => Err(format! {
|
x => unimplemented!("the target, {:?}, is not yet implemented", x),
|
||||||
"the target, {:?}, is not yet implemented",
|
|
||||||
x}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,7 +114,7 @@ fn generate_wrapper<'a, B: Backend<'a>>(
|
||||||
output: &mut Object,
|
output: &mut Object,
|
||||||
wrapper_name: String,
|
wrapper_name: String,
|
||||||
wraps: String,
|
wraps: String,
|
||||||
) -> Result<(), String> {
|
) {
|
||||||
let text_section = output.section_id(StandardSection::Text);
|
let text_section = output.section_id(StandardSection::Text);
|
||||||
let proc_symbol = Symbol {
|
let proc_symbol = Symbol {
|
||||||
name: wrapper_name.as_bytes().to_vec(),
|
name: wrapper_name.as_bytes().to_vec(),
|
||||||
|
@ -128,7 +127,7 @@ fn generate_wrapper<'a, B: Backend<'a>>(
|
||||||
flags: SymbolFlags::None,
|
flags: SymbolFlags::None,
|
||||||
};
|
};
|
||||||
let proc_id = output.add_symbol(proc_symbol);
|
let proc_id = output.add_symbol(proc_symbol);
|
||||||
let (proc_data, offset) = backend.build_wrapped_jmp()?;
|
let (proc_data, offset) = backend.build_wrapped_jmp();
|
||||||
let proc_offset = output.add_symbol_data(proc_id, text_section, proc_data, 16);
|
let proc_offset = output.add_symbol_data(proc_id, text_section, proc_data, 16);
|
||||||
|
|
||||||
let name = wraps.as_str().as_bytes();
|
let name = wraps.as_str().as_bytes();
|
||||||
|
@ -154,13 +153,12 @@ fn generate_wrapper<'a, B: Backend<'a>>(
|
||||||
addend: -4,
|
addend: -4,
|
||||||
};
|
};
|
||||||
|
|
||||||
output
|
match output.add_relocation(text_section, reloc) {
|
||||||
.add_relocation(text_section, reloc)
|
Ok(obj) => obj,
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
Err(e) => internal_error!("{:?}", e),
|
||||||
|
}
|
||||||
Ok(())
|
|
||||||
} else {
|
} else {
|
||||||
Err(format!("failed to find fn symbol for {:?}", wraps))
|
unimplemented!("failed to find fn symbol for {:?}", wraps);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -169,7 +167,7 @@ fn build_object<'a, B: Backend<'a>>(
|
||||||
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
|
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
mut backend: B,
|
mut backend: B,
|
||||||
mut output: Object,
|
mut output: Object,
|
||||||
) -> Result<Object, String> {
|
) -> Object {
|
||||||
let data_section = output.section_id(StandardSection::Data);
|
let data_section = output.section_id(StandardSection::Data);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -188,25 +186,25 @@ fn build_object<'a, B: Backend<'a>>(
|
||||||
&mut output,
|
&mut output,
|
||||||
"roc_alloc".into(),
|
"roc_alloc".into(),
|
||||||
"malloc".into(),
|
"malloc".into(),
|
||||||
)?;
|
);
|
||||||
generate_wrapper(
|
generate_wrapper(
|
||||||
&mut backend,
|
&mut backend,
|
||||||
&mut output,
|
&mut output,
|
||||||
"roc_realloc".into(),
|
"roc_realloc".into(),
|
||||||
"realloc".into(),
|
"realloc".into(),
|
||||||
)?;
|
);
|
||||||
generate_wrapper(
|
generate_wrapper(
|
||||||
&mut backend,
|
&mut backend,
|
||||||
&mut output,
|
&mut output,
|
||||||
"roc_dealloc".into(),
|
"roc_dealloc".into(),
|
||||||
"free".into(),
|
"free".into(),
|
||||||
)?;
|
);
|
||||||
generate_wrapper(
|
generate_wrapper(
|
||||||
&mut backend,
|
&mut backend,
|
||||||
&mut output,
|
&mut output,
|
||||||
"roc_panic".into(),
|
"roc_panic".into(),
|
||||||
"roc_builtins.utils.test_panic".into(),
|
"roc_builtins.utils.test_panic".into(),
|
||||||
)?;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup layout_ids for procedure calls.
|
// Setup layout_ids for procedure calls.
|
||||||
|
@ -253,7 +251,7 @@ fn build_object<'a, B: Backend<'a>>(
|
||||||
let mut relocations = bumpalo::vec![in env.arena];
|
let mut relocations = bumpalo::vec![in env.arena];
|
||||||
for (fn_name, section_id, proc_id, proc) in procs {
|
for (fn_name, section_id, proc_id, proc) in procs {
|
||||||
let mut local_data_index = 0;
|
let mut local_data_index = 0;
|
||||||
let (proc_data, relocs) = backend.build_proc(proc)?;
|
let (proc_data, relocs) = backend.build_proc(proc);
|
||||||
let proc_offset = output.add_symbol_data(proc_id, section_id, proc_data, 16);
|
let proc_offset = output.add_symbol_data(proc_id, section_id, proc_data, 16);
|
||||||
for reloc in relocs {
|
for reloc in relocs {
|
||||||
let elfreloc = match reloc {
|
let elfreloc = match reloc {
|
||||||
|
@ -293,7 +291,7 @@ fn build_object<'a, B: Backend<'a>>(
|
||||||
addend: -4,
|
addend: -4,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("failed to find data symbol for {:?}", name));
|
internal_error!("failed to find data symbol for {:?}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Relocation::LinkedFunction { offset, name } => {
|
Relocation::LinkedFunction { offset, name } => {
|
||||||
|
@ -323,7 +321,7 @@ fn build_object<'a, B: Backend<'a>>(
|
||||||
addend: -4,
|
addend: -4,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("failed to find fn symbol for {:?}", name));
|
internal_error!("failed to find fn symbol for {:?}", name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Relocation::JmpToReturn { .. } => unreachable!(),
|
Relocation::JmpToReturn { .. } => unreachable!(),
|
||||||
|
@ -332,9 +330,10 @@ fn build_object<'a, B: Backend<'a>>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (section_id, reloc) in relocations {
|
for (section_id, reloc) in relocations {
|
||||||
|
match output.add_relocation(section_id, reloc) {
|
||||||
|
Ok(obj) => obj,
|
||||||
|
Err(e) => internal_error!("{:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
output
|
output
|
||||||
.add_relocation(section_id, reloc)
|
|
||||||
.map_err(|e| format!("{:?}", e))?;
|
|
||||||
}
|
|
||||||
Ok(output)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -955,7 +955,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
|
||||||
}
|
}
|
||||||
|
|
||||||
CallType::HigherOrder(higher_order) => {
|
CallType::HigherOrder(higher_order) => {
|
||||||
let bytes = higher_order.specialization_id.to_bytes();
|
let bytes = higher_order.passed_function.specialization_id.to_bytes();
|
||||||
let callee_var = CalleeSpecVar(&bytes);
|
let callee_var = CalleeSpecVar(&bytes);
|
||||||
let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap();
|
let func_spec = func_spec_solutions.callee_spec(callee_var).unwrap();
|
||||||
|
|
||||||
|
@ -1108,7 +1108,7 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
|
||||||
..
|
..
|
||||||
} => build_tag(env, scope, union_layout, *tag_id, arguments, None, parent),
|
} => build_tag(env, scope, union_layout, *tag_id, arguments, None, parent),
|
||||||
|
|
||||||
Reset(symbol) => {
|
Reset { symbol, .. } => {
|
||||||
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
|
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
|
||||||
let tag_ptr = tag_ptr.into_pointer_value();
|
let tag_ptr = tag_ptr.into_pointer_value();
|
||||||
|
|
||||||
|
@ -4686,20 +4686,23 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
func_spec: FuncSpec,
|
func_spec: FuncSpec,
|
||||||
higher_order: &HigherOrderLowLevel<'a>,
|
higher_order: &HigherOrderLowLevel<'a>,
|
||||||
) -> BasicValueEnum<'ctx> {
|
) -> BasicValueEnum<'ctx> {
|
||||||
|
use roc_mono::ir::PassedFunction;
|
||||||
use roc_mono::low_level::HigherOrder::*;
|
use roc_mono::low_level::HigherOrder::*;
|
||||||
|
|
||||||
let HigherOrderLowLevel {
|
let HigherOrderLowLevel {
|
||||||
op,
|
op,
|
||||||
arg_layouts: argument_layouts,
|
passed_function,
|
||||||
ret_layout: result_layout,
|
|
||||||
function_owns_closure_data,
|
|
||||||
function_name,
|
|
||||||
function_env,
|
|
||||||
..
|
..
|
||||||
} = higher_order;
|
} = higher_order;
|
||||||
|
|
||||||
let function_owns_closure_data = *function_owns_closure_data;
|
let PassedFunction {
|
||||||
let function_name = *function_name;
|
argument_layouts,
|
||||||
|
return_layout: result_layout,
|
||||||
|
owns_captured_environment: function_owns_closure_data,
|
||||||
|
name: function_name,
|
||||||
|
captured_environment,
|
||||||
|
..
|
||||||
|
} = *passed_function;
|
||||||
|
|
||||||
// macros because functions cause lifetime issues related to the `env` or `layout_ids`
|
// macros because functions cause lifetime issues related to the `env` or `layout_ids`
|
||||||
macro_rules! function_details {
|
macro_rules! function_details {
|
||||||
|
@ -4712,7 +4715,8 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
return_layout,
|
return_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (closure, closure_layout) = load_symbol_and_lambda_set(scope, function_env);
|
let (closure, closure_layout) =
|
||||||
|
load_symbol_and_lambda_set(scope, &captured_environment);
|
||||||
|
|
||||||
(function, closure, closure_layout)
|
(function, closure, closure_layout)
|
||||||
}};
|
}};
|
||||||
|
@ -4737,14 +4741,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
closure_layout,
|
closure_layout,
|
||||||
function_owns_closure_data,
|
function_owns_closure_data,
|
||||||
argument_layouts,
|
argument_layouts,
|
||||||
*result_layout,
|
result_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
crate::llvm::build_list::list_walk_generic(
|
crate::llvm::build_list::list_walk_generic(
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
roc_function_call,
|
roc_function_call,
|
||||||
result_layout,
|
&result_layout,
|
||||||
list,
|
list,
|
||||||
element_layout,
|
element_layout,
|
||||||
default,
|
default,
|
||||||
|
@ -4974,7 +4978,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
closure_layout,
|
closure_layout,
|
||||||
function_owns_closure_data,
|
function_owns_closure_data,
|
||||||
argument_layouts,
|
argument_layouts,
|
||||||
*result_layout,
|
result_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
list_keep_if(env, layout_ids, roc_function_call, list, element_layout)
|
list_keep_if(env, layout_ids, roc_function_call, list, element_layout)
|
||||||
|
@ -5003,14 +5007,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
closure_layout,
|
closure_layout,
|
||||||
function_owns_closure_data,
|
function_owns_closure_data,
|
||||||
argument_layouts,
|
argument_layouts,
|
||||||
*result_layout,
|
result_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
list_keep_oks(
|
list_keep_oks(
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
roc_function_call,
|
roc_function_call,
|
||||||
result_layout,
|
&result_layout,
|
||||||
list,
|
list,
|
||||||
before_layout,
|
before_layout,
|
||||||
after_layout,
|
after_layout,
|
||||||
|
@ -5042,14 +5046,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
closure_layout,
|
closure_layout,
|
||||||
function_owns_closure_data,
|
function_owns_closure_data,
|
||||||
argument_layouts,
|
argument_layouts,
|
||||||
*result_layout,
|
result_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
list_keep_errs(
|
list_keep_errs(
|
||||||
env,
|
env,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
roc_function_call,
|
roc_function_call,
|
||||||
result_layout,
|
&result_layout,
|
||||||
list,
|
list,
|
||||||
before_layout,
|
before_layout,
|
||||||
after_layout,
|
after_layout,
|
||||||
|
@ -5094,7 +5098,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
closure_layout,
|
closure_layout,
|
||||||
function_owns_closure_data,
|
function_owns_closure_data,
|
||||||
argument_layouts,
|
argument_layouts,
|
||||||
*result_layout,
|
result_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
list_sort_with(
|
list_sort_with(
|
||||||
|
@ -5197,7 +5201,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
|
||||||
closure_layout,
|
closure_layout,
|
||||||
function_owns_closure_data,
|
function_owns_closure_data,
|
||||||
argument_layouts,
|
argument_layouts,
|
||||||
*result_layout,
|
result_layout,
|
||||||
);
|
);
|
||||||
|
|
||||||
dict_walk(
|
dict_walk(
|
||||||
|
@ -5522,6 +5526,24 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
|
|
||||||
list_join(env, parent, list, outer_list_layout)
|
list_join(env, parent, list, outer_list_layout)
|
||||||
}
|
}
|
||||||
|
NumToStr => {
|
||||||
|
// Num.toStr : Num a -> Str
|
||||||
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
|
let (num, num_layout) = load_symbol_and_layout(scope, &args[0]);
|
||||||
|
|
||||||
|
match num_layout {
|
||||||
|
Layout::Builtin(Builtin::Int(int_width)) => {
|
||||||
|
let int = num.into_int_value();
|
||||||
|
|
||||||
|
str_from_int(env, int, *int_width)
|
||||||
|
}
|
||||||
|
Layout::Builtin(Builtin::Float(_float_width)) => {
|
||||||
|
str_from_float(env, scope, args[0])
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
|
NumAbs | NumNeg | NumRound | NumSqrtUnchecked | NumLogUnchecked | NumSin | NumCos
|
||||||
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
|
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
|
||||||
debug_assert_eq!(args.len(), 1);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
@ -6020,6 +6042,10 @@ fn run_low_level<'a, 'ctx, 'env>(
|
||||||
| ListAny | ListAll | ListFindUnsafe | DictWalk => {
|
| ListAny | ListAll | ListFindUnsafe | DictWalk => {
|
||||||
unreachable!("these are higher order, and are handled elsewhere")
|
unreachable!("these are higher order, and are handled elsewhere")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RefCountGetPtr | RefCountInc | RefCountDec => {
|
||||||
|
unreachable!("LLVM backend does not use lowlevels for refcounting");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -406,7 +406,7 @@ pub fn str_from_utf8<'a, 'ctx, 'env>(
|
||||||
decode_from_utf8_result(env, result_ptr).into()
|
decode_from_utf8_result(env, result_ptr).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Str.fromInt : Int -> Str
|
/// Str.fromFloat : Int -> Str
|
||||||
pub fn str_from_float<'a, 'ctx, 'env>(
|
pub fn str_from_float<'a, 'ctx, 'env>(
|
||||||
env: &Env<'a, 'ctx, 'env>,
|
env: &Env<'a, 'ctx, 'env>,
|
||||||
scope: &Scope<'a, 'ctx>,
|
scope: &Scope<'a, 'ctx>,
|
||||||
|
|
|
@ -19,6 +19,8 @@ use roc_module::symbol::Interns;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
use roc_mono::layout::{Builtin, Layout, LayoutIds, UnionLayout};
|
||||||
|
|
||||||
|
/// "Infinite" reference count, for static values
|
||||||
|
/// Ref counts are encoded as negative numbers where isize::MIN represents 1
|
||||||
pub const REFCOUNT_MAX: usize = 0_usize;
|
pub const REFCOUNT_MAX: usize = 0_usize;
|
||||||
|
|
||||||
pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> {
|
pub fn refcount_1(ctx: &Context, ptr_bytes: u32) -> IntValue<'_> {
|
||||||
|
|
|
@ -3,11 +3,12 @@ use bumpalo::{self, collections::Vec};
|
||||||
use code_builder::Align;
|
use code_builder::Align;
|
||||||
use roc_collections::all::MutMap;
|
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::{Interns, Symbol};
|
||||||
|
use roc_mono::gen_refcount::{RefcountProcGenerator, REFCOUNT_MAX};
|
||||||
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, LayoutIds};
|
use roc_mono::layout::{Builtin, Layout, LayoutIds};
|
||||||
|
|
||||||
use crate::layout::{StackMemoryFormat, WasmLayout};
|
use crate::layout::{CallConv, ReturnMethod, WasmLayout};
|
||||||
use crate::low_level::{decode_low_level, LowlevelBuildResult};
|
use crate::low_level::{decode_low_level, LowlevelBuildResult};
|
||||||
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
use crate::storage::{Storage, StoredValue, StoredValueKind};
|
||||||
use crate::wasm_module::linking::{
|
use crate::wasm_module::linking::{
|
||||||
|
@ -20,7 +21,7 @@ use crate::wasm_module::sections::{
|
||||||
};
|
};
|
||||||
use crate::wasm_module::{
|
use crate::wasm_module::{
|
||||||
code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType,
|
code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType,
|
||||||
LocalId, Signature, SymInfo, ValueType,
|
LinkingSubSection, LocalId, Signature, SymInfo, ValueType,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
|
copy_memory, CopyMemoryConfig, Env, BUILTINS_IMPORT_MODULE_NAME, MEMORY_NAME, PTR_SIZE,
|
||||||
|
@ -37,18 +38,21 @@ const CONST_SEGMENT_INDEX: usize = 0;
|
||||||
|
|
||||||
pub struct WasmBackend<'a> {
|
pub struct WasmBackend<'a> {
|
||||||
env: &'a Env<'a>,
|
env: &'a Env<'a>,
|
||||||
|
interns: &'a mut Interns,
|
||||||
|
|
||||||
// Module-level data
|
// Module-level data
|
||||||
pub module: WasmModule<'a>,
|
module: WasmModule<'a>,
|
||||||
layout_ids: LayoutIds<'a>,
|
layout_ids: LayoutIds<'a>,
|
||||||
constant_sym_index_map: MutMap<&'a str, usize>,
|
constant_sym_index_map: MutMap<&'a str, usize>,
|
||||||
builtin_sym_index_map: MutMap<&'a str, usize>,
|
builtin_sym_index_map: MutMap<&'a str, usize>,
|
||||||
proc_symbols: Vec<'a, Symbol>,
|
proc_symbols: Vec<'a, (Symbol, u32)>,
|
||||||
pub linker_symbols: Vec<'a, SymInfo>,
|
linker_symbols: Vec<'a, SymInfo>,
|
||||||
|
refcount_proc_gen: RefcountProcGenerator<'a>,
|
||||||
|
|
||||||
// Function-level data
|
// Function-level data
|
||||||
code_builder: CodeBuilder<'a>,
|
code_builder: CodeBuilder<'a>,
|
||||||
storage: Storage<'a>,
|
storage: Storage<'a>,
|
||||||
|
symbol_layouts: MutMap<Symbol, Layout<'a>>,
|
||||||
|
|
||||||
/// how many blocks deep are we (used for jumps)
|
/// how many blocks deep are we (used for jumps)
|
||||||
block_depth: u32,
|
block_depth: u32,
|
||||||
|
@ -58,10 +62,12 @@ pub struct WasmBackend<'a> {
|
||||||
impl<'a> WasmBackend<'a> {
|
impl<'a> WasmBackend<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
env: &'a Env<'a>,
|
env: &'a Env<'a>,
|
||||||
|
interns: &'a mut Interns,
|
||||||
layout_ids: LayoutIds<'a>,
|
layout_ids: LayoutIds<'a>,
|
||||||
proc_symbols: Vec<'a, Symbol>,
|
proc_symbols: Vec<'a, (Symbol, u32)>,
|
||||||
mut linker_symbols: Vec<'a, SymInfo>,
|
mut linker_symbols: Vec<'a, SymInfo>,
|
||||||
mut exports: Vec<'a, Export>,
|
mut exports: Vec<'a, Export>,
|
||||||
|
refcount_proc_gen: RefcountProcGenerator<'a>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
const MEMORY_INIT_SIZE: u32 = 1024 * 1024;
|
const MEMORY_INIT_SIZE: u32 = 1024 * 1024;
|
||||||
let arena = env.arena;
|
let arena = env.arena;
|
||||||
|
@ -124,6 +130,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
WasmBackend {
|
WasmBackend {
|
||||||
env,
|
env,
|
||||||
|
interns,
|
||||||
|
|
||||||
// Module-level data
|
// Module-level data
|
||||||
module,
|
module,
|
||||||
|
@ -133,15 +140,47 @@ impl<'a> WasmBackend<'a> {
|
||||||
builtin_sym_index_map: MutMap::default(),
|
builtin_sym_index_map: MutMap::default(),
|
||||||
proc_symbols,
|
proc_symbols,
|
||||||
linker_symbols,
|
linker_symbols,
|
||||||
|
refcount_proc_gen,
|
||||||
|
|
||||||
// Function-level data
|
// Function-level data
|
||||||
block_depth: 0,
|
block_depth: 0,
|
||||||
joinpoint_label_map: MutMap::default(),
|
joinpoint_label_map: MutMap::default(),
|
||||||
code_builder: CodeBuilder::new(arena),
|
code_builder: CodeBuilder::new(arena),
|
||||||
storage: Storage::new(arena),
|
storage: Storage::new(arena),
|
||||||
|
symbol_layouts: MutMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn generate_refcount_procs(&mut self) -> Vec<'a, Proc<'a>> {
|
||||||
|
let ident_ids = self
|
||||||
|
.interns
|
||||||
|
.all_ident_ids
|
||||||
|
.get_mut(&self.env.module_id)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
self.refcount_proc_gen
|
||||||
|
.generate_refcount_procs(self.env.arena, ident_ids)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn finalize_module(mut self) -> WasmModule<'a> {
|
||||||
|
let symbol_table = LinkingSubSection::SymbolTable(self.linker_symbols);
|
||||||
|
self.module.linking.subsections.push(symbol_table);
|
||||||
|
self.module
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Register the debug names of Symbols in a global lookup table
|
||||||
|
/// so that they have meaningful names when you print them.
|
||||||
|
/// Particularly useful after generating IR for refcount procedures
|
||||||
|
#[cfg(debug_assertions)]
|
||||||
|
pub fn register_symbol_debug_names(&self) {
|
||||||
|
let module_id = self.env.module_id;
|
||||||
|
let ident_ids = self.interns.all_ident_ids.get(&module_id).unwrap();
|
||||||
|
self.env.module_id.register_debug_idents(ident_ids);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(debug_assertions))]
|
||||||
|
pub fn register_symbol_debug_names(&self) {}
|
||||||
|
|
||||||
/// Reset function-level data
|
/// Reset function-level data
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
// Push the completed CodeBuilder into the module and swap it for a new empty one
|
// Push the completed CodeBuilder into the module and swap it for a new empty one
|
||||||
|
@ -151,6 +190,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
self.storage.clear();
|
self.storage.clear();
|
||||||
self.joinpoint_label_map.clear();
|
self.joinpoint_label_map.clear();
|
||||||
|
self.symbol_layouts.clear();
|
||||||
assert_eq!(self.block_depth, 0);
|
assert_eq!(self.block_depth, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -160,33 +200,37 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
***********************************************************/
|
***********************************************************/
|
||||||
|
|
||||||
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<(), String> {
|
pub fn build_proc(&mut self, proc: &Proc<'a>) -> Result<(), String> {
|
||||||
// println!("\ngenerating procedure {:?}\n", _sym);
|
// println!("\ngenerating procedure {:?}\n", proc.name);
|
||||||
|
|
||||||
self.start_proc(&proc);
|
self.start_proc(proc);
|
||||||
|
|
||||||
self.build_stmt(&proc.body, &proc.ret_layout)?;
|
self.build_stmt(&proc.body, &proc.ret_layout)?;
|
||||||
|
|
||||||
self.finalize_proc()?;
|
self.finalize_proc()?;
|
||||||
self.reset();
|
self.reset();
|
||||||
|
|
||||||
// println!("\nfinished generating {:?}\n", _sym);
|
// println!("\nfinished generating {:?}\n", proc.name);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_proc(&mut self, proc: &Proc<'a>) {
|
fn start_proc(&mut self, proc: &Proc<'a>) {
|
||||||
let ret_layout = WasmLayout::new(&proc.ret_layout);
|
let ret_layout = WasmLayout::new(&proc.ret_layout);
|
||||||
let ret_type = if ret_layout.is_stack_memory() {
|
|
||||||
|
let ret_type = match ret_layout.return_method() {
|
||||||
|
ReturnMethod::Primitive(ty) => Some(ty),
|
||||||
|
ReturnMethod::NoReturnValue => None,
|
||||||
|
ReturnMethod::WriteToPointerArg => {
|
||||||
self.storage.arg_types.push(PTR_TYPE);
|
self.storage.arg_types.push(PTR_TYPE);
|
||||||
self.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
|
|
||||||
None
|
None
|
||||||
} else {
|
}
|
||||||
let ty = ret_layout.value_type();
|
|
||||||
self.start_block(BlockType::Value(ty)); // block to ensure all paths pop stack memory (if any)
|
|
||||||
Some(ty)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Create a block so we can exit the function without skipping stack frame "pop" code.
|
||||||
|
// We never use the `return` instruction. Instead, we break from this block.
|
||||||
|
self.start_block(BlockType::from(ret_type));
|
||||||
|
|
||||||
for (layout, symbol) in proc.args {
|
for (layout, symbol) in proc.args {
|
||||||
let arg_layout = WasmLayout::new(layout);
|
let arg_layout = WasmLayout::new(layout);
|
||||||
self.storage
|
self.storage
|
||||||
|
@ -219,10 +263,9 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
***********************************************************/
|
***********************************************************/
|
||||||
|
|
||||||
/// start a loop that leaves a value on the stack
|
fn start_loop(&mut self, block_type: BlockType) {
|
||||||
fn start_loop_with_return(&mut self, value_type: ValueType) {
|
|
||||||
self.block_depth += 1;
|
self.block_depth += 1;
|
||||||
self.code_builder.loop_(BlockType::Value(value_type));
|
self.code_builder.loop_(block_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_block(&mut self, block_type: BlockType) {
|
fn start_block(&mut self, block_type: BlockType) {
|
||||||
|
@ -240,6 +283,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
Stmt::Let(_, _, _, _) => {
|
Stmt::Let(_, _, _, _) => {
|
||||||
let mut current_stmt = stmt;
|
let mut current_stmt = stmt;
|
||||||
while let Stmt::Let(sym, expr, layout, following) = current_stmt {
|
while let Stmt::Let(sym, expr, layout, following) = current_stmt {
|
||||||
|
// println!("let {:?} = {}", sym, expr.to_pretty(200)); // ignore `following`! Too confusing otherwise.
|
||||||
|
|
||||||
let wasm_layout = WasmLayout::new(layout);
|
let wasm_layout = WasmLayout::new(layout);
|
||||||
|
|
||||||
let kind = match following {
|
let kind = match following {
|
||||||
|
@ -265,6 +310,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.symbol_layouts.insert(*sym, *layout);
|
||||||
|
|
||||||
current_stmt = *following;
|
current_stmt = *following;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -335,7 +382,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool));
|
let is_bool = matches!(cond_layout, Layout::Builtin(Builtin::Bool));
|
||||||
let cond_type = WasmLayout::new(cond_layout).value_type();
|
let cond_type = WasmLayout::new(cond_layout).arg_types(CallConv::C)[0];
|
||||||
|
|
||||||
// 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() {
|
||||||
|
@ -419,10 +466,14 @@ impl<'a> WasmBackend<'a> {
|
||||||
|
|
||||||
self.end_block();
|
self.end_block();
|
||||||
|
|
||||||
// A `return` inside of a `loop` seems to make it so that the `loop` itself
|
// A loop (or any block) needs to declare the type of the value it leaves on the stack on exit.
|
||||||
// also "returns" (so, leaves on the stack) a value of the return type.
|
// The runtime needs this to statically validate the program before running it.
|
||||||
let return_wasm_layout = WasmLayout::new(ret_layout);
|
let loop_block_type = match WasmLayout::new(ret_layout).return_method() {
|
||||||
self.start_loop_with_return(return_wasm_layout.value_type());
|
ReturnMethod::Primitive(ty) => BlockType::Value(ty),
|
||||||
|
ReturnMethod::WriteToPointerArg => BlockType::NoResult,
|
||||||
|
ReturnMethod::NoReturnValue => BlockType::NoResult,
|
||||||
|
};
|
||||||
|
self.start_loop(loop_block_type);
|
||||||
|
|
||||||
self.build_stmt(body, ret_layout)?;
|
self.build_stmt(body, ret_layout)?;
|
||||||
|
|
||||||
|
@ -451,9 +502,46 @@ impl<'a> WasmBackend<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Stmt::Refcounting(_modify, following) => {
|
Stmt::Refcounting(modify, following) => {
|
||||||
// TODO: actually deal with refcounting. For hello world, we just skipped it.
|
let value = modify.get_symbol();
|
||||||
self.build_stmt(following, ret_layout)?;
|
let layout = self.symbol_layouts.get(&value).unwrap();
|
||||||
|
|
||||||
|
let ident_ids = self
|
||||||
|
.interns
|
||||||
|
.all_ident_ids
|
||||||
|
.get_mut(&self.env.module_id)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let (rc_stmt, new_proc_info) = self
|
||||||
|
.refcount_proc_gen
|
||||||
|
.expand_refcount_stmt(ident_ids, *layout, modify, *following);
|
||||||
|
|
||||||
|
if false {
|
||||||
|
self.register_symbol_debug_names();
|
||||||
|
println!("## rc_stmt:\n{}\n{:?}", rc_stmt.to_pretty(200), rc_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we're creating a new RC procedure, we need to store its symbol data,
|
||||||
|
// so that we can correctly generate calls to it.
|
||||||
|
if let Some((rc_proc_sym, rc_proc_layout)) = new_proc_info {
|
||||||
|
let wasm_fn_index = self.proc_symbols.len() as u32;
|
||||||
|
let linker_sym_index = self.linker_symbols.len() as u32;
|
||||||
|
|
||||||
|
let name = self
|
||||||
|
.layout_ids
|
||||||
|
.get_toplevel(rc_proc_sym, &rc_proc_layout)
|
||||||
|
.to_symbol_string(rc_proc_sym, self.interns);
|
||||||
|
|
||||||
|
self.proc_symbols.push((rc_proc_sym, linker_sym_index));
|
||||||
|
self.linker_symbols
|
||||||
|
.push(SymInfo::Function(WasmObjectSymbol::Defined {
|
||||||
|
flags: 0,
|
||||||
|
index: wasm_fn_index,
|
||||||
|
name,
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.build_stmt(&rc_stmt, ret_layout)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -488,45 +576,35 @@ impl<'a> WasmBackend<'a> {
|
||||||
return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
|
return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut wasm_args_tmp: Vec<Symbol>;
|
let (param_types, ret_type) = self.storage.load_symbols_for_call(
|
||||||
let (wasm_args, has_return_val) = match wasm_layout {
|
self.env.arena,
|
||||||
WasmLayout::StackMemory { .. } => {
|
&mut self.code_builder,
|
||||||
wasm_args_tmp =
|
arguments,
|
||||||
Vec::with_capacity_in(arguments.len() + 1, self.env.arena);
|
*sym,
|
||||||
wasm_args_tmp.push(*sym);
|
&wasm_layout,
|
||||||
wasm_args_tmp.extend_from_slice(*arguments);
|
CallConv::C,
|
||||||
(wasm_args_tmp.as_slice(), false)
|
|
||||||
}
|
|
||||||
_ => (*arguments, true),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.storage.load_symbols(&mut self.code_builder, wasm_args);
|
|
||||||
|
|
||||||
// Index of the called function in the code section. Assumes all functions end up in the binary.
|
|
||||||
// (We may decide to keep all procs even if calls are inlined, in case platform calls them)
|
|
||||||
let func_index = match self.proc_symbols.iter().position(|s| s == func_sym) {
|
|
||||||
Some(i) => i as u32,
|
|
||||||
None => {
|
|
||||||
// TODO: actually useful linking! Push a relocation for it.
|
|
||||||
return Err(format!(
|
|
||||||
"Not yet supported: calling foreign function {:?}",
|
|
||||||
func_sym
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Index of the function's name in the symbol table
|
|
||||||
// Same as the function index since those are the first symbols we add
|
|
||||||
let symbol_index = func_index;
|
|
||||||
|
|
||||||
self.code_builder.call(
|
|
||||||
func_index,
|
|
||||||
symbol_index,
|
|
||||||
wasm_args.len(),
|
|
||||||
has_return_val,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(())
|
for (func_index, (ir_sym, linker_sym_index)) in
|
||||||
|
self.proc_symbols.iter().enumerate()
|
||||||
|
{
|
||||||
|
if ir_sym == func_sym {
|
||||||
|
let num_wasm_args = param_types.len();
|
||||||
|
let has_return_val = ret_type.is_some();
|
||||||
|
self.code_builder.call(
|
||||||
|
func_index as u32,
|
||||||
|
*linker_sym_index,
|
||||||
|
num_wasm_args,
|
||||||
|
has_return_val,
|
||||||
|
);
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!(
|
||||||
|
"Could not find procedure {:?}\nKnown procedures: {:?}",
|
||||||
|
func_sym, self.proc_symbols
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
CallType::LowLevel { op: lowlevel, .. } => {
|
CallType::LowLevel { op: lowlevel, .. } => {
|
||||||
|
@ -561,6 +639,27 @@ impl<'a> WasmBackend<'a> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Expr::Array { .. } => Err(format!("Expression is not yet implemented {:?}", 2)),
|
||||||
|
|
||||||
|
Expr::EmptyArray => {
|
||||||
|
if let StoredValue::StackMemory { location, .. } = storage {
|
||||||
|
let (local_id, offset) =
|
||||||
|
location.local_and_offset(self.storage.stack_frame_pointer);
|
||||||
|
|
||||||
|
// This is a minor cheat. We only need the first two 32 bit
|
||||||
|
// chunks here. We fill both chunks with zeros, so we
|
||||||
|
// can simplify things to a single group of 64 bit operations instead of
|
||||||
|
// doing the below twice for 32 bits.
|
||||||
|
self.code_builder.get_local(local_id);
|
||||||
|
self.code_builder.i64_const(0);
|
||||||
|
self.code_builder.i64_store(Align::Bytes4, offset);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
unreachable!("Unexpected storage for {:?}", sym)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
x => Err(format!("Expression is not yet implemented {:?}", x)),
|
x => Err(format!("Expression is not yet implemented {:?}", x)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -572,14 +671,13 @@ impl<'a> WasmBackend<'a> {
|
||||||
return_sym: Symbol,
|
return_sym: Symbol,
|
||||||
return_layout: WasmLayout,
|
return_layout: WasmLayout,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
// Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use.
|
let (param_types, ret_type) = self.storage.load_symbols_for_call(
|
||||||
// It's only different from the C ABI for small structs, and we are using Zig for all of those cases.
|
self.env.arena,
|
||||||
// This is a workaround for a bug in Zig. If later versions fix it, we can change to the C ABI.
|
|
||||||
self.storage.load_symbols_fastcc(
|
|
||||||
&mut self.code_builder,
|
&mut self.code_builder,
|
||||||
arguments,
|
arguments,
|
||||||
return_sym,
|
return_sym,
|
||||||
&return_layout,
|
&return_layout,
|
||||||
|
CallConv::Zig,
|
||||||
);
|
);
|
||||||
|
|
||||||
let build_result = decode_low_level(
|
let build_result = decode_low_level(
|
||||||
|
@ -594,7 +692,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
match build_result {
|
match build_result {
|
||||||
Done => Ok(()),
|
Done => Ok(()),
|
||||||
BuiltinCall(name) => {
|
BuiltinCall(name) => {
|
||||||
self.call_zig_builtin(name, arguments, &return_layout);
|
self.call_zig_builtin(name, param_types, ret_type);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
NotImplemented => Err(format!(
|
NotImplemented => Err(format!(
|
||||||
|
@ -611,7 +709,7 @@ impl<'a> WasmBackend<'a> {
|
||||||
sym: Symbol,
|
sym: Symbol,
|
||||||
layout: &Layout<'a>,
|
layout: &Layout<'a>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let not_supported_error = || Err(format!("Literal value {:?} is not yet implemented", lit));
|
let not_supported_error = || panic!("Literal value {:?} is not yet implemented", lit);
|
||||||
|
|
||||||
match storage {
|
match storage {
|
||||||
StoredValue::VirtualMachineStack { value_type, .. } => {
|
StoredValue::VirtualMachineStack { value_type, .. } => {
|
||||||
|
@ -655,6 +753,8 @@ impl<'a> WasmBackend<'a> {
|
||||||
stack_mem_bytes[7] = 0x80 | (len as u8);
|
stack_mem_bytes[7] = 0x80 | (len as u8);
|
||||||
let str_as_int = i64::from_le_bytes(stack_mem_bytes);
|
let str_as_int = i64::from_le_bytes(stack_mem_bytes);
|
||||||
|
|
||||||
|
// Write all 8 bytes at once using an i64
|
||||||
|
// Str is normally two i32's, but in this special case, we can get away with fewer instructions
|
||||||
self.code_builder.get_local(local_id);
|
self.code_builder.get_local(local_id);
|
||||||
self.code_builder.i64_const(str_as_int);
|
self.code_builder.i64_const(str_as_int);
|
||||||
self.code_builder.i64_store(Align::Bytes4, offset);
|
self.code_builder.i64_store(Align::Bytes4, offset);
|
||||||
|
@ -712,10 +812,13 @@ impl<'a> WasmBackend<'a> {
|
||||||
None => {
|
None => {
|
||||||
let const_segment_bytes = &mut self.module.data.segments[CONST_SEGMENT_INDEX].init;
|
let const_segment_bytes = &mut self.module.data.segments[CONST_SEGMENT_INDEX].init;
|
||||||
|
|
||||||
// Store the string in the data section, to be loaded on module instantiation
|
// Store the string in the data section
|
||||||
// RocStr `elements` field will point to that constant data, not the heap
|
// Prefix it with a special refcount value (treated as "infinity")
|
||||||
let segment_offset = const_segment_bytes.len() as u32;
|
// The string's `elements` field points at the data after the refcount
|
||||||
let elements_addr = segment_offset + CONST_SEGMENT_BASE_ADDR;
|
let refcount_max_bytes: [u8; 4] = (REFCOUNT_MAX as i32).to_le_bytes();
|
||||||
|
const_segment_bytes.extend_from_slice(&refcount_max_bytes);
|
||||||
|
let elements_offset = const_segment_bytes.len() as u32;
|
||||||
|
let elements_addr = elements_offset + CONST_SEGMENT_BASE_ADDR;
|
||||||
const_segment_bytes.extend_from_slice(string.as_bytes());
|
const_segment_bytes.extend_from_slice(string.as_bytes());
|
||||||
|
|
||||||
// Generate linker info
|
// Generate linker info
|
||||||
|
@ -723,12 +826,12 @@ impl<'a> WasmBackend<'a> {
|
||||||
let name = self
|
let name = self
|
||||||
.layout_ids
|
.layout_ids
|
||||||
.get(sym, layout)
|
.get(sym, layout)
|
||||||
.to_symbol_string(sym, &self.env.interns);
|
.to_symbol_string(sym, self.interns);
|
||||||
let linker_symbol = SymInfo::Data(DataSymbol::Defined {
|
let linker_symbol = SymInfo::Data(DataSymbol::Defined {
|
||||||
flags: 0,
|
flags: 0,
|
||||||
name,
|
name,
|
||||||
segment_index: CONST_SEGMENT_INDEX as u32,
|
segment_index: CONST_SEGMENT_INDEX as u32,
|
||||||
segment_offset,
|
segment_offset: elements_offset,
|
||||||
size: string.len() as u32,
|
size: string.len() as u32,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -767,7 +870,9 @@ impl<'a> WasmBackend<'a> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return Err(format!("Not supported yet: zero-size struct at {:?}", sym));
|
// Zero-size struct. No code to emit.
|
||||||
|
// These values are purely conceptual, they only exist internally in the compiler
|
||||||
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -789,7 +894,15 @@ impl<'a> WasmBackend<'a> {
|
||||||
/// Generate a call instruction to a Zig builtin function.
|
/// Generate a call instruction to a Zig builtin function.
|
||||||
/// And if we haven't seen it before, add an Import and linker data for it.
|
/// And if we haven't seen it before, add an Import and linker data for it.
|
||||||
/// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
|
/// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI.
|
||||||
fn call_zig_builtin(&mut self, name: &'a str, arguments: &[Symbol], ret_layout: &WasmLayout) {
|
fn call_zig_builtin(
|
||||||
|
&mut self,
|
||||||
|
name: &'a str,
|
||||||
|
param_types: Vec<'a, ValueType>,
|
||||||
|
ret_type: Option<ValueType>,
|
||||||
|
) {
|
||||||
|
let num_wasm_args = param_types.len();
|
||||||
|
let has_return_val = ret_type.is_some();
|
||||||
|
|
||||||
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
|
let (fn_index, linker_symbol_index) = match self.builtin_sym_index_map.get(name) {
|
||||||
Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
|
Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
|
||||||
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
|
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
|
||||||
|
@ -799,51 +912,14 @@ impl<'a> WasmBackend<'a> {
|
||||||
},
|
},
|
||||||
|
|
||||||
None => {
|
None => {
|
||||||
let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
|
// Wasm function signature
|
||||||
|
let signature = Signature {
|
||||||
let ret_type = if ret_layout.is_stack_memory() {
|
|
||||||
param_types.push(ValueType::I32);
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(ret_layout.value_type())
|
|
||||||
};
|
|
||||||
|
|
||||||
for arg in arguments {
|
|
||||||
match self.storage.get(arg) {
|
|
||||||
StoredValue::StackMemory { size, format, .. } => {
|
|
||||||
use StackMemoryFormat::*;
|
|
||||||
|
|
||||||
match format {
|
|
||||||
Aggregate => {
|
|
||||||
// Zig's "fast calling convention" packs structs into CPU registers
|
|
||||||
// (stack machine slots) if possible. If they're small enough they
|
|
||||||
// can go into an I32 or I64. If they're big, they're pointers (I32).
|
|
||||||
if *size > 4 && *size <= 8 {
|
|
||||||
param_types.push(ValueType::I64)
|
|
||||||
} else {
|
|
||||||
// either
|
|
||||||
//
|
|
||||||
// - this is a small value, that fits in an i32
|
|
||||||
// - this is a big value, we pass a memory address
|
|
||||||
param_types.push(ValueType::I32)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Int128 | Float128 | Decimal => {
|
|
||||||
// these types are passed as 2 i64s
|
|
||||||
param_types.push(ValueType::I64);
|
|
||||||
param_types.push(ValueType::I64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
stored => param_types.push(stored.value_type()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let signature_index = self.module.types.insert(Signature {
|
|
||||||
param_types,
|
param_types,
|
||||||
ret_type,
|
ret_type,
|
||||||
});
|
};
|
||||||
|
let signature_index = self.module.types.insert(signature);
|
||||||
|
|
||||||
|
// Declare it as an import since it comes from a different .o file
|
||||||
let import_index = self.module.import.entries.len() as u32;
|
let import_index = self.module.import.entries.len() as u32;
|
||||||
let import = Import {
|
let import = Import {
|
||||||
module: BUILTINS_IMPORT_MODULE_NAME,
|
module: BUILTINS_IMPORT_MODULE_NAME,
|
||||||
|
@ -852,22 +928,22 @@ impl<'a> WasmBackend<'a> {
|
||||||
};
|
};
|
||||||
self.module.import.entries.push(import);
|
self.module.import.entries.push(import);
|
||||||
|
|
||||||
|
// Provide symbol information for the linker
|
||||||
let sym_idx = self.linker_symbols.len();
|
let sym_idx = self.linker_symbols.len();
|
||||||
let sym_info = SymInfo::Function(WasmObjectSymbol::Imported {
|
let sym_info = SymInfo::Function(WasmObjectSymbol::Imported {
|
||||||
flags: WASM_SYM_UNDEFINED,
|
flags: WASM_SYM_UNDEFINED,
|
||||||
index: import_index,
|
index: import_index,
|
||||||
});
|
});
|
||||||
self.linker_symbols.push(sym_info);
|
self.linker_symbols.push(sym_info);
|
||||||
|
|
||||||
|
// Remember that we have created all of this data, and don't need to do it again
|
||||||
self.builtin_sym_index_map.insert(name, sym_idx);
|
self.builtin_sym_index_map.insert(name, sym_idx);
|
||||||
|
|
||||||
(import_index, sym_idx as u32)
|
(import_index, sym_idx as u32)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.code_builder.call(
|
|
||||||
fn_index,
|
self.code_builder
|
||||||
linker_symbol_index,
|
.call(fn_index, linker_symbol_index, num_wasm_args, has_return_val);
|
||||||
arguments.len(),
|
|
||||||
true, // TODO: handle builtins with no return value
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,26 @@
|
||||||
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||||
use roc_mono::layout::{Layout, UnionLayout};
|
use roc_mono::layout::{Layout, UnionLayout};
|
||||||
|
|
||||||
use crate::{wasm_module::ValueType, PTR_SIZE, PTR_TYPE};
|
use crate::wasm_module::ValueType;
|
||||||
|
use crate::{PTR_SIZE, PTR_TYPE};
|
||||||
|
|
||||||
|
/// Manually keep up to date with the Zig version we are using for builtins
|
||||||
|
pub const BUILTINS_ZIG_VERSION: ZigVersion = ZigVersion::Zig8;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum ReturnMethod {
|
||||||
|
/// This layout is returned from a Wasm function "normally" as a Primitive
|
||||||
|
Primitive(ValueType),
|
||||||
|
/// This layout is returned by writing to a pointer passed as the first argument
|
||||||
|
WriteToPointerArg,
|
||||||
|
/// This layout is empty and requires no return value or argument (e.g. refcount helpers)
|
||||||
|
NoReturnValue,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum StackMemoryFormat {
|
pub enum StackMemoryFormat {
|
||||||
/// Record, Str, List, Dict, etc.
|
/// Record, Str, List, Dict, etc.
|
||||||
Aggregate,
|
DataStructure,
|
||||||
Int128,
|
Int128,
|
||||||
Float128,
|
Float128,
|
||||||
Decimal,
|
Decimal,
|
||||||
|
@ -25,9 +39,6 @@ pub enum WasmLayout {
|
||||||
alignment_bytes: u32,
|
alignment_bytes: u32,
|
||||||
format: StackMemoryFormat,
|
format: StackMemoryFormat,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Local pointer to heap memory
|
|
||||||
HeapMemory,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WasmLayout {
|
impl WasmLayout {
|
||||||
|
@ -82,7 +93,7 @@ impl WasmLayout {
|
||||||
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
|
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
format: StackMemoryFormat::Aggregate,
|
format: StackMemoryFormat::DataStructure,
|
||||||
},
|
},
|
||||||
|
|
||||||
Layout::Union(
|
Layout::Union(
|
||||||
|
@ -91,26 +102,95 @@ impl WasmLayout {
|
||||||
| NullableWrapped { .. }
|
| NullableWrapped { .. }
|
||||||
| NullableUnwrapped { .. },
|
| NullableUnwrapped { .. },
|
||||||
)
|
)
|
||||||
| Layout::RecursivePointer => Self::HeapMemory,
|
| Layout::RecursivePointer => Self::Primitive(PTR_TYPE, PTR_SIZE),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn value_type(&self) -> ValueType {
|
/// The `ValueType`s to use for this layout when calling a Wasm function
|
||||||
|
/// One Roc argument can become 0, 1, or 2 Wasm arguments
|
||||||
|
pub fn arg_types(&self, conv: CallConv) -> &'static [ValueType] {
|
||||||
|
use ValueType::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Primitive(type_, _) => *type_,
|
// 1 Roc argument => 1 Wasm argument (same for all calling conventions)
|
||||||
_ => PTR_TYPE,
|
Self::Primitive(I32, _) => &[I32],
|
||||||
|
Self::Primitive(I64, _) => &[I64],
|
||||||
|
Self::Primitive(F32, _) => &[F32],
|
||||||
|
Self::Primitive(F64, _) => &[F64],
|
||||||
|
|
||||||
|
// 1 Roc argument => 0-2 Wasm arguments (depending on size and calling convention)
|
||||||
|
Self::StackMemory { size, format, .. } => conv.stack_memory_arg_types(*size, *format),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn size(&self) -> u32 {
|
pub fn return_method(&self) -> ReturnMethod {
|
||||||
match self {
|
match self {
|
||||||
Self::Primitive(_, size) => *size,
|
Self::Primitive(ty, _) => ReturnMethod::Primitive(*ty),
|
||||||
Self::StackMemory { size, .. } => *size,
|
Self::StackMemory { size, .. } => {
|
||||||
Self::HeapMemory => PTR_SIZE,
|
if *size == 0 {
|
||||||
|
ReturnMethod::NoReturnValue
|
||||||
|
} else {
|
||||||
|
ReturnMethod::WriteToPointerArg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_stack_memory(&self) -> bool {
|
#[derive(PartialEq, Eq)]
|
||||||
matches!(self, Self::StackMemory { .. })
|
pub enum ZigVersion {
|
||||||
|
Zig8,
|
||||||
|
Zig9,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub enum CallConv {
|
||||||
|
/// The C calling convention, as defined here:
|
||||||
|
/// https://github.com/WebAssembly/tool-conventions/blob/main/BasicCABI.md
|
||||||
|
C,
|
||||||
|
/// The calling convention that Zig 0.8 or 0.9 generates for Wasm when we *ask* it
|
||||||
|
/// for the .C calling convention, due to bugs in both versions of the Zig compiler.
|
||||||
|
Zig,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallConv {
|
||||||
|
/// The Wasm argument types to use when passing structs or 128-bit numbers
|
||||||
|
pub fn stack_memory_arg_types(
|
||||||
|
&self,
|
||||||
|
size: u32,
|
||||||
|
format: StackMemoryFormat,
|
||||||
|
) -> &'static [ValueType] {
|
||||||
|
use StackMemoryFormat::*;
|
||||||
|
use ValueType::*;
|
||||||
|
|
||||||
|
match format {
|
||||||
|
Int128 | Float128 | Decimal => &[I64, I64],
|
||||||
|
|
||||||
|
DataStructure => {
|
||||||
|
if size == 0 {
|
||||||
|
// Zero-size Roc values like `{}` => no Wasm arguments
|
||||||
|
return &[];
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
CallConv::C => {
|
||||||
|
&[I32] // Always pass structs by reference (pointer to stack memory)
|
||||||
|
}
|
||||||
|
|
||||||
|
CallConv::Zig => {
|
||||||
|
if size <= 4 {
|
||||||
|
&[I32] // Small struct: pass by value
|
||||||
|
} else if size <= 8 {
|
||||||
|
&[I64] // Small struct: pass by value
|
||||||
|
} else if size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 {
|
||||||
|
&[I64, I32] // Medium struct: pass by value, as two Wasm arguments
|
||||||
|
} else if size <= 16 {
|
||||||
|
&[I64, I64] // Medium struct: pass by value, as two Wasm arguments
|
||||||
|
} else {
|
||||||
|
&[I32] // Large struct: pass by reference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,17 @@ pub mod wasm_module;
|
||||||
|
|
||||||
use bumpalo::{self, collections::Vec, Bump};
|
use bumpalo::{self, collections::Vec, Bump};
|
||||||
|
|
||||||
|
use roc_builtins::bitcode::IntWidth;
|
||||||
use roc_collections::all::{MutMap, MutSet};
|
use roc_collections::all::{MutMap, MutSet};
|
||||||
use roc_module::low_level::LowLevel;
|
use roc_module::low_level::LowLevel;
|
||||||
use roc_module::symbol::{Interns, Symbol};
|
use roc_module::symbol::{Interns, ModuleId, Symbol};
|
||||||
|
use roc_mono::gen_refcount::RefcountProcGenerator;
|
||||||
use roc_mono::ir::{Proc, ProcLayout};
|
use roc_mono::ir::{Proc, ProcLayout};
|
||||||
use roc_mono::layout::LayoutIds;
|
use roc_mono::layout::LayoutIds;
|
||||||
|
|
||||||
use crate::backend::WasmBackend;
|
use crate::backend::WasmBackend;
|
||||||
use crate::wasm_module::{
|
use crate::wasm_module::{
|
||||||
Align, CodeBuilder, Export, ExportType, LinkingSubSection, LocalId, SymInfo, ValueType,
|
Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule,
|
||||||
WasmModule,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const PTR_SIZE: u32 = 4;
|
const PTR_SIZE: u32 = 4;
|
||||||
|
@ -29,27 +30,29 @@ pub const STACK_POINTER_NAME: &str = "__stack_pointer";
|
||||||
|
|
||||||
pub struct Env<'a> {
|
pub struct Env<'a> {
|
||||||
pub arena: &'a Bump,
|
pub arena: &'a Bump,
|
||||||
pub interns: Interns,
|
pub module_id: ModuleId,
|
||||||
pub exposed_to_host: MutSet<Symbol>,
|
pub exposed_to_host: MutSet<Symbol>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_module<'a>(
|
pub fn build_module<'a>(
|
||||||
env: &'a Env,
|
env: &'a Env<'a>,
|
||||||
|
interns: &'a mut Interns,
|
||||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
) -> Result<std::vec::Vec<u8>, String> {
|
) -> Result<std::vec::Vec<u8>, String> {
|
||||||
let (mut wasm_module, _) = build_module_help(env, procedures)?;
|
let (mut wasm_module, _) = build_module_help(env, interns, procedures)?;
|
||||||
let mut buffer = std::vec::Vec::with_capacity(4096);
|
let mut buffer = std::vec::Vec::with_capacity(4096);
|
||||||
wasm_module.serialize_mut(&mut buffer);
|
wasm_module.serialize_mut(&mut buffer);
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_module_help<'a>(
|
pub fn build_module_help<'a>(
|
||||||
env: &'a Env,
|
env: &'a Env<'a>,
|
||||||
|
interns: &'a mut Interns,
|
||||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
) -> Result<(WasmModule<'a>, u32), String> {
|
) -> Result<(WasmModule<'a>, u32), String> {
|
||||||
let mut layout_ids = LayoutIds::default();
|
let mut layout_ids = LayoutIds::default();
|
||||||
let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
let mut procs = Vec::with_capacity_in(procedures.len(), env.arena);
|
||||||
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
|
let mut proc_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
||||||
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
let mut linker_symbols = Vec::with_capacity_in(procedures.len() * 2, env.arena);
|
||||||
let mut exports = Vec::with_capacity_in(4, env.arena);
|
let mut exports = Vec::with_capacity_in(4, env.arena);
|
||||||
let mut main_fn_index = None;
|
let mut main_fn_index = None;
|
||||||
|
@ -61,12 +64,11 @@ pub fn build_module_help<'a>(
|
||||||
if LowLevel::from_inlined_wrapper(sym).is_some() {
|
if LowLevel::from_inlined_wrapper(sym).is_some() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
generated_procs.push(proc);
|
procs.push(proc);
|
||||||
generated_symbols.push(sym);
|
|
||||||
|
|
||||||
let fn_name = layout_ids
|
let fn_name = layout_ids
|
||||||
.get_toplevel(sym, &layout)
|
.get_toplevel(sym, &layout)
|
||||||
.to_symbol_string(sym, &env.interns);
|
.to_symbol_string(sym, interns);
|
||||||
|
|
||||||
if env.exposed_to_host.contains(&sym) {
|
if env.exposed_to_host.contains(&sym) {
|
||||||
main_fn_index = Some(fn_index);
|
main_fn_index = Some(fn_index);
|
||||||
|
@ -78,29 +80,54 @@ pub fn build_module_help<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
let linker_sym = SymInfo::for_function(fn_index, fn_name);
|
let linker_sym = SymInfo::for_function(fn_index, fn_name);
|
||||||
|
proc_symbols.push((sym, linker_symbols.len() as u32));
|
||||||
linker_symbols.push(linker_sym);
|
linker_symbols.push(linker_sym);
|
||||||
|
|
||||||
fn_index += 1;
|
fn_index += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the Wasm module
|
|
||||||
let (mut module, linker_symbols) = {
|
|
||||||
let mut backend = WasmBackend::new(
|
let mut backend = WasmBackend::new(
|
||||||
env,
|
env,
|
||||||
|
interns,
|
||||||
layout_ids,
|
layout_ids,
|
||||||
generated_symbols.clone(),
|
proc_symbols,
|
||||||
linker_symbols,
|
linker_symbols,
|
||||||
exports,
|
exports,
|
||||||
|
RefcountProcGenerator::new(env.arena, IntWidth::I32, env.module_id),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
|
if false {
|
||||||
backend.build_proc(proc, sym)?;
|
println!("## procs");
|
||||||
|
for proc in procs.iter() {
|
||||||
|
println!("{}", proc.to_pretty(200));
|
||||||
|
println!("{:#?}", proc);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
(backend.module, backend.linker_symbols)
|
|
||||||
};
|
|
||||||
|
|
||||||
let symbol_table = LinkingSubSection::SymbolTable(linker_symbols);
|
// Generate procs from user code
|
||||||
module.linking.subsections.push(symbol_table);
|
for proc in procs.iter() {
|
||||||
|
backend.build_proc(proc)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate IR for refcounting procs
|
||||||
|
let refcount_procs = backend.generate_refcount_procs();
|
||||||
|
|
||||||
|
backend.register_symbol_debug_names();
|
||||||
|
|
||||||
|
if false {
|
||||||
|
println!("## refcount_procs");
|
||||||
|
for proc in refcount_procs.iter() {
|
||||||
|
println!("{}", proc.to_pretty(200));
|
||||||
|
println!("{:#?}", proc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate Wasm for refcounting procs
|
||||||
|
for proc in refcount_procs.iter() {
|
||||||
|
backend.build_proc(proc)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let module = backend.finalize_module();
|
||||||
|
|
||||||
Ok((module, main_fn_index.unwrap()))
|
Ok((module, main_fn_index.unwrap()))
|
||||||
}
|
}
|
||||||
|
@ -118,6 +145,9 @@ pub fn copy_memory(code_builder: &mut CodeBuilder, config: CopyMemoryConfig) {
|
||||||
if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset {
|
if config.from_ptr == config.to_ptr && config.from_offset == config.to_offset {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if config.size == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let alignment = Align::from(config.alignment_bytes);
|
let alignment = Align::from(config.alignment_bytes);
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
|
@ -2,12 +2,9 @@ use roc_builtins::bitcode::{self, FloatWidth};
|
||||||
use roc_module::low_level::{LowLevel, LowLevel::*};
|
use roc_module::low_level::{LowLevel, LowLevel::*};
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
|
|
||||||
use crate::layout::{StackMemoryFormat, WasmLayout};
|
use crate::layout::{StackMemoryFormat::*, WasmLayout};
|
||||||
use crate::storage::Storage;
|
use crate::storage::{Storage, StoredValue};
|
||||||
use crate::wasm_module::{
|
use crate::wasm_module::{CodeBuilder, ValueType::*};
|
||||||
CodeBuilder,
|
|
||||||
ValueType::{self, *},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub enum LowlevelBuildResult {
|
pub enum LowlevelBuildResult {
|
||||||
Done,
|
Done,
|
||||||
|
@ -71,95 +68,168 @@ pub fn decode_low_level<'a>(
|
||||||
F64 => code_builder.f64_add(),
|
F64 => code_builder.f64_add(),
|
||||||
},
|
},
|
||||||
WasmLayout::StackMemory { format, .. } => match format {
|
WasmLayout::StackMemory { format, .. } => match format {
|
||||||
StackMemoryFormat::Aggregate => return NotImplemented,
|
DataStructure => return NotImplemented,
|
||||||
StackMemoryFormat::Int128 => return NotImplemented,
|
Int128 => return NotImplemented,
|
||||||
StackMemoryFormat::Float128 => return NotImplemented,
|
Float128 => return NotImplemented,
|
||||||
StackMemoryFormat::Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW),
|
Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW),
|
||||||
},
|
},
|
||||||
WasmLayout::HeapMemory { .. } => return NotImplemented,
|
|
||||||
},
|
},
|
||||||
NumAddWrap => match ret_layout.value_type() {
|
NumAddWrap => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, size) => match value_type {
|
||||||
I32 => {
|
I32 => {
|
||||||
code_builder.i32_add();
|
code_builder.i32_add();
|
||||||
wrap_i32(code_builder, ret_layout.size());
|
// TODO: is *deliberate* wrapping really in the spirit of things?
|
||||||
|
// The point of choosing NumAddWrap is to go fast by skipping checks, but we're making it slower.
|
||||||
|
wrap_i32(code_builder, *size);
|
||||||
}
|
}
|
||||||
I64 => code_builder.i64_add(),
|
I64 => code_builder.i64_add(),
|
||||||
F32 => code_builder.f32_add(),
|
F32 => code_builder.f32_add(),
|
||||||
F64 => code_builder.f64_add(),
|
F64 => code_builder.f64_add(),
|
||||||
},
|
},
|
||||||
|
WasmLayout::StackMemory { format, .. } => match format {
|
||||||
|
DataStructure => return NotImplemented,
|
||||||
|
Int128 => return NotImplemented,
|
||||||
|
Float128 => return NotImplemented,
|
||||||
|
Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumToStr => return NotImplemented,
|
||||||
NumAddChecked => return NotImplemented,
|
NumAddChecked => return NotImplemented,
|
||||||
NumSub => match ret_layout.value_type() {
|
NumSub => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_sub(),
|
I32 => code_builder.i32_sub(),
|
||||||
I64 => code_builder.i64_sub(),
|
I64 => code_builder.i64_sub(),
|
||||||
F32 => code_builder.f32_sub(),
|
F32 => code_builder.f32_sub(),
|
||||||
F64 => code_builder.f64_sub(),
|
F64 => code_builder.f64_sub(),
|
||||||
},
|
},
|
||||||
NumSubWrap => match ret_layout.value_type() {
|
WasmLayout::StackMemory { format, .. } => match format {
|
||||||
|
DataStructure => return NotImplemented,
|
||||||
|
Int128 => return NotImplemented,
|
||||||
|
Float128 => return NotImplemented,
|
||||||
|
Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumSubWrap => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, size) => match value_type {
|
||||||
I32 => {
|
I32 => {
|
||||||
code_builder.i32_sub();
|
code_builder.i32_sub();
|
||||||
wrap_i32(code_builder, ret_layout.size());
|
wrap_i32(code_builder, *size);
|
||||||
}
|
}
|
||||||
I64 => code_builder.i64_sub(),
|
I64 => code_builder.i64_sub(),
|
||||||
F32 => code_builder.f32_sub(),
|
F32 => code_builder.f32_sub(),
|
||||||
F64 => code_builder.f64_sub(),
|
F64 => code_builder.f64_sub(),
|
||||||
},
|
},
|
||||||
|
WasmLayout::StackMemory { format, .. } => match format {
|
||||||
|
DataStructure => return NotImplemented,
|
||||||
|
Int128 => return NotImplemented,
|
||||||
|
Float128 => return NotImplemented,
|
||||||
|
Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW),
|
||||||
|
},
|
||||||
|
},
|
||||||
NumSubChecked => return NotImplemented,
|
NumSubChecked => return NotImplemented,
|
||||||
NumMul => match ret_layout.value_type() {
|
NumMul => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_mul(),
|
I32 => code_builder.i32_mul(),
|
||||||
I64 => code_builder.i64_mul(),
|
I64 => code_builder.i64_mul(),
|
||||||
F32 => code_builder.f32_mul(),
|
F32 => code_builder.f32_mul(),
|
||||||
F64 => code_builder.f64_mul(),
|
F64 => code_builder.f64_mul(),
|
||||||
},
|
},
|
||||||
NumMulWrap => match ret_layout.value_type() {
|
WasmLayout::StackMemory { format, .. } => match format {
|
||||||
|
DataStructure => return NotImplemented,
|
||||||
|
Int128 => return NotImplemented,
|
||||||
|
Float128 => return NotImplemented,
|
||||||
|
Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NumMulWrap => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, size) => match value_type {
|
||||||
I32 => {
|
I32 => {
|
||||||
code_builder.i32_mul();
|
code_builder.i32_mul();
|
||||||
wrap_i32(code_builder, ret_layout.size());
|
wrap_i32(code_builder, *size);
|
||||||
}
|
}
|
||||||
I64 => code_builder.i64_mul(),
|
I64 => code_builder.i64_mul(),
|
||||||
F32 => code_builder.f32_mul(),
|
F32 => code_builder.f32_mul(),
|
||||||
F64 => code_builder.f64_mul(),
|
F64 => code_builder.f64_mul(),
|
||||||
},
|
},
|
||||||
|
WasmLayout::StackMemory { format, .. } => match format {
|
||||||
|
DataStructure => return NotImplemented,
|
||||||
|
Int128 => return NotImplemented,
|
||||||
|
Float128 => return NotImplemented,
|
||||||
|
Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW),
|
||||||
|
},
|
||||||
|
},
|
||||||
NumMulChecked => return NotImplemented,
|
NumMulChecked => return NotImplemented,
|
||||||
NumGt => match storage.get(&args[0]).value_type() {
|
NumGt => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_gt_s(),
|
I32 => code_builder.i32_gt_s(),
|
||||||
I64 => code_builder.i64_gt_s(),
|
I64 => code_builder.i64_gt_s(),
|
||||||
F32 => code_builder.f32_gt(),
|
F32 => code_builder.f32_gt(),
|
||||||
F64 => code_builder.f64_gt(),
|
F64 => code_builder.f64_gt(),
|
||||||
},
|
},
|
||||||
NumGte => match storage.get(&args[0]).value_type() {
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumGte => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_ge_s(),
|
I32 => code_builder.i32_ge_s(),
|
||||||
I64 => code_builder.i64_ge_s(),
|
I64 => code_builder.i64_ge_s(),
|
||||||
F32 => code_builder.f32_ge(),
|
F32 => code_builder.f32_ge(),
|
||||||
F64 => code_builder.f64_ge(),
|
F64 => code_builder.f64_ge(),
|
||||||
},
|
},
|
||||||
NumLt => match storage.get(&args[0]).value_type() {
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumLt => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_lt_s(),
|
I32 => code_builder.i32_lt_s(),
|
||||||
I64 => code_builder.i64_lt_s(),
|
I64 => code_builder.i64_lt_s(),
|
||||||
F32 => code_builder.f32_lt(),
|
F32 => code_builder.f32_lt(),
|
||||||
F64 => code_builder.f64_lt(),
|
F64 => code_builder.f64_lt(),
|
||||||
},
|
},
|
||||||
NumLte => match storage.get(&args[0]).value_type() {
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumLte => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_le_s(),
|
I32 => code_builder.i32_le_s(),
|
||||||
I64 => code_builder.i64_le_s(),
|
I64 => code_builder.i64_le_s(),
|
||||||
F32 => code_builder.f32_le(),
|
F32 => code_builder.f32_le(),
|
||||||
F64 => code_builder.f64_le(),
|
F64 => code_builder.f64_le(),
|
||||||
},
|
},
|
||||||
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
NumCompare => return NotImplemented,
|
NumCompare => return NotImplemented,
|
||||||
NumDivUnchecked => match ret_layout.value_type() {
|
NumDivUnchecked => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_div_s(),
|
I32 => code_builder.i32_div_s(),
|
||||||
I64 => code_builder.i64_div_s(),
|
I64 => code_builder.i64_div_s(),
|
||||||
F32 => code_builder.f32_div(),
|
F32 => code_builder.f32_div(),
|
||||||
F64 => code_builder.f64_div(),
|
F64 => code_builder.f64_div(),
|
||||||
},
|
},
|
||||||
|
StoredValue::StackMemory { format, .. } => match format {
|
||||||
|
DataStructure => return NotImplemented,
|
||||||
|
Int128 => return NotImplemented,
|
||||||
|
Float128 => return NotImplemented,
|
||||||
|
Decimal => return BuiltinCall(bitcode::DEC_DIV),
|
||||||
|
},
|
||||||
|
},
|
||||||
NumDivCeilUnchecked => return NotImplemented,
|
NumDivCeilUnchecked => return NotImplemented,
|
||||||
NumRemUnchecked => match ret_layout.value_type() {
|
NumRemUnchecked => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_rem_s(),
|
I32 => code_builder.i32_rem_s(),
|
||||||
I64 => code_builder.i64_rem_s(),
|
I64 => code_builder.i64_rem_s(),
|
||||||
F32 => return NotImplemented,
|
F32 => return NotImplemented,
|
||||||
F64 => return NotImplemented,
|
F64 => return NotImplemented,
|
||||||
},
|
},
|
||||||
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
NumIsMultipleOf => return NotImplemented,
|
NumIsMultipleOf => return NotImplemented,
|
||||||
NumAbs => match ret_layout.value_type() {
|
NumAbs => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => {
|
I32 => {
|
||||||
let arg_storage = storage.get(&args[0]).to_owned();
|
let arg_storage = storage.get(&args[0]).to_owned();
|
||||||
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
|
storage.ensure_value_has_local(code_builder, args[0], arg_storage);
|
||||||
|
@ -187,51 +257,66 @@ pub fn decode_low_level<'a>(
|
||||||
F32 => code_builder.f32_abs(),
|
F32 => code_builder.f32_abs(),
|
||||||
F64 => code_builder.f64_abs(),
|
F64 => code_builder.f64_abs(),
|
||||||
},
|
},
|
||||||
NumNeg => {
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
match ret_layout.value_type() {
|
},
|
||||||
|
NumNeg => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => {
|
I32 => {
|
||||||
// Unfortunate local.set/local.get
|
|
||||||
code_builder.i32_const(0);
|
code_builder.i32_const(0);
|
||||||
storage.load_symbols(code_builder, args);
|
storage.load_symbols(code_builder, args);
|
||||||
code_builder.i32_sub();
|
code_builder.i32_sub();
|
||||||
}
|
}
|
||||||
I64 => {
|
I64 => {
|
||||||
// Unfortunate local.set/local.get
|
|
||||||
code_builder.i64_const(0);
|
code_builder.i64_const(0);
|
||||||
storage.load_symbols(code_builder, args);
|
storage.load_symbols(code_builder, args);
|
||||||
code_builder.i64_sub();
|
code_builder.i64_sub();
|
||||||
}
|
}
|
||||||
F32 => code_builder.f32_neg(),
|
F32 => code_builder.f32_neg(),
|
||||||
F64 => code_builder.f64_neg(),
|
F64 => code_builder.f64_neg(),
|
||||||
}
|
},
|
||||||
}
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
NumSin => return NotImplemented,
|
NumSin => return NotImplemented,
|
||||||
NumCos => return NotImplemented,
|
NumCos => return NotImplemented,
|
||||||
NumSqrtUnchecked => return NotImplemented,
|
NumSqrtUnchecked => return NotImplemented,
|
||||||
NumLogUnchecked => return NotImplemented,
|
NumLogUnchecked => return NotImplemented,
|
||||||
NumRound => {
|
NumRound => match storage.get(&args[0]) {
|
||||||
// FIXME
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
// thread 'gen_num::f64_round' panicked at 'called `Result::unwrap()` on an `Err` value:
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
// Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })',
|
F32 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F32]),
|
||||||
// compiler/test_gen/src/helpers/wasm.rs:185:53
|
F64 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F64]),
|
||||||
// Note: Wasm has a `nearest` op, but it does round-to-even when fraction is exactly 0.5
|
_ => return NotImplemented,
|
||||||
// which fails tests. Will this do? Or is specific behaviour important?
|
},
|
||||||
let width = float_width_from_layout(ret_layout);
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
return BuiltinCall(&bitcode::NUM_ROUND[width]);
|
},
|
||||||
}
|
NumToFloat => {
|
||||||
NumToFloat => match (ret_layout.value_type(), storage.get(&args[0]).value_type()) {
|
use StoredValue::*;
|
||||||
|
let stored = storage.get(&args[0]);
|
||||||
|
match ret_layout {
|
||||||
|
WasmLayout::Primitive(ret_type, _) => match stored {
|
||||||
|
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
|
||||||
|
match (ret_type, value_type) {
|
||||||
(F32, I32) => code_builder.f32_convert_s_i32(),
|
(F32, I32) => code_builder.f32_convert_s_i32(),
|
||||||
(F32, I64) => code_builder.f32_convert_s_i64(),
|
(F32, I64) => code_builder.f32_convert_s_i64(),
|
||||||
(F32, F32) => {}
|
(F32, F32) => {}
|
||||||
(F32, F64) => code_builder.f32_demote_f64(),
|
(F32, F64) => code_builder.f32_demote_f64(),
|
||||||
|
|
||||||
(F64, I32) => code_builder.f64_convert_s_i32(),
|
(F64, I32) => code_builder.f64_convert_s_i32(),
|
||||||
(F64, I64) => code_builder.f64_convert_s_i64(),
|
(F64, I64) => code_builder.f64_convert_s_i64(),
|
||||||
(F64, F32) => code_builder.f64_promote_f32(),
|
(F64, F32) => code_builder.f64_promote_f32(),
|
||||||
(F64, F64) => {}
|
(F64, F64) => {}
|
||||||
|
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StackMemory { .. } => return NotImplemented,
|
||||||
},
|
},
|
||||||
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
}
|
||||||
|
}
|
||||||
NumPow => return NotImplemented,
|
NumPow => return NotImplemented,
|
||||||
NumCeiling => match ret_layout.value_type() {
|
NumCeiling => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => {
|
I32 => {
|
||||||
code_builder.f32_ceil();
|
code_builder.f32_ceil();
|
||||||
code_builder.i32_trunc_s_f32()
|
code_builder.i32_trunc_s_f32()
|
||||||
|
@ -242,8 +327,11 @@ pub fn decode_low_level<'a>(
|
||||||
}
|
}
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
NumPowInt => return NotImplemented,
|
NumPowInt => return NotImplemented,
|
||||||
NumFloor => match ret_layout.value_type() {
|
NumFloor => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => {
|
I32 => {
|
||||||
code_builder.f32_floor();
|
code_builder.f32_floor();
|
||||||
code_builder.i32_trunc_s_f32()
|
code_builder.i32_trunc_s_f32()
|
||||||
|
@ -254,7 +342,10 @@ pub fn decode_low_level<'a>(
|
||||||
}
|
}
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
NumIsFinite => match ret_layout.value_type() {
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumIsFinite => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_const(1),
|
I32 => code_builder.i32_const(1),
|
||||||
I64 => code_builder.i32_const(1),
|
I64 => code_builder.i32_const(1),
|
||||||
F32 => {
|
F32 => {
|
||||||
|
@ -272,6 +363,8 @@ pub fn decode_low_level<'a>(
|
||||||
code_builder.i64_ne();
|
code_builder.i64_ne();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
NumAtan => {
|
NumAtan => {
|
||||||
let width = float_width_from_layout(ret_layout);
|
let width = float_width_from_layout(ret_layout);
|
||||||
return BuiltinCall(&bitcode::NUM_ATAN[width]);
|
return BuiltinCall(&bitcode::NUM_ATAN[width]);
|
||||||
|
@ -286,41 +379,65 @@ pub fn decode_low_level<'a>(
|
||||||
}
|
}
|
||||||
NumBytesToU16 => return NotImplemented,
|
NumBytesToU16 => return NotImplemented,
|
||||||
NumBytesToU32 => return NotImplemented,
|
NumBytesToU32 => return NotImplemented,
|
||||||
NumBitwiseAnd => match ret_layout.value_type() {
|
NumBitwiseAnd => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_and(),
|
I32 => code_builder.i32_and(),
|
||||||
I64 => code_builder.i64_and(),
|
I64 => code_builder.i64_and(),
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
NumBitwiseXor => match ret_layout.value_type() {
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumBitwiseXor => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_xor(),
|
I32 => code_builder.i32_xor(),
|
||||||
I64 => code_builder.i64_xor(),
|
I64 => code_builder.i64_xor(),
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
NumBitwiseOr => match ret_layout.value_type() {
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumBitwiseOr => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_or(),
|
I32 => code_builder.i32_or(),
|
||||||
I64 => code_builder.i64_or(),
|
I64 => code_builder.i64_or(),
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
NumShiftLeftBy => {
|
NumShiftLeftBy => {
|
||||||
// Unfortunate local.set/local.get
|
// Swap order of arguments
|
||||||
storage.load_symbols(code_builder, &[args[1], args[0]]);
|
storage.load_symbols(code_builder, &[args[1], args[0]]);
|
||||||
match ret_layout.value_type() {
|
match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_shl(),
|
I32 => code_builder.i32_shl(),
|
||||||
I64 => code_builder.i64_shl(),
|
I64 => code_builder.i64_shl(),
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
|
},
|
||||||
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
NumShiftRightBy => match ret_layout.value_type() {
|
NumShiftRightBy => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_shr_s(),
|
I32 => code_builder.i32_shr_s(),
|
||||||
I64 => code_builder.i64_shr_s(),
|
I64 => code_builder.i64_shr_s(),
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
NumShiftRightZfBy => match ret_layout.value_type() {
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumShiftRightZfBy => match ret_layout {
|
||||||
|
WasmLayout::Primitive(value_type, _) => match value_type {
|
||||||
I32 => code_builder.i32_shr_u(),
|
I32 => code_builder.i32_shr_u(),
|
||||||
I64 => code_builder.i64_shr_u(),
|
I64 => code_builder.i64_shr_u(),
|
||||||
_ => panic_ret_type(),
|
_ => panic_ret_type(),
|
||||||
},
|
},
|
||||||
NumIntCast => match (ret_layout.value_type(), storage.get(&args[0]).value_type()) {
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
|
NumIntCast => {
|
||||||
|
use StoredValue::*;
|
||||||
|
let stored = storage.get(&args[0]);
|
||||||
|
match ret_layout {
|
||||||
|
WasmLayout::Primitive(ret_type, _) => match stored {
|
||||||
|
VirtualMachineStack { value_type, .. } | Local { value_type, .. } => {
|
||||||
|
match (ret_type, value_type) {
|
||||||
(I32, I32) => {}
|
(I32, I32) => {}
|
||||||
(I32, I64) => code_builder.i32_wrap_i64(),
|
(I32, I64) => code_builder.i32_wrap_i64(),
|
||||||
(I32, F32) => code_builder.i32_trunc_s_f32(),
|
(I32, F32) => code_builder.i32_trunc_s_f32(),
|
||||||
|
@ -340,30 +457,45 @@ pub fn decode_low_level<'a>(
|
||||||
(F64, I64) => code_builder.f64_convert_s_i64(),
|
(F64, I64) => code_builder.f64_convert_s_i64(),
|
||||||
(F64, F32) => code_builder.f64_promote_f32(),
|
(F64, F32) => code_builder.f64_promote_f32(),
|
||||||
(F64, F64) => {}
|
(F64, F64) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
StackMemory { .. } => return NotImplemented,
|
||||||
},
|
},
|
||||||
Eq => {
|
WasmLayout::StackMemory { .. } => return NotImplemented,
|
||||||
// TODO: For non-number types, this will implement pointer equality, which is wrong
|
}
|
||||||
match storage.get(&args[0]).value_type() {
|
}
|
||||||
|
Eq => match storage.get(&args[0]) {
|
||||||
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_eq(),
|
I32 => code_builder.i32_eq(),
|
||||||
I64 => code_builder.i64_eq(),
|
I64 => code_builder.i64_eq(),
|
||||||
F32 => code_builder.f32_eq(),
|
F32 => code_builder.f32_eq(),
|
||||||
F64 => code_builder.f64_eq(),
|
F64 => code_builder.f64_eq(),
|
||||||
}
|
},
|
||||||
}
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
NotEq => {
|
},
|
||||||
// TODO: For non-number types, this will implement pointer inequality, which is wrong
|
NotEq => match storage.get(&args[0]) {
|
||||||
match storage.get(&args[0]).value_type() {
|
StoredValue::VirtualMachineStack { value_type, .. }
|
||||||
|
| StoredValue::Local { value_type, .. } => match value_type {
|
||||||
I32 => code_builder.i32_ne(),
|
I32 => code_builder.i32_ne(),
|
||||||
I64 => code_builder.i64_ne(),
|
I64 => code_builder.i64_ne(),
|
||||||
F32 => code_builder.f32_ne(),
|
F32 => code_builder.f32_ne(),
|
||||||
F64 => code_builder.f64_ne(),
|
F64 => code_builder.f64_ne(),
|
||||||
}
|
},
|
||||||
}
|
StoredValue::StackMemory { .. } => return NotImplemented,
|
||||||
|
},
|
||||||
And => code_builder.i32_and(),
|
And => code_builder.i32_and(),
|
||||||
Or => code_builder.i32_or(),
|
Or => code_builder.i32_or(),
|
||||||
Not => code_builder.i32_eqz(),
|
Not => code_builder.i32_eqz(),
|
||||||
Hash => return NotImplemented,
|
Hash => return NotImplemented,
|
||||||
ExpectTrue => return NotImplemented,
|
ExpectTrue => return NotImplemented,
|
||||||
|
RefCountGetPtr => {
|
||||||
|
code_builder.i32_const(4);
|
||||||
|
code_builder.i32_sub();
|
||||||
|
}
|
||||||
|
RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF),
|
||||||
|
RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF),
|
||||||
}
|
}
|
||||||
Done
|
Done
|
||||||
}
|
}
|
||||||
|
@ -390,9 +522,9 @@ fn wrap_i32(code_builder: &mut CodeBuilder, size: u32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth {
|
fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth {
|
||||||
if wasm_layout.value_type() == ValueType::F32 {
|
match wasm_layout {
|
||||||
FloatWidth::F32
|
WasmLayout::Primitive(F32, _) => FloatWidth::F32,
|
||||||
} else {
|
WasmLayout::Primitive(F64, _) => FloatWidth::F64,
|
||||||
FloatWidth::F64
|
_ => panic!("{:?} does not have a FloatWidth", wasm_layout),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,11 @@ use bumpalo::Bump;
|
||||||
use roc_collections::all::MutMap;
|
use roc_collections::all::MutMap;
|
||||||
use roc_module::symbol::Symbol;
|
use roc_module::symbol::Symbol;
|
||||||
|
|
||||||
use crate::layout::{StackMemoryFormat, WasmLayout};
|
use crate::layout::{
|
||||||
|
CallConv, ReturnMethod, StackMemoryFormat, WasmLayout, ZigVersion, BUILTINS_ZIG_VERSION,
|
||||||
|
};
|
||||||
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState};
|
||||||
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_SIZE, PTR_TYPE};
|
use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_TYPE};
|
||||||
|
|
||||||
pub enum StoredValueKind {
|
pub enum StoredValueKind {
|
||||||
Parameter,
|
Parameter,
|
||||||
|
@ -55,17 +57,29 @@ pub enum StoredValue {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StoredValue {
|
impl StoredValue {
|
||||||
pub fn value_type(&self) -> ValueType {
|
/// Value types to pass to Wasm functions
|
||||||
|
/// One Roc value can become 0, 1, or 2 Wasm arguments
|
||||||
|
pub fn arg_types(&self, conv: CallConv) -> &'static [ValueType] {
|
||||||
|
use ValueType::*;
|
||||||
match self {
|
match self {
|
||||||
Self::VirtualMachineStack { value_type, .. } => *value_type,
|
// Simple numbers: 1 Roc argument => 1 Wasm argument
|
||||||
Self::Local { value_type, .. } => *value_type,
|
Self::VirtualMachineStack { value_type, .. } | Self::Local { value_type, .. } => {
|
||||||
Self::StackMemory { .. } => ValueType::I32,
|
match value_type {
|
||||||
|
I32 => &[I32],
|
||||||
|
I64 => &[I64],
|
||||||
|
F32 => &[F32],
|
||||||
|
F64 => &[F64],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Stack memory values: 1 Roc argument => 0-2 Wasm arguments
|
||||||
|
Self::StackMemory { size, format, .. } => conv.stack_memory_arg_types(*size, *format),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper structure for WasmBackend, to keep track of how values are stored,
|
/// Helper structure for WasmBackend, to keep track of how values are stored,
|
||||||
/// including the VM stack, local variables, and linear memory
|
/// including the VM stack, local variables, and linear memory
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Storage<'a> {
|
pub struct Storage<'a> {
|
||||||
pub arg_types: Vec<'a, ValueType>,
|
pub arg_types: Vec<'a, ValueType>,
|
||||||
pub local_types: Vec<'a, ValueType>,
|
pub local_types: Vec<'a, ValueType>,
|
||||||
|
@ -133,18 +147,6 @@ impl<'a> Storage<'a> {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
WasmLayout::HeapMemory => {
|
|
||||||
match kind {
|
|
||||||
StoredValueKind::Parameter => self.arg_types.push(PTR_TYPE),
|
|
||||||
_ => self.local_types.push(PTR_TYPE),
|
|
||||||
}
|
|
||||||
StoredValue::Local {
|
|
||||||
local_id: next_local_id,
|
|
||||||
value_type: PTR_TYPE,
|
|
||||||
size: PTR_SIZE,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WasmLayout::StackMemory {
|
WasmLayout::StackMemory {
|
||||||
size,
|
size,
|
||||||
alignment_bytes,
|
alignment_bytes,
|
||||||
|
@ -152,12 +154,18 @@ impl<'a> Storage<'a> {
|
||||||
} => {
|
} => {
|
||||||
let location = match kind {
|
let location = match kind {
|
||||||
StoredValueKind::Parameter => {
|
StoredValueKind::Parameter => {
|
||||||
|
if *size > 0 {
|
||||||
self.arg_types.push(PTR_TYPE);
|
self.arg_types.push(PTR_TYPE);
|
||||||
StackMemoryLocation::PointerArg(next_local_id)
|
StackMemoryLocation::PointerArg(next_local_id)
|
||||||
|
} else {
|
||||||
|
// An argument with zero size is purely conceptual, and will not exist in Wasm.
|
||||||
|
// However we need to track the symbol, so we treat it like a local variable.
|
||||||
|
StackMemoryLocation::FrameOffset(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StoredValueKind::Variable => {
|
StoredValueKind::Variable => {
|
||||||
if self.stack_frame_pointer.is_none() {
|
if self.stack_frame_pointer.is_none() && *size > 0 {
|
||||||
self.stack_frame_pointer = Some(next_local_id);
|
self.stack_frame_pointer = Some(next_local_id);
|
||||||
self.local_types.push(PTR_TYPE);
|
self.local_types.push(PTR_TYPE);
|
||||||
}
|
}
|
||||||
|
@ -243,13 +251,20 @@ impl<'a> Storage<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
StoredValue::StackMemory {
|
StoredValue::StackMemory {
|
||||||
location, format, ..
|
location,
|
||||||
|
format,
|
||||||
|
size,
|
||||||
|
..
|
||||||
} => {
|
} => {
|
||||||
|
if size == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||||
|
|
||||||
code_builder.get_local(local_id);
|
code_builder.get_local(local_id);
|
||||||
|
|
||||||
if format == StackMemoryFormat::Aggregate {
|
if format == StackMemoryFormat::DataStructure {
|
||||||
if offset != 0 {
|
if offset != 0 {
|
||||||
code_builder.i32_const(offset as i32);
|
code_builder.i32_const(offset as i32);
|
||||||
code_builder.i32_add();
|
code_builder.i32_add();
|
||||||
|
@ -269,6 +284,44 @@ impl<'a> Storage<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn load_symbol_zig(&mut self, code_builder: &mut CodeBuilder, arg: Symbol) {
|
||||||
|
if let StoredValue::StackMemory {
|
||||||
|
location,
|
||||||
|
size,
|
||||||
|
alignment_bytes,
|
||||||
|
format: StackMemoryFormat::DataStructure,
|
||||||
|
} = self.get(&arg)
|
||||||
|
{
|
||||||
|
if *size == 0 {
|
||||||
|
// do nothing
|
||||||
|
} else if *size > 16 {
|
||||||
|
self.load_symbol_ccc(code_builder, arg);
|
||||||
|
} else {
|
||||||
|
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||||
|
code_builder.get_local(local_id);
|
||||||
|
let align = Align::from(*alignment_bytes);
|
||||||
|
|
||||||
|
if *size == 1 {
|
||||||
|
code_builder.i32_load8_u(align, offset);
|
||||||
|
} else if *size == 2 {
|
||||||
|
code_builder.i32_load16_u(align, offset);
|
||||||
|
} else if *size <= 4 {
|
||||||
|
code_builder.i32_load(align, offset);
|
||||||
|
} else if *size <= 8 {
|
||||||
|
code_builder.i64_load(align, offset);
|
||||||
|
} else if *size <= 12 && BUILTINS_ZIG_VERSION == ZigVersion::Zig9 {
|
||||||
|
code_builder.i64_load(align, offset);
|
||||||
|
code_builder.i32_load(align, offset + 8);
|
||||||
|
} else {
|
||||||
|
code_builder.i64_load(align, offset);
|
||||||
|
code_builder.i64_load(align, offset + 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.load_symbol_ccc(code_builder, arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// stack memory values are returned by pointer. e.g. a roc function
|
/// stack memory values are returned by pointer. e.g. a roc function
|
||||||
///
|
///
|
||||||
/// add : I128, I128 -> I128
|
/// add : I128, I128 -> I128
|
||||||
|
@ -284,7 +337,11 @@ impl<'a> Storage<'a> {
|
||||||
StoredValue::VirtualMachineStack { .. } | StoredValue::Local { .. } => {
|
StoredValue::VirtualMachineStack { .. } | StoredValue::Local { .. } => {
|
||||||
unreachable!("these storage types are not returned by writing to a pointer")
|
unreachable!("these storage types are not returned by writing to a pointer")
|
||||||
}
|
}
|
||||||
StoredValue::StackMemory { location, .. } => {
|
StoredValue::StackMemory { location, size, .. } => {
|
||||||
|
if size == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
||||||
|
|
||||||
code_builder.get_local(local_id);
|
code_builder.get_local(local_id);
|
||||||
|
@ -311,58 +368,58 @@ impl<'a> Storage<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Load symbols in a way compatible with LLVM's "fast calling convention"
|
/// Load symbols for a function call
|
||||||
/// A bug in Zig means it always uses this for Wasm even when we specify C calling convention.
|
pub fn load_symbols_for_call(
|
||||||
/// It squashes small structs into primitive values where possible, avoiding stack memory
|
|
||||||
/// in favour of CPU registers (or VM stack values, which eventually become CPU registers).
|
|
||||||
/// We need to convert some of our structs from our internal C-like representation to work with Zig.
|
|
||||||
/// We are sticking to C ABI for better compatibility on the platform side.
|
|
||||||
pub fn load_symbols_fastcc(
|
|
||||||
&mut self,
|
&mut self,
|
||||||
|
arena: &'a Bump,
|
||||||
code_builder: &mut CodeBuilder,
|
code_builder: &mut CodeBuilder,
|
||||||
symbols: &[Symbol],
|
arguments: &[Symbol],
|
||||||
return_symbol: Symbol,
|
return_symbol: Symbol,
|
||||||
return_layout: &WasmLayout,
|
return_layout: &WasmLayout,
|
||||||
) {
|
call_conv: CallConv,
|
||||||
// Note: we are not doing verify_stack_match in this case so we may generate more code.
|
) -> (Vec<'a, ValueType>, Option<ValueType>) {
|
||||||
// We would need more bookkeeping in CodeBuilder to track which representation is on the stack!
|
let mut wasm_arg_types = Vec::with_capacity_in(arguments.len() * 2 + 1, arena);
|
||||||
|
let mut wasm_args = Vec::with_capacity_in(arguments.len() * 2 + 1, arena);
|
||||||
|
|
||||||
if return_layout.is_stack_memory() {
|
let return_method = return_layout.return_method();
|
||||||
// Load the address where the return value should be written
|
let return_type = match return_method {
|
||||||
|
ReturnMethod::Primitive(ty) => Some(ty),
|
||||||
|
ReturnMethod::NoReturnValue => None,
|
||||||
|
ReturnMethod::WriteToPointerArg => {
|
||||||
|
wasm_arg_types.push(PTR_TYPE);
|
||||||
|
wasm_args.push(return_symbol);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for arg in arguments {
|
||||||
|
let stored = self.symbol_storage_map.get(arg).unwrap();
|
||||||
|
let arg_types = stored.arg_types(call_conv);
|
||||||
|
wasm_arg_types.extend_from_slice(arg_types);
|
||||||
|
match arg_types.len() {
|
||||||
|
0 => {}
|
||||||
|
1 => wasm_args.push(*arg),
|
||||||
|
2 => wasm_args.extend_from_slice(&[*arg, *arg]),
|
||||||
|
n => unreachable!("Cannot have {} Wasm arguments for 1 Roc argument", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the symbols were already at the top of the stack, do nothing!
|
||||||
|
// Should be common for simple cases, due to the structure of the Mono IR
|
||||||
|
if !code_builder.verify_stack_match(&wasm_args) {
|
||||||
|
if return_method == ReturnMethod::WriteToPointerArg {
|
||||||
self.load_return_address_ccc(code_builder, return_symbol);
|
self.load_return_address_ccc(code_builder, return_symbol);
|
||||||
|
};
|
||||||
|
|
||||||
|
for arg in arguments {
|
||||||
|
match call_conv {
|
||||||
|
CallConv::C => self.load_symbol_ccc(code_builder, *arg),
|
||||||
|
CallConv::Zig => self.load_symbol_zig(code_builder, *arg),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for sym in symbols {
|
(wasm_arg_types, return_type)
|
||||||
if let StoredValue::StackMemory {
|
|
||||||
location,
|
|
||||||
size,
|
|
||||||
alignment_bytes,
|
|
||||||
format: StackMemoryFormat::Aggregate,
|
|
||||||
} = self.get(sym)
|
|
||||||
{
|
|
||||||
if *size == 0 {
|
|
||||||
unimplemented!("Passing zero-sized values is not implemented yet");
|
|
||||||
} else if *size > 8 {
|
|
||||||
return self.load_symbol_ccc(code_builder, *sym);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
|
|
||||||
code_builder.get_local(local_id);
|
|
||||||
let align = Align::from(*alignment_bytes);
|
|
||||||
|
|
||||||
if *size == 1 {
|
|
||||||
code_builder.i32_load8_u(align, offset);
|
|
||||||
} else if *size == 2 {
|
|
||||||
code_builder.i32_load16_u(align, offset);
|
|
||||||
} else if *size <= 4 {
|
|
||||||
code_builder.i32_load(align, offset);
|
|
||||||
} else {
|
|
||||||
code_builder.i64_load(align, offset);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.load_symbol_ccc(code_builder, *sym);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate code to copy a StoredValue to an arbitrary memory location
|
/// Generate code to copy a StoredValue to an arbitrary memory location
|
||||||
|
|
|
@ -50,6 +50,15 @@ impl BlockType {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<Option<ValueType>> for BlockType {
|
||||||
|
fn from(opt: Option<ValueType>) -> Self {
|
||||||
|
match opt {
|
||||||
|
Some(ty) => BlockType::Value(ty),
|
||||||
|
None => BlockType::NoResult,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A control block in our model of the VM
|
/// A control block in our model of the VM
|
||||||
/// Child blocks cannot "see" values from their parent block
|
/// Child blocks cannot "see" values from their parent block
|
||||||
struct VmBlock<'a> {
|
struct VmBlock<'a> {
|
||||||
|
@ -225,7 +234,9 @@ impl<'a> CodeBuilder<'a> {
|
||||||
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
|
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
|
||||||
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
|
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
|
||||||
let pushed_at = self.code.len();
|
let pushed_at = self.code.len();
|
||||||
let top_symbol: &mut Symbol = current_stack.last_mut().unwrap();
|
let top_symbol: &mut Symbol = current_stack
|
||||||
|
.last_mut()
|
||||||
|
.unwrap_or_else(|| unreachable!("Empty stack when trying to set Symbol {:?}", sym));
|
||||||
*top_symbol = sym;
|
*top_symbol = sym;
|
||||||
|
|
||||||
VmSymbolState::Pushed { pushed_at }
|
VmSymbolState::Pushed { pushed_at }
|
||||||
|
@ -429,11 +440,13 @@ impl<'a> CodeBuilder<'a> {
|
||||||
) {
|
) {
|
||||||
self.build_local_declarations(local_types);
|
self.build_local_declarations(local_types);
|
||||||
|
|
||||||
|
if frame_size != 0 {
|
||||||
if let Some(frame_ptr_id) = frame_pointer {
|
if let Some(frame_ptr_id) = frame_pointer {
|
||||||
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES);
|
let aligned_size = round_up_to_alignment(frame_size, FRAME_ALIGNMENT_BYTES);
|
||||||
self.build_stack_frame_push(aligned_size, frame_ptr_id);
|
self.build_stack_frame_push(aligned_size, frame_ptr_id);
|
||||||
self.build_stack_frame_pop(aligned_size, frame_ptr_id);
|
self.build_stack_frame_pop(aligned_size, frame_ptr_id);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.code.push(END as u8);
|
self.code.push(END as u8);
|
||||||
|
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
if [[ -z "$1" || -z "$2" ]]
|
|
||||||
then
|
|
||||||
echo "$0 needs 2 arguments: the directories to compare"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
||||||
OVERHEAD_BYTES=114 # total file size minus generated code size (test wrapper + module headers)
|
|
||||||
|
|
||||||
|
|
||||||
printf "filename \tLHS\tRHS\tchange\n"
|
|
||||||
printf "======== \t===\t===\t======\n"
|
|
||||||
|
|
||||||
for f in `ls $1/wasm`
|
|
||||||
do
|
|
||||||
if [[ ! -f "$2/wasm/$f" ]]
|
|
||||||
then
|
|
||||||
echo "$f found in $1/wasm but not in $2/wasm"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
SIZE1=$(stat --format '%s' "$1/wasm/$f")
|
|
||||||
SIZE2=$(stat --format '%s' "$2/wasm/$f")
|
|
||||||
CHANGE=$(( $SIZE2 - $SIZE1 ))
|
|
||||||
NET_SIZE1=$(( $SIZE1 - $OVERHEAD_BYTES ))
|
|
||||||
NET_SIZE2=$(( $SIZE2 - $OVERHEAD_BYTES ))
|
|
||||||
PERCENT_CHANGE=$(( $CHANGE * 100 / $NET_SIZE1 ))
|
|
||||||
printf "%s\t%d\t%d\t%d\t%d%%\n" $f $NET_SIZE1 $NET_SIZE2 $CHANGE $PERCENT_CHANGE
|
|
||||||
done
|
|
|
@ -1,24 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
TARGET_DIR=$1
|
|
||||||
|
|
||||||
if [[ -z "$TARGET_DIR" ]]
|
|
||||||
then
|
|
||||||
echo "$0 needs an argument: target directory for output wasm and wat files"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
rm -rf output $TARGET_DIR
|
|
||||||
mkdir -p output $TARGET_DIR $TARGET_DIR/wasm $TARGET_DIR/wat
|
|
||||||
cargo test -- --test-threads=1 --nocapture
|
|
||||||
|
|
||||||
mv output/* $TARGET_DIR/wasm
|
|
||||||
|
|
||||||
for f in `ls $TARGET_DIR/wasm`
|
|
||||||
do
|
|
||||||
wasm2wat $TARGET_DIR/wasm/$f -o $TARGET_DIR/wat/${f%.wasm}.wat
|
|
||||||
done
|
|
||||||
|
|
||||||
|
|
||||||
SIZE=$(du -b "$TARGET_DIR/wasm")
|
|
||||||
echo "Total bytes *.wasm = $SIZE"
|
|
|
@ -20,6 +20,7 @@ use roc_module::symbol::{
|
||||||
};
|
};
|
||||||
use roc_mono::ir::{
|
use roc_mono::ir::{
|
||||||
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
|
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
|
||||||
|
UpdateModeIds,
|
||||||
};
|
};
|
||||||
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
|
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
|
||||||
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
|
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
|
||||||
|
@ -835,6 +836,7 @@ enum Msg<'a> {
|
||||||
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations>,
|
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations>,
|
||||||
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
problems: Vec<roc_mono::ir::MonoProblem>,
|
problems: Vec<roc_mono::ir::MonoProblem>,
|
||||||
|
update_mode_ids: UpdateModeIds,
|
||||||
module_timing: ModuleTiming,
|
module_timing: ModuleTiming,
|
||||||
subs: Subs,
|
subs: Subs,
|
||||||
},
|
},
|
||||||
|
@ -2098,6 +2100,7 @@ fn update<'a>(
|
||||||
MadeSpecializations {
|
MadeSpecializations {
|
||||||
module_id,
|
module_id,
|
||||||
mut ident_ids,
|
mut ident_ids,
|
||||||
|
mut update_mode_ids,
|
||||||
subs,
|
subs,
|
||||||
procedures,
|
procedures,
|
||||||
external_specializations_requested,
|
external_specializations_requested,
|
||||||
|
@ -2124,6 +2127,7 @@ fn update<'a>(
|
||||||
arena,
|
arena,
|
||||||
module_id,
|
module_id,
|
||||||
&mut ident_ids,
|
&mut ident_ids,
|
||||||
|
&mut update_mode_ids,
|
||||||
&mut state.procedures,
|
&mut state.procedures,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -3922,6 +3926,7 @@ fn make_specializations<'a>(
|
||||||
) -> Msg<'a> {
|
) -> Msg<'a> {
|
||||||
let make_specializations_start = SystemTime::now();
|
let make_specializations_start = SystemTime::now();
|
||||||
let mut mono_problems = Vec::new();
|
let mut mono_problems = Vec::new();
|
||||||
|
let mut update_mode_ids = UpdateModeIds::new();
|
||||||
// do the thing
|
// do the thing
|
||||||
let mut mono_env = roc_mono::ir::Env {
|
let mut mono_env = roc_mono::ir::Env {
|
||||||
arena,
|
arena,
|
||||||
|
@ -3930,7 +3935,7 @@ fn make_specializations<'a>(
|
||||||
home,
|
home,
|
||||||
ident_ids: &mut ident_ids,
|
ident_ids: &mut ident_ids,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
update_mode_counter: 0,
|
update_mode_ids: &mut update_mode_ids,
|
||||||
// call_specialization_counter=0 is reserved
|
// call_specialization_counter=0 is reserved
|
||||||
call_specialization_counter: 1,
|
call_specialization_counter: 1,
|
||||||
};
|
};
|
||||||
|
@ -3973,6 +3978,7 @@ fn make_specializations<'a>(
|
||||||
layout_cache,
|
layout_cache,
|
||||||
procedures,
|
procedures,
|
||||||
problems: mono_problems,
|
problems: mono_problems,
|
||||||
|
update_mode_ids,
|
||||||
subs,
|
subs,
|
||||||
external_specializations_requested,
|
external_specializations_requested,
|
||||||
module_timing,
|
module_timing,
|
||||||
|
@ -4016,6 +4022,7 @@ fn build_pending_specializations<'a>(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut mono_problems = std::vec::Vec::new();
|
let mut mono_problems = std::vec::Vec::new();
|
||||||
|
let mut update_mode_ids = UpdateModeIds::new();
|
||||||
let mut subs = solved_subs.into_inner();
|
let mut subs = solved_subs.into_inner();
|
||||||
let mut mono_env = roc_mono::ir::Env {
|
let mut mono_env = roc_mono::ir::Env {
|
||||||
arena,
|
arena,
|
||||||
|
@ -4024,7 +4031,7 @@ fn build_pending_specializations<'a>(
|
||||||
home,
|
home,
|
||||||
ident_ids: &mut ident_ids,
|
ident_ids: &mut ident_ids,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
update_mode_counter: 0,
|
update_mode_ids: &mut update_mode_ids,
|
||||||
// call_specialization_counter=0 is reserved
|
// call_specialization_counter=0 is reserved
|
||||||
call_specialization_counter: 1,
|
call_specialization_counter: 1,
|
||||||
};
|
};
|
||||||
|
|
|
@ -108,6 +108,7 @@ pub enum LowLevel {
|
||||||
NumShiftRightBy,
|
NumShiftRightBy,
|
||||||
NumShiftRightZfBy,
|
NumShiftRightZfBy,
|
||||||
NumIntCast,
|
NumIntCast,
|
||||||
|
NumToStr,
|
||||||
Eq,
|
Eq,
|
||||||
NotEq,
|
NotEq,
|
||||||
And,
|
And,
|
||||||
|
@ -115,6 +116,9 @@ pub enum LowLevel {
|
||||||
Not,
|
Not,
|
||||||
Hash,
|
Hash,
|
||||||
ExpectTrue,
|
ExpectTrue,
|
||||||
|
RefCountGetPtr,
|
||||||
|
RefCountInc,
|
||||||
|
RefCountDec,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! higher_order {
|
macro_rules! higher_order {
|
||||||
|
@ -186,12 +190,10 @@ impl LowLevel {
|
||||||
Symbol::STR_ENDS_WITH => Some(StrEndsWith),
|
Symbol::STR_ENDS_WITH => Some(StrEndsWith),
|
||||||
Symbol::STR_SPLIT => Some(StrSplit),
|
Symbol::STR_SPLIT => Some(StrSplit),
|
||||||
Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes),
|
Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes),
|
||||||
Symbol::STR_FROM_INT => Some(StrFromInt),
|
|
||||||
Symbol::STR_FROM_UTF8 => None,
|
Symbol::STR_FROM_UTF8 => None,
|
||||||
Symbol::STR_FROM_UTF8_RANGE => None,
|
Symbol::STR_FROM_UTF8_RANGE => None,
|
||||||
Symbol::STR_TO_UTF8 => Some(StrToUtf8),
|
Symbol::STR_TO_UTF8 => Some(StrToUtf8),
|
||||||
Symbol::STR_REPEAT => Some(StrRepeat),
|
Symbol::STR_REPEAT => Some(StrRepeat),
|
||||||
Symbol::STR_FROM_FLOAT => Some(StrFromFloat),
|
|
||||||
Symbol::STR_TRIM => Some(StrTrim),
|
Symbol::STR_TRIM => Some(StrTrim),
|
||||||
Symbol::STR_TRIM_LEFT => Some(StrTrimLeft),
|
Symbol::STR_TRIM_LEFT => Some(StrTrimLeft),
|
||||||
Symbol::STR_TRIM_RIGHT => Some(StrTrimRight),
|
Symbol::STR_TRIM_RIGHT => Some(StrTrimRight),
|
||||||
|
@ -268,6 +270,7 @@ impl LowLevel {
|
||||||
Symbol::NUM_CEILING => Some(NumCeiling),
|
Symbol::NUM_CEILING => Some(NumCeiling),
|
||||||
Symbol::NUM_POW_INT => Some(NumPowInt),
|
Symbol::NUM_POW_INT => Some(NumPowInt),
|
||||||
Symbol::NUM_FLOOR => Some(NumFloor),
|
Symbol::NUM_FLOOR => Some(NumFloor),
|
||||||
|
Symbol::NUM_TO_STR => Some(NumToStr),
|
||||||
// => Some(NumIsFinite),
|
// => Some(NumIsFinite),
|
||||||
Symbol::NUM_ATAN => Some(NumAtan),
|
Symbol::NUM_ATAN => Some(NumAtan),
|
||||||
Symbol::NUM_ACOS => Some(NumAcos),
|
Symbol::NUM_ACOS => Some(NumAcos),
|
||||||
|
|
|
@ -992,6 +992,7 @@ define_builtins! {
|
||||||
104 NUM_BYTES_TO_U32: "bytesToU32"
|
104 NUM_BYTES_TO_U32: "bytesToU32"
|
||||||
105 NUM_CAST_TO_NAT: "#castToNat"
|
105 NUM_CAST_TO_NAT: "#castToNat"
|
||||||
106 NUM_DIV_CEIL: "divCeil"
|
106 NUM_DIV_CEIL: "divCeil"
|
||||||
|
107 NUM_TO_STR: "toStr"
|
||||||
}
|
}
|
||||||
2 BOOL: "Bool" => {
|
2 BOOL: "Bool" => {
|
||||||
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
|
||||||
|
@ -1017,19 +1018,17 @@ define_builtins! {
|
||||||
7 STR_COUNT_GRAPHEMES: "countGraphemes"
|
7 STR_COUNT_GRAPHEMES: "countGraphemes"
|
||||||
8 STR_STARTS_WITH: "startsWith"
|
8 STR_STARTS_WITH: "startsWith"
|
||||||
9 STR_ENDS_WITH: "endsWith"
|
9 STR_ENDS_WITH: "endsWith"
|
||||||
10 STR_FROM_INT: "fromInt"
|
10 STR_FROM_UTF8: "fromUtf8"
|
||||||
11 STR_FROM_FLOAT: "fromFloat"
|
11 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
|
||||||
12 STR_FROM_UTF8: "fromUtf8"
|
12 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
|
||||||
13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
|
13 STR_TO_UTF8: "toUtf8"
|
||||||
14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
|
14 STR_STARTS_WITH_CODE_PT: "startsWithCodePt"
|
||||||
15 STR_TO_UTF8: "toUtf8"
|
15 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
|
||||||
16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt"
|
16 STR_FROM_UTF8_RANGE: "fromUtf8Range"
|
||||||
17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
|
17 STR_REPEAT: "repeat"
|
||||||
18 STR_FROM_UTF8_RANGE: "fromUtf8Range"
|
18 STR_TRIM: "trim"
|
||||||
19 STR_REPEAT: "repeat"
|
19 STR_TRIM_LEFT: "trimLeft"
|
||||||
20 STR_TRIM: "trim"
|
20 STR_TRIM_RIGHT: "trimRight"
|
||||||
21 STR_TRIM_LEFT: "trimLeft"
|
|
||||||
22 STR_TRIM_RIGHT: "trimRight"
|
|
||||||
}
|
}
|
||||||
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
|
||||||
|
|
|
@ -210,12 +210,15 @@ where
|
||||||
|
|
||||||
let mut builder = TypeDefBuilder::new();
|
let mut builder = TypeDefBuilder::new();
|
||||||
|
|
||||||
let variant_types = build_variant_types(&mut builder, &union_layout)?;
|
let variant_types = recursive_variant_types(&mut builder, &union_layout)?;
|
||||||
let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout {
|
let root_type = if let UnionLayout::NonNullableUnwrapped(_) = union_layout {
|
||||||
debug_assert_eq!(variant_types.len(), 1);
|
debug_assert_eq!(variant_types.len(), 1);
|
||||||
variant_types[0]
|
variant_types[0]
|
||||||
} else {
|
} else {
|
||||||
builder.add_union_type(&variant_types)?
|
let data_type = builder.add_union_type(&variant_types)?;
|
||||||
|
let cell_type = builder.add_heap_cell_type();
|
||||||
|
|
||||||
|
builder.add_tuple_type(&[cell_type, data_type])?
|
||||||
};
|
};
|
||||||
|
|
||||||
let type_def = builder.build(root_type)?;
|
let type_def = builder.build(root_type)?;
|
||||||
|
@ -699,30 +702,30 @@ fn call_spec(
|
||||||
call.arguments,
|
call.arguments,
|
||||||
),
|
),
|
||||||
HigherOrder(HigherOrderLowLevel {
|
HigherOrder(HigherOrderLowLevel {
|
||||||
specialization_id,
|
|
||||||
closure_env_layout,
|
closure_env_layout,
|
||||||
update_mode,
|
update_mode,
|
||||||
op,
|
op,
|
||||||
arg_layouts,
|
passed_function,
|
||||||
ret_layout,
|
|
||||||
function_name,
|
|
||||||
function_env,
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
use crate::low_level::HigherOrder::*;
|
use crate::low_level::HigherOrder::*;
|
||||||
|
|
||||||
let array = specialization_id.to_bytes();
|
let array = passed_function.specialization_id.to_bytes();
|
||||||
let spec_var = CalleeSpecVar(&array);
|
let spec_var = CalleeSpecVar(&array);
|
||||||
|
|
||||||
let mode = update_mode.to_bytes();
|
let mode = update_mode.to_bytes();
|
||||||
let update_mode_var = UpdateModeVar(&mode);
|
let update_mode_var = UpdateModeVar(&mode);
|
||||||
|
|
||||||
let it = arg_layouts.iter().copied();
|
let it = passed_function.argument_layouts.iter().copied();
|
||||||
let bytes = func_name_bytes_help(*function_name, it, ret_layout);
|
let bytes =
|
||||||
|
func_name_bytes_help(passed_function.name, it, &passed_function.return_layout);
|
||||||
let name = FuncName(&bytes);
|
let name = FuncName(&bytes);
|
||||||
let module = MOD_APP;
|
let module = MOD_APP;
|
||||||
|
|
||||||
let closure_env = env.symbols[function_env];
|
let closure_env = env.symbols[&passed_function.captured_environment];
|
||||||
|
|
||||||
|
let return_layout = &passed_function.return_layout;
|
||||||
|
let argument_layouts = passed_function.argument_layouts;
|
||||||
|
|
||||||
macro_rules! call_function {
|
macro_rules! call_function {
|
||||||
($builder: expr, $block:expr, [$($arg:expr),+ $(,)?]) => {{
|
($builder: expr, $block:expr, [$($arg:expr),+ $(,)?]) => {{
|
||||||
|
@ -754,7 +757,7 @@ fn call_spec(
|
||||||
Ok(new_state)
|
Ok(new_state)
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_layout = arg_layouts[0];
|
let state_layout = argument_layouts[0];
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
let init_state = state;
|
let init_state = state;
|
||||||
|
|
||||||
|
@ -775,7 +778,7 @@ fn call_spec(
|
||||||
Ok(new_state)
|
Ok(new_state)
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_layout = arg_layouts[0];
|
let state_layout = argument_layouts[0];
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
let init_state = state;
|
let init_state = state;
|
||||||
|
|
||||||
|
@ -799,7 +802,7 @@ fn call_spec(
|
||||||
Ok(new_state)
|
Ok(new_state)
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_layout = arg_layouts[0];
|
let state_layout = argument_layouts[0];
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
let init_state = state;
|
let init_state = state;
|
||||||
|
|
||||||
|
@ -820,9 +823,9 @@ fn call_spec(
|
||||||
list_append(builder, block, update_mode_var, state, new_element)
|
list_append(builder, block, update_mode_var, state, new_element)
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_element_type = layout_spec(builder, ret_layout)?;
|
let output_element_type = layout_spec(builder, return_layout)?;
|
||||||
|
|
||||||
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
|
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
|
|
||||||
let init_state = new_list(builder, block, output_element_type)?;
|
let init_state = new_list(builder, block, output_element_type)?;
|
||||||
|
@ -843,9 +846,9 @@ fn call_spec(
|
||||||
list_append(builder, block, update_mode_var, state, new_element)
|
list_append(builder, block, update_mode_var, state, new_element)
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_element_type = layout_spec(builder, ret_layout)?;
|
let output_element_type = layout_spec(builder, return_layout)?;
|
||||||
|
|
||||||
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
|
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
|
|
||||||
let init_state = new_list(builder, block, output_element_type)?;
|
let init_state = new_list(builder, block, output_element_type)?;
|
||||||
|
@ -867,11 +870,10 @@ fn call_spec(
|
||||||
|
|
||||||
builder.add_update(block, update_mode_var, cell)?;
|
builder.add_update(block, update_mode_var, cell)?;
|
||||||
|
|
||||||
let new_cell = builder.add_new_heap_cell(block)?;
|
with_new_heap_cell(builder, block, bag)
|
||||||
builder.add_make_tuple(block, &[new_cell, bag])
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0]));
|
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
let init_state = list;
|
let init_state = list;
|
||||||
|
|
||||||
|
@ -896,9 +898,9 @@ fn call_spec(
|
||||||
list_append(builder, block, update_mode_var, state, new_element)
|
list_append(builder, block, update_mode_var, state, new_element)
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_element_type = layout_spec(builder, ret_layout)?;
|
let output_element_type = layout_spec(builder, return_layout)?;
|
||||||
|
|
||||||
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
|
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
|
|
||||||
let init_state = new_list(builder, block, output_element_type)?;
|
let init_state = new_list(builder, block, output_element_type)?;
|
||||||
|
@ -929,9 +931,9 @@ fn call_spec(
|
||||||
list_append(builder, block, update_mode_var, state, new_element)
|
list_append(builder, block, update_mode_var, state, new_element)
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_element_type = layout_spec(builder, ret_layout)?;
|
let output_element_type = layout_spec(builder, return_layout)?;
|
||||||
|
|
||||||
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
|
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
|
|
||||||
let init_state = new_list(builder, block, output_element_type)?;
|
let init_state = new_list(builder, block, output_element_type)?;
|
||||||
|
@ -968,9 +970,9 @@ fn call_spec(
|
||||||
list_append(builder, block, update_mode_var, state, new_element)
|
list_append(builder, block, update_mode_var, state, new_element)
|
||||||
};
|
};
|
||||||
|
|
||||||
let output_element_type = layout_spec(builder, ret_layout)?;
|
let output_element_type = layout_spec(builder, return_layout)?;
|
||||||
|
|
||||||
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
|
let state_layout = Layout::Builtin(Builtin::List(return_layout));
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
|
|
||||||
let init_state = new_list(builder, block, output_element_type)?;
|
let init_state = new_list(builder, block, output_element_type)?;
|
||||||
|
@ -998,12 +1000,11 @@ fn call_spec(
|
||||||
builder.add_recursive_touch(block, removed_element)?;
|
builder.add_recursive_touch(block, removed_element)?;
|
||||||
|
|
||||||
let new_bag = builder.add_get_tuple_field(block, removed, 0)?;
|
let new_bag = builder.add_get_tuple_field(block, removed, 0)?;
|
||||||
let new_cell = builder.add_new_heap_cell(block)?;
|
|
||||||
|
|
||||||
builder.add_make_tuple(block, &[new_cell, new_bag])
|
with_new_heap_cell(builder, block, new_bag)
|
||||||
};
|
};
|
||||||
|
|
||||||
let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0]));
|
let state_layout = Layout::Builtin(Builtin::List(&argument_layouts[0]));
|
||||||
let state_type = layout_spec(builder, &state_layout)?;
|
let state_type = layout_spec(builder, &state_layout)?;
|
||||||
let init_state = list;
|
let init_state = list;
|
||||||
|
|
||||||
|
@ -1018,7 +1019,7 @@ fn call_spec(
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let result_repr = ResultRepr::from_layout(ret_layout);
|
let result_repr = ResultRepr::from_layout(return_layout);
|
||||||
|
|
||||||
let output_element_layout = match (keep_result, result_repr) {
|
let output_element_layout = match (keep_result, result_repr) {
|
||||||
(KeepResult::Errs, ResultRepr::ResultConcrete { err, .. }) => err,
|
(KeepResult::Errs, ResultRepr::ResultConcrete { err, .. }) => err,
|
||||||
|
@ -1131,7 +1132,7 @@ fn call_spec(
|
||||||
let list = env.symbols[xs];
|
let list = env.symbols[xs];
|
||||||
|
|
||||||
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
|
// ListFindUnsafe returns { value: v, found: Bool=Int1 }
|
||||||
let output_layouts = vec![arg_layouts[0], Layout::Builtin(Builtin::Bool)];
|
let output_layouts = vec![argument_layouts[0], Layout::Builtin(Builtin::Bool)];
|
||||||
let output_layout = Layout::Struct(&output_layouts);
|
let output_layout = Layout::Struct(&output_layouts);
|
||||||
let output_type = layout_spec(builder, &output_layout)?;
|
let output_type = layout_spec(builder, &output_layout)?;
|
||||||
|
|
||||||
|
@ -1181,8 +1182,7 @@ fn list_append(
|
||||||
|
|
||||||
let new_bag = builder.add_bag_insert(block, bag, to_insert)?;
|
let new_bag = builder.add_bag_insert(block, bag, to_insert)?;
|
||||||
|
|
||||||
let new_cell = builder.add_new_heap_cell(block)?;
|
with_new_heap_cell(builder, block, new_bag)
|
||||||
builder.add_make_tuple(block, &[new_cell, new_bag])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn lowlevel_spec(
|
fn lowlevel_spec(
|
||||||
|
@ -1268,8 +1268,7 @@ fn lowlevel_spec(
|
||||||
|
|
||||||
builder.add_bag_insert(block, bag, to_insert)?;
|
builder.add_bag_insert(block, bag, to_insert)?;
|
||||||
|
|
||||||
let new_cell = builder.add_new_heap_cell(block)?;
|
with_new_heap_cell(builder, block, bag)
|
||||||
builder.add_make_tuple(block, &[new_cell, bag])
|
|
||||||
}
|
}
|
||||||
ListSwap => {
|
ListSwap => {
|
||||||
let list = env.symbols[&arguments[0]];
|
let list = env.symbols[&arguments[0]];
|
||||||
|
@ -1279,8 +1278,7 @@ fn lowlevel_spec(
|
||||||
|
|
||||||
let _unit = builder.add_update(block, update_mode_var, cell)?;
|
let _unit = builder.add_update(block, update_mode_var, cell)?;
|
||||||
|
|
||||||
let new_cell = builder.add_new_heap_cell(block)?;
|
with_new_heap_cell(builder, block, bag)
|
||||||
builder.add_make_tuple(block, &[new_cell, bag])
|
|
||||||
}
|
}
|
||||||
ListReverse => {
|
ListReverse => {
|
||||||
let list = env.symbols[&arguments[0]];
|
let list = env.symbols[&arguments[0]];
|
||||||
|
@ -1290,8 +1288,7 @@ fn lowlevel_spec(
|
||||||
|
|
||||||
let _unit = builder.add_update(block, update_mode_var, cell)?;
|
let _unit = builder.add_update(block, update_mode_var, cell)?;
|
||||||
|
|
||||||
let new_cell = builder.add_new_heap_cell(block)?;
|
with_new_heap_cell(builder, block, bag)
|
||||||
builder.add_make_tuple(block, &[new_cell, bag])
|
|
||||||
}
|
}
|
||||||
ListAppend => {
|
ListAppend => {
|
||||||
let list = env.symbols[&arguments[0]];
|
let list = env.symbols[&arguments[0]];
|
||||||
|
@ -1359,8 +1356,7 @@ fn lowlevel_spec(
|
||||||
|
|
||||||
builder.add_bag_insert(block, bag, key_value)?;
|
builder.add_bag_insert(block, bag, key_value)?;
|
||||||
|
|
||||||
let new_cell = builder.add_new_heap_cell(block)?;
|
with_new_heap_cell(builder, block, bag)
|
||||||
builder.add_make_tuple(block, &[new_cell, bag])
|
|
||||||
}
|
}
|
||||||
_other => {
|
_other => {
|
||||||
// println!("missing {:?}", _other);
|
// println!("missing {:?}", _other);
|
||||||
|
@ -1381,13 +1377,10 @@ fn recursive_tag_variant(
|
||||||
) -> Result<TypeId> {
|
) -> Result<TypeId> {
|
||||||
let when_recursive = WhenRecursive::Loop(*union_layout);
|
let when_recursive = WhenRecursive::Loop(*union_layout);
|
||||||
|
|
||||||
let data_id = build_recursive_tuple_type(builder, fields, &when_recursive)?;
|
build_recursive_tuple_type(builder, fields, &when_recursive)
|
||||||
let cell_id = builder.add_heap_cell_type();
|
|
||||||
|
|
||||||
builder.add_tuple_type(&[cell_id, data_id])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_variant_types(
|
fn recursive_variant_types(
|
||||||
builder: &mut impl TypeContext,
|
builder: &mut impl TypeContext,
|
||||||
union_layout: &UnionLayout,
|
union_layout: &UnionLayout,
|
||||||
) -> Result<Vec<TypeId>> {
|
) -> Result<Vec<TypeId>> {
|
||||||
|
@ -1396,12 +1389,8 @@ fn build_variant_types(
|
||||||
let mut result;
|
let mut result;
|
||||||
|
|
||||||
match union_layout {
|
match union_layout {
|
||||||
NonRecursive(tags) => {
|
NonRecursive(_) => {
|
||||||
result = Vec::with_capacity(tags.len());
|
unreachable!()
|
||||||
|
|
||||||
for tag in tags.iter() {
|
|
||||||
result.push(build_tuple_type(builder, tag)?);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Recursive(tags) => {
|
Recursive(tags) => {
|
||||||
result = Vec::with_capacity(tags.len());
|
result = Vec::with_capacity(tags.len());
|
||||||
|
@ -1425,8 +1414,7 @@ fn build_variant_types(
|
||||||
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let unit = builder.add_tuple_type(&[])?;
|
result.push(recursive_tag_variant(builder, union_layout, &[])?);
|
||||||
result.push(unit);
|
|
||||||
|
|
||||||
for tag in tags[cutoff..].iter() {
|
for tag in tags[cutoff..].iter() {
|
||||||
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
result.push(recursive_tag_variant(builder, union_layout, tag)?);
|
||||||
|
@ -1436,7 +1424,7 @@ fn build_variant_types(
|
||||||
nullable_id,
|
nullable_id,
|
||||||
other_fields: fields,
|
other_fields: fields,
|
||||||
} => {
|
} => {
|
||||||
let unit = builder.add_tuple_type(&[])?;
|
let unit = recursive_tag_variant(builder, union_layout, &[])?;
|
||||||
let other_type = recursive_tag_variant(builder, union_layout, fields)?;
|
let other_type = recursive_tag_variant(builder, union_layout, fields)?;
|
||||||
|
|
||||||
if *nullable_id {
|
if *nullable_id {
|
||||||
|
@ -1482,18 +1470,16 @@ fn expr_spec<'a>(
|
||||||
tag_id,
|
tag_id,
|
||||||
arguments,
|
arguments,
|
||||||
} => {
|
} => {
|
||||||
let variant_types = build_variant_types(builder, tag_layout)?;
|
|
||||||
|
|
||||||
let data_id = build_tuple_value(builder, env, block, arguments)?;
|
let data_id = build_tuple_value(builder, env, block, arguments)?;
|
||||||
let cell_id = builder.add_new_heap_cell(block)?;
|
|
||||||
|
|
||||||
let value_id = match tag_layout {
|
let value_id = match tag_layout {
|
||||||
UnionLayout::NonRecursive(_) => {
|
UnionLayout::NonRecursive(tags) => {
|
||||||
|
let variant_types = non_recursive_variant_types(builder, tags)?;
|
||||||
let value_id = build_tuple_value(builder, env, block, arguments)?;
|
let value_id = build_tuple_value(builder, env, block, arguments)?;
|
||||||
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
|
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
|
||||||
}
|
}
|
||||||
UnionLayout::NonNullableUnwrapped(_) => {
|
UnionLayout::NonNullableUnwrapped(_) => {
|
||||||
let value_id = builder.add_make_tuple(block, &[cell_id, data_id])?;
|
let value_id = data_id;
|
||||||
|
|
||||||
let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes();
|
let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes();
|
||||||
let type_name = TypeName(&type_name_bytes);
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
@ -1502,32 +1488,24 @@ fn expr_spec<'a>(
|
||||||
|
|
||||||
return builder.add_make_named(block, MOD_APP, type_name, value_id);
|
return builder.add_make_named(block, MOD_APP, type_name, value_id);
|
||||||
}
|
}
|
||||||
UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?,
|
UnionLayout::Recursive(_) => data_id,
|
||||||
UnionLayout::NullableWrapped { nullable_id, .. } => {
|
UnionLayout::NullableWrapped { .. } => data_id,
|
||||||
if *tag_id == *nullable_id as _ {
|
UnionLayout::NullableUnwrapped { .. } => data_id,
|
||||||
data_id
|
|
||||||
} else {
|
|
||||||
builder.add_make_tuple(block, &[cell_id, data_id])?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
UnionLayout::NullableUnwrapped { nullable_id, .. } => {
|
|
||||||
if *tag_id == *nullable_id as _ {
|
|
||||||
data_id
|
|
||||||
} else {
|
|
||||||
builder.add_make_tuple(block, &[cell_id, data_id])?
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let variant_types = recursive_variant_types(builder, tag_layout)?;
|
||||||
|
|
||||||
let union_id =
|
let union_id =
|
||||||
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?;
|
builder.add_make_union(block, &variant_types, *tag_id as u32, value_id)?;
|
||||||
|
|
||||||
|
let tag_value_id = with_new_heap_cell(builder, block, union_id)?;
|
||||||
|
|
||||||
let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes();
|
let type_name_bytes = recursive_tag_union_name_bytes(tag_layout).as_bytes();
|
||||||
let type_name = TypeName(&type_name_bytes);
|
let type_name = TypeName(&type_name_bytes);
|
||||||
|
|
||||||
env.type_names.insert(*tag_layout);
|
env.type_names.insert(*tag_layout);
|
||||||
|
|
||||||
builder.add_make_named(block, MOD_APP, type_name, union_id)
|
builder.add_make_named(block, MOD_APP, type_name, tag_value_id)
|
||||||
}
|
}
|
||||||
Struct(fields) => build_tuple_value(builder, env, block, fields),
|
Struct(fields) => build_tuple_value(builder, env, block, fields),
|
||||||
UnionAtIndex {
|
UnionAtIndex {
|
||||||
|
@ -1553,16 +1531,20 @@ fn expr_spec<'a>(
|
||||||
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);
|
||||||
|
|
||||||
|
// unwrap the named wrapper
|
||||||
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
|
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
|
||||||
let variant_id = builder.add_unwrap_union(block, union_id, *tag_id as u32)?;
|
|
||||||
|
// now we have a tuple (cell, union { ... }); decompose
|
||||||
|
let heap_cell = builder.add_get_tuple_field(block, union_id, TAG_CELL_INDEX)?;
|
||||||
|
let union_data = builder.add_get_tuple_field(block, union_id, TAG_DATA_INDEX)?;
|
||||||
|
|
||||||
// we're reading from this value, so touch the heap cell
|
// we're reading from this value, so touch the heap cell
|
||||||
let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?;
|
|
||||||
builder.add_touch(block, heap_cell)?;
|
builder.add_touch(block, heap_cell)?;
|
||||||
|
|
||||||
let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?;
|
// next, unwrap the union at the tag id that we've got
|
||||||
|
let variant_id = builder.add_unwrap_union(block, union_data, *tag_id as u32)?;
|
||||||
|
|
||||||
builder.add_get_tuple_field(block, tuple_value_id, index)
|
builder.add_get_tuple_field(block, variant_id, index)
|
||||||
}
|
}
|
||||||
UnionLayout::NonNullableUnwrapped { .. } => {
|
UnionLayout::NonNullableUnwrapped { .. } => {
|
||||||
let index = (*index) as u32;
|
let index = (*index) as u32;
|
||||||
|
@ -1573,16 +1555,20 @@ fn expr_spec<'a>(
|
||||||
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);
|
||||||
|
|
||||||
let variant_id =
|
// a tuple ( cell, union { ... } )
|
||||||
builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
|
let union_id = builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
|
||||||
|
|
||||||
|
// decompose
|
||||||
|
let heap_cell = builder.add_get_tuple_field(block, union_id, TAG_CELL_INDEX)?;
|
||||||
|
let union_data = builder.add_get_tuple_field(block, union_id, TAG_DATA_INDEX)?;
|
||||||
|
|
||||||
// we're reading from this value, so touch the heap cell
|
// we're reading from this value, so touch the heap cell
|
||||||
let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?;
|
|
||||||
builder.add_touch(block, heap_cell)?;
|
builder.add_touch(block, heap_cell)?;
|
||||||
|
|
||||||
let tuple_value_id = builder.add_get_tuple_field(block, variant_id, 1)?;
|
// next, unwrap the union at the tag id that we've got
|
||||||
|
let variant_id = builder.add_unwrap_union(block, union_data, *tag_id as u32)?;
|
||||||
|
|
||||||
builder.add_get_tuple_field(block, tuple_value_id, index)
|
builder.add_get_tuple_field(block, variant_id, index)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
StructAtIndex {
|
StructAtIndex {
|
||||||
|
@ -1613,9 +1599,7 @@ fn expr_spec<'a>(
|
||||||
if all_constants {
|
if all_constants {
|
||||||
new_static_list(builder, block)
|
new_static_list(builder, block)
|
||||||
} else {
|
} else {
|
||||||
let cell = builder.add_new_heap_cell(block)?;
|
with_new_heap_cell(builder, block, bag)
|
||||||
|
|
||||||
builder.add_make_tuple(block, &[cell, bag])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1626,7 +1610,7 @@ fn expr_spec<'a>(
|
||||||
}
|
}
|
||||||
_ => unreachable!("empty array does not have a list layout"),
|
_ => unreachable!("empty array does not have a list layout"),
|
||||||
},
|
},
|
||||||
Reset(symbol) => {
|
Reset { symbol, .. } => {
|
||||||
let type_id = layout_spec(builder, layout)?;
|
let type_id = layout_spec(builder, layout)?;
|
||||||
let value_id = env.symbols[symbol];
|
let value_id = env.symbols[symbol];
|
||||||
|
|
||||||
|
@ -1637,7 +1621,11 @@ fn expr_spec<'a>(
|
||||||
|
|
||||||
builder.add_terminate(block, type_id)
|
builder.add_terminate(block, type_id)
|
||||||
}
|
}
|
||||||
GetTagId { .. } => builder.add_make_tuple(block, &[]),
|
GetTagId { .. } => {
|
||||||
|
// TODO touch heap cell in recursive cases
|
||||||
|
|
||||||
|
builder.add_make_tuple(block, &[])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1658,6 +1646,19 @@ fn layout_spec(builder: &mut impl TypeContext, layout: &Layout) -> Result<TypeId
|
||||||
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
|
layout_spec_help(builder, layout, &WhenRecursive::Unreachable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn non_recursive_variant_types(
|
||||||
|
builder: &mut impl TypeContext,
|
||||||
|
tags: &[&[Layout]],
|
||||||
|
) -> Result<Vec<TypeId>> {
|
||||||
|
let mut result = Vec::with_capacity(tags.len());
|
||||||
|
|
||||||
|
for tag in tags.iter() {
|
||||||
|
result.push(build_tuple_type(builder, tag)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
|
||||||
fn layout_spec_help(
|
fn layout_spec_help(
|
||||||
builder: &mut impl TypeContext,
|
builder: &mut impl TypeContext,
|
||||||
layout: &Layout,
|
layout: &Layout,
|
||||||
|
@ -1674,8 +1675,6 @@ fn layout_spec_help(
|
||||||
when_recursive,
|
when_recursive,
|
||||||
),
|
),
|
||||||
Union(union_layout) => {
|
Union(union_layout) => {
|
||||||
let variant_types = build_variant_types(builder, union_layout)?;
|
|
||||||
|
|
||||||
match union_layout {
|
match union_layout {
|
||||||
UnionLayout::NonRecursive(&[]) => {
|
UnionLayout::NonRecursive(&[]) => {
|
||||||
// must model Void as Unit, otherwise we run into problems where
|
// must model Void as Unit, otherwise we run into problems where
|
||||||
|
@ -1683,7 +1682,10 @@ fn layout_spec_help(
|
||||||
// which is of course not possible
|
// which is of course not possible
|
||||||
builder.add_tuple_type(&[])
|
builder.add_tuple_type(&[])
|
||||||
}
|
}
|
||||||
UnionLayout::NonRecursive(_) => builder.add_union_type(&variant_types),
|
UnionLayout::NonRecursive(tags) => {
|
||||||
|
let variant_types = non_recursive_variant_types(builder, tags)?;
|
||||||
|
builder.add_union_type(&variant_types)
|
||||||
|
}
|
||||||
UnionLayout::Recursive(_)
|
UnionLayout::Recursive(_)
|
||||||
| UnionLayout::NullableUnwrapped { .. }
|
| UnionLayout::NullableUnwrapped { .. }
|
||||||
| UnionLayout::NullableWrapped { .. }
|
| UnionLayout::NullableWrapped { .. }
|
||||||
|
@ -1777,10 +1779,21 @@ const LIST_BAG_INDEX: u32 = 1;
|
||||||
const DICT_CELL_INDEX: u32 = LIST_CELL_INDEX;
|
const DICT_CELL_INDEX: u32 = LIST_CELL_INDEX;
|
||||||
const DICT_BAG_INDEX: u32 = LIST_BAG_INDEX;
|
const DICT_BAG_INDEX: u32 = LIST_BAG_INDEX;
|
||||||
|
|
||||||
fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result<ValueId> {
|
const TAG_CELL_INDEX: u32 = 0;
|
||||||
|
const TAG_DATA_INDEX: u32 = 1;
|
||||||
|
|
||||||
|
fn with_new_heap_cell(
|
||||||
|
builder: &mut FuncDefBuilder,
|
||||||
|
block: BlockId,
|
||||||
|
value: ValueId,
|
||||||
|
) -> Result<ValueId> {
|
||||||
let cell = builder.add_new_heap_cell(block)?;
|
let cell = builder.add_new_heap_cell(block)?;
|
||||||
|
builder.add_make_tuple(block, &[cell, value])
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_list(builder: &mut FuncDefBuilder, block: BlockId, element_type: TypeId) -> Result<ValueId> {
|
||||||
let bag = builder.add_empty_bag(block, element_type)?;
|
let bag = builder.add_empty_bag(block, element_type)?;
|
||||||
builder.add_make_tuple(block, &[cell, bag])
|
with_new_heap_cell(builder, block, bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_dict(
|
fn new_dict(
|
||||||
|
@ -1789,10 +1802,9 @@ fn new_dict(
|
||||||
key_type: TypeId,
|
key_type: TypeId,
|
||||||
value_type: TypeId,
|
value_type: TypeId,
|
||||||
) -> Result<ValueId> {
|
) -> Result<ValueId> {
|
||||||
let cell = builder.add_new_heap_cell(block)?;
|
|
||||||
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
|
let element_type = builder.add_tuple_type(&[key_type, value_type])?;
|
||||||
let bag = builder.add_empty_bag(block, element_type)?;
|
let bag = builder.add_empty_bag(block, element_type)?;
|
||||||
builder.add_make_tuple(block, &[cell, bag])
|
with_new_heap_cell(builder, block, bag)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
|
fn new_static_string(builder: &mut FuncDefBuilder, block: BlockId) -> Result<ValueId> {
|
||||||
|
|
|
@ -595,20 +595,17 @@ impl<'a> BorrowInfState<'a> {
|
||||||
|
|
||||||
HigherOrder(HigherOrderLowLevel {
|
HigherOrder(HigherOrderLowLevel {
|
||||||
op,
|
op,
|
||||||
arg_layouts,
|
passed_function,
|
||||||
ret_layout,
|
|
||||||
function_name,
|
|
||||||
function_env,
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
use crate::low_level::HigherOrder::*;
|
use crate::low_level::HigherOrder::*;
|
||||||
|
|
||||||
let closure_layout = ProcLayout {
|
let closure_layout = ProcLayout {
|
||||||
arguments: arg_layouts,
|
arguments: passed_function.argument_layouts,
|
||||||
result: *ret_layout,
|
result: passed_function.return_layout,
|
||||||
};
|
};
|
||||||
|
|
||||||
let function_ps = match param_map.get_symbol(*function_name, closure_layout) {
|
let function_ps = match param_map.get_symbol(passed_function.name, closure_layout) {
|
||||||
Some(function_ps) => function_ps,
|
Some(function_ps) => function_ps,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
};
|
};
|
||||||
|
@ -692,7 +689,7 @@ impl<'a> BorrowInfState<'a> {
|
||||||
// own the closure environment if the function needs to own it
|
// own the closure environment if the function needs to own it
|
||||||
let function_env_position = op.function_arity();
|
let function_env_position = op.function_arity();
|
||||||
if let Some(false) = function_ps.get(function_env_position).map(|p| p.borrow) {
|
if let Some(false) = function_ps.get(function_env_position).map(|p| p.borrow) {
|
||||||
self.own_var(*function_env);
|
self.own_var(passed_function.captured_environment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -726,7 +723,7 @@ impl<'a> BorrowInfState<'a> {
|
||||||
// the function must take it as an owned parameter
|
// the function must take it as an owned parameter
|
||||||
self.own_args_if_param(xs);
|
self.own_args_if_param(xs);
|
||||||
}
|
}
|
||||||
Reset(x) => {
|
Reset { symbol: x, .. } => {
|
||||||
self.own_var(z);
|
self.own_var(z);
|
||||||
self.own_var(*x);
|
self.own_var(*x);
|
||||||
}
|
}
|
||||||
|
@ -980,9 +977,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
| NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
|
| NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
|
||||||
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
|
||||||
|
|
||||||
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
|
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
|
||||||
| NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin
|
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos
|
||||||
| NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
|
| NumAsin | NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
|
||||||
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[owned, borrowed]),
|
||||||
|
@ -1008,6 +1005,10 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
|
||||||
SetFromList => arena.alloc_slice_copy(&[owned]),
|
SetFromList => arena.alloc_slice_copy(&[owned]),
|
||||||
|
|
||||||
ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
|
ExpectTrue => arena.alloc_slice_copy(&[irrelevant]),
|
||||||
|
|
||||||
|
RefCountGetPtr | RefCountInc | RefCountDec => {
|
||||||
|
unreachable!("Refcounting lowlevel calls are inserted *after* borrow checking");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
410
compiler/mono/src/gen_refcount.rs
Normal file
410
compiler/mono/src/gen_refcount.rs
Normal file
|
@ -0,0 +1,410 @@
|
||||||
|
use bumpalo::collections::vec::Vec;
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use roc_builtins::bitcode::IntWidth;
|
||||||
|
use roc_module::ident::Ident;
|
||||||
|
use roc_module::low_level::LowLevel;
|
||||||
|
use roc_module::symbol::{IdentIds, ModuleId, Symbol};
|
||||||
|
|
||||||
|
use crate::ir::{
|
||||||
|
BranchInfo, Call, CallSpecId, CallType, Expr, HostExposedLayouts, Literal, ModifyRc, Proc,
|
||||||
|
ProcLayout, SelfRecursive, Stmt, UpdateModeId,
|
||||||
|
};
|
||||||
|
use crate::layout::{Builtin, Layout};
|
||||||
|
|
||||||
|
const LAYOUT_BOOL: Layout = Layout::Builtin(Builtin::Bool);
|
||||||
|
const LAYOUT_UNIT: Layout = Layout::Struct(&[]);
|
||||||
|
const LAYOUT_PTR: Layout = Layout::RecursivePointer;
|
||||||
|
const LAYOUT_U32: Layout = Layout::Builtin(Builtin::Int(IntWidth::U32));
|
||||||
|
|
||||||
|
/// "Infinite" reference count, for static values
|
||||||
|
/// Ref counts are encoded as negative numbers where isize::MIN represents 1
|
||||||
|
pub const REFCOUNT_MAX: usize = 0;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
pub enum RefcountOp {
|
||||||
|
Inc,
|
||||||
|
Dec,
|
||||||
|
DecRef,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate specialized refcounting code in mono IR format
|
||||||
|
/// -------------------------------------------------------
|
||||||
|
///
|
||||||
|
/// Any backend that wants to use this, needs a field of type `RefcountProcGenerator`.
|
||||||
|
///
|
||||||
|
/// Whenever the backend sees a `Stmt::Refcounting`, it calls
|
||||||
|
/// `RefcountProcGenerator::expand_refcount_stmt()`, which returns IR statements
|
||||||
|
/// to call a refcounting procedure. The backend can then generate target code
|
||||||
|
/// for those IR statements instead of the original `Refcounting` statement.
|
||||||
|
///
|
||||||
|
/// Essentially we are expanding the `Refcounting` statement into a more detailed
|
||||||
|
/// form that's more suitable for code generation.
|
||||||
|
///
|
||||||
|
/// But so far, we've only mentioned _calls_ to the refcounting procedures.
|
||||||
|
/// The procedures themselves don't exist yet!
|
||||||
|
///
|
||||||
|
/// So when the backend has finished with all the `Proc`s from user code,
|
||||||
|
/// it's time to call `RefcountProcGenerator::generate_refcount_procs()`,
|
||||||
|
/// which generates the `Procs` for refcounting helpers. The backend can
|
||||||
|
/// simply generate target code for these `Proc`s just like any other Proc.
|
||||||
|
///
|
||||||
|
pub struct RefcountProcGenerator<'a> {
|
||||||
|
arena: &'a Bump,
|
||||||
|
home: ModuleId,
|
||||||
|
ptr_size: u32,
|
||||||
|
layout_isize: Layout<'a>,
|
||||||
|
/// List of refcounting procs to generate, specialised by Layout and RefCountOp
|
||||||
|
/// Order of insertion is preserved, since it is important for Wasm backend
|
||||||
|
procs_to_generate: Vec<'a, (Layout<'a>, RefcountOp, Symbol)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> RefcountProcGenerator<'a> {
|
||||||
|
pub fn new(arena: &'a Bump, intwidth_isize: IntWidth, home: ModuleId) -> Self {
|
||||||
|
RefcountProcGenerator {
|
||||||
|
arena,
|
||||||
|
home,
|
||||||
|
ptr_size: intwidth_isize.stack_size(),
|
||||||
|
layout_isize: Layout::Builtin(Builtin::Int(intwidth_isize)),
|
||||||
|
procs_to_generate: Vec::with_capacity_in(16, arena),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Expands the IR node Stmt::Refcounting to a more detailed IR Stmt that calls a helper proc.
|
||||||
|
/// The helper procs themselves can be generated later by calling `generate_refcount_procs`
|
||||||
|
pub fn expand_refcount_stmt(
|
||||||
|
&mut self,
|
||||||
|
ident_ids: &mut IdentIds,
|
||||||
|
layout: Layout<'a>,
|
||||||
|
modify: &ModifyRc,
|
||||||
|
following: &'a Stmt<'a>,
|
||||||
|
) -> (Stmt<'a>, Option<(Symbol, ProcLayout<'a>)>) {
|
||||||
|
match modify {
|
||||||
|
ModifyRc::Inc(structure, amount) => {
|
||||||
|
let layout_isize = self.layout_isize;
|
||||||
|
|
||||||
|
let (is_existing, proc_name) =
|
||||||
|
self.get_proc_symbol(ident_ids, layout, RefcountOp::Inc);
|
||||||
|
|
||||||
|
// Define a constant for the amount to increment
|
||||||
|
let amount_sym = self.create_symbol(ident_ids, "amount");
|
||||||
|
let amount_expr = Expr::Literal(Literal::Int(*amount as i128));
|
||||||
|
let amount_stmt = |next| Stmt::Let(amount_sym, amount_expr, layout_isize, next);
|
||||||
|
|
||||||
|
// Call helper proc, passing the Roc structure and constant amount
|
||||||
|
let arg_layouts = self.arena.alloc([layout, layout_isize]);
|
||||||
|
let call_result_empty = self.create_symbol(ident_ids, "call_result_empty");
|
||||||
|
let call_expr = Expr::Call(Call {
|
||||||
|
call_type: CallType::ByName {
|
||||||
|
name: proc_name,
|
||||||
|
ret_layout: &LAYOUT_UNIT,
|
||||||
|
arg_layouts,
|
||||||
|
specialization_id: CallSpecId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([*structure, amount_sym]),
|
||||||
|
});
|
||||||
|
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
|
||||||
|
let rc_stmt = amount_stmt(self.arena.alloc(call_stmt));
|
||||||
|
|
||||||
|
// Create a linker symbol for the helper proc if this is the first usage
|
||||||
|
let new_proc_info = if is_existing {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((
|
||||||
|
proc_name,
|
||||||
|
ProcLayout {
|
||||||
|
arguments: arg_layouts,
|
||||||
|
result: LAYOUT_UNIT,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
(rc_stmt, new_proc_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
ModifyRc::Dec(structure) => {
|
||||||
|
let (is_existing, proc_name) =
|
||||||
|
self.get_proc_symbol(ident_ids, layout, RefcountOp::Dec);
|
||||||
|
|
||||||
|
// Call helper proc, passing the Roc structure
|
||||||
|
let arg_layouts = self.arena.alloc([layout, self.layout_isize]);
|
||||||
|
let call_result_empty = self.create_symbol(ident_ids, "call_result_empty");
|
||||||
|
let call_expr = Expr::Call(Call {
|
||||||
|
call_type: CallType::ByName {
|
||||||
|
name: proc_name,
|
||||||
|
ret_layout: &LAYOUT_UNIT,
|
||||||
|
arg_layouts: self.arena.alloc([layout]),
|
||||||
|
specialization_id: CallSpecId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([*structure]),
|
||||||
|
});
|
||||||
|
|
||||||
|
let rc_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
|
||||||
|
|
||||||
|
// Create a linker symbol for the helper proc if this is the first usage
|
||||||
|
let new_proc_info = if is_existing {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some((
|
||||||
|
proc_name,
|
||||||
|
ProcLayout {
|
||||||
|
arguments: arg_layouts,
|
||||||
|
result: LAYOUT_UNIT,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
(rc_stmt, new_proc_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
ModifyRc::DecRef(structure) => {
|
||||||
|
// No generated procs for DecRef, just lowlevel calls
|
||||||
|
|
||||||
|
// Get a pointer to the refcount itself
|
||||||
|
let rc_ptr_sym = self.create_symbol(ident_ids, "rc_ptr");
|
||||||
|
let rc_ptr_expr = Expr::Call(Call {
|
||||||
|
call_type: CallType::LowLevel {
|
||||||
|
op: LowLevel::RefCountGetPtr,
|
||||||
|
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([*structure]),
|
||||||
|
});
|
||||||
|
let rc_ptr_stmt = |next| Stmt::Let(rc_ptr_sym, rc_ptr_expr, LAYOUT_PTR, next);
|
||||||
|
|
||||||
|
// Pass the refcount pointer to the lowlevel call (see utils.zig)
|
||||||
|
let call_result_empty = self.create_symbol(ident_ids, "call_result_empty");
|
||||||
|
let call_expr = Expr::Call(Call {
|
||||||
|
call_type: CallType::LowLevel {
|
||||||
|
op: LowLevel::RefCountDec,
|
||||||
|
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([rc_ptr_sym]),
|
||||||
|
});
|
||||||
|
let call_stmt = Stmt::Let(call_result_empty, call_expr, LAYOUT_UNIT, following);
|
||||||
|
let rc_stmt = rc_ptr_stmt(self.arena.alloc(call_stmt));
|
||||||
|
|
||||||
|
(rc_stmt, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate refcounting helper procs, each specialized to a particular Layout.
|
||||||
|
/// For example `List (Result { a: Str, b: Int } Str)` would get its own helper
|
||||||
|
/// to update the refcounts on the List, the Result and the strings.
|
||||||
|
pub fn generate_refcount_procs(
|
||||||
|
&mut self,
|
||||||
|
arena: &'a Bump,
|
||||||
|
ident_ids: &mut IdentIds,
|
||||||
|
) -> Vec<'a, Proc<'a>> {
|
||||||
|
// Move the vector so we can loop over it safely
|
||||||
|
let mut procs_to_generate = Vec::with_capacity_in(0, arena);
|
||||||
|
std::mem::swap(&mut self.procs_to_generate, &mut procs_to_generate);
|
||||||
|
|
||||||
|
let mut procs = Vec::with_capacity_in(procs_to_generate.len(), arena);
|
||||||
|
for (layout, op, proc_symbol) in procs_to_generate.drain(0..) {
|
||||||
|
let proc = match layout {
|
||||||
|
Layout::Builtin(Builtin::Str) => self.gen_modify_str(ident_ids, op, proc_symbol),
|
||||||
|
_ => todo!("Refcounting is not yet implemented for Layout {:?}", layout),
|
||||||
|
};
|
||||||
|
procs.push(proc);
|
||||||
|
}
|
||||||
|
|
||||||
|
procs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Find the Symbol of the procedure for this layout and refcount operation,
|
||||||
|
/// or create one if needed.
|
||||||
|
fn get_proc_symbol(
|
||||||
|
&mut self,
|
||||||
|
ident_ids: &mut IdentIds,
|
||||||
|
layout: Layout<'a>,
|
||||||
|
op: RefcountOp,
|
||||||
|
) -> (bool, Symbol) {
|
||||||
|
let found = self
|
||||||
|
.procs_to_generate
|
||||||
|
.iter()
|
||||||
|
.find(|(l, o, _)| *l == layout && *o == op);
|
||||||
|
|
||||||
|
if let Some((_, _, existing_symbol)) = found {
|
||||||
|
(true, *existing_symbol)
|
||||||
|
} else {
|
||||||
|
let layout_name = layout_debug_name(&layout);
|
||||||
|
let unique_idx = self.procs_to_generate.len();
|
||||||
|
let debug_name = format!("#rc{:?}_{}_{}", op, layout_name, unique_idx);
|
||||||
|
let new_symbol: Symbol = self.create_symbol(ident_ids, &debug_name);
|
||||||
|
self.procs_to_generate.push((layout, op, new_symbol));
|
||||||
|
(false, new_symbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn create_symbol(&mut self, ident_ids: &mut IdentIds, debug_name: &str) -> Symbol {
|
||||||
|
let ident_id = ident_ids.add(Ident::from(debug_name));
|
||||||
|
Symbol::new(self.home, ident_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn return_unit(&mut self, ident_ids: &mut IdentIds) -> Stmt<'a> {
|
||||||
|
let unit = self.create_symbol(ident_ids, "unit");
|
||||||
|
let ret_stmt = self.arena.alloc(Stmt::Ret(unit));
|
||||||
|
Stmt::Let(unit, Expr::Struct(&[]), LAYOUT_UNIT, ret_stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn gen_args(&mut self, op: RefcountOp, layout: Layout<'a>) -> &'a [(Layout<'a>, Symbol)] {
|
||||||
|
let roc_value = (layout, Symbol::ARG_1);
|
||||||
|
match op {
|
||||||
|
RefcountOp::Inc => {
|
||||||
|
let inc_amount = (self.layout_isize, Symbol::ARG_2);
|
||||||
|
self.arena.alloc([roc_value, inc_amount])
|
||||||
|
}
|
||||||
|
RefcountOp::Dec | RefcountOp::DecRef => self.arena.alloc([roc_value]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Generate a procedure to modify the reference count of a Str
|
||||||
|
fn gen_modify_str(
|
||||||
|
&mut self,
|
||||||
|
ident_ids: &mut IdentIds,
|
||||||
|
op: RefcountOp,
|
||||||
|
proc_name: Symbol,
|
||||||
|
) -> Proc<'a> {
|
||||||
|
let string = Symbol::ARG_1;
|
||||||
|
let layout_isize = self.layout_isize;
|
||||||
|
|
||||||
|
// Get the string length as a signed int
|
||||||
|
let len = self.create_symbol(ident_ids, "len");
|
||||||
|
let len_expr = Expr::StructAtIndex {
|
||||||
|
index: 1,
|
||||||
|
field_layouts: self.arena.alloc([LAYOUT_PTR, layout_isize]),
|
||||||
|
structure: string,
|
||||||
|
};
|
||||||
|
let len_stmt = |next| Stmt::Let(len, len_expr, layout_isize, next);
|
||||||
|
|
||||||
|
// Zero
|
||||||
|
let zero = self.create_symbol(ident_ids, "zero");
|
||||||
|
let zero_expr = Expr::Literal(Literal::Int(0));
|
||||||
|
let zero_stmt = |next| Stmt::Let(zero, zero_expr, layout_isize, next);
|
||||||
|
|
||||||
|
// is_big_str = (len >= 0);
|
||||||
|
// Treat len as isize so that the small string flag is the same as the sign bit
|
||||||
|
let is_big_str = self.create_symbol(ident_ids, "is_big_str");
|
||||||
|
let is_big_str_expr = Expr::Call(Call {
|
||||||
|
call_type: CallType::LowLevel {
|
||||||
|
op: LowLevel::NumGte,
|
||||||
|
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([len, zero]),
|
||||||
|
});
|
||||||
|
let is_big_str_stmt = |next| Stmt::Let(is_big_str, is_big_str_expr, LAYOUT_BOOL, next);
|
||||||
|
|
||||||
|
// Get the pointer to the string elements
|
||||||
|
let elements = self.create_symbol(ident_ids, "elements");
|
||||||
|
let elements_expr = Expr::StructAtIndex {
|
||||||
|
index: 0,
|
||||||
|
field_layouts: self.arena.alloc([LAYOUT_PTR, layout_isize]),
|
||||||
|
structure: string,
|
||||||
|
};
|
||||||
|
let elements_stmt = |next| Stmt::Let(elements, elements_expr, LAYOUT_PTR, next);
|
||||||
|
|
||||||
|
// Get a pointer to the refcount value, just below the elements pointer
|
||||||
|
let rc_ptr = self.create_symbol(ident_ids, "rc_ptr");
|
||||||
|
let rc_ptr_expr = Expr::Call(Call {
|
||||||
|
call_type: CallType::LowLevel {
|
||||||
|
op: LowLevel::RefCountGetPtr,
|
||||||
|
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([elements]),
|
||||||
|
});
|
||||||
|
let rc_ptr_stmt = |next| Stmt::Let(rc_ptr, rc_ptr_expr, LAYOUT_PTR, next);
|
||||||
|
|
||||||
|
// Alignment constant
|
||||||
|
let alignment = self.create_symbol(ident_ids, "alignment");
|
||||||
|
let alignment_expr = Expr::Literal(Literal::Int(self.ptr_size as i128));
|
||||||
|
let alignment_stmt = |next| Stmt::Let(alignment, alignment_expr, LAYOUT_U32, next);
|
||||||
|
|
||||||
|
// Call the relevant Zig lowlevel to actually modify the refcount
|
||||||
|
let zig_call_result = self.create_symbol(ident_ids, "zig_call_result");
|
||||||
|
let zig_call_expr = match op {
|
||||||
|
RefcountOp::Inc => Expr::Call(Call {
|
||||||
|
call_type: CallType::LowLevel {
|
||||||
|
op: LowLevel::RefCountInc,
|
||||||
|
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([rc_ptr, Symbol::ARG_2]),
|
||||||
|
}),
|
||||||
|
RefcountOp::Dec | RefcountOp::DecRef => Expr::Call(Call {
|
||||||
|
call_type: CallType::LowLevel {
|
||||||
|
op: LowLevel::RefCountDec,
|
||||||
|
update_mode: UpdateModeId::BACKEND_DUMMY,
|
||||||
|
},
|
||||||
|
arguments: self.arena.alloc([rc_ptr, alignment]),
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
let zig_call_stmt = |next| Stmt::Let(zig_call_result, zig_call_expr, LAYOUT_UNIT, next);
|
||||||
|
|
||||||
|
// Generate an `if` to skip small strings but modify big strings
|
||||||
|
let then_branch = elements_stmt(self.arena.alloc(
|
||||||
|
//
|
||||||
|
rc_ptr_stmt(self.arena.alloc(
|
||||||
|
//
|
||||||
|
alignment_stmt(self.arena.alloc(
|
||||||
|
//
|
||||||
|
zig_call_stmt(self.arena.alloc(
|
||||||
|
//
|
||||||
|
Stmt::Ret(zig_call_result),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
let if_stmt = Stmt::Switch {
|
||||||
|
cond_symbol: is_big_str,
|
||||||
|
cond_layout: LAYOUT_BOOL,
|
||||||
|
branches: self.arena.alloc([(1, BranchInfo::None, then_branch)]),
|
||||||
|
default_branch: (
|
||||||
|
BranchInfo::None,
|
||||||
|
self.arena.alloc(self.return_unit(ident_ids)),
|
||||||
|
),
|
||||||
|
ret_layout: LAYOUT_UNIT,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Combine the statements in sequence
|
||||||
|
let body = len_stmt(self.arena.alloc(
|
||||||
|
//
|
||||||
|
zero_stmt(self.arena.alloc(
|
||||||
|
//
|
||||||
|
is_big_str_stmt(self.arena.alloc(
|
||||||
|
//
|
||||||
|
if_stmt,
|
||||||
|
)),
|
||||||
|
)),
|
||||||
|
));
|
||||||
|
|
||||||
|
let args = self.gen_args(op, Layout::Builtin(Builtin::Str));
|
||||||
|
|
||||||
|
Proc {
|
||||||
|
name: proc_name,
|
||||||
|
args,
|
||||||
|
body,
|
||||||
|
closure_data_layout: None,
|
||||||
|
ret_layout: LAYOUT_UNIT,
|
||||||
|
is_self_recursive: SelfRecursive::NotSelfRecursive,
|
||||||
|
must_own_arguments: false,
|
||||||
|
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to derive a debug function name from a layout
|
||||||
|
fn layout_debug_name<'a>(layout: &Layout<'a>) -> &'static str {
|
||||||
|
match layout {
|
||||||
|
Layout::Builtin(Builtin::List(_)) => "list",
|
||||||
|
Layout::Builtin(Builtin::Set(_)) => "set",
|
||||||
|
Layout::Builtin(Builtin::Dict(_, _)) => "dict",
|
||||||
|
Layout::Builtin(Builtin::Str) => "str",
|
||||||
|
Layout::Builtin(builtin) => {
|
||||||
|
debug_assert!(!builtin.is_refcounted());
|
||||||
|
unreachable!("Builtin {:?} is not refcounted", builtin);
|
||||||
|
}
|
||||||
|
Layout::Struct(_) => "struct",
|
||||||
|
Layout::Union(_) => "union",
|
||||||
|
Layout::LambdaSet(_) => "lambdaset",
|
||||||
|
Layout::RecursivePointer => "recursive_pointer",
|
||||||
|
}
|
||||||
|
}
|
|
@ -110,7 +110,7 @@ pub fn occurring_variables_expr(expr: &Expr<'_>, result: &mut MutSet<Symbol>) {
|
||||||
result.extend(arguments.iter().copied());
|
result.extend(arguments.iter().copied());
|
||||||
result.insert(*symbol);
|
result.insert(*symbol);
|
||||||
}
|
}
|
||||||
Reset(x) => {
|
Reset { symbol: x, .. } => {
|
||||||
result.insert(*x);
|
result.insert(*x);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -468,12 +468,8 @@ impl<'a> Context<'a> {
|
||||||
HigherOrder(HigherOrderLowLevel {
|
HigherOrder(HigherOrderLowLevel {
|
||||||
op,
|
op,
|
||||||
closure_env_layout,
|
closure_env_layout,
|
||||||
specialization_id,
|
|
||||||
update_mode,
|
update_mode,
|
||||||
arg_layouts,
|
passed_function,
|
||||||
ret_layout,
|
|
||||||
function_name,
|
|
||||||
function_env,
|
|
||||||
..
|
..
|
||||||
}) => {
|
}) => {
|
||||||
// setup
|
// setup
|
||||||
|
@ -483,16 +479,14 @@ impl<'a> Context<'a> {
|
||||||
($borrows:expr) => {
|
($borrows:expr) => {
|
||||||
Expr::Call(crate::ir::Call {
|
Expr::Call(crate::ir::Call {
|
||||||
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
|
call_type: if let Some(OWNED) = $borrows.map(|p| p.borrow) {
|
||||||
|
let mut passed_function = *passed_function;
|
||||||
|
passed_function.owns_captured_environment = true;
|
||||||
|
|
||||||
let higher_order = HigherOrderLowLevel {
|
let higher_order = HigherOrderLowLevel {
|
||||||
op: *op,
|
op: *op,
|
||||||
closure_env_layout: *closure_env_layout,
|
closure_env_layout: *closure_env_layout,
|
||||||
function_owns_closure_data: true,
|
|
||||||
specialization_id: *specialization_id,
|
|
||||||
update_mode: *update_mode,
|
update_mode: *update_mode,
|
||||||
function_name: *function_name,
|
passed_function,
|
||||||
function_env: *function_env,
|
|
||||||
arg_layouts,
|
|
||||||
ret_layout: *ret_layout,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
CallType::HigherOrder(self.arena.alloc(higher_order))
|
CallType::HigherOrder(self.arena.alloc(higher_order))
|
||||||
|
@ -521,11 +515,14 @@ impl<'a> Context<'a> {
|
||||||
const CLOSURE_DATA: bool = BORROWED;
|
const CLOSURE_DATA: bool = BORROWED;
|
||||||
|
|
||||||
let function_layout = ProcLayout {
|
let function_layout = ProcLayout {
|
||||||
arguments: arg_layouts,
|
arguments: passed_function.argument_layouts,
|
||||||
result: *ret_layout,
|
result: passed_function.return_layout,
|
||||||
};
|
};
|
||||||
|
|
||||||
let function_ps = match self.param_map.get_symbol(*function_name, function_layout) {
|
let function_ps = match self
|
||||||
|
.param_map
|
||||||
|
.get_symbol(passed_function.name, function_layout)
|
||||||
|
{
|
||||||
Some(function_ps) => function_ps,
|
Some(function_ps) => function_ps,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
};
|
};
|
||||||
|
@ -761,7 +758,7 @@ impl<'a> Context<'a> {
|
||||||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||||
}
|
}
|
||||||
|
|
||||||
EmptyArray | Literal(_) | Reset(_) | RuntimeErrorFunction(_) => {
|
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
|
||||||
// EmptyArray is always stack-allocated
|
// EmptyArray is always stack-allocated
|
||||||
// function pointers are persistent
|
// function pointers are persistent
|
||||||
self.arena.alloc(Stmt::Let(z, v, l, b))
|
self.arena.alloc(Stmt::Let(z, v, l, b))
|
||||||
|
@ -779,7 +776,7 @@ impl<'a> Context<'a> {
|
||||||
// must this value be consumed?
|
// must this value be consumed?
|
||||||
let consume = consume_expr(&self.vars, expr);
|
let consume = consume_expr(&self.vars, expr);
|
||||||
|
|
||||||
let reset = matches!(expr, Expr::Reset(_));
|
let reset = matches!(expr, Expr::Reset { .. });
|
||||||
|
|
||||||
self.update_var_info_help(symbol, layout, persistent, consume, reset)
|
self.update_var_info_help(symbol, layout, persistent, consume, reset)
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,8 +37,8 @@ static_assertions::assert_eq_size!([u8; 19 * 8], Stmt);
|
||||||
#[cfg(target_arch = "aarch64")]
|
#[cfg(target_arch = "aarch64")]
|
||||||
static_assertions::assert_eq_size!([u8; 20 * 8], Stmt);
|
static_assertions::assert_eq_size!([u8; 20 * 8], Stmt);
|
||||||
static_assertions::assert_eq_size!([u8; 6 * 8], ProcLayout);
|
static_assertions::assert_eq_size!([u8; 6 * 8], ProcLayout);
|
||||||
static_assertions::assert_eq_size!([u8; 8 * 8], Call);
|
static_assertions::assert_eq_size!([u8; 7 * 8], Call);
|
||||||
static_assertions::assert_eq_size!([u8; 6 * 8], CallType);
|
static_assertions::assert_eq_size!([u8; 5 * 8], CallType);
|
||||||
|
|
||||||
macro_rules! return_on_layout_error {
|
macro_rules! return_on_layout_error {
|
||||||
($env:expr, $layout_result:expr) => {
|
($env:expr, $layout_result:expr) => {
|
||||||
|
@ -318,11 +318,17 @@ impl<'a> Proc<'a> {
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
home: ModuleId,
|
home: ModuleId,
|
||||||
ident_ids: &'i mut IdentIds,
|
ident_ids: &'i mut IdentIds,
|
||||||
|
update_mode_ids: &'i mut UpdateModeIds,
|
||||||
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
|
||||||
) {
|
) {
|
||||||
for (_, proc) in procs.iter_mut() {
|
for (_, proc) in procs.iter_mut() {
|
||||||
let new_proc =
|
let new_proc = crate::reset_reuse::insert_reset_reuse(
|
||||||
crate::reset_reuse::insert_reset_reuse(arena, home, ident_ids, proc.clone());
|
arena,
|
||||||
|
home,
|
||||||
|
ident_ids,
|
||||||
|
update_mode_ids,
|
||||||
|
proc.clone(),
|
||||||
|
);
|
||||||
*proc = new_proc;
|
*proc = new_proc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -988,8 +994,8 @@ pub struct Env<'a, 'i> {
|
||||||
pub home: ModuleId,
|
pub home: ModuleId,
|
||||||
pub ident_ids: &'i mut IdentIds,
|
pub ident_ids: &'i mut IdentIds,
|
||||||
pub ptr_bytes: u32,
|
pub ptr_bytes: u32,
|
||||||
pub update_mode_counter: u64,
|
pub update_mode_ids: &'i mut UpdateModeIds,
|
||||||
pub call_specialization_counter: u64,
|
pub call_specialization_counter: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'i> Env<'a, 'i> {
|
impl<'a, 'i> Env<'a, 'i> {
|
||||||
|
@ -1000,13 +1006,7 @@ impl<'a, 'i> Env<'a, 'i> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_update_mode_id(&mut self) -> UpdateModeId {
|
pub fn next_update_mode_id(&mut self) -> UpdateModeId {
|
||||||
let id = UpdateModeId {
|
self.update_mode_ids.next_id()
|
||||||
id: self.update_mode_counter,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.update_mode_counter += 1;
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn next_call_specialization_id(&mut self) -> CallSpecId {
|
pub fn next_call_specialization_id(&mut self) -> CallSpecId {
|
||||||
|
@ -1282,24 +1282,49 @@ impl<'a> Call<'a> {
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct CallSpecId {
|
pub struct CallSpecId {
|
||||||
id: u64,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CallSpecId {
|
impl CallSpecId {
|
||||||
pub fn to_bytes(self) -> [u8; 8] {
|
pub fn to_bytes(self) -> [u8; 4] {
|
||||||
self.id.to_ne_bytes()
|
self.id.to_ne_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dummy value for generating refcount helper procs in the backends
|
||||||
|
/// This happens *after* specialization so it's safe
|
||||||
|
pub const BACKEND_DUMMY: Self = Self { id: 0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub struct UpdateModeId {
|
pub struct UpdateModeId {
|
||||||
id: u64,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UpdateModeId {
|
impl UpdateModeId {
|
||||||
pub fn to_bytes(self) -> [u8; 8] {
|
pub fn to_bytes(self) -> [u8; 4] {
|
||||||
self.id.to_ne_bytes()
|
self.id.to_ne_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Dummy value for generating refcount helper procs in the backends
|
||||||
|
/// This happens *after* alias analysis so it's safe
|
||||||
|
pub const BACKEND_DUMMY: Self = Self { id: 0 };
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub struct UpdateModeIds {
|
||||||
|
next: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateModeIds {
|
||||||
|
pub const fn new() -> Self {
|
||||||
|
Self { next: 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next_id(&mut self) -> UpdateModeId {
|
||||||
|
let id = UpdateModeId { id: self.next };
|
||||||
|
self.next += 1;
|
||||||
|
id
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -1321,31 +1346,35 @@ pub enum CallType<'a> {
|
||||||
HigherOrder(&'a HigherOrderLowLevel<'a>),
|
HigherOrder(&'a HigherOrderLowLevel<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||||
|
pub struct PassedFunction<'a> {
|
||||||
|
/// name of the top-level function that is passed as an argument
|
||||||
|
/// e.g. in `List.map xs Num.abs` this would be `Num.abs`
|
||||||
|
pub name: Symbol,
|
||||||
|
|
||||||
|
pub argument_layouts: &'a [Layout<'a>],
|
||||||
|
pub return_layout: Layout<'a>,
|
||||||
|
|
||||||
|
pub specialization_id: CallSpecId,
|
||||||
|
|
||||||
|
/// Symbol of the environment captured by the function argument
|
||||||
|
pub captured_environment: Symbol,
|
||||||
|
|
||||||
|
pub owns_captured_environment: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub struct HigherOrderLowLevel<'a> {
|
pub struct HigherOrderLowLevel<'a> {
|
||||||
pub op: crate::low_level::HigherOrder,
|
pub op: crate::low_level::HigherOrder,
|
||||||
|
|
||||||
|
/// TODO I _think_ we can get rid of this, perhaps only keeping track of
|
||||||
/// the layout of the closure argument, if any
|
/// the layout of the closure argument, if any
|
||||||
pub closure_env_layout: Option<Layout<'a>>,
|
pub closure_env_layout: Option<Layout<'a>>,
|
||||||
|
|
||||||
/// name of the top-level function that is passed as an argument
|
|
||||||
/// e.g. in `List.map xs Num.abs` this would be `Num.abs`
|
|
||||||
pub function_name: Symbol,
|
|
||||||
|
|
||||||
/// Symbol of the environment captured by the function argument
|
|
||||||
pub function_env: Symbol,
|
|
||||||
|
|
||||||
/// does the function argument need to own the closure data
|
|
||||||
pub function_owns_closure_data: bool,
|
|
||||||
|
|
||||||
/// specialization id of the function argument, used for name generation
|
|
||||||
pub specialization_id: CallSpecId,
|
|
||||||
|
|
||||||
/// update mode of the higher order lowlevel itself
|
/// update mode of the higher order lowlevel itself
|
||||||
pub update_mode: UpdateModeId,
|
pub update_mode: UpdateModeId,
|
||||||
|
|
||||||
/// function layout, used for name generation
|
pub passed_function: PassedFunction<'a>,
|
||||||
pub arg_layouts: &'a [Layout<'a>],
|
|
||||||
pub ret_layout: Layout<'a>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
@ -1390,13 +1419,17 @@ pub enum Expr<'a> {
|
||||||
Reuse {
|
Reuse {
|
||||||
symbol: Symbol,
|
symbol: Symbol,
|
||||||
update_tag_id: bool,
|
update_tag_id: bool,
|
||||||
|
update_mode: UpdateModeId,
|
||||||
// normal Tag fields
|
// normal Tag fields
|
||||||
tag_layout: UnionLayout<'a>,
|
tag_layout: UnionLayout<'a>,
|
||||||
tag_name: TagName,
|
tag_name: TagName,
|
||||||
tag_id: TagIdIntType,
|
tag_id: TagIdIntType,
|
||||||
arguments: &'a [Symbol],
|
arguments: &'a [Symbol],
|
||||||
},
|
},
|
||||||
Reset(Symbol),
|
Reset {
|
||||||
|
symbol: Symbol,
|
||||||
|
update_mode: UpdateModeId,
|
||||||
|
},
|
||||||
|
|
||||||
RuntimeErrorFunction(&'a str),
|
RuntimeErrorFunction(&'a str),
|
||||||
}
|
}
|
||||||
|
@ -1491,6 +1524,7 @@ impl<'a> Expr<'a> {
|
||||||
symbol,
|
symbol,
|
||||||
tag_name,
|
tag_name,
|
||||||
arguments,
|
arguments,
|
||||||
|
update_mode,
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
let doc_tag = match tag_name {
|
let doc_tag = match tag_name {
|
||||||
|
@ -1508,11 +1542,19 @@ impl<'a> Expr<'a> {
|
||||||
.text("Reuse ")
|
.text("Reuse ")
|
||||||
.append(symbol_to_doc(alloc, *symbol))
|
.append(symbol_to_doc(alloc, *symbol))
|
||||||
.append(alloc.space())
|
.append(alloc.space())
|
||||||
|
.append(format!("{:?}", update_mode))
|
||||||
|
.append(alloc.space())
|
||||||
.append(doc_tag)
|
.append(doc_tag)
|
||||||
.append(alloc.space())
|
.append(alloc.space())
|
||||||
.append(alloc.intersperse(it, " "))
|
.append(alloc.intersperse(it, " "))
|
||||||
}
|
}
|
||||||
Reset(symbol) => alloc.text("Reset ").append(symbol_to_doc(alloc, *symbol)),
|
Reset {
|
||||||
|
symbol,
|
||||||
|
update_mode,
|
||||||
|
} => alloc.text(format!(
|
||||||
|
"Reset {{ symbol: {:?}, id: {} }}",
|
||||||
|
symbol, update_mode.id
|
||||||
|
)),
|
||||||
|
|
||||||
Struct(args) => {
|
Struct(args) => {
|
||||||
let it = args.iter().map(|s| symbol_to_doc(alloc, *s));
|
let it = args.iter().map(|s| symbol_to_doc(alloc, *s));
|
||||||
|
@ -1557,6 +1599,17 @@ impl<'a> Expr<'a> {
|
||||||
.append(symbol_to_doc(alloc, *structure)),
|
.append(symbol_to_doc(alloc, *structure)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn to_pretty(&self, width: usize) -> String {
|
||||||
|
let allocator = BoxAllocator;
|
||||||
|
let mut w = std::vec::Vec::new();
|
||||||
|
self.to_doc::<_, ()>(&allocator)
|
||||||
|
.1
|
||||||
|
.render(width, &mut w)
|
||||||
|
.unwrap();
|
||||||
|
w.push(b'\n');
|
||||||
|
String::from_utf8(w).unwrap()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Stmt<'a> {
|
impl<'a> Stmt<'a> {
|
||||||
|
@ -4144,16 +4197,21 @@ pub fn with_hole<'a>(
|
||||||
op,
|
op,
|
||||||
closure_data_symbol,
|
closure_data_symbol,
|
||||||
|(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| {
|
|(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| {
|
||||||
|
let passed_function = PassedFunction {
|
||||||
|
name: top_level_function,
|
||||||
|
captured_environment: closure_data_symbol,
|
||||||
|
owns_captured_environment: false,
|
||||||
|
specialization_id,
|
||||||
|
argument_layouts: arg_layouts,
|
||||||
|
return_layout: ret_layout,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
let higher_order = HigherOrderLowLevel {
|
let higher_order = HigherOrderLowLevel {
|
||||||
op: crate::low_level::HigherOrder::$ho { $($x,)* },
|
op: crate::low_level::HigherOrder::$ho { $($x,)* },
|
||||||
closure_env_layout,
|
closure_env_layout,
|
||||||
specialization_id,
|
|
||||||
update_mode,
|
update_mode,
|
||||||
function_owns_closure_data: false,
|
passed_function,
|
||||||
function_env: closure_data_symbol,
|
|
||||||
function_name: top_level_function,
|
|
||||||
arg_layouts,
|
|
||||||
ret_layout,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
self::Call {
|
self::Call {
|
||||||
|
@ -5715,7 +5773,7 @@ fn substitute_in_expr<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Reuse { .. } | Reset(_) => unreachable!("reset/reuse have not been introduced yet"),
|
Reuse { .. } | Reset { .. } => unreachable!("reset/reuse have not been introduced yet"),
|
||||||
|
|
||||||
Struct(args) => {
|
Struct(args) => {
|
||||||
let mut did_change = false;
|
let mut did_change = false;
|
||||||
|
|
|
@ -2367,7 +2367,7 @@ fn layout_from_tag_union<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
|
pub fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
|
||||||
// the ext_var is empty
|
// the ext_var is empty
|
||||||
let fields = roc_types::types::gather_fields(subs, RecordFields::empty(), ext_var);
|
let fields = roc_types::types::gather_fields(subs, RecordFields::empty(), ext_var);
|
||||||
|
|
||||||
|
@ -2375,13 +2375,13 @@ fn ext_var_is_empty_record(subs: &Subs, ext_var: Variable) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
fn ext_var_is_empty_record(_subs: &Subs, _ext_var: Variable) -> bool {
|
pub fn ext_var_is_empty_record(_subs: &Subs, _ext_var: Variable) -> bool {
|
||||||
// This should only ever be used in debug_assert! macros
|
// This should only ever be used in debug_assert! macros
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(debug_assertions)]
|
#[cfg(debug_assertions)]
|
||||||
fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
|
pub fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
|
||||||
// the ext_var is empty
|
// the ext_var is empty
|
||||||
let mut ext_fields = std::vec::Vec::new();
|
let mut ext_fields = std::vec::Vec::new();
|
||||||
match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) {
|
match roc_types::pretty_print::chase_ext_tag_union(subs, ext_var, &mut ext_fields) {
|
||||||
|
@ -2391,7 +2391,7 @@ fn ext_var_is_empty_tag_union(subs: &Subs, ext_var: Variable) -> bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(debug_assertions))]
|
#[cfg(not(debug_assertions))]
|
||||||
fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool {
|
pub fn ext_var_is_empty_tag_union(_: &Subs, _: Variable) -> bool {
|
||||||
// This should only ever be used in debug_assert! macros
|
// This should only ever be used in debug_assert! macros
|
||||||
unreachable!();
|
unreachable!();
|
||||||
}
|
}
|
||||||
|
|
842
compiler/mono/src/layout_soa.rs
Normal file
842
compiler/mono/src/layout_soa.rs
Normal file
|
@ -0,0 +1,842 @@
|
||||||
|
use crate::layout::{ext_var_is_empty_record, ext_var_is_empty_tag_union};
|
||||||
|
use roc_builtins::bitcode::{FloatWidth, IntWidth};
|
||||||
|
use roc_collections::all::MutMap;
|
||||||
|
use roc_module::ident::TagName;
|
||||||
|
use roc_module::symbol::Symbol;
|
||||||
|
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||||
|
use roc_types::types::RecordField;
|
||||||
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Index<T> {
|
||||||
|
index: u32,
|
||||||
|
_marker: std::marker::PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Index<T> {
|
||||||
|
pub const fn new(index: u32) -> Self {
|
||||||
|
Self {
|
||||||
|
index,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub struct Slice<T> {
|
||||||
|
start: u32,
|
||||||
|
length: u16,
|
||||||
|
_marker: std::marker::PhantomData<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Slice<T> {
|
||||||
|
pub const fn new(start: u32, length: u16) -> Self {
|
||||||
|
Self {
|
||||||
|
start,
|
||||||
|
length,
|
||||||
|
_marker: std::marker::PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn len(&self) -> usize {
|
||||||
|
self.length as _
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn is_empty(&self) -> bool {
|
||||||
|
self.length == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn indices(&self) -> std::ops::Range<usize> {
|
||||||
|
self.start as usize..(self.start as usize + self.length as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_iter(&self) -> impl Iterator<Item = Index<T>> {
|
||||||
|
self.indices().map(|i| Index::new(i as _))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait Reserve {
|
||||||
|
fn reserve(layouts: &mut Layouts, length: usize) -> Self;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reserve for Slice<Layout> {
|
||||||
|
fn reserve(layouts: &mut Layouts, length: usize) -> Self {
|
||||||
|
let start = layouts.layouts.len() as u32;
|
||||||
|
|
||||||
|
let it = std::iter::repeat(Layout::Reserved).take(length);
|
||||||
|
layouts.layouts.extend(it);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
start,
|
||||||
|
length: length as u16,
|
||||||
|
_marker: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Reserve for Slice<Slice<Layout>> {
|
||||||
|
fn reserve(layouts: &mut Layouts, length: usize) -> Self {
|
||||||
|
let start = layouts.layout_slices.len() as u32;
|
||||||
|
|
||||||
|
let empty: Slice<Layout> = Slice::new(0, 0);
|
||||||
|
let it = std::iter::repeat(empty).take(length);
|
||||||
|
layouts.layout_slices.extend(it);
|
||||||
|
|
||||||
|
Self {
|
||||||
|
start,
|
||||||
|
length: length as u16,
|
||||||
|
_marker: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static_assertions::assert_eq_size!([u8; 12], Layout);
|
||||||
|
|
||||||
|
pub struct Layouts {
|
||||||
|
layouts: Vec<Layout>,
|
||||||
|
layout_slices: Vec<Slice<Layout>>,
|
||||||
|
// function_layouts: Vec<(Slice<Layout>, Index<LambdaSet>)>,
|
||||||
|
lambda_sets: Vec<LambdaSet>,
|
||||||
|
symbols: Vec<Symbol>,
|
||||||
|
recursion_variable_to_structure_variable_map: MutMap<Variable, Index<Layout>>,
|
||||||
|
usize_int_width: IntWidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FunctionLayout {
|
||||||
|
/// last element is the result, prior elements the arguments
|
||||||
|
arguments_and_result: Slice<Layout>,
|
||||||
|
pub lambda_set: Index<LambdaSet>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FunctionLayout {
|
||||||
|
pub fn from_var(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
// so we can set some things/clean up
|
||||||
|
Self::from_var_help(layouts, subs, var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_var_help(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
let content = &subs.get_ref(var).content;
|
||||||
|
Self::from_content(layouts, subs, var, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_content(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
content: &Content,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
use LayoutError::*;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
|
||||||
|
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
|
||||||
|
Content::RecursionVar { .. } => Err(TypeError(())),
|
||||||
|
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
|
||||||
|
Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual),
|
||||||
|
Content::Error => Err(TypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_flat_type(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
flat_type: &FlatType,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
use LayoutError::*;
|
||||||
|
|
||||||
|
match flat_type {
|
||||||
|
FlatType::Func(arguments, lambda_set, result) => {
|
||||||
|
let slice = Slice::reserve(layouts, arguments.len() + 1);
|
||||||
|
|
||||||
|
let variable_slice = &subs.variables[arguments.indices()];
|
||||||
|
let it = slice.indices().zip(variable_slice);
|
||||||
|
for (target_index, var) in it {
|
||||||
|
let layout = Layout::from_var_help(layouts, subs, *var)?;
|
||||||
|
layouts.layouts[target_index] = layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
let result_layout = Layout::from_var_help(layouts, subs, *result)?;
|
||||||
|
let result_index: Index<Layout> = Index::new(slice.start + slice.len() as u32 - 1);
|
||||||
|
layouts.layouts[result_index.index as usize] = result_layout;
|
||||||
|
|
||||||
|
let lambda_set = LambdaSet::from_var(layouts, subs, *lambda_set)?;
|
||||||
|
let lambda_set_index = Index::new(layouts.lambda_sets.len() as u32);
|
||||||
|
layouts.lambda_sets.push(lambda_set);
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
arguments_and_result: slice,
|
||||||
|
lambda_set: lambda_set_index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatType::Erroneous(_) => Err(TypeError(())),
|
||||||
|
|
||||||
|
_ => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn argument_slice(&self) -> Slice<Layout> {
|
||||||
|
let mut result = self.arguments_and_result;
|
||||||
|
result.length -= 1;
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
pub fn result_index(&self) -> Index<Layout> {
|
||||||
|
Index::new(self.arguments_and_result.start + self.arguments_and_result.length as u32 - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Idea: don't include the symbols for the first 3 cases in --optimize mode
|
||||||
|
pub enum LambdaSet {
|
||||||
|
Empty {
|
||||||
|
symbol: Index<Symbol>,
|
||||||
|
},
|
||||||
|
Single {
|
||||||
|
symbol: Index<Symbol>,
|
||||||
|
layout: Index<Layout>,
|
||||||
|
},
|
||||||
|
Struct {
|
||||||
|
symbol: Index<Symbol>,
|
||||||
|
layouts: Slice<Layout>,
|
||||||
|
},
|
||||||
|
Union {
|
||||||
|
symbols: Slice<Symbol>,
|
||||||
|
layouts: Slice<Slice<Layout>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LambdaSet {
|
||||||
|
pub fn from_var(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
// so we can set some things/clean up
|
||||||
|
Self::from_var_help(layouts, subs, var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_var_help(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
let content = &subs.get_ref(var).content;
|
||||||
|
Self::from_content(layouts, subs, var, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_content(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
content: &Content,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
use LayoutError::*;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
|
||||||
|
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
|
||||||
|
Content::RecursionVar { .. } => {
|
||||||
|
unreachable!("lambda sets cannot currently be recursive")
|
||||||
|
}
|
||||||
|
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
|
||||||
|
Content::Alias(_, _, actual) => Self::from_var_help(layouts, subs, *actual),
|
||||||
|
Content::Error => Err(TypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_flat_type(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
flat_type: &FlatType,
|
||||||
|
) -> Result<Self, LayoutError> {
|
||||||
|
use FlatType::*;
|
||||||
|
use LayoutError::*;
|
||||||
|
|
||||||
|
match flat_type {
|
||||||
|
TagUnion(union_tags, ext) => {
|
||||||
|
debug_assert!(ext_var_is_empty_tag_union(subs, *ext));
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
!union_tags.is_empty(),
|
||||||
|
"lambda set must contain atleast the function itself"
|
||||||
|
);
|
||||||
|
|
||||||
|
let tag_names = union_tags.tag_names();
|
||||||
|
let closure_names = Self::get_closure_names(layouts, subs, tag_names);
|
||||||
|
|
||||||
|
let variables = union_tags.variables();
|
||||||
|
if variables.len() == 1 {
|
||||||
|
let tag_name = &subs.tag_names[tag_names.start as usize];
|
||||||
|
let symbol = if let TagName::Closure(symbol) = tag_name {
|
||||||
|
let index = Index::new(layouts.symbols.len() as u32);
|
||||||
|
layouts.symbols.push(*symbol);
|
||||||
|
index
|
||||||
|
} else {
|
||||||
|
unreachable!("must be a closure tag")
|
||||||
|
};
|
||||||
|
let variable_slice = subs.variable_slices[variables.start as usize];
|
||||||
|
|
||||||
|
match variable_slice.len() {
|
||||||
|
0 => Ok(LambdaSet::Empty { symbol }),
|
||||||
|
1 => {
|
||||||
|
let var = subs.variables[variable_slice.start as usize];
|
||||||
|
let layout = Layout::from_var(layouts, subs, var)?;
|
||||||
|
|
||||||
|
let index = Index::new(layouts.layouts.len() as u32);
|
||||||
|
layouts.layouts.push(layout);
|
||||||
|
|
||||||
|
Ok(LambdaSet::Single {
|
||||||
|
symbol,
|
||||||
|
layout: index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let slice = Layout::from_variable_slice(layouts, subs, variable_slice)?;
|
||||||
|
|
||||||
|
Ok(LambdaSet::Struct {
|
||||||
|
symbol,
|
||||||
|
layouts: slice,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let layouts =
|
||||||
|
Layout::from_slice_variable_slice(layouts, subs, union_tags.variables())?;
|
||||||
|
|
||||||
|
Ok(LambdaSet::Union {
|
||||||
|
symbols: closure_names,
|
||||||
|
layouts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Erroneous(_) => Err(TypeError(())),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_closure_names(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
subs_slice: roc_types::subs::SubsSlice<TagName>,
|
||||||
|
) -> Slice<Symbol> {
|
||||||
|
let slice = Slice::new(layouts.symbols.len() as u32, subs_slice.len() as u16);
|
||||||
|
|
||||||
|
let tag_names = &subs.tag_names[subs_slice.indices()];
|
||||||
|
|
||||||
|
for tag_name in tag_names {
|
||||||
|
match tag_name {
|
||||||
|
TagName::Closure(symbol) => {
|
||||||
|
layouts.symbols.push(*symbol);
|
||||||
|
}
|
||||||
|
TagName::Global(_) => unreachable!("lambda set tags must be closure tags"),
|
||||||
|
TagName::Private(_) => unreachable!("lambda set tags must be closure tags"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
slice
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum Layout {
|
||||||
|
// theory: we can zero out memory to reserve space for many layouts
|
||||||
|
Reserved,
|
||||||
|
|
||||||
|
// Question: where to store signedness information?
|
||||||
|
Int(IntWidth),
|
||||||
|
Float(FloatWidth),
|
||||||
|
Decimal,
|
||||||
|
|
||||||
|
Str,
|
||||||
|
Dict(Index<(Layout, Layout)>),
|
||||||
|
Set(Index<Layout>),
|
||||||
|
List(Index<Layout>),
|
||||||
|
|
||||||
|
Struct(Slice<Layout>),
|
||||||
|
|
||||||
|
UnionNonRecursive(Slice<Slice<Layout>>),
|
||||||
|
|
||||||
|
Boxed(Index<Layout>),
|
||||||
|
UnionRecursive(Slice<Slice<Layout>>),
|
||||||
|
// UnionNonNullableUnwrapped(Slice<Layout>),
|
||||||
|
// UnionNullableWrapper {
|
||||||
|
// data: NullableUnionIndex,
|
||||||
|
// tag_id: u16,
|
||||||
|
// },
|
||||||
|
//
|
||||||
|
// UnionNullableUnwrappedTrue(Slice<Layout>),
|
||||||
|
// UnionNullableUnwrappedFalse(Slice<Layout>),
|
||||||
|
|
||||||
|
// RecursivePointer,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn round_up_to_alignment(unaligned: u16, alignment_bytes: u16) -> u16 {
|
||||||
|
let unaligned = unaligned as i32;
|
||||||
|
let alignment_bytes = alignment_bytes as i32;
|
||||||
|
if alignment_bytes <= 1 {
|
||||||
|
return unaligned as u16;
|
||||||
|
}
|
||||||
|
if alignment_bytes.count_ones() != 1 {
|
||||||
|
panic!(
|
||||||
|
"Cannot align to {} bytes. Not a power of 2.",
|
||||||
|
alignment_bytes
|
||||||
|
);
|
||||||
|
}
|
||||||
|
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 as u16
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layouts {
|
||||||
|
const VOID_INDEX: Index<Layout> = Index::new(0);
|
||||||
|
const VOID_TUPLE: Index<(Layout, Layout)> = Index::new(0);
|
||||||
|
const UNIT_INDEX: Index<Layout> = Index::new(2);
|
||||||
|
|
||||||
|
pub fn new(usize_int_width: IntWidth) -> Self {
|
||||||
|
let mut layouts = Vec::with_capacity(64);
|
||||||
|
|
||||||
|
layouts.push(Layout::VOID);
|
||||||
|
layouts.push(Layout::VOID);
|
||||||
|
layouts.push(Layout::UNIT);
|
||||||
|
|
||||||
|
// sanity check
|
||||||
|
debug_assert_eq!(layouts[Self::VOID_INDEX.index as usize], Layout::VOID);
|
||||||
|
debug_assert_eq!(layouts[Self::VOID_TUPLE.index as usize + 1], Layout::VOID);
|
||||||
|
debug_assert_eq!(layouts[Self::UNIT_INDEX.index as usize], Layout::UNIT);
|
||||||
|
|
||||||
|
Layouts {
|
||||||
|
layouts: Vec::default(),
|
||||||
|
layout_slices: Vec::default(),
|
||||||
|
lambda_sets: Vec::default(),
|
||||||
|
symbols: Vec::default(),
|
||||||
|
recursion_variable_to_structure_variable_map: MutMap::default(),
|
||||||
|
usize_int_width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sort a slice according to elements' alignment
|
||||||
|
fn sort_slice_by_alignment(&mut self, layout_slice: Slice<Layout>) {
|
||||||
|
let slice = &mut self.layouts[layout_slice.indices()];
|
||||||
|
|
||||||
|
// SAFETY: the align_of function does not mutate the layouts vector
|
||||||
|
// this unsafety is required to circumvent the borrow checker
|
||||||
|
let sneaky_slice =
|
||||||
|
unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr(), slice.len()) };
|
||||||
|
|
||||||
|
sneaky_slice.sort_by(|layout1, layout2| {
|
||||||
|
let align1 = self.align_of_layout(*layout1);
|
||||||
|
let align2 = self.align_of_layout(*layout2);
|
||||||
|
|
||||||
|
// we want the biggest alignment first
|
||||||
|
align2.cmp(&align1)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usize(&self) -> Layout {
|
||||||
|
Layout::Int(self.usize_int_width)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align_of_layout_index(&self, index: Index<Layout>) -> u16 {
|
||||||
|
let layout = self.layouts[index.index as usize];
|
||||||
|
|
||||||
|
self.align_of_layout(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align_of_layout(&self, layout: Layout) -> u16 {
|
||||||
|
let ptr_alignment = self.usize_int_width.alignment_bytes() as u16;
|
||||||
|
|
||||||
|
match layout {
|
||||||
|
Layout::Reserved => unreachable!(),
|
||||||
|
Layout::Int(int_width) => int_width.alignment_bytes() as u16,
|
||||||
|
Layout::Float(float_width) => float_width.alignment_bytes() as u16,
|
||||||
|
Layout::Decimal => IntWidth::U128.alignment_bytes() as u16,
|
||||||
|
Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => ptr_alignment,
|
||||||
|
Layout::Struct(slice) => self.align_of_layout_slice(slice),
|
||||||
|
Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_alignment,
|
||||||
|
Layout::UnionNonRecursive(slices) => {
|
||||||
|
let tag_id_align = IntWidth::I64.alignment_bytes() as u16;
|
||||||
|
|
||||||
|
self.align_of_layout_slices(slices).max(tag_id_align)
|
||||||
|
}
|
||||||
|
// Layout::UnionNonNullableUnwrapped(_) => todo!(),
|
||||||
|
// Layout::UnionNullableWrapper { data, tag_id } => todo!(),
|
||||||
|
// Layout::UnionNullableUnwrappedTrue(_) => todo!(),
|
||||||
|
// Layout::UnionNullableUnwrappedFalse(_) => todo!(),
|
||||||
|
// Layout::RecursivePointer => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invariant: the layouts are sorted from biggest to smallest alignment
|
||||||
|
fn align_of_layout_slice(&self, slice: Slice<Layout>) -> u16 {
|
||||||
|
match slice.into_iter().next() {
|
||||||
|
None => 0,
|
||||||
|
Some(first_index) => self.align_of_layout_index(first_index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn align_of_layout_slices(&self, slice: Slice<Slice<Layout>>) -> u16 {
|
||||||
|
slice
|
||||||
|
.into_iter()
|
||||||
|
.map(|index| self.layout_slices[index.index as usize])
|
||||||
|
.map(|slice| self.align_of_layout_slice(slice))
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Invariant: the layouts are sorted from biggest to smallest alignment
|
||||||
|
fn size_of_layout_slice(&self, slice: Slice<Layout>) -> u16 {
|
||||||
|
match slice.into_iter().next() {
|
||||||
|
None => 0,
|
||||||
|
Some(first_index) => {
|
||||||
|
let alignment = self.align_of_layout_index(first_index);
|
||||||
|
|
||||||
|
let mut sum = 0;
|
||||||
|
|
||||||
|
for index in slice.into_iter() {
|
||||||
|
sum += self.size_of_layout_index(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
round_up_to_alignment(sum, alignment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_of_layout_index(&self, index: Index<Layout>) -> u16 {
|
||||||
|
let layout = self.layouts[index.index as usize];
|
||||||
|
|
||||||
|
self.size_of_layout(layout)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size_of_layout(&self, layout: Layout) -> u16 {
|
||||||
|
let ptr_width = self.usize_int_width.stack_size() as u16;
|
||||||
|
|
||||||
|
match layout {
|
||||||
|
Layout::Reserved => unreachable!(),
|
||||||
|
Layout::Int(int_width) => int_width.stack_size() as _,
|
||||||
|
Layout::Float(float_width) => float_width as _,
|
||||||
|
Layout::Decimal => (std::mem::size_of::<roc_std::RocDec>()) as _,
|
||||||
|
Layout::Str | Layout::Dict(_) | Layout::Set(_) | Layout::List(_) => 2 * ptr_width,
|
||||||
|
Layout::Struct(slice) => self.size_of_layout_slice(slice),
|
||||||
|
Layout::Boxed(_) | Layout::UnionRecursive(_) => ptr_width,
|
||||||
|
Layout::UnionNonRecursive(slices) if slices.is_empty() => 0,
|
||||||
|
Layout::UnionNonRecursive(slices) => {
|
||||||
|
let tag_id = IntWidth::I64;
|
||||||
|
|
||||||
|
let max_slice_size = slices
|
||||||
|
.into_iter()
|
||||||
|
.map(|index| self.layout_slices[index.index as usize])
|
||||||
|
.map(|slice| self.align_of_layout_slice(slice))
|
||||||
|
.max()
|
||||||
|
.unwrap_or_default();
|
||||||
|
|
||||||
|
tag_id.stack_size() as u16 + max_slice_size
|
||||||
|
}
|
||||||
|
// Layout::UnionNonNullableUnwrapped(_) => todo!(),
|
||||||
|
// Layout::UnionNullableWrapper { data, tag_id } => todo!(),
|
||||||
|
// Layout::UnionNullableUnwrappedTrue(_) => todo!(),
|
||||||
|
// Layout::UnionNullableUnwrappedFalse(_) => todo!(),
|
||||||
|
// Layout::RecursivePointer => todo!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum LayoutError {
|
||||||
|
UnresolvedVariable(Variable),
|
||||||
|
TypeError(()),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Layout {
|
||||||
|
pub const UNIT: Self = Self::Struct(Slice::new(0, 0));
|
||||||
|
pub const VOID: Self = Self::UnionNonRecursive(Slice::new(0, 0));
|
||||||
|
|
||||||
|
pub const EMPTY_LIST: Self = Self::List(Layouts::VOID_INDEX);
|
||||||
|
pub const EMPTY_DICT: Self = Self::Dict(Layouts::VOID_TUPLE);
|
||||||
|
pub const EMPTY_SET: Self = Self::Set(Layouts::VOID_INDEX);
|
||||||
|
|
||||||
|
pub fn from_var(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Result<Layout, LayoutError> {
|
||||||
|
// so we can set some things/clean up
|
||||||
|
Self::from_var_help(layouts, subs, var)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_var_help(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Result<Layout, LayoutError> {
|
||||||
|
let content = &subs.get_ref(var).content;
|
||||||
|
Self::from_content(layouts, subs, var, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Used in situations where an unspecialized variable is not a problem,
|
||||||
|
/// and we can substitute with `[]`, the empty tag union.
|
||||||
|
/// e.g. an empty list literal has type `List *`. We can still generate code
|
||||||
|
/// in those cases by just picking any concrete type for the list element,
|
||||||
|
/// and we pick the empty tag union in practice.
|
||||||
|
fn from_var_help_or_void(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
) -> Result<Layout, LayoutError> {
|
||||||
|
let content = &subs.get_ref(var).content;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
Content::FlexVar(_) | Content::RigidVar(_) => Ok(Layout::VOID),
|
||||||
|
|
||||||
|
_ => Self::from_content(layouts, subs, var, content),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_content(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
var: Variable,
|
||||||
|
content: &Content,
|
||||||
|
) -> Result<Layout, LayoutError> {
|
||||||
|
use LayoutError::*;
|
||||||
|
|
||||||
|
match content {
|
||||||
|
Content::FlexVar(_) => Err(UnresolvedVariable(var)),
|
||||||
|
Content::RigidVar(_) => Err(UnresolvedVariable(var)),
|
||||||
|
Content::RecursionVar {
|
||||||
|
structure,
|
||||||
|
opt_name: _,
|
||||||
|
} => {
|
||||||
|
let structure = subs.get_root_key_without_compacting(*structure);
|
||||||
|
|
||||||
|
let entry = layouts
|
||||||
|
.recursion_variable_to_structure_variable_map
|
||||||
|
.entry(structure);
|
||||||
|
|
||||||
|
match entry {
|
||||||
|
Entry::Vacant(vacant) => {
|
||||||
|
let reserved = Index::new(layouts.layouts.len() as _);
|
||||||
|
layouts.layouts.push(Layout::Reserved);
|
||||||
|
|
||||||
|
vacant.insert(reserved);
|
||||||
|
|
||||||
|
let layout = Layout::from_var(layouts, subs, structure)?;
|
||||||
|
|
||||||
|
layouts.layouts[reserved.index as usize] = layout;
|
||||||
|
|
||||||
|
Ok(Layout::Boxed(reserved))
|
||||||
|
}
|
||||||
|
Entry::Occupied(occupied) => {
|
||||||
|
let index = occupied.get();
|
||||||
|
|
||||||
|
Ok(Layout::Boxed(*index))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Content::Structure(flat_type) => Self::from_flat_type(layouts, subs, flat_type),
|
||||||
|
Content::Alias(symbol, _, actual) => {
|
||||||
|
let symbol = *symbol;
|
||||||
|
|
||||||
|
if let Some(int_width) = IntWidth::try_from_symbol(symbol) {
|
||||||
|
return Ok(Layout::Int(int_width));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(float_width) = FloatWidth::try_from_symbol(symbol) {
|
||||||
|
return Ok(Layout::Float(float_width));
|
||||||
|
}
|
||||||
|
|
||||||
|
match symbol {
|
||||||
|
Symbol::NUM_DECIMAL | Symbol::NUM_AT_DECIMAL => Ok(Layout::Decimal),
|
||||||
|
|
||||||
|
Symbol::NUM_NAT | Symbol::NUM_NATURAL | Symbol::NUM_AT_NATURAL => {
|
||||||
|
Ok(layouts.usize())
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
// at this point we throw away alias information
|
||||||
|
Self::from_var_help(layouts, subs, *actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Content::Error => Err(TypeError(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_flat_type(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
flat_type: &FlatType,
|
||||||
|
) -> Result<Layout, LayoutError> {
|
||||||
|
use LayoutError::*;
|
||||||
|
|
||||||
|
match flat_type {
|
||||||
|
FlatType::Apply(Symbol::LIST_LIST, arguments) => {
|
||||||
|
debug_assert_eq!(arguments.len(), 1);
|
||||||
|
|
||||||
|
let element_var = subs.variables[arguments.start as usize];
|
||||||
|
let element_layout = Self::from_var_help_or_void(layouts, subs, element_var)?;
|
||||||
|
|
||||||
|
let element_index = Index::new(layouts.layouts.len() as _);
|
||||||
|
layouts.layouts.push(element_layout);
|
||||||
|
|
||||||
|
Ok(Layout::List(element_index))
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatType::Apply(Symbol::DICT_DICT, arguments) => {
|
||||||
|
debug_assert_eq!(arguments.len(), 2);
|
||||||
|
|
||||||
|
let key_var = subs.variables[arguments.start as usize];
|
||||||
|
let value_var = subs.variables[arguments.start as usize + 1];
|
||||||
|
|
||||||
|
let key_layout = Self::from_var_help_or_void(layouts, subs, key_var)?;
|
||||||
|
let value_layout = Self::from_var_help_or_void(layouts, subs, value_var)?;
|
||||||
|
|
||||||
|
let index = Index::new(layouts.layouts.len() as _);
|
||||||
|
layouts.layouts.push(key_layout);
|
||||||
|
layouts.layouts.push(value_layout);
|
||||||
|
|
||||||
|
Ok(Layout::Dict(index))
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatType::Apply(Symbol::SET_SET, arguments) => {
|
||||||
|
debug_assert_eq!(arguments.len(), 1);
|
||||||
|
|
||||||
|
let element_var = subs.variables[arguments.start as usize];
|
||||||
|
let element_layout = Self::from_var_help_or_void(layouts, subs, element_var)?;
|
||||||
|
|
||||||
|
let element_index = Index::new(layouts.layouts.len() as _);
|
||||||
|
layouts.layouts.push(element_layout);
|
||||||
|
|
||||||
|
Ok(Layout::Set(element_index))
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatType::Apply(symbol, _) => {
|
||||||
|
unreachable!("Symbol {:?} does not have a layout", symbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatType::Func(_arguments, lambda_set, _result) => {
|
||||||
|
// in this case, a function (pointer) is represented by the environment it
|
||||||
|
// captures: the lambda set
|
||||||
|
|
||||||
|
Self::from_var_help(layouts, subs, *lambda_set)
|
||||||
|
}
|
||||||
|
FlatType::Record(fields, ext) => {
|
||||||
|
debug_assert!(ext_var_is_empty_record(subs, *ext));
|
||||||
|
|
||||||
|
let mut slice = Slice::reserve(layouts, fields.len());
|
||||||
|
|
||||||
|
let mut non_optional_fields = 0;
|
||||||
|
let it = slice.indices().zip(fields.iter_all());
|
||||||
|
for (target_index, (_, field_index, var_index)) in it {
|
||||||
|
match subs.record_fields[field_index.index as usize] {
|
||||||
|
RecordField::Optional(_) => {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
RecordField::Required(_) | RecordField::Demanded(_) => {
|
||||||
|
let var = subs.variables[var_index.index as usize];
|
||||||
|
let layout = Layout::from_var_help(layouts, subs, var)?;
|
||||||
|
|
||||||
|
layouts.layouts[target_index] = layout;
|
||||||
|
|
||||||
|
non_optional_fields += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have some wasted space in the case of optional fields; so be it
|
||||||
|
slice.length = non_optional_fields;
|
||||||
|
|
||||||
|
layouts.sort_slice_by_alignment(slice);
|
||||||
|
|
||||||
|
Ok(Layout::Struct(slice))
|
||||||
|
}
|
||||||
|
FlatType::TagUnion(union_tags, ext) => {
|
||||||
|
debug_assert!(ext_var_is_empty_tag_union(subs, *ext));
|
||||||
|
|
||||||
|
let slices =
|
||||||
|
Self::from_slice_variable_slice(layouts, subs, union_tags.variables())?;
|
||||||
|
|
||||||
|
Ok(Layout::UnionNonRecursive(slices))
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatType::FunctionOrTagUnion(_, _, ext) => {
|
||||||
|
debug_assert!(ext_var_is_empty_tag_union(subs, *ext));
|
||||||
|
|
||||||
|
// at this point we know this is a tag
|
||||||
|
Ok(Layout::UNIT)
|
||||||
|
}
|
||||||
|
FlatType::RecursiveTagUnion(rec_var, union_tags, ext) => {
|
||||||
|
debug_assert!(ext_var_is_empty_tag_union(subs, *ext));
|
||||||
|
|
||||||
|
let rec_var = subs.get_root_key_without_compacting(*rec_var);
|
||||||
|
|
||||||
|
let cached = layouts
|
||||||
|
.recursion_variable_to_structure_variable_map
|
||||||
|
.get(&rec_var);
|
||||||
|
|
||||||
|
if let Some(layout_index) = cached {
|
||||||
|
match layouts.layouts[layout_index.index as usize] {
|
||||||
|
Layout::Reserved => {
|
||||||
|
// we have to do the work here to fill this reserved variable in
|
||||||
|
}
|
||||||
|
other => {
|
||||||
|
return Ok(other);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let slices =
|
||||||
|
Self::from_slice_variable_slice(layouts, subs, union_tags.variables())?;
|
||||||
|
|
||||||
|
Ok(Layout::UnionRecursive(slices))
|
||||||
|
}
|
||||||
|
FlatType::Erroneous(_) => Err(TypeError(())),
|
||||||
|
FlatType::EmptyRecord => Ok(Layout::UNIT),
|
||||||
|
FlatType::EmptyTagUnion => Ok(Layout::VOID),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_slice_variable_slice(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
slice_variable_slice: roc_types::subs::SubsSlice<roc_types::subs::VariableSubsSlice>,
|
||||||
|
) -> Result<Slice<Slice<Layout>>, LayoutError> {
|
||||||
|
let slice = Slice::reserve(layouts, slice_variable_slice.len());
|
||||||
|
|
||||||
|
let variable_slices = &subs.variable_slices[slice_variable_slice.indices()];
|
||||||
|
let it = slice.indices().zip(variable_slices);
|
||||||
|
for (target_index, variable_slice) in it {
|
||||||
|
let layout_slice = Layout::from_variable_slice(layouts, subs, *variable_slice)?;
|
||||||
|
layouts.layout_slices[target_index] = layout_slice;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_variable_slice(
|
||||||
|
layouts: &mut Layouts,
|
||||||
|
subs: &Subs,
|
||||||
|
variable_subs_slice: roc_types::subs::VariableSubsSlice,
|
||||||
|
) -> Result<Slice<Layout>, LayoutError> {
|
||||||
|
let slice = Slice::reserve(layouts, variable_subs_slice.len());
|
||||||
|
|
||||||
|
let variable_slice = &subs.variables[variable_subs_slice.indices()];
|
||||||
|
let it = slice.indices().zip(variable_slice);
|
||||||
|
for (target_index, var) in it {
|
||||||
|
let layout = Layout::from_var_help(layouts, subs, *var)?;
|
||||||
|
layouts.layouts[target_index] = layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
layouts.sort_slice_by_alignment(slice);
|
||||||
|
|
||||||
|
Ok(slice)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,9 +4,11 @@
|
||||||
|
|
||||||
pub mod alias_analysis;
|
pub mod alias_analysis;
|
||||||
pub mod borrow;
|
pub mod borrow;
|
||||||
|
pub mod gen_refcount;
|
||||||
pub mod inc_dec;
|
pub mod inc_dec;
|
||||||
pub mod ir;
|
pub mod ir;
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
|
pub mod layout_soa;
|
||||||
pub mod low_level;
|
pub mod low_level;
|
||||||
pub mod reset_reuse;
|
pub mod reset_reuse;
|
||||||
pub mod tail_recursion;
|
pub mod tail_recursion;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet};
|
use crate::inc_dec::{collect_stmt, occurring_variables_expr, JPLiveVarMap, LiveVarSet};
|
||||||
use crate::ir::{BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt};
|
use crate::ir::{
|
||||||
|
BranchInfo, Call, Expr, ListLiteralElement, Proc, Stmt, UpdateModeId, UpdateModeIds,
|
||||||
|
};
|
||||||
use crate::layout::{Layout, TagIdIntType, UnionLayout};
|
use crate::layout::{Layout, TagIdIntType, UnionLayout};
|
||||||
use bumpalo::collections::Vec;
|
use bumpalo::collections::Vec;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
@ -10,12 +12,14 @@ pub fn insert_reset_reuse<'a, 'i>(
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
home: ModuleId,
|
home: ModuleId,
|
||||||
ident_ids: &'i mut IdentIds,
|
ident_ids: &'i mut IdentIds,
|
||||||
|
update_mode_ids: &'i mut UpdateModeIds,
|
||||||
mut proc: Proc<'a>,
|
mut proc: Proc<'a>,
|
||||||
) -> Proc<'a> {
|
) -> Proc<'a> {
|
||||||
let mut env = Env {
|
let mut env = Env {
|
||||||
arena,
|
arena,
|
||||||
home,
|
home,
|
||||||
ident_ids,
|
ident_ids,
|
||||||
|
update_mode_ids,
|
||||||
jp_live_vars: Default::default(),
|
jp_live_vars: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -50,6 +54,7 @@ struct Env<'a, 'i> {
|
||||||
/// required for creating new `Symbol`s
|
/// required for creating new `Symbol`s
|
||||||
home: ModuleId,
|
home: ModuleId,
|
||||||
ident_ids: &'i mut IdentIds,
|
ident_ids: &'i mut IdentIds,
|
||||||
|
update_mode_ids: &'i mut UpdateModeIds,
|
||||||
|
|
||||||
jp_live_vars: JPLiveVarMap,
|
jp_live_vars: JPLiveVarMap,
|
||||||
}
|
}
|
||||||
|
@ -64,7 +69,7 @@ impl<'a, 'i> Env<'a, 'i> {
|
||||||
|
|
||||||
fn function_s<'a, 'i>(
|
fn function_s<'a, 'i>(
|
||||||
env: &mut Env<'a, 'i>,
|
env: &mut Env<'a, 'i>,
|
||||||
w: Symbol,
|
w: Opportunity,
|
||||||
c: &CtorInfo<'a>,
|
c: &CtorInfo<'a>,
|
||||||
stmt: &'a Stmt<'a>,
|
stmt: &'a Stmt<'a>,
|
||||||
) -> &'a Stmt<'a> {
|
) -> &'a Stmt<'a> {
|
||||||
|
@ -84,7 +89,8 @@ fn function_s<'a, 'i>(
|
||||||
let update_tag_id = true;
|
let update_tag_id = true;
|
||||||
|
|
||||||
let new_expr = Expr::Reuse {
|
let new_expr = Expr::Reuse {
|
||||||
symbol: w,
|
symbol: w.symbol,
|
||||||
|
update_mode: w.update_mode,
|
||||||
update_tag_id,
|
update_tag_id,
|
||||||
tag_layout: *tag_layout,
|
tag_layout: *tag_layout,
|
||||||
tag_id: *tag_id,
|
tag_id: *tag_id,
|
||||||
|
@ -175,13 +181,22 @@ fn function_s<'a, 'i>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct Opportunity {
|
||||||
|
symbol: Symbol,
|
||||||
|
update_mode: UpdateModeId,
|
||||||
|
}
|
||||||
|
|
||||||
fn try_function_s<'a, 'i>(
|
fn try_function_s<'a, 'i>(
|
||||||
env: &mut Env<'a, 'i>,
|
env: &mut Env<'a, 'i>,
|
||||||
x: Symbol,
|
x: Symbol,
|
||||||
c: &CtorInfo<'a>,
|
c: &CtorInfo<'a>,
|
||||||
stmt: &'a Stmt<'a>,
|
stmt: &'a Stmt<'a>,
|
||||||
) -> &'a Stmt<'a> {
|
) -> &'a Stmt<'a> {
|
||||||
let w = env.unique_symbol();
|
let w = Opportunity {
|
||||||
|
symbol: env.unique_symbol(),
|
||||||
|
update_mode: env.update_mode_ids.next_id(),
|
||||||
|
};
|
||||||
|
|
||||||
let new_stmt = function_s(env, w, c, stmt);
|
let new_stmt = function_s(env, w, c, stmt);
|
||||||
|
|
||||||
|
@ -194,7 +209,7 @@ fn try_function_s<'a, 'i>(
|
||||||
|
|
||||||
fn insert_reset<'a>(
|
fn insert_reset<'a>(
|
||||||
env: &mut Env<'a, '_>,
|
env: &mut Env<'a, '_>,
|
||||||
w: Symbol,
|
w: Opportunity,
|
||||||
x: Symbol,
|
x: Symbol,
|
||||||
union_layout: UnionLayout<'a>,
|
union_layout: UnionLayout<'a>,
|
||||||
mut stmt: &'a Stmt<'a>,
|
mut stmt: &'a Stmt<'a>,
|
||||||
|
@ -216,16 +231,21 @@ fn insert_reset<'a>(
|
||||||
| Array { .. }
|
| Array { .. }
|
||||||
| EmptyArray
|
| EmptyArray
|
||||||
| Reuse { .. }
|
| Reuse { .. }
|
||||||
| Reset(_)
|
| Reset { .. }
|
||||||
| RuntimeErrorFunction(_) => break,
|
| RuntimeErrorFunction(_) => break,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let reset_expr = Expr::Reset(x);
|
let reset_expr = Expr::Reset {
|
||||||
|
symbol: x,
|
||||||
|
update_mode: w.update_mode,
|
||||||
|
};
|
||||||
|
|
||||||
let layout = Layout::Union(union_layout);
|
let layout = Layout::Union(union_layout);
|
||||||
|
|
||||||
stmt = env.arena.alloc(Stmt::Let(w, reset_expr, layout, stmt));
|
stmt = env
|
||||||
|
.arena
|
||||||
|
.alloc(Stmt::Let(w.symbol, reset_expr, layout, stmt));
|
||||||
|
|
||||||
for (symbol, expr, expr_layout) in stack.into_iter().rev() {
|
for (symbol, expr, expr_layout) in stack.into_iter().rev() {
|
||||||
stmt = env
|
stmt = env
|
||||||
|
@ -584,7 +604,7 @@ fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool {
|
||||||
Expr::Reuse {
|
Expr::Reuse {
|
||||||
symbol, arguments, ..
|
symbol, arguments, ..
|
||||||
} => needle == *symbol || arguments.iter().any(|s| *s == needle),
|
} => needle == *symbol || arguments.iter().any(|s| *s == needle),
|
||||||
Expr::Reset(symbol) => needle == *symbol,
|
Expr::Reset { symbol, .. } => needle == *symbol,
|
||||||
Expr::RuntimeErrorFunction(_) => false,
|
Expr::RuntimeErrorFunction(_) => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,4 @@ pretty_assertions = "1.0.0"
|
||||||
indoc = "1.0.3"
|
indoc = "1.0.3"
|
||||||
quickcheck = "1.0.3"
|
quickcheck = "1.0.3"
|
||||||
quickcheck_macros = "1.0.0"
|
quickcheck_macros = "1.0.0"
|
||||||
diff = "0.1.12"
|
roc_test_utils = { path = "../../test_utils" }
|
||||||
ansi_term = "0.12.1"
|
|
||||||
|
|
|
@ -23,6 +23,7 @@ mod test_parse {
|
||||||
use roc_parse::parser::{Parser, State, SyntaxError};
|
use roc_parse::parser::{Parser, State, SyntaxError};
|
||||||
use roc_parse::test_helpers::parse_expr_with;
|
use roc_parse::test_helpers::parse_expr_with;
|
||||||
use roc_region::all::{Located, Region};
|
use roc_region::all::{Located, Region};
|
||||||
|
use roc_test_utils::assert_multiline_str_eq;
|
||||||
use std::{f64, i64};
|
use std::{f64, i64};
|
||||||
|
|
||||||
macro_rules! snapshot_tests {
|
macro_rules! snapshot_tests {
|
||||||
|
@ -254,10 +255,7 @@ mod test_parse {
|
||||||
} else {
|
} else {
|
||||||
let expected_result = std::fs::read_to_string(&result_path).unwrap();
|
let expected_result = std::fs::read_to_string(&result_path).unwrap();
|
||||||
|
|
||||||
// TODO: do a diff over the "real" content of these strings, rather than
|
assert_multiline_str_eq!(expected_result, actual_result);
|
||||||
// the debug-formatted content. As is, we get an ugly single-line diff
|
|
||||||
// from pretty_assertions
|
|
||||||
assert_eq!(expected_result, actual_result);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -837,25 +837,13 @@ fn type_to_variable<'a>(
|
||||||
actual,
|
actual,
|
||||||
lambda_set_variables,
|
lambda_set_variables,
|
||||||
} => {
|
} => {
|
||||||
// the rank of these variables is NONE (encoded as 0 in practice)
|
if let Some(reserved) = Variable::get_reserved(*symbol) {
|
||||||
// using them for other ranks causes issues
|
|
||||||
if rank.is_none() {
|
if rank.is_none() {
|
||||||
// TODO replace by arithmetic?
|
// reserved variables are stored with rank NONE
|
||||||
match *symbol {
|
return reserved;
|
||||||
Symbol::NUM_I128 => return Variable::I128,
|
} else {
|
||||||
Symbol::NUM_I64 => return Variable::I64,
|
// for any other rank, we need to copy; it takes care of adjusting the rank
|
||||||
Symbol::NUM_I32 => return Variable::I32,
|
return deep_copy_var(subs, rank, pools, reserved);
|
||||||
Symbol::NUM_I16 => return Variable::I16,
|
|
||||||
Symbol::NUM_I8 => return Variable::I8,
|
|
||||||
|
|
||||||
Symbol::NUM_U128 => return Variable::U128,
|
|
||||||
Symbol::NUM_U64 => return Variable::U64,
|
|
||||||
Symbol::NUM_U32 => return Variable::U32,
|
|
||||||
Symbol::NUM_U16 => return Variable::U16,
|
|
||||||
Symbol::NUM_U8 => return Variable::U8,
|
|
||||||
|
|
||||||
Symbol::NUM_NAT => return Variable::NAT,
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -868,7 +856,11 @@ fn type_to_variable<'a>(
|
||||||
lambda_set_variables,
|
lambda_set_variables,
|
||||||
);
|
);
|
||||||
|
|
||||||
let alias_variable = type_to_variable(subs, rank, pools, arena, actual);
|
let alias_variable = if let Symbol::RESULT_RESULT = *symbol {
|
||||||
|
roc_result_to_var(subs, rank, pools, arena, actual)
|
||||||
|
} else {
|
||||||
|
type_to_variable(subs, rank, pools, arena, actual)
|
||||||
|
};
|
||||||
let content = Content::Alias(*symbol, alias_variables, alias_variable);
|
let content = Content::Alias(*symbol, alias_variables, alias_variable);
|
||||||
|
|
||||||
register(subs, rank, pools, content)
|
register(subs, rank, pools, content)
|
||||||
|
@ -941,6 +933,52 @@ fn alias_to_var<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn roc_result_to_var<'a>(
|
||||||
|
subs: &mut Subs,
|
||||||
|
rank: Rank,
|
||||||
|
pools: &mut Pools,
|
||||||
|
arena: &'a bumpalo::Bump,
|
||||||
|
result_type: &Type,
|
||||||
|
) -> Variable {
|
||||||
|
match result_type {
|
||||||
|
Type::TagUnion(tags, ext) => {
|
||||||
|
debug_assert!(ext.is_empty_tag_union());
|
||||||
|
debug_assert!(tags.len() == 2);
|
||||||
|
|
||||||
|
if let [(err, err_args), (ok, ok_args)] = &tags[..] {
|
||||||
|
debug_assert_eq!(err, &subs.tag_names[0]);
|
||||||
|
debug_assert_eq!(ok, &subs.tag_names[1]);
|
||||||
|
|
||||||
|
if let ([err_type], [ok_type]) = (err_args.as_slice(), ok_args.as_slice()) {
|
||||||
|
let err_var = type_to_variable(subs, rank, pools, arena, err_type);
|
||||||
|
let ok_var = type_to_variable(subs, rank, pools, arena, ok_type);
|
||||||
|
|
||||||
|
let start = subs.variables.len() as u32;
|
||||||
|
let err_slice = SubsSlice::new(start, 1);
|
||||||
|
let ok_slice = SubsSlice::new(start + 1, 1);
|
||||||
|
|
||||||
|
subs.variables.push(err_var);
|
||||||
|
subs.variables.push(ok_var);
|
||||||
|
|
||||||
|
let variables = SubsSlice::new(subs.variable_slices.len() as _, 2);
|
||||||
|
subs.variable_slices.push(err_slice);
|
||||||
|
subs.variable_slices.push(ok_slice);
|
||||||
|
|
||||||
|
let union_tags = UnionTags::from_slices(Subs::RESULT_TAG_NAMES, variables);
|
||||||
|
let ext = Variable::EMPTY_TAG_UNION;
|
||||||
|
|
||||||
|
let content = Content::Structure(FlatType::TagUnion(union_tags, ext));
|
||||||
|
|
||||||
|
return register(subs, rank, pools, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!("invalid arguments to Result.Result; canonicalization should catch this!")
|
||||||
|
}
|
||||||
|
_ => unreachable!("not a valid type inside a Result.Result alias"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)
|
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)
|
||||||
where
|
where
|
||||||
F: FnMut(&T, &T) -> std::cmp::Ordering,
|
F: FnMut(&T, &T) -> std::cmp::Ordering,
|
||||||
|
|
|
@ -228,10 +228,10 @@ mod solve_expr {
|
||||||
infer_eq_without_problem(
|
infer_eq_without_problem(
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
Str.fromInt
|
Num.toStr
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
"Int * -> Str",
|
"Num * -> Str",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4543,8 +4543,8 @@ mod solve_expr {
|
||||||
|> Str.concat ") ("
|
|> Str.concat ") ("
|
||||||
|> Str.concat (printExpr b)
|
|> Str.concat (printExpr b)
|
||||||
|> Str.concat ")"
|
|> Str.concat ")"
|
||||||
Val v -> Str.fromInt v
|
Val v -> Num.toStr v
|
||||||
Var v -> "Var " |> Str.concat (Str.fromInt v)
|
Var v -> "Var " |> Str.concat (Num.toStr v)
|
||||||
|
|
||||||
main : Str
|
main : Str
|
||||||
main = printExpr (Var 3)
|
main = printExpr (Var 3)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#![cfg(feature = "gen-llvm")]
|
#![cfg(feature = "gen-llvm")]
|
||||||
|
#![cfg(feature = "gen-wasm")]
|
||||||
|
|
||||||
#[cfg(feature = "gen-llvm")]
|
#[cfg(feature = "gen-llvm")]
|
||||||
use crate::helpers::llvm::assert_evals_to;
|
use crate::helpers::llvm::assert_evals_to;
|
||||||
|
@ -6,8 +7,8 @@ use crate::helpers::llvm::assert_evals_to;
|
||||||
// #[cfg(feature = "gen-dev")]
|
// #[cfg(feature = "gen-dev")]
|
||||||
// use crate::helpers::dev::assert_evals_to;
|
// use crate::helpers::dev::assert_evals_to;
|
||||||
|
|
||||||
// #[cfg(feature = "gen-wasm")]
|
#[cfg(feature = "gen-wasm")]
|
||||||
// use crate::helpers::wasm::assert_evals_to;
|
use crate::helpers::wasm::assert_evals_to;
|
||||||
|
|
||||||
use crate::helpers::with_larger_debug_stack;
|
use crate::helpers::with_larger_debug_stack;
|
||||||
//use crate::assert_wasm_evals_to as assert_evals_to;
|
//use crate::assert_wasm_evals_to as assert_evals_to;
|
||||||
|
@ -22,7 +23,7 @@ fn roc_list_construction() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn empty_list_literal() {
|
fn empty_list_literal() {
|
||||||
assert_evals_to!("[]", RocList::from_slice(&[]), RocList<i64>);
|
assert_evals_to!("[]", RocList::from_slice(&[]), RocList<i64>);
|
||||||
}
|
}
|
||||||
|
@ -34,6 +35,7 @@ fn list_literal_empty_record() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
fn int_singleton_list_literal() {
|
fn int_singleton_list_literal() {
|
||||||
assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList<i64>);
|
assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList<i64>);
|
||||||
}
|
}
|
||||||
|
|
|
@ -520,7 +520,7 @@ fn f64_log_negative() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||||
fn f64_round() {
|
fn f64_round() {
|
||||||
assert_evals_to!("Num.round 3.6", 4, i64);
|
assert_evals_to!("Num.round 3.6", 4, i64);
|
||||||
assert_evals_to!("Num.round 3.4", 3, i64);
|
assert_evals_to!("Num.round 3.4", 3, i64);
|
||||||
|
@ -813,7 +813,7 @@ fn gen_add_i64() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||||
fn gen_sub_dec() {
|
fn gen_sub_dec() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -1171,7 +1171,7 @@ fn gen_order_of_arithmetic_ops_complex_float() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||||
fn if_guard_bind_variable_false() {
|
fn if_guard_bind_variable_false() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -1190,7 +1190,7 @@ fn if_guard_bind_variable_false() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||||
fn if_guard_bind_variable_true() {
|
fn if_guard_bind_variable_true() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -1990,3 +1990,35 @@ fn when_on_i16() {
|
||||||
i16
|
i16
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
|
fn num_to_str() {
|
||||||
|
use roc_std::RocStr;
|
||||||
|
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"Num.toStr 1234"#,
|
||||||
|
RocStr::from_slice("1234".as_bytes()),
|
||||||
|
RocStr
|
||||||
|
);
|
||||||
|
assert_evals_to!(r#"Num.toStr 0"#, RocStr::from_slice("0".as_bytes()), RocStr);
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"Num.toStr -1"#,
|
||||||
|
RocStr::from_slice("-1".as_bytes()),
|
||||||
|
RocStr
|
||||||
|
);
|
||||||
|
|
||||||
|
let max = format!("{}", i64::MAX);
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"Num.toStr Num.maxInt"#,
|
||||||
|
RocStr::from_slice(max.as_bytes()),
|
||||||
|
RocStr
|
||||||
|
);
|
||||||
|
|
||||||
|
let min = format!("{}", i64::MIN);
|
||||||
|
assert_evals_to!(
|
||||||
|
r#"Num.toStr Num.minInt"#,
|
||||||
|
RocStr::from_slice(min.as_bytes()),
|
||||||
|
RocStr
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -996,7 +996,7 @@ fn annotation_without_body() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||||
fn simple_closure() {
|
fn simple_closure() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
@ -2602,7 +2602,7 @@ fn hit_unresolved_type_variable() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||||
fn pattern_match_empty_record() {
|
fn pattern_match_empty_record() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
|
|
@ -254,7 +254,7 @@ fn twice_record_access() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
|
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
|
||||||
fn empty_record() {
|
fn empty_record() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(
|
indoc!(
|
||||||
|
|
|
@ -527,40 +527,6 @@ fn str_starts_with_false_small_str() {
|
||||||
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
|
||||||
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]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
fn str_from_utf8_pass_single_ascii() {
|
fn str_from_utf8_pass_single_ascii() {
|
||||||
|
@ -838,8 +804,8 @@ fn nested_recursive_literal() {
|
||||||
|> Str.concat ") ("
|
|> Str.concat ") ("
|
||||||
|> Str.concat (printExpr b)
|
|> Str.concat (printExpr b)
|
||||||
|> Str.concat ")"
|
|> Str.concat ")"
|
||||||
Val v -> "Val " |> Str.concat (Str.fromInt v)
|
Val v -> "Val " |> Str.concat (Num.toStr v)
|
||||||
Var v -> "Var " |> Str.concat (Str.fromInt v)
|
Var v -> "Var " |> Str.concat (Num.toStr v)
|
||||||
|
|
||||||
printExpr expr
|
printExpr expr
|
||||||
"#
|
"#
|
||||||
|
@ -875,12 +841,6 @@ fn str_join_comma_single() {
|
||||||
assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
|
assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
|
||||||
fn str_from_float() {
|
|
||||||
assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(any(feature = "gen-llvm"))]
|
#[cfg(any(feature = "gen-llvm"))]
|
||||||
fn str_to_utf8() {
|
fn str_to_utf8() {
|
||||||
|
|
|
@ -188,8 +188,7 @@ pub fn helper(
|
||||||
};
|
};
|
||||||
|
|
||||||
let target = target_lexicon::Triple::host();
|
let target = target_lexicon::Triple::host();
|
||||||
let module_object =
|
let module_object = roc_gen_dev::build_module(&env, &target, procedures);
|
||||||
roc_gen_dev::build_module(&env, &target, procedures).expect("failed to compile module");
|
|
||||||
|
|
||||||
let module_out = module_object
|
let module_out = module_object
|
||||||
.write()
|
.write()
|
||||||
|
|
|
@ -79,8 +79,9 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
||||||
|
|
||||||
use roc_load::file::MonomorphizedModule;
|
use roc_load::file::MonomorphizedModule;
|
||||||
let MonomorphizedModule {
|
let MonomorphizedModule {
|
||||||
|
module_id,
|
||||||
procedures,
|
procedures,
|
||||||
interns,
|
mut interns,
|
||||||
exposed_to_host,
|
exposed_to_host,
|
||||||
..
|
..
|
||||||
} = loaded;
|
} = loaded;
|
||||||
|
@ -114,12 +115,12 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
||||||
|
|
||||||
let env = roc_gen_wasm::Env {
|
let env = roc_gen_wasm::Env {
|
||||||
arena,
|
arena,
|
||||||
interns,
|
module_id,
|
||||||
exposed_to_host,
|
exposed_to_host,
|
||||||
};
|
};
|
||||||
|
|
||||||
let (mut wasm_module, main_fn_index) =
|
let (mut wasm_module, main_fn_index) =
|
||||||
roc_gen_wasm::build_module_help(&env, procedures).unwrap();
|
roc_gen_wasm::build_module_help(&env, &mut interns, procedures).unwrap();
|
||||||
|
|
||||||
T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index);
|
T::insert_test_wrapper(arena, &mut wasm_module, TEST_WRAPPER_NAME, main_fn_index);
|
||||||
|
|
||||||
|
@ -136,7 +137,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
||||||
let store = Store::default();
|
let store = Store::default();
|
||||||
|
|
||||||
// Keep the final .wasm file for debugging with wasm-objdump or wasm2wat
|
// Keep the final .wasm file for debugging with wasm-objdump or wasm2wat
|
||||||
const DEBUG_WASM_FILE: bool = true;
|
const DEBUG_WASM_FILE: bool = false;
|
||||||
|
|
||||||
let wasmer_module = {
|
let wasmer_module = {
|
||||||
let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped
|
let tmp_dir: TempDir; // directory for normal test runs, deleted when dropped
|
||||||
|
@ -166,8 +167,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
||||||
// write the module to a file so the linker can access it
|
// write the module to a file so the linker can access it
|
||||||
std::fs::write(&app_o_file, &module_bytes).unwrap();
|
std::fs::write(&app_o_file, &module_bytes).unwrap();
|
||||||
|
|
||||||
let _linker_output = std::process::Command::new("zig")
|
let args = &[
|
||||||
.args(&[
|
|
||||||
"wasm-ld",
|
"wasm-ld",
|
||||||
// input files
|
// input files
|
||||||
app_o_file.to_str().unwrap(),
|
app_o_file.to_str().unwrap(),
|
||||||
|
@ -188,11 +188,21 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
|
||||||
"test_wrapper",
|
"test_wrapper",
|
||||||
"--export",
|
"--export",
|
||||||
"#UserApp_main_1",
|
"#UserApp_main_1",
|
||||||
])
|
];
|
||||||
|
|
||||||
|
let linker_output = std::process::Command::new("zig")
|
||||||
|
.args(args)
|
||||||
.output()
|
.output()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// dbg!(_linker_output);
|
if !linker_output.status.success() {
|
||||||
|
print!("\nLINKER FAILED\n");
|
||||||
|
for arg in args {
|
||||||
|
print!("{} ", arg);
|
||||||
|
}
|
||||||
|
println!("\n{}", std::str::from_utf8(&linker_output.stdout).unwrap());
|
||||||
|
println!("{}", std::str::from_utf8(&linker_output.stderr).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
Module::from_file(&store, &final_wasm_file).unwrap()
|
Module::from_file(&store, &final_wasm_file).unwrap()
|
||||||
};
|
};
|
||||||
|
|
|
@ -141,6 +141,16 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Wasm32TestResult for () {
|
||||||
|
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
|
||||||
|
// Main's symbol index is the same as its function index, since the first symbols we created were for procs
|
||||||
|
let main_symbol_index = main_function_index;
|
||||||
|
code_builder.call(main_function_index, main_symbol_index, 0, false);
|
||||||
|
code_builder.get_global(0);
|
||||||
|
code_builder.build_fn_header(&[], 0, None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<T, U> Wasm32TestResult for (T, U)
|
impl<T, U> Wasm32TestResult for (T, U)
|
||||||
where
|
where
|
||||||
T: Wasm32TestResult + FromWasm32Memory,
|
T: Wasm32TestResult + FromWasm32Memory,
|
||||||
|
|
|
@ -446,39 +446,6 @@ fn str_starts_with_false_small_str() {
|
||||||
assert_evals_to!(r#"Str.startsWith "1234" "23""#, false, bool);
|
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]
|
// #[test]
|
||||||
// fn str_from_utf8_pass_single_ascii() {
|
// fn str_from_utf8_pass_single_ascii() {
|
||||||
// assert_evals_to!(
|
// assert_evals_to!(
|
||||||
|
@ -729,8 +696,8 @@ fn str_starts_with_false_small_str() {
|
||||||
// |> Str.concat ") ("
|
// |> Str.concat ") ("
|
||||||
// |> Str.concat (printExpr b)
|
// |> Str.concat (printExpr b)
|
||||||
// |> Str.concat ")"
|
// |> Str.concat ")"
|
||||||
// Val v -> "Val " |> Str.concat (Str.fromInt v)
|
// Val v -> "Val " |> Str.concat (Num.toStr v)
|
||||||
// Var v -> "Var " |> Str.concat (Str.fromInt v)
|
// Var v -> "Var " |> Str.concat (Num.toStr v)
|
||||||
|
|
||||||
// printExpr expr
|
// printExpr expr
|
||||||
// "#
|
// "#
|
||||||
|
@ -763,11 +730,6 @@ fn str_starts_with_false_small_str() {
|
||||||
// assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
|
// 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]
|
// #[test]
|
||||||
// fn str_to_utf8() {
|
// fn str_to_utf8() {
|
||||||
// assert_evals_to!(
|
// assert_evals_to!(
|
||||||
|
@ -903,8 +865,8 @@ fn str_starts_with_false_small_str() {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_repeat_small() {
|
fn str_repeat_small() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(r#"Str.repeat "Roc" 3"#),
|
indoc!(r#"Str.repeat "Roc" 2"#),
|
||||||
RocStr::from("RocRocRoc"),
|
RocStr::from("RocRoc"),
|
||||||
RocStr
|
RocStr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -941,8 +903,8 @@ fn str_trim_small_blank_string() {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_trim_small_to_small() {
|
fn str_trim_small_to_small() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(r#"Str.trim " hello world ""#),
|
indoc!(r#"Str.trim " hello ""#),
|
||||||
RocStr::from("hello world"),
|
RocStr::from("hello"),
|
||||||
RocStr
|
RocStr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -959,8 +921,8 @@ fn str_trim_large_to_large_unique() {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_trim_large_to_small_unique() {
|
fn str_trim_large_to_small_unique() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(r#"Str.trim (Str.concat " " "hello world ")"#),
|
indoc!(r#"Str.trim (Str.concat " " "hello ")"#),
|
||||||
RocStr::from("hello world"),
|
RocStr::from("hello"),
|
||||||
RocStr
|
RocStr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -990,15 +952,12 @@ fn str_trim_large_to_small_shared() {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
original : Str
|
original : Str
|
||||||
original = " hello world "
|
original = " hello "
|
||||||
|
|
||||||
{ trimmed: Str.trim original, original: original }
|
{ trimmed: Str.trim original, original: original }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
(
|
(RocStr::from(" hello "), RocStr::from("hello"),),
|
||||||
RocStr::from(" hello world "),
|
|
||||||
RocStr::from("hello world"),
|
|
||||||
),
|
|
||||||
(RocStr, RocStr)
|
(RocStr, RocStr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1009,12 +968,12 @@ fn str_trim_small_to_small_shared() {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
original : Str
|
original : Str
|
||||||
original = " hello world "
|
original = " hello "
|
||||||
|
|
||||||
{ trimmed: Str.trim original, original: original }
|
{ trimmed: Str.trim original, original: original }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
(RocStr::from(" hello world "), RocStr::from("hello world"),),
|
(RocStr::from(" hello "), RocStr::from("hello"),),
|
||||||
(RocStr, RocStr)
|
(RocStr, RocStr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1027,8 +986,8 @@ fn str_trim_left_small_blank_string() {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_trim_left_small_to_small() {
|
fn str_trim_left_small_to_small() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(r#"Str.trimLeft " hello world ""#),
|
indoc!(r#"Str.trimLeft " hello ""#),
|
||||||
RocStr::from("hello world "),
|
RocStr::from("hello "),
|
||||||
RocStr
|
RocStr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1045,8 +1004,8 @@ fn str_trim_left_large_to_large_unique() {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_trim_left_large_to_small_unique() {
|
fn str_trim_left_large_to_small_unique() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#),
|
indoc!(r#"Str.trimLeft (Str.concat " " "hello ")"#),
|
||||||
RocStr::from("hello world "),
|
RocStr::from("hello "),
|
||||||
RocStr
|
RocStr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1059,8 +1018,8 @@ fn str_trim_right_small_blank_string() {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_trim_right_small_to_small() {
|
fn str_trim_right_small_to_small() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(r#"Str.trimRight " hello world ""#),
|
indoc!(r#"Str.trimRight " hello ""#),
|
||||||
RocStr::from(" hello world"),
|
RocStr::from(" hello"),
|
||||||
RocStr
|
RocStr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1077,8 +1036,8 @@ fn str_trim_right_large_to_large_unique() {
|
||||||
#[test]
|
#[test]
|
||||||
fn str_trim_right_large_to_small_unique() {
|
fn str_trim_right_large_to_small_unique() {
|
||||||
assert_evals_to!(
|
assert_evals_to!(
|
||||||
indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#),
|
indoc!(r#"Str.trimRight (Str.concat " hello" " ")"#),
|
||||||
RocStr::from(" hello world"),
|
RocStr::from(" hello"),
|
||||||
RocStr
|
RocStr
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1108,15 +1067,12 @@ fn str_trim_right_large_to_small_shared() {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
original : Str
|
original : Str
|
||||||
original = " hello world "
|
original = " hello "
|
||||||
|
|
||||||
{ trimmed: Str.trimRight original, original: original }
|
{ trimmed: Str.trimRight original, original: original }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
(
|
(RocStr::from(" hello "), RocStr::from(" hello"),),
|
||||||
RocStr::from(" hello world "),
|
|
||||||
RocStr::from(" hello world"),
|
|
||||||
),
|
|
||||||
(RocStr, RocStr)
|
(RocStr, RocStr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1127,12 +1083,12 @@ fn str_trim_right_small_to_small_shared() {
|
||||||
indoc!(
|
indoc!(
|
||||||
r#"
|
r#"
|
||||||
original : Str
|
original : Str
|
||||||
original = " hello world "
|
original = " hello "
|
||||||
|
|
||||||
{ trimmed: Str.trimRight original, original: original }
|
{ trimmed: Str.trimRight original, original: original }
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
(RocStr::from(" hello world "), RocStr::from(" hello world"),),
|
(RocStr::from(" hello "), RocStr::from(" hello"),),
|
||||||
(RocStr, RocStr)
|
(RocStr, RocStr)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -708,6 +708,29 @@ impl Variable {
|
||||||
pub const fn index(&self) -> u32 {
|
pub const fn index(&self) -> u32 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const fn get_reserved(symbol: Symbol) -> Option<Variable> {
|
||||||
|
// Must be careful here: the variables must in fact be in Subs
|
||||||
|
match symbol {
|
||||||
|
Symbol::NUM_I128 => Some(Variable::I128),
|
||||||
|
Symbol::NUM_I64 => Some(Variable::I64),
|
||||||
|
Symbol::NUM_I32 => Some(Variable::I32),
|
||||||
|
Symbol::NUM_I16 => Some(Variable::I16),
|
||||||
|
Symbol::NUM_I8 => Some(Variable::I8),
|
||||||
|
|
||||||
|
Symbol::NUM_U128 => Some(Variable::U128),
|
||||||
|
Symbol::NUM_U64 => Some(Variable::U64),
|
||||||
|
Symbol::NUM_U32 => Some(Variable::U32),
|
||||||
|
Symbol::NUM_U16 => Some(Variable::U16),
|
||||||
|
Symbol::NUM_U8 => Some(Variable::U8),
|
||||||
|
|
||||||
|
Symbol::NUM_NAT => Some(Variable::NAT),
|
||||||
|
|
||||||
|
Symbol::BOOL_BOOL => Some(Variable::BOOL),
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Variable> for OptVariable {
|
impl From<Variable> for OptVariable {
|
||||||
|
@ -1012,6 +1035,8 @@ fn define_integer_types(subs: &mut Subs) {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Subs {
|
impl Subs {
|
||||||
|
pub const RESULT_TAG_NAMES: SubsSlice<TagName> = SubsSlice::new(0, 2);
|
||||||
|
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::with_capacity(0)
|
Self::with_capacity(0)
|
||||||
}
|
}
|
||||||
|
@ -1019,10 +1044,15 @@ impl Subs {
|
||||||
pub fn with_capacity(capacity: usize) -> Self {
|
pub fn with_capacity(capacity: usize) -> Self {
|
||||||
let capacity = capacity.max(Variable::NUM_RESERVED_VARS);
|
let capacity = capacity.max(Variable::NUM_RESERVED_VARS);
|
||||||
|
|
||||||
|
let mut tag_names = Vec::with_capacity(32);
|
||||||
|
|
||||||
|
tag_names.push(TagName::Global("Err".into()));
|
||||||
|
tag_names.push(TagName::Global("Ok".into()));
|
||||||
|
|
||||||
let mut subs = Subs {
|
let mut subs = Subs {
|
||||||
utable: UnificationTable::default(),
|
utable: UnificationTable::default(),
|
||||||
variables: Default::default(),
|
variables: Default::default(),
|
||||||
tag_names: Default::default(),
|
tag_names,
|
||||||
field_names: Default::default(),
|
field_names: Default::default(),
|
||||||
record_fields: Default::default(),
|
record_fields: Default::default(),
|
||||||
// store an empty slice at the first position
|
// store an empty slice at the first position
|
||||||
|
|
|
@ -175,8 +175,8 @@ main = "Hello, world!"
|
||||||
#[test]
|
#[test]
|
||||||
fn call_builtin() {
|
fn call_builtin() {
|
||||||
expect_html_def(
|
expect_html_def(
|
||||||
r#"myVal = Str.fromInt 1234"#,
|
r#"myVal = Num.toStr 1234"#,
|
||||||
"<span class=\"syntax-value\">myVal</span><span class=\"syntax-operator\"> = </span><span class=\"syntax-value\">Str.fromInt</span><span class=\"syntax-blank\"> </span><span class=\"syntax-number\">1234</span>\n\n",
|
"<span class=\"syntax-value\">myVal</span><span class=\"syntax-operator\"> = </span><span class=\"syntax-value\">Num.toStr</span><span class=\"syntax-blank\"> </span><span class=\"syntax-number\">1234</span>\n\n",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,7 @@ roc_reporting = { path = "../reporting" }
|
||||||
roc_solve = { path = "../compiler/solve" }
|
roc_solve = { path = "../compiler/solve" }
|
||||||
ven_graph = { path = "../vendor/pathfinding" }
|
ven_graph = { path = "../vendor/pathfinding" }
|
||||||
bumpalo = { version = "3.8.0", features = ["collections"] }
|
bumpalo = { version = "3.8.0", features = ["collections"] }
|
||||||
arraystring = "0.3.0"
|
arrayvec = "0.7.2"
|
||||||
libc = "0.2.106"
|
libc = "0.2.106"
|
||||||
page_size = "0.4.2"
|
page_size = "0.4.2"
|
||||||
# once winit 0.26 is out, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time.
|
# once winit 0.26 is out, check if copypasta can be updated simultaneously so they use the same versions for their dependencies. This will save build time.
|
||||||
|
|
|
@ -36,14 +36,12 @@ pub fn update_small_string(
|
||||||
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
|
.get_offset_to_node_id(old_caret_pos, curr_mark_node_id)?;
|
||||||
|
|
||||||
if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() {
|
if node_caret_offset != 0 && node_caret_offset < content_str_mut.len() {
|
||||||
if old_array_str.len() < ArrString::capacity() {
|
if old_array_str.len() < old_array_str.capacity() {
|
||||||
if let Expr2::SmallStr(ref mut mut_array_str) =
|
if let Expr2::SmallStr(ref mut mut_array_str) =
|
||||||
ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?)
|
ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?)
|
||||||
{
|
{
|
||||||
// safe because we checked the length
|
// safe because we checked the length
|
||||||
unsafe {
|
mut_array_str.push(*new_char);
|
||||||
mut_array_str.push_unchecked(*new_char);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
@ -137,7 +135,7 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
|
||||||
} = get_node_context(ed_model)?;
|
} = get_node_context(ed_model)?;
|
||||||
|
|
||||||
if curr_mark_node.is_blank() {
|
if curr_mark_node.is_blank() {
|
||||||
let new_expr2_node = Expr2::SmallStr(arraystring::ArrayString::new());
|
let new_expr2_node = Expr2::SmallStr(arrayvec::ArrayString::new());
|
||||||
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
|
let curr_mark_node_nls = curr_mark_node.get_newlines_at_end();
|
||||||
|
|
||||||
ed_model
|
ed_model
|
||||||
|
|
|
@ -13,9 +13,9 @@ main =
|
||||||
optimized = eval (constFolding (reassoc e))
|
optimized = eval (constFolding (reassoc e))
|
||||||
|
|
||||||
unoptimized
|
unoptimized
|
||||||
|> Str.fromInt
|
|> Num.toStr
|
||||||
|> Str.concat " & "
|
|> Str.concat " & "
|
||||||
|> Str.concat (Str.fromInt optimized)
|
|> Str.concat (Num.toStr optimized)
|
||||||
|> Task.putLine
|
|> Task.putLine
|
||||||
|
|
||||||
Expr : [
|
Expr : [
|
||||||
|
|
|
@ -115,9 +115,9 @@ deriv : I64, Expr -> IO Expr
|
||||||
deriv = \i, f ->
|
deriv = \i, f ->
|
||||||
fprime = d "x" f
|
fprime = d "x" f
|
||||||
line =
|
line =
|
||||||
Str.fromInt (i + 1)
|
Num.toStr (i + 1)
|
||||||
|> Str.concat " count: "
|
|> Str.concat " count: "
|
||||||
|> Str.concat (Str.fromInt (count fprime))
|
|> Str.concat (Num.toStr (count fprime))
|
||||||
|
|
||||||
Task.putLine line
|
Task.putLine line
|
||||||
|> Task.after \_ -> Task.succeed fprime
|
|> Task.after \_ -> Task.succeed fprime
|
||||||
|
|
|
@ -7,7 +7,7 @@ main : Task.Task {} []
|
||||||
main =
|
main =
|
||||||
Task.after Task.getInt \n ->
|
Task.after Task.getInt \n ->
|
||||||
queens n # original koka 13
|
queens n # original koka 13
|
||||||
|> Str.fromInt
|
|> Num.toStr
|
||||||
|> Task.putLine
|
|> Task.putLine
|
||||||
|
|
||||||
ConsList a : [ Nil, Cons a (ConsList a) ]
|
ConsList a : [ Nil, Cons a (ConsList a) ]
|
||||||
|
|
|
@ -7,7 +7,7 @@ show = \list ->
|
||||||
else
|
else
|
||||||
content =
|
content =
|
||||||
list
|
list
|
||||||
|> List.map Str.fromInt
|
|> List.map Num.toStr
|
||||||
|> Str.joinWith ", "
|
|> Str.joinWith ", "
|
||||||
|
|
||||||
"[ \(content) ]"
|
"[ \(content) ]"
|
||||||
|
|
|
@ -48,14 +48,15 @@ resultWithDefault = \res, default ->
|
||||||
main : Task.Task {} []
|
main : Task.Task {} []
|
||||||
main =
|
main =
|
||||||
Task.after Task.getInt \n ->
|
Task.after Task.getInt \n ->
|
||||||
|
# original koka n = 4_200_000
|
||||||
ms : ConsList Map
|
ms : ConsList Map
|
||||||
ms = makeMap 5 n # original koka n = 4_200_000
|
ms = makeMap 5 n
|
||||||
|
|
||||||
when ms is
|
when ms is
|
||||||
Cons head _ ->
|
Cons head _ ->
|
||||||
val = fold (\_, v, r -> if v then r + 1 else r) head 0
|
val = fold (\_, v, r -> if v then r + 1 else r) head 0
|
||||||
val
|
val
|
||||||
|> Str.fromInt
|
|> Num.toStr
|
||||||
|> Task.putLine
|
|> Task.putLine
|
||||||
|
|
||||||
Nil ->
|
Nil ->
|
||||||
|
|
|
@ -20,7 +20,7 @@ main =
|
||||||
val = fold (\_, v, r -> if v then r + 1 else r) m 0
|
val = fold (\_, v, r -> if v then r + 1 else r) m 0
|
||||||
|
|
||||||
val
|
val
|
||||||
|> Str.fromInt
|
|> Num.toStr
|
||||||
|> Task.putLine
|
|> Task.putLine
|
||||||
|
|
||||||
boom : Str -> a
|
boom : Str -> a
|
||||||
|
|
|
@ -13,7 +13,7 @@ main =
|
||||||
|> Task.putLine
|
|> Task.putLine
|
||||||
|
|
||||||
show : RedBlackTree I64 {} -> Str
|
show : RedBlackTree I64 {} -> Str
|
||||||
show = \tree -> showRBTree tree Str.fromInt (\{} -> "{}")
|
show = \tree -> showRBTree tree Num.toStr (\{} -> "{}")
|
||||||
|
|
||||||
showRBTree : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
|
showRBTree : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
|
||||||
showRBTree = \tree, showKey, showValue ->
|
showRBTree = \tree, showKey, showValue ->
|
||||||
|
|
|
@ -13,7 +13,7 @@ main =
|
||||||
# Task.putLine (showBool test1)
|
# Task.putLine (showBool test1)
|
||||||
#
|
#
|
||||||
# _ ->
|
# _ ->
|
||||||
# ns = Str.fromInt n
|
# ns = Num.toStr n
|
||||||
# Task.putLine "No test \(ns)"
|
# Task.putLine "No test \(ns)"
|
||||||
|
|
||||||
showBool : Bool -> Str
|
showBool : Bool -> Str
|
||||||
|
|
8
examples/cli/README.md
Normal file
8
examples/cli/README.md
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# Command Line Interface (CLI) Example
|
||||||
|
|
||||||
|
This is an example of how to make an extremely basic CLI in Roc.
|
||||||
|
|
||||||
|
There's not currently much documentation for the CLI platform (which also doesn't support many operations at this point!)
|
||||||
|
but you can look at [the modules it includes](platform) - for example,
|
||||||
|
multiple other modules use the [`Task`](platform/Task.roc) module, including the
|
||||||
|
[`Stdin`](platform/Stdin.roc) and [`Stdout`](platform/Stdout.roc) modules.
|
|
@ -33,7 +33,7 @@ toStrData: Data -> Str
|
||||||
toStrData = \data ->
|
toStrData = \data ->
|
||||||
when data is
|
when data is
|
||||||
Lambda _ -> "[]"
|
Lambda _ -> "[]"
|
||||||
Number n -> Str.fromInt (Num.intCast n)
|
Number n -> Num.toStr (Num.intCast n)
|
||||||
Var v -> Variable.toStr v
|
Var v -> Variable.toStr v
|
||||||
|
|
||||||
toStrState: State -> Str
|
toStrState: State -> Str
|
||||||
|
@ -49,7 +49,7 @@ toStrState = \state ->
|
||||||
|
|
||||||
toStr: Context -> Str
|
toStr: Context -> Str
|
||||||
toStr = \{scopes, stack, state, vars} ->
|
toStr = \{scopes, stack, state, vars} ->
|
||||||
depth = Str.fromInt (List.len scopes)
|
depth = Num.toStr (List.len scopes)
|
||||||
stateStr = toStrState state
|
stateStr = toStrState state
|
||||||
stackStr = Str.joinWith (List.map stack toStrData) " "
|
stackStr = Str.joinWith (List.map stack toStrData) " "
|
||||||
varsStr = Str.joinWith (List.map vars toStrData) " "
|
varsStr = Str.joinWith (List.map vars toStrData) " "
|
||||||
|
|
|
@ -203,7 +203,7 @@ interpretCtx = \ctx ->
|
||||||
# This is supposed to flush io buffers. We don't buffer, so it does nothing
|
# This is supposed to flush io buffers. We don't buffer, so it does nothing
|
||||||
interpretCtx newCtx
|
interpretCtx newCtx
|
||||||
Ok (T x _) ->
|
Ok (T x _) ->
|
||||||
data = Str.fromInt (Num.intCast x)
|
data = Num.toStr (Num.intCast x)
|
||||||
Task.fail (InvalidChar data)
|
Task.fail (InvalidChar data)
|
||||||
Err NoScope ->
|
Err NoScope ->
|
||||||
Task.fail NoScope
|
Task.fail NoScope
|
||||||
|
@ -358,7 +358,7 @@ stepExecCtx = \ctx, char ->
|
||||||
0x2E -> # `.` write int
|
0x2E -> # `.` write int
|
||||||
when popNumber ctx is
|
when popNumber ctx is
|
||||||
Ok (T popCtx num) ->
|
Ok (T popCtx num) ->
|
||||||
{} <- Task.await (Stdout.raw (Str.fromInt (Num.intCast num)))
|
{} <- Task.await (Stdout.raw (Num.toStr (Num.intCast num)))
|
||||||
Task.succeed popCtx
|
Task.succeed popCtx
|
||||||
Err e ->
|
Err e ->
|
||||||
Task.fail e
|
Task.fail e
|
||||||
|
@ -395,7 +395,7 @@ stepExecCtx = \ctx, char ->
|
||||||
Ok var ->
|
Ok var ->
|
||||||
Task.succeed (Context.pushStack ctx (Var var))
|
Task.succeed (Context.pushStack ctx (Var var))
|
||||||
Err _ ->
|
Err _ ->
|
||||||
data = Str.fromInt (Num.intCast x)
|
data = Num.toStr (Num.intCast x)
|
||||||
Task.fail (InvalidChar data)
|
Task.fail (InvalidChar data)
|
||||||
|
|
||||||
unaryOp: Context, (I32 -> I32) -> Result Context InterpreterErrors
|
unaryOp: Context, (I32 -> I32) -> Result Context InterpreterErrors
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
1. Download the latest nightly from the assets [here](https://github.com/rtfeldman/roc/releases).
|
0. Download the latest nightly from the assets [here](https://github.com/rtfeldman/roc/releases).
|
||||||
2. Untar the archive:
|
0. Untar the archive:
|
||||||
```
|
```
|
||||||
tar -xf roc_nightly-linux_x86_64-<VERSION>.tar.gz
|
tar -xf roc_nightly-linux_x86_64-<VERSION>.tar.gz
|
||||||
```
|
```
|
||||||
3. To be able to run examples:
|
0. Some fresh installs require executing `sudo apt update`, it is not needed to execute `sudo apt upgrade` after this.
|
||||||
|
0. To be able to run examples:
|
||||||
- for the Rust example:
|
- for the Rust example:
|
||||||
```
|
```
|
||||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
@ -16,15 +17,15 @@
|
||||||
```
|
```
|
||||||
- for the C example:
|
- for the C example:
|
||||||
```
|
```
|
||||||
sudo apt install clang
|
sudo apt install build-essential clang
|
||||||
```
|
```
|
||||||
4. Run examples with:
|
0. Run examples with:
|
||||||
```
|
```
|
||||||
# Rust
|
# Rust. If you installed rust in this terminal you'll need to open a new one first!
|
||||||
./roc examples/hello-rust/Hello.roc
|
./roc examples/hello-rust/Hello.roc
|
||||||
# Zig
|
# Zig
|
||||||
./roc examples/hello-zig/Hello.roc
|
./roc examples/hello-zig/Hello.roc
|
||||||
# C
|
# C
|
||||||
./roc examples/hello-world/Hello.roc
|
./roc examples/hello-world/Hello.roc
|
||||||
```
|
```
|
||||||
5. See [here](../README.md#examples) for the other examples.
|
0. See [here](../README.md#examples) for the other examples.
|
||||||
|
|
|
@ -4,16 +4,10 @@ use std::io;
|
||||||
fn main() -> io::Result<()> {
|
fn main() -> io::Result<()> {
|
||||||
let matches = build_app().get_matches();
|
let matches = build_app().get_matches();
|
||||||
|
|
||||||
let exit_code = match matches.subcommand_name() {
|
let exit_code = match matches.subcommand() {
|
||||||
None => Ok::<i32, io::Error>(-1),
|
None => Ok::<i32, io::Error>(-1),
|
||||||
Some(CMD_PREPROCESS) => {
|
Some((CMD_PREPROCESS, sub_matches)) => preprocess(sub_matches),
|
||||||
let sub_matches = matches.subcommand_matches(CMD_PREPROCESS).unwrap();
|
Some((CMD_SURGERY, sub_matches)) => surgery(sub_matches),
|
||||||
preprocess(sub_matches)
|
|
||||||
}
|
|
||||||
Some(CMD_SURGERY) => {
|
|
||||||
let sub_matches = matches.subcommand_matches(CMD_SURGERY).unwrap();
|
|
||||||
surgery(sub_matches)
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}?;
|
}?;
|
||||||
std::process::exit(exit_code);
|
std::process::exit(exit_code);
|
||||||
|
|
|
@ -13,7 +13,7 @@ mod test_reporting {
|
||||||
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_module::symbol::{Interns, ModuleId};
|
use roc_module::symbol::{Interns, ModuleId};
|
||||||
use roc_mono::ir::{Procs, Stmt};
|
use roc_mono::ir::{Procs, Stmt, UpdateModeIds};
|
||||||
use roc_mono::layout::LayoutCache;
|
use roc_mono::layout::LayoutCache;
|
||||||
use roc_reporting::report::{
|
use roc_reporting::report::{
|
||||||
can_problem, mono_problem, parse_problem, type_problem, Report, Severity, BLUE_CODE,
|
can_problem, mono_problem, parse_problem, type_problem, Report, Severity, BLUE_CODE,
|
||||||
|
@ -91,6 +91,7 @@ mod test_reporting {
|
||||||
// Compile and add all the Procs before adding main
|
// Compile and add all the Procs before adding main
|
||||||
let mut procs = Procs::new_in(&arena);
|
let mut procs = Procs::new_in(&arena);
|
||||||
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
|
let mut ident_ids = interns.all_ident_ids.remove(&home).unwrap();
|
||||||
|
let mut update_mode_ids = UpdateModeIds::new();
|
||||||
|
|
||||||
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
// Populate Procs and Subs, and get the low-level Expr from the canonical Expr
|
||||||
let ptr_bytes = 8;
|
let ptr_bytes = 8;
|
||||||
|
@ -101,8 +102,8 @@ mod test_reporting {
|
||||||
problems: &mut mono_problems,
|
problems: &mut mono_problems,
|
||||||
home,
|
home,
|
||||||
ident_ids: &mut ident_ids,
|
ident_ids: &mut ident_ids,
|
||||||
|
update_mode_ids: &mut update_mode_ids,
|
||||||
ptr_bytes,
|
ptr_bytes,
|
||||||
update_mode_counter: 0,
|
|
||||||
// call_specialization_counter=0 is reserved
|
// call_specialization_counter=0 is reserved
|
||||||
call_specialization_counter: 1,
|
call_specialization_counter: 1,
|
||||||
};
|
};
|
||||||
|
|
|
@ -319,7 +319,7 @@ table = \{ height, width, title ? "", description ? "" } ->
|
||||||
This says that `table` takes a record with two *required* fields (`height` and
|
This says that `table` takes a record with two *required* fields (`height` and
|
||||||
`width` and two *optional* fields (`title` and `description`). It also says that
|
`width` and two *optional* fields (`title` and `description`). It also says that
|
||||||
the `height` and `width` fields have the type `Pixels` (a type alias for some
|
the `height` and `width` fields have the type `Pixels` (a type alias for some
|
||||||
numeric type), whereas the `title` and `description` fields have the type `Str.`
|
numeric type), whereas the `title` and `description` fields have the type `Str`.
|
||||||
This means you can choose to omit `title`, `description`, or both, when calling
|
This means you can choose to omit `title`, `description`, or both, when calling
|
||||||
the function...but if you provide them, they must have the type `Str`.
|
the function...but if you provide them, they must have the type `Str`.
|
||||||
|
|
||||||
|
@ -708,7 +708,7 @@ because `@` tags are not allowed in the exposing list. Only code written in this
|
||||||
`Username` module can instantiate a `@Username` value.
|
`Username` module can instantiate a `@Username` value.
|
||||||
|
|
||||||
> If I were to write `@Username` inside another module (e.g. `Main`), it would compile,
|
> If I were to write `@Username` inside another module (e.g. `Main`), it would compile,
|
||||||
> but that `@Username` would be type-incompatible with one created inside the `Username` module.
|
> but that `@Username` would be type-incompatible with the one created inside the `Username` module.
|
||||||
> Even trying to use `==` on them would be a type mismatch, because I would be comparing
|
> Even trying to use `==` on them would be a type mismatch, because I would be comparing
|
||||||
> a `[ Username.@Username Str ]*` with a `[ Main.@Username Str ]*`, which are incompatible.
|
> a `[ Username.@Username Str ]*` with a `[ Main.@Username Str ]*`, which are incompatible.
|
||||||
|
|
||||||
|
|
12
test_utils/Cargo.toml
Normal file
12
test_utils/Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
[package]
|
||||||
|
name = "roc_test_utils"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["The Roc Contributors"]
|
||||||
|
license = "UPL-1.0"
|
||||||
|
edition = "2018"
|
||||||
|
description = "Utility functions used all over the code base."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
pretty_assertions = "1.0.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
18
test_utils/src/lib.rs
Normal file
18
test_utils/src/lib.rs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use pretty_assertions::assert_eq as _pretty_assert_eq;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
pub struct DebugAsDisplay<T>(pub T);
|
||||||
|
|
||||||
|
impl<T: std::fmt::Display> std::fmt::Debug for DebugAsDisplay<T> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! assert_multiline_str_eq {
|
||||||
|
($a:expr, $b:expr) => {
|
||||||
|
$crate::_pretty_assert_eq!($crate::DebugAsDisplay($a), $crate::DebugAsDisplay($b))
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue