diff --git a/crates/compiler/gen_wasm/src/low_level.rs b/crates/compiler/gen_wasm/src/low_level.rs index f58cd387e5..c79298129b 100644 --- a/crates/compiler/gen_wasm/src/low_level.rs +++ b/crates/compiler/gen_wasm/src/low_level.rs @@ -1610,13 +1610,46 @@ impl<'a> LowLevelCall<'a> { } } NumShiftRightBy => { - backend.storage.load_symbols( - &mut backend.code_builder, - &[self.arguments[1], self.arguments[0]], - ); + let bits = self.arguments[0]; + let num = self.arguments[1]; match CodeGenNumType::from(self.ret_layout) { - I32 => backend.code_builder.i32_shr_s(), - I64 => backend.code_builder.i64_shr_s(), + I32 => { + // In most languages this operation is for signed numbers, but Roc defines it on all integers. + // So the argument is implicitly converted to signed before the shift operator. + // We need to make that conversion explicit for i8 and i16, which use Wasm's i32 type. + let bit_width = 8 * self.ret_layout.stack_size(TARGET_INFO) as i32; + if bit_width < 32 && !symbol_is_signed_int(backend, num) { + // Sign-extend the number by shifting left and right again + backend + .storage + .load_symbols(&mut backend.code_builder, &[num]); + backend.code_builder.i32_const(32 - bit_width); + backend.code_builder.i32_shl(); + backend.code_builder.i32_const(32 - bit_width); + backend.code_builder.i32_shr_s(); + backend + .storage + .load_symbols(&mut backend.code_builder, &[bits]); + + // Do the actual bitshift operation + backend.code_builder.i32_shr_s(); + + // Restore to unsigned + backend.code_builder.i32_const((1 << bit_width) - 1); + backend.code_builder.i32_and(); + } else { + backend + .storage + .load_symbols(&mut backend.code_builder, &[num, bits]); + backend.code_builder.i32_shr_s(); + } + } + I64 => { + backend + .storage + .load_symbols(&mut backend.code_builder, &[num, bits]); + backend.code_builder.i64_shr_s(); + } I128 => todo!("{:?} for I128", self.lowlevel), _ => panic_ret_type(), } @@ -1624,7 +1657,7 @@ impl<'a> LowLevelCall<'a> { NumShiftRightZfBy => { match CodeGenNumType::from(self.ret_layout) { I32 => { - // This is normally an unsigned operation, but Roc defines it on all integer types. + // In most languages this operation is for unsigned numbers, but Roc defines it on all integers. // So the argument is implicitly converted to unsigned before the shift operator. // We need to make that conversion explicit for i8 and i16, which use Wasm's i32 type. let bit_width = 8 * self.ret_layout.stack_size(TARGET_INFO); diff --git a/crates/compiler/test_gen/src/gen_num.rs b/crates/compiler/test_gen/src/gen_num.rs index b5b42aad4e..72bc40c797 100644 --- a/crates/compiler/test_gen/src/gen_num.rs +++ b/crates/compiler/test_gen/src/gen_num.rs @@ -2004,25 +2004,22 @@ fn shift_left_by() { fn shift_right_by() { // Sign Extended Right Shift - let is_wasm = cfg!(feature = "gen-wasm"); let is_llvm_release_mode = cfg!(feature = "gen-llvm") && !cfg!(debug_assertions); // FIXME (Brian) Something funny happening with 8-bit binary literals in tests - if !is_wasm { - assert_evals_to!( - "Num.shiftRightBy 2 (Num.toI8 0b1100_0000u8)", - 0b1111_0000u8 as i8, - i8 - ); - assert_evals_to!("Num.shiftRightBy 2 0b0100_0000i8", 0b0001_0000i8, i8); - assert_evals_to!("Num.shiftRightBy 1 0b1110_0000u8", 0b1111_0000u8, u8); - assert_evals_to!("Num.shiftRightBy 2 0b1100_0000u8", 0b1111_0000u8, u8); - assert_evals_to!("Num.shiftRightBy 12 0b0100_0000u8", 0b0000_0000u8, u8); + assert_evals_to!( + "Num.shiftRightBy 2 (Num.toI8 0b1100_0000u8)", + 0b1111_0000u8 as i8, + i8 + ); + assert_evals_to!("Num.shiftRightBy 2 0b0100_0000i8", 0b0001_0000i8, i8); + assert_evals_to!("Num.shiftRightBy 1 0b1110_0000u8", 0b1111_0000u8, u8); + assert_evals_to!("Num.shiftRightBy 2 0b1100_0000u8", 0b1111_0000u8, u8); + assert_evals_to!("Num.shiftRightBy 12 0b0100_0000u8", 0b0000_0000u8, u8); - // LLVM in release mode returns 0 instead of -1 for some reason - if !is_llvm_release_mode { - assert_evals_to!("Num.shiftRightBy 12 0b1000_0000u8", 0b1111_1111u8, u8); - } + // LLVM in release mode returns 0 instead of -1 for some reason + if !is_llvm_release_mode { + assert_evals_to!("Num.shiftRightBy 12 0b1000_0000u8", 0b1111_1111u8, u8); } assert_evals_to!("Num.shiftRightBy 0 12", 12, i64); assert_evals_to!("Num.shiftRightBy 1 12", 6, i64);