From d56d3311d4692f54959a97269c427e7f2fbcd2d0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 24 Apr 2023 21:39:15 +0200 Subject: [PATCH 01/28] implement and test all regn_regn moves --- .../compiler/gen_dev/src/generic64/x86_64.rs | 149 ++++++++++++++++-- 1 file changed, 134 insertions(+), 15 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 54bcbc3fd5..33c2aaf4ad 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1858,6 +1858,34 @@ fn add_reg_extension(reg: T, byte: u8) -> u8 { } } +#[inline(always)] +fn binop_reg8_reg8(op_code: u8, buf: &mut Vec, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + if dst_high || src_high { + let rex = add_rm_extension(dst, REX); + let rex = add_reg_extension(src, rex); + + buf.extend([rex, op_code, 0xC0 | dst_mod | (src_mod << 3)]) + } else { + let rex_prefix = [ + X86_64GeneralReg::RBP, + X86_64GeneralReg::RSP, + X86_64GeneralReg::RSI, + X86_64GeneralReg::RDI, + ]; + + if rex_prefix.contains(&src) || rex_prefix.contains(&dst) { + buf.push(0x40); + } + + buf.extend([op_code, 0xC0 | dst_mod | (src_mod << 3)]); + } +} + #[inline(always)] fn binop_reg16_reg16( op_code: u8, @@ -2401,20 +2429,83 @@ fn lea_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg) { ]) } -/// `MOV r/m64,r64` -> Move r64 to r/m64. -/// This will not generate anything if dst and src are the same. -#[inline(always)] -fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - if dst != src { - raw_mov_reg64_reg64(buf, dst, src); +fn raw_mov_reg_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, +) { + match register_width { + RegisterWidth::W8 => binop_reg8_reg8(0x88, buf, dst, src), + RegisterWidth::W16 => binop_reg16_reg16(0x89, buf, dst, src), + RegisterWidth::W32 => binop_reg32_reg32(0x89, buf, dst, src), + RegisterWidth::W64 => binop_reg64_reg64(0x89, buf, dst, src), + } +} + +fn raw_movsx_reg_reg( + buf: &mut Vec, + input_width: RegisterWidth, + output_width: RegisterWidth, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, +) { + match (input_width, output_width) { + (RegisterWidth::W8, RegisterWidth::W16) => { + buf.push(0x0F); + buf.push(0xBE); + binop_reg8_reg8(0x89, buf, dst, src); + } + (RegisterWidth::W8, RegisterWidth::W32) => { + buf.push(0x0F); + buf.push(0xBF); + binop_reg8_reg8(0x89, buf, dst, src); + } + (RegisterWidth::W8, RegisterWidth::W64) => { + buf.push(0x48); + buf.push(0x0F); + buf.push(0xBE); + binop_reg8_reg8(0x89, buf, dst, src); + } + (RegisterWidth::W16, RegisterWidth::W32) => { + buf.push(0x66); + buf.push(0x0F); + buf.push(0xBF); + binop_reg16_reg16(0x89, buf, dst, src); + } + (RegisterWidth::W16, RegisterWidth::W64) => { + buf.push(0x48); + buf.push(0x0F); + buf.push(0xBF); + binop_reg16_reg16(0x89, buf, dst, src); + } + (RegisterWidth::W32, RegisterWidth::W64) => { + buf.push(0x48); + buf.push(0x0F); + buf.push(0xBF); + binop_reg32_reg32(0x89, buf, dst, src); + } + _ => panic!("Invalid input/output register width combination"), } } /// `MOV r/m64,r64` -> Move r64 to r/m64. -/// This will always generate the move. It is used for verification. +/// This will not generate anything if dst and src are the same. #[inline(always)] -fn raw_mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - binop_reg64_reg64(0x89, buf, dst, src); +fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { + mov_reg_reg(buf, RegisterWidth::W64, dst, src) +} + +#[inline(always)] +fn mov_reg_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, +) { + if dst != src { + raw_mov_reg_reg(buf, register_width, dst, src); + } } // The following base and stack based operations could be optimized based on how many bytes the offset actually is. @@ -3051,8 +3142,8 @@ mod tests { X86_64GeneralReg::RDX => "edx", X86_64GeneralReg::RBP => "ebp", X86_64GeneralReg::RSP => "esp", - X86_64GeneralReg::RDI => "edi", X86_64GeneralReg::RSI => "esi", + X86_64GeneralReg::RDI => "edi", X86_64GeneralReg::R8 => "r8d", X86_64GeneralReg::R9 => "r9d", X86_64GeneralReg::R10 => "r10d", @@ -3073,8 +3164,8 @@ mod tests { X86_64GeneralReg::RDX => "dx", X86_64GeneralReg::RBP => "bp", X86_64GeneralReg::RSP => "sp", - X86_64GeneralReg::RDI => "di", X86_64GeneralReg::RSI => "si", + X86_64GeneralReg::RDI => "di", X86_64GeneralReg::R8 => "r8w", X86_64GeneralReg::R9 => "r9w", X86_64GeneralReg::R10 => "r10w", @@ -3095,8 +3186,9 @@ mod tests { X86_64GeneralReg::RDX => "dl", X86_64GeneralReg::RBP => "bpl", X86_64GeneralReg::RSP => "spl", - X86_64GeneralReg::RDI => "dil", X86_64GeneralReg::RSI => "sil", + X86_64GeneralReg::RDI => "dil", + X86_64GeneralReg::R8 => "r8b", X86_64GeneralReg::R9 => "r9b", X86_64GeneralReg::R10 => "r10b", @@ -3111,6 +3203,13 @@ mod tests { const TEST_I32: i32 = 0x12345678; const TEST_I64: i64 = 0x1234_5678_9ABC_DEF0; + const ALL_REGISTER_WIDTHS: &[RegisterWidth] = &[ + RegisterWidth::W8, + RegisterWidth::W16, + RegisterWidth::W32, + RegisterWidth::W64, + ]; + const ALL_GENERAL_REGS: &[X86_64GeneralReg] = &[ X86_64GeneralReg::RAX, X86_64GeneralReg::RBX, @@ -3118,8 +3217,8 @@ mod tests { X86_64GeneralReg::RDX, X86_64GeneralReg::RBP, X86_64GeneralReg::RSP, - X86_64GeneralReg::RDI, X86_64GeneralReg::RSI, + X86_64GeneralReg::RDI, X86_64GeneralReg::R8, X86_64GeneralReg::R9, X86_64GeneralReg::R10, @@ -3434,8 +3533,28 @@ mod tests { #[test] fn test_mov_reg64_reg64() { disassembler_test!( - raw_mov_reg64_reg64, - |reg1, reg2| format!("mov {}, {}", reg1, reg2), + raw_mov_reg_reg, + |w, reg1, reg2| { + match w { + RegisterWidth::W8 => format!( + "mov {}, {}", + X86_64GeneralReg::low_8bits_string(®1), + X86_64GeneralReg::low_8bits_string(®2) + ), + RegisterWidth::W16 => format!( + "mov {}, {}", + X86_64GeneralReg::low_16bits_string(®1), + X86_64GeneralReg::low_16bits_string(®2) + ), + RegisterWidth::W32 => format!( + "mov {}, {}", + X86_64GeneralReg::low_32bits_string(®1), + X86_64GeneralReg::low_32bits_string(®2) + ), + RegisterWidth::W64 => format!("mov {}, {}", reg1, reg2), + } + }, + ALL_REGISTER_WIDTHS, ALL_GENERAL_REGS, ALL_GENERAL_REGS ); From 312fb23567470d9a3001e7174565934b54165f66 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:03:50 +0200 Subject: [PATCH 02/28] add zig builtins for wrapped mul and shift right (for 128-bit ints) --- crates/compiler/builtins/bitcode/src/main.zig | 4 +++ crates/compiler/builtins/bitcode/src/num.zig | 25 +++++++++++++++++++ crates/compiler/builtins/src/bitcode.rs | 4 +++ 3 files changed, 33 insertions(+) diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index 4c571b52bb..f219ca8203 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -73,6 +73,9 @@ comptime { exportNumFn(num.bytesToU64C, "bytes_to_u64"); exportNumFn(num.bytesToU128C, "bytes_to_u128"); + exportNumFn(num.shiftRightZeroFillI128, "shift_right_zero_fill.i128"); + exportNumFn(num.shiftRightZeroFillU128, "shift_right_zero_fill.u128"); + inline for (INTEGERS) |T, i| { num.exportPow(T, ROC_BUILTINS ++ "." ++ NUM ++ ".pow_int."); num.exportDivCeil(T, ROC_BUILTINS ++ "." ++ NUM ++ ".div_ceil."); @@ -91,6 +94,7 @@ comptime { num.exportMulWithOverflow(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_with_overflow."); num.exportMulOrPanic(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_or_panic."); num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated."); + num.exportMulWrappedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".mul_wrapped."); num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits."); num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits."); diff --git a/crates/compiler/builtins/bitcode/src/num.zig b/crates/compiler/builtins/bitcode/src/num.zig index dbbf87c1cf..f85eeb015c 100644 --- a/crates/compiler/builtins/bitcode/src/num.zig +++ b/crates/compiler/builtins/bitcode/src/num.zig @@ -464,6 +464,31 @@ pub fn exportMulSaturatedInt(comptime T: type, comptime W: type, comptime name: @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } +pub fn exportMulWrappedInt(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T, other: T) callconv(.C) T { + return self *% other; + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + +pub export fn shiftRightZeroFillI128(self: i128, other: u8) i128 { + if (other & 0b1000_0000 > 0) { + return 0; + } else { + return self >> @intCast(u7, other); + } +} + +pub export fn shiftRightZeroFillU128(self: u128, other: u8) u128 { + if (other & 0b1000_0000 > 0) { + return 0; + } else { + return self >> @intCast(u7, other); + } +} + pub fn exportMulOrPanic(comptime T: type, comptime W: type, comptime name: []const u8) void { comptime var f = struct { fn func(self: T, other: T) callconv(.C) T { diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 42c4c07260..7bc621a88a 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -285,10 +285,14 @@ pub const NUM_SUB_CHECKED_FLOAT: IntrinsicName = pub const NUM_MUL_OR_PANIC_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.mul_or_panic"); pub const NUM_MUL_SATURATED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.mul_saturated"); +pub const NUM_MUL_WRAP_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.mul_wrapped"); pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num.mul_with_overflow"); pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.num.mul_with_overflow"); +pub const NUM_SHIFT_RIGHT_ZERO_FILL: IntrinsicName = + int_intrinsic!("roc_builtins.num.shift_right_zero_fill"); + pub const NUM_COUNT_LEADING_ZERO_BITS: IntrinsicName = int_intrinsic!("roc_builtins.num.count_leading_zero_bits"); pub const NUM_COUNT_TRAILING_ZERO_BITS: IntrinsicName = From e8532c2f3c4afb33eed7ee555f39b26a8780067a Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:09:10 +0200 Subject: [PATCH 03/28] rename some temporary symbol usages --- crates/compiler/gen_dev/src/generic64/mod.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index e4b637ac81..641a8968d0 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1513,7 +1513,7 @@ impl< self.caller_procs.push(caller_proc); - let inc_n_data = Symbol::DEV_TMP5; + let inc_n_data = Symbol::DEV_TMP; self.build_fn_pointer(&inc_n_data, inc_n_data_string); self.build_fn_pointer(&caller, caller_string); @@ -1539,7 +1539,7 @@ impl< } self.load_literal( - &Symbol::DEV_TMP3, + &Symbol::DEV_TMP2, &Layout::BOOL, &Literal::Bool(higher_order.passed_function.owns_captured_environment), ); @@ -1558,7 +1558,7 @@ impl< caller, data, inc_n_data, - Symbol::DEV_TMP3, + Symbol::DEV_TMP2, alignment, old_element_width, new_element_width, @@ -1584,15 +1584,15 @@ impl< .claim_stack_area(dst, self.layout_interner.stack_size(ret_layout)); self.build_fn_call( - &Symbol::DEV_TMP4, + &Symbol::DEV_TMP3, bitcode::LIST_MAP.to_string(), &arguments, &layouts, &ret_layout, ); - self.free_symbol(&Symbol::DEV_TMP3); - self.free_symbol(&Symbol::DEV_TMP5); + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); // Return list value from fn call self.storage_manager.copy_symbol_to_stack_offset( @@ -1603,7 +1603,7 @@ impl< &ret_layout, ); - self.free_symbol(&Symbol::DEV_TMP4); + self.free_symbol(&Symbol::DEV_TMP3); } HigherOrder::ListMap2 { .. } => todo!(), HigherOrder::ListMap3 { .. } => todo!(), From 74444d288387aa6a813e5f80193fab63b667806a Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:10:17 +0200 Subject: [PATCH 04/28] 128-bit mul and shift in dev backend --- crates/compiler/gen_dev/src/generic64/mod.rs | 31 +++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 641a8968d0..5a50b7f5ef 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1093,6 +1093,21 @@ impl< src2_reg, ); } + Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128)) => { + let int_width = match *layout { + Layout::I128 => IntWidth::I128, + Layout::U128 => IntWidth::U128, + _ => unreachable!(), + }; + + self.build_fn_call( + dst, + bitcode::NUM_MUL_WRAP_INT[int_width].to_string(), + &[*src1, *src2], + &[*layout, *layout], + layout, + ); + } Layout::Builtin(Builtin::Float(FloatWidth::F64)) => { let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); let src1_reg = self.storage_manager.load_to_float_reg(&mut self.buf, src1); @@ -2686,7 +2701,21 @@ impl< let buf = &mut self.buf; match int_width { - IntWidth::U128 | IntWidth::I128 => todo!(), + IntWidth::U128 | IntWidth::I128 => { + let layout = match int_width { + IntWidth::I128 => Layout::I128, + IntWidth::U128 => Layout::U128, + _ => unreachable!(), + }; + + self.build_fn_call( + dst, + bitcode::NUM_SHIFT_RIGHT_ZERO_FILL[int_width].to_string(), + &[*src1, *src2], + &[layout, layout], + &layout, + ); + } _ => { let dst_reg = self.storage_manager.claim_general_reg(buf, dst); let src1_reg = self.storage_manager.load_to_general_reg(buf, src1); From 43624ade272be02c5553eeaa5683abb3a88b8ced Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:11:54 +0200 Subject: [PATCH 05/28] assembly for irem and urem --- .../compiler/gen_dev/src/generic64/aarch64.rs | 26 ++++++++ crates/compiler/gen_dev/src/generic64/mod.rs | 65 +++++++++++++++++++ .../compiler/gen_dev/src/generic64/x86_64.rs | 40 ++++++++++++ crates/compiler/test_gen/src/gen_num.rs | 22 ++----- 4 files changed, 138 insertions(+), 15 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index cf5469b39e..92bd9a0132 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -581,6 +581,32 @@ impl Assembler for AArch64Assembler { udiv_reg64_reg64_reg64(buf, dst, src1, src2); } + fn irem_reg64_reg64_reg64<'a, ASM, CC>( + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager<'a, '_, AArch64GeneralReg, AArch64FloatReg, ASM, CC>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + todo!() + } + + fn urem_reg64_reg64_reg64<'a, ASM, CC>( + _buf: &mut Vec<'a, u8>, + _storage_manager: &mut StorageManager<'a, '_, AArch64GeneralReg, AArch64FloatReg, ASM, CC>, + _dst: AArch64GeneralReg, + _src1: AArch64GeneralReg, + _src2: AArch64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + todo!() + } + #[inline(always)] fn mul_freg32_freg32_freg32( buf: &mut Vec<'_, u8>, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 5a50b7f5ef..d70315b7eb 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -387,6 +387,7 @@ pub trait Assembler: Sized + Copy { ) where ASM: Assembler, CC: CallConv; + fn udiv_reg64_reg64_reg64<'a, ASM, CC>( buf: &mut Vec<'a, u8>, storage_manager: &mut StorageManager<'a, '_, GeneralReg, FloatReg, ASM, CC>, @@ -397,6 +398,26 @@ pub trait Assembler: Sized + Copy { ASM: Assembler, CC: CallConv; + fn irem_reg64_reg64_reg64<'a, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, '_, GeneralReg, FloatReg, ASM, CC>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ) where + ASM: Assembler, + CC: CallConv; + + fn urem_reg64_reg64_reg64<'a, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, '_, GeneralReg, FloatReg, ASM, CC>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ) where + ASM: Assembler, + CC: CallConv; + fn sub_reg64_reg64_imm32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src1: GeneralReg, imm32: i32); fn sub_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, @@ -1180,6 +1201,50 @@ impl< } } + fn build_num_rem(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>) { + match self.layout_interner.get(*layout) { + Layout::Builtin(Builtin::Int( + IntWidth::I64 | IntWidth::I32 | IntWidth::I16 | IntWidth::I8, + )) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); + + ASM::irem_reg64_reg64_reg64( + &mut self.buf, + &mut self.storage_manager, + dst_reg, + src1_reg, + src2_reg, + ); + } + Layout::Builtin(Builtin::Int( + IntWidth::U64 | IntWidth::U32 | IntWidth::U16 | IntWidth::U8, + )) => { + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); + let src1_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src1); + let src2_reg = self + .storage_manager + .load_to_general_reg(&mut self.buf, src2); + + ASM::urem_reg64_reg64_reg64( + &mut self.buf, + &mut self.storage_manager, + dst_reg, + src1_reg, + src2_reg, + ); + } + x => todo!("NumDiv: layout, {:?}", x), + } + } + fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &InLayout<'a>) { match self.layout_interner.get(*layout) { Layout::Builtin(Builtin::Int(IntWidth::I64 | IntWidth::U64)) => { diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 33c2aaf4ad..4e0718d338 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1322,6 +1322,46 @@ impl Assembler for X86_64Assembler { mov_reg64_reg64(buf, dst, X86_64GeneralReg::RAX); } + fn irem_reg64_reg64_reg64<'a, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, '_, X86_64GeneralReg, X86_64FloatReg, ASM, CC>, + dst: X86_64GeneralReg, + src1: X86_64GeneralReg, + src2: X86_64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + use crate::generic64::RegStorage; + + storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RAX)); + storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RDX)); + + mov_reg64_reg64(buf, X86_64GeneralReg::RAX, src1); + idiv_reg64_reg64(buf, src2); + mov_reg64_reg64(buf, dst, X86_64GeneralReg::RDX); + } + + fn urem_reg64_reg64_reg64<'a, ASM, CC>( + buf: &mut Vec<'a, u8>, + storage_manager: &mut StorageManager<'a, '_, X86_64GeneralReg, X86_64FloatReg, ASM, CC>, + dst: X86_64GeneralReg, + src1: X86_64GeneralReg, + src2: X86_64GeneralReg, + ) where + ASM: Assembler, + CC: CallConv, + { + use crate::generic64::RegStorage; + + storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RAX)); + storage_manager.ensure_reg_free(buf, RegStorage::General(X86_64GeneralReg::RDX)); + + mov_reg64_reg64(buf, X86_64GeneralReg::RAX, src1); + udiv_reg64_reg64(buf, src2); + mov_reg64_reg64(buf, dst, X86_64GeneralReg::RDX); + } + #[inline(always)] fn jmp_imm32(buf: &mut Vec<'_, u8>, offset: i32) -> usize { jmp_imm32(buf, offset); diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 5704559f9a..d0a3d458dd 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -1176,29 +1176,21 @@ fn gen_div_checked_by_zero_i64() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn gen_rem_i64() { - assert_evals_to!( - indoc!( - r#" - Num.rem 8 3 - "# - ), - 2, - i64 - ); + assert_evals_to!("Num.rem 8 3", 2, i64); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn gen_rem_checked_div_by_zero_i64() { assert_evals_to!( indoc!( r#" - when Num.remChecked 8 0 is - Err DivByZero -> 4 - Ok _ -> -23 - "# + when Num.remChecked 8 0 is + Err DivByZero -> 4 + Ok _ -> -23 + "# ), 4, i64 From 17fde9dd9d33d64b916d1e5fb623384ad3a34c63 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:25:00 +0200 Subject: [PATCH 06/28] complete `unbox_to_stack` --- crates/compiler/gen_dev/src/generic64/mod.rs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index d70315b7eb..95bab3b90f 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2954,15 +2954,10 @@ impl< } } - if size - copied > 0 { - panic!("value only partially copied"); - } - - /* if size - copied >= 4 { for _ in (0..(size - copied)).step_by(4) { - ASM::mov_reg32_base32(buf, reg, from_offset + copied); - ASM::mov_base32_reg32(buf, to_offset + copied, reg); + ASM::mov_reg32_mem32_offset32(buf, tmp_reg, ptr_reg, copied); + ASM::mov_base32_reg32(buf, base_offset, tmp_reg); copied += 4; } @@ -2970,8 +2965,8 @@ impl< if size - copied >= 2 { for _ in (0..(size - copied)).step_by(2) { - ASM::mov_reg16_base32(buf, reg, from_offset + copied); - ASM::mov_base32_reg16(buf, to_offset + copied, reg); + ASM::mov_reg16_mem16_offset32(buf, tmp_reg, ptr_reg, copied); + ASM::mov_base32_reg16(buf, base_offset, tmp_reg); copied += 2; } @@ -2979,13 +2974,12 @@ impl< if size - copied >= 1 { for _ in (0..(size - copied)).step_by(1) { - ASM::mov_reg8_base32(buf, reg, from_offset + copied); - ASM::mov_base32_reg8(buf, to_offset + copied, reg); + ASM::mov_reg8_mem8_offset32(buf, tmp_reg, ptr_reg, copied); + ASM::mov_base32_reg8(buf, base_offset, tmp_reg); copied += 1; } } - */ } fn ptr_read( From 9bdf9e4b99d7687b0aaad33daac5423ace15ac74 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:28:40 +0200 Subject: [PATCH 07/28] make mov between different-sized registers more robust --- .../compiler/gen_dev/src/generic64/aarch64.rs | 14 ++++++-- crates/compiler/gen_dev/src/generic64/mod.rs | 36 +++++++++++++++++-- .../compiler/gen_dev/src/generic64/x86_64.rs | 13 ++++--- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 92bd9a0132..02ecdf3222 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -751,8 +751,18 @@ impl Assembler for AArch64Assembler { fmov_freg_freg(buf, FloatWidth::F64, dst, src); } #[inline(always)] - fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: AArch64GeneralReg, src: AArch64GeneralReg) { - mov_reg64_reg64(buf, dst, src); + fn mov_reg_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + dst: AArch64GeneralReg, + src: AArch64GeneralReg, + ) { + match register_width { + RegisterWidth::W8 => todo!(), + RegisterWidth::W16 => todo!(), + RegisterWidth::W32 => todo!(), + RegisterWidth::W64 => mov_reg64_reg64(buf, dst, src), + } } #[inline(always)] diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 95bab3b90f..eddabc2a2e 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -37,6 +37,18 @@ pub enum RegisterWidth { W64, } +impl RegisterWidth { + fn try_from_layout(layout: InLayout) -> Option { + match layout { + Layout::BOOL | Layout::I8 | Layout::U8 => Some(RegisterWidth::W8), + Layout::I16 | Layout::U16 => Some(RegisterWidth::W16), + Layout::U32 | Layout::I32 => Some(RegisterWidth::W32), + Layout::I64 | Layout::U64 => Some(RegisterWidth::W64), + _ => None, + } + } +} + pub trait CallConv>: Sized + Copy { @@ -256,7 +268,25 @@ pub trait Assembler: Sized + Copy { ); fn mov_reg64_imm64(buf: &mut Vec<'_, u8>, dst: GeneralReg, imm: i64); fn mov_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); - fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg); + + fn mov_reg_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + dst: GeneralReg, + src: GeneralReg, + ); + fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg) { + Self::mov_reg_reg(buf, RegisterWidth::W64, dst, src); + } + fn mov_reg32_reg32(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg) { + Self::mov_reg_reg(buf, RegisterWidth::W32, dst, src); + } + fn mov_reg16_reg16(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg) { + Self::mov_reg_reg(buf, RegisterWidth::W16, dst, src); + } + fn mov_reg8_reg8(buf: &mut Vec<'_, u8>, dst: GeneralReg, src: GeneralReg) { + Self::mov_reg_reg(buf, RegisterWidth::W8, dst, src); + } // base32 is similar to stack based instructions but they reference the base/frame pointer. fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); @@ -800,8 +830,10 @@ impl< // move return value to dst. match *ret_layout { single_register_integers!() => { + let width = RegisterWidth::try_from_layout(*ret_layout).unwrap(); + let dst_reg = self.storage_manager.claim_general_reg(&mut self.buf, dst); - ASM::mov_reg64_reg64(&mut self.buf, dst_reg, CC::GENERAL_RETURN_REGS[0]); + ASM::mov_reg_reg(&mut self.buf, width, dst_reg, CC::GENERAL_RETURN_REGS[0]); } single_register_floats!() => { let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 4e0718d338..b72128b152 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -4,10 +4,10 @@ use crate::{ single_register_layouts, Relocation, }; use bumpalo::collections::Vec; -use roc_builtins::bitcode::FloatWidth; +use roc_builtins::bitcode::{FloatWidth, IntWidth}; use roc_error_macros::internal_error; use roc_module::symbol::Symbol; -use roc_mono::layout::{InLayout, Layout, LayoutInterner, STLayoutInterner, UnionLayout}; +use roc_mono::layout::{Builtin, InLayout, Layout, LayoutInterner, STLayoutInterner, UnionLayout}; use super::{CompareOperation, RegisterWidth}; @@ -1425,8 +1425,13 @@ impl Assembler for X86_64Assembler { movsd_freg64_freg64(buf, dst, src); } #[inline(always)] - fn mov_reg64_reg64(buf: &mut Vec<'_, u8>, dst: X86_64GeneralReg, src: X86_64GeneralReg) { - mov_reg64_reg64(buf, dst, src); + fn mov_reg_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + ) { + mov_reg_reg(buf, register_width, dst, src); } #[inline(always)] From dd85f517451d62d2ee33dd275da5524d5a10551c Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:32:17 +0200 Subject: [PATCH 08/28] actually expose Num.rem --- crates/compiler/gen_dev/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index ddec2b4ea7..65996e763f 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -578,6 +578,8 @@ trait Backend<'a> { ); self.build_num_div(sym, &args[0], &args[1], ret_layout) } + + LowLevel::NumRemUnchecked => self.build_num_rem(sym, &args[0], &args[1], ret_layout), LowLevel::NumNeg => { debug_assert_eq!( 1, @@ -1307,6 +1309,9 @@ trait Backend<'a> { /// build_num_mul stores `src1 / src2` into dst. fn build_num_div(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>); + /// build_num_mul stores `src1 % src2` into dst. + fn build_num_rem(&mut self, dst: &Symbol, src1: &Symbol, src2: &Symbol, layout: &InLayout<'a>); + /// build_num_neg stores the negated value of src into dst. fn build_num_neg(&mut self, dst: &Symbol, src: &Symbol, layout: &InLayout<'a>); From 2e3c915780d0f83fad0c066bb0195706225f2725 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 14:39:40 +0200 Subject: [PATCH 09/28] 128-bit min/max --- crates/compiler/test_gen/src/gen_num.rs | 24 ++++-------------------- 1 file changed, 4 insertions(+), 20 deletions(-) diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index d0a3d458dd..f41f62c4a1 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2133,31 +2133,15 @@ fn shift_right_cast_i8() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn min_i128() { - assert_evals_to!( - indoc!( - r#" - Num.minI128 - "# - ), - i128::MIN, - i128 - ); + assert_evals_to!("Num.minI128", i128::MIN, i128); } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn max_i128() { - assert_evals_to!( - indoc!( - r#" - Num.maxI128 - "# - ), - i128::MAX, - i128 - ); + assert_evals_to!("Num.maxI128", i128::MAX, i128); } #[test] From 3ebc4bb9adeb9e584d96904bed14ee75f1bec597 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 21:11:46 +0200 Subject: [PATCH 10/28] 128-bit integer equality --- crates/compiler/gen_dev/src/generic64/mod.rs | 38 +++++++++++++++++++- crates/compiler/test_gen/src/gen_num.rs | 2 +- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index eddabc2a2e..2b9ab39484 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1334,7 +1334,43 @@ impl< let src2_reg = self .storage_manager .load_to_general_reg(&mut self.buf, src2); - ASM::eq_reg64_reg64_reg64(&mut self.buf, width, dst_reg, src1_reg, src2_reg); + ASM::eq_reg_reg_reg(&mut self.buf, width, dst_reg, src1_reg, src2_reg); + } + Layout::U128 | Layout::I128 => { + let buf = &mut self.buf; + + let dst_reg = self.storage_manager.claim_general_reg(buf, dst); + + // put the arguments on the stack + let (src1_offset, _) = self.storage_manager.stack_offset_and_size(src1); + let (src2_offset, _) = self.storage_manager.stack_offset_and_size(src2); + + let tmp1 = self + .storage_manager + .claim_general_reg(buf, &Symbol::DEV_TMP); + let tmp2 = self + .storage_manager + .claim_general_reg(buf, &Symbol::DEV_TMP2); + + // move the upper 8 bytes of both arguments into a register + ASM::mov_reg64_base32(buf, tmp1, src1_offset); + ASM::mov_reg64_base32(buf, tmp2, src2_offset); + + // store the result in our destination + ASM::eq_reg64_reg64_reg64(buf, dst_reg, tmp1, tmp2); + + // move the lower 8 bytes of both arguments into a register + ASM::mov_reg64_base32(buf, tmp1, src1_offset + 8); + ASM::mov_reg64_base32(buf, tmp2, src2_offset + 8); + + // store the result in tmp1 + ASM::eq_reg64_reg64_reg64(buf, tmp1, tmp1, tmp2); + + // now and dst and tmp1, storing the result in dst + ASM::and_reg64_reg64_reg64(buf, dst_reg, dst_reg, tmp1); + + self.storage_manager.free_symbol(&Symbol::DEV_TMP); + self.storage_manager.free_symbol(&Symbol::DEV_TMP2); } Layout::F32 => todo!("NumEq: layout, {:?}", self.layout_interner.dbg(Layout::F32)), Layout::F64 => todo!("NumEq: layout, {:?}", self.layout_interner.dbg(Layout::F64)), diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index f41f62c4a1..1045dc3283 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -3898,7 +3898,7 @@ fn when_on_decimals() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn when_on_i128() { assert_evals_to!( indoc!( From 2b849f167f44b3b8d4eba2a6738709df3da9322f Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 21:56:59 +0200 Subject: [PATCH 11/28] rename register function --- crates/compiler/gen_dev/src/generic64/aarch64.rs | 2 +- crates/compiler/gen_dev/src/generic64/mod.rs | 13 +++++++++++-- crates/compiler/gen_dev/src/generic64/x86_64.rs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 02ecdf3222..f0a01416f7 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -1021,7 +1021,7 @@ impl Assembler for AArch64Assembler { } #[inline(always)] - fn eq_reg64_reg64_reg64( + fn eq_reg_reg_reg( buf: &mut Vec<'_, u8>, _register_width: RegisterWidth, dst: AArch64GeneralReg, diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 2b9ab39484..9d1aaf4d84 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -456,7 +456,7 @@ pub trait Assembler: Sized + Copy { src2: GeneralReg, ); - fn eq_reg64_reg64_reg64( + fn eq_reg_reg_reg( buf: &mut Vec<'_, u8>, register_width: RegisterWidth, dst: GeneralReg, @@ -464,6 +464,15 @@ pub trait Assembler: Sized + Copy { src2: GeneralReg, ); + fn eq_reg64_reg64_reg64( + buf: &mut Vec<'_, u8>, + dst: GeneralReg, + src1: GeneralReg, + src2: GeneralReg, + ) { + Self::eq_reg_reg_reg(buf, RegisterWidth::W64, dst, src1, src2) + } + fn neq_reg64_reg64_reg64( buf: &mut Vec<'_, u8>, register_width: RegisterWidth, @@ -1393,7 +1402,7 @@ impl< let width = RegisterWidth::W8; // we're comparing booleans let dst_reg = self.storage_manager.load_to_general_reg(&mut self.buf, dst); - ASM::eq_reg64_reg64_reg64(&mut self.buf, width, dst_reg, dst_reg, tmp_reg); + ASM::eq_reg_reg_reg(&mut self.buf, width, dst_reg, dst_reg, tmp_reg); } other => { let ident_ids = self diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index b72128b152..fd3af2852e 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1635,7 +1635,7 @@ impl Assembler for X86_64Assembler { } #[inline(always)] - fn eq_reg64_reg64_reg64( + fn eq_reg_reg_reg( buf: &mut Vec<'_, u8>, register_width: RegisterWidth, dst: X86_64GeneralReg, From 0f058c8b464f7f06a6007cb45353ace16ddad7c6 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 21:58:02 +0200 Subject: [PATCH 12/28] 128-bit multiplication --- crates/compiler/gen_dev/src/generic64/mod.rs | 7 +++++ .../compiler/gen_dev/src/generic64/x86_64.rs | 26 ++++++++++++++++++- crates/compiler/gen_dev/src/lib.rs | 18 ++----------- crates/compiler/test_gen/src/gen_num.rs | 20 +++++++------- 4 files changed, 43 insertions(+), 28 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 9d1aaf4d84..f72ddf4dbc 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -848,6 +848,13 @@ impl< let dst_reg = self.storage_manager.claim_float_reg(&mut self.buf, dst); ASM::mov_freg64_freg64(&mut self.buf, dst_reg, CC::FLOAT_RETURN_REGS[0]); } + Layout::I128 | Layout::U128 => { + let offset = self.storage_manager.claim_stack_area(dst, 16); + + ASM::mov_base32_reg64(&mut self.buf, offset + 0, CC::GENERAL_RETURN_REGS[0]); + ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); + } + other => { // match self.layout_interner.get(other) { diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index fd3af2852e..734f7de6c5 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -458,8 +458,32 @@ impl X64_64SystemVStoreArgs { match in_layout { single_register_integers!() => self.store_arg_general(buf, storage_manager, sym), single_register_floats!() => self.store_arg_float(buf, storage_manager, sym), + Layout::I128 | Layout::U128 => { + let (offset, _) = storage_manager.stack_offset_and_size(&sym); + + if self.general_i + 1 < Self::GENERAL_PARAM_REGS.len() { + let reg1 = Self::GENERAL_PARAM_REGS[self.general_i + 0]; + let reg2 = Self::GENERAL_PARAM_REGS[self.general_i + 1]; + + X86_64Assembler::mov_reg64_base32(buf, reg1, offset + 0); + X86_64Assembler::mov_reg64_base32(buf, reg2, offset + 8); + + self.general_i += 2; + } else { + // Copy to stack using return reg as buffer. + let reg = Self::GENERAL_RETURN_REGS[0]; + + X86_64Assembler::mov_reg64_base32(buf, reg, offset + 0); + X86_64Assembler::mov_stack32_reg64(buf, self.tmp_stack_offset + 0, reg); + + X86_64Assembler::mov_reg64_base32(buf, reg, offset + 8); + X86_64Assembler::mov_stack32_reg64(buf, self.tmp_stack_offset + 8, reg); + + self.tmp_stack_offset += 16; + } + } x if layout_interner.stack_size(x) == 0 => {} - x if layout_interner.stack_size(x) > 16 => { + x if layout_interner.stack_size(x) == 16 => { // TODO: Double check this. // Just copy onto the stack. // Use return reg as buffer because it will be empty right now. diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index 65996e763f..cd10214880 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -546,22 +546,8 @@ trait Backend<'a> { arg_layouts, ret_layout, ), - LowLevel::NumMul => { - debug_assert_eq!( - 2, - args.len(), - "NumMul: expected to have exactly two argument" - ); - debug_assert_eq!( - arg_layouts[0], arg_layouts[1], - "NumMul: expected all arguments of to have the same layout" - ); - debug_assert_eq!( - arg_layouts[0], *ret_layout, - "NumMul: expected to have the same argument and return layout" - ); - self.build_num_mul(sym, &args[0], &args[1], ret_layout) - } + LowLevel::NumMul => self.build_num_mul(sym, &args[0], &args[1], ret_layout), + LowLevel::NumMulWrap => self.build_num_mul(sym, &args[0], &args[1], ret_layout), LowLevel::NumDivTruncUnchecked | LowLevel::NumDivFrac => { debug_assert_eq!( 2, diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 1045dc3283..5307b9ac49 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -1970,17 +1970,15 @@ fn float_negative_mul_overflow() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] -fn int_mul_wrap() { - assert_evals_to!( - indoc!( - r#" - Num.mulWrap Num.maxI64 2 - "# - ), - -2, - i64 - ); +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn int_mul_wrap_i64() { + assert_evals_to!("Num.mulWrap Num.maxI64 2", -2, i64); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn int_mul_wrap_i128() { + assert_evals_to!("Num.mulWrap Num.maxI128 2", -2, i128); } #[test] From f6ebeff2986b3fd5f04320b872cc0cd024e3f416 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 22:17:18 +0200 Subject: [PATCH 13/28] unboxing of non-recursive tag unions --- crates/compiler/gen_dev/src/generic64/mod.rs | 17 +++++++++++++---- .../compiler/gen_dev/src/generic64/x86_64.rs | 2 +- .../compiler/test_gen/src/gen_primitives.rs | 19 +++++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index f72ddf4dbc..ebde7bbf31 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -3032,7 +3032,7 @@ impl< if size - copied >= 8 { for _ in (0..(size - copied)).step_by(8) { ASM::mov_reg64_mem64_offset32(buf, tmp_reg, ptr_reg, copied); - ASM::mov_base32_reg64(buf, base_offset, tmp_reg); + ASM::mov_base32_reg64(buf, base_offset + copied, tmp_reg); copied += 8; } @@ -3041,7 +3041,7 @@ impl< if size - copied >= 4 { for _ in (0..(size - copied)).step_by(4) { ASM::mov_reg32_mem32_offset32(buf, tmp_reg, ptr_reg, copied); - ASM::mov_base32_reg32(buf, base_offset, tmp_reg); + ASM::mov_base32_reg32(buf, base_offset + copied, tmp_reg); copied += 4; } @@ -3050,7 +3050,7 @@ impl< if size - copied >= 2 { for _ in (0..(size - copied)).step_by(2) { ASM::mov_reg16_mem16_offset32(buf, tmp_reg, ptr_reg, copied); - ASM::mov_base32_reg16(buf, base_offset, tmp_reg); + ASM::mov_base32_reg16(buf, base_offset + copied, tmp_reg); copied += 2; } @@ -3059,7 +3059,7 @@ impl< if size - copied >= 1 { for _ in (0..(size - copied)).step_by(1) { ASM::mov_reg8_mem8_offset32(buf, tmp_reg, ptr_reg, copied); - ASM::mov_base32_reg8(buf, base_offset, tmp_reg); + ASM::mov_base32_reg8(buf, base_offset + copied, tmp_reg); copied += 1; } @@ -3132,6 +3132,15 @@ impl< }); } + Layout::Union(UnionLayout::NonRecursive(_)) => { + // put it on the stack + let stack_size = layout_interner.stack_size(element_in_layout); + + storage_manager.with_tmp_general_reg(buf, |storage_manager, buf, tmp_reg| { + Self::unbox_to_stack(buf, storage_manager, dst, stack_size, ptr_reg, tmp_reg); + }); + } + _ => todo!("unboxing of {:?}", layout_interner.dbg(element_in_layout)), } } diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 734f7de6c5..7d8a7ae01d 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -483,7 +483,7 @@ impl X64_64SystemVStoreArgs { } } x if layout_interner.stack_size(x) == 0 => {} - x if layout_interner.stack_size(x) == 16 => { + x if layout_interner.stack_size(x) > 16 => { // TODO: Double check this. // Just copy onto the stack. // Use return reg as buffer because it will be empty right now. diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 791bcda754..4bfcade3b3 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -3288,6 +3288,25 @@ fn box_and_unbox_big_string() { ) } +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] +fn box_and_unbox_nonrecursive_tag() { + assert_evals_to!( + indoc!( + r#" + result : Result U64 U64 + result = Ok 42 + + result + |> Box.box + |> Box.unbox + "# + ), + roc_std::RocResult::ok(42), + roc_std::RocResult + ) +} + #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn box_num() { From b648507a91462a36995d9d4f451627a45e224bc9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 22:19:47 +0200 Subject: [PATCH 14/28] 128-bit value passing code --- crates/compiler/gen_dev/src/generic64/x86_64.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 7d8a7ae01d..a6ef33d3ef 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -657,6 +657,10 @@ impl X64_64SystemVLoadArgs { storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); self.argument_offset += stack_size as i32; } + Layout::Builtin(Builtin::Int(IntWidth::U128 | IntWidth::I128)) => { + storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); + self.argument_offset += stack_size as i32; + } Layout::Union(UnionLayout::NonRecursive(_)) => { // for now, just also store this on the stack storage_manager.complex_stack_arg(&sym, self.argument_offset, stack_size); From eaef21f7d4d42898a41e41b00e13c261ee025b9f Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 22:21:45 +0200 Subject: [PATCH 15/28] intcast cases (probably wrong) --- crates/compiler/gen_dev/src/generic64/mod.rs | 71 +++++++++++++++++++- crates/compiler/test_gen/src/gen_num.rs | 2 +- 2 files changed, 71 insertions(+), 2 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index ebde7bbf31..ad172b3ae1 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2900,8 +2900,40 @@ impl< source: IntWidth, target: IntWidth, ) { + use IntWidth::*; + let buf = &mut self.buf; + match (source, target) { + (U128, U64) => { + let dst_reg = self.storage_manager.claim_general_reg(buf, dst); + + let (offset, _size) = self.storage_manager.stack_offset_and_size(src); + + ASM::mov_reg64_base32(buf, dst_reg, offset + 8); + + return; + } + (U64, U128) => { + let src_reg = self.storage_manager.load_to_general_reg(buf, src); + + let base_offset = self.storage_manager.claim_stack_area(&dst, 16); + + let tmp = Symbol::DEV_TMP; + let tmp_reg = self.storage_manager.claim_general_reg(buf, &tmp); + + // move a zero into the lower 8 bytes + ASM::mov_reg64_imm64(buf, tmp_reg, 0x0); + ASM::mov_base32_reg64(buf, base_offset, tmp_reg); + + ASM::mov_base32_reg64(buf, base_offset + 8, src_reg); + + return; + } + + _ => {} + } + let dst_reg = self.storage_manager.claim_general_reg(buf, dst); let src_reg = self.storage_manager.load_to_general_reg(buf, src); @@ -2911,7 +2943,44 @@ impl< _ => todo!("int cast from {source:?} to {target:?}"), } } else { - todo!("int cast from {source:?} to {target:?}"); + match (source, target) { + (U8, U16 | U32 | U64) => { + // zero out the register + ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); + + // move the 8-bit integer + ASM::mov_reg_reg(buf, RegisterWidth::W8, dst_reg, src_reg); + } + (U16, U32 | U64) => { + // zero out the register + ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); + + // move the 16-bit integer + ASM::mov_reg_reg(buf, RegisterWidth::W16, dst_reg, src_reg); + } + (U32, U64) => { + // zero out the register + ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); + + // move the 32-bit integer + ASM::mov_reg_reg(buf, RegisterWidth::W32, dst_reg, src_reg); + } + (U64, U32) => { + // move as a 32-bit integer (leaving any other bits behind) + ASM::mov_reg_reg(buf, RegisterWidth::W32, dst_reg, src_reg); + } + (U64, U16) => { + // move as a 16-bit integer (leaving any other bits behind) + ASM::mov_reg_reg(buf, RegisterWidth::W16, dst_reg, src_reg); + } + (U64, I8) => { + // + ASM::mov_reg_reg(buf, RegisterWidth::W8, dst_reg, src_reg); + + // TODO extension? + } + _ => todo!("int cast from {source:?} to {target:?}"), + } } } } diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 5307b9ac49..7214dc83aa 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -3684,7 +3684,7 @@ fn to_float_f64() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] // https://github.com/roc-lang/roc/issues/2696 fn upcast_of_int_is_zext() { assert_evals_to!( From 048615e6a5f4225dc050639090a18f9e6dca02fc Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 23:33:39 +0200 Subject: [PATCH 16/28] fix wrong temp symbol used --- crates/compiler/gen_dev/src/generic64/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index ad172b3ae1..b85cce3863 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -1763,7 +1763,7 @@ impl< self.layout_interner, &mut self.buf, base_offset, - &Symbol::DEV_TMP4, + &Symbol::DEV_TMP3, &ret_layout, ); From 64130dbbbcc412360db0139821836513f0fc14e5 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 23:33:52 +0200 Subject: [PATCH 17/28] use bool literals for bools --- crates/compiler/mono/src/code_gen_help/equality.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/compiler/mono/src/code_gen_help/equality.rs b/crates/compiler/mono/src/code_gen_help/equality.rs index 6262936d8d..f4552fc540 100644 --- a/crates/compiler/mono/src/code_gen_help/equality.rs +++ b/crates/compiler/mono/src/code_gen_help/equality.rs @@ -54,11 +54,11 @@ pub fn eq_generic<'a>( Stmt::Let( Symbol::BOOL_TRUE, - Expr::Literal(Literal::Int(1i128.to_ne_bytes())), + Expr::Literal(Literal::Bool(true)), LAYOUT_BOOL, root.arena.alloc(Stmt::Let( Symbol::BOOL_FALSE, - Expr::Literal(Literal::Int(0i128.to_ne_bytes())), + Expr::Literal(Literal::Bool(false)), LAYOUT_BOOL, root.arena.alloc(main_body), )), From f95c79bb531b26e882ff671ebe69794bd05d44a9 Mon Sep 17 00:00:00 2001 From: Folkert Date: Wed, 26 Apr 2023 23:58:01 +0200 Subject: [PATCH 18/28] copy arguments that are not size multiple of 8 --- .../compiler/gen_dev/src/generic64/aarch64.rs | 28 +++++-- crates/compiler/gen_dev/src/generic64/mod.rs | 21 ++++- .../compiler/gen_dev/src/generic64/x86_64.rs | 80 +++++++++++++++---- 3 files changed, 105 insertions(+), 24 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index f0a01416f7..45b4568670 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -980,14 +980,26 @@ impl Assembler for AArch64Assembler { todo!("saving floating point reg to stack for AArch64"); } #[inline(always)] - fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: AArch64GeneralReg) { - if offset < 0 { - todo!("negative stack offsets for AArch64"); - } else if offset < (0xFFF << 8) { - debug_assert!(offset % 8 == 0); - str_reg64_reg64_imm12(buf, src, AArch64GeneralReg::ZRSP, (offset as u16) >> 3); - } else { - todo!("stack offsets over 32k for AArch64"); + fn mov_stack32_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + offset: i32, + src: AArch64GeneralReg, + ) { + match register_width { + RegisterWidth::W8 => todo!(), + RegisterWidth::W16 => todo!(), + RegisterWidth::W32 => todo!(), + RegisterWidth::W64 => { + if offset < 0 { + todo!("negative stack offsets for AArch64"); + } else if offset < (0xFFF << 8) { + debug_assert!(offset % 8 == 0); + str_reg64_reg64_imm12(buf, src, AArch64GeneralReg::ZRSP, (offset as u16) >> 3); + } else { + todo!("stack offsets over 32k for AArch64"); + } + } } } #[inline(always)] diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index b85cce3863..a05ab3e1d3 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -362,7 +362,26 @@ pub trait Assembler: Sized + Copy { fn mov_freg64_stack32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); fn mov_reg64_stack32(buf: &mut Vec<'_, u8>, dst: GeneralReg, offset: i32); fn mov_stack32_freg64(buf: &mut Vec<'_, u8>, offset: i32, src: FloatReg); - fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg); + + fn mov_stack32_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + offset: i32, + src: GeneralReg, + ); + + fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg) { + Self::mov_stack32_reg(buf, RegisterWidth::W64, offset, src) + } + fn mov_stack32_reg32(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg) { + Self::mov_stack32_reg(buf, RegisterWidth::W32, offset, src) + } + fn mov_stack32_reg16(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg) { + Self::mov_stack32_reg(buf, RegisterWidth::W16, offset, src) + } + fn mov_stack32_reg8(buf: &mut Vec<'_, u8>, offset: i32, src: GeneralReg) { + Self::mov_stack32_reg(buf, RegisterWidth::W8, offset, src) + } fn sqrt_freg64_freg64(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); fn sqrt_freg32_freg32(buf: &mut Vec<'_, u8>, dst: FloatReg, src: FloatReg); diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index a6ef33d3ef..a97f54afec 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -536,21 +536,50 @@ impl X64_64SystemVStoreArgs { self.tmp_stack_offset += size as i32; } Layout::Union(UnionLayout::NonRecursive(_)) => { - // for now, just also store this on the stack + type ASM = X86_64Assembler; + + let tmp_reg = Self::GENERAL_RETURN_REGS[0]; + let stack_offset = self.tmp_stack_offset as i32; + + let mut copied = 0; let (base_offset, size) = storage_manager.stack_offset_and_size(&sym); - debug_assert_eq!(base_offset % 8, 0); - for i in (0..size as i32).step_by(8) { - X86_64Assembler::mov_reg64_base32( - buf, - Self::GENERAL_RETURN_REGS[0], - base_offset + i, - ); - X86_64Assembler::mov_stack32_reg64( - buf, - self.tmp_stack_offset + i, - Self::GENERAL_RETURN_REGS[0], - ); + + if size - copied >= 8 { + for _ in (0..(size - copied)).step_by(8) { + ASM::mov_reg64_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg64(buf, stack_offset + copied as i32, tmp_reg); + + copied += 8; + } } + + if size - copied >= 4 { + for _ in (0..(size - copied)).step_by(4) { + ASM::mov_reg32_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg32(buf, stack_offset + copied as i32, tmp_reg); + + copied += 4; + } + } + + if size - copied >= 2 { + for _ in (0..(size - copied)).step_by(2) { + ASM::mov_reg16_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg16(buf, stack_offset + copied as i32, tmp_reg); + + copied += 2; + } + } + + if size - copied >= 1 { + for _ in (0..(size - copied)).step_by(1) { + ASM::mov_reg8_base32(buf, tmp_reg, base_offset + copied as i32); + ASM::mov_stack32_reg8(buf, stack_offset + copied as i32, tmp_reg); + + copied += 1; + } + } + self.tmp_stack_offset += size as i32; } _ => { @@ -1631,8 +1660,13 @@ impl Assembler for X86_64Assembler { movsd_base64_offset32_freg64(buf, X86_64GeneralReg::RSP, offset, src) } #[inline(always)] - fn mov_stack32_reg64(buf: &mut Vec<'_, u8>, offset: i32, src: X86_64GeneralReg) { - mov_base64_offset32_reg64(buf, X86_64GeneralReg::RSP, offset, src) + fn mov_stack32_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + offset: i32, + src: X86_64GeneralReg, + ) { + mov_base_offset32_reg(buf, register_width, X86_64GeneralReg::RSP, offset, src) } #[inline(always)] @@ -2583,6 +2617,22 @@ fn mov_reg_reg( // The following base and stack based operations could be optimized based on how many bytes the offset actually is. +#[inline(always)] +fn mov_base_offset32_reg( + buf: &mut Vec<'_, u8>, + register_width: RegisterWidth, + base: X86_64GeneralReg, + offset: i32, + src: X86_64GeneralReg, +) { + match register_width { + RegisterWidth::W8 => mov_base16_offset32_reg16(buf, base, offset, src), + RegisterWidth::W16 => mov_base16_offset32_reg16(buf, base, offset, src), + RegisterWidth::W32 => mov_base32_offset32_reg32(buf, base, offset, src), + RegisterWidth::W64 => mov_base64_offset32_reg64(buf, base, offset, src), + } +} + /// `MOV r/m64,r64` -> Move r64 to r/m64, where m64 references a base + offset. #[inline(always)] fn mov_base64_offset32_reg64( From c6c28e7c5df4c627d08cb8b899d38edb27ffed5c Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 00:29:21 +0200 Subject: [PATCH 19/28] implement Crash --- .../compiler/builtins/bitcode/src/utils.zig | 15 +++++---- crates/compiler/gen_dev/src/lib.rs | 32 +++++++++++++++++-- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/crates/compiler/builtins/bitcode/src/utils.zig b/crates/compiler/builtins/bitcode/src/utils.zig index 841b143863..8e5271d711 100644 --- a/crates/compiler/builtins/bitcode/src/utils.zig +++ b/crates/compiler/builtins/bitcode/src/utils.zig @@ -107,15 +107,16 @@ pub fn memcpy(dst: [*]u8, src: [*]u8, size: usize) void { // indirection because otherwise zig creates an alias to the panic function which our LLVM code // does not know how to deal with -pub fn test_panic(c_ptr: *anyopaque, alignment: u32) callconv(.C) void { +pub fn test_panic(c_ptr: *anyopaque, crash_tag: u32) callconv(.C) void { _ = c_ptr; - _ = alignment; - // const cstr = @ptrCast([*:0]u8, c_ptr); + _ = crash_tag; - // const stderr = std.io.getStdErr().writer(); - // stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable; - - // std.c.exit(1); + // const cstr = @ptrCast([*:0]u8, c_ptr); + // + // const stderr = std.io.getStdErr().writer(); + // stderr.print("Roc panicked: {s}!\n", .{cstr}) catch unreachable; + // + // std.c.exit(1); } pub const Inc = fn (?[*]u8) callconv(.C) void; diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index cd10214880..ad597aac63 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -14,8 +14,8 @@ use roc_module::low_level::{LowLevel, LowLevelWrapperType}; use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_mono::code_gen_help::{CallerProc, CodeGenHelp}; use roc_mono::ir::{ - BranchInfo, CallType, Expr, HigherOrderLowLevel, JoinPointId, ListLiteralElement, Literal, - Param, Proc, ProcLayout, SelfRecursive, Stmt, + BranchInfo, CallType, CrashTag, Expr, HigherOrderLowLevel, JoinPointId, ListLiteralElement, + Literal, Param, Proc, ProcLayout, SelfRecursive, Stmt, }; use roc_mono::layout::{ Builtin, InLayout, Layout, LayoutIds, LayoutInterner, STLayoutInterner, TagIdIntType, @@ -279,9 +279,33 @@ trait Backend<'a> { self.build_jump(id, args, arg_layouts.into_bump_slice(), ret_layout); self.free_symbols(stmt); } + Stmt::Crash(msg, crash_tag) => self.roc_panic(*msg, *crash_tag), x => todo!("the statement, {:?}", x), } } + + fn roc_panic(&mut self, msg: Symbol, crash_tag: CrashTag) { + self.load_literal( + &Symbol::DEV_TMP, + &Layout::U32, + &Literal::Int((crash_tag as u128).to_ne_bytes()), + ); + + // Now that the arguments are needed, load them if they are literals. + let arguments = &[msg, Symbol::DEV_TMP]; + self.load_literal_symbols(arguments); + self.build_fn_call( + &Symbol::DEV_TMP2, + String::from("roc_panic"), + arguments, + &[Layout::STR, Layout::U32], + &Layout::UNIT, + ); + + self.free_symbol(&Symbol::DEV_TMP); + self.free_symbol(&Symbol::DEV_TMP2); + } + // build_switch generates a instructions for a switch statement. fn build_switch( &mut self, @@ -1758,7 +1782,9 @@ trait Backend<'a> { Stmt::Expect { .. } => todo!("expect is not implemented in the dev backend"), Stmt::ExpectFx { .. } => todo!("expect-fx is not implemented in the dev backend"), - Stmt::Crash(..) => todo!("crash is not implemented in the dev backend"), + Stmt::Crash(msg, _crash_tag) => { + self.set_last_seen(*msg, stmt); + } } } From fc391b1ab21f25413ca55b6ae0fef3cbf305eefc Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 00:39:30 +0200 Subject: [PATCH 20/28] clippy --- crates/compiler/gen_dev/src/generic64/mod.rs | 4 ++-- crates/compiler/gen_dev/src/generic64/x86_64.rs | 9 +++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index a05ab3e1d3..95ca82dfc9 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -870,7 +870,7 @@ impl< Layout::I128 | Layout::U128 => { let offset = self.storage_manager.claim_stack_area(dst, 16); - ASM::mov_base32_reg64(&mut self.buf, offset + 0, CC::GENERAL_RETURN_REGS[0]); + ASM::mov_base32_reg64(&mut self.buf, offset, CC::GENERAL_RETURN_REGS[0]); ASM::mov_base32_reg64(&mut self.buf, offset + 8, CC::GENERAL_RETURN_REGS[1]); } @@ -2936,7 +2936,7 @@ impl< (U64, U128) => { let src_reg = self.storage_manager.load_to_general_reg(buf, src); - let base_offset = self.storage_manager.claim_stack_area(&dst, 16); + let base_offset = self.storage_manager.claim_stack_area(dst, 16); let tmp = Symbol::DEV_TMP; let tmp_reg = self.storage_manager.claim_general_reg(buf, &tmp); diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index a97f54afec..6379f94a99 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -462,10 +462,10 @@ impl X64_64SystemVStoreArgs { let (offset, _) = storage_manager.stack_offset_and_size(&sym); if self.general_i + 1 < Self::GENERAL_PARAM_REGS.len() { - let reg1 = Self::GENERAL_PARAM_REGS[self.general_i + 0]; + let reg1 = Self::GENERAL_PARAM_REGS[self.general_i]; let reg2 = Self::GENERAL_PARAM_REGS[self.general_i + 1]; - X86_64Assembler::mov_reg64_base32(buf, reg1, offset + 0); + X86_64Assembler::mov_reg64_base32(buf, reg1, offset); X86_64Assembler::mov_reg64_base32(buf, reg2, offset + 8); self.general_i += 2; @@ -473,8 +473,8 @@ impl X64_64SystemVStoreArgs { // Copy to stack using return reg as buffer. let reg = Self::GENERAL_RETURN_REGS[0]; - X86_64Assembler::mov_reg64_base32(buf, reg, offset + 0); - X86_64Assembler::mov_stack32_reg64(buf, self.tmp_stack_offset + 0, reg); + X86_64Assembler::mov_reg64_base32(buf, reg, offset); + X86_64Assembler::mov_stack32_reg64(buf, self.tmp_stack_offset, reg); X86_64Assembler::mov_reg64_base32(buf, reg, offset + 8); X86_64Assembler::mov_stack32_reg64(buf, self.tmp_stack_offset + 8, reg); @@ -2550,6 +2550,7 @@ fn raw_mov_reg_reg( } } +#[allow(unused)] fn raw_movsx_reg_reg( buf: &mut Vec, input_width: RegisterWidth, From 2aca07d8893ee448a967cd03934946c4fcb5c7c8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 00:43:59 +0200 Subject: [PATCH 21/28] asserted restrictions have been lifted --- crates/compiler/gen_dev/src/generic64/storage.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/storage.rs b/crates/compiler/gen_dev/src/generic64/storage.rs index 4fb8b70df9..3c674dd4f9 100644 --- a/crates/compiler/gen_dev/src/generic64/storage.rs +++ b/crates/compiler/gen_dev/src/generic64/storage.rs @@ -852,15 +852,10 @@ impl< ) } _ if layout_interner.stack_size(*layout) == 0 => {} - // TODO: Verify this is always true. - // The dev backend does not deal with refcounting and does not care about if data is safe to memcpy. - // It is just temporarily storing the value due to needing to free registers. - // Later, it will be reloaded and stored in refcounted as needed. - _ if layout_interner.stack_size(*layout) > 8 => { + Layout::Struct { .. } | Layout::Union(UnionLayout::NonRecursive(_)) => { let (from_offset, size) = self.stack_offset_and_size(sym); - debug_assert_eq!(from_offset % 8, 0); - debug_assert_eq!(size % 8, 0); debug_assert_eq!(size, layout_interner.stack_size(*layout)); + self.copy_to_stack_offset(buf, size, from_offset, to_offset) } x => todo!("copying data to the stack with layout, {:?}", x), From 91079d38444701e01b9e14506ca66061bf67b6d3 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 01:20:53 +0200 Subject: [PATCH 22/28] wasm backend: mulWrap for i128 --- crates/compiler/gen_wasm/src/low_level.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index 6eb321e155..6cb8f03fa6 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -1014,8 +1014,7 @@ impl<'a> LowLevelCall<'a> { NumMulWrap => match self.ret_layout_raw { Layout::Builtin(Builtin::Int(width)) => match width { IntWidth::I128 | IntWidth::U128 => { - // TODO: don't panic - self.load_args_and_call_zig(backend, &bitcode::NUM_MUL_OR_PANIC_INT[width]) + self.load_args_and_call_zig(backend, &bitcode::NUM_MUL_WRAP_INT[width]) } IntWidth::I64 | IntWidth::U64 => { self.load_args(backend); From 9357e1ce2b92e4b967d91a1f5757cb1001f8335b Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 10:44:54 +0200 Subject: [PATCH 23/28] export fn does not work for wasm/llvm --- crates/compiler/builtins/bitcode/src/num.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/compiler/builtins/bitcode/src/num.zig b/crates/compiler/builtins/bitcode/src/num.zig index f85eeb015c..a9cbfcf2bf 100644 --- a/crates/compiler/builtins/bitcode/src/num.zig +++ b/crates/compiler/builtins/bitcode/src/num.zig @@ -473,7 +473,7 @@ pub fn exportMulWrappedInt(comptime T: type, comptime name: []const u8) void { @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); } -pub export fn shiftRightZeroFillI128(self: i128, other: u8) i128 { +pub fn shiftRightZeroFillI128(self: i128, other: u8) callconv(.C) i128 { if (other & 0b1000_0000 > 0) { return 0; } else { @@ -481,7 +481,7 @@ pub export fn shiftRightZeroFillI128(self: i128, other: u8) i128 { } } -pub export fn shiftRightZeroFillU128(self: u128, other: u8) u128 { +pub fn shiftRightZeroFillU128(self: u128, other: u8) callconv(.C) u128 { if (other & 0b1000_0000 > 0) { return 0; } else { From 1dd4b470ddc66760227bde9612b4065a2a26528e Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 11:18:11 +0200 Subject: [PATCH 24/28] more casts, u128 literals --- crates/compiler/gen_dev/src/generic64/mod.rs | 7 +++++-- crates/compiler/test_gen/src/gen_num.rs | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 95ca82dfc9..c7609ccfa0 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2544,7 +2544,7 @@ impl< ASM::mov_reg64_imm64(&mut self.buf, reg, i128::from_ne_bytes(val) as i64); } ( - Literal::Int(bytes), + Literal::Int(bytes) | Literal::U128(bytes), Layout::Builtin(Builtin::Int(IntWidth::I128 | IntWidth::U128)), ) => { self.storage_manager.with_tmp_general_reg( @@ -2643,7 +2643,7 @@ impl< self.create_array(sym, &Layout::U8, elements.into_bump_slice()) } } - x => todo!("loading literal, {:?}", x), + _ => todo!("loading literal {:?} with layout {:?}", lit, layout), } } @@ -2959,6 +2959,9 @@ impl< if source.stack_size() == target.stack_size() { match source.stack_size() { 8 => ASM::mov_reg64_reg64(buf, dst_reg, src_reg), + 4 => ASM::mov_reg32_reg32(buf, dst_reg, src_reg), + 2 => ASM::mov_reg16_reg16(buf, dst_reg, src_reg), + 1 => ASM::mov_reg8_reg8(buf, dst_reg, src_reg), _ => todo!("int cast from {source:?} to {target:?}"), } } else { diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 7214dc83aa..f1ac14f90e 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2097,7 +2097,7 @@ fn shift_right_zf_by() { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn shift_right_cast_i8() { // FIXME (Brian) Something funny happening with 8-bit binary literals in tests From d10ae2412aecaeb90c2290a95a132052ea215ea0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 11:36:56 +0200 Subject: [PATCH 25/28] num conversion --- crates/compiler/gen_dev/src/generic64/mod.rs | 21 +- crates/compiler/test_gen/src/gen_num.rs | 258 ++++--------------- 2 files changed, 63 insertions(+), 216 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index c7609ccfa0..44d2e0cc54 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -2966,7 +2966,8 @@ impl< } } else { match (source, target) { - (U8, U16 | U32 | U64) => { + // -- CASTING UP -- + (I8 | U8, U16 | U32 | U64) => { // zero out the register ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); @@ -2987,19 +2988,25 @@ impl< // move the 32-bit integer ASM::mov_reg_reg(buf, RegisterWidth::W32, dst_reg, src_reg); } - (U64, U32) => { + (I8, I16 | I32 | I64) => { + // zero out the register + ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); + + // move the 8-bit integer + ASM::mov_reg_reg(buf, RegisterWidth::W8, dst_reg, src_reg); + } + // -- CASTING DOWN -- + (U64 | I64, I32 | U32) => { // move as a 32-bit integer (leaving any other bits behind) ASM::mov_reg_reg(buf, RegisterWidth::W32, dst_reg, src_reg); } - (U64, U16) => { + (U64 | I64 | U32 | I32, I16 | U16) => { // move as a 16-bit integer (leaving any other bits behind) ASM::mov_reg_reg(buf, RegisterWidth::W16, dst_reg, src_reg); } - (U64, I8) => { - // + (U64 | I64 | U32 | I32 | U16 | I16, I8 | U8) => { + // move as an 8-bit integer (leaving any other bits behind) ASM::mov_reg_reg(buf, RegisterWidth::W8, dst_reg, src_reg); - - // TODO extension? } _ => todo!("int cast from {source:?} to {target:?}"), } diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index f1ac14f90e..f03d3d1ce7 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2145,281 +2145,121 @@ fn max_i128() { #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_i64() { - assert_evals_to!( - indoc!( - r#" - Num.minI64 - "# - ), - i64::MIN, - i64 - ); + assert_evals_to!("Num.minI64", i64::MIN, i64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_i64() { - assert_evals_to!( - indoc!( - r#" - Num.maxI64 - "# - ), - i64::MAX, - i64 - ); + assert_evals_to!("Num.maxI64", i64::MAX, i64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_u64() { - assert_evals_to!( - indoc!( - r#" - Num.minU64 - "# - ), - u64::MIN, - u64 - ); + assert_evals_to!("Num.minU64", u64::MIN, u64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_u64() { - assert_evals_to!( - indoc!( - r#" - Num.maxU64 - "# - ), - u64::MAX, - u64 - ); + assert_evals_to!("Num.maxU64", u64::MAX, u64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_i32() { - assert_evals_to!( - indoc!( - r#" - Num.minI32 - "# - ), - i32::MIN, - i32 - ); + assert_evals_to!("Num.minI32", i32::MIN, i32); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_i32() { - assert_evals_to!( - indoc!( - r#" - Num.maxI32 - "# - ), - i32::MAX, - i32 - ); + assert_evals_to!("Num.maxI32", i32::MAX, i32); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_u32() { - assert_evals_to!( - indoc!( - r#" - Num.minU32 - "# - ), - u32::MIN, - u32 - ); + assert_evals_to!("Num.minU32", u32::MIN, u32); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_u32() { - assert_evals_to!( - indoc!( - r#" - Num.maxU32 - "# - ), - u32::MAX, - u32 - ); + assert_evals_to!("Num.maxU32", u32::MAX, u32); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_i16() { - assert_evals_to!( - indoc!( - r#" - Num.minI16 - "# - ), - i16::MIN, - i16 - ); + assert_evals_to!("Num.minI16", i16::MIN, i16); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_i16() { - assert_evals_to!( - indoc!( - r#" - Num.maxI16 - "# - ), - i16::MAX, - i16 - ); + assert_evals_to!("Num.maxI16", i16::MAX, i16); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_u16() { - assert_evals_to!( - indoc!( - r#" - Num.minU16 - "# - ), - u16::MIN, - u16 - ); + assert_evals_to!("Num.minU16", u16::MIN, u16); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_u16() { - assert_evals_to!( - indoc!( - r#" - Num.maxU16 - "# - ), - u16::MAX, - u16 - ); + assert_evals_to!("Num.maxU16", u16::MAX, u16); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_i8() { - assert_evals_to!( - indoc!( - r#" - Num.minI8 - "# - ), - i8::MIN, - i8 - ); + assert_evals_to!("Num.minI8", i8::MIN, i8); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_i8() { - assert_evals_to!( - indoc!( - r#" - Num.maxI8 - "# - ), - i8::MAX, - i8 - ); + assert_evals_to!("Num.maxI8", i8::MAX, i8); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_u8() { - assert_evals_to!( - indoc!( - r#" - Num.minU8 - "# - ), - u8::MIN, - u8 - ); + assert_evals_to!("Num.minU8", u8::MIN, u8); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_u8() { - assert_evals_to!( - indoc!( - r#" - Num.maxU8 - "# - ), - u8::MAX, - u8 - ); + assert_evals_to!("Num.maxU8", u8::MAX, u8); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_f64() { - assert_evals_to!( - indoc!( - r#" - Num.maxF64 - "# - ), - f64::MAX, - f64 - ); + assert_evals_to!("Num.maxF64", f64::MAX, f64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_f64() { - assert_evals_to!( - indoc!( - r#" - Num.minF64 - "# - ), - f64::MIN, - f64 - ); + assert_evals_to!("Num.minF64", f64::MIN, f64); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn max_f32() { - assert_evals_to!( - indoc!( - r#" - Num.maxF32 - "# - ), - f32::MAX, - f32 - ); + assert_evals_to!("Num.maxF32", f32::MAX, f32); } #[test] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] fn min_f32() { - assert_evals_to!( - indoc!( - r#" - Num.minF32 - "# - ), - f32::MIN, - f32 - ); + assert_evals_to!("Num.minF32", f32::MIN, f32); } #[test] @@ -2432,7 +2272,7 @@ fn to_nat_truncate_wraps() { macro_rules! num_conversion_tests { ($($fn:expr, $typ:ty, ($($test_name:ident, $input:expr, $output:expr $(, [$($support_gen:literal),*])? )*))*) => {$($( #[test] - #[cfg(any(feature = "gen-llvm", $($(feature = $support_gen)*)?))] + #[cfg(any(feature = "gen-llvm", $($(feature = $support_gen,)*)?))] fn $test_name() { let input = format!("{} {}", $fn, $input); assert_evals_to!(&input, $output, $typ) @@ -2442,25 +2282,25 @@ macro_rules! num_conversion_tests { num_conversion_tests! { "Num.toI8", i8, ( - to_i8_same_width, "15u8", 15, ["gen-wasm"] - to_i8_truncate, "115i32", 115, ["gen-wasm"] - to_i8_truncate_wraps, "500i32", -12, ["gen-wasm"] + to_i8_same_width, "15u8", 15, ["gen-wasm", "gen-dev"] + to_i8_truncate, "115i32", 115, ["gen-wasm", "gen-dev"] + to_i8_truncate_wraps, "500i32", -12, ["gen-wasm", "gen-dev"] ) "Num.toI16", i16, ( - to_i16_same_width, "15u16", 15, ["gen-wasm"] - to_i16_extend, "15i8", 15, ["gen-wasm"] - to_i16_truncate, "115i32", 115, ["gen-wasm"] - to_i16_truncate_wraps, "60000i32", -5536, ["gen-wasm"] + to_i16_same_width, "15u16", 15, ["gen-wasm", "gen-dev"] + to_i16_extend, "15i8", 15, ["gen-wasm", "gen-dev"] + to_i16_truncate, "115i32", 115, ["gen-wasm", "gen-dev"] + to_i16_truncate_wraps, "60000i32", -5536, ["gen-wasm", "gen-dev"] ) "Num.toI32", i32, ( - to_i32_same_width, "15u32", 15, ["gen-wasm"] - to_i32_extend, "15i8", 15, ["gen-wasm"] - to_i32_truncate, "115i64", 115, ["gen-wasm"] - to_i32_truncate_wraps, "5000000000i64", 705032704, ["gen-wasm"] + to_i32_same_width, "15u32", 15, ["gen-wasm", "gen-dev"] + to_i32_extend, "15i8", 15, ["gen-wasm", "gen-dev"] + to_i32_truncate, "115i64", 115, ["gen-wasm", "gen-dev"] + to_i32_truncate_wraps, "5000000000i64", 705032704, ["gen-wasm", "gen-dev"] ) "Num.toI64", i64, ( - to_i64_same_width, "15u64", 15, ["gen-wasm"] - to_i64_extend, "15i8", 15, ["gen-wasm"] + to_i64_same_width, "15u64", 15, ["gen-wasm", "gen-dev"] + to_i64_extend, "15i8", 15, ["gen-wasm", "gen-dev"] to_i64_truncate, "115i128", 115 to_i64_truncate_wraps, "10_000_000_000_000_000_000i128", -8446744073709551616 ) @@ -2469,25 +2309,25 @@ num_conversion_tests! { to_i128_extend, "15i8", 15 ) "Num.toU8", u8, ( - to_u8_same_width, "15i8", 15, ["gen-wasm"] - to_u8_truncate, "115i32", 115, ["gen-wasm"] - to_u8_truncate_wraps, "500i32", 244, ["gen-wasm"] + to_u8_same_width, "15i8", 15, ["gen-wasm", "gen-dev"] + to_u8_truncate, "115i32", 115, ["gen-wasm", "gen-dev"] + to_u8_truncate_wraps, "500i32", 244, ["gen-wasm", "gen-dev"] ) "Num.toU16", u16, ( - to_u16_same_width, "15i16", 15, ["gen-wasm"] - to_u16_extend, "15i8", 15, ["gen-wasm"] - to_u16_truncate, "115i32", 115, ["gen-wasm"] - to_u16_truncate_wraps, "600000000i32", 17920, ["gen-wasm"] + to_u16_same_width, "15i16", 15, ["gen-wasm", "gen-dev"] + to_u16_extend, "15i8", 15, ["gen-wasm", "gen-dev"] + to_u16_truncate, "115i32", 115, ["gen-wasm", "gen-dev"] + to_u16_truncate_wraps, "600000000i32", 17920, ["gen-wasm", "gen-dev"] ) "Num.toU32", u32, ( - to_u32_same_width, "15i32", 15, ["gen-wasm"] - to_u32_extend, "15i8", 15, ["gen-wasm"] - to_u32_truncate, "115i64", 115, ["gen-wasm"] - to_u32_truncate_wraps, "5000000000000000000i64", 1156841472, ["gen-wasm"] + to_u32_same_width, "15i32", 15, ["gen-wasm", "gen-dev"] + to_u32_extend, "15i8", 15, ["gen-wasm", "gen-dev"] + to_u32_truncate, "115i64", 115, ["gen-wasm", "gen-dev"] + to_u32_truncate_wraps, "5000000000000000000i64", 1156841472, ["gen-wasm", "gen-dev"] ) "Num.toU64", u64, ( - to_u64_same_width, "15i64", 15, ["gen-wasm"] - to_u64_extend, "15i8", 15, ["gen-wasm"] + to_u64_same_width, "15i64", 15, ["gen-wasm", "gen-dev"] + to_u64_extend, "15i8", 15, ["gen-wasm", "gen-dev"] to_u64_truncate, "115i128", 115 to_u64_truncate_wraps, "10_000_000_000_000_000_000_000i128", 1864712049423024128 ) @@ -2496,8 +2336,8 @@ num_conversion_tests! { to_u128_extend, "15i8", 15 ) "Num.toNat", usize, ( - to_nat_same_width, "15i64", 15, ["gen-wasm"] - to_nat_extend, "15i8", 15, ["gen-wasm"] + to_nat_same_width, "15i64", 15, ["gen-wasm", "gen-dev"] + to_nat_extend, "15i8", 15, ["gen-wasm", "gen-dev"] to_nat_truncate, "115i128", 115 ) "Num.toF32", f32, ( From 10a497fdde7389db564482034d98356b41f02519 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 12:24:25 +0200 Subject: [PATCH 26/28] sign extension WIP --- .../compiler/gen_dev/src/generic64/aarch64.rs | 11 +++++++++++ crates/compiler/gen_dev/src/generic64/mod.rs | 17 ++++++++++++++++- crates/compiler/gen_dev/src/generic64/x86_64.rs | 10 ++++++++++ crates/compiler/test_gen/src/gen_num.rs | 3 +++ 4 files changed, 40 insertions(+), 1 deletion(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index 45b4568670..b5fccb764a 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -765,6 +765,17 @@ impl Assembler for AArch64Assembler { } } + #[inline(always)] + fn movsx_reg_reg( + _buf: &mut Vec<'_, u8>, + _input_width: RegisterWidth, + _output_width: RegisterWidth, + _dst: AArch64GeneralReg, + _src: AArch64GeneralReg, + ) { + todo!("move with sign extension"); + } + #[inline(always)] fn mov_freg64_base32(_buf: &mut Vec<'_, u8>, _dst: AArch64FloatReg, _offset: i32) { todo!("loading floating point reg from base offset for AArch64"); diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index 44d2e0cc54..a17d50548c 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -288,6 +288,15 @@ pub trait Assembler: Sized + Copy { Self::mov_reg_reg(buf, RegisterWidth::W8, dst, src); } + // move with sign extension + fn movsx_reg_reg( + buf: &mut Vec<'_, u8>, + input_width: RegisterWidth, + output_width: RegisterWidth, + dst: GeneralReg, + src: GeneralReg, + ); + // base32 is similar to stack based instructions but they reference the base/frame pointer. fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: FloatReg, offset: i32); @@ -2993,7 +3002,13 @@ impl< ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); // move the 8-bit integer - ASM::mov_reg_reg(buf, RegisterWidth::W8, dst_reg, src_reg); + ASM::movsx_reg_reg( + buf, + RegisterWidth::W8, + RegisterWidth::W16, + dst_reg, + src_reg, + ); } // -- CASTING DOWN -- (U64 | I64, I32 | U32) => { diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 6379f94a99..8495be5df5 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1490,6 +1490,16 @@ impl Assembler for X86_64Assembler { ) { mov_reg_reg(buf, register_width, dst, src); } + #[inline(always)] + fn movsx_reg_reg( + buf: &mut Vec<'_, u8>, + input_width: RegisterWidth, + output_width: RegisterWidth, + dst: X86_64GeneralReg, + src: X86_64GeneralReg, + ) { + raw_movsx_reg_reg(buf, input_width, output_width, dst, src); + } #[inline(always)] fn mov_freg64_base32(buf: &mut Vec<'_, u8>, dst: X86_64FloatReg, offset: i32) { diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index f03d3d1ce7..6ec8ae821e 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2289,18 +2289,21 @@ num_conversion_tests! { "Num.toI16", i16, ( to_i16_same_width, "15u16", 15, ["gen-wasm", "gen-dev"] to_i16_extend, "15i8", 15, ["gen-wasm", "gen-dev"] + to_i16_sign_extend, "-15i8", -15, ["gen-wasm", "gen-dev"] to_i16_truncate, "115i32", 115, ["gen-wasm", "gen-dev"] to_i16_truncate_wraps, "60000i32", -5536, ["gen-wasm", "gen-dev"] ) "Num.toI32", i32, ( to_i32_same_width, "15u32", 15, ["gen-wasm", "gen-dev"] to_i32_extend, "15i8", 15, ["gen-wasm", "gen-dev"] + to_i32_sign_extend, "-15i8", -15, ["gen-wasm", "gen-dev"] to_i32_truncate, "115i64", 115, ["gen-wasm", "gen-dev"] to_i32_truncate_wraps, "5000000000i64", 705032704, ["gen-wasm", "gen-dev"] ) "Num.toI64", i64, ( to_i64_same_width, "15u64", 15, ["gen-wasm", "gen-dev"] to_i64_extend, "15i8", 15, ["gen-wasm", "gen-dev"] + to_i64_sign_extend, "-15i8", -15, ["gen-wasm", "gen-dev"] to_i64_truncate, "115i128", 115 to_i64_truncate_wraps, "10_000_000_000_000_000_000i128", -8446744073709551616 ) From 0bf3eefbf2590d4c3757d1da8eb3aadc42153a5a Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 13:50:27 +0200 Subject: [PATCH 27/28] dev backend: Num.isMultipleOf --- crates/compiler/builtins/bitcode/src/main.zig | 2 ++ crates/compiler/builtins/bitcode/src/num.zig | 24 +++++++++++++++++++ crates/compiler/builtins/src/bitcode.rs | 2 ++ crates/compiler/gen_dev/src/lib.rs | 5 ++++ crates/compiler/test_gen/src/gen_num.rs | 2 +- 5 files changed, 34 insertions(+), 1 deletion(-) diff --git a/crates/compiler/builtins/bitcode/src/main.zig b/crates/compiler/builtins/bitcode/src/main.zig index f219ca8203..2062f4819f 100644 --- a/crates/compiler/builtins/bitcode/src/main.zig +++ b/crates/compiler/builtins/bitcode/src/main.zig @@ -96,6 +96,8 @@ comptime { num.exportMulSaturatedInt(T, WIDEINTS[i], ROC_BUILTINS ++ "." ++ NUM ++ ".mul_saturated."); num.exportMulWrappedInt(T, ROC_BUILTINS ++ "." ++ NUM ++ ".mul_wrapped."); + num.exportIsMultipleOf(T, ROC_BUILTINS ++ "." ++ NUM ++ ".is_multiple_of."); + num.exportCountLeadingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_leading_zero_bits."); num.exportCountTrailingZeroBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_trailing_zero_bits."); num.exportCountOneBits(T, ROC_BUILTINS ++ "." ++ NUM ++ ".count_one_bits."); diff --git a/crates/compiler/builtins/bitcode/src/num.zig b/crates/compiler/builtins/bitcode/src/num.zig index a9cbfcf2bf..15173be6ee 100644 --- a/crates/compiler/builtins/bitcode/src/num.zig +++ b/crates/compiler/builtins/bitcode/src/num.zig @@ -254,6 +254,30 @@ fn bytesToU128(arg: RocList, position: usize) u128 { return @bitCast(u128, [_]u8{ bytes[position], bytes[position + 1], bytes[position + 2], bytes[position + 3], bytes[position + 4], bytes[position + 5], bytes[position + 6], bytes[position + 7], bytes[position + 8], bytes[position + 9], bytes[position + 10], bytes[position + 11], bytes[position + 12], bytes[position + 13], bytes[position + 14], bytes[position + 15] }); } +fn isMultipleOf(comptime T: type, lhs: T, rhs: T) bool { + if (rhs == 0 or rhs == -1) { + // lhs is a multiple of rhs iff + // + // - rhs == -1 + // - both rhs and lhs are 0 + // + // the -1 case is important for overflow reasons `isize::MIN % -1` crashes in rust + return (rhs == -1) or (lhs == 0); + } else { + const rem = @mod(lhs, rhs); + return rem == 0; + } +} + +pub fn exportIsMultipleOf(comptime T: type, comptime name: []const u8) void { + comptime var f = struct { + fn func(self: T, other: T) callconv(.C) bool { + return @call(.{ .modifier = always_inline }, isMultipleOf, .{ T, self, other }); + } + }.func; + @export(f, .{ .name = name ++ @typeName(T), .linkage = .Strong }); +} + fn addWithOverflow(comptime T: type, self: T, other: T) WithOverflow(T) { switch (@typeInfo(T)) { .Int => { diff --git a/crates/compiler/builtins/src/bitcode.rs b/crates/compiler/builtins/src/bitcode.rs index 7bc621a88a..d4e6955a74 100644 --- a/crates/compiler/builtins/src/bitcode.rs +++ b/crates/compiler/builtins/src/bitcode.rs @@ -290,6 +290,8 @@ pub const NUM_MUL_CHECKED_INT: IntrinsicName = int_intrinsic!("roc_builtins.num. pub const NUM_MUL_CHECKED_FLOAT: IntrinsicName = float_intrinsic!("roc_builtins.num.mul_with_overflow"); +pub const NUM_IS_MULTIPLE_OF: IntrinsicName = int_intrinsic!("roc_builtins.num.is_multiple_of"); + pub const NUM_SHIFT_RIGHT_ZERO_FILL: IntrinsicName = int_intrinsic!("roc_builtins.num.shift_right_zero_fill"); diff --git a/crates/compiler/gen_dev/src/lib.rs b/crates/compiler/gen_dev/src/lib.rs index ad597aac63..2c16c3f0b4 100644 --- a/crates/compiler/gen_dev/src/lib.rs +++ b/crates/compiler/gen_dev/src/lib.rs @@ -1165,6 +1165,11 @@ trait Backend<'a> { self.build_num_int_cast(sym, &args[0], source_width, target_width) } + LowLevel::NumIsMultipleOf => { + let int_width = arg_layouts[0].try_int_width().unwrap(); + let intrinsic = bitcode::NUM_IS_MULTIPLE_OF[int_width].to_string(); + self.build_fn_call(sym, intrinsic, args, arg_layouts, ret_layout); + } x => todo!("low level, {:?}", x), } } diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 6ec8ae821e..77f5501334 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2521,7 +2521,7 @@ to_int_checked_tests! { } #[test] -#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] fn is_multiple_of_signed() { // true assert_evals_to!("Num.isMultipleOf 5 1", true, bool); From 5363b95c5fc1c70424e96b6005f10905b9eca633 Mon Sep 17 00:00:00 2001 From: Folkert Date: Thu, 27 Apr 2023 19:40:57 +0200 Subject: [PATCH 28/28] move with sign extension --- .../compiler/gen_dev/src/generic64/aarch64.rs | 1 - crates/compiler/gen_dev/src/generic64/mod.rs | 15 +--- .../compiler/gen_dev/src/generic64/x86_64.rs | 86 +++++++++++-------- crates/compiler/test_gen/src/gen_num.rs | 9 +- 4 files changed, 59 insertions(+), 52 deletions(-) diff --git a/crates/compiler/gen_dev/src/generic64/aarch64.rs b/crates/compiler/gen_dev/src/generic64/aarch64.rs index b5fccb764a..03fc59e8e2 100644 --- a/crates/compiler/gen_dev/src/generic64/aarch64.rs +++ b/crates/compiler/gen_dev/src/generic64/aarch64.rs @@ -769,7 +769,6 @@ impl Assembler for AArch64Assembler { fn movsx_reg_reg( _buf: &mut Vec<'_, u8>, _input_width: RegisterWidth, - _output_width: RegisterWidth, _dst: AArch64GeneralReg, _src: AArch64GeneralReg, ) { diff --git a/crates/compiler/gen_dev/src/generic64/mod.rs b/crates/compiler/gen_dev/src/generic64/mod.rs index a17d50548c..2a47879b2e 100644 --- a/crates/compiler/gen_dev/src/generic64/mod.rs +++ b/crates/compiler/gen_dev/src/generic64/mod.rs @@ -292,7 +292,6 @@ pub trait Assembler: Sized + Copy { fn movsx_reg_reg( buf: &mut Vec<'_, u8>, input_width: RegisterWidth, - output_width: RegisterWidth, dst: GeneralReg, src: GeneralReg, ); @@ -2998,18 +2997,10 @@ impl< ASM::mov_reg_reg(buf, RegisterWidth::W32, dst_reg, src_reg); } (I8, I16 | I32 | I64) => { - // zero out the register - ASM::xor_reg64_reg64_reg64(buf, dst_reg, dst_reg, dst_reg); - - // move the 8-bit integer - ASM::movsx_reg_reg( - buf, - RegisterWidth::W8, - RegisterWidth::W16, - dst_reg, - src_reg, - ); + ASM::movsx_reg_reg(buf, RegisterWidth::W8, dst_reg, src_reg) } + (I16, I32 | I64) => ASM::movsx_reg_reg(buf, RegisterWidth::W16, dst_reg, src_reg), + (I32, I64) => ASM::movsx_reg_reg(buf, RegisterWidth::W32, dst_reg, src_reg), // -- CASTING DOWN -- (U64 | I64, I32 | U32) => { // move as a 32-bit integer (leaving any other bits behind) diff --git a/crates/compiler/gen_dev/src/generic64/x86_64.rs b/crates/compiler/gen_dev/src/generic64/x86_64.rs index 8495be5df5..aadcf1a923 100644 --- a/crates/compiler/gen_dev/src/generic64/x86_64.rs +++ b/crates/compiler/gen_dev/src/generic64/x86_64.rs @@ -1494,11 +1494,10 @@ impl Assembler for X86_64Assembler { fn movsx_reg_reg( buf: &mut Vec<'_, u8>, input_width: RegisterWidth, - output_width: RegisterWidth, dst: X86_64GeneralReg, src: X86_64GeneralReg, ) { - raw_movsx_reg_reg(buf, input_width, output_width, dst, src); + raw_movsx_reg_reg(buf, input_width, dst, src); } #[inline(always)] @@ -2564,46 +2563,31 @@ fn raw_mov_reg_reg( fn raw_movsx_reg_reg( buf: &mut Vec, input_width: RegisterWidth, - output_width: RegisterWidth, dst: X86_64GeneralReg, src: X86_64GeneralReg, ) { - match (input_width, output_width) { - (RegisterWidth::W8, RegisterWidth::W16) => { - buf.push(0x0F); - buf.push(0xBE); - binop_reg8_reg8(0x89, buf, dst, src); + let dst_high = dst as u8 > 7; + let dst_mod = dst as u8 % 8; + let src_high = src as u8 > 7; + let src_mod = src as u8 % 8; + + // NOTE src and dst seem to be flipped here. It works this way though + let mod_rm = 0xC0 | (dst_mod << 3) | src_mod; + + let rex = add_rm_extension(src, REX_W); + let rex = add_reg_extension(dst, rex); + + match input_width { + RegisterWidth::W8 => { + buf.extend([rex, 0x0f, 0xbe, mod_rm]); } - (RegisterWidth::W8, RegisterWidth::W32) => { - buf.push(0x0F); - buf.push(0xBF); - binop_reg8_reg8(0x89, buf, dst, src); + RegisterWidth::W16 => { + buf.extend([rex, 0x0f, 0xbf, mod_rm]); } - (RegisterWidth::W8, RegisterWidth::W64) => { - buf.push(0x48); - buf.push(0x0F); - buf.push(0xBE); - binop_reg8_reg8(0x89, buf, dst, src); + RegisterWidth::W32 => { + buf.extend([rex, 0x63, mod_rm]); } - (RegisterWidth::W16, RegisterWidth::W32) => { - buf.push(0x66); - buf.push(0x0F); - buf.push(0xBF); - binop_reg16_reg16(0x89, buf, dst, src); - } - (RegisterWidth::W16, RegisterWidth::W64) => { - buf.push(0x48); - buf.push(0x0F); - buf.push(0xBF); - binop_reg16_reg16(0x89, buf, dst, src); - } - (RegisterWidth::W32, RegisterWidth::W64) => { - buf.push(0x48); - buf.push(0x0F); - buf.push(0xBF); - binop_reg32_reg32(0x89, buf, dst, src); - } - _ => panic!("Invalid input/output register width combination"), + RegisterWidth::W64 => { /* do nothing */ } } } @@ -3694,6 +3678,36 @@ mod tests { ); } + #[test] + fn test_movsx_reg64_reg64() { + disassembler_test!( + raw_movsx_reg_reg, + |w, reg1, reg2| { + match w { + RegisterWidth::W8 => format!( + "movsx {}, {}", + reg1, + X86_64GeneralReg::low_8bits_string(®2) + ), + RegisterWidth::W16 => format!( + "movsx {}, {}", + reg1, + X86_64GeneralReg::low_16bits_string(®2) + ), + RegisterWidth::W32 => format!( + "movsxd {}, {}", + reg1, + X86_64GeneralReg::low_32bits_string(®2) + ), + RegisterWidth::W64 => String::new(), + } + }, + ALL_REGISTER_WIDTHS, + ALL_GENERAL_REGS, + ALL_GENERAL_REGS + ); + } + #[test] fn test_movsd_freg64_base64_offset32() { disassembler_test!( diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index 77f5501334..d90c1375e2 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2289,21 +2289,24 @@ num_conversion_tests! { "Num.toI16", i16, ( to_i16_same_width, "15u16", 15, ["gen-wasm", "gen-dev"] to_i16_extend, "15i8", 15, ["gen-wasm", "gen-dev"] - to_i16_sign_extend, "-15i8", -15, ["gen-wasm", "gen-dev"] + to_i16_sign_extend_i8, "-15i8", -15, ["gen-wasm", "gen-dev"] to_i16_truncate, "115i32", 115, ["gen-wasm", "gen-dev"] to_i16_truncate_wraps, "60000i32", -5536, ["gen-wasm", "gen-dev"] ) "Num.toI32", i32, ( to_i32_same_width, "15u32", 15, ["gen-wasm", "gen-dev"] to_i32_extend, "15i8", 15, ["gen-wasm", "gen-dev"] - to_i32_sign_extend, "-15i8", -15, ["gen-wasm", "gen-dev"] + to_i32_sign_extend_i8, "-15i8", -15, ["gen-wasm", "gen-dev"] + to_i32_sign_extend_i16, "-15i16", -15, ["gen-wasm", "gen-dev"] to_i32_truncate, "115i64", 115, ["gen-wasm", "gen-dev"] to_i32_truncate_wraps, "5000000000i64", 705032704, ["gen-wasm", "gen-dev"] ) "Num.toI64", i64, ( to_i64_same_width, "15u64", 15, ["gen-wasm", "gen-dev"] to_i64_extend, "15i8", 15, ["gen-wasm", "gen-dev"] - to_i64_sign_extend, "-15i8", -15, ["gen-wasm", "gen-dev"] + to_i64_sign_extend_i8, "-15i8", -15, ["gen-wasm", "gen-dev"] + to_i64_sign_extend_i16, "-15i16", -15, ["gen-wasm", "gen-dev"] + to_i64_sign_extend_i32, "-15i32", -15, ["gen-wasm", "gen-dev"] to_i64_truncate, "115i128", 115 to_i64_truncate_wraps, "10_000_000_000_000_000_000i128", -8446744073709551616 )