@@ -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 ) ]
176165pub ( crate ) enum DropKind {
177166 Value ,
178167 Storage ,
@@ -202,8 +191,7 @@ pub enum BreakableTarget {
202191
203192impl 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
266273impl < ' 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
0 commit comments