//===--- CallerAnalysis.h - Determine callees per call site -----*- C++ -*-===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #ifndef SWIFT_SILOPTIMIZER_ANALYSIS_CALLERANALYSIS_H #define SWIFT_SILOPTIMIZER_ANALYSIS_CALLERANALYSIS_H #include "swift/SIL/SILFunction.h" #include "swift/SIL/SILInstruction.h" #include "swift/SIL/SILModule.h" #include "swift/SILOptimizer/Analysis/Analysis.h" #include "swift/SILOptimizer/Utils/InstOptUtils.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallSet.h" namespace swift { /// A lazy caller analysis that works by only recomputing its state upon /// an ask for information. /// /// This laziness is implemented by the pass invalidating its internal state for /// a function F when receiving an invalidation message for F and then adding F /// to a recompute list. When the method getFunctionInfo is called, the /// recompute list is used to recompute all of the invalidated state. /// /// Originally, swift had an eagerly recomputed caller analysis. This was found /// to cause large compile time problems since after every invalidation we /// needed to walk every function in the module to update any invalidated /// state. This in combination with a sequence of invalidating function passes /// can lead to many invalidations even if the information is not used. /// /// NOTE: We use the term caller in a loose way here since in this analysis, we /// consider partial applies of the function to be "callers" of the function. class CallerAnalysis final : public SILAnalysis { public: class FunctionInfo; private: struct CallerInfo; struct ApplySiteFinderVisitor; /// Current module we are analyzing. SILModule &mod; /// A map between all the functions and their callsites in the module. mutable llvm::DenseMap funcInfos; /// A set of functions that needs to be recomputed before we can serve /// queries. llvm::SetVector recomputeFunctionList; public: CallerAnalysis(SILModule *m); static bool classof(const SILAnalysis *s) { return s->getKind() == SILAnalysisKind::Caller; } /// Invalidate all information in this analysis. void invalidate() override; /// Invalidate all of the information for a specific caller function. void invalidate(SILFunction *caller, InvalidationKind k) override { // Should we invalidate based on the invalidation kind. bool shouldInvalidate = k & InvalidationKind::CallsAndInstructions; if (!shouldInvalidate) return; // This function has become "unknown" to us. Invalidate any callsite // information related to this function. invalidateKnownCallees(caller); // Make sure this function is recomputed next time. recomputeFunctionList.insert(caller); } /// Notify the analysis about a newly created or modified function. void notifyAddedOrModifiedFunction(SILFunction *f) override { auto &fInfo = getOrInsertFunctionInfo(f); (void)fInfo; recomputeFunctionList.insert(f); } /// Notify the analysis about a function which will be deleted from the /// module. void notifyWillDeleteFunction(SILFunction *f) override; /// Notify the analysis about changed witness or vtables. /// /// Today this is unimplemented since the CallerAnalysis does not attempt... /// yet... to understand class_method or witness_method. Once that is /// is implemented, void invalidateFunctionTables() override {} /// Look up the function info that we have stored for f, recomputing all /// invalidating parts of the call graph. /// /// Warning: The returned FunctionInfo is only alive until the next call to /// `getFunctionInfo`. const FunctionInfo &getFunctionInfo(SILFunction *f) const; SWIFT_DEBUG_DUMP; /// 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; /// 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. SWIFT_DEBUG_DUMPER(print(const char *filePath)); void verify() const override; void verify(SILFunction *f) const override; 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. /// /// This is a function that just looks up the function info for f and then /// calls: /// /// void invalidateKnownCallees(SILFunction *caller, /// FunctionInfo &callerInfo); void invalidateKnownCallees(SILFunction *f); /// Using the passed in caller info and caller function, eliminate the edge /// inbetween the caller and its callees. /// /// NOTE: This does not remove the "book keeping" backedges from the caller /// function to its own set of callers. This must be invalidated by using /// invalidateAllInfo. void invalidateKnownCallees(SILFunction *caller, FunctionInfo &callerInfo); /// Invalidate both the known callees of f and the known callers of f. void invalidateAllInfo(SILFunction *f, FunctionInfo &fInfo); /// Helper method that reprocesses all elements of recomputeFunctionList and /// then clears the function list. /// /// NOTE: This is the part of the analysis that performs the lazy /// recomputation upon access. 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 &getOrInsertFunctionInfo(SILFunction *f); /// Internal only method for unsafely getting the FunctionInfo for the /// SILFunction \p f. /// /// This is valid since we assume that we have maintained our datastructures /// via notifications for functions being added/destroyed. /// /// NOTE: In asserts builds this routine asserts if we do not have function /// info for f. /// NOTE: This routine _WILL NOT_ perform any recomputation of the callgraph. FunctionInfo &unsafeGetFunctionInfo(SILFunction *f); /// const_cast version of FunctionInfo &unsafeGetFunctionInfo(SILFunction *). const FunctionInfo &unsafeGetFunctionInfo(SILFunction *f) const; /// Helper function for verification that hoists out the callerInfo. void verify(SILFunction *caller, const FunctionInfo &callerInfo) const; }; /// Auxiliary 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. /// /// NOTE: We know that we will never have more than 2^16 generic arguments due /// to the runtime implementation. uint8_t numPartiallyAppliedArguments[2]; /// A boolean flag to say if we actually have partially applied arguments. /// /// Otherwise it would be unable to distinguish not having partially applied /// arguments from having zero partially applied argument. bool hasPartiallyAppliedArguments : 1; /// 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; std::optional getNumPartiallyAppliedArguments() const { if (!hasPartiallyAppliedArguments) { return std::nullopt; } auto *x = reinterpret_cast(numPartiallyAppliedArguments); return unsigned(*x); } void setNumPartiallyAppliedArguments(unsigned newValue) { hasPartiallyAppliedArguments = true; auto *x = reinterpret_cast(numPartiallyAppliedArguments); x[0] = uint16_t(newValue); } }; /// This is a representation of the caller information that we have associated /// with a specific function. /// /// NOTE: If needed, this can be extended to contain the callsites of the /// function. Today we do not provide any functionality that requires knowledge /// of exact callsites, so it would just be a waste of memory. class CallerAnalysis::FunctionInfo { friend class CallerAnalysis; using CallerInfo = CallerAnalysis::CallerInfo; struct YAMLRepresentation; // A static_assert to make sure that CallerInfo remains 3 bytes for // performance reasons. We are going to store a bunch of these in callerStates // so we want them to be as small as possible. static_assert(sizeof(CallerAnalysis::CallerInfo) == 3, "Expected caller info to be 3 bytes for performance reasons"); /// 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; /// Private helper type to reduce 80 column violations. using CallerStatesValueType = decltype(callerStates)::value_type; /// This set vector maintains edges from a function to its callees. /// /// This is used in caller analysis to quickly invalidate all of the callee /// information associated with a function when the function's body is /// invalidated. /// /// NOTE: If a function is deleted, we can not only use this callee set. We /// also have to eliminate caller edges pointing at the function. /// /// \see CallerAnalysis::invalidateExistingCalleeRelation. llvm::SmallSetVector calleeStates; /// True if this function is something that could be called via a vtable or /// a witness table. This does not include escaping uses. /// /// TODO: For now this is very conservative and is only set to false 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. bool mayHaveIndirectCallers : 1; /// Whether the function is sufficiently visible to be called by a different /// module. bool mayHaveExternalCallers : 1; public: FunctionInfo(SILFunction *f); /// Returns true if and only if the analysis was able to find all direct /// callers of this function /and/ if the function can not be called /// indirectly given visibility and compilation mode. /// /// This implies that the function can be replaced by an optimized form of the /// function (e.g. a specialized function) without needing to introduce a /// thunk since we can rewrite all of the callers to call the new function. bool foundAllCallers() const { return hasOnlyCompleteDirectCallerSets() && !mayHaveIndirectCallers && !mayHaveExternalCallers; } /// Returns true if this function has at least one direct caller. bool hasDirectCaller() const { return callerStates.size() && llvm::any_of(callerStates, [](const CallerStatesValueType &v) { return v.second.hasFullApply; }); } /// Returns the minimum number of partially applied arguments over all partial /// applications of this function. /// /// This is useful information to have since in many cases, all of the partial /// applies of a function partially apply the same number of arguments. In /// such a case, we may be able to eliminate some of the partially applied /// arguments without increasing code-size. /// /// NOTE: This returning non-zero /does not/ mean that we found all such apply /// sites. 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.getNumPartiallyAppliedArguments()) { foundArg = true; minArgs = std::min(minArgs, numArgs.value()); } } return foundArg ? minArgs : 0; } /// Returns true if we were able to find all direct callers of this function. /// /// NOTE: The function may still be called indirectly. To ascertain if we /// found all of the function's direct callers and that it does not have any /// indirect callers, \see FunctionInfo::foundAllCallers(). bool hasOnlyCompleteDirectCallerSets() const { return llvm::all_of(callerStates, [](const CallerStatesValueType &v) { return v.second.isDirectCallerSetComplete; }); } /// Return a range containing the partial and full apply sites that we found /// in the given caller for our callee. auto getAllReferencingCallers() const -> decltype(llvm::make_range(callerStates.begin(), callerStates.end())) { return llvm::make_range(callerStates.begin(), callerStates.end()); } SWIFT_DEBUG_DUMP; void print(llvm::raw_ostream &os) const; }; } // end namespace swift #endif