Skip to content

Commit 61646fb

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 7be3670 commit 61646fb

File tree

1 file changed

+143
-13
lines changed

1 file changed

+143
-13
lines changed

src/librustc_mir/transform/generator.rs

+143-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();
@@ -1279,11 +1271,25 @@ impl<'tcx> MirPass<'tcx> for StateTransform {
12791271

12801272
let always_live_locals = storage::AlwaysLiveLocals::new(&body);
12811273

1274+
let liveness_info =
1275+
locals_live_across_suspend_points(tcx, body, source, &always_live_locals, movable);
1276+
1277+
sanitize_witness(tcx, body, def_id, interior, &upvars, &liveness_info.saved_locals);
1278+
1279+
if tcx.sess.opts.debugging_opts.validate_mir {
1280+
let mut vis = EnsureGeneratorFieldAssignmentsNeverAlias {
1281+
assigned_local: None,
1282+
saved_locals: &liveness_info.saved_locals,
1283+
storage_conflicts: &liveness_info.storage_conflicts,
1284+
};
1285+
1286+
vis.visit_body(body);
1287+
}
1288+
12821289
// Extract locals which are live across suspension point into `layout`
12831290
// `remap` gives a mapping from local indices onto generator struct indices
12841291
// `storage_liveness` tells us which locals have live storage at suspension points
1285-
let (remap, layout, storage_liveness) =
1286-
compute_layout(tcx, source, &upvars, interior, &always_live_locals, movable, body);
1292+
let (remap, layout, storage_liveness) = compute_layout(liveness_info, body);
12871293

12881294
let can_return = can_return(tcx, body);
12891295

@@ -1334,3 +1340,127 @@ impl<'tcx> MirPass<'tcx> for StateTransform {
13341340
create_generator_resume_function(tcx, transform, source, body, can_return);
13351341
}
13361342
}
1343+
1344+
/// Looks for any assignments between locals (e.g., `_4 = _5`) that will both be converted to fields
1345+
/// in the generator state machine but whose storage does not conflict.
1346+
///
1347+
/// This would occur if the assignment is the last use of `_5` but the initial definition of `_4`
1348+
/// and we weren't extra careful to mark all locals used inside a statement as conflicting.
1349+
/// Validation needs to happen immediately before `TransformVisitor` is invoked.
1350+
///
1351+
/// This is could lead to miscompilations because the statement `_4 = _5` will be lowered to field
1352+
/// accesses into the generator state machine
1353+
struct EnsureGeneratorFieldAssignmentsNeverAlias<'a> {
1354+
saved_locals: &'a GeneratorSavedLocals,
1355+
storage_conflicts: &'a BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal>,
1356+
assigned_local: Option<GeneratorSavedLocal>,
1357+
}
1358+
1359+
impl EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
1360+
fn saved_local_for_direct_place(&self, place: Place<'_>) -> Option<GeneratorSavedLocal> {
1361+
if place.is_indirect() {
1362+
return None;
1363+
}
1364+
1365+
self.saved_locals.get(place.local)
1366+
}
1367+
1368+
fn check_assigned_place(&mut self, place: Place<'tcx>, f: impl FnOnce(&mut Self)) {
1369+
if let Some(assigned_local) = self.saved_local_for_direct_place(place) {
1370+
assert!(self.assigned_local.is_none(), "`check_assigned_local` must not recurse");
1371+
1372+
self.assigned_local = Some(assigned_local);
1373+
f(self);
1374+
self.assigned_local = None;
1375+
}
1376+
}
1377+
}
1378+
1379+
impl Visitor<'tcx> for EnsureGeneratorFieldAssignmentsNeverAlias<'_> {
1380+
fn visit_place(&mut self, place: &Place<'tcx>, context: PlaceContext, location: Location) {
1381+
let lhs = match self.assigned_local {
1382+
Some(l) => l,
1383+
None => {
1384+
// We should be visiting all places used in the MIR.
1385+
assert!(!context.is_use());
1386+
return;
1387+
}
1388+
};
1389+
1390+
let rhs = match self.saved_local_for_direct_place(*place) {
1391+
Some(l) => l,
1392+
None => return,
1393+
};
1394+
1395+
if !self.storage_conflicts.contains(lhs, rhs) {
1396+
bug!(
1397+
"Assignment between generator saved locals whose storage does not conflict: \
1398+
{:?}: {:?} = {:?}",
1399+
location,
1400+
lhs,
1401+
rhs,
1402+
);
1403+
}
1404+
}
1405+
1406+
fn visit_statement(&mut self, statement: &Statement<'tcx>, location: Location) {
1407+
match &statement.kind {
1408+
StatementKind::Assign(box (lhs, rhs)) => {
1409+
self.check_assigned_place(*lhs, |this| this.visit_rvalue(rhs, location));
1410+
}
1411+
1412+
// FIXME: Does `llvm_asm!` have any aliasing requirements?
1413+
StatementKind::LlvmInlineAsm(_) => {}
1414+
1415+
StatementKind::FakeRead(..)
1416+
| StatementKind::SetDiscriminant { .. }
1417+
| StatementKind::StorageLive(_)
1418+
| StatementKind::StorageDead(_)
1419+
| StatementKind::Retag(..)
1420+
| StatementKind::AscribeUserType(..)
1421+
| StatementKind::Nop => {}
1422+
}
1423+
}
1424+
1425+
fn visit_terminator(&mut self, terminator: &Terminator<'tcx>, location: Location) {
1426+
// Checking for aliasing in terminators is probably overkill, but until we have actual
1427+
// semantics, we should be conservative here.
1428+
match &terminator.kind {
1429+
TerminatorKind::Call {
1430+
func,
1431+
args,
1432+
destination: Some((dest, _)),
1433+
cleanup: _,
1434+
from_hir_call: _,
1435+
} => {
1436+
self.check_assigned_place(*dest, |this| {
1437+
this.visit_operand(func, location);
1438+
for arg in args {
1439+
this.visit_operand(arg, location);
1440+
}
1441+
});
1442+
}
1443+
1444+
TerminatorKind::Yield { value, resume: _, resume_arg, drop: _ } => {
1445+
self.check_assigned_place(*resume_arg, |this| this.visit_operand(value, location));
1446+
}
1447+
1448+
// FIXME: Does `asm!` have any aliasing requirements?
1449+
TerminatorKind::InlineAsm { .. } => {}
1450+
1451+
TerminatorKind::Call { .. }
1452+
| TerminatorKind::Goto { .. }
1453+
| TerminatorKind::SwitchInt { .. }
1454+
| TerminatorKind::Resume
1455+
| TerminatorKind::Abort
1456+
| TerminatorKind::Return
1457+
| TerminatorKind::Unreachable
1458+
| TerminatorKind::Drop { .. }
1459+
| TerminatorKind::DropAndReplace { .. }
1460+
| TerminatorKind::Assert { .. }
1461+
| TerminatorKind::GeneratorDrop
1462+
| TerminatorKind::FalseEdge { .. }
1463+
| TerminatorKind::FalseUnwind { .. } => {}
1464+
}
1465+
}
1466+
}

0 commit comments

Comments
 (0)