Skip to content

Commit 1a66c77

Browse files
authored
Merge pull request #21067 from ravikandhadai/yieldcheck
2 parents 4007e56 + a9b0ebe commit 1a66c77

File tree

8 files changed

+938
-1
lines changed

8 files changed

+938
-1
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,19 @@ WARNING(warning_int_to_fp_inexact, none,
358358
"'%1' is not exactly representable as %0; it becomes '%2'",
359359
(Type, StringRef, StringRef))
360360

361+
362+
// Yield usage errors
363+
ERROR(return_before_yield, none, "accessor must yield before returning",())
364+
365+
ERROR(multiple_yields, none, "accessor must not yield more than once", ())
366+
367+
NOTE(previous_yield, none, "previous yield was here", ())
368+
369+
ERROR(possible_return_before_yield, none,
370+
"accessor must yield on all paths before returning", ())
371+
372+
NOTE(one_yield, none, "yield along one path is here", ())
373+
361374
#ifndef DIAG_NO_UNDEF
362375
# if defined(DIAG)
363376
# undef DIAG

include/swift/SILOptimizer/PassManager/Passes.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ PASS(SimplifyUnreachableContainingBlocks, "simplify-unreachable-containing-block
299299
"Utility pass. Removes all non-term insts from blocks with unreachable terms")
300300
PASS(SerializeSILPass, "serialize-sil",
301301
"Utility pass. Serializes the current SILModule")
302+
PASS(YieldOnceCheck, "yield-once-check",
303+
"Check correct usage of yields in yield-once coroutines")
302304
PASS(BugReducerTester, "bug-reducer-tester",
303305
"sil-bug-reducer Tool Testing by Asserting on a Sentinel Function")
304306
PASS_RANGE(AllPasses, AADumper, BugReducerTester)

