From fdab5f25572abab6275aa93a6f68c6ba47d2dade Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 15:55:56 -0800 Subject: [PATCH 1/9] Comment the getForwardingOwnership() API. I was badly confused for a while. --- include/swift/SIL/SILInstruction.h | 10 ++++++++++ include/swift/SIL/SILValue.h | 21 +++++++++++++-------- 2 files changed, 23 insertions(+), 8 deletions(-) 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..d5d2788f951a7 100644 --- a/include/swift/SIL/SILValue.h +++ b/include/swift/SIL/SILValue.h @@ -880,15 +880,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. /// -/// 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. +/// 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 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) { From ead5fa3f6506a15929671f4f51d6df2901540c67 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 15:57:55 -0800 Subject: [PATCH 2/9] Move getOwnershipKind from SILValue to ValueBase. The API for values is on the ValueBase. SILValue is supposed to be a pointer-like wrapper class. Accessing a value's API is always done as 'value->api()'. The ValueBase subclasses, like SingleValueInstruction, need to inherit the API. I didn't have time to fix all the cases of value.isOwnershipKind() throughout the code. --- include/swift/SIL/SILValue.h | 18 +++++++++++++++++- lib/SIL/IR/ValueOwnership.cpp | 6 +++--- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/include/swift/SIL/SILValue.h b/include/swift/SIL/SILValue.h index d5d2788f951a7..35f2b70fd3928 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; 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; } From 2434891d5174f61e61986d575e6bae89f9154f80 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 16:02:13 -0800 Subject: [PATCH 3/9] Add Operand->isConsuming() API. This should be used instead of isLifetimeEnding wherever the code assumes an owned value is consumed. isLifetimeEnding should only be used when the code is expected to handle both owned and guaranteed values. --- include/swift/SIL/SILValue.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/include/swift/SIL/SILValue.h b/include/swift/SIL/SILValue.h index 35f2b70fd3928..e9adeca386ff0 100644 --- a/include/swift/SIL/SILValue.h +++ b/include/swift/SIL/SILValue.h @@ -676,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 { From d50f220018fc9827b4b0f6734d5b26adfd92d2f5 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 16:04:38 -0800 Subject: [PATCH 4/9] Add a makeUserIteratorRange API To allow a value's use-list to be pased into an LLVM API that expects an llvm::iterator_range. --- include/swift/SILOptimizer/Utils/InstOptUtils.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/include/swift/SILOptimizer/Utils/InstOptUtils.h b/include/swift/SILOptimizer/Utils/InstOptUtils.h index cfd6e6e4181a2..753e905282864 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. From 0c09d42447eddce0cfe85e973505af657dada022 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 16:10:12 -0800 Subject: [PATCH 5/9] Comment OSSA APIs makeNewValueAvailable & endLifetimeAtLeakingBlocks --- include/swift/SILOptimizer/Utils/InstOptUtils.h | 8 ++++++++ lib/SILOptimizer/Utils/InstOptUtils.cpp | 2 ++ 2 files changed, 10 insertions(+) diff --git a/include/swift/SILOptimizer/Utils/InstOptUtils.h b/include/swift/SILOptimizer/Utils/InstOptUtils.h index 753e905282864..40dd0963f7796 100644 --- a/include/swift/SILOptimizer/Utils/InstOptUtils.h +++ b/include/swift/SILOptimizer/Utils/InstOptUtils.h @@ -708,10 +708,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/lib/SILOptimizer/Utils/InstOptUtils.cpp b/lib/SILOptimizer/Utils/InstOptUtils.cpp index 23af53febd267..04e6e4fa4d4de 100644 --- a/lib/SILOptimizer/Utils/InstOptUtils.cpp +++ b/lib/SILOptimizer/Utils/InstOptUtils.cpp @@ -2052,6 +2052,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()) From dd525d02e7e8a484da4a311a4c0eef7e637e3b6a Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 16:11:52 -0800 Subject: [PATCH 6/9] Add requiresOSSACleanup API. --- include/swift/SILOptimizer/Utils/OwnershipOptUtils.h | 7 +++++++ 1 file changed, 7 insertions(+) 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; From ca0d1893485c7e7c822e854acf114ddf999b908e Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 16:12:46 -0800 Subject: [PATCH 7/9] Add assert and comments to OperandOwnershipClassifier::visitFullApply I had a difficult time determining to set of possible operand ownership kinds for callee operands. --- lib/SIL/IR/OperandOwnership.cpp | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) 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 From b908b9fe0424e98a5517a0aa6445fc899a0487e0 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 16:14:07 -0800 Subject: [PATCH 8/9] EagerSpecializer: only run SIL verification when needed This was running the SIL verifier in the asserts build unconditionally on every function even if this pass did nothing. --- lib/SILOptimizer/Transforms/EagerSpecializer.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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); From ba9f52071b9fdba482bc69828c33593148f04765 Mon Sep 17 00:00:00 2001 From: Andrew Trick Date: Tue, 23 Feb 2021 16:53:12 -0800 Subject: [PATCH 9/9] OSSA: simplify-cfg support for trivial block arguments. Enable most simplify-cfg optimizations as long as the block arguments have trivial types. Enable most simplify CFG unit tests cases. This massively reduces the size of the CFG during OSSA passes. Test cases that aren't supported in OSSA yet have been moved to a separate test file for disabled OSSA tests, Full simplify-cfg support is currently blocked on OSSA utilities which I haven't checked in yet. --- .../swift/SILOptimizer/Utils/InstOptUtils.h | 8 +- lib/SIL/IR/SILFunctionType.cpp | 3 + lib/SILGen/SILGenThunk.cpp | 1 - lib/SILOptimizer/Transforms/SimplifyCFG.cpp | 261 ++- lib/SILOptimizer/Utils/Devirtualize.cpp | 33 +- lib/SILOptimizer/Utils/InstOptUtils.cpp | 94 +- test/SILOptimizer/dead_array_elim.swift | 1 - test/SILOptimizer/simplify_cfg_args_ossa.sil | 793 ++++++++ .../simplify_cfg_args_ossa_disabled.sil | 34 + test/SILOptimizer/simplify_cfg_ossa.sil | 1809 +++++++++++++++++ .../simplify_cfg_ossa_disabled.sil | 1289 ++++++++++++ .../simplify_cfg_tryapply_ossa.sil | 416 ++++ test/SILOptimizer/string_optimization.swift | 1 - 13 files changed, 4640 insertions(+), 103 deletions(-) create mode 100644 test/SILOptimizer/simplify_cfg_args_ossa.sil create mode 100644 test/SILOptimizer/simplify_cfg_args_ossa_disabled.sil create mode 100644 test/SILOptimizer/simplify_cfg_ossa.sil create mode 100644 test/SILOptimizer/simplify_cfg_ossa_disabled.sil create mode 100644 test/SILOptimizer/simplify_cfg_tryapply_ossa.sil diff --git a/include/swift/SILOptimizer/Utils/InstOptUtils.h b/include/swift/SILOptimizer/Utils/InstOptUtils.h index 40dd0963f7796..c52a3eee804cd 100644 --- a/include/swift/SILOptimizer/Utils/InstOptUtils.h +++ b/include/swift/SILOptimizer/Utils/InstOptUtils.h @@ -273,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. /// 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/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/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 04e6e4fa4d4de..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}; } 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