Merge remote-tracking branch 'origin/trunk' into partialproc-by-reference

This commit is contained in:
Folkert 2021-11-03 15:24:58 +01:00
commit c5005d3dd1
77 changed files with 4144 additions and 3003 deletions

View File

@ -43,3 +43,4 @@ Locria Cyber <locriacyber@noreply.users.github.com>
Matthias Beyer <mail@beyermatthias.de>
Tim Whiting <tim@whitings.org>
Logan Lowder <logan.lowder@logikcull.com>
Joshua Warner <joshuawarner32@gmail.com>

View File

@ -82,7 +82,7 @@ There are also alternative installation options at http://releases.llvm.org/down
## Using Nix
:exclamation: **Our Nix setup is currently broken, you'll have to install manually for now** :exclamation:
:exclamation: **Our Nix setup is not yet working on MacOS, you'll have to install manually for now** :exclamation:
### Install
@ -94,7 +94,7 @@ First, install nix:
`curl -L https://nixos.org/nix/install | sh`
If MacOS and using a version >= 10.15:
If you're on MacOS and using a OS version >= 10.15:
`sh <(curl -L https://nixos.org/nix/install) --darwin-use-unencrypted-nix-store-volume`
@ -104,7 +104,7 @@ You may prefer to setup up the volume manually by following nix documentation.
### Usage
Now with nix installed you just need to run one command:
Now with nix installed, you just need to run one command:
`nix-shell`
@ -120,33 +120,49 @@ You should be in a repl now. Have fun!
### Extra tips
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/target/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
If you plan on using `nix-shell` regularly, check out [direnv](https://direnv.net/) and [lorri](https://github.com/nix-community/lorri). Whenever you `cd` into `roc/`, they will automatically load the Nix dependecies into your current shell, so you never have to run nix-shell directly!
### Editor
When you want to run the editor from Ubuntu inside nix you need to install [nixGL](https://github.com/guibou/nixGL) as well:
`cargo run edit` should work from NixOS, if you use a nix-shell from inside another OS, follow the instructions below.
#### Nvidia GPU
Outside of a nix shell, execute the following:
```
nix-channel --add https://github.com/guibou/nixGL/archive/main.tar.gz nixgl && nix-channel --update
nix-env -iA nixgl.auto.nixVulkanNvidia
```
Running the editor does not work with `nix-shell --pure`.
```
nix-shell
```
460.91.03 may be different for you, type nixVulkanNvidia and press tab to autocomplete for your version.
```
nixVulkanNvidia-460.91.03 cargo run edit
```
#### Integrated Intel Graphics
:exclamation: ** Our Nix setup currently cannot run the editor with integrated intel graphics, see #1856 ** :exclamation:
Outside of a nix shell, run:
```bash
nix-shell
git clone https://github.com/guibou/nixGL
cd nixGL
```
If you have an Nvidia graphics card, run:
```
nix-env -f ./ -iA nixVulkanNvidia
```
If you have integrated Intel graphics, run:
```
nix-env -f ./ -iA nixVulkanIntel
```
Check the [nixGL repo](https://github.com/guibou/nixGL) for other configurations.
Now you should be able to run the editor:
```bash
cd roc
nixVulkanNvidia cargo run edit `# replace Nvidia with the config you chose in the previous step`
cd to the roc repo, and run (without --pure):
```
nix-shell
nixVulkanIntel cargo run edit
```
#### Other configs
Check the [nixGL repo](https://github.com/guibou/nixGL) for other graphics configurations.
## Troubleshooting
@ -197,6 +213,11 @@ on Windows. After lots of help from [**@IanMacKenzie**](https://github.com/IanMa
Once all that was done, `cargo` ran successfully for Roc!
### Build speed on WSL/WSL2
If your Roc project folder is in the Windows filesystem but you're compiling from Linux, rebuilds may be as much as 20x slower than they should be!
Disk access during linking seems to be the bottleneck. It's recommended to move your folder to the Linux filesystem.
## Use LLD for the linker
Using [`lld` for Rust's linker](https://github.com/rust-lang/rust/issues/39915#issuecomment-538049306)

115
Cargo.lock generated
View File

@ -498,7 +498,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"criterion",
"inlinable_string",
"rlimit",
"roc_cli",
"roc_collections",
@ -1851,12 +1850,6 @@ dependencies = [
"syn 1.0.76",
]
[[package]]
name = "inlinable_string"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3094308123a0e9fd59659ce45e22de9f53fc1d2ac6e1feb9fef988e4f76cad77"
[[package]]
name = "inplace_it"
version = "0.3.3"
@ -3579,9 +3572,7 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"inkwell 0.1.0",
"inlinable_string",
"libloading 0.6.7",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3613,7 +3604,6 @@ name = "roc_builtins"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3631,7 +3621,6 @@ dependencies = [
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3658,9 +3647,7 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"inkwell 0.1.0",
"libc",
"libloading 0.6.7",
"maplit",
"mimalloc",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -3724,7 +3711,6 @@ name = "roc_constrain"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3742,7 +3728,6 @@ name = "roc_docs"
version = "0.1.0"
dependencies = [
"bumpalo",
"maplit",
"pretty_assertions 0.5.1",
"pulldown-cmark",
"roc_ast",
@ -3750,7 +3735,6 @@ dependencies = [
"roc_can",
"roc_code_markup",
"roc_collections",
"roc_fmt",
"roc_load",
"roc_module",
"roc_parse",
@ -3781,7 +3765,6 @@ dependencies = [
"indoc 1.0.3",
"libc",
"log",
"maplit",
"nonempty",
"page_size",
"palette",
@ -3796,7 +3779,6 @@ dependencies = [
"roc_can",
"roc_code_markup",
"roc_collections",
"roc_fmt",
"roc_load",
"roc_module",
"roc_parse",
@ -3807,7 +3789,6 @@ dependencies = [
"roc_types",
"roc_unify",
"rodio",
"ropey",
"serde",
"snafu",
"tempfile",
@ -3817,7 +3798,6 @@ dependencies = [
"wgpu",
"wgpu_glyph",
"winit",
"zerocopy",
]
[[package]]
@ -3828,7 +3808,6 @@ dependencies = [
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -3847,9 +3826,7 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"itertools 0.9.0",
"libc",
"libloading 0.6.7",
"maplit",
"object 0.24.0",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -3884,8 +3861,6 @@ dependencies = [
"im-rc 14.3.0",
"indoc 0.3.6",
"inkwell 0.1.0",
"libc",
"maplit",
"morphic_lib",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -3915,7 +3890,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"indoc 0.3.6",
"libc",
"parity-wasm",
"pretty_assertions 0.5.1",
"roc_builtins",
@ -3992,7 +3966,6 @@ dependencies = [
"bumpalo",
"indoc 0.3.6",
"lazy_static",
"maplit",
"pretty_assertions 0.5.1",
"roc_collections",
"roc_ident",
@ -4009,7 +3982,6 @@ dependencies = [
"hashbrown 0.11.2",
"indoc 0.3.6",
"linked-hash-map",
"maplit",
"morphic_lib",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
@ -4051,7 +4023,6 @@ name = "roc_problem"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4074,7 +4045,6 @@ dependencies = [
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4098,7 +4068,6 @@ version = "0.1.0"
dependencies = [
"bumpalo",
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4125,7 +4094,6 @@ name = "roc_types"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4141,7 +4109,6 @@ name = "roc_unify"
version = "0.1.0"
dependencies = [
"indoc 0.3.6",
"maplit",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
@ -4170,15 +4137,6 @@ dependencies = [
"minimp3",
]
[[package]]
name = "ropey"
version = "1.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9150aff6deb25b20ed110889f070a678bcd1033e46e5e9d6fb1abeab17947f28"
dependencies = [
"smallvec",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
@ -4624,18 +4582,6 @@ dependencies = [
"unicode-xid 0.2.2",
]
[[package]]
name = "synstructure"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "474aaa926faa1603c40b7885a9eaea29b444d1cb2850cb7c0e37bb1a4182f4fa"
dependencies = [
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
"unicode-xid 0.2.2",
]
[[package]]
name = "target-lexicon"
version = "0.12.2"
@ -4677,7 +4623,6 @@ dependencies = [
"inkwell 0.1.0",
"libc",
"libloading 0.6.7",
"maplit",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
"roc_build",
@ -4710,31 +4655,16 @@ name = "test_mono"
version = "0.1.0"
dependencies = [
"bumpalo",
"either",
"im 14.3.0",
"im-rc 14.3.0",
"indoc 0.3.6",
"libc",
"libloading 0.6.7",
"pretty_assertions 0.5.1",
"quickcheck 0.8.5",
"quickcheck_macros 0.8.0",
"roc_build",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_constrain",
"roc_load",
"roc_module",
"roc_mono",
"roc_parse",
"roc_problem",
"roc_region",
"roc_reporting",
"roc_solve",
"roc_types",
"roc_unify",
"target-lexicon",
"test_mono_macros",
]
@ -4742,12 +4672,34 @@ dependencies = [
name = "test_mono_macros"
version = "0.1.0"
dependencies = [
"darling 0.10.2",
"proc-macro2 1.0.29",
"quote 1.0.9",
"syn 1.0.76",
]
[[package]]
name = "test_wasm"
version = "0.1.0"
dependencies = [
"bumpalo",
"indoc 0.3.6",
"libc",
"parity-wasm",
"pretty_assertions 0.5.1",
"roc_builtins",
"roc_can",
"roc_collections",
"roc_gen_wasm",
"roc_load",
"roc_module",
"roc_std",
"roc_types",
"target-lexicon",
"tempfile",
"wasmer",
"wasmer-wasi",
]
[[package]]
name = "textwrap"
version = "0.11.0"
@ -5749,24 +5701,3 @@ checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
dependencies = [
"linked-hash-map",
]
[[package]]
name = "zerocopy"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6580539ad917b7c026220c4b3f2c08d52ce54d6ce0dc491e66002e35388fab46"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d498dbd1fd7beb83c86709ae1c33ca50942889473473d287d56ce4770a18edfb"
dependencies = [
"proc-macro2 1.0.29",
"syn 1.0.76",
"synstructure",
]

View File

@ -24,6 +24,7 @@ members = [
"compiler/build",
"compiler/arena_pool",
"compiler/test_gen",
"compiler/test_wasm",
"vendor/ena",
"vendor/inkwell",
"vendor/pathfinding",

View File

@ -1,4 +1,4 @@
FROM rust:1.54-slim-bullseye
FROM rust:1.56.1-slim-bullseye
WORKDIR /earthbuild
prep-debian:

View File

@ -23,7 +23,7 @@ libc = "0.2"
page_size = "0.4"
snafu = { version = "0.6", features = ["backtraces"] }
ven_graph = { path = "../vendor/pathfinding" }
indoc = "1.0"
[dev-dependencies]
pretty_assertions = "0.6"
indoc = "1.0"

View File

@ -10,8 +10,9 @@
///
/// Pages also use the node value 0 (all 0 bits) to mark nodes as unoccupied.
/// This is important for performance.
use libc::{c_void, MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use libc::{MAP_ANONYMOUS, MAP_PRIVATE, PROT_READ, PROT_WRITE};
use std::any::type_name;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;
use std::ptr::null;

View File

@ -1,6 +1,6 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;

View File

@ -1,8 +1,8 @@
use super::pool::{NodeId, Pool, NODE_BYTES};
use super::shallow_clone::ShallowClone;
use libc::c_void;
use std::any::type_name;
use std::cmp::Ordering;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::mem::size_of;

View File

@ -65,7 +65,6 @@ rustyline-derive = { git = "https://github.com/rtfeldman/rustyline", tag = "prom
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.2", features = ["collections"] }
libc = "0.2"
libloading = "0.6"
mimalloc = { version = "0.1.26", default-features = false }
@ -78,7 +77,6 @@ wasmer-wasi = "2.0.0"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
@ -90,4 +88,3 @@ cli_utils = { path = "cli_utils" }
[[bench]]
name = "time_bench"
harness = false

View File

@ -15,8 +15,7 @@ roc_collections = { path = "../../compiler/collections" }
roc_load = { path = "../../compiler/load" }
roc_module = { path = "../../compiler/module" }
bumpalo = { version = "3.6.1", features = ["collections"] }
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
inlinable_string = "0.1"
criterion = { git = "https://github.com/Anton-4/criterion.rs"}
serde = { version = "1.0", features = ["derive"] }
serde-xml-rs = "0.4"
strip-ansi-escapes = "0.1"

View File

@ -119,7 +119,7 @@ pub fn build_app<'a>() -> App<'a> {
)
.subcommand(
App::new(CMD_DOCS)
.about("Generate documentation for Roc modules")
.about("Generate documentation for Roc modules (Work In Progress)")
.arg(Arg::with_name(DIRECTORY_OR_FILES)
.index(1)
.multiple(true)

View File

@ -362,7 +362,7 @@ mod cli_run {
stdin: &[],
input_file: Some("examples/hello.false"),
expected_ending:"Hello, World!\n",
use_valgrind: false,
use_valgrind: true,
}
},
}

View File

@ -27,7 +27,6 @@ roc_std = { path = "../../roc_std" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
inlinable_string = "0.1.0"
libloading = "0.6"
tempfile = "3.1.0"
serde_json = "1.0"
@ -36,7 +35,6 @@ target-lexicon = "0.12.2"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -1,4 +1,4 @@
use crate::target::arch_str;
use crate::target::{arch_str, target_triple_str};
#[cfg(feature = "llvm")]
use libloading::{Error, Library};
use roc_builtins::bitcode;
@ -372,6 +372,20 @@ pub fn rebuild_host(
shared_lib_path,
)
}
Architecture::Aarch64(_) => {
let emit_bin = format!("-femit-bin={}", host_dest_native.to_str().unwrap());
build_zig_host_native(
&env_path,
&env_home,
&emit_bin,
zig_host_src.to_str().unwrap(),
zig_str_path.to_str().unwrap(),
target_triple_str(target),
opt_level,
shared_lib_path,
)
}
_ => panic!("Unsupported architecture {:?}", target.architecture),
};

View File

@ -12,7 +12,6 @@ roc_module = { path = "../module" }
roc_types = { path = "../types" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -140,6 +140,7 @@ const Caller0 = fn (?[*]u8, ?[*]u8) callconv(.C) void;
const Caller1 = fn (?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller2 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller3 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
const Caller4 = fn (?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8, ?[*]u8) callconv(.C) void;
pub fn listReverse(list: RocList, alignment: u32, element_width: usize, update_mode: UpdateMode) callconv(.C) RocList {
if (list.bytes) |source_ptr| {
@ -352,6 +353,70 @@ pub fn listMap3(
}
}
pub fn listMap4(
list1: RocList,
list2: RocList,
list3: RocList,
list4: RocList,
caller: Caller4,
data: Opaque,
inc_n_data: IncN,
data_is_owned: bool,
alignment: u32,
a_width: usize,
b_width: usize,
c_width: usize,
d_width: usize,
e_width: usize,
dec_a: Dec,
dec_b: Dec,
dec_c: Dec,
dec_d: Dec,
) callconv(.C) RocList {
const output_length = std.math.min(std.math.min(list1.len(), list2.len()), std.math.min(list3.len(), list4.len()));
decrementTail(list1, output_length, a_width, dec_a);
decrementTail(list2, output_length, b_width, dec_b);
decrementTail(list3, output_length, c_width, dec_c);
decrementTail(list4, output_length, d_width, dec_d);
if (data_is_owned) {
inc_n_data(data, output_length);
}
if (list1.bytes) |source_a| {
if (list2.bytes) |source_b| {
if (list3.bytes) |source_c| {
if (list4.bytes) |source_d| {
const output = RocList.allocate(alignment, output_length, e_width);
const target_ptr = output.bytes orelse unreachable;
var i: usize = 0;
while (i < output_length) : (i += 1) {
const element_a = source_a + i * a_width;
const element_b = source_b + i * b_width;
const element_c = source_c + i * c_width;
const element_d = source_d + i * d_width;
const target = target_ptr + i * e_width;
caller(data, element_a, element_b, element_c, element_d, target);
}
return output;
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
} else {
return RocList.empty();
}
}
pub fn listKeepIf(
list: RocList,
caller: Caller1,

View File

@ -26,6 +26,7 @@ comptime {
exportListFn(list.listMap, "map");
exportListFn(list.listMap2, "map2");
exportListFn(list.listMap3, "map3");
exportListFn(list.listMap4, "map4");
exportListFn(list.listMapWithIndex, "map_with_index");
exportListFn(list.listKeepIf, "keep_if");
exportListFn(list.listWalk, "walk");

View File

@ -25,6 +25,7 @@ interface List
map,
map2,
map3,
map4,
mapWithIndex,
mapOrDrop,
mapJoin,
@ -269,6 +270,11 @@ map2 : List a, List b, (a, b -> c) -> List c
## Repeat until a list runs out of elements.
map3 : List a, List b, List c, (a, b, c -> d) -> List d
## Run a transformation function on the first element of each list,
## and use that as the first element in the returned list.
## Repeat until a list runs out of elements.
map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
## This works like [List.map], except it also passes the index
## of the element to the conversion function.
mapWithIndex : List before, (before, Nat -> after) -> List after

View File

@ -165,6 +165,7 @@ pub const SET_FROM_LIST: &str = "roc_builtins.dict.set_from_list";
pub const LIST_MAP: &str = "roc_builtins.list.map";
pub const LIST_MAP2: &str = "roc_builtins.list.map2";
pub const LIST_MAP3: &str = "roc_builtins.list.map3";
pub const LIST_MAP4: &str = "roc_builtins.list.map4";
pub const LIST_MAP_WITH_INDEX: &str = "roc_builtins.list.map_with_index";
pub const LIST_KEEP_IF: &str = "roc_builtins.list.keep_if";
pub const LIST_KEEP_OKS: &str = "roc_builtins.list.keep_oks";

View File

@ -64,7 +64,7 @@ const TVAR1: VarId = VarId::from_u32(1);
const TVAR2: VarId = VarId::from_u32(2);
const TVAR3: VarId = VarId::from_u32(3);
const TVAR4: VarId = VarId::from_u32(4);
const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(5);
const TOP_LEVEL_CLOSURE_VAR: VarId = VarId::from_u32(10);
pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
let mut types = HashMap::with_capacity_and_hasher(NUM_BUILTIN_IMPORTS, default_hasher());
@ -930,6 +930,27 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
)
};
{
let_tvars! {a, b, c, d, e, cvar};
// map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
add_top_level_function_type!(
Symbol::LIST_MAP4,
vec![
list_type(flex(a)),
list_type(flex(b)),
list_type(flex(c)),
list_type(flex(d)),
closure(
vec![flex(a), flex(b), flex(c), flex(d)],
cvar,
Box::new(flex(e))
),
],
Box::new(list_type(flex(e))),
)
};
// append : List elem, elem -> List elem
add_top_level_function_type!(
Symbol::LIST_APPEND,

View File

@ -20,7 +20,6 @@ bumpalo = { version = "3.6.1", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -89,6 +89,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_MAP => list_map,
LIST_MAP2 => list_map2,
LIST_MAP3 => list_map3,
LIST_MAP4 => list_map4,
LIST_DROP => list_drop,
LIST_DROP_AT => list_drop_at,
LIST_DROP_LAST => list_drop_last,
@ -290,6 +291,41 @@ fn lowlevel_4(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
)
}
fn lowlevel_5(symbol: Symbol, op: LowLevel, var_store: &mut VarStore) -> Def {
let arg1_var = var_store.fresh();
let arg2_var = var_store.fresh();
let arg3_var = var_store.fresh();
let arg4_var = var_store.fresh();
let arg5_var = var_store.fresh();
let ret_var = var_store.fresh();
let body = RunLowLevel {
op,
args: vec![
(arg1_var, Var(Symbol::ARG_1)),
(arg2_var, Var(Symbol::ARG_2)),
(arg3_var, Var(Symbol::ARG_3)),
(arg4_var, Var(Symbol::ARG_4)),
(arg5_var, Var(Symbol::ARG_5)),
],
ret_var,
};
defn(
symbol,
vec![
(arg1_var, Symbol::ARG_1),
(arg2_var, Symbol::ARG_2),
(arg3_var, Symbol::ARG_3),
(arg4_var, Symbol::ARG_4),
(arg5_var, Symbol::ARG_5),
],
var_store,
body,
ret_var,
)
}
/// Num.maxInt : Int
fn num_max_int(symbol: Symbol, var_store: &mut VarStore) -> Def {
let int_var = var_store.fresh();
@ -2541,11 +2577,16 @@ fn list_map2(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_3(symbol, LowLevel::ListMap2, var_store)
}
/// List.map3 : List a, List b, (a, b -> c) -> List c
/// List.map3 : List a, List b, List c, (a, b, c -> d) -> List d
fn list_map3(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_4(symbol, LowLevel::ListMap3, var_store)
}
/// List.map4 : List a, List b, List c, List d, (a, b, c, d -> e) -> List e
fn list_map4(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_5(symbol, LowLevel::ListMap4, var_store)
}
/// List.sortWith : List a, (a, a -> Ordering) -> List a
fn list_sort_with(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_2(symbol, LowLevel::ListSortWith, var_store)

View File

@ -16,7 +16,6 @@ roc_builtins = { path = "../builtins" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -16,7 +16,6 @@ bumpalo = { version = "3.6.1", features = ["collections"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -9,12 +9,10 @@ edition = "2018"
[dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_load = { path = "../load" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_mono = { path = "../mono" }
@ -22,7 +20,6 @@ im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
target-lexicon = "0.12.2"
libloading = "0.6"
object = { version = "0.24", features = ["write"] }
[dev-dependencies]
@ -30,17 +27,18 @@ roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
roc_load = { path = "../load" }
roc_constrain = { path = "../constrain" }
roc_std = { path = "../../roc_std" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"
tempfile = "3.1.0"
itertools = "0.9"
libloading = "0.6"
[features]
target-aarch64 = ["roc_build/target-aarch64"]

View File

@ -830,8 +830,9 @@ impl<
layout: &Layout<'a>,
fields: &'a [Symbol],
) -> Result<(), String> {
let struct_size = layout.stack_size(PTR_SIZE);
if let Layout::Struct(field_layouts) = layout {
let struct_size = layout.stack_size(PTR_SIZE);
if struct_size > 0 {
let offset = self.claim_stack_size(struct_size)?;
self.symbol_storage_map.insert(
@ -862,7 +863,6 @@ impl<
Ok(())
} else {
// This is a single element struct. Just copy the single field to the stack.
let struct_size = layout.stack_size(PTR_SIZE);
let offset = self.claim_stack_size(struct_size)?;
self.symbol_storage_map.insert(
*sym,

View File

@ -31,10 +31,8 @@ roc_load = { path = "../load" }
roc_reporting = { path = "../reporting" }
roc_build = { path = "../build" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
libc = "0.2"

View File

@ -10,9 +10,9 @@ use crate::llvm::build_hash::generic_hash;
use crate::llvm::build_list::{
self, allocate_list, empty_list, empty_polymorphic_list, list_append, list_concat,
list_contains, list_drop, list_drop_at, list_get_unsafe, list_join, list_keep_errs,
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map_with_index,
list_prepend, list_range, list_repeat, list_reverse, list_set, list_single, list_sort_with,
list_swap,
list_keep_if, list_keep_oks, list_len, list_map, list_map2, list_map3, list_map4,
list_map_with_index, list_prepend, list_range, list_repeat, list_reverse, list_set,
list_single, list_sort_with, list_swap,
};
use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
@ -1453,7 +1453,33 @@ pub fn build_tag<'a, 'ctx, 'env>(
UnionLayout::NonRecursive(tags) => {
debug_assert!(union_size > 1);
let ctx = env.context;
let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes);
let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type();
let wrapper_type = env
.context
.struct_type(&[internal_type, tag_id_type.into()], false);
let result_alloca = env.builder.build_alloca(wrapper_type, "tag_opaque");
// Initialize all memory of the alloca. This _should_ not be required, but currently
// LLVM can access uninitialized memory after applying some optimizations. Hopefully
// we can in the future adjust code gen so this is no longer an issue.
//
// An example is
//
// main : Task.Task {} []
// main =
// when List.len [ Ok "foo", Err 42, Ok "spam" ] is
// n -> Task.putLine (Str.fromInt n)
//
// Here the decrement function of result must first check it's an Ok tag,
// then defers to string decrement, which must check is the string is small or large
//
// After inlining, those checks are combined. That means that even if the tag is Err,
// a check is done on the "string" to see if it is big or small, which will touch the
// uninitialized memory.
let all_zeros = wrapper_type.const_zero();
env.builder.build_store(result_alloca, all_zeros);
// Determine types
let num_fields = arguments.len() + 1;
@ -1484,54 +1510,46 @@ pub fn build_tag<'a, 'ctx, 'env>(
}
}
}
// Create the struct_type
let struct_type = ctx.struct_type(field_types.into_bump_slice(), false);
// Insert field exprs into struct_val
let struct_val =
struct_from_fields(env, struct_type, field_vals.into_iter().enumerate());
// How we create tag values
//
// The memory layout of tags can be different. e.g. in
//
// [ Ok Int, Err Str ]
//
// the `Ok` tag stores a 64-bit integer, the `Err` tag stores a struct.
// All tags of a union must have the same length, for easy addressing (e.g. array lookups).
// So we need to ask for the maximum of all tag's sizes, even if most tags won't use
// all that memory, and certainly won't use it in the same way (the tags have fields of
// different types/sizes)
//
// In llvm, we must be explicit about the type of value we're creating: we can't just
// make a unspecified block of memory. So what we do is create a byte array of the
// desired size. Then when we know which tag we have (which is here, in this function),
// we need to cast that down to the array of bytes that llvm expects
//
// There is the bitcast instruction, but it doesn't work for arrays. So we need to jump
// through some hoops using store and load to get this to work: the array is put into a
// one-element struct, which can be cast to the desired type.
//
// This tricks comes from
// https://github.com/raviqqe/ssf/blob/bc32aae68940d5bddf5984128e85af75ca4f4686/ssf-llvm/src/expression_compiler.rs#L116
let internal_type = block_of_memory_slices(env.context, tags, env.ptr_bytes);
let data = cast_tag_to_block_of_memory(env, struct_val, internal_type);
let tag_id_type = basic_type_from_layout(env, &tag_id_layout).into_int_type();
let wrapper_type = env
.context
.struct_type(&[data.get_type(), tag_id_type.into()], false);
// store the tag id
let tag_id_ptr = env
.builder
.build_struct_gep(result_alloca, TAG_ID_INDEX, "get_opaque_data")
.unwrap();
let tag_id_intval = tag_id_type.const_int(tag_id as u64, false);
env.builder.build_store(tag_id_ptr, tag_id_intval);
let field_vals = [
(TAG_DATA_INDEX as usize, data),
(TAG_ID_INDEX as usize, tag_id_intval.into()),
];
// Create the struct_type
let struct_type = env
.context
.struct_type(field_types.into_bump_slice(), false);
struct_from_fields(env, wrapper_type, field_vals.iter().copied()).into()
let struct_opaque_ptr = env
.builder
.build_struct_gep(result_alloca, TAG_DATA_INDEX, "get_opaque_data")
.unwrap();
let struct_ptr = env.builder.build_pointer_cast(
struct_opaque_ptr,
struct_type.ptr_type(AddressSpace::Generic),
"to_specific",
);
// Insert field exprs into struct_val
//let struct_val =
//struct_from_fields(env, struct_type, field_vals.into_iter().enumerate());
// Insert field exprs into struct_val
for (index, field_val) in field_vals.iter().copied().enumerate() {
let index: u32 = index as u32;
let ptr = env
.builder
.build_struct_gep(struct_ptr, index, "get_tag_field_ptr")
.unwrap();
env.builder.build_store(ptr, field_val);
}
env.builder.build_load(result_alloca, "load_result")
}
UnionLayout::Recursive(tags) => {
debug_assert!(union_size > 1);
@ -2653,14 +2671,6 @@ pub fn complex_bitcast_struct_struct<'ctx>(
complex_bitcast(builder, from_value.into(), to_type.into(), name).into_struct_value()
}
fn cast_tag_to_block_of_memory<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
from_value: StructValue<'ctx>,
to_type: BasicTypeEnum<'ctx>,
) -> BasicValueEnum<'ctx> {
complex_bitcast_check_size(env, from_value.into(), to_type, "tag_to_block_of_memory")
}
pub fn cast_block_of_memory_to_tag<'ctx>(
builder: &Builder<'ctx>,
from_value: StructValue<'ctx>,
@ -4609,6 +4619,67 @@ fn run_higher_order_low_level<'a, 'ctx, 'env>(
_ => unreachable!("invalid list layout"),
}
}
ListMap4 { xs, ys, zs, ws } => {
let (list1, list1_layout) = load_symbol_and_layout(scope, &xs);
let (list2, list2_layout) = load_symbol_and_layout(scope, &ys);
let (list3, list3_layout) = load_symbol_and_layout(scope, &zs);
let (list4, list4_layout) = load_symbol_and_layout(scope, &ws);
let (function, closure, closure_layout) = function_details!();
match (
list1_layout,
list2_layout,
list3_layout,
list4_layout,
return_layout,
) {
(
Layout::Builtin(Builtin::List(element1_layout)),
Layout::Builtin(Builtin::List(element2_layout)),
Layout::Builtin(Builtin::List(element3_layout)),
Layout::Builtin(Builtin::List(element4_layout)),
Layout::Builtin(Builtin::List(result_layout)),
) => {
let argument_layouts = &[
**element1_layout,
**element2_layout,
**element3_layout,
**element4_layout,
];
let roc_function_call = roc_function_call(
env,
layout_ids,
function,
closure,
closure_layout,
function_owns_closure_data,
argument_layouts,
);
list_map4(
env,
layout_ids,
roc_function_call,
list1,
list2,
list3,
list4,
element1_layout,
element2_layout,
element3_layout,
element4_layout,
result_layout,
)
}
(Layout::Builtin(Builtin::EmptyList), _, _, _, _)
| (_, Layout::Builtin(Builtin::EmptyList), _, _, _)
| (_, _, Layout::Builtin(Builtin::EmptyList), _, _)
| (_, _, _, Layout::Builtin(Builtin::EmptyList), _) => empty_list(env),
_ => unreachable!("invalid list layout"),
}
}
ListMapWithIndex { xs } => {
// List.mapWithIndex : List before, (Nat, before -> after) -> List after
let (list, list_layout) = load_symbol_and_layout(scope, &xs);
@ -5640,7 +5711,7 @@ fn run_low_level<'a, 'ctx, 'env>(
cond
}
ListMap | ListMap2 | ListMap3 | ListMapWithIndex | ListKeepIf | ListWalk
ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk
| ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith
| DictWalk => unreachable!("these are higher order, and are handled elsewhere"),
}

View File

@ -821,6 +821,51 @@ pub fn list_map3<'a, 'ctx, 'env>(
)
}
pub fn list_map4<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
roc_function_call: RocFunctionCall<'ctx>,
list1: BasicValueEnum<'ctx>,
list2: BasicValueEnum<'ctx>,
list3: BasicValueEnum<'ctx>,
list4: BasicValueEnum<'ctx>,
element1_layout: &Layout<'a>,
element2_layout: &Layout<'a>,
element3_layout: &Layout<'a>,
element4_layout: &Layout<'a>,
result_layout: &Layout<'a>,
) -> BasicValueEnum<'ctx> {
let dec_a = build_dec_wrapper(env, layout_ids, element1_layout);
let dec_b = build_dec_wrapper(env, layout_ids, element2_layout);
let dec_c = build_dec_wrapper(env, layout_ids, element3_layout);
let dec_d = build_dec_wrapper(env, layout_ids, element4_layout);
call_bitcode_fn_returns_list(
env,
&[
pass_list_cc(env, list1),
pass_list_cc(env, list2),
pass_list_cc(env, list3),
pass_list_cc(env, list4),
roc_function_call.caller.into(),
pass_as_opaque(env, roc_function_call.data),
roc_function_call.inc_n_data.into(),
roc_function_call.data_is_owned.into(),
env.alignment_intvalue(result_layout),
layout_width(env, element1_layout),
layout_width(env, element2_layout),
layout_width(env, element3_layout),
layout_width(env, element4_layout),
layout_width(env, result_layout),
dec_a.as_global_value().as_pointer_value().into(),
dec_b.as_global_value().as_pointer_value().into(),
dec_c.as_global_value().as_pointer_value().into(),
dec_d.as_global_value().as_pointer_value().into(),
],
bitcode::LIST_MAP4,
)
}
/// List.concat : List elem, List elem -> List elem
pub fn list_concat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
@ -965,6 +1010,8 @@ where
let ctx = env.context;
let builder = env.builder;
let entry = env.builder.get_insert_block().unwrap();
// constant 1i64
let one = env.ptr_int().const_int(1, false);
@ -976,15 +1023,15 @@ where
builder.build_unconditional_branch(loop_bb);
builder.position_at_end(loop_bb);
let curr_index = builder
.build_load(index_alloca, index_name)
.into_int_value();
let next_index = builder.build_int_add(curr_index, one, "nextindex");
let current_index_phi = env.builder.build_phi(env.ptr_int(), "current_index");
let current_index = current_index_phi.as_basic_value().into_int_value();
builder.build_store(index_alloca, next_index);
let next_index = builder.build_int_add(current_index, one, "next_index");
current_index_phi.add_incoming(&[(&next_index, loop_bb), (&env.ptr_int().const_zero(), entry)]);
// The body of the loop
loop_fn(curr_index);
loop_fn(current_index);
// #index < end
let loop_end_cond = bounds_check_comparison(builder, next_index, end);

View File

@ -15,7 +15,6 @@ parity-wasm = { git = "https://github.com/brian-carroll/parity-wasm", branch = "
roc_std = { path = "../../roc_std" }
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
[dev-dependencies]
roc_can = { path = "../can" }
@ -25,6 +24,6 @@ roc_types = { path = "../types" }
roc_module = { path = "../module" }
indoc = "0.3.3"
pretty_assertions = "0.5.1"
libc = "0.2"
target-lexicon = "0.12.2"
tempfile = "3.1.0"
wasmer-wasi = "2.0.0"

View File

@ -1,6 +1,6 @@
use bumpalo::collections::Vec;
use parity_wasm::builder;
use parity_wasm::builder::{CodeLocation, FunctionDefinition, ModuleBuilder};
use parity_wasm::builder::{FunctionDefinition, ModuleBuilder};
use roc_collections::all::MutMap;
use roc_module::low_level::LowLevel;
@ -10,8 +10,10 @@ use roc_mono::layout::{Builtin, Layout};
use crate::code_builder::{BlockType, CodeBuilder, ValueType};
use crate::layout::WasmLayout;
use crate::module_builder::RelocationEntry;
use crate::serialize::SerialBuffer;
use crate::storage::{Storage, StoredValue, StoredValueKind};
use crate::{copy_memory, overwrite_padded_u32, CopyMemoryConfig, Env, LocalId, PTR_TYPE};
use crate::{copy_memory, CopyMemoryConfig, Env, LocalId, PTR_TYPE};
// Don't allocate any constant data at address zero or near it. Would be valid, but bug-prone.
// Follow Emscripten's example by using 1kB (4 bytes would probably do)
@ -26,9 +28,10 @@ pub struct WasmBackend<'a> {
// Module-level data
pub module_builder: ModuleBuilder,
pub code_section_bytes: std::vec::Vec<u8>,
pub code_relocations: Vec<'a, RelocationEntry>,
_data_offset_map: MutMap<Literal<'a>, u32>,
_data_offset_next: u32,
proc_symbol_map: MutMap<Symbol, CodeLocation>,
proc_symbols: &'a [Symbol],
// Function-level data
code_builder: CodeBuilder<'a>,
@ -40,15 +43,12 @@ pub struct WasmBackend<'a> {
}
impl<'a> WasmBackend<'a> {
pub fn new(env: &'a Env<'a>, num_procs: usize) -> Self {
// Code section is prefixed with the number of Wasm functions
// For now, this is the same as the number of IR procedures (until we start inlining!)
pub fn new(env: &'a Env<'a>, proc_symbols: &'a [Symbol]) -> Self {
let mut code_section_bytes = std::vec::Vec::with_capacity(4096);
// Reserve space for code section header: inner byte length and number of functions
// Padded to the maximum 5 bytes each, so we can update later without moving everything
code_section_bytes.resize(10, 0);
overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs as u32); // gets modified in unit tests
// Code section header
code_section_bytes.reserve_padded_u32(); // byte length, to be written at the end
code_section_bytes.encode_padded_u32(proc_symbols.len() as u32); // modified later in unit tests
WasmBackend {
env,
@ -58,7 +58,8 @@ impl<'a> WasmBackend<'a> {
code_section_bytes,
_data_offset_map: MutMap::default(),
_data_offset_next: UNUSED_DATA_SECTION_BYTES,
proc_symbol_map: MutMap::default(),
proc_symbols,
code_relocations: Vec::with_capacity_in(256, env.arena),
// Function-level data
block_depth: 0,
@ -81,7 +82,7 @@ impl<'a> WasmBackend<'a> {
***********************************************************/
pub fn build_proc(&mut self, proc: Proc<'a>, sym: Symbol) -> Result<u32, String> {
pub fn build_proc(&mut self, proc: Proc<'a>, _sym: Symbol) -> Result<u32, String> {
// println!("\ngenerating procedure {:?}\n", sym);
// Use parity-wasm to add the signature in "types" and "functions" sections
@ -89,7 +90,6 @@ impl<'a> WasmBackend<'a> {
let empty_function_def = self.start_proc(&proc);
let location = self.module_builder.push_function(empty_function_def);
let function_index = location.body;
self.proc_symbol_map.insert(sym, location);
self.build_stmt(&proc.body, &proc.ret_layout)?;
@ -141,9 +141,8 @@ impl<'a> WasmBackend<'a> {
self.storage.stack_frame_pointer,
);
self.code_builder
.serialize(&mut self.code_section_bytes)
.map_err(|e| format!("{:?}", e))?;
let relocs = self.code_builder.serialize(&mut self.code_section_bytes);
self.code_relocations.extend(relocs);
Ok(())
}
@ -396,13 +395,29 @@ impl<'a> WasmBackend<'a> {
self.storage.load_symbols(&mut self.code_builder, wasm_args);
let function_location = self.proc_symbol_map.get(func_sym).ok_or(format!(
"Cannot find function {:?} called from {:?}",
func_sym, sym
))?;
// Index of the called function in the code section
// TODO: account for inlined functions when we start doing that (remember we emit procs out of order)
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 supporteed: calling foreign function {:?}",
func_sym
));
}
};
// Index of the function's name in the symbol table
let symbol_index = func_index; // TODO: update this when we add other things to the symbol table
self.code_builder.call(
func_index,
symbol_index,
wasm_args.len(),
has_return_val,
);
self.code_builder
.call(function_location.body, wasm_args.len(), has_return_val);
Ok(())
}

View File

@ -1,17 +1,14 @@
use bumpalo::collections::Vec;
use bumpalo::collections::vec::{Drain, Vec};
use bumpalo::Bump;
use core::panic;
use std::fmt::Debug;
use roc_module::symbol::Symbol;
use crate::{
encode_f32, encode_f64, encode_i32, encode_i64, encode_u32, round_up_to_alignment, LocalId,
FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID,
};
use crate::{opcodes::*, overwrite_padded_u32};
const DEBUG_LOG: bool = false;
use crate::module_builder::{IndexRelocType, RelocationEntry};
use crate::opcodes::*;
use crate::serialize::SerialBuffer;
use crate::{round_up_to_alignment, LocalId, FRAME_ALIGNMENT_BYTES, STACK_POINTER_GLOBAL_ID};
/// Wasm value type. (Rust representation matches Wasm encoding)
#[repr(u8)]
@ -143,6 +140,10 @@ pub struct CodeBuilder<'a> {
/// Our simulation model of the Wasm stack machine
/// Keeps track of where Symbol values are in the VM stack
vm_stack: Vec<'a, Symbol>,
/// Linker info to help combine the Roc module with builtin & platform modules,
/// e.g. to modify call instructions when function indices change
relocations: Vec<'a, RelocationEntry>,
}
#[allow(clippy::new_without_default)]
@ -155,6 +156,7 @@ impl<'a> CodeBuilder<'a> {
preamble: Vec::with_capacity_in(32, arena),
inner_length: Vec::with_capacity_in(5, arena),
vm_stack: Vec::with_capacity_in(32, arena),
relocations: Vec::with_capacity_in(32, arena),
}
}
@ -216,7 +218,7 @@ impl<'a> CodeBuilder<'a> {
let start = self.insert_bytes.len();
self.insert_bytes.push(opcode);
encode_u32(&mut self.insert_bytes, immediate);
self.insert_bytes.encode_u32(immediate);
self.insert_locations.push(InsertLocation {
insert_at,
@ -313,14 +315,14 @@ impl<'a> CodeBuilder<'a> {
if *t == batch_type {
batch_size += 1;
} else {
encode_u32(&mut self.preamble, batch_size);
self.preamble.encode_u32(batch_size);
self.preamble.push(batch_type as u8);
batch_type = *t;
batch_size = 1;
num_batches += 1;
}
}
encode_u32(&mut self.preamble, batch_size);
self.preamble.encode_u32(batch_size);
self.preamble.push(batch_type as u8);
num_batches += 1;
@ -333,7 +335,7 @@ impl<'a> CodeBuilder<'a> {
let old_len = self.preamble.len();
self.preamble.resize(old_len + 4, 0);
self.preamble.copy_within(1..old_len, 5);
overwrite_padded_u32(&mut self.preamble[0..5], num_batches);
self.preamble.overwrite_padded_u32(0, num_batches);
}
}
@ -342,14 +344,14 @@ impl<'a> CodeBuilder<'a> {
// Can't use the usual instruction methods because they push to self.code.
// This is the only case where we push instructions somewhere different.
self.preamble.push(GETGLOBAL);
encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID);
self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID);
self.preamble.push(I32CONST);
encode_i32(&mut self.preamble, frame_size);
self.preamble.encode_i32(frame_size);
self.preamble.push(I32SUB);
self.preamble.push(TEELOCAL);
encode_u32(&mut self.preamble, frame_pointer.0);
self.preamble.encode_u32(frame_pointer.0);
self.preamble.push(SETGLOBAL);
encode_u32(&mut self.preamble, STACK_POINTER_GLOBAL_ID);
self.preamble.encode_u32(STACK_POINTER_GLOBAL_ID);
}
/// Generate instruction bytes to release a frame of stack memory on leaving the function
@ -381,27 +383,53 @@ impl<'a> CodeBuilder<'a> {
self.code.push(END);
let inner_len = self.preamble.len() + self.code.len() + self.insert_bytes.len();
encode_u32(&mut self.inner_length, inner_len as u32);
self.inner_length.encode_u32(inner_len as u32);
}
/// Write out all the bytes in the right order
pub fn serialize<W: std::io::Write>(&mut self, writer: &mut W) -> std::io::Result<()> {
writer.write_all(&self.inner_length)?;
writer.write_all(&self.preamble)?;
pub fn serialize<T: SerialBuffer>(
&mut self,
code_section_buf: &mut T,
) -> Drain<RelocationEntry> {
code_section_buf.append_slice(&self.inner_length);
code_section_buf.append_slice(&self.preamble);
// We created each insertion when a local was used for the _second_ time.
// But we want them in the order they were first assigned, which may not be the same.
// Sort insertions. They are not created in order of assignment, but in order of *second* usage.
self.insert_locations.sort_by_key(|loc| loc.insert_at);
let mut pos: usize = 0;
// Do the insertions & update relocation offsets
const CODE_SECTION_BODY_OFFSET: usize = 5;
let mut reloc_index = 0;
let mut code_pos: usize = 0;
for location in self.insert_locations.iter() {
writer.write_all(&self.code[pos..location.insert_at])?;
writer.write_all(&self.insert_bytes[location.start..location.end])?;
pos = location.insert_at;
// Relocation offset needs to be an index into the body of the code section, but
// at this point it is an index into self.code. Need to adjust for all previous functions
// in the code section, and for insertions in the current function.
let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET;
while reloc_index < self.relocations.len()
&& self.relocations[reloc_index].offset() < location.insert_at as u32
{
let offset_ref = self.relocations[reloc_index].offset_mut();
*offset_ref += (section_body_pos - code_pos) as u32;
reloc_index += 1;
}
code_section_buf.append_slice(&self.code[code_pos..location.insert_at]);
code_section_buf.append_slice(&self.insert_bytes[location.start..location.end]);
code_pos = location.insert_at;
}
let section_body_pos = code_section_buf.size() - CODE_SECTION_BODY_OFFSET;
while reloc_index < self.relocations.len() {
let offset_ref = self.relocations[reloc_index].offset_mut();
*offset_ref += (section_body_pos - code_pos) as u32;
reloc_index += 1;
}
let len = self.code.len();
writer.write_all(&self.code[pos..len])
code_section_buf.append_slice(&self.code[code_pos..len]);
self.relocations.drain(0..)
}
/**********************************************************
@ -426,15 +454,16 @@ impl<'a> CodeBuilder<'a> {
self.code.push(immediate);
}
fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) {
// public for use in test code
pub fn inst_imm32(&mut self, opcode: u8, pops: usize, push: bool, immediate: u32) {
self.inst(opcode, pops, push);
encode_u32(&mut self.code, immediate);
self.code.encode_u32(immediate);
}
fn inst_mem(&mut self, opcode: u8, pops: usize, push: bool, align: Align, offset: u32) {
self.inst(opcode, pops, push);
self.code.push(align as u8);
encode_u32(&mut self.code, offset);
self.code.encode_u32(offset);
}
/**********************************************************
@ -469,13 +498,20 @@ impl<'a> CodeBuilder<'a> {
pub fn br_if(&mut self, levels: u32) {
self.inst_imm32(BRIF, 1, false, levels);
}
#[allow(dead_code)]
fn br_table() {
panic!("TODO");
}
instruction_no_args!(return_, RETURN, 0, false);
pub fn call(&mut self, function_index: u32, n_args: usize, has_return_val: bool) {
pub fn call(
&mut self,
function_index: u32,
symbol_index: u32,
n_args: usize,
has_return_val: bool,
) {
let stack_depth = self.vm_stack.len();
if n_args > stack_depth {
panic!(
@ -488,8 +524,21 @@ impl<'a> CodeBuilder<'a> {
self.vm_stack.push(Symbol::WASM_ANONYMOUS_STACK_VALUE);
}
self.code.push(CALL);
encode_u32(&mut self.code, function_index);
// Write the index of the function to be called.
// Also make a RelocationEntry so the linker can see that this byte offset relates to a function by name.
// Here we initialise the offset to an index of self.code. After completing the function, we'll add
// other factors to make it relative to the code section. (All insertions will be known then.)
let offset = self.code.len() as u32;
self.code.encode_padded_u32(function_index);
self.relocations.push(RelocationEntry::Index {
type_id: IndexRelocType::FunctionIndexLeb,
offset,
symbol_index,
});
}
#[allow(dead_code)]
fn call_indirect() {
panic!("Not implemented. Roc doesn't use function pointers");
}
@ -545,19 +594,19 @@ impl<'a> CodeBuilder<'a> {
}
pub fn i32_const(&mut self, x: i32) {
self.inst(I32CONST, 0, true);
encode_i32(&mut self.code, x);
self.code.encode_i32(x);
}
pub fn i64_const(&mut self, x: i64) {
self.inst(I64CONST, 0, true);
encode_i64(&mut self.code, x);
self.code.encode_i64(x);
}
pub fn f32_const(&mut self, x: f32) {
self.inst(F32CONST, 0, true);
encode_f32(&mut self.code, x);
self.code.encode_f32(x);
}
pub fn f64_const(&mut self, x: f64) {
self.inst(F64CONST, 0, true);
encode_f64(&mut self.code, x);
self.code.encode_f64(x);
}
// TODO: Consider creating unified methods for numerical ops like 'eq' and 'add',

View File

@ -1,14 +1,12 @@
mod backend;
pub mod code_builder;
pub mod from_wasm32_memory;
mod layout;
pub mod module_builder;
pub mod opcodes;
pub mod serialize;
mod storage;
#[allow(dead_code)]
pub mod code_builder;
#[allow(dead_code)]
mod opcodes;
use bumpalo::{self, collections::Vec, Bump};
use parity_wasm::builder;
@ -20,6 +18,10 @@ use roc_mono::layout::LayoutIds;
use crate::backend::WasmBackend;
use crate::code_builder::{Align, CodeBuilder, ValueType};
use crate::module_builder::{
LinkingSection, LinkingSubSection, RelocationSection, SectionId, SymInfo,
};
use crate::serialize::{SerialBuffer, Serialize};
const PTR_SIZE: u32 = 4;
const PTR_TYPE: ValueType = ValueType::I32;
@ -44,7 +46,7 @@ pub fn build_module<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<std::vec::Vec<u8>, String> {
let (builder, code_section_bytes, _) = build_module_help(env, procedures)?;
let (builder, code_section_bytes) = build_module_help(env, procedures)?;
let mut module = builder.build();
replace_code_section(&mut module, code_section_bytes);
@ -56,26 +58,20 @@ pub fn build_module<'a>(
pub fn build_module_help<'a>(
env: &'a Env,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) -> Result<(builder::ModuleBuilder, std::vec::Vec<u8>, u32), String> {
let mut backend = WasmBackend::new(env, procedures.len());
) -> Result<(builder::ModuleBuilder, std::vec::Vec<u8>), String> {
let proc_symbols = Vec::from_iter_in(procedures.keys().map(|(sym, _)| *sym), env.arena);
let mut backend = WasmBackend::new(env, &proc_symbols);
let mut layout_ids = LayoutIds::default();
let mut symbol_table_entries = Vec::with_capacity_in(procedures.len(), env.arena);
// Sort procedures by occurrence order
//
// We sort by the "name", but those are interned strings, and the name that is
// interned first will have a lower number.
//
// But, the name that occurs first is always `main` because it is in the (implicit)
// file header. Therefore sorting high to low will put other functions before main
//
// This means that for now other functions in the file have to be ordered "in reverse": if A
// uses B, then the name of A must first occur after the first occurrence of the name of B
let mut procedures = Vec::from_iter_in(procedures.into_iter(), env.arena);
procedures.sort_by(|a, b| b.0 .0.cmp(&a.0 .0));
for (i, ((sym, layout), proc)) in procedures.into_iter().enumerate() {
let proc_name = layout_ids
.get(proc.name, &proc.ret_layout)
.to_symbol_string(proc.name, &env.interns);
symbol_table_entries.push(SymInfo::for_function(i as u32, proc_name));
let function_index = backend.build_proc(proc, sym)?;
let mut function_index: u32 = 0;
for ((sym, layout), proc) in procedures {
function_index = backend.build_proc(proc, sym)?;
if env.exposed_to_host.contains(&sym) {
let fn_name = layout_ids
.get_toplevel(sym, &layout)
@ -92,12 +88,42 @@ pub fn build_module_help<'a>(
// Update code section length
let inner_length = (backend.code_section_bytes.len() - 5) as u32;
overwrite_padded_u32(&mut backend.code_section_bytes[0..5], inner_length);
backend
.code_section_bytes
.overwrite_padded_u32(0, inner_length);
// Because of the sorting above, we know the last function in the `for` is the main function.
// Here we grab its index and return it, so that the test_wrapper is able to call it.
// This is a workaround until we implement object files with symbols and relocations.
let main_function_index = function_index;
// linking metadata section
let mut linking_section_bytes = std::vec::Vec::with_capacity(symbol_table_entries.len() * 20);
let linking_section = LinkingSection {
subsections: bumpalo::vec![in env.arena;
LinkingSubSection::SymbolTable(symbol_table_entries)
],
};
linking_section.serialize(&mut linking_section_bytes);
backend.module_builder = backend.module_builder.with_section(Section::Unparsed {
id: SectionId::Custom as u8,
payload: linking_section_bytes,
});
// We always output the code section at the same index relative to other sections, and we need that for relocations.
// TODO: If there's a data section, this will be 6 so we'll need logic for that
// TODO: Build a cleaner solution after we replace parity-wasm with our own module_builder
const CODE_SECTION_INDEX: u32 = 5;
let code_reloc_section = RelocationSection {
name: "reloc.CODE",
target_section_index: CODE_SECTION_INDEX,
entries: &backend.code_relocations,
};
let mut code_reloc_section_bytes = std::vec::Vec::with_capacity(256);
code_reloc_section.serialize(&mut code_reloc_section_bytes);
// Must come after linking section
backend.module_builder = backend.module_builder.with_section(Section::Unparsed {
id: SectionId::Custom as u8,
payload: code_reloc_section_bytes,
});
const MIN_MEMORY_SIZE_KB: u32 = 1024;
const PAGE_SIZE_KB: u32 = 64;
@ -119,24 +145,20 @@ pub fn build_module_help<'a>(
.build();
backend.module_builder.push_global(stack_pointer_global);
Ok((
backend.module_builder,
backend.code_section_bytes,
main_function_index,
))
Ok((backend.module_builder, backend.code_section_bytes))
}
/// Replace parity-wasm's code section with our own handmade one
pub fn replace_code_section(module: &mut Module, code_section_bytes: std::vec::Vec<u8>) {
let sections = module.sections_mut();
let mut code_section_index = usize::MAX;
for (i, s) in sections.iter().enumerate() {
if let Section::Code(_) = s {
code_section_index = i;
}
}
let code_section_index = sections
.iter()
.position(|s| matches!(s, Section::Code(_)))
.unwrap();
sections[code_section_index] = Section::Unparsed {
id: CODE_SECTION_ID,
id: SectionId::Code as u8,
payload: code_section_bytes,
};
}
@ -192,192 +214,3 @@ pub fn round_up_to_alignment(unaligned: i32, alignment_bytes: i32) -> i32 {
pub fn debug_panic<E: std::fmt::Debug>(error: E) {
panic!("{:?}", error);
}
/// Write an unsigned value into the provided buffer in LEB-128 format, returning byte length
///
/// All integers in Wasm are variable-length encoded, which saves space for small values.
/// The most significant bit indicates "more bytes are coming", and the other 7 are payload.
macro_rules! encode_uleb128 {
($name: ident, $ty: ty) => {
pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize {
let mut x = value;
let start_len = buffer.len();
while x >= 0x80 {
buffer.push(0x80 | ((x & 0x7f) as u8));
x >>= 7;
}
buffer.push(x as u8);
buffer.len() - start_len
}
};
}
encode_uleb128!(encode_u32, u32);
encode_uleb128!(encode_u64, u64);
/// Write a *signed* value into the provided buffer in LEB-128 format, returning byte length
macro_rules! encode_sleb128 {
($name: ident, $ty: ty) => {
pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) -> usize {
let mut x = value;
let start_len = buffer.len();
loop {
let byte = (x & 0x7f) as u8;
x >>= 7;
let byte_is_negative = (byte & 0x40) != 0;
if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) {
buffer.push(byte);
break;
}
buffer.push(byte | 0x80);
}
buffer.len() - start_len
}
};
}
encode_sleb128!(encode_i32, i32);
encode_sleb128!(encode_i64, i64);
/// No LEB encoding, and always little-endian regardless of compiler host.
macro_rules! encode_float {
($name: ident, $ty: ty) => {
pub fn $name<'a>(buffer: &mut Vec<'a, u8>, value: $ty) {
let mut x = value.to_bits();
let size = std::mem::size_of::<$ty>();
for _ in 0..size {
buffer.push((x & 0xff) as u8);
x >>= 8;
}
}
};
}
encode_float!(encode_f32, f32);
encode_float!(encode_f64, f64);
/// Overwrite a LEB-128 encoded u32 value, padded to maximum length (5 bytes)
///
/// We need some fixed-length values so we can overwrite them without moving all following bytes.
/// Many parts of the binary format are prefixed with their length, which we only know at the end.
/// And relocation values get updated during linking.
/// This can help us to avoid copies, which is good for speed, but there's a tradeoff with output size.
///
/// The value 3 is encoded as 0x83 0x80 0x80 0x80 0x00.
/// https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections
pub fn overwrite_padded_u32(buffer: &mut [u8], value: u32) {
let mut x = value;
for byte in buffer.iter_mut().take(4) {
*byte = 0x80 | ((x & 0x7f) as u8);
x >>= 7;
}
buffer[4] = x as u8;
}
#[cfg(test)]
mod tests {
use super::*;
use bumpalo::{self, collections::Vec, Bump};
fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(5, arena);
encode_u32(&mut buffer, value);
buffer
}
#[test]
fn test_encode_u32() {
let a = &Bump::new();
assert_eq!(help_u32(a, 0), &[0]);
assert_eq!(help_u32(a, 64), &[64]);
assert_eq!(help_u32(a, 0x7f), &[0x7f]);
assert_eq!(help_u32(a, 0x80), &[0x80, 0x01]);
assert_eq!(help_u32(a, 0x3fff), &[0xff, 0x7f]);
assert_eq!(help_u32(a, 0x4000), &[0x80, 0x80, 0x01]);
assert_eq!(help_u32(a, u32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x0f]);
}
fn help_u64<'a>(arena: &'a Bump, value: u64) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(10, arena);
encode_u64(&mut buffer, value);
buffer
}
#[test]
fn test_encode_u64() {
let a = &Bump::new();
assert_eq!(help_u64(a, 0), &[0]);
assert_eq!(help_u64(a, 64), &[64]);
assert_eq!(help_u64(a, 0x7f), &[0x7f]);
assert_eq!(help_u64(a, 0x80), &[0x80, 0x01]);
assert_eq!(help_u64(a, 0x3fff), &[0xff, 0x7f]);
assert_eq!(help_u64(a, 0x4000), &[0x80, 0x80, 0x01]);
assert_eq!(
help_u64(a, u64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01],
);
}
fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(5, arena);
encode_i32(&mut buffer, value);
buffer
}
#[test]
fn test_encode_i32() {
let a = &Bump::new();
assert_eq!(help_i32(a, 0), &[0]);
assert_eq!(help_i32(a, 1), &[1]);
assert_eq!(help_i32(a, -1), &[0x7f]);
assert_eq!(help_i32(a, 63), &[63]);
assert_eq!(help_i32(a, 64), &[0xc0, 0x0]);
assert_eq!(help_i32(a, -64), &[0x40]);
assert_eq!(help_i32(a, -65), &[0xbf, 0x7f]);
assert_eq!(help_i32(a, i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]);
assert_eq!(help_i32(a, i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]);
}
fn help_i64<'a>(arena: &'a Bump, value: i64) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(10, arena);
encode_i64(&mut buffer, value);
buffer
}
#[test]
fn test_encode_i64() {
let a = &Bump::new();
assert_eq!(help_i64(a, 0), &[0]);
assert_eq!(help_i64(a, 1), &[1]);
assert_eq!(help_i64(a, -1), &[0x7f]);
assert_eq!(help_i64(a, 63), &[63]);
assert_eq!(help_i64(a, 64), &[0xc0, 0x0]);
assert_eq!(help_i64(a, -64), &[0x40]);
assert_eq!(help_i64(a, -65), &[0xbf, 0x7f]);
assert_eq!(
help_i64(a, i64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00],
);
assert_eq!(
help_i64(a, i64::MIN),
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f],
);
}
#[test]
fn test_overwrite_u32_padded() {
let mut buffer = [0, 0, 0, 0, 0];
overwrite_padded_u32(&mut buffer, u32::MAX);
assert_eq!(buffer, [0xff, 0xff, 0xff, 0xff, 0x0f]);
overwrite_padded_u32(&mut buffer, 0);
assert_eq!(buffer, [0x80, 0x80, 0x80, 0x80, 0x00]);
overwrite_padded_u32(&mut buffer, 127);
assert_eq!(buffer, [0xff, 0x80, 0x80, 0x80, 0x00]);
overwrite_padded_u32(&mut buffer, 128);
assert_eq!(buffer, [0x80, 0x81, 0x80, 0x80, 0x00]);
}
}

View File

@ -0,0 +1,499 @@
use bumpalo::collections::vec::Vec;
use crate::code_builder::Align;
use crate::serialize::{SerialBuffer, Serialize};
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum SectionId {
Custom = 0,
Type = 1,
Import = 2,
Function = 3,
Table = 4,
Memory = 5,
Global = 6,
Export = 7,
Start = 8,
Element = 9,
Code = 10,
Data = 11,
DataCount = 12,
}
struct SectionHeaderIndices {
size_index: usize,
body_index: usize,
}
/// Write a section header, returning the position of the encoded length
fn _write_section_header<T: SerialBuffer>(buffer: &mut T, id: SectionId) -> SectionHeaderIndices {
buffer.append_byte(id as u8);
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Write a custom section header, returning the position of the encoded length
fn write_custom_section_header<T: SerialBuffer>(
buffer: &mut T,
name: &str,
) -> SectionHeaderIndices {
// buffer.append_byte(SectionId::Custom as u8); // TODO: uncomment when we get rid of parity_wasm
let size_index = buffer.reserve_padded_u32();
let body_index = buffer.size();
name.serialize(buffer);
SectionHeaderIndices {
size_index,
body_index,
}
}
/// Update a section header with its final size, after writing the bytes
fn update_section_size<T: SerialBuffer>(buffer: &mut T, header_indices: SectionHeaderIndices) {
let size = buffer.size() - header_indices.body_index;
buffer.overwrite_padded_u32(header_indices.size_index, size as u32);
}
fn serialize_vector_with_count<'a, SB, S>(buffer: &mut SB, items: &Vec<'a, S>)
where
SB: SerialBuffer,
S: Serialize,
{
buffer.encode_u32(items.len() as u32);
for item in items.iter() {
item.serialize(buffer);
}
}
/*******************************************************************
*
* Relocation sections
*
* https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#relocation-sections
*
*******************************************************************/
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum IndexRelocType {
/// a function index encoded as a 5-byte [varuint32]. Used for the immediate argument of a `call` instruction.
FunctionIndexLeb = 0,
/// a function table index encoded as a 5-byte [varint32].
/// Used to refer to the immediate argument of a `i32.const` instruction, e.g. taking the address of a function.
TableIndexSleb = 1,
/// a function table index encoded as a [uint32], e.g. taking the address of a function in a static data initializer.
TableIndexI32 = 2,
/// a type index encoded as a 5-byte [varuint32], e.g. the type immediate in a `call_indirect`.
TypeIndexLeb = 6,
/// a global index encoded as a 5-byte [varuint32], e.g. the index immediate in a `get_global`.
GlobalIndexLeb = 7,
/// an event index encoded as a 5-byte [varuint32]. Used for the immediate argument of a `throw` and `if_except` instruction.
EventIndexLeb = 10,
/// a global index encoded as [uint32].
GlobalIndexI32 = 13,
/// the 64-bit counterpart of `R_WASM_TABLE_INDEX_SLEB`. A function table index encoded as a 10-byte [varint64].
/// Used to refer to the immediate argument of a `i64.const` instruction, e.g. taking the address of a function in Wasm64.
TableIndexSleb64 = 18,
/// the 64-bit counterpart of `R_WASM_TABLE_INDEX_I32`.
/// A function table index encoded as a [uint64], e.g. taking the address of a function in a static data initializer.
TableIndexI64 = 19,
/// a table number encoded as a 5-byte [varuint32]. Used for the table immediate argument in the table.* instructions.
TableNumberLeb = 20,
}
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum OffsetRelocType {
/// a linear memory index encoded as a 5-byte [varuint32].
/// Used for the immediate argument of a `load` or `store` instruction, e.g. directly loading from or storing to a C++ global.
MemoryAddrLeb = 3,
/// a linear memory index encoded as a 5-byte [varint32].
/// Used for the immediate argument of a `i32.const` instruction, e.g. taking the address of a C++ global.
MemoryAddrSleb = 4,
/// a linear memory index encoded as a [uint32], e.g. taking the address of a C++ global in a static data initializer.
MemoryAddrI32 = 5,
/// a byte offset within code section for the specific function encoded as a [uint32].
/// The offsets start at the actual function code excluding its size field.
FunctionOffsetI32 = 8,
/// a byte offset from start of the specified section encoded as a [uint32].
SectionOffsetI32 = 9,
/// the 64-bit counterpart of `R_WASM_MEMORY_ADDR_LEB`. A 64-bit linear memory index encoded as a 10-byte [varuint64],
/// Used for the immediate argument of a `load` or `store` instruction on a 64-bit linear memory array.
MemoryAddrLeb64 = 14,
/// the 64-bit counterpart of `R_WASM_MEMORY_ADDR_SLEB`. A 64-bit linear memory index encoded as a 10-byte [varint64].
/// Used for the immediate argument of a `i64.const` instruction.
MemoryAddrSleb64 = 15,
/// the 64-bit counterpart of `R_WASM_MEMORY_ADDR`. A 64-bit linear memory index encoded as a [uint64],
/// e.g. taking the 64-bit address of a C++ global in a static data initializer.
MemoryAddrI64 = 16,
}
#[derive(Debug)]
pub enum RelocationEntry {
Index {
type_id: IndexRelocType,
offset: u32, // offset 0 means the next byte after section id and size
symbol_index: u32, // index in symbol table
},
Offset {
type_id: OffsetRelocType,
offset: u32, // offset 0 means the next byte after section id and size
symbol_index: u32, // index in symbol table
addend: i32, // addend to add to the address
},
}
impl RelocationEntry {
pub fn offset(&self) -> u32 {
match self {
Self::Index { offset, .. } => *offset,
Self::Offset { offset, .. } => *offset,
}
}
pub fn offset_mut(&mut self) -> &mut u32 {
match self {
Self::Index { offset, .. } => offset,
Self::Offset { offset, .. } => offset,
}
}
}
impl RelocationEntry {
pub fn for_function_call(offset: u32, symbol_index: u32) -> Self {
RelocationEntry::Index {
type_id: IndexRelocType::FunctionIndexLeb,
offset,
symbol_index,
}
}
}
impl Serialize for RelocationEntry {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Index {
type_id,
offset,
symbol_index,
} => {
buffer.append_byte(*type_id as u8);
buffer.encode_u32(*offset);
buffer.encode_u32(*symbol_index);
}
Self::Offset {
type_id,
offset,
symbol_index,
addend,
} => {
buffer.append_byte(*type_id as u8);
buffer.encode_u32(*offset);
buffer.encode_u32(*symbol_index);
buffer.encode_i32(*addend);
}
}
}
}
#[derive(Debug)]
pub struct RelocationSection<'a> {
pub name: &'a str,
/// The *index* (not ID!) of the target section in the module
pub target_section_index: u32,
pub entries: &'a Vec<'a, RelocationEntry>,
}
impl<'a> Serialize for RelocationSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
let header_indices = write_custom_section_header(buffer, self.name);
buffer.encode_u32(self.target_section_index);
serialize_vector_with_count(buffer, self.entries);
update_section_size(buffer, header_indices);
}
}
/*******************************************************************
*
* Linking section
*
* https://github.com/WebAssembly/tool-conventions/blob/main/Linking.md#linking-metadata-section
*
*******************************************************************/
/// Linking metadata for data segments
pub struct LinkingSegment {
pub name: String,
pub alignment: Align,
pub flags: u32,
}
impl Serialize for LinkingSegment {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
/// Linking metadata for init (start) functions
pub struct LinkingInitFunc {
pub priority: u32,
pub symbol_index: u32, // index in the symbol table, not the function index
}
impl Serialize for LinkingInitFunc {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
//----------------
//
// Common data
//
//----------------
#[repr(u8)]
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
pub enum ComdatSymKind {
Data = 0,
Function = 1,
Global = 2,
Event = 3,
Table = 4,
Section = 5,
}
pub struct ComdatSym {
pub kind: ComdatSymKind,
pub index: u32,
}
impl Serialize for ComdatSym {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
/// Linking metadata for common data
/// A COMDAT group may contain one or more functions, data segments, and/or custom sections.
/// The linker will include all of these elements with a given group name from one object file,
/// and will exclude any element with this group name from all other object files.
#[allow(dead_code)]
pub struct LinkingComdat<'a> {
name: String,
flags: u32,
syms: Vec<'a, ComdatSym>,
}
impl<'a> Serialize for LinkingComdat<'a> {
fn serialize<T: SerialBuffer>(&self, _buffer: &mut T) {
todo!();
}
}
//----------------
//
// Symbol table
//
//----------------
/// Indicating that this is a weak symbol. When
/// linking multiple modules defining the same symbol, all weak definitions are
/// discarded if any strong definitions exist; then if multiple weak definitions
/// exist all but one (unspecified) are discarded; and finally it is an error if
/// more than one definition remains.
pub const WASM_SYM_BINDING_WEAK: u32 = 1;
/// Indicating that this is a local symbol (this is exclusive with `WASM_SYM_BINDING_WEAK`).
/// Local symbols are not to be exported, or linked to other modules/sections.
/// The names of all non-local symbols must be unique, but the names of local symbols
/// are not considered for uniqueness. A local function or global symbol cannot reference an import.
pub const WASM_SYM_BINDING_LOCAL: u32 = 2;
/// Indicating that this is a hidden symbol.
/// Hidden symbols are not to be exported when performing the final link, but
/// may be linked to other modules.
pub const WASM_SYM_VISIBILITY_HIDDEN: u32 = 4;
/// Indicating that this symbol is not defined.
/// For non-data symbols, this must match whether the symbol is an import
/// or is defined; for data symbols, determines whether a segment is specified.
pub const WASM_SYM_UNDEFINED: u32 = 0x10; // required if the symbol refers to an import
/// The symbol is intended to be exported from the
/// wasm module to the host environment. This differs from the visibility flags
/// in that it effects the static linker.
pub const WASM_SYM_EXPORTED: u32 = 0x20;
/// The symbol uses an explicit symbol name,
/// rather than reusing the name from a wasm import. This allows it to remap
/// imports from foreign WebAssembly modules into local symbols with different
/// names.
pub const WASM_SYM_EXPLICIT_NAME: u32 = 0x40; // use the name from the symbol table, not from the import
/// The symbol is intended to be included in the
/// linker output, regardless of whether it is used by the program.
pub const WASM_SYM_NO_STRIP: u32 = 0x80;
pub enum WasmObjectSymbol {
Defined { index: u32, name: String },
Imported { index: u32 },
}
impl Serialize for WasmObjectSymbol {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Defined { index, name } => {
buffer.encode_u32(*index);
buffer.encode_u32(name.len() as u32);
buffer.append_slice(name.as_bytes());
}
Self::Imported { index } => {
buffer.encode_u32(*index);
}
}
}
}
pub enum DataSymbol {
Defined {
name: String,
index: u32,
offset: u32,
size: u32,
},
Imported {
name: String,
},
}
impl Serialize for DataSymbol {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
match self {
Self::Defined {
name,
index,
offset,
size,
} => {
buffer.encode_u32(name.len() as u32);
buffer.append_slice(name.as_bytes());
buffer.encode_u32(*index);
buffer.encode_u32(*offset);
buffer.encode_u32(*size);
}
Self::Imported { name } => {
buffer.encode_u32(name.len() as u32);
buffer.append_slice(name.as_bytes());
}
}
}
}
/// section index (not section id!)
#[derive(Clone, Copy, Debug)]
pub struct SectionIndex(u32);
pub enum SymInfoFields {
Function(WasmObjectSymbol),
Data(DataSymbol),
Global(WasmObjectSymbol),
Section(SectionIndex),
Event(WasmObjectSymbol),
Table(WasmObjectSymbol),
}
pub struct SymInfo {
flags: u32,
info: SymInfoFields,
}
impl SymInfo {
pub fn for_function(wasm_function_index: u32, name: String) -> Self {
let linking_symbol = WasmObjectSymbol::Defined {
index: wasm_function_index,
name,
};
SymInfo {
flags: 0,
info: SymInfoFields::Function(linking_symbol),
}
}
}
impl Serialize for SymInfo {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_byte(match self.info {
SymInfoFields::Function(_) => 0,
SymInfoFields::Data(_) => 1,
SymInfoFields::Global(_) => 2,
SymInfoFields::Section(_) => 3,
SymInfoFields::Event(_) => 4,
SymInfoFields::Table(_) => 5,
});
buffer.encode_u32(self.flags);
match &self.info {
SymInfoFields::Function(x) => x.serialize(buffer),
SymInfoFields::Data(x) => x.serialize(buffer),
SymInfoFields::Global(x) => x.serialize(buffer),
SymInfoFields::Section(SectionIndex(x)) => {
buffer.encode_u32(*x);
}
SymInfoFields::Event(x) => x.serialize(buffer),
SymInfoFields::Table(x) => x.serialize(buffer),
};
}
}
//--------------------------------
//
// Linking subsections
//
//--------------------------------
pub enum LinkingSubSection<'a> {
/// Extra metadata about the data segments.
SegmentInfo(Vec<'a, LinkingSegment>),
/// Specifies a list of constructor functions to be called at startup.
/// These constructors will be called in priority order after memory has been initialized.
InitFuncs(Vec<'a, LinkingInitFunc>),
/// Specifies the COMDAT groups of associated linking objects, which are linked only once and all together.
ComdatInfo(Vec<'a, LinkingComdat<'a>>),
/// Specifies extra information about the symbols present in the module.
SymbolTable(Vec<'a, SymInfo>),
}
impl<'a> Serialize for LinkingSubSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.append_byte(match self {
Self::SegmentInfo(_) => 5,
Self::InitFuncs(_) => 6,
Self::ComdatInfo(_) => 7,
Self::SymbolTable(_) => 8,
});
let payload_len_index = buffer.reserve_padded_u32();
let payload_start_index = buffer.size();
match self {
Self::SegmentInfo(items) => serialize_vector_with_count(buffer, items),
Self::InitFuncs(items) => serialize_vector_with_count(buffer, items),
Self::ComdatInfo(items) => serialize_vector_with_count(buffer, items),
Self::SymbolTable(items) => serialize_vector_with_count(buffer, items),
}
buffer.overwrite_padded_u32(
payload_len_index,
(buffer.size() - payload_start_index) as u32,
);
}
}
const LINKING_VERSION: u8 = 2;
pub struct LinkingSection<'a> {
pub subsections: Vec<'a, LinkingSubSection<'a>>,
}
impl<'a> Serialize for LinkingSection<'a> {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
let header_indices = write_custom_section_header(buffer, "linking");
buffer.append_byte(LINKING_VERSION);
for subsection in self.subsections.iter() {
subsection.serialize(buffer);
}
update_section_size(buffer, header_indices);
}
}

View File

@ -0,0 +1,350 @@
use bumpalo::collections::vec::Vec;
/// Write an unsigned integer into the provided buffer in LEB-128 format, returning byte length
///
/// All integers in Wasm are variable-length encoded, which saves space for small values.
/// The most significant bit indicates "more bytes are coming", and the other 7 are payload.
macro_rules! encode_uleb128 {
($name: ident, $ty: ty) => {
fn $name(&mut self, value: $ty) -> usize {
let mut x = value;
let start_len = self.size();
while x >= 0x80 {
self.append_byte(0x80 | ((x & 0x7f) as u8));
x >>= 7;
}
self.append_byte(x as u8);
self.size() - start_len
}
};
}
/// Write a signed integer into the provided buffer in LEB-128 format, returning byte length
macro_rules! encode_sleb128 {
($name: ident, $ty: ty) => {
fn $name(&mut self, value: $ty) -> usize {
let mut x = value;
let start_len = self.size();
loop {
let byte = (x & 0x7f) as u8;
x >>= 7;
let byte_is_negative = (byte & 0x40) != 0;
if ((x == 0 && !byte_is_negative) || (x == -1 && byte_is_negative)) {
self.append_byte(byte);
break;
}
self.append_byte(byte | 0x80);
}
self.size() - start_len
}
};
}
macro_rules! write_unencoded {
($name: ident, $ty: ty) => {
/// write an unencoded little-endian integer (only used in relocations)
fn $name(&mut self, value: $ty) {
let mut x = value;
let size = std::mem::size_of::<$ty>();
for _ in 0..size {
self.append_byte((x & 0xff) as u8);
x >>= 8;
}
}
};
}
macro_rules! encode_padded_sleb128 {
($name: ident, $ty: ty) => {
/// write a maximally-padded SLEB128 integer (only used in relocations)
fn $name(&mut self, value: $ty) {
let mut x = value;
let size = (std::mem::size_of::<$ty>() / 4) * 5;
for _ in 0..(size - 1) {
self.append_byte(0x80 | (x & 0x7f) as u8);
x >>= 7;
}
self.append_byte((x & 0x7f) as u8);
}
};
}
pub trait SerialBuffer {
fn append_byte(&mut self, b: u8);
fn append_slice(&mut self, b: &[u8]);
fn size(&self) -> usize;
encode_uleb128!(encode_u32, u32);
encode_uleb128!(encode_u64, u64);
encode_sleb128!(encode_i32, i32);
encode_sleb128!(encode_i64, i64);
fn reserve_padded_u32(&mut self) -> usize;
fn encode_padded_u32(&mut self, value: u32) -> usize;
fn overwrite_padded_u32(&mut self, index: usize, value: u32);
fn encode_f32(&mut self, value: f32) {
self.write_unencoded_u32(value.to_bits());
}
fn encode_f64(&mut self, value: f64) {
self.write_unencoded_u64(value.to_bits());
}
// methods for relocations
write_unencoded!(write_unencoded_u32, u32);
write_unencoded!(write_unencoded_u64, u64);
encode_padded_sleb128!(encode_padded_i32, i32);
encode_padded_sleb128!(encode_padded_i64, i64);
}
pub trait Serialize {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T);
}
impl Serialize for str {
fn serialize<T: SerialBuffer>(&self, buffer: &mut T) {
buffer.encode_u32(self.len() as u32);
buffer.append_slice(self.as_bytes());
}
}
fn overwrite_padded_u32_help(buffer: &mut [u8], value: u32) {
let mut x = value;
for byte in buffer.iter_mut().take(4) {
*byte = 0x80 | ((x & 0x7f) as u8);
x >>= 7;
}
buffer[4] = x as u8;
}
impl SerialBuffer for std::vec::Vec<u8> {
fn append_byte(&mut self, b: u8) {
self.push(b);
}
fn append_slice(&mut self, b: &[u8]) {
self.extend_from_slice(b);
}
fn size(&self) -> usize {
self.len()
}
fn reserve_padded_u32(&mut self) -> usize {
let index = self.len();
self.resize(index + 5, 0xff);
index
}
fn encode_padded_u32(&mut self, value: u32) -> usize {
let index = self.len();
let new_len = index + 5;
self.resize(new_len, 0);
overwrite_padded_u32_help(&mut self[index..new_len], value);
index
}
fn overwrite_padded_u32(&mut self, index: usize, value: u32) {
overwrite_padded_u32_help(&mut self[index..(index + 5)], value);
}
}
impl<'a> SerialBuffer for Vec<'a, u8> {
fn append_byte(&mut self, b: u8) {
self.push(b);
}
fn append_slice(&mut self, b: &[u8]) {
self.extend_from_slice(b);
}
fn size(&self) -> usize {
self.len()
}
fn reserve_padded_u32(&mut self) -> usize {
let index = self.len();
self.resize(index + 5, 0xff);
index
}
fn encode_padded_u32(&mut self, value: u32) -> usize {
let index = self.len();
let new_len = index + 5;
self.resize(new_len, 0);
overwrite_padded_u32_help(&mut self[index..new_len], value);
index
}
fn overwrite_padded_u32(&mut self, index: usize, value: u32) {
overwrite_padded_u32_help(&mut self[index..(index + 5)], value);
}
}
#[cfg(test)]
mod tests {
use super::*;
use bumpalo::{self, collections::Vec, Bump};
fn help_u32<'a>(arena: &'a Bump, value: u32) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(5, arena);
buffer.encode_u32(value);
buffer
}
#[test]
fn test_encode_u32() {
let a = &Bump::new();
assert_eq!(help_u32(a, 0), &[0]);
assert_eq!(help_u32(a, 64), &[64]);
assert_eq!(help_u32(a, 0x7f), &[0x7f]);
assert_eq!(help_u32(a, 0x80), &[0x80, 0x01]);
assert_eq!(help_u32(a, 0x3fff), &[0xff, 0x7f]);
assert_eq!(help_u32(a, 0x4000), &[0x80, 0x80, 0x01]);
assert_eq!(help_u32(a, u32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x0f]);
}
fn help_u64<'a>(arena: &'a Bump, value: u64) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(10, arena);
buffer.encode_u64(value);
buffer
}
#[test]
fn test_encode_u64() {
let a = &Bump::new();
assert_eq!(help_u64(a, 0), &[0]);
assert_eq!(help_u64(a, 64), &[64]);
assert_eq!(help_u64(a, 0x7f), &[0x7f]);
assert_eq!(help_u64(a, 0x80), &[0x80, 0x01]);
assert_eq!(help_u64(a, 0x3fff), &[0xff, 0x7f]);
assert_eq!(help_u64(a, 0x4000), &[0x80, 0x80, 0x01]);
assert_eq!(
help_u64(a, u64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01],
);
}
fn help_i32<'a>(arena: &'a Bump, value: i32) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(5, arena);
buffer.encode_i32(value);
buffer
}
#[test]
fn test_encode_i32() {
let a = &Bump::new();
assert_eq!(help_i32(a, 0), &[0]);
assert_eq!(help_i32(a, 1), &[1]);
assert_eq!(help_i32(a, -1), &[0x7f]);
assert_eq!(help_i32(a, 63), &[63]);
assert_eq!(help_i32(a, 64), &[0xc0, 0x0]);
assert_eq!(help_i32(a, -64), &[0x40]);
assert_eq!(help_i32(a, -65), &[0xbf, 0x7f]);
assert_eq!(help_i32(a, i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]);
assert_eq!(help_i32(a, i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]);
}
fn help_i64<'a>(arena: &'a Bump, value: i64) -> Vec<'a, u8> {
let mut buffer = Vec::with_capacity_in(10, arena);
buffer.encode_i64(value);
buffer
}
#[test]
fn test_encode_i64() {
let a = &Bump::new();
assert_eq!(help_i64(a, 0), &[0]);
assert_eq!(help_i64(a, 1), &[1]);
assert_eq!(help_i64(a, -1), &[0x7f]);
assert_eq!(help_i64(a, 63), &[63]);
assert_eq!(help_i64(a, 64), &[0xc0, 0x0]);
assert_eq!(help_i64(a, -64), &[0x40]);
assert_eq!(help_i64(a, -65), &[0xbf, 0x7f]);
assert_eq!(
help_i64(a, i64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00],
);
assert_eq!(
help_i64(a, i64::MIN),
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f],
);
}
#[test]
fn test_overwrite_u32_padded() {
let mut buffer = [0, 0, 0, 0, 0];
overwrite_padded_u32_help(&mut buffer, u32::MAX);
assert_eq!(buffer, [0xff, 0xff, 0xff, 0xff, 0x0f]);
overwrite_padded_u32_help(&mut buffer, 0);
assert_eq!(buffer, [0x80, 0x80, 0x80, 0x80, 0x00]);
overwrite_padded_u32_help(&mut buffer, 127);
assert_eq!(buffer, [0xff, 0x80, 0x80, 0x80, 0x00]);
overwrite_padded_u32_help(&mut buffer, 128);
assert_eq!(buffer, [0x80, 0x81, 0x80, 0x80, 0x00]);
}
#[test]
fn test_write_unencoded_u32() {
let mut buffer = std::vec::Vec::with_capacity(4);
buffer.write_unencoded_u32(0);
assert_eq!(buffer, &[0, 0, 0, 0]);
buffer.clear();
buffer.write_unencoded_u32(u32::MAX);
assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff]);
}
#[test]
fn test_write_unencoded_u64() {
let mut buffer = std::vec::Vec::with_capacity(8);
buffer.write_unencoded_u64(0);
assert_eq!(buffer, &[0, 0, 0, 0, 0, 0, 0, 0]);
buffer.clear();
buffer.write_unencoded_u64(u64::MAX);
assert_eq!(buffer, &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff]);
}
fn help_pad_i32(val: i32) -> std::vec::Vec<u8> {
let mut buffer = std::vec::Vec::with_capacity(5);
buffer.encode_padded_i32(val);
buffer
}
#[test]
fn test_encode_padded_i32() {
assert_eq!(help_pad_i32(0), &[0x80, 0x80, 0x80, 0x80, 0x00]);
assert_eq!(help_pad_i32(1), &[0x81, 0x80, 0x80, 0x80, 0x00]);
assert_eq!(help_pad_i32(-1), &[0xff, 0xff, 0xff, 0xff, 0x7f]);
assert_eq!(help_pad_i32(i32::MAX), &[0xff, 0xff, 0xff, 0xff, 0x07]);
assert_eq!(help_pad_i32(i32::MIN), &[0x80, 0x80, 0x80, 0x80, 0x78]);
}
fn help_pad_i64(val: i64) -> std::vec::Vec<u8> {
let mut buffer = std::vec::Vec::with_capacity(10);
buffer.encode_padded_i64(val);
buffer
}
#[test]
fn test_encode_padded_i64() {
assert_eq!(
help_pad_i64(0),
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]
);
assert_eq!(
help_pad_i64(1),
&[0x81, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]
);
assert_eq!(
help_pad_i64(-1),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]
);
assert_eq!(
help_pad_i64(i64::MAX),
&[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00],
);
assert_eq!(
help_pad_i64(i64::MIN),
&[0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7f],
);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,916 +0,0 @@
#[macro_use]
extern crate indoc;
#[macro_use]
mod helpers;
#[cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
mod wasm_records {
// #[test]
// fn basic_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { y: 17, x: 15, z: 19 }.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: 17, z: 19 }.y
// "#
// ),
// 17,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: 17, z: 19 }.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
// "#
// ),
// 12,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
// "#
// ),
// 2,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn f64_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.x
// "#
// ),
// 15.1,
// f64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.y
// "#
// ),
// 17.2,
// f64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.z
// "#
// ),
// 19.3,
// f64
// );
// }
// #[test]
// fn fn_record() {
// assert_evals_to!(
// indoc!(
// r#"
// getRec = \x -> { y: 17, x, z: 19 }
// (getRec 15).x
// "#
// ),
// 15,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.y
// "#
// ),
// 17,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z
// "#
// ),
// 19,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z + rec.x
// "#
// ),
// 34,
// i64
// );
// }
// #[test]
// fn def_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17, x: 15, z: 19 }
//
// rec.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
//
// rec.y
// "#
// ),
// 17,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
//
// rec.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn when_on_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2 } is
// { x } -> x + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn when_record_with_guard_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2, y: 3.14 } is
// { x: var } -> var + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn let_with_record_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// { x } = { x: 0x2, y: 3.14 }
//
// x
// "#
// ),
// 2,
// i64
// );
// }
//
// #[test]
// fn record_guard_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2, y: 3.14 } is
// { x: 0x4 } -> 5
// { x } -> x + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn twice_record_access() {
// assert_evals_to!(
// indoc!(
// r#"
// x = {a: 0x2, b: 0x3 }
//
// x.a + x.b
// "#
// ),
// 5,
// i64
// );
// }
// #[test]
// fn empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// v = {}
//
// v
// "#
// ),
// (),
// ()
// );
// }
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3 }
"#
),
3,
i64
);
}
#[test]
fn i64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
(3, 5),
(i64, i64)
);
}
#[test]
fn i64_record3_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 17 }
"#
),
(3, 5, 17),
(i64, i64, i64)
);
}
#[test]
fn f64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1 }
"#
),
(3.1, 5.1),
(f64, f64)
);
}
#[test]
fn f64_record3_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1, z: 17.1 }
"#
),
(3.1, 5.1, 17.1),
(f64, f64, f64)
);
}
#[test]
fn bool_record4_literal() {
assert_evals_to!(
indoc!(
r#"
record : { a : Bool, b : Bool, c : Bool, d : Bool }
record = { a: True, b: False, c : False, d : True }
record
"#
),
[true, false, false, true],
[bool; 4]
);
}
#[test]
fn i64_record9_literal() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
"#
),
[3, 5, 17, 1, 9, 12, 13, 14, 15],
[i64; 9]
);
}
#[test]
fn bool_literal() {
assert_evals_to!(
indoc!(
r#"
x : Bool
x = True
x
"#
),
true,
bool
);
}
// #[test]
// fn optional_field_when_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// main =
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_function_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \{ x ? 10, y } -> x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default_nested() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_singleton_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x : 4 } is
// { x ? 3 } -> x
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn optional_field_empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { } is
// { x ? 3 } -> x
// "#
// ),
// 3,
// i64
// );
// }
#[test]
fn return_record_3() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 4 }
"#
),
(3, 5, 4),
(i64, i64, i64)
);
}
#[test]
fn return_record_4() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2 }
"#
),
[3, 5, 4, 2],
[i64; 4]
);
}
#[test]
fn return_record_5() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1 }
"#
),
[3, 5, 4, 2, 1],
[i64; 5]
);
}
#[test]
fn return_record_6() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
"#
),
[3, 5, 4, 2, 1, 7],
[i64; 6]
);
}
#[test]
fn return_record_7() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
"#
),
[3, 5, 4, 2, 1, 7, 8],
[i64; 7]
);
}
#[test]
fn return_record_float_int() {
assert_evals_to!(
indoc!(
r#"
{ a: 3.14, b: 0x1 }
"#
),
(3.14, 0x1),
(f64, i64)
);
}
#[test]
fn return_record_int_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 0x1, b: 3.14 }
"#
),
(0x1, 3.14),
(i64, f64)
);
}
#[test]
fn return_record_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14 }
"#
),
(6.28, 3.14),
(f64, f64)
);
}
#[test]
fn return_record_float_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14, c: 0.1 }
"#
),
(6.28, 3.14, 0.1),
(f64, f64, f64)
);
}
// #[test]
// fn return_nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
// "#
// ),
// (0x0, (6.28, 3.14, 0.1)),
// (i64, (f64, f64, f64))
// );
// }
// #[test]
// fn accessor() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn accessor_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 }
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn update_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42, bar: 6 }
// { rec & foo: rec.foo + 1 }
// "#
// ),
// (6, 43),
// (i64, i64)
// );
// }
// #[test]
// fn update_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42}
// { rec & foo: rec.foo + 1 }
// "#
// ),
// 43,
// i64
// );
// }
// #[test]
// fn booleans_in_record() {
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
// (true, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
// (false, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
// (true, false),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
// (false, false),
// (bool, bool)
// );
// }
// #[test]
// fn alignment_in_record() {
// assert_evals_to!(
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
// (32i64, true, 2u8),
// (i64, bool, u8)
// );
// }
#[test]
fn stack_memory_return_from_branch() {
// stack memory pointer should end up in the right place after returning from a branch
assert_evals_to!(
indoc!(
r#"
stackMemoryJunk = { x: 999, y: 111 }
if True then
{ x: 123, y: 321 }
else
stackMemoryJunk
"#
),
(123, 321),
(i64, i64)
);
}
// #[test]
// fn blue_and_present() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue, y: 7 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn blue_and_absent() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue }
// "#
// ),
// 3,
// i64
// );
// }
}

