Skip to content

Commit e6b9fc4

Browse files
[FuncSpec] Global ranking of specialisations
The `FunctionSpecialization` pass chooses specializations among the opportunities presented by a single function and its calls, progressively penalizing subsequent specialization attempts by artificially increasing the cost of a specialization, depending on how many specialization were applied before. Thus the chosen specializations are sensitive to the order the functions appear in the module and may be worse than others, had those others been considered earlier. This patch makes the `FunctionSpecialization` pass rank the specializations globally, i.e. choose the "best" specializations among the all possible specializations in the module, for all functions. Since this involved quite a bit of redesign of the pass data structures, this patch also carries: * removal of duplicate specializations * optimization of call sites update, by collecting per specialization the list of call sites that can be directly rewritten, without prior expensive check if the call constants and their positions match those of the specialized function. A bit of a write-up up about the FuncSpec data structures and operation: Each potential function specialisation is kept in a single vector (`AllSpecs` in `FunctionSpecializer::run`). This vector is populated by `FunctionSpecializer::findSpecializations`. The `findSpecializations` member function has a local `DenseMap` to eliminate duplicates - with each call to the current function, `findSpecializations` builds a specialisation signature (`SpecSig`) and looks it in the duplicates map. If the signature is present, the function records the call to rewrite into the existing specialisation instance. If the signature is absent, it means we have a new specialisation instance - the function calculates the gain and creates a new entry in `AllSpecs`. Negative gain specialisation are ignored at this point, unless forced. The potential specialisations for a function form a contiguous range in the `AllSpecs` [1]. This range is recorded in `SpecMap SM`, so we can quickly find all specialisations for a function. Once we have all the potential specialisations with their gains we need to choose the best ones, which fit in the module specialisation budget. This is done by using a max-heap (`std::make_heap`, `std::push_heap`, etc) to find the best `NSpec` specialisations with a single traversal of the `AllSpecs` vector. The heap itself is contained with a small vector (`BestSpecs`) of indices into `AllSpecs`, since elements of `AllSpecs` are a bit too heavy to shuffle around. Next the chosen specialisation are performed, that is, functions cloned, `SCCPSolver` primed, and known call sites updated. Then we run the `SCCPSolver` to propagate constants in the cloned functions, after which we walk the calls of the original functions to update them to call the specialised functions. --- [1] This range may contain specialisation that were discarded and is not ordered in any way. One alternative design is to keep a vector indices of all specialisations for this function (which would initially be, `i`, `i+1`, `i+2`, etc) and later sort them by gain, pushing non-applied ones to the back. This has the potential to speed `updateCallSites` up. Reviewed By: ChuanqiXu, labrinea Differential Revision: https://reviews.llvm.org/D139346 Change-Id: I708851eb38f07c42066637085b833ca91b195998
1 parent 3a43e68 commit e6b9fc4

File tree

7 files changed

+339
-157
lines changed

7 files changed

+339
-157
lines changed

llvm/include/llvm/Transforms/IPO/FunctionSpecialization.h

Lines changed: 67 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -60,18 +60,57 @@
6060
using namespace llvm;
6161

