@@ -691,6 +691,9 @@ struct UseState {
691
691
// / [assign] that are reinits that we will convert to inits and true reinits.
692
692
llvm::SmallMapVector<SILInstruction *, TypeTreeLeafTypeRange, 4 > reinitInsts;
693
693
694
+ // / The set of drop_deinits of this mark_must_check
695
+ SmallSetVector<SILInstruction *, 2 > dropDeinitInsts;
696
+
694
697
// / A "inout terminator use" is an implicit liveness use of the entire value
695
698
// / placed on a terminator. We use this both so we add liveness for the
696
699
// / terminator user and so that we can use the set to quickly identify later
@@ -712,6 +715,31 @@ struct UseState {
712
715
return inoutTermUsers.count (inst);
713
716
}
714
717
718
+ // / Returns true if the given instruction is within the same block as a reinit
719
+ // / and precedes a reinit instruction in that block.
720
+ bool precedesReinitInSameBlock (SILInstruction *inst) const {
721
+ SILBasicBlock *block = inst->getParent ();
722
+ SmallSetVector<SILInstruction *, 8 > sameBlockReinits;
723
+
724
+ // First, search for all reinits that are within the same block.
725
+ for (auto &reinit : reinitInsts) {
726
+ if (reinit.first ->getParent () != block)
727
+ continue ;
728
+ sameBlockReinits.insert (reinit.first );
729
+ }
730
+
731
+ if (sameBlockReinits.empty ())
732
+ return false ;
733
+
734
+ // Walk down from the given instruction to see if we encounter a reinit.
735
+ for (auto ii = std::next (inst->getIterator ()); ii != block->end (); ++ii) {
736
+ if (sameBlockReinits.contains (&*ii))
737
+ return true ;
738
+ }
739
+
740
+ return false ;
741
+ }
742
+
715
743
void clear () {
716
744
address = nullptr ;
717
745
destroys.clear ();
@@ -721,6 +749,7 @@ struct UseState {
721
749
takeInsts.clear ();
722
750
initInsts.clear ();
723
751
reinitInsts.clear ();
752
+ dropDeinitInsts.clear ();
724
753
inoutTermUsers.clear ();
725
754
debugValue = nullptr ;
726
755
}
@@ -755,6 +784,10 @@ struct UseState {
755
784
for (auto pair : reinitInsts) {
756
785
llvm::dbgs () << *pair.first ;
757
786
}
787
+ llvm::dbgs () << " DropDeinits:\n " ;
788
+ for (auto *inst : dropDeinitInsts) {
789
+ llvm::dbgs () << *inst;
790
+ }
758
791
llvm::dbgs () << " InOut Term Users:\n " ;
759
792
for (auto *inst : inoutTermUsers) {
760
793
llvm::dbgs () << *inst;
@@ -1737,6 +1770,12 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
1737
1770
LLVM_DEBUG (llvm::dbgs () << " Running copy propagation!\n " );
1738
1771
moveChecker.changed |= moveChecker.canonicalizer .canonicalize ();
1739
1772
1773
+ // Export the drop_deinit's discovered by the ObjectChecker into the
1774
+ // AddressChecker to preserve it for later use. We need to do this since
1775
+ // the ObjectChecker's state gets cleared after running on this LoadInst.
1776
+ for (auto *dropDeinit : moveChecker.canonicalizer .getDropDeinitUses ())
1777
+ moveChecker.addressUseState .dropDeinitInsts .insert (dropDeinit);
1778
+
1740
1779
// If we are asked to perform no_consume_or_assign checking or
1741
1780
// assignable_but_not_consumable checking, if we found any consumes of our
1742
1781
// load, then we need to emit an error.
@@ -2458,10 +2497,41 @@ void MoveOnlyAddressCheckerPImpl::rewriteUses(
2458
2497
FieldSensitiveMultiDefPrunedLiveRange &liveness,
2459
2498
const FieldSensitivePrunedLivenessBoundary &boundary) {
2460
2499
LLVM_DEBUG (llvm::dbgs () << " MoveOnlyAddressChecker Rewrite Uses!\n " );
2461
- // First remove all destroy_addr that have not been claimed.
2500
+
2501
+ // / Whether the marked value appeared in a discard statement.
2502
+ const bool isDiscardingContext = !addressUseState.dropDeinitInsts .empty ();
2503
+
2504
+ // Process destroys
2462
2505
for (auto destroyPair : addressUseState.destroys ) {
2463
- if (!consumes.claimConsume (destroyPair.first , destroyPair.second )) {
2506
+ // / Is this destroy instruction a final consuming use?
2507
+ bool isFinalConsume =
2508
+ consumes.claimConsume (destroyPair.first , destroyPair.second );
2509
+
2510
+ // Remove destroys that are not the final consuming use.
2511
+ if (!isFinalConsume) {
2464
2512
destroyPair.first ->eraseFromParent ();
2513
+ continue ;
2514
+ }
2515
+
2516
+ // Otherwise, if we're in a discarding context, flag this final destroy_addr
2517
+ // as a point where we're missing an explicit `consume self`. The reasoning
2518
+ // here is that if a destroy of self is the final consuming use,
2519
+ // then these are the points where we implicitly destroy self to clean-up
2520
+ // that self var before exiting the scope. An explicit 'consume self'
2521
+ // that is thrown away is a consume of this mark_must_check'd var and not a
2522
+ // destroy of it, according to the use classifier.
2523
+ if (isDiscardingContext) {
2524
+
2525
+ // Since the boundary computations treat a newly-added destroy prior to
2526
+ // a reinit within that same block as a "final consuming use", exclude
2527
+ // such destroys-before-reinit. We are only interested in the final
2528
+ // destroy of a var, not intermediate destroys of the var.
2529
+ if (addressUseState.precedesReinitInSameBlock (destroyPair.first ))
2530
+ continue ;
2531
+
2532
+ auto *dropDeinit = addressUseState.dropDeinitInsts .front ();
2533
+ diagnosticEmitter.emitMissingConsumeInDiscardingContext (destroyPair.first ,
2534
+ dropDeinit);
2465
2535
}
2466
2536
}
2467
2537
0 commit comments