@@ -64,7 +64,7 @@ use rustc_hir::def_id::DefId;
6464use rustc_hir:: lang_items:: { GeneratorStateLangItem , PinTypeLangItem } ;
6565use rustc_index:: bit_set:: { BitMatrix , BitSet } ;
6666use rustc_index:: vec:: { Idx , IndexVec } ;
67- use rustc_middle:: mir:: visit:: { MutVisitor , PlaceContext } ;
67+ use rustc_middle:: mir:: visit:: { MutVisitor , PlaceContext , Visitor } ;
6868use rustc_middle:: mir:: * ;
6969use rustc_middle:: ty:: subst:: SubstsRef ;
7070use rustc_middle:: ty:: GeneratorSubsts ;
@@ -744,27 +744,19 @@ fn sanitize_witness<'tcx>(
744744}
745745
746746fn compute_layout < ' tcx > (
747- tcx : TyCtxt < ' tcx > ,
748- source : MirSource < ' tcx > ,
749- upvars : & Vec < Ty < ' tcx > > ,
750- interior : Ty < ' tcx > ,
751- always_live_locals : & storage:: AlwaysLiveLocals ,
752- movable : bool ,
747+ liveness : LivenessInfo ,
753748 body : & mut Body < ' tcx > ,
754749) -> (
755750 FxHashMap < Local , ( Ty < ' tcx > , VariantIdx , usize ) > ,
756751 GeneratorLayout < ' tcx > ,
757752 IndexVec < BasicBlock , Option < BitSet < Local > > > ,
758753) {
759- // Use a liveness analysis to compute locals which are live across a suspension point
760754 let LivenessInfo {
761755 saved_locals,
762756 live_locals_at_suspension_points,
763757 storage_conflicts,
764758 storage_liveness,
765- } = locals_live_across_suspend_points ( tcx, body, source, always_live_locals, movable) ;
766-
767- sanitize_witness ( tcx, body, source. def_id ( ) , interior, upvars, & saved_locals) ;
759+ } = liveness;
768760
769761 // Gather live local types and their indices.
770762 let mut locals = IndexVec :: < GeneratorSavedLocal , _ > :: new ( ) ;
@@ -1280,11 +1272,25 @@ impl<'tcx> MirPass<'tcx> for StateTransform {
12801272
12811273 let always_live_locals = storage:: AlwaysLiveLocals :: new ( & body) ;
12821274
1275+ let liveness_info =
1276+ locals_live_across_suspend_points ( tcx, body, source, & always_live_locals, movable) ;
1277+
1278+ sanitize_witness ( tcx, body, def_id, interior, & upvars, & liveness_info. saved_locals ) ;
1279+
1280+ if tcx. sess . opts . debugging_opts . validate_mir {
1281+ let mut vis = EnsureGeneratorFieldAssignmentsNeverAlias {
1282+ assigned_local : None ,
1283+ saved_locals : & liveness_info. saved_locals ,
1284+ storage_conflicts : & liveness_info. storage_conflicts ,
1285+ } ;
1286+
1287+ vis. visit_body ( body) ;
1288+ }
1289+
12831290 // Extract locals which are live across suspension point into `layout`
12841291 // `remap` gives a mapping from local indices onto generator struct indices
12851292 // `storage_liveness` tells us which locals have live storage at suspension points
1286- let ( remap, layout, storage_liveness) =
1287- compute_layout ( tcx, source, & upvars, interior, & always_live_locals, movable, body) ;
1293+ let ( remap, layout, storage_liveness) = compute_layout ( liveness_info, body) ;
12881294
12891295 let can_return = can_return ( tcx, body) ;
12901296
@@ -1335,3 +1341,131 @@ impl<'tcx> MirPass<'tcx> for StateTransform {
13351341 create_generator_resume_function ( tcx, transform, source, body, can_return) ;
13361342 }
13371343}
1344+
1345+ /// Looks for any assignments between locals (e.g., `_4 = _5`) that will both be converted to fields
1346+ /// in the generator state machine but whose storage does not conflict.
1347+ ///
1348+ /// Validation needs to happen immediately *before* `TransformVisitor` is invoked, not after.
1349+ ///
1350+ /// This condition would arise when the assignment is the last use of `_5` but the initial
1351+ /// definition of `_4` if we weren't extra careful to mark all locals used inside a statement as
1352+ /// conflicting. Non-conflicting generator saved locals may be stored at the same location within
1353+ /// the generator state machine, which would result in ill-formed MIR: the left-hand and right-hand
1354+ /// sides of an assignment may not alias. This caused a miscompilation in [#73137].
1355+ ///
1356+ /// [#73137]: https://github.com/rust-lang/rust/issues/73137
1357+ struct EnsureGeneratorFieldAssignmentsNeverAlias < ' a > {
1358+ saved_locals : & ' a GeneratorSavedLocals ,
1359+ storage_conflicts : & ' a BitMatrix < GeneratorSavedLocal , GeneratorSavedLocal > ,
1360+ assigned_local : Option < GeneratorSavedLocal > ,
1361+ }
1362+
1363+ impl EnsureGeneratorFieldAssignmentsNeverAlias < ' _ > {
1364+ fn saved_local_for_direct_place ( & self , place : Place < ' _ > ) -> Option < GeneratorSavedLocal > {
1365+ if place. is_indirect ( ) {
1366+ return None ;
1367+ }
1368+
1369+ self . saved_locals . get ( place. local )
1370+ }
1371+
1372+ fn check_assigned_place ( & mut self , place : Place < ' tcx > , f : impl FnOnce ( & mut Self ) ) {
1373+ if let Some ( assigned_local) = self . saved_local_for_direct_place ( place) {
1374+ assert ! ( self . assigned_local. is_none( ) , "`check_assigned_local` must not recurse" ) ;
1375+
1376+ self . assigned_local = Some ( assigned_local) ;
1377+ f ( self ) ;
1378+ self . assigned_local = None ;
1379+ }
1380+ }
1381+ }
1382+
1383+ impl Visitor < ' tcx > for EnsureGeneratorFieldAssignmentsNeverAlias < ' _ > {
1384+ fn visit_place ( & mut self , place : & Place < ' tcx > , context : PlaceContext , location : Location ) {
1385+ let lhs = match self . assigned_local {
1386+ Some ( l) => l,
1387+ None => {
1388+ // We should be visiting all places used in the MIR.
1389+ assert ! ( !context. is_use( ) ) ;
1390+ return ;
1391+ }
1392+ } ;
1393+
1394+ let rhs = match self . saved_local_for_direct_place ( * place) {
1395+ Some ( l) => l,
1396+ None => return ,
1397+ } ;
1398+
1399+ if !self . storage_conflicts . contains ( lhs, rhs) {
1400+ bug ! (
1401+ "Assignment between generator saved locals whose storage does not conflict: \
1402+ {:?}: {:?} = {:?}",
1403+ location,
1404+ lhs,
1405+ rhs,
1406+ ) ;
1407+ }
1408+ }
1409+
1410+ fn visit_statement ( & mut self , statement : & Statement < ' tcx > , location : Location ) {
1411+ match & statement. kind {
1412+ StatementKind :: Assign ( box ( lhs, rhs) ) => {
1413+ self . check_assigned_place ( * lhs, |this| this. visit_rvalue ( rhs, location) ) ;
1414+ }
1415+
1416+ // FIXME: Does `llvm_asm!` have any aliasing requirements?
1417+ StatementKind :: LlvmInlineAsm ( _) => { }
1418+
1419+ StatementKind :: FakeRead ( ..)
1420+ | StatementKind :: SetDiscriminant { .. }
1421+ | StatementKind :: StorageLive ( _)
1422+ | StatementKind :: StorageDead ( _)
1423+ | StatementKind :: Retag ( ..)
1424+ | StatementKind :: AscribeUserType ( ..)
1425+ | StatementKind :: Nop => { }
1426+ }
1427+ }
1428+
1429+ fn visit_terminator ( & mut self , terminator : & Terminator < ' tcx > , location : Location ) {
1430+ // Checking for aliasing in terminators is probably overkill, but until we have actual
1431+ // semantics, we should be conservative here.
1432+ match & terminator. kind {
1433+ TerminatorKind :: Call {
1434+ func,
1435+ args,
1436+ destination : Some ( ( dest, _) ) ,
1437+ cleanup : _,
1438+ from_hir_call : _,
1439+ fn_span : _,
1440+ } => {
1441+ self . check_assigned_place ( * dest, |this| {
1442+ this. visit_operand ( func, location) ;
1443+ for arg in args {
1444+ this. visit_operand ( arg, location) ;
1445+ }
1446+ } ) ;
1447+ }
1448+
1449+ TerminatorKind :: Yield { value, resume : _, resume_arg, drop : _ } => {
1450+ self . check_assigned_place ( * resume_arg, |this| this. visit_operand ( value, location) ) ;
1451+ }
1452+
1453+ // FIXME: Does `asm!` have any aliasing requirements?
1454+ TerminatorKind :: InlineAsm { .. } => { }
1455+
1456+ TerminatorKind :: Call { .. }
1457+ | TerminatorKind :: Goto { .. }
1458+ | TerminatorKind :: SwitchInt { .. }
1459+ | TerminatorKind :: Resume
1460+ | TerminatorKind :: Abort
1461+ | TerminatorKind :: Return
1462+ | TerminatorKind :: Unreachable
1463+ | TerminatorKind :: Drop { .. }
1464+ | TerminatorKind :: DropAndReplace { .. }
1465+ | TerminatorKind :: Assert { .. }
1466+ | TerminatorKind :: GeneratorDrop
1467+ | TerminatorKind :: FalseEdge { .. }
1468+ | TerminatorKind :: FalseUnwind { .. } => { }
1469+ }
1470+ }
1471+ }
0 commit comments