Rollup merge of #143194 - folkertdev:fix-single-element-simd-bitcast, r=workingjubilee

fix bitcast of single-element SIMD vectors

in effect this reverts https://github.com/rust-lang/rust/pull/142768 and adds additional tests. That PR relaxed the conditions on an early return in an incorrect way that would create broken LLVM IR.

https://godbolt.org/z/PaaGWTv5a

```rust
#![feature(repr_simd)]

#[repr(simd)]
#[derive(Clone, Copy)]
struct S([i64; 1]);

#[no_mangle]
pub extern "C" fn single_element_simd(b: S) -> i64 {
    unsafe { std::mem::transmute(b) }
}
```
at the time of writing generates this LLVM IR, where the type of the return is different from the function's return type.

```llvm
define noundef i64 ``````@single_element_simd(<1`````` x i64> %b) unnamed_addr {
start:
  ret <1 x i64> %b
}
```

The test output is actually the same for the existing tests, showing that the change didn't actually matter for any tested behavior. It is probably a bit faster to do the early return, but, well, it's incorrect in general.

zullip thread: [#t-compiler > Is transmuting a &#96;T&#96; to &#96;Tx1&#96; (one-element SIMD vector) UB?](https://rust-lang.zulipchat.com/#narrow/channel/131828-t-compiler/topic/Is.20transmuting.20a.20.60T.60.20to.20.60Tx1.60.20.28one-element.20SIMD.20vector.29.20UB.3F/with/526262799)
cc ``````@sayantn``````
r? ``````@scottmcm``````
This commit is contained in:
Matthias Krüger
2025-07-02 19:28:06 +02:00
committed by GitHub
2 changed files with 47 additions and 18 deletions

View File

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

View File

@@ -1,6 +1,12 @@
//@ add-core-stubs
//@ compile-flags: -C opt-level=0 -C no-prepopulate-passes //@ compile-flags: -C opt-level=0 -C no-prepopulate-passes
#![crate_type = "lib"] #![crate_type = "lib"]
#![feature(no_core, repr_simd, arm_target_feature, mips_target_feature, s390x_target_feature)]
#![no_core]
extern crate minicore;
use minicore::*;
// With opaque ptrs in LLVM, `transmute` can load/store any `alloca` as any type, // 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`. // without needing to pointercast, and SRoA will turn that into a `bitcast`.
@@ -14,7 +20,7 @@
// CHECK-NEXT: ret i32 %_0 // CHECK-NEXT: ret i32 %_0
#[no_mangle] #[no_mangle]
pub fn f32_to_bits(x: f32) -> u32 { pub fn f32_to_bits(x: f32) -> u32 {
unsafe { std::mem::transmute(x) } unsafe { mem::transmute(x) }
} }
// CHECK-LABEL: define{{.*}}i8 @bool_to_byte(i1 zeroext %b) // CHECK-LABEL: define{{.*}}i8 @bool_to_byte(i1 zeroext %b)
@@ -22,7 +28,7 @@ pub fn f32_to_bits(x: f32) -> u32 {
// CHECK-NEXT: ret i8 %_0 // CHECK-NEXT: ret i8 %_0
#[no_mangle] #[no_mangle]
pub fn bool_to_byte(b: bool) -> u8 { pub fn bool_to_byte(b: bool) -> u8 {
unsafe { std::mem::transmute(b) } unsafe { mem::transmute(b) }
} }
// CHECK-LABEL: define{{.*}}zeroext i1 @byte_to_bool(i8{{.*}} %byte) // CHECK-LABEL: define{{.*}}zeroext i1 @byte_to_bool(i8{{.*}} %byte)
@@ -30,14 +36,14 @@ pub fn bool_to_byte(b: bool) -> u8 {
// CHECK-NEXT: ret i1 %_0 // CHECK-NEXT: ret i1 %_0
#[no_mangle] #[no_mangle]
pub unsafe fn byte_to_bool(byte: u8) -> bool { pub unsafe fn byte_to_bool(byte: u8) -> bool {
std::mem::transmute(byte) mem::transmute(byte)
} }
// CHECK-LABEL: define{{.*}}ptr @ptr_to_ptr(ptr %p) // CHECK-LABEL: define{{.*}}ptr @ptr_to_ptr(ptr %p)
// CHECK: ret ptr %p // CHECK: ret ptr %p
#[no_mangle] #[no_mangle]
pub fn ptr_to_ptr(p: *mut u16) -> *mut u8 { pub fn ptr_to_ptr(p: *mut u16) -> *mut u8 {
unsafe { std::mem::transmute(p) } unsafe { mem::transmute(p) }
} }
// CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int(ptr %p) // CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int(ptr %p)
@@ -45,7 +51,7 @@ pub fn ptr_to_ptr(p: *mut u16) -> *mut u8 {
// CHECK-NEXT: ret [[USIZE]] %_0 // CHECK-NEXT: ret [[USIZE]] %_0
#[no_mangle] #[no_mangle]
pub fn ptr_to_int(p: *mut u16) -> usize { pub fn ptr_to_int(p: *mut u16) -> usize {
unsafe { std::mem::transmute(p) } unsafe { mem::transmute(p) }
} }
// CHECK: define{{.*}}ptr @int_to_ptr([[USIZE]] %i) // CHECK: define{{.*}}ptr @int_to_ptr([[USIZE]] %i)
@@ -53,7 +59,7 @@ pub fn ptr_to_int(p: *mut u16) -> usize {
// CHECK-NEXT: ret ptr %_0 // CHECK-NEXT: ret ptr %_0
#[no_mangle] #[no_mangle]
pub fn int_to_ptr(i: usize) -> *mut u16 { pub fn int_to_ptr(i: usize) -> *mut u16 {
unsafe { std::mem::transmute(i) } unsafe { mem::transmute(i) }
} }
// This is the one case where signedness matters to transmuting: // This is the one case where signedness matters to transmuting:
@@ -70,7 +76,7 @@ pub enum FakeBoolSigned {
// CHECK-NEXT: ret i8 %_0 // CHECK-NEXT: ret i8 %_0
#[no_mangle] #[no_mangle]
pub fn bool_to_fake_bool_signed(b: bool) -> FakeBoolSigned { pub fn bool_to_fake_bool_signed(b: bool) -> FakeBoolSigned {
unsafe { std::mem::transmute(b) } unsafe { mem::transmute(b) }
} }
// CHECK-LABEL: define{{.*}}i1 @fake_bool_signed_to_bool(i8 %b) // CHECK-LABEL: define{{.*}}i1 @fake_bool_signed_to_bool(i8 %b)
@@ -78,7 +84,7 @@ pub fn bool_to_fake_bool_signed(b: bool) -> FakeBoolSigned {
// CHECK-NEXT: ret i1 %_0 // CHECK-NEXT: ret i1 %_0
#[no_mangle] #[no_mangle]
pub fn fake_bool_signed_to_bool(b: FakeBoolSigned) -> bool { pub fn fake_bool_signed_to_bool(b: FakeBoolSigned) -> bool {
unsafe { std::mem::transmute(b) } unsafe { mem::transmute(b) }
} }
#[repr(u8)] #[repr(u8)]
@@ -91,12 +97,41 @@ pub enum FakeBoolUnsigned {
// CHECK: ret i1 %b // CHECK: ret i1 %b
#[no_mangle] #[no_mangle]
pub fn bool_to_fake_bool_unsigned(b: bool) -> FakeBoolUnsigned { pub fn bool_to_fake_bool_unsigned(b: bool) -> FakeBoolUnsigned {
unsafe { std::mem::transmute(b) } unsafe { mem::transmute(b) }
} }
// CHECK-LABEL: define{{.*}}i1 @fake_bool_unsigned_to_bool(i1 zeroext %b) // CHECK-LABEL: define{{.*}}i1 @fake_bool_unsigned_to_bool(i1 zeroext %b)
// CHECK: ret i1 %b // CHECK: ret i1 %b
#[no_mangle] #[no_mangle]
pub fn fake_bool_unsigned_to_bool(b: FakeBoolUnsigned) -> bool { pub fn fake_bool_unsigned_to_bool(b: FakeBoolUnsigned) -> bool {
unsafe { std::mem::transmute(b) } unsafe { mem::transmute(b) }
}
#[repr(simd)]
struct S([i64; 1]);
// CHECK-LABEL: define{{.*}}i64 @single_element_simd_to_scalar(<1 x i64> %b)
// CHECK: bitcast <1 x i64> %b to i64
// CHECK: ret i64
#[no_mangle]
#[cfg_attr(target_family = "wasm", target_feature(enable = "simd128"))]
#[cfg_attr(target_arch = "arm", target_feature(enable = "neon"))]
#[cfg_attr(target_arch = "x86", target_feature(enable = "sse"))]
#[cfg_attr(target_arch = "mips", target_feature(enable = "msa"))]
#[cfg_attr(target_arch = "s390x", target_feature(enable = "vector"))]
pub extern "C" fn single_element_simd_to_scalar(b: S) -> i64 {
unsafe { mem::transmute(b) }
}
// CHECK-LABEL: define{{.*}}<1 x i64> @scalar_to_single_element_simd(i64 %b)
// CHECK: bitcast i64 %b to <1 x i64>
// CHECK: ret <1 x i64>
#[no_mangle]
#[cfg_attr(target_family = "wasm", target_feature(enable = "simd128"))]
#[cfg_attr(target_arch = "arm", target_feature(enable = "neon"))]
#[cfg_attr(target_arch = "x86", target_feature(enable = "sse"))]
#[cfg_attr(target_arch = "mips", target_feature(enable = "msa"))]
#[cfg_attr(target_arch = "s390x", target_feature(enable = "vector"))]
pub extern "C" fn scalar_to_single_element_simd(b: i64) -> S {
unsafe { mem::transmute(b) }
} }