Skip to content

Commit 249b11a

Browse files
committed
[SIL Diagnostics] Improve diagnostics for yield-once coroutines
when the coroutines yield in some paths but not in all paths. <rdar://48184430>
1 parent a6ec750 commit 249b11a

File tree

6 files changed

+536
-64
lines changed

6 files changed

+536
-64
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,20 @@ NOTE(previous_yield, none, "previous yield was here", ())
369369
ERROR(possible_return_before_yield, none,
370370
"accessor must yield on all paths before returning", ())
371371

372-
NOTE(one_yield, none, "yield along one path is here", ())
372+
NOTE(branch_doesnt_yield, none,
373+
"missing yield when the condition is %select{false|true}0", (bool))
374+
375+
NOTE(named_case_doesnt_yield, none, "missing yield in the %0 case",
376+
(Identifier))
377+
378+
NOTE(case_doesnt_yield, none, "missing yield in "
379+
"%select{this|the nil|the non-nil}0 case", (unsigned))
380+
381+
NOTE(switch_value_case_doesnt_yield, none, "missing yield in the %0 case",
382+
(StringRef))
383+
384+
NOTE(try_branch_doesnt_yield, none, "missing yield when error is "
385+
"%select{not |}0thrown", (bool))
373386

374387
#ifndef DIAG_NO_UNDEF
375388
# if defined(DIAG)

include/swift/SIL/SILInstruction.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7189,6 +7189,15 @@ class SwitchValueInst final
71897189
assert(hasDefault() && "doesn't have a default");
71907190
return getSuccessorBuf()[getNumCases()];
71917191
}
7192+
7193+
Optional<unsigned> getUniqueCaseForDestination(SILBasicBlock *bb) const {
7194+
for (unsigned i = 0; i < getNumCases(); ++i) {
7195+
if (getCase(i).second == bb) {
7196+
return i + 1;
7197+
}
7198+
}
7199+
return None;
7200+
}
71927201
};
71937202

71947203
/// Common implementation for the switch_enum and

include/swift/SIL/SILSuccessor.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,12 @@ class SILSuccessor {
113113
return *this;
114114
}
115115

