blob: b2a722f3ed6dceff44aec840872c4439768326ac [file] [log] [blame]
//===--- 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/ArrayRef.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/TinyPtrVector.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 {
invalidateAllInfo(f);
recomputeFunctionList.remove(f);
// Now that we have invalidated all references to the function, delete it.
funcInfos.erase(f);
}
/// 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.
const FunctionInfo &getFunctionInfo(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;
/// 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");
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);
/// 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;
};
/// 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.
///
/// 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;
Optional<unsigned> getNumPartiallyAppliedArguments() const {
if (!hasPartiallyAppliedArguments) {
return None;
}
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.getValue());
}
}
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());
}
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
#endif