1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
|
//===-- llvm-split: command line tool for testing module splitting --------===//
//
// 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 program can be used to test the llvm::SplitModule and
// TargetMachine::splitModule functions.
//
//===----------------------------------------------------------------------===//
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Bitcode/BitcodeWriter.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/IR/PassInstrumentation.h"
#include "llvm/IR/PassManager.h"
#include "llvm/IR/Verifier.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/MC/TargetRegistry.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/TargetSelect.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/WithColor.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetMachine.h"
#include "llvm/TargetParser/Triple.h"
#include "llvm/Transforms/IPO/GlobalDCE.h"
#include "llvm/Transforms/Utils/SplitModule.h"
#include "llvm/Transforms/Utils/SplitModuleByCategory.h"
using namespace llvm;
static cl::OptionCategory SplitCategory("Split Options");
static cl::opt<std::string> InputFilename(cl::Positional,
cl::desc("<input bitcode file>"),
cl::init("-"),
cl::value_desc("filename"),
cl::cat(SplitCategory));
static cl::opt<std::string> OutputFilename("o",
cl::desc("Override output filename"),
cl::value_desc("filename"),
cl::cat(SplitCategory));
static cl::opt<unsigned> NumOutputs("j", cl::Prefix, cl::init(2),
cl::desc("Number of output files"),
cl::cat(SplitCategory));
static cl::opt<bool>
PreserveLocals("preserve-locals", cl::Prefix, cl::init(false),
cl::desc("Split without externalizing locals"),
cl::cat(SplitCategory));
static cl::opt<bool>
RoundRobin("round-robin", cl::Prefix, cl::init(false),
cl::desc("Use round-robin distribution of functions to "
"modules instead of the default name-hash-based one"),
cl::cat(SplitCategory));
static cl::opt<std::string>
MTriple("mtriple",
cl::desc("Target triple. When present, a TargetMachine is created "
"and TargetMachine::splitModule is used instead of the "
"common SplitModule logic."),
cl::value_desc("triple"), cl::cat(SplitCategory));
static cl::opt<std::string>
MCPU("mcpu", cl::desc("Target CPU, ignored if --mtriple is not used"),
cl::value_desc("cpu"), cl::cat(SplitCategory));
enum class SplitByCategoryType {
SBCT_ByModuleId,
SBCT_ByKernel,
SBCT_None,
};
static cl::opt<SplitByCategoryType> SplitByCategory(
"split-by-category",
cl::desc("Split by category. If present, splitting by category is used "
"with the specified categorization type."),
cl::Optional, cl::init(SplitByCategoryType::SBCT_None),
cl::values(clEnumValN(SplitByCategoryType::SBCT_ByModuleId, "module-id",
"one output module per translation unit marked with "
"\"module-id\" attribute"),
clEnumValN(SplitByCategoryType::SBCT_ByKernel, "kernel",
"one output module per kernel")),
cl::cat(SplitCategory));
static cl::opt<bool> OutputAssembly{
"S", cl::desc("Write output as LLVM assembly"), cl::cat(SplitCategory)};
void writeStringToFile(StringRef Content, StringRef Path) {
std::error_code EC;
raw_fd_ostream OS(Path, EC);
if (EC) {
errs() << formatv("error opening file: {0}, error: {1}\n", Path,
EC.message());
exit(1);
}
OS << Content << "\n";
}
void writeModuleToFile(const Module &M, StringRef Path, bool OutputAssembly) {
int FD = -1;
if (std::error_code EC = sys::fs::openFileForWrite(Path, FD)) {
errs() << formatv("error opening file: {0}, error: {1}", Path, EC.message())
<< '\n';
exit(1);
}
raw_fd_ostream OS(FD, /*ShouldClose*/ true);
if (OutputAssembly)
M.print(OS, /*AssemblyAnnotationWriter*/ nullptr);
else
WriteBitcodeToFile(M, OS);
}
/// EntryPointCategorizer is used for splitting by category either by module-id
/// or by kernels. It doesn't provide categories for functions other than
/// kernels. Categorizer computes a string key for the given Function and
/// records the association between the string key and an integer category. If a
/// string key is already belongs to some category than the corresponding
/// integer category is returned.
class EntryPointCategorizer {
public:
EntryPointCategorizer(SplitByCategoryType Type) : Type(Type) {}
EntryPointCategorizer() = delete;
EntryPointCategorizer(EntryPointCategorizer &) = delete;
EntryPointCategorizer &operator=(const EntryPointCategorizer &) = delete;
EntryPointCategorizer(EntryPointCategorizer &&) = default;
EntryPointCategorizer &operator=(EntryPointCategorizer &&) = default;
/// Returns integer specifying the category for the given \p F.
/// If the given function isn't a kernel then returns std::nullopt.
std::optional<int> operator()(const Function &F) {
if (!isEntryPoint(F))
return std::nullopt; // skip the function.
auto StringKey = computeFunctionCategory(Type, F);
if (auto it = StrKeyToID.find(StringRef(StringKey)); it != StrKeyToID.end())
return it->second;
int ID = static_cast<int>(StrKeyToID.size());
return StrKeyToID.try_emplace(std::move(StringKey), ID).first->second;
}
private:
static bool isEntryPoint(const Function &F) {
if (F.isDeclaration())
return false;
return F.getCallingConv() == CallingConv::SPIR_KERNEL ||
F.getCallingConv() == CallingConv::AMDGPU_KERNEL ||
F.getCallingConv() == CallingConv::PTX_Kernel;
}
static SmallString<0> computeFunctionCategory(SplitByCategoryType Type,
const Function &F) {
static constexpr char ATTR_MODULE_ID[] = "module-id";
SmallString<0> Key;
switch (Type) {
case SplitByCategoryType::SBCT_ByKernel:
Key = F.getName().str();
break;
case SplitByCategoryType::SBCT_ByModuleId:
Key = F.getFnAttribute(ATTR_MODULE_ID).getValueAsString().str();
break;
default:
llvm_unreachable("unexpected mode.");
}
return Key;
}
private:
struct KeyInfo {
static SmallString<0> getEmptyKey() { return SmallString<0>(""); }
static SmallString<0> getTombstoneKey() { return SmallString<0>("-"); }
static bool isEqual(const SmallString<0> &LHS, const SmallString<0> &RHS) {
return LHS == RHS;
}
static unsigned getHashValue(const SmallString<0> &S) {
return llvm::hash_value(StringRef(S));
}
};
SplitByCategoryType Type;
DenseMap<SmallString<0>, int, KeyInfo> StrKeyToID;
};
void cleanupModule(Module &M) {
ModuleAnalysisManager MAM;
MAM.registerPass([&] { return PassInstrumentationAnalysis(); });
ModulePassManager MPM;
MPM.addPass(GlobalDCEPass()); // Delete unreachable globals.
MPM.run(M, MAM);
}
Error runSplitModuleByCategory(std::unique_ptr<Module> M) {
size_t OutputID = 0;
auto PostSplitCallback = [&](std::unique_ptr<Module> MPart) {
if (verifyModule(*MPart)) {
errs() << "Broken Module!\n";
exit(1);
}
// TODO: DCE is a crucial pass since it removes unused declarations.
// At the moment, LIT checking can't be perfomed without DCE.
cleanupModule(*MPart);
size_t ID = OutputID;
++OutputID;
StringRef ModuleSuffix = OutputAssembly ? ".ll" : ".bc";
std::string ModulePath =
(Twine(OutputFilename) + "_" + Twine(ID) + ModuleSuffix).str();
writeModuleToFile(*MPart, ModulePath, OutputAssembly);
};
auto Categorizer = EntryPointCategorizer(SplitByCategory);
splitModuleTransitiveFromEntryPoints(std::move(M), Categorizer,
PostSplitCallback);
return Error::success();
}
int main(int argc, char **argv) {
InitLLVM X(argc, argv);
LLVMContext Context;
SMDiagnostic Err;
cl::HideUnrelatedOptions({&SplitCategory, &getColorCategory()});
cl::ParseCommandLineOptions(argc, argv, "LLVM module splitter\n");
std::unique_ptr<TargetMachine> TM;
if (!MTriple.empty()) {
InitializeAllTargets();
InitializeAllTargetMCs();
std::string Error;
const Target *T = TargetRegistry::lookupTarget(MTriple, Error);
if (!T) {
errs() << "unknown target '" << MTriple << "': " << Error << "\n";
return 1;
}
TargetOptions Options;
TM = std::unique_ptr<TargetMachine>(T->createTargetMachine(
Triple(MTriple), MCPU, /*FS*/ "", Options, std::nullopt, std::nullopt));
}
std::unique_ptr<Module> M = parseIRFile(InputFilename, Err, Context);
if (!M) {
Err.print(argv[0], errs());
return 1;
}
unsigned I = 0;
const auto HandleModulePart = [&](std::unique_ptr<Module> MPart) {
std::error_code EC;
std::unique_ptr<ToolOutputFile> Out(
new ToolOutputFile(OutputFilename + utostr(I++), EC, sys::fs::OF_None));
if (EC) {
errs() << EC.message() << '\n';
exit(1);
}
if (verifyModule(*MPart, &errs())) {
errs() << "Broken module!\n";
exit(1);
}
WriteBitcodeToFile(*MPart, Out->os());
// Declare success.
Out->keep();
};
if (SplitByCategory != SplitByCategoryType::SBCT_None) {
auto E = runSplitModuleByCategory(std::move(M));
if (E) {
errs() << E << "\n";
Err.print(argv[0], errs());
return 1;
}
return 0;
}
if (TM) {
if (PreserveLocals) {
errs() << "warning: --preserve-locals has no effect when using "
"TargetMachine::splitModule\n";
}
if (RoundRobin)
errs() << "warning: --round-robin has no effect when using "
"TargetMachine::splitModule\n";
if (TM->splitModule(*M, NumOutputs, HandleModulePart))
return 0;
errs() << "warning: "
"TargetMachine::splitModule failed, falling back to default "
"splitModule implementation\n";
}
SplitModule(*M, NumOutputs, HandleModulePart, PreserveLocals, RoundRobin);
return 0;
}
|