diff --git a/crates/compiler/gen_wasm/src/backend.rs b/crates/compiler/gen_wasm/src/backend.rs index ce374c31e1..4c734b5ec1 100644 --- a/crates/compiler/gen_wasm/src/backend.rs +++ b/crates/compiler/gen_wasm/src/backend.rs @@ -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; } - 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), + 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; + self.code_builder.get_local(LocalId(i as u32)); + 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 diff --git a/crates/compiler/gen_wasm/src/lib.rs b/crates/compiler/gen_wasm/src/lib.rs index 1d00e29f64..7b580f8ce0 100644 --- a/crates/compiler/gen_wasm/src/lib.rs +++ b/crates/compiler/gen_wasm/src/lib.rs @@ -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), } } diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index d8bd80c81f..f0038316ac 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -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)); - ProcLayout { - arguments: wrapper_arg_layouts.into_bump_slice(), - result: Layout::UNIT, - captures_niche: fn_name.captures_niche(), + 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), } } diff --git a/crates/compiler/gen_wasm/src/storage.rs b/crates/compiler/gen_wasm/src/storage.rs index 0c7b03d5da..5f11037d29 100644 --- a/crates/compiler/gen_wasm/src/storage.rs +++ b/crates/compiler/gen_wasm/src/storage.rs @@ -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); diff --git a/crates/compiler/gen_wasm/src/wasm32_result.rs b/crates/compiler/gen_wasm/src/wasm32_result.rs index 9c5ae09f77..43db6ff086 100644 --- a/crates/compiler/gen_wasm/src/wasm32_result.rs +++ b/crates/compiler/gen_wasm/src/wasm32_result.rs @@ -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 Wasm32Result for RocList { } } +impl Wasm32Result for RocResult { + 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 Wasm32Result for &'_ T { build_wrapper_body_primitive!(i32_store, Align::Bytes4); } diff --git a/crates/compiler/gen_wasm/src/wasm32_sized.rs b/crates/compiler/gen_wasm/src/wasm32_sized.rs index 3ba889c022..a127474c66 100644 --- a/crates/compiler/gen_wasm/src/wasm32_sized.rs +++ b/crates/compiler/gen_wasm/src/wasm32_sized.rs @@ -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 Wasm32Sized for RocList { const ALIGN_OF_WASM: usize = 4; } +impl Wasm32Sized for RocResult { + 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 Wasm32Sized for &'_ T { const SIZE_OF_WASM: usize = 4; const ALIGN_OF_WASM: usize = 4; diff --git a/crates/compiler/test_gen/src/gen_abilities.rs b/crates/compiler/test_gen/src/gen_abilities.rs index 065501e3f9..b207f42561 100644 --- a/crates/compiler/test_gen/src/gen_abilities.rs +++ b/crates/compiler/test_gen/src/gen_abilities.rs @@ -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!( diff --git a/crates/compiler/test_gen/src/gen_list.rs b/crates/compiler/test_gen/src/gen_list.rs index 2c674b64ed..e5268772a1 100644 --- a/crates/compiler/test_gen/src/gen_list.rs +++ b/crates/compiler/test_gen/src/gen_list.rs @@ -307,8 +307,9 @@ fn list_split() { (RocList, RocList,) ); } + #[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); 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!( diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index f02f5b5c6e..bebed9b687 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -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!( diff --git a/crates/compiler/test_gen/src/gen_result.rs b/crates/compiler/test_gen/src/gen_result.rs index fb757fd7b3..7c524083e1 100644 --- a/crates/compiler/test_gen/src/gen_result.rs +++ b/crates/compiler/test_gen/src/gen_result.rs @@ -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#" diff --git a/crates/compiler/test_gen/src/gen_tags.rs b/crates/compiler/test_gen/src/gen_tags.rs index a2de31653d..50ade390da 100644 --- a/crates/compiler/test_gen/src/gen_tags.rs +++ b/crates/compiler/test_gen/src/gen_tags.rs @@ -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!( diff --git a/crates/compiler/test_gen/src/helpers/from_wasmer_memory.rs b/crates/compiler/test_gen/src/helpers/from_wasmer_memory.rs index d8e149959f..bf37cd9de6 100644 --- a/crates/compiler/test_gen/src/helpers/from_wasmer_memory.rs +++ b/crates/compiler/test_gen/src/helpers/from_wasmer_memory.rs @@ -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 FromWasmerMemory for RocList { } } +impl FromWasmerMemory + for RocResult +{ + fn decode(memory: &wasmer::Memory, offset: u32) -> Self { + let tag_offset = Ord::max(T::ACTUAL_WIDTH, E::ACTUAL_WIDTH); + let tag = ::decode(memory, offset + tag_offset as u32); + if tag == 1 { + let value = ::decode(memory, offset); + RocResult::ok(value) + } else { + let payload = ::decode(memory, offset); + RocResult::err(payload) + } + } +} + impl FromWasmerMemory for &'_ T { fn decode(memory: &wasmer::Memory, offset: u32) -> Self { let elements = ::decode(memory, offset); diff --git a/crates/compiler/test_gen/src/helpers/wasm.rs b/crates/compiler/test_gen/src/helpers/wasm.rs index fad15bc621..de0e863397 100644 --- a/crates/compiler/test_gen/src/helpers/wasm.rs +++ b/crates/compiler/test_gen/src/helpers/wasm.rs @@ -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) => {{