Skip to content

Commit 3d2dc6e

Browse files
committed
Auto merge of #45753 - sinkuu:mir_copyprop_arg, r=arielb1
Fix MIR CopyPropagation errneously propagating assignments to function args Compiling this code with MIR CopyPropagation activated will result in printing `5`, because CopyProp errneously propagates the assignment of `5` to all `x`: ```rust fn bar(mut x: u8) { println!("{}", x); x = 5; } fn main() { bar(123); } ``` If a local is propagated, it will result in an ICE at trans due to an use-before-def: ```rust fn dummy(x: u8) -> u8 { x } fn foo(mut x: u8) { x = dummy(x); // this will assign a local to `x` } ``` Currently CopyProp conservatively gives up if there are multiple assignments to a local, but it is not took into account that arguments are already assigned from the beginning. This PR fixes the problem by preventing propagation of assignments to function arguments.
2 parents 4b6f725 + 1142524 commit 3d2dc6e

File tree

3 files changed

+183
-5
lines changed

3 files changed

+183
-5
lines changed

src/librustc_mir/transform/copy_prop.rs

+46-4
Original file line numberDiff line numberDiff line change
@@ -69,10 +69,14 @@ impl MirPass for CopyPropagation {
6969
return;
7070
}
7171

72+
let mut def_use_analysis = DefUseAnalysis::new(mir);
7273
loop {
73-
let mut def_use_analysis = DefUseAnalysis::new(mir);
7474
def_use_analysis.analyze(mir);
7575

76+
if eliminate_self_assignments(mir, &def_use_analysis) {
77+
def_use_analysis.analyze(mir);
78+
}
79+
7680
let mut changed = false;
7781
for dest_local in mir.local_decls.indices() {
7882
debug!("Considering destination local: {:?}", dest_local);
@@ -99,9 +103,14 @@ impl MirPass for CopyPropagation {
99103
dest_local);
100104
continue
101105
}
102-
let dest_lvalue_def = dest_use_info.defs_and_uses.iter().filter(|lvalue_def| {
103-
lvalue_def.context.is_mutating_use() && !lvalue_def.context.is_drop()
104-
}).next().unwrap();
106+
// Conservatively gives up if the dest is an argument,
107+
// because there may be uses of the original argument value.
108+
if mir.local_kind(dest_local) == LocalKind::Arg {
109+
debug!(" Can't copy-propagate local: dest {:?} (argument)",
110+
dest_local);
111+
continue;
112+
}
113+
let dest_lvalue_def = dest_use_info.defs_not_including_drop().next().unwrap();
105114
location = dest_lvalue_def.location;
106115

107116
let basic_block = &mir[location.block];
@@ -151,6 +160,39 @@ impl MirPass for CopyPropagation {
151160
}
152161
}
153162

163+
fn eliminate_self_assignments<'tcx>(
164+
mir: &mut Mir<'tcx>,
165+
def_use_analysis: &DefUseAnalysis<'tcx>,
166+
) -> bool {
167+
let mut changed = false;
168+
169+
for dest_local in mir.local_decls.indices() {
170+
let dest_use_info = def_use_analysis.local_info(dest_local);
171+
172+
for def in dest_use_info.defs_not_including_drop() {
173+
let location = def.location;
174+
if let Some(stmt) = mir[location.block].statements.get(location.statement_index) {
175+
match stmt.kind {
176+
StatementKind::Assign(
177+
Lvalue::Local(local),
178+
Rvalue::Use(Operand::Consume(Lvalue::Local(src_local))),
179+
) if local == dest_local && dest_local == src_local => {}
180+
_ => {
181+
continue;
182+
}
183+
}
184+
} else {
185+
continue;
186+
}
187+
debug!("Deleting a self-assignment for {:?}", dest_local);
188+
mir.make_statement_nop(location);
189+
changed = true;
190+
}
191+
}
192+
193+
changed
194+
}
195+
154196
enum Action<'tcx> {
155197
PropagateLocalCopy(Local),
156198
PropagateConstant(Constant<'tcx>),

src/librustc_mir/util/def_use.rs

+21-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ use rustc::mir::visit::{LvalueContext, MutVisitor, Visitor};
1515
use rustc_data_structures::indexed_vec::IndexVec;
1616
use std::marker::PhantomData;
1717
use std::mem;
18+
use std::slice;
19+
use std::iter;
1820

1921
pub struct DefUseAnalysis<'tcx> {
2022
info: IndexVec<Local, Info<'tcx>>,
@@ -39,13 +41,21 @@ impl<'tcx> DefUseAnalysis<'tcx> {
3941
}
4042

4143
pub fn analyze(&mut self, mir: &Mir<'tcx>) {
44+
self.clear();
45+
4246
let mut finder = DefUseFinder {
4347
info: mem::replace(&mut self.info, IndexVec::new()),
4448
};
4549
finder.visit_mir(mir);
4650
self.info = finder.info
4751
}
4852

53+
fn clear(&mut self) {
54+
for info in &mut self.info {
55+
info.clear();
56+
}
57+
}
58+
4959
pub fn local_info(&self, local: Local) -> &Info<'tcx> {
5060
&self.info[local]
5161
}
@@ -93,14 +103,24 @@ impl<'tcx> Info<'tcx> {
93103
}
94104
}
95105