6262
namespace llvm {
63-
// Bookkeeping struct to pass data from the analysis and profitability phase
64-
// to the actual transform helper functions.
65-
struct SpecializationInfo {
66-
SmallVector<ArgInfo, 8> Args; // Stores the {formal,actual} argument pairs.
67-
InstructionCost Gain; // Profitability: Gain = Bonus - Cost.
68-
Function *Clone; // The definition of the specialized function.
63+
// Specialization signature, used to uniquely designate a specialization within
64+
// a function.
65+
struct SpecSig {
66+
// Hashing support, used to distinguish between ordinary, empty, or tombstone
67+
// keys.
68+
unsigned Key = 0;
69+
SmallVector<ArgInfo, 4> Args;
70+
71+
bool operator==(const SpecSig &Other) const {
72+
if (Key != Other.Key || Args.size() != Other.Args.size())
73+
return false;
74+
for (size_t I = 0; I < Args.size(); ++I)
75+
if (Args[I] != Other.Args[I])
76+
return false;
77+
return true;
78+
}
79+
80+
friend hash_code hash_value(const SpecSig &S) {
81+
return hash_combine(hash_value(S.Key),
82+
hash_combine_range(S.Args.begin(), S.Args.end()));
83+
}
84+
};
85+
86+
// Specialization instance.
87+
struct Spec {
88+
// Original function.
89+
Function *F;
90+
91+
// Cloned function, a specialized version of the original one.
92+
Function *Clone = nullptr;
93+
94+
// Specialization signature.
95+
SpecSig Sig;
96+
97+
// Profitability of the specialization.
98+
InstructionCost Gain;
99+
100+
// List of call sites, matching this specialization.
101+
SmallVector<CallBase *> CallSites;
102+
103+
Spec(Function *F, const SpecSig &S, InstructionCost G)
104+
: F(F), Sig(S), Gain(G) {}
105+
Spec(Function *F, const SpecSig &&S, InstructionCost G)
106+
: F(F), Sig(S), Gain(G) {}
69107
};
70108

71-
using CallSpecBinding = std::pair<CallBase *, SpecializationInfo>;
72-
// We are using MapVector because it guarantees deterministic iteration
73-
// order across executions.
74-
using SpecializationMap = SmallMapVector<CallBase *, SpecializationInfo, 8>;
109+
// Map of potential specializations for each function. The FunctionSpecializer
110+
// keeps the discovered specialisation opportunities for the module in a single
111+
// vector, where the specialisations of each function form a contiguous range.
112+
// This map's value is the beginning and the end of that range.
113+
using SpecMap = DenseMap<Function *, std::pair<unsigned, unsigned>>;
75114

76115
class FunctionSpecializer {
77116

@@ -137,18 +176,23 @@ class FunctionSpecializer {
137176
// Compute the code metrics for function \p F.
138177
CodeMetrics &analyzeFunction(Function *F);
139178

140-
/// This function decides whether it's worthwhile to specialize function
141-
/// \p F based on the known constant values its arguments can take on. It
142-
/// only discovers potential specialization opportunities without actually
143-
/// applying them.
144-
///
145-
/// \returns true if any specializations have been found.
179+
/// @brief Find potential specialization opportunities.
180+
/// @param F Function to specialize
181+
/// @param Cost Cost of specializing a function. Final gain is this cost
182+
/// minus benefit
183+
/// @param AllSpecs A vector to add potential specializations to.
184+
/// @param SM A map for a function's specialisation range
185+
/// @return True, if any potential specializations were found
146186
bool findSpecializations(Function *F, InstructionCost Cost,
147-
SmallVectorImpl<CallSpecBinding> &WorkList);
187+
SmallVectorImpl<Spec> &AllSpecs, SpecMap &SM);
148188

149189
bool isCandidateFunction(Function *F);
150190

151-
Function *createSpecialization(Function *F, CallSpecBinding &Specialization);
191+
/// @brief Create a specialization of \p F and prime the SCCPSolver
192+
/// @param F Function to specialize
193+
/// @param S Which specialization to create
194+
/// @return The new, cloned function
195+
Function *createSpecialization(Function *F, const SpecSig &S);
152196

153197
/// Compute and return the cost of specializing function \p F.
154198
InstructionCost getSpecializationCost(Function *F);
@@ -165,9 +209,11 @@ class FunctionSpecializer {
165209
/// have a constant value. Return that constant.
166210
Constant *getCandidateConstant(Value *V);
167211

168-
/// Redirects callsites of function \p F to its specialized copies.
169-
void updateCallSites(Function *F,
170-
SmallVectorImpl<CallSpecBinding> &Specializations);
212+
/// @brief Find and update calls to \p F, which match a specialization
213+
/// @param F Orginal function
214+
/// @param Begin Start of a range of possibly matching specialisations
215+
/// @param End End of a range (exclusive) of possibly matching specialisations
216+
void updateCallSites(Function *F, const Spec *Begin, const Spec *End);
171217
};
172218
} // namespace llvm
173219

llvm/include/llvm/Transforms/Utils/SCCPSolver.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,17 @@ struct ArgInfo {
5252
Argument *Formal; // The Formal argument being analysed.
5353
Constant *Actual; // A corresponding actual constant argument.
5454

55-
ArgInfo(Argument *F, Constant *A) : Formal(F), Actual(A){};
55+
ArgInfo(Argument *F, Constant *A) : Formal(F), Actual(A) {}
56+
57+
bool operator==(const ArgInfo &Other) const {
58+
return Formal == Other.Formal && Actual == Other.Actual;
59+
}
60+
61+
bool operator!=(const ArgInfo &Other) const { return !(*this == Other); }
62+
63+
friend hash_code hash_value(const ArgInfo &A) {
64+
return hash_combine(hash_value(A.Formal), hash_value(A.Actual));
65+
}
5666
};
5767

5868
class SCCPInstVisitor;

0 commit comments

Comments
 (0)