Skip to content

Avoid some allocas in codegen of CastKind::Transmute #109549

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -396,7 +396,8 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
OperandValue::Immediate(newval)
}
mir::CastKind::Transmute => {
bug!("Transmute operand {:?} in `codegen_rvalue_operand`", operand);
// `rvalue_creates_operand` checked that the immediate matches
OperandValue::Immediate(operand.immediate())
}
};
OperandRef { val, layout: cast }
Expand Down Expand Up @@ -726,11 +727,23 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {

impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
pub fn rvalue_creates_operand(&self, rvalue: &mir::Rvalue<'tcx>, span: Span) -> bool {
match *rvalue {
mir::Rvalue::Cast(mir::CastKind::Transmute, ..) =>
// FIXME: Now that transmute is an Rvalue, it would be nice if
// it could create `Immediate`s for scalars, where possible.
false,
match rvalue {
mir::Rvalue::Cast(mir::CastKind::Transmute, operand, dst_ty) => {
let dst_ty = self.cx.layout_of(self.monomorphize(*dst_ty));
if !self.cx.is_backend_immediate(dst_ty) {
return false;
}

let src_ty = operand.ty(self.mir, self.cx.tcx());
let src_ty = self.cx.layout_of(self.monomorphize(src_ty));
if !self.cx.is_backend_immediate(src_ty) {
return false;
}

// FIXME: This only handles the easy exact matches for now;
// future work could also support `bitcast`s and such.
self.cx.immediate_backend_type(src_ty) == self.cx.immediate_backend_type(dst_ty)
}
mir::Rvalue::Ref(..) |
mir::Rvalue::CopyForDeref(..) |
mir::Rvalue::AddressOf(..) |
Expand Down
24 changes: 16 additions & 8 deletions tests/codegen/intrinsics/transmute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,20 +79,16 @@ pub unsafe fn check_from_uninhabited(x: BigNever) -> u16 {
// CHECK-LABEL: @check_to_newtype(
#[no_mangle]
pub unsafe fn check_to_newtype(x: u64) -> Scalar64 {
// CHECK: %0 = alloca i64
// CHECK: store i64 %x, ptr %0
// CHECK: %1 = load i64, ptr %0
// CHECK: ret i64 %1
// CHECK-NOT: alloca
// CHECK: ret i64 %x
transmute(x)
}

// CHECK-LABEL: @check_from_newtype(
#[no_mangle]
pub unsafe fn check_from_newtype(x: Scalar64) -> u64 {
// CHECK: %0 = alloca i64
// CHECK: store i64 %x, ptr %0
// CHECK: %1 = load i64, ptr %0
// CHECK: ret i64 %1
// CHECK-NOT: alloca
// CHECK: ret i64 %x
transmute(x)
}

Expand Down Expand Up @@ -194,3 +190,15 @@ pub unsafe fn check_long_array_more_aligned(x: [u8; 100]) -> [u32; 25] {
// CHECK-NEXT: ret void
transmute(x)
}

// CHECK-LABEL: @check_intermediate_passthrough(
#[no_mangle]
pub unsafe fn check_intermediate_passthrough(x: u32) -> i32 {
// CHECK: start
// CHECK: %[[TMP:.+]] = add i32 1, %x
// CHECK: %[[RET:.+]] = add i32 %[[TMP]], 1
// CHECK: ret i32 %[[RET]]
unsafe {
transmute::<u32, i32>(1 + x) + 1
}
}
31 changes: 15 additions & 16 deletions tests/codegen/transmute-scalar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

// With opaque ptrs in LLVM, `transmute` can load/store any `alloca` as any type,
// without needing to pointercast, and SRoA will turn that into a `bitcast`.
// As such, there's no longer special-casing in `transmute` to attempt to
// generate `bitcast` ourselves, as that just made the IR longer.
// Thus for place-to-place transmutes, there's no point in bothering to attempt
// to `bitcast` the values, and we can just write them directly.

// FIXME: That said, `bitcast`s could still be a valuable addition if they could
// be done in `rvalue_creates_operand`, and thus avoid the `alloca`s entirely.
// be done in `rvalue_creates_operand`, and thereby avoid the `alloca`s entirely.

// CHECK-LABEL: define{{.*}}i32 @f32_to_bits(float noundef %x)
// CHECK: store float %{{.*}}, ptr %0
Expand All @@ -22,8 +22,8 @@ pub fn f32_to_bits(x: f32) -> u32 {

// CHECK-LABEL: define{{.*}}i8 @bool_to_byte(i1 noundef zeroext %b)
// CHECK: %1 = zext i1 %b to i8
// CHECK-NEXT: store i8 %1, {{.*}} %0
// CHECK-NEXT: %2 = load i8, {{.*}} %0
// CHECK-NEXT: store i8 %1, ptr %0
// CHECK-NEXT: %2 = load i8, ptr %0
// CHECK: ret i8 %2
#[no_mangle]
pub fn bool_to_byte(b: bool) -> u8 {
Expand All @@ -32,36 +32,35 @@ pub fn bool_to_byte(b: bool) -> u8 {

// CHECK-LABEL: define{{.*}}noundef zeroext i1 @byte_to_bool(i8 noundef %byte)
// CHECK: store i8 %byte, ptr %0
// CHECK-NEXT: %1 = load i8, {{.*}} %0
// CHECK-NEXT: %1 = load i8, ptr %0
// CHECK-NEXT: %2 = trunc i8 %1 to i1
// CHECK: ret i1 %2
#[no_mangle]
pub unsafe fn byte_to_bool(byte: u8) -> bool {
std::mem::transmute(byte)
}

// CHECK-LABEL: define{{.*}}{{i8\*|ptr}} @ptr_to_ptr({{i16\*|ptr}} noundef %p)
// CHECK: store {{i8\*|ptr}} %{{.*}}, {{.*}} %0
// CHECK-NEXT: %[[RES:.*]] = load {{i8\*|ptr}}, {{.*}} %0
// CHECK: ret {{i8\*|ptr}} %[[RES]]
// CHECK-LABEL: define{{.*}}ptr @ptr_to_ptr(ptr noundef %p)
// CHECK: start
// CHECK-NEXT: ret ptr %p
#[no_mangle]
pub fn ptr_to_ptr(p: *mut u16) -> *mut u8 {
unsafe { std::mem::transmute(p) }
}

// CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int({{i16\*|ptr}} noundef %p)
// CHECK: store {{i16\*|ptr}} %p, {{.*}}
// CHECK-NEXT: %[[RES:.*]] = load [[USIZE]], {{.*}} %0
// CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int(ptr noundef %p)
// CHECK: store ptr %p, {{.*}}
// CHECK-NEXT: %[[RES:.*]] = load [[USIZE]], ptr %0
// CHECK: ret [[USIZE]] %[[RES]]
#[no_mangle]
pub fn ptr_to_int(p: *mut u16) -> usize {
unsafe { std::mem::transmute(p) }
}

// CHECK: define{{.*}}{{i16\*|ptr}} @int_to_ptr([[USIZE]] noundef %i)
// CHECK: define{{.*}}ptr @int_to_ptr([[USIZE]] noundef %i)
// CHECK: store [[USIZE]] %i, {{.*}}
// CHECK-NEXT: %[[RES:.*]] = load {{i16\*|ptr}}, {{.*}} %0
// CHECK: ret {{i16\*|ptr}} %[[RES]]
// CHECK-NEXT: %[[RES:.*]] = load ptr, {{.*}} %0
// CHECK: ret ptr %[[RES]]
#[no_mangle]
pub fn int_to_ptr(i: usize) -> *mut u16 {
unsafe { std::mem::transmute(i) }
Expand Down