Skip to content

Commit

Permalink
Handle try_apply in the diagnostic unreachable-code pass.
Browse files Browse the repository at this point in the history
A somewhat more natural solution would be to replace the
normal successor with an edge to an unreachable block,
but that would require adding blocks during the iteration.

Add a small amount of SIL infrastructure for asking a
pred_iterator exactly which edge out of a terminator it
represents.

Swift SVN r28514
  • Loading branch information
rjmccall committed May 13, 2015
1 parent 3a2bb97 commit f262b13
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 4 deletions.
9 changes: 9 additions & 0 deletions include/swift/SIL/SILInstruction.h
Original file line number Diff line number Diff line change
Expand Up @@ -3717,6 +3717,15 @@ class TryApplyInstBase : public TermInst {
return DestBBs;
}

bool isNormalSuccessorRef(SILSuccessor *successor) const {
assert(successor == &DestBBs[0] || successor == &DestBBs[1]);
return successor == &DestBBs[0];
}
bool isErrorSuccessorRef(SILSuccessor *successor) const {
assert(successor == &DestBBs[0] || successor == &DestBBs[1]);
return successor == &DestBBs[1];
}

SILBasicBlock *getNormalBB() { return DestBBs[NormalIdx]; }
const SILBasicBlock *getNormalBB() const { return DestBBs[NormalIdx]; }
SILBasicBlock *getErrorBB() { return DestBBs[ErrorIdx]; }
Expand Down
3 changes: 2 additions & 1 deletion include/swift/SIL/SILSuccessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ class SILSuccessorIterator {
++copy;
return copy;
}


SILSuccessor *getSuccessorRef() const { return Cur; }
SILBasicBlock *operator*();
};

Expand Down
38 changes: 35 additions & 3 deletions lib/SILPasses/DiagnoseUnreachable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -407,8 +407,7 @@ static void setOutsideBlockUsesToUndef(SILInstruction *I) {
Use->set(SILUndef::get(Use->get().getType(), Mod));
}

static SILInstruction *getAsCallToNoReturn(SILInstruction *I,
SILBasicBlock &BB) {
static SILInstruction *getAsCallToNoReturn(SILInstruction *I) {
if (auto *AI = dyn_cast<ApplyInst>(I))
if (AI->getOrigCalleeType()->isNoReturn())
return AI;
Expand All @@ -425,6 +424,35 @@ static SILInstruction *getAsCallToNoReturn(SILInstruction *I,
return nullptr;
}

static SILInstruction *getPrecedingCallToNoReturn(SILBasicBlock &BB) {
// All the predecessors must satisfy this condition; pick the first
// as a representative. SILGen doesn't actually re-use blocks for
// the normal edge, but it's good to be prepared.
SILInstruction *first = nullptr;
for (auto i = BB.pred_begin(), e = BB.pred_end(); i != e; ++i) {
SILBasicBlock *predBB = *i;

// As a precaution, bail out if we have a self-loop. It's not
// clear what transformations (if any) on naive SILGen output
// would ever produce that, but still, don't do it. It's probably
// only possible in code that's already otherwise provable to be
// unreachable.
if (predBB == &BB) return nullptr;

// The predecessor must be the normal edge from a try_apply
// that invokes a noreturn function.
if (auto TAI = dyn_cast<TryApplyInst>((*i)->getTerminator())) {
if (TAI->getOrigCalleeType()->isNoReturn() &&
TAI->isNormalSuccessorRef(i.getSuccessorRef())) {
if (!first) first = TAI;
continue;
}
}
return nullptr;
}
return first;
}

static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
UnreachableUserCodeReportingState *State) {
auto I = BB.begin(), E = BB.end();
Expand All @@ -434,6 +462,10 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
// Collection of all instructions that should be deleted.
llvm::SmallVector<SILInstruction*, 32> ToBeDeleted;

// If all of the predecessor blocks end in a try_apply to a noreturn
// function, the entire block is dead.
NoReturnCall = getPrecedingCallToNoReturn(BB);

// Does this block contain a call to a noreturn function?
while (I != E) {
auto CurrentInst = I;
Expand Down Expand Up @@ -476,7 +508,7 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,

// Check if this instruction is the first call to noreturn in this block.
if (!NoReturnCall) {
NoReturnCall = getAsCallToNoReturn(CurrentInst, BB);
NoReturnCall = getAsCallToNoReturn(CurrentInst);
}
}

Expand Down
57 changes: 57 additions & 0 deletions test/SILPasses/diagnose_unreachable.sil
Original file line number Diff line number Diff line change
Expand Up @@ -368,3 +368,60 @@ bb0:
%z = builtin "unreachable"() : $()
unreachable
}

sil @throwing_noreturn : $@convention(thin) @noreturn () -> @error ErrorType

// CHECK-LABEL:sil @try_apply_1
// CHECK: bb1(
// CHECK-NEXT: unreachable
// CHECK: bb2(
// CHECK-NEXT: throw
sil @try_apply_1 : $@convention(thin) () -> @error ErrorType {
bb0:
%0 = function_ref @throwing_noreturn : $@convention(thin) @noreturn () -> @error ErrorType
try_apply %0() : $@convention(thin) @noreturn () -> @error ErrorType, normal bb1, error bb2
bb1(%1 : $()):
%2 = tuple ()
return %2 : $()
bb2(%3 : $ErrorType):
throw %3 : $ErrorType
}

// CHECK-LABEL:sil @try_apply_2
// CHECK: bb3(
// CHECK-NEXT: unreachable
// CHECK: bb4(
// CHECK-NEXT: throw
sil @try_apply_2 : $@convention(thin) (Builtin.Int1) -> @error ErrorType {
bb0(%0 : $Builtin.Int1):
%1 = function_ref @throwing_noreturn : $@convention(thin) @noreturn () -> @error ErrorType
cond_br %0, bb1, bb2
bb1:
try_apply %1() : $@convention(thin) @noreturn () -> @error ErrorType, normal bb3, error bb4
bb2:
try_apply %1() : $@convention(thin) @noreturn () -> @error ErrorType, normal bb3, error bb4
bb3(%2 : $()):
%3 = tuple ()
return %3 : $()
bb4(%4 : $ErrorType):
throw %4 : $ErrorType
}

// This should arguably be ill-formed.
// CHECK-LABEL:sil @try_apply_3
// CHECK: br bb1
// CHECK: bb1(
// CHECK-NOT: unreachable
// CHECK: try_apply
// CHECK: bb2(
// CHECK-NEXT: throw
sil @try_apply_3 : $@convention(thin) () -> @error ErrorType {
bb0:
%0 = tuple ()
br bb1(%0 : $())
bb1(%1 : $()):
%2 = function_ref @throwing_noreturn : $@convention(thin) @noreturn () -> @error ErrorType
try_apply %2() : $@convention(thin) @noreturn () -> @error ErrorType, normal bb1, error bb2
bb2(%3 : $ErrorType):
throw %3 : $ErrorType
}
12 changes: 12 additions & 0 deletions test/SILPasses/unreachable_code.swift
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,18 @@ public func testFailingCast(s:String) -> Int {
return s as! Int // expected-warning {{cast from 'String' to unrelated type 'Int' always fails}}
}

enum MyError : ErrorType { case A }

@noreturn func raise() throws { throw MyError.A }

func test_raise_1() throws -> Int {
try raise()
}

func test_raise_2() throws -> Int {
try raise() // expected-note {{a call to a noreturn function}}
try raise() // expected-warning {{will never be executed}}
}

while true {
}
Expand Down

0 comments on commit f262b13

Please sign in to comment.