Merge branch 'trunk' into shritesh/aarch64-tests

This commit is contained in:
Shritesh Bhattarai 2021-11-01 15:48:24 -04:00 committed by GitHub
commit 2b014efe7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1711 additions and 562 deletions

View File

@ -42,4 +42,4 @@ Viktor Fröberg <vikfroberg@gmail.com>
Locria Cyber <locriacyber@noreply.users.github.com>
Matthias Beyer <mail@beyermatthias.de>
Tim Whiting <tim@whitings.org>
Logan Lowder <logan.lowder@logikcull.com>

View File

@ -50,8 +50,12 @@ If you want to install it manually, you can also download Zig directly [here](ht
**version: 12.0.x**
For macOS, you can install LLVM 12 using `brew install llvm@12` and then adding
`/usr/local/opt/llvm/bin` to your `PATH`. You can confirm this worked by
`/usr/local/opt/llvm@12/bin` to your `PATH`. You can confirm this worked by
running `llc --version` - it should mention "LLVM version 12.0.0" at the top.
You may also need to manually specify a prefix env var like so:
```
export LLVM_SYS_120_PREFIX=/usr/local/opt/llvm@12
```
For Ubuntu and Debian:
```
@ -62,7 +66,7 @@ chmod +x llvm.sh
```
If you use this script, you'll need to add `clang` and `llvm-as` to your `PATH`.
By default, the script installs them as `llvm-as-12` and `clang-12`,
By default, the script installs them as `clang-12` and `llvm-as-12`,
respectively. You can address this with symlinks like so:
```

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());
@ -753,6 +753,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
add_top_level_function_type!(
Symbol::LIST_MIN,
vec![list_type(num_type(flex(TVAR1)))],
Box::new(result_type(num_type(flex(TVAR1)), list_was_empty.clone())),
);
// max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
add_top_level_function_type!(
Symbol::LIST_MAX,
vec![list_type(num_type(flex(TVAR1)))],
Box::new(result_type(num_type(flex(TVAR1)), list_was_empty)),
);
@ -923,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

@ -81,6 +81,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
LIST_CONCAT => list_concat,
LIST_CONTAINS => list_contains,
LIST_MIN => list_min,
LIST_MAX => list_max,
LIST_SUM => list_sum,
LIST_PRODUCT => list_product,
LIST_PREPEND => list_prepend,
@ -88,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,
@ -289,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();
@ -2268,6 +2305,136 @@ fn list_min_lt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr {
)
}
// max : List (Num a) -> Result (Num a) [ ListWasEmpty ]*
fn list_max(symbol: Symbol, var_store: &mut VarStore) -> Def {
let arg_var = var_store.fresh();
let bool_var = var_store.fresh();
let list_var = var_store.fresh();
let len_var = Variable::NAT;
let num_var = len_var;
let num_precision_var = Variable::NATURAL;
let list_elem_var = var_store.fresh();
let ret_var = var_store.fresh();
let closure_var = var_store.fresh();
// Perform a bounds check. If it passes, delegate to List.getUnsafe.
let body = If {
cond_var: bool_var,
branch_var: var_store.fresh(),
branches: vec![(
// if-condition
no_region(
// List.len list != 0
RunLowLevel {
op: LowLevel::NotEq,
args: vec![
(len_var, int(num_var, num_precision_var, 0)),
(
len_var,
RunLowLevel {
op: LowLevel::ListLen,
args: vec![(list_var, Var(Symbol::ARG_1))],
ret_var: len_var,
},
),
],
ret_var: bool_var,
},
),
// list was not empty
no_region(
// Ok ( List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc )
tag(
"Ok",
vec![
// List.walk list (List.getUnsafe list 0) \acc,elem -> if elem < acc then elem else acc
RunLowLevel {
op: LowLevel::ListWalk,
args: vec![
(list_var, Var(Symbol::ARG_1)),
// (List.getUnsafe list 0)
(
list_elem_var,
RunLowLevel {
op: LowLevel::ListGetUnsafe,
args: vec![
(list_var, Var(Symbol::ARG_1)),
(arg_var, int(num_var, num_precision_var, 0)),
],
ret_var: list_elem_var,
},
),
// \acc,elem -> if elem < acc then elem else acc
(closure_var, list_max_gt(list_elem_var, var_store)),
],
ret_var: list_elem_var,
},
],
var_store,
),
),
)],
final_else: Box::new(
// list was empty
no_region(
// Err ListWasEmpty
tag(
"Err",
vec![tag("ListWasEmpty", Vec::new(), var_store)],
var_store,
),
),
),
};
defn(
symbol,
vec![(list_var, Symbol::ARG_1)],
var_store,
body,
ret_var,
)
}
// \acc,elem -> if elem > acc then elem else acc
fn list_max_gt(list_elem_var: Variable, var_store: &mut VarStore) -> Expr {
let bool_var = var_store.fresh();
// if elem > acc then elem else acc
let body = If {
cond_var: bool_var,
branch_var: list_elem_var,
branches: vec![(
// if-condition
no_region(
// elem > acc
RunLowLevel {
op: LowLevel::NumGt,
args: vec![
(list_elem_var, Var(Symbol::ARG_4)),
(list_elem_var, Var(Symbol::ARG_3)),
],
ret_var: bool_var,
},
),
// return elem
no_region(Var(Symbol::ARG_4)),
)],
// return acc
final_else: Box::new(no_region(Var(Symbol::ARG_3))),
};
defn_help(
Symbol::LIST_MAX_GT,
vec![
(list_elem_var, Symbol::ARG_3),
(list_elem_var, Symbol::ARG_4),
],
var_store,
body,
list_elem_var,
)
}
/// List.sum : List (Num a) -> Num a
fn list_sum(symbol: Symbol, var_store: &mut VarStore) -> Def {
let num_var = var_store.fresh();
@ -2410,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

@ -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,
@ -4609,6 +4609,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 +5701,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>,

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

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

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

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

@ -1058,6 +1058,9 @@ define_builtins! {
35 LIST_DROP_LAST: "dropLast"
36 LIST_MIN: "min"
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

@ -771,6 +771,37 @@ fn call_spec(
builder.add_call(block, spec_var, module, name, argument)?;
}
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 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 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 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 bag4 = builder.add_get_tuple_field(block, list4, LIST_BAG_INDEX)?;
let _cell4 = builder.add_get_tuple_field(block, list4, LIST_CELL_INDEX)?;
let elem4 = builder.add_bag_get(block, bag4)?;
let argument = if closure_env_layout.is_none() {
builder.add_make_tuple(block, &[elem1, elem2, elem3, elem4])?
} else {
builder.add_make_tuple(block, &[elem1, elem2, elem3, elem4, closure_env])?
};
builder.add_call(block, spec_var, module, name, argument)?;
}
ListKeepIf { xs } | ListKeepOks { xs } | ListKeepErrs { xs } => {
let list = env.symbols[xs];
let closure_env = env.symbols[function_env];

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

@ -576,6 +576,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

@ -4134,6 +4134,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 {

View File

@ -8,6 +8,7 @@ use roc_types::subs::{
Content, FlatType, RecordFields, Subs, UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::{gather_fields_unsorted_iter, RecordField};
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use ven_pretty::{DocAllocator, DocBuilder};
@ -2534,6 +2535,64 @@ struct IdsByLayout<'a> {
next_id: u32,
}
impl<'a> IdsByLayout<'a> {
#[inline(always)]
fn insert_layout(&mut self, layout: Layout<'a>) -> LayoutId {
match self.by_id.entry(layout) {
Entry::Vacant(vacant) => {
let answer = self.next_id;
vacant.insert(answer);
self.next_id += 1;
LayoutId(answer)
}
Entry::Occupied(occupied) => LayoutId(*occupied.get()),
}
}
#[inline(always)]
fn singleton_layout(layout: Layout<'a>) -> (Self, LayoutId) {
let mut by_id = HashMap::with_capacity_and_hasher(1, default_hasher());
by_id.insert(layout, 1);
let ids_by_layout = IdsByLayout {
by_id,
toplevels_by_id: Default::default(),
next_id: 2,
};
(ids_by_layout, LayoutId(1))
}
#[inline(always)]
fn insert_toplevel(&mut self, layout: crate::ir::ProcLayout<'a>) -> LayoutId {
match self.toplevels_by_id.entry(layout) {
Entry::Vacant(vacant) => {
let answer = self.next_id;
vacant.insert(answer);
self.next_id += 1;
LayoutId(answer)
}
Entry::Occupied(occupied) => LayoutId(*occupied.get()),
}
}
#[inline(always)]
fn singleton_toplevel(layout: crate::ir::ProcLayout<'a>) -> (Self, LayoutId) {
let mut toplevels_by_id = HashMap::with_capacity_and_hasher(1, default_hasher());
toplevels_by_id.insert(layout, 1);
let ids_by_layout = IdsByLayout {
by_id: Default::default(),
toplevels_by_id,
next_id: 2,
};
(ids_by_layout, LayoutId(1))
}
}
#[derive(Default)]
pub struct LayoutIds<'a> {
by_symbol: MutMap<Symbol, IdsByLayout<'a>>,
@ -2542,60 +2601,38 @@ pub struct LayoutIds<'a> {
impl<'a> LayoutIds<'a> {
/// Returns a LayoutId which is unique for the given symbol and layout.
/// If given the same symbol and same layout, returns the same LayoutId.
#[inline(always)]
pub fn get<'b>(&mut self, symbol: Symbol, layout: &'b Layout<'a>) -> LayoutId {
// Note: this function does some weird stuff to satisfy the borrow checker.
// There's probably a nicer way to write it that still works.
let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout {
by_id: HashMap::with_capacity_and_hasher(1, default_hasher()),
toplevels_by_id: Default::default(),
next_id: 1,
});
match self.by_symbol.entry(symbol) {
Entry::Vacant(vacant) => {
let (ids_by_layout, layout_id) = IdsByLayout::singleton_layout(*layout);
// Get the id associated with this layout, or default to next_id.
let answer = ids.by_id.get(layout).copied().unwrap_or(ids.next_id);
vacant.insert(ids_by_layout);
// If we had to default to next_id, it must not have been found;
// store the ID we're going to return and increment next_id.
if answer == ids.next_id {
ids.by_id.insert(*layout, ids.next_id);
ids.next_id += 1;
layout_id
}
Entry::Occupied(mut occupied_ids) => occupied_ids.get_mut().insert_layout(*layout),
}
LayoutId(answer)
}
/// Returns a LayoutId which is unique for the given symbol and layout.
/// If given the same symbol and same layout, returns the same LayoutId.
#[inline(always)]
pub fn get_toplevel<'b>(
&mut self,
symbol: Symbol,
layout: &'b crate::ir::ProcLayout<'a>,
) -> LayoutId {
// Note: this function does some weird stuff to satisfy the borrow checker.
// There's probably a nicer way to write it that still works.
let ids = self.by_symbol.entry(symbol).or_insert_with(|| IdsByLayout {
by_id: Default::default(),
toplevels_by_id: HashMap::with_capacity_and_hasher(1, default_hasher()),
next_id: 1,
});
match self.by_symbol.entry(symbol) {
Entry::Vacant(vacant) => {
let (ids_by_layout, layout_id) = IdsByLayout::singleton_toplevel(*layout);
// Get the id associated with this layout, or default to next_id.
let answer = ids
.toplevels_by_id
.get(layout)
.copied()
.unwrap_or(ids.next_id);
vacant.insert(ids_by_layout);
// If we had to default to next_id, it must not have been found;
// store the ID we're going to return and increment next_id.
if answer == ids.next_id {
ids.toplevels_by_id.insert(*layout, ids.next_id);
ids.next_id += 1;
layout_id
}
Entry::Occupied(mut occupied_ids) => occupied_ids.get_mut().insert_toplevel(*layout),
}
LayoutId(answer)
}
}

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

@ -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!(
@ -1975,6 +2006,32 @@ fn list_min() {
);
}
#[test]
fn list_max() {
assert_evals_to!(
indoc!(
r#"
when List.max [] is
Ok val -> val
Err _ -> -1
"#
),
-1,
i64
);
assert_evals_to!(
indoc!(
r#"
when List.max [3, 1, 2] is
Ok val -> val
Err _ -> -1
"#
),
3,
i64
);
}
#[test]
fn list_sum() {
assert_evals_to!("List.sum []", 0, i64);