Skip to content

Commit 0735f9f

Browse files
committed
[move-only] Teach SILGen that lets captured by a closure must be no copy.
The current diagnostic that we are emitting is not perfect, but at least this prevents us from saying that an error is not occuring here. I am going to file a bug to track the QoI work of improving the diagnostic. rdar://103313305
1 parent 2a8f069 commit 0735f9f

File tree

8 files changed

+360
-149
lines changed

8 files changed

+360
-149
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -731,6 +731,8 @@ ERROR(sil_moveonlychecker_value_used_after_consume, none,
731731
"'%0' used after consume. Lifetime extension of variable requires a copy", (StringRef))
732732
ERROR(sil_moveonlychecker_guaranteed_value_consumed, none,
733733
"'%0' has guaranteed ownership but was consumed", (StringRef))
734+
ERROR(sil_moveonlychecker_guaranteed_value_captured_by_closure, none,
735+
"'%0' has guaranteed ownership but was consumed due to being captured by a closure", (StringRef))
734736
ERROR(sil_moveonlychecker_inout_not_reinitialized_before_end_of_function, none,
735737
"'%0' consumed but not reinitialized before end of function", (StringRef))
736738
ERROR(sil_moveonlychecker_value_consumed_in_a_loop, none,
@@ -740,6 +742,8 @@ ERROR(sil_moveonlychecker_exclusivity_violation, none,
740742

741743
NOTE(sil_moveonlychecker_consuming_use_here, none,
742744
"consuming use", ())
745+
NOTE(sil_moveonlychecker_consuming_closure_use_here, none,
746+
"closure capture", ())
743747
NOTE(sil_moveonlychecker_nonconsuming_use_here, none,
744748
"non-consuming use", ())
745749
ERROR(sil_moveonlychecker_not_understand_no_implicit_copy, none,

lib/SILGen/SILGenBuilder.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -954,3 +954,12 @@ SILGenBuilder::createMarkMustCheckInst(SILLocation loc, ManagedValue value,
954954
loc, value.forward(getSILGenFunction()), kind);
955955
return cloner.clone(mdi);
956956
}
957+
958+
ManagedValue SILGenBuilder::emitCopyValueOperation(SILLocation loc,
959+
ManagedValue value) {
960+
auto cvi = SILBuilder::emitCopyValueOperation(loc, value.getValue());
961+
// Trivial type.
962+
if (cvi == value.getValue())
963+
return value;
964+
return SGF.emitManagedRValueWithCleanup(cvi);
965+
}

lib/SILGen/SILGenBuilder.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,9 @@ class SILGenBuilder : public SILBuilder {
430430
using SILBuilder::createMarkMustCheckInst;
431431
ManagedValue createMarkMustCheckInst(SILLocation loc, ManagedValue value,
432432
MarkMustCheckInst::CheckKind kind);
433+
434+
using SILBuilder::emitCopyValueOperation;
435+
ManagedValue emitCopyValueOperation(SILLocation Loc, ManagedValue v);
433436
};
434437

435438
} // namespace Lowering

lib/SILGen/SILGenProlog.cpp

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -520,34 +520,47 @@ static void emitCaptureArguments(SILGenFunction &SGF,
520520
auto &lowering = SGF.getTypeLowering(type);
521521
// Constant decls are captured by value.
522522
SILType ty = lowering.getLoweredType();
523-
SILValue val = SGF.F.begin()->createFunctionArgument(ty, VD);
524-
525-
bool NeedToDestroyValueAtExit = false;
523+
ManagedValue val = ManagedValue::forUnmanaged(
524+
SGF.F.begin()->createFunctionArgument(ty, VD));
526525

527526
// If the original variable was settable, then Sema will have treated the
528527
// VarDecl as an lvalue, even in the closure's use. As such, we need to
529528
// allow formation of the address for this captured value. Create a
530529
// temporary within the closure to provide this address.
531530
if (VD->isSettable(VD->getDeclContext())) {
532-
auto addr = SGF.emitTemporaryAllocation(VD, ty);
531+
auto addr = SGF.emitTemporary(VD, lowering);
533532
// We have created a copy that needs to be destroyed.
534533
val = SGF.B.emitCopyValueOperation(Loc, val);
535-
NeedToDestroyValueAtExit = true;
536-
lowering.emitStore(SGF.B, VD, val, addr, StoreOwnershipQualifier::Init);
537-
val = addr;
534+
// We use the SILValue version of this because the SILGenBuilder version
535+
// will create a cloned cleanup, which we do not want since our temporary
536+
// already has a cleanup.
537+
//
538+
// MG: Is this the right semantics for createStore? Seems like that
539+
// should be potentially a different API.
540+
SGF.B.emitStoreValueOperation(VD, val.forward(SGF), addr->getAddress(),
541+
StoreOwnershipQualifier::Init);
542+
addr->finishInitialization(SGF);
543+
val = addr->getManagedAddress();
544+
}
545+
546+
// If this constant is a move only type, we need to add no_copy checking to
547+
// ensure that we do not consume this captured value in the function. This
548+
// is because closures can be invoked multiple times which is inconsistent
549+
// with consuming the move only type.
550+
if (val.getType().isMoveOnly()) {
551+
val = val.ensurePlusOne(SGF, Loc);
552+
val = SGF.B.createMarkMustCheckInst(Loc, val,
553+
MarkMustCheckInst::CheckKind::NoCopy);
538554
}
539555

540-
SGF.VarLocs[VD] = SILGenFunction::VarLoc::get(val);
541-
if (auto *AllocStack = dyn_cast<AllocStackInst>(val))
556+
SGF.VarLocs[VD] = SILGenFunction::VarLoc::get(val.getValue());
557+
if (auto *AllocStack = dyn_cast<AllocStackInst>(val.getValue())) {
542558
AllocStack->setArgNo(ArgNo);
543-
else {
559+
} else {
544560
SILDebugVariable DbgVar(VD->isLet(), ArgNo);
545-
SGF.B.createDebugValue(Loc, val, DbgVar);
561+
SGF.B.createDebugValue(Loc, val.getValue(), DbgVar);
546562
}
547563

548-
// TODO: Closure contexts should always be guaranteed.
549-
if (NeedToDestroyValueAtExit && !lowering.isTrivial())
550-
SGF.enterDestroyCleanup(val);
551564
break;
552565
}
553566

lib/SILOptimizer/Mandatory/MoveOnlyObjectChecker.cpp

Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ struct OSSACanonicalizer {
9999
bool foundConsumingUseRequiringCopy() const {
100100
return consumingUsesNeedingCopy.size();
101101
}
102+
103+
bool hasPartialApplyConsumingUse() const {
104+
return llvm::any_of(consumingUsesNeedingCopy,
105+
[](Operand *use) {
106+
return isa<PartialApplyInst>(use->getUser());
107+
}) ||
108+
llvm::any_of(finalConsumingUses, [](Operand *use) {
109+
return isa<PartialApplyInst>(use->getUser());
110+
});
111+
}
112+
113+
bool hasNonPartialApplyConsumingUse() const {
114+
return llvm::any_of(consumingUsesNeedingCopy,
115+
[](Operand *use) {
116+
return !isa<PartialApplyInst>(use->getUser());
117+
}) ||
118+
llvm::any_of(finalConsumingUses, [](Operand *use) {
119+
return !isa<PartialApplyInst>(use->getUser());
120+
});
121+
}
102122
};
103123

104124
} // anonymous namespace
@@ -147,7 +167,12 @@ struct DiagnosticEmitter {
147167
}
148168

149169
private:
150-
void emitDiagnosticsForFoundUses() const;
170+
/// Emit diagnostics for the final consuming uses and consuming uses needing
171+
/// copy. If filter is non-null, allow for the caller to pre-process operands
172+
/// and emit their own diagnostic. If filter returns true, then we assume that
173+
/// the caller processed it correctly. false, then we continue to process it.
174+
void emitDiagnosticsForFoundUses(bool ignorePartialApply = false) const;
175+
void emitDiagnosticsForPartialApplyUses() const;
151176
};
152177

