diff --git a/include/swift/SIL/InstructionUtils.h b/include/swift/SIL/InstructionUtils.h index ec8c46631ef94..5da5d00963812 100644 --- a/include/swift/SIL/InstructionUtils.h +++ b/include/swift/SIL/InstructionUtils.h @@ -135,6 +135,55 @@ struct LLVM_LIBRARY_VISIBILITY FindClosureResult { /// by a reabstraction thunk. FindClosureResult findClosureForAppliedArg(SILValue V); +struct LLVM_LIBRARY_VISIBILITY FindLocalApplySitesResult { + /// Set to true if the function-ref_ref escapes into a use that our analysis + /// does not understand. Set to false if we found a use that had an actual + /// escape. Set to None if we did not find any call sites, but also didn't + /// find any "escaping uses" as well. + /// + /// The none case is so that we can distinguish in between saying that a value + /// did escape and saying that we did not find any conservative information. + Optional escapes; + + /// Contains the list of local non fully applied partial apply sites that we + /// found. + SmallVector partialApplySites; + + /// Contains the list of full apply sites that we found. + SmallVector fullApplySites; + + /// Deleted default constructor. This is a move only type. + FindLocalApplySitesResult() + : escapes(), partialApplySites(), fullApplySites() {} + FindLocalApplySitesResult(const FindLocalApplySitesResult &) = delete; + FindLocalApplySitesResult(FindLocalApplySitesResult &&) = default; + ~FindLocalApplySitesResult() = default; + + /// Treat this function ref as escaping if we either found an actual user we + /// didn't understand. + /// + /// When determining if we have an "interesting" result, we want to return + /// true if escaping is true or false. That is because conservatively we want + /// to know that we found some sort of information. On the other hand, for + /// determining if non-conservatively we actually did escape, we do not want + /// to consider not finding a value for escaping as equivalent to having an + /// escape. + bool isEscaping() const { return escapes.getValueOr(false); } + + /// We convert to true if we have any "non"-conservative information about the + /// FunctionRefInst that was processed. + /// + /// NOTE: We want to return true if escapes has any value. Otherwise, we may + /// ignore e + operator bool() const { + return escapes.hasValue() || partialApplySites.size() || + fullApplySites.size(); + } +}; + +/// Returns true if we found any apply sites for the given function_ref. +FindLocalApplySitesResult findLocalApplySites(FunctionRefInst *FRI); + /// A utility class for evaluating whether a newly parsed or deserialized /// function has qualified or unqualified ownership. /// diff --git a/include/swift/SIL/Projection.h b/include/swift/SIL/Projection.h index eace9a2d001c8..980bc066c2a84 100644 --- a/include/swift/SIL/Projection.h +++ b/include/swift/SIL/Projection.h @@ -22,11 +22,12 @@ #ifndef SWIFT_SIL_PROJECTION_H #define SWIFT_SIL_PROJECTION_H +#include "swift/AST/TypeAlignments.h" #include "swift/Basic/NullablePtr.h" #include "swift/Basic/PointerIntEnum.h" -#include "swift/AST/TypeAlignments.h" -#include "swift/SIL/SILValue.h" +#include "swift/Basic/STLExtras.h" #include "swift/SIL/SILInstruction.h" +#include "swift/SIL/SILValue.h" #include "swift/SILOptimizer/Analysis/ARCAnalysis.h" #include "swift/SILOptimizer/Analysis/RCIdentityAnalysis.h" #include "llvm/ADT/Hashing.h" @@ -766,13 +767,15 @@ class ProjectionTreeNode { ~ProjectionTreeNode() = default; ProjectionTreeNode(const ProjectionTreeNode &) = default; - llvm::ArrayRef getChildProjections() { - return llvm::makeArrayRef(ChildProjections); + bool isLeaf() const { return ChildProjections.empty(); } + + ArrayRef getChildProjections() const { + return llvm::makeArrayRef(ChildProjections); } - llvm::Optional &getProjection() { return Proj; } + Optional &getProjection() { return Proj; } - llvm::SmallVector getNonProjUsers() const { + const SmallVectorImpl &getNonProjUsers() const { return NonProjUsers; }; @@ -929,6 +932,24 @@ class ProjectionTree { return false; } + void getAllLeafTypes(llvm::SmallVectorImpl &outArray) const { + llvm::SmallVector worklist; + worklist.push_back(getRoot()); + + while (!worklist.empty()) { + auto *node = worklist.pop_back_val(); + // If we have a leaf node, add its type. + if (node->isLeaf()) { + outArray.push_back(node->getType()); + continue; + } + + // Otherwise, add the nodes children to the worklist. + transform(node->getChildProjections(), std::back_inserter(worklist), + [&](unsigned idx) { return getNode(idx); }); + } + } + void getLiveLeafTypes(llvm::SmallVectorImpl &OutArray) const { for (unsigned LeafIndex : LiveLeafIndices) { const ProjectionTreeNode *Node = getNode(LeafIndex); @@ -955,7 +976,9 @@ class ProjectionTree { void replaceValueUsesWithLeafUses(SILBuilder &B, SILLocation Loc, llvm::SmallVectorImpl &Leafs); - + + void getUsers(SmallPtrSetImpl &users) const; + private: void createRoot(SILType BaseTy) { assert(ProjectionTreeNodes.empty() && diff --git a/include/swift/SILOptimizer/Analysis/ARCAnalysis.h b/include/swift/SILOptimizer/Analysis/ARCAnalysis.h index 6ccfb72bc3e6a..d61266a51bec7 100644 --- a/include/swift/SILOptimizer/Analysis/ARCAnalysis.h +++ b/include/swift/SILOptimizer/Analysis/ARCAnalysis.h @@ -289,7 +289,7 @@ class ConsumedArgToEpilogueReleaseMatcher { auto iter = ArgInstMap.find(arg); if (iter == ArgInstMap.end()) return false; - return iter->second.foundSomeButNotAllReleases(); + return iter->second.getPartiallyPostDomReleases().hasValue(); } bool isSingleRelease(SILArgument *arg) const { @@ -334,6 +334,17 @@ class ConsumedArgToEpilogueReleaseMatcher { return completeList.getValue(); } + Optional> + getPartiallyPostDomReleaseSet(SILArgument *arg) const { + auto iter = ArgInstMap.find(arg); + if (iter == ArgInstMap.end()) + return None; + auto partialList = iter->second.getPartiallyPostDomReleases(); + if (!partialList) + return None; + return partialList; + } + ArrayRef getReleasesForArgument(SILValue value) const { auto *arg = dyn_cast(value); if (!arg) diff --git a/include/swift/SILOptimizer/Analysis/CallerAnalysis.h b/include/swift/SILOptimizer/Analysis/CallerAnalysis.h index 375c06eb18f06..cd741fb0be076 100644 --- a/include/swift/SILOptimizer/Analysis/CallerAnalysis.h +++ b/include/swift/SILOptimizer/Analysis/CallerAnalysis.h @@ -44,125 +44,224 @@ class CallerAnalysis : public SILAnalysis { class FunctionInfo; private: + struct CallerInfo; + /// Current module we are analyzing. - SILModule &Mod; + SILModule &mod; /// A map between all the functions and their callsites in the module. - llvm::DenseMap FuncInfos; + /// + /// We use a map vector to ensure that when we dump the state of the caller + /// analysis, + llvm::DenseMap funcInfos; /// A list of functions that needs to be recomputed. - llvm::SetVector RecomputeFunctionList; - - /// Iterate over all the call sites in the function and update - /// CallInfo. - void processFunctionCallSites(SILFunction *F); - - /// This function is about to become "unknown" to us. Invalidate any - /// callsite information related to it. - void invalidateExistingCalleeRelation(SILFunction *F); - - void processRecomputeFunctionList() { - for (auto &F : RecomputeFunctionList) { - processFunctionCallSites(F); - } - RecomputeFunctionList.clear(); - } + llvm::SetVector recomputeFunctionList; public: - CallerAnalysis(SILModule *M) : SILAnalysis(AnalysisKind::Caller), Mod(*M) { - // Make sure we compute everything first time called. - for (auto &F : Mod) { - FuncInfos.FindAndConstruct(&F); - RecomputeFunctionList.insert(&F); - } - } + CallerAnalysis(SILModule *m); - static bool classof(const SILAnalysis *S) { - return S->getKind() == AnalysisKind::Caller; + static bool classof(const SILAnalysis *s) { + return s->getKind() == AnalysisKind::Caller; } /// Invalidate all information in this analysis. virtual void invalidate() override { - FuncInfos.clear(); - RecomputeFunctionList.clear(); - for (auto &F : Mod) { - RecomputeFunctionList.insert(&F); + funcInfos.clear(); + recomputeFunctionList.clear(); + for (auto &f : mod) { + recomputeFunctionList.insert(&f); } } - /// Invalidate all of the information for a specific function. - virtual void invalidate(SILFunction *F, InvalidationKind K) override { + /// Invalidate all of the information for a specific caller function. + virtual void invalidate(SILFunction *caller, InvalidationKind k) override { // Should we invalidate based on the invalidation kind. - bool shouldInvalidate = K & InvalidationKind::CallsAndInstructions; + bool shouldInvalidate = k & InvalidationKind::CallsAndInstructions; if (!shouldInvalidate) return; // This function has become "unknown" to us. Invalidate any callsite // information related to this function. - invalidateExistingCalleeRelation(F); + invalidateExistingCalleeRelation(caller); + // Make sure this function is recomputed next time. - RecomputeFunctionList.insert(F); + recomputeFunctionList.insert(caller); } /// Notify the analysis about a newly created function. - virtual void notifyAddFunction(SILFunction *F) override { - RecomputeFunctionList.insert(F); + virtual void notifyAddFunction(SILFunction *f) override { + recomputeFunctionList.insert(f); } /// Notify the analysis about a function which will be deleted from the /// module. - virtual void notifyDeleteFunction(SILFunction *F) override { - invalidateExistingCalleeRelation(F); - RecomputeFunctionList.remove(F); + virtual void notifyDeleteFunction(SILFunction *f) override { + invalidateExistingCalleeRelation(f); + recomputeFunctionList.remove(f); } /// Notify the analysis about changed witness or vtables. - virtual void invalidateFunctionTables() override { } + virtual void invalidateFunctionTables() override {} + + /// Look up the function info that we have stored for f, recomputing all + /// invalidating parts of the call graph. + const FunctionInfo &getCallerInfo(SILFunction *f) const; + + LLVM_ATTRIBUTE_DEPRECATED(void dump() const LLVM_ATTRIBUTE_USED, + "Only for use in the debugger"); + + /// Print the state of the caller analysis as a sequence of yaml documents for + /// each callee we are tracking. + void print(llvm::raw_ostream &os) const; - const FunctionInfo &getCallerInfo(SILFunction *F) { - // Recompute every function in the invalidated function list and empty the - // list. - processRecomputeFunctionList(); - return FuncInfos[F]; + /// Print the state of the caller analysis as a sequence of yaml documents for + /// each callee we are tracking to the passed in file path. + LLVM_ATTRIBUTE_DEPRECATED(void print(const char *filePath) + const LLVM_ATTRIBUTE_USED, + "Only for use in the debugger"); + +private: + /// Iterate over all the call sites in the function and update + /// CallInfo. + void processFunctionCallSites(SILFunction *f); + + /// This function is about to become "unknown" to us. Invalidate any + /// callsite information related to it. + void invalidateExistingCalleeRelation(SILFunction *f); + + void processRecomputeFunctionList() { + for (auto &f : recomputeFunctionList) { + processFunctionCallSites(f); + } + recomputeFunctionList.clear(); } + + /// Internal only way for getting a caller info. Will insert f if needed and + /// _WILL NOT_ preform any recomputation of the callgraph. + FunctionInfo &getOrInsertCallerInfo(SILFunction *f); }; -/// NOTE: this can be extended to contain the callsites of the function. +/// Auxillary information that we store about a specific caller. +struct CallerAnalysis::CallerInfo { + /// Given a SILFunction F that contains at least one partial apply of the + /// given function, map F to the minimum number of partial applied + /// arguments of any partial application in F. + /// + /// By storing the minimum number of partial applied arguments, we are able + /// to decide quickly if we are able to eliminate dead captured arguments. + Optional numPartialAppliedArguments; + + /// True if this caller performs at least one full application of the + /// callee. + bool hasFullApply : 1; + + /// True if this caller can guarantee that all direct caller's of this + /// function inside of it can be found. + /// + /// NOTE: This does not imply that a function can not be called + /// indirectly. That is a separate query that is type system specific. + bool isDirectCallerSetComplete : 1; + + CallerInfo() + : numPartialAppliedArguments(), hasFullApply(false), + isDirectCallerSetComplete(false) {} +}; + +/// This is a representation of the caller information that we have associated +/// with a specific function. +/// +/// NOTE: this can be extended to contain the callsites of the function. For +/// now there is no need for the exact call sites due to us using only the +/// caller information. By not implementing this we save memory and get rid of +/// dead code. class CallerAnalysis::FunctionInfo { friend class CallerAnalysis; + using CallerInfo = CallerAnalysis::CallerInfo; + struct YAMLRepresentation; + +public: + /// FIXME: Upstream in LLVM this is a public using declaration on + /// MapVector (MapVector::value_type). In the version of LLVM that + /// Swift compiles against currently this is not true, so we provide + /// this for ease of use now. + /// + /// This is meant to be an internal implementation detail. + using CallerStatesValueType = std::pair; - /// A list of all the functions this function calls or partially applies. - llvm::SetVector Callees; - /// A list of all the callers this function has. - llvm::SmallSet Callers; +private: + /// A map from a function containing uses of a function_ref of the callee to + /// the state that we store about the caller's body. + llvm::SmallMapVector callerStates; - /// The number of partial applied arguments of this function. + /// True if this function is something that could be called via a vtable or + /// a witness table. This does not include escaping uses. /// - /// Specifically, it stores the minimum number of partial applied arguments - /// of each function which contain one or multiple partial_applys of this - /// function. - /// This is a little bit off-topic because a partial_apply is not really - /// a "call" of this function. - llvm::DenseMap PartialAppliers; + /// For now this is very conservative and is only set to not be true if we + /// have a function whose representation never can escape. In future cases, + /// we should consider refining this to take into account the compilation + /// visibility of a protocol conformance or class (and thus if we have + /// enough visibility to). + bool mayHaveIndirectCallers : 1; + + /// This is a special set vector that is an abuse as a performance + /// optimization. We in this case are treating the function info data + /// structure as a source of info about callers so that we can update a + /// caller's callees when we invalidate a caller. (See + /// invalidateExistingCalleeRelation). + llvm::SmallSetVector calleeStates; public: + FunctionInfo(SILFunction *f); + + bool hasAllCallers() const { + return hasOnlyCompleteDirectCallerSets() && !mayHaveIndirectCallers; + } + /// Returns true if this function has at least one caller. - bool hasCaller() const { return !Callers.empty(); } + bool hasCaller() const { + return callerStates.size() && + llvm::any_of(callerStates, [](const CallerStatesValueType &v) { + return v.second.hasFullApply; + }); + } /// Returns non zero if this function is partially applied anywhere. /// /// The return value is the minimum number of partially applied arguments. /// Usually all partial applies of a function partially apply the same /// number of arguments anyway. - int getMinPartialAppliedArgs() const { - int minArgs = 0; - for (auto Iter : PartialAppliers) { - int numArgs = Iter.second; - if (minArgs == 0 || numArgs < minArgs) - minArgs = numArgs; + unsigned getMinPartialAppliedArgs() const { + if (callerStates.empty()) + return 0; + + bool foundArg = false; + unsigned minArgs = UINT_MAX; + for (const auto &iter : callerStates) { + if (auto numArgs = iter.second.numPartialAppliedArguments) { + foundArg = true; + minArgs = std::min(minArgs, numArgs.getValue()); + } } - return minArgs; + + return foundArg ? minArgs : 0; } + + bool hasOnlyCompleteDirectCallerSets() const { + return llvm::all_of(callerStates, [](const CallerStatesValueType &v) { + return v.second.isDirectCallerSetComplete; + }); + } + + auto getAllReferencingCallers() const + -> decltype(llvm::make_range(callerStates.begin(), callerStates.end())) { + return llvm::make_range(callerStates.begin(), callerStates.end()); + } + + LLVM_ATTRIBUTE_DEPRECATED(void dump() const LLVM_ATTRIBUTE_USED, + "Only for use in the debugger"); + + void print(llvm::raw_ostream &os) const; }; } // end namespace swift diff --git a/lib/SIL/InstructionUtils.cpp b/lib/SIL/InstructionUtils.cpp index 04252abaf3317..627c8bf9e7e85 100644 --- a/lib/SIL/InstructionUtils.cpp +++ b/lib/SIL/InstructionUtils.cpp @@ -455,6 +455,70 @@ FindClosureResult swift::findClosureForAppliedArg(SILValue V) { return FindClosureResult(PAI, false); } +FindLocalApplySitesResult swift::findLocalApplySites(FunctionRefInst *FRI) { + SmallVector worklist(FRI->use_begin(), FRI->use_end()); + + FindLocalApplySitesResult f; + + // Optimistically state that we have no escapes before our def-use dataflow. + f.escapes = false; + while (!worklist.empty()) { + auto *op = worklist.pop_back_val(); + auto *user = op->getUser(); + + // If we have a full apply site as our user. + if (auto apply = FullApplySite::isa(user)) { + if (apply.getCallee() == op->get()) { + f.fullApplySites.push_back(apply); + continue; + } + } + + // If we have a partial apply as a user, start tracking it, but also look at + // its users. + if (auto *pai = dyn_cast(user)) { + if (pai->getCallee() == op->get()) { + // Track the partial apply that we saw so we can potentially eliminate + // dead closure arguments. + f.partialApplySites.push_back(pai); + // Look to see if we can find a full application of this partial apply + // as well. + copy(pai->getUses(), std::back_inserter(worklist)); + continue; + } + } + + // Otherwise, see if we have any function casts to look through... + switch (user->getKind()) { + case SILInstructionKind::ThinToThickFunctionInst: + case SILInstructionKind::ConvertFunctionInst: + case SILInstructionKind::ConvertEscapeToNoEscapeInst: + copy(cast(user)->getUses(), + std::back_inserter(worklist)); + continue; + + // Look through any reference count instructions since these are not + // escapes: + case SILInstructionKind::CopyValueInst: + copy(cast(user)->getUses(), std::back_inserter(worklist)); + continue; + case SILInstructionKind::StrongRetainInst: + case SILInstructionKind::StrongReleaseInst: + case SILInstructionKind::RetainValueInst: + case SILInstructionKind::ReleaseValueInst: + case SILInstructionKind::DestroyValueInst: + continue; + default: + break; + } + + // But everything else is considered an escape. + f.escapes = true; + } + + return f; +} + namespace { enum class OwnershipQualifiedKind { diff --git a/lib/SIL/Projection.cpp b/lib/SIL/Projection.cpp index 29cc60b72a431..934678578aaa2 100644 --- a/lib/SIL/Projection.cpp +++ b/lib/SIL/Projection.cpp @@ -1476,3 +1476,11 @@ replaceValueUsesWithLeafUses(SILBuilder &Builder, SILLocation Loc, NewNodes.clear(); } } + +void ProjectionTree::getUsers(SmallPtrSetImpl &users) const { + for (auto *node : ProjectionTreeNodes) { + for (auto *op : node->getNonProjUsers()) { + users.insert(op->getUser()); + } + } +} diff --git a/lib/SILOptimizer/Analysis/CallerAnalysis.cpp b/lib/SILOptimizer/Analysis/CallerAnalysis.cpp index 7121b5fda2829..a4d62171014cd 100644 --- a/lib/SILOptimizer/Analysis/CallerAnalysis.cpp +++ b/lib/SILOptimizer/Analysis/CallerAnalysis.cpp @@ -11,64 +11,297 @@ //===----------------------------------------------------------------------===// #include "swift/SILOptimizer/Analysis/CallerAnalysis.h" - +#include "swift/SIL/InstructionUtils.h" #include "swift/SIL/SILModule.h" #include "swift/SILOptimizer/Utils/Local.h" +#include "llvm/Support/FileSystem.h" +#include "llvm/Support/YAMLTraits.h" using namespace swift; -void CallerAnalysis::processFunctionCallSites(SILFunction *F) { - // Scan the whole module and search Apply sites. - for (auto &BB : *F) { - for (auto &II : BB) { - if (auto Apply = FullApplySite::isa(&II)) { - SILFunction *CalleeFn = Apply.getCalleeFunction(); - if (!CalleeFn) +namespace { +using FunctionInfo = CallerAnalysis::FunctionInfo; +} // end anonymous namespace + +//===----------------------------------------------------------------------===// +// CallerAnalysis::FunctionInfo +//===----------------------------------------------------------------------===// + +CallerAnalysis::FunctionInfo::FunctionInfo(SILFunction *f) + : callerStates(), + mayHaveIndirectCallers(canBeCalledIndirectly(f->getRepresentation())) {} + +//===----------------------------------------------------------------------===// +// CallerAnalysis +//===----------------------------------------------------------------------===// + +// NOTE: This is only meant to be used by external users of +// CallerAnalysis since we recompute. For internal uses, please +// instead use getOrInsertFunctionInfo. +const FunctionInfo &CallerAnalysis::getCallerInfo(SILFunction *f) const { + // Recompute every function in the invalidated function list and empty the + // list. + auto &self = const_cast(*this); + self.processRecomputeFunctionList(); + return self.getOrInsertCallerInfo(f); +} + +// Private only version of this function for mutable callers that tries to +// initialize a new f. +FunctionInfo &CallerAnalysis::getOrInsertCallerInfo(SILFunction *f) { + return funcInfos.try_emplace(f, f).first->second; +} + +CallerAnalysis::CallerAnalysis(SILModule *m) + : SILAnalysis(AnalysisKind::Caller), mod(*m) { + // Make sure we add everything to the recompute function list when we start. + for (auto &f : mod) { + getOrInsertCallerInfo(&f); + recomputeFunctionList.insert(&f); + } +} + +static bool mayHaveIndirectCallers(SILFunction *calleeFn) { + // We do not support specialized vtables so specialized methods should never + // be able to be called indirectly. + if (calleeFn->getLinkage() == SILLinkage::Shared) + return false; + return canBeCalledIndirectly(calleeFn->getRepresentation()); +} + +void CallerAnalysis::processFunctionCallSites(SILFunction *callerFn) { + // First grab our caller info so that we can store back references + // from our callerFn to the calleeFn so that we can invalidate all + // callee info about our caller efficiently. + FunctionInfo &callerInfo = getOrInsertCallerInfo(callerFn); + +#ifndef NDEBUG + llvm::SmallPtrSet visitedCallSites; + llvm::SmallSetVector callSitesThatMustBeVisited; +#endif + + // Scan the caller function and search for full or partial apply + // sites in the caller function. + for (auto &block : *callerFn) { + for (auto &i : block) { +#ifndef NDEBUG + // If this is a call site that we visited as part of seeing a different + // function_ref, skip it. We know that it has been processed correctly. + if (visitedCallSites.count(&i)) + continue; +#endif + + // Try to find the apply sites. + if (auto *fri = dyn_cast(&i)) { + if (auto result = findLocalApplySites(fri)) { + auto *calleeFn = fri->getReferencedFunction(); + FunctionInfo &calleeInfo = getOrInsertCallerInfo(calleeFn); + + calleeInfo.mayHaveIndirectCallers = + mayHaveIndirectCallers(calleeFn); + + // Next create our caller state. + auto iter = calleeInfo.callerStates.insert({callerFn, {}}); + // If we succeeded in inserting a new value, put in an optimistic + // value for escaping. + if (iter.second) { + iter.first->second.isDirectCallerSetComplete = true; + } + iter.first->second.isDirectCallerSetComplete &= !result.isEscaping(); + + if (result.fullApplySites.size()) { + callerInfo.calleeStates.insert(calleeFn); + iter.first->second.hasFullApply = true; +#ifndef NDEBUG + for (auto applySite : result.fullApplySites) { + visitedCallSites.insert(applySite.getInstruction()); + callSitesThatMustBeVisited.remove(applySite.getInstruction()); + } +#endif + } + + if (result.partialApplySites.size()) { + callerInfo.calleeStates.insert(calleeFn); + auto &optMin = iter.first->second.numPartialAppliedArguments; + unsigned min = optMin.getValueOr(UINT_MAX); + for (ApplySite partialSite : result.partialApplySites) { + min = std::min(min, partialSite.getNumArguments()); + } + optMin = min; +#ifndef NDEBUG + for (auto applySite : result.partialApplySites) { + visitedCallSites.insert(applySite.getInstruction()); + callSitesThatMustBeVisited.remove(applySite.getInstruction()); + } +#endif + } continue; + } + } - // Update the callee information for this function. - FunctionInfo &CallerInfo = FuncInfos[F]; - CallerInfo.Callees.insert(CalleeFn); - - // Update the callsite information for the callee. - FunctionInfo &CalleeInfo = FuncInfos[CalleeFn]; - CalleeInfo.Callers.insert(F); +#ifndef NDEBUG + // Make sure that we are in sync with FullApplySite. + if (auto apply = FullApplySite::isa(&i)) { + if (apply.getCalleeFunction() && !visitedCallSites.count(&i)) { + callSitesThatMustBeVisited.insert(&i); + } continue; } - if (auto *PAI = dyn_cast(&II)) { - SILFunction *CalleeFn = PAI->getCalleeFunction(); - if (!CalleeFn) - continue; - // Update the callee information for this function. - FunctionInfo &CallerInfo = FuncInfos[F]; - CallerInfo.Callees.insert(CalleeFn); - - // Update the partial-apply information for the callee. - FunctionInfo &CalleeInfo = FuncInfos[CalleeFn]; - int &minAppliedArgs = CalleeInfo.PartialAppliers[F]; - int numArgs = (int)PAI->getNumArguments(); - if (minAppliedArgs == 0 || numArgs < minAppliedArgs) { - minAppliedArgs = numArgs; + // Make sure that we are in sync with looking for partial apply callees. + if (auto *pai = dyn_cast(&i)) { + if (pai->getCalleeFunction() && !visitedCallSites.count(&i)) { + callSitesThatMustBeVisited.insert(pai); } continue; } - } - } +#endif + } + } + +#ifndef NDEBUG + if (callSitesThatMustBeVisited.empty()) + return; + llvm::errs() << "Found unhandled call sites!\n"; + while (callSitesThatMustBeVisited.size()) { + auto *i = callSitesThatMustBeVisited.pop_back_val(); + llvm::errs() << "Inst: " << *i; + } + assert(false && "Unhandled call site?!"); +#endif } -void CallerAnalysis::invalidateExistingCalleeRelation(SILFunction *F) { - FunctionInfo &CallerInfo = FuncInfos[F]; - for (auto Callee : CallerInfo.Callees) { - FunctionInfo &CalleeInfo = FuncInfos[Callee]; - CalleeInfo.Callers.erase(F); - CalleeInfo.PartialAppliers.erase(F); +void CallerAnalysis::invalidateExistingCalleeRelation(SILFunction *caller) { + // Look up the callees that our caller refers to and invalidate any + // values that point back at the caller. + FunctionInfo &callerInfo = getOrInsertCallerInfo(caller); + + while (callerInfo.calleeStates.size()) { + auto *callee = callerInfo.calleeStates.pop_back_val(); + FunctionInfo &calleeInfo = getOrInsertCallerInfo(callee); + assert(calleeInfo.callerStates.count(caller) && + "Referenced callee is not fully/partially applied in the caller?!"); + + // Then remove the caller from this specific callee's info struct + // and to be conservative mark the callee as potentially having an + // escaping use that we do not understand. + calleeInfo.callerStates.erase(caller); + } +} + +//===----------------------------------------------------------------------===// +// CallerAnalysis YAML Dump +//===----------------------------------------------------------------------===// + +namespace { + +using llvm::yaml::IO; +using llvm::yaml::MappingTraits; +using llvm::yaml::Output; +using llvm::yaml::ScalarEnumerationTraits; +using llvm::yaml::SequenceTraits; + +/// A special struct that marshals call graph state into a form that +/// is easy for llvm's yaml i/o to dump. Its structure is meant to +/// correspond to how the data should be shown by the printer, so +/// naturally it is slightly redundant. +struct YAMLCallGraphNode { + StringRef calleeName; + bool hasCaller; + unsigned minPartialAppliedArgs; + bool hasOnlyCompleteDirectCallerSets; + bool hasAllCallers; + std::vector partialAppliers; + std::vector fullAppliers; + + YAMLCallGraphNode() = delete; + ~YAMLCallGraphNode() = default; + + /// Deleted copy constructor. This is a move only data structure. + YAMLCallGraphNode(const YAMLCallGraphNode &) = delete; + YAMLCallGraphNode(YAMLCallGraphNode &&) = default; + YAMLCallGraphNode &operator=(const YAMLCallGraphNode &) = delete; + YAMLCallGraphNode &operator=(YAMLCallGraphNode &&) = default; + + YAMLCallGraphNode(StringRef calleeName, bool hasCaller, + unsigned minPartialAppliedArgs, + bool hasOnlyCompleteDirectCallerSets, bool hasAllCallers, + std::vector &&partialAppliers, + std::vector &&fullAppliers) + : calleeName(calleeName), hasCaller(hasCaller), + minPartialAppliedArgs(minPartialAppliedArgs), + hasOnlyCompleteDirectCallerSets(hasOnlyCompleteDirectCallerSets), + hasAllCallers(hasAllCallers), + partialAppliers(std::move(partialAppliers)), + fullAppliers(std::move(fullAppliers)) {} +}; + +} // end anonymous namespace + +namespace llvm { +namespace yaml { + +template <> struct MappingTraits { + static void mapping(IO &io, YAMLCallGraphNode &func) { + io.mapRequired("calleeName", func.calleeName); + io.mapRequired("hasCaller", func.hasCaller); + io.mapRequired("minPartialAppliedArgs", func.minPartialAppliedArgs); + io.mapRequired("hasOnlyCompleteDirectCallerSets", + func.hasOnlyCompleteDirectCallerSets); + io.mapRequired("hasAllCallers", func.hasAllCallers); + io.mapRequired("partialAppliers", func.partialAppliers); + io.mapRequired("fullAppliers", func.fullAppliers); + } +}; + +} // namespace yaml +} // namespace llvm + +void CallerAnalysis::dump() const { print(llvm::errs()); } + +void CallerAnalysis::print(const char *filePath) const { + using namespace llvm::sys; + std::error_code error; + llvm::raw_fd_ostream fileOutputStream(filePath, error, fs::F_Text); + if (error) { + llvm::errs() << "Failed to open path \"" << filePath << "\" for writing.!"; + llvm_unreachable("default error handler"); + } + print(fileOutputStream); +} + +void CallerAnalysis::print(llvm::raw_ostream &os) const { + llvm::yaml::Output yout(os); + + // NOTE: We purposely do not iterate over our internal state here to ensure + // that we dump for all functions and that we dump the state we have stored + // with the functions in module order. + for (auto &f : mod) { + const auto &fi = getCallerInfo(&f); + + std::vector partialAppliers; + std::vector fullAppliers; + for (auto &apply : fi.getAllReferencingCallers()) { + if (apply.second.hasFullApply) { + fullAppliers.push_back(apply.first->getName()); + } + if (apply.second.numPartialAppliedArguments.hasValue()) { + partialAppliers.push_back(apply.first->getName()); + } + } + + YAMLCallGraphNode node( + f.getName(), fi.hasCaller(), fi.getMinPartialAppliedArgs(), + fi.hasOnlyCompleteDirectCallerSets(), fi.hasAllCallers(), + std::move(partialAppliers), std::move(fullAppliers)); + yout << node; } } //===----------------------------------------------------------------------===// // Main Entry Point //===----------------------------------------------------------------------===// -SILAnalysis *swift::createCallerAnalysis(SILModule *M) { - return new CallerAnalysis(M); + +SILAnalysis *swift::createCallerAnalysis(SILModule *mod) { + return new CallerAnalysis(mod); } diff --git a/lib/SILOptimizer/FunctionSignatureTransforms/ArgumentExplosionTransform.cpp b/lib/SILOptimizer/FunctionSignatureTransforms/ArgumentExplosionTransform.cpp index 21a3ca467e497..572e9c695caa3 100644 --- a/lib/SILOptimizer/FunctionSignatureTransforms/ArgumentExplosionTransform.cpp +++ b/lib/SILOptimizer/FunctionSignatureTransforms/ArgumentExplosionTransform.cpp @@ -9,6 +9,15 @@ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// +/// +/// \file +/// +/// This file contains an implementation of the partial dead argument +/// elimination optimization. We do this to attempt to remove non-trivial +/// arguments of callees to eliminate lifetime constraints of a large argument +/// on values in the caller. +/// +//===----------------------------------------------------------------------===// #define DEBUG_TYPE "fso-argument-explosion-transform" #include "FunctionSignatureOpts.h" @@ -25,12 +34,70 @@ static llvm::cl::opt FSODisableArgExplosion( // Utility //===----------------------------------------------------------------------===// +static bool +shouldExplodeTrivial(FunctionSignatureTransformDescriptor &transformDesc, + ArgumentDescriptor &argDesc, SILType ty, + unsigned maxExplosionSize) { + // Only blow up trivial parameters if we will not form a thunk... + if (!transformDesc.hasOnlyDirectCallers) { + return false; + } + + // ... and reduce the size of the argument by a reasonable amount. + unsigned explosionSize = argDesc.ProjTree.getLiveLeafCount(); + return explosionSize <= maxExplosionSize; +} + /// Return true if it's both legal and a good idea to explode this argument. -static bool shouldExplode(ArgumentDescriptor &argDesc, - ConsumedArgToEpilogueReleaseMatcher &ERM) { - // We cannot optimize the argument. - if (!argDesc.canOptimizeLiveArg()) +/// +/// Our main interest here is to expose more opportunities for ARC. This means +/// that we are not interested in exploding (and partially DCEing) structs in +/// the following cases: +/// +/// 1. Completely dead arguments. This is handled by dead argument elimination. +/// +/// 2. Values that are completely trivial. By splitting these up we create +/// more register pressure during argument marshalling and do not really add +/// any advantage. We only eliminate them +/// +/// 3. Structs with many live leaf nodes. Our heuristic is 1-3 live leaf +/// nodes. Otherwise again we run into register pressure/spilling issues. +/// +/// One important thing to note here is that the last two cases could be dealt +/// with more effectively by having FSO consider the number of arguments +/// created in total instead of not reasoning about this and hoping the +/// heuristic works. +/// +/// With that in mind, we want to perform argument exploding in the following +/// cases (assuming our live leaf restriction): +/// +/// 1. Non-trivial structs that only have live trivial parts. This at the SIL +/// level eliminates ARC restrictions on the caller by the callee. +/// +/// 2. Splitting non-trivial structs that have multiple non-trivial live leaf +/// nodes. This is useful because it enables the low level ARC optimizer to +/// consider the arguments as having different RC identities and thus pair +/// retains/releases in an easier way. +/// +/// What is important to notice here is that we do not want to explode +/// arguments if. +static bool +shouldExplode(FunctionSignatureTransformDescriptor &transformDesc, + ArgumentDescriptor &argDesc, + ConsumedArgToEpilogueReleaseMatcher &epilogueReleaseMatcher) { + // No passes can optimize this argument, so just bail. + if (!argDesc.canOptimizeLiveArg()) { return false; + } + + // We do not explode parameters that are completely dead. This is so we can + // rely on normal dead argument elimination to eliminate such parameters. + // + // We compute this early since it is already computed at this point. + unsigned naiveExplosionSize = argDesc.ProjTree.getLiveLeafCount(); + if (naiveExplosionSize == 0) { + return false; + } // See if the projection tree consists of potentially multiple levels of // structs containing one field. In such a case, there is no point in @@ -38,30 +105,105 @@ static bool shouldExplode(ArgumentDescriptor &argDesc, // // Also, in case of a type can not be exploded, e.g an enum, we treat it // as a singleton. - if (argDesc.ProjTree.isSingleton()) + if (argDesc.ProjTree.isSingleton()) { return false; + } + // Ok, we have a case that we may be able to handle. First make sure that the + // current global size expansion heuristic does not ban us from expanding this + // type. auto *arg = argDesc.Arg; - if (!shouldExpand(arg->getModule(), arg->getType().getObjectType())) { + auto &module = arg->getModule(); + auto ty = arg->getType().getObjectType(); + if (!shouldExpand(module, ty)) { return false; } - // If this argument is @owned and we can not find all the releases for it - // try to explode it, maybe we can find some of the releases and O2G some - // of its components. + // If we have a singular argument, be more aggressive about our max explosion + // size. If we were unable to expand the value we know that it will be + // exploded so use UINT_MAX. + unsigned maxExplosionSize = 3; + if (transformDesc.ArgumentDescList.size() == 1) { + maxExplosionSize = UINT_MAX; + } + + // Ok, this is something that globally we are not forbidden from + // expanded. First check if our type is completely trivial. We never want to + // explode arguments that are trivial so return false. See comment above. + if (ty.isTrivial(module)) { + return shouldExplodeTrivial(transformDesc, argDesc, ty, maxExplosionSize); + } + + // Ok, we think that this /may/ be profitable to optimize. Grab our leaf node + // types. We already know that we have a strictily non-trivial type. If by + // performing partial DCE we will eliminate a non-trivial argument, we want to + // eliminate that argument to eliminate an ARC lifetime restriction in our + // caller scope. + llvm::SmallVector allTypes; + argDesc.ProjTree.getAllLeafTypes(allTypes); + llvm::SmallVector liveNodes; + argDesc.ProjTree.getLiveLeafNodes(liveNodes); + + unsigned numInputNonTrivialLeafNodes = + llvm::count_if(allTypes, [&](SILType t) { return !t.isTrivial(module); }); + unsigned numNonTrivialLiveLeafNodes = + llvm::count_if(liveNodes, [&](const ProjectionTreeNode *n) { + return n->getType().isTrivial(module); + }); + + // If we will reduce the number of values without adding too many arguments... + if (naiveExplosionSize <= maxExplosionSize) { + // ... and we know that we will not introduce a thunk, be aggressive. + if (transformDesc.hasOnlyDirectCallers) { + return true; + } + + // Otherwise, only explode if we will reduce the number of non-trivial leaf + // types. + if (numNonTrivialLiveLeafNodes < numInputNonTrivialLeafNodes) { + return true; + } + } + + // Ok, this is an argument with more than 3 live leaf nodes. See if after + // performing o2g we will be able to reduce our number of non-trivial nodes. // - // This is a potentially a very profitable optimization. Ignore other - // heuristics. - if (arg->hasConvention(SILArgumentConvention::Direct_Owned) && - ERM.hasSomeReleasesForArgument(arg)) - return true; + // *NOTE* This does not create a phase ordering issue since we re-run the + // pipeline after we run FSO a first time. + if (numNonTrivialLiveLeafNodes > 1 && + argDesc.hasConvention(SILArgumentConvention::Direct_Owned)) { + if (auto releases = + epilogueReleaseMatcher.getPartiallyPostDomReleaseSet(arg)) { + llvm::SmallPtrSet users; + for (auto *i : *releases) + users.insert(i); - unsigned explosionSize = argDesc.ProjTree.getLiveLeafCount(); - return explosionSize >= 1 && explosionSize <= 3; + // *NOTE* This will still include trivial parameters. We only + // will delete non-trivial parameters. + unsigned newExplosionSize = naiveExplosionSize; + for (auto *node : liveNodes) { + // If all of our users are epilogue releases, reduce the explosion size. + if (llvm::all_of(node->getNonProjUsers(), [&](Operand *op) { + return users.count(op->getUser()); + })) { + --newExplosionSize; + } + } + + // See if newExplosionSize is less than our max allowed explosion size. If + // we reduce this value then we know we will reduce the number of + // non-trivial nodes. We just don't want to expand the number of arguments + // too much. + return newExplosionSize <= maxExplosionSize; + } + } + + // Otherwise, we are not reducing the number of live non-trivial values + return false; } //===----------------------------------------------------------------------===// -// Implementation +// Top Level Implementation //===----------------------------------------------------------------------===// bool FunctionSignatureTransform::ArgumentExplosionAnalyzeParameters() { @@ -95,7 +237,7 @@ bool FunctionSignatureTransform::ArgumentExplosionAnalyzeParameters() { continue; A.ProjTree.computeUsesAndLiveness(A.Arg); - A.Explode = shouldExplode(A, ArgToReturnReleaseMap); + A.Explode = shouldExplode(TransformDescriptor, A, ArgToReturnReleaseMap); // Modified self argument. if (A.Explode && Args[i]->isSelf()) { diff --git a/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.cpp b/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.cpp index 69bb6dd3561aa..bdd9d28cfbca5 100644 --- a/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.cpp +++ b/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.cpp @@ -64,11 +64,17 @@ using ArgumentIndexMap = llvm::SmallDenseMap; //===----------------------------------------------------------------------===// /// Set to true to enable the support for partial specialization. -llvm::cl::opt +static llvm::cl::opt FSOEnableGenerics("sil-fso-enable-generics", llvm::cl::init(true), llvm::cl::desc("Support function signature optimization " "of generic functions")); +static llvm::cl::opt + FSOOptimizeIfNotCalled("sil-fso-optimize-if-not-called", + llvm::cl::init(false), + llvm::cl::desc("Optimize even if a function isn't " + "called. For testing only!")); + static bool isSpecializableRepresentation(SILFunctionTypeRepresentation Rep, bool OptForPartialApply) { switch (Rep) { @@ -613,9 +619,15 @@ void FunctionSignatureTransform::createFunctionSignatureOptimizedFunction() { // Run the optimization. bool FunctionSignatureTransform::run(bool hasCaller) { - bool Changed = false; + // We use a reference here on purpose so our transformations can know if we + // are going to make a thunk and thus should just optimize. + bool &Changed = TransformDescriptor.Changed; + bool hasOnlyDirectCallers = TransformDescriptor.hasOnlyDirectCallers; SILFunction *F = TransformDescriptor.OriginalFunction; + // If we are asked to assume a caller for testing purposes, set the flag. + hasCaller |= FSOOptimizeIfNotCalled; + if (!hasCaller && canBeCalledIndirectly(F->getRepresentation())) { DEBUG(llvm::dbgs() << " function has no caller -> abort\n"); return false; @@ -631,7 +643,7 @@ bool FunctionSignatureTransform::run(bool hasCaller) { // Run DeadArgument elimination transformation. We only specialize // if this function has a caller inside the current module or we have // already created a thunk. - if ((hasCaller || Changed) && DeadArgumentAnalyzeParameters()) { + if ((hasCaller || Changed || hasOnlyDirectCallers) && DeadArgumentAnalyzeParameters()) { Changed = true; DEBUG(llvm::dbgs() << " remove dead arguments\n"); DeadArgumentTransformFunction(); @@ -649,7 +661,7 @@ bool FunctionSignatureTransform::run(bool hasCaller) { // In order to not miss any opportunity, we send the optimized function // to the passmanager to optimize any opportunities exposed by argument // explosion. - if ((hasCaller || Changed) && ArgumentExplosionAnalyzeParameters()) { + if ((hasCaller || Changed || hasOnlyDirectCallers) && ArgumentExplosionAnalyzeParameters()) { Changed = true; } @@ -755,7 +767,7 @@ class FunctionSignatureOpts : public SILFunctionTransform { return; } - CallerAnalysis *CA = PM->getAnalysis(); + const CallerAnalysis *CA = PM->getAnalysis(); const CallerAnalysis::FunctionInfo &FuncInfo = CA->getCallerInfo(F); // Check the signature of F to make sure that it is a function that we @@ -799,7 +811,8 @@ class FunctionSignatureOpts : public SILFunctionTransform { // Owned to guaranteed optimization. FunctionSignatureTransform FST(F, RCIA, EA, Mangler, AIM, - ArgumentDescList, ResultDescList); + ArgumentDescList, ResultDescList, + FuncInfo.hasAllCallers()); bool Changed = false; if (OptForPartialApply) { diff --git a/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.h b/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.h index fb618a0f1d528..3401fa4289c35 100644 --- a/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.h +++ b/lib/SILOptimizer/FunctionSignatureTransforms/FunctionSignatureOpts.h @@ -110,9 +110,13 @@ struct ArgumentDescriptor { return Arg->hasConvention(P); } + /// Returns true if all function signature opt passes are able to process + /// this. bool canOptimizeLiveArg() const { - if (Arg->getType().isObject()) + if (Arg->getType().isObject()) { return true; + } + // @in arguments of generic types can be processed. if (Arg->getType().hasArchetype() && Arg->getType().isAddress() && @@ -193,6 +197,13 @@ struct FunctionSignatureTransformDescriptor { /// will use during our optimization. MutableArrayRef ResultDescList; + /// Are we going to make a change to this function? + bool Changed; + + /// Does this function only have direct callers. In such a case we know that + /// all thunks we create will be eliminated so we can be more aggressive. + bool hasOnlyDirectCallers; + /// Return a function name based on the current state of ArgumentDescList and /// ResultDescList. /// @@ -288,9 +299,11 @@ class FunctionSignatureTransform { Mangle::FunctionSignatureSpecializationMangler &Mangler, llvm::SmallDenseMap &AIM, llvm::SmallVector &ADL, - llvm::SmallVector &RDL) - : TransformDescriptor{F, nullptr, AIM, false, ADL, RDL}, RCIA(RCIA), - EA(EA) {} + llvm::SmallVector &RDL, + bool hasOnlyDirectCallers) + : TransformDescriptor{F, nullptr, AIM, false, ADL, RDL, false, + hasOnlyDirectCallers}, + RCIA(RCIA), EA(EA) {} /// Return the optimized function. SILFunction *getOptimizedFunction() { diff --git a/lib/SILOptimizer/UtilityPasses/CallerAnalysisPrinter.cpp b/lib/SILOptimizer/UtilityPasses/CallerAnalysisPrinter.cpp index e150b4f3f2d2e..dc9ae67397035 100644 --- a/lib/SILOptimizer/UtilityPasses/CallerAnalysisPrinter.cpp +++ b/lib/SILOptimizer/UtilityPasses/CallerAnalysisPrinter.cpp @@ -14,10 +14,11 @@ // //===----------------------------------------------------------------------===// -#include "swift/SILOptimizer/Analysis/CallerAnalysis.h" #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILModule.h" +#include "swift/SILOptimizer/Analysis/CallerAnalysis.h" #include "swift/SILOptimizer/PassManager/Transforms.h" +#include "llvm/Support/YAMLTraits.h" #include "llvm/Support/raw_ostream.h" using namespace swift; @@ -26,19 +27,15 @@ using namespace swift; namespace { +/// A pass that dumps the caller analysis state in yaml form. Intended to allow +/// for visualizing of the caller analysis via external data visualization and +/// analysis programs. class CallerAnalysisPrinterPass : public SILModuleTransform { /// The entry point to the transformation. void run() override { auto *CA = getAnalysis(); - for (auto &F : *getModule()) { - const CallerAnalysis::FunctionInfo &FI = CA->getCallerInfo(&F); - const char *hasCaller = FI.hasCaller() ? "true" : "false"; - llvm::outs() << "Function " << F.getName() << " has caller: " - << hasCaller << ", partial applied args = " - << FI.getMinPartialAppliedArgs() << "\n"; - } + CA->print(llvm::outs()); } - }; } // end anonymous namespace diff --git a/test/SILOptimizer/caller_analysis.sil b/test/SILOptimizer/caller_analysis.sil new file mode 100644 index 0000000000000..d5fc85122370a --- /dev/null +++ b/test/SILOptimizer/caller_analysis.sil @@ -0,0 +1,519 @@ +// RUN: %target-sil-opt -assume-parsing-unqualified-ownership-sil -enable-sil-verify-all %s -caller-analysis-printer -o /dev/null | %FileCheck --check-prefix=CHECK %s + +sil_stage canonical + +import Builtin + +// CHECK-LABEL: calleeName: dead_func +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil hidden @dead_func : $@convention(thin) () -> () { + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: call_top +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil hidden @call_top : $@convention(thin) () -> () { +bb0: + %0 = function_ref @call_middle : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: call_middle +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - call_top +// CHECK-NEXT: ... +sil hidden @call_middle : $@convention(thin) () -> () { +bb0: + %0 = function_ref @call_bottom : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: call_bottom +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - call_middle +// CHECK-NEXT: ... +sil hidden @call_bottom : $@convention(thin) () -> () { +bb0: + %0 = tuple () + return %0 : $() +} + +// CHECK-LABEL: calleeName: self_recursive_func +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - self_recursive_func +// CHECK-NEXT: ... +sil hidden @self_recursive_func : $@convention(thin) () -> () { +bb0: + %0 = function_ref @self_recursive_func : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: mutual_recursive_func1 +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - mutual_recursive_func2 +// CHECK-NEXT: ... +sil hidden @mutual_recursive_func1 : $@convention(thin) () -> () { +bb0: + %0 = function_ref @mutual_recursive_func2 : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: mutual_recursive_func2 +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - mutual_recursive_func1 +// CHECK-NEXT: ... +sil hidden @mutual_recursive_func2 : $@convention(thin) () -> () { +bb0: + %0 = function_ref @mutual_recursive_func1 : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: multi_called +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - multi_calles +// CHECK-NEXT: ... +sil hidden @multi_called : $@convention(thin) () -> () { +bb0: + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: multi_calles +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil hidden @multi_calles : $@convention(thin) () -> () { +bb0: + %0 = function_ref @multi_called : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + cond_br undef, bb1, bb2 +bb1: + %2 = apply %0() : $@convention(thin) () -> () + br bb3 +bb2: + %3 = apply %0() : $@convention(thin) () -> () + br bb3 +bb3: + %4 = tuple () + return %4 : $() +} + +// CHECK-LABEL: calleeName: multi_callers +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - multi_caller1 +// CHECK-NEXT: - multi_caller2 +// CHECK-NEXT: ... +sil hidden @multi_callers : $@convention(thin) () -> () { +bb0: + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: multi_caller1 +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil hidden @multi_caller1 : $@convention(thin) () -> () { +bb0: + %0 = function_ref @multi_callers : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + %2 = tuple () + return %2 : $() +} + +// CHECK-LABEL: calleeName: multi_caller2 +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil hidden @multi_caller2 : $@convention(thin) () -> () { +bb0: + %0 = function_ref @multi_callers : $@convention(thin) () -> () + %1 = apply %0() : $@convention(thin) () -> () + %2 = tuple () + return %2 : $() +} + +// This doesn't have all the direct caller sets since we return the +// partial_apply. +// +// CHECK-LABEL: calleeName: closure1 +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 1 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: false +// CHECK-NEXT: hasAllCallers: false +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: - partial_apply_one_arg +// CHECK-NEXT: - partial_apply_two_args1 +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil @closure1 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + return %0 : $Builtin.Int32 +} + +// CHECK-LABEL: calleeName: closure2 +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 2 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: false +// CHECK-NEXT: hasAllCallers: false +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: - partial_apply_two_args2 +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil @closure2 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + return %0 : $Builtin.Int32 +} + +// CHECK-LABEL: calleeName: partial_apply_one_arg +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil @partial_apply_one_arg : $@convention(thin) (Builtin.Int32) -> @owned @callee_owned (Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @closure1 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply %1(%0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + return %2 : $@callee_owned (Builtin.Int32) -> Builtin.Int32 +} + +// CHECK-LABEL: calleeName: partial_apply_two_args1 +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil @partial_apply_two_args1 : $@convention(thin) (Builtin.Int32) -> @owned @callee_owned () -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @closure1 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + return %2 : $@callee_owned () -> Builtin.Int32 +} + +// CHECK-LABEL: calleeName: partial_apply_two_args2 +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil @partial_apply_two_args2 : $@convention(thin) (Builtin.Int32) -> @owned @callee_owned () -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @closure2 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + return %2 : $@callee_owned () -> Builtin.Int32 +} + +// CHECK-LABEL: calleeName: called_closure +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 2 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: ... +sil @called_closure : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + return %0 : $Builtin.Int32 +} + +sil @partial_apply_that_is_applied : $@convention(thin) (Builtin.Int32) -> () { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @called_closure : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %3 = apply %2() : $@callee_owned () -> Builtin.Int32 + %9999 = tuple() + return %9999 : $() +} + +// We should ignore destroys in the fullness of time. Once we handle that +// correctly, we should have the complete caller set here. +// +// CHECK-LABEL: calleeName: called_closure_then_destroy +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 2 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: ... +sil @called_closure_then_destroy : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + return %0 : $Builtin.Int32 +} + +sil @partial_apply_that_is_applied_and_destroyed : $@convention(thin) (Builtin.Int32) -> () { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @called_closure_then_destroy : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %3 = apply %2() : $@callee_owned () -> Builtin.Int32 + strong_release %2 : $@callee_owned () -> Builtin.Int32 + %9999 = tuple() + return %9999 : $() +} + +// CHECK-LABEL: calleeName: called_escaping_closure +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 2 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: false +// CHECK-NEXT: hasAllCallers: false +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: ... +sil @called_escaping_closure : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + return %0 : $Builtin.Int32 +} + +sil @partial_apply_that_is_applied_and_escapes : $@convention(thin) (Builtin.Int32) -> @owned @callee_guaranteed () -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @called_escaping_closure : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply [callee_guaranteed] %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %3 = apply %2() : $@callee_guaranteed () -> Builtin.Int32 + return %2 : $@callee_guaranteed () -> Builtin.Int32 +} + +// Make sure that we ignore strong_retain. +// +// CHECK-LABEL: calleeName: called_closure_then_copy_destroy +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 2 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied +// CHECK-NEXT: ... +sil @called_closure_then_copy_destroy : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + return %0 : $Builtin.Int32 +} + +sil @partial_apply_that_is_applied_and_copy_destroy : $@convention(thin) (Builtin.Int32) -> () { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @called_closure_then_copy_destroy : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + strong_retain %2 : $@callee_owned () -> Builtin.Int32 + retain_value %2 : $@callee_owned () -> Builtin.Int32 + %3 = apply %2() : $@callee_owned () -> Builtin.Int32 + release_value %2 : $@callee_owned () -> Builtin.Int32 + strong_release %2 : $@callee_owned () -> Builtin.Int32 + %9999 = tuple() + return %9999 : $() +} + +// We should ignore escapes of non-escaping partial applies in the fullness of +// time. Once we handle that correctly, we should have the complete caller set +// here as well as an application. This would require us to have a flow +// sensitive callgraph analysis. +// +// CHECK-LABEL: calleeName: noescape_callee +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 2 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: false +// CHECK-NEXT: hasAllCallers: false +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied_and_passed_noescape +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil @noescape_callee : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { +bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): + return %0 : $Builtin.Int32 +} + +// CHECK-LABEL: calleeName: noescape_caller +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: true +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - partial_apply_that_is_applied_and_passed_noescape +// CHECK-NEXT: - thin_to_thick_is_applied_and_passed_noescape +// CHECK-NEXT: ... +sil @noescape_caller : $@convention(thin) (@noescape @callee_owned () -> Builtin.Int32) -> () { +bb0(%0 : $@noescape @callee_owned () -> Builtin.Int32): + %1 = apply %0() : $@noescape @callee_owned () -> Builtin.Int32 + %9999 = tuple() + return %9999 : $() +} + +sil @partial_apply_that_is_applied_and_passed_noescape : $@convention(thin) (Builtin.Int32) -> () { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @noescape_callee : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 + %3 = convert_escape_to_noescape %2 : $@callee_owned () -> Builtin.Int32 to $@noescape @callee_owned () -> Builtin.Int32 + %4 = function_ref @noescape_caller : $@convention(thin) (@noescape @callee_owned () -> Builtin.Int32) -> () + apply %4(%3) : $@convention(thin) (@noescape @callee_owned () -> Builtin.Int32) -> () + %9999 = tuple() + return %9999 : $() +} + +// CHECK-LABEL: calleeName: noescape_callee2 +// CHECK-NEXT: hasCaller: false +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: false +// CHECK-NEXT: hasAllCallers: false +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: ... +sil @noescape_callee2 : $@convention(thin) () -> Builtin.Int32 { +bb0: + return undef : $Builtin.Int32 +} + +sil @thin_to_thick_is_applied_and_passed_noescape : $@convention(thin) (Builtin.Int32) -> () { +bb0(%0 : $Builtin.Int32): + %1 = function_ref @noescape_callee2 : $@convention(thin) () -> Builtin.Int32 + %2 = thin_to_thick_function %1 : $@convention(thin) () -> Builtin.Int32 to $@callee_owned () -> Builtin.Int32 + %3 = convert_escape_to_noescape %2 : $@callee_owned () -> Builtin.Int32 to $@noescape @callee_owned () -> Builtin.Int32 + %4 = function_ref @noescape_caller : $@convention(thin) (@noescape @callee_owned () -> Builtin.Int32) -> () + apply %4(%3) : $@convention(thin) (@noescape @callee_owned () -> Builtin.Int32) -> () + %9999 = tuple() + return %9999 : $() +} + +class Klass { + @_silgen_name("called_method") + func doSomething() {} + + @_silgen_name("final_called_method") + final func finalDoSomething() {} + +} + +// Check that we know that we have a complete direct caller set, but that we do +// not have all callers since based on our trivial heuristic today, we always +// assume methods could be called indirectly. +// +// CHECK-LABEL: calleeName: called_method +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: false +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - apply_called_method +// CHECK-NEXT: ... +sil @called_method : $@convention(method) (@guaranteed Klass) -> () { +bb0(%0 : $Klass): + %9999 = tuple() + return %9999 : $() +} + +sil @apply_called_method : $@convention(thin) (@guaranteed Klass) -> () { +bb0(%0 : $Klass): + %1 = function_ref @called_method : $@convention(method) (@guaranteed Klass) -> () + apply %1(%0) : $@convention(method) (@guaranteed Klass) -> () + %9999 = tuple() + return %9999 : $() +} + +// Once we understand final, in the fullness of time we should find all callers +// in this example. Today we do not though. +// +// CHECK-LABEL: calleeName: final_called_method +// CHECK-NEXT: hasCaller: true +// CHECK-NEXT: minPartialAppliedArgs: 0 +// CHECK-NEXT: hasOnlyCompleteDirectCallerSets: true +// CHECK-NEXT: hasAllCallers: false +// CHECK-NEXT: partialAppliers: +// CHECK-NEXT: fullAppliers: +// CHECK-NEXT: - final_apply_called_method +// CHECK-NEXT: ... +sil @final_called_method : $@convention(method) (@guaranteed Klass) -> () { +bb0(%0 : $Klass): + %9999 = tuple() + return %9999 : $() +} + +sil @final_apply_called_method : $@convention(thin) (@guaranteed Klass) -> () { +bb0(%0 : $Klass): + %1 = function_ref @final_called_method : $@convention(method) (@guaranteed Klass) -> () + apply %1(%0) : $@convention(method) (@guaranteed Klass) -> () + %9999 = tuple() + return %9999 : $() +} diff --git a/test/SILOptimizer/caller_analysis_printer.sil b/test/SILOptimizer/caller_analysis_printer.sil deleted file mode 100644 index 3d43a0ce1ea66..0000000000000 --- a/test/SILOptimizer/caller_analysis_printer.sil +++ /dev/null @@ -1,149 +0,0 @@ -// RUN: %target-sil-opt -assume-parsing-unqualified-ownership-sil -enable-sil-verify-all %s -caller-analysis-printer -o /dev/null | %FileCheck --check-prefix=CHECK %s - -// CHECK: Function dead_func has caller: false, partial applied args = 0 -// CHECK: Function call_top has caller: false, partial applied args = 0 -// CHECK: Function call_middle has caller: true, partial applied args = 0 -// CHECK: Function call_bottom has caller: true, partial applied args = 0 -// CHECK: Function self_recursive_func has caller: true, partial applied args = 0 -// CHECK: Function mutual_recursive_func1 has caller: true, partial applied args = 0 -// CHECK: Function mutual_recursive_func2 has caller: true, partial applied args = 0 -// CHECK: Function multi_called has caller: true, partial applied args = 0 -// CHECK: Function multi_calles has caller: false, partial applied args = 0 -// CHECK: Function multi_callers has caller: true, partial applied args = 0 -// CHECK: Function multi_caller1 has caller: false, partial applied args = 0 -// CHECK: Function multi_caller2 has caller: false, partial applied args = 0 -// CHECK: Function closure1 has caller: false, partial applied args = 1 -// CHECK: Function closure2 has caller: false, partial applied args = 2 -// CHECK: Function partial_apply_one_arg has caller: false, partial applied args = 0 -// CHECK: Function partial_apply_two_args1 has caller: false, partial applied args = 0 -// CHECK: Function partial_apply_two_args2 has caller: false, partial applied args = 0 - -sil_stage canonical - -import Builtin - -sil hidden @dead_func : $@convention(thin) () -> () { - %2 = tuple () - return %2 : $() -} - -sil hidden @call_top : $@convention(thin) () -> () { -bb0: - %0 = function_ref @call_middle : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - %2 = tuple () - return %2 : $() -} - -sil hidden @call_middle : $@convention(thin) () -> () { -bb0: - %0 = function_ref @call_bottom : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - %2 = tuple () - return %2 : $() -} - -sil hidden @call_bottom : $@convention(thin) () -> () { -bb0: - %0 = tuple () - return %0 : $() -} - -sil hidden @self_recursive_func : $@convention(thin) () -> () { -bb0: - %0 = function_ref @self_recursive_func : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - %2 = tuple () - return %2 : $() -} - -sil hidden @mutual_recursive_func1 : $@convention(thin) () -> () { -bb0: - %0 = function_ref @mutual_recursive_func2 : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - %2 = tuple () - return %2 : $() -} - -sil hidden @mutual_recursive_func2 : $@convention(thin) () -> () { -bb0: - %0 = function_ref @mutual_recursive_func1 : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - %2 = tuple () - return %2 : $() -} - -sil hidden @multi_called : $@convention(thin) () -> () { -bb0: - %2 = tuple () - return %2 : $() -} - -sil hidden @multi_calles : $@convention(thin) () -> () { -bb0: - %0 = function_ref @multi_called : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - cond_br undef, bb1, bb2 -bb1: - %2 = apply %0() : $@convention(thin) () -> () - br bb3 -bb2: - %3 = apply %0() : $@convention(thin) () -> () - br bb3 -bb3: - %4 = tuple () - return %4 : $() -} - -sil hidden @multi_callers : $@convention(thin) () -> () { -bb0: - %2 = tuple () - return %2 : $() -} - -sil hidden @multi_caller1 : $@convention(thin) () -> () { -bb0: - %0 = function_ref @multi_callers : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - %2 = tuple () - return %2 : $() -} - -sil hidden @multi_caller2 : $@convention(thin) () -> () { -bb0: - %0 = function_ref @multi_callers : $@convention(thin) () -> () - %1 = apply %0() : $@convention(thin) () -> () - %2 = tuple () - return %2 : $() -} - -sil @closure1 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { -bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): - return %0 : $Builtin.Int32 -} - -sil @closure2 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 { -bb0(%0 : $Builtin.Int32, %1 : $Builtin.Int32): - return %0 : $Builtin.Int32 -} - -sil @partial_apply_one_arg : $@convention(thin) (Builtin.Int32) -> @owned @callee_owned (Builtin.Int32) -> Builtin.Int32 { -bb0(%0 : $Builtin.Int32): - %1 = function_ref @closure1 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 - %2 = partial_apply %1(%0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 - return %2 : $@callee_owned (Builtin.Int32) -> Builtin.Int32 -} - -sil @partial_apply_two_args1 : $@convention(thin) (Builtin.Int32) -> @owned @callee_owned () -> Builtin.Int32 { -bb0(%0 : $Builtin.Int32): - %1 = function_ref @closure1 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 - %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 - return %2 : $@callee_owned () -> Builtin.Int32 -} - -sil @partial_apply_two_args2 : $@convention(thin) (Builtin.Int32) -> @owned @callee_owned () -> Builtin.Int32 { -bb0(%0 : $Builtin.Int32): - %1 = function_ref @closure2 : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 - %2 = partial_apply %1(%0, %0) : $@convention(thin) (Builtin.Int32, Builtin.Int32) -> Builtin.Int32 - return %2 : $@callee_owned () -> Builtin.Int32 -} diff --git a/test/SILOptimizer/funcsig_explode_heuristic.sil b/test/SILOptimizer/funcsig_explode_heuristic.sil new file mode 100644 index 0000000000000..09d91c6f8523a --- /dev/null +++ b/test/SILOptimizer/funcsig_explode_heuristic.sil @@ -0,0 +1,207 @@ +// RUN: %target-sil-opt -enable-objc-interop -assume-parsing-unqualified-ownership-sil -enable-sil-verify-all -function-signature-opts -sil-fso-disable-dead-argument -sil-fso-disable-owned-to-guaranteed -enable-expand-all -sil-fso-optimize-if-not-called %s | %FileCheck %s + +// *NOTE* We turn off all other fso optimizations including dead arg so we can +// make sure that we are not exploding those. + +sil_stage canonical + +import Builtin + +////////////////// +// Declarations // +////////////////// + +struct BigTrivial { + var x1: Builtin.Int32 + var x2: Builtin.Int32 + var x3: Builtin.Int32 + var x4: Builtin.Int32 + var x5: Builtin.Int32 + var x6: Builtin.Int32 +} + +class Klass {} + +struct LargeNonTrivialStructOneNonTrivialField { + var k1: Klass + var k2: Klass + var x1: Builtin.Int32 + var x2: Builtin.Int32 + var x3: Builtin.Int32 + var x4: Builtin.Int32 +} + +sil @int_user : $@convention(thin) (Builtin.Int32) -> () +sil @consuming_user : $@convention(thin) (@owned Klass) -> () +sil @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + +/////////// +// Tests // +/////////// + +// We should never optimize this. If we did this would become a thunk, so we +// know that just be checking NFC we have proven no optimization has occured. +// +// CHECK-LABEL: sil @never_explode_trivial : $@convention(thin) (BigTrivial) -> () { +// CHECK: } // end sil function 'never_explode_trivial' +sil @never_explode_trivial : $@convention(thin) (BigTrivial) -> () { +bb0(%0 : $BigTrivial): + %1 = struct_extract %0 : $BigTrivial, #BigTrivial.x1 + %intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> () + apply %intfunc(%1) : $@convention(thin) (Builtin.Int32) -> () + %9999 = tuple() + return %9999 : $() +} + +// If a value is never used, do not touch it. We leave it for dead argument +// elimination. We have delibrately turned this off to test that behavior. +// +// CHECK-LABEL: sil @big_arg_with_no_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK-NOT: apply +// CHECK: } // end sil function 'big_arg_with_no_uses' +sil @big_arg_with_no_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %9999 = tuple() + return %9999 : $() +} + +// We are using a single non-trivial field of the struct. We should explode this +// so we eliminate the second non-trivial leaf. +// +// CHECK-LABEL: sil [thunk] [always_inline] @big_arg_with_one_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField): +// CHECK: [[FUNC:%.*]] = function_ref @$S31big_arg_with_one_nontrivial_useTf4x_n +// CHECK: [[FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 +// CHECK: apply [[FUNC]]([[FIELD]]) +// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use' +sil @big_arg_with_one_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + apply %2(%1) : $@convention(thin) (@guaranteed Klass) -> () + %9999 = tuple() + return %9999 : $() +} + +// We are using a single non-trivial field and a single trivial field. We are +// willing to blow this up. +// +// CHECK-LABEL: sil [thunk] [always_inline] @big_arg_with_one_nontrivial_use_one_trivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField): +// CHECK: [[FUNC:%.*]] = function_ref @$S032big_arg_with_one_nontrivial_use_d9_trivial_F0Tf4x_n : $@convention(thin) (@guaranteed Klass, Builtin.Int32) -> () +// CHECK: [[TRIVIAL_FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1 +// CHECK: [[NON_TRIVIAL_FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 +// CHECK: apply [[FUNC]]([[NON_TRIVIAL_FIELD]], [[TRIVIAL_FIELD]]) +// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use_one_trivial_use' +sil @big_arg_with_one_nontrivial_use_one_trivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1 + %3 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + apply %3(%1) : $@convention(thin) (@guaranteed Klass) -> () + %intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> () + apply %intfunc(%2) : $@convention(thin) (Builtin.Int32) -> () + %9999 = tuple() + return %9999 : $() +} + +// We can still explode this, since our limit is 3 values. +// +// CHECK-LABEL: sil [thunk] [always_inline] @big_arg_with_one_nontrivial_use_two_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField): +// CHECK: [[FUNC:%.*]] = function_ref @$S48big_arg_with_one_nontrivial_use_two_trivial_usesTf4x_n : $@convention(thin) +// CHECK: [[TRIVIAL_FIELD1:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x2 +// CHECK: [[TRIVIAL_FIELD2:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1 +// CHECK: [[NON_TRIVIAL_FIELD:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 +// CHECK: apply [[FUNC]]([[NON_TRIVIAL_FIELD]], [[TRIVIAL_FIELD2]], [[TRIVIAL_FIELD1]]) +sil @big_arg_with_one_nontrivial_use_two_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1 + %3 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x2 + %4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + apply %4(%1) : $@convention(thin) (@guaranteed Klass) -> () + %intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> () + apply %intfunc(%2) : $@convention(thin) (Builtin.Int32) -> () + apply %intfunc(%3) : $@convention(thin) (Builtin.Int32) -> () + %9999 = tuple() + return %9999 : $() +} + +// We do not blow up the struct here since we have 4 uses, not 3. +// +// CHECK-LABEL: sil @big_arg_with_one_nontrivial_use_three_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +sil @big_arg_with_one_nontrivial_use_three_trivial_uses : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x1 + %3 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x2 + %3a = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.x3 + %4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + apply %4(%1) : $@convention(thin) (@guaranteed Klass) -> () + %intfunc = function_ref @int_user : $@convention(thin) (Builtin.Int32) -> () + apply %intfunc(%2) : $@convention(thin) (Builtin.Int32) -> () + apply %intfunc(%3) : $@convention(thin) (Builtin.Int32) -> () + apply %intfunc(%3a) : $@convention(thin) (Builtin.Int32) -> () + %9999 = tuple() + return %9999 : $() +} + +// In this case, we shouldn't blow up the struct since we have not reduced the +// number of non-trivial leaf nodes used. +// +// CHECK-LABEL: sil @big_arg_with_two_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +sil @big_arg_with_two_nontrivial_use : $@convention(thin) (@guaranteed LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2 + %3 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + apply %3(%1) : $@convention(thin) (@guaranteed Klass) -> () + apply %3(%2) : $@convention(thin) (@guaranteed Klass) -> () + %9999 = tuple() + return %9999 : $() +} + +// If we have one non-trivial value that is live and only live because of a +// destroy, we can delete the argument after performing o2g. +// +// We are using a single non-trivial field of the struct. We should explode this +// so we eliminate the second non-trivial leaf. +// +// CHECK-LABEL: sil [thunk] [always_inline] @big_arg_with_one_nontrivial_use_o2g_other_dead : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK-NOT: release_value +// CHECK: apply +// CHECK-NOT: release_value +// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use_o2g_other_dead' +sil @big_arg_with_one_nontrivial_use_o2g_other_dead : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + release_value %1 : $Klass + %9999 = tuple() + return %9999 : $() +} + +// If we have two non-trivial values that are live and one is always dead and +// the other is kept alive due to a release, we can get rid of both since FSO +// reruns with o2g. Test here that we explode it appropriatel even though we +// aren't reducing the number of non-trivial uses. The +// funcsig_explode_heuristic_inline.sil test makes sure we in combination +// produce the appropriate SIL. +// +// We check that we can inline this correctly in the inline test. +// +// CHECK-LABEL: sil [thunk] [always_inline] @big_arg_with_one_nontrivial_use_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField): +// CHECK: [[FUNC:%.*]] = function_ref @$S35big_arg_with_one_nontrivial_use_o2gTf4x_n : $@convention(thin) (@owned Klass, @owned Klass) -> () +// CHECK: apply [[FUNC]]( +// CHECK: } // end sil function 'big_arg_with_one_nontrivial_use_o2g' +sil @big_arg_with_one_nontrivial_use_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2 + %3 = function_ref @consuming_user : $@convention(thin) (@owned Klass) -> () + apply %3(%2) : $@convention(thin) (@owned Klass) -> () + release_value %1 : $Klass + %9999 = tuple() + return %9999 : $() +} diff --git a/test/SILOptimizer/funcsig_explode_heuristic_inline.sil b/test/SILOptimizer/funcsig_explode_heuristic_inline.sil new file mode 100644 index 0000000000000..dfa09681aaac1 --- /dev/null +++ b/test/SILOptimizer/funcsig_explode_heuristic_inline.sil @@ -0,0 +1,93 @@ +// RUN: %target-sil-opt -enable-objc-interop -assume-parsing-unqualified-ownership-sil -enable-sil-verify-all -sil-inline-generics -inline -function-signature-opts -enable-expand-all %s | %FileCheck %s + +sil_stage canonical + +import Builtin + +////////////////// +// Declarations // +////////////////// + +class Klass {} + +struct LargeNonTrivialStructOneNonTrivialField { + var k1: Klass + var k2: Klass + var x1: Builtin.Int32 + var x2: Builtin.Int32 + var x3: Builtin.Int32 + var x4: Builtin.Int32 +} + +sil @consuming_user : $@convention(thin) (@owned Klass) -> () +sil @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + +// This test makes sure that if we have two non-trivial values that are live and +// one is always dead and the other is a value that we have a release for, we +// can get rid of the first argument and FSO the other. Test here that we +// explode it appropriately and do a partial o2g even though we aren't reducing +// the number of non-trivial uses. + +// CHECK-LABEL: sil @caller1 : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField): +// CHECK: [[FIELD1:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2 +// CHECK: [[FIELD2:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 +// CHECK: [[FUNC:%.*]] = function_ref @$S11partial_o2gTf4x_nTf4gn_n : $@convention(thin) (@guaranteed Klass, @owned Klass) -> () +// CHECK: apply [[FUNC]]([[FIELD2]], [[FIELD1]]) : $@convention(thin) (@guaranteed Klass, @owned Klass) -> () +// CHECK: release_value [[FIELD2]] +// CHECK: } // end sil function 'caller1' +sil @caller1 : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = function_ref @partial_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () + apply %1(%0) : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () + %9999 = tuple() + return %9999 : $() +} + +// If we have two non-trivial values that are live and one is always dead and +// the other is kept alive due to a release, we can get rid of both since FSO +// reruns with o2g. Test here that we explode it appropriately and do a partial +// o2g even though we aren't reducing the number of non-trivial uses. +sil [noinline] @partial_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2 + %3 = function_ref @consuming_user : $@convention(thin) (@owned Klass) -> () + apply %3(%2) : $@convention(thin) (@owned Klass) -> () + %4 = function_ref @guaranteed_user : $@convention(thin) (@guaranteed Klass) -> () + apply %4(%1) :$@convention(thin) (@guaranteed Klass) -> () + release_value %1 : $Klass + %9999 = tuple() + return %9999 : $() +} + +// CHECK-LABEL: sil @caller2 : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +// CHECK: bb0([[ARG:%.*]] : $LargeNonTrivialStructOneNonTrivialField): +// CHECK: [[FIELD1:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2 +// CHECK: [[FIELD2:%.*]] = struct_extract [[ARG]] : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 +// CHECK: [[FUNC:%.*]] = function_ref @$S23partiallydead_after_o2gTf4x_nTf4dn_n : $@convention(thin) (@owned Klass) -> () +// CHECK: apply [[FUNC]]([[FIELD1]]) : $@convention(thin) (@owned Klass) -> () +// CHECK: release_value [[FIELD2]] +// CHECK: } // end sil function 'caller2' +sil @caller2 : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = function_ref @partiallydead_after_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () + apply %1(%0) : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () + %9999 = tuple() + return %9999 : $() +} + +// If we have two non-trivial values that are live and one is always dead and +// the other is kept alive due to a release, we can get rid of both since FSO +// reruns with o2g. Test here that we explode it appropriately and do a partial +// o2g even though we aren't reducing the number of non-trivial uses. +sil [noinline] @partiallydead_after_o2g : $@convention(thin) (@owned LargeNonTrivialStructOneNonTrivialField) -> () { +bb0(%0 : $LargeNonTrivialStructOneNonTrivialField): + %1 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k1 + %2 = struct_extract %0 : $LargeNonTrivialStructOneNonTrivialField, #LargeNonTrivialStructOneNonTrivialField.k2 + %3 = function_ref @consuming_user : $@convention(thin) (@owned Klass) -> () + apply %3(%2) : $@convention(thin) (@owned Klass) -> () + release_value %1 : $Klass + %9999 = tuple() + return %9999 : $() +} diff --git a/test/SILOptimizer/functionsigopts.sil b/test/SILOptimizer/functionsigopts.sil index 4ef3bcb9a561d..a5e7b0a0b11f6 100644 --- a/test/SILOptimizer/functionsigopts.sil +++ b/test/SILOptimizer/functionsigopts.sil @@ -8,33 +8,33 @@ import Swift // Data Structures // ///////////////////// -class foo { +class Foo { var a: Int - deinit + deinit init() } -class bar { - var start: foo - var end: foo - deinit +class Bar { + var start: Foo + var end: Foo + deinit init() } -struct baz { - var start: foo - var end: foo +struct Baz { + var start: Foo + var end: Foo init() } -struct boo { - var tbaz = baz() +struct Boo { + var tBaz = Baz() var a = 0 init() } -struct lotsoffield { - var tbaz = baz() +struct LotsOfFields { + var tBaz = Baz() var a = 0 var b = 0 var c = 0 @@ -42,15 +42,15 @@ struct lotsoffield { init() } -struct goo { - var left : foo - var right : foo - var top : foo - var bottom : foo +struct Goo { + var left : Foo + var right : Foo + var top : Foo + var bottom : Foo } public protocol P { - func foo() -> Int64 + func Foo() -> Int64 } public protocol KlassFoo : class { @@ -80,11 +80,11 @@ bb0: } // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @argument_with_incomplete_epilogue_release -// CHECK: [[IN2:%.*]] = struct_extract [[IN1:%.*]] : $goo, #goo.top -// CHECK: function_ref @$S41argument_with_incomplete_epilogue_releaseTf4x_nTf4gn_n : $@convention(thin) (@guaranteed foo, @owned foo) -> () +// CHECK: [[IN2:%.*]] = struct_extract [[IN1:%.*]] : $Goo, #Goo.top +// CHECK: function_ref @$S41argument_with_incomplete_epilogue_releaseTf4x_nTf4gn_n : $@convention(thin) (@guaranteed Foo, @owned Foo) -> () // CHECK: release_value [[IN2]] -sil @argument_with_incomplete_epilogue_release : $@convention(thin) (@owned goo) -> () { -bb0(%0 : $goo): +sil @argument_with_incomplete_epilogue_release : $@convention(thin) (@owned Goo) -> () { +bb0(%0 : $Goo): // make inline costs = 2 %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -111,18 +111,18 @@ bb0(%0 : $goo): - %1 = struct_extract %0 : $goo, #goo.top - %2 = ref_element_addr %1 : $foo, #foo.a + %1 = struct_extract %0 : $Goo, #Goo.top + %2 = ref_element_addr %1 : $Foo, #Foo.a %3 = load %2 : $*Int %4 = function_ref @use_Int : $@convention(thin) (Int) -> () apply %4(%3) : $@convention(thin) (Int) -> () - %5 = struct_extract %0 : $goo, #goo.bottom - %6 = ref_element_addr %5 : $foo, #foo.a + %5 = struct_extract %0 : $Goo, #Goo.bottom + %6 = ref_element_addr %5 : $Foo, #Foo.a %7 = load %6 : $*Int apply %4(%7) : $@convention(thin) (Int) -> () - release_value %1 : $foo + release_value %1 : $Foo %8 = tuple () return %8 : $() } @@ -136,15 +136,15 @@ bb0(%0 : $goo): sil [serialized] @user : $@convention(thin) (Builtin.NativeObject) -> () sil [serialized] @create_object : $@convention(thin) () -> Builtin.NativeObject -// Make sure argument is exploded and the baz part is not passed in as argument, as its only use +// Make sure argument is exploded and the Baz part is not passed in as argument, as its only use // is a release. // // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @dead_argument_due_to_only_release_user // CHECK: [[IN:%.*]] = function_ref @$S38dead_argument_due_to_only_release_userTf4gX_n -// CHECK: [[IN2:%.*]] = struct_extract [[IN1:%.*]] : $boo, #boo.a +// CHECK: [[IN2:%.*]] = struct_extract [[IN1:%.*]] : $Boo, #Boo.a // CHECK: apply [[IN]]([[IN2]]) -sil @dead_argument_due_to_only_release_user : $@convention(thin) (@owned boo) -> (Int, Int) { -bb0(%0 : $boo): +sil @dead_argument_due_to_only_release_user : $@convention(thin) (@owned Boo) -> (Int, Int) { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -169,23 +169,23 @@ bb0(%0 : $boo): %c21 = builtin "assert_configuration"() : $Builtin.Int32 %c22 = builtin "assert_configuration"() : $Builtin.Int32 - %1 = struct_extract %0 : $boo, #boo.tbaz - %2 = struct_extract %0 : $boo, #boo.a - release_value %1 : $baz + %1 = struct_extract %0 : $Boo, #Boo.tBaz + %2 = struct_extract %0 : $Boo, #Boo.a + release_value %1 : $Baz %4 = tuple (%2 : $Int, %2 : $Int) return %4 : $(Int, Int) } -// Make sure argument is exploded and the baz part is not passed in as argument, as its only use +// Make sure argument is exploded and the Baz part is not passed in as argument, as its only use // is a release. // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @dead_argument_due_to_only_release_user_but__exploded // CHECK: [[FN1:%.*]] = function_ref @$S52dead_argument_due_to_only_release_user_but__explodedTf4gX_n -// CHECK: [[IN1:%.*]] = struct_extract %0 : $lotsoffield, #lotsoffield.c -// CHECK: [[IN2:%.*]] = struct_extract %0 : $lotsoffield, #lotsoffield.b -// CHECK: [[IN3:%.*]] = struct_extract %0 : $lotsoffield, #lotsoffield.a +// CHECK: [[IN1:%.*]] = struct_extract %0 : $LotsOfFields, #LotsOfFields.c +// CHECK: [[IN2:%.*]] = struct_extract %0 : $LotsOfFields, #LotsOfFields.b +// CHECK: [[IN3:%.*]] = struct_extract %0 : $LotsOfFields, #LotsOfFields.a // CHECK: apply [[FN1]]([[IN3]], [[IN2]], [[IN1]]) -sil @dead_argument_due_to_only_release_user_but__exploded : $@convention(thin) (@owned lotsoffield) -> (Int, Int, Int) { -bb0(%0 : $lotsoffield): +sil @dead_argument_due_to_only_release_user_but__exploded : $@convention(thin) (@owned LotsOfFields) -> (Int, Int, Int) { +bb0(%0 : $LotsOfFields): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -210,21 +210,24 @@ bb0(%0 : $lotsoffield): %c21 = builtin "assert_configuration"() : $Builtin.Int32 %c22 = builtin "assert_configuration"() : $Builtin.Int32 - %1 = struct_extract %0 : $lotsoffield, #lotsoffield.tbaz - %2 = struct_extract %0 : $lotsoffield, #lotsoffield.a - %3 = struct_extract %0 : $lotsoffield, #lotsoffield.b - %4 = struct_extract %0 : $lotsoffield, #lotsoffield.c - release_value %1 : $baz + %1 = struct_extract %0 : $LotsOfFields, #LotsOfFields.tBaz + %2 = struct_extract %0 : $LotsOfFields, #LotsOfFields.a + %3 = struct_extract %0 : $LotsOfFields, #LotsOfFields.b + %4 = struct_extract %0 : $LotsOfFields, #LotsOfFields.c + release_value %1 : $Baz %5 = tuple (%2 : $Int, %3 : $Int, %4 : $Int) return %5 : $(Int, Int, Int) } -// Make sure argument is exploded and the baz part is not passed in as argument, as its only use -// is a release. +// Since this is a value that contains only a singular owned type, there is no +// point from an ARC perspective in splitting it up. We still want to perform +// owned to guaranteed though so we know we are oging to already create a +// thunk. Thus we are not creating code-size. +// // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @dead_argument_due_to_more_than_release_user -// CHECK: [[FN1:%.*]] = function_ref @$S43dead_argument_due_to_more_than_release_userTf4gX_n : $@convention(thin) (@guaranteed baz, Int) -> (Int, Int) -sil @dead_argument_due_to_more_than_release_user : $@convention(thin) (@owned boo) -> (Int, Int) { -bb0(%0 : $boo): +// CHECK: [[FN1:%.*]] = function_ref @$S43dead_argument_due_to_more_than_release_userTf4gX_n : $@convention(thin) (@guaranteed Baz, Int) -> (Int, Int) +sil @dead_argument_due_to_more_than_release_user : $@convention(thin) (@owned Boo) -> (Int, Int) { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -249,10 +252,10 @@ bb0(%0 : $boo): %c21 = builtin "assert_configuration"() : $Builtin.Int32 %c22 = builtin "assert_configuration"() : $Builtin.Int32 - %1 = struct_extract %0 : $boo, #boo.tbaz - %2 = struct_extract %0 : $boo, #boo.a - retain_value %1 : $baz - release_value %1 : $baz + %1 = struct_extract %0 : $Boo, #Boo.tBaz + %2 = struct_extract %0 : $Boo, #Boo.a + retain_value %1 : $Baz + release_value %1 : $Baz %4 = tuple (%2 : $Int, %2 : $Int) return %4 : $(Int, Int) } @@ -304,12 +307,12 @@ bb3: // Make sure %0 is a dead argument. // // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @exploded_release_to_dead_argument -// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $boo): +// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $Boo): // CHECK: [[IN1:%.*]] = function_ref @$S33exploded_release_to_dead_argumentTf4d_n // CHECK: apply [[IN1]]() // CHECK: release_value [[INPUT_ARG0]] -sil @exploded_release_to_dead_argument : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): +sil @exploded_release_to_dead_argument : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -334,11 +337,11 @@ bb0(%0 : $boo): %c21 = builtin "assert_configuration"() : $Builtin.Int32 %c22 = builtin "assert_configuration"() : $Builtin.Int32 - %1 = struct_extract %0 : $boo, #boo.tbaz - %2 = struct_extract %1 : $baz, #baz.start - %3 = struct_extract %1 : $baz, #baz.end - release_value %2 : $foo - release_value %3 : $foo + %1 = struct_extract %0 : $Boo, #Boo.tBaz + %2 = struct_extract %1 : $Baz, #Baz.start + %3 = struct_extract %1 : $Baz, #Baz.end + release_value %2 : $Foo + release_value %3 : $Foo %f = function_ref @update_global: $@convention(thin) () -> () apply %f() : $@convention(thin) () -> () %4 = tuple () @@ -349,11 +352,11 @@ bb0(%0 : $boo): // Make sure %0 is not a dead argument, but gets converted to a guaranteed arg. // // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @exploded_release_to_guaranteed_param -// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $boo): +// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $Boo): // CHECK: [[IN1:%.*]] = function_ref @$S36exploded_release_to_guaranteed_paramTf4gX_n // CHECK: release_value [[INPUT_ARG0]] -sil @exploded_release_to_guaranteed_param : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): +sil @exploded_release_to_guaranteed_param : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -378,25 +381,25 @@ bb0(%0 : $boo): %c21 = builtin "assert_configuration"() : $Builtin.Int32 %c22 = builtin "assert_configuration"() : $Builtin.Int32 - %1 = struct_extract %0 : $boo, #boo.tbaz - %2 = struct_extract %1 : $baz, #baz.start - %3 = struct_extract %1 : $baz, #baz.end - %4 = struct_extract %0 : $boo, #boo.a + %1 = struct_extract %0 : $Boo, #Boo.tBaz + %2 = struct_extract %1 : $Baz, #Baz.start + %3 = struct_extract %1 : $Baz, #Baz.end + %4 = struct_extract %0 : $Boo, #Boo.a %5 = function_ref @use_Int : $@convention(thin) (Int) -> () apply %5(%4) : $@convention(thin) (Int) -> () - release_value %2 : $foo - release_value %3 : $foo + release_value %2 : $Foo + release_value %3 : $Foo %6 = tuple () return %6 : $() } // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @single_owned_return_value -// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $boo): +// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $Boo): // CHECK: [[IN1:%.*]] = function_ref @$S25single_owned_return_valueTf4n_g // CHECK: [[IN2:%.*]] = apply [[IN1]]([[INPUT_ARG0]] // CHECK: retain_value [[IN2]] -sil @single_owned_return_value : $@convention(thin) (@owned boo) -> @owned boo { -bb0(%0 : $boo): +sil @single_owned_return_value : $@convention(thin) (@owned Boo) -> @owned Boo { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -421,8 +424,8 @@ bb0(%0 : $boo): %c21 = builtin "assert_configuration"() : $Builtin.Int32 %c22 = builtin "assert_configuration"() : $Builtin.Int32 - retain_value %0 : $boo - return %0 : $boo + retain_value %0 : $Boo + return %0 : $Boo } @@ -430,8 +433,8 @@ bb0(%0 : $boo): // CHECK: function_ref @$S45single_owned_return_value_with_self_recursionTf4n_g // CHECK: [[RET:%.*]] = apply // CHECK: retain_value [[RET]] -sil @single_owned_return_value_with_self_recursion : $@convention(thin) (@owned boo) -> @owned boo { -bb0(%0 : $boo): +sil @single_owned_return_value_with_self_recursion : $@convention(thin) (@owned Boo) -> @owned Boo { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -458,23 +461,23 @@ bb0(%0 : $boo): cond_br undef, bb1, bb2 bb1: - retain_value %0 : $boo - br bb3(%0 : $boo) + retain_value %0 : $Boo + br bb3(%0 : $Boo) bb2: - %2 = function_ref @single_owned_return_value_with_self_recursion : $@convention(thin) (@owned boo) -> @owned boo - %3 = apply %2(%0) : $@convention(thin) (@owned boo) -> @owned boo - br bb3 (%3 : $boo) -bb3(%4 : $boo): - return %4 : $boo + %2 = function_ref @single_owned_return_value_with_self_recursion : $@convention(thin) (@owned Boo) -> @owned Boo + %3 = apply %2(%0) : $@convention(thin) (@owned Boo) -> @owned Boo + br bb3 (%3 : $Boo) +bb3(%4 : $Boo): + return %4 : $Boo } // CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @single_owned_return_value_with_interfering_release -// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $boo): +// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $Boo): // CHECK: [[IN1:%.*]] = function_ref @$S50single_owned_return_value_with_interfering_releaseTf4x_nTf4gnn_n // CHECK-NOT: retain_value // CHECK: return -sil @single_owned_return_value_with_interfering_release : $@convention(thin) (@owned boo) -> boo { -bb0(%0 : $boo): +sil @single_owned_return_value_with_interfering_release : $@convention(thin) (@owned Boo) -> Boo { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -499,22 +502,22 @@ bb0(%0 : $boo): %c21 = builtin "assert_configuration"() : $Builtin.Int32 %c22 = builtin "assert_configuration"() : $Builtin.Int32 - retain_value %0 : $boo - %1 = struct_extract %0 : $boo, #boo.tbaz - %2 = struct_extract %1 : $baz, #baz.start - release_value %2: $foo - return %0 : $boo + retain_value %0 : $Boo + %1 = struct_extract %0 : $Boo, #Boo.tBaz + %2 = struct_extract %1 : $Baz, #Baz.start + release_value %2: $Foo + return %0 : $Boo } // Make sure we do not move the retain_value in the throw block. // -// CHECK-LABEL: sil [serialized] [signature_optimized_thunk] [always_inline] @owned_to_unowned_retval_with_error_result : $@convention(thin) (@owned boo) -> (@owned boo, @error Error) { -// CHECK: function_ref @$S41owned_to_unowned_retval_with_error_resultTfq4n_g : $@convention(thin) (@owned boo) -> (boo, @error Error) +// CHECK-LABEL: sil [serialized] [signature_optimized_thunk] [always_inline] @owned_to_unowned_retval_with_error_result : $@convention(thin) (@owned Boo) -> (@owned Boo, @error Error) { +// CHECK: function_ref @$S41owned_to_unowned_retval_with_error_resultTfq4n_g : $@convention(thin) (@owned Boo) -> (Boo, @error Error) // CHECK: bb1 // CHECK-NOT: retain_value // CHECK: bb2 -sil [serialized] @owned_to_unowned_retval_with_error_result : $@convention(thin) (@owned boo) -> (@owned boo, @error Error) { -bb0(%0 : $boo): +sil [serialized] @owned_to_unowned_retval_with_error_result : $@convention(thin) (@owned Boo) -> (@owned Boo, @error Error) { +bb0(%0 : $Boo): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 %c2 = builtin "assert_configuration"() : $Builtin.Int32 @@ -542,11 +545,11 @@ bb0(%0 : $boo): cond_br undef, bb1, bb2 bb1: - retain_value %0 : $boo - return %0 : $boo + retain_value %0 : $Boo + return %0 : $Boo bb2: - retain_value %0 : $boo + retain_value %0 : $Boo throw undef : $Error } @@ -690,11 +693,11 @@ bb0(%0 : $Builtin.NativeObject, %1 : $Builtin.NativeObject): return %5 : $() } -sil [serialized] @exploded_release_to_guaranteed_param_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): - %2 = function_ref @exploded_release_to_guaranteed_param : $@convention(thin) (@owned boo) -> () - retain_value %0 : $boo - %4 = apply %2(%0) : $@convention(thin) (@owned boo) -> () +sil [serialized] @exploded_release_to_guaranteed_param_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): + %2 = function_ref @exploded_release_to_guaranteed_param : $@convention(thin) (@owned Boo) -> () + retain_value %0 : $Boo + %4 = apply %2(%0) : $@convention(thin) (@owned Boo) -> () %5 = tuple() return %5 : $() } @@ -707,27 +710,27 @@ bb0(%0 : $boo): // CHECK: [[RET:%.*]] = apply // CHECK: retain_value [[RET]] // CHECK: release_value [[RET]] -sil @single_owned_return_value_with_self_recursion_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): - %2 = function_ref @single_owned_return_value_with_self_recursion : $@convention(thin) (@owned boo) -> @owned boo - %4 = apply %2(%0) : $@convention(thin) (@owned boo) -> @owned boo - release_value %4 : $boo +sil @single_owned_return_value_with_self_recursion_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): + %2 = function_ref @single_owned_return_value_with_self_recursion : $@convention(thin) (@owned Boo) -> @owned Boo + %4 = apply %2(%0) : $@convention(thin) (@owned Boo) -> @owned Boo + release_value %4 : $Boo %5 = tuple() return %5 : $() } -sil [serialized] @exploded_release_to_dead_param_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): - %2 = function_ref @exploded_release_to_dead_argument : $@convention(thin) (@owned boo) -> () - retain_value %0 : $boo - %4 = apply %2(%0) : $@convention(thin) (@owned boo) -> () +sil [serialized] @exploded_release_to_dead_param_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): + %2 = function_ref @exploded_release_to_dead_argument : $@convention(thin) (@owned Boo) -> () + retain_value %0 : $Boo + %4 = apply %2(%0) : $@convention(thin) (@owned Boo) -> () %5 = tuple() return %5 : $() } -sil [serialized] @single_owned_return_value_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): +sil [serialized] @single_owned_return_value_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): cond_br undef, bb1, bb2 bb1: @@ -737,14 +740,14 @@ bb2: br bb3 bb3: - %2 = function_ref @single_owned_return_value : $@convention(thin) (@owned boo) -> @owned boo - %3 = apply %2(%0) : $@convention(thin) (@owned boo) -> @owned boo + %2 = function_ref @single_owned_return_value : $@convention(thin) (@owned Boo) -> @owned Boo + %3 = apply %2(%0) : $@convention(thin) (@owned Boo) -> @owned Boo %4 = tuple() return %4 : $() } -sil [serialized] @single_owned_return_value_with_interfering_release_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): +sil [serialized] @single_owned_return_value_with_interfering_release_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): cond_br undef, bb1, bb2 bb1: @@ -754,14 +757,14 @@ bb2: br bb3 bb3: - %2 = function_ref @single_owned_return_value_with_interfering_release : $@convention(thin) (@owned boo) -> boo - %3 = apply %2(%0) : $@convention(thin) (@owned boo) -> boo + %2 = function_ref @single_owned_return_value_with_interfering_release : $@convention(thin) (@owned Boo) -> Boo + %3 = apply %2(%0) : $@convention(thin) (@owned Boo) -> Boo %4 = tuple() return %4 : $() } -sil [serialized] @owned_to_unowned_retval_with_error_result_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): +sil [serialized] @owned_to_unowned_retval_with_error_result_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): cond_br undef, bb1, bb2 bb1: @@ -771,10 +774,10 @@ bb2: br bb3 bb3: - %2 = function_ref @owned_to_unowned_retval_with_error_result : $@convention(thin) (@owned boo) -> (@owned boo, @error Error) - try_apply %2(%0) : $@convention(thin) (@owned boo) -> (@owned boo, @error Error), normal bb4, error bb5 + %2 = function_ref @owned_to_unowned_retval_with_error_result : $@convention(thin) (@owned Boo) -> (@owned Boo, @error Error) + try_apply %2(%0) : $@convention(thin) (@owned Boo) -> (@owned Boo, @error Error), normal bb4, error bb5 -bb4(%99 : $boo): +bb4(%99 : $Boo): %4 = tuple() return %4 : $() @@ -782,34 +785,34 @@ bb5(%100 : $Error): unreachable } -sil [serialized] @dead_argument_due_to_only_release_user_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): - %2 = function_ref @dead_argument_due_to_only_release_user : $@convention(thin) (@owned boo) -> (Int, Int) - %4 = apply %2(%0) : $@convention(thin) (@owned boo) -> (Int, Int) +sil [serialized] @dead_argument_due_to_only_release_user_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): + %2 = function_ref @dead_argument_due_to_only_release_user : $@convention(thin) (@owned Boo) -> (Int, Int) + %4 = apply %2(%0) : $@convention(thin) (@owned Boo) -> (Int, Int) %5 = tuple() return %5 : $() } -sil [serialized] @dead_argument_due_to_only_release_user_but__exploded_callsite : $@convention(thin) (@owned lotsoffield) -> () { -bb0(%0 : $lotsoffield): - %2 = function_ref @dead_argument_due_to_only_release_user_but__exploded : $@convention(thin) (@owned lotsoffield) -> (Int, Int, Int) - %4 = apply %2(%0) : $@convention(thin) (@owned lotsoffield) -> (Int, Int, Int) +sil [serialized] @dead_argument_due_to_only_release_user_but__exploded_callsite : $@convention(thin) (@owned LotsOfFields) -> () { +bb0(%0 : $LotsOfFields): + %2 = function_ref @dead_argument_due_to_only_release_user_but__exploded : $@convention(thin) (@owned LotsOfFields) -> (Int, Int, Int) + %4 = apply %2(%0) : $@convention(thin) (@owned LotsOfFields) -> (Int, Int, Int) %5 = tuple() return %5 : $() } -sil [serialized] @dead_argument_due_to_more_than_release_user_callsite : $@convention(thin) (@owned boo) -> () { -bb0(%0 : $boo): - %2 = function_ref @dead_argument_due_to_more_than_release_user : $@convention(thin) (@owned boo) -> (Int, Int) - %4 = apply %2(%0) : $@convention(thin) (@owned boo) -> (Int, Int) +sil [serialized] @dead_argument_due_to_more_than_release_user_callsite : $@convention(thin) (@owned Boo) -> () { +bb0(%0 : $Boo): + %2 = function_ref @dead_argument_due_to_more_than_release_user : $@convention(thin) (@owned Boo) -> (Int, Int) + %4 = apply %2(%0) : $@convention(thin) (@owned Boo) -> (Int, Int) %5 = tuple() return %5 : $() } -sil [serialized] @argument_with_incomplete_epilogue_release_callsite : $@convention(thin) (@owned goo) -> () { -bb0(%0 : $goo): - %2 = function_ref @argument_with_incomplete_epilogue_release : $@convention(thin) (@owned goo) -> () - %4 = apply %2(%0) : $@convention(thin) (@owned goo) -> () +sil [serialized] @argument_with_incomplete_epilogue_release_callsite : $@convention(thin) (@owned Goo) -> () { +bb0(%0 : $Goo): + %2 = function_ref @argument_with_incomplete_epilogue_release : $@convention(thin) (@owned Goo) -> () + %4 = apply %2(%0) : $@convention(thin) (@owned Goo) -> () %5 = tuple() return %5 : $() } @@ -1332,33 +1335,33 @@ bb0(%0 : $Builtin.NativeObject, %1 : $Builtin.NativeObject): // CHECK-NEGATIVE-NOT: sil {{.*}}_dont_explode_single_enum -sil [noinline] @dont_explode_single_enum : $@convention(thin) (@owned Optional<(foo, foo)>) -> @owned foo { -bb0(%0 : $Optional<(foo, foo)>): - %281 = unchecked_enum_data %0 : $Optional<(foo, foo)>, #Optional.some!enumelt.1 - %282 = tuple_extract %281 : $(foo, foo), 0 - %283 = tuple_extract %281 : $(foo, foo), 1 - strong_release %283 : $foo - return %282 : $foo +sil [noinline] @dont_explode_single_enum : $@convention(thin) (@owned Optional<(Foo, Foo)>) -> @owned Foo { +bb0(%0 : $Optional<(Foo, Foo)>): + %281 = unchecked_enum_data %0 : $Optional<(Foo, Foo)>, #Optional.some!enumelt.1 + %282 = tuple_extract %281 : $(Foo, Foo), 0 + %283 = tuple_extract %281 : $(Foo, Foo), 1 + strong_release %283 : $Foo + return %282 : $Foo } // CHECK-LABEL: sil @call_with_single_enum -// CHECK: [[F:%[0-9]+]] = function_ref @dont_explode_single_enum : $@convention(thin) (@owned Optional<(foo, foo)>) -> @owned foo +// CHECK: [[F:%[0-9]+]] = function_ref @dont_explode_single_enum : $@convention(thin) (@owned Optional<(Foo, Foo)>) -> @owned Foo // CHECK: apply [[F]](%0) // CHECK: return -sil @call_with_single_enum : $@convention(thin) (@owned Optional<(foo, foo)>) -> @owned foo { -bb0(%0 : $Optional<(foo, foo)>): - %f = function_ref @dont_explode_single_enum : $@convention(thin) (@owned Optional<(foo, foo)>) -> @owned foo - %a = apply %f(%0) : $@convention(thin) (@owned Optional<(foo, foo)>) -> @owned foo - return %a : $foo +sil @call_with_single_enum : $@convention(thin) (@owned Optional<(Foo, Foo)>) -> @owned Foo { +bb0(%0 : $Optional<(Foo, Foo)>): + %f = function_ref @dont_explode_single_enum : $@convention(thin) (@owned Optional<(Foo, Foo)>) -> @owned Foo + %a = apply %f(%0) : $@convention(thin) (@owned Optional<(Foo, Foo)>) -> @owned Foo + return %a : $Foo } // Check if externally available functions are optimized. -sil public_external [noinline] @externally_available_with_dead_arg : $@convention(thin) (@guaranteed foo) -> () { -bb0(%0 : $foo): +sil public_external [noinline] @externally_available_with_dead_arg : $@convention(thin) (@guaranteed Foo) -> () { +bb0(%0 : $Foo): %r = tuple() return %r : $() } @@ -1367,10 +1370,10 @@ bb0(%0 : $foo): // CHECK: [[F:%[0-9]+]] = function_ref @$S34externally_available_with_dead_argTf4d_n : $@convention(thin) () -> () // CHECK: apply [[F]]() // CHECK: return -sil @call_externally_available : $@convention(thin) (@guaranteed foo) -> () { -bb0(%0 : $foo): - %f = function_ref @externally_available_with_dead_arg : $@convention(thin) (@guaranteed foo) -> () - %a = apply %f(%0) : $@convention(thin) (@guaranteed foo) -> () +sil @call_externally_available : $@convention(thin) (@guaranteed Foo) -> () { +bb0(%0 : $Foo): + %f = function_ref @externally_available_with_dead_arg : $@convention(thin) (@guaranteed Foo) -> () + %a = apply %f(%0) : $@convention(thin) (@guaranteed Foo) -> () %r = tuple() return %r : $() } @@ -1378,11 +1381,11 @@ bb0(%0 : $foo): // We should remove the array semantic from specialized calls. -// CHECK-LABEL: sil [serialized] [signature_optimized_thunk] [always_inline] [_semantics "array.foobar"] @array_semantic : $@convention(method) (@owned Builtin.NativeObject) -> () { +// CHECK-LABEL: sil [serialized] [signature_optimized_thunk] [always_inline] [_semantics "array.Foobar"] @array_semantic : $@convention(method) (@owned Builtin.NativeObject) -> () { // CHECK: [[FUNC_REF:%[0-9]+]] = function_ref @$S14array_semanticTfq4g_n : $@convention(thin) (@guaranteed Builtin.NativeObject) -> () // CHECK: apply [[FUNC_REF]] // CHECK: release_value -sil [serialized] [_semantics "array.foobar"] @array_semantic : $@convention(method) (@owned Builtin.NativeObject) -> () { +sil [serialized] [_semantics "array.Foobar"] @array_semantic : $@convention(method) (@owned Builtin.NativeObject) -> () { bb0(%0 : $Builtin.NativeObject): // make it a non-trivial function %c1 = builtin "assert_configuration"() : $Builtin.Int32 @@ -1566,7 +1569,7 @@ bb0(%0 : $T): sil hidden [noinline] @generic_in_to_guaranteed : $@convention(thin) (@in T) -> Int64 { bb0(%0 : $*T): - %2 = witness_method $T, #P.foo!1 : (Self) -> () -> Int64 : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> Int64 + %2 = witness_method $T, #P.Foo!1 : (Self) -> () -> Int64 : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> Int64 %3 = apply %2(%0) : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@in_guaranteed τ_0_0) -> Int64 destroy_addr %0 : $*T return %3 : $Int64 @@ -1591,14 +1594,14 @@ bb0(%0 : $*T): return %15 : $Int64 } -// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @generic_func_with_dead_non_generic_arg : $@convention(thin) (@owned foo, @in T) -> () +// CHECK-LABEL: sil [signature_optimized_thunk] [always_inline] @generic_func_with_dead_non_generic_arg : $@convention(thin) (@owned Foo, @in T) -> () // CHECK: function_ref @$S027generic_func_with_dead_non_A4_argTf4dd_n : $@convention(thin) () -> () // Call the specialization which is not polymorphic. // CHECK: apply // CHECK: destroy_addr // CHECK: end sil function 'generic_func_with_dead_non_generic_arg' -sil [noinline] @generic_func_with_dead_non_generic_arg : $@convention(thin) (@owned foo, @in T) -> () { -bb0(%0 : $foo, %1 : $*T): +sil [noinline] @generic_func_with_dead_non_generic_arg : $@convention(thin) (@owned Foo, @in T) -> () { +bb0(%0 : $Foo, %1 : $*T): destroy_addr %1 : $*T %r = tuple() return %r : $() @@ -1723,19 +1726,19 @@ bb0(%0 : $*T): // CHECK-NOT: strong_release // CHECK: return -// CHECK-LABEL: sil shared @$S25single_owned_return_valueTf4n_g : $@convention(thin) (@owned boo) -> boo -// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $boo): +// CHECK-LABEL: sil shared @$S25single_owned_return_valueTf4n_g : $@convention(thin) (@owned Boo) -> Boo +// CHECK: bb0([[INPUT_ARG0:%[0-9]+]] : $Boo): // CHECK-NOT: retain_value // CHECK: return // There should not be a single retain in this function. // -// CHECK-LABEL: sil shared @$S45single_owned_return_value_with_self_recursionTf4n_g : $@convention(thin) (@owned boo) -> boo +// CHECK-LABEL: sil shared @$S45single_owned_return_value_with_self_recursionTf4n_g : $@convention(thin) (@owned Boo) -> Boo // CHECK: bb0 // CHECK-NOT: retain_value // CHECK: return -// CHECK-LABEL: @$S41owned_to_unowned_retval_with_error_resultTfq4n_g : $@convention(thin) (@owned boo) -> (boo, @error Error) { +// CHECK-LABEL: @$S41owned_to_unowned_retval_with_error_resultTfq4n_g : $@convention(thin) (@owned Boo) -> (Boo, @error Error) { // CHECK: bb2 // CHECK: retain_value // CHECK: throw diff --git a/utils/cmpcodesize/setup.py b/utils/cmpcodesize/setup.py index 5a92c8b39ce06..98303850a43cb 100644 --- a/utils/cmpcodesize/setup.py +++ b/utils/cmpcodesize/setup.py @@ -50,5 +50,8 @@ 'console_scripts': [ 'cmpcodesize = cmpcodesize:main', ], - } + }, + install_requires=[ + 'pyyaml==3.12' + ], )