@@ -12,8 +12,8 @@ use rustc_middle::mir::visit::{
1212use rustc_middle:: mir:: * ;
1313use rustc_middle:: ty:: print:: with_no_trimmed_paths;
1414use 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 } ;
1717use rustc_session:: lint;
1818use rustc_span:: Span ;
1919use 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+
12351319impl < ' 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
12501335impl < ' 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> {
12941382struct 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
13021433impl < ' 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 }
0 commit comments