mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
LLVM has removed llvm::Optional, move over to std::optional. Also clang-format to fix up all the renamed #includes.
353 lines
13 KiB
C++
353 lines
13 KiB
C++
//===--- 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<SILFunction *, FunctionInfo> funcInfos;
|
|
|
|
/// A set of functions that needs to be recomputed before we can serve
|
|
/// queries.
|
|
llvm::SetVector<SILFunction *> 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<unsigned> getNumPartiallyAppliedArguments() const {
|
|
if (!hasPartiallyAppliedArguments) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto *x = reinterpret_cast<const uint16_t *>(numPartiallyAppliedArguments);
|
|
return unsigned(*x);
|
|
}
|
|
|
|
void setNumPartiallyAppliedArguments(unsigned newValue) {
|
|
hasPartiallyAppliedArguments = true;
|
|
auto *x = reinterpret_cast<uint16_t *>(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<SILFunction *, CallerInfo, 1> 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<SILFunction *, 1> 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
|