106+
fn clear(&mut self) {
107+
self.defs_and_uses.clear();
108+
}
109+
96110
pub fn def_count(&self) -> usize {
97111
self.defs_and_uses.iter().filter(|lvalue_use| lvalue_use.context.is_mutating_use()).count()
98112
}
99113

100114
pub fn def_count_not_including_drop(&self) -> usize {
115+
self.defs_not_including_drop().count()
116+
}
117+
118+
pub fn defs_not_including_drop(
119+
&self,
120+
) -> iter::Filter<slice::Iter<Use<'tcx>>, fn(&&Use<'tcx>) -> bool> {
101121
self.defs_and_uses.iter().filter(|lvalue_use| {
102122
lvalue_use.context.is_mutating_use() && !lvalue_use.context.is_drop()
103-
}).count()
123+
})
104124
}
105125

106126
pub fn use_count(&self) -> usize {
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
// Check that CopyPropagation does not propagate an assignment to a function argument
12+
// (doing so can break usages of the original argument value)
13+
14+
fn dummy(x: u8) -> u8 {
15+
x
16+
}
17+
18+
fn foo(mut x: u8) {
19+
// calling `dummy` to make an use of `x` that copyprop cannot eliminate
20+
x = dummy(x); // this will assign a local to `x`
21+
}
22+
23+
fn bar(mut x: u8) {
24+
dummy(x);
25+
x = 5;
26+
}
27+
28+
fn baz(mut x: i32) {
29+
// self-assignment to a function argument should be eliminated
30+
x = x;
31+
}
32+
33+
fn main() {
34+
// Make sure the function actually gets instantiated.
35+
foo(0);
36+
bar(0);
37+
baz(0);
38+
}
39+
40+
// END RUST SOURCE
41+
// START rustc.foo.CopyPropagation.before.mir
42+
// bb0: {
43+
// StorageLive(_2);
44+
// StorageLive(_3);
45+
// _3 = _1;
46+
// _2 = const dummy(_3) -> bb1;
47+
// }
48+
// bb1: {
49+
// StorageDead(_3);
50+
// _1 = _2;
51+
// StorageDead(_2);
52+
// _0 = ();
53+
// return;
54+
// }
55+
// END rustc.foo.CopyPropagation.before.mir
56+
// START rustc.foo.CopyPropagation.after.mir
57+
// bb0: {
58+
// StorageLive(_2);
59+
// nop;
60+
// nop;
61+
// _2 = const dummy(_1) -> bb1;
62+
// }
63+
// bb1: {
64+
// nop;
65+
// _1 = _2;
66+
// StorageDead(_2);
67+
// _0 = ();
68+
// return;
69+
// }
70+
// END rustc.foo.CopyPropagation.after.mir
71+
// START rustc.bar.CopyPropagation.before.mir
72+
// bb0: {
73+
// StorageLive(_3);
74+
// _3 = _1;
75+
// _2 = const dummy(_3) -> bb1;
76+
// }
77+
// bb1: {
78+
// StorageDead(_3);
79+
// _1 = const 5u8;
80+
// _0 = ();
81+
// return;
82+
// }
83+
// END rustc.bar.CopyPropagation.before.mir
84+
// START rustc.bar.CopyPropagation.after.mir
85+
// bb0: {
86+
// nop;
87+
// nop;
88+
// _2 = const dummy(_1) -> bb1;
89+
// }
90+
// bb1: {
91+
// nop;
92+
// _1 = const 5u8;
93+
// _0 = ();
94+
// return;
95+
// }
96+
// END rustc.bar.CopyPropagation.after.mir
97+
// START rustc.baz.CopyPropagation.before.mir
98+
// bb0: {
99+
// StorageLive(_2);
100+
// _2 = _1;
101+
// _1 = _2;
102+
// StorageDead(_2);
103+
// _0 = ();
104+
// return;
105+
// }
106+
// END rustc.baz.CopyPropagation.before.mir
107+
// START rustc.baz.CopyPropagation.after.mir
108+
// bb0: {
109+
// nop;
110+
// nop;
111+
// nop;
112+
// nop;
113+
// _0 = ();
114+
// return;
115+
// }
116+
// END rustc.baz.CopyPropagation.after.mir

0 commit comments

Comments
 (0)