@@ -104,25 +104,14 @@ struct Scope {
104
104
/// the span of that region_scope
105
105
region_scope_span : Span ,
106
106
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
-
120
107
/// set of places to drop when exiting this scope. This starts
121
108
/// out empty but grows as variables are declared during the
122
109
/// building process. This is a stack, so we always drop from the
123
110
/// end of the vector (top of the stack) first.
124
111
drops : Vec < DropData > ,
125
112
113
+ moved_locals : Vec < Local > ,
114
+
126
115
/// The cache for drop chain on “normal” exit into a particular BasicBlock.
127
116
cached_exits : FxHashMap < ( BasicBlock , region:: Scope ) , BasicBlock > ,
128
117
@@ -172,7 +161,7 @@ struct CachedBlock {
172
161
generator_drop : Option < BasicBlock > ,
173
162
}
174
163
175
- #[ derive( Debug ) ]
164
+ #[ derive( Debug , PartialEq , Eq ) ]
176
165
pub ( crate ) enum DropKind {
177
166
Value ,
178
167
Storage ,
@@ -202,8 +191,7 @@ pub enum BreakableTarget {
202
191
203
192
impl CachedBlock {
204
193
fn invalidate ( & mut self ) {
205
- self . generator_drop = None ;
206
- self . unwind = None ;
194
+ * self = CachedBlock :: default ( ) ;
207
195
}
208
196
209
197
fn get ( & self , generator_drop : bool ) -> Option < BasicBlock > {
@@ -261,6 +249,25 @@ impl Scope {
261
249
scope : self . source_scope
262
250
}
263
251
}
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
+ }
264
271
}
265
272
266
273
impl < ' tcx > Scopes < ' tcx > {
@@ -274,8 +281,8 @@ impl<'tcx> Scopes<'tcx> {
274
281
source_scope : vis_scope,
275
282
region_scope : region_scope. 0 ,
276
283
region_scope_span : region_scope. 1 . span ,
277
- needs_cleanup : false ,
278
284
drops : vec ! [ ] ,
285
+ moved_locals : vec ! [ ] ,
279
286
cached_generator_drop : None ,
280
287
cached_exits : Default :: default ( ) ,
281
288
cached_unwind : CachedBlock :: default ( ) ,
@@ -295,7 +302,7 @@ impl<'tcx> Scopes<'tcx> {
295
302
296
303
fn may_panic ( & self , scope_count : usize ) -> bool {
297
304
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 ( ) )
299
306
}
300
307
301
308
/// Finds the breakable scope for a given label. This is used for
@@ -480,7 +487,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
480
487
block,
481
488
unwind_to,
482
489
self . arg_count,
483
- false ,
490
+ false , // not generator
491
+ false , // not unwind path
484
492
) ) ;
485
493
486
494
block. unit ( )
@@ -572,7 +580,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
572
580
block,
573
581
unwind_to,
574
582
self . arg_count,
575
- false ,
583
+ false , // not generator
584
+ false , // not unwind path
576
585
) ) ;
577
586
578
587
scope = next_scope;
@@ -622,7 +631,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
622
631
block,
623
632
unwind_to,
624
633
self . arg_count,
625
- true ,
634
+ true , // is generator
635
+ true , // is cached path
626
636
) ) ;
627
637
}
628
638
@@ -801,10 +811,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> {
801
811
// cache of outer scope stays intact.
802
812
scope. invalidate_cache ( !needs_drop, self . is_generator , this_scope) ;
803
813
if this_scope {
804
- if let DropKind :: Value = drop_kind {
805
- scope. needs_cleanup = true ;
806
- }
807
-
808
814
let region_scope_span = region_scope. span ( self . hir . tcx ( ) ,
809
815
& self . hir . region_scope_tree ) ;
810
816
// Attribute scope exit drops to scope's closing brace.
@@ -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,8 +1122,17 @@ 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
+
1049
1126
match drop_data. kind {
1050
1127
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
+
1051
1136
let unwind_to = get_unwind_to ( scope, is_generator, drop_idx, generator_drop)
1052
1137
. unwrap_or ( last_unwind_to) ;
1053
1138
0 commit comments