diff --git a/include/swift/SIL/SILInstruction.h b/include/swift/SIL/SILInstruction.h index 3c9c2ad2a9bdb..814f227e85aa9 100644 --- a/include/swift/SIL/SILInstruction.h +++ b/include/swift/SIL/SILInstruction.h @@ -1025,6 +1025,16 @@ class OwnershipForwardingMixin { } public: + /// Forwarding ownership is determined by the forwarding instruction's + /// constant ownership attribute. If forwarding ownership is owned, then the + /// instruction moves an owned operand to its result, ending its lifetime. If + /// forwarding ownership is guaranteed, then the instruction propagates the + /// lifetime of its borrows operand through its result. + /// + /// The resulting forwarded value's ownership, returned by getOwnershipKind(), + /// is not identical to the forwarding ownership. It differs when the result + /// is trivial type. e.g. an owned or guaranteed value can be cast to a + /// trivial type using owned or guaranteed forwarding. ValueOwnershipKind getForwardingOwnershipKind() const { return ownershipKind; } diff --git a/include/swift/SIL/SILValue.h b/include/swift/SIL/SILValue.h index fbb289692a0a2..e9adeca386ff0 100644 --- a/include/swift/SIL/SILValue.h +++ b/include/swift/SIL/SILValue.h @@ -515,6 +515,16 @@ class ValueBase : public SILNode, public SILAllocated { /// result index, or None if it is not defined by an instruction. Optional getDefiningInstructionResult(); + /// Returns the ValueOwnershipKind that describes this SILValue's ownership + /// semantics if the SILValue has ownership semantics. Returns is a value + /// without any Ownership Semantics. + /// + /// An example of a SILValue without ownership semantics is a + /// struct_element_addr. + /// + /// NOTE: This is implemented in ValueOwnership.cpp not SILValue.cpp. + ValueOwnershipKind getOwnershipKind() const; + static bool classof(SILNodePointer node) { return node->getKind() >= SILNodeKind::First_ValueBase && node->getKind() <= SILNodeKind::Last_ValueBase; @@ -592,6 +602,8 @@ class SILValue { /// If this SILValue is a result of an instruction, return its /// defining instruction. Returns nullptr otherwise. + /// + /// FIXME: remove this redundant API from SILValue. SILInstruction *getDefiningInstruction() { return Value->getDefiningInstruction(); } @@ -610,7 +622,11 @@ class SILValue { /// struct_element_addr. /// /// NOTE: This is implemented in ValueOwnership.cpp not SILValue.cpp. - ValueOwnershipKind getOwnershipKind() const; + /// + /// FIXME: remove this redundant API from SILValue. + ValueOwnershipKind getOwnershipKind() const { + return Value->getOwnershipKind(); + } /// Verify that this SILValue and its uses respects ownership invariants. void verifyOwnership(DeadEndBlocks *DEBlocks) const; @@ -660,6 +676,11 @@ class OwnershipConstraint { return lifetimeConstraint; } + bool isConsuming() const { + return ownershipKind == OwnershipKind::Owned + && lifetimeConstraint == UseLifetimeConstraint::LifetimeEnding; + } + bool satisfiedBy(const Operand *use) const; bool satisfiesConstraint(ValueOwnershipKind testKind) const { @@ -880,15 +901,20 @@ inline bool canAcceptUnownedValue(OperandOwnership operandOwnership) { } } -/// Return the OperandOwnership for a forwarded operand when the forwarded -/// result has this ValueOwnershipKind. \p allowUnowned is true for a subset -/// of forwarding operations that are allowed to propagate Unowned values. +/// Return the OperandOwnership for a forwarded operand when the forwarding +/// operation has this "forwarding ownership" (as returned by +/// getForwardingOwnershipKind()). \p allowUnowned is true for a subset of +/// forwarding operations that are allowed to propagate Unowned values. +/// +/// Forwarding ownership is determined by the forwarding instruction's constant +/// ownership attribute. If forwarding ownership is owned, then the instruction +/// moves owned operand to its result, ending its lifetime. If forwarding +/// ownership is guaranteed, then the instruction propagates the lifetime of its +/// borrows operand through its result. /// -/// The ownership of a forwarded value is derived from the forwarding -/// instruction's constant ownership attribute. If the result is owned, then the -/// instruction moves owned operand to its result, ending its lifetime. If the -/// result is guaranteed value, then the instruction propagates the lifetime of -/// its borrows operand through its result. +/// The resulting forwarded value typically has forwarding ownership, but may +/// differ when the result is trivial type. e.g. an owned or guaranteed value +/// can be cast to a trivial type using owned or guaranteed forwarding. inline OperandOwnership ValueOwnershipKind::getForwardingOperandOwnership(bool allowUnowned) const { switch (value) { diff --git a/include/swift/SILOptimizer/Utils/InstOptUtils.h b/include/swift/SILOptimizer/Utils/InstOptUtils.h index cfd6e6e4181a2..c52a3eee804cd 100644 --- a/include/swift/SILOptimizer/Utils/InstOptUtils.h +++ b/include/swift/SILOptimizer/Utils/InstOptUtils.h @@ -47,6 +47,14 @@ inline TransformRange makeUserRange(Range range) { return makeTransformRange(range, UserTransform(toUser)); } +/// Transform a use_iterator range (Operand*) into an llvm::iterator_range +/// of users (SILInstruction *) +inline iterator_range> +makeUserIteratorRange(iterator_range useRange) { + auto toUser = [](Operand *operand) { return operand->getUser(); }; + return llvm::map_range(useRange, UserTransform(toUser)); +} + using DeadInstructionSet = llvm::SmallSetVector; /// Create a retain of \p Ptr before the \p InsertPt. @@ -265,9 +273,15 @@ SILValue getConcreteValueOfExistentialBoxAddr(SILValue addr, /// - a type of the return value is a subclass of the expected return type. /// - actual return type and expected return type differ in optionality. /// - both types are tuple-types and some of the elements need to be casted. +/// +/// \p usePoints is required when \p value has guaranteed ownership. It must be +/// the last users of the returned, casted value. A usePoint cannot be a +/// BranchInst (a phi is never the last guaranteed user). \p builder's current +/// insertion point must dominate all \p usePoints. std::pair castValueToABICompatibleType(SILBuilder *builder, SILLocation Loc, - SILValue value, SILType srcTy, SILType destTy); + SILValue value, SILType srcTy, SILType destTy, + ArrayRef usePoints); /// Peek through trivial Enum initialization, typically for pointless /// Optionals. /// @@ -700,10 +714,18 @@ makeCopiedValueAvailable(SILValue value, SILBasicBlock *inBlock); /// Given a newly created @owned value \p value without any uses, this utility /// inserts control equivalent copy and destroy at leaking blocks to adjust /// ownership and make \p value available for use at \p inBlock. +/// +/// inBlock must be the only point at which \p value will be consumed. If this +/// consuming point is within a loop, this will create and return a copy of \p +/// value inside \p inBlock. SILValue makeNewValueAvailable(SILValue value, SILBasicBlock *inBlock); /// Given an ssa value \p value, create destroy_values at leaking blocks +/// +/// Warning: This does not properly cleanup an OSSA lifetime with a consuming +/// use blocks inside a loop relative to \p value. The client must create +/// separate copies for any uses within the loop. void endLifetimeAtLeakingBlocks(SILValue value, ArrayRef userBBs); diff --git a/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h b/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h index fa6753453b0b5..65062b995b6f2 100644 --- a/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h +++ b/include/swift/SILOptimizer/Utils/OwnershipOptUtils.h @@ -27,6 +27,13 @@ namespace swift { +/// Returns true if this value requires OSSA cleanups. +inline bool requiresOSSACleanup(SILValue v) { + return v->getFunction()->hasOwnership() + && v.getOwnershipKind() != OwnershipKind::None + && v.getOwnershipKind() != OwnershipKind::Unowned; +} + // Defined in BasicBlockUtils.h struct JointPostDominanceSetComputer; diff --git a/lib/SIL/IR/OperandOwnership.cpp b/lib/SIL/IR/OperandOwnership.cpp index 12f4d843a5333..17e2949c77643 100644 --- a/lib/SIL/IR/OperandOwnership.cpp +++ b/lib/SIL/IR/OperandOwnership.cpp @@ -443,9 +443,24 @@ OperandOwnershipClassifier::visitFullApply(FullApplySite apply) { ? SILArgumentConvention(apply.getSubstCalleeType()->getCalleeConvention()) : apply.getArgumentConvention(op); - return getFunctionArgOwnership( - argConv, - /*hasScopeInCaller*/ apply.beginsCoroutineEvaluation()); + auto argOwnership = getFunctionArgOwnership( + argConv, /*hasScopeInCaller*/ apply.beginsCoroutineEvaluation()); + + // OSSA cleanup needs to handle each of these callee ownership cases. + // + // OperandOwnership::ForwardingConsume is only for thick @callee_owned. + // + // OperandOwnership::Borrow would only happen for a coroutine closure, which + // isn't yet possible. + if (apply.isCalleeOperand(op)) { + assert((argOwnership == OperandOwnership::TrivialUse + || argOwnership == OperandOwnership::UnownedInstantaneousUse + || argOwnership == OperandOwnership::InstantaneousUse + || argOwnership == OperandOwnership::ForwardingConsume + || argOwnership == OperandOwnership::Borrow) && + "unsupported callee ownership"); + } + return argOwnership; } OperandOwnership diff --git a/lib/SIL/IR/SILFunctionType.cpp b/lib/SIL/IR/SILFunctionType.cpp index 56f7937d63c44..d7604da0190df 100644 --- a/lib/SIL/IR/SILFunctionType.cpp +++ b/lib/SIL/IR/SILFunctionType.cpp @@ -4319,6 +4319,9 @@ TypeConverter::getLoweredFormalTypes(SILDeclRef constant, // match exactly. // TODO: More sophisticated param and return ABI compatibility rules could // diverge. +// +// Note: all cases recognized here must be handled in the SILOptimizer's +// castValueToABICompatibleType(). static bool areABICompatibleParamsOrReturns(SILType a, SILType b, SILFunction *inFunction) { // Address parameters are all ABI-compatible, though the referenced diff --git a/lib/SIL/IR/ValueOwnership.cpp b/lib/SIL/IR/ValueOwnership.cpp index 3c2bb016430f1..6bb36c8f9311b 100644 --- a/lib/SIL/IR/ValueOwnership.cpp +++ b/lib/SIL/IR/ValueOwnership.cpp @@ -586,7 +586,7 @@ ValueOwnershipKindClassifier::visitBuiltinInst(BuiltinInst *BI) { // Top Level Entrypoint //===----------------------------------------------------------------------===// -ValueOwnershipKind SILValue::getOwnershipKind() const { +ValueOwnershipKind ValueBase::getOwnershipKind() const { // If we do not have an undef, we should always be able to get to our function // here. If we do not have ownership enabled, just return none for everything // to short circuit ownership optimizations. Since SILUndef in either case @@ -594,7 +594,7 @@ ValueOwnershipKind SILValue::getOwnershipKind() const { // // We assume that any time we are in SILBuilder and call this without having a // value in a block yet, ossa is enabled. - if (auto *block = Value->getParentBlock()) { + if (auto *block = getParentBlock()) { auto *f = block->getParent(); // If our block isn't in a function, then it must be in a global // variable. We don't verify ownership there so just return @@ -609,7 +609,7 @@ ValueOwnershipKind SILValue::getOwnershipKind() const { } ValueOwnershipKindClassifier Classifier; - auto result = Classifier.visit(const_cast(Value)); + auto result = Classifier.visit(const_cast(this)); assert(result && "Returned ownership kind invalid on values"); return result; } diff --git a/lib/SILGen/SILGenThunk.cpp b/lib/SILGen/SILGenThunk.cpp index c7361ec326954..07474b0b69781 100644 --- a/lib/SILGen/SILGenThunk.cpp +++ b/lib/SILGen/SILGenThunk.cpp @@ -260,7 +260,6 @@ SILGenModule::getOrCreateForeignAsyncCompletionHandlerImplFunction( auto flagIndex = convention.completionHandlerFlagParamIndex(); FuncDecl *resumeIntrinsic; - Type replacementTypes[] = {resumeType}; SILBasicBlock *returnBB = nullptr; if (errorIndex) { diff --git a/lib/SILOptimizer/Transforms/EagerSpecializer.cpp b/lib/SILOptimizer/Transforms/EagerSpecializer.cpp index d35519a3a1348..68e9576202dc9 100644 --- a/lib/SILOptimizer/Transforms/EagerSpecializer.cpp +++ b/lib/SILOptimizer/Transforms/EagerSpecializer.cpp @@ -888,7 +888,11 @@ void EagerSpecializerTransform::run() { // removed. for (auto *SA : attrsToRemove) F.removeSpecializeAttr(SA); - F.verify(); + + // If any specializations were created, reverify the original body now that it + // has checks. + if (!newFunctions.empty()) + F.verify(); for (SILFunction *newF : newFunctions) { addFunctionToPassManagerWorklist(newF, nullptr); diff --git a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp index e9e4d74407652..78918098d79b4 100644 --- a/lib/SILOptimizer/Transforms/SimplifyCFG.cpp +++ b/lib/SILOptimizer/Transforms/SimplifyCFG.cpp @@ -32,6 +32,7 @@ #include "swift/SILOptimizer/Utils/CastOptimizer.h" #include "swift/SILOptimizer/Utils/ConstantFolding.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" +#include "swift/SILOptimizer/Utils/OwnershipOptUtils.h" #include "swift/SILOptimizer/Utils/SILInliner.h" #include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h" #include "swift/SILOptimizer/Utils/SILSSAUpdater.h" @@ -663,8 +664,19 @@ bool SimplifyCFG::dominatorBasedSimplify(DominanceAnalysis *DA) { // Split all critical edges such that we can move code onto edges. This is // also required for SSA construction in dominatorBasedSimplifications' jump // threading. It only splits new critical edges it creates by jump threading. - bool Changed = - EnableJumpThread ? splitAllCriticalEdges(Fn, DT, nullptr) : false; + bool Changed = false; + if (!Fn.hasOwnership() && EnableJumpThread) { + Changed = splitAllCriticalEdges(Fn, DT, nullptr); + } + + // TODO: OSSA phi support. Even if all block arguments are trivial, + // jump-threading may require creation of guaranteed phis, which may require + // creation of nested borrow scopes. + if (Fn.hasOwnership()) { + for (auto &BB : Fn) + Changed |= simplifyArgs(&BB); + return Changed; + } unsigned MaxIter = MaxIterationsOfDominatorBasedSimplify; SmallVector BlocksForWorklist; @@ -1017,6 +1029,12 @@ static bool hasInjectedEnumAtEndOfBlock(SILBasicBlock *block, SILValue enumAddr) /// tryJumpThreading - Check to see if it looks profitable to duplicate the /// destination of an unconditional jump into the bottom of this block. bool SimplifyCFG::tryJumpThreading(BranchInst *BI) { + // TODO: OSSA phi support. Even if all block arguments are trivial, + // jump-threading may require creation of guaranteed phis, which may require + // creation of nested borrow scopes. + if (Fn.hasOwnership()) + return false; + auto *DestBB = BI->getDestBB(); auto *SrcBB = BI->getParent(); TermInst *destTerminator = DestBB->getTerminator(); @@ -1238,6 +1256,9 @@ TrampolineDest::TrampolineDest(SILBasicBlock *sourceBB, } while (nextBI); // Check that all the target block arguments are only used by the branch. + // + // TODO: OSSA; also handle dead block args that are trivial or destroyed in + // the same block. for (SILValue blockArg : targetBB->getArguments()) { Operand *operand = blockArg->getSingleUse(); if (!operand || operand->getUser() != targetBranch) { @@ -1457,9 +1478,9 @@ static SILValue skipInvert(SILValue Cond, bool &Inverted, /// Returns the first cond_fail if it is the first side-effect /// instruction in this block. static CondFailInst *getFirstCondFail(SILBasicBlock *BB) { - auto It = BB->begin(); CondFailInst *CondFail = nullptr; // Skip instructions that don't have side-effects. + auto It = BB->begin(); while (It != BB->end() && !(CondFail = dyn_cast(It))) { if (It->mayHaveSideEffects()) return nullptr; @@ -1470,12 +1491,24 @@ static CondFailInst *getFirstCondFail(SILBasicBlock *BB) { /// If the first side-effect instruction in this block is a cond_fail that /// is guaranteed to fail, it is returned. +/// +/// The returned CondFailInst may be in a successor of \p BB. +/// /// The \p Cond is the condition from a cond_br in the predecessor block. The /// cond_fail must only fail if \p BB is entered through this predecessor block. /// If \p Inverted is true, \p BB is on the false-edge of the cond_br. static CondFailInst *getUnConditionalFail(SILBasicBlock *BB, SILValue Cond, bool Inverted) { - CondFailInst *CondFail = getFirstCondFail(BB); + // Handle a CFG edge to the cond_fail block with no side effects. + auto *condfailBB = BB; + if (isa(BB->getTerminator())) { + for (auto It = BB->begin(); It != BB->end(); ++It) { + if (It->mayHaveSideEffects()) + return nullptr; + } + condfailBB = BB->getSingleSuccessorBlock(); + } + CondFailInst *CondFail = getFirstCondFail(condfailBB); if (!CondFail) return nullptr; @@ -1577,18 +1610,42 @@ bool SimplifyCFG::simplifyCondBrBlock(CondBranchInst *BI) { } } + // For a valid TrampolineDest, the destBB has no other predecessors, so remove + // all the branch arguments--they are no longer phis once their predecessor + // block is a cond_br instead of a br. + auto eraseTrampolineDestArgs = [](TrampolineDest &trampolineDest) { + SILBasicBlock *destBB = trampolineDest.destBB; + assert(trampolineDest.newSourceBranchArgs.size() + == destBB->getArguments().size()); + // Erase in reverse order to pop each element as we go. + for (unsigned i = destBB->getArguments().size(); i != 0;) { + --i; + destBB->getArgument(i)->replaceAllUsesWith( + trampolineDest.newSourceBranchArgs[i]); + destBB->eraseArgument(i); + } + }; + // If the destination block is a simple trampoline (jump to another block) // then jump directly. + // + // Avoid creating self-loops on a cond_br. The loop block requires blocks + // arguments for loop-carried values without breaking dominance--we can't have + // an earlier instruction depending on a value defined later in the block. auto trueTrampolineDest = TrampolineDest(ThisBB, TrueSide); if (trueTrampolineDest - && trueTrampolineDest.destBB->getSinglePredecessorBlock()) { + && trueTrampolineDest.destBB->getSinglePredecessorBlock() + && trueTrampolineDest.destBB != ThisBB) { + LLVM_DEBUG(llvm::dbgs() << "true-trampoline from bb" << ThisBB->getDebugID() << " to bb" << trueTrampolineDest.destBB->getDebugID() << '\n'); + SmallVector falseArgsCopy(FalseArgs.begin(), FalseArgs.end()); + eraseTrampolineDestArgs(trueTrampolineDest); SILBuilderWithScope(BI).createCondBranch( BI->getLoc(), BI->getCondition(), trueTrampolineDest.destBB, - trueTrampolineDest.newSourceBranchArgs, FalseSide, falseArgsCopy, + {}, FalseSide, falseArgsCopy, BI->getTrueBBCount(), BI->getFalseBBCount()); BI->eraseFromParent(); @@ -1600,16 +1657,21 @@ bool SimplifyCFG::simplifyCondBrBlock(CondBranchInst *BI) { auto falseTrampolineDest = TrampolineDest(ThisBB, FalseSide); if (falseTrampolineDest - && falseTrampolineDest.destBB->getSinglePredecessorBlock()) { + && falseTrampolineDest.destBB->getSinglePredecessorBlock() + && falseTrampolineDest.destBB != ThisBB) { + LLVM_DEBUG(llvm::dbgs() << "false-trampoline from bb" << ThisBB->getDebugID() << " to bb" << falseTrampolineDest.destBB->getDebugID() << '\n'); + SmallVector trueArgsCopy(TrueArgs.begin(), TrueArgs.end()); + eraseTrampolineDestArgs(falseTrampolineDest); SILBuilderWithScope(BI).createCondBranch( BI->getLoc(), BI->getCondition(), TrueSide, trueArgsCopy, - falseTrampolineDest.destBB, falseTrampolineDest.newSourceBranchArgs, - BI->getTrueBBCount(), BI->getFalseBBCount()); + falseTrampolineDest.destBB, {}, BI->getTrueBBCount(), + BI->getFalseBBCount()); BI->eraseFromParent(); + substitutedBlockPreds(FalseSide, ThisBB); removeIfDead(FalseSide); addToWorklist(ThisBB); @@ -1756,6 +1818,11 @@ static bool isOnlyUnreachable(SILBasicBlock *BB) { /// switch_enum where all but one block consists of just an /// "unreachable" with an unchecked_enum_data and branch. bool SimplifyCFG::simplifySwitchEnumUnreachableBlocks(SwitchEnumInst *SEI) { + if (Fn.hasOwnership()) { + // TODO: OSSA; cleanup terminator results. + if (!SEI->getOperand()->getType().isTrivial(Fn)) + return false; + } auto Count = SEI->getNumCases(); SILBasicBlock *Dest = nullptr; @@ -1779,6 +1846,8 @@ bool SimplifyCFG::simplifySwitchEnumUnreachableBlocks(SwitchEnumInst *SEI) { Dest = EnumCase.second; } + LLVM_DEBUG(llvm::dbgs() << "remove unreachable case " << *SEI); + if (!Dest) { addToWorklist(SEI->getParent()); SILBuilderWithScope(SEI).createUnreachable(SEI->getLoc()); @@ -1798,8 +1867,6 @@ bool SimplifyCFG::simplifySwitchEnumUnreachableBlocks(SwitchEnumInst *SEI) { return true; } - LLVM_DEBUG(llvm::dbgs() << "remove " << *SEI); - auto &Mod = SEI->getModule(); auto OpndTy = SEI->getOperand()->getType(); auto Ty = OpndTy.getEnumElementType( @@ -2047,6 +2114,12 @@ static bool hasSameUltimateSuccessor(SILBasicBlock *noneBB, SILBasicBlock *someB /// %4 = enum #Optional.none /// br mergeBB(%4) bool SimplifyCFG::simplifySwitchEnumOnObjcClassOptional(SwitchEnumInst *SEI) { + // TODO: OSSA; handle non-trivial enum case cleanup + // (simplify_switch_enum_objc.sil). + if (Fn.hasOwnership()) { + return false; + } + auto optional = SEI->getOperand(); auto optionalPayloadType = optional->getType().getOptionalObjectType(); if (!optionalPayloadType || @@ -2076,6 +2149,8 @@ bool SimplifyCFG::simplifySwitchEnumOnObjcClassOptional(SwitchEnumInst *SEI) { optionalPayload)) return false; + LLVM_DEBUG(llvm::dbgs() << "simplify switch_enum on ObjC Class Optional\n"); + SILBuilderWithScope Builder(SEI); auto *payloadCast = Builder.createUncheckedRefCast(SEI->getLoc(), optional, optionalPayloadType); @@ -2105,6 +2180,12 @@ bool SimplifyCFG::simplifySwitchEnumBlock(SwitchEnumInst *SEI) { auto *LiveBlock = SEI->getCaseDestination(EnumCase.get()); auto *ThisBB = SEI->getParent(); + if (Fn.hasOwnership()) { + // TODO: OSSA; cleanup terminator results. + if (!SEI->getOperand()->getType().isTrivial(Fn)) + return false; + } + bool DroppedLiveBlock = false; // Copy the successors into a vector, dropping one entry for the liveblock. SmallVector Dests; @@ -2116,23 +2197,31 @@ bool SimplifyCFG::simplifySwitchEnumBlock(SwitchEnumInst *SEI) { Dests.push_back(S); } - LLVM_DEBUG(llvm::dbgs() << "remove " << *SEI); + LLVM_DEBUG(llvm::dbgs() << "fold switch " << *SEI); auto *EI = dyn_cast(SEI->getOperand()); SILBuilderWithScope Builder(SEI); if (!LiveBlock->args_empty()) { SILValue PayLoad; - if (EI) { - PayLoad = EI->getOperand(); + if (SEI->hasDefault() && LiveBlock == SEI->getDefaultBB()) { + assert(Fn.hasOwnership() && "Only OSSA default case has an argument"); + PayLoad = SEI->getOperand(); } else { - PayLoad = Builder.createUncheckedEnumData(SEI->getLoc(), - SEI->getOperand(), EnumCase.get()); + if (EI) { + PayLoad = EI->getOperand(); + } else { + PayLoad = Builder.createUncheckedEnumData(SEI->getLoc(), + SEI->getOperand(), + EnumCase.get()); + } } Builder.createBranch(SEI->getLoc(), LiveBlock, PayLoad); } else { Builder.createBranch(SEI->getLoc(), LiveBlock); } SEI->eraseFromParent(); + // TODO: also remove this EnumInst in OSSA default case when the only + // remaining uses are destroys, and incidental uses. if (EI && EI->use_empty()) EI->eraseFromParent(); addToWorklist(ThisBB); @@ -2186,7 +2275,7 @@ bool SimplifyCFG::simplifySwitchValueBlock(SwitchValueInst *SVI) { Dests.push_back(S); } - LLVM_DEBUG(llvm::dbgs() << "remove " << *SVI); + LLVM_DEBUG(llvm::dbgs() << "fold select " << *SVI); SILBuilderWithScope(SVI).createBranch(SVI->getLoc(), LiveBlock); SVI->eraseFromParent(); @@ -2322,11 +2411,19 @@ bool SimplifyCFG::simplifyCheckedCastBranchBlock(CheckedCastBranchInst *CCBI) { }); MadeChange |= bool(CastOpt.simplifyCheckedCastBranchInst(CCBI)); + + LLVM_DEBUG(if (MadeChange) + llvm::dbgs() << "simplify checked_cast_br block\n"); return MadeChange; } bool SimplifyCFG::simplifyCheckedCastValueBranchBlock( CheckedCastValueBranchInst *CCBI) { + // TODO: OSSA; handle cleanups for opaque cases (simplify_cfg_opaque.sil). + if (Fn.hasOwnership()) { + return false; + } + auto SuccessBB = CCBI->getSuccessBB(); auto FailureBB = CCBI->getFailureBB(); auto ThisBB = CCBI->getParent(); @@ -2357,6 +2454,9 @@ bool SimplifyCFG::simplifyCheckedCastValueBranchBlock( }); MadeChange |= bool(CastOpt.simplifyCheckedCastValueBranchInst(CCBI)); + + LLVM_DEBUG(if (MadeChange) + llvm::dbgs() << "simplify checked_cast_value block\n"); return MadeChange; } @@ -2391,6 +2491,9 @@ simplifyCheckedCastAddrBranchBlock(CheckedCastAddrBranchInst *CCABI) { }); MadeChange |= bool(CastOpt.simplifyCheckedCastAddrBranchInst(CCABI)); + + LLVM_DEBUG(if (MadeChange) + llvm::dbgs() << "simplify checked_cast_addr block\n"); return MadeChange; } @@ -2416,6 +2519,9 @@ static SILValue getActualCallee(SILValue Callee) { /// Checks if the callee of \p TAI is a convert from a function without /// error result. +/// +/// The new \p Callee must be reachable from \p TAI's callee operand by +/// following the chain of OwnershipForwardingConversionInsts. static bool isTryApplyOfConvertFunction(TryApplyInst *TAI, SILValue &Callee, SILType &CalleeType) { @@ -2472,7 +2578,6 @@ static bool isTryApplyWithUnreachableError(TryApplyInst *TAI, } bool SimplifyCFG::simplifyTryApplyBlock(TryApplyInst *TAI) { - SILValue Callee; SILType CalleeType; @@ -2480,6 +2585,8 @@ bool SimplifyCFG::simplifyTryApplyBlock(TryApplyInst *TAI) { if (isTryApplyOfConvertFunction(TAI, Callee, CalleeType) || isTryApplyWithUnreachableError(TAI, Callee, CalleeType)) { + LLVM_DEBUG(llvm::dbgs() << "simplify try_apply block\n"); + auto CalleeFnTy = CalleeType.castTo(); SILFunctionConventions calleeConv(CalleeFnTy, TAI->getModule()); auto ResultTy = calleeConv.getSILResultType( @@ -2511,7 +2618,7 @@ bool SimplifyCFG::simplifyTryApplyBlock(TryApplyInst *TAI) { // Cast argument if required. std::tie(Arg, std::ignore) = castValueToABICompatibleType( &Builder, TAI->getLoc(), Arg, origConv.getSILArgumentType(i, context), - targetConv.getSILArgumentType(i, context)); + targetConv.getSILArgumentType(i, context), {TAI}); Args.push_back(Arg); } @@ -2519,18 +2626,43 @@ bool SimplifyCFG::simplifyTryApplyBlock(TryApplyInst *TAI) { && "The number of arguments should match"); LLVM_DEBUG(llvm::dbgs() << "replace with apply: " << *TAI); - ApplyInst *NewAI = Builder.createApply(TAI->getLoc(), Callee, + + // If the new callee is owned, copy it to extend the lifetime + // + // TODO: The original convert_function will likely be dead after + // replacement. It could be deleted on-the-fly with a utility to avoid + // creating a new copy. + auto calleeLoc = RegularLocation::getAutoGeneratedLocation(); + auto newCallee = Callee; + if (requiresOSSACleanup(newCallee)) { + newCallee = SILBuilderWithScope(newCallee->getNextInstruction()) + .createCopyValue(calleeLoc, newCallee); + newCallee = makeNewValueAvailable(newCallee, TAI->getParent()); + } + ApplyInst *NewAI = Builder.createApply(TAI->getLoc(), newCallee, TAI->getSubstitutionMap(), Args, CalleeFnTy->hasErrorResult()); auto Loc = TAI->getLoc(); auto *NormalBB = TAI->getNormalBB(); + assert(NewAI->getOwnershipKind() != OwnershipKind::Guaranteed); + // Non-guaranteed values don't need use points when casting. SILValue CastedResult; std::tie(CastedResult, std::ignore) = castValueToABICompatibleType( - &Builder, Loc, NewAI, ResultTy, OrigResultTy); - - Builder.createBranch(Loc, NormalBB, { CastedResult }); + &Builder, Loc, NewAI, ResultTy, OrigResultTy, /*usePoints*/ {}); + + BranchInst *branch = Builder.createBranch(Loc, NormalBB, { CastedResult }); + + auto *oldCalleeOper = TAI->getCalleeOperand(); + if (oldCalleeOper->getOwnershipConstraint().isConsuming()) { + // Destroy the oldCallee before the new call. + SILBuilderWithScope(NewAI).createDestroyValue( + TAI->getLoc(), oldCalleeOper->get()); + } else if (newCallee != Callee) { + // Destroy the copied newCallee after the call. + SILBuilderWithScope(branch).createDestroyValue(TAI->getLoc(), newCallee); + } TAI->eraseFromParent(); return true; } @@ -2560,6 +2692,15 @@ bool SimplifyCFG::simplifyTermWithIdenticalDestBlocks(SILBasicBlock *BB) { return false; } TermInst *Term = BB->getTerminator(); + // TODO: OSSA; cleanup nontrivial terminator operands (if this ever actually + // happens) + if (Fn.hasOwnership()) { + if (llvm::any_of(Term->getOperandValues(), [this](SILValue op) { + return !op->getType().isTrivial(Fn); + })) { + return false; + } + } LLVM_DEBUG(llvm::dbgs() << "replace term with identical dests: " << *Term); SILBuilderWithScope(Term).createBranch(Term->getLoc(), commonDest.destBB, commonDest.newSourceBranchArgs); @@ -2592,7 +2733,6 @@ bool SimplifyCFG::simplifyTermWithIdenticalDestBlocks(SILBasicBlock *BB) { /// bb3(%a) // %a is dead /// static bool tryMoveCondFailToPreds(SILBasicBlock *BB) { - CondFailInst *CFI = getFirstCondFail(BB); if (!CFI) return false; @@ -2642,10 +2782,11 @@ static bool tryMoveCondFailToPreds(SILBasicBlock *BB) { // Move the cond_fail to the predecessor blocks. for (auto *Pred : BB->getPredecessorBlocks()) { SILValue incoming = condArg->getIncomingPhiValue(Pred); + SILBuilderWithScope Builder(Pred->getTerminator()); - createCondFail(CFI, incoming, CFI->getMessage(), inverted, Builder); } + // cond_fail takes a trivial Int1. No cleanup is needed. CFI->eraseFromParent(); return true; } @@ -2657,8 +2798,6 @@ bool SimplifyCFG::simplifyBlocks() { for (auto &BB : Fn) addToWorklist(&BB); - bool hasOwnership = Fn.hasOwnership(); - // Iteratively simplify while there is still work to do. while (SILBasicBlock *BB = popWorklist()) { // If the block is dead, remove it. @@ -2677,10 +2816,6 @@ bool SimplifyCFG::simplifyBlocks() { continue; } - // We do not jump thread at all now. - if (hasOwnership) - continue; - // If this unconditional branch has BBArgs, check to see if duplicating // the destination would allow it to be simplified. This is a simple form // of jump threading. @@ -2693,14 +2828,10 @@ bool SimplifyCFG::simplifyBlocks() { Changed |= simplifyCondBrBlock(cast(TI)); break; case TermKind::SwitchValueInst: - if (hasOwnership) - continue; // FIXME: Optimize for known switch values. Changed |= simplifySwitchValueBlock(cast(TI)); break; case TermKind::SwitchEnumInst: { - if (hasOwnership) - continue; auto *SEI = cast(TI); if (simplifySwitchEnumBlock(SEI)) { Changed = true; @@ -2716,29 +2847,19 @@ bool SimplifyCFG::simplifyBlocks() { Changed |= simplifyUnreachableBlock(cast(TI)); break; case TermKind::CheckedCastBranchInst: - if (hasOwnership) - continue; Changed |= simplifyCheckedCastBranchBlock(cast(TI)); break; case TermKind::CheckedCastValueBranchInst: - if (hasOwnership) - continue; Changed |= simplifyCheckedCastValueBranchBlock( cast(TI)); break; case TermKind::CheckedCastAddrBranchInst: - if (hasOwnership) - continue; Changed |= simplifyCheckedCastAddrBranchBlock(cast(TI)); break; case TermKind::TryApplyInst: - if (hasOwnership) - continue; Changed |= simplifyTryApplyBlock(cast(TI)); break; case TermKind::SwitchEnumAddrInst: - if (hasOwnership) - continue; Changed |= simplifyTermWithIdenticalDestBlocks(BB); break; case TermKind::ThrowInst: @@ -2746,19 +2867,12 @@ bool SimplifyCFG::simplifyBlocks() { case TermKind::ReturnInst: case TermKind::UnwindInst: case TermKind::YieldInst: - if (hasOwnership) - continue; break; case TermKind::AwaitAsyncContinuationInst: - if (hasOwnership) - continue; // TODO(async): Simplify AwaitAsyncContinuationInst break; } - if (hasOwnership) - continue; - // If the block has a cond_fail, try to move it to the predecessors. Changed |= tryMoveCondFailToPreds(BB); @@ -2775,6 +2889,13 @@ bool SimplifyCFG::simplifyBlocks() { /// Canonicalize all switch_enum and switch_enum_addr instructions. /// If possible, replace the default with the corresponding unique case. bool SimplifyCFG::canonicalizeSwitchEnums() { + if (Fn.hasOwnership()) { + // TODO: OSSA. In OSSA, the default switch_enum case passes the + // original enum as a block argument. This needs to check that the block + // argument is dead, then replace it with the a new argument for the default + // payload. + return false; + } bool Changed = false; for (auto &BB : Fn) { TermInst *TI = BB.getTerminator(); @@ -2790,6 +2911,8 @@ bool SimplifyCFG::canonicalizeSwitchEnums() { if (!elementDecl) continue; + LLVM_DEBUG(llvm::dbgs() << "simplify canonical switch_enum\n"); + // Construct a new instruction by copying all the case entries. SmallVector, 4> CaseBBs; for (int idx = 0, numIdcs = SWI.getNumCases(); idx < numIdcs; ++idx) { @@ -2871,6 +2994,12 @@ static bool shouldTailDuplicate(SILBasicBlock &Block) { bool SimplifyCFG::tailDuplicateObjCMethodCallSuccessorBlocks() { SmallVector ObjCBlocks; + // TODO: OSSA phi support. Even if all block arguments are trivial, + // jump-threading may require creation of guaranteed phis, which may require + // creation of nested borrow scopes. + if (Fn.hasOwnership()) { + return false; + } // Collect blocks to tail duplicate. for (auto &BB : Fn) { SILBasicBlock *DestBB; @@ -3077,6 +3206,11 @@ RemoveDeadArgsWhenSplitting("sroa-args-remove-dead-args-after", llvm::cl::init(true)); bool ArgumentSplitter::split() { + if (Arg->getFunction()->hasOwnership()) { + // TODO: OSSA phi support + if (!Arg->getType().isTrivial(*Arg->getFunction())) + return false; + } SILBasicBlock *ParentBB = Arg->getParent(); if (!createNewArguments()) @@ -3190,7 +3324,6 @@ static bool splitBBArguments(SILFunction &Fn) { } bool SimplifyCFG::run() { - LLVM_DEBUG(llvm::dbgs() << "### Run SimplifyCFG on " << Fn.getName() << '\n'); // Disable some expensive optimizations if the function is huge. @@ -3199,17 +3332,6 @@ bool SimplifyCFG::run() { // First remove any block not reachable from the entry. bool Changed = removeUnreachableBlocks(Fn); - // If we have ownership bail. We just want to remove unreachable blocks and - // simplify. - if (Fn.hasOwnership()) { - DT = nullptr; - if (simplifyBlocks()) { - removeUnreachableBlocks(Fn); - Changed = true; - } - return Changed; - } - // Find the set of loop headers. We don't want to jump-thread through headers. findLoopHeaders(); @@ -3874,6 +3996,15 @@ bool SimplifyCFG::simplifyArgs(SILBasicBlock *BB) { if (BB->pred_empty()) return false; + if (Fn.hasOwnership()) { + // TODO: OSSA phi support + if (llvm::any_of(BB->getArguments(), [this](SILArgument *arg) { + return !arg->getType().isTrivial(Fn); + })) { + return false; + } + } + // Ignore blocks that are successors of terminators with mandatory args. for (SILBasicBlock *pred : BB->getPredecessorBlocks()) { if (hasMandatoryArgument(pred->getTerminator())) @@ -3942,6 +4073,7 @@ bool SimplifyCFG::simplifyProgramTerminationBlock(SILBasicBlock *BB) { default: continue; } + LLVM_DEBUG(llvm::dbgs() << "remove dead-end destroy " << I); InstsToRemove.insert(&I); } @@ -3978,7 +4110,6 @@ namespace { class JumpThreadSimplifyCFGPass : public SILFunctionTransform { public: void run() override { - // FIXME: Handle ownership. if (SimplifyCFG(*getFunction(), *this, getOptions().VerifyAll, /*EnableJumpThread=*/true) .run()) diff --git a/lib/SILOptimizer/Utils/Devirtualize.cpp b/lib/SILOptimizer/Utils/Devirtualize.cpp index 31d183a494584..24e4fe05d7b37 100644 --- a/lib/SILOptimizer/Utils/Devirtualize.cpp +++ b/lib/SILOptimizer/Utils/Devirtualize.cpp @@ -461,9 +461,11 @@ replaceApplyInst(SILBuilder &builder, SILLocation loc, ApplyInst *oldAI, } } - // Check if any casting is required for the return value. + // Check if any casting is required for the return value. newAI cannot be a + // guaranteed value, so this cast cannot generate borrow scopes and it can be + // used anywhere the original oldAI was used. auto castRes = castValueToABICompatibleType( - &builder, loc, newAI, newAI->getType(), oldAI->getType()); + &builder, loc, newAI, newAI->getType(), oldAI->getType(), /*usePoints*/ {}); oldAI->replaceAllUsesWith(castRes.first); return {newAI, castRes.second}; @@ -519,8 +521,11 @@ replaceTryApplyInst(SILBuilder &builder, SILLocation loc, TryApplyInst *oldTAI, builder.setInsertionPoint(resultBB); SILValue resultValue = resultBB->getArgument(0); + // resultValue cannot be a guaranteed value, so this cast cannot generate + // borrow scopes and it can be used anywhere the original oldAI was + // used--usePoints are not required. std::tie(resultValue, std::ignore) = castValueToABICompatibleType( - &builder, loc, resultValue, newResultTy, oldResultTy); + &builder, loc, resultValue, newResultTy, oldResultTy, /*usePoints*/ {}); builder.createBranch(loc, normalBB, {resultValue}); } @@ -548,8 +553,12 @@ replaceBeginApplyInst(SILBuilder &builder, SILLocation loc, for (auto i : indices(oldYields)) { auto oldYield = oldYields[i]; auto newYield = newYields[i]; + // Insert any end_borrow if the yielded value before the token's uses. + SmallVector users( + makeUserIteratorRange(oldBAI->getTokenResult()->getUses())); auto yieldCastRes = castValueToABICompatibleType( - &builder, loc, newYield, newYield->getType(), oldYield->getType()); + &builder, loc, newYield, newYield->getType(), oldYield->getType(), + users); oldYield->replaceAllUsesWith(yieldCastRes.first); changedCFG |= yieldCastRes.second; } @@ -584,8 +593,11 @@ replacePartialApplyInst(SILBuilder &builder, SILLocation loc, builder.createPartialApply(loc, newFn, newSubs, newArgs, convention); // Check if any casting is required for the partially-applied function. + // A non-guaranteed cast needs no usePoints. + assert(newPAI->getOwnershipKind() != OwnershipKind::Guaranteed); auto castRes = castValueToABICompatibleType( - &builder, loc, newPAI, newPAI->getType(), oldPAI->getType()); + &builder, loc, newPAI, newPAI->getType(), oldPAI->getType(), + /*usePoints*/ {}); oldPAI->replaceAllUsesWith(castRes.first); return {newPAI, castRes.second}; @@ -768,7 +780,7 @@ swift::devirtualizeClassMethod(FullApplySite applySite, applySite.getFunction()->getTypeExpansionContext())) { auto castRes = castValueToABICompatibleType( &builder, loc, *indirectResultArgIter, indirectResultArgIter->getType(), - resultTy); + resultTy, {applySite.getInstruction()}); newArgs.push_back(castRes.first); changedCFG |= castRes.second; ++indirectResultArgIter; @@ -787,8 +799,10 @@ swift::devirtualizeClassMethod(FullApplySite applySite, arg = borrowBuilder.createBeginBorrow(loc, arg); newArgBorrows.push_back(arg); } - auto argCastRes = castValueToABICompatibleType(&builder, loc, arg, - paramArgIter->getType(), paramType); + auto argCastRes = + castValueToABICompatibleType(&builder, loc, arg, + paramArgIter->getType(), paramType, + {applySite.getInstruction()}); newArgs.push_back(argCastRes.first); changedCFG |= argCastRes.second; @@ -1017,7 +1031,8 @@ devirtualizeWitnessMethod(ApplySite applySite, SILFunction *f, borrowedArgs.push_back(arg); } auto argCastRes = castValueToABICompatibleType( - &argBuilder, applySite.getLoc(), arg, arg->getType(), paramType); + &argBuilder, applySite.getLoc(), arg, arg->getType(), paramType, + applySite.getInstruction()); arg = argCastRes.first; changedCFG |= argCastRes.second; } diff --git a/lib/SILOptimizer/Utils/InstOptUtils.cpp b/lib/SILOptimizer/Utils/InstOptUtils.cpp index 23af53febd267..68e3cfb2f3626 100644 --- a/lib/SILOptimizer/Utils/InstOptUtils.cpp +++ b/lib/SILOptimizer/Utils/InstOptUtils.cpp @@ -936,12 +936,23 @@ SILLinkage swift::getSpecializedLinkage(SILFunction *f, SILLinkage linkage) { /// to avoid any divergence between the check and the implementation in the /// future. /// +/// \p usePoints are required when \p value has guaranteed ownership. It must be +/// the last users of the returned, casted value. A usePoint cannot be a +/// BranchInst (a phi is never the last guaranteed user). \p builder's current +/// insertion point must dominate all \p usePoints. \p usePoints must +/// collectively post-dominate \p builder's current insertion point. +/// /// NOTE: The implementation of this function is very closely related to the -/// rules checked by SILVerifier::requireABICompatibleFunctionTypes. +/// rules checked by SILVerifier::requireABICompatibleFunctionTypes. It must +/// handle all cases recognized by SILFunctionType::isABICompatibleWith (see +/// areABICompatibleParamsOrReturns()). std::pair swift::castValueToABICompatibleType(SILBuilder *builder, SILLocation loc, SILValue value, SILType srcTy, - SILType destTy) { + SILType destTy, + ArrayRef usePoints) { + assert(value.getOwnershipKind() != OwnershipKind::Guaranteed + || !usePoints.empty() && "guaranteed value must have use points"); // No cast is required if types are the same. if (srcTy == destTy) @@ -994,38 +1005,71 @@ swift::castValueToABICompatibleType(SILBuilder *builder, SILLocation loc, // Unwrap the original optional value. auto *someDecl = builder->getASTContext().getOptionalSomeDecl(); - auto *noneBB = builder->getFunction().createBasicBlock(); - auto *someBB = builder->getFunction().createBasicBlock(); auto *curBB = builder->getInsertionPoint()->getParent(); - auto *contBB = curBB->split(builder->getInsertionPoint()); - contBB->createPhiArgument(destTy, OwnershipKind::Owned); + auto *someBB = builder->getFunction().createBasicBlockAfter(curBB); + auto *noneBB = builder->getFunction().createBasicBlockAfter(someBB); + + auto *phi = contBB->createPhiArgument(destTy, value.getOwnershipKind()); + if (phi->getOwnershipKind() == OwnershipKind::Guaranteed) { + auto createEndBorrow = [&](SILBasicBlock::iterator insertPt) { + builder->setInsertionPoint(insertPt); + builder->createEndBorrow(loc, phi); + }; + for (SILInstruction *user : usePoints) { + if (isa(user)) { + assert(!isa(user) && "no branch as guaranteed use point"); + for (auto *succBB : user->getParent()->getSuccessorBlocks()) { + createEndBorrow(succBB->begin()); + } + continue; + } + createEndBorrow(std::next(user->getIterator())); + } + } SmallVector, 1> caseBBs; caseBBs.push_back(std::make_pair(someDecl, someBB)); builder->setInsertionPoint(curBB); builder->createSwitchEnum(loc, value, noneBB, caseBBs); - - // Handle the Some case. - builder->setInsertionPoint(someBB); - SILValue unwrappedValue = - builder->createUncheckedEnumData(loc, value, someDecl); + // In OSSA switch_enum destinations have terminator results. + // + // TODO: This should be in a switchEnum utility. + SILValue unwrappedValue; + if (builder->hasOwnership()) { + // Create a terminator result, NOT a phi, despite the API name. + noneBB->createPhiArgument(value->getType(), OwnershipKind::None); + unwrappedValue = + someBB->createPhiArgument(optionalSrcTy, value.getOwnershipKind()); + builder->setInsertionPoint(someBB); + } else { + builder->setInsertionPoint(someBB); + unwrappedValue = builder->createUncheckedEnumData(loc, value, someDecl); + } // Cast the unwrapped value. SILValue castedUnwrappedValue; std::tie(castedUnwrappedValue, std::ignore) = castValueToABICompatibleType( - builder, loc, unwrappedValue, optionalSrcTy, optionalDestTy); - // Wrap into optional. - auto castedValue = + builder, loc, unwrappedValue, optionalSrcTy, optionalDestTy, usePoints); + // Wrap into optional. An owned value is forwarded through the cast and into + // the Optional. A borrowed value will have a nested borrow for the + // rewrapped Optional. + SILValue someValue = builder->createOptionalSome(loc, castedUnwrappedValue, destTy); - builder->createBranch(loc, contBB, {castedValue}); + if (phi->getOwnershipKind() == OwnershipKind::Guaranteed) { + someValue = builder->createBeginBorrow(loc, someValue); + } + builder->createBranch(loc, contBB, {someValue}); // Handle the None case. builder->setInsertionPoint(noneBB); - castedValue = builder->createOptionalNone(loc, destTy); - builder->createBranch(loc, contBB, {castedValue}); + SILValue noneValue = builder->createOptionalNone(loc, destTy); + if (phi->getOwnershipKind() == OwnershipKind::Guaranteed) { + noneValue = builder->createBeginBorrow(loc, noneValue); + } + builder->createBranch(loc, contBB, {noneValue}); builder->setInsertionPoint(contBB->begin()); - return {contBB->getArgument(0), true}; + return {phi, true}; } // Src is not optional, but dest is optional. @@ -1040,7 +1084,8 @@ swift::castValueToABICompatibleType(SILBuilder *builder, SILLocation loc, builder->createOptionalSome(loc, value, loweredOptionalSrcType); // Cast the wrapped value. return castValueToABICompatibleType(builder, loc, wrappedValue, - wrappedValue->getType(), destTy); + wrappedValue->getType(), destTy, + usePoints); } // Handle tuple types. @@ -1048,17 +1093,16 @@ swift::castValueToABICompatibleType(SILBuilder *builder, SILLocation loc, if (auto srcTupleTy = srcTy.getAs()) { SmallVector expectedTuple; bool changedCFG = false; - for (unsigned i = 0, e = srcTupleTy->getNumElements(); i < e; ++i) { - SILValue element = builder->createTupleExtract(loc, value, i); + auto castElement = [&](unsigned idx, SILValue element) { // Cast the value if necessary. bool neededCFGChange; std::tie(element, neededCFGChange) = castValueToABICompatibleType( - builder, loc, element, srcTy.getTupleElementType(i), - destTy.getTupleElementType(i)); + builder, loc, element, srcTy.getTupleElementType(idx), + destTy.getTupleElementType(idx), usePoints); changedCFG |= neededCFGChange; expectedTuple.push_back(element); - } - + }; + builder->emitDestructureValueOperation(loc, value, castElement); return {builder->createTuple(loc, destTy, expectedTuple), changedCFG}; } @@ -2052,6 +2096,8 @@ bool swift::tryEliminateOnlyOwnershipUsedForwardingInst( return true; } +// The consuming use blocks are assumed either not to inside a loop relative to +// \p value or they must have their own copies. void swift::endLifetimeAtLeakingBlocks(SILValue value, ArrayRef uses) { if (!value->getFunction()->hasOwnership()) diff --git a/test/SILOptimizer/dead_array_elim.swift b/test/SILOptimizer/dead_array_elim.swift index 4716da890b26f..e67d25e82648e 100644 --- a/test/SILOptimizer/dead_array_elim.swift +++ b/test/SILOptimizer/dead_array_elim.swift @@ -1,7 +1,6 @@ // RUN: %target-swift-frontend -O -emit-sil -primary-file %s | %FileCheck %s // REQUIRES: swift_stdlib_no_asserts -// REQUIRES: PTRSIZE=64 // These tests check whether DeadObjectElimination pass runs as a part of the // optimization pipeline and eliminates dead array literals in Swift code. diff --git a/test/SILOptimizer/simplify_cfg_args_ossa.sil b/test/SILOptimizer/simplify_cfg_args_ossa.sil new file mode 100644 index 0000000000000..cf86a035ec0b2 --- /dev/null +++ b/test/SILOptimizer/simplify_cfg_args_ossa.sil @@ -0,0 +1,793 @@ +// RUN: %target-sil-opt -enable-sil-verify-all %s -jumpthread-simplify-cfg | %FileCheck %s + +sil_stage raw + +import Builtin +import Swift + +sil_global private @globalinit_token11 : $Builtin.Word + +sil_global private @globalinit_token120 : $Builtin.Word + +sil @globalinit_func11 : $@convention(thin) () -> () + +// CHECK: sil [ossa] @a_method_to_simplify +sil [ossa] @a_method_to_simplify : $@convention(thin) (Builtin.RawPointer, @owned Builtin.NativeObject, Builtin.Int32) -> Builtin.RawPointer { +bb0(%0 : $Builtin.RawPointer, %1 : @owned $Builtin.NativeObject, %2 : $Builtin.Int32): + %4 = struct $UnsafeMutablePointer (%0 : $Builtin.RawPointer) // user: %37 + %8 = integer_literal $Builtin.Int1, 0 // user: %23 + %10 = global_addr @globalinit_token11 : $*Builtin.Word // user: %11 + %11 = address_to_pointer %10 : $*Builtin.Word to $Builtin.RawPointer // user: %14 + %12 = function_ref @globalinit_func11 : $@convention(thin) () -> () // users: %26, %13 + %14 = builtin "once"(%11 : $Builtin.RawPointer, %12 : $@convention(thin) () -> ()) : $() + %15 = alloc_stack $Int32 // users: %38, %40, %41 + %16 = struct_element_addr %15 : $*Int32, #Int32._value // users: %28, %17 + %17 = load [trivial] %16 : $*Builtin.Int32 // user: %20 + %18 = integer_literal $Builtin.Int32, -1 // user: %20 + %20 = builtin "xor_Int32"(%17 : $Builtin.Int32, %18 : $Builtin.Int32) : $Builtin.Int32 // user: %22 + %22 = builtin "and_Int32"(%2 : $Builtin.Int32, %20 : $Builtin.Int32) : $Builtin.Int32 // users: %30, %23 +// CHECK-NOT: cond_br + cond_br %8, bb1, bb2 + +bb1: // Preds: bb0 + %24 = global_addr @globalinit_token11 : $*Builtin.Word // user: %25 + %25 = address_to_pointer %24 : $*Builtin.Word to $Builtin.RawPointer // user: %27 + %27 = builtin "once"(%25 : $Builtin.RawPointer, %12 : $@convention(thin) () -> ()) : $() + %28 = load [trivial] %16 : $*Builtin.Int32 // user: %30 +// CHECK: [[VAR_21:%[0-9]+]] = builtin "and_Int32" + %30 = builtin "or_Int32"(%22 : $Builtin.Int32, %28 : $Builtin.Int32) : $Builtin.Int32 // user: %31 + br bb3(%30 : $Builtin.Int32) // id: %31 + +bb2: + br bb3(%22 : $Builtin.Int32) // id: %23 + +bb3(%32 : $Builtin.Int32): // Preds: bb0 bb1 +// CHECK-NOT: tuple + %33 = tuple () // user: %34 + br bb4(%33 : $()) // id: %34 + +bb4(%35 : $()): // Preds: bb2 +// CHECK-NEXT: struct $Int32 ([[VAR_21]] : $Builtin.Int32) + %36 = struct $Int32 (%32 : $Builtin.Int32) // user: %37 + destroy_value %1 : $Builtin.NativeObject + dealloc_stack %15 : $*Int32 // id: %41 +// CHECK: return + return %0 : $Builtin.RawPointer +} + +//CHECK-LABEL: remove_dead_args +//CHECK-NOT: br bb2([[VAR_21:%[0-9]+]] : $Bool) +//CHECK: bb3: +//CHECK-NEXT: tuple () +//CHECK-NEXT: return +sil [ossa] @remove_dead_args: $@convention(thin) (Int32) -> () { +bb0(%0 : $Int32): + %1 = integer_literal $Builtin.Int32, 37 + %3 = struct_extract %0 : $Int32, #Int32._value + %4 = builtin "cmp_ne_Int32"(%3 : $Builtin.Int32, %1 : $Builtin.Int32) : $Builtin.Int1 + %5 = struct $Bool (%4 : $Builtin.Int1) + cond_br %4, bb1, bb3 + +bb1: + %7 = integer_literal $Builtin.Int32, 42 + %8 = builtin "cmp_ne_Int32"(%3 : $Builtin.Int32, %7 : $Builtin.Int32) : $Builtin.Int1 + %9 = struct $Bool (%8 : $Builtin.Int1) + br bb2(%9 : $Bool) + +bb3: // Preds: bb0 + %13 = tuple() + br bb2(%5 : $Bool) // id: %14 + +// Remove this ARG. +bb2(%11 : $Bool): // Preds: bb1 bb3 + %12 = tuple () // user: %13 + return %12 : $() // id: %13 + +} + +enum X { + case A + case B((Int32, Int32)) + case C(Int32) +} + +// CHECK-LABEL: simplify_switch_include_args +sil [ossa] @simplify_switch_include_args : $@convention(thin) (Int32, Int32) -> Int32 { +bb0(%0 : $Int32, %1 : $Int32): +// CHECK: return %0 : $Int32 + %2 = tuple (%0 : $Int32, %1 : $Int32) // user: %3 + %3 = enum $X, #X.B!enumelt, %2 : $(Int32, Int32) // user: %4 + switch_enum %3 : $X, case #X.A!enumelt: bb1, case #X.B!enumelt: bb3, case #X.C!enumelt: bb5 // id: %4 + +bb1: // Preds: bb0 + br bb2 // id: %5 + +bb2: // Preds: bb1 + %6 = integer_literal $Builtin.Int32, 0 // user: %7 + %7 = struct $Int32 (%6 : $Builtin.Int32) // user: %8 + br bb7(%7 : $Int32) // id: %8 + +bb3(%9 : $(Int32, Int32)): // Preds: bb0 + %10 = tuple_extract %9 : $(Int32, Int32), 0 // user: %12 + br bb4 // id: %11 + +bb4: // Preds: bb3 + br bb7(%10 : $Int32) // id: %12 + +bb5(%13 : $Int32): // Preds: bb0 + br bb6 // id: %14 + +bb6: // Preds: bb5 + br bb7(%13 : $Int32) // id: %15 + +bb7(%16 : $Int32): // Preds: bb2 bb4 bb6 + return %16 : $Int32 // id: %17 +} + +// CHECK-LABEL: simplify_switch_dont_include_args +sil [ossa] @simplify_switch_dont_include_args : $@convention(thin) (Int32, Int32) -> Int32 { +bb0(%0 : $Int32, %1 : $Int32): + %2 = tuple (%0 : $Int32, %1 : $Int32) // user: %3 + %3 = enum $X, #X.B!enumelt, %2 : $(Int32, Int32) // user: %4 + switch_enum %3 : $X, case #X.A!enumelt: bb1, case #X.B!enumelt: bb3, case #X.C!enumelt: bb5 // id: %4 + +bb1: // Preds: bb0 + br bb2 // id: %5 + +bb2: // Preds: bb1 + %6 = integer_literal $Builtin.Int32, 0 // user: %7 + %7 = struct $Int32 (%6 : $Builtin.Int32) // user: %8 + br bb6 + +bb3(%9 : $(Int32, Int32)): // Preds: bb0 + %10 = tuple_extract %9 : $(Int32, Int32), 0 // user: %12 + br bb4 // id: %11 + +bb4: // Preds: bb3 + br bb6 // id: %12 + +bb5(%xc : $Int32): + br bb6 + +bb6: + return %0 : $Int32 +} + +// CHECK-LABEL: simplify_switch_dont_include_args_nested +sil [ossa] @simplify_switch_dont_include_args_nested : $@convention(thin) (Int32, Int32, X) -> Int32 { +// CHECK: bb0 +// CHECK: switch_enum %2 +bb0(%0 : $Int32, %1 : $Int32, %2 : $X): + switch_enum %2 : $X, case #X.A!enumelt: bb3, case #X.B!enumelt: bb1, case #X.C!enumelt: bb5 + +// CHECK: bb1 +// CHECK-NOT: switch_enum +bb1(%12: $(Int32, Int32)): + switch_enum %2 : $X, case #X.A!enumelt: bb6, case #X.B!enumelt: bb7, case #X.C!enumelt: bb8 + +bb2: + %6 = integer_literal $Builtin.Int32, 0 + %7 = struct $Int32 (%6 : $Builtin.Int32) + br bb9(%7 : $Int32) + +bb3: + %9 = integer_literal $Builtin.Int32, 1 + %10 = struct $Int32 (%9 : $Builtin.Int32) + br bb4(%10 : $Int32) + +bb4(%16 : $Int32): + br bb9(%16 : $Int32) + +bb5(%18 : $Int32): + br bb9(%18 : $Int32) + +bb6: + %20 = integer_literal $Builtin.Int32, 2 + %21 = struct $Int32 (%20 : $Builtin.Int32) + br bb9(%21 : $Int32) + +bb7(%xc: $(Int32, Int32)): + %22 = integer_literal $Builtin.Int32, 3 + %23 = struct $Int32 (%22 : $Builtin.Int32) + br bb9(%23 : $Int32) + +bb8(%25 : $Int32): + br bb9(%25 : $Int32) + +bb9(%27 : $Int32): + return %27 : $Int32 + +} + +// CHECK-LABEL: simplify_switch_wrong_args +sil [ossa] @simplify_switch_wrong_args : $@convention(thin) (Int32, Int32, X, Builtin.Int1) -> Int32 { +// CHECK: bb0 +// CHECK: switch_enum %2 +bb0(%0 : $Int32, %1 : $Int32, %2 : $X, %24 : $Builtin.Int1): + switch_enum %2 : $X, case #X.A!enumelt: bb3, case #X.B!enumelt: bb10, case #X.C!enumelt: bb11 + +bb10(%21 : $(Int32, Int32)): + %22 = tuple_extract %21 : $(Int32, Int32), 1 + cond_br %24, bb1, bb5 + +// CHECK: bb1 +// CHECK-NOT: switch_enum +bb1: + switch_enum %2 : $X, case #X.A!enumelt: bb7, case #X.B!enumelt: bb8, case #X.C!enumelt: bb9 + +bb2: + %6 = integer_literal $Builtin.Int32, 0 + %7 = struct $Int32 (%6 : $Builtin.Int32) + br bb6(%7 : $Int32) + +bb3: + %9 = integer_literal $Builtin.Int32, 1 + %10 = struct $Int32 (%9 : $Builtin.Int32) + br bb4(%10 : $Int32) + +bb4(%16 : $Int32): + br bb6(%16 : $Int32) + +bb11(%18 : $Int32): + br bb6(%18 : $Int32) + +bb5: + br bb6(%22 : $Int32) + +bb6(%19 : $Int32): + return %19 : $Int32 + +bb7: + %14 = integer_literal $Builtin.Int32, 2 + %15 = struct $Int32 (%14 : $Builtin.Int32) + br bb6(%15 : $Int32) + +bb8(%23 : $(Int32, Int32)): + %17 = tuple_extract %21 : $(Int32, Int32), 0 + br bb6(%17 : $Int32) + +bb9(%20 : $Int32): + br bb6(%20 : $Int32) +} + +enum EA { + case cA + case cB(Int32) + case cC(Float) +} + + +// CHECK-LABEL: fold_unreachable_terminators1 +sil [ossa] @fold_unreachable_terminators1 : $@convention(thin) (EA) -> () { +// CHECK: bb0 +// CHECK-NEXT: [[VAL:%[a-zA-Z0-9]+]] = tuple () +// CHECK-NEXT: return [[VAL]] +bb0(%0 : $EA): + switch_enum %0 : $EA, case #EA.cA!enumelt: bb1, case #EA.cB!enumelt: bb3, case #EA.cC!enumelt: bb5 + +bb1: + br bb2 + +bb2: + br bb7 + +bb3(%6 : $Int32): + br bb4 + +bb4: + unreachable + +bb5(%10 : $Float): + br bb6 + +bb6: + unreachable + +bb7: + %14 = tuple () + return %14 : $() +} + +// CHECK-LABEL: fold_unreachable_terminators2 +sil [ossa] @fold_unreachable_terminators2 : $@convention(thin) (EA) -> Int32 { +// CHECK: bb0 +bb0(%0 : $EA): +// CHECK-NEXT: [[VAL:%[a-zA-Z0-9]+]] = unchecked_enum_data %0 : $EA, #EA.cB!enumelt{{.*}} // user: %2 +// CHECK-NEXT: return [[VAL]] : $Int32 + switch_enum %0 : $EA, case #EA.cA!enumelt: bb1, case #EA.cB!enumelt: bb3, case #EA.cC!enumelt: bb5 + +bb1: + br bb2 + +bb2: + unreachable + +bb3(%6 : $Int32): + br bb4 + +bb4: + return %6 : $Int32 + +bb5(%10 : $Float): + br bb6 + +bb6: + unreachable +} + +// CHECK-LABEL: simplify_switch_to_select_enum +sil [ossa] @simplify_switch_to_select_enum : $@convention(thin) (X) -> Bool { +bb0(%0 : $X): +// CHECK: bb0 +// CHECK: [[TRUE:%.*]] = integer_literal {{.*}} -1 +// CHECK: integer_literal {{.*}} 0 +// CHECK: [[FALSE:%.*]] = integer_literal {{.*}} 0 +// CHECK: [[VAL:%[a-zA-Z0-9]+]] = select_enum %0 : $X, case #X.A!enumelt: [[TRUE]], default [[FALSE]] +// CHECK-NOT: switch_enum +// CHECK-NOT: bb2 +// CHECK: [[RETVAL:%[a-zA-Z0-9]+]] = struct $Bool ([[VAL]] : $Builtin.Int1) +// CHECK-NEXT: return [[RETVAL]] : $Bool + %2 = integer_literal $Builtin.Int1, 0 + switch_enum %0 : $X, case #X.A!enumelt: bb1, case #X.B!enumelt: bb2, case #X.C!enumelt: bb3 + +bb1: // Preds: bb0 + %10 = integer_literal $Builtin.Int1, -1 + br bb4(%10 : $Builtin.Int1) + +bb2(%xb : $(Int32, Int32)): // Preds: bb0 + br bb4(%2 : $Builtin.Int1) + +bb3(%xc : $Int32): // Preds: bb0 + br bb4(%2 : $Builtin.Int1) + +bb4(%20 : $Builtin.Int1): // Preds: bb1 bb2 bb3 + %11 = struct $Bool (%20 : $Builtin.Int1) + return %11 : $Bool +} + + +sil [ossa] @external_f : $@convention(thin) () -> () + +// Check that switch_enum to select_enum conversion does not +// take place in a presence of side-effects. +// CHECK-LABEL: simplify_switch_to_select_enum_with_side_effects +sil [ossa] @simplify_switch_to_select_enum_with_side_effects : $@convention(thin) (X) -> Int32 { +bb0(%0 : $X): +// CHECK: bb0 +// CHECK-NOT: select_enum +// CHECK: switch_enum + switch_enum %0 : $X, case #X.A!enumelt: bb1, case #X.B!enumelt: bb2, case #X.C!enumelt: bb3 + +// CHECK: bb1: +bb1: // Preds: bb0 + %10 = integer_literal $Builtin.Int32, 1 + br bb4(%10 : $Builtin.Int32) + +bb2(%xb : $(Int32, Int32)): // Preds: bb0 + // This BB contains a side-effect. Therefore switch_enum cannot be eliminated. + %11 = function_ref @external_f : $@convention(thin) () -> () + %12 = apply %11() : $@convention(thin) () -> () + %13 = integer_literal $Builtin.Int32, 2 + br bb4(%13 : $Builtin.Int32) + +// CHECK: bb3( +// CHECK: br bb4 +bb3(%xc : $Int32): // Preds: bb0 + %14 = integer_literal $Builtin.Int32, 3 + br bb4(%14 : $Builtin.Int32) + +bb4(%20 : $Builtin.Int32): // Preds: bb1 bb2 bb3 +// CHECK: bb4([[VAL:%[a-zA-Z0-9]+]]{{.*}}): +// CHECK: [[RETVAL:%[a-zA-Z0-9]+]] = struct $Int32 ([[VAL]] : $Builtin.Int32) +// CHECK-NEXT: return [[RETVAL]] : $Int32 + %21 = struct $Int32 (%20 : $Builtin.Int32) + return %21 : $Int32 +} + +enum TwoCases { + case A + case B +} + +struct S { + var x: Builtin.Int32 +} + +sil [ossa]@useSandInt : $@convention(thin) (S, Builtin.Int32) -> () + +// CHECK-LABEL: sil [ossa] @dont_opt_switch_enum_with_arg_bb +sil [ossa] @dont_opt_switch_enum_with_arg_bb : $@convention(thin) (TwoCases, S, S) -> Builtin.Int32 { +bb0(%0 : $TwoCases, %1 : $S, %2 : $S): + %3 = integer_literal $Builtin.Int32, 3 + %4 = integer_literal $Builtin.Int32, 4 + // CHECK: switch_enum + switch_enum %0 : $TwoCases, case #TwoCases.A!enumelt: bb1, case #TwoCases.B!enumelt: bb2 + +bb1: + br bb3(%1 : $S) + +bb2: + br bb3(%2 : $S) + +bb3(%10 : $S): + br bb4(%3 : $Builtin.Int32) + +bb4(%20 : $Builtin.Int32): + %11 = function_ref @useSandInt : $@convention(thin) (S, Builtin.Int32) -> () + %12 = apply %11(%10, %20) : $@convention(thin) (S, Builtin.Int32) -> () + cond_br undef, bb5, bb6 + +bb5: + br bb4(%4 : $Builtin.Int32) + +bb6: + // CHECK: return + return %20 : $Builtin.Int32 +} + +enum E { + case Nope, Yup(Builtin.Int1) +} + +// CHECK-LABEL: sil [ossa] @simplify_switch_enum_pred_no_arg +sil [ossa] @simplify_switch_enum_pred_no_arg : $@convention(thin) (E) -> Builtin.Int1 { +bb0(%0 : $E): +// CHECK-LABEL: bb0 +// CHECK: switch_enum %0 : $E, case #E.Nope!enumelt: bb1, case #E.Yup!enumelt: bb2 + switch_enum %0 : $E, case #E.Nope!enumelt: bb1, case #E.Yup!enumelt: bb2 + +bb1: + %1 = integer_literal $Builtin.Int1, -1 + br bb5(%1 : $Builtin.Int1) + +bb2(%yup : $Builtin.Int1): +// CHECK: unchecked_enum_data %0 : $E, #E.Yup!enumelt +// CHECK-NOT: switch_enum +// CHECK: return + switch_enum %0 : $E, case #E.Yup!enumelt: bb4, case #E.Nope!enumelt: bb3 + +bb3: + %2 = integer_literal $Builtin.Int1, 0 + br bb5(%2 : $Builtin.Int1) + +bb4(%3 : $Builtin.Int1): + br bb5(%3 : $Builtin.Int1) + +bb5(%4 : $Builtin.Int1): + return %4 : $Builtin.Int1 +} + +// CHECK-LABEL: sil [ossa] @same_destination_unused_arg +sil [ossa] @same_destination_unused_arg : $@convention(thin) (Optional) -> Optional { +bb0(%0 : $Optional): + %t = integer_literal $Builtin.Int1, 1 + %f = integer_literal $Builtin.Int1, 0 +// CHECK: select_enum +// CHECK-NEXT: return + %1 = select_enum %0 : $Optional, case #Optional.some!enumelt: %t, default %f : $Builtin.Int1 + cond_br %1, bb1, bb2 + +bb1: + br bb3(%0 : $Optional) + +bb2: + br bb3(%0 : $Optional) + +bb3(%3 : $Optional): + return %0 : $Optional +} + +public enum Numbers { + case First, Second, Third, Fourth, Fifth + //var hashValue: Int32 { get } +} + + +// Check that one of the switch_enum instructions can be +// converted into select_enum instructions, even though +// the destination block is used as a target also by +// another switch_enum instruction + +// CHECK-LABEL: sil [ossa] @FormSelectEnumFromTwoSelectSwitches +// CHECK: switch_enum %0 : $Numbers +// CHECK-NOT: switch_enum +// CHECK: select_enum +// CHECK: return +sil [ossa] @FormSelectEnumFromTwoSelectSwitches : $@convention(thin) (Numbers) -> Int32 { +bb0(%0 : $Numbers): + debug_value %0 : $Numbers, let, name "e" // id: %1 + switch_enum %0 : $Numbers, case #Numbers.First!enumelt: bb1, case #Numbers.Second!enumelt: bb3, case #Numbers.Third!enumelt: bb4, default bb6 // id: %2 + +bb1: // Preds: bb0 + br bb2 // id: %3 + +bb2: // Preds: bb1 + %4 = integer_literal $Builtin.Int32, 42 // user: %5 + br bb20(%4 : $Builtin.Int32) // id: %6 + +bb3: // Preds: bb0 + br bb5 // id: %7 + +bb4: // Preds: bb0 + br bb5 // id: %8 + +bb5: // Preds: bb3 bb4 + %9 = integer_literal $Builtin.Int32, 24 // user: %10 + br bb20(%9 : $Builtin.Int32) // id: %11 + +bb6(%default1 : $Numbers): // Preds: bb0 + br bb7 // id: %12 + +bb7: // Preds: bb6 + br bb8 // id: %13 + +bb8: // Preds: bb7 + br bb9 // id: %14 + +bb9: // Preds: bb8 + switch_enum %0 : $Numbers, case #Numbers.First!enumelt: bb10, case #Numbers.Second!enumelt: bb12, case #Numbers.Third!enumelt: bb13, case #Numbers.Fourth!enumelt: bb15, default bb17 // id: %15 + +bb10: // Preds: bb9 + br bb11 // id: %16 + +bb11: // Preds: bb10 + %17 = integer_literal $Builtin.Int32, 42 // user: %18 + br bb20(%17 : $Builtin.Int32) // id: %19 + +bb12: // Preds: bb9 + br bb14 // id: %20 + +bb13: // Preds: bb9 + br bb14 // id: %21 + +bb14: // Preds: bb12 bb13 + %22 = integer_literal $Builtin.Int32, 24 // user: %23 + br bb20(%22 : $Builtin.Int32) // id: %24 + +bb15: // Preds: bb9 + br bb16 // id: %25 + +bb16: // Preds: bb15 + %26 = integer_literal $Builtin.Int32, 100 // user: %27 + br bb20(%26 : $Builtin.Int32) // id: %28 + +bb17(%default2 : $Numbers): // Preds: bb9 + br bb18 // id: %29 + +bb18: // Preds: bb17 + br bb19 // id: %30 + +bb19: // Preds: bb18 + %31 = integer_literal $Builtin.Int32, 100 // user: %32 + br bb20(%31 : $Builtin.Int32) // id: %33 + +bb20(%34 : $Builtin.Int32): // Preds: bb2 bb5 bb11 bb14 bb16 bb19 + %35 = struct $Int32 (%34 : $Builtin.Int32) // user: %36 + return %35 : $Int32 // id: %36 +} + +// CHECK-LABEL: sil [ossa] @FormSelectEnumIntResult +// CHECK-NOT: switch_enum +// CHECK: select_enum +// CHECK: return +sil [ossa] @FormSelectEnumIntResult : $@convention(thin) (Numbers) -> Int32 { +bb0(%0 : $Numbers): + debug_value %0 : $Numbers, let, name "e" // id: %1 + switch_enum %0 : $Numbers, case #Numbers.First!enumelt: bb1, case #Numbers.Second!enumelt: bb3, case #Numbers.Third!enumelt: bb4, case #Numbers.Fourth!enumelt: bb6, default bb8 // id: %2 + +bb1: // Preds: bb0 + br bb2 // id: %3 + +bb2: // Preds: bb1 + %4 = integer_literal $Builtin.Int32, 42 // user: %5 + br bb11(%4 : $Builtin.Int32) // id: %6 + +bb3: // Preds: bb0 + br bb5 // id: %7 + +bb4: // Preds: bb0 + br bb5 // id: %8 + +bb5: // Preds: bb3 bb4 + %9 = integer_literal $Builtin.Int32, 24 // user: %10 + br bb11(%9 : $Builtin.Int32) // id: %11 + +bb6: // Preds: bb0 + br bb7 // id: %12 + +bb7: // Preds: bb6 + %13 = integer_literal $Builtin.Int32, 100 // user: %14 + br bb11(%13 : $Builtin.Int32) // id: %15 + +bb8(%default1 : $Numbers): // Preds: bb0 + br bb9 // id: %16 + +bb9: // Preds: bb8 + br bb10 // id: %17 + +bb10: // Preds: bb9 + %18 = integer_literal $Builtin.Int32, 100 // user: %19 + br bb11(%18 : $Builtin.Int32) // id: %20 + +bb11(%21 : $Builtin.Int32): // Preds: bb2 bb5 bb7 bb10 + %22 = struct $Int32 (%21: $Builtin.Int32) + return %22 : $Int32 // id: %22 +} + +// CHECK-LABEL: sil [ossa] @FormSelectEnumBoolResult +// CHECK-NOT: switch_enum +// CHECK: select_enum +// CHECK: return +sil [ossa] @FormSelectEnumBoolResult : $@convention(thin) (Numbers) -> Bool { +bb0(%0 : $Numbers): + debug_value %0 : $Numbers, let, name "e" // id: %1 + switch_enum %0 : $Numbers, case #Numbers.First!enumelt: bb1, case #Numbers.Second!enumelt: bb3, case #Numbers.Third!enumelt: bb4, default bb6 // id: %2 + +bb1: // Preds: bb0 + br bb2 // id: %3 + +bb2: // Preds: bb1 + %4 = integer_literal $Builtin.Int1, -1 // user: %5 + br bb9(%4 : $Builtin.Int1) // id: %6 + +bb3: // Preds: bb0 + br bb5 // id: %7 + +bb4: // Preds: bb0 + br bb5 // id: %8 + +bb5: // Preds: bb3 bb4 + %9 = integer_literal $Builtin.Int1, 0 // user: %10 + br bb9(%9 : $Builtin.Int1) // id: %11 + +bb6(%default1 : $Numbers): // Preds: bb0 + br bb7 // id: %12 + +bb7: // Preds: bb6 + br bb8 // id: %13 + +bb8: // Preds: bb7 + %14 = integer_literal $Builtin.Int1, -1 // user: %15 + br bb9(%14 : $Builtin.Int1) // id: %16 + +bb9(%17 : $Builtin.Int1): // Preds: bb2 bb5 bb8 + %18 = struct $Bool (%17 : $Builtin.Int1) + return %18 : $Bool // id: %19 +} + +// CHECK-LABEL: sil [ossa] @DontFormSelectEnumBoolResult +// CHECK: switch_enum +// CHECK-NOT: select_enum +// CHECK: return +sil [ossa] @DontFormSelectEnumBoolResult : $@convention(thin) (Numbers, Bool) -> Bool { +bb0(%0 : $Numbers, %1 : $Bool): + debug_value %0 : $Numbers, let, name "e" // id: %2 + %2 = struct_extract %1 : $Bool, #Bool._value + switch_enum %0 : $Numbers, case #Numbers.First!enumelt: bb1, case #Numbers.Second!enumelt: bb3, case #Numbers.Third!enumelt: bb4, default bb6 // id: %4 + +bb1: // Preds: bb0 + br bb2 // id: %5 + +bb2: // Preds: bb1 + %6 = integer_literal $Builtin.Int1, -1 // user: %7 + br bb10(%6 : $Builtin.Int1) // id: %8 + +bb3: // Preds: bb0 + br bb5 // id: %9 + +bb4: // Preds: bb0 + br bb5 // id: %10 + +bb5: // Preds: bb3 bb4 + %11 = integer_literal $Builtin.Int1, 0 // user: %12 + br bb10(%11 : $Builtin.Int1) // id: %13 + +bb6(%default1 : $Numbers): // Preds: bb0 + br bb7 // id: %14 + +bb7: // Preds: bb6 + br bb8 // id: %15 + +bb8: // Preds: bb7 + br bb9 // id: %16 + +bb9: // Preds: bb8 + br bb10(%2 : $Builtin.Int1) // id: %17 + +bb10(%18 : $Builtin.Int1): // Preds: bb2 bb5 bb9 + %19 = struct $Bool (%18 : $Builtin.Int1) + return %19 : $Bool // id: %19 +} + +// --- Test simplifyArguments; unwrap enum argument +// This optimization should not be defeated by the existence of other +// (unrelated) block arguments +struct Payload { + init() +} + +enum MyEnum { + case payload(Payload) +} + +enum ThreeWay { + case one + case two + case three + @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: ThreeWay, _ b: ThreeWay) -> Bool + var hashValue: Int { get } + func hash(into hasher: inout Hasher) +} + +// CHECK-LABEL: sil hidden [noinline] [ossa] @testUnwrapEnumArg : $@convention(thin) () -> () { +// CHECK: bb0: +// CHECK: br bb1(undef : $Builtin.Int64, %{{.*}} : $Payload) +// CHECK: bb1(%{{.*}} : $Builtin.Int64, %{{.*}} : $Payload): +// CHECK: alloc_stack $Payload +// CHECK: switch_enum undef : $ThreeWay, case #ThreeWay.one!enumelt: bb4, case #ThreeWay.two!enumelt: bb2, case #ThreeWay.three!enumelt: bb3 +// CHECK: bb2: +// CHECK: [[P2:%.*]] = load [trivial] %{{.*}} : $*Payload +// CHECK: enum $MyEnum, #MyEnum.payload!enumelt, [[P2]] : $Payload +// CHECK: dealloc_stack %{{.*}} : $*Payload +// CHECK: br bb1(undef : $Builtin.Int64, [[P2]] : $Payload) +// CHECK: bb3: +// CHECK: [[P3:%.*]] = load [trivial] %{{.*}} : $*Payload +// CHECK: enum $MyEnum, #MyEnum.payload!enumelt, [[P3]] : $Payload +// CHECK: dealloc_stack %{{.*}} : $*Payload +// CHECK: br bb7 +// CHECK: bb4: +// CHECK: [[P4:%.*]] = load [trivial] %{{.*}} : $*Payload +// CHECK: enum $MyEnum, #MyEnum.payload!enumelt, [[P4]] : $Payload +// CHECK: dealloc_stack %{{.*}} : $*Payload +// CHECK: cond_br undef, bb6, bb5 +// CHECK: bb5: +// CHECK: br bb1(undef : $Builtin.Int64, [[P4]] : $Payload) +// CHECK: bb6: +// CHECK: br bb7 +// CHECK: bb7: +// CHECK: return %{{.*}} : $() +// CHECK-LABEL: } // end sil function 'testUnwrapEnumArg' +sil hidden [noinline] [ossa] @testUnwrapEnumArg : $@convention(thin) () -> () { +bb0: + %0 = struct $Payload (undef : $_UIntBuffer) + %1 = enum $MyEnum, #MyEnum.payload!enumelt, %0 : $Payload + br bb1(undef : $Builtin.Int64, %1 : $MyEnum) + +bb1(%3 : $Builtin.Int64, %4 : $MyEnum): + %5 = alloc_stack $Payload + %6 = unchecked_enum_data %4 : $MyEnum, #MyEnum.payload!enumelt + store %6 to [trivial] %5 : $*Payload + %8 = builtin "cmp_eq_Int64"(%3 : $Builtin.Int64, undef : $Builtin.Int64) : $Builtin.Int1 + switch_enum undef : $ThreeWay, case #ThreeWay.one!enumelt: bb4, case #ThreeWay.two!enumelt: bb2, case #ThreeWay.three!enumelt: bb3 + +bb2: + %10 = load [trivial] %5 : $*Payload + %11 = enum $MyEnum, #MyEnum.payload!enumelt, %10 : $Payload + dealloc_stack %5 : $*Payload + br bb6(%11 : $MyEnum) + +bb3: + %14 = load [trivial] %5 : $*Payload + %15 = enum $MyEnum, #MyEnum.payload!enumelt, %14 : $Payload + dealloc_stack %5 : $*Payload + br bb8 + +bb4: + %18 = load [trivial] %5 : $*Payload + %19 = enum $MyEnum, #MyEnum.payload!enumelt, %18 : $Payload + dealloc_stack %5 : $*Payload + cond_br undef, bb7, bb5 + +bb5: + br bb6(%19 : $MyEnum) + +bb6(%23 : $MyEnum): + br bb1(undef : $Builtin.Int64, %23 : $MyEnum) + +bb7: + br bb8 + +bb8: + %25 = tuple () + return %25 : $() +} diff --git a/test/SILOptimizer/simplify_cfg_args_ossa_disabled.sil b/test/SILOptimizer/simplify_cfg_args_ossa_disabled.sil new file mode 100644 index 0000000000000..e6f09819ca841 --- /dev/null +++ b/test/SILOptimizer/simplify_cfg_args_ossa_disabled.sil @@ -0,0 +1,34 @@ +// RUN: %target-sil-opt -enable-objc-interop -enforce-exclusivity=none -enable-sil-verify-all %s -jumpthread-simplify-cfg | %FileCheck %s +// +// These tests case are converted to OSSA form, but aren't yet +// optimized in OSSA mode. Move them to simplify_cfg_ossa.sil when +// they are enabled. +// +// REQUIRES: disabled + +class A {} + +//CHECK-LABEL: no_remove_mandatory_dead_args +//CHECK: checked_cast_br {{%.*}} : $AnyObject to A, bb1 +//CHECK-NOT: bb1 +//CHECK: bb1([[VAR:%[0-9]+]] : $A) +//CHECK-NOT: [[VAR]] +//CHECK: return +sil [ossa] @no_remove_mandatory_dead_args : $@convention(thin) (@owned AnyObject) -> Int32 { +bb0(%0 : @owned $AnyObject): + checked_cast_br %0 : $AnyObject to A, bb1, bb2 // id: %3 + +bb1(%1 : $A): + %2 = integer_literal $Builtin.Int32, 1 + destroy_value %1 : $A + br bb3(%2 : $Builtin.Int32) + +bb2(%default : $AnyObject): + %3 = integer_literal $Builtin.Int32, 0 + destroy_value %default : $AnyObject + br bb3(%3 : $Builtin.Int32) + +bb3(%4 : $Builtin.Int32): + %5 = struct $Int32 (%4 : $Builtin.Int32) + return %5 : $Int32 +} diff --git a/test/SILOptimizer/simplify_cfg_ossa.sil b/test/SILOptimizer/simplify_cfg_ossa.sil new file mode 100644 index 0000000000000..8563fad256c31 --- /dev/null +++ b/test/SILOptimizer/simplify_cfg_ossa.sil @@ -0,0 +1,1809 @@ +// RUN: %target-sil-opt -enable-objc-interop -enforce-exclusivity=none -enable-sil-verify-all %s -jumpthread-simplify-cfg | %FileCheck %s + +// Declare this SIL to be canonical because some tests break raw SIL +// conventions. e.g. address-type block args. -enforce-exclusivity=none is also +// required to allow address-type block args in canonical SIL. +sil_stage canonical + +import Builtin +import Swift + +/////////////////////// +// Type Declarations // +/////////////////////// + +class Klass { + var a: Int + deinit + init() +} + +class B {} +class E : B {} + +sil [ossa] @consume_klass : $@convention(thin) (@owned Klass) -> () +sil [ossa] @use_klass : $@convention(thin) (@guaranteed Klass) -> () +sil [ossa] @get_klass : $@convention(thin) () -> @owned Klass + +struct KlassWrapper { + var k: Klass +} + +internal enum CompareResult { + case equal + case less +} + +struct FakeBool { + @_hasStorage var value: Builtin.Int1 { get set } + init(value: Builtin.Int1) +} + +sil [ossa] @dummy : $@convention(thin) () -> FakeBool + +/////////// +// Tests // +/////////// + +// CHECK-LABEL: sil [ossa] @test_dead_block : +// CHECK-NEXT: bb0: +// CHECK-NEXT: unreachable +// CHECK-NEXT: } +sil [ossa] @test_dead_block : $() -> () { +bb0: + unreachable + +bb1: + %4 = integer_literal $Builtin.Int64, 1 + br bb2 + +bb2: + %5 = struct $Int64 (%4 : $Builtin.Int64) + unreachable +} + +// CHECK-LABEL: @release_in_arcinert_termination_block +// CHECK: bb0 +// CHECK: unreachable +// CHECK: } +sil [ossa] @release_in_arcinert_termination_block : $(@owned Klass) -> () { +bb0(%0 : @owned $Klass): + br bb1 + +bb1: + destroy_value %0 : $Klass + unreachable +} + +// CHECK-LABEL: @release_in_nonarcinert_termination_block : +// CHECK: bb0 +// CHECK: destroy_value +// CHECK: apply +// CHECK: unreachable +// CHECK: } // end sil function 'release_in_nonarcinert_termination_block' +sil [ossa] @release_in_nonarcinert_termination_block : $(@owned Klass) -> () { +bb0(%0 : @owned $Klass): + %1 = copy_value %0 : $Klass + br bb1 + +bb1: + destroy_value %0 : $Klass + %2 = function_ref @consume_klass : $@convention(thin) (@owned Klass) -> () + apply %2(%1) : $@convention(thin) (@owned Klass) -> () + unreachable +} + +// CHECK-LABEL: @test_single_pred_block : +// CHECK: bb3([[ARG:%[0-9]+]] : $Builtin.Int64): +// CHECK: struct $Int64 +// CHECK-NEXT: return +// CHECK: } // end sil function 'test_single_pred_block' +sil [ossa] @test_single_pred_block : $@convention(thin) (Builtin.Int1) -> Int64 { +bb0(%0 : $Builtin.Int1): + cond_br %0, bb1, bb3 + +bb1: + %4 = integer_literal $Builtin.Int64, 1 + br bb2(%4 : $Builtin.Int64) + +bb3: + %9 = integer_literal $Builtin.Int64, 2 + br bb2(%9 : $Builtin.Int64) + +bb2(%6 : $Builtin.Int64): + %7 = struct $Int64 (%6 : $Builtin.Int64) + br bb4(%7 : $Int64) + +bb4(%8 : $Int64): + return %8 : $Int64 +} + +// CHECK-LABEL: @test_single_pred_block_nontrivial : +// CHECK: bb3([[ARG:%[0-9]+]] : @owned $Klass): +// CHECK: [[WRAPPED:%.*]] = struct $KlassWrapper ([[ARG]] : +// CHECK-NEXT: return [[WRAPPED]] +// CHECK: } // end sil function 'test_single_pred_block_nontrivial' +sil [ossa] @test_single_pred_block_nontrivial : $@convention(thin) (Builtin.Int1) -> @owned KlassWrapper { +bb0(%0 : $Builtin.Int1): + %f = function_ref @get_klass : $@convention(thin) () -> @owned Klass + cond_br %0, bb1, bb3 + +bb1: + %4a = apply %f() : $@convention(thin) () -> @owned Klass + br bb2(%4a : $Klass) + +bb3: + %4b = apply %f() : $@convention(thin) () -> @owned Klass + br bb2(%4b : $Klass) + +bb2(%5 : @owned $Klass): + %6 = struct $KlassWrapper (%5 : $Klass) + br bb4(%6 : $KlassWrapper) + +bb4(%9 : @owned $KlassWrapper): + return %9 : $KlassWrapper +} + +// CHECK-LABEL: sil [ossa] @canonicalize_not_branch : +// CHECK: bb0([[ARG:%.*]] : $Builtin.Int1): +// CHECK: cond_br [[ARG]], bb2, bb1 +// CHECK: bb1: +// CHECK: integer_literal {{.*}} 2 +// CHECK: br +// CHECK: bb2: +// CHECK: integer_literal {{.*}} 3 +// CHECK: br +// CHECK: } // end sil function 'canonicalize_not_branch' +sil [ossa] @canonicalize_not_branch : $@convention(thin) (Builtin.Int1) -> (Builtin.Int32) { +bb0(%0 : $Builtin.Int1): + %1 = integer_literal $Builtin.Int1, -1 + %2 = builtin "xor_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %2, bb1, bb2 + +bb1: + %4 = integer_literal $Builtin.Int32, 2 + br bb3(%4 : $Builtin.Int32) + +bb2: + %5 = integer_literal $Builtin.Int32, 3 + br bb3(%5 : $Builtin.Int32) + +bb3(%6 : $Builtin.Int32): + return %6 : $Builtin.Int32 +} + +// CHECK-LABEL: sil [ossa] @canonicalize_not_branch_expect : +// CHECK: bb0([[ARG:%.*]] : $Builtin.Int1): +// CHECK: [[ZERO:%.*]] = integer_literal $Builtin.Int1, 0 +// CHECK: [[EXPECT:%.*]] = builtin "int_expect_Int1"([[ARG]] : $Builtin.Int1, [[ZERO]] +// CHECK: cond_br [[EXPECT]], bb2, bb1 +// CHECK: bb1: +// CHECK: integer_literal {{.*}} 2 +// CHECK: br +// CHECK: bb2: +// CHECK: integer_literal {{.*}} 3 +// CHECK: br +// CHECK: } // end sil function 'canonicalize_not_branch_expect' +sil [ossa] @canonicalize_not_branch_expect : $@convention(thin) (Builtin.Int1) -> (Builtin.Int32) { +bb0(%0 : $Builtin.Int1): + %1 = integer_literal $Builtin.Int1, -1 + %2 = builtin "xor_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + %3 = builtin "int_expect_Int1"(%2 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %3, bb1, bb2 + +bb1: + %4 = integer_literal $Builtin.Int32, 2 + br bb3(%4 : $Builtin.Int32) + +bb2: + %5 = integer_literal $Builtin.Int32, 3 + br bb3(%5 : $Builtin.Int32) + +bb3(%6 : $Builtin.Int32): + return %6 : $Builtin.Int32 +} + +enum BoolLike { case true_, false_ } +// BoolLike with a payload of state on the true_ side. + +// func testThread(a : BoolLike) -> Int32 { +// if a { return 42 } else { return 17 } } +// +// CHECK-LABEL: sil [ossa] @testThread : +// CHECK: switch_enum_addr %0 : $*BoolLike, case #BoolLike.true_!enumelt: bb1, case #BoolLike.false_!enumelt: bb2 +// CHECK: } // end sil function 'testThread' +sil [ossa] @testThread : $@convention(thin) (@in BoolLike) -> Int64 { +bb0(%0 : $*BoolLike): + switch_enum_addr %0 : $*BoolLike, case #BoolLike.true_!enumelt: bb1, case #BoolLike.false_!enumelt: bb3 // id: %3 + +bb1: // Preds: bb0 + %4 = integer_literal $Builtin.Int1, -1 // user: %5 + br bb2(%4 : $Builtin.Int1) // id: %5 + +bb2(%6 : $Builtin.Int1): // Preds: bb3 bb1 + br bb4 // id: %7 + +bb3: // Preds: bb0 + %8 = integer_literal $Builtin.Int1, 0 // user: %9 + br bb2(%8 : $Builtin.Int1) // id: %9 + +bb4: // Preds: bb2 + cond_br %6, bb5, bb6 // id: %10 + +bb5: // Preds: bb4 + %11 = metatype $@thin Int64.Type + %12 = integer_literal $Builtin.Int64, 42 // user: %13 + %13 = struct $Int64 (%12 : $Builtin.Int64) // user: %14 + br bb7(%13 : $Int64) // id: %14 + +bb6: // Preds: bb4 + %15 = metatype $@thin Int64.Type + %16 = integer_literal $Builtin.Int64, 17 // user: %17 + %17 = struct $Int64 (%16 : $Builtin.Int64) // user: %18 + br bb7(%17 : $Int64) // id: %18 + +bb7(%19 : $Int64): // Preds: bb6 bb5 + return %19 : $Int64 // id: %21 +} + +/// CHECK-LABEL: sil [ossa] @testCondBrFold +/// CHECK: bb0( +/// CHECK-NEXT: return %1 : $Int64 +sil [ossa] @testCondBrFold : $@convention(thin) (Int64, Int64) -> Int64 { +bb0(%0 : $Int64, %1 : $Int64): + %8 = integer_literal $Builtin.Int1, 0 + cond_br %8, bb1, bb2 +bb1: + unreachable +bb2: + return %1 : $Int64 +} + +/// CHECK-LABEL: sil [ossa] @testSwitchEnumFold +/// CHECK: bb0( +/// CHECK-NEXT: return %0 : $Int64 +sil [ossa] @testSwitchEnumFold : $@convention(thin) (Int64) -> Int64 { +bb0(%0 : $Int64): + %1 = enum $BoolLike, #BoolLike.true_!enumelt + switch_enum %1 : $BoolLike, case #BoolLike.true_!enumelt: bb2, case #BoolLike.false_!enumelt: bb1 +bb1: + unreachable +bb2: + return %0 : $Int64 +} + +// CHECK-LABEL: @elim_trampoline +// CHECK: bb0 +// CHECK: cond_br {{.*}}, bb1, bb2 +// CHECK:bb1: +// CHECK: br bb3(%1 +// CHECK:bb2: +// CHECK: br bb3(%2 +// CHECK:bb3({{.*}}): +// CHECK: return +sil [ossa] @elim_trampoline : $@convention(thin) (Builtin.Int1, Int64, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64, %2 : $Int64): + cond_br %0, bb1, bb2 + +bb1: + br bb3(%1 : $Int64) + +bb2: + br bb3(%2 : $Int64) + +bb3(%5 : $Int64): + return %5 : $Int64 +} + +// CHECK-LABEL: @elim_trampoline2 +// CHECK-NOT: cond_br +// CHECK: return +sil [ossa] @elim_trampoline2 : $@convention(thin) (Builtin.Int1, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64): + cond_br %0, bb1, bb2 + +bb1: + br bb3(%1 : $Int64) + +bb2: + br bb3(%1 : $Int64) + +bb3(%4 : $Int64): + return %4 : $Int64 +} + +// CHECK-LABEL: @elim_trampoline_debug +// CHECK-NOT: cond_br %0, bb1(%1 : $Int64), bb1(%1 : $Int64) +// CHECK: return +sil [ossa] @elim_trampoline_debug : $@convention(thin) (Builtin.Int1, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64): + cond_br %0, bb1, bb2 + +bb1: + debug_value %0 : $Builtin.Int1 + br bb3(%1 : $Int64) + +bb2: + br bb3(%1 : $Int64) + +bb3(%4 : $Int64): + return %4 : $Int64 +} + +// CHECK-LABEL: @elim_trampoline3 +// CHECK: cond_br %0, bb1, bb2 +// CHECK:bb1: +// CHECK: br bb3(%1 +// CHECK:bb2: +// CHECK: br bb3(%2 +// CHECK:bb3({{.*}}): +// CHECK: return +sil [ossa] @elim_trampoline3 : $@convention(thin) (Builtin.Int1, Int64, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64, %2 : $Int64): + cond_br %0, bb1, bb2 + +bb1: + br bb3(%1 : $Int64) + +bb2: + br bb3(%2 : $Int64) + +bb3(%5 : $Int64): + br bb4(%5 : $Int64) + +bb4(%6 : $Int64): + return %6 : $Int64 +} + +// Make sure the cond_br is not simplified as it would create +// a critical edge. +// CHECK-LABEL: @elim_trampoline4 +// CHECK: cond_br %0, bb2, bb1 +// CHECK: bb1: +// CHECK: br bb3 +// CHECK: bb2: +// CHECK-NEXT: br bb3 +// CHECK: bb3 +// CHECK: return +// CHECK: } + +sil [ossa] @external_f : $@convention(thin) () -> () + +sil [ossa] @elim_trampoline4 : $@convention(thin) (Builtin.Int1, Int64, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64, %2 : $Int64): + cond_br %0, bb1, bb2 + +bb1: + br bb3(%1 : $Int64) + +bb2: + br bb4(%2 : $Int64) + +bb4(%4 : $Int64): + %55 = function_ref @external_f : $@convention(thin) () -> () + apply %55() : $@convention(thin) () -> () + br bb5(%4: $Int64) + +bb3(%5 : $Int64): + br bb5(%5 : $Int64) + +bb5(%6 : $Int64): + return %6 : $Int64 +} + +// Make sure that a conditional branch to a trampoline without parameters +// gets eliminated by branching to a target of this trampoline. +// CHECK-LABEL: @elim_trampoline5 +// CHECK: cond_br %{{.*}}, bb1, bb3 +// CHECK: bb3: +// CHECK-NEXT: br bb4 +// CHECK: bb4: +// CHECK: br bb8({{.*}}) +// CHECK: bb5: +// CHECK-NEXT: builtin +// CHECK-NEXT: cond_br +// CHECK: bb8{{.*}}: +// Last cond_br should branch directly to the target of a trampoline +// instead of the original bb3 +// CHECK: cond_br {{.*}}, bb5, bb9 +// CHECK: } +sil [ossa] @elim_trampoline5 : $@convention(thin) (Int32) -> () { +bb0(%0 : $Int32): + %1 = integer_literal $Builtin.Int32, 0 + %2 = struct_extract %0 : $Int32, #Int32._value + %3 = builtin "cmp_eq_Int32"(%1 : $Builtin.Int32, %2 : $Builtin.Int32) : $Builtin.Int1 + cond_br %3, bb1, bb2a + +bb1: + br bbReturn + +bbReturn: + %5 = tuple () + return %5 : $() + +bb2a: + br bb2(%1 : $Builtin.Int32) + +bb2(%7 : $Builtin.Int32): + %8 = integer_literal $Builtin.Int32, 1 + %9 = integer_literal $Builtin.Int1, 0 + %10 = integer_literal $Builtin.Int32, 2 + br bb5(%1 : $Builtin.Int32) + +bb3: + br bb4(%8 : $Builtin.Int32) + +bb4(%13 : $Builtin.Int32): + %14 = builtin "cmp_eq_Int32"(%13 : $Builtin.Int32, %2 : $Builtin.Int32) : $Builtin.Int1 + cond_br %14, bb4a, bb2b + +bb2b: + br bb2(%13 : $Builtin.Int32) + +bb4a: + br bbReturn + +bb5(%16 : $Builtin.Int32): + %17 = builtin "sadd_with_overflow_Int32"(%16 : $Builtin.Int32, %8 : $Builtin.Int32, %9 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %18 = tuple_extract %17 : $(Builtin.Int32, Builtin.Int1), 0 + %19 = builtin "cmp_eq_Int32"(%18 : $Builtin.Int32, %10 : $Builtin.Int32) : $Builtin.Int1 + cond_br %19, bb3, bb6 + +bb6: + br bb5(%18 : $Builtin.Int32) +} + +// CHECK-LABEL: @elim_trampoline_loop +// Make sure we are not crashing on this one. +// CHECK: return +sil [ossa] @elim_trampoline_loop : $@convention(thin) (Builtin.Int1, Int64, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64, %2 : $Int64): + cond_br %0, bb1, bb2 + +bb1: + br bb3(%1 : $Int64) + +bb2: + br bb2a(%2 : $Int64) + +bb2a(%4a : $Int64): + br bb2a(%4a : $Int64) + +bb3(%5 : $Int64): + br bb4(%5 : $Int64) + +bb4(%6 : $Int64): + return %6 : $Int64 +} + +// CHECK-LABEL: @elim_common_arg +// CHECK: bb3: +// CHECK-NEXT: return %1 +sil [ossa] @elim_common_arg : $@convention(thin) (Builtin.Int1, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64): + %f1 = function_ref @external_f : $@convention(thin) () -> () + cond_br %0, bb1, bb2 + +bb1: + apply %f1() : $@convention(thin) () -> () + br bb3(%1 : $Int64) + +bb2: + apply %f1() : $@convention(thin) () -> () + br bb3(%1 : $Int64) + +bb3(%a1 : $Int64): + return %a1 : $Int64 +} + +// CHECK-LABEL: @elim_diamonds +// CHECK: bb0 +// CHECK-NEXT: return %1 +sil [ossa] @elim_diamonds : $@convention(thin) (Builtin.Int1, Int64, Int64) -> Int64 { +bb0(%0 : $Builtin.Int1, %1 : $Int64, %2 : $Int64): + cond_br %0, bb1, bb2 + +bb1: + br bb3(%1 : $Int64) + +bb2: + br bb3(%1 : $Int64) + +bb3(%5 : $Int64): + cond_br %0, bb4, bb5 + +bb4: + br bb6(%5 : $Int64) + +bb5: + br bb6(%5 : $Int64) + +bb6(%8 : $Int64): + return %8 : $Int64 +} + +// CHECK-LABEL: @infinite_loop +// CHECK: bb0 +// CHECK-NEXT: br bb1 +// CHECK: bb1 +// CHECK-NEXT: br bb1 +sil [ossa] @infinite_loop : $@convention(thin) () -> () { +bb0: + br bb1 +bb1: + br bb1 +} + +import Builtin +import Swift + +// CHECK-LABEL: @dead_loop +// CHECK-NOT: br bb +sil [ossa] @dead_loop : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int1, 0 // users: %1, %4 + %2 = integer_literal $Builtin.Int1, -1 // user: %11 + cond_br %0, bb1, bb3 // id: %4 + +bb1: // Preds: bb0 + %5 = integer_literal $Builtin.Int32, 0 // users: %6, %7 + %6 = struct $Int32 (%5 : $Builtin.Int32) + br bb2(%5 : $Builtin.Int32) // id: %7 + +bb2(%8 : $Builtin.Int32): // Preds: bb1 bb2 + %9 = integer_literal $Builtin.Int32, 1 // user: %11 + %11 = builtin "sadd_with_overflow_Int32"(%8 : $Builtin.Int32, %9 : $Builtin.Int32, %2 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) // users: %12, %13 + %12 = tuple_extract %11 : $(Builtin.Int32, Builtin.Int1), 0 // users: %15, %16 + %13 = tuple_extract %11 : $(Builtin.Int32, Builtin.Int1), 1 // user: %14 + cond_fail %13 : $Builtin.Int1 // id: %14 + %15 = struct $Int32 (%12 : $Builtin.Int32) + br bb2(%12 : $Builtin.Int32) // id: %16 + +bb3: // Preds: bb0 + %17 = tuple () // user: %18 + return %17 : $() // id: %18 +} + +class C { + final var value: Int32 + init(v: Int32) +} + + +enum A { + case B, C, D +} + +// CHECK-LABEL: cannot_optimize_switch_enum +sil [ossa] @cannot_optimize_switch_enum : $@convention(thin) (A) -> () { +// CHECK: bb0 +bb0(%0 : $A): +// CHECK: %1 = function_ref +// CHECK-NEXT: switch_enum %0 : $A, case #A.B!enumelt: bb1, default [[BB:bb[0-9a-zA-Z]+]] + %f1 = function_ref @external_f : $@convention(thin) () -> () + switch_enum %0 : $A, case #A.B!enumelt: bb1, default bb2 + +bb1: + apply %f1() : $@convention(thin) () -> () + br bb5 + +// CHECK: [[BB]] +bb2(%defaultArg : $A): +// CHECK-NEXT: switch_enum %0 + switch_enum %0 : $A, case #A.C!enumelt: bb3, default bb4 + +bb3: + apply %f1() : $@convention(thin) () -> () + br bb5 + +bb4(%defaultArg2 : $A): + apply %f1() : $@convention(thin) () -> () + br bb5 + +bb5: + %6 = tuple () + return %6 : $() +} + +// CHECK-LABEL: sil [ossa] @simplify_switch_enum1 +// CHECK: bb0: +// CHECK-NOT: bb[0-9] +// CHECK: apply +// CHECK-NEXT: return +sil [ossa] @simplify_switch_enum1 : $@convention(thin) () -> Int32 { +bb0: + %10 = integer_literal $Builtin.Int32, 2 + %11 = struct $Int32 (%10 : $Builtin.Int32) + %20 = integer_literal $Builtin.Int32, 3 + %21 = struct $Int32 (%20 : $Builtin.Int32) + cond_br undef, bb1, bb2 + +bb1: + %12 = enum $Optional, #Optional.some!enumelt, %11 : $Int32 + br bb3(%12 : $Optional) + +bb2: + %22 = enum $Optional, #Optional.some!enumelt, %21 : $Int32 + br bb3(%22 : $Optional) + +bb3(%30 : $Optional): + %u = function_ref @unknown : $@convention(thin) () -> () + apply %u() : $@convention(thin) () -> () + switch_enum %30 : $Optional, case #Optional.none!enumelt: bb4, case #Optional.some!enumelt: bb5 + +bb4: + br bb6(%11 : $Int32) + +bb5(%payload : $Int32): + br bb6(%21 : $Int32) + +bb6(%r : $Int32): + return %r : $Int32 +} + +// CHECK-LABEL: sil [ossa] @simplify_switch_enum2 +// CHECK: bb3([[A:%[0-9]+]] : $Optional): +// CHECK: apply +// CHECK: [[R:%[0-9]+]] = unchecked_enum_data [[A]] : $Optional, #Optional.some!enumelt +// CHECK: return [[R]] +sil [ossa] @simplify_switch_enum2 : $@convention(thin) (Optional) -> Int32 { +bb0(%0 : $Optional): + %10 = integer_literal $Builtin.Int32, 2 + %11 = struct $Int32 (%10 : $Builtin.Int32) + %20 = integer_literal $Builtin.Int32, 3 + %21 = struct $Int32 (%20 : $Builtin.Int32) + switch_enum %0 : $Optional, case #Optional.none!enumelt: bb1, case #Optional.some!enumelt: bb2 + +bb1: + %12 = enum $Optional, #Optional.some!enumelt, %11 : $Int32 + br bb3(%12 : $Optional) + +bb2(%0payload : $Int32): + br bb3(%0 : $Optional) + +bb3(%30 : $Optional): + %u = function_ref @unknown : $@convention(thin) () -> () + apply %u() : $@convention(thin) () -> () + switch_enum %30 : $Optional, case #Optional.none!enumelt: bb4, case #Optional.some!enumelt: bb5 + +bb4: + br bb6(%21 : $Int32) + +bb5(%p : $Int32): + br bb6(%p : $Int32) + +bb6(%r : $Int32): + return %r : $Int32 +} + +// CHECK-LABEL: sil [ossa] @identical_switch_enum_addr_dests : $@convention(thin) (@in Optional) -> () { +// CHECK: bb0(%0 : $*Optional): +// CHECK-NEXT: tuple +// CHECK-NEXT: return +sil [ossa] @identical_switch_enum_addr_dests : $@convention(thin) (@in Optional) -> () { +bb0(%0 : $*Optional): + switch_enum_addr %0 : $*Optional, case #Optional.none!enumelt: bb1, case #Optional.some!enumelt: bb2 + +bb1: + br bb3 + +bb2: + br bb3 + +bb3: + %r = tuple() + return %r : $() +} + +// CHECK-LABEL: sil [ossa] @identical_switch_value_dests : $@convention(thin) (Builtin.Int32) -> () { +// CHECK: bb0(%0 : $Builtin.Int32): +// CHECK-NEXT: integer_literal +// CHECK-NEXT: integer_literal +// CHECK-NEXT: tuple +// CHECK-NEXT: return +sil [ossa] @identical_switch_value_dests : $@convention(thin) (Builtin.Int32) -> () { +bb0(%0 : $Builtin.Int32): + %1 = integer_literal $Builtin.Int32, 24 + %2 = integer_literal $Builtin.Int32, 25 + switch_value %0 : $Builtin.Int32, case %1: bb1, case %2: bb2 + +bb1: + br bb3 + +bb2: + br bb3 + +bb3: + %r = tuple() + return %r : $() +} + +// CHECK-LABEL: sil [ossa] @fold_switch_value : $@convention(thin) () -> Int32 { +// CHECK: bb0: +// CHECK-NOT: bb1 +// CHECK: integer_literal $Builtin.Int32, 100 +// CHECK-NEXT: struct +// CHECK-NEXT: return +sil [ossa] @fold_switch_value : $@convention(thin) () -> Int32 { +bb0: + %1 = integer_literal $Builtin.Int32, 24 + %2 = integer_literal $Builtin.Int32, 25 + switch_value %1 : $Builtin.Int32, case %1: bb1, case %2: bb2 + +bb1: + %3 = integer_literal $Builtin.Int32, 100 + br bb3(%3 : $Builtin.Int32) + +bb2: + %4 = integer_literal $Builtin.Int32, 200 + br bb3(%4 : $Builtin.Int32) + +bb3(%5 : $Builtin.Int32): + %r = struct $Int32 (%5 : $Builtin.Int32) + return %r : $Int32 +} + +enum OneCase { + case First +} + +enum TwoCase { + case First + case Second +} + +enum ThreeCase { + case First + case Second + case Third +} + +sil [ossa] @unknown : $@convention(thin) () -> () +sil [ossa] @int1_user : $@convention(thin) (Builtin.Int1) -> () + +// CHECK-LABEL: sil [ossa] @select_enum_case_canonicalization : $@convention(thin) (OneCase, TwoCase, TwoCase, ThreeCase, ThreeCase, ThreeCase) -> () { +// CHECK: bb0([[ONE_1:%.*]] : $OneCase, [[TWO_1:%.*]] : $TwoCase, [[TWO_2:%.*]] : $TwoCase, [[THREE_1:%.*]] : $ThreeCase, [[THREE_2:%.*]] : $ThreeCase, [[THREE_3:%.*]] : $ThreeCase): +// CHECK: [[TAG1:%.*]] = select_enum [[ONE_1]] : $OneCase, case #OneCase.First!enumelt: [[TRUE:%[0-9]+]] +// CHECK: [[TAG2:%.*]] = select_enum [[TWO_1]] : $TwoCase, case #TwoCase.First!enumelt: [[TRUE:%[0-9]+]] +// CHECK: [[TAG3:%.*]] = select_enum [[TWO_2]] : $TwoCase, case #TwoCase.First!enumelt: [[TRUE:%[0-9]+]] +// CHECK: [[TAG3_OLD:%.*]] = select_enum [[TWO_2]] : $TwoCase, case #TwoCase.Second!enumelt: [[TRUE:%[0-9]+]] +// CHECK: [[TAG4:%.*]] = select_enum [[THREE_1]] : $ThreeCase, case #ThreeCase.First!enumelt: [[TRUE:%[0-9]+]] +// CHECK: [[TAG5:%.*]] = select_enum [[THREE_2]] : $ThreeCase, case #ThreeCase.Second!enumelt: [[TRUE:%[0-9]+]] +// CHECK: [[TAG6:%.*]] = select_enum [[THREE_3]] : $ThreeCase, case #ThreeCase.Third!enumelt: [[TRUE:%[0-9]+]] +// CHECK: cond_br [[TAG1]], bb1, bb6 +// CHECK: cond_br [[TAG2]], bb2, bb3 +// CHECK: [[INT1_USER_FUN:%.*]] = function_ref @int1_user : $@convention(thin) (Builtin.Int1) -> () +// CHECK: apply [[INT1_USER_FUN]]([[TAG3_OLD]]) +// CHECK: cond_br [[TAG3]], bb5, bb4 +// CHECK: cond_br [[TAG4]], bb7, bb8 +// CHECK: cond_br [[TAG5]], bb9, bb10 +// CHECK: cond_br [[TAG6]], bb11, bb12 +sil [ossa] @select_enum_case_canonicalization : $@convention(thin) (OneCase, TwoCase, TwoCase, ThreeCase, ThreeCase, ThreeCase) -> () { +bb0(%0 : $OneCase, %1 : $TwoCase, %2 : $TwoCase, %3 : $ThreeCase, %4 : $ThreeCase, %5 : $ThreeCase): + %t = integer_literal $Builtin.Int1, 1 + %f = integer_literal $Builtin.Int1, 0 + %6 = select_enum %0 : $OneCase, case #OneCase.First!enumelt: %t, default %f : $Builtin.Int1 + %7 = select_enum %1 : $TwoCase, case #TwoCase.First!enumelt: %t, default %f : $Builtin.Int1 + %8 = select_enum %2 : $TwoCase, case #TwoCase.Second!enumelt: %t, default %f : $Builtin.Int1 + %9 = select_enum %3 : $ThreeCase, case #ThreeCase.First!enumelt: %t, default %f : $Builtin.Int1 + %10 = select_enum %4 : $ThreeCase, case #ThreeCase.Second!enumelt: %t, default %f : $Builtin.Int1 + %11 = select_enum %5 : $ThreeCase, case #ThreeCase.Third!enumelt: %t, default %f : $Builtin.Int1 + %12 = function_ref @unknown : $@convention(thin) () -> () + cond_br %6, bb1a, bb1b + +bb1a: + apply %12() : $@convention(thin) () -> () + cond_br %7, bb2a, bb3a + +bb2a: + apply %12() : $@convention(thin) () -> () + %13 = function_ref @int1_user : $@convention(thin) (Builtin.Int1) -> () + apply %13(%8) : $@convention(thin) (Builtin.Int1) -> () + cond_br %8, bb4a, bb5a + +bb3a: + apply %12() : $@convention(thin) () -> () + br exit + +bb4a: + apply %12() : $@convention(thin) () -> () + br exit + +bb5a: + apply %12() : $@convention(thin) () -> () + br exit + +bb1b: + apply %12() : $@convention(thin) () -> () + cond_br %9, bb2b, bb3b + +bb2b: + apply %12() : $@convention(thin) () -> () + cond_br %10, bb4b, bb5b + +bb3b: + apply %12() : $@convention(thin) () -> () + cond_br %11, bb6b, bb7b + +bb4b: + apply %12() : $@convention(thin) () -> () + br exit + +bb5b: + apply %12() : $@convention(thin) () -> () + br exit + +bb6b: + apply %12() : $@convention(thin) () -> () + br exit + +bb7b: + apply %12() : $@convention(thin) () -> () + br exit + +exit: + apply %12() : $@convention(thin) () -> () + %9999 = tuple() + return %9999 : $() +} + +enum IntEnum : Int32 { + case E0 + case E1 + case E2 +} + +// CHECK-LABEL: sil [ossa] @create_select_value : $@convention(thin) (Builtin.Int32) -> Optional { +// CHECK-DAG: [[X2:%[0-9]+]] = integer_literal $Builtin.Int32, 0 +// CHECK-DAG: [[X5:%[0-9]+]] = enum $Optional, #Optional.none!enumelt +// CHECK-DAG: [[X6:%[0-9]+]] = integer_literal $Builtin.Int32, 2 +// CHECK-DAG: [[X7:%[0-9]+]] = enum $IntEnum, #IntEnum.E2!enumelt +// CHECK-DAG: [[X8:%[0-9]+]] = enum $Optional, #Optional.some!enumelt, [[X7]] : $IntEnum +// CHECK-DAG: [[X9:%[0-9]+]] = integer_literal $Builtin.Int32, 1 +// CHECK-DAG: [[X10:%[0-9]+]] = enum $IntEnum, #IntEnum.E1!enumelt +// CHECK-DAG: [[X11:%[0-9]+]] = enum $Optional, #Optional.some!enumelt, [[X10]] : $IntEnum +// CHECK-DAG: [[X12:%[0-9]+]] = enum $IntEnum, #IntEnum.E0!enumelt +// CHECK-DAG: [[X13:%[0-9]+]] = enum $Optional, #Optional.some!enumelt, [[X12]] : $IntEnum +// CHECK-DAG: [[X14:%[0-9]+]] = select_value %0 : $Builtin.Int32, case [[X6]]: [[X8]], case [[X9]]: [[X11]], case [[X2]]: [[X13]], default [[X5]] : $Optional +// CHECK-DAG: return [[X14]] : $Optional +sil [ossa] @create_select_value : $@convention(thin) (Builtin.Int32) -> Optional { +bb0(%0 : $Builtin.Int32): + %2 = integer_literal $Builtin.Int32, 0 + %4 = builtin "cmp_eq_Int32"(%2 : $Builtin.Int32, %0 : $Builtin.Int32) : $Builtin.Int1 + cond_br %4, bb1, bb2 + +bb1: + %6 = enum $IntEnum, #IntEnum.E0!enumelt + %7 = enum $Optional, #Optional.some!enumelt, %6 : $IntEnum + br bb7(%7 : $Optional) + +bb2: + %9 = integer_literal $Builtin.Int32, 1 + %10 = builtin "cmp_eq_Int32"(%9 : $Builtin.Int32, %0 : $Builtin.Int32) : $Builtin.Int1 + cond_br %10, bb3, bb4 + +bb3: + %12 = enum $IntEnum, #IntEnum.E1!enumelt + %13 = enum $Optional, #Optional.some!enumelt, %12 : $IntEnum + br bb7(%13 : $Optional) + +bb4: + %15 = integer_literal $Builtin.Int32, 2 + %16 = builtin "cmp_eq_Int32"(%15 : $Builtin.Int32, %0 : $Builtin.Int32) : $Builtin.Int1 + cond_br %16, bb5, bb6 + +bb5: + %18 = enum $IntEnum, #IntEnum.E2!enumelt + %19 = enum $Optional, #Optional.some!enumelt, %18 : $IntEnum + br bb7(%19 : $Optional) + +bb6: + %21 = enum $Optional, #Optional.none!enumelt + br bb7(%21 : $Optional) + +bb7(%23 : $Optional): + return %23 : $Optional +} + +// CHECK-LABEL: sil [ossa] @checked_cast_anyobject_metatypeinst_to_class +// CHECK: bb0 +// CHECK-NOT: checked_cast +// CHECK-NOT: bb1 +// CHECK: [[RET:%.*]] = tuple () +// CHECK: return [[RET]] : $() + +sil [ossa] @checked_cast_anyobject_metatypeinst_to_class : $@convention(thin)() -> () { +bb0: + %0 = metatype $@thick AnyObject.Protocol + checked_cast_br %0 : $@thick AnyObject.Protocol to B.Type, bb1, bb2 + +bb1(%3 : $@thick B.Type): + br bb3 + +bb2(%default : $@thick AnyObject.Protocol): + br bb3 + +bb3: + %2 = tuple() + return %2 : $() +} + + +// CHECK-LABEL: sil [ossa] @remove_cond_fail_trueblock +// CHECK: bb0([[COND:%.*]] : +// CHECK-NOT: bb +// CHECK: cond_fail [[COND]] +// CHECK-NOT: bb +// CHECK: return +sil [ossa] @remove_cond_fail_trueblock : $@convention(thin)(Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1): + cond_br %0, bb1, bb2 +bb1: + %1 = integer_literal $Builtin.Int1, -1 + cond_fail %1 : $Builtin.Int1 + unreachable +bb2: + %2 = tuple() + return %2 : $() +} + +// CHECK-LABEL: sil [ossa] @remove_cond_fail_falseblock +// CHECK: bb0([[COND:%.*]] : +// CHECK-NOT: bb +// CHECK: [[TRUE:%.*]] = integer_literal $Builtin.Int1, -1 +// CHECK-NOT: bb +// CHECK: [[NOTCOND:%.*]] = builtin "xor_Int1"([[COND]]{{.*}}, [[TRUE]] +// CHECK: cond_fail [[NOTCOND]] +// CHECK-NOT: bb +// CHECK: return +sil [ossa] @remove_cond_fail_falseblock : $@convention(thin)(Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1): + cond_br %0, bb2, bb1 + +bb1: + %1 = integer_literal $Builtin.Int1, -1 + cond_fail %1 : $Builtin.Int1 + unreachable + +bb2: + %2 = tuple() + return %2 : $() +} + +// CHECK-LABEL: sil [ossa] @dont_remove_cond_fail_wrong_const +// CHECK: bb0(%0 : $Builtin.Int1): +// CHECK-NEXT: cond_br %0, bb1, bb2 +sil [ossa] @dont_remove_cond_fail_wrong_const : $@convention(thin) (Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1): + cond_br %0, bb1, bb1a + +bb1: + %i1 = integer_literal $Builtin.Int1, 0 + cond_fail %i1 : $Builtin.Int1 + br bb2 + +bb1a: + br bb2 + +bb2: + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil [ossa] @remove_cond_fail_same_cond_in_true +// CHECK: bb0([[COND:%[0-9]*]] +// CHECK-NOT: bb +// CHECK: cond_fail [[COND]] +// CHECK: bb1: +// CHECK: return +sil [ossa] @remove_cond_fail_same_cond_in_true : $@convention(thin)(Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + cond_br %0, bb1b, bb2 + +bb1b: + br bb1 + +bb1: + cond_fail %0 : $Builtin.Int1 + unreachable + +bb2: + // Make bb1 not dominated from bb0 to prevent that dominator based + // simplification does the same thing. + %55 = function_ref @external_f : $@convention(thin) () -> () + apply %55() : $@convention(thin) () -> () + cond_br %1, bb1a, bb3 + +bb1a: + br bb1 + +bb3: + %2 = tuple() + return %2 : $() +} + +// CHECK-LABEL: sil [ossa] @remove_cond_fail_same_cond_in_false +// CHECK: bb0([[COND:%[0-9]*]] +// CHECK-NOT: bb +// CHECK: [[INV:%[0-9]*]] = builtin "xor_Int1"([[COND]] +// CHECK-NOT: bb +// CHECK: cond_fail [[INV]] +// CHECK: bb1: +// CHECK: return +sil [ossa] @remove_cond_fail_same_cond_in_false : $@convention(thin)(Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + cond_br %0, bb2, bb1b + +bb1b: + br bb1 + +bb1: + %i1 = integer_literal $Builtin.Int1, -1 + %i2 = builtin "xor_Int1"(%0 : $Builtin.Int1, %i1 : $Builtin.Int1) : $Builtin.Int1 + cond_fail %i2 : $Builtin.Int1 + unreachable + +bb2: + // Make bb1 not dominated from bb0 to prevent that dominator based + // simplification does the same thing. + %55 = function_ref @external_f : $@convention(thin) () -> () + apply %55() : $@convention(thin) () -> () + cond_br %1, bb1a, bb3 + +bb1a: + br bb1 + +bb3: + %2 = tuple() + return %2 : $() +} + +// CHECK-LABEL: sil [ossa] @remove_cond_fail_same_cond_in_false2 +// CHECK: bb0([[COND:%[0-9]*]] +// CHECK-NOT: bb +// CHECK: cond_fail [[COND]] +// CHECK: bb1: +// CHECK: return +sil [ossa] @remove_cond_fail_same_cond_in_false2 : $@convention(thin)(Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + %i1 = integer_literal $Builtin.Int1, -1 + %i2 = builtin "xor_Int1"(%0 : $Builtin.Int1, %i1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %i2, bb2, bb1a + +bb1a: + br bb1 + +bb1: + cond_fail %0 : $Builtin.Int1 + unreachable + +bb2: + // Make bb1 not dominated from bb0 to prevent that dominator based + // simplification does the same thing. + %55 = function_ref @external_f : $@convention(thin) () -> () + apply %55() : $@convention(thin) () -> () + cond_br %1, bb1b, bb3 + +bb1b: + br bb1 + +bb3: + %2 = tuple() + return %2 : $() +} + +// CHECK-LABEL: sil [ossa] @dont_remove_cond_fail_same_cond_in_false +// CHECK: bb0([[COND:%[0-9]*]] +// CHECK-NEXT: cond_br +// CHECK: bb1: +// CHECK: br bb2 +// CHECK: bb2: +// CHECK-NEXT: cond_fail [[COND]] +// CHECK: return +sil [ossa] @dont_remove_cond_fail_same_cond_in_false : $@convention(thin)(Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + cond_br %0, bb3, bb1 + +bb1: + br bb2 + +bb2: + cond_fail %0 : $Builtin.Int1 + unreachable + +bb3: + // Make bb1 not dominated from bb0. + %55 = function_ref @external_f : $@convention(thin) () -> () + apply %55() : $@convention(thin) () -> () + cond_br %1, bb4, bb5 + +bb4: + br bb2 + +bb5: + %2 = tuple() + return %2 : $() +} + +// CHECK-LABEL: sil [ossa] @move_cond_fail +// CHECK: bb1: +// CHECK-NEXT: apply +// CHECK-NEXT: [[X:%[0-9]*]] = integer_literal $Builtin.Int1, -1 +// CHECK-NEXT: cond_fail [[X]] +// CHECK-NEXT: br bb3 +// CHECK: bb2: +// CHECK-NEXT: apply +// CHECK-NEXT: cond_fail %1 +// CHECK-NEXT: br bb3 +// CHECK: bb3: +// CHECK-NOT: cond_fail +// CHECK: return +sil [ossa] @move_cond_fail : $@convention(thin) (Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + %f1 = function_ref @external_f : $@convention(thin) () -> () + cond_br %0, bb2, bb1 + +bb1: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + %i1 = integer_literal $Builtin.Int1, -1 + br bb3(%i1 : $Builtin.Int1) + +bb2: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%1 : $Builtin.Int1) + +bb3(%a3 : $Builtin.Int1): + cond_fail %a3 : $Builtin.Int1 + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil [ossa] @move_cond_fail_inverted +// CHECK: bb1: +// CHECK-NEXT: apply +// CHECK-NEXT: [[X:%[0-9]*]] = integer_literal $Builtin.Int1, -1 +// CHECK-NEXT: [[Y:%[0-9]*]] = builtin "xor_Int1"(%2 : $Builtin.Int1, [[X]] : $Builtin.Int1) +// CHECK-NEXT: cond_fail [[Y]] +// CHECK-NEXT: br bb3 +// CHECK: bb2: +// CHECK-NEXT: apply +// CHECK-NEXT: [[R:%[0-9]*]] = integer_literal $Builtin.Int1, -1 +// CHECK-NEXT: [[S:%[0-9]*]] = builtin "xor_Int1"(%1 : $Builtin.Int1, [[R]] : $Builtin.Int1) +// CHECK-NEXT: cond_fail [[S]] +// CHECK-NEXT: br bb3 +// CHECK: bb3({{.*}}): +// CHECK-NOT: cond_fail +// CHECK: return +sil [ossa] @move_cond_fail_inverted : $@convention(thin) (Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + %2 = integer_literal $Builtin.Int1, -1 + %f1 = function_ref @external_f : $@convention(thin) () -> () + cond_br %0, bb2, bb1 + +bb1: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%2 : $Builtin.Int1) + +bb2: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%1 : $Builtin.Int1) + +bb3(%a3 : $Builtin.Int1): + %v1 = builtin "xor_Int1"(%a3 : $Builtin.Int1, %2 : $Builtin.Int1) : $Builtin.Int1 + cond_fail %v1 : $Builtin.Int1 + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil [ossa] @dont_move_cond_fail_no_const +// CHECK: bb1: +// CHECK-NEXT: apply +// CHECK-NEXT: br bb3 +// CHECK: bb2: +// CHECK-NEXT: apply +// CHECK-NEXT: br bb3 +// CHECK: bb3({{.*}}): +// CHECK-NEXT: cond_fail +sil [ossa] @dont_move_cond_fail_no_const : $@convention(thin) (Builtin.Int1, Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1, %2 : $Builtin.Int1): + %f1 = function_ref @external_f : $@convention(thin) () -> () + cond_br %0, bb2, bb1 + +bb1: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%1 : $Builtin.Int1) + +bb2: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%2 : $Builtin.Int1) + +bb3(%a3 : $Builtin.Int1): + cond_fail %a3 : $Builtin.Int1 + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil [ossa] @dont_move_cond_fail_no_postdom +// CHECK: bb1: +// CHECK-NEXT: apply +// CHECK-NEXT: integer_literal +// CHECK-NEXT: br bb4 +// CHECK: bb2: +// CHECK-NEXT: apply +// CHECK-NEXT: cond_br +// CHECK: bb3: +// CHECK: br bb4 +// CHECK: bb4({{.*}}): +// CHECK-NEXT: apply +// CHECK-NEXT: cond_fail +sil [ossa] @dont_move_cond_fail_no_postdom : $@convention(thin) (Builtin.Int1, Builtin.Int1, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1, %2 : $Builtin.Int1): + %f1 = function_ref @external_f : $@convention(thin) () -> () + cond_br %0, bb2, bb1 + +bb1: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + %i1 = integer_literal $Builtin.Int1, -1 + br bb3b(%i1 : $Builtin.Int1) + +bb3b(%1b : $Builtin.Int1): + br bb3(%1b : $Builtin.Int1) + +bb2: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + cond_br %2, bb3a, bb4a + +bb3a: + br bb3(%1 : $Builtin.Int1) + +bb3(%a3 : $Builtin.Int1): + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + cond_fail %a3 : $Builtin.Int1 + br bb4 + +bb4a: + br bb4 + +bb4: + %r = tuple () + return %r : $() +} + +// CHECK-LABEL: sil [ossa] @dont_move_cond_fail_multiple_uses +// CHECK: bb1: +// CHECK-NEXT: apply +// CHECK-NEXT: integer_literal +// CHECK-NEXT: br bb3 +// CHECK: bb2: +// CHECK-NEXT: apply +// CHECK-NEXT: br bb3 +// CHECK: bb3({{.*}}): +// CHECK-NEXT: cond_fail +sil [ossa] @dont_move_cond_fail_multiple_uses : $@convention(thin) (Builtin.Int1, Builtin.Int1) -> Builtin.Int1 { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + %f1 = function_ref @external_f : $@convention(thin) () -> () + cond_br %0, bb2, bb1 + +bb1: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + %i1 = integer_literal $Builtin.Int1, -1 + br bb3(%i1 : $Builtin.Int1) + +bb2: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%1 : $Builtin.Int1) + +bb3(%a3 : $Builtin.Int1): + cond_fail %a3 : $Builtin.Int1 + return %a3 : $Builtin.Int1 +} + +// CHECK-LABEL: sil [ossa] @dont_move_cond_fail_multiple_uses2 +// CHECK: bb1: +// CHECK-NEXT: apply +// CHECK-NEXT: br bb3 +// CHECK: bb2: +// CHECK-NEXT: apply +// CHECK-NEXT: br bb3 +// CHECK: bb3({{.*}}): +// CHECK-NEXT: builtin "xor_Int1" +// CHECK-NEXT: cond_fail +// CHECK-NEXT: return +sil [ossa] @dont_move_cond_fail_multiple_uses2 : $@convention(thin) (Builtin.Int1, Builtin.Int1) -> Builtin.Int1 { +bb0(%0 : $Builtin.Int1, %1 : $Builtin.Int1): + %f1 = function_ref @external_f : $@convention(thin) () -> () + %i1 = integer_literal $Builtin.Int1, -1 + cond_br %0, bb2, bb1 + +bb1: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%i1 : $Builtin.Int1) + +bb2: + apply %f1() : $@convention(thin) () -> () // prevent other CFG optimizations + br bb3(%1 : $Builtin.Int1) + +bb3(%a3 : $Builtin.Int1): + %v1 = builtin "xor_Int1"(%a3 : $Builtin.Int1, %i1 : $Builtin.Int1) : $Builtin.Int1 + cond_fail %v1 : $Builtin.Int1 + return %v1 : $Builtin.Int1 +} + +// CHECK-LABEL: sil [ossa] @successful_checked_cast_br_on_alloc_ref +// CHECK: bb0 +// CHECK-NEXT: alloc_ref +// CHECK-NOT: checked_cast_br +// CHECK-NOT: bb1 +// CHECK: destroy_value +// CHECK: integer_literal $Builtin.Int32, 1 +// CHECK: return +sil [ossa] @successful_checked_cast_br_on_alloc_ref : $() -> Builtin.Int32 { +bb0: + %1 = alloc_ref $B + checked_cast_br [exact] %1 : $B to B, bb1, bb2 + +bb1(%2 : @owned $B): + destroy_value %2 : $B + %3 = integer_literal $Builtin.Int32, 1 + br bb3 (%3 : $Builtin.Int32) + +bb2(%2a : @owned $B): + destroy_value %2a : $B + %5 = integer_literal $Builtin.Int32, 2 + br bb3 (%5 : $Builtin.Int32) + +bb3(%10 : $Builtin.Int32): + return %10 : $Builtin.Int32 +} + +// CHECK-LABEL: sil [ossa] @failing_checked_cast_br_on_alloc_ref +// CHECK: bb0 +// CHECK-NEXT: alloc_ref +// CHECK-NOT: checked_cast_br +// CHECK-NOT: bb1 +// CHECK: destroy_value +// CHECK: integer_literal $Builtin.Int32, 2 +// CHECK: return +sil [ossa] @failing_checked_cast_br_on_alloc_ref : $() -> Builtin.Int32 { +bb0: + %1 = alloc_ref $E + %2 = upcast %1 : $E to $B + checked_cast_br [exact] %2 : $B to B, bb1, bb2 + +bb1(%3 : @owned $B): + destroy_value %3 : $B + %4 = integer_literal $Builtin.Int32, 1 + br bb3 (%4 : $Builtin.Int32) + +bb2(%3a : @owned $B): + destroy_value %3a : $B + %5 = integer_literal $Builtin.Int32, 2 + br bb3 (%5 : $Builtin.Int32) + +bb3(%10 : $Builtin.Int32): + return %10 : $Builtin.Int32 +} + +// CHECK-LABEL: sil [ossa] @unpack_enum_arg +// CHECK: bb1: +// CHECK: br bb3(%0 : $Int) +// CHECK: bb2: +// CHECK: br bb3(%1 : $Int) +// CHECK: bb3([[A:%[0-9]+]] : $Int): +// CHECK: return [[A]] +sil [ossa] @unpack_enum_arg : $@convention(thin) (Int, Int) -> Int { +bb0(%0 : $Int, %1 : $Int): + cond_br undef, bb1, bb2 + +bb1: + %2 = enum $Optional, #Optional.some!enumelt, %0 : $Int + br bb3(%2 : $Optional) + +bb2: + %3 = enum $Optional, #Optional.some!enumelt, %1 : $Int + br bb3(%3 : $Optional) + +bb3(%4 : $Optional): + %5 = unchecked_enum_data %4 : $Optional, #Optional.some!enumelt + return %5 : $Int +} + +// CHECK-LABEL: sil [ossa] @dont_crash_on_enum_payload_is_enum +// CHECK: bb0(%0 : $TwoCase): +// CHECK: switch_enum %0 +// CHECK: bb1: +// CHECK: return +sil [ossa] @dont_crash_on_enum_payload_is_enum : $@convention(thin) (TwoCase) -> () { +bb0(%0 : $TwoCase): + %x = enum $Optional, #Optional.some!enumelt, %0 : $TwoCase + br bb1(%x : $Optional) + +bb1(%10 : $Optional): + switch_enum %10 : $Optional, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb5 + +bb2(%1 : $TwoCase): + // Simplification of that switch_enum crashed the compiler because + // SILArgument::getIncomingValues returns %10 ($Optional) as the + // incoming value for %1 ($TwoCase). + // rdar://problem/26251856 + switch_enum %1 : $TwoCase, case #TwoCase.First!enumelt: bb3, case #TwoCase.Second!enumelt: bb4 + +bb3: + %f1 = function_ref @unknown : $@convention(thin) () -> () + %n1 = apply %f1() : $@convention(thin) () -> () + br bb6 + +bb4: + br bb6 + +bb5: + br bb6 + +bb6: + %6 = tuple () + return %6 : $() +} + +class Base { + @inline(never) func inner() + func middle() + func outer() +} + +class Derived : Base { + override func inner() + @inline(never) final override func middle() +} + +class Final : Derived { +} + +enum TestEnum { + case int64(Builtin.Int64) + case string (Base) + case none +} + +enum MyError : Error { + case a + case b + case c(TestEnum) +} + +enum MyError2 : Error { + case o(Optional) +} + +sil [ossa] @foo : $@convention(thin) (Builtin.Int64) -> Builtin.Int8 +sil [ossa] @foo2 : $@convention(thin) (Builtin.Int32) -> Builtin.Int8 + +sil [ossa] @dont_thread_throw_block : $@convention(thin) (@guaranteed TestEnum) -> (Builtin.Int8, @error Error) { +bb0(%0 : @guaranteed $TestEnum): + switch_enum %0 : $TestEnum, case #TestEnum.int64!enumelt: bb1, default bb4 + +bb1(%5 : $Builtin.Int64): + %7 = function_ref @foo : $@convention(thin) (Builtin.Int64) -> Builtin.Int8 + %9 = apply %7(%5) : $@convention(thin) (Builtin.Int64) -> Builtin.Int8 + br bb6(%9 : $Builtin.Int8) + +bb2(%11 : $Builtin.Int32): + %13 = function_ref @foo2 : $@convention(thin) (Builtin.Int32) -> Builtin.Int8 + %15 = apply %13(%11) : $@convention(thin) (Builtin.Int32) -> Builtin.Int8 + br bb6(%15 : $Builtin.Int8) + +bb4(%default : @guaranteed $TestEnum): + %22 = alloc_existential_box $Error, $MyError2 + %22a = begin_borrow %22 : $Error + %23 = project_existential_box $MyError2 in %22a : $Error + switch_enum %0 : $TestEnum, case #TestEnum.none!enumelt: bbnone, case #TestEnum.int64!enumelt: bb7, case #TestEnum.string!enumelt: bb10 + +bbnone: + %tn = enum $TestEnum, #TestEnum.none!enumelt + %en = enum $MyError, #MyError.c!enumelt, %tn : $TestEnum + br bb5(%en : $MyError) + +bb7(%50 : $Builtin.Int64): + %t = enum $TestEnum, #TestEnum.int64!enumelt, %50 : $Builtin.Int64 + %e1 = enum $MyError, #MyError.c!enumelt, %t : $TestEnum + br bb5(%e1 : $MyError) + +bb10(%53 : @guaranteed $Base): + %t4 = enum $TestEnum, #TestEnum.string!enumelt, %53 : $Base + %e4 = enum $MyError, #MyError.c!enumelt, %t4 : $TestEnum + %e4a = copy_value %e4 : $MyError + br bb5(%e4a : $MyError) + +bb5(%e : @owned $MyError): + %89 = enum $Optional, #Optional.some!enumelt, %e : $MyError + %e5 = enum $MyError2, #MyError2.o!enumelt, %89 : $Optional + store %e5 to [init] %23 : $*MyError2 + end_borrow %22a : $Error + throw %22 : $Error + +bb6(%44 : $Builtin.Int8): + return %44 : $Builtin.Int8 +} + +protocol Q {} + +class IsQ : Q {} + +// checked_cast_br take_* should be replaced by a destroy in case it ever +// converts a managed object to an unmanaged value. Currently this doesn't +// happen at the language level because bridge (NSNumber->Int) casts aren't +// represented with checked_cast_br. +// --- +// CHECK-LABEL: sil [ossa] @test_dead_checked_cast_br : $@convention(thin) (@in IsQ) -> () { +// CHECK: bb0(%0 : $*IsQ): +// CHECK: [[Q:%.*]] = alloc_stack $Q +// CHECK: [[LD:%.*]] = load [take] %0 : $*IsQ +// CHECK: destroy_value [[LD]] : $IsQ +// CHECK: dealloc_stack [[Q]] : $*Q +// CHECK: [[R:%.*]] = tuple () +// CHECK: return [[R]] : $() +// CHECK-LABEL: } // end sil function 'test_dead_checked_cast_br' +sil [ossa] @test_dead_checked_cast_br : $@convention(thin) (@in IsQ) -> () { +bb0(%0 : $*IsQ): + %p = alloc_stack $Q + checked_cast_addr_br take_always IsQ in %0 : $*IsQ to Q in %p : $*Q, bb1, bb3 + +bb1: + %m1 = integer_literal $Builtin.Int1, -1 + br bb2(%m1 : $Builtin.Int1) + +bb2(%5 : $Builtin.Int1): + // To avoid violating ownership, Q needs to be destroyed here. However, that + // would create a use of the checked_cast, defeating the test. In theory, Q + // could be some unmananged type with no destroy, but we don't have a way to + // express that in the type system, and bridged casts don't yet go through + // this optimization path. + dealloc_stack %p : $*Q + %r = tuple () + return %r : $() + +bb3: + %z = integer_literal $Builtin.Int1, 0 + br bb2(%z : $Builtin.Int1) +} + +// CHECK-LABEL: sil [ossa] @dont_hang +// CHECK: bb6: +// CHECK: integer_literal $Builtin.Int64, 1 +// CHECK-NEXT: br bb6 +// CHECK-NEXT: } +sil [ossa] @dont_hang : $@convention(thin) () -> () { +bb0: + cond_br undef, bb1, bb4 + +bb1: + %0 = integer_literal $Builtin.Int64, 1 + cond_br undef, bb2a, bb6a + +bb2a: + br bb2 + +bb2: + br bb3 + +bb3: + br bb5 + +bb4: + %1 = integer_literal $Builtin.Int64, 1 + br bb3 + +bb5: + br bb2 + +bb6a: + br bb6 + +bb6: + %2 = integer_literal $Builtin.Int64, 1 + br bb7 + +bb7: + br bb5 +} + +// CHECK-LABEL: sil [ossa] @test_constant_folding +// CHECK: [[R:%[0-9]+]] = integer_literal $Builtin.Int32, 30 +// CHECK: return [[R]] : $Builtin.Int32 +// CHECK-NEXT: } +sil [ossa] @test_constant_folding : $@convention(thin) () -> Builtin.Int32 { +bb0: + %0 = integer_literal $Builtin.Int1, 0 + %20 = integer_literal $Builtin.Int32, 20 + %30 = integer_literal $Builtin.Int32, 30 + cond_br %0, bb1, bb2 +bb1: + br bb3(%20 : $Builtin.Int32) +bb2: + br bb3(%30 : $Builtin.Int32) + +bb3(%2 : $Builtin.Int32): + %3 = builtin "cmp_slt_Int32"(%2 : $Builtin.Int32, %30 : $Builtin.Int32) : $Builtin.Int1 + cond_br %3, bb4, bb5 +bb4: + br bb6(%20 : $Builtin.Int32) +bb5: + br bb6(%30 : $Builtin.Int32) + +bb6(%4 : $Builtin.Int32): + %5 = builtin "cmp_slt_Int32"(%4 : $Builtin.Int32, %30 : $Builtin.Int32) : $Builtin.Int1 + cond_br %5, bb7, bb8 +bb7: + br bb9(%20 : $Builtin.Int32) +bb8: + br bb9(%30 : $Builtin.Int32) + +bb9(%6 : $Builtin.Int32): + return %6 : $Builtin.Int32 +} + +struct TestStr { + let a: Int32 + let c: Int32 +} + +enum TestEnm { + case X + case Y(TestStr) +} + +// CHECK-LABEL: sil [ossa] @dont_crash +// CHECK: bb0(%0 : $TestEnm, %1 : $Int32): +// CHECK-NEXT: %2 = tuple () +// CHECK-NEXT: return %2 : $() +sil [ossa] @dont_crash : $@convention(method) (TestEnm, Int32) -> () { +bb0(%2 : $TestEnm, %3 : $Int32): + %98 = integer_literal $Builtin.Int1, -1 + cond_br %98, bb2a, bb3 + +bb2a: + br bb2 + +bb2: + %18 = tuple() + return %18 : $() + +bb3: + br bb8(%2 : $TestEnm) + +bb8(%47 : $TestEnm): + %49 = unchecked_enum_data %47 : $TestEnm, #TestEnm.Y!enumelt + %57 = struct_extract %49 : $TestStr, #TestStr.c + cond_br undef, bb9, bb11 + +bb9: + br bb10 + +bb10: + %64 = struct $TestStr (%3 : $Int32, %57 : $Int32) + %65 = enum $TestEnm, #TestEnm.Y!enumelt, %64 : $TestStr + cond_br undef, bb2b, bb16 + +bb2b: + br bb2 + +bb11: + br bb10 + + +bb16: + br bb8(%65 : $TestEnm) +} + +sil [ossa] @print : $@convention(thin) (@guaranteed String) -> () +sil [ossa] @yield_string : $@yield_once @convention(thin) () -> @yields @guaranteed String + +sil [ossa] @dont_clone_begin_apply : $(Builtin.Int1, @guaranteed String) -> () { +bb0(%condition : $Builtin.Int1, %arg : @guaranteed $String): + %print = function_ref @print : $@convention(thin) (@guaranteed String) -> () + cond_br %condition, bb1, bb2 +bb1: + apply %print(%arg) : $@convention(thin) (@guaranteed String) -> () + br bb3 +bb2: + br bb3 +bb3: + %yield_string = function_ref @yield_string : $@yield_once @convention(thin) () -> @yields @guaranteed String + (%yield, %token) = begin_apply %yield_string() : $@yield_once @convention(thin) () -> @yields @guaranteed String + cond_br %condition, bb4, bb5 +bb4: + apply %print(%yield) : $@convention(thin) (@guaranteed String) -> () + br bb6 +bb5: + br bb6 +bb6: + end_apply %token + %rv = tuple () + return %rv : $() +} + +class X { + @objc func f() { } +} + +sil [ossa] @external_g : $@convention(thin) () -> () + +// Don't tail duplicate dynamic_method_br. IRGen cannot handle phi nodes of +// objc_methods. +// CHECK-LABEL: sil [ossa] @dont_tail_duplicate_dynamic_method_br +// CHECK: dynamic_method_br +// CHECK-NOT: dynamic_method_br +// CHECK: return +sil [ossa] @dont_tail_duplicate_dynamic_method_br : $@convention(thin) (@owned Builtin.AnyObject, Builtin.Int1) -> () { +bb0(%x : @owned $Builtin.AnyObject, %b : $Builtin.Int1): + cond_br %b, bb1, bb2 + +bb1: + %f = function_ref @external_f : $@convention(thin) () -> () + apply %f() : $@convention(thin) () -> () + %x2 = copy_value %x : $Builtin.AnyObject + br bb3(%x2 : $Builtin.AnyObject) + +bb2: + %g = function_ref @external_g : $@convention(thin) () -> () + apply %g() : $@convention(thin) () -> () + %x3 = copy_value %x : $Builtin.AnyObject + br bb3(%x3 : $Builtin.AnyObject) + +bb3(%y : @owned $Builtin.AnyObject): + destroy_value %x : $Builtin.AnyObject + dynamic_method_br %y : $Builtin.AnyObject, #X.f!foreign, bb4, bb5 + +bb4(%m : $@convention(objc_method) (Builtin.AnyObject) -> ()): + br bb6 + +bb5: + br bb6 + +bb6: + destroy_value %y : $Builtin.AnyObject + %r = tuple() + return %r : $() +} + +// OSSA-only test. +// +// Test that simplifySwitchEnumBlock does not assert on a default +// switch case with an argument. +// +// CHECK-LABEL: sil [ossa] @testSwitchEnumDefaultArg : $@convention(thin) (CompareResult) -> FakeBool { +// CHECK: bb0(%0 : $CompareResult): +// CHECK-NOT: switch +// CHECK-NOT: br +// CHECK: [[CALL:%.*]] = apply +// CHECK: return [[CALL]] : $FakeBool +// CHECK-LABEL: } // end sil function 'testSwitchEnumDefaultArg' +sil [ossa] @testSwitchEnumDefaultArg : $@convention(thin) (CompareResult) -> FakeBool { +bb0(%0 : $CompareResult): + %1 = enum $CompareResult, #CompareResult.equal!enumelt + switch_enum %1 : $CompareResult, case #CompareResult.less!enumelt: bb1, default bb2 + +bb1: + %3 = integer_literal $Builtin.Int1, -1 + %4 = struct $FakeBool(%3 : $Builtin.Int1) + br bb3(%3 : $Builtin.Int1) + +bb2(%6 : $CompareResult): + %7 = integer_literal $Builtin.Int1, 0 + %8 = struct $FakeBool(%7 : $Builtin.Int1) + br bb3(%7 : $Builtin.Int1) + +bb3(%10 : $Builtin.Int1): + cond_br %10, bb4, bb5 + +bb4: + %11 = struct $FakeBool(%10 : $Builtin.Int1) + br bb6(%11 : $FakeBool) + +bb5: + %f = function_ref @dummy : $@convention(thin) () -> FakeBool + %call = apply %f() : $@convention(thin) () -> FakeBool + br bb6(%call : $FakeBool) + +bb6(%14 : $FakeBool): + return %14 : $FakeBool +} diff --git a/test/SILOptimizer/simplify_cfg_ossa_disabled.sil b/test/SILOptimizer/simplify_cfg_ossa_disabled.sil new file mode 100644 index 0000000000000..d0c55fb0beb71 --- /dev/null +++ b/test/SILOptimizer/simplify_cfg_ossa_disabled.sil @@ -0,0 +1,1289 @@ +// RUN: %target-sil-opt -enable-objc-interop -enforce-exclusivity=none -enable-sil-verify-all %s -jumpthread-simplify-cfg | %FileCheck %s +// +// These tests case are converted to OSSA form, but aren't yet +// optimized in OSSA mode. Move them to simplify_cfg_ossa.sil when +// they are enabled. +// +// REQUIRES: disabled + +class C {} + +internal enum EnumC { + case one + case two(C) +} + +struct FakeBool { + @_hasStorage var value: Builtin.Int1 { get set } + init(value: Builtin.Int1) +} + +// Test that simplifySwitchEnumBlock handles a default +// switch case with a nontrivial argument. +sil [ossa] @testSwitchEnumDefaultArg : $@convention(thin) () -> FakeBool { +bb0: + %1 = enum $EnumC, #EnumC.one!enumelt + switch_enum %1 : $EnumC, case #EnumC.two!enumelt: bb1, default bb2 + +bb1: + %3 = integer_literal $Builtin.Int1, -1 + %4 = struct $FakeBool (%3 : $Builtin.Int1) + br bb3(%3 : $Builtin.Int1) + +bb2(%6 : $EnumC): + %7 = integer_literal $Builtin.Int1, 0 + %8 = struct $FakeBool (%7 : $Builtin.Int1) + destroy_value %6 : $EnumC + br bb3(%7 : $Builtin.Int1) + +bb3(%10 : $Builtin.Int1): + cond_br %10, bb4, bb5 + +bb4: + br bb6(undef : $FakeBool) + +bb5: + br bb6(undef : $FakeBool) + +bb6(%14 : $FakeBool): + return %14 : $FakeBool +} + +// func testThread2(a : Int32) -> Int32 { +// enum b = (a ? _true : _false) +// if b == _true { return 42 } else { return 17 } +// + +/// CHECK-LABEL: sil [ossa] @testThread2 +/// CHECK: bb0([[COND:%.*]] : {{.*}}): +/// CHECK: cond_br [[COND]], bb1, bb2 +/// CHECK: bb1: +/// CHECK: integer_literal $Builtin.Int64, 42 +/// CHeCK: br bb3 +/// CHECK: bb2: +/// CHECK: integer_literal $Builtin.Int64, 17 +/// CHECK: br bb3 +/// CHECK: bb3 +/// CHECK: return + +sil [ossa] @testThread2 : $@convention(thin) (Builtin.Int1) -> Int64 { +bb0(%0 : $Builtin.Int1): + %t = integer_literal $Builtin.Int1, 1 + %f = integer_literal $Builtin.Int1, 0 + cond_br %0, bb1, bb2 + +bb1: // Preds: bb0 + %4 = enum $BoolLike, #BoolLike.true_!enumelt // user: %5 + br bb3(%4 : $BoolLike) // id: %5 + +bb2: // Preds: bb0 + %8 = enum $BoolLike, #BoolLike.false_!enumelt // user: %9 + br bb3(%8 : $BoolLike) // id: %9 + +bb3(%6 : $BoolLike): // Preds: bb3 bb1 + %100 = select_enum %6 : $BoolLike, case #BoolLike.true_!enumelt: %t, case #BoolLike.false_!enumelt: %f : $Builtin.Int1 + br bb4 // id: %7 + +bb4: // Preds: bb2 + cond_br %100, bb5, bb6 // id: %10 + +bb5: // Preds: bb4 + %11 = metatype $@thin Int64.Type + %12 = integer_literal $Builtin.Int64, 42 // user: %13 + %13 = struct $Int64 (%12 : $Builtin.Int64) // user: %14 + br bb7(%13 : $Int64) // id: %14 + +bb6: // Preds: bb4 + %15 = metatype $@thin Int64.Type + %16 = integer_literal $Builtin.Int64, 17 // user: %17 + %17 = struct $Int64 (%16 : $Builtin.Int64) // user: %18 + br bb7(%17 : $Int64) // id: %18 + +bb7(%19 : $Int64): // Preds: bb6 bb5 + return %19 : $Int64 // id: %21 +} + +// func testThread3(a : Int32) -> Int32 { +// (enum b, val) = (a ? (_true, 16) : (_false, 17)) +// if b == true { return 42 } else { return v } } +// + + +/// CHECK-LABEL: sil [ossa] @testThread3 +/// CHECK: bb0([[COND:%.*]] : {{.*}}): +/// CHECK: cond_br [[COND]], bb1, bb2 +/// CHECK: bb1: +/// CHECK: integer_literal $Builtin.Int64, 42 +/// CHeCK: br bb3 +/// CHECK: bb2: +/// CHECK: integer_literal $Builtin.Int64, 17 +/// CHECK: br bb3 +/// CHECK: bb3 +/// CHECK: return + +sil [ossa] @testThread3 : $@convention(thin) (Builtin.Int1) -> Int64 { +bb0(%0 : $Builtin.Int1): + %t = integer_literal $Builtin.Int1, 1 + %f = integer_literal $Builtin.Int1, 0 + cond_br %0, bb1, bb2 + +bb1: // Preds: bb0 + %4 = enum $BoolLike, #BoolLike.true_!enumelt // user: %5 + %40 = integer_literal $Builtin.Int64, 16 + br bb3(%4 : $BoolLike, %40 : $Builtin.Int64) // id: %5 + +bb2: // Preds: bb0 + %8 = enum $BoolLike, #BoolLike.false_!enumelt // user: %9 + %80 = integer_literal $Builtin.Int64, 17 + br bb3(%8 : $BoolLike, %80 : $Builtin.Int64) // id: %9 + +bb3(%6 : $BoolLike, %60 : $Builtin.Int64): // Preds: bb3 bb1 + %100 = select_enum %6 : $BoolLike, case #BoolLike.true_!enumelt: %t, case #BoolLike.false_!enumelt: %f : $Builtin.Int1 + br bb4 // id: %7 + +bb4: // Preds: bb2 + cond_br %100, bb5, bb6 // id: %10 + +bb5: // Preds: bb4 + %11 = metatype $@thin Int64.Type + %12 = integer_literal $Builtin.Int64, 42 // user: %13 + %13 = struct $Int64 (%12 : $Builtin.Int64) // user: %14 + br bb7(%13 : $Int64) // id: %14 + +bb6: // Preds: bb4 + %15 = metatype $@thin Int64.Type + %17 = struct $Int64 (%60 : $Builtin.Int64) // user: %18 + br bb7(%17 : $Int64) // id: %18 + +bb7(%19 : $Int64): // Preds: bb6 bb5 + return %19 : $Int64 // id: %21 +} + +// We should be able to compile this down to returning the parameter +// but we're not quite there yet. +// CHECK-LABEL: @nop +sil [ossa] @nop : $@convention(thin) (Bool) -> Bool { +bb0(%0 : $Bool): + %1 = struct_extract %0 : $Bool, #Bool._value +// CHECK: cond_br %1, bb2, bb1 + cond_br %1, bb1, bb2 + +// CHECK: bb1: +// CHECK: br bb3 +// CHECK: bb2: +// CHECK: br bb3 +bb1: + %3 = integer_literal $Builtin.Int1, 0 + %4 = struct $Bool (%3 : $Builtin.Int1) + br bb3(%4 : $Bool) + +bb2: + %6 = integer_literal $Builtin.Int1, -1 // user: %7 + %7 = struct $Bool (%6 : $Builtin.Int1) // user: %8 + br bb3(%7 : $Bool) // id: %8 + +// CHECK: bb3 +bb3(%9 : $Bool): // Preds: bb1 bb2 +// CHECK-NOT: struct_extract + %10 = struct_extract %9 : $Bool, #Bool._value // user: %11 +// CHECK: return + cond_br %10, bb4, bb5 // id: %11 + +// CHECK-NOT: bb4 +bb4: // Preds: bb3 + %12 = integer_literal $Builtin.Int1, 0 // user: %13 + %13 = struct $Bool (%12 : $Builtin.Int1) // user: %14 + br bb6(%13 : $Bool) // id: %14 + +// CHECK-NOT: bb5 +bb5: // Preds: bb3 + %15 = integer_literal $Builtin.Int1, -1 // user: %16 + %16 = struct $Bool (%15 : $Builtin.Int1) // user: %17 + br bb6(%16 : $Bool) // id: %17 + +bb6(%18 : $Bool): // Preds: bb4 bb5 + return %18 : $Bool // id: %19 +} + +// CHECK-LABEL: @redundant_switchenum +sil [ossa] @redundant_switchenum_owned : $@convention(thin) (@owned Optional) -> Int32 { +bb0(%0 : @owned $Optional): + %1 = copy_value %0 : $Optional + switch_enum %1 : $Optional, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 + +// CHECK: bb1: +bb1(%1b : @owned $C): + destroy_value %1b : $C + %9 = integer_literal $Builtin.Int1, -1 + %10 = struct $Bool (%9 : $Builtin.Int1) +// CHECK: br [[DEST:[a-zA-Z0-9]+]] + br bb3(%10 : $Bool) + +// CHECK: bb2: +bb2: + %17 = integer_literal $Builtin.Int1, 0 + %18 = struct $Bool (%17 : $Builtin.Int1) +// CHECK: br [[DEST]] + br bb3(%18 : $Bool) + +// CHECK: [[DEST]]({{.*}}): +bb3(%12 : $Bool): + %15 = struct_extract %12 : $Bool, #Bool._value +// CHECK-NOT: cond_br +// CHECK: return + cond_br %15, bb4, bb7 + +// CHECK-NOT: bb4: +bb4: + %21 = alloc_stack $Optional + %0b = copy_value %0 : $Optional + store %0b to [init] %21 : $*Optional +// CHECK-NOT: switch_enum + %0c = copy_value %0 : $Optional + switch_enum %0c : $Optional, case #Optional.some!enumelt: bb5, case #Optional.none!enumelt: bb6 + +bb5(%0cC : @owned $C): + destroy_value %0cC : $C + %25 = unchecked_take_enum_data_addr %21 : $*Optional, #Optional.some!enumelt + %26 = load [take] %25 : $*C + dealloc_stack %21 : $*Optional + %26a = begin_borrow %26 : $C + %29 = ref_element_addr %26a : $C, #C.value + %30 = load [trivial] %29 : $*Int32 + end_borrow %26a : $C + destroy_value %26 : $C + br bb8(%30 : $Int32) + +bb6: + %34 = builtin "int_trap"() : $() + unreachable + +bb7: + %36 = integer_literal $Builtin.Int32, 0 + %37 = struct $Int32 (%36 : $Builtin.Int32) + br bb8(%37 : $Int32) + +bb8(%39 : $Int32): + destroy_value %0 : $Optional + return %39 : $Int32 +} + +sil [ossa] @redundant_switchenum_guaranteed : $@convention(thin) (@guaranteed Optional) -> Int32 { +bb0(%0 : @guaranteed $Optional): + switch_enum %0 : $Optional, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 + +// CHECK: bb1: +bb1(%0b : @guaranteed $C): + %9 = integer_literal $Builtin.Int1, -1 + %10 = struct $Bool (%9 : $Builtin.Int1) +// CHECK: br [[DEST:[a-zA-Z0-9]+]] + br bb3(%10 : $Bool) + +// CHECK: bb2: +bb2: + %17 = integer_literal $Builtin.Int1, 0 + %18 = struct $Bool (%17 : $Builtin.Int1) +// CHECK: br [[DEST]] + br bb3(%18 : $Bool) + +// CHECK: [[DEST]]({{.*}}): +bb3(%12 : $Bool): + %15 = struct_extract %12 : $Bool, #Bool._value +// CHECK-NOT: cond_br +// CHECK: return + cond_br %15, bb4, bb7 + +// CHECK-NOT: bb4: +bb4: + %21 = alloc_stack $Optional + %0a = copy_value %0 : $Optional + store %0a to [init] %21 : $*Optional +// CHECK-NOT: switch_enum + switch_enum %0 : $Optional, case #Optional.some!enumelt: bb5, case #Optional.none!enumelt: bb6 + +bb5(%bb5Some : @guaranteed $C): + %25 = unchecked_take_enum_data_addr %21 : $*Optional, #Optional.some!enumelt + %26 = load [take] %25 : $*C + dealloc_stack %21 : $*Optional + %26a = begin_borrow %26 : $C + %29 = ref_element_addr %26a : $C, #C.value + %30 = load [trivial] %29 : $*Int32 + end_borrow %26a : $C + destroy_value %26 : $C + br bb8(%30 : $Int32) + +bb6: + %34 = builtin "int_trap"() : $() + unreachable + +bb7: + %36 = integer_literal $Builtin.Int32, 0 + %37 = struct $Int32 (%36 : $Builtin.Int32) + br bb8(%37 : $Int32) + +bb8(%39 : $Int32): + return %39 : $Int32 +} + +// TODO: The optimization does not yet handle unused payload arguments. +// +// CHECK-LABEL: sil [ossa] @identical_switch_enum_dests : $@convention(thin) (Optional) -> () { +// CHECK: bb0(%0 : $Optional): +// CHECK-NEXT: tuple +// CHECK-NEXT: return +sil [ossa] @identical_switch_enum_dests : $@convention(thin) (Optional) -> () { +bb0(%0 : $Optional): + switch_enum %0 : $Optional, case #Optional.none!enumelt: bb1, case #Optional.some!enumelt: bb2 + +bb1: + br bb3 + +bb2(%payload : $Int32): + br bb3 + +bb3: + %r = tuple() + return %r : $() +} + +// CHECK-LABEL: @dominator_based_simplify_condbr +// CHECK: integer_literal $Builtin.Int64, 1 +// CHECK-NOT: integer_literal $Builtin.Int64, 2 +// CHECK-NOT: integer_literal $Builtin.Int64, 3 +// CHECK: integer_literal $Builtin.Int64, 4 +// CHECK: return +sil [ossa] @dominator_based_simplify_condbr : $@convention(thin) (Builtin.Int1) -> Int64 { +bb0(%0 : $Builtin.Int1): + %l1 = integer_literal $Builtin.Int1, -1 + cond_br %0, bb1, bb4 + +bb1: + cond_br %0, bb2, bb3 + +bb2: + %1 = integer_literal $Builtin.Int64, 1 + br bb7(%1 : $Builtin.Int64) + +bb3: + %2 = integer_literal $Builtin.Int64, 2 + br bb7(%2 : $Builtin.Int64) + +bb4: + // expect-intrinsics should be transparent for checking the condition. + %x1 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %l1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %x1, bb5, bb6 + +bb5: + %3 = integer_literal $Builtin.Int64, 3 + br bb7(%3 : $Builtin.Int64) + +bb6: + %4 = integer_literal $Builtin.Int64, 4 + br bb7(%4 : $Builtin.Int64) + +bb7(%6 : $Builtin.Int64): + %7 = struct $Int64 (%6 : $Builtin.Int64) + return %7 : $Int64 +} + +// CHECK-LABEL: @dominator_based_simplify_condbr_with_inverts +// CHECK-NOT: integer_literal $Builtin.Int64, 1 +// CHECK: [[I2:%[0-9]+]] = integer_literal $Builtin.Int64, 2 +// CHECK: br bb3([[I2]] : $Builtin.Int64) +// CHECK-NOT: integer_literal $Builtin.Int64, 3 +// CHECK: [[I4:%[0-9]+]] = integer_literal $Builtin.Int64, 4 +// CHECK: br bb3([[I4]] : $Builtin.Int64) +// CHECK: bb3([[R:%[0-9]+]] : $Builtin.Int64): +// CHECK-NEXT: return [[R]] +sil [ossa] @dominator_based_simplify_condbr_with_inverts : $@convention(thin) (Builtin.Int1) -> Builtin.Int64 { +bb0(%0 : $Builtin.Int1): + %l1 = integer_literal $Builtin.Int1, -1 + %x1 = builtin "xor_Int1"(%0 : $Builtin.Int1, %l1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %x1, bb1, bb4 + +bb1: + cond_br %0, bb2, bb3 + +bb2: + %1 = integer_literal $Builtin.Int64, 1 + br bb7(%1 : $Builtin.Int64) + +bb3: + %2 = integer_literal $Builtin.Int64, 2 + br bb7(%2 : $Builtin.Int64) + +bb4: + // expect-intrinsics should be transparent for checking the condition. + %x2 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %l1 : $Builtin.Int1) : $Builtin.Int1 + %x3 = builtin "xor_Int1"(%0 : $Builtin.Int1, %l1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %x3, bb5, bb6 + +bb5: + %3 = integer_literal $Builtin.Int64, 3 + br bb7(%3 : $Builtin.Int64) + +bb6: + %4 = integer_literal $Builtin.Int64, 4 + br bb7(%4 : $Builtin.Int64) + +bb7(%6 : $Builtin.Int64): + return %6 : $Builtin.Int64 +} + +// CHECK-LABEL: @switch_enum_dominates_switch_enum_arg +// CHECK: bb0(%0 : $Optional): +// CHECK-NEXT: switch_enum %0 {{.*}} case #Optional.some!enumelt: bb2 +// CHECK: bb2: +// CHECK-NEXT: [[D:%[0-9]+]] = unchecked_enum_data %0 +// CHECK-NEXT: br bb3([[D]] : $Builtin.Int32) +// CHECK: bb3([[R:%[0-9]+]] : $Builtin.Int32): +// CHECK-NEXT: return [[R]] +sil [ossa] @switch_enum_dominates_switch_enum_arg : $@convention(thin) (Optional) -> Builtin.Int32 { +bb0(%0 : $Optional): + switch_enum %0 : $Optional, case #Optional.none!enumelt: bb1, case #Optional.some!enumelt: bb2 + +bb1: + %i1 = integer_literal $Builtin.Int32, 1 + br bb5(%i1 : $Builtin.Int32) + +bb2(%payload : $Builtin.Int32): + switch_enum %0 : $Optional, case #Optional.none!enumelt: bb3, case #Optional.some!enumelt: bb4 + +bb3: + %i2 = integer_literal $Builtin.Int32, 2 + br bb5(%i2 : $Builtin.Int32) + +bb4(%e : $Builtin.Int32): + br bb5(%e : $Builtin.Int32) + +bb5(%r : $Builtin.Int32): + return %r : $Builtin.Int32 +} + +// CHECK-LABEL: @switch_enum_dominates_switch_enum_arg_reuse +// CHECK: bb0(%0 : $Optional): +// CHECK-NEXT: switch_enum %0 {{.*}} case #Optional.some!enumelt: bb2 +// CHECK: bb2({{.*}} : $Builtin.Int32): +// CHECK-NEXT: [[A:%[0-9]+]] = unchecked_enum_data %0 +// CHECK-NEXT: br bb3([[A]] : $Builtin.Int32) +// CHECK: bb3([[R:%[0-9]+]] : $Builtin.Int32): +// CHECK-NEXT: return [[R]] +sil [ossa] @switch_enum_dominates_switch_enum_arg_reuse : $@convention(thin) (Optional) -> Builtin.Int32 { +bb0(%0 : $Optional): + switch_enum %0 : $Optional, case #Optional.none!enumelt: bb1, case #Optional.some!enumelt: bb2 + +bb1: + %i1 = integer_literal $Builtin.Int32, 1 + br bb5(%i1 : $Builtin.Int32) + +bb2(%d : $Builtin.Int32): + switch_enum %0 : $Optional, case #Optional.none!enumelt: bb3, case #Optional.some!enumelt: bb4 + +bb3: + %i2 = integer_literal $Builtin.Int32, 2 + br bb5(%i2 : $Builtin.Int32) + +bb4(%e : $Builtin.Int32): + br bb5(%e : $Builtin.Int32) + +bb5(%r : $Builtin.Int32): + return %r : $Builtin.Int32 +} + +// CHECK-LABEL: sil [ossa] @simplify_loop_header +// CHECK: bb1( +// CHECK: cond_br {{.*}}, bb3, bb2 +// CHECK: bb2: +// CHECK: enum $Optional, #Optional.some +// CHECK: br bb1( +// CHECK: bb3: +// CHECK: return + +sil [ossa] @simplify_loop_header : $@convention(thin) () -> () { +bb0: + %0 = integer_literal $Builtin.Int32, 0 + %1 = integer_literal $Builtin.Int32, 1000 + br bb1(%0 : $Builtin.Int32) + +bb1(%3 : $Builtin.Int32): + %4 = struct $Int32 (%3 : $Builtin.Int32) + %6 = builtin "cmp_eq_Int32"(%3 : $Builtin.Int32, %1 : $Builtin.Int32) : $Builtin.Int1 + cond_br %6, bb2, bb3 + +bb2: + %8 = enum $Optional, #Optional.none!enumelt + br bb4(%3 : $Builtin.Int32, %8 : $Optional) + +bb3: + %10 = integer_literal $Builtin.Int32, 1 + %12 = integer_literal $Builtin.Int1, -1 + %13 = builtin "sadd_with_overflow_Int32"(%3 : $Builtin.Int32, %10 : $Builtin.Int32, %12 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %14 = tuple_extract %13 : $(Builtin.Int32, Builtin.Int1), 0 + %15 = tuple_extract %13 : $(Builtin.Int32, Builtin.Int1), 1 + cond_fail %15 : $Builtin.Int1 + %17 = enum $Optional, #Optional.some!enumelt, %4 : $Int32 + br bb4(%14 : $Builtin.Int32, %17 : $Optional) + +bb4(%19 : $Builtin.Int32, %20 : $Optional): + switch_enum %20 : $Optional, case #Optional.some!enumelt: bb5, case #Optional.none!enumelt: bb6 + +bb5(%payload : $Int32): + // This could be a use like we generate for a loop with an induction + // variable use like in: + // for i in 1..10 { a[i] = i } + %9 = unchecked_enum_data %20 : $Optional, #Optional.some!enumelt + br bb1(%19 : $Builtin.Int32) + +bb6: + %23 = tuple () + return %23 : $() +} + +// CHECK-LABEL: sil [ossa] @jumpthread_switch_enum +// CHECK-NOT: switch_enum +// CHECK: return + +sil [ossa] @jumpthread_switch_enum : $@convention(thin) (Int32) -> Int32 { +bb0(%0 : $Int32): + %1 = integer_literal $Builtin.Int32, 0 + %2 = integer_literal $Builtin.Int32, 1 + %3 = struct_extract %0 : $Int32, #Int32._value + %5 = integer_literal $Builtin.Int1, -1 + %6 = builtin "sadd_with_overflow_Int32"(%3 : $Builtin.Int32, %2 : $Builtin.Int32, %5 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %7 = tuple_extract %6 : $(Builtin.Int32, Builtin.Int1), 0 + %8 = tuple_extract %6 : $(Builtin.Int32, Builtin.Int1), 1 + cond_fail %8 : $Builtin.Int1 + %11 = builtin "cmp_eq_Int32"(%2 : $Builtin.Int32, %7 : $Builtin.Int32) : $Builtin.Int1 + cond_br %11, bb8a, bb2 + +bb2: + %14 = integer_literal $Builtin.Int32, 2 + %15 = struct $Int32 (%2 : $Builtin.Int32) + %16 = enum $Optional, #Optional.some!enumelt, %15 : $Int32 + br bb3(%1 : $Builtin.Int32, %14 : $Builtin.Int32, %16 : $Optional) + +bb3(%18 : $Builtin.Int32, %19 : $Builtin.Int32, %20 : $Optional): + switch_enum %20 : $Optional, case #Optional.some!enumelt: bb4, case #Optional.none!enumelt: bb5 + +bb4(%unusedPayload : $Int32): + %22 = unchecked_enum_data %20 : $Optional, #Optional.some!enumelt + %23 = struct_extract %22 : $Int32, #Int32._value + %24 = builtin "sadd_with_overflow_Int32"(%18 : $Builtin.Int32, %23 : $Builtin.Int32, %5 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %25 = tuple_extract %24 : $(Builtin.Int32, Builtin.Int1), 0 + %26 = tuple_extract %24 : $(Builtin.Int32, Builtin.Int1), 1 + cond_fail %26 : $Builtin.Int1 + %28 = struct $Int32 (%19 : $Builtin.Int32) + %29 = builtin "cmp_eq_Int32"(%19 : $Builtin.Int32, %7 : $Builtin.Int32) : $Builtin.Int1 + cond_br %29, bb6, bb7 + +bb5: + cond_fail %5 : $Builtin.Int1 + unreachable + +bb6: + br bb8(%25 : $Builtin.Int32) + +bb7: + %34 = builtin "sadd_with_overflow_Int32"(%19 : $Builtin.Int32, %2 : $Builtin.Int32, %5 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %35 = tuple_extract %34 : $(Builtin.Int32, Builtin.Int1), 0 + %36 = tuple_extract %34 : $(Builtin.Int32, Builtin.Int1), 1 + cond_fail %36 : $Builtin.Int1 + %38 = enum $Optional, #Optional.some!enumelt, %28 : $Int32 + br bb3(%25 : $Builtin.Int32, %35 : $Builtin.Int32, %38 : $Optional) + +bb8a: + br bb8(%1 : $Builtin.Int32) + +bb8(%40 : $Builtin.Int32): + %41 = struct $Int32 (%40 : $Builtin.Int32) + return %41 : $Int32 +} + +// CHECK-LABEL: sil [ossa] @jumpthread_switch_enum2 +// CHECK-NOT: switch_enum +// CHECK: return + +sil [ossa] @jumpthread_switch_enum2 : $@convention(thin) (Int32) -> Int32 { +bb0(%0 : $Int32): + %1 = integer_literal $Builtin.Int32, 0 + %3 = enum $Optional, #Optional.some!enumelt, %0 : $Int32 + %4 = enum $Optional, #Optional.none!enumelt + br bb1(%1 : $Builtin.Int32, %3 : $Optional) + +bb1(%6 : $Builtin.Int32, %7 : $Optional): + switch_enum %7 : $Optional, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb3 + +bb2(%unusedPayload : $Int32): + %9 = unchecked_enum_data %7 : $Optional, #Optional.some!enumelt + %10 = struct_extract %9 : $Int32, #Int32._value + %11 = integer_literal $Builtin.Int1, 0 + %12 = builtin "sadd_with_overflow_Int32"(%6 : $Builtin.Int32, %10 : $Builtin.Int32, %11 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %13 = tuple_extract %12 : $(Builtin.Int32, Builtin.Int1), 0 + br bb1(%13 : $Builtin.Int32, %4 : $Optional) + +bb3: + %17 = struct $Int32 (%6 : $Builtin.Int32) + return %17 : $Int32 +} + +// CHECK-LABEL: sil [ossa] @jumpthread_switch_enum3 +// CHECK: switch_enum %0 : $Optional, case #Optional.none!enumelt: bb2, case #Optional.some!enumelt: bb1 +// CHECK: bb1: +// CHECK-NEXT: br bb5 +// CHECK: bb2: +// CHECK-NEXT: switch_enum +// CHECK: bb3: +// CHECK-NEXT: br bb5 +// CHECK: bb4: +// CHECK-NEXT: br bb6 +// CHECK: bb5: +// CHECK-NEXT: br bb6 +// CHECK: bb6({{.*}}): +// CHECK-NEXT: return +sil [ossa] @jumpthread_switch_enum3 : $@convention(thin) (Optional) -> Int32 { +bb0(%0 : $Optional): + %10 = integer_literal $Builtin.Int32, 2 + %11 = struct $Int32 (%10 : $Builtin.Int32) + %20 = integer_literal $Builtin.Int32, 3 + %21 = struct $Int32 (%20 : $Builtin.Int32) + switch_enum %0 : $Optional, case #Optional.none!enumelt: bb1, case #Optional.some!enumelt: bb2 + +bb1: + br bb3(undef : $Optional) + +bb2(%unusedPayload : $Int32): + br bb3(%0 : $Optional) + +bb3(%30 : $Optional): + switch_enum %30 : $Optional, case #Optional.none!enumelt: bb4, case #Optional.some!enumelt: bb5 + +bb4: + br bb6(%21 : $Int32) + +bb5(%unusedPayload2 : $Int32): + br bb6(%11 : $Int32) + +bb6(%r : $Int32): + return %r : $Int32 +} + +// CHECK-LABEL: sil [ossa] @jumpthread_switch_enum4 +// CHECK: bb0: +// CHECK-NEXT: cond_br undef, bb1, bb2 +// CHECK: bb1: +// CHECK: enum $Optional, #Optional.none!enumelt +// CHECK: br bb3 +// CHECK: bb2: +// CHECK: integer_literal $Builtin.Int32, 0 +// CHECK: enum $Optional, #Optional.some!enumelt +// CHECK: br bb3 +// CHECK: bb3 +// CHECK: integer_literal {{.*}}, 27 +// CHECK: integer_literal {{.*}}, 28 +// CHECK: return +sil [ossa] @jumpthread_switch_enum4 : $@convention(thin) () -> Builtin.Int32 { +bb0: + %c0 = builtin "assert_configuration"() : $Builtin.Int32 + cond_br undef, bb1, bb2 + +bb1: + %4 = enum $Optional, #Optional.none!enumelt + cond_br undef, bb3a, bb4a + +bb2: + %6 = integer_literal $Builtin.Int32, 0 + %7 = struct $Int32 (%6 : $Builtin.Int32) + %8 = enum $Optional, #Optional.some!enumelt, %7 : $Int32 + br bb3(%8 : $Optional) + +bb3a: + br bb3(%4 : $Optional) + +bb3(%10 : $Optional): + // Some instruction which is not "trivial" + %c1 = builtin "assert_configuration"() : $Builtin.Int32 + br bb4(%10 : $Optional, %c1 : $Builtin.Int32) + +bb4a: + br bb4(%4 : $Optional, %c0 : $Builtin.Int32) + +bb4(%13 : $Optional, %carg1 : $Builtin.Int32): + switch_enum %13 : $Optional, case #Optional.some!enumelt: bb5, case #Optional.none!enumelt: bb6 + +bb5(%unusedPayload : $Int32): + %r1 = integer_literal $Builtin.Int32, 27 + %c2 = builtin "assert_configuration"() : $Builtin.Int32 + br bb7(%r1 : $Builtin.Int32, %c2 : $Builtin.Int32) + +bb6: + %r2 = integer_literal $Builtin.Int32, 28 + %c3 = builtin "assert_configuration"() : $Builtin.Int32 + br bb7(%r2 : $Builtin.Int32, %c3 : $Builtin.Int32) + +bb7(%r : $Builtin.Int32, %carg2 : $Builtin.Int32): + return %r : $Builtin.Int32 +} + +// CHECK-LABEL: sil [ossa] @jumpthread_switch_enum5 +// CHECK: bb0: +// CHECK: br bb1 +// CHECK: bb1({{.*}}): +// CHECK-NEXT: cond_br undef, bb2, bb3 +// CHECK: bb2: +// CHECK: br bb1 +// CHECK: bb3: +// CHECK-NEXT: tuple +// CHECK-NEXT: return +sil [ossa] @jumpthread_switch_enum5 : $@convention(thin) () -> () { +bb0: + %6 = integer_literal $Builtin.Int32, 0 + %7 = struct $Int32 (%6 : $Builtin.Int32) + %8 = enum $Optional, #Optional.some!enumelt, %7 : $Int32 + br bb1(%8 : $Optional) + +bb1(%13 : $Optional): + switch_enum %13 : $Optional, case #Optional.some!enumelt: bb2, case #Optional.none!enumelt: bb3 + +bb2(%unusedPayload : $Int32): + br bb4 + +bb3: + %55 = function_ref @external_f : $@convention(thin) () -> () + apply %55() : $@convention(thin) () -> () + br bb4 + +bb4: + cond_br undef, bb1a, bb5 + +bb1a: + br bb1(%13 : $Optional) + +bb5: + %r = tuple () + return %r : $() +} + +/// Don't jumpthread blocks that contain objc method instructions. We don't +/// support building phis with objc method values. + +class Bar { + init() + @objc func foo() +} + + +// CHECK-LABEL: @dont_jumpthread_switch_enum +// CHECK: objc_method +// CHECK: switch_enum +// CHECK: return + +sil [ossa] @dont_jumpthread_switch_enum : $@convention(thin) (Int32) -> Int32 { +bb0(%0 : $Int32): + %100 = alloc_ref $Bar + %1 = integer_literal $Builtin.Int32, 0 + %2 = integer_literal $Builtin.Int32, 1 + %3 = struct_extract %0 : $Int32, #Int32._value + %5 = integer_literal $Builtin.Int1, -1 + %6 = builtin "sadd_with_overflow_Int32"(%3 : $Builtin.Int32, %2 : $Builtin.Int32, %5 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %7 = tuple_extract %6 : $(Builtin.Int32, Builtin.Int1), 0 + %8 = tuple_extract %6 : $(Builtin.Int32, Builtin.Int1), 1 + cond_fail %8 : $Builtin.Int1 + %11 = builtin "cmp_eq_Int32"(%2 : $Builtin.Int32, %7 : $Builtin.Int32) : $Builtin.Int1 + cond_br %11, bb8a, bb2 + +bb2: + %14 = integer_literal $Builtin.Int32, 2 + %15 = struct $Int32 (%2 : $Builtin.Int32) + %16 = enum $Optional, #Optional.some!enumelt, %15 : $Int32 + br bb3(%1 : $Builtin.Int32, %14 : $Builtin.Int32, %16 : $Optional) + +bb3(%18 : $Builtin.Int32, %19 : $Builtin.Int32, %20 : $Optional): + %101 = objc_method %100 : $Bar, #Bar.foo!foreign : (Bar) -> () -> (), $@convention(objc_method) (Bar) -> () + switch_enum %20 : $Optional, case #Optional.some!enumelt: bb4, case #Optional.none!enumelt: bb5 + +bb4(%unusedPayload : $Int32): + %102 = apply %101(%100) : $@convention(objc_method) (Bar) -> () + %22 = unchecked_enum_data %20 : $Optional, #Optional.some!enumelt + %23 = struct_extract %22 : $Int32, #Int32._value + %24 = builtin "sadd_with_overflow_Int32"(%18 : $Builtin.Int32, %23 : $Builtin.Int32, %5 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %25 = tuple_extract %24 : $(Builtin.Int32, Builtin.Int1), 0 + %26 = tuple_extract %24 : $(Builtin.Int32, Builtin.Int1), 1 + cond_fail %26 : $Builtin.Int1 + %28 = struct $Int32 (%19 : $Builtin.Int32) + %29 = builtin "cmp_eq_Int32"(%19 : $Builtin.Int32, %7 : $Builtin.Int32) : $Builtin.Int1 + cond_br %29, bb6, bb7 + +bb5: + cond_fail %5 : $Builtin.Int1 + unreachable + +bb6: + br bb8(%25 : $Builtin.Int32) + +bb7: + %34 = builtin "sadd_with_overflow_Int32"(%19 : $Builtin.Int32, %2 : $Builtin.Int32, %5 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %35 = tuple_extract %34 : $(Builtin.Int32, Builtin.Int1), 0 + %36 = tuple_extract %34 : $(Builtin.Int32, Builtin.Int1), 1 + cond_fail %36 : $Builtin.Int1 + %38 = enum $Optional, #Optional.none!enumelt + br bb3(%25 : $Builtin.Int32, %35 : $Builtin.Int32, %38 : $Optional) + +bb8a: + br bb8(%1 : $Builtin.Int32) + +bb8(%40 : $Builtin.Int32): + destroy_value %100 : $Bar + %41 = struct $Int32 (%40 : $Builtin.Int32) + return %41 : $Int32 +} + +// CHECK-LABEL: sil [ossa] @dont_crash_jump_threading_single_case_switch_enums +// CHECK-NOT: switch_enum +// CHECK: } // end sil function +sil [ossa] @dont_crash_jump_threading_single_case_switch_enums : $@convention(thin) () -> () { +bb0: + br bb1(undef : $OneCase) + +bb1(%19 : $OneCase): + switch_enum %19 : $OneCase, case #OneCase.First!enumelt: bb2 + +bb2: + switch_enum %19 : $OneCase, case #OneCase.First!enumelt: bb3 + +bb3: + %48 = enum $OneCase, #OneCase.First!enumelt + cond_br undef, bb6, bb7 + +bb6: + %72 = tuple () + return %72 : $() + +bb7: + br bb1(%48 : $OneCase) +} + +@objc protocol ObjcProto { func foo() } + +// CHECK-LABEL: sil [ossa] @thread_objc_method_call_succ_block +// CHECK: bb0 +// CHECK: cond_br {{.*}}, bb1, bb2 +// CHECK: bb1 +// CHECK: objc_method +// CHECK: apply +// CHECK: strong_release +// CHECK: cond_br {{.*}}, bb3, bb4 +// CHECK: bb2 +// CHECK: strong_release +// CHECK: cond_br {{.*}}, bb3, bb4 +// CHECK: bb3 +// CHECK: cond_fail +// CHECK: br bb4 +// CHECK: bb4: +// CHECK: strong_release +// CHECK: return + +sil [ossa] @thread_objc_method_call_succ_block : $@convention(thin) (Builtin.Int1, @owned T, Builtin.Int1) -> () { +bb0(%0: $Builtin.Int1, %1 : @owned $T, %2 : $Builtin.Int1): + %1a = copy_value %1 : $T + cond_br %0, bb1 , bb2a + +bb1: + %3 = objc_method %1a : $T, #ObjcProto.foo!foreign, $@convention(objc_method) <τ_0_0 where τ_0_0 : ObjcProto> (τ_0_0) -> () + %4 = apply %3(%1a) : $@convention(objc_method) <τ_0_0 where τ_0_0 : ObjcProto> (τ_0_0) -> () + br bb2 + +bb2a: + br bb2 + +bb2: + destroy_value %1a : $T + cond_br %2, bb3, bb4a + +bb3: + cond_fail %0 : $Builtin.Int1 + br bb4 + +bb4a: + br bb4 + +bb4: + destroy_value %1 : $T + %41 = tuple () + return %41 : $() +} + +sil [ossa] @f_use : $@convention(thin) (Builtin.Int32) -> () + +// CHECK-LABEL: sil [ossa] @switch_enum_jumpthreading_bug +// CHECK: bb1: +// CHECK: [[INVADD:%.*]] = builtin "sadd +// CHECK: [[EXT:%.*]] = tuple_extract [[INVADD]] +// CHECK: switch_enum {{.*}} case #Optional.some!enumelt: bb2 + +// CHECK: bb2{{.*}} +// CHECK: br bb4({{.*}} : $Builtin.Int32, %2 : $Builtin.Int32, [[EXT]] + +// CHECK: bb4({{.*}} : $Builtin.Int32, [[CUR:%.*]] : $Builtin.Int32, [[NEXT:%.*]] : $Builtin.Int32 +// CHECK: [[F:%.*]] = function_ref @f +// CHECK: apply [[F]]([[CUR]]) +// CHECK: cond_br {{.*}}, bb5, bb6 + +// CHECK: bb5: +// CHECK: [[VARADD:%.*]] = builtin "sadd_with_overflow_Int32"([[NEXT]] : $Builtin.Int32 +// CHECK: [[NEXT2:%.*]] = tuple_extract [[VARADD]] +// CHECK: br bb4({{.*}} : $Builtin.Int32, [[NEXT]] : $Builtin.Int32, [[NEXT2]] + + +sil [ossa] @switch_enum_jumpthreading_bug : $@convention(thin) (Optional, Builtin.Int1, Builtin.Int32, Builtin.Int1) -> Builtin.Int32 { +bb0(%0 : $Optional, %1 : $Builtin.Int1, %2: $Builtin.Int32, %3 : $Builtin.Int1): + cond_br %1, bb2, bb10a + +bb2: + br bb3(%2 : $Builtin.Int32, %0 : $Optional) + +bb3(%10 : $Builtin.Int32, %7 : $Optional): + %4 = integer_literal $Builtin.Int32, 1 + %5 = integer_literal $Builtin.Int1, -1 + %6 = builtin "sadd_with_overflow_Int32"(%10 : $Builtin.Int32, %4 : $Builtin.Int32, %5 : $Builtin.Int1) : $(Builtin.Int32, Builtin.Int1) + %16 = tuple_extract %6 : $(Builtin.Int32, Builtin.Int1), 0 + switch_enum %7 : $Optional, case #Optional.some!enumelt: bb5, case #Optional.none!enumelt: bb4 + +bb4: + cond_fail %5 : $Builtin.Int1 + unreachable + +bb5(%9 : $Builtin.Int32): + %f = function_ref @f_use : $@convention(thin) (Builtin.Int32) -> () + %a = apply %f(%10) : $@convention(thin) (Builtin.Int32) -> () + cond_br %3, bb6, bb10b + +bb6: + %8 = enum $Optional, #Optional.some!enumelt, %9 : $Builtin.Int32 + br bb3(%16 : $Builtin.Int32, %8 : $Optional) + +bb10a: + br bb10 + +bb10b: + br bb10 + +bb10: + br bb11(%2: $Builtin.Int32) + +bb11(%100 : $Builtin.Int32): + return %100 : $Builtin.Int32 +} + +sil [ossa] @a : $@convention(thin) () -> () +sil [ossa] @b : $@convention(thin) () -> () +sil [ossa] @c : $@convention(thin) () -> () +sil [ossa] @d : $@convention(thin) () -> () + +// CHECK-LABEL: sil [ossa] @jump_thread_diamond +// CHECK: bb1: +// CHECK: [[A:%.*]] = function_ref @a +// CHECK: apply [[A]] +// CHECK: [[C:%.*]] = function_ref @c +// CHECK: apply [[C]] +// CHECK: br bb3 +// CHECK: bb2: +// CHECK: [[B:%.*]] = function_ref @b +// CHECK: apply [[B]] +// CHECK: [[D:%.*]] = function_ref @d +// CHECK: apply [[D]] +// CHECK: br bb3 + +// CHECK: return +sil [ossa] @jump_thread_diamond : $@convention(thin) (Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1): + %1 = integer_literal $Builtin.Int1, -1 + %2 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + %8 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + %9 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %8, bb1, bb2 + +bb1: + %10 = function_ref @a : $@convention(thin) () -> () + %11 = apply %10() : $@convention(thin) () -> () + br bb3 + +bb2: + %13 = function_ref @b : $@convention(thin) () -> () + %14 = apply %13() : $@convention(thin) () -> () + br bb3 + +bb3: + %19 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + %20 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + %21 = builtin "int_expect_Int1"(%0 : $Builtin.Int1, %1 : $Builtin.Int1) : $Builtin.Int1 + cond_br %21, bb4, bb5 + +bb4: + %23 = function_ref @c : $@convention(thin) () -> () + %24 = apply %23() : $@convention(thin) () -> () + br bb6 + +bb5: + %26 = function_ref @d : $@convention(thin) () -> () + %27 = apply %26() : $@convention(thin) () -> () + br bb6 + +bb6: + %29 = tuple () + return %29 : $() +} + +enum AnEnum { + case B(Builtin.Int32), C(Builtin.Int16) +} + +sil [ossa] @f : $@convention(thin) (Builtin.Int32) -> () +sil [ossa] @f2 : $@convention(thin) (Builtin.Int16) -> () + +// CHECK-LABEL: sil [ossa] @jump_thread_switch_enum +// CHECK: bb0([[ARG:%.*]] : $AnEnum): +// CHECK: [[F:%.*]] = function_ref @f : $@convention(thin) (Builtin.Int32) -> () +// CHECK: [[F2:%.*]] = function_ref @f2 : $@convention(thin) (Builtin.Int16) -> () +// CHECK: switch_enum [[ARG]] : $AnEnum, case #AnEnum.B!enumelt: bb2, case #AnEnum.C!enumelt: bb1 + +// CHECK: bb1([[ARG3:%.*]] : $Builtin.Int16): +// CHECK: apply [[F2]]([[ARG3]]) +// CHECK: [[UED2:%.*]] = unchecked_enum_data [[ARG]] : $AnEnum, #AnEnum.C!enumelt +// CHECK: apply [[F2]]([[UED2]]) +// CHECK: br bb3 + +// CHECK: bb2([[ARG2:%.*]] : $Builtin.Int32): +// CHECK: apply [[F]]([[ARG2]]) +// CHECK: [[UED:%.*]] = unchecked_enum_data [[ARG]] : $AnEnum, #AnEnum.B!enumelt +// CHECK: apply [[F]]([[UED]]) +// CHECK: br bb3 + +sil [ossa] @jump_thread_switch_enum : $@convention(thin) (AnEnum) -> () { +bb0(%0 : $AnEnum): + + %1 = function_ref @f : $@convention(thin) (Builtin.Int32) -> () + + %2 = function_ref @f2 : $@convention(thin) (Builtin.Int16) -> () + switch_enum %0 : $AnEnum, case #AnEnum.B!enumelt: bb1, case #AnEnum.C!enumelt: bb3 + +bb1(%4 : $Builtin.Int32): + br bb2 + +bb2: + %6 = apply %1(%4) : $@convention(thin) (Builtin.Int32) -> () + br bb5 + +bb3(%8 : $Builtin.Int16): + br bb4 + +bb4: + %10 = apply %2(%8) : $@convention(thin) (Builtin.Int16) -> () + br bb5 + +bb5: + switch_enum %0 : $AnEnum, case #AnEnum.C!enumelt: bb6, case #AnEnum.B!enumelt: bb8 + +bb6(%13 : $Builtin.Int16): + br bb7 + +bb7: + %15 = apply %2(%13) : $@convention(thin) (Builtin.Int16) -> () + br bb10 + +bb8(%17 : $Builtin.Int32): + br bb9 + +bb9: + %19 = apply %1(%17) : $@convention(thin) (Builtin.Int32) -> () + br bb10 + +bb10: + %21 = tuple () + return %21 : $() +} + +sil [ossa] @fB : $@convention(thin) () -> () +sil [ossa] @fC : $@convention(thin) () -> () + + +// Make sure that we correctly thread such that we end up calling @fB on the +// AnEnum.B path. + +// CHECK-LABEL: sil [ossa] @dont_jump_thread_switch_enum_to_cond_br +// CHECK: [[BFUN:%.*]] = function_ref @fB : $@convention(thin) () -> () +// CHECK: [[FALSE:%.*]] = integer_literal $Builtin.Int1, 0 +// CHECK: switch_enum [[ENUM:%.*]] : $AnEnum, case #AnEnum.B!enumelt: bb2 +// CHECK: bb2: +// CHECK: [[F:%.*]] = select_enum [[ENUM]] : $AnEnum, case #AnEnum.B!enumelt: [[FALSE]] +// CHECK: cond_br [[F]], bb3, bb4 +// CHECK: bb4: +// CHECK-NOT: br +// CHECK: apply [[BFUN]] +// CHECK: br + +sil [ossa] @dont_jump_thread_switch_enum_to_cond_br : $@convention(thin) (AnEnum) -> () { +bb0(%0 : $AnEnum): + %1 = function_ref @fB : $@convention(thin) () -> () + %2 = function_ref @fC : $@convention(thin) () -> () + %t = integer_literal $Builtin.Int1, 1 + %f = integer_literal $Builtin.Int1, 0 + switch_enum %0 : $AnEnum, case #AnEnum.B!enumelt: bb4, case #AnEnum.C!enumelt: bb5 + +bb4(%bPayload : $Builtin.Int32): + br bb1 + +bb5(%cPayload : $Builtin.Int16): + %8 = select_enum %0 : $AnEnum, case #AnEnum.B!enumelt: %f, case #AnEnum.C!enumelt: %t : $Builtin.Int1 + cond_br %8, bb10a, bb1a + +bb1a: + br bb1 + +bb1: + %3 = select_enum %0 : $AnEnum, case #AnEnum.B!enumelt: %f, case #AnEnum.C!enumelt: %t : $Builtin.Int1 + cond_br %3, bb2, bb3 + +bb2: + %6 = apply %2() : $@convention(thin) () -> () + br bb10 + +bb3: + %7 = apply %1() : $@convention(thin) () -> () + br bb10 + +bb10a: + br bb10 + +bb10: + %21 = tuple () + return %21 : $() +} + +// CHECK-LABEL: sil [ossa] @unpack_enum_arg_non_trivial_owned : +// CHECK: bb1: +// CHECK: br bb3(%0 : $Klass) +// CHECK: bb2: +// CHECK: br bb3(%1 : $Klass) +// CHECK: bb3([[A:%[0-9]+]] : @owned $Klass): +// CHECK: return [[A]] +sil [ossa] @unpack_enum_arg_non_trivial_owned : $@convention(thin) (@guaranteed Klass, @guaranteed Klass) -> @owned Klass { +bb0(%0 : @guaranteed $Klass, %1 : @guaranteed $Klass): + cond_br undef, bb1, bb2 + +bb1: + %0a = copy_value %0 : $Klass + %2 = enum $Optional, #Optional.some!enumelt, %0a : $Klass + br bb3(%2 : $Optional) + +bb2: + %1a = copy_value %1 : $Klass + %3 = enum $Optional, #Optional.some!enumelt, %1a : $Klass + br bb3(%3 : $Optional) + +bb3(%4 : @owned $Optional): + %5 = unchecked_enum_data %4 : $Optional, #Optional.some!enumelt + return %5 : $Klass +} + +// CHECK-LABEL: sil [ossa] @unpack_enum_arg_non_trivial_guaranteed : +// CHECK: bb1: +// CHECK: br bb3(%0 : $Klass) +// CHECK: bb2: +// CHECK: br bb3(%1 : $Klass) +// CHECK: bb3([[A:%[0-9]+]] : @owned $Klass): +// CHECK: return [[A]] +sil [ossa] @unpack_enum_arg_non_trivial_guaranteed : $@convention(thin) (@guaranteed Klass, @guaranteed Klass) -> @owned Klass { +bb0(%0 : @guaranteed $Klass, %1 : @guaranteed $Klass): + cond_br undef, bb1, bb2 + +bb1: + %2 = enum $Optional, #Optional.some!enumelt, %0 : $Klass + %2a = begin_borrow %2 : $Optional + br bb3(%2a : $Optional) + +bb2: + %3 = enum $Optional, #Optional.some!enumelt, %1 : $Klass + %3a = begin_borrow %3 : $Optional + br bb3(%3a : $Optional) + +bb3(%4 : @guaranteed $Optional): + %5 = unchecked_enum_data %4 : $Optional, #Optional.some!enumelt + %5a = copy_value %5 : $Klass + end_borrow %4 : $Optional + return %5a : $Klass +} + +// CHECK-LABEL: sil [ossa] @unpack_enum_arg_non_trivial_guaranteed_load_borrow : +// CHECK: bb1: +// CHECK: br bb3(%0 : $Klass) +// CHECK: bb2: +// CHECK: br bb3(%1 : $Klass) +// CHECK: bb3([[A:%[0-9]+]] : @owned $Klass): +// CHECK: return [[A]] +sil [ossa] @unpack_enum_arg_non_trivial_guaranteed_load_borrow : $@convention(thin) (@in_guaranteed Klass, @in_guaranteed Klass) -> @owned Klass { +bb0(%0 : $*Klass, %1 : $*Klass): + %0a = load_borrow %0 : $*Klass + %1a = load_borrow %1 : $*Klass + cond_br undef, bb1, bb2 + +bb1: + %2 = enum $Optional, #Optional.some!enumelt, %0a : $Klass + %2a = begin_borrow %2 : $Optional + br bb3(%2a : $Optional) + +bb2: + %3 = enum $Optional, #Optional.some!enumelt, %1a : $Klass + %3a = begin_borrow %3 : $Optional + br bb3(%3a : $Optional) + +bb3(%4 : @guaranteed $Optional): + %5 = unchecked_enum_data %4 : $Optional, #Optional.some!enumelt + %5a = copy_value %5 : $Klass + end_borrow %4 : $Optional + end_borrow %1a : $Klass + end_borrow %0a : $Klass + return %5a : $Klass +} + +// CHECK-LABEL: sil [ossa] @jump_thread_retain_release : +// CHECK: cond_br +// CHECK: cond_br %3, bb4, bb3 +// +// CHECK: bb3: +// CHECK: %3 = copy_value %2 : $Klass +// CHECK: destroy_value %2 : $Klass +// CHECK: br bb5(%3 : $Klass) +// +// CHECK: bb4: +// CHECK: %4 = copy_value %1 : $Klass +// CHECK: destroy_value %1 : $Klass +// CHECK: br bb5(%4 : $Klass) +// CHECK: } // end sil function 'jump_thread_retain_release' +sil [ossa] @jump_thread_retain_release : $@convention(thin) (Builtin.Int1, @owned Klass, @owned Klass, Builtin.Int1) -> () { +bb0(%0 : $Builtin.Int1, %1 : @owned $Klass, %2 : @owned $Klass, %3 : $Builtin.Int1): + cond_br %0, bb2, bb1 + +bb1: + br bb6(%2 : $Klass) + +bb2: + cond_br %3, bb3, bb4 + +bb3: + %1a = copy_value %1 : $Klass + br bb5(%1a : $Klass) + +bb4: + %2a = copy_value %2 : $Klass + br bb5(%2a : $Klass) + +bb5(%11 : @owned $Klass): + destroy_value %2 : $Klass + br bb6(%11 : $Klass) + +bb6(%14 : @owned $Klass): + destroy_value %14 : $Klass + destroy_value %1 : $Klass + %16 = tuple () + return %16 : $() +} diff --git a/test/SILOptimizer/simplify_cfg_tryapply_ossa.sil b/test/SILOptimizer/simplify_cfg_tryapply_ossa.sil new file mode 100644 index 0000000000000..b36e3322f6d0c --- /dev/null +++ b/test/SILOptimizer/simplify_cfg_tryapply_ossa.sil @@ -0,0 +1,416 @@ +// RUN: %target-sil-opt -enable-objc-interop -enforce-exclusivity=none -enable-sil-verify-all %s -jumpthread-simplify-cfg | %FileCheck %s + +// Declare this SIL to be canonical because some tests break raw SIL +// conventions. e.g. address-type block args. -enforce-exclusivity=none is also +// required to allow address-type block args in canonical SIL. +sil_stage canonical + +import Builtin +import Swift + +public class EE { + init() +} + +public class BB { + init() +} + +public class CC : BB { + @inline(never) init(e: EE) + override init() +} + +public protocol PP { + var prop1: BB? { get } +} + +public class DD : PP { + public var prop1: BB? { get } + init() +} + +class B {} +class E : B {} + +sil [ossa] @unknown : $@convention(thin) () -> () + +sil [ossa] @adder : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + +sil [ossa] @rethrow_function : $@convention(thin) (@owned @callee_owned (Int) -> (Int, @error Error)) -> (Int, @error Error) +sil [ossa] @non_throwing_closure : $@convention(thin) (Int) -> Int + +sil [ossa] @rethrowGuaranteed : $@convention(thin) (@guaranteed (Optional, B)) -> Int +sil [ossa] @rethrowOwned : $@convention(thin) (@owned (Optional, B)) -> Int + +// CHECK-LABEL: sil [ossa] @replace_try_apply_with_apply +// CHECK: [[R:%[0-9]+]] = apply [nothrow] %1(%{{[0-9]+}}) : $@convention(thin) (@owned @callee_owned (Int) -> (Int, @error Error)) -> (Int, @error Error) +// CHECK-NEXT: dealloc_stack +// CHECK-NEXT: return [[R]] : $Int +sil [ossa] @replace_try_apply_with_apply : $@convention(thin) () -> Int { +bb0: + %as = alloc_stack $Builtin.Int32 + %0 = function_ref @rethrow_function : $@convention(thin) (@owned @callee_owned (Int) -> (Int, @error Error)) -> (Int, @error Error) + %1 = function_ref @non_throwing_closure : $@convention(thin) (Int) -> Int + %2 = thin_to_thick_function %1 : $@convention(thin) (Int) -> Int to $@callee_owned (Int) -> Int + %3 = convert_function %2 : $@callee_owned (Int) -> Int to $@callee_owned (Int) -> (Int, @error Error) + try_apply %0(%3) : $@convention(thin) (@owned @callee_owned (Int) -> (Int, @error Error)) -> (Int, @error Error), normal bb1, error bb2 + +bb1(%5 : $Int): + dealloc_stack %as: $*Builtin.Int32 + return %5 : $Int + +bb2(%8 : @owned $Error): + dealloc_stack %as: $*Builtin.Int32 + unreachable +} + +// CHECK-LABEL: sil [ossa] @replace_try_apply_with_apply_cast_return_type : $@convention(method) (@guaranteed DD) -> @owned Optional +// CHECK: bb0 +// CHECK: [[INIT:%.*]] = apply %{{.*}} +// CHECK: [[COPY:%.*]] = copy_value [[INIT]] : $@callee_owned (@owned EE) -> @owned CC +// CHECK: [[CVT:%.*]] = convert_function [[INIT]] +// CHECK-NOT: try_apply +// CHECK: destroy_value [[CVT]] : $@callee_owned (@owned EE) -> (@owned Optional, @error Error) +// CHECK: apply [[COPY]] +// Check that return value is properly casted +// CHECK-NEXT: enum $Optional, #Optional.some!enumelt, %{{.*}} : $CC +// CHECK-NEXT: upcast %{{.*}} : $Optional to $Optional +// CHECK-NEXT: return +sil [ossa] @replace_try_apply_with_apply_cast_return_type: $@convention(method) (@guaranteed DD) -> @owned Optional { +bb0(%0 : @guaranteed $DD): + %1 = alloc_ref $EE + debug_value %1 : $EE + %3 = function_ref @initCC : $@convention(thin) (@thick CC.Type) -> @owned @callee_owned (@owned EE) -> @owned CC + %4 = metatype $@thick CC.Type + %5 = apply %3(%4) : $@convention(thin) (@thick CC.Type) -> @owned @callee_owned (@owned EE) -> @owned CC + %6 = convert_function %5 : $@callee_owned (@owned EE) -> @owned CC to $@callee_owned (@owned EE) -> (@owned Optional, @error Error) + try_apply %6(%1) : $@callee_owned (@owned EE) -> (@owned Optional, @error Error), normal bb1, error bb2 + +bb1(%8 : @owned $Optional): + return %8 : $Optional + +bb2(%10 : @owned $Error): + unreachable +} + +// Check that we don't crash on this, because we perform casting +// if the argument types of the converted function types do not match. +// CHECK-LABEL: try_apply_with_apply_of_cast_argument +// CHECK-NOT: try_apply {{%[0-9]+}} +// CHECK: convert_function +// CHECK: upcast +// CHECK: apply +// CHECK-NOT: try_apply +// CHECK: return +sil [ossa] @try_apply_with_apply_of_cast_argument: $@convention(method) (@owned CC) -> @owned BB { +bb0(%0 : @owned $CC): + %3 = function_ref @takeBB : $@convention(thin) (@owned BB) -> @owned BB + %6 = convert_function %3 : $@convention(thin) (@owned BB) -> @owned BB to $@convention(thin) (@owned CC) -> (@owned BB, @error Error) + try_apply %6(%0) : $@convention(thin) (@owned CC) -> (@owned BB, @error Error), normal bb1, error bb2 + +bb1(%8 : @owned $BB): + return %8 : $BB + +bb2(%10 : @owned $Error): + // Prevent that the conversion is done because the error block is empty and unreachable. + %12 = function_ref @unknown : $@convention(thin) () -> () + apply %12() : $@convention(thin) () -> () + unreachable +} + +sil [noinline] @initCC : $@convention(thin) (@thick CC.Type) -> @owned @callee_owned (@owned EE) -> @owned CC + +sil [noinline] @takeBB : $@convention(thin) (@owned BB) -> @owned BB + +// Check that we don't crash on this. +// The compiler should be able to cast between the labeled and unlabeled return tuple types. +// CHECK-LABEL: @try_apply_with_convert_function_returning_casted_tuple +// CHECK: [[T0:%.*]] = apply {{%[0-9]+}} +// CHECK: return [[T0]] +sil [ossa] @try_apply_with_convert_function_returning_casted_tuple: $@convention(thin) () -> (Int32, Int32) { +bb0: + %3 = function_ref @returnTuple : $@convention(thin) () -> (Int32, Int32) + %6 = convert_function %3 : $@convention(thin) () -> (Int32, Int32) to $@convention(thin) () -> (Int32, Int32, @error Error) + try_apply %6() : $@convention(thin) () -> (Int32, Int32, @error Error), normal bb1, error bb2 + +bb1(%8 : $(Int32, Int32)): + return %8 : $(Int32, Int32) + +bb2(%10 : @owned $Error): + // Prevent that the conversion is done because the error block is empty and unreachable. + %12 = function_ref @unknown : $@convention(thin) () -> () + apply %12() : $@convention(thin) () -> () + unreachable +} + +sil [noinline] @returnTuple: $@convention(thin) () -> (Int32, Int32) + +public class AAA { +} + +public class BBB : AAA { +} + +@inline(never) func returnUnlabeledTuple(b: BBB) -> (BBB, BBB) + +func testit(f: (BBB) throws -> (AAA, AAA), _ b: BBB) throws -> (AAA, AAA) + +func callit(b: BBB) throws -> (AAA, AAA) + +sil [ossa] [noinline] @returnUnlabeledTupleOfClasses : $@convention(thin) (@owned BBB) -> @owned (BBB, BBB) { +bb0(%0 : @owned $BBB): + debug_value %0 : $BBB + %0a = copy_value %0 : $BBB + %3 = tuple (%0a : $BBB, %0 : $BBB) + return %3 : $(BBB, BBB) +} + +sil [ossa] @testFunctorReturningUnlabeledTuple : $@convention(thin) (@owned @callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error), @owned BBB) -> (@owned (AAA, AAA), @error Error) { +bb0(%0 : @owned $@callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error), %1 : @owned $BBB): + debug_value %0 : $@callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error) + debug_value %1 : $BBB + %0a = copy_value %0 : $@callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error) + %1a = copy_value %1 : $BBB + try_apply %0a(%1a) : $@callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error), normal bb1, error bb2 + +bb1(%7 : @owned $(AAA, AAA)): + (%8, %9) = destructure_tuple %7 : $(AAA, AAA) + %10 = tuple (%8 : $AAA, %9 : $AAA) + destroy_value %1 : $BBB + destroy_value %0 : $@callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error) + return %10 : $(AAA, AAA) + +bb2(%14 : @owned $Error): + destroy_value %1 : $BBB + destroy_value %0 : $@callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error) + throw %14 : $Error +} + + +// Check that we don't crash on this. Currently we just do not optimize try_apply if +// we cannot cast the actual return type into expected return type. +// TODO: Change the checks when we support more complex casts of return types. +// CHECK-LABEL: @testCallingFunctionWithFunctorReturningUnlabeledTuple +// CHECK: try_apply {{%[0-9]+}} +// CHECK: return +sil [ossa] @testCallingFunctionWithFunctorReturningUnlabeledTuple : $@convention(thin) (@owned BBB) -> (@owned (AAA, AAA), @error Error) { +bb0(%0 : @owned $BBB): + debug_value %0 : $BBB + + %2 = function_ref @testFunctorReturningUnlabeledTuple : $@convention(thin) (@owned @callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error), @owned BBB) -> (@owned (AAA, AAA), @error Error) + + %3 = function_ref @returnUnlabeledTupleOfClasses : $@convention(thin) (@owned BBB) -> @owned (BBB, BBB) + %4 = thin_to_thick_function %3 : $@convention(thin) (@owned BBB) -> @owned (BBB, BBB) to $@callee_owned (@owned BBB) -> @owned (BBB, BBB) + %5 = convert_function %4 : $@callee_owned (@owned BBB) -> @owned (BBB, BBB) to $@callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error) + %0a = copy_value %0 : $BBB + try_apply %2(%5, %0a) : $@convention(thin) (@owned @callee_owned (@owned BBB) -> (@owned (AAA, AAA), @error Error), @owned BBB) -> (@owned (AAA, AAA), @error Error), normal bb1, error bb2 + +bb1(%8 : @owned $(AAA, AAA)): + (%9, %10) = destructure_tuple %8 : $(AAA, AAA) + %11 = tuple (%9 : $AAA, %10 : $AAA) + destroy_value %0 : $BBB + return %11 : $(AAA, AAA) + +bb2(%14 : @owned $Error): + destroy_value %0 : $BBB + throw %14 : $Error +} + +struct UP { + +} + +struct UBP { +} + +struct CAB { + +} + +sil [ossa] @CABIdentityGetter : $@convention(thin) <τ_0_0> (UBP<τ_0_0>) -> UP<()> +sil [ossa] @CABwithUnsafeBufferPointer : $@convention(method) <τ_0_0><τ_1_0> (@owned @callee_owned (UBP<τ_0_0>) -> (@out τ_1_0, @error Error), @guaranteed CAB<τ_0_0>) -> (@out τ_1_0, @error Error) +sil [ossa] @thunk_helper : $@convention(thin) <τ_0_0> (UBP<τ_0_0>, @owned @callee_owned (UBP<τ_0_0>) -> (UP<()>, @error Error)) -> (@out UP<()>, @error Error) + +// CHECK-LABEL: sil [ossa] @check_parameters_casting_with_generics : +// CHECK-NOT: try_apply +// CHECK: apply [nothrow] %{{.*}}> +// CHECK: return +sil [ossa] @check_parameters_casting_with_generics : $@convention(method) (CAB) -> UP<()> { +bb0(%0 : $CAB): + // function_ref Swift._ContiguousArrayBuffer.withUnsafeBufferPointer (CAB)((Swift.UBP) throws -> B) throws -> B + %2 = function_ref @CABwithUnsafeBufferPointer : $@convention(method) <τ_0_0><τ_1_0> (@owned @callee_owned (UBP<τ_0_0>) -> (@out τ_1_0, @error Error), @guaranteed CAB<τ_0_0>) -> (@out τ_1_0, @error Error) + // function_ref Swift._ContiguousArrayBuffer.(identity.getter : UP<()>).(closure #1) + %3 = function_ref @CABIdentityGetter : $@convention(thin) <τ_0_0> (UBP<τ_0_0>) -> UP<()> + %4 = partial_apply %3() : $@convention(thin) <τ_0_0> (UBP<τ_0_0>) -> UP<()> + %5 = convert_function %4 : $@callee_owned (UBP) -> UP<()> to $@callee_owned (UBP) -> (UP<()>, @error Error) + // function_ref reabstraction thunk helper from @callee_owned (@unowned UBP) -> (@unowned UP<()>, @error @owned Swift.Error) to @callee_owned (@unowned UBP) -> (@out UP<()>, @error @owned Swift.Error) + %6 = function_ref @thunk_helper : $@convention(thin) <τ_0_0> (UBP<τ_0_0>, @owned @callee_owned (UBP<τ_0_0>) -> (UP<()>, @error Error)) -> (@out UP<()>, @error Error) + %7 = partial_apply %6(%5) : $@convention(thin) <τ_0_0> (UBP<τ_0_0>, @owned @callee_owned (UBP<τ_0_0>) -> (UP<()>, @error Error)) -> (@out UP<()>, @error Error) + %8 = alloc_stack $UP<()> + %9 = unchecked_addr_cast %8 : $*UP<()> to $*UP<()> + %10 = convert_function %7 : $@callee_owned (UBP) -> (@out UP<()>, @error Error) to $@callee_owned (UBP) -> (@out UP<()>, @error Error) + try_apply %2>(%8, %10, %0) : $@convention(method) <τ_0_0><τ_1_0> (@owned @callee_owned (UBP<τ_0_0>) -> (@out τ_1_0, @error Error), @guaranteed CAB<τ_0_0>) -> (@out τ_1_0, @error Error), normal bb1, error bb2 + +bb1(%12 : $()): + %13 = load [trivial] %8 : $*UP<()> + dealloc_stack %8 : $*UP<()> + return %13 : $UP<()> + +bb2(%16 : @owned $Error): + unreachable +} + +// Test try_apply to apply conversion where the owned callee is defined in a non-postdominated block. +// +// CHECK-LABEL: sil [ossa] @test_owned_callee : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +// CHECK: [[PA:%.*]] = partial_apply %2(%0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 +// CHECK: [[COPY:%.*]] = copy_value [[PA]] : $@callee_owned (Builtin.Int32) -> Builtin.Int32 +// CHECK: [[CVT:%.*]] = convert_function [[PA]] : $@callee_owned (Builtin.Int32) -> Builtin.Int32 to $@callee_owned (Builtin.Int32) -> (Builtin.Int32, @error Error) +// CHECK: cond_br undef, bb1, bb2 +// CHECK: bb1: +// CHECK: destroy_value %5 : $@callee_owned (Builtin.Int32) -> (Builtin.Int32, @error Error) +// CHECK: [[CALL:%.*]] = apply [[COPY]] +// CHECK: br bb3([[CALL]] : $Builtin.Int32) +// CHECK: bb2: // Preds: bb0 +// CHECK: destroy_value [[COPY]] : $@callee_owned (Builtin.Int32) -> Builtin.Int32 +// CHECK: destroy_value [[CVT]] : $@callee_owned (Builtin.Int32) -> (Builtin.Int32, @error Error) +// CHECK: br bb3( +// CHECK: bb3( +// CHECK-NEXT: return +// CHECK-LABEL: } // end sil function 'test_owned_callee' +sil [ossa] @test_owned_callee : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + %f = function_ref @adder : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %pa = partial_apply %f(%0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %conv = convert_function %pa : $@callee_owned (Builtin.Int32) -> (Builtin.Int32) to $@callee_owned (Builtin.Int32) -> (Builtin.Int32, @error Error) + // Test OSSA cleanup by creating a leaking block. + cond_br undef, bb1, bb4 + +bb1: + try_apply %conv(%1) : $@callee_owned (Builtin.Int32) -> (Builtin.Int32, @error Error), normal bb2, error bb3 + +bb2(%r : $Builtin.Int32): + br bb5(%r : $Builtin.Int32) + +bb3(%e : $Error): + %r1 = integer_literal $Builtin.Int32, 0 + br bb5(%r1 : $Builtin.Int32) + +bb4: + %const = integer_literal $Builtin.Int32, 1 + destroy_value %conv : $@callee_owned (Builtin.Int32) -> (Builtin.Int32, @error Error) + br bb5(%const : $Builtin.Int32) + +bb5(%res : $Builtin.Int32): + return %res : $Builtin.Int32 +} + + +// TODO: The extra copy/destroy here could be avoided. +// +// CHECK-LABEL: sil [ossa] @test_noescape +// CHECK: [[FN:%.*]] = function_ref @adder +// CHECK: [[PA:%.*]] = partial_apply [callee_guaranteed] [[FN]](%0) +// CHECK: [[COPY:%.*]] = copy_value [[PA]] : $@callee_guaranteed (Builtin.Int32) -> Builtin.Int32 +// CHECK-NOT: try_apply +// CHECK: apply [[COPY]](%1) +// CHECK: destroy_value [[COPY]] : $@callee_guaranteed (Builtin.Int32) -> Builtin.Int32 +// CHECK: return +sil [ossa] @test_noescape : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + %f = function_ref @adder : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %pa = partial_apply [callee_guaranteed] %f(%0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %conv = convert_function %pa : $@callee_guaranteed (Builtin.Int32) -> (Builtin.Int32) to $@callee_guaranteed (Builtin.Int32) -> (Builtin.Int32, @error Error) + %ne = convert_escape_to_noescape %conv : $@callee_guaranteed (Builtin.Int32) -> (Builtin.Int32, @error Error) to $@noescape @callee_guaranteed (Builtin.Int32) -> (Builtin.Int32, @error Error) + try_apply %ne(%1) : $@noescape @callee_guaranteed (Builtin.Int32) -> (Builtin.Int32, @error Error), normal bb1, error bb2 + +bb1(%r : $Builtin.Int32): + br bb3(%r : $Builtin.Int32) + +bb2(%e : $Error): + %r1 = integer_literal $Builtin.Int32, 0 + br bb3(%r1 : $Builtin.Int32) + +bb3(%res : $Builtin.Int32): + destroy_value %conv : $@callee_guaranteed (Builtin.Int32) -> (Builtin.Int32, @error Error) + return %res : $Builtin.Int32 +} + +// Test castValueToABICompatibleType with guaranteed tuples and optionals. +// Create a nested borrow scope for the Optional tuple element +// +// CHECK-LABEL: sil [ossa] @replaceTryApplyGuaranteedTuple : $@convention(thin) (@guaranteed Optional, @guaranteed E) -> Int { +// CHECK: bb0(%0 : @guaranteed $Optional, %1 : @guaranteed $E): +// CHECK: [[INCAST:%.*]] = unchecked_ref_cast %0 : $Optional to $Optional +// CHECK: [[INTUP:%.*]] = tuple ([[INCAST]] : $Optional, %1 : $E) +// CHECK: ([[EXTR0:%.*]], [[EXTR1:%.*]]) = destructure_tuple [[INTUP]] : $(Optional, E) +// CHECK: switch_enum [[EXTR0]] : $Optional, case #Optional.some!enumelt: bb1, default bb2 +// CHECK: bb1([[UNWRAP:%.*]] : @guaranteed $AAA): +// CHECK: [[ACAST:%.*]] = unchecked_ref_cast [[UNWRAP]] : $AAA to $B +// CHECK: [[SOME:%.*]] = enum $Optional, #Optional.some!enumelt, [[ACAST]] : $B +// CHECK: [[BORROWSOME:%.*]] = begin_borrow [[SOME]] : $Optional +// CHECK: br bb3([[BORROWSOME]] : $Optional) +// CHECK: bb2(%{{.*}} : $Optional): +// CHECK: [[NONE:%.*]] = enum $Optional, #Optional.none!enumelt +// CHECK: [[BORROWNONE:%.*]] = begin_borrow [[NONE]] : $Optional +// CHECK: br bb3([[BORROWNONE]] : $Optional) +// CHECK: bb3([[REWRAP:%.*]] : @guaranteed $Optional): +// CHECK: [[ECAST:%.*]] = upcast [[EXTR1]] : $E to $B +// CHECK: [[OUTTUP:%.*]] = tuple ([[REWRAP]] : $Optional, [[ECAST]] : $B) +// CHECK: apply %{{.*}}([[OUTTUP]]) : $@convention(thin) (@guaranteed (Optional, B)) -> Int +// CHECK: end_borrow [[REWRAP]] : $Optional +// CHECK: return +// CHECK-LABEL: } // end sil function 'replaceTryApplyGuaranteedTuple' +sil [ossa] @replaceTryApplyGuaranteedTuple : $@convention(thin) (@guaranteed Optional, @guaranteed E) -> Int { +bb0(%0 : @guaranteed $Optional, %1 : @guaranteed $E): + %cast = unchecked_ref_cast %0 : $Optional to $Optional + %tuple = tuple (%cast : $Optional, %1 : $E) + %2 = function_ref @rethrowGuaranteed : $@convention(thin) (@guaranteed (Optional, B)) -> Int + %3 = convert_function %2 : $@convention(thin) (@guaranteed (Optional, B)) -> Int to $@convention(thin) (@guaranteed (Optional, E)) -> (Int, @error Error) + try_apply %3(%tuple) : $@convention(thin) (@guaranteed (Optional, E)) -> (Int, @error Error), normal bb1, error bb2 + +bb1(%5 : $Int): + return %5 : $Int + +bb2(%8 : @owned $Error): + unreachable +} + +// Test castValueToABICompatibleType with owned tuples and optionals. +// Create a destructure for the Optional tuple element. +// +// CHECK-LABEL: sil [ossa] @replaceTryApplyOwnedTuple : $@convention(thin) (@owned Optional, @owned E) -> Int { +// CHECK: bb0(%0 : @owned $Optional, %1 : @owned $E): +// CHECK: [[INCAST:%.*]] = unchecked_ref_cast %0 : $Optional to $Optional +// CHECK: [[INTUP:%.*]] = tuple ([[INCAST]] : $Optional, %1 : $E) +// CHECK: ([[EXTR0:%.*]], [[EXTR1:%.*]]) = destructure_tuple [[INTUP]] : $(Optional, E) +// CHECK: switch_enum [[EXTR0]] : $Optional, case #Optional.some!enumelt: bb1, default bb2 +// CHECK: bb1([[UNWRAP:%.*]] : @owned $AAA): +// CHECK: [[ACAST:%.*]] = unchecked_ref_cast [[UNWRAP]] : $AAA to $B +// CHECK: [[SOME:%.*]] = enum $Optional, #Optional.some!enumelt, [[ACAST]] : $B +// CHECK: br bb3([[SOME]] : $Optional) +// CHECK: bb2(%{{.*}} : $Optional): +// CHECK: [[NONE:%.*]] = enum $Optional, #Optional.none!enumelt +// CHECK: br bb3([[NONE]] : $Optional) +// CHECK: bb3([[REWRAP:%.*]] : @owned $Optional): +// CHECK: [[ECAST:%.*]] = upcast [[EXTR1]] : $E to $B +// CHECK: [[OUTTUP:%.*]] = tuple ([[REWRAP]] : $Optional, [[ECAST]] : $B) +// CHECK: apply %{{.*}}([[OUTTUP]]) : $@convention(thin) (@owned (Optional, B)) -> Int +// CHECK: return +// CHECK-LABEL: } // end sil function 'replaceTryApplyOwnedTuple' +sil [ossa] @replaceTryApplyOwnedTuple : $@convention(thin) (@owned Optional, @owned E) -> Int { +bb0(%0 : @owned $Optional, %1 : @owned $E): + %cast = unchecked_ref_cast %0 : $Optional to $Optional + %tuple = tuple (%cast : $Optional, %1 : $E) + %2 = function_ref @rethrowOwned : $@convention(thin) (@owned (Optional, B)) -> Int + %3 = convert_function %2 : $@convention(thin) (@owned (Optional, B)) -> Int to $@convention(thin) (@owned (Optional, E)) -> (Int, @error Error) + try_apply %3(%tuple) : $@convention(thin) (@owned (Optional, E)) -> (Int, @error Error), normal bb1, error bb2 + +bb1(%5 : $Int): + return %5 : $Int + +bb2(%8 : @owned $Error): + unreachable +} + diff --git a/test/SILOptimizer/string_optimization.swift b/test/SILOptimizer/string_optimization.swift index 866a9e561602d..601003195b81f 100644 --- a/test/SILOptimizer/string_optimization.swift +++ b/test/SILOptimizer/string_optimization.swift @@ -5,7 +5,6 @@ // RUN: %target-run %t/a.out | %FileCheck %s -check-prefix=CHECK-OUTPUT // REQUIRES: executable_test,swift_stdlib_no_asserts -// REQUIRES: PTRSIZE=64 #if _runtime(_ObjC) import Foundation