Avoid a bitcast FFI call in transmuting

For things that only change the valid ranges, we can just skip the `LLVMBuildBitCast` call.

I tried to tweak this a bit more and broke stuff, so I also added some extra tests for that as we apparently didn't have coverage.
This commit is contained in:
Scott McMurray
2025-06-19 21:44:01 -07:00
parent d1d8e386c5
commit 5d16a7b884
2 changed files with 53 additions and 2 deletions

View File

@@ -1123,7 +1123,7 @@ pub(super) fn transmute_immediate<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
// While optimizations will remove no-op transmutes, they might still be
// there in debug or things that aren't no-op in MIR because they change
// the Rust type but not the underlying layout/niche.
if from_scalar == to_scalar && from_backend_ty == to_backend_ty {
if from_scalar == to_scalar {
return imm;
}
@@ -1142,7 +1142,13 @@ pub(super) fn transmute_immediate<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>>(
assume_scalar_range(bx, imm, from_scalar, from_backend_ty);
imm = match (from_scalar.primitive(), to_scalar.primitive()) {
(Int(..) | Float(_), Int(..) | Float(_)) => bx.bitcast(imm, to_backend_ty),
(Int(..) | Float(_), Int(..) | Float(_)) => {
if from_backend_ty == to_backend_ty {
imm
} else {
bx.bitcast(imm, to_backend_ty)
}
}
(Pointer(..), Pointer(..)) => bx.pointercast(imm, to_backend_ty),
(Int(..), Pointer(..)) => bx.ptradd(bx.const_null(bx.type_ptr()), imm),
(Pointer(..), Int(..)) => {

View File

@@ -55,3 +55,48 @@ pub fn ptr_to_int(p: *mut u16) -> usize {
pub fn int_to_ptr(i: usize) -> *mut u16 {
unsafe { std::mem::transmute(i) }
}
// This is the one case where signedness matters to transmuting:
// the LLVM type is `i8` here because of `repr(i8)`,
// whereas below with the `repr(u8)` it's `i1` in LLVM instead.
#[repr(i8)]
pub enum FakeBoolSigned {
False = 0,
True = 1,
}
// CHECK-LABEL: define{{.*}}i8 @bool_to_fake_bool_signed(i1 zeroext %b)
// CHECK: %_0 = zext i1 %b to i8
// CHECK-NEXT: ret i8 %_0
#[no_mangle]
pub fn bool_to_fake_bool_signed(b: bool) -> FakeBoolSigned {
unsafe { std::mem::transmute(b) }
}
// CHECK-LABEL: define{{.*}}i1 @fake_bool_signed_to_bool(i8 %b)
// CHECK: %_0 = trunc nuw i8 %b to i1
// CHECK-NEXT: ret i1 %_0
#[no_mangle]
pub fn fake_bool_signed_to_bool(b: FakeBoolSigned) -> bool {
unsafe { std::mem::transmute(b) }
}
#[repr(u8)]
pub enum FakeBoolUnsigned {
False = 0,
True = 1,
}
// CHECK-LABEL: define{{.*}}i1 @bool_to_fake_bool_unsigned(i1 zeroext %b)
// CHECK: ret i1 %b
#[no_mangle]
pub fn bool_to_fake_bool_unsigned(b: bool) -> FakeBoolUnsigned {
unsafe { std::mem::transmute(b) }
}
// CHECK-LABEL: define{{.*}}i1 @fake_bool_unsigned_to_bool(i1 zeroext %b)
// CHECK: ret i1 %b
#[no_mangle]
pub fn fake_bool_unsigned_to_bool(b: FakeBoolUnsigned) -> bool {
unsafe { std::mem::transmute(b) }
}