Skip to content

Commit 5ea28a7

Browse files
authored
Merge pull request swiftlang#38906 from eeckstein/fix-diagnose-infinite-recursion
DiagnoseInfiniteRecursion: fix a false warning caused by dead-end blocks
2 parents 831d772 + 14e7e4a commit 5ea28a7

File tree

2 files changed

+98
-74
lines changed

2 files changed

+98
-74
lines changed

lib/SILOptimizer/Mandatory/DiagnoseInfiniteRecursion.cpp

Lines changed: 86 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
#include "swift/SIL/ApplySite.h"
4545
#include "swift/SIL/MemAccessUtils.h"
4646
#include "swift/SIL/BasicBlockData.h"
47+
#include "swift/SIL/BasicBlockDatastructures.h"
4748
#include "swift/SILOptimizer/PassManager/Transforms.h"
4849
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
4950
#include "swift/SILOptimizer/Utils/Devirtualize.h"
@@ -275,38 +276,33 @@ struct BlockInfo {
275276
/// non-null if this block contains a recursive call.
276277
SILInstruction *recursiveCall;
277278

278-
/// The number of successors which reach a `return`.
279-
unsigned numSuccsNotReachingReturn;
279+
/// The number of successors which reach a recursive call, but not the
280+
/// function exit, i.e. successors for which
281+
/// reachesRecursiveCall && !reachesFunctionExit
282+
unsigned numSuccsReachingRecursiveCall;
280283

281284
/// True if the block has a terminator with an invariant condition.
282285
///
283286
/// Note: "invariant" means: invariant with respect to the expected invariants,
284287
/// which are passed to the constructor.
285288
bool hasInvariantCondition;
286289

287-
/// Is there any path from the this block to a function return, without going
290+
/// Is there any path from the this block to a function exit, without going
288291
/// through a recursive call?
289292
///
290-
/// This flag is propagated up the control flow, starting at returns.
291-
///
292293
/// Note that if memory is expected to be invariant, all memory-writing
293-
/// instructions are also considered as a "return".
294-
bool reachesReturn;
295-
296-
/// Is there any path from the entry to this block without going through a
297-
/// `reachesReturn` block.
298-
///
299-
/// This flag is propagated down the control flow, starting at entry. If this
300-
/// flag reaches a block with a recursiveCall, it means that it's an infinite
301-
/// recursive call.
302-
bool reachableFromEntry;
294+
/// instructions are also considered as a "function exit".
295+
bool reachesFunctionExit;
303296

297+
/// Is there any path from the this block to a recursive call?
298+
bool reachesRecursiveCall;
299+
304300
/// Get block information with expected \p invariants.
305301
BlockInfo(SILBasicBlock *block, Invariants invariants) :
306302
recursiveCall(nullptr),
307-
numSuccsNotReachingReturn(block->getNumSuccessors()),
303+
numSuccsReachingRecursiveCall(0),
308304
hasInvariantCondition(invariants.isInvariant(block->getTerminator())),
309-
reachesReturn(false), reachableFromEntry(false) {
305+
reachesFunctionExit(false), reachesRecursiveCall(false) {
310306
for (SILInstruction &inst : *block) {
311307
if (auto applySite = FullApplySite::isa(&inst)) {
312308
// Ignore blocks which call a @_semantics("programtermination_point").
@@ -319,22 +315,23 @@ struct BlockInfo {
319315
if (isRecursiveCall(applySite) &&
320316
invariants.hasInvariantArguments(applySite)) {
321317
recursiveCall = &inst;
318+
reachesRecursiveCall = true;
322319
return;
323320
}
324321
}
325322
if (invariants.isMemoryInvariant() && mayWriteToMemory(&inst)) {
326323
// If we are assuming that all memory is invariant, a memory-writing
327324
// instruction potentially breaks the infinite recursion loop. For the
328-
// sake of the anlaysis, it's like a function return.
329-
reachesReturn = true;
325+
// sake of the anlaysis, it's like a function exit.
326+
reachesFunctionExit = true;
330327
return;
331328
}
332329
}
333330
TermInst *term = block->getTerminator();
334331
if (term->isFunctionExiting() ||
335332
// Also treat non-assert-like unreachables as returns, like "exit()".
336333
term->isProgramTerminating()) {
337-
reachesReturn = true;
334+
reachesFunctionExit = true;
338335
}
339336
}
340337
};
@@ -383,91 +380,117 @@ class InfiniteRecursionAnalysis {
383380
return BlockInfo(block, invariants);
384381
}) { }
385382

386-
/// Propagates the `reachesReturn` flags up the control flow and returns true
387-
/// if the flag reaches the entry block.
388-
bool isEntryReachableFromReturn() {
389-
// Contains blocks for which the `reachesReturn` flag is set.
390-
SmallVector<SILBasicBlock *, 32> workList;
383+
/// Propagates the `reachesRecursiveCall` flags up the control flow.
384+
void propagateRecursiveCalls() {
385+
StackList<SILBasicBlock *> workList(blockInfos.getFunction());
386+
387+
// Initialize the workList with all blocks which contain recursive calls.
388+
for (auto bd : blockInfos) {
389+
if (bd.data.reachesRecursiveCall)
390+
workList.push_back(&bd.block);
391+
}
392+
393+
while (!workList.empty()) {
394+
SILBasicBlock *block = workList.pop_back_val();
395+
assert(blockInfos[block].reachesRecursiveCall);
396+
for (auto *pred : block->getPredecessorBlocks()) {
397+
BlockInfo &predInfo = blockInfos[pred];
398+
predInfo.numSuccsReachingRecursiveCall += 1;
399+
if (!predInfo.reachesRecursiveCall) {
400+
predInfo.reachesRecursiveCall = true;
401+
workList.push_back(pred);
402+
}
403+
}
404+
}
405+
}
406+
407+
/// Propagates the `reachesFunctionExit` flags up the control flow.
408+
void propagateFunctionExits() {
409+
StackList<SILBasicBlock *> workList(blockInfos.getFunction());
391410

392-
// Initialize the workList with all function-return blocks.
411+
// Initialize the workList with all function-exiting blocks.
393412
for (auto bd : blockInfos) {
394-
if (bd.data.reachesReturn)
413+
if (bd.data.reachesFunctionExit)
395414
workList.push_back(&bd.block);
396415
}
397416

398417
while (!workList.empty()) {
399418
SILBasicBlock *block = workList.pop_back_val();
419+
BlockInfo &info = blockInfos[block];
420+
assert(info.reachesFunctionExit);
400421
for (auto *pred : block->getPredecessorBlocks()) {
401422
BlockInfo &predInfo = blockInfos[pred];
402-
if (predInfo.reachesReturn ||
423+
424+
if (info.reachesRecursiveCall) {
425+
// Update `numSuccsReachingRecursiveCall`, because this counter
426+
// excludes successors which reach a function exit.
427+
assert(predInfo.numSuccsReachingRecursiveCall > 0);
428+
predInfo.numSuccsReachingRecursiveCall -= 1;
429+
}
430+
431+
if (predInfo.reachesFunctionExit ||
403432
// Recursive calls block the flag propagation.
404433
predInfo.recursiveCall != nullptr)
405434
continue;
406435

407-
assert(predInfo.numSuccsNotReachingReturn > 0);
408-
predInfo.numSuccsNotReachingReturn -= 1;
409-
410436
// This is the trick for handling invariant conditions: usually the
411-
// `reachesReturn` flag is propagated if _any_ of the successors has it
412-
// set.
437+
// `reachesFunctionExit` flag is propagated if _any_ of the successors
438+
// has it set.
413439
// For invariant conditions, it's only propagated if _all_ successors
414-
// have it set. If at least one of the successors reaches a recursive
415-
// call and this successor is taken once, it will be taken forever
416-
// (because the condition is invariant).
440+
// which reach recursive calls also reach a function exit.
441+
// If at least one of the successors reaches a recursive call (but not
442+
// a function exit) and this successor is taken once, it will be taken
443+
// forever (because the condition is invariant).
417444
if (predInfo.hasInvariantCondition &&
418-
predInfo.numSuccsNotReachingReturn > 0)
445+
predInfo.numSuccsReachingRecursiveCall > 0)
419446
continue;
420447

421-
predInfo.reachesReturn = true;
448+
predInfo.reachesFunctionExit = true;
422449
workList.push_back(pred);
423450
}
424451
}
425-
return blockInfos.entry().data.reachesReturn;
426452
}
453+
454+
/// Finds all infinite recursive calls reachable from the entry and issues
455+
/// warnings.
456+
/// Returns true if the function contains infinite recursive calls.
457+
bool issueWarningsForInfiniteRecursiveCalls() {
458+
const BlockInfo &entryInfo = blockInfos.entry().data;
459+
if (!entryInfo.reachesRecursiveCall || entryInfo.reachesFunctionExit)
460+
return false;
427461

428-
/// Propagates the `reachableFromEntry` flags down the control flow and
429-
/// issues a warning if it reaches a recursive call.
430-
/// Returns true, if at least one recursive call is found.
431-
bool findRecursiveCallsAndDiagnose() {
432-
SmallVector<SILBasicBlock *, 32> workList;
433-
auto entry = blockInfos.entry();
434-
entry.data.reachableFromEntry = true;
435-
workList.push_back(&entry.block);
462+
BasicBlockWorklist workList(blockInfos.getFunction());
463+
workList.push(&blockInfos.entry().block);
436464

437-
bool foundInfiniteRecursion = false;
438-
while (!workList.empty()) {
439-
SILBasicBlock *block = workList.pop_back_val();
465+
while (SILBasicBlock *block = workList.pop()) {
440466
if (auto *recursiveCall = blockInfos[block].recursiveCall) {
441467
blockInfos.getFunction()->getModule().getASTContext().Diags.diagnose(
442468
recursiveCall->getLoc().getSourceLoc(),
443469
diag::warn_infinite_recursive_call);
444-
foundInfiniteRecursion = true;
445470
continue;
446471
}
447472
for (auto *succ : block->getSuccessorBlocks()) {
448473
BlockInfo &succInfo = blockInfos[succ];
449-
if (!succInfo.reachesReturn && !succInfo.reachableFromEntry) {
450-
succInfo.reachableFromEntry = true;
451-
workList.push_back(succ);
452-
}
474+
if (succInfo.reachesRecursiveCall && !succInfo.reachesFunctionExit)
475+
workList.pushIfNotVisited(succ);
453476
}
454477
}
455-
return foundInfiniteRecursion;
478+
return true;
456479
}
457480

458481
public:
459482

460483
LLVM_ATTRIBUTE_USED void dump() {
461484
for (auto bd : blockInfos) {
462485
llvm::dbgs() << "bb" << bd.block.getDebugID()
463-
<< ": numSuccs= " << bd.data.numSuccsNotReachingReturn;
486+
<< ": numSuccs= " << bd.data.numSuccsReachingRecursiveCall;
464487
if (bd.data.recursiveCall)
465488
llvm::dbgs() << " hasRecursiveCall";
466489
if (bd.data.hasInvariantCondition)
467490
llvm::dbgs() << " hasInvariantCondition";
468-
if (bd.data.reachesReturn)
469-
llvm::dbgs() << " reachesReturn";
470-
if (bd.data.reachableFromEntry)
491+
if (bd.data.reachesFunctionExit)
492+
llvm::dbgs() << " reachesFunctionExit";
493+
if (bd.data.reachesRecursiveCall)
471494
llvm::dbgs() << " reachesRecursiveCall";
472495
llvm::dbgs() << '\n';
473496
}
@@ -477,20 +500,9 @@ class InfiniteRecursionAnalysis {
477500
/// Returns true, if at least one recursive call is found.
478501
static bool analyzeAndDiagnose(SILFunction *function, Invariants invariants) {
479502
InfiniteRecursionAnalysis analysis(function, invariants);
480-
if (analysis.isEntryReachableFromReturn())
481-
return false;
482-
483-
// Now we know that the function never returns.
484-
// There can be three cases:
485-
// 1. All paths end up in an abnormal program termination, like fatalError().
486-
// We don't want to warn about this. It's probably intention.
487-
// 2. There is an infinite loop.
488-
// We don't want to warn about this either. Maybe it's intention. Anyway,
489-
// this case is handled by the DiagnoseUnreachable pass.
490-
// 3. There is an infinite recursion.
491-
// That's what we are interested in. We do a forward propagation to find
492-
// the actual infinite recursive call(s) - if any.
493-
return analysis.findRecursiveCallsAndDiagnose();
503+
analysis.propagateRecursiveCalls();
504+
analysis.propagateFunctionExits();
505+
return analysis.issueWarningsForInfiniteRecursiveCalls();
494506
}
495507
};
496508

test/SILOptimizer/infinite_recursion.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@ func multipleArgsNoWarning(_ x : Int, _ y : Int) {
103103
}
104104
}
105105

106+
struct A {}
107+
struct B {}
108+
109+
func deadEndBlockInElseBranch(_ x : Int) {
110+
if x != 0 {
111+
deadEndBlockInElseBranch(x - 1) // no warning
112+
} else {
113+
_ = unsafeBitCast(A(), to: B.self)
114+
}
115+
}
116+
117+
106118
struct Str {
107119
var x = 27
108120

0 commit comments

Comments
 (0)