diff --git a/compiler/builtins/bitcode/src/main.zig b/compiler/builtins/bitcode/src/main.zig index c772eafa1a..4d097a8705 100644 --- a/compiler/builtins/bitcode/src/main.zig +++ b/compiler/builtins/bitcode/src/main.zig @@ -116,6 +116,12 @@ comptime { num.exportAcos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".acos."); num.exportAtan(T, ROC_BUILTINS ++ "." ++ NUM ++ ".atan."); + num.exportSin(T, ROC_BUILTINS ++ "." ++ NUM ++ ".sin."); + num.exportCos(T, ROC_BUILTINS ++ "." ++ NUM ++ ".cos."); + + num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow."); + num.exportLog(T, ROC_BUILTINS ++ "." ++ NUM ++ ".log."); + num.exportIsFinite(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_finite."); } } diff --git a/compiler/builtins/bitcode/src/num.zig b/compiler/builtins/bitcode/src/num.zig index d25b1fd653..3238cae28a 100644 --- a/compiler/builtins/bitcode/src/num.zig +++ b/compiler/builtins/bitcode/src/num.zig @@ -90,6 +90,33 @@ pub fn exportAtan(comptime T: type, comptime name: []const u8) void { @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } +pub fn exportSin(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) T { + return @sin(input); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportCos(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) T { + return @cos(input); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub fn exportLog(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(input: T) callconv(.C) T { + return @log(input); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + pub fn exportRoundF32(comptime T: type, comptime name: []const u8) void { comptime var f = struct { fn func(input: f32) callconv(.C) T { diff --git a/compiler/builtins/src/bitcode.rs b/compiler/builtins/src/bitcode.rs index 308ba2eba9..8dea1a7134 100644 --- a/compiler/builtins/src/bitcode.rs +++ b/compiler/builtins/src/bitcode.rs @@ -271,13 +271,17 @@ macro_rules! int_intrinsic { }}; } +pub const NUM_SIN: IntrinsicName = float_intrinsic!("roc_builtins.num.sin"); +pub const NUM_COS: IntrinsicName = float_intrinsic!("roc_builtins.num.cos"); pub const NUM_ASIN: IntrinsicName = float_intrinsic!("roc_builtins.num.asin"); pub const NUM_ACOS: IntrinsicName = float_intrinsic!("roc_builtins.num.acos"); pub const NUM_ATAN: IntrinsicName = float_intrinsic!("roc_builtins.num.atan"); pub const NUM_IS_FINITE: IntrinsicName = float_intrinsic!("roc_builtins.num.is_finite"); +pub const NUM_LOG: IntrinsicName = float_intrinsic!("roc_builtins.num.log"); +pub const NUM_POW: IntrinsicName = float_intrinsic!("roc_builtins.num.pow"); + pub const NUM_POW_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.pow_int"); pub const NUM_DIV_CEIL: IntrinsicName = int_intrinsic!("roc_builtins.num.div_ceil"); - pub const NUM_ROUND_F32: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f32"); pub const NUM_ROUND_F64: IntrinsicName = int_intrinsic!("roc_builtins.num.round_f64"); diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index 7fd8b8fde2..09b0f3cf79 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -17,7 +17,7 @@ use roc_std::RocDec; use crate::layout::{CallConv, ReturnMethod, WasmLayout}; use crate::low_level::{call_higher_order_lowlevel, LowLevelCall}; -use crate::storage::{Storage, StoredValue, StoredValueKind}; +use crate::storage::{Storage, StoredValue, StoredVarKind}; use crate::wasm_module::linking::{DataSymbol, WasmObjectSymbol}; use crate::wasm_module::sections::{ ConstExpr, DataMode, DataSegment, Export, Global, GlobalType, Import, ImportDesc, Limits, @@ -414,10 +414,8 @@ impl<'a> WasmBackend<'a> { // We never use the `return` instruction. Instead, we break from this block. self.start_block(); - for (layout, symbol) in proc.args { - self.storage - .allocate(*layout, *symbol, StoredValueKind::Parameter); - } + self.storage + .allocate_args(proc.args, &mut self.code_builder, self.env.arena); if let Some(ty) = ret_type { let ret_var = self.storage.create_anonymous_local(ty); @@ -660,8 +658,8 @@ impl<'a> WasmBackend<'a> { } let kind = match following { - Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredValueKind::ReturnValue, - _ => StoredValueKind::Variable, + Stmt::Ret(ret_sym) if *sym == *ret_sym => StoredVarKind::ReturnValue, + _ => StoredVarKind::Variable, }; self.stmt_let_store_expr(*sym, layout, expr, kind); @@ -677,9 +675,9 @@ impl<'a> WasmBackend<'a> { sym: Symbol, layout: &Layout<'a>, expr: &Expr<'a>, - kind: StoredValueKind, + kind: StoredVarKind, ) { - let sym_storage = self.storage.allocate(*layout, sym, kind); + let sym_storage = self.storage.allocate_var(*layout, sym, kind); self.expr(sym, expr, layout, &sym_storage); @@ -817,10 +815,10 @@ impl<'a> WasmBackend<'a> { // make locals for join pointer parameters let mut jp_param_storages = Vec::with_capacity_in(parameters.len(), self.env.arena); for parameter in parameters.iter() { - let mut param_storage = self.storage.allocate( + let mut param_storage = self.storage.allocate_var( parameter.layout, parameter.symbol, - StoredValueKind::Variable, + StoredVarKind::Variable, ); param_storage = self.storage.ensure_value_has_local( &mut self.code_builder, @@ -1019,7 +1017,7 @@ impl<'a> WasmBackend<'a> { let (upper_bits, lower_bits) = RocDec::from_ne_bytes(*bytes).as_bits(); write128(lower_bits as i64, upper_bits); } - Literal::Int(x) => { + Literal::Int(x) | Literal::U128(x) => { let lower_bits = (i128::from_ne_bytes(*x) & 0xffff_ffff_ffff_ffff) as i64; let upper_bits = (i128::from_ne_bytes(*x) >> 64) as i64; write128(lower_bits, upper_bits); @@ -1072,7 +1070,8 @@ impl<'a> WasmBackend<'a> { self.code_builder.i32_store(Align::Bytes4, offset + 8); }; } - _ => invalid_error(), + // Bools and bytes should not be stored in the stack frame + Literal::Bool(_) | Literal::Byte(_) => invalid_error(), } } @@ -1420,7 +1419,7 @@ impl<'a> WasmBackend<'a> { elem_sym, elem_layout, &expr, - StoredValueKind::Variable, + StoredVarKind::Variable, ); elem_sym diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 84c3a96bda..37083ecd48 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -578,10 +578,39 @@ impl<'a> LowLevelCall<'a> { _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), } } - NumSin => todo!("{:?}", self.lowlevel), - NumCos => todo!("{:?}", self.lowlevel), - NumSqrtUnchecked => todo!("{:?}", self.lowlevel), - NumLogUnchecked => todo!("{:?}", self.lowlevel), + NumSin => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_SIN[width]); + } + _ => panic_ret_type(), + }, + NumCos => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_COS[width]); + } + _ => panic_ret_type(), + }, + NumSqrtUnchecked => { + self.load_args(backend); + match self.ret_layout { + Layout::Builtin(Builtin::Float(FloatWidth::F32)) => { + backend.code_builder.f32_sqrt() + } + Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { + backend.code_builder.f64_sqrt() + } + Layout::Builtin(Builtin::Float(FloatWidth::F128)) => { + todo!("sqrt for f128") + } + _ => panic_ret_type(), + } + } + NumLogUnchecked => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_LOG[width]); + } + _ => panic_ret_type(), + }, NumToFrac => { self.load_args(backend); let ret_type = CodeGenNumType::from(self.ret_layout); @@ -600,7 +629,12 @@ impl<'a> LowLevelCall<'a> { _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), } } - NumPow => todo!("{:?}", self.lowlevel), + NumPow => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_POW[width]); + } + _ => panic_ret_type(), + }, NumRound => { self.load_args(backend); let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); @@ -687,8 +721,8 @@ impl<'a> LowLevelCall<'a> { } _ => panic_ret_type(), }, - NumBytesToU16 => todo!("{:?}", self.lowlevel), - NumBytesToU32 => todo!("{:?}", self.lowlevel), + NumBytesToU16 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U16), + NumBytesToU32 => self.load_args_and_call_zig(backend, bitcode::NUM_BYTES_TO_U32), NumBitwiseAnd => { self.load_args(backend); match CodeGenNumType::from(self.ret_layout) { @@ -785,7 +819,36 @@ impl<'a> LowLevelCall<'a> { todo!("implement toF32 and toF64"); } NumToIntChecked => { - todo!() + let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]]; + + let (arg_width, ret_width) = match (arg_layout, self.ret_layout) { + ( + Layout::Builtin(Builtin::Int(arg_width)), + Layout::Struct { + field_layouts: &[Layout::Builtin(Builtin::Int(ret_width)), ..], + .. + }, + ) => (arg_width, ret_width), + _ => { + internal_error!( + "NumToIntChecked is not defined for signature {:?} -> {:?}", + arg_layout, + self.ret_layout + ); + } + }; + + if arg_width.is_signed() { + self.load_args_and_call_zig( + backend, + &bitcode::NUM_INT_TO_INT_CHECKING_MAX_AND_MIN[ret_width][arg_width], + ) + } else { + self.load_args_and_call_zig( + backend, + &bitcode::NUM_INT_TO_INT_CHECKING_MAX[ret_width][arg_width], + ) + } } NumToFloatChecked => { todo!("implement toF32Checked and toF64Checked"); @@ -938,16 +1001,7 @@ impl<'a> LowLevelCall<'a> { locations: [StackMemoryLocation; 2], ) { match format { - StackMemoryFormat::Decimal => { - // Both args are finite - num_is_finite(backend, self.arguments[0]); - num_is_finite(backend, self.arguments[1]); - backend.code_builder.i32_and(); - - // AND they have the same bytes - Self::eq_num128_bytes(backend, locations); - backend.code_builder.i32_and(); - } + StackMemoryFormat::Decimal => Self::eq_num128_bytes(backend, locations), StackMemoryFormat::Int128 => Self::eq_num128_bytes(backend, locations), diff --git a/compiler/gen_wasm/src/storage.rs b/compiler/gen_wasm/src/storage.rs index c41b4d44b8..5374d41acc 100644 --- a/compiler/gen_wasm/src/storage.rs +++ b/compiler/gen_wasm/src/storage.rs @@ -10,8 +10,7 @@ use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; use crate::wasm_module::{Align, CodeBuilder, LocalId, ValueType, VmSymbolState}; use crate::{copy_memory, round_up_to_alignment, CopyMemoryConfig, PTR_TYPE}; -pub enum StoredValueKind { - Parameter, +pub enum StoredVarKind { Variable, ReturnValue, } @@ -124,7 +123,7 @@ impl<'a> Storage<'a> { id } - /// Allocate storage for a Roc value + /// Allocate storage for a Roc variable /// /// Wasm primitives (i32, i64, f32, f64) are allocated "storage" on the VM stack. /// This is really just a way to model how the stack machine works as a sort of @@ -134,31 +133,21 @@ impl<'a> Storage<'a> { /// /// Structs and Tags are stored in memory rather than in Wasm primitives. /// They are allocated a certain offset and size in the stack frame. - pub fn allocate( + pub fn allocate_var( &mut self, layout: Layout<'a>, symbol: Symbol, - kind: StoredValueKind, + kind: StoredVarKind, ) -> StoredValue { let next_local_id = self.get_next_local_id(); let wasm_layout = WasmLayout::new(&layout); self.symbol_layouts.insert(symbol, layout); let storage = match wasm_layout { - WasmLayout::Primitive(value_type, size) => match kind { - StoredValueKind::Parameter => { - self.arg_types.push(value_type); - StoredValue::Local { - local_id: next_local_id, - value_type, - size, - } - } - _ => StoredValue::VirtualMachineStack { - vm_state: VmSymbolState::NotYetPushed, - value_type, - size, - }, + WasmLayout::Primitive(value_type, size) => StoredValue::VirtualMachineStack { + vm_state: VmSymbolState::NotYetPushed, + value_type, + size, }, WasmLayout::StackMemory { @@ -167,18 +156,7 @@ impl<'a> Storage<'a> { format, } => { let location = match kind { - StoredValueKind::Parameter => { - if size > 0 { - self.arg_types.push(PTR_TYPE); - StackMemoryLocation::PointerArg(next_local_id) - } else { - // An argument with zero size is purely conceptual, and will not exist in Wasm. - // However we need to track the symbol, so we treat it like a local variable. - StackMemoryLocation::FrameOffset(0) - } - } - - StoredValueKind::Variable => { + StoredVarKind::Variable => { if self.stack_frame_pointer.is_none() && size > 0 { self.stack_frame_pointer = Some(next_local_id); self.local_types.push(PTR_TYPE); @@ -192,7 +170,7 @@ impl<'a> Storage<'a> { StackMemoryLocation::FrameOffset(offset as u32) } - StoredValueKind::ReturnValue => StackMemoryLocation::PointerArg(LocalId(0)), + StoredVarKind::ReturnValue => StackMemoryLocation::PointerArg(LocalId(0)), }; StoredValue::StackMemory { @@ -209,6 +187,98 @@ impl<'a> Storage<'a> { storage } + /// Allocate storage for a Roc procedure argument + /// Each argument is also a local variable. Their indices come before other locals. + /// Structs and Tags are passed as pointers into the caller's frame + /// 128-bit numbers are passed as two i64's, but we immediately store them in the + /// stack frame, because it's a lot easier to keep track of the data flow. + pub fn allocate_args( + &mut self, + args: &[(Layout<'a>, Symbol)], + code_builder: &mut CodeBuilder, + arena: &'a Bump, + ) { + let mut wide_number_args = Vec::with_capacity_in(args.len(), arena); + + for (layout, symbol) in args { + self.symbol_layouts.insert(*symbol, *layout); + let wasm_layout = WasmLayout::new(layout); + let local_index = self.arg_types.len() as u32; + + let storage = match wasm_layout { + WasmLayout::Primitive(value_type, size) => { + self.arg_types.push(value_type); + StoredValue::Local { + local_id: LocalId(local_index), + value_type, + size, + } + } + WasmLayout::StackMemory { + size, + alignment_bytes, + format, + } => { + use StackMemoryFormat::*; + + self.arg_types + .extend_from_slice(CallConv::C.stack_memory_arg_types(size, format)); + + let location = match format { + Int128 | Float128 | Decimal => { + // passed as two i64's but stored in the stack frame + wide_number_args.push(local_index); + let loc = + StackMemoryLocation::FrameOffset(self.stack_frame_size as u32); + self.stack_frame_size += size as i32; + loc + } + DataStructure => { + 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. + StackMemoryLocation::FrameOffset(0) + } else { + StackMemoryLocation::PointerArg(LocalId(local_index)) + } + } + }; + + StoredValue::StackMemory { + location, + size, + alignment_bytes, + format, + } + } + }; + + self.symbol_storage_map.insert(*symbol, storage.clone()); + } + + // 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 { + let frame_ptr = LocalId(self.arg_types.len() as u32); + self.stack_frame_pointer = Some(frame_ptr); + self.local_types.push(PTR_TYPE); + + let mut offset = 0; + for arg_index in wide_number_args.iter().copied() { + code_builder.get_local(frame_ptr); + code_builder.get_local(LocalId(arg_index)); + code_builder.i64_store(Align::Bytes8, offset); + + code_builder.get_local(frame_ptr); + code_builder.get_local(LocalId(arg_index + 1)); + code_builder.i64_store(Align::Bytes8, offset + 8); + + offset += 16; + } + } + } + /// Get storage info for a given symbol pub fn get(&self, sym: &Symbol) -> &StoredValue { self.symbol_storage_map.get(sym).unwrap_or_else(|| { @@ -471,18 +541,21 @@ impl<'a> Storage<'a> { alignment_bytes, .. } => { - let (from_ptr, from_offset) = location.local_and_offset(self.stack_frame_pointer); - copy_memory( - code_builder, - CopyMemoryConfig { - from_ptr, - from_offset, - to_ptr, - to_offset, - size, - alignment_bytes, - }, - ); + if size > 0 { + let (from_ptr, from_offset) = + location.local_and_offset(self.stack_frame_pointer); + copy_memory( + code_builder, + CopyMemoryConfig { + from_ptr, + from_offset, + to_ptr, + to_offset, + size, + alignment_bytes, + }, + ); + } size } diff --git a/compiler/test_gen/src/gen_num.rs b/compiler/test_gen/src/gen_num.rs index 466dccd5b2..acb06d839a 100644 --- a/compiler/test_gen/src/gen_num.rs +++ b/compiler/test_gen/src/gen_num.rs @@ -468,7 +468,7 @@ fn f32_float_alias() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_sqrt() { assert_evals_to!( indoc!( @@ -484,7 +484,7 @@ fn f64_sqrt() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log() { assert_evals_to!( indoc!( @@ -498,7 +498,7 @@ fn f64_log() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_checked_one() { assert_evals_to!( indoc!( @@ -514,7 +514,7 @@ fn f64_log_checked_one() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_sqrt_zero() { assert_evals_to!( indoc!( @@ -530,7 +530,7 @@ fn f64_sqrt_zero() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_sqrt_checked_negative() { assert_evals_to!( indoc!( @@ -546,7 +546,7 @@ fn f64_sqrt_checked_negative() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_checked_zero() { assert_evals_to!( indoc!( @@ -562,7 +562,7 @@ fn f64_log_checked_zero() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn f64_log_negative() { assert_evals_to!( indoc!( @@ -1170,21 +1170,21 @@ fn gen_is_even() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn sin() { assert_evals_to!("Num.sin 0", 0.0, f64); assert_evals_to!("Num.sin 1.41421356237", 0.9877659459922529, f64); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn cos() { assert_evals_to!("Num.cos 0", 1.0, f64); assert_evals_to!("Num.cos 3.14159265359", -1.0, f64); } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn tan() { assert_evals_to!("Num.tan 0", 0.0, f64); assert_evals_to!("Num.tan 1", 1.557407724654902, f64); @@ -1634,7 +1634,7 @@ fn float_compare() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn pow() { assert_evals_to!("Num.pow 2.0 2.0", 4.0, f64); } @@ -2487,7 +2487,7 @@ num_conversion_tests! { macro_rules! to_int_checked_tests { ($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr)*))*) => {$($( #[test] - #[cfg(any(feature = "gen-llvm"))] + #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn $test_name() { let sentinel = 23; // Some n = Ok n, None = OutOfBounds @@ -2647,7 +2647,7 @@ fn is_multiple_of() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u16_clearly_out_of_bounds() { assert_evals_to!( indoc!( @@ -2664,7 +2664,7 @@ fn bytes_to_u16_clearly_out_of_bounds() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u16_subtly_out_of_bounds() { assert_evals_to!( indoc!( @@ -2681,7 +2681,7 @@ fn bytes_to_u16_subtly_out_of_bounds() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u32_clearly_out_of_bounds() { assert_evals_to!( indoc!( @@ -2698,7 +2698,7 @@ fn bytes_to_u32_clearly_out_of_bounds() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u32_subtly_out_of_bounds() { assert_evals_to!( indoc!( @@ -2715,7 +2715,7 @@ fn bytes_to_u32_subtly_out_of_bounds() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u16_max_u8s() { assert_evals_to!( indoc!( @@ -2731,7 +2731,7 @@ fn bytes_to_u16_max_u8s() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u16_min_u8s() { assert_evals_to!( indoc!( @@ -2747,7 +2747,7 @@ fn bytes_to_u16_min_u8s() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u16_random_u8s() { assert_evals_to!( indoc!( @@ -2763,7 +2763,7 @@ fn bytes_to_u16_random_u8s() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u32_min_u8s() { assert_evals_to!( indoc!( @@ -2779,7 +2779,7 @@ fn bytes_to_u32_min_u8s() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u32_max_u8s() { assert_evals_to!( indoc!( @@ -2795,7 +2795,7 @@ fn bytes_to_u32_max_u8s() { } #[test] -#[cfg(any(feature = "gen-llvm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn bytes_to_u32_random_u8s() { assert_evals_to!( indoc!(