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