Skip to content

Commit 97e58c0

Browse files
committed
Auto merge of #64584 - nikomatsakis:issue-64477-generator-capture-types, r=eddyb
record fewer adjustment types in generator witnesses, avoid spurious drops in MIR construction Don't record all intermediate adjustment types -- That's way more than is needed, and winds up recording types that will never appear in MIR. Note: I'm like 90% sure that this logic is correct, but this stuff is subtle and can be hard to keep straight. However, the risk of this PR is fairly low -- if we miss types here, I believe the most common outcome is an ICE. This fixes the original issue cited by #64477, but I'm leaving the issue open for now since there may be other cases we can detect and improve in a targeted way. r? @Zoxc
2 parents 9ad1e7c + 77fd0a7 commit 97e58c0

File tree

10 files changed

+260
-55
lines changed

10 files changed

+260
-55
lines changed

src/librustc/mir/mod.rs

+9
Original file line numberDiff line numberDiff line change
@@ -1917,6 +1917,15 @@ impl<'tcx> Place<'tcx> {
19171917
}
19181918
}
19191919

1920+
/// If this place represents a local variable like `_X` with no
1921+
/// projections, return `Some(_X)`.
1922+
pub fn as_local(&self) -> Option<Local> {
1923+
match self {
1924+
Place { projection: box [], base: PlaceBase::Local(l) } => Some(*l),
1925+
_ => None,
1926+
}
1927+
}
1928+
19201929
pub fn as_ref(&self) -> PlaceRef<'_, 'tcx> {
19211930
PlaceRef {
19221931
base: &self.base,

src/librustc_mir/build/expr/into.rs

+3
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
244244

245245
let success = this.cfg.start_new_block();
246246
let cleanup = this.diverge_cleanup();
247+
248+
this.record_operands_moved(&args);
249+
247250
this.cfg.terminate(
248251
block,
249252
source_info,

src/librustc_mir/build/scope.rs

+110-25
Original file line numberDiff line numberDiff line change
@@ -104,25 +104,14 @@ struct Scope {
104104
/// the span of that region_scope
105105
region_scope_span: Span,
106106

107-
/// Whether there's anything to do for the cleanup path, that is,
108-
/// when unwinding through this scope. This includes destructors,
109-
/// but not StorageDead statements, which don't get emitted at all
110-
/// for unwinding, for several reasons:
111-
/// * clang doesn't emit llvm.lifetime.end for C++ unwinding
112-
/// * LLVM's memory dependency analysis can't handle it atm
113-
/// * polluting the cleanup MIR with StorageDead creates
114-
/// landing pads even though there's no actual destructors
115-
/// * freeing up stack space has no effect during unwinding
116-
/// Note that for generators we do emit StorageDeads, for the
117-
/// use of optimizations in the MIR generator transform.
118-
needs_cleanup: bool,
119-
120107
/// set of places to drop when exiting this scope. This starts
121108
/// out empty but grows as variables are declared during the
122109
/// building process. This is a stack, so we always drop from the
123110
/// end of the vector (top of the stack) first.
124111
drops: Vec<DropData>,
125112

113+
moved_locals: Vec<Local>,
114+
126115
/// The cache for drop chain on “normal” exit into a particular BasicBlock.
127116
cached_exits: FxHashMap<(BasicBlock, region::Scope), BasicBlock>,
128117

@@ -172,7 +161,7 @@ struct CachedBlock {
172161
generator_drop: Option<BasicBlock>,
173162
}
174163

175-
#[derive(Debug)]
164+
#[derive(Debug, PartialEq, Eq)]
176165
pub(crate) enum DropKind {
177166
Value,
178167
Storage,
@@ -202,8 +191,7 @@ pub enum BreakableTarget {
202191

203192
impl CachedBlock {
204193
fn invalidate(&mut self) {
205-
self.generator_drop = None;
206-
self.unwind = None;
194+
*self = CachedBlock::default();
207195
}
208196

209197
fn get(&self, generator_drop: bool) -> Option<BasicBlock> {
@@ -261,6 +249,25 @@ impl Scope {
261249
scope: self.source_scope
262250
}
263251
}
252+
253+
254+
/// Whether there's anything to do for the cleanup path, that is,
255+
/// when unwinding through this scope. This includes destructors,
256+
/// but not StorageDead statements, which don't get emitted at all
257+
/// for unwinding, for several reasons:
258+
/// * clang doesn't emit llvm.lifetime.end for C++ unwinding
259+
/// * LLVM's memory dependency analysis can't handle it atm
260+
/// * polluting the cleanup MIR with StorageDead creates
261+
/// landing pads even though there's no actual destructors
262+
/// * freeing up stack space has no effect during unwinding
263+
/// Note that for generators we do emit StorageDeads, for the
264+
/// use of optimizations in the MIR generator transform.
265+
fn needs_cleanup(&self) -> bool {
266+
self.drops.iter().any(|drop| match drop.kind {
267+
DropKind::Value => true,
268+
DropKind::Storage => false,
269+
})
270+
}
264271
}
265272

266273
impl<'tcx> Scopes<'tcx> {
@@ -274,8 +281,8 @@ impl<'tcx> Scopes<'tcx> {
274281
source_scope: vis_scope,
275282
region_scope: region_scope.0,
276283
region_scope_span: region_scope.1.span,
277-
needs_cleanup: false,
278284
drops: vec![],
285+
moved_locals: vec![],
279286
cached_generator_drop: None,
280287
cached_exits: Default::default(),
281288
cached_unwind: CachedBlock::default(),
@@ -295,7 +302,7 @@ impl<'tcx> Scopes<'tcx> {
295302

296303
fn may_panic(&self, scope_count: usize) -> bool {
297304
let len = self.len();
298-
self.scopes[(len - scope_count)..].iter().any(|s| s.needs_cleanup)
305+
self.scopes[(len - scope_count)..].iter().any(|s| s.needs_cleanup())
299306
}
300307

301308
/// Finds the breakable scope for a given label. This is used for
@@ -480,7 +487,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
480487
block,
481488
unwind_to,
482489
self.arg_count,
483-
false,
490+
false, // not generator
491+
false, // not unwind path
484492
));
485493

486494
block.unit()
@@ -572,7 +580,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
572580
block,
573581
unwind_to,
574582
self.arg_count,
575-
false,
583+
false, // not generator
584+
false, // not unwind path
576585
));
577586

578587
scope = next_scope;
@@ -622,7 +631,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
622631
block,
623632
unwind_to,
624633
self.arg_count,
625-
true,
634+
true, // is generator
635+
true, // is cached path
626636
));
627637
}
628638

@@ -801,10 +811,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
801811
// cache of outer scope stays intact.
802812
scope.invalidate_cache(!needs_drop, self.is_generator, this_scope);
803813
if this_scope {
804-
if let DropKind::Value = drop_kind {
805-
scope.needs_cleanup = true;
806-
}
807-
808814
let region_scope_span = region_scope.span(self.hir.tcx(),
809815
&self.hir.region_scope_tree);
810816
// Attribute scope exit drops to scope's closing brace.
@@ -822,6 +828,75 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
822828
span_bug!(span, "region scope {:?} not in scope to drop {:?}", region_scope, local);
823829
}
824830

831+
/// Indicates that the "local operand" stored in `local` is
832+
/// *moved* at some point during execution (see `local_scope` for
833+
/// more information about what a "local operand" is -- in short,
834+
/// it's an intermediate operand created as part of preparing some
835+
/// MIR instruction). We use this information to suppress
836+
/// redundant drops on the non-unwind paths. This results in less
837+
/// MIR, but also avoids spurious borrow check errors
838+
/// (c.f. #64391).
839+
///
840+
/// Example: when compiling the call to `foo` here:
841+
///
842+
/// ```rust
843+
/// foo(bar(), ...)
844+
/// ```
845+
///
846+
/// we would evaluate `bar()` to an operand `_X`. We would also
847+
/// schedule `_X` to be dropped when the expression scope for
848+
/// `foo(bar())` is exited. This is relevant, for example, if the
849+
/// later arguments should unwind (it would ensure that `_X` gets
850+
/// dropped). However, if no unwind occurs, then `_X` will be
851+
/// unconditionally consumed by the `call`:
852+
///
853+
/// ```
854+
/// bb {
855+
/// ...
856+
/// _R = CALL(foo, _X, ...)
857+
/// }
858+
/// ```
859+
///
860+
/// However, `_X` is still registered to be dropped, and so if we
861+
/// do nothing else, we would generate a `DROP(_X)` that occurs
862+
/// after the call. This will later be optimized out by the
863+
/// drop-elaboation code, but in the meantime it can lead to
864+
/// spurious borrow-check errors -- the problem, ironically, is
865+
/// not the `DROP(_X)` itself, but the (spurious) unwind pathways
866+
/// that it creates. See #64391 for an example.
867+
pub fn record_operands_moved(
868+
&mut self,
869+
operands: &[Operand<'tcx>],
870+
) {
871+
let scope = match self.local_scope() {
872+
None => {
873+
// if there is no local scope, operands won't be dropped anyway
874+
return;
875+
}
876+
877+
Some(local_scope) => {
878+
self.scopes.iter_mut().find(|scope| scope.region_scope == local_scope)
879+
.unwrap_or_else(|| bug!("scope {:?} not found in scope list!", local_scope))
880+
}
881+
};
882+
883+
// look for moves of a local variable, like `MOVE(_X)`
884+
let locals_moved = operands.iter().flat_map(|operand| match operand {
885+
Operand::Copy(_) | Operand::Constant(_) => None,
886+
Operand::Move(place) => place.as_local(),
887+
});
888+
889+
for local in locals_moved {
890+
// check if we have a Drop for this operand and -- if so
891+
// -- add it to the list of moved operands. Note that this
892+
// local might not have been an operand created for this
893+
// call, it could come from other places too.
894+
if scope.drops.iter().any(|drop| drop.local == local && drop.kind == DropKind::Value) {
895+
scope.moved_locals.push(local);
896+
}
897+
}
898+
}
899+
825900
// Other
826901
// =====
827902
/// Branch based on a boolean condition.
@@ -1020,6 +1095,7 @@ fn build_scope_drops<'tcx>(
10201095
last_unwind_to: BasicBlock,
10211096
arg_count: usize,
10221097
generator_drop: bool,
1098+
is_cached_path: bool,
10231099
) -> BlockAnd<()> {
10241100
debug!("build_scope_drops({:?} -> {:?})", block, scope);
10251101

@@ -1046,8 +1122,17 @@ fn build_scope_drops<'tcx>(
10461122
let drop_data = &scope.drops[drop_idx];
10471123
let source_info = scope.source_info(drop_data.span);
10481124
let local = drop_data.local;
1125+
10491126
match drop_data.kind {
10501127
DropKind::Value => {
1128+
// If the operand has been moved, and we are not on an unwind
1129+
// path, then don't generate the drop. (We only take this into
1130+
// account for non-unwind paths so as not to disturb the
1131+
// caching mechanism.)
1132+
if !is_cached_path && scope.moved_locals.iter().any(|&o| o == local) {
1133+
continue;
1134+
}
1135+
10511136
let unwind_to = get_unwind_to(scope, is_generator, drop_idx, generator_drop)
10521137
.unwrap_or(last_unwind_to);
10531138

src/librustc_typeck/check/generator_interior.rs

+27-6
Original file line numberDiff line numberDiff line change
@@ -181,13 +181,34 @@ impl<'a, 'tcx> Visitor<'tcx> for InteriorVisitor<'a, 'tcx> {
181181

182182
let scope = self.region_scope_tree.temporary_scope(expr.hir_id.local_id);
183183

184-
// Record the unadjusted type
184+
// If there are adjustments, then record the final type --
185+
// this is the actual value that is being produced.
186+
if let Some(adjusted_ty) = self.fcx.tables.borrow().expr_ty_adjusted_opt(expr) {
187+
self.record(adjusted_ty, scope, Some(expr), expr.span);
188+
}
189+
190+
// Also record the unadjusted type (which is the only type if
191+
// there are no adjustments). The reason for this is that the
192+
// unadjusted value is sometimes a "temporary" that would wind
193+
// up in a MIR temporary.
194+
//
195+
// As an example, consider an expression like `vec![].push()`.
196+
// Here, the `vec![]` would wind up MIR stored into a
197+
// temporary variable `t` which we can borrow to invoke
198+
// `<Vec<_>>::push(&mut t)`.
199+
//
200+
// Note that an expression can have many adjustments, and we
201+
// are just ignoring those intermediate types. This is because
202+
// those intermediate values are always linearly "consumed" by
203+
// the other adjustments, and hence would never be directly
204+
// captured in the MIR.
205+
//
206+
// (Note that this partly relies on the fact that the `Deref`
207+
// traits always return references, which means their content
208+
// can be reborrowed without needing to spill to a temporary.
209+
// If this were not the case, then we could conceivably have
210+
// to create intermediate temporaries.)
185211
let ty = self.fcx.tables.borrow().expr_ty(expr);
186212
self.record(ty, scope, Some(expr), expr.span);
187-
188-
// Also include the adjusted types, since these can result in MIR locals
189-
for adjustment in self.fcx.tables.borrow().expr_adjustments(expr) {
190-
self.record(adjustment.target, scope, Some(expr), expr.span);
191-
}
192213
}
193214
}

src/test/mir-opt/box_expr.rs

+4-11
Original file line numberDiff line numberDiff line change
@@ -57,25 +57,18 @@ impl Drop for S {
5757
// }
5858
//
5959
// bb5: {
60-
// drop(_4) -> [return: bb8, unwind: bb6];
60+
// StorageDead(_4);
61+
// StorageDead(_3);
62+
// _0 = ();
63+
// drop(_1) -> bb8;
6164
// }
62-
//
6365
// bb6 (cleanup): {
6466
// drop(_1) -> bb1;
6567
// }
66-
//
6768
// bb7 (cleanup): {
6869
// drop(_4) -> bb6;
6970
// }
70-
//
7171
// bb8: {
72-
// StorageDead(_4);
73-
// StorageDead(_3);
74-
// _0 = ();
75-
// drop(_1) -> bb9;
76-
// }
77-
//
78-
// bb9: {
7972
// StorageDead(_1);
8073
// return;
8174
// }

src/test/mir-opt/generator-storage-dead-unwind.rs

+13-13
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ fn main() {
5757
// StorageLive(_6);
5858
// StorageLive(_7);
5959
// _7 = move _2;
60-
// _6 = const take::<Foo>(move _7) -> [return: bb9, unwind: bb8];
60+
// _6 = const take::<Foo>(move _7) -> [return: bb7, unwind: bb9];
6161
// }
6262
// bb3 (cleanup): {
6363
// StorageDead(_2);
@@ -75,24 +75,24 @@ fn main() {
7575
// bb6: {
7676
// generator_drop;
7777
// }
78-
// bb7 (cleanup): {
79-
// StorageDead(_3);
80-
// StorageDead(_2);
81-
// drop(_1) -> bb1;
82-
// }
83-
// bb8 (cleanup): {
84-
// StorageDead(_7);
85-
// StorageDead(_6);
86-
// goto -> bb7;
87-
// }
88-
// bb9: {
78+
// bb7: {
8979
// StorageDead(_7);
9080
// StorageDead(_6);
9181
// StorageLive(_8);
9282
// StorageLive(_9);
9383
// _9 = move _3;
9484
// _8 = const take::<Bar>(move _9) -> [return: bb10, unwind: bb11];
9585
// }
86+
// bb8 (cleanup): {
87+
// StorageDead(_3);
88+
// StorageDead(_2);
89+
// drop(_1) -> bb1;
90+
// }
91+
// bb9 (cleanup): {
92+
// StorageDead(_7);
93+
// StorageDead(_6);
94+
// goto -> bb8;
95+
// }
9696
// bb10: {
9797
// StorageDead(_9);
9898
// StorageDead(_8);
@@ -104,7 +104,7 @@ fn main() {
104104
// bb11 (cleanup): {
105105
// StorageDead(_9);
106106
// StorageDead(_8);
107-
// goto -> bb7;
107+
// goto -> bb8;
108108
// }
109109
// bb12: {
110110
// return;

0 commit comments

Comments
 (0)