Skip to content

Commit a576ef9

Browse files
committed
[mir-opt] Allow SRoA when an object is transmuted to a field type
1 parent 2371d69 commit a576ef9

6 files changed

Lines changed: 196 additions & 30 deletions

File tree

compiler/rustc_mir_transform/src/sroa.rs

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ impl<'tcx> crate::MirPass<'tcx> for ScalarReplacementOfAggregates {
3232
let typing_env = body.typing_env(tcx);
3333
loop {
3434
debug!(?excluded);
35-
let escaping = escaping_locals(tcx, &excluded, body);
35+
let escaping = escaping_locals(tcx, &excluded, typing_env, body);
3636
debug!(?escaping);
3737
let replacements = compute_flattening(tcx, typing_env, body, escaping);
3838
debug!(?replacements);
39-
let all_dead_locals = replace_flattened_locals(tcx, body, replacements);
39+
let all_dead_locals = replace_flattened_locals(tcx, typing_env, body, replacements);
4040
if !all_dead_locals.is_empty() {
4141
excluded.union(&all_dead_locals);
4242
excluded = {
@@ -65,6 +65,7 @@ impl<'tcx> crate::MirPass<'tcx> for ScalarReplacementOfAggregates {
6565
fn escaping_locals<'tcx>(
6666
tcx: TyCtxt<'tcx>,
6767
excluded: &DenseBitSet<Local>,
68+
typing_env: ty::TypingEnv<'tcx>,
6869
body: &Body<'tcx>,
6970
) -> DenseBitSet<Local> {
7071
let is_excluded_ty = |ty: Ty<'tcx>| {
@@ -88,20 +89,24 @@ fn escaping_locals<'tcx>(
8889

8990
let mut set = DenseBitSet::new_empty(body.local_decls.len());
9091
set.insert_range(RETURN_PLACE..Local::arg(body.arg_count));
91-
for (local, decl) in body.local_decls().iter_enumerated() {
92+
for (local, decl) in body.local_decls.iter_enumerated() {
9293
if excluded.contains(local) || is_excluded_ty(decl.ty) {
9394
set.insert(local);
9495
}
9596
}
96-
let mut visitor = EscapeVisitor { set };
97+
let mut visitor = EscapeVisitor { tcx, typing_env, set, decls: &body.local_decls };
9798
visitor.visit_body(body);
9899
return visitor.set;
99100

100-
struct EscapeVisitor {
101+
struct EscapeVisitor<'tcx, 'a> {
102+
tcx: TyCtxt<'tcx>,
103+
typing_env: ty::TypingEnv<'tcx>,
101104
set: DenseBitSet<Local>,
105+
/// This is used to look at the field types of a transmuted local.
106+
decls: &'a LocalDecls<'tcx>,
102107
}
103108

104-
impl<'tcx> Visitor<'tcx> for EscapeVisitor {
109+
impl<'tcx> Visitor<'tcx> for EscapeVisitor<'tcx, '_> {
105110
fn visit_local(&mut self, local: Local, _: PlaceContext, _: Location) {
106111
self.set.insert(local);
107112
}
@@ -114,6 +119,28 @@ fn escaping_locals<'tcx>(
114119
self.super_place(place, context, location);
115120
}
116121

122+
fn visit_rvalue(&mut self, rvalue: &Rvalue<'tcx>, location: Location) {
123+
// A transmute to a field type is either the same as a read of that
124+
// field or it's UB for a size mismatch, so we can allow SRoA the same
125+
// as if it had been written `Use(op)` with a field projection.
126+
if let Rvalue::Cast(CastKind::Transmute, op, to_ty) = rvalue
127+
&& let Some(place) = op.place()
128+
&& let Some(local) = place.as_local()
129+
&& !self.set.contains(local)
130+
&& find_matching_struct_field(
131+
self.tcx,
132+
self.typing_env,
133+
*to_ty,
134+
self.decls[local].ty,
135+
)
136+
.is_some()
137+
{
138+
return;
139+
}
140+
141+
self.super_rvalue(rvalue, location)
142+
}
143+
117144
fn visit_assign(
118145
&mut self,
119146
lvalue: &Place<'tcx>,
@@ -147,6 +174,27 @@ fn escaping_locals<'tcx>(
147174
}
148175
}
149176

177+
fn find_matching_struct_field<'tcx>(
178+
tcx: TyCtxt<'tcx>,
179+
typing_env: ty::TypingEnv<'tcx>,
180+
needle_ty: Ty<'tcx>,
181+
haystack_ty: Ty<'tcx>,
182+
) -> Option<FieldIdx> {
183+
if let ty::Adt(adt_def, adt_args) = haystack_ty.kind()
184+
&& adt_def.is_struct()
185+
{
186+
for (idx, data) in adt_def.non_enum_variant().fields.iter_enumerated() {
187+
let field_ty = data.ty(tcx, adt_args);
188+
let field_ty = tcx.normalize_erasing_regions(typing_env, field_ty);
189+
if field_ty == needle_ty {
190+
return Some(idx);
191+
}
192+
}
193+
}
194+
195+
None
196+
}
197+
150198
#[derive(Default, Debug)]
151199
struct ReplacementMap<'tcx> {
152200
/// Pre-computed list of all "new" locals for each "old" local. This is used to expand storage
@@ -211,6 +259,7 @@ fn compute_flattening<'tcx>(
211259
/// Perform the replacement computed by `compute_flattening`.
212260
fn replace_flattened_locals<'tcx>(
213261
tcx: TyCtxt<'tcx>,
262+
typing_env: ty::TypingEnv<'tcx>,
214263
body: &mut Body<'tcx>,
215264
replacements: ReplacementMap<'tcx>,
216265
) -> DenseBitSet<Local> {
@@ -227,6 +276,7 @@ fn replace_flattened_locals<'tcx>(
227276

228277
let mut visitor = ReplacementVisitor {
229278
tcx,
279+
typing_env,
230280
local_decls: &body.local_decls,
231281
replacements: &replacements,
232282
all_dead_locals,
@@ -249,7 +299,9 @@ fn replace_flattened_locals<'tcx>(
249299

250300
struct ReplacementVisitor<'tcx, 'll> {
251301
tcx: TyCtxt<'tcx>,
252-
/// This is only used to compute the type for `VarDebugInfoFragment`.
302+
typing_env: ty::TypingEnv<'tcx>,
303+
/// This is used to compute the type for `VarDebugInfoFragment`
304+
/// and to look at the field types of a transmuted local.
253305
local_decls: &'ll LocalDecls<'tcx>,
254306
/// Work to do.
255307
replacements: &'ll ReplacementMap<'tcx>,
@@ -430,6 +482,37 @@ impl<'tcx, 'll> MutVisitor<'tcx> for ReplacementVisitor<'tcx, 'll> {
430482
self.super_statement(statement, location)
431483
}
432484

485+
fn visit_rvalue(&mut self, rvalue: &mut Rvalue<'tcx>, location: Location) {
486+
// We have `other = transmute(move? a)`
487+
// We replace it with
488+
// ```
489+
// other = move? a_i
490+
// ```
491+
// for the one relevant field.
492+
if let Rvalue::Cast(CastKind::Transmute, ref op, to_ty) = *rvalue
493+
&& let Some(op_place) = op.place()
494+
&& let Some(op_local) = op_place.as_local()
495+
&& let is_move = matches!(op, Operand::Move(..))
496+
&& let Some(op_final_locals) = &self.replacements.fragments[op_local]
497+
{
498+
let field_idx = find_matching_struct_field(
499+
self.tcx,
500+
self.typing_env,
501+
to_ty,
502+
self.local_decls[op_local].ty,
503+
)
504+
.unwrap();
505+
let (new_local_ty, new_local) = op_final_locals[field_idx].unwrap();
506+
assert_eq!(new_local_ty, to_ty);
507+
let new_place = Place::from(new_local);
508+
let new_op = if is_move { Operand::Move(new_place) } else { Operand::Copy(new_place) };
509+
*rvalue = Rvalue::Use(new_op, WithRetag::Yes);
510+
return;
511+
}
512+
513+
self.super_rvalue(rvalue, location);
514+
}
515+
433516
fn visit_local(&mut self, local: &mut Local, _: PlaceContext, _: Location) {
434517
assert!(!self.all_dead_locals.contains(*local));
435518
}

tests/codegen-llvm/read_write_unaligned.rs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ unsafe fn read_unaligned_ptr(ptr: *const NonNull<i16>) -> NonNull<i16> {
2222
unsafe fn read_unaligned_i16(ptr: *const NonZero<i16>) -> NonZero<i16> {
2323
// CHECK: start:
2424
// CHECK-NEXT: [[TEMP:%.+]] = load i16, ptr %ptr, align 1
25-
// CHECK-NOT: !noundef
26-
// CHECK-NOT: !range
25+
// CHECK-SAME: !range [[R16:![0-9]+]]
26+
// CHECK-SAME: !noundef
2727
// CHECK-NEXT: ret i16 [[TEMP]]
2828
ptr.read_unaligned()
2929
}
@@ -33,8 +33,8 @@ unsafe fn read_unaligned_i16(ptr: *const NonZero<i16>) -> NonZero<i16> {
3333
unsafe fn typed_copy_unaligned_i32(src: *const NonZero<i32>, dst: *mut NonZero<i32>) {
3434
// CHECK: start:
3535
// CHECK-NEXT: [[TEMP:%.+]] = load i32, ptr %src, align 1
36-
// CHECK-NOT: !noundef
37-
// CHECK-NOT: !range
36+
// CHECK-SAME: !range [[R32:![0-9]+]]
37+
// CHECK-SAME: !noundef
3838
// CHECK-NEXT: store i32 [[TEMP]], ptr %dst, align 1
3939
// CHECK-NEXT: ret void
4040
dst.write_unaligned(src.read_unaligned())
@@ -69,3 +69,6 @@ unsafe fn write_unaligned_huge(ptr: *mut HugeBuffer, val: HugeBuffer) {
6969
// CHECK-NEXT: ret void
7070
ptr.write_unaligned(val)
7171
}
72+
73+
// CHECK: [[R16]] = !{i16 1, i16 0}
74+
// CHECK: [[R32]] = !{i32 1, i32 0}

tests/mir-opt/pre-codegen/unaligned.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,10 @@ pub unsafe fn unaligned_copy_generic<T>(src: *const T, dst: *mut T) {
2525
// CHECK: debug dst => _2;
2626
// CHECK: debug val => [[VAL:_.+]];
2727
// CHECK: [[SRC_P:_.+]] = copy _1 as *const {{.+}}::Packed<T> (PtrToPtr);
28-
// CHECK: [[PACKED1:_.+]] = copy (*[[SRC_P]]);
29-
// CHECK: [[VAL]] = copy [[PACKED1]] as T (Transmute);
28+
// CHECK: [[VAL]] = copy ((*[[SRC_P]]).0: T);
3029
// CHECK: [[DST_P:_.+]] = copy _2 as *mut {{.+}}::Packed<T> (PtrToPtr);
31-
// CHECK: [[PACKED2:_.+]] = {{.+}}::Packed::<T>(copy [[VAL]]);
32-
// CHECK: (*[[DST_P]]) = copy [[PACKED2]];
30+
// CHECK: [[PACKED:_.+]] = {{.+}}::Packed::<T>(copy [[VAL]]);
31+
// CHECK: (*[[DST_P]]) = copy [[PACKED]];
3332
// CHECK-NOT: copy_nonoverlapping
3433
// CHECK-NOT: drop
3534
unsafe {

tests/mir-opt/pre-codegen/unaligned.unaligned_copy_generic.runtime-optimized.after.mir

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,13 @@ fn unaligned_copy_generic(_1: *const T, _2: *mut T) -> () {
44
debug src => _1;
55
debug dst => _2;
66
let mut _0: ();
7-
let _5: T;
7+
let _4: T;
88
scope 1 {
9-
debug val => _5;
9+
debug val => _4;
1010
scope 8 (inlined #[track_caller] write_unaligned::<T>) {
11-
let _6: *mut std::ptr::Packed<T>;
11+
let _5: *mut std::ptr::Packed<T>;
1212
scope 9 {
13-
let _7: std::ptr::Packed<T>;
13+
let _6: std::ptr::Packed<T>;
1414
scope 10 {
1515
scope 12 (inlined #[track_caller] std::ptr::write::<std::ptr::Packed<T>>) {
1616
}
@@ -23,7 +23,6 @@ fn unaligned_copy_generic(_1: *const T, _2: *mut T) -> () {
2323
scope 2 (inlined #[track_caller] read_unaligned::<T>) {
2424
let _3: *const std::ptr::Packed<T>;
2525
scope 3 {
26-
let _4: std::ptr::Packed<T>;
2726
scope 4 {
2827
scope 7 (inlined transmute_neo::<std::ptr::Packed<T>, T>) {
2928
}
@@ -36,22 +35,19 @@ fn unaligned_copy_generic(_1: *const T, _2: *mut T) -> () {
3635
}
3736

3837
bb0: {
39-
StorageLive(_5);
38+
StorageLive(_4);
4039
StorageLive(_3);
4140
_3 = copy _1 as *const std::ptr::Packed<T> (PtrToPtr);
42-
StorageLive(_4);
43-
_4 = copy (*_3);
44-
_5 = copy _4 as T (Transmute);
45-
StorageDead(_4);
41+
_4 = copy ((*_3).0: T);
4642
StorageDead(_3);
43+
StorageLive(_5);
44+
_5 = copy _2 as *mut std::ptr::Packed<T> (PtrToPtr);
4745
StorageLive(_6);
48-
_6 = copy _2 as *mut std::ptr::Packed<T> (PtrToPtr);
49-
StorageLive(_7);
50-
_7 = std::ptr::Packed::<T>(copy _5);
51-
(*_6) = copy _7;
52-
StorageDead(_7);
46+
_6 = std::ptr::Packed::<T>(copy _4);
47+
(*_5) = copy _6;
5348
StorageDead(_6);
5449
StorageDead(_5);
50+
StorageDead(_4);
5551
return;
5652
}
5753
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
- // MIR for `read_unaligned` before ScalarReplacementOfAggregates
2+
+ // MIR for `read_unaligned` after ScalarReplacementOfAggregates
3+
4+
fn read_unaligned(_1: *const T) -> T {
5+
debug ptr => _1;
6+
let mut _0: T;
7+
let _2: *const Packed<T>;
8+
let mut _3: *const Packed<T>;
9+
let mut _4: *const T;
10+
let mut _6: *const Packed<T>;
11+
let mut _7: Packed<T>;
12+
+ let mut _8: T;
13+
scope 1 {
14+
debug packed_ptr => _2;
15+
let _5: Packed<T>;
16+
+ let _9: T;
17+
scope 2 {
18+
- debug packed_val => _5;
19+
+ debug ((packed_val: Packed<T>).0: T) => _9;
20+
}
21+
}
22+
23+
bb0: {
24+
StorageLive(_2);
25+
StorageLive(_3);
26+
StorageLive(_4);
27+
_4 = copy _1;
28+
_3 = move _4 as *const Packed<T> (PtrToPtr);
29+
_2 = copy _3;
30+
StorageDead(_4);
31+
StorageDead(_3);
32+
- StorageLive(_5);
33+
+ StorageLive(_9);
34+
+ nop;
35+
StorageLive(_6);
36+
_6 = copy _2;
37+
- _5 = copy (*_6);
38+
+ _9 = copy ((*_6).0: T);
39+
+ nop;
40+
StorageDead(_6);
41+
- StorageLive(_7);
42+
- _7 = move _5;
43+
- _0 = move _7 as T (Transmute);
44+
- StorageDead(_7);
45+
- StorageDead(_5);
46+
+ StorageLive(_8);
47+
+ nop;
48+
+ _8 = move _9;
49+
+ nop;
50+
+ _0 = move _8;
51+
+ StorageDead(_8);
52+
+ nop;
53+
+ StorageDead(_9);
54+
+ nop;
55+
StorageDead(_2);
56+
return;
57+
}
58+
}
59+

tests/mir-opt/sroa/read_packed.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//@ test-mir-pass: ScalarReplacementOfAggregates
2+
//@ compile-flags: -Cpanic=abort
3+
//@ no-prefer-dynamic
4+
5+
#![crate_type = "lib"]
6+
#![feature(core_intrinsics)]
7+
8+
use std::intrinsics::{read_via_copy, transmute_unchecked};
9+
10+
#[repr(packed)]
11+
struct Packed<T>(T);
12+
13+
// EMIT_MIR read_packed.read_unaligned.ScalarReplacementOfAggregates.diff
14+
pub const unsafe fn read_unaligned<T>(ptr: *const T) -> T {
15+
// CHECK-LABEL: fn read_unaligned(_1: *const T) -> T
16+
// CHECK: debug packed_ptr => [[PPTR:_.+]];
17+
// CHECK: debug ((packed_val: Packed<T>).0: T) => [[VAL:_.+]];
18+
// CHECK: [[TEMP:_.+]] = copy [[PPTR]];
19+
// CHECK: [[VAL]] = copy ((*{{_.+}}).0: T);
20+
unsafe {
21+
let packed_ptr = ptr as *const Packed<T>;
22+
let packed_val = read_via_copy(packed_ptr);
23+
// transmute because you can't destructure it in a `const fn`
24+
transmute_unchecked(packed_val)
25+
}
26+
}

0 commit comments

Comments
 (0)