Skip to content

Commit 31dbf74

Browse files
committed
Auto merge of #157066 - chenyukang:yukang-fix-154457-compound-assignment-read, r=<try>
Fix compound assignments in liveness check
2 parents cced03b + b591e55 commit 31dbf74

15 files changed

Lines changed: 341 additions & 309 deletions

compiler/rustc_mir_transform/src/liveness.rs

Lines changed: 173 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use rustc_middle::mir::visit::{
1212
use rustc_middle::mir::*;
1313
use rustc_middle::ty::print::with_no_trimmed_paths;
1414
use rustc_middle::ty::{self, Ty, TyCtxt};
15-
use rustc_mir_dataflow::fmt::DebugWithContext;
16-
use rustc_mir_dataflow::{Analysis, Backward, ResultsCursor};
15+
use rustc_mir_dataflow::fmt::{DebugWithAdapter, DebugWithContext};
16+
use rustc_mir_dataflow::{Analysis, Backward, JoinSemiLattice, ResultsCursor};
1717
use rustc_session::lint;
1818
use rustc_span::Span;
1919
use rustc_span::edit_distance::find_best_match_for_name;
@@ -136,7 +136,6 @@ pub(crate) fn check_liveness<'tcx>(tcx: TyCtxt<'tcx>, def_id: LocalDefId) -> Den
136136
checked_places.record_debuginfo(&body.var_debug_info);
137137

138138
let self_assignment = find_self_assignments(&checked_places, body);
139-
140139
let mut live =
141140
MaybeLivePlaces { tcx, capture_kind, checked_places: &checked_places, self_assignment }
142141
.iterate_to_fixpoint(tcx, body, None)
@@ -680,8 +679,9 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
680679

