Skip to content

Commit c178e64

Browse files
Look for stores between non-conflicting generator saved locals
This is to prevent the miscompilation in rust-lang#73137 from reappearing. Only runs with `-Zvalidate-mir`.
1 parent 8fc2eeb commit c178e64

File tree

1 file changed

+147
-13
lines changed

1 file changed

+147
-13
lines changed

src/librustc_mir/transform/generator.rs

+147-13
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ use rustc_hir::def_id::DefId;
6464
use rustc_hir::lang_items::{GeneratorStateLangItem, PinTypeLangItem};
6565
use rustc_index::bit_set::{BitMatrix, BitSet};
6666
use rustc_index::vec::{Idx, IndexVec};
67-
use rustc_middle::mir::visit::{MutVisitor, PlaceContext};
67+
use rustc_middle::mir::visit::{MutVisitor, PlaceContext, Visitor};
6868
use rustc_middle::mir::*;
6969
use rustc_middle::ty::subst::SubstsRef;
7070
use rustc_middle::ty::GeneratorSubsts;
@@ -744,27 +744,19 @@ fn sanitize_witness<'tcx>(
744744
}
745745

746746
fn 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

Comments
 (0)