153178
} // anonymous namespace
@@ -172,11 +197,24 @@ void DiagnosticEmitter::emitGuaranteedDiagnostic(
172197
auto &astContext = fn->getASTContext();
173198
StringRef varName = getVariableNameForValue(markedValue);
174199

200+
// See if we have any closure capture uses and emit a better diagnostic.
201+
if (canonicalizer.hasPartialApplyConsumingUse()) {
202+
diagnose(astContext,
203+
markedValue->getDefiningInstruction()->getLoc().getSourceLoc(),
204+
diag::sil_moveonlychecker_guaranteed_value_captured_by_closure,
205+
varName);
206+
emitDiagnosticsForPartialApplyUses();
207+
valuesWithDiagnostics.insert(markedValue);
208+
}
209+
210+
// If we do not have any non-partial apply consuming uses... just exit early.
211+
if (!canonicalizer.hasNonPartialApplyConsumingUse())
212+
return;
213+
175214
diagnose(astContext,
176215
markedValue->getDefiningInstruction()->getLoc().getSourceLoc(),
177216
diag::sil_moveonlychecker_guaranteed_value_consumed, varName);
178-
179-
emitDiagnosticsForFoundUses();
217+
emitDiagnosticsForFoundUses(true /*ignore partial apply uses*/);
180218
valuesWithDiagnostics.insert(markedValue);
181219
}
182220

