@@ -110,6 +110,8 @@ struct Scope {
110110 /// end of the vector (top of the stack) first.
111111 drops : Vec < DropData > ,
112112
113+ moved_locals : Vec < Local > ,
114+
113115 /// The cache for drop chain on “normal” exit into a particular BasicBlock.
114116 cached_exits : FxHashMap < ( BasicBlock , region:: Scope ) , BasicBlock > ,
115117
@@ -159,7 +161,7 @@ struct CachedBlock {
159161 generator_drop : Option < BasicBlock > ,
160162}
161163
162- #[ derive( Debug ) ]
164+ #[ derive( Debug , PartialEq , Eq ) ]
163165pub ( crate ) enum DropKind {
164166 Value ,
165167 Storage ,
@@ -280,6 +282,7 @@ impl<'tcx> Scopes<'tcx> {
280282 region_scope : region_scope. 0 ,
281283 region_scope_span : region_scope. 1 . span ,
282284 drops : vec ! [ ] ,
285+ moved_locals : vec ! [ ] ,
283286 cached_generator_drop : None ,
284287 cached_exits : Default :: default ( ) ,
285288 cached_unwind : CachedBlock :: default ( ) ,
@@ -484,7 +487,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
484487 block,
485488 unwind_to,
486489 self . arg_count,
487- false ,
490+ false , // not generator
491+ false , // not unwind path
488492 ) ) ;
489493
490494 block. unit ( )
@@ -576,7 +580,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
576580 block,
577581 unwind_to,
578582 self . arg_count,
579- false ,
583+ false , // not generator
584+ false , // not unwind path
580585 ) ) ;
581586
582587 scope = next_scope;
@@ -626,7 +631,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
626631 block,
627632 unwind_to,
628633 self . arg_count,
629- true ,
634+ true , // is generator
635+ true , // is cached path
630636 ) ) ;
631637 }
632638
@@ -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,6 +1122,15 @@ 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+
1126+ // If the operand has been moved, and we are not on an unwind
1127+ // path, then don't generate the drop. (We only take this into
1128+ // account for non-unwind paths so as not to disturb the
1129+ // caching mechanism.)
1130+ if !is_cached_path && scope. moved_locals . iter ( ) . any ( |& o| o == local) {
1131+ continue ;
1132+ }
1133+
10491134 match drop_data. kind {
10501135 DropKind :: Value => {
10511136 let unwind_to = get_unwind_to ( scope, is_generator, drop_idx, generator_drop)
0 commit comments