//===--- DiagnoseUnreachable.cpp - Diagnose unreachable code --------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "sil-diagnose-unreachable" #include "swift/AST/DiagnosticsSIL.h" #include "swift/AST/Expr.h" #include "swift/AST/Pattern.h" #include "swift/AST/Stmt.h" #include "swift/Basic/Assertions.h" #include "swift/Basic/Defer.h" #include "swift/SIL/MemAccessUtils.h" #include "swift/SIL/Projection.h" #include "swift/SIL/SILArgument.h" #include "swift/SIL/SILBuilder.h" #include "swift/SIL/SILUndef.h" #include "swift/SIL/TerminatorUtils.h" #include "swift/SIL/BasicBlockDatastructures.h" #include "swift/SIL/OwnershipUtils.h" #include "swift/SILOptimizer/PassManager/Passes.h" #include "swift/SILOptimizer/PassManager/Transforms.h" #include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/Debug.h" using namespace swift; STATISTIC(NumBlocksRemoved, "Number of unreachable basic blocks removed"); STATISTIC(NumInstructionsRemoved, "Number of unreachable instructions removed"); STATISTIC(NumTerminatorsFolded, "Number of terminators folded"); STATISTIC(NumBasicBlockArgsPropagated, "Number of basic block arguments propagated"); template static void diagnose(ASTContext &Context, SourceLoc loc, Diag diag, U &&...args) { Context.Diags.diagnose(loc, diag, std::forward(args)...); } enum class UnreachableKind { FoldedBranch, FoldedSwitchEnum, NoreturnCall, UnavailableEnumElement, }; /// Information about a folded conditional branch instruction: it's location /// and whether the condition evaluated to true or false. struct UnreachableInfo { UnreachableKind Kind; /// The location of the instruction that caused the unreachability. SILLocation Loc; /// If this is the FoldedBranch kind, specifies if the condition is /// always true. bool CondIsAlwaysTrue; }; /// \class UnreachableUserCodeReportingState Contains extra state we need to /// communicate from condition branch folding stage to the unreachable blocks /// removal stage of the path. /// /// To report unreachable user code, we detect the blocks that contain user /// code and are not reachable (along any of the preceding paths). Note that we /// only want to report the first statement on the unreachable path. Keeping /// the info about which branch folding had produced the unreachable block makes /// it possible. class UnreachableUserCodeReportingState { public: UnreachableUserCodeReportingState(SILFunction *F) : PossiblyUnreachableBlocks(F), BlocksWithErrors(F) {} /// The set of top-level blocks that became immediately unreachable due /// to conditional branch folding, etc. /// /// This is a SetVector since several blocks may lead to the same error /// report and we iterate through these when producing the diagnostic. BasicBlockSetVector PossiblyUnreachableBlocks; /// The set of blocks in which we reported unreachable code errors. /// These are used to ensure that we don't issue duplicate reports. /// /// Note, this set is different from the PossiblyUnreachableBlocks as these /// are the blocks that do contain user code and they might not be immediate /// successors of a folded branch. BasicBlockSet BlocksWithErrors; /// A map from the PossiblyUnreachableBlocks to the folded conditional /// branches that caused each of them to be unreachable. This extra info is /// used to enhance the diagnostics. llvm::DenseMap MetaMap; }; /// Propagate/remove basic block input values when all predecessors /// supply the same arguments. /// /// NOTE: Since BranchInst always forwards guaranteed and owned parameters the /// same way (like owned parameters), we do not need to add any special handling /// for guaranteed parameters here. This is because if all of the incoming /// values into my guaranteed phi is the same, then we know that said incoming /// value must dominate the phi by definition. static void propagateBasicBlockArgs(SILBasicBlock &BB) { // This functions would simplify the code as following: // // bb0: // br bb2(%1 : $Builtin.Int1, %2 : $Builtin.Int1) // bb1: // br bb2(%1 : $Builtin.Int1, %2 : $Builtin.Int1) // bb2(%3 : $Builtin.Int1, %4 : $Builtin.Int1): // use(%3 : $Builtin.Int1) // use(%4 : $Builtin.Int1) // => // bb0: // br bb2 // bb1: // br bb2 // bb2: // use(%1 : $Builtin.Int1) // use(%2 : $Builtin.Int1) // If there are no predecessors or no arguments, there is nothing to do. if (BB.pred_empty() || BB.args_empty()) return; // Check if all the predecessors supply the same arguments to the BB. SmallVector Args; bool checkArgs = false; for (SILBasicBlock::pred_iterator PI = BB.pred_begin(), PE = BB.pred_end(); PI != PE; ++PI) { SILBasicBlock *PredB = *PI; // We are only simplifying cases where all predecessors are // unconditional branch instructions. if (!isa(PredB->getTerminator())) return; BranchInst *BI = cast(PredB->getTerminator()); unsigned Idx = 0; assert(!BI->getArgs().empty()); for (OperandValueArrayRef::iterator AI = BI->getArgs().begin(), AE = BI->getArgs().end(); AI != AE; ++AI, ++Idx) { // When processing the first predecessor, record the arguments. if (!checkArgs) Args.push_back(*AI); else // On each subsequent predecessor, check the arguments. if (Args[Idx] != *AI) return; } // After the first branch is processed, the arguments vector is populated. assert(!Args.empty()); checkArgs = true; } // If we've reached this point, the optimization is valid, so optimize. // We know that the incoming arguments from all predecessors are the same, // so just use them directly and remove the basic block parameters. // Drop the arguments from the branch instructions by creating a new branch // instruction and deleting the old one. llvm::SmallVector ToBeDeleted; for (SILBasicBlock::pred_iterator PI = BB.pred_begin(), PE = BB.pred_end(); PI != PE; ++PI) { SILBasicBlock *PredB = *PI; BranchInst *BI = cast(PredB->getTerminator()); SILBuilderWithScope Bldr(PredB, BI); Bldr.createBranch(BI->getLoc(), BI->getDestBB()); ToBeDeleted.push_back(BI); } // Drop the parameters from basic blocks and replace all uses with the passed // in arguments. unsigned Idx = 0; for (SILBasicBlock::arg_iterator AI = BB.args_begin(), AE = BB.args_end(); AI != AE; ++AI, ++Idx) { // FIXME: These could be further propagatable now, we might want to move // this to CCP and trigger another round of copy propagation. SILArgument *Arg = *AI; // We were able to fold, so all users should use the new folded value. if (auto *bfi = getBorrowedFromUser(Arg)) { bfi->replaceAllUsesWith(Args[Idx]); bfi->eraseFromParent(); } else { Arg->replaceAllUsesWith(Args[Idx]); } ++NumBasicBlockArgsPropagated; } // Remove args from the block. BB.dropAllArguments(); // The old branch instructions are no longer used, erase them. recursivelyDeleteTriviallyDeadInstructions(ToBeDeleted, true); NumInstructionsRemoved += ToBeDeleted.size(); } static bool constantFoldEnumTerminator(SILBasicBlock &BB, UnreachableUserCodeReportingState *State, SwitchEnumTermInst SUI, EnumElementDecl *TheEnumElem, EnumInst *EnumInst) { SILBasicBlock *TheSuccessorBlock = nullptr; int ReachableBlockIdx = -1; for (unsigned Idx = 0; Idx < SUI.getNumCases(); ++Idx) { const EnumElementDecl *EI; SILBasicBlock *BI; std::tie(EI, BI) = SUI.getCase(Idx); if (EI == TheEnumElem) { TheSuccessorBlock = BI; ReachableBlockIdx = Idx; break; } } SILBasicBlock *DB = nullptr; if (!TheSuccessorBlock) { if (SUI.hasDefault()) { DB = SUI.getDefaultBB(); if (!isa(DB->getTerminator())) { TheSuccessorBlock = DB; ReachableBlockIdx = SUI.getNumCases(); } } } // Not fully covered switches will be diagnosed later. SILGen represents // them with a Default basic block with an unreachable instruction. // We are going to produce an error on all unreachable instructions not // eliminated by DCE. if (!TheSuccessorBlock) return false; // Replace the switch with a branch to the TheSuccessorBlock. SILBuilderWithScope B(&BB, SUI); SILLocation Loc = SUI->getLoc(); if (!TheSuccessorBlock->args_empty()) { // If the successor block that we are looking at is the default block, // we create an argument not for the enum case, but for the original // value. SILValue branchOperand; if (TheSuccessorBlock != DB) { branchOperand = B.createUncheckedEnumData(Loc, EnumInst, TheEnumElem); } else { branchOperand = EnumInst; } B.createBranch(Loc, TheSuccessorBlock, branchOperand); } else B.createBranch(Loc, TheSuccessorBlock); // Produce diagnostic info if we are not within an inlined function or // template instantiation. // FIXME: Do not report if we are within a template instantiation. assert(ReachableBlockIdx >= 0); if (Loc.is() && State) { // Find the first unreachable block in the switch so that we could use // it for better diagnostics. SILBasicBlock *UnreachableBlock = nullptr; if (SUI.getNumCases() > 1) { // More than one case. UnreachableBlock = (ReachableBlockIdx == 0) ? SUI.getCase(1).second : SUI.getCase(0).second; } else { if (SUI.getNumCases() == 1 && SUI.hasDefault()) { // One case and a default. UnreachableBlock = (ReachableBlockIdx == 0) ? SUI.getDefaultBB() : SUI.getCase(0).second; } } // Generate diagnostic info. if (UnreachableBlock && !State->PossiblyUnreachableBlocks.contains(UnreachableBlock)) { State->PossiblyUnreachableBlocks.insert(UnreachableBlock); State->MetaMap.insert(std::pair( UnreachableBlock, UnreachableInfo{UnreachableKind::FoldedSwitchEnum, Loc, true})); } } LLVM_DEBUG(llvm::dbgs() << "Folding terminator: " << **SUI); recursivelyDeleteTriviallyDeadInstructions(SUI, true); ++NumTerminatorsFolded; return true; } static bool constantFoldEnumAddrTerminator( SILBasicBlock &BB, UnreachableUserCodeReportingState *State, SwitchEnumTermInst SUI, const EnumElementDecl *TheEnumElem) { SILBasicBlock *TheSuccessorBlock = nullptr; int ReachableBlockIdx = -1; for (unsigned Idx = 0; Idx < SUI.getNumCases(); ++Idx) { const EnumElementDecl *EI; SILBasicBlock *BI; std::tie(EI, BI) = SUI.getCase(Idx); if (EI == TheEnumElem) { TheSuccessorBlock = BI; ReachableBlockIdx = Idx; break; } } SILBasicBlock *DB = nullptr; if (!TheSuccessorBlock) { if (SUI.hasDefault()) { DB = SUI.getDefaultBB(); if (!isa(DB->getTerminator())) { TheSuccessorBlock = DB; ReachableBlockIdx = SUI.getNumCases(); } } } // Not fully covered switches will be diagnosed later. SILGen represents // them with a Default basic block with an unreachable instruction. // We are going to produce an error on all unreachable instructions not // eliminated by DCE. if (!TheSuccessorBlock) return false; // Replace the switch with a branch to the TheSuccessorBlock. SILBuilderWithScope B(&BB, SUI); SILLocation Loc = SUI->getLoc(); B.createBranch(Loc, TheSuccessorBlock); // Produce diagnostic info if we are not within an inlined function or // template instantiation. // FIXME: Do not report if we are within a template instantiation. assert(ReachableBlockIdx >= 0); if (Loc.is() && State) { // Find the first unreachable block in the switch so that we could use // it for better diagnostics. SILBasicBlock *UnreachableBlock = nullptr; if (SUI.getNumCases() > 1) { // More than one case. UnreachableBlock = (ReachableBlockIdx == 0) ? SUI.getCase(1).second : SUI.getCase(0).second; } else { if (SUI.getNumCases() == 1 && SUI.hasDefault()) { // One case and a default. UnreachableBlock = (ReachableBlockIdx == 0) ? SUI.getDefaultBB() : SUI.getCase(0).second; } } // Generate diagnostic info. if (UnreachableBlock && !State->PossiblyUnreachableBlocks.contains(UnreachableBlock)) { State->PossiblyUnreachableBlocks.insert(UnreachableBlock); State->MetaMap.insert(std::pair( UnreachableBlock, UnreachableInfo{UnreachableKind::FoldedSwitchEnum, Loc, true})); } } LLVM_DEBUG(llvm::dbgs() << "Folding terminator: " << *SUI); recursivelyDeleteTriviallyDeadInstructions(SUI, true); ++NumTerminatorsFolded; return true; } static InjectEnumAddrInst * getAllocStackSingleInitializingInjectEnumAddr(SwitchEnumAddrInst *SEAI) { auto *stackSlot = dyn_cast(SEAI->getOperand()); if (!stackSlot) return nullptr; LLVM_DEBUG(llvm::dbgs() << "Visiting Stack: " << *stackSlot); InjectEnumAddrInst *singleInitializer = nullptr; InitEnumDataAddrInst *singleInitializerAddr = nullptr; SmallVector worklist(stackSlot->use_begin(), stackSlot->use_end()); LLVM_DEBUG(SWIFT_DEFER { llvm::dbgs() << "Exiting!\n"; }); while (worklist.size()) { auto *op = worklist.pop_back_val(); LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *op->getUser()); if (auto *svi = Projection::isAddressProjection(op->getUser())) { LLVM_DEBUG(llvm::dbgs() << "Address projection. Continuing\n"); llvm::copy(svi->getUses(), std::back_inserter(worklist)); continue; } auto *user = op->getUser(); // Skip our self. if (user == SEAI) { LLVM_DEBUG(llvm::dbgs() << "Skipping SEAI.\n"); continue; } if (isa(user) || isa(user) || isa(user) || isa(user)) { LLVM_DEBUG(llvm::dbgs() << "Skipping loads/lifetime ends\n"); continue; } // If we are reading from the memory we are ok. if (auto *cai = dyn_cast(user)) { if (cai->getDest() == op->get() || cai->isTakeOfSrc() == IsTake) { LLVM_DEBUG(llvm::dbgs() << "Found cai taking from src. Bailing!\n"); return nullptr; } LLVM_DEBUG(llvm::dbgs() << "Skipping!\n"); continue; } // Stash the initializer addr. We want to make sure it doesn't // escape after we process. if (auto *iedai = dyn_cast(user)) { if (singleInitializerAddr) { LLVM_DEBUG(llvm::dbgs() << "Multiple InitEnumDataAddrInst?!\n"); return nullptr; } singleInitializerAddr = iedai; LLVM_DEBUG(llvm::dbgs() << "Continuing\n"); continue; } if (auto *ieai = dyn_cast(user)) { // If single initializer is already set, if (singleInitializer) { LLVM_DEBUG(llvm::dbgs() << "Multiple InitEnumDataAddrInst?!\n"); return nullptr; } singleInitializer = ieai; LLVM_DEBUG(llvm::dbgs() << "Continuing\n"); continue; } LLVM_DEBUG(llvm::dbgs() << "Bailing at end of loop!\n"); return nullptr; } LLVM_DEBUG(llvm::dbgs() << "After Loop\n"); // If we didn't find a single initializer bail. We were initialized // multiple times suggesting we are not actually looking at a SILGen // temporary. if (!singleInitializer) { LLVM_DEBUG(llvm::dbgs() << "Did not find single initializer! Bailing!\n"); return nullptr; } // If we didn't have an addr, then it means we had a case without a // payload. if (!singleInitializerAddr) { assert(!singleInitializer->getElement()->hasAssociatedValues()); LLVM_DEBUG(llvm::dbgs() << "Did not find single initializer addr! Bailing!\n"); return singleInitializer; } // Otherwise, make sure we are initialized only once and never // escape. llvm::copy(singleInitializerAddr->getUses(), std::back_inserter(worklist)); bool foundInitializer = false; while (worklist.size()) { auto *op = worklist.pop_back_val(); LLVM_DEBUG(llvm::dbgs() << "Read only check for: " << *op->getUser()); // Look through projections. if (auto *svi = Projection::isAddressProjection(op->getUser())) { llvm::copy(svi->getUses(), std::back_inserter(worklist)); continue; } // Skip memory initializing operands. We should only ever see one // since SILGen always initializes temporary allocations (our // target) that way. if (isa(op->getUser())) { if (foundInitializer) { LLVM_DEBUG(llvm::dbgs() << "Found multiple initializers! Bailing!\n"); return nullptr; } foundInitializer = true; continue; } if (auto *cai = dyn_cast(op->getUser())) { if (cai->getDest() != op->get() || cai->isInitializationOfDest() != IsInitialization) { return nullptr; } if (foundInitializer) { LLVM_DEBUG(llvm::dbgs() << "Found multiple initializers! Bailing!\n"); return nullptr; } foundInitializer = true; continue; } // Anything else consider unacceptable. LLVM_DEBUG(llvm::dbgs() << "Found unknown addr initializer\n"); return nullptr; } // If we did not find a single address initializer, bail. if (!foundInitializer) return nullptr; return singleInitializer; } static bool constantFoldTerminator(SILBasicBlock &BB, UnreachableUserCodeReportingState *State) { TermInst *TI = BB.getTerminator(); // Process conditional branches with constant conditions. if (auto *CBI = dyn_cast(TI)) { SILValue V = CBI->getCondition(); SILLocation Loc = CBI->getLoc(); if (IntegerLiteralInst *ConstCond = dyn_cast_or_null(V)) { SILBuilderWithScope B(&BB, CBI); // Determine which of the successors is unreachable and create a new // terminator that only branches to the reachable successor. SILBasicBlock *UnreachableBlock = nullptr; bool CondIsTrue = false; if (ConstCond->getValue() == APInt(1, /*value*/ 0, false)) { B.createBranch(Loc, CBI->getFalseBB(), CBI->getFalseArgs()); UnreachableBlock = CBI->getTrueBB(); } else { assert(ConstCond->getValue() == APInt(1, /*value*/ 1, false) && "Our representation of true/false does not match."); B.createBranch(Loc, CBI->getTrueBB(), CBI->getTrueArgs()); UnreachableBlock = CBI->getFalseBB(); CondIsTrue = true; } recursivelyDeleteTriviallyDeadInstructions(TI, true); ++NumInstructionsRemoved; // Produce an unreachable code warning for this basic block if it // contains user code (only if we are not within an inlined function or a // template instantiation). // FIXME: Do not report if we are within a template instantiation. // FIXME: Checking for LabeledConditionalStmt is a hack; it's meant to // catch cases where we have a #available or similar non-expression // condition that was trivially true or false. In these cases we expect // the unreachable block to be reachable on another platform and shouldn't // emit any warnings about it; if this is not the case it's Sema's // responsibility to warn about it. if (Loc.is() && State && !State->PossiblyUnreachableBlocks.contains(UnreachableBlock) && !Loc.isASTNode()) { // If this is the first time we see this unreachable block, store it // along with the folded branch info. State->PossiblyUnreachableBlocks.insert(UnreachableBlock); State->MetaMap.insert( std::pair( UnreachableBlock, UnreachableInfo{UnreachableKind::FoldedBranch, Loc, CondIsTrue})); } ++NumTerminatorsFolded; return true; } } // Constant fold switch enum. // %1 = enum $Bool, #Bool.false!unionelt // switch_enum %1 : $Bool, case #Bool.true!unionelt: bb1, // case #Bool.false!unionelt: bb2 // => // br bb2 if (auto *SEI = dyn_cast(TI)) { if (auto *TheEnum = dyn_cast(SEI->getOperand())) { return constantFoldEnumTerminator(BB, State, SEI, TheEnum->getElement(), TheEnum); } } if (auto *SEAI = dyn_cast(TI)) { // We look for an alloc_stack that never escapes and that is initialized // only once. This ensures we only need to find one initialization. This is // a common pattern when unwrapping optional values in transparent // functions. // // TODO: This needs a better name. if (auto *IEAI = getAllocStackSingleInitializingInjectEnumAddr(SEAI)) { return constantFoldEnumAddrTerminator(BB, State, SEAI, IEAI->getElement()); } } // Constant fold switch int. // %1 = integer_literal $Builtin.Int64, 2 // switch_value %1 : $Builtin.Int64, case 1: bb1, case 2: bb2 // => // br bb2 if (auto *SUI = dyn_cast(TI)) { if (IntegerLiteralInst *SwitchVal = dyn_cast(SUI->getOperand())) { SILBasicBlock *TheSuccessorBlock = nullptr; for (unsigned Idx = 0; Idx < SUI->getNumCases(); ++Idx) { APInt AI; SILValue EI; SILBasicBlock *BI; std::tie(EI, BI) = SUI->getCase(Idx); // TODO: Check that EI is really an IntegerLiteralInst AI = dyn_cast(EI)->getValue(); if (AI == SwitchVal->getValue()) TheSuccessorBlock = BI; } if (!TheSuccessorBlock) if (SUI->hasDefault()) TheSuccessorBlock = SUI->getDefaultBB(); // Add the branch instruction with the block. if (TheSuccessorBlock) { SILBuilderWithScope B(&BB, TI); B.createBranch(TI->getLoc(), TheSuccessorBlock); recursivelyDeleteTriviallyDeadInstructions(TI, true); ++NumTerminatorsFolded; return true; } // TODO: Warn on unreachable user code here as well. } } return false; } /// Check if this instruction corresponds to user-written code. static bool isUserCode(const SILInstruction *I) { SILLocation Loc = I->getLoc(); if (Loc.isAutoGenerated()) return false; // Branch instructions are not user code. These could belong to the control // flow statement we are folding (ex: while loop). // Also, unreachable instructions are not user code, they are "expected" in // unreachable blocks. if ((isa(I) || isa(I)) && Loc.is()) return false; // If the instruction corresponds to user-written return or some other // statement, we know it corresponds to user code. if (Loc.is() || Loc.is()) return !Loc.isImplicit(); return false; } static void setOutsideBlockUsesToUndef(SILInstruction *I) { if (!I->hasUsesOfAnyResult()) return; SILBasicBlock *BB = I->getParent(); // Replace all uses outside of I's basic block by undef. llvm::SmallVector Uses; for (auto result : I->getResults()) Uses.append(result->use_begin(), result->use_end()); for (auto *Use : Uses) if (Use->getUser()->getParent() != BB) Use->set(SILUndef::get(Use->get())); } static SILInstruction *getAsCallToNoReturn(SILInstruction *I) { if (auto *AI = dyn_cast(I)) if (AI->isCalleeNoReturn()) return AI; if (auto *BI = dyn_cast(I)) { if (BI->getModule().isNoReturnBuiltinOrIntrinsic(BI->getName())) return BI; } // These appear in accessors for stored properties with uninhabited // type. Since a type containing an uninhabited stored property is // itself uninhabited, we treat these identically to fatalError(), etc. if (auto *SEI = dyn_cast(I)) { if (SEI->getType().getASTType()->isUninhabited()) return SEI; } if (auto *SEAI = dyn_cast(I)) { if (SEAI->getType().getASTType()->isUninhabited()) return SEAI; } 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((*i)->getTerminator())) { if (TAI->isCalleeNoReturn() && TAI->isNormalSuccessorRef(i.getSuccessorRef())) { if (!first) first = TAI; continue; } } return nullptr; } return first; } static bool isUnavailableCodeReachedCall(SILInstruction *I) { if (auto *AI = dyn_cast(I)) if (AI->hasSemantics(SEMANTICS_UNAVAILABLE_CODE_REACHED)) return true; return false; } static bool simplifyBlocksWithCallsToNoReturn(SILBasicBlock &BB, UnreachableUserCodeReportingState *State) { auto I = BB.begin(), E = BB.end(); bool DiagnosedUnreachableCode = false; SILInstruction *NoReturnCall = nullptr; // Collection of all instructions that should be deleted. llvm::SmallVector ToBeDeleted; // If all of the predecessor blocks end in a try_apply to a noreturn // function, the entire block is dead. NoReturnCall = getPrecedingCallToNoReturn(BB); // Diagnose the unreachable code within the same block as the call to // noreturn. auto diagnoseUnreachableCode = [&](SILInstruction *noReturnCall, SILInstruction *currInst) { if (DiagnosedUnreachableCode) return false; // If current instruction belongs to the no-return call itself, skip it. // // It could happen when i.e. result has to be copied to be passed to // some call. if (currInst->getLoc().hasSameSourceLocation(noReturnCall->getLoc())) return false; if (!isUserCode(currInst)) return false; // If we have an instruction that is an end_borrow, ignore it. This // happens when passing a guaranteed argument through generic code paths // to no return functions. if (isa(currInst)) return false; // If we have an ignored use whose operand is our no return call, ignore it. if (auto *i = dyn_cast(currInst)) { // This handles try_apply, apply, begin_apply. if (auto *inst = i->getOperand()->getDefiningInstructionOrTerminator(); inst && inst == noReturnCall) { return false; } } // destroy_value [dead_end] instructions are inserted at the availability // boundary by lifetime completion. Such instructions correctly mark the // lifetime boundary of the destroyed value and never arise from dead user // code. auto *dvi = dyn_cast(currInst); if (dvi && dvi->isDeadEnd()) return false; // If no-return instruction is not something we can point in code or // it's an explicit cast, skip it. if (!noReturnCall->getLoc().is() || noReturnCall->getLoc().isASTNode()) return false; // If the no-return instruction is a call to the unavailable code reached // diagnostic function then we assume that the call was inserted by the // compiler because the function is semantically unavailable. Diagnosing the // user written body of the function as unreachable would be redundant. if (isUnavailableCodeReachedCall(noReturnCall)) return false; diagnose(BB.getModule().getASTContext(), currInst->getLoc().getSourceLoc(), diag::unreachable_code); diagnose(BB.getModule().getASTContext(), noReturnCall->getLoc().getSourceLoc(), diag::call_to_noreturn_note); return true; }; // Does this block contain a call to a noreturn function? while (I != E) { auto *CurrentInst = &*I; // Move the iterator before we remove instructions to avoid iterator // invalidation issues. ++I; // Remove all instructions following the noreturn call. if (NoReturnCall) { // We will need to delete the instruction later on. ToBeDeleted.push_back(CurrentInst); DiagnosedUnreachableCode |= diagnoseUnreachableCode(NoReturnCall, CurrentInst); // We are going to bluntly remove these instructions. Change uses in // different basic blocks to undef. This is safe because all control flow // created by transparent inlining of functions applications after a call // to a 'noreturn' function is control dependent on the call to the // noreturn function and therefore dead. setOutsideBlockUsesToUndef(CurrentInst); ++NumInstructionsRemoved; continue; } // Check if this instruction is the first call to noreturn in this block. if (!NoReturnCall) { NoReturnCall = getAsCallToNoReturn(CurrentInst); } } if (!NoReturnCall) return false; // If the call is to the 'unreachable' builtin, then remove the call, // as it is redundant with the actual unreachable terminator. if (auto Builtin = dyn_cast(NoReturnCall)) { if (Builtin->getName().str() == "unreachable") ToBeDeleted.push_back(NoReturnCall); } // Record the diagnostic info. if (!DiagnosedUnreachableCode && NoReturnCall->getLoc().is() && State){ for (auto SI = BB.succ_begin(), SE = BB.succ_end(); SI != SE; ++SI) { SILBasicBlock *UnreachableBlock = *SI; if (!State->PossiblyUnreachableBlocks.contains(UnreachableBlock)) { // If this is the first time we see this unreachable block, store it // along with the noreturn call info. State->PossiblyUnreachableBlocks.insert(UnreachableBlock); State->MetaMap.insert( std::pair( UnreachableBlock, UnreachableInfo{UnreachableKind::NoreturnCall, NoReturnCall->getLoc(), true })); } } } auto *Scope = NoReturnCall->getDebugScope(); recursivelyDeleteTriviallyDeadInstructions(ToBeDeleted, true); NumInstructionsRemoved += ToBeDeleted.size(); // Add an unreachable terminator. The terminator has an invalid source // location to signal to the DataflowDiagnostic pass that this code does // not correspond to user code. // Share the scope with the preceding BB. This causes the debug info to be // much smaller and easier to read, but otherwise has no effect. SILBuilder B(&BB); B.setCurrentDebugScope(Scope); B.createUnreachable(ArtificialUnreachableLocation()); return true; } /// Replaces `switch_enum` / `switch_enum_addr` instructions that have cases /// matching unavailable enum elements with new instructions that have those /// cases removed since unavailable enum elements cannot be instantiated at /// run time. /// /// If this pass removes a case, it will add a default case that traps if there /// was not already an existing default case. This artificial default case /// serves two purposes. The first is to ensure the new switch instruction still /// "covers" the entire enum as required by SIL verification (this analysis /// could be changed in the future to ignore unavailable elements). Secondly, /// the trapping default case may help developers catch issues at runtime where /// UB has been invoked and an unavailable element has been instantiated /// unexpectedly. Ideally, this debugging aid would be available consistently, /// but since we cannot leave any references to the unavailable elements in SIL /// it is not possible to transform switch instructions that already have /// default cases in such a way that unavailable elements would be detected. static bool eliminateSwitchDispatchOnUnavailableElements( SILBasicBlock &BB, UnreachableUserCodeReportingState *State) { SwitchEnumTermInst SWI(BB.getTerminator()); if (!SWI) return false; EnumDecl *ED = SWI->getOperand(0)->getType().getEnumOrBoundGenericEnum(); assert(ED && "operand is not an enum"); // No need to check the instruction if all elements are available. if (!ED->hasCasesUnavailableDuringLowering()) return false; SILLocation Loc = SWI->getLoc(); bool DidRemoveUnavailableCase = false; SmallVector, 4> NewCaseBBs; for (unsigned i : range(SWI.getNumCases())) { auto CaseBB = SWI.getCase(i); auto availableAtr = CaseBB.first->getUnavailableAttr(); if (availableAtr && availableAtr->isUnconditionallyUnavailable()) { // Mark the basic block as potentially unreachable. SILBasicBlock *UnreachableBlock = CaseBB.second; if (!State->PossiblyUnreachableBlocks.contains(UnreachableBlock)) { // If this is the first time we see this unreachable block, store it. State->PossiblyUnreachableBlocks.insert(UnreachableBlock); State->MetaMap.insert( {UnreachableBlock, UnreachableInfo{UnreachableKind::UnavailableEnumElement, Loc, true}}); } DidRemoveUnavailableCase = true; } else { NewCaseBBs.push_back(CaseBB); } } if (!DidRemoveUnavailableCase) return false; // Since at least one case was removed, we need to add a default case that // traps if there isn't already an existing default case. The resulting SIL // will have a structure that matches what SILGen would have produced for a // switch statment that was written in source with unavailable cases // unhandled. SILBasicBlock *DefaultBB = SWI.getDefaultBBOrNull().getPtrOrNull(); bool DidCreateDefault = false; if (!DefaultBB) { DefaultBB = BB.getParent()->createBasicBlock(); SILLocation genLoc = Loc.asAutoGenerated(); SILBuilder B(DefaultBB); B.createUnconditionalFail(genLoc, "unexpected enum value"); B.createUnreachable(genLoc); DidCreateDefault = true; } if (isa(*SWI)) { auto *SEI = SILBuilderWithScope(SWI).createSwitchEnum( Loc, SWI.getOperand(), DefaultBB, NewCaseBBs); if (DidCreateDefault) SEI->createDefaultResult(); } else { assert(isa(*SWI) && "unknown switch_enum instruction"); SILBuilderWithScope(SWI).createSwitchEnumAddr(Loc, SWI.getOperand(), DefaultBB, NewCaseBBs); } SWI->eraseFromParent(); return true; } /// Issue an "unreachable code" diagnostic if the blocks contains or /// leads to another block that contains user code. /// /// Note, we rely on SILLocation information to determine if SILInstructions /// correspond to user code. static bool diagnoseUnreachableBlock( SILBasicBlock &B, SILModule &M, const BasicBlockSet &Reachable, UnreachableUserCodeReportingState *State, SILBasicBlock *TopLevelB, BasicBlockSet &Visited) { if (Visited.contains(&B)) return false; Visited.insert(&B); assert(State); for (auto I = B.begin(), E = B.end(); I != E; ++I) { SILLocation Loc = I->getLoc(); // If we've reached an implicit return, we have not found any user code and // can stop searching for it. if (Loc.is() || Loc.isAutoGenerated()) return false; // Without debug info serialization, source locations are deserialized as // implicit by default and as a result this diagnostic is never produced on // deserialized code. With support for serializing debug info/source // locations, the implicit flag is deserialized properly . However, this // creates new diagnostic warnings in many tests. Until these tests are // fixed, we suppress these warnings to match existing behavior. if (Loc.isFilenameAndLocation()) continue; // Check if the instruction corresponds to user-written code, also make // sure we don't report an error twice for the same instruction. if (isUserCode(&*I) && !State->BlocksWithErrors.contains(&B)) { // Emit the diagnostic. auto BrInfoIter = State->MetaMap.find(TopLevelB); assert(BrInfoIter != State->MetaMap.end()); auto BrInfo = BrInfoIter->second; switch (BrInfo.Kind) { case (UnreachableKind::FoldedBranch): // Emit the diagnostic on the unreachable block and emit the // note on the branch responsible for the unreachable code. diagnose(M.getASTContext(), Loc.getSourceLoc(), diag::unreachable_code); diagnose(M.getASTContext(), BrInfo.Loc.getSourceLoc(), diag::unreachable_code_branch, BrInfo.CondIsAlwaysTrue); break; case (UnreachableKind::FoldedSwitchEnum): { // If we are warning about a switch condition being a constant, the main // emphasis should be on the condition (to ensure we have a single // message per switch). const SwitchStmt *SS = BrInfo.Loc.getAsASTNode(); if (!SS) break; assert(SS); const Expr *SE = SS->getSubjectExpr(); diagnose(M.getASTContext(), SE->getLoc(), diag::switch_on_a_constant); diagnose(M.getASTContext(), Loc.getSourceLoc(), diag::unreachable_code_note); break; } case (UnreachableKind::NoreturnCall): { // Specialcase when we are warning about unreachable code after a call // to a noreturn function. if (!BrInfo.Loc.isASTNode() && !BrInfo.Loc.isSILFile()) { assert(BrInfo.Loc.isASTNode()); diagnose(M.getASTContext(), Loc.getSourceLoc(), diag::unreachable_code); diagnose(M.getASTContext(), BrInfo.Loc.getSourceLoc(), diag::call_to_noreturn_note); } break; } case (UnreachableKind::UnavailableEnumElement): { // Don't diagnose blocks removed because they were only reachable from // a case matching an unavailable enum element. break; } } // Record that we've reported this unreachable block to avoid duplicates // in the future. State->BlocksWithErrors.insert(&B); return true; } } // This block could be empty if it's terminator has been folded. if (B.empty()) return false; // If we have not found user code in this block, inspect it's successors. // Check if at least one of the successors contains user code. for (auto I = B.succ_begin(), E = B.succ_end(); I != E; ++I) { SILBasicBlock *SB = *I; bool HasReachablePred = false; for (auto PI = SB->pred_begin(), PE = SB->pred_end(); PI != PE; ++PI) { if (Reachable.contains(*PI)) HasReachablePred = true; } // If all of the predecessors of this successor are unreachable, check if // it contains user code. if (!HasReachablePred && diagnoseUnreachableBlock(*SB, M, Reachable, State, TopLevelB, Visited)) return true; } return false; } static bool removeUnreachableBlocks(SILFunction &F, SILModule &M, UnreachableUserCodeReportingState *State) { if (F.empty()) return false; BasicBlockSet Reachable(&F); SmallVector Worklist; Worklist.push_back(&F.front()); Reachable.insert(&F.front()); unsigned numReachableBlocks = 1; // Collect all reachable blocks by walking the successors. do { SILBasicBlock *BB = Worklist.pop_back_val(); for (auto SI = BB->succ_begin(), SE = BB->succ_end(); SI != SE; ++SI) { if (Reachable.insert(*SI)) { Worklist.push_back(*SI); ++numReachableBlocks; } } } while (!Worklist.empty()); // If everything is reachable, we are done. if (numReachableBlocks == F.size()) return false; // Diagnose user written unreachable code. if (State) { for (auto BI = State->PossiblyUnreachableBlocks.begin(), BE = State->PossiblyUnreachableBlocks.end(); BI != BE; ++BI) { SILBasicBlock *BB = *BI; if (!Reachable.contains(BB)) { BasicBlockSet visited(&F); diagnoseUnreachableBlock(**BI, M, Reachable, State, BB, visited); } } } // Remove references from the dead blocks. for (auto I = F.begin(), E = F.end(); I != E; ++I) { SILBasicBlock *BB = &*I; if (Reachable.contains(BB)) continue; // Drop references to other blocks. recursivelyDeleteTriviallyDeadInstructions(BB->getTerminator(), true); ++NumInstructionsRemoved; } // Delete dead instructions and everything that could become dead after // their deletion. llvm::SmallVector ToBeDeleted; for (auto BI = F.begin(), BE = F.end(); BI != BE; ++BI) if (!Reachable.contains(&*BI)) for (auto I = BI->begin(), E = BI->end(); I != E; ++I) ToBeDeleted.push_back(&*I); recursivelyDeleteTriviallyDeadInstructions(ToBeDeleted, true); NumInstructionsRemoved += ToBeDeleted.size(); // Delete the dead blocks. for (auto I = F.begin(), E = F.end(); I != E;) { SILBasicBlock *BB = &*I; ++I; if (!Reachable.contains(BB)) { F.eraseBlock(BB); ++NumBlocksRemoved; } } return true; } /// Scan the function for any calls to noreturn functions. If we find one, /// change the block to have an unreachable instruction right after it, and /// diagnose any user code after it as being unreachable. This pass happens /// before the definite initialization pass so that it doesn't see infeasible /// control flow edges. static void performNoReturnFunctionProcessing(SILFunction &Fn, SILFunctionTransform *T) { LLVM_DEBUG(llvm::errs() << "*** No return function processing: " << Fn.getName() << "\n"); bool Changed = false; for (auto &BB : Fn) { // Remove instructions from the basic block after a call to a noreturn // function. Changed |= simplifyBlocksWithCallsToNoReturn(BB, nullptr); } if (Changed) { removeUnreachableBlocks(Fn); T->invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody); } } static void diagnoseUnreachable(SILFunction &Fn) { LLVM_DEBUG(llvm::errs() << "*** Diagnose Unreachable processing: " << Fn.getName() << "\n"); UnreachableUserCodeReportingState State(&Fn); for (auto &BB : Fn) { // Simplify the blocks with terminators that rely on constant conditions. if (constantFoldTerminator(BB, &State)) continue; // Remove instructions from the basic block after a call to a noreturn // function. if (simplifyBlocksWithCallsToNoReturn(BB, &State)) continue; // Remove the cases of switch_enum / switch_enum_addr instructions that // match unavailable enum elements. if (eliminateSwitchDispatchOnUnavailableElements(BB, &State)) continue; } // Remove unreachable blocks. removeUnreachableBlocks(Fn, Fn.getModule(), &State); for (auto &BB : Fn) { propagateBasicBlockArgs(BB); } for (auto &BB : Fn) { // Simplify the blocks with terminators that rely on constant conditions. if (constantFoldTerminator(BB, &State)) { continue; } // Remove instructions from the basic block after a call to a noreturn // function. if (simplifyBlocksWithCallsToNoReturn(BB, &State)) continue; // Remove the cases of switch_enum / switch_enum_addr instructions that // match unavailable enum elements. if (eliminateSwitchDispatchOnUnavailableElements(BB, &State)) continue; } // Remove unreachable blocks. removeUnreachableBlocks(Fn, Fn.getModule(), &State); } // External entry point for other passes, which must do their own invalidation. void swift::performSILDiagnoseUnreachable(SILModule *M) { for (auto &Fn : *M) diagnoseUnreachable(Fn); } namespace { class NoReturnFolding : public SILFunctionTransform { void run() override { // Don't rerun diagnostics on deserialized functions. if (getFunction()->wasDeserializedCanonical()) return; performNoReturnFunctionProcessing(*getFunction(), this); } }; } // end anonymous namespace SILTransform *swift::createNoReturnFolding() { return new NoReturnFolding(); } namespace { // This pass reruns on deserialized SIL because diagnostic constant propagation // can expose unreachable blocks which are then removed by this pass. class DiagnoseUnreachable : public SILFunctionTransform { void run() override { diagnoseUnreachable(*getFunction()); invalidateAnalysis(SILAnalysis::InvalidationKind::FunctionBody); } }; } // end anonymous namespace SILTransform *swift::createDiagnoseUnreachable() { return new DiagnoseUnreachable(); }