@@ -193,7 +231,8 @@ void DiagnosticEmitter::emitOwnedDiagnostic(MarkMustCheckInst *markedValue) {
193231
valuesWithDiagnostics.insert(markedValue);
194232
}
195233

196-
void DiagnosticEmitter::emitDiagnosticsForFoundUses() const {
234+
void DiagnosticEmitter::emitDiagnosticsForFoundUses(
235+
bool ignorePartialApplyUses) const {
197236
auto &astContext = fn->getASTContext();
198237

199238
for (auto *consumingUse : canonicalizer.consumingUsesNeedingCopy) {
@@ -209,6 +248,9 @@ void DiagnosticEmitter::emitDiagnosticsForFoundUses() const {
209248
}
210249
}
211250

251+
if (ignorePartialApplyUses &&
252+
isa<PartialApplyInst>(consumingUse->getUser()))
253+
continue;
212254
diagnose(astContext, loc.getSourceLoc(),
213255
diag::sil_moveonlychecker_consuming_use_here);
214256
}
@@ -226,11 +268,58 @@ void DiagnosticEmitter::emitDiagnosticsForFoundUses() const {
226268
}
227269
}
228270

271+
if (ignorePartialApplyUses &&
272+
isa<PartialApplyInst>(consumingUse->getUser()))
273+
continue;
274+
229275
diagnose(astContext, loc.getSourceLoc(),
230276
diag::sil_moveonlychecker_consuming_use_here);
231277
}
232278
}
233279

280+
void DiagnosticEmitter::emitDiagnosticsForPartialApplyUses() const {
281+
auto &astContext = fn->getASTContext();
282+
283+
for (auto *consumingUse : canonicalizer.consumingUsesNeedingCopy) {
284+
// See if the consuming use is an owned moveonly_to_copyable whose only
285+
// user is a return. In that case, use the return loc instead. We do this
286+
// b/c it is illegal to put a return value location on a non-return value
287+
// instruction... so we have to hack around this slightly.
288+
auto *user = consumingUse->getUser();
289+
auto loc = user->getLoc();
290+
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
291+
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
292+
loc = ri->getLoc();
293+
}
294+
}
295+
296+
if (!isa<PartialApplyInst>(consumingUse->getUser()))
297+
continue;
298+
diagnose(astContext, loc.getSourceLoc(),
299+
diag::sil_moveonlychecker_consuming_closure_use_here);
300+
}
301+
302+
for (auto *consumingUse : canonicalizer.finalConsumingUses) {
303+
// See if the consuming use is an owned moveonly_to_copyable whose only
304+
// user is a return. In that case, use the return loc instead. We do this
305+
// b/c it is illegal to put a return value location on a non-return value
306+
// instruction... so we have to hack around this slightly.
307+
auto *user = consumingUse->getUser();
308+
auto loc = user->getLoc();
309+
if (auto *mtc = dyn_cast<MoveOnlyWrapperToCopyableValueInst>(user)) {
310+
if (auto *ri = mtc->getSingleUserOfType<ReturnInst>()) {
311+
loc = ri->getLoc();
312+
}
313+
}
314+
315+
if (!isa<PartialApplyInst>(consumingUse->getUser()))
316+
continue;
317+
318+
diagnose(astContext, loc.getSourceLoc(),
319+
diag::sil_moveonlychecker_consuming_closure_use_here);
320+
}
321+
}
322+
234323
//===----------------------------------------------------------------------===//
235324
// MARK: Main Pass
236325
//===----------------------------------------------------------------------===//

test/SILGen/closures.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -845,3 +845,22 @@ func closeOverStructDontCopyTrivial() {
845845
a = StructWithMutatingMethod()
846846
takeClosure { a.x }
847847
}
848+
849+
// Make sure that we handle closure argument initialization when due to forward
850+
// declaration we represent a let as an lvalue
851+
func test() {
852+
let k: SomeClass
853+
let k2: SomeClass
854+
var boolean: Bool { true }
855+
856+
if boolean {
857+
k = SomeClass()
858+
k2 = SomeClass()
859+
} else {
860+
k = SomeClass()
861+
k2 = SomeClass()
862+
}
863+
864+
assert(k.x == k2.x)
865+
}
866+

0 commit comments

Comments
 (0)