| //@ add-minicore |
| //@ compile-flags: -Copt-level=3 -Cno-prepopulate-passes -Z merge-functions=disabled -Z randomize-layout=no |
| //@ revisions: powerpc64 x86_64 |
| //@[powerpc64] compile-flags: --target powerpc64-unknown-linux-gnu |
| //@[powerpc64] needs-llvm-components: powerpc |
| //@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu |
| //@[x86_64] needs-llvm-components: x86 |
| |
| // Regression test for <https://github.com/rust-lang/rust/issues/157373>. |
| // |
| // These cases specifically exercise direct codegen of small non-zero constant |
| // aggregates as a single integer store. They are chosen so they fail without |
| // `try_codegen_const_aggregate_as_immediate`. |
| |
| #![crate_type = "lib"] |
| #![feature(no_core, lang_items)] |
| #![no_core] |
| |
| extern crate minicore; |
| |
| use minicore::*; |
| |
| #[inline(always)] |
| unsafe fn ptr_write<T>(dest: *mut T, value: T) { |
| *dest = value; |
| } |
| |
| trait MaybeUninitExt<T> { |
| fn as_mut_ptr(&mut self) -> *mut T; |
| fn write(&mut self, value: T); |
| } |
| |
| impl<T> MaybeUninitExt<T> for MaybeUninit<T> { |
| fn as_mut_ptr(&mut self) -> *mut T { |
| self as *mut _ as *mut T |
| } |
| |
| fn write(&mut self, value: T) { |
| unsafe { |
| ptr_write(self.as_mut_ptr(), value); |
| } |
| } |
| } |
| |
| // Inner padding between b (offset 2, size 1) and c (offset 4, size 4). |
| #[repr(C)] |
| pub struct InnerPadded { |
| a: u16, |
| b: u8, |
| c: u32, |
| } |
| |
| #[repr(transparent)] |
| pub struct Nested1(InnerPadded); |
| |
| #[repr(transparent)] |
| pub struct Nested2(Nested1); |
| |
| // PR 157690's original ptr::write entry point, checked against the current |
| // aggregate-immediate codegen shape. |
| // CHECK-LABEL: @via_ptr_write( |
| #[no_mangle] |
| pub fn via_ptr_write(dest: &mut MaybeUninit<InnerPadded>) { |
| let val = InnerPadded { a: 0, b: 0, c: 0 }; |
| // CHECK: %val = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) |
| // CHECK-NEXT: store i64 0, ptr %val, align 4 |
| // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) |
| unsafe { |
| ptr_write(dest.as_mut_ptr(), val); |
| } |
| } |
| |
| // PR 157690's original MaybeUninit::write entry point. |
| // CHECK-LABEL: @via_maybe_uninit_write( |
| #[no_mangle] |
| pub fn via_maybe_uninit_write(dest: &mut MaybeUninit<InnerPadded>) { |
| let val = InnerPadded { a: 0, b: 0, c: 0 }; |
| // CHECK: %val = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) |
| // CHECK-NEXT: store i64 0, ptr %val, align 4 |
| // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %{{.*}}, i64 8, i1 false) |
| dest.write(val); |
| } |
| |
| // Constant non-zero initialization: emitted as a single store including zero padding. |
| // CHECK-LABEL: @const_init_non_zero( |
| #[no_mangle] |
| pub fn const_init_non_zero(dest: *mut InnerPadded) { |
| let val = InnerPadded { a: 0, b: 1, c: 0 }; |
| // CHECK: %val = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) |
| // x86_64-NEXT: store i64 65536, ptr %val, align 4 |
| // powerpc64-NEXT: store i64 1099511627776, ptr %val, align 4 |
| // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) |
| unsafe { |
| ptr_write(dest, val); |
| } |
| } |
| |
| // From issue #157373: nesting wrapper structs used to change the lowering |
| // shape enough that LLVM would sometimes find the wide store only in the |
| // nested case. |
| // CHECK-LABEL: @bad( |
| #[no_mangle] |
| pub fn bad(a: &mut InnerPadded) { |
| let x = InnerPadded { a: 0, b: 1, c: 0 }; |
| // x86_64: store i64 65536, ptr %x, align 4 |
| // powerpc64: store i64 1099511627776, ptr %x, align 4 |
| *a = x; |
| } |
| |
| // CHECK-LABEL: @good( |
| #[no_mangle] |
| pub fn good(a: &mut Nested2) { |
| let x = InnerPadded { a: 0, b: 1, c: 0 }; |
| // x86_64: store i64 65536, ptr %x, align 4 |
| // powerpc64: store i64 1099511627776, ptr %x, align 4 |
| *a = Nested2(Nested1(x)); |
| } |
| |
| // The same direct constant aggregate packing should apply through ptr::write on MaybeUninit. |
| // CHECK-LABEL: @via_ptr_write_non_zero( |
| #[no_mangle] |
| pub fn via_ptr_write_non_zero(dest: &mut MaybeUninit<InnerPadded>) { |
| let val = InnerPadded { a: 0, b: 1, c: 0 }; |
| // CHECK: %val = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) |
| // x86_64-NEXT: store i64 65536, ptr %val, align 4 |
| // powerpc64-NEXT: store i64 1099511627776, ptr %val, align 4 |
| // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) |
| unsafe { |
| ptr_write(dest.as_mut_ptr(), val); |
| } |
| } |
| |
| // The same direct constant aggregate packing should apply through MaybeUninit::write. |
| // CHECK-LABEL: @via_maybe_uninit_write_non_zero( |
| #[no_mangle] |
| pub fn via_maybe_uninit_write_non_zero(dest: &mut MaybeUninit<InnerPadded>) { |
| let val = InnerPadded { a: 0, b: 1, c: 0 }; |
| // CHECK: %val = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) |
| // x86_64-NEXT: store i64 65536, ptr %val, align 4 |
| // powerpc64-NEXT: store i64 1099511627776, ptr %val, align 4 |
| // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %{{.*}}, i64 8, i1 false) |
| dest.write(val); |
| } |
| |
| // CHECK-LABEL: @bad_non_zero( |
| #[no_mangle] |
| pub fn bad_non_zero(a: &mut InnerPadded) { |
| let x = InnerPadded { a: 0, b: 1, c: 0 }; |
| // CHECK: %x = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %x) |
| // x86_64-NEXT: store i64 65536, ptr %x, align 4 |
| // powerpc64-NEXT: store i64 1099511627776, ptr %x, align 4 |
| // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %x, i64 8, i1 false) |
| *a = x; |
| } |
| |
| // CHECK-LABEL: @good_non_zero( |
| #[no_mangle] |
| pub fn good_non_zero(a: &mut Nested2) { |
| let x = InnerPadded { a: 0, b: 1, c: 0 }; |
| // CHECK: %x = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %x) |
| // x86_64-NEXT: store i64 65536, ptr %x, align 4 |
| // powerpc64-NEXT: store i64 1099511627776, ptr %x, align 4 |
| // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %a, ptr align 4 %{{.*}}, i64 8, i1 false) |
| *a = Nested2(Nested1(x)); |
| } |
| |
| // Trailing padding only (no inter-field padding): a (offset 0, size 4), |
| // b (offset 4, size 2), c (offset 6, size 1), trailing pad (offset 7, size 1). |
| #[repr(C)] |
| pub struct TailOnly { |
| a: u32, |
| b: u16, |
| c: u8, |
| } |
| |
| type TupleTailOnly = (u32, u16, u8); |
| |
| // PR 157690's trailing-padding-only entry point. |
| // CHECK-LABEL: @tail_only_write( |
| #[no_mangle] |
| pub fn tail_only_write(dest: &mut MaybeUninit<TailOnly>) { |
| let val = TailOnly { a: 0, b: 0, c: 0 }; |
| // CHECK: %val = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) |
| // CHECK-NEXT: store i64 0, ptr %val, align 4 |
| // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) |
| unsafe { |
| ptr_write(dest.as_mut_ptr(), val); |
| } |
| } |
| |
| // Tuple aggregates should use the same const-packing path as structs when the |
| // whole tuple is constant and small enough to fit in an integer store. |
| // CHECK-LABEL: @tuple_tail_only_non_zero( |
| #[no_mangle] |
| pub fn tuple_tail_only_non_zero(dest: *mut TupleTailOnly) { |
| let val: TupleTailOnly = (0, 1, 0); |
| // CHECK: %val = alloca [8 x i8], align 4 |
| // CHECK-NEXT: call void @llvm.lifetime.start.p0({{(i64 8, )?}}ptr %val) |
| // x86_64-NEXT: store i64 4294967296, ptr %val, align 4 |
| // powerpc64-NEXT: store i64 65536, ptr %val, align 4 |
| // CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 4 %dest, ptr align 4 %val, i64 8, i1 false) |
| unsafe { |
| ptr_write(dest, val); |
| } |
| } |
| |
| // Regression test for the debug assertion failure in |
| // `try_codegen_const_aggregate_as_immediate` when the MIR aggregate's |
| // variant index doesn't match the layout's `Variants::Single { index }`. |
| // |
| // When `Data(Void)` is uninhabited, the layout of `E<Void>` collapses to |
| // `Variants::Single { index: 0 }` (only `Empty`). But generic code |
| // monomorphized with `T = Void` still contains an aggregate for `Data(x)` |
| // with 1 operand. The optimization must bail out gracefully instead of |
| // asserting `operands.len() == dest.layout.fields.count()` (1 == 0). |
| // |
| // See <https://github.com/rust-lang/rust/pull/157690>. |
| enum Void {} |
| |
| enum E<T> { |
| Empty, |
| Data(T), |
| } |
| |
| #[inline(never)] |
| fn make_data<T>(x: T) -> E<T> { |
| E::Data(x) |
| } |
| |
| // Force codegen of `make_data::<Void>`. Without the variant index check, |
| // this triggers: assertion `left == right` failed (left: 1, right: 0). |
| // CHECK-LABEL: @force_variant_mismatch( |
| #[no_mangle] |
| pub fn force_variant_mismatch() -> fn(Void) -> E<Void> { |
| make_data::<Void> |
| } |