681680
for (bb, bb_data) in traversal::postorder(body) {
682681
cursor.seek_to_block_end(bb);
683-
let live = cursor.get();
684-
ever_live.union(live);
682+
let live_domain = cursor.get();
683+
ever_live.union(&live_domain.use_live);
684+
let live = live_domain.assignments();
685685

686686
let terminator = bb_data.terminator();
687687
match &terminator.kind {
@@ -718,8 +718,9 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
718718
for (statement_index, statement) in bb_data.statements.iter().enumerate().rev() {
719719
let location = Location { block: bb, statement_index };
720720
cursor.seek_before_primary_effect(location);
721-
let live = cursor.get();
722-
ever_live.union(live);
721+
let live_domain = cursor.get();
722+
ever_live.union(&live_domain.use_live);
723+
let live = live_domain.assignments();
723724
match &statement.kind {
724725
StatementKind::Assign((place, _)) => {
725726
check_place(
@@ -756,8 +757,8 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
756757
// Check liveness of function arguments on entry.
757758
{
758759
cursor.seek_to_block_start(START_BLOCK);
759-
let live = cursor.get();
760-
ever_live.union(live);
760+
let live_domain = cursor.get();
761+
ever_live.union(&live_domain.use_live);
761762

762763
// Verify that arguments and captured values are useful.
763764
for (index, place) in checked_places.iter() {
@@ -778,7 +779,7 @@ impl<'a, 'tcx> AssignmentResult<'a, 'tcx> {
778779
let access = Access {
779780
kind,
780781
location: Location::START,
781-
live: live.contains(index),
782+
live: live_domain.use_live.contains(index),
782783
is_direct: true,
783784
};
784785
assignments[index].insert(source_info, access);
@@ -1232,30 +1233,117 @@ pub struct MaybeLivePlaces<'a, 'tcx> {
12321233
self_assignment: FxHashSet<Location>,
12331234
}
12341235

1236+
#[derive(Clone, Debug, PartialEq, Eq)]
1237+
pub struct MaybeLivePlacesDomain {
1238+
/// Places whose current value may still be observed by an ordinary user-visible use.
1239+
///
1240+
/// This drives `unused_variables`. In the following example, the read of `a` performed by
1241+
/// `a += 1` is only needed to compute the new assigned value, so it does not make `a` live in
1242+
/// this domain:
1243+
///
1244+
/// ```text
1245+
/// let mut a = 0;
1246+
/// a += 1;
1247+
/// ```
1248+
///
1249+
/// If there is no later ordinary use of `a`, the final assignment can still be reported as
1250+
/// unused.
1251+
use_live: DenseBitSet<PlaceIndex>,
1252+
1253+
/// Places whose current value may still be read by a later assignment.
1254+
///
1255+
/// This drives `unused_assignments`. It differs from `use_live` for self-assignments, where a
1256+
/// later assignment reads the old value without counting as an ordinary use. For example:
1257+
///
1258+
/// ```text
1259+
/// let mut a = 0;
1260+
/// if cond {
1261+
/// a = 1;
1262+
/// } else {
1263+
/// a = 2;
1264+
/// }
1265+
/// a += 1;
1266+
/// ```
1267+
///
1268+
/// The old-value read in `a += 1` makes both branch assignments live in this domain, even
1269+
/// though it does not make them live in `use_live`.
1270+
///
1271+
/// `None` means there are no self-assignments in this body, so assignment-read liveness is the
1272+
/// same as ordinary use liveness and we avoid allocating a second bitset.
1273+
assignment_live: Option<DenseBitSet<PlaceIndex>>,
1274+
}
1275+
1276+
impl MaybeLivePlacesDomain {
1277+
fn new_empty(len: usize, track_assignments_separately: bool) -> Self {
1278+
Self {
1279+
use_live: DenseBitSet::new_empty(len),
1280+
assignment_live: track_assignments_separately.then(|| DenseBitSet::new_empty(len)),
1281+
}
1282+
}
1283+
1284+
fn assignments(&self) -> &DenseBitSet<PlaceIndex> {
1285+
// Without self-assignments, assignment-read liveness is identical to ordinary use
1286+
// liveness. Reuse `use_live` in that common case.
1287+
self.assignment_live.as_ref().unwrap_or(&self.use_live)
1288+
}
1289+
}
1290+
1291+
impl JoinSemiLattice for MaybeLivePlacesDomain {
1292+
fn join(&mut self, other: &Self) -> bool {
1293+
let use_changed = self.use_live.union(&other.use_live);
1294+
let assignment_changed = match (&mut self.assignment_live, &other.assignment_live) {
1295+
(Some(this), Some(other)) => this.union(other),
1296+
(None, None) => false,
1297+
_ => bug!("assignment-read liveness should be enabled consistently"),
1298+
};
1299+
1300+
use_changed | assignment_changed
1301+
}
1302+
}
1303+
1304+
impl DebugWithContext<MaybeLivePlaces<'_, '_>> for MaybeLivePlacesDomain {
1305+
fn fmt_with(
1306+
&self,
1307+
ctxt: &MaybeLivePlaces<'_, '_>,
1308+
f: &mut std::fmt::Formatter<'_>,
1309+
) -> std::fmt::Result {
1310+
let mut debug = f.debug_struct("MaybeLivePlacesDomain");
1311+
debug.field("used_live", &DebugWithAdapter { this: &self.use_live, ctxt });
1312+
if let Some(assign_live) = &self.assignment_live {
1313+
debug.field("assignment_live", &DebugWithAdapter { this: assign_live, ctxt });
1314+
}
1315+
debug.finish()
1316+
}
1317+
}
1318+
12351319
impl<'tcx> MaybeLivePlaces<'_, 'tcx> {
12361320
fn transfer_function<'a>(
12371321
&'a self,
1238-
trans: &'a mut DenseBitSet<PlaceIndex>,
1322+
trans: &'a mut MaybeLivePlacesDomain,
12391323
) -> TransferFunction<'a, 'tcx> {
12401324
TransferFunction {
12411325
tcx: self.tcx,
12421326
checked_places: &self.checked_places,
12431327
capture_kind: self.capture_kind,
1244-
trans,
1328+
places: trans,
12451329
self_assignment: &self.self_assignment,
1330+
mode: TransferMode::Both,
12461331
}
12471332
}
12481333
}
12491334

12501335
impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {
1251-
type Domain = DenseBitSet<PlaceIndex>;
1336+
type Domain = MaybeLivePlacesDomain;
12521337
type Direction = Backward;
12531338

12541339
const NAME: &'static str = "liveness-lint";
12551340

12561341
fn bottom_value(&self, _: &Body<'tcx>) -> Self::Domain {
12571342
// bottom = not live
1258-
DenseBitSet::new_empty(self.checked_places.len())
1343+
MaybeLivePlacesDomain::new_empty(
1344+
self.checked_places.len(),
1345+
!self.self_assignment.is_empty(),
1346+
)
12591347
}
12601348

12611349
fn initialize_start_block(&self, _: &Body<'tcx>, _: &mut Self::Domain) {
@@ -1294,9 +1382,52 @@ impl<'tcx> Analysis<'tcx> for MaybeLivePlaces<'_, 'tcx> {
12941382
struct TransferFunction<'a, 'tcx> {
12951383
tcx: TyCtxt<'tcx>,
12961384
checked_places: &'a PlaceSet<'tcx>,
1297-
trans: &'a mut DenseBitSet<PlaceIndex>,
1385+
places: &'a mut MaybeLivePlacesDomain,
12981386
capture_kind: CaptureKind,
12991387
self_assignment: &'a FxHashSet<Location>,
1388+
mode: TransferMode,
1389+
}
1390+
1391+
#[derive(Copy, Clone)]
1392+
enum TransferMode {
1393+
Used,
1394+
Assignments,
1395+
Both,
1396+
}
1397+
1398+
impl<'tcx> TransferFunction<'_, 'tcx> {
1399+
fn with_mode<R>(&mut self, mode: TransferMode, f: impl FnOnce(&mut Self) -> R) -> R {
1400+
let old_mode = self.mode;
1401+
self.mode = mode;
1402+
let result = f(self);
1403+
self.mode = old_mode;
1404+
result
1405+
}
1406+
1407+
fn update_place(&mut self, index: PlaceIndex, def_use: DefUse) {
1408+
match def_use {
1409+
DefUse::Def => {
1410+
if matches!(self.mode, TransferMode::Used | TransferMode::Both) {
1411+
self.places.use_live.remove(index);
1412+
}
1413+
if matches!(self.mode, TransferMode::Assignments | TransferMode::Both)
1414+
&& let Some(assignments) = &mut self.places.assignment_live
1415+
{
1416+
assignments.remove(index);
1417+
}
1418+
}
1419+
DefUse::Use => {
1420+
if matches!(self.mode, TransferMode::Used | TransferMode::Both) {
1421+
self.places.use_live.insert(index);
1422+
}
1423+
if matches!(self.mode, TransferMode::Assignments | TransferMode::Both)
1424+
&& let Some(assignments) = &mut self.places.assignment_live
1425+
{
1426+
assignments.insert(index);
1427+
}
1428+
}
1429+
}
1430+
}
13001431
}
13011432

13021433
impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
@@ -1312,6 +1443,14 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
13121443
StatementKind::Assign((ref dest, ref rvalue))
13131444
if self.self_assignment.contains(&location) =>
13141445
{
1446+
// A read done only to compute the next value of a self-assignment does not make the
1447+
// variable used, but it does make the previous assignment read. Apply the ordinary
1448+
// transfer to the assignment-liveness bits, then the restricted transfer to the
1449+
// user-visible "used" bits.
1450+
self.with_mode(TransferMode::Assignments, |this| {
1451+
this.visit_assign(dest, rvalue, location);
1452+
});
1453+
13151454
if let Rvalue::BinaryOp(
13161455
BinOp::AddWithOverflow | BinOp::SubWithOverflow | BinOp::MulWithOverflow,
13171456
(_, rhs),
@@ -1320,23 +1459,29 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
13201459
// We are computing the binary operation:
13211460
// - the LHS will be assigned, so we don't read it;
13221461
// - the RHS still needs to be read.
1323-
self.visit_operand(rhs, location);
1324-
self.visit_place(
1325-
dest,
1326-
PlaceContext::MutatingUse(MutatingUseContext::Store),
1327-
location,
1328-
);
1462+
self.with_mode(TransferMode::Used, |this| {
1463+
this.visit_operand(rhs, location);
1464+
this.visit_place(
1465+
dest,
1466+
PlaceContext::MutatingUse(MutatingUseContext::Store),
1467+
location,
1468+
);
1469+
});
13291470
} else if let Rvalue::BinaryOp(_, (_, rhs)) = rvalue {
13301471
// We are computing the binary operation:
13311472
// - the LHS is being updated, so we don't read it;
13321473
// - the RHS still needs to be read.
1333-
self.visit_operand(rhs, location);
1474+
self.with_mode(TransferMode::Used, |this| {
1475+
this.visit_operand(rhs, location);
1476+
});
13341477
} else {
13351478
// This is the second part of a checked self-assignment,
13361479
// we are assigning the result.
13371480
// We do not consider the write to the destination as a `def`.
13381481
// `self_assignment` must be false if the assignment is indirect.
1339-
self.visit_rvalue(rvalue, location);
1482+
self.with_mode(TransferMode::Used, |this| {
1483+
this.visit_rvalue(rvalue, location);
1484+
});
13401485
}
13411486
}
13421487
_ => self.super_statement(statement, location),
@@ -1358,7 +1503,7 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
13581503
if place.local == ty::CAPTURE_STRUCT_LOCAL
13591504
&& place.projection.last() == Some(&PlaceElem::Deref)
13601505
{
1361-
self.trans.insert(index);
1506+
self.update_place(index, DefUse::Use);
13621507
}
13631508
}
13641509
}
@@ -1408,10 +1553,10 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
14081553

14091554
match DefUse::for_place(extra_projections, context) {
14101555
Some(DefUse::Def) => {
1411-
self.trans.remove(index);
1556+
self.update_place(index, DefUse::Def);
14121557
}
14131558
Some(DefUse::Use) => {
1414-
self.trans.insert(index);
1559+
self.update_place(index, DefUse::Use);
14151560
}
14161561
None => {}
14171562
}
@@ -1425,10 +1570,10 @@ impl<'tcx> Visitor<'tcx> for TransferFunction<'_, 'tcx> {
14251570
debug_assert_eq!(_proj, &[]);
14261571
match DefUse::for_place(&[], context) {
14271572
Some(DefUse::Def) => {
1428-
self.trans.remove(index);
1573+
self.update_place(index, DefUse::Def);
14291574
}
14301575
Some(DefUse::Use) => {
1431-
self.trans.insert(index);
1576+
self.update_place(index, DefUse::Use);
14321577
}
14331578
_ => {}
14341579
}

tests/ui/closures/2229_closure_analysis/diagnostics/liveness.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ pub fn f() {
2626
// Read and written to, but never actually used.
2727
(move || {
2828
c.x += 1; //~ WARN value captured by `c.x` is never read
29-
//~| WARN value assigned to `c.x` is never read
3029
a += 1; //~ WARN value captured by `a` is never read
31-
//~| WARN value assigned to `a` is never read
3230
})();
3331

3432
(move || {

0 commit comments

Comments
 (0)