blob: 8db246f739ab170f176e799ba04b28720212a68c [file] [log] [blame]
//===- FunctionSpecialization.h - Function Specialization -----------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This specialises functions with constant parameters. Constant parameters
// like function pointers and constant globals are propagated to the callee by
// specializing the function. The main benefit of this pass at the moment is
// that indirect calls are transformed into direct calls, which provides inline
// opportunities that the inliner would not have been able to achieve. That's
// why function specialisation is run before the inliner in the optimisation
// pipeline; that is by design. Otherwise, we would only benefit from constant
// passing, which is a valid use-case too, but hasn't been explored much in
// terms of performance uplifts, cost-model and compile-time impact.
//
// Current limitations:
// - It does not yet handle integer ranges. We do support "literal constants",
// but that's off by default under an option.
// - The cost-model could be further looked into (it mainly focuses on inlining
// benefits),
//
// Ideas:
// - With a function specialization attribute for arguments, we could have
// a direct way to steer function specialization, avoiding the cost-model,
// and thus control compile-times / code-size.
//
// Todos:
// - Specializing recursive functions relies on running the transformation a
// number of times, which is controlled by option
// `func-specialization-max-iters`. Thus, increasing this value and the
// number of iterations, will linearly increase the number of times recursive
// functions get specialized, see also the discussion in
// https://reviews.llvm.org/D106426 for details. Perhaps there is a
// compile-time friendlier way to control/limit the number of specialisations
// for recursive functions.
// - Don't transform the function if function specialization does not trigger;
// the SCCPSolver may make IR changes.
//
// References:
// - 2021 LLVM Dev Mtg “Introducing function specialisation, and can we enable
// it by default?”, https://www.youtube.com/watch?v=zJiCjeXgV5Q
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_TRANSFORMS_IPO_FUNCTIONSPECIALIZATION_H
#define LLVM_TRANSFORMS_IPO_FUNCTIONSPECIALIZATION_H
#include "llvm/Analysis/CodeMetrics.h"
#include "llvm/Analysis/InlineCost.h"
#include "llvm/Analysis/LoopInfo.h"
#include "llvm/Analysis/TargetTransformInfo.h"
#include "llvm/Transforms/Scalar/SCCP.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/SCCPSolver.h"
#include "llvm/Transforms/Utils/SizeOpts.h"
using namespace llvm;
namespace llvm {
// Specialization signature, used to uniquely designate a specialization within
// a function.
struct SpecSig {
// Hashing support, used to distinguish between ordinary, empty, or tombstone
// keys.
unsigned Key = 0;
SmallVector<ArgInfo, 4> Args;
bool operator==(const SpecSig &Other) const {
if (Key != Other.Key || Args.size() != Other.Args.size())
return false;
for (size_t I = 0; I < Args.size(); ++I)
if (Args[I] != Other.Args[I])
return false;
return true;
}
friend hash_code hash_value(const SpecSig &S) {
return hash_combine(hash_value(S.Key),
hash_combine_range(S.Args.begin(), S.Args.end()));
}
};
// Specialization instance.
struct Spec {
// Original function.
Function *F;
// Cloned function, a specialized version of the original one.
Function *Clone = nullptr;
// Specialization signature.
SpecSig Sig;
// Profitability of the specialization.
InstructionCost Gain;
// List of call sites, matching this specialization.
SmallVector<CallBase *> CallSites;
Spec(Function *F, const SpecSig &S, InstructionCost G)
: F(F), Sig(S), Gain(G) {}
Spec(Function *F, const SpecSig &&S, InstructionCost G)
: F(F), Sig(S), Gain(G) {}
};
// Map of potential specializations for each function. The FunctionSpecializer
// keeps the discovered specialisation opportunities for the module in a single
// vector, where the specialisations of each function form a contiguous range.
// This map's value is the beginning and the end of that range.
using SpecMap = DenseMap<Function *, std::pair<unsigned, unsigned>>;
class FunctionSpecializer {
/// The IPSCCP Solver.
SCCPSolver &Solver;
Module &M;
/// Analysis manager, needed to invalidate analyses.
FunctionAnalysisManager *FAM;
/// Analyses used to help determine if a function should be specialized.
std::function<const TargetLibraryInfo &(Function &)> GetTLI;
std::function<TargetTransformInfo &(Function &)> GetTTI;
std::function<AssumptionCache &(Function &)> GetAC;
// The number of functions specialised, used for collecting statistics and
// also in the cost model.
unsigned NbFunctionsSpecialized = 0;
SmallPtrSet<Function *, 32> SpecializedFuncs;
SmallPtrSet<Function *, 32> FullySpecialized;
DenseMap<Function *, CodeMetrics> FunctionMetrics;
public:
FunctionSpecializer(
SCCPSolver &Solver, Module &M, FunctionAnalysisManager *FAM,
std::function<const TargetLibraryInfo &(Function &)> GetTLI,
std::function<TargetTransformInfo &(Function &)> GetTTI,
std::function<AssumptionCache &(Function &)> GetAC)
: Solver(Solver), M(M), FAM(FAM), GetTLI(GetTLI), GetTTI(GetTTI),
GetAC(GetAC) {}
~FunctionSpecializer() {
// Eliminate dead code.
removeDeadFunctions();
cleanUpSSA();
}
bool isClonedFunction(Function *F) { return SpecializedFuncs.count(F); }
bool run();
private:
Constant *getPromotableAlloca(AllocaInst *Alloca, CallInst *Call);
/// A constant stack value is an AllocaInst that has a single constant
/// value stored to it. Return this constant if such an alloca stack value
/// is a function argument.
Constant *getConstantStackValue(CallInst *Call, Value *Val);
/// Iterate over the argument tracked functions see if there
/// are any new constant values for the call instruction via
/// stack variables.
void promoteConstantStackValues();
/// Clean up fully specialized functions.
void removeDeadFunctions();
/// Remove any ssa_copy intrinsics that may have been introduced.
void cleanUpSSA();
// Compute the code metrics for function \p F.
CodeMetrics &analyzeFunction(Function *F);
/// @brief Find potential specialization opportunities.
/// @param F Function to specialize
/// @param Cost Cost of specializing a function. Final gain is this cost
/// minus benefit
/// @param AllSpecs A vector to add potential specializations to.
/// @param SM A map for a function's specialisation range
/// @return True, if any potential specializations were found
bool findSpecializations(Function *F, InstructionCost Cost,
SmallVectorImpl<Spec> &AllSpecs, SpecMap &SM);
bool isCandidateFunction(Function *F);
/// @brief Create a specialization of \p F and prime the SCCPSolver
/// @param F Function to specialize
/// @param S Which specialization to create
/// @return The new, cloned function
Function *createSpecialization(Function *F, const SpecSig &S);
/// Compute and return the cost of specializing function \p F.
InstructionCost getSpecializationCost(Function *F);
/// Compute a bonus for replacing argument \p A with constant \p C.
InstructionCost getSpecializationBonus(Argument *A, Constant *C,
const LoopInfo &LI);
/// Determine if it is possible to specialise the function for constant values
/// of the formal parameter \p A.
bool isArgumentInteresting(Argument *A);
/// Check if the value \p V (an actual argument) is a constant or can only
/// have a constant value. Return that constant.
Constant *getCandidateConstant(Value *V);
/// @brief Find and update calls to \p F, which match a specialization
/// @param F Orginal function
/// @param Begin Start of a range of possibly matching specialisations
/// @param End End of a range (exclusive) of possibly matching specialisations
void updateCallSites(Function *F, const Spec *Begin, const Spec *End);
};
} // namespace llvm
#endif // LLVM_TRANSFORMS_IPO_FUNCTIONSPECIALIZATION_H