View File

@ -1004,7 +1004,7 @@ impl ModuleTiming {
.checked_sub(*read_roc_file)
};
calculate(end_time.duration_since(*start_time)).unwrap_or_else(Duration::default)
calculate(end_time.duration_since(*start_time)).unwrap_or_default()
}
}

View File

@ -16,5 +16,4 @@ snafu = { version = "0.6", features = ["backtraces"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"

View File

@ -33,6 +33,7 @@ pub enum LowLevel {
ListMap,
ListMap2,
ListMap3,
ListMap4,
ListMapWithIndex,
ListKeepIf,
ListWalk,
@ -211,6 +212,7 @@ macro_rules! higher_order {
ListMap
| ListMap2
| ListMap3
| ListMap4
| ListMapWithIndex
| ListKeepIf
| ListWalk
@ -243,6 +245,7 @@ impl LowLevel {
ListMap => 1,
ListMap2 => 2,
ListMap3 => 3,
ListMap4 => 4,
ListMapWithIndex => 1,
ListKeepIf => 1,
ListWalk => 2,

View File

@ -1060,6 +1060,7 @@ define_builtins! {
37 LIST_MIN_LT: "#minlt"
38 LIST_MAX: "max"
39 LIST_MAX_GT: "#maxGt"
40 LIST_MAP4: "map4"
}
5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View File

@ -29,7 +29,6 @@ roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -561,6 +561,84 @@ fn build_tuple_type(builder: &mut impl TypeContext, layouts: &[Layout]) -> Resul
builder.add_tuple_type(&field_types)
}
#[repr(u32)]
#[derive(Clone, Copy)]
enum KeepResult {
Errs = ERR_TAG_ID,
Oks = OK_TAG_ID,
}
impl KeepResult {
fn invert(&self) -> Self {
match self {
KeepResult::Errs => KeepResult::Oks,
KeepResult::Oks => KeepResult::Errs,
}
}
}
#[derive(Clone, Copy)]
enum ResultRepr<'a> {
Int1,
NonRecursive { err: Layout<'a>, ok: Layout<'a> },
}
impl<'a> ResultRepr<'a> {
fn from_layout(layout: &Layout<'a>) -> Self {
match layout {
Layout::Union(UnionLayout::NonRecursive(tags)) => ResultRepr::NonRecursive {
err: tags[ERR_TAG_ID as usize][0],
ok: tags[OK_TAG_ID as usize][0],
},
Layout::Builtin(Builtin::Int1) => ResultRepr::Int1,
other => unreachable!("unexpected layout: {:?}", other),
}
}
fn unwrap(
&self,
builder: &mut FuncDefBuilder,
block: BlockId,
err_or_ok: ValueId,
keep_tag_id: u32,
) -> Result<ValueId> {
match self {
ResultRepr::NonRecursive { .. } => {
let unwrapped = builder.add_unwrap_union(block, err_or_ok, keep_tag_id)?;
builder.add_get_tuple_field(block, unwrapped, 0)
}
ResultRepr::Int1 => builder.add_make_tuple(block, &[]),
}
}
}
fn add_loop(
builder: &mut FuncDefBuilder,
block: BlockId,
state_type: TypeId,
init_state: ValueId,
make_body: impl for<'a> FnOnce(&'a mut FuncDefBuilder, BlockId, ValueId) -> Result<ValueId>,
) -> Result<ValueId> {
let sub_block = builder.add_block();
let (loop_cont, loop_arg) = builder.declare_continuation(sub_block, state_type, state_type)?;
let body = builder.add_block();
let ret_branch = builder.add_block();
let loop_branch = builder.add_block();
let new_state = make_body(builder, loop_branch, loop_arg)?;
let unreachable = builder.add_jump(loop_branch, loop_cont, new_state, state_type)?;
let result = builder.add_choice(
body,
&[
BlockExpr(ret_branch, loop_arg),
BlockExpr(loop_branch, unreachable),
],
)?;
builder.define_continuation(loop_cont, BlockExpr(body, result))?;
let unreachable = builder.add_jump(sub_block, loop_cont, init_state, state_type)?;
builder.add_sub_block(block, BlockExpr(sub_block, unreachable))
}
fn call_spec(
builder: &mut FuncDefBuilder,
env: &Env,
@ -613,6 +691,7 @@ fn call_spec(
HigherOrderLowLevel {
specialization_id,
closure_env_layout,
update_mode,
op,
arg_layouts,
ret_layout,
@ -620,194 +699,405 @@ fn call_spec(
function_env,
..
} => {
use crate::low_level::HigherOrder::*;
let array = 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 name = FuncName(&bytes);
let module = MOD_APP;
use crate::low_level::HigherOrder::*;
let closure_env = env.symbols[function_env];
macro_rules! call_function {
($builder: expr, $block:expr, [$($arg:expr),+ $(,)?]) => {{
let argument = if closure_env_layout.is_none() {
$builder.add_make_tuple($block, &[$($arg),+])?
} else {
$builder.add_make_tuple($block, &[$($arg),+, closure_env])?
};
$builder.add_call($block, spec_var, module, name, argument)?
}};
}
match op {
DictWalk { xs, state } => {
let dict = env.symbols[xs];
let state = env.symbols[state];
let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, dict, DICT_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, dict, DICT_BAG_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let element = builder.add_bag_get(block, bag)?;
let key = builder.add_get_tuple_field(block, first, 0)?;
let val = builder.add_get_tuple_field(block, first, 1)?;
let key = builder.add_get_tuple_field(block, element, 0)?;
let val = builder.add_get_tuple_field(block, element, 1)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[state, key, val])?
} else {
builder.add_make_tuple(block, &[state, key, val, closure_env])?
let new_state = call_function!(builder, block, [state, key, val]);
Ok(new_state)
};
builder.add_call(block, spec_var, module, name, argument)?;
let state_layout = arg_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListWalk { xs, state }
| ListWalkBackwards { xs, state }
| ListWalkUntil { xs, state } => {
ListWalk { xs, state } | ListWalkBackwards { xs, state } => {
let list = env.symbols[xs];
let state = env.symbols[state];
let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let element = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[state, first])?
} else {
builder.add_make_tuple(block, &[state, first, closure_env])?
let new_state = call_function!(builder, block, [state, element]);
Ok(new_state)
};
builder.add_call(block, spec_var, module, name, argument)?;
let state_layout = arg_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListWalkUntil { xs, state } => {
let list = env.symbols[xs];
let state = env.symbols[state];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let continue_or_stop = call_function!(builder, block, [state, element]);
// just assume it is a continue
let unwrapped = builder.add_unwrap_union(block, continue_or_stop, 0)?;
let new_state = builder.add_get_tuple_field(block, unwrapped, 0)?;
Ok(new_state)
};
let state_layout = arg_layouts[0];
let state_type = layout_spec(builder, &state_layout)?;
let init_state = state;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMapWithIndex { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let index = builder.add_make_tuple(block, &[])?;
let element = builder.add_bag_get(block, input_bag)?;
let index = builder.add_make_tuple(block, &[])?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[index, first])?
} else {
builder.add_make_tuple(block, &[index, first, closure_env])?
let new_element = call_function!(builder, block, [index, element]);
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let element = builder.add_bag_get(block, input_bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1])?
} else {
builder.add_make_tuple(block, &[elem1, closure_env])?
let new_element = call_function!(builder, block, [element]);
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListSortWith { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, state, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, state, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let element_1 = builder.add_bag_get(block, bag)?;
let element_2 = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem1])?
} else {
builder.add_make_tuple(block, &[elem1, elem1, closure_env])?
let _ = call_function!(builder, block, [element_1, element_2]);
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])
};
builder.add_call(block, spec_var, module, name, argument)?;
let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap2 { xs, ys } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, closure_env])?
let new_element = call_function!(builder, block, [element_1, element_2]);
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap3 { xs, ys, zs } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let list3 = env.symbols[zs];
let closure_env = env.symbols[function_env];
let bag1 = builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let _cell1 = builder.add_get_tuple_field(block, list1, LIST_CELL_INDEX)?;
let elem1 = builder.add_bag_get(block, bag1)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let input_bag_3 =
builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let bag2 = builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let _cell2 = builder.add_get_tuple_field(block, list2, LIST_CELL_INDEX)?;
let elem2 = builder.add_bag_get(block, bag2)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let element_3 = builder.add_bag_get(block, input_bag_3)?;
let bag3 = builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let _cell3 = builder.add_get_tuple_field(block, list3, LIST_CELL_INDEX)?;
let elem3 = builder.add_bag_get(block, bag3)?;
let new_element =
call_function!(builder, block, [element_1, element_2, element_3]);
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2, elem3])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, elem3, closure_env])?
list_append(builder, block, update_mode_var, state, new_element)
};
builder.add_call(block, spec_var, module, name, argument)?;
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListMap4 { xs, ys, zs, ws } => {
let list1 = env.symbols[xs];
let list2 = env.symbols[ys];
let list3 = env.symbols[zs];
let list4 = env.symbols[ws];
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let input_bag_1 =
builder.add_get_tuple_field(block, list1, LIST_BAG_INDEX)?;
let input_bag_2 =
builder.add_get_tuple_field(block, list2, LIST_BAG_INDEX)?;
let input_bag_3 =
builder.add_get_tuple_field(block, list3, LIST_BAG_INDEX)?;
let input_bag_4 =
builder.add_get_tuple_field(block, list4, LIST_BAG_INDEX)?;
let element_1 = builder.add_bag_get(block, input_bag_1)?;
let element_2 = builder.add_bag_get(block, input_bag_2)?;
let element_3 = builder.add_bag_get(block, input_bag_3)?;
let element_4 = builder.add_bag_get(block, input_bag_4)?;
let new_element = call_function!(
builder,
block,
[element_1, element_2, element_3, element_4]
);
list_append(builder, block, update_mode_var, state, new_element)
};
let output_element_type = layout_spec(builder, ret_layout)?;
let state_layout = Layout::Builtin(Builtin::List(ret_layout));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => {
ListKeepIf { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
// let _cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, state, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, state, LIST_CELL_INDEX)?;
let first = builder.add_bag_get(block, bag)?;
let element = builder.add_bag_get(block, bag)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[first])?
} else {
builder.add_make_tuple(block, &[first, closure_env])?
let _ = call_function!(builder, block, [element]);
// NOTE: we assume the element is not kept
builder.add_update(block, update_mode_var, cell)?;
let removed = builder.add_bag_remove(block, bag)?;
// decrement the removed element
let removed_element = builder.add_get_tuple_field(block, removed, 1)?;
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])
};
let result = builder.add_call(block, spec_var, module, name, argument)?;
let unit = builder.add_tuple_type(&[])?;
builder.add_unknown_with(block, &[result], unit)?;
let state_layout = Layout::Builtin(Builtin::List(&arg_layouts[0]));
let state_type = layout_spec(builder, &state_layout)?;
let init_state = list;
add_loop(builder, block, state_type, init_state, loop_body)
}
ListKeepOks { xs } | ListKeepErrs { xs } => {
let list = env.symbols[xs];
let keep_result = match op {
ListKeepOks { .. } => KeepResult::Oks,
ListKeepErrs { .. } => KeepResult::Errs,
_ => unreachable!(),
};
let result_repr = ResultRepr::from_layout(ret_layout);
let output_element_layout = match (keep_result, result_repr) {
(KeepResult::Errs, ResultRepr::NonRecursive { err, .. }) => err,
(KeepResult::Oks, ResultRepr::NonRecursive { ok, .. }) => ok,
(_, ResultRepr::Int1) => Layout::Struct(&[]),
};
let loop_body = |builder: &mut FuncDefBuilder, block, state| {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let element = builder.add_bag_get(block, bag)?;
let err_or_ok = call_function!(builder, block, [element]);
let kept_branch = builder.add_block();
let not_kept_branch = builder.add_block();
let element_kept = {
let block = kept_branch;
// a Result can be represented as a Int1
let new_element = result_repr.unwrap(
builder,
block,
err_or_ok,
keep_result as u32,
)?;
list_append(builder, block, update_mode_var, state, new_element)?
};
let element_not_kept = {
let block = not_kept_branch;
// a Result can be represented as a Int1
let dropped_element = result_repr.unwrap(
builder,
block,
err_or_ok,
keep_result.invert() as u32,
)?;
// decrement the element we will not keep
builder.add_recursive_touch(block, dropped_element)?;
state
};
builder.add_choice(
block,
&[
BlockExpr(not_kept_branch, element_not_kept),
BlockExpr(kept_branch, element_kept),
],
)
};
let output_element_type = layout_spec(builder, &output_element_layout)?;
let init_state = new_list(builder, block, output_element_type)?;
let state_layout = Layout::Builtin(Builtin::List(&output_element_layout));
let state_type = layout_spec(builder, &state_layout)?;
add_loop(builder, block, state_type, init_state, loop_body)
}
}
// TODO overly pessimstic
// filter_map because one of the arguments is a function name, which
// is not defined in the env
let arguments: Vec<_> = call
.arguments
.iter()
.filter_map(|symbol| env.symbols.get(symbol))
.copied()
.collect();
let result_type = layout_spec(builder, layout)?;
builder.add_unknown_with(block, &arguments, result_type)
}
}
}
fn list_append(
builder: &mut FuncDefBuilder,
block: BlockId,
update_mode_var: UpdateModeVar,
list: ValueId,
to_insert: ValueId,
) -> Result<ValueId> {
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
let new_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])
}
fn lowlevel_spec(
builder: &mut FuncDefBuilder,
env: &Env,
@ -883,6 +1173,10 @@ fn lowlevel_spec(
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
// decrement the overwritten element
let overwritten = builder.add_bag_get(block, bag)?;
let _unit = builder.add_recursive_touch(block, overwritten)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
builder.add_bag_insert(block, bag, to_insert)?;
@ -916,16 +1210,7 @@ fn lowlevel_spec(
let list = env.symbols[&arguments[0]];
let to_insert = env.symbols[&arguments[1]];
let bag = builder.add_get_tuple_field(block, list, LIST_BAG_INDEX)?;
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;
let _unit = builder.add_update(block, update_mode_var, cell)?;
// TODO new heap cell
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])
list_append(builder, block, update_mode_var, list, to_insert)
}
StrToUtf8 => {
let string = env.symbols[&arguments[0]];
@ -1425,8 +1710,8 @@ fn static_list_type<TC: TypeContext>(builder: &mut TC) -> Result<TypeId> {
builder.add_tuple_type(&[cell, bag])
}
// const OK_TAG_ID: u8 = 1u8;
// const ERR_TAG_ID: u8 = 0u8;
const OK_TAG_ID: u32 = 1;
const ERR_TAG_ID: u32 = 0;
const LIST_CELL_INDEX: u32 = 0;
const LIST_BAG_INDEX: u32 = 1;

View File

@ -651,6 +651,21 @@ impl<'a> BorrowInfState<'a> {
self.own_var(*zs);
}
}
ListMap4 { xs, ys, zs, ws } => {
// own the lists if the function wants to own the element
if !function_ps[0].borrow {
self.own_var(*xs);
}
if !function_ps[1].borrow {
self.own_var(*ys);
}
if !function_ps[2].borrow {
self.own_var(*zs);
}
if !function_ps[3].borrow {
self.own_var(*ws);
}
}
ListSortWith { xs } => {
// always own the input list
self.own_var(*xs);
@ -933,6 +948,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
ListMap | ListMapWithIndex => arena.alloc_slice_copy(&[owned, function, closure_data]),
ListMap2 => arena.alloc_slice_copy(&[owned, owned, function, closure_data]),
ListMap3 => arena.alloc_slice_copy(&[owned, owned, owned, function, closure_data]),
ListMap4 => arena.alloc_slice_copy(&[owned, owned, owned, owned, function, closure_data]),
ListKeepIf | ListKeepOks | ListKeepErrs => {
arena.alloc_slice_copy(&[owned, function, closure_data])
}

View File

@ -467,6 +467,7 @@ impl<'a> Context<'a> {
op,
closure_env_layout,
specialization_id,
update_mode,
arg_layouts,
ret_layout,
function_name,
@ -485,6 +486,7 @@ impl<'a> Context<'a> {
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,
@ -576,6 +578,27 @@ impl<'a> Context<'a> {
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMap4 { xs, ys, zs, ws } => {
let borrows = [
function_ps[0].borrow,
function_ps[1].borrow,
function_ps[2].borrow,
function_ps[3].borrow,
FUNCTION,
CLOSURE_DATA,
];
let b = self.add_dec_after_lowlevel(arguments, &borrows, b, b_live_vars);
let b = decref_if_owned!(function_ps[0].borrow, *xs, b);
let b = decref_if_owned!(function_ps[1].borrow, *ys, b);
let b = decref_if_owned!(function_ps[2].borrow, *zs, b);
let b = decref_if_owned!(function_ps[3].borrow, *ws, b);
let v = create_call!(function_ps.get(3));
&*self.arena.alloc(Stmt::Let(z, v, l, b))
}
ListMapWithIndex { xs } => {
let borrows = [function_ps[1].borrow, FUNCTION, CLOSURE_DATA];

View File

@ -1181,6 +1181,10 @@ pub enum CallType<'a> {
/// specialization id of the function argument, used for name generation
specialization_id: CallSpecId,
/// update mode of the higher order lowlevel itself
update_mode: UpdateModeId,
/// function layout, used for name generation
arg_layouts: &'a [Layout<'a>],
ret_layout: Layout<'a>,
@ -4016,11 +4020,12 @@ pub fn with_hole<'a>(
lambda_set,
op,
closure_data_symbol,
|top_level_function, closure_data, closure_env_layout, specialization_id| self::Call {
|(top_level_function, closure_data, closure_env_layout, specialization_id, update_mode)| self::Call {
call_type: CallType::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,
@ -4144,6 +4149,16 @@ pub fn with_hole<'a>(
match_on_closure_argument!(ListMap3, [xs, ys, zs])
}
ListMap4 => {
debug_assert_eq!(arg_symbols.len(), 5);
let xs = arg_symbols[0];
let ys = arg_symbols[1];
let zs = arg_symbols[2];
let ws = arg_symbols[3];
match_on_closure_argument!(ListMap4, [xs, ys, zs, ws])
}
_ => {
let call = self::Call {
call_type: CallType::LowLevel {
@ -6192,7 +6207,7 @@ fn reuse_function_symbol<'a>(
// and closures by unification. Here we record whether this function captures
// anything.
let captures = partial_proc.captured_symbols.captures();
let captured = partial_proc.captured_symbols.clone();
let captured = partial_proc.captured_symbols;
match res_layout {
RawFunctionLayout::Function(_, lambda_set, _) => {
@ -7903,6 +7918,8 @@ pub fn num_argument_to_int_or_float(
}
}
type ToLowLevelCallArguments<'a> = (Symbol, Symbol, Option<Layout<'a>>, CallSpecId, UpdateModeId);
/// Use the lambda set to figure out how to make a lowlevel call
#[allow(clippy::too_many_arguments)]
fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
@ -7916,7 +7933,7 @@ fn lowlevel_match_on_lambda_set<'a, ToLowLevelCall>(
hole: &'a Stmt<'a>,
) -> Stmt<'a>
where
ToLowLevelCall: Fn(Symbol, Symbol, Option<Layout<'a>>, CallSpecId) -> Call<'a> + Copy,
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
match lambda_set.runtime_representation() {
Layout::Union(union_layout) => {
@ -7951,12 +7968,14 @@ where
Layout::Struct(_) => match lambda_set.set.get(0) {
Some((function_symbol, _)) => {
let call_spec_id = env.next_call_specialization_id();
let call = to_lowlevel_call(
let update_mode = env.next_update_mode_id();
let call = to_lowlevel_call((
*function_symbol,
closure_data_symbol,
lambda_set.is_represented(),
call_spec_id,
);
update_mode,
));
build_call(env, call, assigned, return_layout, env.arena.alloc(hole))
}
@ -8021,7 +8040,7 @@ fn lowlevel_union_lambda_set_to_switch<'a, ToLowLevelCall>(
hole: &'a Stmt<'a>,
) -> Stmt<'a>
where
ToLowLevelCall: Fn(Symbol, Symbol, Option<Layout<'a>>, CallSpecId) -> Call<'a> + Copy,
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
debug_assert!(!lambda_set.is_empty());
@ -8035,12 +8054,14 @@ where
let hole = Stmt::Jump(join_point_id, env.arena.alloc([assigned]));
let call_spec_id = env.next_call_specialization_id();
let call = to_lowlevel_call(
let update_mode = env.next_update_mode_id();
let call = to_lowlevel_call((
*function_symbol,
closure_data_symbol,
closure_env_layout,
call_spec_id,
);
update_mode,
));
let stmt = build_call(env, call, assigned, return_layout, env.arena.alloc(hole));
branches.push((i as u64, BranchInfo::None, stmt));
@ -8458,7 +8479,7 @@ fn lowlevel_enum_lambda_set_to_switch<'a, ToLowLevelCall>(
hole: &'a Stmt<'a>,
) -> Stmt<'a>
where
ToLowLevelCall: Fn(Symbol, Symbol, Option<Layout<'a>>, CallSpecId) -> Call<'a> + Copy,
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
debug_assert!(!lambda_set.is_empty());
@ -8472,12 +8493,14 @@ where
let hole = Stmt::Jump(join_point_id, env.arena.alloc([result_symbol]));
let call_spec_id = env.next_call_specialization_id();
let call = to_lowlevel_call(
let update_mode = env.next_update_mode_id();
let call = to_lowlevel_call((
*function_symbol,
closure_data_symbol,
closure_env_layout,
call_spec_id,
);
update_mode,
));
let stmt = build_call(
env,
call,

View File

@ -2,18 +2,55 @@ use roc_module::symbol::Symbol;
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum HigherOrder {
ListMap { xs: Symbol },
ListMap2 { xs: Symbol, ys: Symbol },
ListMap3 { xs: Symbol, ys: Symbol, zs: Symbol },
ListMapWithIndex { xs: Symbol },
ListKeepIf { xs: Symbol },
ListWalk { xs: Symbol, state: Symbol },
ListWalkUntil { xs: Symbol, state: Symbol },
ListWalkBackwards { xs: Symbol, state: Symbol },
ListKeepOks { xs: Symbol },
ListKeepErrs { xs: Symbol },
ListSortWith { xs: Symbol },
DictWalk { xs: Symbol, state: Symbol },
ListMap {
xs: Symbol,
},
ListMap2 {
xs: Symbol,
ys: Symbol,
},
ListMap3 {
xs: Symbol,
ys: Symbol,
zs: Symbol,
},
ListMap4 {
xs: Symbol,
ys: Symbol,
zs: Symbol,
ws: Symbol,
},
ListMapWithIndex {
xs: Symbol,
},
ListKeepIf {
xs: Symbol,
},
ListWalk {
xs: Symbol,
state: Symbol,
},
ListWalkUntil {
xs: Symbol,
state: Symbol,
},
ListWalkBackwards {
xs: Symbol,
state: Symbol,
},
ListKeepOks {
xs: Symbol,
},
ListKeepErrs {
xs: Symbol,
},
ListSortWith {
xs: Symbol,
},
DictWalk {
xs: Symbol,
state: Symbol,
},
}
impl HigherOrder {
@ -22,6 +59,7 @@ impl HigherOrder {
HigherOrder::ListMap { .. } => 1,
HigherOrder::ListMap2 { .. } => 2,
HigherOrder::ListMap3 { .. } => 3,
HigherOrder::ListMap4 { .. } => 4,
HigherOrder::ListMapWithIndex { .. } => 2,
HigherOrder::ListKeepIf { .. } => 1,
HigherOrder::ListWalk { .. } => 2,
@ -128,202 +166,3 @@ enum FirstOrder {
Hash,
ExpectTrue,
}
/*
enum FirstOrHigher {
First(FirstOrder),
Higher(HigherOrder),
}
fn from_low_level(low_level: &LowLevel, arguments: &[Symbol]) -> FirstOrHigher {
use FirstOrHigher::*;
use FirstOrder::*;
use HigherOrder::*;
match low_level {
LowLevel::StrConcat => First(StrConcat),
LowLevel::StrJoinWith => First(StrJoinWith),
LowLevel::StrIsEmpty => First(StrIsEmpty),
LowLevel::StrStartsWith => First(StrStartsWith),
LowLevel::StrStartsWithCodePt => First(StrStartsWithCodePt),
LowLevel::StrEndsWith => First(StrEndsWith),
LowLevel::StrSplit => First(StrSplit),
LowLevel::StrCountGraphemes => First(StrCountGraphemes),
LowLevel::StrFromInt => First(StrFromInt),
LowLevel::StrFromUtf8 => First(StrFromUtf8),
LowLevel::StrFromUtf8Range => First(StrFromUtf8Range),
LowLevel::StrToUtf8 => First(StrToUtf8),
LowLevel::StrRepeat => First(StrRepeat),
LowLevel::StrFromFloat => First(StrFromFloat),
LowLevel::ListLen => First(ListLen),
LowLevel::ListGetUnsafe => First(ListGetUnsafe),
LowLevel::ListSet => First(ListSet),
LowLevel::ListDrop => First(ListDrop),
LowLevel::ListDropAt => First(ListDropAt),
LowLevel::ListSingle => First(ListSingle),
LowLevel::ListRepeat => First(ListRepeat),
LowLevel::ListReverse => First(ListReverse),
LowLevel::ListConcat => First(ListConcat),
LowLevel::ListContains => First(ListContains),
LowLevel::ListAppend => First(ListAppend),
LowLevel::ListPrepend => First(ListPrepend),
LowLevel::ListJoin => First(ListJoin),
LowLevel::ListRange => First(ListRange),
LowLevel::ListSwap => First(ListSwap),
LowLevel::DictSize => First(DictSize),
LowLevel::DictEmpty => First(DictEmpty),
LowLevel::DictInsert => First(DictInsert),
LowLevel::DictRemove => First(DictRemove),
LowLevel::DictContains => First(DictContains),
LowLevel::DictGetUnsafe => First(DictGetUnsafe),
LowLevel::DictKeys => First(DictKeys),
LowLevel::DictValues => First(DictValues),
LowLevel::DictUnion => First(DictUnion),
LowLevel::DictIntersection => First(DictIntersection),
LowLevel::DictDifference => First(DictDifference),
LowLevel::SetFromList => First(SetFromList),
LowLevel::NumAdd => First(NumAdd),
LowLevel::NumAddWrap => First(NumAddWrap),
LowLevel::NumAddChecked => First(NumAddChecked),
LowLevel::NumSub => First(NumSub),
LowLevel::NumSubWrap => First(NumSubWrap),
LowLevel::NumSubChecked => First(NumSubChecked),
LowLevel::NumMul => First(NumMul),
LowLevel::NumMulWrap => First(NumMulWrap),
LowLevel::NumMulChecked => First(NumMulChecked),
LowLevel::NumGt => First(NumGt),
LowLevel::NumGte => First(NumGte),
LowLevel::NumLt => First(NumLt),
LowLevel::NumLte => First(NumLte),
LowLevel::NumCompare => First(NumCompare),
LowLevel::NumDivUnchecked => First(NumDivUnchecked),
LowLevel::NumRemUnchecked => First(NumRemUnchecked),
LowLevel::NumIsMultipleOf => First(NumIsMultipleOf),
LowLevel::NumAbs => First(NumAbs),
LowLevel::NumNeg => First(NumNeg),
LowLevel::NumSin => First(NumSin),
LowLevel::NumCos => First(NumCos),
LowLevel::NumSqrtUnchecked => First(NumSqrtUnchecked),
LowLevel::NumLogUnchecked => First(NumLogUnchecked),
LowLevel::NumRound => First(NumRound),
LowLevel::NumToFloat => First(NumToFloat),
LowLevel::NumPow => First(NumPow),
LowLevel::NumCeiling => First(NumCeiling),
LowLevel::NumPowInt => First(NumPowInt),
LowLevel::NumFloor => First(NumFloor),
LowLevel::NumIsFinite => First(NumIsFinite),
LowLevel::NumAtan => First(NumAtan),
LowLevel::NumAcos => First(NumAcos),
LowLevel::NumAsin => First(NumAsin),
LowLevel::NumBitwiseAnd => First(NumBitwiseAnd),
LowLevel::NumBitwiseXor => First(NumBitwiseXor),
LowLevel::NumBitwiseOr => First(NumBitwiseOr),
LowLevel::NumShiftLeftBy => First(NumShiftLeftBy),
LowLevel::NumShiftRightBy => First(NumShiftRightBy),
LowLevel::NumBytesToU16 => First(NumBytesToU16),
LowLevel::NumBytesToU32 => First(NumBytesToU32),
LowLevel::NumShiftRightZfBy => First(NumShiftRightZfBy),
LowLevel::NumIntCast => First(NumIntCast),
LowLevel::Eq => First(Eq),
LowLevel::NotEq => First(NotEq),
LowLevel::And => First(And),
LowLevel::Or => First(Or),
LowLevel::Not => First(Not),
LowLevel::Hash => First(Hash),
LowLevel::ExpectTrue => First(ExpectTrue),
LowLevel::ListMap => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListMap {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListMap2 => {
debug_assert_eq!(arguments.len(), 4);
Higher(ListMap2 {
xs: arguments[0],
ys: arguments[1],
function_name: arguments[2],
function_env: arguments[3],
})
}
LowLevel::ListMap3 => {
debug_assert_eq!(arguments.len(), 5);
Higher(ListMap3 {
xs: arguments[0],
ys: arguments[1],
zs: arguments[2],
function_name: arguments[3],
function_env: arguments[4],
})
}
LowLevel::ListMapWithIndex => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListMapWithIndex {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListKeepIf => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListKeepIf {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListWalk => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListWalk {
xs: arguments[0],
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListWalkUntil => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListWalkUntil {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListWalkBackwards => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListWalkBackwards {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListKeepOks => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListKeepOks {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListKeepErrs => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListKeepErrs {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::ListSortWith => {
debug_assert_eq!(arguments.len(), 3);
Higher(ListSortWith {
function_name: arguments[1],
function_env: arguments[2],
})
}
LowLevel::DictWalk => {
debug_assert_eq!(arguments.len(), 3);
Higher(DictWalk {
function_name: arguments[1],
function_env: arguments[2],
})
}
}
}
*/

View File

@ -1,9 +1,7 @@
use crate::ast::CommentOrNewline;
use crate::ast::Spaceable;
use crate::parser::{
self, and, backtrackable, BadInputError, Col, Parser,
Progress::{self, *},
Row, State,
self, and, backtrackable, BadInputError, Col, Parser, Progress::*, Row, State,
};
use bumpalo::collections::vec::Vec;
use bumpalo::Bump;
@ -181,109 +179,6 @@ where
spaces_help_help(min_indent, space_problem, indent_problem)
}
pub fn spaces_till_end_of_line<'a, E: 'a>(
tab_problem: fn(Row, Col) -> E,
) -> impl Parser<'a, Option<&'a str>, E> {
move |_, mut state: State<'a>| {
let mut bytes = state.bytes;
let mut row = state.line;
let mut col = state.column;
for c in bytes {
match c {
b' ' => {
bytes = &bytes[1..];
col += 1;
}
b'\n' => {
bytes = &bytes[1..];
row += 1;
col = 0;
state.line = row;
state.column = col;
state.bytes = bytes;
return Ok((MadeProgress, None, state));
}
b'\r' => {
bytes = &bytes[1..];
}
b'\t' => {
return Err((
MadeProgress,
tab_problem(row, col),
State {
line: row,
column: col,
..state
},
))
}
b'#' => match chomp_line_comment(bytes) {
Ok(comment) => {
state.line += 1;
state.column = 0;
let width = 1 + comment.len();
if let Some(b'\n') = bytes.get(width) {
state.bytes = &bytes[width + 1..];
} else {
state.bytes = &bytes[width..];
}
return Ok((MadeProgress, Some(comment), state));
}
Err(_) => unreachable!("we check the first character is a #"),
},
_ => break,
}
}
if state.column == col {
Ok((NoProgress, None, state))
} else {
Ok((
MadeProgress,
None,
State {
column: col,
bytes,
..state
},
))
}
}
}
fn chomp_line_comment(buffer: &[u8]) -> Result<&str, Progress> {
if let Some(b'#') = buffer.get(0) {
if (&buffer[1..]).starts_with(b"# ") {
// this is a doc comment, not a line comment
Err(NoProgress)
} else {
use encode_unicode::CharExt;
let mut chomped = 1;
while let Ok((ch, width)) = char::from_utf8_slice_start(&buffer[chomped..]) {
if ch == '\n' {
break;
} else {
chomped += width;
}
}
let comment_bytes = &buffer[1..chomped];
let comment = unsafe { std::str::from_utf8_unchecked(comment_bytes) };
Ok(comment)
}
} else {
Err(NoProgress)
}
}
#[inline(always)]
fn spaces_help_help<'a, E>(
min_indent: u16,

View File

@ -13,7 +13,6 @@ roc_parse = { path = "../parse" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -27,7 +27,6 @@ roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -20,7 +20,6 @@ roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
tempfile = "3.1.0"
quickcheck = "0.8"

View File

@ -21,7 +21,6 @@ roc_builtins = { path = "../builtins" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -28,7 +28,6 @@ im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
either = "1.6.1"
indoc = "0.3.3"
libc = "0.2"
inkwell = { path = "../../vendor/inkwell" }
target-lexicon = "0.12.2"
@ -38,11 +37,11 @@ wasmer-wasi = "2.0.0"
tempfile = "3.1.0"
[dev-dependencies]
maplit = "1.0.1"
quickcheck = "0.8"
quickcheck_macros = "0.8"
tokio = { version = "0.2", features = ["blocking", "fs", "sync", "rt-threaded"] }
bumpalo = { version = "3.6.1", features = ["collections"] }
indoc = "0.3.3"
[features]
default = []

View File

@ -31,9 +31,9 @@ pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
use roc_gen_llvm::llvm::build::PanicTagId;
use libc::c_char;
use std::convert::TryFrom;
use std::ffi::CStr;
use std::os::raw::c_char;
match PanicTagId::try_from(tag_id) {
Ok(PanicTagId::NullTerminatedString) => {
@ -763,6 +763,37 @@ fn list_map_closure() {
);
}
#[test]
fn list_map4_group() {
assert_evals_to!(
indoc!(
r#"
List.map4 [1,2,3] [3,2,1] [2,1,3] [3,1,2] (\a, b, c, d -> Group a b c d)
"#
),
RocList::from_slice(&[(1, 3, 2, 3), (2, 2, 1, 1), (3, 1, 3, 2)]),
RocList<(i64, i64, i64, i64)>
);
}
#[test]
fn list_map4_different_length() {
assert_evals_to!(
indoc!(
r#"
List.map4
["h", "i", "j", "k"]
["o", "p", "q"]
["l", "m"]
["a"]
(\a, b, c, d -> Str.concat a (Str.concat b (Str.concat c d)))
"#
),
RocList::from_slice(&[RocStr::from_slice("hola".as_bytes()),]),
RocList<RocStr>
);
}
#[test]
fn list_map3_group() {
assert_evals_to!(

View File

@ -434,8 +434,8 @@ fn wasm_roc_panic(address: u32, tag_id: u32) {
let width = 100;
let c_ptr = (ptr.deref(memory, 0, width)).unwrap();
use libc::c_char;
use std::ffi::CStr;
use std::os::raw::c_char;
let slice = unsafe { CStr::from_ptr(c_ptr as *const _ as *const c_char) };
string = slice.to_str().unwrap();
});

View File

@ -5,35 +5,16 @@ authors = ["The Roc Contributors"]
license = "UPL-1.0"
edition = "2018"
[dependencies]
[dev-dependencies]
roc_collections = { path = "../collections" }
roc_region = { path = "../region" }
roc_module = { path = "../module" }
roc_problem = { path = "../problem" }
roc_types = { path = "../types" }
roc_builtins = { path = "../builtins" }
roc_constrain = { path = "../constrain" }
roc_unify = { path = "../unify" }
roc_solve = { path = "../solve" }
roc_reporting = { path = "../reporting" }
roc_load = { path = "../load" }
roc_can = { path = "../can" }
roc_parse = { path = "../parse" }
roc_build = { path = "../build" }
roc_mono = { path = "../mono" }
test_mono_macros = { path = "../test_mono_macros" }
im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version!
bumpalo = { version = "3.6.1", features = ["collections"] }
either = "1.6.1"
indoc = "0.3.3"
libc = "0.2"
target-lexicon = "0.12.2"
libloading = "0.6"
[dev-dependencies]
pretty_assertions = "0.5.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"
bumpalo = { version = "3.6.1", features = ["collections"] }
indoc = "0.3.3"

View File

@ -11,5 +11,4 @@ proc-macro = true
[dependencies]
syn = { version = "1.0.39", features = ["full", "extra-traits"] }
quote = "1.0.7"
darling = "0.10.2"
proc-macro2 = "1.0.24"

View File

@ -0,0 +1,31 @@
[package]
name = "test_wasm"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
# roc_module = { path = "../module" }
# roc_mono = { path = "../mono" }
# # TODO: switch to parity-wasm 0.44 once it's out (allows bumpalo vectors in some places)
parity-wasm = { git = "https://github.com/brian-carroll/parity-wasm", branch = "master" }
wasmer = "2.0.0"
wasmer-wasi = "2.0.0"
roc_collections = { path = "../collections" }
roc_std = { path = "../../roc_std" }
bumpalo = { version = "3.6.1", features = ["collections"] }
roc_gen_wasm = { path = "../gen_wasm" }
roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" }
roc_load = { path = "../load" }
roc_types = { path = "../types" }
roc_module = { path = "../module" }
indoc = "0.3.3"
pretty_assertions = "0.5.1"
libc = "0.2"
target-lexicon = "0.12.2"
tempfile = "3.1.0"

View File

@ -67,18 +67,12 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
use roc_load::file::MonomorphizedModule;
let MonomorphizedModule {
procedures: top_procedures,
procedures,
interns,
exposed_to_host,
..
} = loaded;
let mut procedures = MutMap::default();
for (key, proc) in top_procedures {
procedures.insert(key, proc);
}
// You can comment and uncomment this block out to get more useful information
// while you're working on the wasm backend!
// {
@ -95,6 +89,13 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
// println!("=================================\n");
// }
debug_assert_eq!(exposed_to_host.len(), 1);
let main_fn_symbol = loaded.entry_point.symbol;
let main_fn_index = procedures
.keys()
.position(|(s, _)| *s == main_fn_symbol)
.unwrap();
let exposed_to_host = exposed_to_host.keys().copied().collect::<MutSet<_>>();
let env = roc_gen_wasm::Env {
@ -103,7 +104,7 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
exposed_to_host,
};
let (mut builder, mut code_section_bytes, main_function_index) =
let (mut builder, mut code_section_bytes) =
roc_gen_wasm::build_module_help(&env, procedures).unwrap();
T::insert_test_wrapper(
@ -111,14 +112,14 @@ pub fn helper_wasm<'a, T: Wasm32TestResult>(
&mut builder,
&mut code_section_bytes,
TEST_WRAPPER_NAME,
main_function_index,
main_fn_index as u32,
);
let mut parity_module = builder.build();
replace_code_section(&mut parity_module, code_section_bytes);
let module_bytes = parity_module.into_bytes().unwrap();
// for debugging (e.g. with wasm2wat)
// for debugging (e.g. with wasm2wat or wasm-objdump)
if false {
use std::io::Write;

View File

@ -1,5 +1,3 @@
extern crate bumpalo;
#[macro_use]
pub mod eval;
pub mod wasm32_test_result;

View File

@ -3,7 +3,7 @@ use parity_wasm::elements::Internal;
use roc_gen_wasm::code_builder::{Align, CodeBuilder, ValueType};
use roc_gen_wasm::from_wasm32_memory::FromWasm32Memory;
use roc_gen_wasm::{overwrite_padded_u32, LocalId};
use roc_gen_wasm::{serialize::SerialBuffer, LocalId};
use roc_std::{RocDec, RocList, RocOrder, RocStr};
pub trait Wasm32TestResult {
@ -30,15 +30,15 @@ pub trait Wasm32TestResult {
let mut code_builder = CodeBuilder::new(arena);
Self::build_wrapper_body(&mut code_builder, main_function_index);
code_builder.serialize(code_section_bytes).unwrap();
code_builder.serialize(code_section_bytes);
let mut num_procs = 0;
for (i, byte) in code_section_bytes[5..10].iter().enumerate() {
num_procs += ((byte & 0x7f) as u32) << (i * 7);
}
let inner_length = (code_section_bytes.len() - 5) as u32;
overwrite_padded_u32(&mut code_section_bytes[0..5], inner_length);
overwrite_padded_u32(&mut code_section_bytes[5..10], num_procs + 1);
code_section_bytes.overwrite_padded_u32(0, inner_length);
code_section_bytes.overwrite_padded_u32(5, num_procs + 1);
}
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32);
@ -53,7 +53,8 @@ macro_rules! build_wrapper_body_primitive {
let frame_size = 8;
code_builder.get_local(frame_pointer_id);
code_builder.call(main_function_index, 0, true);
// Raw "call" instruction. Don't bother with symbol & relocation since we're not going to link.
code_builder.inst_imm32(roc_gen_wasm::opcodes::CALL, 0, true, main_function_index);
code_builder.$store_instruction($align, 0);
code_builder.get_local(frame_pointer_id);
@ -80,7 +81,8 @@ fn build_wrapper_body_stack_memory(
let frame_pointer = Some(local_id);
code_builder.get_local(local_id);
code_builder.call(main_function_index, 0, true);
// Raw "call" instruction. Don't bother with symbol & relocation since we're not going to link.
code_builder.inst_imm32(roc_gen_wasm::opcodes::CALL, 0, true, main_function_index);
code_builder.get_local(local_id);
code_builder.finalize(local_types, size as i32, frame_pointer);
}

View File

@ -0,0 +1,3 @@
mod helpers;
pub mod wasm_num;
pub mod wasm_records;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,911 @@
#![cfg(all(test, target_os = "linux", any(target_arch = "x86_64"/*, target_arch = "aarch64"*/)))]
use crate::assert_evals_to;
use indoc::indoc;
// #[test]
// fn basic_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { y: 17, x: 15, z: 19 }.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: 17, z: 19 }.y
// "#
// ),
// 17,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: 17, z: 19 }.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.a
// "#
// ),
// 12,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.b
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.y.c
// "#
// ),
// 2,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// { x: 15, y: { a: 12, b: 15, c: 2}, z: 19 }.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn f64_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.x
// "#
// ),
// 15.1,
// f64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.y
// "#
// ),
// 17.2,
// f64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17.2, x: 15.1, z: 19.3 }
//
// rec.z
// "#
// ),
// 19.3,
// f64
// );
// }
// #[test]
// fn fn_record() {
// assert_evals_to!(
// indoc!(
// r#"
// getRec = \x -> { y: 17, x, z: 19 }
// (getRec 15).x
// "#
// ),
// 15,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.y
// "#
// ),
// 17,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z
// "#
// ),
// 19,
// i64
// );
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
// rec.z + rec.x
// "#
// ),
// 34,
// i64
// );
// }
// #[test]
// fn def_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { y: 17, x: 15, z: 19 }
//
// rec.x
// "#
// ),
// 15,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
//
// rec.y
// "#
// ),
// 17,
// i64
// );
//
// assert_evals_to!(
// indoc!(
// r#"
// rec = { x: 15, y: 17, z: 19 }
//
// rec.z
// "#
// ),
// 19,
// i64
// );
// }
//
// #[test]
// fn when_on_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2 } is
// { x } -> x + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn when_record_with_guard_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2, y: 3.14 } is
// { x: var } -> var + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn let_with_record_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// { x } = { x: 0x2, y: 3.14 }
//
// x
// "#
// ),
// 2,
// i64
// );
// }
//
// #[test]
// fn record_guard_pattern() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x: 0x2, y: 3.14 } is
// { x: 0x4 } -> 5
// { x } -> x + 3
// "#
// ),
// 5,
// i64
// );
// }
//
// #[test]
// fn twice_record_access() {
// assert_evals_to!(
// indoc!(
// r#"
// x = {a: 0x2, b: 0x3 }
//
// x.a + x.b
// "#
// ),
// 5,
// i64
// );
// }
// #[test]
// fn empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// v = {}
//
// v
// "#
// ),
// (),
// ()
// );
// }
#[test]
fn i64_record1_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3 }
"#
),
3,
i64
);
}
#[test]
fn i64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5 }
"#
),
(3, 5),
(i64, i64)
);
}
#[test]
fn i64_record3_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 17 }
"#
),
(3, 5, 17),
(i64, i64, i64)
);
}
#[test]
fn f64_record2_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1 }
"#
),
(3.1, 5.1),
(f64, f64)
);
}
#[test]
fn f64_record3_literal() {
assert_evals_to!(
indoc!(
r#"
{ x: 3.1, y: 5.1, z: 17.1 }
"#
),
(3.1, 5.1, 17.1),
(f64, f64, f64)
);
}
#[test]
fn bool_record4_literal() {
assert_evals_to!(
indoc!(
r#"
record : { a : Bool, b : Bool, c : Bool, d : Bool }
record = { a: True, b: False, c : False, d : True }
record
"#
),
[true, false, false, true],
[bool; 4]
);
}
#[test]
fn i64_record9_literal() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 17, d: 1, e: 9, f: 12, g: 13, h: 14, i: 15 }
"#
),
[3, 5, 17, 1, 9, 12, 13, 14, 15],
[i64; 9]
);
}
#[test]
fn bool_literal() {
assert_evals_to!(
indoc!(
r#"
x : Bool
x = True
x
"#
),
true,
bool
);
}
// #[test]
// fn optional_field_when_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// main =
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// a = f { x: Blue, y: 7 }
// b = f { x: Blue }
// c = f { x: Red, y: 11 }
// d = f { x: Red }
// a * b * c * d
// "#
// ),
// 3 * 5 * 7 * 11,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_when_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \r ->
// { x ? 10, y } = r
// x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_let_no_use_default_nested() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// { x ? 10, y } = r
// x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_function_use_default() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { y: 9 }
// "#
// ),
// 19,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// app "test" provides [ main ] to "./platform"
// f = \{ x ? 10, y } -> x + y
// main =
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// #[ignore]
// fn optional_field_function_no_use_default_nested() {
// // blocked on https://github.com/rtfeldman/roc/issues/786
// assert_evals_to!(
// indoc!(
// r#"
// f = \{ x ? 10, y } -> x + y
// f { x: 4, y: 9 }
// "#
// ),
// 13,
// i64
// );
// }
// #[test]
// fn optional_field_singleton_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { x : 4 } is
// { x ? 3 } -> x
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn optional_field_empty_record() {
// assert_evals_to!(
// indoc!(
// r#"
// when { } is
// { x ? 3 } -> x
// "#
// ),
// 3,
// i64
// );
// }
#[test]
fn return_record_3() {
assert_evals_to!(
indoc!(
r#"
{ x: 3, y: 5, z: 4 }
"#
),
(3, 5, 4),
(i64, i64, i64)
);
}
#[test]
fn return_record_4() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2 }
"#
),
[3, 5, 4, 2],
[i64; 4]
);
}
#[test]
fn return_record_5() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1 }
"#
),
[3, 5, 4, 2, 1],
[i64; 5]
);
}
#[test]
fn return_record_6() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7 }
"#
),
[3, 5, 4, 2, 1, 7],
[i64; 6]
);
}
#[test]
fn return_record_7() {
assert_evals_to!(
indoc!(
r#"
{ a: 3, b: 5, c: 4, d: 2, e: 1, f: 7, g: 8 }
"#
),
[3, 5, 4, 2, 1, 7, 8],
[i64; 7]
);
}
#[test]
fn return_record_float_int() {
assert_evals_to!(
indoc!(
r#"
{ a: 3.14, b: 0x1 }
"#
),
(3.14, 0x1),
(f64, i64)
);
}
#[test]
fn return_record_int_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 0x1, b: 3.14 }
"#
),
(0x1, 3.14),
(i64, f64)
);
}
#[test]
fn return_record_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14 }
"#
),
(6.28, 3.14),
(f64, f64)
);
}
#[test]
fn return_record_float_float_float() {
assert_evals_to!(
indoc!(
r#"
{ a: 6.28, b: 3.14, c: 0.1 }
"#
),
(6.28, 3.14, 0.1),
(f64, f64, f64)
);
}
// #[test]
// fn return_nested_record() {
// assert_evals_to!(
// indoc!(
// r#"
// { flag: 0x0, payload: { a: 6.28, b: 3.14, c: 0.1 } }
// "#
// ),
// (0x0, (6.28, 3.14, 0.1)),
// (i64, (f64, f64, f64))
// );
// }
// #[test]
// fn accessor() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 } + .foo { bar: 6.28, foo: 3 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn accessor_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// .foo { foo: 4 }
// "#
// ),
// 4,
// i64
// );
// }
// #[test]
// fn update_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42, bar: 6 }
// { rec & foo: rec.foo + 1 }
// "#
// ),
// (6, 43),
// (i64, i64)
// );
// }
// #[test]
// fn update_single_element_record() {
// assert_evals_to!(
// indoc!(
// r#"
// rec = { foo: 42}
// { rec & foo: rec.foo + 1 }
// "#
// ),
// 43,
// i64
// );
// }
// #[test]
// fn booleans_in_record() {
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 == 1 }"),
// (true, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 == 1 }"),
// (false, true),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 == 1, y: 1 != 1 }"),
// (true, false),
// (bool, bool)
// );
// assert_evals_to!(
// indoc!("{ x: 1 != 1, y: 1 != 1 }"),
// (false, false),
// (bool, bool)
// );
// }
// #[test]
// fn alignment_in_record() {
// assert_evals_to!(
// indoc!("{ c: 32, b: if True then Red else if True then Green else Blue, a: 1 == 1 }"),
// (32i64, true, 2u8),
// (i64, bool, u8)
// );
// }
#[test]
fn stack_memory_return_from_branch() {
// stack memory pointer should end up in the right place after returning from a branch
assert_evals_to!(
indoc!(
r#"
stackMemoryJunk = { x: 999, y: 111 }
if True then
{ x: 123, y: 321 }
else
stackMemoryJunk
"#
),
(123, 321),
(i64, i64)
);
}
// #[test]
// fn blue_and_present() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue, y: 7 }
// "#
// ),
// 7,
// i64
// );
// }
// #[test]
// fn blue_and_absent() {
// assert_evals_to!(
// indoc!(
// r#"
// f = \r ->
// when r is
// { x: Blue, y ? 3 } -> y
// { x: Red, y ? 5 } -> y
// f { x: Blue }
// "#
// ),
// 3,
// i64
// );
// }