lib/SIL/SILVerifier.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4494,7 +4494,7 @@ class SILVerifier : public SILVerifierBase<SILVerifier> {
44944494
/// - accesses must be uniquely ended
44954495
/// - flow-sensitive states must be equivalent on all paths into a block
44964496
void verifyFlowSensitiveRules(SILFunction *F) {
4497-
// Do a breath-first search through the basic blocks.
4497+
// Do a traversal of the basic blocks.
44984498
// Note that we intentionally don't verify these properties in blocks
44994499
// that can't be reached from the entry block.
45004500
llvm::DenseMap<SILBasicBlock*, VerifyFlowSensitiveRulesDetails::BBState> visitedBBs;

lib/SILOptimizer/Mandatory/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ silopt_register_sources(
1717
SemanticARCOpts.cpp
1818
ClosureLifetimeFixup.cpp
1919
RawSILInstLowering.cpp
20+
YieldOnceCheck.cpp
2021
)
Lines changed: 325 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
//===------ YieldOnceCheck.cpp - Check usage of yields in accessors ------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
// This pass statically verifies that yield-once coroutines, such as the
14+
// generalized accessors `read` and `modify`, yield exactly once in every
15+
// invocation, and diagnoses any violation of this property. This pass uses a
16+
// linear-time, data-flow analysis to check that every path in the control-flow
17+
// graph of the coroutine has a yield instruction before a return instruction.
18+
19+
#define DEBUG_TYPE "yield-once-check"
20+
#include "swift/AST/ASTWalker.h"
21+
#include "swift/AST/DiagnosticsSIL.h"
22+
#include "swift/AST/Expr.h"
23+
#include "swift/AST/Stmt.h"
24+
#include "swift/SIL/BasicBlockUtils.h"
25+
#include "swift/SIL/Dominance.h"
26+
#include "swift/SILOptimizer/PassManager/Transforms.h"
27+
#include "llvm/ADT/DenseSet.h"
28+
29+
using namespace swift;
30+
31+
namespace {
32+
33+
class YieldOnceCheck : public SILFunctionTransform {
34+
35+
template <typename... T, typename... U>
36+
static InFlightDiagnostic diagnose(ASTContext &Context, SourceLoc loc,
37+
Diag<T...> diag, U &&... args) {
38+
return Context.Diags.diagnose(loc, diag, std::forward<U>(args)...);
39+
}
40+
41+
/// Data-flow analysis state that is associated with basic blocks.
42+
struct BBState {
43+
44+
/// Indicates whether a basic block is encountered before seeing a yield
45+
/// (BeforeYield) or after seeing a yield (AfterYield), or in both states
46+
/// (Conflict). This enum is a semi-lattice where Conflict is the top and
47+
/// the merge of BeforeYield and AfterYield states is Conflict.
48+
enum YieldState { BeforeYield, AfterYield, Conflict } yieldState;
49+
50+
private:
51+
// The following state is maintained for emitting diagnostics.
52+
53+
/// For AfterYield and Conflict states, this field records the yield
54+
/// instruction that was seen while propagating the state.
55+
SILInstruction *yieldInst;
56+
57+
BBState(YieldState yState, SILInstruction *yieldI)
58+
: yieldState(yState), yieldInst(yieldI) {}
59+
60+
public:
61+
static BBState getInitialState() { return BBState(BeforeYield, nullptr); }
62+
63+
static BBState getAfterYieldState(SILInstruction *yieldI) {
64+
assert(yieldI);
65+
return BBState(AfterYield, yieldI);
66+
}
67+
68+
static BBState getConflictState(SILInstruction *yieldI) {
69+
assert(yieldI);
70+
return BBState(Conflict, yieldI);
71+
}
72+
73+
SILInstruction *getYieldInstruction() const {
74+
assert(yieldState == AfterYield || yieldState == Conflict);
75+
return yieldInst;
76+
}
77+
};
78+
79+
/// A structure that records an error found during the analysis along with
80+
/// some context information that will be used by diagnostics.
81+
struct YieldError {
82+
/// The kind of error.
83+
enum Kind { MultipleYield, ReturnBeforeYield, ReturnOnConflict } errorKind;
84+
/// The termination instruction where the error should be reported.
85+
SILInstruction *termInst;
86+
/// The input state when the error is encountered.
87+
BBState inState;
88+
89+
private:
90+
YieldError(Kind kind, SILInstruction *term, BBState state)
91+
: errorKind(kind), termInst(term), inState(state) {}
92+
93+
public:
94+
static YieldError getMultipleYieldError(YieldInst *yield, BBState state) {
95+
assert(state.yieldState != BBState::BeforeYield);
96+
return YieldError(MultipleYield, yield, state);
97+
}
98+
99+
static YieldError getReturnBeforeYieldError(ReturnInst *returnI,
100+
BBState state) {
101+
assert(state.yieldState == BBState::BeforeYield);
102+
return YieldError(ReturnBeforeYield, returnI, state);
103+
}
104+
105+
static YieldError getReturnOnConflict(ReturnInst *returnI, BBState state) {
106+
assert(state.yieldState == BBState::Conflict);
107+
return YieldError(ReturnOnConflict, returnI, state);
108+
}
109+
};
110+
111+
/// Transfer function of the data-flow analysis.
112+
///
113+
/// \param bb Basic block that should be processed
114+
/// \param inState BBState at the start of the basic block
115+
/// \param error out parameter that will contain information about
116+
/// an error that is detected.
117+
/// \return the state at the exit of the basic block if it can be computed
118+
/// and None otherwise.
119+
static Optional<BBState>
120+
transferStateThroughBasicBlock(SILBasicBlock *bb, BBState inState,
121+
Optional<YieldError> &error) {
122+
error = None;
123+
auto *term = bb->getTerminator();
124+
125+
if (auto *returnInst = dyn_cast<ReturnInst>(term)) {
126+
if (inState.yieldState == BBState::BeforeYield) {
127+
error = YieldError::getReturnBeforeYieldError(returnInst, inState);
128+
return None;
129+
}
130+
131+
if (inState.yieldState == BBState::Conflict) {
132+
error = YieldError::getReturnOnConflict(returnInst, inState);
133+
return None;
134+
}
135+
return inState;
136+
}
137+
138+
if (auto *yieldInst = dyn_cast<YieldInst>(term)) {
139+
if (inState.yieldState != BBState::BeforeYield) {
140+
error = YieldError::getMultipleYieldError(yieldInst, inState);
141+
return None;
142+
}
143+
144+
// If the current state is BeforeYield and if the basic block ends in a
145+
// yield the new state is AfterYield.
146+
return inState.getAfterYieldState(term);
147+
}
148+
149+
// We cannot have throws within generalized accessors.
150+
assert(!isa<ThrowInst>(term));
151+
152+
return inState;
153+
}
154+
155+
/// Merge operation of the data-flow analysis.
156+
///
157+
/// \param mergeBlock the basic block that is reached with two states
158+
/// \param oldState the previous state at the entry of the basic block
159+
/// \param newState the current state at the entry of the basic block
160+
/// \return the new state obtained by merging the oldState with the newState
161+
static BBState merge(SILBasicBlock *mergeBlock, BBState oldState,
162+
BBState newState) {
163+
auto oldYieldState = oldState.yieldState;
164+
auto newYieldState = newState.yieldState;
165+
166+
if (oldYieldState == BBState::Conflict) {
167+
return oldState;
168+
}
169+
170+
if (newYieldState == BBState::Conflict) {
171+
return newState;
172+
}
173+
174+
if (oldYieldState == newYieldState) {
175+
return oldState;
176+
}
177+
178+
// Here, one state is AfterYield and the other one is BeforeYield.
179+
// Merging them will result in Conflict.
180+
assert((newYieldState == BBState::AfterYield &&
181+
oldYieldState == BBState::BeforeYield) ||
182+
(newYieldState == BBState::BeforeYield &&
183+
oldYieldState == BBState::AfterYield));
184+
185+
SILInstruction *yieldInst = (newYieldState == BBState::AfterYield)
186+
? newState.getYieldInstruction()
187+
: oldState.getYieldInstruction();
188+
189+
return BBState::getConflictState(yieldInst);
190+
}
191+
192+
/// Perform a data-flow analysis to check whether there is exactly one
193+
/// yield before a return in every path in the control-flow graph.
194+
/// Diagnostics are not reported for nodes unreachable from the entry of
195+
/// the control-flow graph.
196+
void diagnoseYieldOnceUsage(SILFunction &fun) {
197+
llvm::DenseMap<SILBasicBlock *, BBState> bbToStateMap;
198+
SmallVector<SILBasicBlock *, 16> worklist;
199+
200+
auto *entryBB = fun.getEntryBlock();
201+
bbToStateMap.try_emplace(entryBB, BBState::getInitialState());
202+
worklist.push_back(entryBB);
203+
204+
// ReturnBeforeYield errors, which denote that no paths yield before
205+
// returning, are not diagnosed until the analysis completes, in order to
206+
// distinguish them from ReturnOnConflict errors, which happen when some
207+
// paths yield and some don't.
208+
Optional<YieldError> returnBeforeYieldError = None;
209+
210+
// The algorithm uses a worklist to propagate the state through basic
211+
// blocks until a fix point. Since the state lattice has height one, each
212+
// basic block will be visited at most twice, and at most once if there are
213+
// no conflicts (which are errors). The basic blocks are added to the
214+
// worklist in a breadth-first fashion. The order of visiting basic blocks
215+
// is not important for correctness, but it could change the errors
216+
// diagnosed when there are multiple errors. Breadth-first order diagnoses
217+
// errors along shorter paths to return.
218+
while (!worklist.empty()) {
219+
SILBasicBlock *bb = worklist.pop_back_val();
220+
const BBState &state = bbToStateMap.find(bb)->getSecond();
221+
222+
Optional<YieldError> errorResult = None;
223+
auto resultState = transferStateThroughBasicBlock(bb, state, errorResult);
224+
225+
if (!resultState.hasValue()) {
226+
auto error = errorResult.getValue();
227+
228+
// ReturnBeforeYield errors will not be reported until the analysis
229+
// completes. So record it and continue.
230+
if (error.errorKind == YieldError::ReturnBeforeYield) {
231+
if (!returnBeforeYieldError.hasValue()) {
232+
returnBeforeYieldError = error;
233+
}
234+
continue;
235+
}
236+
237+
emitDiagnostics(error, fun);
238+
return;
239+
}
240+
241+
auto nextState = resultState.getValue();
242+
243+
for (auto *succBB : bb->getSuccessorBlocks()) {
244+
// Optimistically try to set the state of the successor as next state.
245+
auto insertResult = bbToStateMap.try_emplace(succBB, nextState);
246+
247+
// If the insertion was successful, it means we are seeing the successor
248+
// for the first time. Add the successor to the worklist.
249+
if (insertResult.second) {
250+
worklist.insert(worklist.begin(), succBB);
251+
continue;
252+
}
253+
254+
// Here the successor already has a state. Merge the current and
255+
// previous states and propagate it if it is different from the
256+
// old state.
257+
const auto &oldState = insertResult.first->second;
258+
auto mergedState = merge(succBB, oldState, nextState);
259+
260+
if (mergedState.yieldState == oldState.yieldState)
261+
continue;
262+
263+
// Even though at this point there has to be an error since there is an
264+
// inconsistency between states coming along two different paths,
265+
// continue propagation of this conflict state to determine
266+
// whether this results in multiple-yields error or return-on-conflict
267+
// error.
268+
insertResult.first->second = mergedState;
269+
worklist.insert(worklist.begin(), succBB);
270+
}
271+
}
272+
273+
if (returnBeforeYieldError.hasValue()) {
274+
emitDiagnostics(returnBeforeYieldError.getValue(), fun);
275+
}
276+
}
277+
278+
void emitDiagnostics(YieldError &error, SILFunction &fun) {
279+
ASTContext &astCtx = fun.getModule().getASTContext();
280+
281+
switch (error.errorKind) {
282+
case YieldError::ReturnBeforeYield: {
283+
diagnose(astCtx, error.termInst->getLoc().getSourceLoc(),
284+
diag::return_before_yield);
285+
return;
286+
}
287+
case YieldError::MultipleYield: {
288+
diagnose(astCtx, error.termInst->getLoc().getSourceLoc(),
289+
diag::multiple_yields);
290+
291+
// Add a note that points to the previous yield.
292+
diagnose(astCtx,
293+
error.inState.getYieldInstruction()->getLoc().getSourceLoc(),
294+
diag::previous_yield);
295+
return;
296+
}
297+
case YieldError::ReturnOnConflict: {
298+
// Emit an error on the return statement.
299+
diagnose(astCtx, error.termInst->getLoc().getSourceLoc(),
300+
diag::possible_return_before_yield);
301+
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);
305+
}
306+
}
307+
}
308+
309+
/// The entry point to the transformation.
310+
void run() override {
311+
auto *fun = getFunction();
312+
313+
if (fun->getLoweredFunctionType()->getCoroutineKind() !=
314+
SILCoroutineKind::YieldOnce)
315+
return;
316+
317+
diagnoseYieldOnceUsage(*fun);
318+
}
319+
};
320+
321+
} // end anonymous namespace
322+
323+
SILTransform *swift::createYieldOnceCheck() {
324+
return new YieldOnceCheck();
325+
}

lib/SILOptimizer/PassManager/PassPipeline.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ static void addMandatoryOptPipeline(SILPassPipelinePlan &P) {
124124
P.addGuaranteedARCOpts();
125125
P.addDiagnoseUnreachable();
126126
P.addDiagnoseInfiniteRecursion();
127+
P.addYieldOnceCheck();
127128
P.addEmitDFDiagnostics();
128129
// Canonical swift requires all non cond_br critical edges to be split.
129130
P.addSplitNonCondBrCriticalEdges();

0 commit comments

Comments
 (0)