xref: /llvm-project/llvm/lib/CGData/StableFunctionMap.cpp (revision fe69a20cc1e46bf8473aaef1be8a1805c80fc9d4)
1 //===-- StableFunctionMap.cpp ---------------------------------------------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This implements the functionality for the StableFunctionMap class, which
10 // manages the mapping of stable function hashes to their metadata. It includes
11 // methods for inserting, merging, and finalizing function entries, as well as
12 // utilities for handling function names and IDs.
13 //
14 //===----------------------------------------------------------------------===//
15 
16 #include "llvm/CGData/StableFunctionMap.h"
17 #include "llvm/ADT/SmallSet.h"
18 #include "llvm/Support/CommandLine.h"
19 #include "llvm/Support/Debug.h"
20 
21 #define DEBUG_TYPE "stable-function-map"
22 
23 using namespace llvm;
24 
25 static cl::opt<unsigned>
26     GlobalMergingMinMerges("global-merging-min-merges",
27                            cl::desc("Minimum number of similar functions with "
28                                     "the same hash required for merging."),
29                            cl::init(2), cl::Hidden);
30 static cl::opt<unsigned> GlobalMergingMinInstrs(
31     "global-merging-min-instrs",
32     cl::desc("The minimum instruction count required when merging functions."),
33     cl::init(1), cl::Hidden);
34 static cl::opt<unsigned> GlobalMergingMaxParams(
35     "global-merging-max-params",
36     cl::desc(
37         "The maximum number of parameters allowed when merging functions."),
38     cl::init(std::numeric_limits<unsigned>::max()), cl::Hidden);
39 static cl::opt<bool> GlobalMergingSkipNoParams(
40     "global-merging-skip-no-params",
41     cl::desc("Skip merging functions with no parameters."), cl::init(true),
42     cl::Hidden);
43 static cl::opt<double> GlobalMergingInstOverhead(
44     "global-merging-inst-overhead",
45     cl::desc("The overhead cost associated with each instruction when lowering "
46              "to machine instruction."),
47     cl::init(1.2), cl::Hidden);
48 static cl::opt<double> GlobalMergingParamOverhead(
49     "global-merging-param-overhead",
50     cl::desc("The overhead cost associated with each parameter when merging "
51              "functions."),
52     cl::init(2.0), cl::Hidden);
53 static cl::opt<double>
54     GlobalMergingCallOverhead("global-merging-call-overhead",
55                               cl::desc("The overhead cost associated with each "
56                                        "function call when merging functions."),
57                               cl::init(1.0), cl::Hidden);
58 static cl::opt<double> GlobalMergingExtraThreshold(
59     "global-merging-extra-threshold",
60     cl::desc("An additional cost threshold that must be exceeded for merging "
61              "to be considered beneficial."),
62     cl::init(0.0), cl::Hidden);
63 
64 unsigned StableFunctionMap::getIdOrCreateForName(StringRef Name) {
65   auto It = NameToId.find(Name);
66   if (It != NameToId.end())
67     return It->second;
68   unsigned Id = IdToName.size();
69   assert(Id == NameToId.size() && "ID collision");
70   IdToName.emplace_back(Name.str());
71   NameToId[IdToName.back()] = Id;
72   return Id;
73 }
74 
75 std::optional<std::string> StableFunctionMap::getNameForId(unsigned Id) const {
76   if (Id >= IdToName.size())
77     return std::nullopt;
78   return IdToName[Id];
79 }
80 
81 void StableFunctionMap::insert(const StableFunction &Func) {
82   assert(!Finalized && "Cannot insert after finalization");
83   auto FuncNameId = getIdOrCreateForName(Func.FunctionName);
84   auto ModuleNameId = getIdOrCreateForName(Func.ModuleName);
85   auto IndexOperandHashMap = std::make_unique<IndexOperandHashMapType>();
86   for (auto &[Index, Hash] : Func.IndexOperandHashes)
87     (*IndexOperandHashMap)[Index] = Hash;
88   auto FuncEntry = std::make_unique<StableFunctionEntry>(
89       Func.Hash, FuncNameId, ModuleNameId, Func.InstCount,
90       std::move(IndexOperandHashMap));
91   insert(std::move(FuncEntry));
92 }
93 
94 void StableFunctionMap::merge(const StableFunctionMap &OtherMap) {
95   assert(!Finalized && "Cannot merge after finalization");
96   for (auto &[Hash, Funcs] : OtherMap.HashToFuncs) {
97     auto &ThisFuncs = HashToFuncs[Hash];
98     for (auto &Func : Funcs) {
99       auto FuncNameId =
100           getIdOrCreateForName(*OtherMap.getNameForId(Func->FunctionNameId));
101       auto ModuleNameId =
102           getIdOrCreateForName(*OtherMap.getNameForId(Func->ModuleNameId));
103       auto ClonedIndexOperandHashMap =
104           std::make_unique<IndexOperandHashMapType>(*Func->IndexOperandHashMap);
105       ThisFuncs.emplace_back(std::make_unique<StableFunctionEntry>(
106           Func->Hash, FuncNameId, ModuleNameId, Func->InstCount,
107           std::move(ClonedIndexOperandHashMap)));
108     }
109   }
110 }
111 
112 size_t StableFunctionMap::size(SizeType Type) const {
113   switch (Type) {
114   case UniqueHashCount:
115     return HashToFuncs.size();
116   case TotalFunctionCount: {
117     size_t Count = 0;
118     for (auto &Funcs : HashToFuncs)
119       Count += Funcs.second.size();
120     return Count;
121   }
122   case MergeableFunctionCount: {
123     size_t Count = 0;
124     for (auto &[Hash, Funcs] : HashToFuncs)
125       if (Funcs.size() >= 2)
126         Count += Funcs.size();
127     return Count;
128   }
129   }
130   llvm_unreachable("Unhandled size type");
131 }
132 
133 using ParamLocs = SmallVector<IndexPair>;
134 static void removeIdenticalIndexPair(
135     SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>> &SFS) {
136   auto &RSF = SFS[0];
137   unsigned StableFunctionCount = SFS.size();
138 
139   SmallVector<IndexPair> ToDelete;
140   for (auto &[Pair, Hash] : *(RSF->IndexOperandHashMap)) {
141     bool Identical = true;
142     for (unsigned J = 1; J < StableFunctionCount; ++J) {
143       auto &SF = SFS[J];
144       const auto &SHash = SF->IndexOperandHashMap->at(Pair);
145       if (Hash != SHash) {
146         Identical = false;
147         break;
148       }
149     }
150 
151     // No need to parameterize them if the hashes are identical across stable
152     // functions.
153     if (Identical)
154       ToDelete.emplace_back(Pair);
155   }
156 
157   for (auto &Pair : ToDelete)
158     for (auto &SF : SFS)
159       SF->IndexOperandHashMap->erase(Pair);
160 }
161 
162 static bool isProfitable(
163     const SmallVector<std::unique_ptr<StableFunctionMap::StableFunctionEntry>>
164         &SFS) {
165   unsigned StableFunctionCount = SFS.size();
166   if (StableFunctionCount < GlobalMergingMinMerges)
167     return false;
168 
169   unsigned InstCount = SFS[0]->InstCount;
170   if (InstCount < GlobalMergingMinInstrs)
171     return false;
172 
173   double Cost = 0.0;
174   SmallSet<stable_hash, 8> UniqueHashVals;
175   for (auto &SF : SFS) {
176     UniqueHashVals.clear();
177     for (auto &[IndexPair, Hash] : *SF->IndexOperandHashMap)
178       UniqueHashVals.insert(Hash);
179     unsigned ParamCount = UniqueHashVals.size();
180     if (ParamCount > GlobalMergingMaxParams)
181       return false;
182     // Theoretically, if ParamCount is 0, it results in identical code folding
183     // (ICF), which we can skip merging here since the linker already handles
184     // ICF. This pass would otherwise introduce unnecessary thunks that are
185     // merely direct jumps. However, enabling this could be beneficial depending
186     // on downstream passes, so we provide an option for it.
187     if (GlobalMergingSkipNoParams && ParamCount == 0)
188       return false;
189     Cost += ParamCount * GlobalMergingParamOverhead + GlobalMergingCallOverhead;
190   }
191   Cost += GlobalMergingExtraThreshold;
192 
193   double Benefit =
194       InstCount * (StableFunctionCount - 1) * GlobalMergingInstOverhead;
195   bool Result = Benefit > Cost;
196   LLVM_DEBUG(dbgs() << "isProfitable: Hash = " << SFS[0]->Hash << ", "
197                     << "StableFunctionCount = " << StableFunctionCount
198                     << ", InstCount = " << InstCount
199                     << ", Benefit = " << Benefit << ", Cost = " << Cost
200                     << ", Result = " << (Result ? "true" : "false") << "\n");
201   return Result;
202 }
203 
204 void StableFunctionMap::finalize(bool SkipTrim) {
205   for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It) {
206     auto &[StableHash, SFS] = *It;
207 
208     // Group stable functions by ModuleIdentifier.
209     std::stable_sort(SFS.begin(), SFS.end(),
210                      [&](const std::unique_ptr<StableFunctionEntry> &L,
211                          const std::unique_ptr<StableFunctionEntry> &R) {
212                        return *getNameForId(L->ModuleNameId) <
213                               *getNameForId(R->ModuleNameId);
214                      });
215 
216     // Consider the first function as the root function.
217     auto &RSF = SFS[0];
218 
219     bool Invalid = false;
220     unsigned StableFunctionCount = SFS.size();
221     for (unsigned I = 1; I < StableFunctionCount; ++I) {
222       auto &SF = SFS[I];
223       assert(RSF->Hash == SF->Hash);
224       if (RSF->InstCount != SF->InstCount) {
225         Invalid = true;
226         break;
227       }
228       if (RSF->IndexOperandHashMap->size() != SF->IndexOperandHashMap->size()) {
229         Invalid = true;
230         break;
231       }
232       for (auto &P : *RSF->IndexOperandHashMap) {
233         auto &InstOpndIndex = P.first;
234         if (!SF->IndexOperandHashMap->count(InstOpndIndex)) {
235           Invalid = true;
236           break;
237         }
238       }
239     }
240     if (Invalid) {
241       HashToFuncs.erase(It);
242       continue;
243     }
244 
245     if (SkipTrim)
246       continue;
247 
248     // Trim the index pair that has the same operand hash across
249     // stable functions.
250     removeIdenticalIndexPair(SFS);
251 
252     if (!isProfitable(SFS))
253       HashToFuncs.erase(It);
254   }
255 
256   Finalized = true;
257 }
258