blob: 8455b1d8583a542c1117b9feb63f2ee8f0f37c65 [file] [edit]
//@ 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>
}