Skip to content

Commit f16bf16

Browse files
authored
Merge pull request #65911 from gottesmm/fix-closures
[move-only] Fix noncopyable semantics for let closures to match how we process inout captured by closures
2 parents b6a2791 + 0b38bba commit f16bf16

17 files changed

+1856
-255
lines changed

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,9 +112,6 @@ PASS(AccessMarkerElimination, "access-marker-elim",
112112
"Access Marker Elimination.")
113113
PASS(AddressLowering, "address-lowering",
114114
"SIL Address Lowering")
115-
PASS(EarlyAllocBoxToStack, "early-allocbox-to-stack",
116-
"Stack Promotion of Box Objects. Doesn't attempt to promote noncopyable "
117-
"types captured by escaping closures")
118115
PASS(AllocBoxToStack, "allocbox-to-stack",
119116
"Stack Promotion of Box Objects")
120117
IRGEN_PASS(AllocStackHoisting, "alloc-stack-hoisting",

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerTester.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,20 +105,18 @@ class MoveOnlyAddressCheckerTesterPass : public SILFunctionTransform {
105105
<< '\n');
106106

107107
bool madeChange = false;
108-
unsigned diagCount = 0;
109108
if (moveIntroducersToProcess.empty()) {
110109
LLVM_DEBUG(llvm::dbgs() << "No move introducers found?!\n");
111110
} else {
112111
borrowtodestructure::IntervalMapAllocator allocator;
113112
MoveOnlyAddressChecker checker{getFunction(), diagnosticEmitter,
114113
allocator, domTree, poa};
115114
madeChange = checker.check(moveIntroducersToProcess);
116-
diagCount = checker.diagnosticEmitter.getDiagnosticCount();
117115
}
118116

119117
// If we did not emit any diagnostics, emit a diagnostic if we missed any
120118
// copies.
121-
if (!diagCount) {
119+
if (!diagnosticEmitter.emittedDiagnostic()) {
122120
emitCheckerMissedCopyOfNonCopyableTypeErrors(getFunction(),
123121
diagnosticEmitter);
124122
}

lib/SILOptimizer/Mandatory/MoveOnlyAddressCheckerUtils.cpp

Lines changed: 131 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@
225225
#include "swift/AST/AccessScope.h"
226226
#include "swift/AST/DiagnosticEngine.h"
227227
#include "swift/AST/DiagnosticsSIL.h"
228+
#include "swift/AST/SemanticAttrs.h"
228229
#include "swift/Basic/Debug.h"
229230
#include "swift/Basic/Defer.h"
230231
#include "swift/Basic/FrozenMultiMap.h"
@@ -492,6 +493,92 @@ static bool isInOutDefThatNeedsEndOfFunctionLiveness(MarkMustCheckInst *markedAd
492493
return false;
493494
}
494495

496+
//===----------------------------------------------------------------------===//
497+
// MARK: Partial Apply Utilities
498+
//===----------------------------------------------------------------------===//
499+
500+
static bool findNonEscapingPartialApplyUses(
501+
PartialApplyInst *pai, TypeTreeLeafTypeRange leafRange,
502+
llvm::SmallMapVector<SILInstruction *, TypeTreeLeafTypeRange, 4>
503+
&livenessUses) {
504+
StackList<Operand *> worklist(pai->getFunction());
505+
for (auto *use : pai->getUses())
506+
worklist.push_back(use);
507+
508+
LLVM_DEBUG(llvm::dbgs() << "Searching for partial apply uses!\n");
509+
while (!worklist.empty()) {
510+
auto *use = worklist.pop_back_val();
511+
512+
if (use->isTypeDependent())
513+
continue;
514+
515+
auto *user = use->getUser();
516+
517+
// These instructions do not cause us to escape.
518+
if (isIncidentalUse(user) || isa<DestroyValueInst>(user))
519+
continue;
520+
521+
// Look through these instructions.
522+
if (isa<BeginBorrowInst>(user) || isa<CopyValueInst>(user) ||
523+
isa<MoveValueInst>(user) ||
524+
// If we capture this partial_apply in another partial_apply, then we
525+
// know that said partial_apply must not have escaped the value since
526+
// otherwise we could not have an inout_aliasable argument or be
527+
// on_stack. Process it recursively so that we treat uses of that
528+
// partial_apply and applies of that partial_apply as uses of our
529+
// partial_apply.
530+
//
531+
// We have this separately from the other look through sections so that
532+
// we can make it clearer what we are doing here.
533+
isa<PartialApplyInst>(user)) {
534+
for (auto *use : cast<SingleValueInstruction>(user)->getUses())
535+
worklist.push_back(use);
536+
continue;
537+
}
538+
539+
// If we have a mark_dependence and are the value, look through the
540+
// mark_dependence.
541+
if (auto *mdi = dyn_cast<MarkDependenceInst>(user)) {
542+
if (mdi->getValue() == use->get()) {
543+
for (auto *use : mdi->getUses())
544+
worklist.push_back(use);
545+
continue;
546+
}
547+
}
548+
549+
if (auto apply = FullApplySite::isa(user)) {
550+
// If we apply the function or pass the function off to an apply, then we
551+
// need to treat the function application as a liveness use of the
552+
// variable since if the partial_apply is invoked within the function
553+
// application, we may access the captured variable.
554+
livenessUses.insert({user, leafRange});
555+
if (apply.beginsCoroutineEvaluation()) {
556+
// If we have a coroutine, we need to treat the abort_apply and
557+
// end_apply as liveness uses since once we execute one of those
558+
// instructions, we have returned control to the coroutine which means
559+
// that we could then access the captured variable again.
560+
auto *bai = cast<BeginApplyInst>(user);
561+
SmallVector<EndApplyInst *, 4> endApplies;
562+
SmallVector<AbortApplyInst *, 4> abortApplies;
563+
bai->getCoroutineEndPoints(endApplies, abortApplies);
564+
for (auto *eai : endApplies)
565+
livenessUses.insert({eai, leafRange});
566+
for (auto *aai : abortApplies)
567+
livenessUses.insert({aai, leafRange});
568+
}
569+
continue;
570+
}
571+
572+
LLVM_DEBUG(
573+
llvm::dbgs()
574+
<< "Found instruction we did not understand... returning false!\n");
575+
LLVM_DEBUG(llvm::dbgs() << "Instruction: " << *user);
576+
return false;
577+
}
578+
579+
return true;
580+
}
581+
495582
//===----------------------------------------------------------------------===//
496583
// MARK: Find Candidate Mark Must Checks
497584
//===----------------------------------------------------------------------===//
@@ -1634,10 +1721,15 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
16341721
<< "Found mark must check [nocopy] error: " << *user);
16351722
auto *fArg = dyn_cast<SILFunctionArgument>(
16361723
stripAccessMarkers(markedValue->getOperand()));
1637-
if (fArg && fArg->isClosureCapture() && fArg->getType().isAddress()) {
1638-
moveChecker.diagnosticEmitter.emitPromotedBoxArgumentError(
1639-
markedValue, fArg);
1724+
// If we have a closure captured that we specialized, we should have a
1725+
// no consume or assign and should emit a normal guaranteed diagnostic.
1726+
if (fArg && fArg->isClosureCapture() &&
1727+
fArg->getArgumentConvention().isInoutConvention()) {
1728+
assert(checkKind == MarkMustCheckInst::CheckKind::NoConsumeOrAssign);
1729+
moveChecker.diagnosticEmitter.emitObjectGuaranteedDiagnostic(
1730+
markedValue);
16401731
} else {
1732+
// Otherwise, we need to emit an escaping closure error.
16411733
moveChecker.diagnosticEmitter
16421734
.emitAddressEscapingClosureCaptureLoadedAndConsumed(markedValue);
16431735
}
@@ -1792,6 +1884,19 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
17921884
}
17931885

17941886
if (auto *pas = dyn_cast<PartialApplyInst>(user)) {
1887+
if (auto *fArg = dyn_cast<SILFunctionArgument>(
1888+
stripAccessMarkers(markedValue->getOperand()))) {
1889+
// If we are processing an inout convention and we emitted an error on the
1890+
// partial_apply, we shouldn't process this mark_must_check, but squelch
1891+
// the compiler doesn't understand error.
1892+
if (fArg->getArgumentConvention().isInoutConvention() &&
1893+
pas->getCalleeFunction()->hasSemanticsAttr(
1894+
semantics::NO_MOVEONLY_DIAGNOSTICS)) {
1895+
diagnosticEmitter.emitEarlierPassEmittedDiagnostic(markedValue);
1896+
return false;
1897+
}
1898+
}
1899+
17951900
if (pas->isOnStack() ||
17961901
ApplySite(pas).getArgumentConvention(*op).isInoutConvention()) {
17971902
LLVM_DEBUG(llvm::dbgs() << "Found on stack partial apply or inout usage!\n");
@@ -1803,13 +1908,18 @@ bool GatherUsesVisitor::visitUse(Operand *op) {
18031908
return false;
18041909
}
18051910

1806-
useState.livenessUses.insert({user, *leafRange});
1807-
for (auto *use : pas->getConsumingUses()) {
1808-
LLVM_DEBUG(llvm::dbgs()
1809-
<< "Adding consuming use of partial apply as liveness use: "
1810-
<< *use->getUser());
1811-
useState.livenessUses.insert({use->getUser(), *leafRange});
1911+
// Attempt to find calls of the non-escaping partial apply and places
1912+
// where the partial apply is passed to a function. We treat those as
1913+
// liveness uses. If we find a use we don't understand, we return false
1914+
// here.
1915+
if (!findNonEscapingPartialApplyUses(pas, *leafRange,
1916+
useState.livenessUses)) {
1917+
LLVM_DEBUG(
1918+
llvm::dbgs()
1919+
<< "Failed to understand use of a non-escaping partial apply?!\n");
1920+
return false;
18121921
}
1922+
18131923
return true;
18141924
}
18151925
}
@@ -2534,9 +2644,19 @@ bool MoveOnlyAddressChecker::check(
25342644
LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *markedValue);
25352645

25362646
// Perform our address check.
2647+
unsigned diagnosticEmittedByEarlierPassCount =
2648+
diagnosticEmitter.getDiagnosticEmittedByEarlierPassCount();
25372649
if (!pimpl.performSingleCheck(markedValue)) {
2538-
LLVM_DEBUG(llvm::dbgs()
2539-
<< "Failed to perform single check! Emitting error!\n");
2650+
if (diagnosticEmittedByEarlierPassCount !=
2651+
diagnosticEmitter.getDiagnosticEmittedByEarlierPassCount()) {
2652+
LLVM_DEBUG(
2653+
llvm::dbgs()
2654+
<< "Failed to perform single check but found earlier emitted "
2655+
"error. Not emitting checker doesn't understand diagnostic!\n");
2656+
continue;
2657+
}
2658+
LLVM_DEBUG(llvm::dbgs() << "Failed to perform single check! Emitting "
2659+
"compiler doesn't understand diagnostic!\n");
25402660
// If we fail the address check in some way, set the diagnose!
25412661
diagnosticEmitter.emitCheckerDoesntUnderstandDiagnostic(markedValue);
25422662
}

lib/SILOptimizer/Mandatory/MoveOnlyChecker.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ class MoveOnlyCheckerPass : public SILFunctionTransform {
179179
// remain. If we emitted a diagnostic, we just want to rewrite all of the
180180
// non-copyable copies into explicit variants below and let the user
181181
// recompile.
182-
if (!checker.diagnosticEmitter.getDiagnosticCount()) {
182+
if (!checker.diagnosticEmitter.emittedDiagnostic()) {
183183
emitCheckerMissedCopyOfNonCopyableTypeErrors(getFunction(),
184184
checker.diagnosticEmitter);
185185
}

lib/SILOptimizer/Mandatory/MoveOnlyDiagnostics.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,23 @@ class DiagnosticEmitter {
5252

5353
bool emittedCheckerDoesntUnderstandDiagnostic = false;
5454

55+
/// This is incremented every time that the checker determines that an earlier
56+
/// pass emitted a diagnostic while processing a mark_must_check. In such a
57+
/// case, we want to suppress:
58+
///
59+
/// 1. Emitting the compiler doesn't understand how to check error for the
60+
/// specific mark_must_check.
61+
///
62+
/// 2. The "copy of noncopyable type" error over the entire function since us
63+
/// stopping processing at some point may have left copies.
64+
///
65+
/// We use a counter rather than a boolean here so that a caller that is
66+
/// processing an individual mark_must_check can determine if the checker
67+
/// identified such an earlier pass diagnostic for the specific allocation so
68+
/// that we can still emit "compiler doesn't understand" errors for other
69+
/// allocations.
70+
unsigned diagnosticEmittedByEarlierPassCount = 0;
71+
5572
public:
5673
DiagnosticEmitter(SILFunction *inputFn) : fn(inputFn) {}
5774

@@ -67,11 +84,37 @@ class DiagnosticEmitter {
6784
return *canonicalizer.get();
6885
}
6986

87+
/// Returns true if when processing any allocation in the current function:
88+
///
89+
/// 1. This diagnostic emitter emitted a diagnostic.
90+
/// 2. The user of the diagnostic emitter signaled to the diagnostic emitter
91+
/// that it detected an earlier diagnostic was emitted that prevented it
92+
/// from performing checking.
93+
///
94+
/// DISCUSSION: This is used by the checker to decide whether or not it should
95+
/// emit "found copy of a noncopyable type" error. If the checker emitted one
96+
/// of these diagnostics, then the checker may have stopped processing early
97+
/// and left copies since it was no longer able to check. In such a case, we
98+
/// want the user to fix the pre-existing errors and re-run.
99+
bool emittedDiagnostic() const {
100+
return getDiagnosticCount() || getDiagnosticEmittedByEarlierPassCount();
101+
}
102+
70103
unsigned getDiagnosticCount() const { return diagnosticCount; }
104+
71105
bool didEmitCheckerDoesntUnderstandDiagnostic() const {
72106
return emittedCheckerDoesntUnderstandDiagnostic;
73107
}
74108

109+
bool getDiagnosticEmittedByEarlierPassCount() const {
110+
return diagnosticEmittedByEarlierPassCount;
111+
}
112+
113+
void emitEarlierPassEmittedDiagnostic(MarkMustCheckInst *mmci) {
114+
++diagnosticEmittedByEarlierPassCount;
115+
registerDiagnosticEmitted(mmci);
116+
}
117+
75118
/// Used at the end of the MoveOnlyAddressChecker to tell the user in a nice
76119
/// way to file a bug.
77120
void emitCheckedMissedCopyError(SILInstruction *copyInst);

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
125125
// This guarantees that stack-promotable boxes have [static] enforcement.
126126
P.addAccessEnforcementSelection();
127127

128-
P.addEarlyAllocBoxToStack();
128+
P.addAllocBoxToStack();
129129
P.addNoReturnFolding();
130130
addDefiniteInitialization(P);
131131

@@ -169,11 +169,6 @@ static void addMandatoryDiagnosticOptPipeline(SILPassPipelinePlan &P) {
169169
// End Ownership Optimizations
170170
//===---
171171

172-
// Now that we have finished checking noncopyable types, run later alloc box
173-
// to stack, so that we promote to the stack any heap boxes that are captured
174-
// by escaping closures where the closure does not actually escape.
175-
P.addAllocBoxToStack();
176-
177172
#ifndef NDEBUG
178173
// Add a verification pass to check our work when skipping
179174
// function bodies.

0 commit comments

Comments
 (0)