//@ unit-test: ScalarReplacementOfAggregates //@ compile-flags: -Cpanic=abort //@ no-prefer-dynamic struct Tag(usize); #[repr(C)] struct S(Tag, Tag, Tag); impl Drop for Tag { #[inline(never)] fn drop(&mut self) {} } /// Check that SROA excludes structs with a `Drop` implementation. pub fn dropping() { // CHECK-LABEL: fn dropping( // CHECK: [[aggregate:_[0-9]+]]: S; // CHECK: bb0: { // CHECK: [[aggregate]] = S S(Tag(0), Tag(1), Tag(2)).1; } /// Check that SROA excludes enums. pub fn enums(a: usize) -> usize { // CHECK-LABEL: fn enums( // CHECK: [[enum:_[0-9]+]]: std::option::Option; // CHECK: bb0: { // CHECK: [[enum]] = Option::::Some // CHECK: _5 = (([[enum]] as Some).0: usize) // CHECK: _0 = _5 if let Some(a) = Some(a) { a } else { 0 } } /// Check that SROA destructures `U`. pub fn structs(a: f32) -> f32 { // CHECK-LABEL: fn structs( struct U { _foo: usize, a: f32, } // CHECK: [[ret:_0]]: f32; // CHECK: [[struct:_[0-9]+]]: structs::U; // CHECK: [[a_tmp:_[0-9]+]]: f32; // CHECK: [[foo:_[0-9]+]]: usize; // CHECK: [[a_ret:_[0-9]+]]: f32; // CHECK: bb0: { // CHECK-NOT: [[struct]] // CHECK: [[a_tmp]] = _1; // CHECK-NOT: [[struct]] // CHECK: [[foo]] = const 0_usize; // CHECK-NOT: [[struct]] // CHECK: [[a_ret]] = move [[a_tmp]]; // CHECK-NOT: [[struct]] // CHECK: _0 = [[a_ret]]; // CHECK-NOT: [[struct]] U { _foo: 0, a }.a } /// Check that SROA excludes unions. pub fn unions(a: f32) -> u32 { // CHECK-LABEL: fn unions( union Repr { f: f32, u: u32, } // CHECK: [[union:_[0-9]+]]: unions::Repr; // CHECK: bb0: { // CHECK: [[union]] = Repr { // CHECK: _0 = ([[union]].1: u32) unsafe { Repr { f: a }.u } } #[derive(Copy, Clone)] struct Foo { a: u8, b: (), c: &'static str, d: Option, } /// Check that non-escaping uses of a struct are destructured. pub fn flat() { // CHECK-LABEL: fn flat( // CHECK: [[struct:_[0-9]+]]: Foo; // CHECK: bb0: { // CHECK: [[init_unit:_[0-9]+]] = (); // CHECK: [[init_opt_isize:_[0-9]+]] = Option::::Some // CHECK: [[destr_five:_[0-9]+]] = const 5_u8; // CHECK: [[destr_unit:_[0-9]+]] = move [[init_unit]]; // CHECK: [[destr_a:_[0-9]+]] = const "a"; // CHECK: [[destr_opt_isize:_[0-9]+]] = move [[init_opt_isize]]; let Foo { a, b, c, d } = Foo { a: 5, b: (), c: "a", d: Some(-4) }; let _ = a; let _ = b; let _ = c; let _ = d; } #[repr(C)] struct Escaping { a: u32, b: u32, c: u32, } fn g() -> u32 { 3 } fn f(a: *const u32) { println!("{}", unsafe { *a.add(2) }); } // `f` uses the `&e.a` to access `e.c`. This is UB according to Miri today; however, // T-opsem has not finalized that decision and as such rustc should not rely on // it. If SROA were to rely on it, it would be (almost) correct to turn `e` into // three distinct locals - one for each field - and pass a reference to only one // of them to `f`. However, this would lead to a miscompilation because `b` and `c` // might no longer appear right after `a` in memory. pub fn escaping() { // CHECK-LABEL: fn escaping( // CHECK: [[ptr:_[0-9]+]]: *const u32; // CHECK: [[ref:_[0-9]+]]: &u32; // CHECK: [[struct:_[0-9]+]]: Escaping; // CHECK: [[a:_[0-9]+]]: u32; // CHECK: bb0: { // CHECK: [[struct]] = Escaping { // CHECK: [[ref]] = &([[struct]].0 // CHECK: [[ptr]] = &raw const (*[[ref]]); f(&Escaping { a: 1, b: 2, c: g() }.a); } /// Check that copies from an internal struct are destructured and reassigned to /// the original struct. fn copies(x: Foo) { // CHECK-LABEL: fn copies( // CHECK: [[external:_[0-9]+]]: Foo) -> // CHECK: [[internal:_[0-9]+]]: Foo; // CHECK: [[byte:_[0-9]+]]: u8; // CHECK: [[unit:_[0-9]+]]: (); // CHECK: [[str:_[0-9]+]]: &str; // CHECK: [[opt_isize:_[0-9]+]]: std::option::Option; // CHECK: bb0: { // CHECK: [[byte]] = ([[external]].0 // CHECK: [[unit]] = ([[external]].1 // CHECK: [[str]] = ([[external]].2 // CHECK: [[opt_isize]] = ([[external]].3 let y = x; let t = y.a; let u = y.c; let z = y; let a = z.b; } /// Check that copies from an internal struct are destructured and reassigned to /// the original struct. fn ref_copies(x: &Foo) { // CHECK-LABEL: fn ref_copies( // CHECK: [[external:_[0-9]+]]: &Foo) -> // CHECK: [[internal:_[0-9]+]]: Foo; // CHECK: [[byte:_[0-9]+]]: u8; // CHECK: [[unit:_[0-9]+]]: (); // CHECK: [[str:_[0-9]+]]: &str; // CHECK: [[opt_isize:_[0-9]+]]: std::option::Option; // CHECK: bb0: { // CHECK: [[byte]] = ((*[[external]]).0 // CHECK: [[unit]] = ((*[[external]]).1 // CHECK: [[str]] = ((*[[external]]).2 // CHECK: [[opt_isize]] = ((*[[external]]).3 let y = *x; let t = y.a; let u = y.c; } /// Check that deaggregated assignments from constants are placed after the constant's /// assignment. Also check that copying field accesses from the copy of the constant are /// reassigned to copy from the constant. fn constant() { // CHECK-LABEL: constant( // CHECK: [[constant:_[0-9]+]]: (usize, u8); // CHECK: [[t:_[0-9]+]]: usize; // CHECK: [[u:_[0-9]+]]: u8; // CHECK: bb0: { // CHECK-NOT: [[constant]] // CHECK: [[constant]] = const // CHECK: [[t]] = move ([[constant]].0: usize) // CHECK: [[u]] = move ([[constant]].1: u8) const U: (usize, u8) = (5, 9); let y = U; let t = y.0; let u = y.1; } fn main() { // CHECK-LABEL: fn main( dropping(); enums(5); structs(5.); unions(5.); flat(); escaping(); copies(Foo { a: 5, b: (), c: "a", d: Some(-4) }); ref_copies(&Foo { a: 5, b: (), c: "a", d: Some(-4) }); constant(); } // EMIT_MIR structs.dropping.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.enums.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.structs.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.unions.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.flat.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.escaping.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.copies.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.ref_copies.ScalarReplacementOfAggregates.diff // EMIT_MIR structs.constant.ScalarReplacementOfAggregates.diff