1//===-- llvm-cgdata.cpp - LLVM CodeGen Data Tool --------------------------===//
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// llvm-cgdata parses raw codegen data embedded in compiled binary files, and
10// merges them into a single .cgdata file. It can also inspect and maninuplate
11// a .cgdata file. This .cgdata can contain various codegen data like outlining
12// information, and it can be used to optimize the code in the subsequent build.
13//
14//===----------------------------------------------------------------------===//
15#include "llvm/ADT/StringRef.h"
16#include "llvm/CGData/CodeGenDataReader.h"
17#include "llvm/CGData/CodeGenDataWriter.h"
18#include "llvm/IR/LLVMContext.h"
19#include "llvm/Object/Archive.h"
20#include "llvm/Object/Binary.h"
21#include "llvm/Option/ArgList.h"
22#include "llvm/Option/Option.h"
23#include "llvm/Support/CommandLine.h"
24#include "llvm/Support/Driver.h"
25#include "llvm/Support/LibraryOptionsContext.h"
26#include "llvm/Support/Path.h"
27#include "llvm/Support/VirtualFileSystem.h"
28#include "llvm/Support/WithColor.h"
29#include "llvm/Support/raw_ostream.h"
30
31using namespace llvm;
32using namespace llvm::object;
33
34enum CGDataFormat {
35 Invalid,
36 Text,
37 Binary,
38};
39
40enum CGDataAction {
41 Convert,
42 Merge,
43 Show,
44};
45
46// Command-line option boilerplate.
47namespace {
48enum ID {
49 OPT_INVALID = 0, // This is not an option ID.
50#define OPTION(...) LLVM_MAKE_OPT_ID(__VA_ARGS__),
51#include "Opts.inc"
52#undef OPTION
53};
54
55using namespace llvm::opt;
56#define OPTTABLE_CODE
57#include "Opts.inc"
58
59class CGDataOptTable : public opt::OptTable {
60public:
61 CGDataOptTable() : OptTable(optionTables()) {}
62};
63} // end anonymous namespace
64
65// Options
66static StringRef ToolName;
67static std::string OutputFilename = "-";
68static std::string Filename;
69static bool ShowCGDataVersion;
70static bool SkipTrim;
71static bool LazyLoading;
72static CGDataAction Action;
73static std::optional<CGDataFormat> OutputFormat;
74static std::vector<std::string> InputFilenames;
75
76static void exitWithError(Twine Message, StringRef Whence = "",
77 StringRef Hint = "") {
78 WithColor::error();
79 if (!Whence.empty())
80 errs() << Whence << ": ";
81 errs() << Message << "\n";
82 if (!Hint.empty())
83 WithColor::note() << Hint << "\n";
84 ::exit(status: 1);
85}
86
87static void exitWithError(Error E, StringRef Whence = "") {
88 if (E.isA<CGDataError>()) {
89 handleAllErrors(E: std::move(E), Handlers: [&](const CGDataError &IPE) {
90 exitWithError(Message: IPE.message(), Whence);
91 });
92 return;
93 }
94
95 exitWithError(Message: toString(E: std::move(E)), Whence);
96}
97
98static void exitWithErrorCode(std::error_code EC, StringRef Whence = "") {
99 exitWithError(Message: EC.message(), Whence);
100}
101
102static int convert_main(int argc, const char *argv[]) {
103 std::error_code EC;
104 raw_fd_ostream OS(OutputFilename, EC,
105 OutputFormat == CGDataFormat::Text
106 ? sys::fs::OF_TextWithCRLF
107 : sys::fs::OF_None);
108 if (EC)
109 exitWithErrorCode(EC, Whence: OutputFilename);
110
111 auto FS = vfs::getRealFileSystem();
112 auto ReaderOrErr = CodeGenDataReader::create(LibraryOptions: cl::LibraryOptionsContext(),
113 Path: Filename, FS&: *FS, LazyLoading);
114 if (Error E = ReaderOrErr.takeError())
115 exitWithError(E: std::move(E), Whence: Filename);
116
117 CodeGenDataWriter Writer;
118 auto Reader = ReaderOrErr->get();
119 if (Reader->hasOutlinedHashTree()) {
120 OutlinedHashTreeRecord Record(Reader->releaseOutlinedHashTree());
121 Writer.addRecord(Record);
122 }
123 if (Reader->hasStableFunctionMap()) {
124 StableFunctionMapRecord Record(Reader->releaseStableFunctionMap());
125 Writer.addRecord(Record);
126 }
127
128 if (OutputFormat == CGDataFormat::Text) {
129 if (Error E = Writer.writeText(OS))
130 exitWithError(E: std::move(E));
131 } else {
132 if (Error E = Writer.write(OS))
133 exitWithError(E: std::move(E));
134 }
135
136 return 0;
137}
138
139static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
140 OutlinedHashTreeRecord &GlobalOutlineRecord,
141 StableFunctionMapRecord &GlobalFunctionMapRecord);
142
143static bool handleArchive(StringRef Filename, Archive &Arch,
144 OutlinedHashTreeRecord &GlobalOutlineRecord,
145 StableFunctionMapRecord &GlobalFunctionMapRecord) {
146 bool Result = true;
147 Error Err = Error::success();
148 for (const auto &Child : Arch.children(Err)) {
149 auto BuffOrErr = Child.getMemoryBufferRef();
150 if (Error E = BuffOrErr.takeError())
151 exitWithError(E: std::move(E), Whence: Filename);
152 auto NameOrErr = Child.getName();
153 if (Error E = NameOrErr.takeError())
154 exitWithError(E: std::move(E), Whence: Filename);
155 std::string Name = (Filename + "(" + NameOrErr.get() + ")").str();
156 Result &= handleBuffer(Filename: Name, Buffer: BuffOrErr.get(), GlobalOutlineRecord,
157 GlobalFunctionMapRecord);
158 }
159 if (Err)
160 exitWithError(E: std::move(Err), Whence: Filename);
161 return Result;
162}
163
164static bool handleBuffer(StringRef Filename, MemoryBufferRef Buffer,
165 OutlinedHashTreeRecord &GlobalOutlineRecord,
166 StableFunctionMapRecord &GlobalFunctionMapRecord) {
167 Expected<std::unique_ptr<object::Binary>> BinOrErr =
168 object::createBinary(Source: Buffer);
169 if (Error E = BinOrErr.takeError())
170 exitWithError(E: std::move(E), Whence: Filename);
171
172 bool Result = true;
173 if (auto *Obj = dyn_cast<ObjectFile>(Val: BinOrErr->get())) {
174 if (Error E = CodeGenDataReader::mergeFromObjectFile(
175 Obj, GlobalOutlineRecord, GlobalFunctionMapRecord))
176 exitWithError(E: std::move(E), Whence: Filename);
177 } else if (auto *Arch = dyn_cast<Archive>(Val: BinOrErr->get())) {
178 Result &= handleArchive(Filename, Arch&: *Arch, GlobalOutlineRecord,
179 GlobalFunctionMapRecord);
180 } else {
181 // TODO: Support for the MachO universal binary format.
182 errs() << "Error: unsupported binary file: " << Filename << "\n";
183 Result = false;
184 }
185
186 return Result;
187}
188
189static bool handleFile(StringRef Filename,
190 OutlinedHashTreeRecord &GlobalOutlineRecord,
191 StableFunctionMapRecord &GlobalFunctionMapRecord) {
192 ErrorOr<std::unique_ptr<MemoryBuffer>> BuffOrErr =
193 MemoryBuffer::getFileOrSTDIN(Filename);
194 if (std::error_code EC = BuffOrErr.getError())
195 exitWithErrorCode(EC, Whence: Filename);
196 return handleBuffer(Filename, Buffer: *BuffOrErr.get(), GlobalOutlineRecord,
197 GlobalFunctionMapRecord);
198}
199
200static int merge_main(int argc, const char *argv[]) {
201 bool Result = true;
202 OutlinedHashTreeRecord GlobalOutlineRecord;
203 StableFunctionMapRecord GlobalFunctionMapRecord;
204 for (auto &Filename : InputFilenames)
205 Result &=
206 handleFile(Filename, GlobalOutlineRecord, GlobalFunctionMapRecord);
207
208 if (!Result)
209 exitWithError(Message: "failed to merge codegen data files.");
210
211 GlobalFunctionMapRecord.finalize(LibraryOptions: cl::LibraryOptionsContext(), SkipTrim);
212
213 CodeGenDataWriter Writer;
214 if (!GlobalOutlineRecord.empty())
215 Writer.addRecord(Record&: GlobalOutlineRecord);
216 if (!GlobalFunctionMapRecord.empty())
217 Writer.addRecord(Record&: GlobalFunctionMapRecord);
218
219 std::error_code EC;
220 raw_fd_ostream OS(OutputFilename, EC,
221 OutputFormat == CGDataFormat::Text
222 ? sys::fs::OF_TextWithCRLF
223 : sys::fs::OF_None);
224 if (EC)
225 exitWithErrorCode(EC, Whence: OutputFilename);
226
227 if (OutputFormat == CGDataFormat::Text) {
228 if (Error E = Writer.writeText(OS))
229 exitWithError(E: std::move(E));
230 } else {
231 if (Error E = Writer.write(OS))
232 exitWithError(E: std::move(E));
233 }
234
235 return 0;
236}
237
238static int show_main(int argc, const char *argv[]) {
239 std::error_code EC;
240 raw_fd_ostream OS(OutputFilename.data(), EC, sys::fs::OF_TextWithCRLF);
241 if (EC)
242 exitWithErrorCode(EC, Whence: OutputFilename);
243
244 auto FS = vfs::getRealFileSystem();
245 auto ReaderOrErr = CodeGenDataReader::create(LibraryOptions: cl::LibraryOptionsContext(),
246 Path: Filename, FS&: *FS, LazyLoading);
247 if (Error E = ReaderOrErr.takeError())
248 exitWithError(E: std::move(E), Whence: Filename);
249
250 auto Reader = ReaderOrErr->get();
251 if (ShowCGDataVersion)
252 OS << "Version: " << Reader->getVersion() << "\n";
253
254 if (Reader->hasOutlinedHashTree()) {
255 auto Tree = Reader->releaseOutlinedHashTree();
256 OS << "Outlined hash tree:\n";
257 OS << " Total Node Count: " << Tree->size() << "\n";
258 OS << " Terminal Node Count: " << Tree->size(/*GetTerminalCountOnly=*/true)
259 << "\n";
260 OS << " Depth: " << Tree->depth() << "\n";
261 }
262 if (Reader->hasStableFunctionMap()) {
263 auto Map = Reader->releaseStableFunctionMap();
264 OS << "Stable function map:\n";
265 OS << " Unique hash Count: " << Map->size() << "\n";
266 OS << " Total function Count: "
267 << Map->size(Type: StableFunctionMap::TotalFunctionCount) << "\n";
268 OS << " Mergeable function Count: "
269 << Map->size(Type: StableFunctionMap::MergeableFunctionCount) << "\n";
270 }
271
272 return 0;
273}
274
275static void parseArgs(int argc, char **argv) {
276 CGDataOptTable Tbl;
277 ToolName = argv[0];
278 llvm::BumpPtrAllocator A;
279 llvm::StringSaver Saver{A};
280 llvm::opt::InputArgList Args =
281 Tbl.parseArgs(Argc: argc, Argv: argv, Unknown: OPT_UNKNOWN, Saver, ErrorFn: [&](StringRef Msg) {
282 llvm::errs() << Msg << '\n';
283 std::exit(status: 1);
284 });
285
286 if (Args.hasArg(Ids: OPT_help)) {
287 Tbl.printHelp(
288 OS&: llvm::outs(),
289 Usage: "llvm-cgdata <action> [options] (<binary files>|<.cgdata file>)",
290 Title: ToolName.str().c_str());
291 std::exit(status: 0);
292 }
293 if (Args.hasArg(Ids: OPT_version)) {
294 cl::PrintVersionMessage();
295 std::exit(status: 0);
296 }
297
298 ShowCGDataVersion = Args.hasArg(Ids: OPT_cgdata_version);
299 SkipTrim = Args.hasArg(Ids: OPT_skip_trim);
300
301 if (opt::Arg *A = Args.getLastArg(Ids: OPT_format)) {
302 StringRef OF = A->getValue();
303 OutputFormat = StringSwitch<CGDataFormat>(OF)
304 .Case(S: "text", Value: CGDataFormat::Text)
305 .Case(S: "binary", Value: CGDataFormat::Binary)
306 .Default(Value: CGDataFormat::Invalid);
307 if (OutputFormat == CGDataFormat::Invalid)
308 exitWithError(Message: "unsupported format '" + OF + "'");
309 }
310
311 InputFilenames = Args.getAllArgValues(Id: OPT_INPUT);
312 if (InputFilenames.empty())
313 exitWithError(Message: "No input file is specified.");
314 Filename = InputFilenames[0];
315
316 if (Args.hasArg(Ids: OPT_output)) {
317 OutputFilename = Args.getLastArgValue(Id: OPT_output);
318 for (auto &Filename : InputFilenames)
319 if (Filename == OutputFilename)
320 exitWithError(
321 Message: "Input file name cannot be the same as the output file name!\n");
322 }
323
324 opt::Arg *ActionArg = nullptr;
325 for (opt::Arg *Arg : Args.filtered(Ids: OPT_action_group)) {
326 if (ActionArg)
327 exitWithError(Message: "Only one action is allowed.");
328 ActionArg = Arg;
329 }
330 if (!ActionArg)
331 exitWithError(Message: "One action is required.");
332
333 switch (ActionArg->getOption().getID()) {
334 case OPT_show:
335 if (InputFilenames.size() != 1)
336 exitWithError(Message: "only one input file is allowed.");
337 Action = CGDataAction::Show;
338 break;
339 case OPT_convert:
340 // The default output format is text for convert.
341 if (!OutputFormat)
342 OutputFormat = CGDataFormat::Text;
343 if (InputFilenames.size() != 1)
344 exitWithError(Message: "only one input file is allowed.");
345 Action = CGDataAction::Convert;
346 break;
347 case OPT_merge:
348 // The default output format is binary for merge.
349 if (!OutputFormat)
350 OutputFormat = CGDataFormat::Binary;
351 Action = CGDataAction::Merge;
352 break;
353 default:
354 llvm_unreachable("unrecognized action");
355 }
356
357 LazyLoading = Args.hasArg(Ids: OPT_indexed_codegen_data_lazy_loading);
358}
359
360int llvm_cgdata_main(int argc, char **argvNonConst, const llvm::ToolContext &) {
361 const char **argv = const_cast<const char **>(argvNonConst);
362 parseArgs(argc, argv: argvNonConst);
363
364 switch (Action) {
365 case CGDataAction::Convert:
366 return convert_main(argc, argv);
367 case CGDataAction::Merge:
368 return merge_main(argc, argv);
369 case CGDataAction::Show:
370 return show_main(argc, argv);
371 }
372
373 llvm_unreachable("unrecognized action");
374}
375