Skip to content

Commit afaeca7

Browse files
committed
[SILOptimizer] Don't diagnose unreachable instructions that belong to no-return func
The pass is attempting to diagnose any user-written code that appears after the result of no-return function call. It has to skip any instructions that happen after the no-return call but are associated with it, such as `alloc_stack` to pass result of such call indirectly. This helps to avoid extraneous diagnostics for synthesized code i.e. a result builder with `buildExpression`: ``` static func buildExpression<T>(_ e: T) -> T { e } ``` The following example would produce extraneous warning: ``` switch <value> { case ... default: fatalError() } ``` because it is translated into: ``` switch <value> { case ... default: { var $__builderDefault = buildExpression(fatalError()) $__builderSwitch = buildEither(second: $__builderDefault) } } ``` In such cases all instructions that follow `fatalError()` call are synthesized except to `alloc_stack $Never` which is passed to `buildExpression`, that instruction is anchored on the `fatalError()` itself which is where the diagnostic would point, which is incorrect. Resolves: rdar://104775183
1 parent 3fd895a commit afaeca7

File tree

2 files changed

+92
-20
lines changed

2 files changed

+92
-20
lines changed

lib/SILOptimizer/Mandatory/DiagnoseUnreachable.cpp

Lines changed: 40 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,44 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
745745
// function, the entire block is dead.
746746
NoReturnCall = getPrecedingCallToNoReturn(BB);
747747

748+
// Diagnose the unreachable code within the same block as the call to
749+
// noreturn.
750+
auto diagnoseUnreachableCode = [&](SILInstruction *noReturnCall,
751+
SILInstruction *currInst) {
752+
if (DiagnosedUnreachableCode)
753+
return false;
754+
755+
// If current instruction belongs to the no-return call itself, skip it.
756+
//
757+
// It could happen when i.e. result has to be copied to be passed to
758+
// some call.
759+
if (currInst->getLoc().hasSameSourceLocation(noReturnCall->getLoc()))
760+
return false;
761+
762+
if (!isUserCode(currInst))
763+
return false;
764+
765+
// If we have an instruction that is an end_borrow, ignore it. This
766+
// happens when passing a guaranteed argument through generic code paths
767+
// to no return functions.
768+
if (isa<EndBorrowInst>(currInst))
769+
return false;
770+
771+
// If no-return instruction is not something we can point in code or
772+
// it's an explicit cast, skip it.
773+
if (!noReturnCall->getLoc().is<RegularLocation>() ||
774+
noReturnCall->getLoc().isASTNode<ExplicitCastExpr>())
775+
return false;
776+
777+
diagnose(BB.getModule().getASTContext(), currInst->getLoc().getSourceLoc(),
778+
diag::unreachable_code);
779+
diagnose(BB.getModule().getASTContext(),
780+
noReturnCall->getLoc().getSourceLoc(),
781+
diag::call_to_noreturn_note);
782+
783+
return true;
784+
};
785+
748786
// Does this block contain a call to a noreturn function?
749787
while (I != E) {
750788
auto *CurrentInst = &*I;
@@ -758,26 +796,8 @@ static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB,
758796
// We will need to delete the instruction later on.
759797
ToBeDeleted.push_back(CurrentInst);
760798

761-
// Diagnose the unreachable code within the same block as the call to
762-
// noreturn.
763-
if (isUserCode(CurrentInst) && !DiagnosedUnreachableCode) {
764-
// If we have an instruction that is an end_borrow, ignore it. This
765-
// happens when passing a guaranteed argument through generic code paths
766-
// to no return functions.
767-
if (!isa<EndBorrowInst>(CurrentInst)) {
768-
if (NoReturnCall->getLoc().is<RegularLocation>()) {
769-
if (!NoReturnCall->getLoc().isASTNode<ExplicitCastExpr>()) {
770-
diagnose(BB.getModule().getASTContext(),
771-
CurrentInst->getLoc().getSourceLoc(),
772-
diag::unreachable_code);
773-
diagnose(BB.getModule().getASTContext(),
774-
NoReturnCall->getLoc().getSourceLoc(),
775-
diag::call_to_noreturn_note);
776-
DiagnosedUnreachableCode = true;
777-
}
778-
}
779-
}
780-
}
799+
DiagnosedUnreachableCode |=
800+
diagnoseUnreachableCode(NoReturnCall, CurrentInst);
781801

782802
// We are going to bluntly remove these instructions. Change uses in
783803
// different basic blocks to undef. This is safe because all control flow

test/SILOptimizer/diagnose_unreachable.swift

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -518,3 +518,55 @@ func keypathWithDynamicLookup() {
518518
// when used in conjunction with a dynamicMemberLookup enum.
519519
let _ = \DynamicLookupEnum.innerEnum // no warning
520520
}
521+
522+
func test_no_warnings_with_fatalError_when_wrapped_in_buildExpression() {
523+
enum Either<T,U> {
524+
case first(T)
525+
case second(U)
526+
}
527+
528+
@resultBuilder
529+
struct MyBuilder {
530+
static func buildExpression<T>(_ e: T) -> T { e }
531+
static func buildBlock() -> () { }
532+
533+
static func buildBlock<T1>(_ t1: T1) -> T1 {
534+
return t1
535+
}
536+
537+
static func buildBlock<T1, T2>(_ t1: T1, _ t2: T2) -> (T1, T2) {
538+
return (t1, t2)
539+
}
540+
541+
static func buildBlock<T1, T2, T3>(_ t1: T1, _ t2: T2, _ t3: T3) -> (T1, T2, T3) {
542+
return (t1, t2, t3)
543+
}
544+
545+
static func buildEither<T,U>(first value: T) -> Either<T, U> {
546+
return .first(value)
547+
}
548+
549+
static func buildEither<T,U>(second value: U) -> Either<T, U> {
550+
return .second(value)
551+
}
552+
}
553+
554+
func test<T>(@MyBuilder _: (Int) -> T) {}
555+
556+
test {
557+
if $0 < 0 {
558+
fatalError() // ok, no warning even though fatalError() is wrapped
559+
} else if $0 > 0 {
560+
42
561+
} else {
562+
0
563+
}
564+
}
565+
566+
test {
567+
switch $0 {
568+
case 0: "0"
569+
default: fatalError() // Ok, no warning even though fatalError() is wrapped
570+
}
571+
}
572+
}

0 commit comments

Comments
 (0)