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