116+
pred_iterator operator++(int) {
117+
auto old = *this;
118+
++*this;
119+
return old;
120+
}
121+
116122
pred_iterator operator+(unsigned distance) const {
117123
auto copy = *this;
118124
if (distance == 0)

lib/SILOptimizer/Mandatory/YieldOnceCheck.cpp

Lines changed: 223 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222
#include "swift/AST/Expr.h"
2323
#include "swift/AST/Stmt.h"
2424
#include "swift/SIL/BasicBlockUtils.h"
25+
#include "swift/SIL/CFG.h"
2526
#include "swift/SIL/Dominance.h"
2627
#include "swift/SILOptimizer/PassManager/Transforms.h"
28+
#include "llvm/ADT/BreadthFirstIterator.h"
2729
#include "llvm/ADT/DenseSet.h"
2830

2931
using namespace swift;
@@ -48,32 +50,54 @@ class YieldOnceCheck : public SILFunctionTransform {
4850
enum YieldState { BeforeYield, AfterYield, Conflict } yieldState;
4951

5052
private:
51-
// The following state is maintained for emitting diagnostics.
53+
// The following states are maintained for emitting diagnostics.
5254

5355
/// For AfterYield and Conflict states, this field records the yield
5456
/// instruction that was seen while propagating the state.
5557
SILInstruction *yieldInst;
5658

57-
BBState(YieldState yState, SILInstruction *yieldI)
58-
: yieldState(yState), yieldInst(yieldI) {}
59+
/// For Conflict state, these fields record the basic blocks that
60+
/// propagated the 'AfterYield' and 'BeforeYield' states which resulted
61+
/// in the Conflict.
62+
SILBasicBlock *yieldingPred;
63+
SILBasicBlock *nonYieldingPred;
64+
65+
BBState(YieldState yState, SILInstruction *yieldI, SILBasicBlock *yieldPred,
66+
SILBasicBlock *noYieldPred)
67+
: yieldState(yState), yieldInst(yieldI), yieldingPred(yieldPred),
68+
nonYieldingPred(noYieldPred) {}
5969

6070
public:
61-
static BBState getInitialState() { return BBState(BeforeYield, nullptr); }
71+
static BBState getInitialState() {
72+
return BBState(BeforeYield, nullptr, nullptr, nullptr);
73+
}
6274

6375
static BBState getAfterYieldState(SILInstruction *yieldI) {
6476
assert(yieldI);
65-
return BBState(AfterYield, yieldI);
77+
return BBState(AfterYield, yieldI, nullptr, nullptr);
6678
}
6779

68-
static BBState getConflictState(SILInstruction *yieldI) {
69-
assert(yieldI);
70-
return BBState(Conflict, yieldI);
80+
static BBState getConflictState(SILInstruction *yieldI,
81+
SILBasicBlock *yieldPred,
82+
SILBasicBlock *noYieldPred) {
83+
assert(yieldI && yieldPred && noYieldPred);
84+
return BBState(Conflict, yieldI, yieldPred, noYieldPred);
7185
}
7286

7387
SILInstruction *getYieldInstruction() const {
7488
assert(yieldState == AfterYield || yieldState == Conflict);
7589
return yieldInst;
7690
}
91+
92+
SILBasicBlock *getYieldingPred() {
93+
assert(yieldState == Conflict);
94+
return yieldingPred;
95+
}
96+
97+
SILBasicBlock *getNonYieldingPred() {
98+
assert(yieldState == Conflict);
99+
return nonYieldingPred;
100+
}
77101
};
78102

79103
/// A structure that records an error found during the analysis along with
@@ -157,9 +181,16 @@ class YieldOnceCheck : public SILFunctionTransform {
157181
/// \param mergeBlock the basic block that is reached with two states
158182
/// \param oldState the previous state at the entry of the basic block
159183
/// \param newState the current state at the entry of the basic block
184+
/// \param newStatePred the predecessor of 'mergeBlock' that has propagated
185+
/// the 'newState'.
186+
/// \param bbToStateMap a map from the basic blocks visited by the analysis
187+
/// to the BBStates in which they were seen. This is used to identify
188+
/// blocks that propagate conflicting states when the merge results
189+
/// in a conflict.
160190
/// \return the new state obtained by merging the oldState with the newState
161191
static BBState merge(SILBasicBlock *mergeBlock, BBState oldState,
162-
BBState newState) {
192+
BBState newState, SILBasicBlock *newStatePred,
193+
llvm::DenseMap<SILBasicBlock *, BBState> &bbToStateMap) {
163194
auto oldYieldState = oldState.yieldState;
164195
auto newYieldState = newState.yieldState;
165196

@@ -182,11 +213,27 @@ class YieldOnceCheck : public SILFunctionTransform {
182213
(newYieldState == BBState::BeforeYield &&
183214
oldYieldState == BBState::AfterYield));
184215

185-
SILInstruction *yieldInst = (newYieldState == BBState::AfterYield)
186-
? newState.getYieldInstruction()
187-
: oldState.getYieldInstruction();
188-
189-
return BBState::getConflictState(yieldInst);
216+
// For diagnostics, find another predecessor of 'mergeBlock' that was
217+
// previously seen by the analysis. This predecessor would have
218+
// propagated the 'oldState'.
219+
SILBasicBlock *oldStatePred = nullptr;
220+
for (auto predBB : mergeBlock->getPredecessorBlocks()) {
221+
if (predBB != newStatePred && bbToStateMap.count(predBB)) {
222+
oldStatePred = predBB;
223+
break;
224+
}
225+
}
226+
assert(oldStatePred);
227+
228+
if (oldState.yieldState == BBState::BeforeYield) {
229+
return BBState::getConflictState(newState.getYieldInstruction(),
230+
/* yieldPred */ newStatePred,
231+
/* noYieldPred */ oldStatePred);
232+
} else {
233+
return BBState::getConflictState(oldState.getYieldInstruction(),
234+
/* yieldPred */ oldStatePred,
235+
/* noYieldPred */ newStatePred);
236+
}
190237
}
191238

192239
/// Perform a data-flow analysis to check whether there is exactly one
@@ -195,6 +242,7 @@ class YieldOnceCheck : public SILFunctionTransform {
195242
/// the control-flow graph.
196243
void diagnoseYieldOnceUsage(SILFunction &fun) {
197244
llvm::DenseMap<SILBasicBlock *, BBState> bbToStateMap;
245+
198246
SmallVector<SILBasicBlock *, 16> worklist;
199247

200248
auto *entryBB = fun.getEntryBlock();
@@ -234,7 +282,7 @@ class YieldOnceCheck : public SILFunctionTransform {
234282
continue;
235283
}
236284

237-
emitDiagnostics(error, fun);
285+
emitDiagnostics(error, fun, bbToStateMap);
238286
return;
239287
}
240288

@@ -255,7 +303,7 @@ class YieldOnceCheck : public SILFunctionTransform {
255303
// previous states and propagate it if it is different from the
256304
// old state.
257305
const auto &oldState = insertResult.first->second;
258-
auto mergedState = merge(succBB, oldState, nextState);
306+
auto mergedState = merge(succBB, oldState, nextState, bb, bbToStateMap);
259307

260308
if (mergedState.yieldState == oldState.yieldState)
261309
continue;
@@ -271,11 +319,12 @@ class YieldOnceCheck : public SILFunctionTransform {
271319
}
272320

273321
if (returnBeforeYieldError.hasValue()) {
274-
emitDiagnostics(returnBeforeYieldError.getValue(), fun);
322+
emitDiagnostics(returnBeforeYieldError.getValue(), fun, bbToStateMap);
275323
}
276324
}
277325

278-
void emitDiagnostics(YieldError &error, SILFunction &fun) {
326+
void emitDiagnostics(YieldError &error, SILFunction &fun,
327+
llvm::DenseMap<SILBasicBlock *, BBState> &visitedBBs) {
279328
ASTContext &astCtx = fun.getModule().getASTContext();
280329

281330
switch (error.errorKind) {
@@ -299,9 +348,162 @@ class YieldOnceCheck : public SILFunctionTransform {
299348
diagnose(astCtx, error.termInst->getLoc().getSourceLoc(),
300349
diag::possible_return_before_yield);
301350

302-
// Add a note that points to the yield instruction found.
303-
auto *yieldInst = error.inState.getYieldInstruction();
304-
diagnose(astCtx, yieldInst->getLoc().getSourceLoc(), diag::one_yield);
351+
// Here, the yield state of 'error' is Conflict.
352+
auto &conflictState = error.inState;
353+
354+
// Emit a note that pin points the branch construct that resulted in
355+
// this conflict. Note that a conflict state is created at a merge block
356+
// when along one incoming edge the analysis sees a BeforeYield state
357+
// and along another it sees an AfterYield state.
358+
// Also note that, by the definition of the merge operation,
359+
// 'error.yieldingPred()' is the immediate predecessor of the merge block
360+
// that propagates AfterYield state, and 'error.nonYieldingPred()' is
361+
// the immediate predecessor of the merge block that propagates a
362+
// BeforeYield state.
363+
auto yieldPred = conflictState.getYieldingPred();
364+
auto noYieldPred = conflictState.getNonYieldingPred();
365+
366+
// Step 1: find a branching SIL instruction where one branch has
367+
// 'yieldPred' and another branch has 'noYieldPred'. For instance,
368+
// in the following example, cond_br is the instruction to find.
369+
// cond_br bb1, bb2
370+
// bb1:
371+
// yield resume yieldPred, unwind err
372+
// bb2:
373+
// br noYieldPred
374+
// yieldPred:
375+
// br mergePoint
376+
// noYieldPred:
377+
// br mergePoint
378+
// mergePoint:
379+
// ...
380+
// Intuitively, the conflicting branch is the instruction where
381+
// 'yieldPred' and 'noYieldPred' meet when traversing the CFG in the
382+
// reverse order. More formally, the branching instruction is a
383+
// "lowest common ancestor" of 'yieldPred' and 'noYieldPred' in the
384+
// the DAG obtained from the CFG by ignoring back edges of loops.
385+
//
386+
// Note that the lowest common ancestor may not be unique in a DAG.
387+
// But, any such ancestor could be considered as the conflicting branch as
388+
// 'yieldPred' and 'noYieldPred' will belong to two different branches of
389+
// every such ancestor.
390+
391+
// Find all transitive predecessors of 'yieldPred' that were visited
392+
// during the analysis
393+
SmallPtrSet<SILBasicBlock *, 8> predecessorsOfYieldPred;
394+
for (auto *predBB :
395+
llvm::breadth_first<llvm::Inverse<SILBasicBlock *>>(yieldPred)) {
396+
if (visitedBBs.count(predBB)) {
397+
predecessorsOfYieldPred.insert(predBB);
398+
}
399+
}
400+
401+
// Find the first predecessor of 'noYieldPred' that is also a predecessor
402+
// of 'yieldPred', in the breadth-first search order of the reversed CFG.
403+
SILBasicBlock *lowestCommonAncestorBB = nullptr;
404+
SmallPtrSet<SILBasicBlock *, 8> predecessorsOfNoYieldPred;
405+
for (auto *pred :
406+
llvm::breadth_first<llvm::Inverse<SILBasicBlock *>>(noYieldPred)) {
407+
if (!visitedBBs.count(pred)) {
408+
continue;
409+
}
410+
if (predecessorsOfYieldPred.count(pred)) {
411+
lowestCommonAncestorBB = pred;
412+
break;
413+
}
414+
predecessorsOfNoYieldPred.insert(pred);
415+
}
416+
assert(lowestCommonAncestorBB);
417+
418+
auto *conflictingBranch = lowestCommonAncestorBB->getTerminator();
419+
420+
// Step 2: Find the target basic block of the 'conflictingBranch' that
421+
// doesn't yield.
422+
SILBasicBlock *noYieldTarget = nullptr;
423+
for (auto *succ : lowestCommonAncestorBB->getSuccessorBlocks()) {
424+
if (predecessorsOfNoYieldPred.count(succ)) {
425+
noYieldTarget = succ;
426+
break;
427+
}
428+
}
429+
assert(noYieldTarget);
430+
431+
// Step 3: Report specialized diagnostics for each kind of conflicting
432+
// branch.
433+
434+
// For conditional-branch instructions, which correspond to the
435+
// conditions of 'if', 'where' or 'guard' statements, report for what
436+
// truth value the branch doesn't yield.
437+
if (auto *condbr = dyn_cast<CondBranchInst>(conflictingBranch)) {
438+
diagnose(astCtx, condbr->getLoc().getSourceLoc(),
439+
diag::branch_doesnt_yield,
440+
/*non-yielding branch*/ condbr->getTrueBB() == noYieldTarget);
441+
return;
442+
}
443+
444+
// For switch_enum instructions, report the case that doesn't yield.
445+
enum SwitchCaseKind { Default, OptionNil, OptionSome };
446+
447+
if (auto *switchEnum = dyn_cast<SwitchEnumInstBase>(conflictingBranch)) {
448+
auto enumCaseLoc = noYieldTarget->begin()->getLoc().getSourceLoc();
449+
450+
if (switchEnum->hasDefault() &&
451+
switchEnum->getDefaultBB() == noYieldTarget) {
452+
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, Default);
453+
return;
454+
}
455+
456+
// Find the case identifier that doesn't yield.
457+
NullablePtr<EnumElementDecl> enumElemDecl =
458+
switchEnum->getUniqueCaseForDestination(noYieldTarget);
459+
assert(enumElemDecl.isNonNull());
460+
461+
// Specialize diagnostics for cases of an optional.
462+
if (enumElemDecl.get() == astCtx.getOptionalNoneDecl()) {
463+
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, OptionNil);
464+
} else if (enumElemDecl.get() == astCtx.getOptionalSomeDecl()) {
465+
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, OptionSome);
466+
} else {
467+
diagnose(astCtx, enumCaseLoc, diag::named_case_doesnt_yield,
468+
enumElemDecl.get()->getName());
469+
}
470+
return;
471+
}
472+
473+
// For switch_value instructions, report the case number that doesn't
474+
// yield.
475+
if (auto *switchValue = dyn_cast<SwitchValueInst>(conflictingBranch)) {
476+
auto enumCaseLoc = noYieldTarget->begin()->getLoc().getSourceLoc();
477+
478+
if (switchValue->hasDefault() &&
479+
switchValue->getDefaultBB() == noYieldTarget) {
480+
diagnose(astCtx, enumCaseLoc, diag::case_doesnt_yield, Default);
481+
return;
482+
}
483+
// Find the case that doesn't yield.
484+
Optional<unsigned> caseNumberOpt =
485+
switchValue->getUniqueCaseForDestination(noYieldTarget);
486+
assert(caseNumberOpt.hasValue());
487+
488+
auto caseNumber = caseNumberOpt.getValue();
489+
diagnose(
490+
astCtx, enumCaseLoc, diag::switch_value_case_doesnt_yield,
491+
(Twine(caseNumber) + llvm::getOrdinalSuffix(caseNumber)).str());
492+
return;
493+
}
494+
495+
// For try-apply instructions, report whether throwing or non-throwing
496+
// case doesn't yield.
497+
if (auto *tryApply = dyn_cast<TryApplyInst>(conflictingBranch)) {
498+
diagnose(astCtx, tryApply->getLoc().getSourceLoc(),
499+
diag::try_branch_doesnt_yield,
500+
/*does error case not yield?*/ tryApply->getErrorBB() ==
501+
noYieldTarget);
502+
return;
503+
}
504+
505+
llvm_unreachable("unexpected branch resulting in conflicting yield "
506+
"states found in generalized accessor");
305507
}
306508
}
307509
}

0 commit comments

Comments
 (0)