diff options
Diffstat (limited to 'tests/codegen')
| -rw-r--r-- | tests/codegen/intrinsics/transmute.rs | 196 | ||||
| -rw-r--r-- | tests/codegen/transmute-scalar.rs | 41 |
2 files changed, 210 insertions, 27 deletions
diff --git a/tests/codegen/intrinsics/transmute.rs b/tests/codegen/intrinsics/transmute.rs new file mode 100644 index 00000000000..cefcf9ed9ca --- /dev/null +++ b/tests/codegen/intrinsics/transmute.rs @@ -0,0 +1,196 @@ +// compile-flags: -O -C no-prepopulate-passes +// only-64bit (so I don't need to worry about usize) +// min-llvm-version: 15.0 # this test assumes `ptr`s + +#![crate_type = "lib"] +#![feature(core_intrinsics)] +#![feature(custom_mir)] +#![feature(inline_const)] + +use std::mem::transmute; + +// Some of the cases here are statically rejected by `mem::transmute`, so +// we need to generate custom MIR for those cases to get to codegen. +use std::intrinsics::mir::*; + +enum Never {} + +#[repr(align(2))] +pub struct BigNever(Never, u16, Never); + +#[repr(align(8))] +pub struct Scalar64(i64); + +#[repr(C, align(4))] +pub struct Aggregate64(u16, u8, i8, f32); + +// CHECK-LABEL: @check_bigger_size( +#[no_mangle] +#[custom_mir(dialect = "runtime", phase = "initial")] +pub unsafe fn check_bigger_size(x: u16) -> u32 { + // CHECK: call void @llvm.trap + mir!{ + { + RET = CastTransmute(x); + Return() + } + } +} + +// CHECK-LABEL: @check_smaller_size( +#[no_mangle] +#[custom_mir(dialect = "runtime", phase = "initial")] +pub unsafe fn check_smaller_size(x: u32) -> u16 { + // CHECK: call void @llvm.trap + mir!{ + { + RET = CastTransmute(x); + Return() + } + } +} + +// CHECK-LABEL: @check_to_uninhabited( +#[no_mangle] +#[custom_mir(dialect = "runtime", phase = "initial")] +pub unsafe fn check_to_uninhabited(x: u16) -> BigNever { + // CHECK: call void @llvm.trap + mir!{ + { + RET = CastTransmute(x); + Return() + } + } +} + +// CHECK-LABEL: @check_from_uninhabited( +#[no_mangle] +#[custom_mir(dialect = "runtime", phase = "initial")] +pub unsafe fn check_from_uninhabited(x: BigNever) -> u16 { + // CHECK: call void @llvm.trap + mir!{ + { + RET = CastTransmute(x); + Return() + } + } +} + +// 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 + 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 + transmute(x) +} + +// CHECK-LABEL: @check_to_pair( +#[no_mangle] +pub unsafe fn check_to_pair(x: u64) -> Option<i32> { + // CHECK: %0 = alloca { i32, i32 }, align 4 + // CHECK: store i64 %x, ptr %0, align 4 + transmute(x) +} + +// CHECK-LABEL: @check_from_pair( +#[no_mangle] +pub unsafe fn check_from_pair(x: Option<i32>) -> u64 { + // The two arguments are of types that are only 4-aligned, but they're + // immediates so we can write using the destination alloca's alignment. + const { assert!(std::mem::align_of::<Option<i32>>() == 4) }; + + // CHECK: %0 = alloca i64, align 8 + // CHECK: store i32 %x.0, ptr %1, align 8 + // CHECK: store i32 %x.1, ptr %2, align 4 + // CHECK: %3 = load i64, ptr %0, align 8 + // CHECK: ret i64 %3 + transmute(x) +} + +// CHECK-LABEL: @check_to_float( +#[no_mangle] +pub unsafe fn check_to_float(x: u32) -> f32 { + // CHECK: %0 = alloca float + // CHECK: store i32 %x, ptr %0 + // CHECK: %1 = load float, ptr %0 + // CHECK: ret float %1 + transmute(x) +} + +// CHECK-LABEL: @check_from_float( +#[no_mangle] +pub unsafe fn check_from_float(x: f32) -> u32 { + // CHECK: %0 = alloca i32 + // CHECK: store float %x, ptr %0 + // CHECK: %1 = load i32, ptr %0 + // CHECK: ret i32 %1 + transmute(x) +} + +// CHECK-LABEL: @check_to_bytes( +#[no_mangle] +pub unsafe fn check_to_bytes(x: u32) -> [u8; 4] { + // CHECK: %0 = alloca [4 x i8], align 1 + // CHECK: store i32 %x, ptr %0, align 1 + // CHECK: %1 = load i32, ptr %0, align 1 + // CHECK: ret i32 %1 + transmute(x) +} + +// CHECK-LABEL: @check_from_bytes( +#[no_mangle] +pub unsafe fn check_from_bytes(x: [u8; 4]) -> u32 { + // CHECK: %1 = alloca i32, align 4 + // CHECK: %x = alloca [4 x i8], align 1 + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %1, ptr align 1 %x, i64 4, i1 false) + // CHECK: %3 = load i32, ptr %1, align 4 + // CHECK: ret i32 %3 + transmute(x) +} + +// CHECK-LABEL: @check_to_aggregate( +#[no_mangle] +pub unsafe fn check_to_aggregate(x: u64) -> Aggregate64 { + // CHECK: %0 = alloca %Aggregate64, align 4 + // CHECK: store i64 %x, ptr %0, align 4 + // CHECK: %1 = load i64, ptr %0, align 4 + // CHECK: ret i64 %1 + transmute(x) +} + +// CHECK-LABEL: @check_from_aggregate( +#[no_mangle] +pub unsafe fn check_from_aggregate(x: Aggregate64) -> u64 { + // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 8 %{{[0-9]+}}, ptr align 4 %x, i64 8, i1 false) + transmute(x) +} + +// CHECK-LABEL: @check_long_array_less_aligned( +#[no_mangle] +pub unsafe fn check_long_array_less_aligned(x: [u64; 100]) -> [u16; 400] { + // CHECK-NEXT: start + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 2 %0, ptr align 8 %x, i64 800, i1 false) + // CHECK-NEXT: ret void + transmute(x) +} + +// CHECK-LABEL: @check_long_array_more_aligned( +#[no_mangle] +pub unsafe fn check_long_array_more_aligned(x: [u8; 100]) -> [u32; 25] { + // CHECK-NEXT: start + // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %0, ptr align 1 %x, i64 100, i1 false) + // CHECK-NEXT: ret void + transmute(x) +} diff --git a/tests/codegen/transmute-scalar.rs b/tests/codegen/transmute-scalar.rs index 260dcbac0fc..4d7a80bfbe5 100644 --- a/tests/codegen/transmute-scalar.rs +++ b/tests/codegen/transmute-scalar.rs @@ -1,13 +1,19 @@ // compile-flags: -O -C no-prepopulate-passes +// min-llvm-version: 15.0 # this test assumes `ptr`s and thus no `pointercast`s #![crate_type = "lib"] -// FIXME(eddyb) all of these tests show memory stores and loads, even after a -// scalar `bitcast`, more special-casing is required to remove `alloca` usage. +// 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. + +// 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. // CHECK-LABEL: define{{.*}}i32 @f32_to_bits(float noundef %x) -// CHECK: store i32 %{{.*}}, {{.*}} %0 -// CHECK-NEXT: %[[RES:.*]] = load i32, {{.*}} %0 +// CHECK: store float %{{.*}}, ptr %0 +// CHECK-NEXT: %[[RES:.*]] = load i32, ptr %0 // CHECK: ret i32 %[[RES]] #[no_mangle] pub fn f32_to_bits(x: f32) -> u32 { @@ -25,12 +31,10 @@ pub fn bool_to_byte(b: bool) -> u8 { } // CHECK-LABEL: define{{.*}}noundef zeroext i1 @byte_to_bool(i8 noundef %byte) -// CHECK: %1 = trunc i8 %byte to i1 -// CHECK-NEXT: %2 = zext i1 %1 to i8 -// CHECK-NEXT: store i8 %2, {{.*}} %0 -// CHECK-NEXT: %3 = load i8, {{.*}} %0 -// CHECK-NEXT: %4 = trunc i8 %3 to i1 -// CHECK: ret i1 %4 +// CHECK: store i8 %byte, ptr %0 +// CHECK-NEXT: %1 = load i8, {{.*}} %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) @@ -45,20 +49,8 @@ pub fn ptr_to_ptr(p: *mut u16) -> *mut u8 { unsafe { std::mem::transmute(p) } } -// HACK(eddyb) scalar `transmute`s between pointers and non-pointers are -// currently not special-cased like other scalar `transmute`s, because -// LLVM requires specifically `ptrtoint`/`inttoptr` instead of `bitcast`. -// -// Tests below show the non-special-cased behavior (with the possible -// future special-cased instructions in the "NOTE(eddyb)" comments). - // CHECK: define{{.*}}[[USIZE:i[0-9]+]] @ptr_to_int({{i16\*|ptr}} noundef %p) - -// NOTE(eddyb) see above, the following two CHECK lines should ideally be this: -// %2 = ptrtoint i16* %p to [[USIZE]] -// store [[USIZE]] %2, [[USIZE]]* %0 // CHECK: store {{i16\*|ptr}} %p, {{.*}} - // CHECK-NEXT: %[[RES:.*]] = load [[USIZE]], {{.*}} %0 // CHECK: ret [[USIZE]] %[[RES]] #[no_mangle] @@ -67,12 +59,7 @@ pub fn ptr_to_int(p: *mut u16) -> usize { } // CHECK: define{{.*}}{{i16\*|ptr}} @int_to_ptr([[USIZE]] noundef %i) - -// NOTE(eddyb) see above, the following two CHECK lines should ideally be this: -// %2 = inttoptr [[USIZE]] %i to i16* -// store i16* %2, i16** %0 // CHECK: store [[USIZE]] %i, {{.*}} - // CHECK-NEXT: %[[RES:.*]] = load {{i16\*|ptr}}, {{.*}} %0 // CHECK: ret {{i16\*|ptr}} %[[RES]] #[no_mangle] |
