about summary refs log tree commit diff
path: root/tests/codegen
diff options
context:
space:
mode:
authorScott McMurray <scottmcm@users.noreply.github.com>2023-02-24 18:32:52 -0800
committerScott McMurray <scottmcm@users.noreply.github.com>2023-03-22 15:15:41 -0700
commit64cce5fc7d2c1070adeaa719932b4bbccf27dd46 (patch)
tree918975145ee82f7777d722aedea9cc75f218dcce /tests/codegen
parenta266f11990d9544ee408e213e1eec8cc9eb032b7 (diff)
downloadrust-64cce5fc7d2c1070adeaa719932b4bbccf27dd46.tar.gz
rust-64cce5fc7d2c1070adeaa719932b4bbccf27dd46.zip
Add `CastKind::Transmute` to MIR
Updates `interpret`, `codegen_ssa`, and `codegen_cranelift` to consume the new cast instead of the intrinsic.

Includes `CastTransmute` for custom MIR building, to be able to test the extra UB.
Diffstat (limited to 'tests/codegen')
-rw-r--r--tests/codegen/intrinsics/transmute.rs196
-rw-r--r--tests/codegen/transmute-scalar.rs41
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]