Merge pull request #3434 from rtfeldman/wasm-last-few-builtins

Wasm: all MVP builtins implemented
This commit is contained in:
Folkert de Vries 2022-07-07 22:38:07 +02:00 committed by GitHub
commit 7dcd5d297a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 326 additions and 146 deletions

View File

@ -36,7 +36,8 @@ pub enum ProcSource {
Roc,
Helper,
/// Wrapper function for higher-order calls from Zig to Roc
HigherOrderWrapper(usize),
HigherOrderMapper(usize),
HigherOrderCompare(usize),
}
#[derive(Debug)]
@ -463,7 +464,7 @@ impl<'a> WasmBackend<'a> {
self.module.names.append_function(wasm_fn_index, name);
}
/// Build a wrapper around a Roc procedure so that it can be called from our higher-order Zig builtins.
/// Build a wrapper around a Roc procedure so that it can be called from Zig builtins List.map*
///
/// The generic Zig code passes *pointers* to all of the argument values (e.g. on the heap in a List).
/// Numbers up to 64 bits are passed by value, so we need to load them from the provided pointer.
@ -471,7 +472,7 @@ impl<'a> WasmBackend<'a> {
///
/// NOTE: If the builtins expected the return pointer first and closure data last, we could eliminate the wrapper
/// when all args are pass-by-reference and non-zero size. But currently we need it to swap those around.
pub fn build_higher_order_wrapper(
pub fn build_higher_order_mapper(
&mut self,
wrapper_lookup_idx: usize,
inner_lookup_idx: usize,
@ -515,44 +516,22 @@ impl<'a> WasmBackend<'a> {
for (i, wrapper_arg) in wrapper_arg_layouts.iter().enumerate() {
let is_closure_data = i == 0; // Skip closure data (first for wrapper, last for inner)
let is_return_pointer = i == wrapper_arg_layouts.len() - 1; // Skip return pointer (may not be an arg for inner. And if it is, swaps from end to start)
if is_closure_data || is_return_pointer || wrapper_arg.stack_size(TARGET_INFO) == 0 {
if is_closure_data || is_return_pointer {
continue;
}
let inner_layout = match wrapper_arg {
Layout::Boxed(inner) => inner,
x => internal_error!("Expected a Boxed layout, got {:?}", x),
};
if inner_layout.stack_size(TARGET_INFO) == 0 {
continue;
}
// Load the argument pointer. If it's a primitive value, dereference it too.
n_inner_wasm_args += 1;
// Load wrapper argument. They're all pointers.
self.code_builder.get_local(LocalId(i as u32));
// Dereference any primitive-valued arguments
match wrapper_arg {
Layout::Boxed(inner_arg) => match inner_arg {
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
self.code_builder.i32_load8_u(Bytes1, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => {
self.code_builder.i32_load16_u(Bytes2, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => {
self.code_builder.i32_load(Bytes4, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => {
self.code_builder.i64_load(Bytes8, 0);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
self.code_builder.f32_load(Bytes4, 0);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
self.code_builder.f64_load(Bytes8, 0);
}
Layout::Builtin(Builtin::Bool) => {
self.code_builder.i32_load8_u(Bytes1, 0);
}
_ => {
// Any other layout is a pointer, which we've already loaded. Nothing to do!
}
},
x => internal_error!("Higher-order wrapper: expected a Box layout, got {:?}", x),
}
self.dereference_boxed_value(inner_layout);
}
// If the inner function has closure data, it's the last arg of the inner fn
@ -594,6 +573,90 @@ impl<'a> WasmBackend<'a> {
self.reset();
}
/// Build a wrapper around a Roc comparison proc so that it can be called from higher-order Zig builtins.
/// Comparison procedure signature is: closure_data, a, b -> Order (u8)
///
/// The generic Zig code passes *pointers* to all of the argument values (e.g. on the heap in a List).
/// Numbers up to 64 bits are passed by value, so we need to load them from the provided pointer.
/// Everything else is passed by reference, so we can just pass the pointer through.
pub fn build_higher_order_compare(
&mut self,
wrapper_lookup_idx: usize,
inner_lookup_idx: usize,
) {
use ValueType::*;
let ProcLookupData {
name: wrapper_name,
layout: wrapper_proc_layout,
..
} = self.proc_lookup[wrapper_lookup_idx];
let closure_data_layout = wrapper_proc_layout.arguments[0];
let value_layout = wrapper_proc_layout.arguments[1];
let mut n_inner_args = 2;
if closure_data_layout.stack_size(TARGET_INFO) > 0 {
self.code_builder.get_local(LocalId(0));
n_inner_args += 1;
}
let inner_layout = match value_layout {
Layout::Boxed(inner) => inner,
x => internal_error!("Expected a Boxed layout, got {:?}", x),
};
self.code_builder.get_local(LocalId(1));
self.dereference_boxed_value(inner_layout);
self.code_builder.get_local(LocalId(2));
self.dereference_boxed_value(inner_layout);
// Call the wrapped inner function
let inner_wasm_fn_index = self.fn_index_offset + inner_lookup_idx as u32;
self.code_builder
.call(inner_wasm_fn_index, n_inner_args, true);
// Write empty function header (local variables array with zero length)
self.code_builder.build_fn_header_and_footer(&[], 0, None);
self.module.add_function_signature(Signature {
param_types: bumpalo::vec![in self.env.arena; I32; 3],
ret_type: Some(ValueType::I32),
});
self.append_proc_debug_name(wrapper_name);
self.reset();
}
fn dereference_boxed_value(&mut self, inner: &Layout) {
use Align::*;
match inner {
Layout::Builtin(Builtin::Int(IntWidth::U8 | IntWidth::I8)) => {
self.code_builder.i32_load8_u(Bytes1, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U16 | IntWidth::I16)) => {
self.code_builder.i32_load16_u(Bytes2, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U32 | IntWidth::I32)) => {
self.code_builder.i32_load(Bytes4, 0);
}
Layout::Builtin(Builtin::Int(IntWidth::U64 | IntWidth::I64)) => {
self.code_builder.i64_load(Bytes8, 0);
}
Layout::Builtin(Builtin::Float(FloatWidth::F32)) => {
self.code_builder.f32_load(Bytes4, 0);
}
Layout::Builtin(Builtin::Float(FloatWidth::F64)) => {
self.code_builder.f64_load(Bytes8, 0);
}
Layout::Builtin(Builtin::Bool) => {
self.code_builder.i32_load8_u(Bytes1, 0);
}
_ => {
// Any other layout is a pointer, which we've already loaded. Nothing to do!
}
}
}
/**********************************************************
STATEMENTS

View File

@ -13,7 +13,6 @@ use bumpalo::collections::Vec;
use bumpalo::{self, Bump};
use roc_collections::all::{MutMap, MutSet};
use roc_module::low_level::LowLevelWrapperType;
use roc_module::symbol::{Interns, ModuleId, Symbol};
use roc_mono::code_gen_help::CodeGenHelp;
use roc_mono::ir::{Proc, ProcLayout};
@ -96,17 +95,10 @@ pub fn build_app_module<'a>(
host_module.import.function_count() as u32 + host_module.code.preloaded_count;
// Pre-pass over the procedure names & layouts
// Filter out procs we're going to inline & gather some data for lookups
let mut fn_index: u32 = fn_index_offset;
for ((sym, proc_layout), proc) in procedures.into_iter() {
if matches!(
LowLevelWrapperType::from_symbol(sym),
LowLevelWrapperType::CanBeReplacedBy(_)
) {
continue;
}
// Create a lookup to tell us the final index of each proc in the output file
for (i, ((sym, proc_layout), proc)) in procedures.into_iter().enumerate() {
let fn_index = fn_index_offset + i as u32;
procs.push(proc);
if env.exposed_to_host.contains(&sym) {
maybe_main_fn_index = Some(fn_index);
@ -124,8 +116,6 @@ pub fn build_app_module<'a>(
layout: proc_layout,
source: ProcSource::Roc,
});
fn_index += 1;
}
let mut backend = WasmBackend::new(
@ -179,7 +169,8 @@ pub fn build_app_module<'a>(
match source {
Roc => { /* already generated */ }
Helper => backend.build_proc(helper_iter.next().unwrap()),
HigherOrderWrapper(inner_idx) => backend.build_higher_order_wrapper(idx, *inner_idx),
HigherOrderMapper(inner_idx) => backend.build_higher_order_mapper(idx, *inner_idx),
HigherOrderCompare(inner_idx) => backend.build_higher_order_compare(idx, *inner_idx),
}
}

View File

@ -530,7 +530,45 @@ impl<'a> LowLevelCall<'a> {
backend.call_host_fn_after_loading_args(bitcode::LIST_APPEND, 7, false);
}
ListPrepend => todo!("{:?}", self.lowlevel),
ListPrepend => {
// List.prepend : List elem, elem -> List elem
let list: Symbol = self.arguments[0];
let elem: Symbol = self.arguments[1];
let elem_layout = unwrap_list_elem_layout(self.ret_layout);
let (elem_width, elem_align) = elem_layout.stack_size_and_alignment(TARGET_INFO);
let (elem_local, elem_offset, _) =
ensure_symbol_is_in_memory(backend, elem, *elem_layout, backend.env.arena);
// Zig arguments Wasm types
// (return pointer) i32
// list: RocList i64, i32
// alignment: u32 i32
// element: Opaque i32
// element_width: usize i32
// return pointer and list
backend.storage.load_symbols_for_call(
backend.env.arena,
&mut backend.code_builder,
&[list],
self.ret_symbol,
&WasmLayout::new(&self.ret_layout),
CallConv::Zig,
);
backend.code_builder.i32_const(elem_align as i32);
backend.code_builder.get_local(elem_local);
if elem_offset > 0 {
backend.code_builder.i32_const(elem_offset as i32);
backend.code_builder.i32_add();
}
backend.code_builder.i32_const(elem_width as i32);
backend.call_host_fn_after_loading_args(bitcode::LIST_PREPEND, 6, false);
}
ListSublist => {
// As a low-level, record is destructured
// List.sublist : List elem, start : Nat, len : Nat -> List elem
@ -1980,7 +2018,7 @@ pub fn call_higher_order_lowlevel<'a>(
// We create a wrapper around the passed function, which just unboxes the arguments.
// This allows Zig builtins to have a generic pointer-based interface.
let source = {
let helper_proc_source = {
let passed_proc_layout = ProcLayout {
arguments: argument_layouts,
result: *result_layout,
@ -1993,7 +2031,13 @@ pub fn call_higher_order_lowlevel<'a>(
*name == fn_name.name() && layout == &passed_proc_layout
})
.unwrap();
ProcSource::HigherOrderWrapper(passed_proc_index)
match op {
ListSortWith { .. } => ProcSource::HigherOrderCompare(passed_proc_index),
ListMap { .. } | ListMap2 { .. } | ListMap3 { .. } | ListMap4 { .. } => {
ProcSource::HigherOrderMapper(passed_proc_index)
}
DictWalk { .. } => todo!("DictWalk"),
}
};
let wrapper_sym = backend.create_symbol(&format!("#wrap#{:?}", fn_name));
let wrapper_layout = {
@ -2013,16 +2057,30 @@ pub fn call_higher_order_lowlevel<'a>(
.take(n_non_closure_args)
.map(Layout::Boxed),
);
wrapper_arg_layouts.push(Layout::Boxed(result_layout));
match helper_proc_source {
ProcSource::HigherOrderMapper(_) => {
// Our convention for mappers is that they write to the heap via the last argument
wrapper_arg_layouts.push(Layout::Boxed(result_layout));
ProcLayout {
arguments: wrapper_arg_layouts.into_bump_slice(),
result: Layout::UNIT,
captures_niche: fn_name.captures_niche(),
}
}
ProcSource::HigherOrderCompare(_) => ProcLayout {
arguments: wrapper_arg_layouts.into_bump_slice(),
result: *result_layout,
captures_niche: fn_name.captures_niche(),
},
ProcSource::Roc | ProcSource::Helper => {
internal_error!("Should never reach here for {:?}", helper_proc_source)
}
}
};
let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source);
let wrapper_fn_idx =
backend.register_helper_proc(wrapper_sym, wrapper_layout, helper_proc_source);
let wrapper_fn_ptr = backend.get_fn_ptr(wrapper_fn_idx);
let inc_fn_ptr = match closure_data_layout {
Layout::Struct {
@ -2094,7 +2152,40 @@ pub fn call_higher_order_lowlevel<'a>(
*owns_captured_environment,
),
ListSortWith { .. } | DictWalk { .. } => todo!("{:?}", op),
ListSortWith { xs } => {
let elem_layout = unwrap_list_elem_layout(backend.storage.symbol_layouts[xs]);
let (element_width, alignment) = elem_layout.stack_size_and_alignment(TARGET_INFO);
let cb = &mut backend.code_builder;
// (return pointer) i32
// input: RocList, i64, i32
// caller: CompareFn, i32
// data: Opaque, i32
// inc_n_data: IncN, i32
// data_is_owned: bool, i32
// alignment: u32, i32
// element_width: usize, i32
backend.storage.load_symbols(cb, &[return_sym]);
backend.storage.load_symbol_zig(cb, *xs);
cb.i32_const(wrapper_fn_ptr);
if closure_data_exists {
backend.storage.load_symbols(cb, &[*captured_environment]);
} else {
// load_symbols assumes that a zero-size arg should be eliminated in code gen,
// but that's a specialization that our Zig code doesn't have! Pass a null pointer.
cb.i32_const(0);
}
cb.i32_const(inc_fn_ptr);
cb.i32_const(*owns_captured_environment as i32);
cb.i32_const(alignment as i32);
cb.i32_const(element_width as i32);
backend.call_host_fn_after_loading_args(bitcode::LIST_SORT_WITH, 9, false);
}
DictWalk { .. } => todo!("{:?}", op),
}
}

View File

@ -222,6 +222,7 @@ impl<'a> Storage<'a> {
arena: &'a Bump,
) {
let mut wide_number_args = Vec::with_capacity_in(args.len(), arena);
let mut has_zero_size_arg = false;
for (layout, symbol) in args {
self.symbol_layouts.insert(*symbol, *layout);
@ -260,6 +261,7 @@ impl<'a> Storage<'a> {
if size == 0 {
// An argument with zero size is purely conceptual, and will not exist in Wasm.
// However we need to track the symbol, so we treat it like a local variable.
has_zero_size_arg = true;
StackMemoryLocation::FrameOffset(0)
} else {
StackMemoryLocation::PointerArg(LocalId(local_index))
@ -282,7 +284,7 @@ impl<'a> Storage<'a> {
// If any arguments are 128-bit numbers, store them in the stack frame
// This makes it easier to keep track of which symbols are on the Wasm value stack
// The frame pointer will be the next local after the arguments
if self.stack_frame_size > 0 {
if self.stack_frame_size > 0 || has_zero_size_arg {
let frame_ptr = LocalId(self.arg_types.len() as u32);
self.stack_frame_pointer = Some(frame_ptr);
self.local_types.push(PTR_TYPE);

View File

@ -14,7 +14,7 @@ use crate::wasm_module::{
linking::SymInfo, linking::WasmObjectSymbol, Align, CodeBuilder, Export, ExportType, LocalId,
Signature, ValueType, WasmModule,
};
use roc_std::{RocDec, RocList, RocOrder, RocStr};
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr};
/// Type-driven wrapper generation
pub trait Wasm32Result {
@ -200,6 +200,17 @@ impl<T: Wasm32Result> Wasm32Result for RocList<T> {
}
}
impl<T: Wasm32Sized, E: Wasm32Sized> Wasm32Result for RocResult<T, E> {
fn build_wrapper_body(code_builder: &mut CodeBuilder, main_function_index: u32) {
build_wrapper_body_stack_memory(
code_builder,
main_function_index,
Ord::max(T::ACTUAL_WIDTH, E::ACTUAL_WIDTH)
+ Ord::max(T::ALIGN_OF_WASM, E::ALIGN_OF_WASM),
)
}
}
impl<T: Wasm32Result> Wasm32Result for &'_ T {
build_wrapper_body_primitive!(i32_store, Align::Bytes4);
}

View File

@ -1,4 +1,4 @@
use roc_std::{RocDec, RocList, RocOrder, RocStr};
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr};
pub trait Wasm32Sized: Sized {
const SIZE_OF_WASM: usize;
@ -41,6 +41,11 @@ impl<T: Wasm32Sized> Wasm32Sized for RocList<T> {
const ALIGN_OF_WASM: usize = 4;
}
impl<T: Wasm32Sized, E: Wasm32Sized> Wasm32Sized for RocResult<T, E> {
const ALIGN_OF_WASM: usize = max2(T::ALIGN_OF_WASM, E::ALIGN_OF_WASM);
const SIZE_OF_WASM: usize = max2(T::ACTUAL_WIDTH, E::ACTUAL_WIDTH) + 1;
}
impl<T: Wasm32Sized> Wasm32Sized for &'_ T {
const SIZE_OF_WASM: usize = 4;
const ALIGN_OF_WASM: usize = 4;

View File

@ -10,9 +10,9 @@ use crate::helpers::wasm::assert_evals_to;
#[cfg(test)]
use indoc::indoc;
#[cfg(all(test, feature = "gen-llvm"))]
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocList;
#[cfg(all(test, feature = "gen-llvm"))]
#[cfg(all(test, any(feature = "gen-llvm", feature = "gen-wasm")))]
use roc_std::RocStr;
#[test]
@ -223,7 +223,7 @@ fn ability_used_as_type_still_compiles() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode() {
assert_evals_to!(
indoc!(
@ -269,7 +269,7 @@ fn encode() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn decode() {
assert_evals_to!(
indoc!(
@ -327,7 +327,7 @@ fn decode() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn encode_use_stdlib() {
assert_evals_to!(
indoc!(

View File

@ -307,8 +307,9 @@ fn list_split() {
(RocList<i64>, RocList<i64>,)
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop() {
assert_evals_to!(
"List.drop [1,2,3] 2",
@ -353,7 +354,7 @@ fn list_drop_at() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_intersperse() {
assert_evals_to!(
indoc!(
@ -398,7 +399,7 @@ fn list_drop_at_shared() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_if_empty_list_of_int() {
assert_evals_to!(
indoc!(
@ -415,7 +416,7 @@ fn list_drop_if_empty_list_of_int() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_if_empty_list() {
assert_evals_to!(
indoc!(
@ -432,7 +433,7 @@ fn list_drop_if_empty_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_if_always_false_for_non_empty_list() {
assert_evals_to!(
indoc!(
@ -446,7 +447,7 @@ fn list_drop_if_always_false_for_non_empty_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_if_always_true_for_non_empty_list() {
assert_evals_to!(
indoc!(
@ -460,7 +461,7 @@ fn list_drop_if_always_true_for_non_empty_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_if_geq3() {
assert_evals_to!(
indoc!(
@ -474,7 +475,7 @@ fn list_drop_if_geq3() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_if_string_eq() {
assert_evals_to!(
indoc!(
@ -488,7 +489,7 @@ fn list_drop_if_string_eq() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_last() {
assert_evals_to!(
"List.dropLast [1, 2, 3]",
@ -508,7 +509,7 @@ fn list_drop_last() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_last_mutable() {
assert_evals_to!(
indoc!(
@ -530,7 +531,7 @@ fn list_drop_last_mutable() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_drop_first() {
assert_evals_to!(
"List.dropFirst [1, 2, 3]",
@ -630,7 +631,7 @@ fn list_append_longer_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_prepend() {
assert_evals_to!("List.prepend [] 1", RocList::from_slice(&[1]), RocList<i64>);
assert_evals_to!(
@ -669,7 +670,7 @@ fn list_prepend() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_prepend_bools() {
assert_evals_to!(
"List.prepend [True, False] True",
@ -679,7 +680,7 @@ fn list_prepend_bools() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_prepend_big_list() {
assert_evals_to!(
"List.prepend [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 100, 100, 100, 100] 9",
@ -795,7 +796,7 @@ fn list_walk_until_sum() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_implements_position() {
assert_evals_to!(
r#"
@ -818,7 +819,7 @@ fn list_walk_implements_position() {
Some v -> v
"#,
2,
i64
usize
);
}
@ -841,7 +842,7 @@ fn list_walk_until_even_prefix_sum() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_if_empty_list_of_int() {
assert_evals_to!(
indoc!(
@ -859,7 +860,7 @@ fn list_keep_if_empty_list_of_int() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_if_empty_list() {
assert_evals_to!(
indoc!(
@ -878,7 +879,7 @@ fn list_keep_if_empty_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_if_always_true_for_non_empty_list() {
assert_evals_to!(
indoc!(
@ -900,7 +901,7 @@ fn list_keep_if_always_true_for_non_empty_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_if_always_false_for_non_empty_list() {
assert_evals_to!(
indoc!(
@ -918,7 +919,7 @@ fn list_keep_if_always_false_for_non_empty_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_if_one() {
assert_evals_to!(
indoc!(
@ -936,7 +937,7 @@ fn list_keep_if_one() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_if_str_is_hello() {
assert_evals_to!(
indoc!(
@ -2289,7 +2290,7 @@ fn quicksort_singleton() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn empty_list_increment_decrement() {
assert_evals_to!(
indoc!(
@ -2301,12 +2302,12 @@ fn empty_list_increment_decrement() {
"#
),
0,
i64
usize
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_literal_increment_decrement() {
assert_evals_to!(
indoc!(
@ -2318,7 +2319,7 @@ fn list_literal_increment_decrement() {
"#
),
6,
i64
usize
);
}
@ -2496,7 +2497,7 @@ fn list_product() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_void() {
assert_evals_to!(
"List.keepOks [] (\\x -> x)",
@ -2512,7 +2513,7 @@ fn list_keep_void() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_oks() {
assert_evals_to!(
"List.keepOks [Ok {}, Ok {}] (\\x -> x)",
@ -2537,7 +2538,7 @@ fn list_keep_oks() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_keep_errs() {
assert_evals_to!(
"List.keepErrs [Err {}, Err {}] (\\x -> x)",
@ -2613,7 +2614,7 @@ fn list_range() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_sort_with() {
assert_evals_to!(
"List.sortWith [] Num.compare",
@ -2633,7 +2634,7 @@ fn list_sort_with() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_sort_asc() {
assert_evals_to!(
"List.sortAsc []",
@ -2648,7 +2649,7 @@ fn list_sort_asc() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_sort_desc() {
assert_evals_to!(
"List.sortDesc []",
@ -2712,7 +2713,7 @@ fn list_all_empty_with_unknown_element_type() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = r#"Roc failed with message: "invalid ret_layout""#)]
fn lists_with_incompatible_type_param_in_if() {
assert_evals_to!(
@ -2733,7 +2734,7 @@ fn lists_with_incompatible_type_param_in_if() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn map_with_index_multi_record() {
// see https://github.com/rtfeldman/roc/issues/1700
assert_evals_to!(
@ -2777,7 +2778,7 @@ fn empty_list_of_function_type() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_join_map() {
assert_evals_to!(
indoc!(
@ -2797,7 +2798,7 @@ fn list_join_map() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_join_map_empty() {
assert_evals_to!(
indoc!(
@ -2874,7 +2875,7 @@ fn list_find_empty_layout() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_find_index() {
assert_evals_to!(
indoc!(
@ -2885,12 +2886,12 @@ fn list_find_index() {
"#
),
1,
i64
usize
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_find_index_not_found() {
assert_evals_to!(
indoc!(
@ -2901,12 +2902,12 @@ fn list_find_index_not_found() {
"#
),
999,
i64
usize
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_find_index_empty_typed_list() {
assert_evals_to!(
indoc!(
@ -2917,12 +2918,12 @@ fn list_find_index_empty_typed_list() {
"#
),
999,
i64
usize
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn monomorphized_lists() {
assert_evals_to!(
indoc!(
@ -2936,7 +2937,7 @@ fn monomorphized_lists() {
"#
),
18,
u64
usize
)
}
@ -2960,7 +2961,7 @@ fn with_capacity() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn call_function_in_empty_list() {
assert_evals_to!(
indoc!(

View File

@ -981,7 +981,7 @@ fn overflow_frees_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Roc failed with message: ")]
fn undefined_variable() {
assert_evals_to!(
@ -999,7 +999,7 @@ fn undefined_variable() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Roc failed with message: ")]
fn annotation_without_body() {
assert_evals_to!(
@ -1039,7 +1039,7 @@ fn simple_closure() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn nested_closure() {
assert_evals_to!(
indoc!(
@ -1121,7 +1121,7 @@ fn specialize_closure() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn io_poc_effect() {
assert_non_opt_evals_to!(
indoc!(
@ -1152,7 +1152,7 @@ fn io_poc_effect() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn io_poc_desugared() {
assert_evals_to!(
indoc!(
@ -1374,7 +1374,7 @@ fn linked_list_singleton() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn recursive_function_with_rigid() {
assert_non_opt_evals_to!(
indoc!(
@ -1401,7 +1401,7 @@ fn recursive_function_with_rigid() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn rbtree_insert() {
assert_non_opt_evals_to!(
indoc!(
@ -2020,7 +2020,7 @@ fn hof_conditional() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(
expected = "Roc failed with message: \"Shadowing { original_region: @55-56, shadow: @88-89 Ident"
)]
@ -2497,7 +2497,7 @@ fn backpassing_result() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Shadowing { original_region: @55-56, shadow: @72-73 Ident")]
fn function_malformed_pattern() {
assert_evals_to!(
@ -2589,7 +2589,7 @@ fn module_thunk_is_function() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Roc failed with message: ")]
fn hit_unresolved_type_variable() {
assert_evals_to!(
@ -2732,7 +2732,7 @@ fn lambda_set_byte() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_set_struct_byte() {
assert_evals_to!(
indoc!(
@ -2791,7 +2791,7 @@ fn lambda_set_enum_byte_byte() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn list_walk_until() {
// see https://github.com/rtfeldman/roc/issues/1576
assert_evals_to!(
@ -2872,7 +2872,7 @@ fn int_literal_not_specialized_no_annotation() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn unresolved_tvar_when_capture_is_unused() {
// see https://github.com/rtfeldman/roc/issues/1585
assert_evals_to!(
@ -2899,7 +2899,7 @@ fn unresolved_tvar_when_capture_is_unused() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[should_panic(expected = "Roc failed with message: ")]
fn value_not_exposed_hits_panic() {
assert_evals_to!(
@ -2969,7 +2969,7 @@ fn mix_function_and_closure_level_of_indirection() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
#[cfg_attr(debug_assertions, ignore)] // this test stack-overflows the compiler in debug mode
fn do_pass_bool_byte_closure_layout() {
// see https://github.com/rtfeldman/roc/pull/1706
@ -3172,7 +3172,7 @@ fn alias_defined_out_of_order() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn recursively_build_effect() {
assert_evals_to!(
indoc!(
@ -3243,7 +3243,7 @@ fn polymophic_expression_captured_inside_closure() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_2322() {
assert_evals_to!(
indoc!(
@ -3481,7 +3481,7 @@ fn mutual_recursion_top_level_defs() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn polymorphic_lambda_captures_polymorphic_value() {
assert_evals_to!(
indoc!(
@ -3500,7 +3500,7 @@ fn polymorphic_lambda_captures_polymorphic_value() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_capture_niche_u64_vs_u8_capture() {
assert_evals_to!(
indoc!(
@ -3527,7 +3527,7 @@ fn lambda_capture_niche_u64_vs_u8_capture() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_capture_niches_with_other_lambda_capture() {
assert_evals_to!(
indoc!(
@ -3560,7 +3560,7 @@ fn lambda_capture_niches_with_other_lambda_capture() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_capture_niches_with_non_capturing_function() {
assert_evals_to!(
indoc!(
@ -3593,7 +3593,7 @@ fn lambda_capture_niches_with_non_capturing_function() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn lambda_capture_niches_have_captured_function_in_closure() {
assert_evals_to!(
indoc!(

View File

@ -224,7 +224,7 @@ fn is_err() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn roc_result_ok() {
assert_evals_to!(
indoc!(
@ -241,7 +241,7 @@ fn roc_result_ok() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn roc_result_err() {
assert_evals_to!(
indoc!(
@ -258,7 +258,7 @@ fn roc_result_err() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_2583_specialize_errors_behind_unified_branches() {
assert_evals_to!(
r#"

View File

@ -1182,7 +1182,7 @@ fn applied_tag_function() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn applied_tag_function_result() {
assert_evals_to!(
indoc!(
@ -1221,7 +1221,7 @@ fn applied_tag_function_linked_list() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn applied_tag_function_pair() {
assert_evals_to!(
indoc!(
@ -1260,7 +1260,7 @@ fn tag_must_be_its_own_type() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn recursive_tag_union_into_flat_tag_union() {
// Comprehensive test for correctness in cli/tests/repl_eval
assert_evals_to!(
@ -1535,7 +1535,7 @@ fn issue_2458_deep_recursion_var() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_1162() {
assert_evals_to!(
indoc!(
@ -1705,7 +1705,7 @@ fn issue_2900_unreachable_pattern() {
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn issue_3261_non_nullable_unwrapped_recursive_union_at_index() {
assert_evals_to!(
indoc!(

View File

@ -1,5 +1,5 @@
use roc_gen_wasm::wasm32_sized::Wasm32Sized;
use roc_std::{RocDec, RocList, RocOrder, RocStr};
use roc_std::{RocDec, RocList, RocOrder, RocResult, RocStr};
use std::convert::TryInto;
pub trait FromWasmerMemory: Wasm32Sized {
@ -95,6 +95,22 @@ impl<T: FromWasmerMemory + Clone> FromWasmerMemory for RocList<T> {
}
}
impl<T: FromWasmerMemory + Wasm32Sized, E: FromWasmerMemory + Wasm32Sized> FromWasmerMemory
for RocResult<T, E>
{
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let tag_offset = Ord::max(T::ACTUAL_WIDTH, E::ACTUAL_WIDTH);
let tag = <u8 as FromWasmerMemory>::decode(memory, offset + tag_offset as u32);
if tag == 1 {
let value = <T as FromWasmerMemory>::decode(memory, offset);
RocResult::ok(value)
} else {
let payload = <E as FromWasmerMemory>::decode(memory, offset);
RocResult::err(payload)
}
}
}
impl<T: FromWasmerMemory> FromWasmerMemory for &'_ T {
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let elements = <u32 as FromWasmerMemory>::decode(memory, offset);

View File

@ -364,7 +364,7 @@ macro_rules! assert_evals_to {
};
($src:expr, $expected:expr, $ty:ty, $transform:expr) => {
$crate::helpers::wasm::assert_evals_to!($src, $expected, $ty, $transform, false);
$crate::helpers::wasm::assert_evals_to!($src, $expected, $ty, $transform, false)
};
($src:expr, $expected:expr, $ty:ty, $transform:expr, $ignore_problems: expr) => {{