View File

@ -14,7 +14,6 @@ static_assertions = "1.1.0"
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -12,7 +12,6 @@ roc_types = { path = "../types" }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
indoc = "0.3.3"
quickcheck = "0.8"
quickcheck_macros = "0.8"

View File

@ -14,7 +14,6 @@ roc_load = { path = "../compiler/load" }
roc_builtins = { path = "../compiler/builtins" }
roc_can = { path = "../compiler/can" }
roc_code_markup = { path = "../code_markup"}
roc_fmt = { path = "../compiler/fmt" }
roc_module = { path = "../compiler/module" }
roc_region = { path = "../compiler/region" }
roc_types = { path = "../compiler/types" }
@ -25,6 +24,5 @@ snafu = { version = "0.6", features = ["backtraces"] }
[dev-dependencies]
pretty_assertions = "0.5.1"
maplit = "1.0.1"
tempfile = "3.2.0"
uuid = { version = "0.8", features = ["v4"] }

View File

@ -6,6 +6,12 @@ license = "UPL-1.0"
edition = "2018"
description = "An editor for Roc"
[package.metadata.cargo-udeps.ignore]
# confy is currently unused but should not be removed
normal = ["confy"]
#development = []
#build = []
[dependencies]
roc_ast = { path = "../ast" }
roc_collections = { path = "../compiler/collections" }
@ -19,7 +25,6 @@ roc_module = { path = "../compiler/module" }
roc_problem = { path = "../compiler/problem" }
roc_types = { path = "../compiler/types" }
roc_unify = { path = "../compiler/unify" }
roc_fmt = { path = "../compiler/fmt" }
roc_reporting = { path = "../compiler/reporting" }
roc_solve = { path = "../compiler/solve" }
ven_graph = { path = "../vendor/pathfinding" }
@ -33,7 +38,6 @@ winit = "0.24"
wgpu = "0.10"
glyph_brush = "0.7"
log = "0.4"
zerocopy = "0.3"
env_logger = "0.8"
futures = "0.3"
wgpu_glyph = "0.14"
@ -42,18 +46,13 @@ snafu = { version = "0.6", features = ["backtraces"] }
colored = "2"
pest = "2.1"
pest_derive = "2.1"
ropey = "1.2.0"
copypasta = "0.7.1"
indoc = "1.0"
palette = "0.5"
# confy is currently unused but should not be removed
confy = { git = 'https://github.com/rust-cli/confy', features = [
"yaml_conf"
], default-features = false }
serde = { version = "1.0.123", features = ["derive"] }
nonempty = "0.6.0"
tempfile = "3.2.0"
uuid = { version = "0.8", features = ["v4"] }
fs_extra = "1.2.0"
rodio = "0.14.0"
threadpool = "1.8.1"
@ -64,7 +63,9 @@ features = ["derive"]
[dev-dependencies]
pretty_assertions = "0.6"
maplit = "1.0.1"
quickcheck = "1.0"
quickcheck_macros = "1.0"
rand = "0.8.2"
indoc = "1.0"
tempfile = "3.2.0"
uuid = { version = "0.8", features = ["v4"] }

