Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e7386b3

Browse files
committedSep 14, 2024
Auto merge of #128299 - DianQK:clone-copy, r=cjgillot
Simplify the canonical clone method and the copy-like forms to copy Fixes #128081. The optimized clone method ends up as the following MIR: ``` _2 = copy ((*_1).0: i32); _3 = copy ((*_1).1: u64); _4 = copy ((*_1).2: [i8; 3]); _0 = Foo { a: move _2, b: move _3, c: move _4 }; ``` We can transform this to: ``` _0 = copy (*_1); ``` r? `@cjgillot`
2 parents 02b1be1 + 25d434b commit e7386b3

28 files changed

+1648
-53
lines changed
 

‎compiler/rustc_mir_transform/src/gvn.rs

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,95 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
875875
None
876876
}
877877

878+
fn try_as_place_elem(
879+
&mut self,
880+
proj: ProjectionElem<VnIndex, Ty<'tcx>>,
881+
loc: Location,
882+
) -> Option<PlaceElem<'tcx>> {
883+
Some(match proj {
884+
ProjectionElem::Deref => ProjectionElem::Deref,
885+
ProjectionElem::Field(idx, ty) => ProjectionElem::Field(idx, ty),
886+
ProjectionElem::Index(idx) => {
887+
let Some(local) = self.try_as_local(idx, loc) else {
888+
return None;
889+
};
890+
self.reused_locals.insert(local);
891+
ProjectionElem::Index(local)
892+
}
893+
ProjectionElem::ConstantIndex { offset, min_length, from_end } => {
894+
ProjectionElem::ConstantIndex { offset, min_length, from_end }
895+
}
896+
ProjectionElem::Subslice { from, to, from_end } => {
897+
ProjectionElem::Subslice { from, to, from_end }
898+
}
899+
ProjectionElem::Downcast(symbol, idx) => ProjectionElem::Downcast(symbol, idx),
900+
ProjectionElem::OpaqueCast(idx) => ProjectionElem::OpaqueCast(idx),
901+
ProjectionElem::Subtype(idx) => ProjectionElem::Subtype(idx),
902+
})
903+
}
904+
905+
fn simplify_aggregate_to_copy(
906+
&mut self,
907+
rvalue: &mut Rvalue<'tcx>,
908+
location: Location,
909+
fields: &[VnIndex],
910+
variant_index: VariantIdx,
911+
) -> Option<VnIndex> {
912+
let Some(&first_field) = fields.first() else {
913+
return None;
914+
};
915+
let Value::Projection(copy_from_value, _) = *self.get(first_field) else {
916+
return None;
917+
};
918+
// All fields must correspond one-to-one and come from the same aggregate value.
919+
if fields.iter().enumerate().any(|(index, &v)| {
920+
if let Value::Projection(pointer, ProjectionElem::Field(from_index, _)) = *self.get(v)
921+
&& copy_from_value == pointer
922+
&& from_index.index() == index
923+
{
924+
return false;
925+
}
926+
true
927+
}) {
928+
return None;
929+
}
930+
931+
let mut copy_from_local_value = copy_from_value;
932+
if let Value::Projection(pointer, proj) = *self.get(copy_from_value)
933+
&& let ProjectionElem::Downcast(_, read_variant) = proj
934+
{
935+
if variant_index == read_variant {
936+
// When copying a variant, there is no need to downcast.
937+
copy_from_local_value = pointer;
938+
} else {
939+
// The copied variant must be identical.
940+
return None;
941+
}
942+
}
943+
944+
let tcx = self.tcx;
945+
let mut projection = SmallVec::<[PlaceElem<'tcx>; 1]>::new();
946+
loop {
947+
if let Some(local) = self.try_as_local(copy_from_local_value, location) {
948+
projection.reverse();
949+
let place = Place { local, projection: tcx.mk_place_elems(projection.as_slice()) };
950+
if rvalue.ty(self.local_decls, tcx) == place.ty(self.local_decls, tcx).ty {
951+
self.reused_locals.insert(local);
952+
*rvalue = Rvalue::Use(Operand::Copy(place));
953+
return Some(copy_from_value);
954+
}
955+
return None;
956+
} else if let Value::Projection(pointer, proj) = *self.get(copy_from_local_value)
957+
&& let Some(proj) = self.try_as_place_elem(proj, location)
958+
{
959+
projection.push(proj);
960+
copy_from_local_value = pointer;
961+
} else {
962+
return None;
963+
}
964+
}
965+
}
966+
878967
fn simplify_aggregate(
879968
&mut self,
880969
rvalue: &mut Rvalue<'tcx>,
@@ -971,6 +1060,13 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
9711060
}
9721061
}
9731062

1063+
if let AggregateTy::Def(_, _) = ty
1064+
&& let Some(value) =
1065+
self.simplify_aggregate_to_copy(rvalue, location, &fields, variant_index)
1066+
{
1067+
return Some(value);
1068+
}
1069+
9741070
Some(self.insert(Value::Aggregate(ty, variant_index, fields)))
9751071
}
9761072

@@ -1485,7 +1581,7 @@ impl<'tcx> VnState<'_, 'tcx> {
14851581
}
14861582

