@@ -110,6 +110,8 @@ struct Scope {
110
110
/// end of the vector (top of the stack) first.
111
111
drops : Vec < DropData > ,
112
112
113
+ moved_locals : Vec < Local > ,
114
+
113
115
/// The cache for drop chain on “normal” exit into a particular BasicBlock.
114
116
cached_exits : FxHashMap < ( BasicBlock , region:: Scope ) , BasicBlock > ,
115
117
@@ -159,7 +161,7 @@ struct CachedBlock {
159
161
generator_drop : Option < BasicBlock > ,
160
162
}
161
163
162
- #[ derive( Debug ) ]
164
+ #[ derive( Debug , PartialEq , Eq ) ]
163
165
pub ( crate ) enum DropKind {
164
166
Value ,
165
167
Storage ,
@@ -280,6 +282,7 @@ impl<'tcx> Scopes<'tcx> {
280
282
region_scope : region_scope. 0 ,
281
283
region_scope_span : region_scope. 1 . span ,
282
284
drops : vec ! [ ] ,
285
+ moved_locals : vec ! [ ] ,
283
286
cached_generator_drop : None ,
284
287
cached_exits : Default :: default ( ) ,
285
288
cached_unwind : CachedBlock :: default ( ) ,
@@ -484,7 +487,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
484
487
block,
485
488
unwind_to,
486
489
self . arg_count,
487
- false ,
490
+ false , // not generator
491
+ false , // not unwind path
488
492
) ) ;
489
493
490
494
block. unit ( )
@@ -576,7 +580,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
576
580
block,
577
581
unwind_to,
578
582
self . arg_count,
579
- false ,
583
+ false , // not generator
584
+ false , // not unwind path
580
585
) ) ;
581
586
582
587
scope = next_scope;
@@ -626,7 +631,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
626
631
block,
627
632
unwind_to,
628
633
self . arg_count,
629
- true ,
634
+ true , // is generator
635
+ true , // is cached path
630
636
) ) ;
631
637
}
632
638
@@ -822,6 +828,75 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
822
828
span_bug ! ( span, "region scope {:?} not in scope to drop {:?}" , region_scope, local) ;
823
829
}
824
830
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
+
825
900
// Other
826
901
// =====
827
902
/// Branch based on a boolean condition.
@@ -1020,6 +1095,7 @@ fn build_scope_drops<'tcx>(
1020
1095
last_unwind_to : BasicBlock ,
1021
1096
arg_count : usize ,
1022
1097
generator_drop : bool ,
1098
+ is_cached_path : bool ,
1023
1099
) -> BlockAnd < ( ) > {
1024
1100
debug ! ( "build_scope_drops({:?} -> {:?})" , block, scope) ;
1025
1101
@@ -1046,6 +1122,15 @@ fn build_scope_drops<'tcx>(
1046
1122
let drop_data = & scope. drops [ drop_idx] ;
1047
1123
let source_info = scope. source_info ( drop_data. span ) ;
1048
1124
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
+
1049
1134
match drop_data. kind {
1050
1135
DropKind :: Value => {
1051
1136
let unwind_to = get_unwind_to ( scope, is_generator, drop_idx, generator_drop)
0 commit comments