View File

@ -79,7 +79,9 @@ fn run_event_loop(project_dir_path_opt: Option<&Path>) -> Result<(), Box<dyn Err
compatible_surface: Some(&surface),
})
.await
.expect("Request adapter");
.expect(r#"Request adapter
If you're running this from inside nix, follow the instructions here to resolve this: https://github.com/rtfeldman/roc/blob/trunk/BUILDING_FROM_SOURCE.md#editor
"#);
adapter
.request_device(

View File

@ -12,9 +12,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.100"
version = "0.2.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1fa8cddc8fbbee11227ef194b5317ed014b8acbf15139bd716a18ad3fe99ec5"
checksum = "a60553f9a9e039a333b4e9b20573b9e9b9c0bb3a11e201ccc48ef4283456d673"
[[package]]
name = "roc_std"

View File

@ -11,7 +11,7 @@ app "false"
# 1) The input files are considered too large to just read in at once. Instead it is read via buffer or line.
# 2) The output is also considered too large to generate in memory. It must be printed as we go via buffer or line.
# I think one of the biggest issues with this implementation is that it doesn't return the the platform frequently enough.
# I think one of the biggest issues with this implementation is that it doesn't return to the platform frequently enough.
# What I mean by that is we build a chain of all Tasks period and return that to the host.
# In something like the elm architecture you return a single step with one Task.
# The huge difference here is when it comes to things like stack overflows.

View File

@ -30,12 +30,12 @@ extern "C" {
}
#[no_mangle]
pub unsafe fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void {
libc::malloc(size)
}
#[no_mangle]
pub unsafe fn roc_realloc(
pub unsafe extern "C" fn roc_realloc(
c_ptr: *mut c_void,
new_size: usize,
_old_size: usize,
@ -45,12 +45,12 @@ pub unsafe fn roc_realloc(
}
#[no_mangle]
pub unsafe fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) {
libc::free(c_ptr)
}
#[no_mangle]
pub unsafe fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) {
match tag_id {
0 => {
let slice = CStr::from_ptr(c_ptr as *const c_char);
@ -73,7 +73,7 @@ pub unsafe extern "C" fn roc_memset(dst: *mut c_void, c: i32, n: usize) -> *mut
}
#[no_mangle]
pub fn rust_main() -> i32 {
pub extern "C" fn rust_main() -> i32 {
let arg = env::args().skip(1).next().unwrap();
let arg = RocStr::from_slice(arg.as_bytes());
@ -115,7 +115,7 @@ unsafe fn call_the_closure(closure_data_ptr: *const u8) -> i64 {
}
#[no_mangle]
pub fn roc_fx_getLine() -> RocStr {
pub extern "C" fn roc_fx_getLine() -> RocStr {
use std::io::{self, BufRead};
let stdin = io::stdin();
@ -125,7 +125,7 @@ pub fn roc_fx_getLine() -> RocStr {
}
#[no_mangle]
pub fn roc_fx_getChar() -> u8 {
pub extern "C" fn roc_fx_getChar() -> u8 {
use std::io::{self, BufRead};
let mut buffer = [0];
@ -141,7 +141,7 @@ pub fn roc_fx_getChar() -> u8 {
}
#[no_mangle]
pub fn roc_fx_putLine(line: RocStr) -> () {
pub extern "C" fn roc_fx_putLine(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
println!("{}", string);
@ -154,7 +154,7 @@ pub fn roc_fx_putLine(line: RocStr) -> () {
}
#[no_mangle]
pub fn roc_fx_putRaw(line: RocStr) -> () {
pub extern "C" fn roc_fx_putRaw(line: RocStr) -> () {
let bytes = line.as_slice();
let string = unsafe { std::str::from_utf8_unchecked(bytes) };
print!("{}", string);
@ -167,7 +167,7 @@ pub fn roc_fx_putRaw(line: RocStr) -> () {
}
#[no_mangle]
pub fn roc_fx_getFileLine(br_ptr: *mut BufReader<File>) -> RocStr {
pub extern "C" fn roc_fx_getFileLine(br_ptr: *mut BufReader<File>) -> RocStr {
let br = unsafe { &mut *br_ptr };
let mut line1 = String::default();
@ -178,7 +178,7 @@ pub fn roc_fx_getFileLine(br_ptr: *mut BufReader<File>) -> RocStr {
}
#[no_mangle]
pub fn roc_fx_getFileBytes(br_ptr: *mut BufReader<File>) -> RocList<u8> {
pub extern "C" fn roc_fx_getFileBytes(br_ptr: *mut BufReader<File>) -> RocList<u8> {
let br = unsafe { &mut *br_ptr };
let mut buffer = [0; 0x10 /* This is intentially small to ensure correct implementation */];
@ -190,22 +190,25 @@ pub fn roc_fx_getFileBytes(br_ptr: *mut BufReader<File>) -> RocList<u8> {
}
#[no_mangle]
pub fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) -> () {
pub extern "C" fn roc_fx_closeFile(br_ptr: *mut BufReader<File>) -> () {
unsafe {
Box::from_raw(br_ptr);
}
}
#[no_mangle]
pub fn roc_fx_openFile(name: RocStr) -> *mut BufReader<File> {
pub extern "C" fn roc_fx_openFile(name: RocStr) -> *mut BufReader<File> {
let f = File::open(name.as_str()).expect("Unable to open file");
let br = BufReader::new(f);
// don't mess with the refcount!
core::mem::forget(name);
Box::into_raw(Box::new(br))
}
#[no_mangle]
pub fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () {
pub extern "C" fn roc_fx_withFileOpen(name: RocStr, buffer: *const u8) -> () {
// let f = File::open(name.as_str()).expect("Unable to open file");
// let mut br = BufReader::new(f);

View File

@ -1,9 +1,9 @@
#![allow(non_snake_case)]
use core::ffi::c_void;
use libc::c_char;
use roc_std::RocStr;
use std::ffi::CStr;
use std::os::raw::c_char;
extern "C" {
#[link_name = "roc__mainForHost_1_exposed"]

41
nix/zig.nix Normal file
View File

@ -0,0 +1,41 @@
{ pkgs }:
let
version = "0.8.0";
osName = if pkgs.stdenv.isDarwin then "macos" else "linux";
splitSystem = builtins.split "-" builtins.currentSystem;
arch = builtins.elemAt splitSystem 0;
isAarch64 = arch == "aarch64";
archiveName = "zig-${osName}-${arch}-${version}";
# If your system is not aarch64, we assume it's x86_64
sha256 = if pkgs.stdenv.isDarwin then
if isAarch64 then
"b32d13f66d0e1ff740b3326d66a469ee6baddbd7211fa111c066d3bd57683111"
else
"279f9360b5cb23103f0395dc4d3d0d30626e699b1b4be55e98fd985b62bc6fbe"
else if isAarch64 then
"ee204ca2c2037952cf3f8b10c609373a08a291efa4af7b3c73be0f2b27720470"
else
"502625d3da3ae595c5f44a809a87714320b7a40e6dff4a895b5fa7df3391d01e";
in pkgs.stdenv.mkDerivation {
pname = "zig";
version = version;
src = pkgs.fetchurl {
inherit sha256;
name = "${archiveName}.tar.xz";
url = "https://ziglang.org/download/${version}/${archiveName}.tar.xz";
};
phases = [ "unpackPhase" ];
unpackPhase = ''
mkdir -p $out/bin
tar -xf $src
cp ${archiveName}/zig $out/zig
cp -r ${archiveName}/lib $out/lib
ln -s "$out/zig" "$out/bin/zig"
chmod +x $out/bin/zig
'';
}

View File

@ -136,6 +136,7 @@ impl<T> RocList<T> {
where
T: Clone,
{
assert!(capacity > 0);
assert!(slice.len() <= capacity);
let ptr = slice.as_ptr();
@ -197,7 +198,12 @@ impl<T> RocList<T> {
where
T: Clone,
{
Self::from_slice_with_capacity(slice, slice.len())
// Avoid allocation with empty list.
if slice.is_empty() {
Self::default()
} else {
Self::from_slice_with_capacity(slice, slice.len())
}
}
pub fn as_slice(&self) -> &[T] {

View File

@ -32,6 +32,7 @@ let
llvmPkgs = pkgs.llvmPackages_12;
zig = import ./nix/zig.nix { inherit pkgs; };
debugir = import ./nix/debugir.nix { inherit pkgs; };
inputs = with pkgs; [
@ -70,9 +71,39 @@ in pkgs.mkShell {
# Additional Env vars
LLVM_SYS_120_PREFIX = "${llvmPkgs.llvm.dev}";
NIX_GLIBC_PATH =
if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else "";
LD_LIBRARY_PATH = with pkgs;
lib.makeLibraryPath
([ pkg-config stdenv.cc.cc.lib libffi ncurses zlib ] ++ linuxInputs);
NIX_GLIBC_PATH =
if pkgs.stdenv.isLinux then "${pkgs.glibc_multi.out}/lib" else "";
COREAUDIO_SDK_PATH = if pkgs.stdenv.isDarwin then
# The coreaudio-sys crate is configured to look for things in whatever the
# output of `xcrun --sdk macosx --show-sdk-path` is. However, this does not
# always contain the right frameworks, and it uses system versions instead of
# what we control via Nix. Instead of having to run a lot of extra scripts
# to set our systems up to build, we can just create a SDK directory with
# the same layout as the `MacOSX{version}.sdk` that XCode produces.
pkgs.symlinkJoin {
name = "sdk";
paths = with pkgs.darwin.apple_sdk.frameworks; [
AudioToolbox
AudioUnit
CoreAudio
CoreFoundation
CoreMIDI
OpenAL
];
postBuild = ''
mkdir $out/System
mv $out/Library $out/System
'';
}
else
# TODO: I'm not 100% confident that this being blank won't cause issues for
# Nix-on-Linux development. It may be sufficient to use the pkgs.symlinkJoin
# above regardless of system! That'd set us up for cross-compilation as well.
"";
}