14871583
/// If there is a local which is assigned `index`, and its assignment strictly dominates `loc`,
1488-
/// return it.
1584+
/// return it. If you used this local, add it to `reused_locals` to remove storage statements.
14891585
fn try_as_local(&mut self, index: VnIndex, loc: Location) -> Option<Local> {
14901586
let other = self.rev_locals.get(index)?;
14911587
other

‎tests/codegen/clone_as_copy.rs

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
//@ revisions: DEBUGINFO NODEBUGINFO
2+
//@ compile-flags: -O -Cno-prepopulate-passes
3+
//@ [DEBUGINFO] compile-flags: -Cdebuginfo=full
4+
5+
// From https://github.com/rust-lang/rust/issues/128081.
6+
// Ensure that we only generate a memcpy instruction.
7+
8+
#![crate_type = "lib"]
9+
10+
#[derive(Clone)]
11+
struct SubCloneAndCopy {
12+
v1: u32,
13+
v2: u32,
14+
}
15+
16+
#[derive(Clone)]
17+
struct CloneOnly {
18+
v1: u8,
19+
v2: u8,
20+
v3: u8,
21+
v4: u8,
22+
v5: u8,
23+
v6: u8,
24+
v7: u8,
25+
v8: u8,
26+
v9: u8,
27+
v_sub: SubCloneAndCopy,
28+
v_large: [u8; 256],
29+
}
30+
31+
// CHECK-LABEL: define {{.*}}@clone_only(
32+
#[no_mangle]
33+
pub fn clone_only(v: &CloneOnly) -> CloneOnly {
34+
// CHECK-NOT: call {{.*}}clone
35+
// CHECK-NOT: store i8
36+
// CHECK-NOT: store i32
37+
// CHECK: call void @llvm.memcpy
38+
// CHECK-NEXT: ret void
39+
v.clone()
40+
}

‎tests/codegen/enum/unreachable_enum_default_branch.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,13 @@ pub fn implicit_match(x: Int) -> bool {
2828
// The code is from https://github.com/rust-lang/rust/issues/110097.
2929
// We expect it to generate the same optimized code as a full match.
3030
// CHECK-LABEL: @if_let(
31-
// CHECK-NEXT: start:
31+
// CHECK: start:
32+
// CHECK-NOT: zext
33+
// CHECK: select
3234
// CHECK-NEXT: insertvalue
3335
// CHECK-NEXT: insertvalue
3436
// CHECK-NEXT: ret
3537
#[no_mangle]
3638
pub fn if_let(val: Result<i32, ()>) -> Result<i32, ()> {
37-
if let Ok(x) = val { Ok(x) } else { Err(()) }
39+
if let Ok(x) = val { Ok(x * 2) } else { Err(()) }
3840
}

‎tests/codegen/try_question_mark_nop.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
//@ compile-flags: -O -Z merge-functions=disabled --edition=2021
22
//@ only-x86_64
3+
// FIXME: Remove the `min-llvm-version`.
4+
//@ min-llvm-version: 19
35

46
#![crate_type = "lib"]
57
#![feature(try_blocks)]
68

79
use std::ops::ControlFlow::{self, Break, Continue};
810
use std::ptr::NonNull;
911

12+
// FIXME: The `trunc` and `select` instructions can be eliminated.
1013
// CHECK-LABEL: @option_nop_match_32
1114
#[no_mangle]
1215
pub fn option_nop_match_32(x: Option<u32>) -> Option<u32> {
1316
// CHECK: start:
14-
// CHECK-NEXT: insertvalue { i32, i32 }
17+
// CHECK-NEXT: [[TRUNC:%.*]] = trunc nuw i32 %0 to i1
18+
// CHECK-NEXT: [[FIRST:%.*]] = select i1 [[TRUNC]], i32 %0
19+
// CHECK-NEXT: insertvalue { i32, i32 } poison, i32 [[FIRST]]
1520
// CHECK-NEXT: insertvalue { i32, i32 }
1621
// CHECK-NEXT: ret { i32, i32 }
1722
match x {

‎tests/mir-opt/gvn_clone.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
//@ test-mir-pass: GVN
2+
//@ compile-flags: -Zmir-enable-passes=+InstSimplify-before-inline
3+
4+
// Check if we have transformed the default clone to copy in the specific pipeline.
5+
6+
// EMIT_MIR gvn_clone.{impl#0}-clone.GVN.diff
7+
8+
// CHECK-LABEL: ::clone(
9+
// CHECK-NOT: = AllCopy { {{.*}} };
10+
// CHECK: _0 = copy (*_1);
11+
// CHECK: return;
12+
#[derive(Clone)]
13+
struct AllCopy {
14+
a: i32,
15+
b: u64,
16+
c: [i8; 3],
17+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
- // MIR for `<impl at $DIR/gvn_clone.rs:12:10: 12:15>::clone` before GVN
2+
+ // MIR for `<impl at $DIR/gvn_clone.rs:12:10: 12:15>::clone` after GVN
3+
4+
fn <impl at $DIR/gvn_clone.rs:12:10: 12:15>::clone(_1: &AllCopy) -> AllCopy {
5+
debug self => _1;
6+
let mut _0: AllCopy;
7+
let mut _2: i32;
8+
let mut _3: &i32;
9+
let _4: &i32;
10+
let mut _5: u64;
11+
let mut _6: &u64;
12+
let _7: &u64;
13+
let mut _8: [i8; 3];
14+
let mut _9: &[i8; 3];
15+
let _10: &[i8; 3];
16+
17+
bb0: {
18+
StorageLive(_2);
19+
StorageLive(_3);
20+
- StorageLive(_4);
21+
+ nop;
22+
_4 = &((*_1).0: i32);
23+
_3 = copy _4;
24+
- _2 = copy (*_3);
25+
+ _2 = copy ((*_1).0: i32);
26+
goto -> bb1;
27+
}
28+
29+
bb1: {
30+
StorageDead(_3);
31+
StorageLive(_5);
32+
StorageLive(_6);
33+
- StorageLive(_7);
34+
+ nop;
35+
_7 = &((*_1).1: u64);
36+
_6 = copy _7;
37+
- _5 = copy (*_6);
38+
+ _5 = copy ((*_1).1: u64);
39+
goto -> bb2;
40+
}
41+
42+
bb2: {
43+
StorageDead(_6);
44+
StorageLive(_8);
45+
StorageLive(_9);
46+
- StorageLive(_10);
47+
+ nop;
48+
_10 = &((*_1).2: [i8; 3]);
49+
_9 = copy _10;
50+
- _8 = copy (*_9);
51+
+ _8 = copy ((*_1).2: [i8; 3]);
52+
goto -> bb3;
53+
}
54+
55+
bb3: {
56+
StorageDead(_9);
57+
- _0 = AllCopy { a: move _2, b: move _5, c: move _8 };
58+
+ _0 = copy (*_1);
59+
StorageDead(_8);
60+
StorageDead(_5);
61+
StorageDead(_2);
62+
- StorageDead(_10);
63+
- StorageDead(_7);
64+
- StorageDead(_4);
65+
+ nop;
66+
+ nop;
67+
+ nop;
68+
return;
69+
}
70+
}
71+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
- // MIR for `all_copy` before GVN
2+
+ // MIR for `all_copy` after GVN
3+
4+
fn all_copy(_1: &AllCopy) -> AllCopy {
5+
debug v => _1;
6+
let mut _0: AllCopy;
7+
let _2: i32;
8+
let mut _5: i32;
9+
let mut _6: u64;
10+
let mut _7: [i8; 3];
11+
scope 1 {
12+
debug a => _2;
13+
let _3: u64;
14+
scope 2 {
15+
debug b => _3;
16+
let _4: [i8; 3];
17+
scope 3 {
18+
debug c => _4;
19+
}
20+
}
21+
}
22+
23+
bb0: {
24+
- StorageLive(_2);
25+
+ nop;
26+
_2 = copy ((*_1).0: i32);
27+
- StorageLive(_3);
28+
+ nop;
29+
_3 = copy ((*_1).1: u64);
30+
- StorageLive(_4);
31+
+ nop;
32+
_4 = copy ((*_1).2: [i8; 3]);
33+
StorageLive(_5);
34+
_5 = copy _2;
35+
StorageLive(_6);
36+
_6 = copy _3;
37+
StorageLive(_7);
38+
_7 = copy _4;
39+
- _0 = AllCopy { a: move _5, b: move _6, c: move _7 };
40+
+ _0 = copy (*_1);
41+
StorageDead(_7);
42+
StorageDead(_6);
43+
StorageDead(_5);
44+
- StorageDead(_4);
45+
- StorageDead(_3);
46+
- StorageDead(_2);
47+
+ nop;
48+
+ nop;
49+
+ nop;
50+
return;
51+
}
52+
}
53+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
- // MIR for `all_copy_2` before GVN
2+
+ // MIR for `all_copy_2` after GVN
3+
4+
fn all_copy_2(_1: &&AllCopy) -> AllCopy {
5+
debug v => _1;
6+
let mut _0: AllCopy;
7+
let _2: i32;
8+
let mut _5: i32;
9+
let mut _6: u64;
10+
let mut _7: [i8; 3];
11+
let mut _8: &AllCopy;
12+
let mut _9: &AllCopy;
13+
let mut _10: &AllCopy;
14+
scope 1 {
15+
debug a => _2;
16+
let _3: u64;
17+
scope 2 {
18+
debug b => _3;
19+
let _4: [i8; 3];
20+
scope 3 {
21+
debug c => _4;
22+
}
23+
}
24+
}
25+
26+
bb0: {
27+
- StorageLive(_2);
28+
- _8 = deref_copy (*_1);
29+
+ nop;
30+
+ _8 = copy (*_1);
31+
_2 = copy ((*_8).0: i32);
32+
- StorageLive(_3);
33+
- _9 = deref_copy (*_1);
34+
- _3 = copy ((*_9).1: u64);
35+
- StorageLive(_4);
36+
- _10 = deref_copy (*_1);
37+
- _4 = copy ((*_10).2: [i8; 3]);
38+
+ nop;
39+
+ _9 = copy _8;
40+
+ _3 = copy ((*_8).1: u64);
41+
+ nop;
42+
+ _10 = copy _8;
43+
+ _4 = copy ((*_8).2: [i8; 3]);
44+
StorageLive(_5);
45+
_5 = copy _2;
46+
StorageLive(_6);
47+
_6 = copy _3;
48+
StorageLive(_7);
49+
_7 = copy _4;
50+
- _0 = AllCopy { a: move _5, b: move _6, c: move _7 };
51+
+ _0 = copy (*_8);
52+
StorageDead(_7);
53+
StorageDead(_6);
54+
StorageDead(_5);
55+
- StorageDead(_4);
56+
- StorageDead(_3);
57+
- StorageDead(_2);
58+
+ nop;
59+
+ nop;
60+
+ nop;
61+
return;
62+
}
63+
}
64+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
- // MIR for `all_copy_different_type` before GVN
2+
+ // MIR for `all_copy_different_type` after GVN
3+
4+
fn all_copy_different_type(_1: &AllCopy) -> AllCopy2 {
5+
debug v => _1;
6+
let mut _0: AllCopy2;
7+
let _2: i32;
8+
let mut _5: i32;
9+
let mut _6: u64;
10+
let mut _7: [i8; 3];
11+
scope 1 {
12+
debug a => _2;
13+
let _3: u64;
14+
scope 2 {
15+
debug b => _3;
16+
let _4: [i8; 3];
17+
scope 3 {
18+
debug c => _4;
19+
}
20+
}
21+
}
22+
23+
bb0: {
24+
- StorageLive(_2);
25+
+ nop;
26+
_2 = copy ((*_1).0: i32);
27+
- StorageLive(_3);
28+
+ nop;
29+
_3 = copy ((*_1).1: u64);
30+
- StorageLive(_4);
31+
+ nop;
32+
_4 = copy ((*_1).2: [i8; 3]);
33+
StorageLive(_5);
34+
_5 = copy _2;
35+
StorageLive(_6);
36+
_6 = copy _3;
37+
StorageLive(_7);
38+
_7 = copy _4;
39+
- _0 = AllCopy2 { a: move _5, b: move _6, c: move _7 };
40+
+ _0 = AllCopy2 { a: copy _2, b: copy _3, c: copy _4 };
41+
StorageDead(_7);
42+
StorageDead(_6);
43+
StorageDead(_5);
44+
- StorageDead(_4);
45+
- StorageDead(_3);
46+
- StorageDead(_2);
47+
+ nop;
48+
+ nop;
49+
+ nop;
50+
return;
51+
}
52+
}
53+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
- // MIR for `all_copy_has_changed` before GVN
2+
+ // MIR for `all_copy_has_changed` after GVN
3+
4+
fn all_copy_has_changed(_1: &mut AllCopy) -> AllCopy {
5+
debug v => _1;
6+
let mut _0: AllCopy;
7+
let _2: i32;
8+
let mut _5: i32;
9+
let mut _6: u64;
10+
let mut _7: [i8; 3];
11+
scope 1 {
12+
debug a => _2;
13+
let _3: u64;
14+
scope 2 {
15+
debug b => _3;
16+
let _4: [i8; 3];
17+
scope 3 {
18+
debug c => _4;
19+
}
20+
}
21+
}
22+
23+
bb0: {
24+
- StorageLive(_2);
25+
+ nop;
26+
_2 = copy ((*_1).0: i32);
27+
- StorageLive(_3);
28+
+ nop;
29+
_3 = copy ((*_1).1: u64);
30+
- StorageLive(_4);
31+
+ nop;
32+
_4 = copy ((*_1).2: [i8; 3]);
33+
((*_1).0: i32) = const 1_i32;
34+
StorageLive(_5);
35+
_5 = copy _2;
36+
StorageLive(_6);
37+
_6 = copy _3;
38+
StorageLive(_7);
39+
_7 = copy _4;
40+
- _0 = AllCopy { a: move _5, b: move _6, c: move _7 };
41+
+ _0 = AllCopy { a: copy _2, b: copy _3, c: copy _4 };
42+
StorageDead(_7);
43+
StorageDead(_6);
44+
StorageDead(_5);
45+
- StorageDead(_4);
46+
- StorageDead(_3);
47+
- StorageDead(_2);
48+
+ nop;
49+
+ nop;
50+
+ nop;
51+
return;
52+
}
53+
}
54+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
- // MIR for `all_copy_move` before GVN
2+
+ // MIR for `all_copy_move` after GVN
3+
4+
fn all_copy_move(_1: AllCopy) -> AllCopy {
5+
debug v => _1;
6+
let mut _0: AllCopy;
7+
let _2: i32;
8+
let mut _5: i32;
9+
let mut _6: u64;
10+
let mut _7: [i8; 3];
11+
scope 1 {
12+
debug a => _2;
13+
let _3: u64;
14+
scope 2 {
15+
debug b => _3;
16+
let _4: [i8; 3];
17+
scope 3 {
18+
debug c => _4;
19+
}
20+
}
21+
}
22+
23+
bb0: {
24+
- StorageLive(_2);
25+
+ nop;
26+
_2 = copy (_1.0: i32);
27+
- StorageLive(_3);
28+
+ nop;
29+
_3 = copy (_1.1: u64);
30+
- StorageLive(_4);
31+
+ nop;
32+
_4 = copy (_1.2: [i8; 3]);
33+
StorageLive(_5);
34+
_5 = copy _2;
35+
StorageLive(_6);
36+
_6 = copy _3;
37+
StorageLive(_7);
38+
_7 = copy _4;
39+
- _0 = AllCopy { a: move _5, b: move _6, c: move _7 };
40+
+ _0 = copy _1;
41+
StorageDead(_7);
42+
StorageDead(_6);
43+
StorageDead(_5);
44+
- StorageDead(_4);
45+
- StorageDead(_3);
46+
- StorageDead(_2);
47+
+ nop;
48+
+ nop;
49+
+ nop;
50+
return;
51+
}
52+
}
53+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
- // MIR for `all_copy_ret_2` before GVN
2+
+ // MIR for `all_copy_ret_2` after GVN
3+
4+
fn all_copy_ret_2(_1: &AllCopy) -> (AllCopy, AllCopy) {
5+
debug v => _1;
6+
let mut _0: (AllCopy, AllCopy);
7+
let _2: i32;
8+
let mut _5: AllCopy;
9+
let mut _6: i32;
10+
let mut _7: u64;
11+
let mut _8: [i8; 3];
12+
let mut _9: AllCopy;
13+
let mut _10: i32;
14+
let mut _11: u64;
15+
let mut _12: [i8; 3];
16+
scope 1 {
17+
debug a => _2;
18+
let _3: u64;
19+
scope 2 {
20+
debug b => _3;
21+
let _4: [i8; 3];
22+
scope 3 {
23+
debug c => _4;
24+
}
25+
}
26+
}
27+
28+
bb0: {
29+
- StorageLive(_2);
30+
+ nop;
31+
_2 = copy ((*_1).0: i32);
32+
- StorageLive(_3);
33+
+ nop;
34+
_3 = copy ((*_1).1: u64);
35+
- StorageLive(_4);
36+
+ nop;
37+
_4 = copy ((*_1).2: [i8; 3]);
38+
- StorageLive(_5);
39+
+ nop;
40+
StorageLive(_6);
41+
_6 = copy _2;
42+
StorageLive(_7);
43+
_7 = copy _3;
44+
StorageLive(_8);
45+
_8 = copy _4;
46+
- _5 = AllCopy { a: move _6, b: move _7, c: move _8 };
47+
+ _5 = copy (*_1);
48+
StorageDead(_8);
49+
StorageDead(_7);
50+
StorageDead(_6);
51+
StorageLive(_9);
52+
StorageLive(_10);
53+
_10 = copy _2;
54+
StorageLive(_11);
55+
_11 = copy _3;
56+
StorageLive(_12);
57+
_12 = copy _4;
58+
- _9 = AllCopy { a: move _10, b: move _11, c: move _12 };
59+
+ _9 = copy _5;
60+
StorageDead(_12);
61+
StorageDead(_11);
62+
StorageDead(_10);
63+
- _0 = (move _5, move _9);
64+
+ _0 = (copy _5, copy _5);
65+
StorageDead(_9);
66+
- StorageDead(_5);
67+
- StorageDead(_4);
68+
- StorageDead(_3);
69+
- StorageDead(_2);
70+
+ nop;
71+
+ nop;
72+
+ nop;
73+
+ nop;
74+
return;
75+
}
76+
}
77+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
- // MIR for `all_copy_use_changed` before GVN
2+
+ // MIR for `all_copy_use_changed` after GVN
3+
4+
fn all_copy_use_changed(_1: &mut AllCopy) -> AllCopy {
5+
debug v => _1;
6+
let mut _0: AllCopy;
7+
let mut _2: i32;
8+
let mut _3: i32;
9+
let mut _6: i32;
10+
let mut _7: u64;
11+
let mut _8: [i8; 3];
12+
scope 1 {
13+
debug a => _2;
14+
let _4: u64;
15+
scope 2 {
16+
debug b => _4;
17+
let _5: [i8; 3];
18+
scope 3 {
19+
debug c => _5;
20+
}
21+
}
22+
}
23+
24+
bb0: {
25+
StorageLive(_2);
26+
_2 = copy ((*_1).0: i32);
27+
((*_1).0: i32) = const 1_i32;
28+
StorageLive(_3);
29+
_3 = copy ((*_1).0: i32);
30+
_2 = move _3;
31+
StorageDead(_3);
32+
- StorageLive(_4);
33+
+ nop;
34+
_4 = copy ((*_1).1: u64);
35+
- StorageLive(_5);
36+
+ nop;
37+
_5 = copy ((*_1).2: [i8; 3]);
38+
StorageLive(_6);
39+
_6 = copy _2;
40+
StorageLive(_7);
41+
_7 = copy _4;
42+
StorageLive(_8);
43+
_8 = copy _5;
44+
- _0 = AllCopy { a: move _6, b: move _7, c: move _8 };
45+
+ _0 = AllCopy { a: move _6, b: copy _4, c: copy _5 };
46+
StorageDead(_8);
47+
StorageDead(_7);
48+
StorageDead(_6);
49+
- StorageDead(_5);
50+
- StorageDead(_4);
51+
+ nop;
52+
+ nop;
53+
StorageDead(_2);
54+
return;
55+
}
56+
}
57+
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
- // MIR for `all_copy_use_changed_2` before GVN
2+
+ // MIR for `all_copy_use_changed_2` after GVN
3+
4+
fn all_copy_use_changed_2(_1: &mut AllCopy) -> AllCopy {
5+
debug v => _1;
6+
let mut _0: AllCopy;
7+
let mut _2: i32;
8+
let mut _5: i32;
9+
let mut _6: i32;
10+
let mut _7: u64;
11+
let mut _8: [i8; 3];
12+
scope 1 {
13+
debug a => _2;
14+
let _3: u64;
15+
scope 2 {
16+
debug b => _3;
17+
let _4: [i8; 3];
18+
scope 3 {
19+
debug c => _4;
20+
}
21+
}
22+
}
23+
24+
bb0: {
25+
StorageLive(_2);
26+
_2 = copy ((*_1).0: i32);
27+
- StorageLive(_3);
28+
+ nop;
29+
_3 = copy ((*_1).1: u64);
30+
- StorageLive(_4);
31+
+ nop;
32+
_4 = copy ((*_1).2: [i8; 3]);
33+
((*_1).0: i32) = const 1_i32;
34+
StorageLive(_5);
35+
_5 = copy ((*_1).0: i32);
36+
_2 = move _5;
37+
StorageDead(_5);
38+
StorageLive(_6);
39+
_6 = copy _2;
40+
StorageLive(_7);
41+
_7 = copy _3;
42+
StorageLive(_8);
43+
_8 = copy _4;
44+
- _0 = AllCopy { a: move _6, b: move _7, c: move _8 };
45+
+ _0 = AllCopy { a: move _6, b: copy _3, c: copy _4 };
46+
StorageDead(_8);
47+
StorageDead(_7);
48+
StorageDead(_6);
49+
- StorageDead(_4);
50+
- StorageDead(_3);
51+
+ nop;
52+
+ nop;
53+
StorageDead(_2);
54+
return;
55+
}
56+
}
57+
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
- // MIR for `enum_different_variant` before GVN
2+
+ // MIR for `enum_different_variant` after GVN
3+
4+
fn enum_different_variant(_1: &Enum1) -> Enum1 {
5+
debug v => _1;
6+
let mut _0: Enum1;
7+
let mut _2: isize;
8+
let _3: &AllCopy;
9+
let mut _8: i32;
10+
let mut _9: u64;
11+
let mut _10: [i8; 3];
12+
let mut _11: AllCopy;
13+
let _12: &AllCopy;
14+
let mut _17: i32;
15+
let mut _18: u64;
16+
let mut _19: [i8; 3];
17+
let mut _20: AllCopy;
18+
scope 1 {
19+
debug v => _3;
20+
let _4: i32;
21+
scope 2 {
22+
debug a => _4;
23+
let _5: u64;
24+
scope 3 {
25+
debug b => _5;
26+
let _6: [i8; 3];
27+
scope 4 {
28+
debug c => _6;
29+
let _7: AllCopy;
30+
scope 5 {
31+
debug all_copy => _7;
32+
}
33+
}
34+
}
35+
}
36+
}
37+
scope 6 {
38+
debug v => _12;
39+
let _13: i32;
40+
scope 7 {
41+
debug a => _13;
42+
let _14: u64;
43+
scope 8 {
44+
debug b => _14;
45+
let _15: [i8; 3];
46+
scope 9 {
47+
debug c => _15;
48+
let _16: AllCopy;
49+
scope 10 {
50+
debug all_copy => _16;
51+
}
52+
}
53+
}
54+
}
55+
}
56+
57+
bb0: {
58+
_2 = discriminant((*_1));
59+
switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1];
60+
}
61+
62+
bb1: {
63+
unreachable;
64+
}
65+
66+
bb2: {
67+
StorageLive(_12);
68+
_12 = &(((*_1) as B).0: AllCopy);
69+
- StorageLive(_13);
70+
- _13 = copy ((*_12).0: i32);
71+
- StorageLive(_14);
72+
- _14 = copy ((*_12).1: u64);
73+
- StorageLive(_15);
74+
- _15 = copy ((*_12).2: [i8; 3]);
75+
- StorageLive(_16);
76+
+ nop;
77+
+ _13 = copy ((((*_1) as B).0: AllCopy).0: i32);
78+
+ nop;
79+
+ _14 = copy ((((*_1) as B).0: AllCopy).1: u64);
80+
+ nop;
81+
+ _15 = copy ((((*_1) as B).0: AllCopy).2: [i8; 3]);
82+
+ nop;
83+
StorageLive(_17);
84+
_17 = copy _13;
85+
StorageLive(_18);
86+
_18 = copy _14;
87+
StorageLive(_19);
88+
_19 = copy _15;
89+
- _16 = AllCopy { a: move _17, b: move _18, c: move _19 };
90+
+ _16 = copy (((*_1) as B).0: AllCopy);
91+
StorageDead(_19);
92+
StorageDead(_18);
93+
StorageDead(_17);
94+
StorageLive(_20);
95+
- _20 = move _16;
96+
- _0 = Enum1::A(move _20);
97+
+ _20 = copy _16;
98+
+ _0 = Enum1::A(copy _16);
99+
StorageDead(_20);
100+
- StorageDead(_16);
101+
- StorageDead(_15);
102+
- StorageDead(_14);
103+
- StorageDead(_13);
104+
+ nop;
105+
+ nop;
106+
+ nop;
107+
+ nop;
108+
StorageDead(_12);
109+
goto -> bb4;
110+
}
111+
112+
bb3: {
113+
StorageLive(_3);
114+
_3 = &(((*_1) as A).0: AllCopy);
115+
- StorageLive(_4);
116+
- _4 = copy ((*_3).0: i32);
117+
- StorageLive(_5);
118+
- _5 = copy ((*_3).1: u64);
119+
- StorageLive(_6);
120+
- _6 = copy ((*_3).2: [i8; 3]);
121+
- StorageLive(_7);
122+
+ nop;
123+
+ _4 = copy ((((*_1) as A).0: AllCopy).0: i32);
124+
+ nop;
125+
+ _5 = copy ((((*_1) as A).0: AllCopy).1: u64);
126+
+ nop;
127+
+ _6 = copy ((((*_1) as A).0: AllCopy).2: [i8; 3]);
128+
+ nop;
129+
StorageLive(_8);
130+
_8 = copy _4;
131+
StorageLive(_9);
132+
_9 = copy _5;
133+
StorageLive(_10);
134+
_10 = copy _6;
135+
- _7 = AllCopy { a: move _8, b: move _9, c: move _10 };
136+
+ _7 = copy (((*_1) as A).0: AllCopy);
137+
StorageDead(_10);
138+
StorageDead(_9);
139+
StorageDead(_8);
140+
StorageLive(_11);
141+
- _11 = move _7;
142+
- _0 = Enum1::B(move _11);
143+
+ _11 = copy _7;
144+
+ _0 = Enum1::B(copy _7);
145+
StorageDead(_11);
146+
- StorageDead(_7);
147+
- StorageDead(_6);
148+
- StorageDead(_5);
149+
- StorageDead(_4);
150+
+ nop;
151+
+ nop;
152+
+ nop;
153+
+ nop;
154+
StorageDead(_3);
155+
goto -> bb4;
156+
}
157+
158+
bb4: {
159+
return;
160+
}
161+
}
162+
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
- // MIR for `enum_identical_variant` before GVN
2+
+ // MIR for `enum_identical_variant` after GVN
3+
4+
fn enum_identical_variant(_1: &Enum1) -> Enum1 {
5+
debug v => _1;
6+
let mut _0: Enum1;
7+
let mut _2: isize;
8+
let _3: &AllCopy;
9+
let mut _8: i32;
10+
let mut _9: u64;
11+
let mut _10: [i8; 3];
12+
let mut _11: AllCopy;
13+
let _12: &AllCopy;
14+
let mut _17: i32;
15+
let mut _18: u64;
16+
let mut _19: [i8; 3];
17+
let mut _20: AllCopy;
18+
scope 1 {
19+
debug v => _3;
20+
let _4: i32;
21+
scope 2 {
22+
debug a => _4;
23+
let _5: u64;
24+
scope 3 {
25+
debug b => _5;
26+
let _6: [i8; 3];
27+
scope 4 {
28+
debug c => _6;
29+
let _7: AllCopy;
30+
scope 5 {
31+
debug all_copy => _7;
32+
}
33+
}
34+
}
35+
}
36+
}
37+
scope 6 {
38+
debug v => _12;
39+
let _13: i32;
40+
scope 7 {
41+
debug a => _13;
42+
let _14: u64;
43+
scope 8 {
44+
debug b => _14;
45+
let _15: [i8; 3];
46+
scope 9 {
47+
debug c => _15;
48+
let _16: AllCopy;
49+
scope 10 {
50+
debug all_copy => _16;
51+
}
52+
}
53+
}
54+
}
55+
}
56+
57+
bb0: {
58+
_2 = discriminant((*_1));
59+
switchInt(move _2) -> [0: bb3, 1: bb2, otherwise: bb1];
60+
}
61+
62+
bb1: {
63+
unreachable;
64+
}
65+
66+
bb2: {
67+
StorageLive(_12);
68+
_12 = &(((*_1) as B).0: AllCopy);
69+
- StorageLive(_13);
70+
- _13 = copy ((*_12).0: i32);
71+
- StorageLive(_14);
72+
- _14 = copy ((*_12).1: u64);
73+
- StorageLive(_15);
74+
- _15 = copy ((*_12).2: [i8; 3]);
75+
- StorageLive(_16);
76+
+ nop;
77+
+ _13 = copy ((((*_1) as B).0: AllCopy).0: i32);
78+
+ nop;
79+
+ _14 = copy ((((*_1) as B).0: AllCopy).1: u64);
80+
+ nop;
81+
+ _15 = copy ((((*_1) as B).0: AllCopy).2: [i8; 3]);
82+
+ nop;
83+
StorageLive(_17);
84+
_17 = copy _13;
85+
StorageLive(_18);
86+
_18 = copy _14;
87+
StorageLive(_19);
88+
_19 = copy _15;
89+
- _16 = AllCopy { a: move _17, b: move _18, c: move _19 };
90+
+ _16 = copy (((*_1) as B).0: AllCopy);
91+
StorageDead(_19);
92+
StorageDead(_18);
93+
StorageDead(_17);
94+
StorageLive(_20);
95+
- _20 = move _16;
96+
- _0 = Enum1::B(move _20);
97+
+ _20 = copy _16;
98+
+ _0 = copy (*_1);
99+
StorageDead(_20);
100+
- StorageDead(_16);
101+
- StorageDead(_15);
102+
- StorageDead(_14);
103+
- StorageDead(_13);
104+
+ nop;
105+
+ nop;
106+
+ nop;
107+
+ nop;
108+
StorageDead(_12);
109+
goto -> bb4;
110+
}
111+
112+
bb3: {
113+
StorageLive(_3);
114+
_3 = &(((*_1) as A).0: AllCopy);
115+
- StorageLive(_4);
116+
- _4 = copy ((*_3).0: i32);
117+
- StorageLive(_5);
118+
- _5 = copy ((*_3).1: u64);
119+
- StorageLive(_6);
120+
- _6 = copy ((*_3).2: [i8; 3]);
121+
- StorageLive(_7);
122+
+ nop;
123+
+ _4 = copy ((((*_1) as A).0: AllCopy).0: i32);
124+
+ nop;
125+
+ _5 = copy ((((*_1) as A).0: AllCopy).1: u64);
126+
+ nop;
127+
+ _6 = copy ((((*_1) as A).0: AllCopy).2: [i8; 3]);
128+
+ nop;
129+
StorageLive(_8);
130+
_8 = copy _4;
131+
StorageLive(_9);
132+
_9 = copy _5;
133+
StorageLive(_10);
134+
_10 = copy _6;
135+
- _7 = AllCopy { a: move _8, b: move _9, c: move _10 };
136+
+ _7 = copy (((*_1) as A).0: AllCopy);
137+
StorageDead(_10);
138+
StorageDead(_9);
139+
StorageDead(_8);
140+
StorageLive(_11);
141+
- _11 = move _7;
142+
- _0 = Enum1::A(move _11);
143+
+ _11 = copy _7;
144+
+ _0 = copy (*_1);
145+
StorageDead(_11);
146+
- StorageDead(_7);
147+
- StorageDead(_6);
148+
- StorageDead(_5);
149+
- StorageDead(_4);
150+
+ nop;
151+
+ nop;
152+
+ nop;
153+
+ nop;
154+
StorageDead(_3);
155+
goto -> bb4;
156+
}
157+
158+
bb4: {
159+
return;
160+
}
161+
}
162+
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
- // MIR for `nest_copy` before GVN
2+
+ // MIR for `nest_copy` after GVN
3+
4+
fn nest_copy(_1: &NestCopy) -> NestCopy {
5+
debug v => _1;
6+
let mut _0: NestCopy;
7+
let _2: i32;
8+
let mut _6: i32;
9+
let mut _7: u64;
10+
let mut _8: [i8; 3];
11+
let mut _10: i32;
12+
let mut _11: AllCopy;
13+
scope 1 {
14+
debug a => _2;
15+
let _3: u64;
16+
scope 2 {
17+
debug b => _3;
18+
let _4: [i8; 3];
19+
scope 3 {
20+
debug c => _4;
21+
let _5: AllCopy;
22+
scope 4 {
23+
debug all_copy => _5;
24+
let _9: i32;
25+
scope 5 {
26+
debug d => _9;
27+
}
28+
}
29+
}
30+
}
31+
}
32+
33+
bb0: {
34+
- StorageLive(_2);
35+
+ nop;
36+
_2 = copy (((*_1).1: AllCopy).0: i32);
37+
- StorageLive(_3);
38+
+ nop;
39+
_3 = copy (((*_1).1: AllCopy).1: u64);
40+
- StorageLive(_4);
41+
+ nop;
42+
_4 = copy (((*_1).1: AllCopy).2: [i8; 3]);
43+
- StorageLive(_5);
44+
+ nop;
45+
StorageLive(_6);
46+
_6 = copy _2;
47+
StorageLive(_7);
48+
_7 = copy _3;
49+
StorageLive(_8);
50+
_8 = copy _4;
51+
- _5 = AllCopy { a: move _6, b: move _7, c: move _8 };
52+
+ _5 = copy ((*_1).1: AllCopy);
53+
StorageDead(_8);
54+
StorageDead(_7);
55+
StorageDead(_6);
56+
- StorageLive(_9);
57+
+ nop;
58+
_9 = copy ((*_1).0: i32);
59+
StorageLive(_10);
60+
_10 = copy _9;
61+
StorageLive(_11);
62+
- _11 = move _5;
63+
- _0 = NestCopy { d: move _10, all_copy: move _11 };
64+
+ _11 = copy _5;
65+
+ _0 = copy (*_1);
66+
StorageDead(_11);
67+
StorageDead(_10);
68+
- StorageDead(_9);
69+
- StorageDead(_5);
70+
- StorageDead(_4);
71+
- StorageDead(_3);
72+
- StorageDead(_2);
73+
+ nop;
74+
+ nop;
75+
+ nop;
76+
+ nop;
77+
+ nop;
78+
return;
79+
}
80+
}
81+
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
- // MIR for `remove_storage_dead` before GVN
2+
+ // MIR for `remove_storage_dead` after GVN
3+
4+
fn remove_storage_dead(_1: fn() -> AlwaysSome<T>) -> AlwaysSome<T> {
5+
debug f => _1;
6+
let mut _0: AlwaysSome<T>;
7+
let _2: T;
8+
let mut _3: AlwaysSome<T>;
9+
let mut _4: fn() -> AlwaysSome<T>;
10+
let _5: T;
11+
let mut _6: T;
12+
let mut _7: isize;
13+
let mut _8: isize;
14+
scope 1 {
15+
debug v => _2;
16+
}
17+
scope 2 {
18+
debug v => _5;
19+
}
20+
21+
bb0: {
22+
StorageLive(_2);
23+
- StorageLive(_3);
24+
+ nop;
25+
StorageLive(_4);
26+
_4 = copy _1;
27+
- _3 = move _4() -> [return: bb1, unwind unreachable];
28+
+ _3 = copy _1() -> [return: bb1, unwind unreachable];
29+
}
30+
31+
bb1: {
32+
StorageDead(_4);
33+
- StorageLive(_5);
34+
- _5 = move ((_3 as Some).0: T);
35+
- _2 = move _5;
36+
- StorageDead(_5);
37+
+ nop;
38+
+ _5 = copy ((_3 as Some).0: T);
39+
+ _2 = copy _5;
40+
+ nop;
41+
_7 = discriminant(_3);
42+
- StorageDead(_3);
43+
+ nop;
44+
StorageLive(_6);
45+
- _6 = move _2;
46+
- _0 = AlwaysSome::<T>::Some(move _6);
47+
+ _6 = copy _5;
48+
+ _0 = copy _3;
49+
StorageDead(_6);
50+
StorageDead(_2);
51+
return;
52+
}
53+
}
54+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
- // MIR for `remove_storage_dead_from_index` before GVN
2+
+ // MIR for `remove_storage_dead_from_index` after GVN
3+
4+
fn remove_storage_dead_from_index(_1: fn() -> usize, _2: [SameType; 5]) -> SameType {
5+
let mut _0: SameType;
6+
let mut _3: usize;
7+
let mut _4: i32;
8+
let mut _5: i32;
9+
10+
bb0: {
11+
- StorageLive(_3);
12+
+ nop;
13+
_3 = copy _1() -> [return: bb1, unwind unreachable];
14+
}
15+
16+
bb1: {
17+
_4 = copy (_2[_3].0: i32);
18+
_5 = copy (_2[_3].1: i32);
19+
- StorageDead(_3);
20+
- _0 = SameType { a: copy _4, b: copy _5 };
21+
+ nop;
22+
+ _0 = copy _2[_3];
23+
return;
24+
}
25+
}
26+

‎tests/mir-opt/gvn_copy_aggregate.rs

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
//@ test-mir-pass: GVN
2+
//@ compile-flags: -Cpanic=abort
3+
4+
#![feature(core_intrinsics, custom_mir)]
5+
#![allow(internal_features)]
6+
7+
use std::intrinsics::mir::*;
8+
9+
struct AllCopy {
10+
a: i32,
11+
b: u64,
12+
c: [i8; 3],
13+
}
14+
15+
// EMIT_MIR gvn_copy_aggregate.all_copy.GVN.diff
16+
fn all_copy(v: &AllCopy) -> AllCopy {
17+
// CHECK-LABEL: fn all_copy(
18+
// CHECK: bb0: {
19+
// CHECK-NOT: = AllCopy { {{.*}} };
20+
// CHECK: _0 = copy (*_1);
21+
let a = v.a;
22+
let b = v.b;
23+
let c = v.c;
24+
AllCopy { a, b, c }
25+
}
26+
27+
// EMIT_MIR gvn_copy_aggregate.all_copy_2.GVN.diff
28+
fn all_copy_2(v: &&AllCopy) -> AllCopy {
29+
// CHECK-LABEL: fn all_copy_2(
30+
// CHECK: bb0: {
31+
// CHECK-NOT: = AllCopy { {{.*}} };
32+
// CHECK: [[V1:_.*]] = copy (*_1);
33+
// CHECK: _0 = copy (*[[V1]]);
34+
let a = v.a;
35+
let b = v.b;
36+
let c = v.c;
37+
AllCopy { a, b, c }
38+
}
39+
40+
// EMIT_MIR gvn_copy_aggregate.all_copy_move.GVN.diff
41+
fn all_copy_move(v: AllCopy) -> AllCopy {
42+
// CHECK-LABEL: fn all_copy_move(
43+
// CHECK: bb0: {
44+
// CHECK-NOT: = AllCopy { {{.*}} };
45+
// CHECK: _0 = copy _1;
46+
let a = v.a;
47+
let b = v.b;
48+
let c = v.c;
49+
AllCopy { a, b, c }
50+
}
51+
52+
// EMIT_MIR gvn_copy_aggregate.all_copy_ret_2.GVN.diff
53+
fn all_copy_ret_2(v: &AllCopy) -> (AllCopy, AllCopy) {
54+
// CHECK-LABEL: fn all_copy_ret_2(
55+
// CHECK: bb0: {
56+
// CHECK-NOT: = AllCopy { {{.*}} };
57+
// CHECK: [[V1:_.*]] = copy (*_1);
58+
// CHECK: [[V2:_.*]] = copy [[V1]];
59+
// CHECK: _0 = (copy [[V1]], copy [[V1]]);
60+
let a = v.a;
61+
let b = v.b;
62+
let c = v.c;
63+
(AllCopy { a, b, c }, AllCopy { a, b, c })
64+
}
65+
66+
struct AllCopy2 {
67+
a: i32,
68+
b: u64,
69+
c: [i8; 3],
70+
}
71+
72+
// EMIT_MIR gvn_copy_aggregate.all_copy_different_type.GVN.diff
73+
fn all_copy_different_type(v: &AllCopy) -> AllCopy2 {
74+
// CHECK-LABEL: fn all_copy_different_type(
75+
// CHECK: bb0: {
76+
// CHECK: _0 = AllCopy2 { {{.*}} };
77+
let a = v.a;
78+
let b = v.b;
79+
let c = v.c;
80+
AllCopy2 { a, b, c }
81+
}
82+
83+
struct SameType {
84+
a: i32,
85+
b: i32,
86+
}
87+
88+
// EMIT_MIR gvn_copy_aggregate.same_type_different_index.GVN.diff
89+
fn same_type_different_index(v: &SameType) -> SameType {
90+
// CHECK-LABEL: fn same_type_different_index(
91+
// CHECK: bb0: {
92+
// CHECK: _0 = SameType { {{.*}} };
93+
let a = v.b;
94+
let b = v.a;
95+
SameType { a, b }
96+
}
97+
98+
// EMIT_MIR gvn_copy_aggregate.all_copy_has_changed.GVN.diff
99+
fn all_copy_has_changed(v: &mut AllCopy) -> AllCopy {
100+
// CHECK-LABEL: fn all_copy_has_changed(
101+
// CHECK: bb0: {
102+
// CHECK: _0 = AllCopy { {{.*}} };
103+
let a = v.a;
104+
let b = v.b;
105+
let c = v.c;
106+
v.a = 1;
107+
AllCopy { a, b, c }
108+
}
109+
110+
// FIXME: This can be simplified to `Copy`.
111+
// EMIT_MIR gvn_copy_aggregate.all_copy_use_changed.GVN.diff
112+
fn all_copy_use_changed(v: &mut AllCopy) -> AllCopy {
113+
// CHECK-LABEL: fn all_copy_use_changed(
114+
// CHECK: bb0: {
115+
// CHECK-NOT: _0 = copy (*_1);
116+
// CHECK: = AllCopy { {{.*}} };
117+
let mut a = v.a;
118+
v.a = 1;
119+
a = v.a;
120+
let b = v.b;
121+
let c = v.c;
122+
AllCopy { a, b, c }
123+
}
124+
125+
// FIXME: This can be simplified to `Copy`.
126+
// EMIT_MIR gvn_copy_aggregate.all_copy_use_changed_2.GVN.diff
127+
fn all_copy_use_changed_2(v: &mut AllCopy) -> AllCopy {
128+
// CHECK-LABEL: fn all_copy_use_changed_2(
129+
// CHECK: bb0: {
130+
// CHECK-NOT: _0 = (*_1);
131+
// CHECK: = AllCopy { {{.*}} };
132+
let mut a = v.a;
133+
let b = v.b;
134+
let c = v.c;
135+
v.a = 1;
136+
a = v.a;
137+
AllCopy { a, b, c }
138+
}
139+
140+
struct NestCopy {
141+
d: i32,
142+
all_copy: AllCopy,
143+
}
144+
145+
// EMIT_MIR gvn_copy_aggregate.nest_copy.GVN.diff
146+
fn nest_copy(v: &NestCopy) -> NestCopy {
147+
// CHECK-LABEL: fn nest_copy(
148+
// CHECK: bb0: {
149+
// CHECK-NOT: = AllCopy { {{.*}} };
150+
// CHECK-NOT: = NestCopy { {{.*}} };
151+
let a = v.all_copy.a;
152+
let b = v.all_copy.b;
153+
let c = v.all_copy.c;
154+
let all_copy = AllCopy { a, b, c };
155+
let d = v.d;
156+
NestCopy { d, all_copy }
157+
}
158+
159+
enum Enum1 {
160+
A(AllCopy),
161+
B(AllCopy),
162+
}
163+
164+
// EMIT_MIR gvn_copy_aggregate.enum_identical_variant.GVN.diff
165+
fn enum_identical_variant(v: &Enum1) -> Enum1 {
166+
// CHECK-LABEL: fn enum_identical_variant(
167+
// CHECK-NOT: = AllCopy { {{.*}} };
168+
// CHECK: _0 = copy (*_1);
169+
// CHECK-NOT: = AllCopy { {{.*}} };
170+
// CHECK: _0 = copy (*_1);
171+
match v {
172+
Enum1::A(v) => {
173+
let a = v.a;
174+
let b = v.b;
175+
let c = v.c;
176+
let all_copy = AllCopy { a, b, c };
177+
Enum1::A(all_copy)
178+
}
179+
Enum1::B(v) => {
180+
let a = v.a;
181+
let b = v.b;
182+
let c = v.c;
183+
let all_copy = AllCopy { a, b, c };
184+
Enum1::B(all_copy)
185+
}
186+
}
187+
}
188+
189+
// EMIT_MIR gvn_copy_aggregate.enum_different_variant.GVN.diff
190+
fn enum_different_variant(v: &Enum1) -> Enum1 {
191+
// CHECK-LABEL: fn enum_different_variant(
192+
// CHECK-NOT: = AllCopy { {{.*}} };
193+
// CHECK: [[V1:_.*]] = copy (((*_1) as [[VARIANT1:.*]]).0: AllCopy);
194+
// CHECK: _0 = Enum1::[[VARIANT2:.*]](copy [[V1]]);
195+
// CHECK-NOT: = AllCopy { {{.*}} };
196+
// CHECK: [[V2:_.*]] = copy (((*_1) as [[VARIANT2]]).0: AllCopy);
197+
// CHECK: _0 = Enum1::[[VARIANT1]](copy [[V2]]);
198+
match v {
199+
Enum1::A(v) => {
200+
let a = v.a;
201+
let b = v.b;
202+
let c = v.c;
203+
let all_copy = AllCopy { a, b, c };
204+
Enum1::B(all_copy)
205+
}
206+
Enum1::B(v) => {
207+
let a = v.a;
208+
let b = v.b;
209+
let c = v.c;
210+
let all_copy = AllCopy { a, b, c };
211+
Enum1::A(all_copy)
212+
}
213+
}
214+
}
215+
216+
enum AlwaysSome<T> {
217+
Some(T),
218+
}
219+
220+
// Ensure that we do not access this local after `StorageDead`.
221+
// EMIT_MIR gvn_copy_aggregate.remove_storage_dead.GVN.diff
222+
fn remove_storage_dead<T>(f: fn() -> AlwaysSome<T>) -> AlwaysSome<T> {
223+
// CHECK-LABEL: fn remove_storage_dead(
224+
// CHECK: [[V1:_.*]] = copy _1() -> [return: [[BB1:bb.*]],
225+
// CHECK: [[BB1]]: {
226+
// CHECK-NOT: StorageDead([[V1]]);
227+
// CHECK: _0 = copy [[V1]];
228+
let v = {
229+
match f() {
230+
AlwaysSome::Some(v) => v,
231+
}
232+
};
233+
AlwaysSome::Some(v)
234+
}
235+
236+
// EMIT_MIR gvn_copy_aggregate.remove_storage_dead_from_index.GVN.diff
237+
#[custom_mir(dialect = "analysis")]
238+
fn remove_storage_dead_from_index(f: fn() -> usize, v: [SameType; 5]) -> SameType {
239+
// CHECK-LABEL: fn remove_storage_dead_from_index(
240+
// CHECK: [[V1:_.*]] = copy _1() -> [return: [[BB1:bb.*]],
241+
// CHECK: [[BB1]]: {
242+
// CHECK-NOT: StorageDead([[V1]]);
243+
// CHECK-NOT: = SameType { {{.*}} };
244+
// CHECK: _0 = copy _2[[[V1]]];
245+
mir! {
246+
let index: usize;
247+
let a: i32;
248+
let b: i32;
249+
{
250+
StorageLive(index);
251+
Call(index = f(), ReturnTo(bb1), UnwindUnreachable())
252+
}
253+
bb1 = {
254+
a = v[index].a;
255+
b = v[index].b;
256+
StorageDead(index);
257+
RET = SameType { a, b };
258+
Return()
259+
}
260+
}
261+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
- // MIR for `same_type_different_index` before GVN
2+
+ // MIR for `same_type_different_index` after GVN
3+
4+
fn same_type_different_index(_1: &SameType) -> SameType {
5+
debug v => _1;
6+
let mut _0: SameType;
7+
let _2: i32;
8+
let mut _4: i32;
9+
let mut _5: i32;
10+
scope 1 {
11+
debug a => _2;
12+
let _3: i32;
13+
scope 2 {
14+
debug b => _3;
15+
}
16+
}
17+
18+
bb0: {
19+
- StorageLive(_2);
20+
+ nop;
21+
_2 = copy ((*_1).1: i32);
22+
- StorageLive(_3);
23+
+ nop;
24+
_3 = copy ((*_1).0: i32);
25+
StorageLive(_4);
26+
_4 = copy _2;
27+
StorageLive(_5);
28+
_5 = copy _3;
29+
- _0 = SameType { a: move _4, b: move _5 };
30+
+ _0 = SameType { a: copy _2, b: copy _3 };
31+
StorageDead(_5);
32+
StorageDead(_4);
33+
- StorageDead(_3);
34+
- StorageDead(_2);
35+
+ nop;
36+
+ nop;
37+
return;
38+
}
39+
}
40+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// MIR for `clone_as_copy` after PreCodegen
2+
3+
fn clone_as_copy(_1: &NestCopy) -> NestCopy {
4+
debug v => _1;
5+
let mut _0: NestCopy;
6+
scope 1 (inlined <NestCopy as Clone>::clone) {
7+
debug self => _1;
8+
let _2: &AllCopy;
9+
scope 2 (inlined <AllCopy as Clone>::clone) {
10+
debug self => _2;
11+
}
12+
}
13+
14+
bb0: {
15+
StorageLive(_2);
16+
_2 = &((*_1).1: AllCopy);
17+
_0 = copy (*_1);
18+
StorageDead(_2);
19+
return;
20+
}
21+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// MIR for `enum_clone_as_copy` after PreCodegen
2+
3+
fn enum_clone_as_copy(_1: &Enum1) -> Enum1 {
4+
debug v => _1;
5+
let mut _0: Enum1;
6+
scope 1 (inlined <Enum1 as Clone>::clone) {
7+
debug self => _1;
8+
let mut _2: isize;
9+
let mut _3: &AllCopy;
10+
let mut _4: &NestCopy;
11+
scope 2 {
12+
debug __self_0 => _3;
13+
scope 6 (inlined <AllCopy as Clone>::clone) {
14+
debug self => _3;
15+
}
16+
}
17+
scope 3 {
18+
debug __self_0 => _4;
19+
scope 4 (inlined <NestCopy as Clone>::clone) {
20+
debug self => _4;
21+
let _5: &AllCopy;
22+
scope 5 (inlined <AllCopy as Clone>::clone) {
23+
debug self => _5;
24+
}
25+
}
26+
}
27+
}
28+
29+
bb0: {
30+
StorageLive(_2);
31+
StorageLive(_3);
32+
StorageLive(_4);
33+
_2 = discriminant((*_1));
34+
switchInt(move _2) -> [0: bb1, 1: bb2, otherwise: bb4];
35+
}
36+
37+
bb1: {
38+
_3 = &(((*_1) as A).0: AllCopy);
39+
_0 = copy (*_1);
40+
goto -> bb3;
41+
}
42+
43+
bb2: {
44+
_4 = &(((*_1) as B).0: NestCopy);
45+
StorageLive(_5);
46+
_5 = &((((*_1) as B).0: NestCopy).1: AllCopy);
47+
StorageDead(_5);
48+
_0 = copy (*_1);
49+
goto -> bb3;
50+
}
51+
52+
bb3: {
53+
StorageDead(_4);
54+
StorageDead(_3);
55+
StorageDead(_2);
56+
return;
57+
}
58+
59+
bb4: {
60+
unreachable;
61+
}
62+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
//@ compile-flags: -Cdebuginfo=full
2+
3+
// Check if we have transformed the nested clone to the copy in the complete pipeline.
4+
5+
#[derive(Clone)]
6+
struct AllCopy {
7+
a: i32,
8+
b: u64,
9+
c: [i8; 3],
10+
}
11+
12+
#[derive(Clone)]
13+
struct NestCopy {
14+
a: i32,
15+
b: AllCopy,
16+
c: [i8; 3],
17+
}
18+
19+
#[derive(Clone)]
20+
enum Enum1 {
21+
A(AllCopy),
22+
B(NestCopy),
23+
}
24+
25+
// EMIT_MIR clone_as_copy.clone_as_copy.PreCodegen.after.mir
26+
fn clone_as_copy(v: &NestCopy) -> NestCopy {
27+
// CHECK-LABEL: fn clone_as_copy(
28+
// CHECK-NOT: = AllCopy { {{.*}} };
29+
// CHECK-NOT: = NestCopy { {{.*}} };
30+
// CHECK: _0 = copy (*_1);
31+
// CHECK: return;
32+
v.clone()
33+
}
34+
35+
// FIXME: We can merge into exactly one assignment statement.
36+
// EMIT_MIR clone_as_copy.enum_clone_as_copy.PreCodegen.after.mir
37+
fn enum_clone_as_copy(v: &Enum1) -> Enum1 {
38+
// CHECK-LABEL: fn enum_clone_as_copy(
39+
// CHECK-NOT: = Enum1::
40+
// CHECK: _0 = copy (*_1);
41+
// CHECK: _0 = copy (*_1);
42+
v.clone()
43+
}

‎tests/mir-opt/pre-codegen/no_inlined_clone.{impl#0}-clone.PreCodegen.after.mir

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,9 @@
33
fn <impl at $DIR/no_inlined_clone.rs:9:10: 9:15>::clone(_1: &Foo) -> Foo {
44
debug self => _1;
55
let mut _0: Foo;
6-
let mut _2: i32;
76

87
bb0: {
9-
StorageLive(_2);
10-
_2 = copy ((*_1).0: i32);
11-
_0 = Foo { a: move _2 };
12-
StorageDead(_2);
8+
_0 = copy (*_1);
139
return;
1410
}
1511
}

‎tests/mir-opt/pre-codegen/try_identity.old.PreCodegen.after.mir

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,14 @@ fn old(_1: Result<T, E>) -> Result<T, E> {
1919
}
2020

2121
bb1: {
22-
_3 = move ((_1 as Ok).0: T);
23-
_0 = Result::<T, E>::Ok(copy _3);
22+
_3 = copy ((_1 as Ok).0: T);
23+
_0 = copy _1;
2424
goto -> bb3;
2525
}
2626

2727
bb2: {
28-
_4 = move ((_1 as Err).0: E);
29-
_0 = Result::<T, E>::Err(copy _4);
28+
_4 = copy ((_1 as Err).0: E);
29+
_0 = copy _1;
3030
goto -> bb3;
3131
}
3232

‎tests/mir-opt/pre-codegen/vec_deref.vec_deref_to_slice.PreCodegen.after.panic-abort.mir

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
55
let mut _0: &[u8];
66
scope 1 (inlined <Vec<u8> as Deref>::deref) {
77
debug self => _1;
8-
let mut _7: usize;
8+
let mut _6: usize;
99
scope 2 (inlined Vec::<u8>::as_ptr) {
1010
debug self => _1;
1111
let mut _2: &alloc::raw_vec::RawVec<u8>;
@@ -14,7 +14,6 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
1414
let mut _3: &alloc::raw_vec::RawVecInner;
1515
scope 4 (inlined alloc::raw_vec::RawVecInner::ptr::<u8>) {
1616
debug self => _3;
17-
let mut _6: std::ptr::NonNull<u8>;
1817
scope 5 (inlined alloc::raw_vec::RawVecInner::non_null::<u8>) {
1918
debug self => _3;
2019
let mut _4: std::ptr::NonNull<u8>;
@@ -30,28 +29,28 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
3029
}
3130
}
3231
scope 9 (inlined #[track_caller] <Unique<u8> as Into<NonNull<u8>>>::into) {
33-
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _6;
32+
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _4;
3433
debug ((self: Unique<u8>).1: std::marker::PhantomData<u8>) => const PhantomData::<u8>;
3534
scope 10 (inlined <NonNull<u8> as From<Unique<u8>>>::from) {
36-
debug ((unique: Unique<u8>).0: std::ptr::NonNull<u8>) => _6;
35+
debug ((unique: Unique<u8>).0: std::ptr::NonNull<u8>) => _4;
3736
debug ((unique: Unique<u8>).1: std::marker::PhantomData<u8>) => const PhantomData::<u8>;
3837
scope 11 (inlined Unique::<u8>::as_non_null_ptr) {
39-
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _6;
38+
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _4;
4039
debug ((self: Unique<u8>).1: std::marker::PhantomData<u8>) => const PhantomData::<u8>;
4140
}
4241
}
4342
}
4443
}
4544
scope 12 (inlined NonNull::<u8>::as_ptr) {
46-
debug self => _6;
45+
debug self => _4;
4746
}
4847
}
4948
}
5049
}
5150
scope 13 (inlined std::slice::from_raw_parts::<'_, u8>) {
5251
debug data => _5;
53-
debug len => _7;
54-
let _8: *const [u8];
52+
debug len => _6;
53+
let _7: *const [u8];
5554
scope 14 (inlined core::ub_checks::check_language_ub) {
5655
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
5756
}
@@ -62,10 +61,10 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
6261
}
6362
scope 18 (inlined slice_from_raw_parts::<u8>) {
6463
debug data => _5;
65-
debug len => _7;
64+
debug len => _6;
6665
scope 19 (inlined std::ptr::from_raw_parts::<[u8], u8>) {
6766
debug data_pointer => _5;
68-
debug metadata => _7;
67+
debug metadata => _6;
6968
}
7069
}
7170
}
@@ -76,22 +75,17 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
7675
_2 = &((*_1).0: alloc::raw_vec::RawVec<u8>);
7776
StorageLive(_3);
7877
_3 = &(((*_1).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner);
79-
StorageLive(_6);
80-
StorageLive(_4);
8178
_4 = copy (((((*_1).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner).0: std::ptr::Unique<u8>).0: std::ptr::NonNull<u8>);
8279
_5 = copy (_4.0: *const u8);
83-
_6 = NonNull::<u8> { pointer: copy _5 };
84-
StorageDead(_4);
85-
StorageDead(_6);
8680
StorageDead(_3);
8781
StorageDead(_2);
82+
StorageLive(_6);
83+
_6 = copy ((*_1).1: usize);
8884
StorageLive(_7);
89-
_7 = copy ((*_1).1: usize);
90-
StorageLive(_8);
91-
_8 = *const [u8] from (copy _5, copy _7);
92-
_0 = &(*_8);
93-
StorageDead(_8);
85+
_7 = *const [u8] from (copy _5, copy _6);
86+
_0 = &(*_7);
9487
StorageDead(_7);
88+
StorageDead(_6);
9589
return;
9690
}
9791
}

‎tests/mir-opt/pre-codegen/vec_deref.vec_deref_to_slice.PreCodegen.after.panic-unwind.mir

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
55
let mut _0: &[u8];
66
scope 1 (inlined <Vec<u8> as Deref>::deref) {
77
debug self => _1;
8-
let mut _7: usize;
8+
let mut _6: usize;
99
scope 2 (inlined Vec::<u8>::as_ptr) {
1010
debug self => _1;
1111
let mut _2: &alloc::raw_vec::RawVec<u8>;
@@ -14,7 +14,6 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
1414
let mut _3: &alloc::raw_vec::RawVecInner;
1515
scope 4 (inlined alloc::raw_vec::RawVecInner::ptr::<u8>) {
1616
debug self => _3;
17-
let mut _6: std::ptr::NonNull<u8>;
1817
scope 5 (inlined alloc::raw_vec::RawVecInner::non_null::<u8>) {
1918
debug self => _3;
2019
let mut _4: std::ptr::NonNull<u8>;
@@ -30,28 +29,28 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
3029
}
3130
}
3231
scope 9 (inlined #[track_caller] <Unique<u8> as Into<NonNull<u8>>>::into) {
33-
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _6;
32+
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _4;
3433
debug ((self: Unique<u8>).1: std::marker::PhantomData<u8>) => const PhantomData::<u8>;
3534
scope 10 (inlined <NonNull<u8> as From<Unique<u8>>>::from) {
36-
debug ((unique: Unique<u8>).0: std::ptr::NonNull<u8>) => _6;
35+
debug ((unique: Unique<u8>).0: std::ptr::NonNull<u8>) => _4;
3736
debug ((unique: Unique<u8>).1: std::marker::PhantomData<u8>) => const PhantomData::<u8>;
3837
scope 11 (inlined Unique::<u8>::as_non_null_ptr) {
39-
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _6;
38+
debug ((self: Unique<u8>).0: std::ptr::NonNull<u8>) => _4;
4039
debug ((self: Unique<u8>).1: std::marker::PhantomData<u8>) => const PhantomData::<u8>;
4140
}
4241
}
4342
}
4443
}
4544
scope 12 (inlined NonNull::<u8>::as_ptr) {
46-
debug self => _6;
45+
debug self => _4;
4746
}
4847
}
4948
}
5049
}
5150
scope 13 (inlined std::slice::from_raw_parts::<'_, u8>) {
5251
debug data => _5;
53-
debug len => _7;
54-
let _8: *const [u8];
52+
debug len => _6;
53+
let _7: *const [u8];
5554
scope 14 (inlined core::ub_checks::check_language_ub) {
5655
scope 15 (inlined core::ub_checks::check_language_ub::runtime) {
5756
}
@@ -62,10 +61,10 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
6261
}
6362
scope 18 (inlined slice_from_raw_parts::<u8>) {
6463
debug data => _5;
65-
debug len => _7;
64+
debug len => _6;
6665
scope 19 (inlined std::ptr::from_raw_parts::<[u8], u8>) {
6766
debug data_pointer => _5;
68-
debug metadata => _7;
67+
debug metadata => _6;
6968
}
7069
}
7170
}
@@ -76,22 +75,17 @@ fn vec_deref_to_slice(_1: &Vec<u8>) -> &[u8] {
7675
_2 = &((*_1).0: alloc::raw_vec::RawVec<u8>);
7776
StorageLive(_3);
7877
_3 = &(((*_1).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner);
79-
StorageLive(_6);
80-
StorageLive(_4);
8178
_4 = copy (((((*_1).0: alloc::raw_vec::RawVec<u8>).0: alloc::raw_vec::RawVecInner).0: std::ptr::Unique<u8>).0: std::ptr::NonNull<u8>);
8279
_5 = copy (_4.0: *const u8);
83-
_6 = NonNull::<u8> { pointer: copy _5 };
84-
StorageDead(_4);
85-
StorageDead(_6);
8680
StorageDead(_3);
8781
StorageDead(_2);
82+
StorageLive(_6);
83+
_6 = copy ((*_1).1: usize);
8884
StorageLive(_7);
89-
_7 = copy ((*_1).1: usize);
90-
StorageLive(_8);
91-
_8 = *const [u8] from (copy _5, copy _7);
92-
_0 = &(*_8);
93-
StorageDead(_8);
85+
_7 = *const [u8] from (copy _5, copy _6);
86+
_0 = &(*_7);
9487
StorageDead(_7);
88+
StorageDead(_6);
9589
return;
9690
}
9791
}

0 commit comments

Comments
 (0)
Please sign in to comment.