diff --git a/compiler/gen_wasm/src/backend.rs b/compiler/gen_wasm/src/backend.rs index c68d14529c..d9cc9316d8 100644 --- a/compiler/gen_wasm/src/backend.rs +++ b/compiler/gen_wasm/src/backend.rs @@ -16,7 +16,7 @@ use roc_mono::layout::{Builtin, Layout, LayoutIds, TagIdIntType, UnionLayout}; use roc_reporting::internal_error; use crate::layout::{CallConv, ReturnMethod, StackMemoryFormat, WasmLayout}; -use crate::low_level::{dispatch_low_level, LowlevelBuildResult}; +use crate::low_level::LowLevelCall; use crate::storage::{StackMemoryLocation, Storage, StoredValue, StoredValueKind}; use crate::wasm_module::linking::{DataSymbol, LinkingSegment, WasmObjectSymbol}; use crate::wasm_module::sections::{DataMode, DataSegment}; @@ -35,7 +35,7 @@ use crate::{ const CONST_SEGMENT_BASE_ADDR: u32 = 1024; pub struct WasmBackend<'a> { - env: &'a Env<'a>, + pub env: &'a Env<'a>, interns: &'a mut Interns, // Module-level data @@ -48,8 +48,8 @@ pub struct WasmBackend<'a> { helper_proc_gen: CodeGenHelp<'a>, // Function-level data - code_builder: CodeBuilder<'a>, - storage: Storage<'a>, + pub code_builder: CodeBuilder<'a>, + pub storage: Storage<'a>, /// how many blocks deep are we (used for jumps) block_depth: u32, @@ -805,21 +805,21 @@ impl<'a> WasmBackend<'a> { &mut self, lowlevel: LowLevel, arguments: &'a [Symbol], - return_sym: Symbol, - mono_layout: &Layout<'a>, - storage: &StoredValue, + ret_symbol: Symbol, + ret_layout: &Layout<'a>, + ret_storage: &StoredValue, ) { use LowLevel::*; - let return_layout = WasmLayout::new(mono_layout); + let wasm_layout = WasmLayout::new(ret_layout); match lowlevel { Eq | NotEq => self.build_eq_or_neq( lowlevel, arguments, - return_sym, - return_layout, - mono_layout, - storage, + ret_symbol, + wasm_layout, + ret_layout, + ret_storage, ), PtrCast => { // Don't want Zig calling convention when casting pointers. @@ -829,37 +829,14 @@ impl<'a> WasmBackend<'a> { // Almost all lowlevels take this branch, except for the special cases above _ => { - // Load the arguments using Zig calling convention - let (param_types, ret_type) = self.storage.load_symbols_for_call( - self.env.arena, - &mut self.code_builder, - arguments, - return_sym, - &return_layout, - CallConv::Zig, - ); - - // Generate instructions OR decide which Zig function to call - let build_result = dispatch_low_level( - &mut self.code_builder, - &mut self.storage, + let low_level_call = LowLevelCall { lowlevel, arguments, - &return_layout, - mono_layout, - ); - - // Handle the result - use LowlevelBuildResult::*; - match build_result { - Done => {} - BuiltinCall(name) => { - self.expr_call_zig_builtin(name, param_types, ret_type); - } - NotImplemented => { - todo!("Low level operation {:?}", lowlevel) - } - } + ret_symbol, + ret_layout: ret_layout.to_owned(), + ret_storage: ret_storage.to_owned(), + }; + low_level_call.generate(self); } } } @@ -867,7 +844,7 @@ impl<'a> WasmBackend<'a> { /// Generate a call instruction to a Zig builtin function. /// And if we haven't seen it before, add an Import and linker data for it. /// Zig calls use LLVM's "fast" calling convention rather than our usual C ABI. - fn expr_call_zig_builtin( + pub fn call_zig_builtin_after_loading_args( &mut self, name: &'a str, param_types: Vec<'a, ValueType>, @@ -992,7 +969,7 @@ impl<'a> WasmBackend<'a> { // Call the foreign function. (Zig and C calling conventions are the same for this signature) let param_types = bumpalo::vec![in self.env.arena; ValueType::I32, ValueType::I32]; let ret_type = Some(ValueType::I32); - self.expr_call_zig_builtin("roc_alloc", param_types, ret_type); + self.call_zig_builtin_after_loading_args("roc_alloc", param_types, ret_type); // Save the allocation address to a temporary local variable let local_id = self.storage.create_anonymous_local(ValueType::I32); @@ -1368,7 +1345,7 @@ impl<'a> WasmBackend<'a> { &return_layout, CallConv::Zig, ); - self.expr_call_zig_builtin(bitcode::STR_EQUAL, param_types, ret_type); + self.call_zig_builtin_after_loading_args(bitcode::STR_EQUAL, param_types, ret_type); if matches!(lowlevel, LowLevel::NotEq) { self.code_builder.i32_eqz(); } @@ -1472,22 +1449,25 @@ impl<'a> WasmBackend<'a> { // Both args are finite let first = [arguments[0]]; let second = [arguments[1]]; - dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - LowLevel::NumIsFinite, - &first, - &return_layout, - mono_layout, - ); - dispatch_low_level( - &mut self.code_builder, - &mut self.storage, - LowLevel::NumIsFinite, - &second, - &return_layout, - mono_layout, - ); + + // TODO! + // + // dispatch_low_level( + // &mut self.code_builder, + // &mut self.storage, + // LowLevel::NumIsFinite, + // &first, + // &return_layout, + // mono_layout, + // ); + // dispatch_low_level( + // &mut self.code_builder, + // &mut self.storage, + // LowLevel::NumIsFinite, + // &second, + // &return_layout, + // mono_layout, + // ); self.code_builder.i32_and(); // AND they have the same bytes diff --git a/compiler/gen_wasm/src/low_level.rs b/compiler/gen_wasm/src/low_level.rs index 66947d71fb..4195e05261 100644 --- a/compiler/gen_wasm/src/low_level.rs +++ b/compiler/gen_wasm/src/low_level.rs @@ -1,25 +1,15 @@ +use bumpalo::collections::Vec; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_module::low_level::{LowLevel, LowLevel::*}; use roc_module::symbol::Symbol; use roc_mono::layout::{Builtin, Layout}; use roc_reporting::internal_error; -use crate::layout::{ - StackMemoryFormat::{self, *}, - WasmLayout, -}; -use crate::storage::{Storage, StoredValue}; -use crate::wasm_module::{ - Align, CodeBuilder, - ValueType::{self, *}, -}; - -#[derive(Debug)] -pub enum LowlevelBuildResult { - Done, - BuiltinCall(&'static str), - NotImplemented, -} +use crate::backend::WasmBackend; +use crate::layout::CallConv; +use crate::layout::{StackMemoryFormat, WasmLayout}; +use crate::storage::StoredValue; +use crate::wasm_module::{Align, ValueType}; /// Number types used for Wasm code gen /// Unlike other enums, this contains no details about layout or storage. @@ -29,7 +19,8 @@ pub enum LowlevelBuildResult { /// the smallest integer supported in the Wasm instruction set. /// We may choose different instructions for signed and unsigned integers, /// but they share the same Wasm value type. -enum CodeGenNumber { +#[derive(Clone, Copy, Debug)] +enum CodeGenNumType { I32, // Supported in Wasm instruction set I64, // Supported in Wasm instruction set F32, // Supported in Wasm instruction set @@ -39,9 +30,15 @@ enum CodeGenNumber { Decimal, // Bytes in memory, needs Zig builtins } -impl From> for CodeGenNumber { - fn from(layout: Layout) -> CodeGenNumber { - use CodeGenNumber::*; +impl CodeGenNumType { + pub fn for_symbol<'a>(backend: &WasmBackend<'a>, symbol: Symbol) -> Self { + Self::from(backend.storage.get(&symbol)) + } +} + +impl From> for CodeGenNumType { + fn from(layout: Layout) -> CodeGenNumType { + use CodeGenNumType::*; let not_num_error = || internal_error!("Tried to perform a Num low-level operation on {:?}", layout); @@ -72,23 +69,23 @@ impl From> for CodeGenNumber { } } -impl From for CodeGenNumber { - fn from(value_type: ValueType) -> CodeGenNumber { +impl From for CodeGenNumType { + fn from(value_type: ValueType) -> CodeGenNumType { match value_type { - ValueType::I32 => CodeGenNumber::I32, - ValueType::I64 => CodeGenNumber::I64, - ValueType::F32 => CodeGenNumber::F32, - ValueType::F64 => CodeGenNumber::F64, + ValueType::I32 => CodeGenNumType::I32, + ValueType::I64 => CodeGenNumType::I64, + ValueType::F32 => CodeGenNumType::F32, + ValueType::F64 => CodeGenNumType::F64, } } } -impl From for CodeGenNumber { - fn from(format: StackMemoryFormat) -> CodeGenNumber { +impl From for CodeGenNumType { + fn from(format: StackMemoryFormat) -> CodeGenNumType { match format { - StackMemoryFormat::Int128 => CodeGenNumber::I128, - StackMemoryFormat::Float128 => CodeGenNumber::F128, - StackMemoryFormat::Decimal => CodeGenNumber::Decimal, + StackMemoryFormat::Int128 => CodeGenNumType::I128, + StackMemoryFormat::Float128 => CodeGenNumType::F128, + StackMemoryFormat::Decimal => CodeGenNumType::Decimal, StackMemoryFormat::DataStructure => { internal_error!("Tried to perform a Num low-level operation on a data structure") } @@ -96,586 +93,636 @@ impl From for CodeGenNumber { } } -impl From for CodeGenNumber { - fn from(wasm_layout: WasmLayout) -> CodeGenNumber { +impl From for CodeGenNumType { + fn from(wasm_layout: WasmLayout) -> CodeGenNumType { match wasm_layout { - WasmLayout::Primitive(value_type, _) => CodeGenNumber::from(value_type), - WasmLayout::StackMemory { format, .. } => CodeGenNumber::from(format), + WasmLayout::Primitive(value_type, _) => CodeGenNumType::from(value_type), + WasmLayout::StackMemory { format, .. } => CodeGenNumType::from(format), } } } -impl From for CodeGenNumber { - fn from(stored: StoredValue) -> CodeGenNumber { +impl From<&StoredValue> for CodeGenNumType { + fn from(stored: &StoredValue) -> CodeGenNumType { + use StoredValue::*; match stored { - StoredValue::VirtualMachineStack { value_type, .. } => CodeGenNumber::from(value_type), - StoredValue::Local { value_type, .. } => CodeGenNumber::from(value_type), - StoredValue::StackMemory { format, .. } => CodeGenNumber::from(format), + VirtualMachineStack { value_type, .. } => CodeGenNumType::from(*value_type), + Local { value_type, .. } => CodeGenNumType::from(*value_type), + StackMemory { format, .. } => CodeGenNumType::from(*format), } } } -pub fn dispatch_low_level<'a>( - code_builder: &mut CodeBuilder<'a>, - storage: &mut Storage<'a>, - lowlevel: LowLevel, - args: &[Symbol], - ret_layout: &WasmLayout, - mono_layout: &Layout<'a>, -) -> LowlevelBuildResult { - use LowlevelBuildResult::*; +pub struct LowLevelCall<'a> { + pub lowlevel: LowLevel, + pub arguments: &'a [Symbol], + pub ret_symbol: Symbol, + pub ret_layout: Layout<'a>, + pub ret_storage: StoredValue, +} - let panic_ret_type = - || internal_error!("Invalid return layout for {:?}: {:?}", lowlevel, ret_layout); +impl<'a> LowLevelCall<'a> { + /// Load symbol values for a Zig call or numerical operation + /// For numerical ops, this just pushes the arguments to the Wasm VM's value stack + /// It implements the calling convention used by Zig for both numbers and structs + /// When returning structs, it also loads a stack frame pointer for the return value + fn load_args(&self, backend: &mut WasmBackend<'a>) -> (Vec<'a, ValueType>, Option) { + let fn_signature = backend.storage.load_symbols_for_call( + backend.env.arena, + &mut backend.code_builder, + self.arguments, + self.ret_symbol, + &WasmLayout::new(&self.ret_layout), + CallConv::Zig, + ); + fn_signature + } - match lowlevel { - // Str - StrConcat => return BuiltinCall(bitcode::STR_CONCAT), - StrJoinWith => return BuiltinCall(bitcode::STR_JOIN_WITH), - StrIsEmpty => { - code_builder.i64_const(i64::MIN); - code_builder.i64_eq(); + fn load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) { + let (param_types, ret_type) = self.load_args(backend); + backend.call_zig_builtin_after_loading_args(name, param_types, ret_type); + } + + /// Wrap an integer whose Wasm representation is i32 + /// This may seem like deliberately introducing an error! + /// But we want all targets to behave the same, and hash algos rely on wrapping. + /// Discussion: https://github.com/rtfeldman/roc/pull/2117#discussion_r760723063 + fn wrap_i32(&self, backend: &mut WasmBackend<'a>) { + let invalid = + || internal_error!("Expected integer <= 32 bits, found {:?}", self.ret_layout); + + let (shift, is_signed) = match self.ret_layout { + Layout::Builtin(Builtin::Int(int_width)) => match int_width { + IntWidth::U8 => (24, false), + IntWidth::U16 => (16, false), + IntWidth::I8 => (24, true), + IntWidth::I16 => (16, true), + IntWidth::I32 | IntWidth::U32 => return, + _ => invalid(), + }, + _ => invalid(), + }; + + backend.code_builder.i32_const(shift); + backend.code_builder.i32_shl(); + backend.code_builder.i32_const(shift); + if is_signed { + backend.code_builder.i32_shr_s(); + } else { + backend.code_builder.i32_shr_u(); } - StrStartsWith => return BuiltinCall(bitcode::STR_STARTS_WITH), - StrStartsWithCodePt => return BuiltinCall(bitcode::STR_STARTS_WITH_CODE_PT), - StrEndsWith => return BuiltinCall(bitcode::STR_ENDS_WITH), - StrSplit => { - // LLVM implementation (build_str.rs) does the following - // 1. Call bitcode::STR_COUNT_SEGMENTS - // 2. Allocate a `List Str` - // 3. Call bitcode::STR_STR_SPLIT_IN_PLACE - // 4. Write the elements and length of the List - // To do this here, we need full access to WasmBackend, or we could make a Zig wrapper - return NotImplemented; - } - StrCountGraphemes => return BuiltinCall(bitcode::STR_COUNT_GRAPEHEME_CLUSTERS), - StrToNum => { - let number_layout = match mono_layout { - Layout::Struct(fields) => fields[0], - _ => internal_error!("Unexpected mono layout {:?} for StrToNum", mono_layout), - }; - // match on the return layout to figure out which zig builtin we need - let intrinsic = match number_layout { - Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], - Layout::Builtin(Builtin::Float(float_width)) => &bitcode::STR_TO_FLOAT[float_width], - Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, - rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), - }; + } - return BuiltinCall(intrinsic); - } - StrFromInt => { - // This does not get exposed in user space. We switched to NumToStr instead. - // We can probably just leave this as NotImplemented. We may want remove this LowLevel. - // see: https://github.com/rtfeldman/roc/pull/2108 - return NotImplemented; - } - StrFromFloat => { - // linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3 - // https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html - // https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html - return NotImplemented; - } - StrFromUtf8 => return BuiltinCall(bitcode::STR_FROM_UTF8), - StrTrimLeft => return BuiltinCall(bitcode::STR_TRIM_LEFT), - StrTrimRight => return BuiltinCall(bitcode::STR_TRIM_RIGHT), - StrFromUtf8Range => return BuiltinCall(bitcode::STR_FROM_UTF8_RANGE), - StrToUtf8 => return BuiltinCall(bitcode::STR_TO_UTF8), - StrRepeat => return BuiltinCall(bitcode::STR_REPEAT), - StrTrim => return BuiltinCall(bitcode::STR_TRIM), + pub fn generate(&self, backend: &mut WasmBackend<'a>) { + use CodeGenNumType::*; - // List - ListLen => { - // List structure has already been loaded as i64 (Zig calling convention) - // We want the second (more significant) 32 bits. Shift and convert to i32. - code_builder.i64_const(32); - code_builder.i64_shr_u(); - code_builder.i32_wrap_i64(); - } + let panic_ret_type = || { + internal_error!( + "Invalid return layout for {:?}: {:?}", + self.lowlevel, + self.ret_layout + ) + }; - ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat - | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2 - | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil - | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListSublist - | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe | DictSize | DictEmpty - | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues - | DictUnion | DictIntersection | DictDifference | DictWalk | SetFromList => { - return NotImplemented; - } - - // Num - NumAdd => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_add(), - I64 => code_builder.i64_add(), - F32 => code_builder.f32_add(), - F64 => code_builder.f64_add(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), - }, - }, - NumAddWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_add(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_add(), - F32 => code_builder.f32_add(), - F64 => code_builder.f64_add(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_ADD_WITH_OVERFLOW), - }, - }, - NumToStr => return NotImplemented, - NumAddChecked => return NotImplemented, - NumAddSaturated => return NotImplemented, - NumSub => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_sub(), - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), - }, - }, - NumSubWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_sub(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_sub(), - F32 => code_builder.f32_sub(), - F64 => code_builder.f64_sub(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_SUB_WITH_OVERFLOW), - }, - }, - NumSubChecked => return NotImplemented, - NumSubSaturated => return NotImplemented, - NumMul => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_mul(), - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), - }, - }, - NumMulWrap => match ret_layout { - WasmLayout::Primitive(value_type, size) => match value_type { - I32 => { - code_builder.i32_mul(); - wrap_i32(code_builder, *size); - } - I64 => code_builder.i64_mul(), - F32 => code_builder.f32_mul(), - F64 => code_builder.f64_mul(), - }, - WasmLayout::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_MUL_WITH_OVERFLOW), - }, - }, - NumMulChecked => return NotImplemented, - NumGt => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_gt_s(), - I64 => code_builder.i64_gt_s(), - F32 => code_builder.f32_gt(), - F64 => code_builder.f64_gt(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumGte => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_ge_s(), - I64 => code_builder.i64_ge_s(), - F32 => code_builder.f32_ge(), - F64 => code_builder.f64_ge(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumLt => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_lt_s(), - I64 => code_builder.i64_lt_s(), - F32 => code_builder.f32_lt(), - F64 => code_builder.f64_lt(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumLte => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_le_s(), - I64 => code_builder.i64_le_s(), - F32 => code_builder.f32_le(), - F64 => code_builder.f64_le(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumCompare => return NotImplemented, - NumDivUnchecked => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_div_s(), - I64 => code_builder.i64_div_s(), - F32 => code_builder.f32_div(), - F64 => code_builder.f64_div(), - }, - StoredValue::StackMemory { format, .. } => match format { - DataStructure => return NotImplemented, - Int128 => return NotImplemented, - Float128 => return NotImplemented, - Decimal => return BuiltinCall(bitcode::DEC_DIV), - }, - }, - NumDivCeilUnchecked => return NotImplemented, - NumRemUnchecked => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => code_builder.i32_rem_s(), - I64 => code_builder.i64_rem_s(), - F32 => return NotImplemented, - F64 => return NotImplemented, - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumIsMultipleOf => return NotImplemented, - NumAbs => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - I32 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - storage.load_symbols(code_builder, args); - code_builder.i32_sub(); - storage.load_symbols(code_builder, args); - code_builder.i32_const(0); - code_builder.i32_ge_s(); - code_builder.select(); - } - I64 => { - let arg_storage = storage.get(&args[0]).to_owned(); - storage.ensure_value_has_local(code_builder, args[0], arg_storage); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - storage.load_symbols(code_builder, args); - code_builder.i64_sub(); - storage.load_symbols(code_builder, args); - code_builder.i64_const(0); - code_builder.i64_ge_s(); - code_builder.select(); - } - F32 => code_builder.f32_abs(), - F64 => code_builder.f64_abs(), - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumNeg => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.i32_const(0); - storage.load_symbols(code_builder, args); - code_builder.i32_sub(); - } - I64 => { - code_builder.i64_const(0); - storage.load_symbols(code_builder, args); - code_builder.i64_sub(); - } - F32 => code_builder.f32_neg(), - F64 => code_builder.f64_neg(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumSin => return NotImplemented, - NumCos => return NotImplemented, - NumSqrtUnchecked => return NotImplemented, - NumLogUnchecked => return NotImplemented, - NumRound => match storage.get(&args[0]) { - StoredValue::VirtualMachineStack { value_type, .. } - | StoredValue::Local { value_type, .. } => match value_type { - F32 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F32]), - F64 => return BuiltinCall(&bitcode::NUM_ROUND[FloatWidth::F64]), - _ => return NotImplemented, - }, - StoredValue::StackMemory { .. } => return NotImplemented, - }, - NumToFloat => { - use StoredValue::*; - let stored = storage.get(&args[0]); - match ret_layout { - WasmLayout::Primitive(ret_type, _) => match stored { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match (ret_type, value_type) { - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), - - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} - - _ => panic_ret_type(), - } - } - StackMemory { .. } => return NotImplemented, - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + match self.lowlevel { + // Str + StrConcat => self.load_args_and_call_zig(backend, bitcode::STR_CONCAT), + StrJoinWith => self.load_args_and_call_zig(backend, bitcode::STR_JOIN_WITH), + StrIsEmpty => { + self.load_args(backend); + backend.code_builder.i64_const(i64::MIN); + backend.code_builder.i64_eq(); } - } - NumPow => return NotImplemented, - NumCeiling => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.f32_ceil(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_ceil(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumPowInt => return NotImplemented, - NumFloor => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => { - code_builder.f32_floor(); - code_builder.i32_trunc_s_f32() - } - I64 => { - code_builder.f64_floor(); - code_builder.i64_trunc_s_f64() - } - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumIsFinite => { - use StoredValue::*; - match storage.get(&args[0]) { - VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match value_type { - I32 | I64 => code_builder.i32_const(1), // always true for integers - F32 => { - code_builder.i32_reinterpret_f32(); - code_builder.i32_const(0x7f80_0000); - code_builder.i32_and(); - code_builder.i32_const(0x7f80_0000); - code_builder.i32_ne(); - } - F64 => { - code_builder.i64_reinterpret_f64(); - code_builder.i64_const(0x7ff0_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7ff0_0000_0000_0000); - code_builder.i64_ne(); - } + StrStartsWith => self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH), + StrStartsWithCodePt => { + self.load_args_and_call_zig(backend, bitcode::STR_STARTS_WITH_CODE_PT) + } + StrEndsWith => self.load_args_and_call_zig(backend, bitcode::STR_ENDS_WITH), + StrSplit => { + // LLVM implementation (build_str.rs) does the following + // 1. Call bitcode::STR_COUNT_SEGMENTS + // 2. Allocate a `List Str` + // 3. Call bitcode::STR_STR_SPLIT_IN_PLACE + // 4. Write the elements and length of the List + // To do this here, we need full access to WasmBackend, or we could make a Zig wrapper + todo!("{:?}", self.lowlevel); + } + StrCountGraphemes => { + self.load_args_and_call_zig(backend, bitcode::STR_COUNT_GRAPEHEME_CLUSTERS) + } + StrToNum => { + let number_layout = match self.ret_layout { + Layout::Struct(fields) => fields[0], + _ => { + internal_error!("Unexpected mono layout {:?} for StrToNum", self.ret_layout) } - } - StackMemory { - format, location, .. - } => { - let (local_id, offset) = location.local_and_offset(storage.stack_frame_pointer); + }; + // match on the return layout to figure out which zig builtin we need + let intrinsic = match number_layout { + Layout::Builtin(Builtin::Int(int_width)) => &bitcode::STR_TO_INT[int_width], + Layout::Builtin(Builtin::Float(float_width)) => { + &bitcode::STR_TO_FLOAT[float_width] + } + Layout::Builtin(Builtin::Decimal) => bitcode::DEC_FROM_STR, + rest => internal_error!("Unexpected builtin {:?} for StrToNum", rest), + }; - match format { - Int128 => code_builder.i32_const(1), - Float128 => { - code_builder.get_local(local_id); - code_builder.i64_load(Align::Bytes4, offset + 8); - code_builder.i64_const(0x7fff_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7fff_0000_0000_0000); - code_builder.i64_ne(); - } - Decimal => { - code_builder.get_local(local_id); - code_builder.i64_load(Align::Bytes4, offset + 8); - code_builder.i64_const(0x7100_0000_0000_0000); - code_builder.i64_and(); - code_builder.i64_const(0x7100_0000_0000_0000); - code_builder.i64_ne(); - } - DataStructure => return NotImplemented, + self.load_args_and_call_zig(backend, intrinsic); + } + StrFromInt => { + // This does not get exposed in user space. We switched to NumToStr instead. + // We can probably just leave this as NotImplemented. We may want remove this LowLevel. + // see: https://github.com/rtfeldman/roc/pull/2108 + todo!("{:?}", self.lowlevel); + } + StrFromFloat => { + // linker errors for __ashlti3, __fixunsdfti, __multi3, __udivti3, __umodti3 + // https://gcc.gnu.org/onlinedocs/gccint/Integer-library-routines.html + // https://gcc.gnu.org/onlinedocs/gccint/Soft-float-library-routines.html + todo!("{:?}", self.lowlevel); + } + StrFromUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8), + StrTrimLeft => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_LEFT), + StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT), + StrFromUtf8Range => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8_RANGE), + StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8), + StrRepeat => self.load_args_and_call_zig(backend, bitcode::STR_REPEAT), + StrTrim => self.load_args_and_call_zig(backend, bitcode::STR_TRIM), + + // List + ListLen => match backend.storage.get(&self.arguments[0]) { + StoredValue::StackMemory { location, .. } => { + let (local_id, offset) = + location.local_and_offset(backend.storage.stack_frame_pointer); + backend.code_builder.get_local(local_id); + backend.code_builder.i32_load(Align::Bytes4, offset + 4); + } + _ => internal_error!("invalid storage for List"), + }, + + ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat + | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap + | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk + | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith + | ListSublist | ListDropAt | ListSwap | ListAny | ListAll | ListFindUnsafe + | DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe + | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | DictWalk + | SetFromList => { + todo!("{:?}", self.lowlevel); + } + + // Num + NumAdd => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + CodeGenNumType::I32 => backend.code_builder.i32_add(), + CodeGenNumType::I64 => backend.code_builder.i64_add(), + CodeGenNumType::F32 => backend.code_builder.f32_add(), + CodeGenNumType::F64 => backend.code_builder.f64_add(), + CodeGenNumType::I128 => todo!("{:?}", self.lowlevel), + CodeGenNumType::F128 => todo!("{:?}", self.lowlevel), + CodeGenNumType::Decimal => { + self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW) } } } - } - NumAtan => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ATAN[width]); - } - NumAcos => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ACOS[width]); - } - NumAsin => { - let width = float_width_from_layout(ret_layout); - return BuiltinCall(&bitcode::NUM_ASIN[width]); - } - NumBytesToU16 => return NotImplemented, - NumBytesToU32 => return NotImplemented, - NumBitwiseAnd => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_and(), - I64 => code_builder.i64_and(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumBitwiseXor => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_xor(), - I64 => code_builder.i64_xor(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumBitwiseOr => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_or(), - I64 => code_builder.i64_or(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumShiftLeftBy => { - // Swap order of arguments - storage.load_symbols(code_builder, &[args[1], args[0]]); - match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shl(), - I64 => code_builder.i64_shl(), + + NumAddWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_add(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_add(), + F32 => backend.code_builder.f32_add(), + F64 => backend.code_builder.f64_add(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_ADD_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumToStr => todo!("{:?}", self.lowlevel), + NumAddChecked => todo!("{:?}", self.lowlevel), + NumAddSaturated => todo!("{:?}", self.lowlevel), + NumSub => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_sub(), + I64 => backend.code_builder.i64_sub(), + F32 => backend.code_builder.f32_sub(), + F64 => backend.code_builder.f64_sub(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumSubWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_sub(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_sub(), + F32 => backend.code_builder.f32_sub(), + F64 => backend.code_builder.f64_sub(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_SUB_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumSubChecked => todo!("{:?}", self.lowlevel), + NumSubSaturated => todo!("{:?}", self.lowlevel), + NumMul => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_mul(), + I64 => backend.code_builder.i64_mul(), + F32 => backend.code_builder.f32_mul(), + F64 => backend.code_builder.f64_mul(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_MUL_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumMulWrap => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_mul(); + self.wrap_i32(backend); + } + I64 => backend.code_builder.i64_mul(), + F32 => backend.code_builder.f32_mul(), + F64 => backend.code_builder.f64_mul(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_MUL_WITH_OVERFLOW), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumMulChecked => todo!("{:?}", self.lowlevel), + NumGt => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_gt_s(), + I64 => backend.code_builder.i64_gt_s(), + F32 => backend.code_builder.f32_gt(), + F64 => backend.code_builder.f64_gt(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumGte => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_ge_s(), + I64 => backend.code_builder.i64_ge_s(), + F32 => backend.code_builder.f32_ge(), + F64 => backend.code_builder.f64_ge(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumLt => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_lt_s(), + I64 => backend.code_builder.i64_lt_s(), + F32 => backend.code_builder.f32_lt(), + F64 => backend.code_builder.f64_lt(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumLte => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_le_s(), + I64 => backend.code_builder.i64_le_s(), + F32 => backend.code_builder.f32_le(), + F64 => backend.code_builder.f64_le(), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumCompare => todo!("{:?}", self.lowlevel), + NumDivUnchecked => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_div_s(), + I64 => backend.code_builder.i64_div_s(), + F32 => backend.code_builder.f32_div(), + F64 => backend.code_builder.f64_div(), + Decimal => self.load_args_and_call_zig(backend, bitcode::DEC_DIV), + x => todo!("{:?} for {:?}", self.lowlevel, x), + } + } + NumDivCeilUnchecked => todo!("{:?}", self.lowlevel), + NumRemUnchecked => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => backend.code_builder.i32_rem_s(), + I64 => backend.code_builder.i64_rem_s(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumIsMultipleOf => todo!("{:?}", self.lowlevel), + NumAbs => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + I32 => { + let code_builder = &mut backend.code_builder; + let arg_storage = backend.storage.get(&self.arguments[0]).to_owned(); + backend.storage.ensure_value_has_local( + code_builder, + self.arguments[0], + arg_storage, + ); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_const(0); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_sub(); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i32_const(0); + code_builder.i32_ge_s(); + code_builder.select(); + } + I64 => { + let code_builder = &mut backend.code_builder; + let arg_storage = backend.storage.get(&self.arguments[0]).to_owned(); + backend.storage.ensure_value_has_local( + code_builder, + self.arguments[0], + arg_storage, + ); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_const(0); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_sub(); + backend.storage.load_symbols(code_builder, self.arguments); + code_builder.i64_const(0); + code_builder.i64_ge_s(); + code_builder.select(); + } + F32 => backend.code_builder.f32_abs(), + F64 => backend.code_builder.f64_abs(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumNeg => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.i32_const(0); + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + backend.code_builder.i32_sub(); + } + I64 => { + backend.code_builder.i64_const(0); + backend + .storage + .load_symbols(&mut backend.code_builder, self.arguments); + backend.code_builder.i64_sub(); + } + F32 => backend.code_builder.f32_neg(), + F64 => backend.code_builder.f64_neg(), + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumSin => todo!("{:?}", self.lowlevel), + NumCos => todo!("{:?}", self.lowlevel), + NumSqrtUnchecked => todo!("{:?}", self.lowlevel), + NumLogUnchecked => todo!("{:?}", self.lowlevel), + NumRound => { + self.load_args(backend); + match CodeGenNumType::for_symbol(backend, self.arguments[0]) { + F32 => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F32]) + } + F64 => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND[FloatWidth::F64]) + } + _ => todo!("{:?} for {:?}", self.lowlevel, self.ret_layout), + } + } + NumToFloat => { + self.load_args(backend); + let ret_type = CodeGenNumType::from(self.ret_layout); + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + match (ret_type, arg_type) { + (F32, I32) => backend.code_builder.f32_convert_s_i32(), + (F32, I64) => backend.code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => backend.code_builder.f32_demote_f64(), + + (F64, I32) => backend.code_builder.f64_convert_s_i32(), + (F64, I64) => backend.code_builder.f64_convert_s_i64(), + (F64, F32) => backend.code_builder.f64_promote_f32(), + (F64, F64) => {} + + _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), + } + } + NumPow => todo!("{:?}", self.lowlevel), + NumCeiling => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.f32_ceil(); + backend.code_builder.i32_trunc_s_f32() + } + I64 => { + backend.code_builder.f64_ceil(); + backend.code_builder.i64_trunc_s_f64() + } _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + } } - } - NumShiftRightBy => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shr_s(), - I64 => code_builder.i64_shr_s(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumShiftRightZfBy => match ret_layout { - WasmLayout::Primitive(value_type, _) => match value_type { - I32 => code_builder.i32_shr_u(), - I64 => code_builder.i64_shr_u(), - _ => panic_ret_type(), - }, - WasmLayout::StackMemory { .. } => return NotImplemented, - }, - NumIntCast => { - use StoredValue::*; - let stored = storage.get(&args[0]); - match ret_layout { - WasmLayout::Primitive(ret_type, _) => match stored { + NumPowInt => todo!("{:?}", self.lowlevel), + NumFloor => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => { + backend.code_builder.f32_floor(); + backend.code_builder.i32_trunc_s_f32() + } + I64 => { + backend.code_builder.f64_floor(); + backend.code_builder.i64_trunc_s_f64() + } + _ => panic_ret_type(), + } + } + NumIsFinite => { + use StoredValue::*; + self.load_args(backend); + match backend.storage.get(&self.arguments[0]) { VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { - match (ret_type, value_type) { - (I32, I32) => {} - (I32, I64) => code_builder.i32_wrap_i64(), - (I32, F32) => code_builder.i32_trunc_s_f32(), - (I32, F64) => code_builder.i32_trunc_s_f64(), - - (I64, I32) => code_builder.i64_extend_s_i32(), - (I64, I64) => {} - (I64, F32) => code_builder.i64_trunc_s_f32(), - (I64, F64) => code_builder.i64_trunc_s_f64(), - - (F32, I32) => code_builder.f32_convert_s_i32(), - (F32, I64) => code_builder.f32_convert_s_i64(), - (F32, F32) => {} - (F32, F64) => code_builder.f32_demote_f64(), - - (F64, I32) => code_builder.f64_convert_s_i32(), - (F64, I64) => code_builder.f64_convert_s_i64(), - (F64, F32) => code_builder.f64_promote_f32(), - (F64, F64) => {} + match value_type { + ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), // always true for integers + ValueType::F32 => { + backend.code_builder.i32_reinterpret_f32(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_and(); + backend.code_builder.i32_const(0x7f80_0000); + backend.code_builder.i32_ne(); + } + ValueType::F64 => { + backend.code_builder.i64_reinterpret_f64(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7ff0_0000_0000_0000); + backend.code_builder.i64_ne(); + } } } + StackMemory { + format, location, .. + } => { + let (local_id, offset) = + location.local_and_offset(backend.storage.stack_frame_pointer); - StackMemory { .. } => return NotImplemented, - }, - WasmLayout::StackMemory { .. } => return NotImplemented, + match format { + StackMemoryFormat::Int128 => backend.code_builder.i32_const(1), + StackMemoryFormat::Float128 => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7fff_0000_0000_0000); + backend.code_builder.i64_ne(); + } + StackMemoryFormat::Decimal => { + backend.code_builder.get_local(local_id); + backend.code_builder.i64_load(Align::Bytes4, offset + 8); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_and(); + backend.code_builder.i64_const(0x7100_0000_0000_0000); + backend.code_builder.i64_ne(); + } + StackMemoryFormat::DataStructure => panic_ret_type(), + } + } + } + } + NumAtan => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ATAN[width]); + } + _ => panic_ret_type(), + }, + NumAcos => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ACOS[width]); + } + _ => panic_ret_type(), + }, + NumAsin => match self.ret_layout { + Layout::Builtin(Builtin::Float(width)) => { + self.load_args_and_call_zig(backend, &bitcode::NUM_ASIN[width]); + } + _ => panic_ret_type(), + }, + NumBytesToU16 => todo!("{:?}", self.lowlevel), + NumBytesToU32 => todo!("{:?}", self.lowlevel), + NumBitwiseAnd => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_and(), + I64 => backend.code_builder.i64_and(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumBitwiseXor => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_xor(), + I64 => backend.code_builder.i64_xor(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumBitwiseOr => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_or(), + I64 => backend.code_builder.i64_or(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftLeftBy => { + // Swap order of arguments + backend.storage.load_symbols( + &mut backend.code_builder, + &[self.arguments[1], self.arguments[0]], + ); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shl(), + I64 => backend.code_builder.i64_shl(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftRightBy => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shr_s(), + I64 => backend.code_builder.i64_shr_s(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumShiftRightZfBy => { + self.load_args(backend); + match CodeGenNumType::from(self.ret_layout) { + I32 => backend.code_builder.i32_shr_u(), + I64 => backend.code_builder.i64_shr_u(), + I128 => todo!("{:?} for I128", self.lowlevel), + _ => panic_ret_type(), + } + } + NumIntCast => { + self.load_args(backend); + let ret_type = CodeGenNumType::from(self.ret_layout); + let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); + match (ret_type, arg_type) { + (I32, I32) => {} + (I32, I64) => backend.code_builder.i32_wrap_i64(), + (I32, F32) => backend.code_builder.i32_trunc_s_f32(), + (I32, F64) => backend.code_builder.i32_trunc_s_f64(), + + (I64, I32) => backend.code_builder.i64_extend_s_i32(), + (I64, I64) => {} + (I64, F32) => backend.code_builder.i64_trunc_s_f32(), + (I64, F64) => backend.code_builder.i64_trunc_s_f64(), + + (F32, I32) => backend.code_builder.f32_convert_s_i32(), + (F32, I64) => backend.code_builder.f32_convert_s_i64(), + (F32, F32) => {} + (F32, F64) => backend.code_builder.f32_demote_f64(), + + (F64, I32) => backend.code_builder.f64_convert_s_i32(), + (F64, I64) => backend.code_builder.f64_convert_s_i64(), + (F64, F32) => backend.code_builder.f64_promote_f32(), + (F64, F64) => {} + + _ => todo!("{:?}: {:?} -> {:?}", self.lowlevel, arg_type, ret_type), + } + } + And => { + self.load_args(backend); + backend.code_builder.i32_and(); + } + Or => { + self.load_args(backend); + backend.code_builder.i32_or(); + } + Not => { + self.load_args(backend); + backend.code_builder.i32_eqz(); + } + ExpectTrue => todo!("{:?}", self.lowlevel), + RefCountInc => self.load_args_and_call_zig(backend, bitcode::UTILS_INCREF), + RefCountDec => self.load_args_and_call_zig(backend, bitcode::UTILS_DECREF), + Eq | NotEq | Hash | PtrCast => { + internal_error!("{:?} should be handled in backend.rs", self.lowlevel) } } - And => code_builder.i32_and(), - Or => code_builder.i32_or(), - Not => code_builder.i32_eqz(), - ExpectTrue => return NotImplemented, - RefCountInc => return BuiltinCall(bitcode::UTILS_INCREF), - RefCountDec => return BuiltinCall(bitcode::UTILS_DECREF), - Eq | NotEq | Hash | PtrCast => { - internal_error!("{:?} should be handled in backend.rs", lowlevel) - } - } - Done -} - -/// Wrap an integer whose Wasm representation is i32 -fn wrap_i32(code_builder: &mut CodeBuilder, size: u32) { - match size { - 1 => { - // Underlying Roc value is i8 - code_builder.i32_const(24); - code_builder.i32_shl(); - code_builder.i32_const(24); - code_builder.i32_shr_s(); - } - 2 => { - // Underlying Roc value is i16 - code_builder.i32_const(16); - code_builder.i32_shl(); - code_builder.i32_const(16); - code_builder.i32_shr_s(); - } - _ => {} // the only other possible value is 4, and i32 wraps natively - } -} - -fn float_width_from_layout(wasm_layout: &WasmLayout) -> FloatWidth { - match wasm_layout { - WasmLayout::Primitive(F32, _) => FloatWidth::F32, - WasmLayout::Primitive(F64, _) => FloatWidth::F64, - _ => internal_error!("{:?} does not have a FloatWidth", wasm_layout), } }