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>
James Hegedus <jthegedus@hey.com>
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**
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.
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"
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]]
name = "arrayvec"
version = "0.5.2"
@ -3144,7 +3135,7 @@ dependencies = [
name = "roc_ast"
version = "0.1.0"
dependencies = [
"arraystring",
"arrayvec 0.7.2",
"bumpalo",
"indoc",
"libc",
@ -3327,7 +3318,7 @@ dependencies = [
name = "roc_editor"
version = "0.1.0"
dependencies = [
"arraystring",
"arrayvec 0.7.2",
"bumpalo",
"bytemuck",
"cgmath",
@ -3383,6 +3374,7 @@ dependencies = [
"roc_module",
"roc_parse",
"roc_region",
"roc_test_utils",
]
[[package]]
@ -3400,6 +3392,7 @@ dependencies = [
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_std",
"roc_types",
@ -3524,9 +3517,7 @@ dependencies = [
name = "roc_parse"
version = "0.1.0"
dependencies = [
"ansi_term",
"bumpalo",
"diff",
"encode_unicode",
"indoc",
"pretty_assertions",
@ -3535,6 +3526,7 @@ dependencies = [
"roc_collections",
"roc_module",
"roc_region",
"roc_test_utils",
]
[[package]]
@ -3599,6 +3591,13 @@ dependencies = [
name = "roc_std"
version = "0.1.0"
[[package]]
name = "roc_test_utils"
version = "0.1.0"
dependencies = [
"pretty_assertions",
]
[[package]]
name = "roc_types"
version = "0.1.0"

View file

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

View file

@ -47,7 +47,7 @@ install-zig-llvm-valgrind-clippy-rustfmt:
copy-dirs:
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:
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 printf " on: " >> 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
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.
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

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_unify = { path = "../compiler/unify"}
roc_load = { path = "../compiler/load" }
arraystring = "0.3.0"
arrayvec = "0.7.2"
bumpalo = { version = "3.8.0", features = ["collections"] }
libc = "0.2.106"
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 crate::{
@ -12,7 +12,7 @@ use roc_module::symbol::Symbol;
use super::record_field::RecordField;
pub type ArrString = ArrayString<U30>;
pub type ArrString = ArrayString<24>;
// TODO make the inner types private?
pub type ExprId = NodeId<Expr2>;

View file

@ -181,24 +181,16 @@ pub fn update_str_expr(
enum Either {
MyString(String),
MyPoolStr(PoolStr),
Done,
}
let insert_either = match str_expr {
Expr2::SmallStr(arr_string) => {
// TODO make sure this works for unicode "characters"
let insert_res = arr_string.try_insert(insert_index as u8, new_char);
let mut new_string = arr_string.as_str().to_owned();
match insert_res {
Ok(_) => Either::Done,
_ => {
let mut new_string = arr_string.as_str().to_string();
new_string.insert(insert_index, new_char);
Either::MyString(new_string)
}
}
}
Expr2::Str(old_pool_str) => Either::MyPoolStr(*old_pool_str),
other => UnexpectedASTNode {
required_node_type: "SmallStr or Str",
@ -222,7 +214,6 @@ pub fn update_str_expr(
pool.set(node_id, Expr2::Str(new_pool_str))
}
Either::Done => (),
}
Ok(())

View file

@ -46,15 +46,15 @@ pub fn format(files: std::vec::Vec<PathBuf>) {
);
}));
let ast = ast.remove_spaces(&arena);
let reparsed_ast = reparsed_ast.remove_spaces(&arena);
let ast_normalized = ast.remove_spaces(&arena);
let reparsed_ast_normalized = reparsed_ast.remove_spaces(&arena);
// HACK!
// 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.
// 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
if format!("{:?}", ast) != format!("{:?}", reparsed_ast) {
if format!("{:?}", ast_normalized) != format!("{:?}", reparsed_ast_normalized) {
let mut fail_file = file.clone();
fail_file.set_extension("roc-format-failed");
std::fs::write(&fail_file, &buf).unwrap();
@ -76,6 +76,27 @@ pub fn format(files: std::vec::Vec<PathBuf>) {
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();
}
}

View file

@ -21,7 +21,7 @@ use roc_cli::build;
fn main() -> io::Result<()> {
let matches = build_app().get_matches();
let exit_code = match matches.subcommand_name() {
let exit_code = match matches.subcommand() {
None => {
match matches.index_of(ROC_FILE) {
Some(arg_index) => {
@ -37,14 +37,10 @@ fn main() -> io::Result<()> {
}
}
}
Some(CMD_BUILD) => Ok(build(
matches.subcommand_matches(CMD_BUILD).unwrap(),
BuildConfig::BuildOnly,
)?),
Some(CMD_CHECK) => {
Some((CMD_BUILD, matches)) => Ok(build(matches, BuildConfig::BuildOnly)?),
Some((CMD_CHECK, matches)) => {
let arena = bumpalo::Bump::new();
let matches = matches.subcommand_matches(CMD_CHECK).unwrap();
let emit_timings = matches.is_present(FLAG_TIME);
let filename = matches.value_of(ROC_FILE).unwrap();
let roc_file_path = PathBuf::from(filename);
@ -66,16 +62,14 @@ fn main() -> io::Result<()> {
}
}
}
Some(CMD_REPL) => {
Some((CMD_REPL, _)) => {
repl::main()?;
// Exit 0 if the repl exited normally
Ok(0)
}
Some(CMD_EDIT) => {
Some((CMD_EDIT, matches)) => {
match matches
.subcommand_matches(CMD_EDIT)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES)
.map(|mut values| values.next())
{
@ -90,11 +84,8 @@ fn main() -> io::Result<()> {
// Exit 0 if the editor exited normally
Ok(0)
}
Some(CMD_DOCS) => {
let maybe_values = matches
.subcommand_matches(CMD_DOCS)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES);
Some((CMD_DOCS, matches)) => {
let maybe_values = matches.values_of_os(DIRECTORY_OR_FILES);
let mut values: Vec<OsString> = Vec::new();
@ -128,11 +119,8 @@ fn main() -> io::Result<()> {
Ok(0)
}
Some(CMD_FORMAT) => {
let maybe_values = matches
.subcommand_matches(CMD_FORMAT)
.unwrap()
.values_of_os(DIRECTORY_OR_FILES);
Some((CMD_FORMAT, matches)) => {
let maybe_values = matches.values_of_os(DIRECTORY_OR_FILES);
let mut values: Vec<OsString> = Vec::new();
@ -166,7 +154,7 @@ fn main() -> io::Result<()> {
Ok(0)
}
Some(CMD_VERSION) => {
Some((CMD_VERSION, _)) => {
println!("roc {}", concatcp!(include_str!("../../version.txt"), "\n"));
Ok(0)

79
cli_utils/Cargo.lock generated
View file

@ -90,15 +90,6 @@ dependencies = [
"num-traits",
]
[[package]]
name = "arraystring"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d517c467117e1d8ca795bc8cc90857ff7f79790cca0e26f6e9462694ece0185"
dependencies = [
"typenum",
]
[[package]]
name = "arrayvec"
version = "0.5.2"
@ -191,6 +182,18 @@ dependencies = [
"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]]
name = "block"
version = "0.1.6"
@ -968,6 +971,12 @@ version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394"
[[package]]
name = "funty"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1847abb9cb65d566acd5942e94aea9c8f547ad02c98e1649326fc0e8910b8b1e"
[[package]]
name = "futures"
version = "0.3.17"
@ -1919,6 +1928,28 @@ dependencies = [
"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]]
name = "page_size"
version = "0.4.2"
@ -2210,6 +2241,12 @@ dependencies = [
"proc-macro2",
]
[[package]]
name = "radium"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "643f8f41a8ebc4c5dc4515c82bb8abd397b527fc20fd681b7c011c2aee5d44fb"
[[package]]
name = "rand"
version = "0.8.4"
@ -2375,7 +2412,7 @@ dependencies = [
name = "roc_ast"
version = "0.1.0"
dependencies = [
"arraystring",
"arrayvec 0.7.2",
"bumpalo",
"libc",
"page_size",
@ -2545,7 +2582,7 @@ dependencies = [
name = "roc_editor"
version = "0.1.0"
dependencies = [
"arraystring",
"arrayvec 0.7.2",
"bumpalo",
"bytemuck",
"cgmath",
@ -2603,12 +2640,14 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"object 0.26.2",
"packed_struct",
"roc_builtins",
"roc_collections",
"roc_module",
"roc_mono",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_types",
"roc_unify",
@ -2771,6 +2810,8 @@ dependencies = [
name = "roc_solve"
version = "0.1.0"
dependencies = [
"arrayvec 0.7.2",
"bumpalo",
"roc_can",
"roc_collections",
"roc_module",
@ -2787,6 +2828,7 @@ version = "0.1.0"
name = "roc_types"
version = "0.1.0"
dependencies = [
"bumpalo",
"roc_collections",
"roc_module",
"roc_region",
@ -3163,6 +3205,12 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "tap"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
[[package]]
name = "target-lexicon"
version = "0.12.2"
@ -3807,6 +3855,15 @@ dependencies = [
"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]]
name = "x11-clipboard"
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:
"1 + 2 - 3"
"1 + 8 - 3"
The parser will translate this into the following `Expr`:
BinOp(
Int(1),
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`:

View file

@ -491,11 +491,12 @@ fn gen_from_mono_module_dev_wasm32(
loaded: MonomorphizedModule,
app_o_file: &Path,
) -> CodeGenTiming {
let mut procedures = MutMap::default();
for (key, proc) in loaded.procedures {
procedures.insert(key, proc);
}
let MonomorphizedModule {
module_id,
procedures,
mut interns,
..
} = loaded;
let exposed_to_host = loaded
.exposed_to_host
@ -505,11 +506,11 @@ fn gen_from_mono_module_dev_wasm32(
let env = roc_gen_wasm::Env {
arena,
interns: loaded.interns,
module_id,
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");
@ -533,8 +534,7 @@ fn gen_from_mono_module_dev_assembly(
generate_allocators,
};
let module_object = roc_gen_dev::build_module(&env, target, loaded.procedures)
.expect("failed to compile module");
let module_object = roc_gen_dev::build_module(&env, target, loaded.procedures);
let module_out = module_object
.write()

View file

@ -2,6 +2,7 @@ const std = @import("std");
const mem = std.mem;
const Builder = std.build.Builder;
const CrossTarget = std.zig.CrossTarget;
const Arch = std.Target.Cpu.Arch;
pub fn build(b: *Builder) void {
// b.setPreferredReleaseMode(builtin.Mode.Debug
@ -21,7 +22,12 @@ pub fn build(b: *Builder) void {
test_step.dependOn(&main_tests.step);
// 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 wasm32_target = makeWasm32Target();

View file

@ -138,6 +138,7 @@ comptime {
comptime {
exportUtilsFn(utils.test_panic, "test_panic");
exportUtilsFn(utils.increfC, "incref");
exportUtilsFn(utils.decrefC, "decref");
exportUtilsFn(utils.decrefCheckNullC, "decref_check_null");

View file

@ -104,6 +104,12 @@ pub const IntWidth = enum(u8) {
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(
bytes_or_null: ?[*]isize,
alignment: u32,
@ -261,3 +267,17 @@ pub const UpdateMode = enum(u8) {
Immutable = 0,
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,
atan,
acos,
toStr,
Signed128,
Signed64,
Signed32,

View file

@ -10,8 +10,6 @@ interface Str
countGraphemes,
startsWith,
endsWith,
fromInt,
fromFloat,
fromUtf8,
Utf8Problem,
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 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_CHECK_NULL: &str = "roc_builtins.utils.decref_check_null";

View file

@ -385,6 +385,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
// maxI128 : I128
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
// div : Float a, Float a -> Float a
@ -618,13 +625,6 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
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
add_top_level_function_type!(
Symbol::STR_REPEAT,
@ -702,13 +702,6 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
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
// 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_ENDS_WITH => str_ends_with,
STR_COUNT_GRAPHEMES => str_count_graphemes,
STR_FROM_INT => str_from_int,
STR_FROM_UTF8 => str_from_utf8,
STR_FROM_UTF8_RANGE => str_from_utf8_range,
STR_TO_UTF8 => str_to_utf8,
STR_FROM_FLOAT=> str_from_float,
STR_REPEAT => str_repeat,
STR_TRIM => str_trim,
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_INT_CAST=> num_int_cast,
NUM_MAX_I128=> num_max_i128,
NUM_TO_STR => num_to_str,
RESULT_MAP => result_map,
RESULT_MAP_ERR => result_map_err,
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
fn bool_eq(symbol: Symbol, var_store: &mut VarStore) -> Def {
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 } } ]*
fn str_from_utf8(symbol: Symbol, var_store: &mut VarStore) -> Def {
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)
}
/// 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
fn list_concat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let list_var = var_store.fresh();

View file

@ -15,3 +15,4 @@ bumpalo = { version = "3.8.0", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "1.0.0"
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);
} else {
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(')');
}
}
@ -576,8 +581,10 @@ fn fmt_when<'a>(
while let Some(branch) = it.next() {
let patterns = &branch.patterns;
let expr = &branch.value;
add_spaces(buf, indent + INDENT);
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() {
None => false,
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 {
if is_multiline {
buf.push_str("\n");
add_spaces(buf, indent + INDENT);
buf.push_str("| ");
} else {
buf.push_str(" | ");
newline(buf, indent + INDENT);
}
buf.push_str(" | ");
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);
}
buf.push_str(" ->\n");
buf.push_str(" ->");
newline(buf, indent + INDENT * 2);
add_spaces(buf, indent + (INDENT * 2));
match expr.value {
Expr::SpaceBefore(nested, spaces) => {
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>(
buf: &mut String<'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,
})
}
Expr::If(_, _) => true,
_ => false,
}
}

View file

@ -46,7 +46,7 @@ where
match space {
Newline => {
if !encountered_comment && (consecutive_newlines < 2) {
if iter.peek() == Some(&&Newline) {
if iter.peek() == Some(&&Newline) && consecutive_newlines < 1 {
buf.push('\n');
} else {
newline(buf, indent);

View file

@ -1,6 +1,4 @@
#[macro_use]
extern crate pretty_assertions;
#[macro_use]
extern crate indoc;
extern crate bumpalo;
extern crate roc_fmt;
@ -14,24 +12,35 @@ mod test_fmt {
use roc_fmt::module::fmt_module;
use roc_parse::module::{self, module_defs};
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 input = input.trim_end();
let expected = expected.trim_end();
match roc_parse::test_helpers::parse_expr_with(&arena, input.trim()) {
Ok(actual) => {
let mut buf = String::new_in(&arena);
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)
};
}
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) {
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)
}
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)
};
@ -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]
fn def_with_comment_and_extra_space() {
expr_formats_to(
@ -1835,7 +1870,7 @@ mod test_fmt {
}
#[test]
fn when_with_alternatives() {
fn when_with_alternatives_1() {
expr_formats_same(indoc!(
r#"
when b is
@ -1848,6 +1883,10 @@ mod test_fmt {
5
"#
));
}
#[test]
fn when_with_alternatives_2() {
expr_formats_same(indoc!(
r#"
when b is
@ -1857,6 +1896,10 @@ mod test_fmt {
1
"#
));
}
#[test]
fn when_with_alternatives_3() {
expr_formats_to(
indoc!(
r#"
@ -1874,6 +1917,10 @@ mod test_fmt {
"#
),
);
}
#[test]
fn when_with_alternatives_4() {
expr_formats_to(
indoc!(
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]
fn when_with_moving_comments() {
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]
fn when_guard() {
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
#[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]
fn backpassing_body_on_newline() {
expr_formats_same(indoc!(

View file

@ -16,6 +16,7 @@ roc_builtins = { path = "../builtins" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
roc_reporting = { path = "../../reporting" }
bumpalo = { version = "3.8.0", features = ["collections"] }
target-lexicon = "0.12.2"
# 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_module::symbol::Symbol;
use roc_mono::layout::Layout;
use roc_reporting::internal_error;
#[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug)]
#[allow(dead_code)]
@ -151,13 +152,15 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
saved_regs: &[AArch64GeneralReg],
requested_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.
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.
.ok_or("Ran out of stack space")?
.checked_add(fn_call_stack_size)
.ok_or("Ran out of stack space")?;
.and_then(|size| size.checked_add(fn_call_stack_size))
{
Some(size) => size,
_ => internal_error!("Ran out of stack space"),
};
let alignment = if full_stack_size <= 0 {
0
} else {
@ -194,12 +197,12 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
offset -= 8;
AArch64Assembler::mov_base32_reg64(buf, offset, *reg);
}
Ok(aligned_stack_size)
aligned_stack_size
} else {
Ok(0)
0
}
} 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],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> {
) {
if aligned_stack_size > 0 {
// All the following stores could be optimized by using `STP` to store pairs.
let mut offset = aligned_stack_size;
@ -230,7 +233,6 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
aligned_stack_size,
);
}
Ok(())
}
#[inline(always)]
@ -239,8 +241,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
_symbol_map: &mut MutMap<Symbol, SymbolStorage<AArch64GeneralReg, AArch64FloatReg>>,
_args: &'a [(Layout<'a>, Symbol)],
_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)]
@ -250,8 +252,8 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
_args: &'a [Symbol],
_arg_layouts: &[Layout<'a>],
_ret_layout: &Layout<'a>,
) -> Result<u32, String> {
Err("Storing args not yet implemented for AArch64".to_string())
) -> u32 {
unimplemented!("Storing args not yet implemented for AArch64");
}
fn return_struct<'a>(
@ -260,12 +262,12 @@ impl CallConv<AArch64GeneralReg, AArch64FloatReg> for AArch64Call {
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_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> {
Err("Returning via arg pointer not yet implemented for AArch64".to_string())
fn returns_via_arg_pointer(_ret_layout: &Layout) -> bool {
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_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
use roc_reporting::internal_error;
// 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.
@ -152,7 +153,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
general_saved_regs: &[X86_64GeneralReg],
requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String> {
) -> i32 {
x86_64_generic_setup_stack(
buf,
general_saved_regs,
@ -167,7 +168,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
general_saved_regs: &[X86_64GeneralReg],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> {
) {
x86_64_generic_cleanup_stack(
buf,
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>>,
args: &'a [(Layout<'a>, Symbol)],
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 general_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::RET_POINTER,
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[general_i]),
@ -251,21 +252,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
);
general_i += 2;
} else {
return Err(
"loading strings args on the stack is not yet implemented".to_string()
);
unimplemented!("loading strings args on the stack is not yet implemented");
}
}
Layout::Struct(&[]) => {}
x => {
return Err(format!(
"Loading args with layout {:?} not yet implemented",
x
));
unimplemented!("Loading args with layout {:?} not yet implemented", x);
}
}
}
Ok(())
}
#[inline(always)]
@ -275,7 +270,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<u32, String> {
) -> u32 {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
let mut general_i = 0;
let mut float_i = 0;
@ -284,22 +279,22 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
match ret_layout {
Layout::Builtin(single_register_builtins!() | Builtin::Str) => {}
x => {
return Err(format!(
"receiving return type, {:?}, is not yet implemented",
x
));
unimplemented!("receiving return type, {:?}, is not yet implemented", x);
}
}
for (i, layout) in arg_layouts.iter().enumerate() {
match layout {
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() {
// Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[general_i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg { 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);
}
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
internal_error!("Cannot load floating point symbol into GeneralReg")
}
}
general_i += 1;
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg { 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 { .. } => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
internal_error!("Cannot load floating point symbol into GeneralReg")
}
}
stack_offset += 8;
}
}
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() {
// Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[float_i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg { 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::BaseAndGeneralReg { .. } => {
return Err("Cannot load general symbol into FloatReg".to_string())
internal_error!("Cannot load general symbol into FloatReg")
}
}
float_i += 1;
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg { 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::BaseAndGeneralReg { .. } => {
return Err("Cannot load general symbol into FloatReg".to_string())
internal_error!("Cannot load general symbol into FloatReg")
}
}
stack_offset += 8;
}
}
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() {
// Load the value to the param reg.
let dst1 = Self::GENERAL_PARAM_REGS[general_i];
let dst2 = Self::GENERAL_PARAM_REGS[general_i + 1];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::Base { offset, .. } => {
X86_64Assembler::mov_reg64_base32(buf, dst1, *offset);
X86_64Assembler::mov_reg64_base32(buf, dst2, *offset + 8);
}
_ => {
return Err("Strings only support being loaded from base offsets"
.to_string());
internal_error!(
"Strings only support being loaded from base offsets"
);
}
}
general_i += 2;
} else {
return Err(
unimplemented!(
"calling functions with strings on the stack is not yet implemented"
.to_string(),
);
}
}
Layout::Struct(&[]) => {}
x => {
return Err(format!(
"calling with arg type, {:?}, is not yet implemented",
x
));
unimplemented!("calling with arg type, {:?}, is not yet implemented", x);
}
}
}
Ok(stack_offset as u32)
stack_offset as u32
}
fn return_struct<'a>(
@ -442,14 +430,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64SystemV {
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_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.
// 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],
requested_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)
}
@ -561,7 +549,7 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
saved_regs: &[X86_64GeneralReg],
aligned_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)
}
@ -571,10 +559,10 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
symbol_map: &mut MutMap<Symbol, SymbolStorage<X86_64GeneralReg, X86_64FloatReg>>,
args: &'a [(Layout<'a>, Symbol)],
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 i = 0;
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout)? {
if X86_64WindowsFastcall::returns_via_arg_pointer(ret_layout) {
symbol_map.insert(
Symbol::RET_POINTER,
SymbolStorage::GeneralReg(Self::GENERAL_PARAM_REGS[i]),
@ -595,27 +583,20 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
}
Layout::Builtin(Builtin::Str) => {
// 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."
.to_string(),
);
}
Layout::Struct(&[]) => {}
x => {
return Err(format!(
"Loading args with layout {:?} not yet implemented",
x
));
unimplemented!("Loading args with layout {:?} not yet implemented", x);
}
}
} else {
base_offset += match layout {
Layout::Builtin(single_register_builtins!()) => 8,
x => {
return Err(format!(
"Loading args with layout {:?} not yet implemented",
x
));
unimplemented!("Loading args with layout {:?} not yet implemented", x);
}
};
symbol_map.insert(
@ -628,7 +609,6 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
);
}
}
Ok(())
}
#[inline(always)]
@ -638,29 +618,29 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<u32, String> {
) -> u32 {
let mut stack_offset = Self::SHADOW_SPACE_SIZE as i32;
// For most return layouts we will do nothing.
// In some cases, we need to put the return address as the first arg.
match ret_layout {
Layout::Builtin(single_register_builtins!()) => {}
x => {
return Err(format!(
"receiving return type, {:?}, is not yet implemented",
x
));
unimplemented!("receiving return type, {:?}, is not yet implemented", x);
}
}
for (i, layout) in arg_layouts.iter().enumerate() {
match layout {
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() {
// Load the value to the param reg.
let dst = Self::GENERAL_PARAM_REGS[i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg { 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);
}
SymbolStorage::FloatReg(_) | SymbolStorage::BaseAndFloatReg { .. } => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
internal_error!("Cannot load floating point symbol into GeneralReg")
}
}
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::GeneralReg(reg)
| SymbolStorage::BaseAndGeneralReg { 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 { .. } => {
return Err(
"Cannot load floating point symbol into GeneralReg".to_string()
)
internal_error!("Cannot load floating point symbol into GeneralReg")
}
}
stack_offset += 8;
}
}
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() {
// Load the value to the param reg.
let dst = Self::FLOAT_PARAM_REGS[i];
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg { 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::BaseAndGeneralReg { .. } => {
return Err("Cannot load general symbol into FloatReg".to_string())
unimplemented!("Cannot load general symbol into FloatReg")
}
}
} else {
// Load the value to the stack.
match symbol_map
.get(&args[i])
.ok_or("function argument does not reference any symbol")?
{
match storage {
SymbolStorage::FloatReg(reg)
| SymbolStorage::BaseAndFloatReg { 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::BaseAndGeneralReg { .. } => {
return Err("Cannot load general symbol into FloatReg".to_string())
unimplemented!("Cannot load general symbol into FloatReg")
}
}
stack_offset += 8;
@ -759,20 +732,15 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
}
Layout::Builtin(Builtin::Str) => {
// I think this just needs to be passed on the stack, so not a huge deal.
return Err(
"Passing str args with Windows fast call not yet implemented.".to_string(),
);
unimplemented!("Passing str args with Windows fast call not yet implemented.");
}
Layout::Struct(&[]) => {}
x => {
return Err(format!(
"calling with arg type, {:?}, is not yet implemented",
x
));
unimplemented!("calling with arg type, {:?}, is not yet implemented", x);
}
}
}
Ok(stack_offset as u32)
stack_offset as u32
}
fn return_struct<'a>(
@ -781,14 +749,14 @@ impl CallConv<X86_64GeneralReg, X86_64FloatReg> for X86_64WindowsFastcall {
_struct_size: u32,
_field_layouts: &[Layout<'a>],
_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.
// 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],
requested_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<i32, String> {
) -> i32 {
X86_64Assembler::push_reg64(buf, X86_64GeneralReg::RBP);
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)
.ok_or("Ran out of stack space")?
.checked_add(fn_call_stack_size)
.ok_or("Ran out of stack space")?;
.and_then(|size| size.checked_add(fn_call_stack_size))
{
Some(size) => size,
_ => internal_error!("Ran out of stack space"),
};
let alignment = if full_stack_size <= 0 {
0
} else {
@ -832,12 +802,12 @@ fn x86_64_generic_setup_stack<'a>(
X86_64Assembler::mov_base32_reg64(buf, -offset, *reg);
offset -= 8;
}
Ok(aligned_stack_size)
aligned_stack_size
} else {
Ok(0)
0
}
} 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],
aligned_stack_size: i32,
fn_call_stack_size: i32,
) -> Result<(), String> {
) {
if aligned_stack_size > 0 {
let mut offset = aligned_stack_size - fn_call_stack_size;
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::pop_reg64(buf, X86_64GeneralReg::RBP);
Ok(())
}
impl Assembler<X86_64GeneralReg, X86_64FloatReg> for X86_64Assembler {

View file

@ -13,6 +13,7 @@ use roc_mono::ir::{
SelfRecursive, Stmt,
};
use roc_mono::layout::{Builtin, Layout, LayoutIds};
use roc_reporting::internal_error;
mod generic64;
mod object_builder;
@ -58,7 +59,7 @@ where
Self: Sized,
{
/// 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>;
@ -70,55 +71,48 @@ where
/// 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.
/// 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.
// The backend should track these args so it can use them as needed.
fn load_args(
&mut self,
args: &'a [(Layout<'a>, Symbol)],
ret_layout: &Layout<'a>,
) -> Result<(), String>;
fn load_args(&mut self, args: &'a [(Layout<'a>, Symbol)], ret_layout: &Layout<'a>);
/// 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.
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()
.get(proc.name, &proc.ret_layout)
.to_symbol_string(proc.name, &self.env().interns);
self.reset(proc_name, proc.is_self_recursive);
self.load_args(proc.args, &proc.ret_layout)?;
self.load_args(proc.args, &proc.ret_layout);
for (layout, sym) in proc.args {
self.set_layout_map(*sym, layout)?;
self.set_layout_map(*sym, layout);
}
self.scan_ast(&proc.body);
self.create_free_map();
self.build_stmt(&proc.body, &proc.ret_layout)?;
self.build_stmt(&proc.body, &proc.ret_layout);
self.finalize()
}
/// 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 {
Stmt::Let(sym, expr, layout, following) => {
self.build_expr(sym, expr, layout)?;
self.set_layout_map(*sym, layout)?;
self.free_symbols(stmt)?;
self.build_stmt(following, ret_layout)?;
Ok(())
self.build_expr(sym, expr, layout);
self.set_layout_map(*sym, layout);
self.free_symbols(stmt);
self.build_stmt(following, ret_layout);
}
Stmt::Ret(sym) => {
self.load_literal_symbols(&[*sym])?;
self.return_symbol(sym, ret_layout)?;
self.free_symbols(stmt)?;
Ok(())
self.load_literal_symbols(&[*sym]);
self.return_symbol(sym, ret_layout);
self.free_symbols(stmt);
}
Stmt::Refcounting(_modify, following) => {
// TODO: actually deal with refcounting. For hello world, we just skipped it.
self.build_stmt(following, ret_layout)?;
Ok(())
self.build_stmt(following, ret_layout);
}
Stmt::Switch {
cond_symbol,
@ -127,16 +121,15 @@ where
default_branch,
ret_layout,
} => {
self.load_literal_symbols(&[*cond_symbol])?;
self.load_literal_symbols(&[*cond_symbol]);
self.build_switch(
cond_symbol,
cond_layout,
branches,
default_branch,
ret_layout,
)?;
self.free_symbols(stmt)?;
Ok(())
);
self.free_symbols(stmt);
}
Stmt::Join {
id,
@ -145,11 +138,10 @@ where
remainder,
} => {
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.free_symbols(stmt)?;
Ok(())
self.build_join(id, parameters, body, remainder, ret_layout);
self.free_symbols(stmt);
}
Stmt::Jump(id, args) => {
let mut arg_layouts: bumpalo::collections::Vec<Layout<'a>> =
@ -160,14 +152,13 @@ where
if let Some(layout) = layout_map.get(arg) {
arg_layouts.push(*layout);
} 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.free_symbols(stmt)?;
Ok(())
self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout);
self.free_symbols(stmt);
}
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.
@ -178,7 +169,7 @@ where
branches: &'a [(u64, BranchInfo<'a>, Stmt<'a>)],
default_branch: &(BranchInfo<'a>, &'a Stmt<'a>),
ret_layout: &Layout<'a>,
) -> Result<(), String>;
);
// build_join generates a instructions for a join statement.
fn build_join(
@ -188,7 +179,7 @@ where
body: &'a Stmt<'a>,
remainder: &'a Stmt<'a>,
ret_layout: &Layout<'a>,
) -> Result<(), String>;
);
// build_jump generates a instructions for a jump statement.
fn build_jump(
@ -197,24 +188,18 @@ where
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String>;
);
/// build_expr builds the expressions for the specified symbol.
/// The builder must keep track of the symbol because it may be referred to later.
fn build_expr(
&mut self,
sym: &Symbol,
expr: &Expr<'a>,
layout: &Layout<'a>,
) -> Result<(), String> {
fn build_expr(&mut self, sym: &Symbol, expr: &Expr<'a>, layout: &Layout<'a>) {
match expr {
Expr::Literal(lit) => {
if self.env().lazy_literals {
self.literal_map().insert(*sym, *lit);
} else {
self.load_literal(sym, lit)?;
self.load_literal(sym, lit);
}
Ok(())
}
Expr::Call(roc_mono::ir::Call {
call_type,
@ -244,7 +229,7 @@ where
.get(*func_sym, layout)
.to_symbol_string(*func_sym, &self.env().interns);
// 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)
} else {
self.build_inline_builtin(
@ -266,7 +251,7 @@ where
if let Some(layout) = layout_map.get(arg) {
arg_layouts.push(*layout);
} 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(
@ -277,19 +262,21 @@ where
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) => {
self.load_literal_symbols(fields)?;
self.create_struct(sym, layout, fields)
self.load_literal_symbols(fields);
self.create_struct(sym, layout, fields);
}
Expr::StructAtIndex {
index,
field_layouts,
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],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
) {
// Now that the arguments are needed, load them if they are literals.
self.load_literal_symbols(args)?;
self.load_literal_symbols(args);
match lowlevel {
LowLevel::NumAbs => {
debug_assert_eq!(
@ -480,7 +467,7 @@ where
arg_layouts,
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],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String> {
self.load_literal_symbols(args)?;
) {
self.load_literal_symbols(args);
match func_sym {
Symbol::NUM_IS_ZERO => {
debug_assert_eq!(
@ -507,14 +494,11 @@ where
"NumIsZero: expected to have return layout of type Bool"
);
self.load_literal(&Symbol::DEV_TMP, &Literal::Int(0))?;
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0])?;
self.load_literal(&Symbol::DEV_TMP, &Literal::Int(0));
self.build_eq(sym, &args[0], &Symbol::DEV_TMP, &arg_layouts[0]);
self.free_symbol(&Symbol::DEV_TMP)
}
_ => Err(format!(
"the function, {:?}, is not yet implemented",
func_sym
)),
_ => unimplemented!("the function, {:?}, is not yet implemented", func_sym),
}
}
@ -527,77 +511,31 @@ where
args: &'a [Symbol],
arg_layouts: &[Layout<'a>],
ret_layout: &Layout<'a>,
) -> Result<(), String>;
);
/// build_num_abs stores the absolute value of src into dst.
fn build_num_abs(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
fn build_num_abs(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>);
/// build_num_add stores the sum of src1 and src2 into dst.
fn build_num_add(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
fn build_num_add(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
/// build_num_mul stores `src1 * src2` into dst.
fn build_num_mul(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
fn build_num_mul(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
/// build_num_neg stores the negated value of src into dst.
fn build_num_neg(
&mut self,
dst: &Symbol,
src: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &Layout<'a>);
/// build_num_sub stores the `src1 - src2` difference into dst.
fn build_num_sub(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
layout: &Layout<'a>,
) -> Result<(), String>;
fn build_num_sub(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &Layout<'a>);
/// build_eq stores the result of `src1 == src2` into dst.
fn build_eq(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String>;
fn build_eq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);
/// build_neq stores the result of `src1 != src2` into dst.
fn build_neq(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String>;
fn build_neq(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);
/// build_num_lt stores the result of `src1 < src2` into dst.
fn build_num_lt(
&mut self,
dst: &Symbol,
src1: &Symbol,
src2: &Symbol,
arg_layout: &Layout<'a>,
) -> Result<(), String>;
fn build_num_lt(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, arg_layout: &Layout<'a>);
/// build_num_to_float convert Number to Float
fn build_num_to_float(
@ -606,29 +544,23 @@ where
src: &Symbol,
arg_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.
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 {
for sym in syms {
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.
fn create_struct(
&mut self,
sym: &Symbol,
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String>;
fn create_struct(&mut self, sym: &Symbol, layout: &Layout<'a>, fields: &'a [Symbol]);
/// load_struct_at_index loads into `sym` the value at `index` in `structure`.
fn load_struct_at_index(
@ -637,27 +569,26 @@ where
structure: &Symbol,
index: u64,
field_layouts: &'a [Layout<'a>],
) -> Result<(), String>;
);
/// 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.
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.
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>)) {
for sym in syms {
// 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.
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.
fn set_last_seen(
@ -676,20 +607,18 @@ where
fn last_seen_map(&mut self) -> &mut MutMap<Symbol, *const Stmt<'a>>;
/// 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) {
// Layout map already contains the symbol. We should never need to overwrite.
// If the layout is not the same, that is a bug.
if &old_layout != layout {
Err(format!(
"Overwriting layout for symbol, {:?}. This should never happen. got {:?}, want {:?}",
sym, layout, old_layout
))
} else {
Ok(())
internal_error!(
"Overwriting layout for symbol, {:?}: got {:?}, want {:?}",
sym,
layout,
old_layout
)
}
} else {
Ok(())
}
}
@ -779,8 +708,8 @@ where
self.set_last_seen(*sym, stmt, &owning_symbol);
}
}
Expr::Reset(sym) => {
self.set_last_seen(*sym, stmt, &owning_symbol);
Expr::Reset { symbol, .. } => {
self.set_last_seen(*symbol, stmt, &owning_symbol);
}
Expr::EmptyArray => {}
Expr::RuntimeErrorFunction(_) => {}

View file

@ -10,6 +10,7 @@ use object::{
use roc_collections::all::MutMap;
use roc_module::symbol;
use roc_mono::ir::{Proc, ProcLayout};
use roc_reporting::internal_error;
use target_lexicon::{Architecture as TargetArch, BinaryFormat as TargetBF, Triple};
// This is used by some code below which is currently commented out.
@ -22,7 +23,7 @@ pub fn build_module<'a>(
env: &'a Env,
target: &Triple,
procedures: MutMap<(symbol::Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<Object, String> {
) -> Object {
match target {
Triple {
architecture: TargetArch::X86_64,
@ -34,7 +35,7 @@ pub fn build_module<'a>(
x86_64::X86_64FloatReg,
x86_64::X86_64Assembler,
x86_64::X86_64SystemV,
> = Backend::new(env)?;
> = Backend::new(env);
build_object(
env,
procedures,
@ -52,7 +53,7 @@ pub fn build_module<'a>(
x86_64::X86_64FloatReg,
x86_64::X86_64Assembler,
x86_64::X86_64SystemV,
> = Backend::new(env)?;
> = Backend::new(env);
build_object(
env,
procedures,
@ -74,7 +75,7 @@ pub fn build_module<'a>(
aarch64::AArch64FloatReg,
aarch64::AArch64Assembler,
aarch64::AArch64Call,
> = Backend::new(env)?;
> = Backend::new(env);
build_object(
env,
procedures,
@ -92,7 +93,7 @@ pub fn build_module<'a>(
aarch64::AArch64FloatReg,
aarch64::AArch64Assembler,
aarch64::AArch64Call,
> = Backend::new(env)?;
> = Backend::new(env);
build_object(
env,
procedures,
@ -104,9 +105,7 @@ pub fn build_module<'a>(
),
)
}
x => Err(format! {
"the target, {:?}, is not yet implemented",
x}),
x => unimplemented!("the target, {:?}, is not yet implemented", x),
}
}
@ -115,7 +114,7 @@ fn generate_wrapper<'a, B: Backend<'a>>(
output: &mut Object,
wrapper_name: String,
wraps: String,
) -> Result<(), String> {
) {
let text_section = output.section_id(StandardSection::Text);
let proc_symbol = Symbol {
name: wrapper_name.as_bytes().to_vec(),
@ -128,7 +127,7 @@ fn generate_wrapper<'a, B: Backend<'a>>(
flags: SymbolFlags::None,
};
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 name = wraps.as_str().as_bytes();
@ -154,13 +153,12 @@ fn generate_wrapper<'a, B: Backend<'a>>(
addend: -4,
};
output
.add_relocation(text_section, reloc)
.map_err(|e| format!("{:?}", e))?;
Ok(())
match output.add_relocation(text_section, reloc) {
Ok(obj) => obj,
Err(e) => internal_error!("{:?}", e),
}
} 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>>,
mut backend: B,
mut output: Object,
) -> Result<Object, String> {
) -> Object {
let data_section = output.section_id(StandardSection::Data);
/*
@ -188,25 +186,25 @@ fn build_object<'a, B: Backend<'a>>(
&mut output,
"roc_alloc".into(),
"malloc".into(),
)?;
);
generate_wrapper(
&mut backend,
&mut output,
"roc_realloc".into(),
"realloc".into(),
)?;
);
generate_wrapper(
&mut backend,
&mut output,
"roc_dealloc".into(),
"free".into(),
)?;
);
generate_wrapper(
&mut backend,
&mut output,
"roc_panic".into(),
"roc_builtins.utils.test_panic".into(),
)?;
);
}
// 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];
for (fn_name, section_id, proc_id, proc) in procs {
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);
for reloc in relocs {
let elfreloc = match reloc {
@ -293,7 +291,7 @@ fn build_object<'a, B: Backend<'a>>(
addend: -4,
}
} else {
return Err(format!("failed to find data symbol for {:?}", name));
internal_error!("failed to find data symbol for {:?}", name);
}
}
Relocation::LinkedFunction { offset, name } => {
@ -323,7 +321,7 @@ fn build_object<'a, B: Backend<'a>>(
addend: -4,
}
} else {
return Err(format!("failed to find fn symbol for {:?}", name));
internal_error!("failed to find fn symbol for {:?}", name);
}
}
Relocation::JmpToReturn { .. } => unreachable!(),
@ -332,9 +330,10 @@ fn build_object<'a, B: Backend<'a>>(
}
}
for (section_id, reloc) in relocations {
match output.add_relocation(section_id, reloc) {
Ok(obj) => obj,
Err(e) => internal_error!("{:?}", e),
}
}
output
.add_relocation(section_id, reloc)
.map_err(|e| format!("{:?}", e))?;
}
Ok(output)
}

View file

@ -955,7 +955,7 @@ pub fn build_exp_call<'a, 'ctx, 'env>(
}
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 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),
Reset(symbol) => {
Reset { symbol, .. } => {
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
let tag_ptr = tag_ptr.into_pointer_value();
@ -4686,20 +4686,23 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
func_spec: FuncSpec,
higher_order: &HigherOrderLowLevel<'a>,
) -> BasicValueEnum<'ctx> {
use roc_mono::ir::PassedFunction;
use roc_mono::low_level::HigherOrder::*;
let HigherOrderLowLevel {
op,
arg_layouts: argument_layouts,
ret_layout: result_layout,
function_owns_closure_data,
function_name,
function_env,
passed_function,
..
} = higher_order;
let function_owns_closure_data = *function_owns_closure_data;
let function_name = *function_name;
let PassedFunction {
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`
macro_rules! function_details {
@ -4712,7 +4715,8 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
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)
}};
@ -4737,14 +4741,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
closure_layout,
function_owns_closure_data,
argument_layouts,
*result_layout,
result_layout,
);
crate::llvm::build_list::list_walk_generic(
env,
layout_ids,
roc_function_call,
result_layout,
&result_layout,
list,
element_layout,
default,
@ -4974,7 +4978,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
closure_layout,
function_owns_closure_data,
argument_layouts,
*result_layout,
result_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,
function_owns_closure_data,
argument_layouts,
*result_layout,
result_layout,
);
list_keep_oks(
env,
layout_ids,
roc_function_call,
result_layout,
&result_layout,
list,
before_layout,
after_layout,
@ -5042,14 +5046,14 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
closure_layout,
function_owns_closure_data,
argument_layouts,
*result_layout,
result_layout,
);
list_keep_errs(
env,
layout_ids,
roc_function_call,
result_layout,
&result_layout,
list,
before_layout,
after_layout,
@ -5094,7 +5098,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
closure_layout,
function_owns_closure_data,
argument_layouts,
*result_layout,
result_layout,
);
list_sort_with(
@ -5197,7 +5201,7 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
closure_layout,
function_owns_closure_data,
argument_layouts,
*result_layout,
result_layout,
);
dict_walk(
@ -5522,6 +5526,24 @@ fn run_low_level<'a, 'ctx, 'env>(
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
| NumCeiling | NumFloor | NumToFloat | NumIsFinite | NumAtan | NumAcos | NumAsin => {
debug_assert_eq!(args.len(), 1);
@ -6020,6 +6042,10 @@ fn run_low_level<'a, 'ctx, 'env>(
| ListAny | ListAll | ListFindUnsafe | DictWalk => {
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()
}
/// Str.fromInt : Int -> Str
/// Str.fromFloat : Int -> Str
pub fn str_from_float<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,

View file

@ -19,6 +19,8 @@ use roc_module::symbol::Interns;
use roc_module::symbol::Symbol;
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 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 roc_collections::all::MutMap;
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::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::storage::{Storage, StoredValue, StoredValueKind};
use crate::wasm_module::linking::{
@ -20,7 +21,7 @@ use crate::wasm_module::sections::{
};
use crate::wasm_module::{
code_builder, BlockType, CodeBuilder, ConstExpr, Export, ExportType, Global, GlobalType,
LocalId, Signature, SymInfo, ValueType,
LinkingSubSection, LocalId, Signature, SymInfo, ValueType,
};
use crate::{
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> {
env: &'a Env<'a>,
interns: &'a mut Interns,
// Module-level data
pub module: WasmModule<'a>,
module: WasmModule<'a>,
layout_ids: LayoutIds<'a>,
constant_sym_index_map: MutMap<&'a str, usize>,
builtin_sym_index_map: MutMap<&'a str, usize>,
proc_symbols: Vec<'a, Symbol>,
pub linker_symbols: Vec<'a, SymInfo>,
proc_symbols: Vec<'a, (Symbol, u32)>,
linker_symbols: Vec<'a, SymInfo>,
refcount_proc_gen: RefcountProcGenerator<'a>,
// Function-level data
code_builder: CodeBuilder<'a>,
storage: Storage<'a>,
symbol_layouts: MutMap<Symbol, Layout<'a>>,
/// how many blocks deep are we (used for jumps)
block_depth: u32,
@ -58,10 +62,12 @@ pub struct WasmBackend<'a> {
impl<'a> WasmBackend<'a> {
pub fn new(
env: &'a Env<'a>,
interns: &'a mut Interns,
layout_ids: LayoutIds<'a>,
proc_symbols: Vec<'a, Symbol>,
proc_symbols: Vec<'a, (Symbol, u32)>,
mut linker_symbols: Vec<'a, SymInfo>,
mut exports: Vec<'a, Export>,
refcount_proc_gen: RefcountProcGenerator<'a>,
) -> Self {
const MEMORY_INIT_SIZE: u32 = 1024 * 1024;
let arena = env.arena;
@ -124,6 +130,7 @@ impl<'a> WasmBackend<'a> {
WasmBackend {
env,
interns,
// Module-level data
module,
@ -133,15 +140,47 @@ impl<'a> WasmBackend<'a> {
builtin_sym_index_map: MutMap::default(),
proc_symbols,
linker_symbols,
refcount_proc_gen,
// Function-level data
block_depth: 0,
joinpoint_label_map: MutMap::default(),
code_builder: CodeBuilder::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
fn reset(&mut self) {
// 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.joinpoint_label_map.clear();
self.symbol_layouts.clear();
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> {
// println!("\ngenerating procedure {:?}\n", _sym);
pub fn build_proc(&mut self, proc: &Proc<'a>) -> Result<(), String> {
// println!("\ngenerating procedure {:?}\n", proc.name);
self.start_proc(&proc);
self.start_proc(proc);
self.build_stmt(&proc.body, &proc.ret_layout)?;
self.finalize_proc()?;
self.reset();
// println!("\nfinished generating {:?}\n", _sym);
// println!("\nfinished generating {:?}\n", proc.name);
Ok(())
}
fn start_proc(&mut self, proc: &Proc<'a>) {
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.start_block(BlockType::NoResult); // block to ensure all paths pop stack memory (if any)
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 {
let arg_layout = WasmLayout::new(layout);
self.storage
@ -219,10 +263,9 @@ impl<'a> WasmBackend<'a> {
***********************************************************/
/// start a loop that leaves a value on the stack
fn start_loop_with_return(&mut self, value_type: ValueType) {
fn start_loop(&mut self, block_type: BlockType) {
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) {
@ -240,6 +283,8 @@ impl<'a> WasmBackend<'a> {
Stmt::Let(_, _, _, _) => {
let mut current_stmt = 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 kind = match following {
@ -265,6 +310,8 @@ impl<'a> WasmBackend<'a> {
);
}
self.symbol_layouts.insert(*sym, *layout);
current_stmt = *following;
}
@ -335,7 +382,7 @@ impl<'a> WasmBackend<'a> {
}
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
for (i, (value, _, _)) in branches.iter().enumerate() {
@ -419,10 +466,14 @@ impl<'a> WasmBackend<'a> {
self.end_block();
// A `return` inside of a `loop` seems to make it so that the `loop` itself
// also "returns" (so, leaves on the stack) a value of the return type.
let return_wasm_layout = WasmLayout::new(ret_layout);
self.start_loop_with_return(return_wasm_layout.value_type());
// A loop (or any block) needs to declare the type of the value it leaves on the stack on exit.
// The runtime needs this to statically validate the program before running it.
let loop_block_type = match WasmLayout::new(ret_layout).return_method() {
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)?;
@ -451,9 +502,46 @@ impl<'a> WasmBackend<'a> {
Ok(())
}
Stmt::Refcounting(_modify, following) => {
// TODO: actually deal with refcounting. For hello world, we just skipped it.
self.build_stmt(following, ret_layout)?;
Stmt::Refcounting(modify, following) => {
let value = modify.get_symbol();
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(())
}
@ -488,45 +576,35 @@ impl<'a> WasmBackend<'a> {
return self.build_low_level(lowlevel, arguments, *sym, wasm_layout);
}
let mut wasm_args_tmp: Vec<Symbol>;
let (wasm_args, has_return_val) = match wasm_layout {
WasmLayout::StackMemory { .. } => {
wasm_args_tmp =
Vec::with_capacity_in(arguments.len() + 1, self.env.arena);
wasm_args_tmp.push(*sym);
wasm_args_tmp.extend_from_slice(*arguments);
(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,
let (param_types, ret_type) = self.storage.load_symbols_for_call(
self.env.arena,
&mut self.code_builder,
arguments,
*sym,
&wasm_layout,
CallConv::C,
);
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, .. } => {
@ -561,6 +639,27 @@ impl<'a> WasmBackend<'a> {
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)),
}
}
@ -572,14 +671,13 @@ impl<'a> WasmBackend<'a> {
return_sym: Symbol,
return_layout: WasmLayout,
) -> Result<(), String> {
// Load symbols using the "fast calling convention" that Zig uses instead of the C ABI we normally use.
// It's only different from the C ABI for small structs, and we are using Zig for all of those cases.
// 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(
let (param_types, ret_type) = self.storage.load_symbols_for_call(
self.env.arena,
&mut self.code_builder,
arguments,
return_sym,
&return_layout,
CallConv::Zig,
);
let build_result = decode_low_level(
@ -594,7 +692,7 @@ impl<'a> WasmBackend<'a> {
match build_result {
Done => Ok(()),
BuiltinCall(name) => {
self.call_zig_builtin(name, arguments, &return_layout);
self.call_zig_builtin(name, param_types, ret_type);
Ok(())
}
NotImplemented => Err(format!(
@ -611,7 +709,7 @@ impl<'a> WasmBackend<'a> {
sym: Symbol,
layout: &Layout<'a>,
) -> 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 {
StoredValue::VirtualMachineStack { value_type, .. } => {
@ -655,6 +753,8 @@ impl<'a> WasmBackend<'a> {
stack_mem_bytes[7] = 0x80 | (len as u8);
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.i64_const(str_as_int);
self.code_builder.i64_store(Align::Bytes4, offset);
@ -712,10 +812,13 @@ impl<'a> WasmBackend<'a> {
None => {
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
// RocStr `elements` field will point to that constant data, not the heap
let segment_offset = const_segment_bytes.len() as u32;
let elements_addr = segment_offset + CONST_SEGMENT_BASE_ADDR;
// Store the string in the data section
// Prefix it with a special refcount value (treated as "infinity")
// The string's `elements` field points at the data after the refcount
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());
// Generate linker info
@ -723,12 +826,12 @@ impl<'a> WasmBackend<'a> {
let name = self
.layout_ids
.get(sym, layout)
.to_symbol_string(sym, &self.env.interns);
.to_symbol_string(sym, self.interns);
let linker_symbol = SymInfo::Data(DataSymbol::Defined {
flags: 0,
name,
segment_index: CONST_SEGMENT_INDEX as u32,
segment_offset,
segment_offset: elements_offset,
size: string.len() as u32,
});
@ -767,7 +870,9 @@ impl<'a> WasmBackend<'a> {
);
}
} 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.
/// 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.
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) {
Some(sym_idx) => match &self.linker_symbols[*sym_idx] {
SymInfo::Function(WasmObjectSymbol::Imported { index, .. }) => {
@ -799,51 +912,14 @@ impl<'a> WasmBackend<'a> {
},
None => {
let mut param_types = Vec::with_capacity_in(1 + arguments.len(), self.env.arena);
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 {
// Wasm function signature
let signature = Signature {
param_types,
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 = Import {
module: BUILTINS_IMPORT_MODULE_NAME,
@ -852,22 +928,22 @@ impl<'a> WasmBackend<'a> {
};
self.module.import.entries.push(import);
// Provide symbol information for the linker
let sym_idx = self.linker_symbols.len();
let sym_info = SymInfo::Function(WasmObjectSymbol::Imported {
flags: WASM_SYM_UNDEFINED,
index: import_index,
});
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);
(import_index, sym_idx as u32)
}
};
self.code_builder.call(
fn_index,
linker_symbol_index,
arguments.len(),
true, // TODO: handle builtins with no return value
);
self.code_builder
.call(fn_index, linker_symbol_index, num_wasm_args, has_return_val);
}
}

View file

@ -1,12 +1,26 @@
use roc_builtins::bitcode::{FloatWidth, IntWidth};
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)]
pub enum StackMemoryFormat {
/// Record, Str, List, Dict, etc.
Aggregate,
DataStructure,
Int128,
Float128,
Decimal,
@ -25,9 +39,6 @@ pub enum WasmLayout {
alignment_bytes: u32,
format: StackMemoryFormat,
},
// Local pointer to heap memory
HeapMemory,
}
impl WasmLayout {
@ -82,7 +93,7 @@ impl WasmLayout {
| Layout::Union(NonRecursive(_)) => Self::StackMemory {
size,
alignment_bytes,
format: StackMemoryFormat::Aggregate,
format: StackMemoryFormat::DataStructure,
},
Layout::Union(
@ -91,26 +102,95 @@ impl WasmLayout {
| NullableWrapped { .. }
| 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 {
Self::Primitive(type_, _) => *type_,
_ => PTR_TYPE,
// 1 Roc argument => 1 Wasm argument (same for all calling conventions)
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 {
Self::Primitive(_, size) => *size,
Self::StackMemory { size, .. } => *size,
Self::HeapMemory => PTR_SIZE,
Self::Primitive(ty, _) => ReturnMethod::Primitive(*ty),
Self::StackMemory { size, .. } => {
if *size == 0 {
ReturnMethod::NoReturnValue
} else {
ReturnMethod::WriteToPointerArg
}
}
}
}
}
pub fn is_stack_memory(&self) -> bool {
matches!(self, Self::StackMemory { .. })
#[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
}
}
}
}
}
}
}

View file

@ -6,16 +6,17 @@ pub mod wasm_module;
use bumpalo::{self, collections::Vec, Bump};
use roc_builtins::bitcode::IntWidth;
use roc_collections::all::{MutMap, MutSet};
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::layout::LayoutIds;
use crate::backend::WasmBackend;
use crate::wasm_module::{
Align, CodeBuilder, Export, ExportType, LinkingSubSection, LocalId, SymInfo, ValueType,
WasmModule,
Align, CodeBuilder, Export, ExportType, LocalId, SymInfo, ValueType, WasmModule,
};
const PTR_SIZE: u32 = 4;
@ -29,27 +30,29 @@ pub const STACK_POINTER_NAME: &str = "__stack_pointer";
pub struct Env<'a> {
pub arena: &'a Bump,
pub interns: Interns,
pub module_id: ModuleId,
pub exposed_to_host: MutSet<Symbol>,
}
pub fn build_module<'a>(
env: &'a Env,
env: &'a Env<'a>,
interns: &'a mut Interns,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> 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);
wasm_module.serialize_mut(&mut buffer);
Ok(buffer)
}
pub fn build_module_help<'a>(
env: &'a Env,
env: &'a Env<'a>,
interns: &'a mut Interns,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<(WasmModule<'a>, u32), String> {
let mut layout_ids = LayoutIds::default();
let mut generated_procs = Vec::with_capacity_in(procedures.len(), env.arena);
let mut generated_symbols = Vec::with_capacity_in(procedures.len(), env.arena);
let mut procs = 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 exports = Vec::with_capacity_in(4, env.arena);
let mut main_fn_index = None;
@ -61,12 +64,11 @@ pub fn build_module_help<'a>(
if LowLevel::from_inlined_wrapper(sym).is_some() {
continue;
}
generated_procs.push(proc);
generated_symbols.push(sym);
procs.push(proc);
let fn_name = layout_ids
.get_toplevel(sym, &layout)
.to_symbol_string(sym, &env.interns);
.to_symbol_string(sym, interns);
if env.exposed_to_host.contains(&sym) {
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);
proc_symbols.push((sym, linker_symbols.len() as u32));
linker_symbols.push(linker_sym);
fn_index += 1;
}
// Build the Wasm module
let (mut module, linker_symbols) = {
let mut backend = WasmBackend::new(
env,
interns,
layout_ids,
generated_symbols.clone(),
proc_symbols,
linker_symbols,
exports,
RefcountProcGenerator::new(env.arena, IntWidth::I32, env.module_id),
);
for (proc, sym) in generated_procs.into_iter().zip(generated_symbols) {
backend.build_proc(proc, sym)?;
if false {
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);
module.linking.subsections.push(symbol_table);
// Generate procs from user code
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()))
}
@ -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 {
return;
}
if config.size == 0 {
return;
}
let alignment = Align::from(config.alignment_bytes);
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::symbol::Symbol;
use crate::layout::{StackMemoryFormat, WasmLayout};
use crate::storage::Storage;
use crate::wasm_module::{
CodeBuilder,
ValueType::{self, *},
};
use crate::layout::{StackMemoryFormat::*, WasmLayout};
use crate::storage::{Storage, StoredValue};
use crate::wasm_module::{CodeBuilder, ValueType::*};
pub enum LowlevelBuildResult {
Done,
@ -71,95 +68,168 @@ pub fn decode_low_level<'a>(
F64 => code_builder.f64_add(),
},
WasmLayout::StackMemory { format, .. } => match format {
StackMemoryFormat::Aggregate => return NotImplemented,
StackMemoryFormat::Int128 => return NotImplemented,
StackMemoryFormat::Float128 => return NotImplemented,
StackMemoryFormat::Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW),
DataStructure => return NotImplemented,
Int128 => return NotImplemented,
Float128 => return NotImplemented,
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 => {
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(),
F32 => code_builder.f32_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,
NumSub => match ret_layout.value_type() {
NumSub => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_sub(),
I64 => code_builder.i64_sub(),
F32 => code_builder.f32_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 => {
code_builder.i32_sub();
wrap_i32(code_builder, ret_layout.size());
wrap_i32(code_builder, *size);
}
I64 => code_builder.i64_sub(),
F32 => code_builder.f32_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,
NumMul => match ret_layout.value_type() {
NumMul => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => code_builder.i32_mul(),
I64 => code_builder.i64_mul(),
F32 => code_builder.f32_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 => {
code_builder.i32_mul();
wrap_i32(code_builder, ret_layout.size());
wrap_i32(code_builder, *size);
}
I64 => code_builder.i64_mul(),
F32 => code_builder.f32_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,
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(),
I64 => code_builder.i64_gt_s(),
F32 => code_builder.f32_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(),
I64 => code_builder.i64_ge_s(),
F32 => code_builder.f32_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(),
I64 => code_builder.i64_lt_s(),
F32 => code_builder.f32_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(),
I64 => code_builder.i64_le_s(),
F32 => code_builder.f32_le(),
F64 => code_builder.f64_le(),
},
StoredValue::StackMemory { .. } => 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(),
I64 => code_builder.i64_div_s(),
F32 => code_builder.f32_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,
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(),
I64 => code_builder.i64_rem_s(),
F32 => return NotImplemented,
F64 => return NotImplemented,
},
StoredValue::StackMemory { .. } => 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 => {
let arg_storage = storage.get(&args[0]).to_owned();
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(),
F64 => code_builder.f64_abs(),
},
NumNeg => {
match ret_layout.value_type() {
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumNeg => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => {
// Unfortunate local.set/local.get
code_builder.i32_const(0);
storage.load_symbols(code_builder, args);
code_builder.i32_sub();
}
I64 => {
// Unfortunate local.set/local.get
code_builder.i64_const(0);
storage.load_symbols(code_builder, args);
code_builder.i64_sub();
}
F32 => code_builder.f32_neg(),
F64 => code_builder.f64_neg(),
}
}
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumSin => return NotImplemented,
NumCos => return NotImplemented,
NumSqrtUnchecked => return NotImplemented,
NumLogUnchecked => return NotImplemented,
NumRound => {
// FIXME
// thread 'gen_num::f64_round' panicked at 'called `Result::unwrap()` on an `Err` value:
// Io(Os { code: 2, kind: NotFound, message: "No such file or directory" })',
// compiler/test_gen/src/helpers/wasm.rs:185:53
// Note: Wasm has a `nearest` op, but it does round-to-even when fraction is exactly 0.5
// which fails tests. Will this do? Or is specific behaviour important?
let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ROUND[width]);
}
NumToFloat => match (ret_layout.value_type(), storage.get(&args[0]).value_type()) {
NumRound => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
F32 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F32]),
F64 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F64]),
_ => return NotImplemented,
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NumToFloat => {
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, I64) => code_builder.f32_convert_s_i64(),
(F32, F32) => {}
(F32, F64) => code_builder.f32_demote_f64(),
(F64, I32) => code_builder.f64_convert_s_i32(),
(F64, I64) => code_builder.f64_convert_s_i64(),
(F64, F32) => code_builder.f64_promote_f32(),
(F64, F64) => {}
_ => panic_ret_type(),
}
}
StackMemory { .. } => return NotImplemented,
},
WasmLayout::StackMemory { .. } => return NotImplemented,
}
}
NumPow => return NotImplemented,
NumCeiling => match ret_layout.value_type() {
NumCeiling => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => {
code_builder.f32_ceil();
code_builder.i32_trunc_s_f32()
@ -242,8 +327,11 @@ pub fn decode_low_level<'a>(
}
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumPowInt => return NotImplemented,
NumFloor => match ret_layout.value_type() {
NumFloor => match ret_layout {
WasmLayout::Primitive(value_type, _) => match value_type {
I32 => {
code_builder.f32_floor();
code_builder.i32_trunc_s_f32()
@ -254,7 +342,10 @@ pub fn decode_low_level<'a>(
}
_ => 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),
I64 => code_builder.i32_const(1),
F32 => {
@ -272,6 +363,8 @@ pub fn decode_low_level<'a>(
code_builder.i64_ne();
}
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumAtan => {
let width = float_width_from_layout(ret_layout);
return BuiltinCall(&bitcode::NUM_ATAN[width]);
@ -286,41 +379,65 @@ pub fn decode_low_level<'a>(
}
NumBytesToU16 => 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(),
I64 => code_builder.i64_and(),
_ => 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(),
I64 => code_builder.i64_xor(),
_ => 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(),
I64 => code_builder.i64_or(),
_ => panic_ret_type(),
},
WasmLayout::StackMemory { .. } => return NotImplemented,
},
NumShiftLeftBy => {
// Unfortunate local.set/local.get
// Swap order of arguments
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(),
I64 => code_builder.i64_shl(),
_ => 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(),
I64 => code_builder.i64_shr_s(),
_ => 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(),
I64 => code_builder.i64_shr_u(),
_ => 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, I64) => code_builder.i32_wrap_i64(),
(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, F32) => code_builder.f64_promote_f32(),
(F64, F64) => {}
}
}
StackMemory { .. } => return NotImplemented,
},
Eq => {
// TODO: For non-number types, this will implement pointer equality, which is wrong
match storage.get(&args[0]).value_type() {
WasmLayout::StackMemory { .. } => return NotImplemented,
}
}
Eq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_eq(),
I64 => code_builder.i64_eq(),
F32 => code_builder.f32_eq(),
F64 => code_builder.f64_eq(),
}
}
NotEq => {
// TODO: For non-number types, this will implement pointer inequality, which is wrong
match storage.get(&args[0]).value_type() {
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
NotEq => match storage.get(&args[0]) {
StoredValue::VirtualMachineStack { value_type, .. }
| StoredValue::Local { value_type, .. } => match value_type {
I32 => code_builder.i32_ne(),
I64 => code_builder.i64_ne(),
F32 => code_builder.f32_ne(),
F64 => code_builder.f64_ne(),
}
}
},
StoredValue::StackMemory { .. } => return NotImplemented,
},
And => code_builder.i32_and(),
Or => code_builder.i32_or(),
Not => code_builder.i32_eqz(),
Hash => 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
}
@ -390,9 +522,9 @@ fn wrap_i32(code_builder: &mut CodeBuilder, size: u32) {
}
fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth {
if wasm_layout.value_type() == ValueType::F32 {
FloatWidth::F32
} else {
FloatWidth::F64
match wasm_layout {
WasmLayout::Primitive(F32, _) => FloatWidth::F32,
WasmLayout::Primitive(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_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::{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 {
Parameter,
@ -55,17 +57,29 @@ pub enum 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 {
Self::VirtualMachineStack { value_type, .. } => *value_type,
Self::Local { value_type, .. } => *value_type,
Self::StackMemory { .. } => ValueType::I32,
// Simple numbers: 1 Roc argument => 1 Wasm argument
Self::VirtualMachineStack { value_type, .. } | Self::Local { value_type, .. } => {
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,
/// including the VM stack, local variables, and linear memory
#[derive(Debug)]
pub struct Storage<'a> {
pub arg_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 {
size,
alignment_bytes,
@ -152,12 +154,18 @@ impl<'a> Storage<'a> {
} => {
let location = match kind {
StoredValueKind::Parameter => {
if *size > 0 {
self.arg_types.push(PTR_TYPE);
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 => {
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.local_types.push(PTR_TYPE);
}
@ -243,13 +251,20 @@ impl<'a> Storage<'a> {
}
StoredValue::StackMemory {
location, format, ..
location,
format,
size,
..
} => {
if size == 0 {
return;
}
let (local_id, offset) = location.local_and_offset(self.stack_frame_pointer);
code_builder.get_local(local_id);
if format == StackMemoryFormat::Aggregate {
if format == StackMemoryFormat::DataStructure {
if offset != 0 {
code_builder.i32_const(offset as i32);
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
///
/// add : I128, I128 -> I128
@ -284,7 +337,11 @@ impl<'a> Storage<'a> {
StoredValue::VirtualMachineStack { .. } | StoredValue::Local { .. } => {
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);
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"
/// A bug in Zig means it always uses this for Wasm even when we specify C calling convention.
/// 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(
/// Load symbols for a function call
pub fn load_symbols_for_call(
&mut self,
arena: &'a Bump,
code_builder: &mut CodeBuilder,
symbols: &[Symbol],
arguments: &[Symbol],
return_symbol: Symbol,
return_layout: &WasmLayout,
) {
// Note: we are not doing verify_stack_match in this case so we may generate more code.
// We would need more bookkeeping in CodeBuilder to track which representation is on the stack!
call_conv: CallConv,
) -> (Vec<'a, ValueType>, Option<ValueType>) {
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() {
// Load the address where the return value should be written
let return_method = return_layout.return_method();
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);
};
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 {
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);
}
}
(wasm_arg_types, return_type)
}
/// 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
/// Child blocks cannot "see" values from their parent block
struct VmBlock<'a> {
@ -225,7 +234,9 @@ impl<'a> CodeBuilder<'a> {
pub fn set_top_symbol(&mut self, sym: Symbol) -> VmSymbolState {
let current_stack = &mut self.vm_block_stack.last_mut().unwrap().value_stack;
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;
VmSymbolState::Pushed { pushed_at }
@ -429,11 +440,13 @@ impl<'a> CodeBuilder<'a> {
) {
self.build_local_declarations(local_types);
if frame_size != 0 {
if let Some(frame_ptr_id) = frame_pointer {
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_pop(aligned_size, frame_ptr_id);
}
}
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::{
CapturedSymbols, EntryPoint, ExternalSpecializations, PartialProc, Proc, ProcLayout, Procs,
UpdateModeIds,
};
use roc_mono::layout::{Layout, LayoutCache, LayoutProblem};
use roc_parse::ast::{self, StrLiteral, TypeAnnotation};
@ -835,6 +836,7 @@ enum Msg<'a> {
external_specializations_requested: BumpMap<ModuleId, ExternalSpecializations>,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
problems: Vec<roc_mono::ir::MonoProblem>,
update_mode_ids: UpdateModeIds,
module_timing: ModuleTiming,
subs: Subs,
},
@ -2098,6 +2100,7 @@ fn update<'a>(
MadeSpecializations {
module_id,
mut ident_ids,
mut update_mode_ids,
subs,
procedures,
external_specializations_requested,
@ -2124,6 +2127,7 @@ fn update<'a>(
arena,
module_id,
&mut ident_ids,
&mut update_mode_ids,
&mut state.procedures,
);
@ -3922,6 +3926,7 @@ fn make_specializations<'a>(
) -> Msg<'a> {
let make_specializations_start = SystemTime::now();
let mut mono_problems = Vec::new();
let mut update_mode_ids = UpdateModeIds::new();
// do the thing
let mut mono_env = roc_mono::ir::Env {
arena,
@ -3930,7 +3935,7 @@ fn make_specializations<'a>(
home,
ident_ids: &mut ident_ids,
ptr_bytes,
update_mode_counter: 0,
update_mode_ids: &mut update_mode_ids,
// call_specialization_counter=0 is reserved
call_specialization_counter: 1,
};
@ -3973,6 +3978,7 @@ fn make_specializations<'a>(
layout_cache,
procedures,
problems: mono_problems,
update_mode_ids,
subs,
external_specializations_requested,
module_timing,
@ -4016,6 +4022,7 @@ fn build_pending_specializations<'a>(
};
let mut mono_problems = std::vec::Vec::new();
let mut update_mode_ids = UpdateModeIds::new();
let mut subs = solved_subs.into_inner();
let mut mono_env = roc_mono::ir::Env {
arena,
@ -4024,7 +4031,7 @@ fn build_pending_specializations<'a>(
home,
ident_ids: &mut ident_ids,
ptr_bytes,
update_mode_counter: 0,
update_mode_ids: &mut update_mode_ids,
// call_specialization_counter=0 is reserved
call_specialization_counter: 1,
};

View file

@ -108,6 +108,7 @@ pub enum LowLevel {
NumShiftRightBy,
NumShiftRightZfBy,
NumIntCast,
NumToStr,
Eq,
NotEq,
And,
@ -115,6 +116,9 @@ pub enum LowLevel {
Not,
Hash,
ExpectTrue,
RefCountGetPtr,
RefCountInc,
RefCountDec,
}
macro_rules! higher_order {
@ -186,12 +190,10 @@ impl LowLevel {
Symbol::STR_ENDS_WITH => Some(StrEndsWith),
Symbol::STR_SPLIT => Some(StrSplit),
Symbol::STR_COUNT_GRAPHEMES => Some(StrCountGraphemes),
Symbol::STR_FROM_INT => Some(StrFromInt),
Symbol::STR_FROM_UTF8 => None,
Symbol::STR_FROM_UTF8_RANGE => None,
Symbol::STR_TO_UTF8 => Some(StrToUtf8),
Symbol::STR_REPEAT => Some(StrRepeat),
Symbol::STR_FROM_FLOAT => Some(StrFromFloat),
Symbol::STR_TRIM => Some(StrTrim),
Symbol::STR_TRIM_LEFT => Some(StrTrimLeft),
Symbol::STR_TRIM_RIGHT => Some(StrTrimRight),
@ -268,6 +270,7 @@ impl LowLevel {
Symbol::NUM_CEILING => Some(NumCeiling),
Symbol::NUM_POW_INT => Some(NumPowInt),
Symbol::NUM_FLOOR => Some(NumFloor),
Symbol::NUM_TO_STR => Some(NumToStr),
// => Some(NumIsFinite),
Symbol::NUM_ATAN => Some(NumAtan),
Symbol::NUM_ACOS => Some(NumAcos),

View file

@ -992,6 +992,7 @@ define_builtins! {
104 NUM_BYTES_TO_U32: "bytesToU32"
105 NUM_CAST_TO_NAT: "#castToNat"
106 NUM_DIV_CEIL: "divCeil"
107 NUM_TO_STR: "toStr"
}
2 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" imported // the Bool.Bool type alias
@ -1017,19 +1018,17 @@ define_builtins! {
7 STR_COUNT_GRAPHEMES: "countGraphemes"
8 STR_STARTS_WITH: "startsWith"
9 STR_ENDS_WITH: "endsWith"
10 STR_FROM_INT: "fromInt"
11 STR_FROM_FLOAT: "fromFloat"
12 STR_FROM_UTF8: "fromUtf8"
13 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
14 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
15 STR_TO_UTF8: "toUtf8"
16 STR_STARTS_WITH_CODE_PT: "startsWithCodePt"
17 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
18 STR_FROM_UTF8_RANGE: "fromUtf8Range"
19 STR_REPEAT: "repeat"
20 STR_TRIM: "trim"
21 STR_TRIM_LEFT: "trimLeft"
22 STR_TRIM_RIGHT: "trimRight"
10 STR_FROM_UTF8: "fromUtf8"
11 STR_UT8_PROBLEM: "Utf8Problem" // the Utf8Problem type alias
12 STR_UT8_BYTE_PROBLEM: "Utf8ByteProblem" // the Utf8ByteProblem type alias
13 STR_TO_UTF8: "toUtf8"
14 STR_STARTS_WITH_CODE_PT: "startsWithCodePt"
15 STR_ALIAS_ANALYSIS_STATIC: "#aliasAnalysisStatic" // string with the static lifetime
16 STR_FROM_UTF8_RANGE: "fromUtf8Range"
17 STR_REPEAT: "repeat"
18 STR_TRIM: "trim"
19 STR_TRIM_LEFT: "trimLeft"
20 STR_TRIM_RIGHT: "trimRight"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias

View file

@ -210,12 +210,15 @@ where
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 {
debug_assert_eq!(variant_types.len(), 1);
variant_types[0]
} 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)?;
@ -699,30 +702,30 @@ fn call_spec(
call.arguments,
),
HigherOrder(HigherOrderLowLevel {
specialization_id,
closure_env_layout,
update_mode,
op,
arg_layouts,
ret_layout,
function_name,
function_env,
passed_function,
..
}) => {
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 mode = update_mode.to_bytes();
let update_mode_var = UpdateModeVar(&mode);
let it = arg_layouts.iter().copied();
let bytes = func_name_bytes_help(*function_name, it, ret_layout);
let it = passed_function.argument_layouts.iter().copied();
let bytes =
func_name_bytes_help(passed_function.name, it, &passed_function.return_layout);
let name = FuncName(&bytes);
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 {
($builder: expr, $block:expr, [$($arg:expr),+ $(,)?]) => {{
@ -754,7 +757,7 @@ fn call_spec(
Ok(new_state)
};
let state_layout = arg_layouts[0];
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
@ -775,7 +778,7 @@ fn call_spec(
Ok(new_state)
};
let state_layout = arg_layouts[0];
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
@ -799,7 +802,7 @@ fn call_spec(
Ok(new_state)
};
let state_layout = arg_layouts[0];
let state_layout = argument_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
@ -820,9 +823,9 @@ fn call_spec(
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 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)
};
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 init_state = new_list(builder, block, output_element_type)?;
@ -867,11 +870,10 @@ fn call_spec(
builder.add_update(block, update_mode_var, cell)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
with_new_heap_cell(builder, block, 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 init_state = list;
@ -896,9 +898,9 @@ fn call_spec(
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 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)
};
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 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)
};
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 init_state = new_list(builder, block, output_element_type)?;
@ -998,12 +1000,11 @@ fn call_spec(
builder.add_recursive_touch(block, removed_element)?;
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 init_state = list;
@ -1018,7 +1019,7 @@ fn call_spec(
_ => 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) {
(KeepResult::Errs, ResultRepr::ResultConcrete { err, .. }) => err,
@ -1131,7 +1132,7 @@ fn call_spec(
let list = env.symbols[xs];
// 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_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_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, new_bag])
with_new_heap_cell(builder, block, new_bag)
}
fn lowlevel_spec(
@ -1268,8 +1268,7 @@ fn lowlevel_spec(
builder.add_bag_insert(block, bag, to_insert)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
with_new_heap_cell(builder, block, bag)
}
ListSwap => {
let list = env.symbols[&arguments[0]];
@ -1279,8 +1278,7 @@ fn lowlevel_spec(
let _unit = builder.add_update(block, update_mode_var, cell)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
with_new_heap_cell(builder, block, bag)
}
ListReverse => {
let list = env.symbols[&arguments[0]];
@ -1290,8 +1288,7 @@ fn lowlevel_spec(
let _unit = builder.add_update(block, update_mode_var, cell)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
with_new_heap_cell(builder, block, bag)
}
ListAppend => {
let list = env.symbols[&arguments[0]];
@ -1359,8 +1356,7 @@ fn lowlevel_spec(
builder.add_bag_insert(block, bag, key_value)?;
let new_cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[new_cell, bag])
with_new_heap_cell(builder, block, bag)
}
_other => {
// println!("missing {:?}", _other);
@ -1381,13 +1377,10 @@ fn recursive_tag_variant(
) -> Result<TypeId> {
let when_recursive = WhenRecursive::Loop(*union_layout);
let data_id = build_recursive_tuple_type(builder, fields, &when_recursive)?;
let cell_id = builder.add_heap_cell_type();
builder.add_tuple_type(&[cell_id, data_id])
build_recursive_tuple_type(builder, fields, &when_recursive)
}
fn build_variant_types(
fn recursive_variant_types(
builder: &mut impl TypeContext,
union_layout: &UnionLayout,
) -> Result<Vec<TypeId>> {
@ -1396,12 +1389,8 @@ fn build_variant_types(
let mut result;
match union_layout {
NonRecursive(tags) => {
result = Vec::with_capacity(tags.len());
for tag in tags.iter() {
result.push(build_tuple_type(builder, tag)?);
}
NonRecursive(_) => {
unreachable!()
}
Recursive(tags) => {
result = Vec::with_capacity(tags.len());
@ -1425,8 +1414,7 @@ fn build_variant_types(
result.push(recursive_tag_variant(builder, union_layout, tag)?);
}
let unit = builder.add_tuple_type(&[])?;
result.push(unit);
result.push(recursive_tag_variant(builder, union_layout, &[])?);
for tag in tags[cutoff..].iter() {
result.push(recursive_tag_variant(builder, union_layout, tag)?);
@ -1436,7 +1424,7 @@ fn build_variant_types(
nullable_id,
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)?;
if *nullable_id {
@ -1482,18 +1470,16 @@ fn expr_spec<'a>(
tag_id,
arguments,
} => {
let variant_types = build_variant_types(builder, tag_layout)?;
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 {
UnionLayout::NonRecursive(_) => {
UnionLayout::NonRecursive(tags) => {
let variant_types = non_recursive_variant_types(builder, tags)?;
let value_id = build_tuple_value(builder, env, block, arguments)?;
return builder.add_make_union(block, &variant_types, *tag_id as u32, value_id);
}
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 = TypeName(&type_name_bytes);
@ -1502,32 +1488,24 @@ fn expr_spec<'a>(
return builder.add_make_named(block, MOD_APP, type_name, value_id);
}
UnionLayout::Recursive(_) => builder.add_make_tuple(block, &[cell_id, data_id])?,
UnionLayout::NullableWrapped { nullable_id, .. } => {
if *tag_id == *nullable_id as _ {
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])?
}
}
UnionLayout::Recursive(_) => data_id,
UnionLayout::NullableWrapped { .. } => data_id,
UnionLayout::NullableUnwrapped { .. } => data_id,
};
let variant_types = recursive_variant_types(builder, tag_layout)?;
let union_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 = TypeName(&type_name_bytes);
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),
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 = TypeName(&type_name_bytes);
// unwrap the named wrapper
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
let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?;
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 { .. } => {
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 = TypeName(&type_name_bytes);
let variant_id =
builder.add_unwrap_named(block, MOD_APP, type_name, tag_value_id)?;
// a tuple ( cell, union { ... } )
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
let heap_cell = builder.add_get_tuple_field(block, variant_id, 0)?;
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 {
@ -1613,9 +1599,7 @@ fn expr_spec<'a>(
if all_constants {
new_static_list(builder, block)
} else {
let cell = builder.add_new_heap_cell(block)?;
builder.add_make_tuple(block, &[cell, bag])
with_new_heap_cell(builder, block, bag)
}
}
@ -1626,7 +1610,7 @@ fn expr_spec<'a>(
}
_ => unreachable!("empty array does not have a list layout"),
},
Reset(symbol) => {
Reset { symbol, .. } => {
let type_id = layout_spec(builder, layout)?;
let value_id = env.symbols[symbol];
@ -1637,7 +1621,11 @@ fn expr_spec<'a>(
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)
}
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(
builder: &mut impl TypeContext,
layout: &Layout,
@ -1674,8 +1675,6 @@ fn layout_spec_help(
when_recursive,
),
Union(union_layout) => {
let variant_types = build_variant_types(builder, union_layout)?;
match union_layout {
UnionLayout::NonRecursive(&[]) => {
// 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
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::NullableUnwrapped { .. }
| UnionLayout::NullableWrapped { .. }
@ -1777,10 +1779,21 @@ const LIST_BAG_INDEX: u32 = 1;
const DICT_CELL_INDEX: u32 = LIST_CELL_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)?;
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)?;
builder.add_make_tuple(block, &[cell, bag])
with_new_heap_cell(builder, block, bag)
}
fn new_dict(
@ -1789,10 +1802,9 @@ fn new_dict(
key_type: TypeId,
value_type: TypeId,
) -> Result<ValueId> {
let cell = builder.add_new_heap_cell(block)?;
let element_type = builder.add_tuple_type(&[key_type, value_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> {

View file

@ -595,20 +595,17 @@ impl<'a> BorrowInfState<'a> {
HigherOrder(HigherOrderLowLevel {
op,
arg_layouts,
ret_layout,
function_name,
function_env,
passed_function,
..
}) => {
use crate::low_level::HigherOrder::*;
let closure_layout = ProcLayout {
arguments: arg_layouts,
result: *ret_layout,
arguments: passed_function.argument_layouts,
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,
None => unreachable!(),
};
@ -692,7 +689,7 @@ impl<'a> BorrowInfState<'a> {
// own the closure environment if the function needs to own it
let function_env_position = op.function_arity();
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
self.own_args_if_param(xs);
}
Reset(x) => {
Reset { symbol: x, .. } => {
self.own_var(z);
self.own_var(*x);
}
@ -980,9 +977,9 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
| NumPowInt | NumBitwiseAnd | NumBitwiseXor | NumBitwiseOr | NumShiftLeftBy
| NumShiftRightBy | NumShiftRightZfBy => arena.alloc_slice_copy(&[irrelevant, irrelevant]),
NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked | NumRound
| NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos | NumAsin
| NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
NumToStr | NumAbs | NumNeg | NumSin | NumCos | NumSqrtUnchecked | NumLogUnchecked
| NumRound | NumCeiling | NumFloor | NumToFloat | Not | NumIsFinite | NumAtan | NumAcos
| NumAsin | NumIntCast => arena.alloc_slice_copy(&[irrelevant]),
NumBytesToU16 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
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]),
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.insert(*symbol);
}
Reset(x) => {
Reset { symbol: x, .. } => {
result.insert(*x);
}
@ -468,12 +468,8 @@ impl<'a> Context<'a> {
HigherOrder(HigherOrderLowLevel {
op,
closure_env_layout,
specialization_id,
update_mode,
arg_layouts,
ret_layout,
function_name,
function_env,
passed_function,
..
}) => {
// setup
@ -483,16 +479,14 @@ impl<'a> Context<'a> {
($borrows:expr) => {
Expr::Call(crate::ir::Call {
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 {
op: *op,
closure_env_layout: *closure_env_layout,
function_owns_closure_data: true,
specialization_id: *specialization_id,
update_mode: *update_mode,
function_name: *function_name,
function_env: *function_env,
arg_layouts,
ret_layout: *ret_layout,
passed_function,
};
CallType::HigherOrder(self.arena.alloc(higher_order))
@ -521,11 +515,14 @@ impl<'a> Context<'a> {
const CLOSURE_DATA: bool = BORROWED;
let function_layout = ProcLayout {
arguments: arg_layouts,
result: *ret_layout,
arguments: passed_function.argument_layouts,
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,
None => unreachable!(),
};
@ -761,7 +758,7 @@ impl<'a> Context<'a> {
self.arena.alloc(Stmt::Let(z, v, l, b))
}
EmptyArray | Literal(_) | Reset(_) | RuntimeErrorFunction(_) => {
EmptyArray | Literal(_) | Reset { .. } | RuntimeErrorFunction(_) => {
// EmptyArray is always stack-allocated
// function pointers are persistent
self.arena.alloc(Stmt::Let(z, v, l, b))
@ -779,7 +776,7 @@ impl<'a> Context<'a> {
// must this value be consumed?
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)
}

View file

@ -37,8 +37,8 @@ static_assertions::assert_eq_size!([u8; 19 * 8], Stmt);
#[cfg(target_arch = "aarch64")]
static_assertions::assert_eq_size!([u8; 20 * 8], Stmt);
static_assertions::assert_eq_size!([u8; 6 * 8], ProcLayout);
static_assertions::assert_eq_size!([u8; 8 * 8], Call);
static_assertions::assert_eq_size!([u8; 6 * 8], CallType);
static_assertions::assert_eq_size!([u8; 7 * 8], Call);
static_assertions::assert_eq_size!([u8; 5 * 8], CallType);
macro_rules! return_on_layout_error {
($env:expr, $layout_result:expr) => {
@ -318,11 +318,17 @@ impl<'a> Proc<'a> {
arena: &'a Bump,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) {
for (_, proc) in procs.iter_mut() {
let new_proc =
crate::reset_reuse::insert_reset_reuse(arena, home, ident_ids, proc.clone());
let new_proc = crate::reset_reuse::insert_reset_reuse(
arena,
home,
ident_ids,
update_mode_ids,
proc.clone(),
);
*proc = new_proc;
}
}
@ -988,8 +994,8 @@ pub struct Env<'a, 'i> {
pub home: ModuleId,
pub ident_ids: &'i mut IdentIds,
pub ptr_bytes: u32,
pub update_mode_counter: u64,
pub call_specialization_counter: u64,
pub update_mode_ids: &'i mut UpdateModeIds,
pub call_specialization_counter: u32,
}
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 {
let id = UpdateModeId {
id: self.update_mode_counter,
};
self.update_mode_counter += 1;
id
self.update_mode_ids.next_id()
}
pub fn next_call_specialization_id(&mut self) -> CallSpecId {
@ -1282,24 +1282,49 @@ impl<'a> Call<'a> {
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct CallSpecId {
id: u64,
id: u32,
}
impl CallSpecId {
pub fn to_bytes(self) -> [u8; 8] {
pub fn to_bytes(self) -> [u8; 4] {
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)]
pub struct UpdateModeId {
id: u64,
id: u32,
}
impl UpdateModeId {
pub fn to_bytes(self) -> [u8; 8] {
pub fn to_bytes(self) -> [u8; 4] {
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)]
@ -1321,31 +1346,35 @@ pub enum CallType<'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)]
pub struct HigherOrderLowLevel<'a> {
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
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
pub update_mode: UpdateModeId,
/// function layout, used for name generation
pub arg_layouts: &'a [Layout<'a>],
pub ret_layout: Layout<'a>,
pub passed_function: PassedFunction<'a>,
}
#[derive(Clone, Debug, PartialEq)]
@ -1390,13 +1419,17 @@ pub enum Expr<'a> {
Reuse {
symbol: Symbol,
update_tag_id: bool,
update_mode: UpdateModeId,
// normal Tag fields
tag_layout: UnionLayout<'a>,
tag_name: TagName,
tag_id: TagIdIntType,
arguments: &'a [Symbol],
},
Reset(Symbol),
Reset {
symbol: Symbol,
update_mode: UpdateModeId,
},
RuntimeErrorFunction(&'a str),
}
@ -1491,6 +1524,7 @@ impl<'a> Expr<'a> {
symbol,
tag_name,
arguments,
update_mode,
..
} => {
let doc_tag = match tag_name {
@ -1508,11 +1542,19 @@ impl<'a> Expr<'a> {
.text("Reuse ")
.append(symbol_to_doc(alloc, *symbol))
.append(alloc.space())
.append(format!("{:?}", update_mode))
.append(alloc.space())
.append(doc_tag)
.append(alloc.space())
.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) => {
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)),
}
}
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> {
@ -4144,16 +4197,21 @@ pub fn with_hole<'a>(
op,
closure_data_symbol,
|(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 {
op: crate::low_level::HigherOrder::$ho { $($x,)* },
closure_env_layout,
specialization_id,
update_mode,
function_owns_closure_data: false,
function_env: closure_data_symbol,
function_name: top_level_function,
arg_layouts,
ret_layout,
passed_function,
};
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) => {
let mut did_change = false;

View file

@ -2367,7 +2367,7 @@ fn layout_from_tag_union<'a>(
}
#[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
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))]
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
unreachable!();
}
#[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
let mut ext_fields = std::vec::Vec::new();
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))]
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
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 borrow;
pub mod gen_refcount;
pub mod inc_dec;
pub mod ir;
pub mod layout;
pub mod layout_soa;
pub mod low_level;
pub mod reset_reuse;
pub mod tail_recursion;

View file

@ -1,5 +1,7 @@
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 bumpalo::collections::Vec;
use bumpalo::Bump;
@ -10,12 +12,14 @@ pub fn insert_reset_reuse<'a, 'i>(
arena: &'a Bump,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
mut proc: Proc<'a>,
) -> Proc<'a> {
let mut env = Env {
arena,
home,
ident_ids,
update_mode_ids,
jp_live_vars: Default::default(),
};
@ -50,6 +54,7 @@ struct Env<'a, 'i> {
/// required for creating new `Symbol`s
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
jp_live_vars: JPLiveVarMap,
}
@ -64,7 +69,7 @@ impl<'a, 'i> Env<'a, 'i> {
fn function_s<'a, 'i>(
env: &mut Env<'a, 'i>,
w: Symbol,
w: Opportunity,
c: &CtorInfo<'a>,
stmt: &'a Stmt<'a>,
) -> &'a Stmt<'a> {
@ -84,7 +89,8 @@ fn function_s<'a, 'i>(
let update_tag_id = true;
let new_expr = Expr::Reuse {
symbol: w,
symbol: w.symbol,
update_mode: w.update_mode,
update_tag_id,
tag_layout: *tag_layout,
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>(
env: &mut Env<'a, 'i>,
x: Symbol,
c: &CtorInfo<'a>,
stmt: &'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);
@ -194,7 +209,7 @@ fn try_function_s<'a, 'i>(
fn insert_reset<'a>(
env: &mut Env<'a, '_>,
w: Symbol,
w: Opportunity,
x: Symbol,
union_layout: UnionLayout<'a>,
mut stmt: &'a Stmt<'a>,
@ -216,16 +231,21 @@ fn insert_reset<'a>(
| Array { .. }
| EmptyArray
| Reuse { .. }
| Reset(_)
| Reset { .. }
| 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);
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() {
stmt = env
@ -584,7 +604,7 @@ fn has_live_var_expr<'a>(expr: &'a Expr<'a>, needle: Symbol) -> bool {
Expr::Reuse {
symbol, arguments, ..
} => needle == *symbol || arguments.iter().any(|s| *s == needle),
Expr::Reset(symbol) => needle == *symbol,
Expr::Reset { symbol, .. } => needle == *symbol,
Expr::RuntimeErrorFunction(_) => false,
}
}

View file

@ -17,5 +17,4 @@ pretty_assertions = "1.0.0"
indoc = "1.0.3"
quickcheck = "1.0.3"
quickcheck_macros = "1.0.0"
diff = "0.1.12"
ansi_term = "0.12.1"
roc_test_utils = { path = "../../test_utils" }

View file

@ -23,6 +23,7 @@ mod test_parse {
use roc_parse::parser::{Parser, State, SyntaxError};
use roc_parse::test_helpers::parse_expr_with;
use roc_region::all::{Located, Region};
use roc_test_utils::assert_multiline_str_eq;
use std::{f64, i64};
macro_rules! snapshot_tests {
@ -254,10 +255,7 @@ mod test_parse {
} else {
let expected_result = std::fs::read_to_string(&result_path).unwrap();
// TODO: do a diff over the "real" content of these strings, rather than
// the debug-formatted content. As is, we get an ugly single-line diff
// from pretty_assertions
assert_eq!(expected_result, actual_result);
assert_multiline_str_eq!(expected_result, actual_result);
}
}

View file

@ -837,25 +837,13 @@ fn type_to_variable<'a>(
actual,
lambda_set_variables,
} => {
// the rank of these variables is NONE (encoded as 0 in practice)
// using them for other ranks causes issues
if let Some(reserved) = Variable::get_reserved(*symbol) {
if rank.is_none() {
// TODO replace by arithmetic?
match *symbol {
Symbol::NUM_I128 => return Variable::I128,
Symbol::NUM_I64 => return Variable::I64,
Symbol::NUM_I32 => return Variable::I32,
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,
_ => {}
// reserved variables are stored with rank NONE
return reserved;
} else {
// for any other rank, we need to copy; it takes care of adjusting the rank
return deep_copy_var(subs, rank, pools, reserved);
}
}
@ -868,7 +856,11 @@ fn type_to_variable<'a>(
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);
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)
where
F: FnMut(&T, &T) -> std::cmp::Ordering,

View file

@ -228,10 +228,10 @@ mod solve_expr {
infer_eq_without_problem(
indoc!(
r#"
Str.fromInt
Num.toStr
"#
),
"Int * -> Str",
"Num * -> Str",
);
}
@ -4543,8 +4543,8 @@ mod solve_expr {
|> Str.concat ") ("
|> Str.concat (printExpr b)
|> Str.concat ")"
Val v -> Str.fromInt v
Var v -> "Var " |> Str.concat (Str.fromInt v)
Val v -> Num.toStr v
Var v -> "Var " |> Str.concat (Num.toStr v)
main : Str
main = printExpr (Var 3)

View file

@ -1,4 +1,5 @@
#![cfg(feature = "gen-llvm")]
#![cfg(feature = "gen-wasm")]
#[cfg(feature = "gen-llvm")]
use crate::helpers::llvm::assert_evals_to;
@ -6,8 +7,8 @@ use crate::helpers::llvm::assert_evals_to;
// #[cfg(feature = "gen-dev")]
// use crate::helpers::dev::assert_evals_to;
// #[cfg(feature = "gen-wasm")]
// use crate::helpers::wasm::assert_evals_to;
#[cfg(feature = "gen-wasm")]
use crate::helpers::wasm::assert_evals_to;
use crate::helpers::with_larger_debug_stack;
//use crate::assert_wasm_evals_to as assert_evals_to;
@ -22,7 +23,7 @@ fn roc_list_construction() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn empty_list_literal() {
assert_evals_to!("[]", RocList::from_slice(&[]), RocList<i64>);
}
@ -34,6 +35,7 @@ fn list_literal_empty_record() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn int_singleton_list_literal() {
assert_evals_to!("[1, 2]", RocList::from_slice(&[1, 2]), RocList<i64>);
}

View file

@ -520,7 +520,7 @@ fn f64_log_negative() {
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn f64_round() {
assert_evals_to!("Num.round 3.6", 4, i64);
assert_evals_to!("Num.round 3.4", 3, i64);
@ -813,7 +813,7 @@ fn gen_add_i64() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn gen_sub_dec() {
assert_evals_to!(
indoc!(
@ -1171,7 +1171,7 @@ fn gen_order_of_arithmetic_ops_complex_float() {
}
#[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() {
assert_evals_to!(
indoc!(
@ -1190,7 +1190,7 @@ fn if_guard_bind_variable_false() {
}
#[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() {
assert_evals_to!(
indoc!(
@ -1990,3 +1990,35 @@ fn when_on_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]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn simple_closure() {
assert_evals_to!(
indoc!(
@ -2602,7 +2602,7 @@ fn hit_unresolved_type_variable() {
}
#[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() {
assert_evals_to!(
indoc!(

View file

@ -254,7 +254,7 @@ fn twice_record_access() {
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-dev", feature = "gen-wasm"))]
fn empty_record() {
assert_evals_to!(
indoc!(

View file

@ -527,40 +527,6 @@ fn str_starts_with_false_small_str() {
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]
#[cfg(any(feature = "gen-llvm"))]
fn str_from_utf8_pass_single_ascii() {
@ -838,8 +804,8 @@ fn nested_recursive_literal() {
|> Str.concat ") ("
|> Str.concat (printExpr b)
|> Str.concat ")"
Val v -> "Val " |> Str.concat (Str.fromInt v)
Var v -> "Var " |> Str.concat (Str.fromInt v)
Val v -> "Val " |> Str.concat (Num.toStr v)
Var v -> "Var " |> Str.concat (Num.toStr v)
printExpr expr
"#
@ -875,12 +841,6 @@ fn str_join_comma_single() {
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]
#[cfg(any(feature = "gen-llvm"))]
fn str_to_utf8() {

View file

@ -188,8 +188,7 @@ pub fn helper(
};
let target = target_lexicon::Triple::host();
let module_object =
roc_gen_dev::build_module(&env, &target, procedures).expect("failed to compile module");
let module_object = roc_gen_dev::build_module(&env, &target, procedures);
let module_out = module_object
.write()

View file

@ -79,8 +79,9 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
module_id,
procedures,
interns,
mut interns,
exposed_to_host,
..
} = loaded;
@ -114,12 +115,12 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
let env = roc_gen_wasm::Env {
arena,
interns,
module_id,
exposed_to_host,
};
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);
@ -136,7 +137,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
let store = Store::default();
// 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 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
std::fs::write(&app_o_file, &module_bytes).unwrap();
let _linker_output = std::process::Command::new("zig")
.args(&[
let args = &[
"wasm-ld",
// input files
app_o_file.to_str().unwrap(),
@ -188,11 +188,21 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
"test_wrapper",
"--export",
"#UserApp_main_1",
])
];
let linker_output = std::process::Command::new("zig")
.args(args)
.output()
.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()
};

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)
where
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);
}
// #[test]
// fn str_from_int() {
// assert_evals_to!(
// r#"Str.fromInt 1234"#,
// roc_std::RocStr::from_slice("1234".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt 0"#,
// roc_std::RocStr::from_slice("0".as_bytes()),
// roc_std::RocStr
// );
// assert_evals_to!(
// r#"Str.fromInt -1"#,
// roc_std::RocStr::from_slice("-1".as_bytes()),
// roc_std::RocStr
// );
// let max = format!("{}", i64::MAX);
// assert_evals_to!(
// r#"Str.fromInt Num.maxInt"#,
// RocStr::from_slice(max.as_bytes()),
// RocStr
// );
// let min = format!("{}", i64::MIN);
// assert_evals_to!(
// r#"Str.fromInt Num.minInt"#,
// RocStr::from_slice(min.as_bytes()),
// RocStr
// );
// }
// #[test]
// fn str_from_utf8_pass_single_ascii() {
// assert_evals_to!(
@ -729,8 +696,8 @@ fn str_starts_with_false_small_str() {
// |> Str.concat ") ("
// |> Str.concat (printExpr b)
// |> Str.concat ")"
// Val v -> "Val " |> Str.concat (Str.fromInt v)
// Var v -> "Var " |> Str.concat (Str.fromInt v)
// Val v -> "Val " |> Str.concat (Num.toStr v)
// Var v -> "Var " |> Str.concat (Num.toStr v)
// printExpr expr
// "#
@ -763,11 +730,6 @@ fn str_starts_with_false_small_str() {
// assert_evals_to!(r#"Str.joinWith ["1"] ", " "#, RocStr::from("1"), RocStr);
// }
// #[test]
// fn str_from_float() {
// assert_evals_to!(r#"Str.fromFloat 3.14"#, RocStr::from("3.14"), RocStr);
// }
// #[test]
// fn str_to_utf8() {
// assert_evals_to!(
@ -903,8 +865,8 @@ fn str_starts_with_false_small_str() {
#[test]
fn str_repeat_small() {
assert_evals_to!(
indoc!(r#"Str.repeat "Roc" 3"#),
RocStr::from("RocRocRoc"),
indoc!(r#"Str.repeat "Roc" 2"#),
RocStr::from("RocRoc"),
RocStr
);
}
@ -941,8 +903,8 @@ fn str_trim_small_blank_string() {
#[test]
fn str_trim_small_to_small() {
assert_evals_to!(
indoc!(r#"Str.trim " hello world ""#),
RocStr::from("hello world"),
indoc!(r#"Str.trim " hello ""#),
RocStr::from("hello"),
RocStr
);
}
@ -959,8 +921,8 @@ fn str_trim_large_to_large_unique() {
#[test]
fn str_trim_large_to_small_unique() {
assert_evals_to!(
indoc!(r#"Str.trim (Str.concat " " "hello world ")"#),
RocStr::from("hello world"),
indoc!(r#"Str.trim (Str.concat " " "hello ")"#),
RocStr::from("hello"),
RocStr
);
}
@ -990,15 +952,12 @@ fn str_trim_large_to_small_shared() {
indoc!(
r#"
original : Str
original = " hello world "
original = " hello "
{ trimmed: Str.trim original, original: original }
"#
),
(
RocStr::from(" hello world "),
RocStr::from("hello world"),
),
(RocStr::from(" hello "), RocStr::from("hello"),),
(RocStr, RocStr)
);
}
@ -1009,12 +968,12 @@ fn str_trim_small_to_small_shared() {
indoc!(
r#"
original : Str
original = " hello world "
original = " hello "
{ trimmed: Str.trim original, original: original }
"#
),
(RocStr::from(" hello world "), RocStr::from("hello world"),),
(RocStr::from(" hello "), RocStr::from("hello"),),
(RocStr, RocStr)
);
}
@ -1027,8 +986,8 @@ fn str_trim_left_small_blank_string() {
#[test]
fn str_trim_left_small_to_small() {
assert_evals_to!(
indoc!(r#"Str.trimLeft " hello world ""#),
RocStr::from("hello world "),
indoc!(r#"Str.trimLeft " hello ""#),
RocStr::from("hello "),
RocStr
);
}
@ -1045,8 +1004,8 @@ fn str_trim_left_large_to_large_unique() {
#[test]
fn str_trim_left_large_to_small_unique() {
assert_evals_to!(
indoc!(r#"Str.trimLeft (Str.concat " " "hello world ")"#),
RocStr::from("hello world "),
indoc!(r#"Str.trimLeft (Str.concat " " "hello ")"#),
RocStr::from("hello "),
RocStr
);
}
@ -1059,8 +1018,8 @@ fn str_trim_right_small_blank_string() {
#[test]
fn str_trim_right_small_to_small() {
assert_evals_to!(
indoc!(r#"Str.trimRight " hello world ""#),
RocStr::from(" hello world"),
indoc!(r#"Str.trimRight " hello ""#),
RocStr::from(" hello"),
RocStr
);
}
@ -1077,8 +1036,8 @@ fn str_trim_right_large_to_large_unique() {
#[test]
fn str_trim_right_large_to_small_unique() {
assert_evals_to!(
indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#),
RocStr::from(" hello world"),
indoc!(r#"Str.trimRight (Str.concat " hello" " ")"#),
RocStr::from(" hello"),
RocStr
);
}
@ -1108,15 +1067,12 @@ fn str_trim_right_large_to_small_shared() {
indoc!(
r#"
original : Str
original = " hello world "
original = " hello "
{ trimmed: Str.trimRight original, original: original }
"#
),
(
RocStr::from(" hello world "),
RocStr::from(" hello world"),
),
(RocStr::from(" hello "), RocStr::from(" hello"),),
(RocStr, RocStr)
);
}
@ -1127,12 +1083,12 @@ fn str_trim_right_small_to_small_shared() {
indoc!(
r#"
original : Str
original = " hello world "
original = " hello "
{ trimmed: Str.trimRight original, original: original }
"#
),
(RocStr::from(" hello world "), RocStr::from(" hello world"),),
(RocStr::from(" hello "), RocStr::from(" hello"),),
(RocStr, RocStr)
);
}

View file

@ -708,6 +708,29 @@ impl Variable {
pub const fn index(&self) -> u32 {
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 {
@ -1012,6 +1035,8 @@ fn define_integer_types(subs: &mut Subs) {
}
impl Subs {
pub const RESULT_TAG_NAMES: SubsSlice<TagName> = SubsSlice::new(0, 2);
pub fn new() -> Self {
Self::with_capacity(0)
}
@ -1019,10 +1044,15 @@ impl Subs {
pub fn with_capacity(capacity: usize) -> Self {
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 {
utable: UnificationTable::default(),
variables: Default::default(),
tag_names: Default::default(),
tag_names,
field_names: Default::default(),
record_fields: Default::default(),
// store an empty slice at the first position

View file

@ -175,8 +175,8 @@ main = "Hello, world!"
#[test]
fn call_builtin() {
expect_html_def(
r#"myVal = Str.fromInt 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",
r#"myVal = Num.toStr 1234"#,
"<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" }
ven_graph = { path = "../vendor/pathfinding" }
bumpalo = { version = "3.8.0", features = ["collections"] }
arraystring = "0.3.0"
arrayvec = "0.7.2"
libc = "0.2.106"
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.

View file

@ -36,14 +36,12 @@ pub fn update_small_string(
.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 old_array_str.len() < ArrString::capacity() {
if old_array_str.len() < old_array_str.capacity() {
if let Expr2::SmallStr(ref mut mut_array_str) =
ed_model.module.env.pool.get_mut(ast_node_id.to_expr_id()?)
{
// safe because we checked the length
unsafe {
mut_array_str.push_unchecked(*new_char);
}
mut_array_str.push(*new_char);
} else {
unreachable!()
}
@ -137,7 +135,7 @@ pub fn start_new_string(ed_model: &mut EdModel) -> EdResult<InputOutcome> {
} = get_node_context(ed_model)?;
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();
ed_model

View file

@ -13,9 +13,9 @@ main =
optimized = eval (constFolding (reassoc e))
unoptimized
|> Str.fromInt
|> Num.toStr
|> Str.concat " & "
|> Str.concat (Str.fromInt optimized)
|> Str.concat (Num.toStr optimized)
|> Task.putLine
Expr : [

View file

@ -115,9 +115,9 @@ deriv : I64, Expr -> IO Expr
deriv = \i, f ->
fprime = d "x" f
line =
Str.fromInt (i + 1)
Num.toStr (i + 1)
|> Str.concat " count: "
|> Str.concat (Str.fromInt (count fprime))
|> Str.concat (Num.toStr (count fprime))
Task.putLine line
|> Task.after \_ -> Task.succeed fprime

View file

@ -7,7 +7,7 @@ main : Task.Task {} []
main =
Task.after Task.getInt \n ->
queens n # original koka 13
|> Str.fromInt
|> Num.toStr
|> Task.putLine
ConsList a : [ Nil, Cons a (ConsList a) ]

View file

@ -7,7 +7,7 @@ show = \list ->
else
content =
list
|> List.map Str.fromInt
|> List.map Num.toStr
|> Str.joinWith ", "
"[ \(content) ]"

View file

@ -48,14 +48,15 @@ resultWithDefault = \res, default ->
main : Task.Task {} []
main =
Task.after Task.getInt \n ->
# original koka n = 4_200_000
ms : ConsList Map
ms = makeMap 5 n # original koka n = 4_200_000
ms = makeMap 5 n
when ms is
Cons head _ ->
val = fold (\_, v, r -> if v then r + 1 else r) head 0
val
|> Str.fromInt
|> Num.toStr
|> Task.putLine
Nil ->

View file

@ -20,7 +20,7 @@ main =
val = fold (\_, v, r -> if v then r + 1 else r) m 0
val
|> Str.fromInt
|> Num.toStr
|> Task.putLine
boom : Str -> a

View file

@ -13,7 +13,7 @@ main =
|> Task.putLine
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 = \tree, showKey, showValue ->

View file

@ -13,7 +13,7 @@ main =
# Task.putLine (showBool test1)
#
# _ ->
# ns = Str.fromInt n
# ns = Num.toStr n
# Task.putLine "No test \(ns)"
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 ->
when data is
Lambda _ -> "[]"
Number n -> Str.fromInt (Num.intCast n)
Number n -> Num.toStr (Num.intCast n)
Var v -> Variable.toStr v
toStrState: State -> Str
@ -49,7 +49,7 @@ toStrState = \state ->
toStr: Context -> Str
toStr = \{scopes, stack, state, vars} ->
depth = Str.fromInt (List.len scopes)
depth = Num.toStr (List.len scopes)
stateStr = toStrState state
stackStr = Str.joinWith (List.map stack 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
interpretCtx newCtx
Ok (T x _) ->
data = Str.fromInt (Num.intCast x)
data = Num.toStr (Num.intCast x)
Task.fail (InvalidChar data)
Err NoScope ->
Task.fail NoScope
@ -358,7 +358,7 @@ stepExecCtx = \ctx, char ->
0x2E -> # `.` write int
when popNumber ctx is
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
Err e ->
Task.fail e
@ -395,7 +395,7 @@ stepExecCtx = \ctx, char ->
Ok var ->
Task.succeed (Context.pushStack ctx (Var var))
Err _ ->
data = Str.fromInt (Num.intCast x)
data = Num.toStr (Num.intCast x)
Task.fail (InvalidChar data)
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).
2. Untar the archive:
0. Download the latest nightly from the assets [here](https://github.com/rtfeldman/roc/releases).
0. Untar the archive:
```
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:
```
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
@ -16,15 +17,15 @@
```
- 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
# Zig
./roc examples/hello-zig/Hello.roc
# C
./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<()> {
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),
Some(CMD_PREPROCESS) => {
let sub_matches = matches.subcommand_matches(CMD_PREPROCESS).unwrap();
preprocess(sub_matches)
}
Some(CMD_SURGERY) => {
let sub_matches = matches.subcommand_matches(CMD_SURGERY).unwrap();
surgery(sub_matches)
}
Some((CMD_PREPROCESS, sub_matches)) => preprocess(sub_matches),
Some((CMD_SURGERY, sub_matches)) => surgery(sub_matches),
_ => unreachable!(),
}?;
std::process::exit(exit_code);

View file

@ -13,7 +13,7 @@ mod test_reporting {
use crate::helpers::{can_expr, infer_expr, CanExprOut, ParseErrOut};
use bumpalo::Bump;
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_reporting::report::{
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
let mut procs = Procs::new_in(&arena);
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
let ptr_bytes = 8;
@ -101,8 +102,8 @@ mod test_reporting {
problems: &mut mono_problems,
home,
ident_ids: &mut ident_ids,
update_mode_ids: &mut update_mode_ids,
ptr_bytes,
update_mode_counter: 0,
// call_specialization_counter=0 is reserved
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
`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
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
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.
> 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
> 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))
};
}