Skip to content

Commit ccac43b

Browse files
committedJun 9, 2020
Auto merge of rust-lang#73153 - ecstatic-morse:revert-71956, r=tmandry
Revert rust-lang#71956 ...since it caused unsoundness in rust-lang#73137. Also adds a reduced version of rust-lang#73137 to the test suite. The addition of the `MaybeInitializedLocals` dataflow analysis has not been reverted, but it is no longer used. Presumably there is a more targeted fix, but I'm worried that other bugs may be lurking. I'm not yet sure what the root cause of rust-lang#73137 is. This will need to get backported to beta. r? @tmandry
2 parents 5d39f1f + b6121a5 commit ccac43b

File tree

8 files changed

+414
-137
lines changed

8 files changed

+414
-137
lines changed
 

‎src/librustc_mir/dataflow/impls/borrowed_locals.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,6 @@ impl<K> GenKillAnalysis<'tcx> for MaybeBorrowedLocals<K>
9999
where
100100
K: BorrowAnalysisKind<'tcx>,
101101
{
102-
// The generator transform relies on the fact that this analysis does **not** use "before"
103-
// effects.
104-
105102
fn statement_effect(
106103
&self,
107104
trans: &mut impl GenKill<Self::Idx>,

‎src/librustc_mir/dataflow/impls/init_locals.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,6 @@ impl dataflow::AnalysisDomain<'tcx> for MaybeInitializedLocals {
3333
}
3434

3535
impl dataflow::GenKillAnalysis<'tcx> for MaybeInitializedLocals {
36-
// The generator transform relies on the fact that this analysis does **not** use "before"
37-
// effects.
38-
3936
fn statement_effect(
4037
&self,
4138
trans: &mut impl GenKill<Self::Idx>,

‎src/librustc_mir/dataflow/impls/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ pub use self::borrowed_locals::{MaybeBorrowedLocals, MaybeMutBorrowedLocals};
3030
pub use self::borrows::Borrows;
3131
pub use self::init_locals::MaybeInitializedLocals;
3232
pub use self::liveness::MaybeLiveLocals;
33-
pub use self::storage_liveness::MaybeStorageLive;
33+
pub use self::storage_liveness::{MaybeRequiresStorage, MaybeStorageLive};
3434

3535
/// `MaybeInitializedPlaces` tracks all places that might be
3636
/// initialized upon reaching a particular point in the control flow

‎src/librustc_mir/dataflow/impls/storage_liveness.rs

Lines changed: 233 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
pub use super::*;
22

33
use crate::dataflow::BottomValue;
4-
use crate::dataflow::{self, GenKill};
4+
use crate::dataflow::{self, GenKill, Results, ResultsRefCursor};
55
use crate::util::storage::AlwaysLiveLocals;
6+
use rustc_middle::mir::visit::{NonMutatingUseContext, PlaceContext, Visitor};
67
use rustc_middle::mir::*;
8+
use std::cell::RefCell;
79

810
#[derive(Clone)]
911
pub struct MaybeStorageLive {
@@ -76,3 +78,233 @@ impl BottomValue for MaybeStorageLive {
7678
/// bottom = dead
7779
const BOTTOM_VALUE: bool = false;
7880
}
81+
82+
type BorrowedLocalsResults<'a, 'tcx> = ResultsRefCursor<'a, 'a, 'tcx, MaybeBorrowedLocals>;
83+
84+
/// Dataflow analysis that determines whether each local requires storage at a
85+
/// given location; i.e. whether its storage can go away without being observed.
86+
pub struct MaybeRequiresStorage<'mir, 'tcx> {
87+
body: &'mir Body<'tcx>,
88+
borrowed_locals: RefCell<BorrowedLocalsResults<'mir, 'tcx>>,
89+
}
90+
91+
impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
92+
pub fn new(
93+
body: &'mir Body<'tcx>,
94+
borrowed_locals: &'mir Results<'tcx, MaybeBorrowedLocals>,
95+
) -> Self {
96+
MaybeRequiresStorage {
97+
body,
98+
borrowed_locals: RefCell::new(ResultsRefCursor::new(&body, borrowed_locals)),
99+
}
100+
}
101+
}
102+
103+
impl<'mir, 'tcx> dataflow::AnalysisDomain<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
104+
type Idx = Local;
105+
106+
const NAME: &'static str = "requires_storage";
107+
108+
fn bits_per_block(&self, body: &mir::Body<'tcx>) -> usize {
109+
body.local_decls.len()
110+
}
111+
112+
fn initialize_start_block(&self, body: &mir::Body<'tcx>, on_entry: &mut BitSet<Self::Idx>) {
113+
// The resume argument is live on function entry (we don't care about
114+
// the `self` argument)
115+
for arg in body.args_iter().skip(1) {
116+
on_entry.insert(arg);
117+
}
118+
}
119+
}
120+
121+
impl<'mir, 'tcx> dataflow::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'mir, 'tcx> {
122+
fn before_statement_effect(
123+
&self,
124+
trans: &mut impl GenKill<Self::Idx>,
125+
stmt: &mir::Statement<'tcx>,
126+
loc: Location,
127+
) {
128+
// If a place is borrowed in a statement, it needs storage for that statement.
129+
self.borrowed_locals.borrow().analysis().statement_effect(trans, stmt, loc);
130+
131+
match &stmt.kind {
132+
StatementKind::StorageDead(l) => trans.kill(*l),
133+
134+
// If a place is assigned to in a statement, it needs storage for that statement.
135+
StatementKind::Assign(box (place, _))
136+
| StatementKind::SetDiscriminant { box place, .. } => {
137+
trans.gen(place.local);
138+
}
139+
StatementKind::LlvmInlineAsm(asm) => {
140+
for place in &*asm.outputs {
141+
trans.gen(place.local);
142+
}
143+
}
144+
145+
// Nothing to do for these. Match exhaustively so this fails to compile when new
146+
// variants are added.
147+
StatementKind::AscribeUserType(..)
148+
| StatementKind::FakeRead(..)
149+
| StatementKind::Nop
150+
| StatementKind::Retag(..)
151+
| StatementKind::StorageLive(..) => {}
152+
}
153+
}
154+
155+
fn statement_effect(
156+
&self,
157+
trans: &mut impl GenKill<Self::Idx>,
158+
_: &mir::Statement<'tcx>,
159+
loc: Location,
160+
) {
161+
// If we move from a place then only stops needing storage *after*
162+
// that statement.
163+
self.check_for_move(trans, loc);
164+
}
165+
166+
fn before_terminator_effect(
167+
&self,
168+
trans: &mut impl GenKill<Self::Idx>,
169+
terminator: &mir::Terminator<'tcx>,
170+
loc: Location,
171+
) {
172+
// If a place is borrowed in a terminator, it needs storage for that terminator.
173+
self.borrowed_locals.borrow().analysis().terminator_effect(trans, terminator, loc);
174+
175+
match &terminator.kind {
176+
TerminatorKind::Call { destination: Some((place, _)), .. } => {
177+
trans.gen(place.local);
178+
}
179+
180+
// Note that we do *not* gen the `resume_arg` of `Yield` terminators. The reason for
181+
// that is that a `yield` will return from the function, and `resume_arg` is written
182+
// only when the generator is later resumed. Unlike `Call`, this doesn't require the
183+
// place to have storage *before* the yield, only after.
184+
TerminatorKind::Yield { .. } => {}
185+
186+
TerminatorKind::InlineAsm { operands, .. } => {
187+
for op in operands {
188+
match op {
189+
InlineAsmOperand::Out { place, .. }
190+
| InlineAsmOperand::InOut { out_place: place, .. } => {
191+
if let Some(place) = place {
192+
trans.gen(place.local);
193+
}
194+
}
195+
InlineAsmOperand::In { .. }
196+
| InlineAsmOperand::Const { .. }
197+
| InlineAsmOperand::SymFn { .. }
198+
| InlineAsmOperand::SymStatic { .. } => {}
199+
}
200+
}
201+
}
202+
203+
// Nothing to do for these. Match exhaustively so this fails to compile when new
204+
// variants are added.
205+
TerminatorKind::Call { destination: None, .. }
206+
| TerminatorKind::Abort
207+
| TerminatorKind::Assert { .. }
208+
| TerminatorKind::Drop { .. }
209+
| TerminatorKind::DropAndReplace { .. }
210+
| TerminatorKind::FalseEdge { .. }
211+
| TerminatorKind::FalseUnwind { .. }
212+
| TerminatorKind::GeneratorDrop
213+
| TerminatorKind::Goto { .. }
214+
| TerminatorKind::Resume
215+
| TerminatorKind::Return
216+
| TerminatorKind::SwitchInt { .. }
217+
| TerminatorKind::Unreachable => {}
218+
}
219+
}
220+
221+
fn terminator_effect(
222+
&self,
223+
trans: &mut impl GenKill<Self::Idx>,
224+
terminator: &mir::Terminator<'tcx>,
225+
loc: Location,
226+
) {
227+
match &terminator.kind {
228+
// For call terminators the destination requires storage for the call
229+
// and after the call returns successfully, but not after a panic.
230+
// Since `propagate_call_unwind` doesn't exist, we have to kill the
231+
// destination here, and then gen it again in `call_return_effect`.
232+
TerminatorKind::Call { destination: Some((place, _)), .. } => {
233+
trans.kill(place.local);
234+
}
235+
236+
// Nothing to do for these. Match exhaustively so this fails to compile when new
237+
// variants are added.
238+
TerminatorKind::Call { destination: None, .. }
239+
| TerminatorKind::Yield { .. }
240+
| TerminatorKind::Abort
241+
| TerminatorKind::Assert { .. }
242+
| TerminatorKind::Drop { .. }
243+
| TerminatorKind::DropAndReplace { .. }
244+
| TerminatorKind::FalseEdge { .. }
245+
| TerminatorKind::FalseUnwind { .. }
246+
| TerminatorKind::GeneratorDrop
247+
| TerminatorKind::Goto { .. }
248+
| TerminatorKind::InlineAsm { .. }
249+
| TerminatorKind::Resume
250+
| TerminatorKind::Return
251+
| TerminatorKind::SwitchInt { .. }
252+
| TerminatorKind::Unreachable => {}
253+
}
254+
255+
self.check_for_move(trans, loc);
256+
}
257+
258+
fn call_return_effect(
259+
&self,
260+
trans: &mut impl GenKill<Self::Idx>,
261+
_block: BasicBlock,
262+
_func: &mir::Operand<'tcx>,
263+
_args: &[mir::Operand<'tcx>],
264+
return_place: mir::Place<'tcx>,
265+
) {
266+
trans.gen(return_place.local);
267+
}
268+
269+
fn yield_resume_effect(
270+
&self,
271+
trans: &mut impl GenKill<Self::Idx>,
272+
_resume_block: BasicBlock,
273+
resume_place: mir::Place<'tcx>,
274+
) {
275+
trans.gen(resume_place.local);
276+
}
277+
}
278+
279+
impl<'mir, 'tcx> MaybeRequiresStorage<'mir, 'tcx> {
280+
/// Kill locals that are fully moved and have not been borrowed.
281+
fn check_for_move(&self, trans: &mut impl GenKill<Local>, loc: Location) {
282+
let mut visitor = MoveVisitor { trans, borrowed_locals: &self.borrowed_locals };
283+
visitor.visit_location(&self.body, loc);
284+
}
285+
}
286+
287+
impl<'mir, 'tcx> BottomValue for MaybeRequiresStorage<'mir, 'tcx> {
288+
/// bottom = dead
289+
const BOTTOM_VALUE: bool = false;
290+
}
291+
292+
struct MoveVisitor<'a, 'mir, 'tcx, T> {
293+
borrowed_locals: &'a RefCell<BorrowedLocalsResults<'mir, 'tcx>>,
294+
trans: &'a mut T,
295+
}
296+
297+
impl<'a, 'mir, 'tcx, T> Visitor<'tcx> for MoveVisitor<'a, 'mir, 'tcx, T>
298+
where
299+
T: GenKill<Local>,
300+
{
301+
fn visit_local(&mut self, local: &Local, context: PlaceContext, loc: Location) {
302+
if PlaceContext::NonMutatingUse(NonMutatingUseContext::Move) == context {
303+
let mut borrowed_locals = self.borrowed_locals.borrow_mut();
304+
borrowed_locals.seek_before_primary_effect(loc);
305+
if !borrowed_locals.contains(*local) {
306+
self.trans.kill(*local);
307+
}
308+
}
309+
}
310+
}

‎src/librustc_mir/transform/generator.rs

Lines changed: 136 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
//! Otherwise it drops all the values in scope at the last suspension point.
5151
5252
use crate::dataflow::impls::{
53-
MaybeBorrowedLocals, MaybeInitializedLocals, MaybeLiveLocals, MaybeStorageLive,
53+
MaybeBorrowedLocals, MaybeLiveLocals, MaybeRequiresStorage, MaybeStorageLive,
5454
};
5555
use crate::dataflow::{self, Analysis};
5656
use crate::transform::no_landing_pads::no_landing_pads;
@@ -444,80 +444,86 @@ fn locals_live_across_suspend_points(
444444
movable: bool,
445445
) -> LivenessInfo {
446446
let def_id = source.def_id();
447+
let body_ref: &Body<'_> = &body;
447448

448449
// Calculate when MIR locals have live storage. This gives us an upper bound of their
449450
// lifetimes.
450451
let mut storage_live = MaybeStorageLive::new(always_live_locals.clone())
451-
.into_engine(tcx, body, def_id)
452+
.into_engine(tcx, body_ref, def_id)
452453
.iterate_to_fixpoint()
453-
.into_results_cursor(body);
454-
455-
let mut init = MaybeInitializedLocals
456-
.into_engine(tcx, body, def_id)
457-
.iterate_to_fixpoint()
458-
.into_results_cursor(body);
459-
460-
let mut live = MaybeLiveLocals
461-
.into_engine(tcx, body, def_id)
462-
.iterate_to_fixpoint()
463-
.into_results_cursor(body);
464-
465-
let mut borrowed = MaybeBorrowedLocals::all_borrows()
466-
.into_engine(tcx, body, def_id)
454+
.into_results_cursor(body_ref);
455+
456+
// Calculate the MIR locals which have been previously
457+
// borrowed (even if they are still active).
458+
let borrowed_locals_results =
459+
MaybeBorrowedLocals::all_borrows().into_engine(tcx, body_ref, def_id).iterate_to_fixpoint();
460+
461+
let mut borrowed_locals_cursor =
462+
dataflow::ResultsCursor::new(body_ref, &borrowed_locals_results);
463+
464+
// Calculate the MIR locals that we actually need to keep storage around
465+
// for.
466+
let requires_storage_results = MaybeRequiresStorage::new(body, &borrowed_locals_results)
467+
.into_engine(tcx, body_ref, def_id)
468+
.iterate_to_fixpoint();
469+
let mut requires_storage_cursor =
470+
dataflow::ResultsCursor::new(body_ref, &requires_storage_results);
471+
472+
// Calculate the liveness of MIR locals ignoring borrows.
473+
let mut liveness = MaybeLiveLocals
474+
.into_engine(tcx, body_ref, def_id)
467475
.iterate_to_fixpoint()
468-
.into_results_cursor(body);
469-
470-
// Liveness across yield points is determined by the following boolean equation, where `live`,
471-
// `init` and `borrowed` come from dataflow and `movable` is a property of the generator.
472-
// Movable generators do not allow borrows to live across yield points, so they don't need to
473-
// store a local simply because it is borrowed.
474-
//
475-
// live_across_yield := (live & init) | (!movable & borrowed)
476-
//
477-
let mut locals_live_across_yield_point = |block| {
478-
live.seek_to_block_end(block);
479-
let mut live_locals = live.get().clone();
480-
481-
init.seek_to_block_end(block);
482-
live_locals.intersect(init.get());
483-
484-
if !movable {
485-
borrowed.seek_to_block_end(block);
486-
live_locals.union(borrowed.get());
487-
}
488-
489-
live_locals
490-
};
476+
.into_results_cursor(body_ref);
491477

492478
let mut storage_liveness_map = IndexVec::from_elem(None, body.basic_blocks());
493479
let mut live_locals_at_suspension_points = Vec::new();
494480
let mut live_locals_at_any_suspension_point = BitSet::new_empty(body.local_decls.len());
495481

496482
for (block, data) in body.basic_blocks().iter_enumerated() {
497-
if !matches!(data.terminator().kind, TerminatorKind::Yield { .. }) {
498-
continue;
499-
}
483+
if let TerminatorKind::Yield { .. } = data.terminator().kind {
484+
let loc = Location { block, statement_index: data.statements.len() };
485+
486+
liveness.seek_to_block_end(block);
487+
let mut live_locals = liveness.get().clone();
488+
489+
if !movable {
490+
// The `liveness` variable contains the liveness of MIR locals ignoring borrows.
491+
// This is correct for movable generators since borrows cannot live across
492+
// suspension points. However for immovable generators we need to account for
493+
// borrows, so we conseratively assume that all borrowed locals are live until
494+
// we find a StorageDead statement referencing the locals.
495+
// To do this we just union our `liveness` result with `borrowed_locals`, which
496+
// contains all the locals which has been borrowed before this suspension point.
497+
// If a borrow is converted to a raw reference, we must also assume that it lives
498+
// forever. Note that the final liveness is still bounded by the storage liveness
499+
// of the local, which happens using the `intersect` operation below.
500+
borrowed_locals_cursor.seek_before_primary_effect(loc);
501+
live_locals.union(borrowed_locals_cursor.get());
502+
}
500503

501-
// Store the storage liveness for later use so we can restore the state
502-
// after a suspension point
503-
storage_live.seek_to_block_end(block);
504-
storage_liveness_map[block] = Some(storage_live.get().clone());
504+
// Store the storage liveness for later use so we can restore the state
505+
// after a suspension point
506+
storage_live.seek_before_primary_effect(loc);
507+
storage_liveness_map[block] = Some(storage_live.get().clone());
505508

506-
let mut live_locals = locals_live_across_yield_point(block);
509+
// Locals live are live at this point only if they are used across
510+
// suspension points (the `liveness` variable)
511+
// and their storage is required (the `storage_required` variable)
512+
requires_storage_cursor.seek_before_primary_effect(loc);
513+
live_locals.intersect(requires_storage_cursor.get());
507514

508-
// The combination of `MaybeInitializedLocals` and `MaybeBorrowedLocals` should be strictly
509-
// more precise than `MaybeStorageLive` because they handle `StorageDead` themselves. This
510-
// assumes that the MIR forbids locals from being initialized/borrowed before reaching
511-
// `StorageLive`.
512-
debug_assert!(storage_live.get().superset(&live_locals));
515+
// The generator argument is ignored.
516+
live_locals.remove(SELF_ARG);
513517

514-
// Ignore the generator's `self` argument since it is handled seperately.
515-
live_locals.remove(SELF_ARG);
516-
debug!("block = {:?}, live_locals = {:?}", block, live_locals);
517-
live_locals_at_any_suspension_point.union(&live_locals);
518-
live_locals_at_suspension_points.push(live_locals);
519-
}
518+
debug!("loc = {:?}, live_locals = {:?}", loc, live_locals);
520519

520+
// Add the locals live at this suspension point to the set of locals which live across
521+
// any suspension points
522+
live_locals_at_any_suspension_point.union(&live_locals);
523+
524+
live_locals_at_suspension_points.push(live_locals);
525+
}
526+
}
521527
debug!("live_locals_anywhere = {:?}", live_locals_at_any_suspension_point);
522528

523529
// Renumber our liveness_map bitsets to include only the locals we are
@@ -528,11 +534,10 @@ fn locals_live_across_suspend_points(
528534
.collect();
529535

530536
let storage_conflicts = compute_storage_conflicts(
531-
body,
537+
body_ref,
532538
&live_locals_at_any_suspension_point,
533539
always_live_locals.clone(),
534-
init,
535-
borrowed,
540+
requires_storage_results,
536541
);
537542

538543
LivenessInfo {
@@ -564,37 +569,6 @@ fn renumber_bitset(
564569
out
565570
}
566571

567-
/// Record conflicts between locals at the current dataflow cursor positions.
568-
///
569-
/// You need to seek the cursors before calling this function.
570-
fn record_conflicts_at_curr_loc(
571-
local_conflicts: &mut BitMatrix<Local, Local>,
572-
init: &dataflow::ResultsCursor<'mir, 'tcx, MaybeInitializedLocals>,
573-
borrowed: &dataflow::ResultsCursor<'mir, 'tcx, MaybeBorrowedLocals>,
574-
) {
575-
// A local requires storage if it is initialized or borrowed. For now, a local
576-
// becomes uninitialized if it is moved from, but is still considered "borrowed".
577-
//
578-
// requires_storage := init | borrowed
579-
//
580-
// Just like when determining what locals are live at yield points, there is no need
581-
// to look at storage liveness here, since `init | borrowed` is strictly more precise.
582-
//
583-
// FIXME: This function is called in a loop, so it might be better to pass in a temporary
584-
// bitset rather than cloning here.
585-
let mut requires_storage = init.get().clone();
586-
requires_storage.union(borrowed.get());
587-
588-
for local in requires_storage.iter() {
589-
local_conflicts.union_row_with(&requires_storage, local);
590-
}
591-
592-
// `>1` because the `self` argument always requires storage.
593-
if requires_storage.count() > 1 {
594-
trace!("requires_storage={:?}", requires_storage);
595-
}
596-
}
597-
598572
/// For every saved local, looks for which locals are StorageLive at the same
599573
/// time. Generates a bitset for every local of all the other locals that may be
600574
/// StorageLive simultaneously with that local. This is used in the layout
@@ -603,45 +577,30 @@ fn compute_storage_conflicts(
603577
body: &'mir Body<'tcx>,
604578
stored_locals: &BitSet<Local>,
605579
always_live_locals: storage::AlwaysLiveLocals,
606-
mut init: dataflow::ResultsCursor<'mir, 'tcx, MaybeInitializedLocals>,
607-
mut borrowed: dataflow::ResultsCursor<'mir, 'tcx, MaybeBorrowedLocals>,
580+
requires_storage: dataflow::Results<'tcx, MaybeRequiresStorage<'mir, 'tcx>>,
608581
) -> BitMatrix<GeneratorSavedLocal, GeneratorSavedLocal> {
609-
debug!("compute_storage_conflicts({:?})", body.span);
610582
assert_eq!(body.local_decls.len(), stored_locals.domain_size());
611583

612-
// Locals that are always live conflict with all other locals.
613-
//
614-
// FIXME: Why do we need to handle locals without `Storage{Live,Dead}` specially here?
615-
// Shouldn't it be enough to know whether they are initialized?
616-
let always_live_locals = always_live_locals.into_inner();
617-
let mut local_conflicts = BitMatrix::from_row_n(&always_live_locals, body.local_decls.len());
618-
619-
// Visit every reachable statement and terminator. The exact order does not matter. When two
620-
// locals are live at the same point in time, add an entry in the conflict matrix.
621-
for (block, data) in traversal::preorder(body) {
622-
// Ignore unreachable blocks.
623-
if data.terminator().kind == TerminatorKind::Unreachable {
624-
continue;
625-
}
584+
debug!("compute_storage_conflicts({:?})", body.span);
585+
debug!("always_live = {:?}", always_live_locals);
626586

627-
// Observe the dataflow state *before* all possible locations (statement or terminator) in
628-
// each basic block...
629-
for statement_index in 0..=data.statements.len() {
630-
let loc = Location { block, statement_index };
631-
trace!("record conflicts at {:?}", loc);
632-
init.seek_before_primary_effect(loc);
633-
borrowed.seek_before_primary_effect(loc);
634-
record_conflicts_at_curr_loc(&mut local_conflicts, &init, &borrowed);
635-
}
587+
// Locals that are always live or ones that need to be stored across
588+
// suspension points are not eligible for overlap.
589+
let mut ineligible_locals = always_live_locals.into_inner();
590+
ineligible_locals.intersect(stored_locals);
636591

637-
// ...and then observe the state *after* the terminator effect is applied. As long as
638-
// neither `init` nor `borrowed` has a "before" effect, we will observe all possible
639-
// dataflow states here or in the loop above.
640-
trace!("record conflicts at end of {:?}", block);
641-
init.seek_to_block_end(block);
642-
borrowed.seek_to_block_end(block);
643-
record_conflicts_at_curr_loc(&mut local_conflicts, &init, &borrowed);
644-
}
592+
// Compute the storage conflicts for all eligible locals.
593+
let mut visitor = StorageConflictVisitor {
594+
body,
595+
stored_locals: &stored_locals,
596+
local_conflicts: BitMatrix::from_row_n(&ineligible_locals, body.local_decls.len()),
597+
};
598+
599+
// Visit only reachable basic blocks. The exact order is not important.
600+
let reachable_blocks = traversal::preorder(body).map(|(bb, _)| bb);
601+
requires_storage.visit_with(body, reachable_blocks, &mut visitor);
602+
603+
let local_conflicts = visitor.local_conflicts;
645604

646605
// Compress the matrix using only stored locals (Local -> GeneratorSavedLocal).
647606
//
@@ -653,7 +612,7 @@ fn compute_storage_conflicts(
653612
let mut storage_conflicts = BitMatrix::new(stored_locals.count(), stored_locals.count());
654613
for (idx_a, local_a) in stored_locals.iter().enumerate() {
655614
let saved_local_a = GeneratorSavedLocal::new(idx_a);
656-
if always_live_locals.contains(local_a) {
615+
if ineligible_locals.contains(local_a) {
657616
// Conflicts with everything.
658617
storage_conflicts.insert_all_into_row(saved_local_a);
659618
} else {
@@ -669,6 +628,56 @@ fn compute_storage_conflicts(
669628
storage_conflicts
670629
}
671630

631+
struct StorageConflictVisitor<'mir, 'tcx, 's> {
632+
body: &'mir Body<'tcx>,
633+
stored_locals: &'s BitSet<Local>,
634+
// FIXME(tmandry): Consider using sparse bitsets here once we have good
635+
// benchmarks for generators.
636+
local_conflicts: BitMatrix<Local, Local>,
637+
}
638+
639+
impl dataflow::ResultsVisitor<'mir, 'tcx> for StorageConflictVisitor<'mir, 'tcx, '_> {
640+
type FlowState = BitSet<Local>;
641+
642+
fn visit_statement_before_primary_effect(
643+
&mut self,
644+
state: &Self::FlowState,
645+
_statement: &'mir Statement<'tcx>,
646+
loc: Location,
647+
) {
648+
self.apply_state(state, loc);
649+
}
650+
651+
fn visit_terminator_before_primary_effect(
652+
&mut self,
653+
state: &Self::FlowState,
654+
_terminator: &'mir Terminator<'tcx>,
655+
loc: Location,
656+
) {
657+
self.apply_state(state, loc);
658+
}
659+
}
660+
661+
impl<'body, 'tcx, 's> StorageConflictVisitor<'body, 'tcx, 's> {
662+
fn apply_state(&mut self, flow_state: &BitSet<Local>, loc: Location) {
663+
// Ignore unreachable blocks.
664+
if self.body.basic_blocks()[loc.block].terminator().kind == TerminatorKind::Unreachable {
665+
return;
666+
}
667+
668+
let mut eligible_storage_live = flow_state.clone();
669+
eligible_storage_live.intersect(&self.stored_locals);
670+
671+
for local in eligible_storage_live.iter() {
672+
self.local_conflicts.union_row_with(&eligible_storage_live, local);
673+
}
674+
675+
if eligible_storage_live.count() > 1 {
676+
trace!("at {:?}, eligible_storage_live={:?}", loc, eligible_storage_live);
677+
}
678+
}
679+
}
680+
672681
/// Validates the typeck view of the generator against the actual set of types retained between
673682
/// yield points.
674683
fn sanitize_witness<'tcx>(

‎src/test/ui/async-await/async-fn-size-moved-locals.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,5 +114,5 @@ fn main() {
114114
assert_eq!(1026, std::mem::size_of_val(&single_with_noop()));
115115
assert_eq!(3078, std::mem::size_of_val(&joined()));
116116
assert_eq!(3079, std::mem::size_of_val(&joined_with_noop()));
117-
assert_eq!(6157, std::mem::size_of_val(&mixed_sizes()));
117+
assert_eq!(7181, std::mem::size_of_val(&mixed_sizes()));
118118
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Regression test for <https://github.com/rust-lang/rust/issues/73137>
2+
3+
// run-pass
4+
// edition:2018
5+
6+
#![allow(dead_code)]
7+
#![feature(wake_trait)]
8+
use std::future::Future;
9+
use std::task::{Waker, Wake, Context};
10+
use std::sync::Arc;
11+
12+
struct DummyWaker;
13+
impl Wake for DummyWaker {
14+
fn wake(self: Arc<Self>) {}
15+
}
16+
17+
struct Foo {
18+
a: usize,
19+
b: &'static u32,
20+
}
21+
22+
#[inline(never)]
23+
fn nop<T>(_: T) {}
24+
25+
fn main() {
26+
let mut fut = Box::pin(async {
27+
let action = Foo {
28+
b: &42,
29+
a: async { 0 }.await,
30+
};
31+
32+
// An error in the generator transform caused `b` to be overwritten with `a` when `b` was
33+
// borrowed.
34+
nop(&action.b);
35+
assert_ne!(0usize, unsafe { std::mem::transmute(action.b) });
36+
37+
async {}.await;
38+
});
39+
let waker = Waker::from(Arc::new(DummyWaker));
40+
let mut cx = Context::from_waker(&waker);
41+
let _ = fut.as_mut().poll(&mut cx);
42+
}

‎src/test/ui/generator/size-moved-locals.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,6 @@ fn overlap_x_and_y() -> impl Generator<Yield = (), Return = ()> {
7272
fn main() {
7373
assert_eq!(1025, std::mem::size_of_val(&move_before_yield()));
7474
assert_eq!(1026, std::mem::size_of_val(&move_before_yield_with_noop()));
75-
assert_eq!(1027, std::mem::size_of_val(&overlap_move_points()));
75+
assert_eq!(2051, std::mem::size_of_val(&overlap_move_points()));
7676
assert_eq!(1026, std::mem::size_of_val(&overlap_x_and_y()));
7777
}

0 commit comments

Comments
 (0)
Please sign in to comment.