Merge branch 'trunk' into dev-backend-num-to-float

This commit is contained in:
satotake 2021-12-05 12:46:13 +00:00 committed by GitHub
commit 9ef80444f1
90 changed files with 4872 additions and 1734 deletions

View file

@ -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>

View file

@ -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
View file

@ -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"

View file

@ -32,6 +32,7 @@ members = [
"code_markup", "code_markup",
"reporting", "reporting",
"roc_std", "roc_std",
"test_utils",
"utils", "utils",
"docs", "docs",
"linker", "linker",

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs: copy-dirs:
FROM +install-zig-llvm-valgrind-clippy-rustfmt FROM +install-zig-llvm-valgrind-clippy-rustfmt
COPY --dir cli 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

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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>;

View file

@ -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(())

View file

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

View file

@ -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
View file

@ -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"

View file

@ -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`:

View file

@ -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()

View file

@ -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();

View file

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

View file

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

View file

@ -61,6 +61,7 @@ interface Num
addChecked, addChecked,
atan, atan,
acos, acos,
toStr,
Signed128, Signed128,
Signed64, Signed64,
Signed32, Signed32,

View file

@ -10,8 +10,6 @@ interface Str
countGraphemes, countGraphemes,
startsWith, startsWith,
endsWith, endsWith,
fromInt,
fromFloat,
fromUtf8, fromUtf8,
Utf8Problem, Utf8Problem,
Utf8ByteProblem, Utf8ByteProblem,

View file

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

View file

@ -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 ]*

View file

@ -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();

View file

@ -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" }

View file

@ -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,
} }
} }

View file

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

View file

@ -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!(

View file

@ -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.

View file

@ -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

View file

@ -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 {

View file

@ -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, &param.layout)?; self.set_layout_map(param.symbol, &param.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(_) => {}

View file

@ -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 {
output match output.add_relocation(section_id, reloc) {
.add_relocation(section_id, reloc) Ok(obj) => obj,
.map_err(|e| format!("{:?}", e))?; Err(e) => internal_error!("{:?}", e),
} }
Ok(output) }
output
} }

View file

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

View file

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

View file

@ -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<'_> {

View file

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

View file

@ -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
}
}
}
}
}
#[derive(PartialEq, Eq)]
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
}
}
}
} }
} }
pub fn is_stack_memory(&self) -> bool {
matches!(self, Self::StackMemory { .. })
} }
} }

View file

@ -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;

View file

@ -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),
} }
} }

View file

@ -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

View file

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

View file

@ -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

View file

@ -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"

View file

@ -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,
}; };

View file

@ -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),

View file

@ -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

View file

@ -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> {

View file

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

View 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",
}
}

View file

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

View file

@ -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;

View file

@ -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!();
} }

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

View file

@ -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;

View file

@ -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,
} }
} }

View file

@ -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"

View file

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

View file

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

View file

@ -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)

View file

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

View file

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

View file

@ -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!(

View file

@ -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!(

View file

@ -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() {

View file

@ -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()

View file

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

View file

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

View file

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

View file

@ -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

View file

@ -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",
); );
} }

View file

@ -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.

View file

@ -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

View file

@ -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 : [

View file

@ -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

View file

@ -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) ]

View file

@ -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) ]"

View file

@ -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 ->

View file

@ -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

View file

@ -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 ->

View file

@ -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
View 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.

View file

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

View file

@ -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

View file

@ -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.

View file

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

View file

@ -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,
}; };

View file

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