use bumpalo::collections::Vec; use roc_builtins::bitcode::{self, FloatWidth, IntWidth}; use roc_error_macros::internal_error; use roc_module::low_level::LowLevel; use roc_module::symbol::Symbol; use roc_mono::code_gen_help::HelperOp; use roc_mono::ir::{HigherOrderLowLevel, PassedFunction, ProcLayout}; use roc_mono::layout::{Builtin, Layout, UnionLayout}; use roc_mono::low_level::HigherOrder; use crate::backend::{ProcLookupData, ProcSource, WasmBackend}; use crate::layout::{CallConv, StackMemoryFormat, WasmLayout}; use crate::storage::{StackMemoryLocation, StoredValue}; use crate::wasm_module::{Align, ValueType}; use crate::TARGET_INFO; /// Number types used for Wasm code gen /// Unlike other enums, this contains no details about layout or storage. /// Its purpose is to help simplify the arms of the main lowlevel `match` below. /// /// Note: Wasm I32 is used for Roc I8, I16, I32, U8, U16, and U32, since it's /// 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. #[derive(Clone, Copy, Debug, PartialEq)] enum CodeGenNumType { I32, // Supported in Wasm instruction set I64, // Supported in Wasm instruction set F32, // Supported in Wasm instruction set F64, // Supported in Wasm instruction set I128, // Bytes in memory, needs Zig builtins F128, // Bytes in memory, needs Zig builtins Decimal, // Bytes in memory, needs Zig builtins } impl CodeGenNumType { pub fn for_symbol(backend: &WasmBackend<'_>, 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); match layout { Layout::Builtin(builtin) => match builtin { Builtin::Int(int_width) => match int_width { IntWidth::U8 => I32, IntWidth::U16 => I32, IntWidth::U32 => I32, IntWidth::U64 => I64, IntWidth::U128 => I128, IntWidth::I8 => I32, IntWidth::I16 => I32, IntWidth::I32 => I32, IntWidth::I64 => I64, IntWidth::I128 => I128, }, Builtin::Float(float_width) => match float_width { FloatWidth::F32 => F32, FloatWidth::F64 => F64, FloatWidth::F128 => F128, }, Builtin::Decimal => Decimal, _ => not_num_error(), }, _ => not_num_error(), } } } impl From for CodeGenNumType { fn from(value_type: ValueType) -> CodeGenNumType { match value_type { ValueType::I32 => CodeGenNumType::I32, ValueType::I64 => CodeGenNumType::I64, ValueType::F32 => CodeGenNumType::F32, ValueType::F64 => CodeGenNumType::F64, } } } impl From for CodeGenNumType { fn from(format: StackMemoryFormat) -> CodeGenNumType { match format { 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") } } } } impl From for CodeGenNumType { fn from(wasm_layout: WasmLayout) -> CodeGenNumType { match wasm_layout { WasmLayout::Primitive(value_type, _) => CodeGenNumType::from(value_type), WasmLayout::StackMemory { format, .. } => CodeGenNumType::from(format), } } } impl From<&StoredValue> for CodeGenNumType { fn from(stored: &StoredValue) -> CodeGenNumType { use StoredValue::*; match stored { VirtualMachineStack { value_type, .. } => CodeGenNumType::from(*value_type), Local { value_type, .. } => CodeGenNumType::from(*value_type), StackMemory { format, .. } => CodeGenNumType::from(*format), } } } fn integer_symbol_is_signed(backend: &WasmBackend<'_>, symbol: Symbol) -> bool { return match backend.storage.symbol_layouts[&symbol] { Layout::Builtin(Builtin::Int(int_width)) => int_width.is_signed(), x => internal_error!("Expected integer, found {:?}", x), }; } pub struct LowLevelCall<'a> { pub lowlevel: LowLevel, pub arguments: &'a [Symbol], pub ret_symbol: Symbol, pub ret_layout: Layout<'a>, pub ret_storage: StoredValue, } 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 /// Result is the type signature of the call fn load_args(&self, backend: &mut WasmBackend<'a>) -> (usize, bool, bool) { 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 load_args_and_call_zig(&self, backend: &mut WasmBackend<'a>, name: &'a str) { let (num_wasm_args, has_return_val, ret_zig_packed_struct) = self.load_args(backend); backend.call_zig_builtin_after_loading_args(name, num_wasm_args, has_return_val); if ret_zig_packed_struct { match self.ret_storage { StoredValue::StackMemory { size, alignment_bytes, .. } => { // The address of the return value was already loaded before the call let align = Align::from(alignment_bytes); if size > 4 { backend.code_builder.i64_store(align, 0); } else { backend.code_builder.i32_store(align, 0); } } _ => { internal_error!("Zig packed struct should always be stored to StackMemory") } } } } /// 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(); } } /// Main entrypoint from WasmBackend pub fn generate(&self, backend: &mut WasmBackend<'a>) { use CodeGenNumType::*; use LowLevel::*; let panic_ret_type = || { internal_error!( "Invalid return layout for {:?}: {:?}", self.lowlevel, self.ret_layout ) }; 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 => 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_load8_u(Align::Bytes1, offset + 11); backend.code_builder.i32_const(0x80); backend.code_builder.i32_eq(); } _ => internal_error!("invalid storage for Str"), }, 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 { field_layouts, .. } => field_layouts[0], _ => { internal_error!("Unexpected mono layout {:?} for StrToNum", self.ret_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 layout {:?} for StrToNum", rest), }; 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"), }, ListIsUnique => self.load_args_and_call_zig(backend, bitcode::LIST_IS_UNIQUE), ListMap | ListMap2 | ListMap3 | ListMap4 | ListMapWithIndex | ListKeepIf | ListWalk | ListWalkUntil | ListWalkBackwards | ListKeepOks | ListKeepErrs | ListSortWith | ListAny | ListAll | ListFindUnsafe | DictWalk => { internal_error!("HigherOrder lowlevels should not be handled here") } ListGetUnsafe | ListReplaceUnsafe | ListSingle | ListRepeat | ListReverse | ListConcat | ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListSublist | ListDropAt | ListSwap => { todo!("{:?}", self.lowlevel); } DictSize | DictEmpty | DictInsert | DictRemove | DictContains | DictGetUnsafe | DictKeys | DictValues | DictUnion | DictIntersection | DictDifference | SetFromList | SetToDict => { 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) } } } 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 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i32_gt_s() } else { backend.code_builder.i32_gt_u() } } I64 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i64_gt_s() } else { backend.code_builder.i64_gt_u() } } 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 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i32_ge_s() } else { backend.code_builder.i32_ge_u() } } I64 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i64_ge_s() } else { backend.code_builder.i64_ge_u() } } 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 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i32_lt_s() } else { backend.code_builder.i32_lt_u() } } I64 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i64_lt_s() } else { backend.code_builder.i64_lt_u() } } 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 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i32_le_s() } else { backend.code_builder.i32_le_u() } } I64 => { if integer_symbol_is_signed(backend, self.arguments[0]) { backend.code_builder.i64_le_s() } else { backend.code_builder.i64_le_u() } } 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), NumToFrac => { 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), NumRound => { self.load_args(backend); let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); let ret_type = CodeGenNumType::from(self.ret_layout); let width = match ret_type { CodeGenNumType::I32 => IntWidth::I32, CodeGenNumType::I64 => IntWidth::I64, CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel), _ => internal_error!("Invalid return type for round: {:?}", ret_type), }; match arg_type { F32 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F32[width]), F64 => self.load_args_and_call_zig(backend, &bitcode::NUM_ROUND_F64[width]), _ => internal_error!("Invalid argument type for round: {:?}", arg_type), } } NumCeiling | NumFloor => { self.load_args(backend); let arg_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); let ret_type = CodeGenNumType::from(self.ret_layout); match (arg_type, self.lowlevel) { (F32, NumCeiling) => { backend.code_builder.f32_ceil(); } (F64, NumCeiling) => { backend.code_builder.f64_ceil(); } (F32, NumFloor) => { backend.code_builder.f32_floor(); } (F64, NumFloor) => { backend.code_builder.f64_floor(); } _ => internal_error!("Invalid argument type for ceiling: {:?}", arg_type), } match (ret_type, arg_type) { // TODO: unsigned truncation (I32, F32) => backend.code_builder.i32_trunc_s_f32(), (I32, F64) => backend.code_builder.i32_trunc_s_f64(), (I64, F32) => backend.code_builder.i64_trunc_s_f32(), (I64, F64) => backend.code_builder.i64_trunc_s_f64(), (I128, _) => todo!("{:?} for I128", self.lowlevel), _ => panic_ret_type(), } } NumPowInt => { self.load_args(backend); let base_type = CodeGenNumType::for_symbol(backend, self.arguments[0]); let exponent_type = CodeGenNumType::for_symbol(backend, self.arguments[1]); let ret_type = CodeGenNumType::from(self.ret_layout); debug_assert!(base_type == exponent_type); debug_assert!(exponent_type == ret_type); let width = match ret_type { CodeGenNumType::I32 => IntWidth::I32, CodeGenNumType::I64 => IntWidth::I64, CodeGenNumType::I128 => todo!("{:?} for I128", self.lowlevel), _ => internal_error!("Invalid return type for pow: {:?}", ret_type), }; self.load_args_and_call_zig(backend, &bitcode::NUM_POW_INT[width]) } NumIsFinite => num_is_finite(backend, self.arguments[0]), 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 => { 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_shr_s(), I64 => backend.code_builder.i64_shr_s(), I128 => todo!("{:?} for I128", self.lowlevel), _ => panic_ret_type(), } } NumShiftRightZfBy => { 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_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), } } NumToFloatCast => { todo!("implement toF32 and toF64"); } NumToIntChecked => { todo!() } NumToFloatChecked => { todo!("implement toF32Checked and toF64Checked"); } 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), PtrCast => { let code_builder = &mut backend.code_builder; backend.storage.load_symbols(code_builder, self.arguments); } Hash => todo!("{:?}", self.lowlevel), Eq | NotEq => self.eq_or_neq(backend), BoxExpr | UnboxExpr => { unreachable!("The {:?} operation is turned into mono Expr", self.lowlevel) } } } /// Equality and inequality /// These can operate on any data type (except functions) so they're more complex than other operators. fn eq_or_neq(&self, backend: &mut WasmBackend<'a>) { let arg_layout = backend.storage.symbol_layouts[&self.arguments[0]]; let other_arg_layout = backend.storage.symbol_layouts[&self.arguments[1]]; debug_assert!( arg_layout == other_arg_layout, "Cannot do `==` comparison on different types" ); let invert_result = matches!(self.lowlevel, LowLevel::NotEq); match arg_layout { Layout::Builtin( Builtin::Int(_) | Builtin::Float(_) | Builtin::Bool | Builtin::Decimal, ) => self.eq_or_neq_number(backend), Layout::Builtin(Builtin::Str) => { self.load_args_and_call_zig(backend, bitcode::STR_EQUAL); if invert_result { backend.code_builder.i32_eqz(); } } // Empty record is always equal to empty record. // There are no runtime arguments to check, so just emit true or false. Layout::Struct { field_layouts, .. } if field_layouts.is_empty() => { backend.code_builder.i32_const(!invert_result as i32); } // Void is always equal to void. This is the type for the contents of the empty list in `[] == []` // This instruction will never execute, but we need an i32 for module validation Layout::Union(UnionLayout::NonRecursive(tags)) if tags.is_empty() => { backend.code_builder.i32_const(!invert_result as i32); } Layout::Builtin(Builtin::Dict(_, _) | Builtin::Set(_) | Builtin::List(_)) | Layout::Struct { .. } | Layout::Union(_) | Layout::LambdaSet(_) => { // Don't want Zig calling convention here, we're calling internal Roc functions backend .storage .load_symbols(&mut backend.code_builder, self.arguments); backend.call_eq_specialized( self.arguments, &arg_layout, self.ret_symbol, &self.ret_storage, ); if invert_result { backend.code_builder.i32_eqz(); } } Layout::Boxed(_) => todo!(), Layout::RecursivePointer => { internal_error!( "Tried to apply `==` to RecursivePointer values {:?}", self.arguments, ) } } } fn eq_or_neq_number(&self, backend: &mut WasmBackend<'a>) { use StoredValue::*; match backend.storage.get(&self.arguments[0]).to_owned() { VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { self.load_args(backend); match self.lowlevel { LowLevel::Eq => match value_type { ValueType::I32 => backend.code_builder.i32_eq(), ValueType::I64 => backend.code_builder.i64_eq(), ValueType::F32 => backend.code_builder.f32_eq(), ValueType::F64 => backend.code_builder.f64_eq(), }, LowLevel::NotEq => match value_type { ValueType::I32 => backend.code_builder.i32_ne(), ValueType::I64 => backend.code_builder.i64_ne(), ValueType::F32 => backend.code_builder.f32_ne(), ValueType::F64 => backend.code_builder.f64_ne(), }, _ => internal_error!("{:?} ended up in Equality code", self.lowlevel), } } StackMemory { format, location: location0, .. } => { if let StackMemory { location: location1, .. } = backend.storage.get(&self.arguments[1]).to_owned() { self.eq_num128(backend, format, [location0, location1]); if matches!(self.lowlevel, LowLevel::NotEq) { backend.code_builder.i32_eqz(); } } } } } /// Equality for 12-bit numbers. Checks if they're finite and contain the same bytes /// Takes care of loading the arguments fn eq_num128( &self, backend: &mut WasmBackend<'a>, format: StackMemoryFormat, 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::Int128 => Self::eq_num128_bytes(backend, locations), StackMemoryFormat::Float128 => todo!("equality for f128"), StackMemoryFormat::DataStructure => { internal_error!("Data structure equality is handled elsewhere") } } } /// Check that two 128-bit numbers contain the same bytes /// Loads *half* an argument at a time /// (Don't call "load arguments" or "load symbols" helpers before this, it'll just waste instructions) fn eq_num128_bytes(backend: &mut WasmBackend<'a>, locations: [StackMemoryLocation; 2]) { let (local0, offset0) = locations[0].local_and_offset(backend.storage.stack_frame_pointer); let (local1, offset1) = locations[1].local_and_offset(backend.storage.stack_frame_pointer); // Load & compare the first half of each argument backend.code_builder.get_local(local0); backend.code_builder.i64_load(Align::Bytes8, offset0); backend.code_builder.get_local(local1); backend.code_builder.i64_load(Align::Bytes8, offset1); backend.code_builder.i64_eq(); // Load & compare the second half of each argument backend.code_builder.get_local(local0); backend.code_builder.i64_load(Align::Bytes8, offset0 + 8); backend.code_builder.get_local(local1); backend.code_builder.i64_load(Align::Bytes8, offset1 + 8); backend.code_builder.i64_eq(); // First half matches AND second half matches backend.code_builder.i32_and(); } } /// Helper for NumIsFinite op, and also part of Eq/NotEq fn num_is_finite(backend: &mut WasmBackend<'_>, argument: Symbol) { use StoredValue::*; let stored = backend.storage.get(&argument).to_owned(); match stored { VirtualMachineStack { value_type, .. } | Local { value_type, .. } => { backend .storage .load_symbols(&mut backend.code_builder, &[argument]); match value_type { // Integers are always finite. Just return True. ValueType::I32 | ValueType::I64 => backend.code_builder.i32_const(1), 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); match format { // Integers and fixed-point numbers are always finite. Just return True. StackMemoryFormat::Int128 | StackMemoryFormat::Decimal => { backend.code_builder.i32_const(1) } // f128 is not supported anywhere else but it's easy to support it here, so why not... 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::DataStructure => { internal_error!("Tried to perform NumIsFinite on a data structure") } } } } } pub fn call_higher_order_lowlevel<'a>( backend: &mut WasmBackend<'a>, return_sym: Symbol, return_layout: &Layout<'a>, higher_order: &'a HigherOrderLowLevel<'a>, ) { use HigherOrder::*; let HigherOrderLowLevel { op, passed_function, .. } = higher_order; let PassedFunction { name: fn_name, argument_layouts, return_layout: result_layout, owns_captured_environment, captured_environment, .. } = passed_function; let closure_data_layout = match backend.storage.symbol_layouts[captured_environment] { Layout::LambdaSet(lambda_set) => lambda_set.runtime_representation(), Layout::Struct { field_layouts: &[], .. } => Layout::UNIT, x => internal_error!("Closure data has an invalid layout\n{:?}", x), }; let closure_data_exists: bool = closure_data_layout != Layout::UNIT; // 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 passed_proc_layout = ProcLayout { arguments: argument_layouts, result: *result_layout, }; let passed_proc_index = backend .proc_lookup .iter() .position(|ProcLookupData { name, layout, .. }| { name == fn_name && layout == &passed_proc_layout }) .unwrap(); ProcSource::HigherOrderWrapper(passed_proc_index) }; let wrapper_sym = backend.create_symbol(&format!("#wrap#{:?}", fn_name)); let wrapper_layout = { let mut wrapper_arg_layouts: Vec> = Vec::with_capacity_in(argument_layouts.len() + 1, backend.env.arena); let n_non_closure_args = if closure_data_exists { argument_layouts.len() - 1 } else { argument_layouts.len() }; wrapper_arg_layouts.push(closure_data_layout); wrapper_arg_layouts.extend( argument_layouts .iter() .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, } }; let wrapper_fn_idx = backend.register_helper_proc(wrapper_sym, wrapper_layout, source); let wrapper_fn_ptr = backend.get_fn_table_index(wrapper_fn_idx); let inc_fn_ptr = match closure_data_layout { Layout::Struct { field_layouts: &[], .. } => { // Our code gen would ignore the Unit arg, but the Zig builtin passes a pointer for it! // That results in an exception (type signature mismatch in indirect call). // The workaround is to use I32 layout, treating the (ignored) pointer as an integer. backend.get_refcount_fn_ptr(Layout::Builtin(Builtin::Int(IntWidth::I32)), HelperOp::Inc) } _ => backend.get_refcount_fn_ptr(closure_data_layout, HelperOp::Inc), }; match op { ListMap { xs } => list_map_n( bitcode::LIST_MAP, backend, &[*xs], return_sym, *return_layout, wrapper_fn_ptr, inc_fn_ptr, closure_data_exists, *captured_environment, *owns_captured_environment, ), ListMap2 { xs, ys } => list_map_n( bitcode::LIST_MAP2, backend, &[*xs, *ys], return_sym, *return_layout, wrapper_fn_ptr, inc_fn_ptr, closure_data_exists, *captured_environment, *owns_captured_environment, ), ListMap3 { xs, ys, zs } => list_map_n( bitcode::LIST_MAP3, backend, &[*xs, *ys, *zs], return_sym, *return_layout, wrapper_fn_ptr, inc_fn_ptr, closure_data_exists, *captured_environment, *owns_captured_environment, ), ListMap4 { xs, ys, zs, ws } => list_map_n( bitcode::LIST_MAP4, backend, &[*xs, *ys, *zs, *ws], return_sym, *return_layout, wrapper_fn_ptr, inc_fn_ptr, closure_data_exists, *captured_environment, *owns_captured_environment, ), ListMapWithIndex { .. } | ListKeepIf { .. } | ListWalk { .. } | ListWalkUntil { .. } | ListWalkBackwards { .. } | ListKeepOks { .. } | ListKeepErrs { .. } | ListSortWith { .. } | ListAny { .. } | ListAll { .. } | ListFindUnsafe { .. } | DictWalk { .. } => todo!("{:?}", op), } } fn unwrap_list_elem_layout(list_layout: Layout<'_>) -> &Layout<'_> { match list_layout { Layout::Builtin(Builtin::List(x)) => x, e => internal_error!("expected List layout, got {:?}", e), } } #[allow(clippy::too_many_arguments)] fn list_map_n<'a>( zig_fn_name: &'static str, backend: &mut WasmBackend<'a>, arg_symbols: &[Symbol], return_sym: Symbol, return_layout: Layout<'a>, wrapper_fn_ptr: i32, inc_fn_ptr: i32, closure_data_exists: bool, captured_environment: Symbol, owns_captured_environment: bool, ) { let arg_elem_layouts = Vec::from_iter_in( arg_symbols .iter() .map(|sym| *unwrap_list_elem_layout(backend.storage.symbol_layouts[sym])), backend.env.arena, ); let elem_ret = unwrap_list_elem_layout(return_layout); let (elem_ret_size, elem_ret_align) = elem_ret.stack_size_and_alignment(TARGET_INFO); let cb = &mut backend.code_builder; backend.storage.load_symbols(cb, &[return_sym]); for s in arg_symbols { backend.storage.load_symbol_zig(cb, *s); } 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(elem_ret_align as i32); for el in arg_elem_layouts.iter() { cb.i32_const(el.stack_size(TARGET_INFO) as i32); } cb.i32_const(elem_ret_size as i32); // If we have lists of different lengths, we may need to decrement let num_wasm_args = if arg_elem_layouts.len() > 1 { for el in arg_elem_layouts.iter() { let ptr = backend.get_refcount_fn_ptr(*el, HelperOp::Dec); backend.code_builder.i32_const(ptr); } 7 + arg_elem_layouts.len() * 4 } else { 7 + arg_elem_layouts.len() * 3 }; let has_return_val = false; backend.call_zig_builtin_after_loading_args(zig_fn_name, num_wasm_args, has_return_val); }