xref: /llvm-project/llvm/tools/llvm-tli-checker/llvm-tli-checker.cpp (revision 62dd488164f5b68cce1ac3825f857b0108476c3c)
1 //===-- llvm-tli-checker.cpp - Compare TargetLibraryInfo to SDK libraries -===//
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 #include "llvm/ADT/SmallString.h"
10 #include "llvm/ADT/StringMap.h"
11 #include "llvm/ADT/Triple.h"
12 #include "llvm/Analysis/TargetLibraryInfo.h"
13 #include "llvm/Config/llvm-config.h"
14 #include "llvm/Demangle/Demangle.h"
15 #include "llvm/Object/Archive.h"
16 #include "llvm/Object/ELFObjectFile.h"
17 #include "llvm/Option/ArgList.h"
18 #include "llvm/Option/Option.h"
19 #include "llvm/Support/FileSystem.h"
20 #include "llvm/Support/InitLLVM.h"
21 #include "llvm/Support/Path.h"
22 #include "llvm/Support/WithColor.h"
23 
24 using namespace llvm;
25 using namespace llvm::object;
26 
27 // Command-line option boilerplate.
28 namespace {
29 enum ID {
30   OPT_INVALID = 0, // This is not an option ID.
31 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
32                HELPTEXT, METAVAR, VALUES)                                      \
33   OPT_##ID,
34 #include "Opts.inc"
35 #undef OPTION
36 };
37 
38 #define PREFIX(NAME, VALUE) const char *const NAME[] = VALUE;
39 #include "Opts.inc"
40 #undef PREFIX
41 
42 const opt::OptTable::Info InfoTable[] = {
43 #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM,  \
44                HELPTEXT, METAVAR, VALUES)                                      \
45   {                                                                            \
46       PREFIX,      NAME,      HELPTEXT,                                        \
47       METAVAR,     OPT_##ID,  opt::Option::KIND##Class,                        \
48       PARAM,       FLAGS,     OPT_##GROUP,                                     \
49       OPT_##ALIAS, ALIASARGS, VALUES},
50 #include "Opts.inc"
51 #undef OPTION
52 };
53 
54 class TLICheckerOptTable : public opt::OptTable {
55 public:
56   TLICheckerOptTable() : OptTable(InfoTable) {}
57 };
58 } // namespace
59 
60 // We have three levels of reporting.
61 enum class ReportKind {
62   Error,       // For argument parsing errors.
63   Summary,     // Report counts but not details.
64   Discrepancy, // Report where TLI and the library differ.
65   Full         // Report for every known-to-TLI function.
66 };
67 
68 // Most of the ObjectFile interfaces return an Expected<T>, so make it easy
69 // to ignore those.
70 template <typename T> T unwrapIgnoreError(Expected<T> E) {
71   if (E)
72     return std::move(*E);
73   // Sink the error and return a nothing value.
74   consumeError(E.takeError());
75   return T();
76 }
77 
78 static void fail(const Twine &Message) {
79   WithColor::error() << Message << '\n';
80   exit(EXIT_FAILURE);
81 }
82 
83 // Some problem occurred with an archive member; complain and continue.
84 static void reportArchiveChildIssue(const object::Archive::Child &C, int Index,
85                                     StringRef ArchiveFilename) {
86   // First get the member name.
87   std::string ChildName;
88   Expected<StringRef> NameOrErr = C.getName();
89   if (NameOrErr)
90     ChildName = std::string(NameOrErr.get());
91   else {
92     // Ignore the name-fetch error, just report the index.
93     consumeError(NameOrErr.takeError());
94     ChildName = "<file index: " + std::to_string(Index) + ">";
95   }
96 
97   WithColor::warning() << ArchiveFilename << "(" << ChildName
98                        << "): member is not usable\n";
99 }
100 
101 // Return Name, and if Name is mangled, append "aka" and the demangled name.
102 static std::string PrintableName(StringRef Name) {
103   std::string OutputName = "'";
104   OutputName += Name;
105   OutputName += "'";
106   if (Name.startswith("_Z") || Name.startswith("??")) {
107     OutputName += " aka ";
108     OutputName += demangle(Name.str());
109   }
110   return OutputName;
111 }
112 
113 // Store all the names that TargetLibraryInfo knows about; the bool indicates
114 // whether TLI has it marked as "available" for the target of interest.
115 // This is a vector to preserve the sorted order for better reporting.
116 struct TLINameList : std::vector<std::pair<StringRef, bool>> {
117   // Record all the TLI info in the vector.
118   void initialize(StringRef TargetTriple);
119   // Print out what we found.
120   void dump();
121 };
122 TLINameList TLINames;
123 
124 void TLINameList::initialize(StringRef TargetTriple) {
125   Triple T(TargetTriple);
126   TargetLibraryInfoImpl TLII(T);
127   TargetLibraryInfo TLI(TLII);
128 
129   reserve(LibFunc::NumLibFuncs);
130   size_t NumAvailable = 0;
131   for (unsigned FI = 0; FI != LibFunc::NumLibFuncs; ++FI) {
132     LibFunc LF = (LibFunc)FI;
133     bool Available = TLI.has(LF);
134     // getName returns names only for available funcs.
135     TLII.setAvailable(LF);
136     emplace_back(TLI.getName(LF), Available);
137     if (Available)
138       ++NumAvailable;
139   }
140   outs() << "TLI knows " << LibFunc::NumLibFuncs << " symbols, " << NumAvailable
141          << " available for '" << TargetTriple << "'\n";
142 }
143 
144 void TLINameList::dump() {
145   // Assume this gets called after initialize(), so we have the above line of
146   // output as a header.  So, for example, no need to repeat the triple.
147   for (auto &TLIName : TLINames) {
148     outs() << (TLIName.second ? "    " : "not ")
149            << "available: " << PrintableName(TLIName.first) << '\n';
150   }
151 }
152 
153 // Store all the exported symbol names we found in the input libraries.
154 // We use a map to get hashed lookup speed; the bool is meaningless.
155 class SDKNameMap : public StringMap<bool> {
156   void populateFromObject(ObjectFile *O);
157   void populateFromArchive(Archive *A);
158 
159 public:
160   void populateFromFile(StringRef LibDir, StringRef LibName);
161 };
162 SDKNameMap SDKNames;
163 
164 // Given an ObjectFile, extract the global function symbols.
165 void SDKNameMap::populateFromObject(ObjectFile *O) {
166   // FIXME: Support COFF.
167   if (!O->isELF()) {
168     WithColor::warning() << "Only ELF-format files are supported\n";
169     return;
170   }
171   auto *ELF = cast<const ELFObjectFileBase>(O);
172 
173   for (auto I = ELF->getDynamicSymbolIterators().begin();
174        I != ELF->getDynamicSymbolIterators().end(); ++I) {
175     // We want only global function symbols.
176     SymbolRef::Type Type = unwrapIgnoreError(I->getType());
177     uint32_t Flags = unwrapIgnoreError(I->getFlags());
178     StringRef Name = unwrapIgnoreError(I->getName());
179     if (Type == SymbolRef::ST_Function && (Flags & SymbolRef::SF_Global))
180       insert({Name, true});
181   }
182 }
183 
184 // Unpack an archive and populate from the component object files.
185 // This roughly imitates dumpArchive() from llvm-objdump.cpp.
186 void SDKNameMap::populateFromArchive(Archive *A) {
187   Error Err = Error::success();
188   int Index = -1;
189   for (auto &C : A->children(Err)) {
190     ++Index;
191     Expected<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary();
192     if (!ChildOrErr) {
193       if (auto E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError())) {
194         // Issue a generic warning.
195         consumeError(std::move(E));
196         reportArchiveChildIssue(C, Index, A->getFileName());
197       }
198       continue;
199     }
200     if (ObjectFile *O = dyn_cast<ObjectFile>(&*ChildOrErr.get()))
201       populateFromObject(O);
202     // Ignore non-object archive members.
203   }
204   if (Err)
205     WithColor::defaultErrorHandler(std::move(Err));
206 }
207 
208 // Unpack a library file and extract the global function names.
209 void SDKNameMap::populateFromFile(StringRef LibDir, StringRef LibName) {
210   // Pick an arbitrary but reasonable default size.
211   SmallString<255> Filepath(LibDir);
212   sys::path::append(Filepath, LibName);
213   if (!sys::fs::exists(Filepath)) {
214     WithColor::warning() << "Could not find '" << StringRef(Filepath) << "'\n";
215     return;
216   }
217   outs() << "\nLooking for symbols in '" << StringRef(Filepath) << "'\n";
218   auto ExpectedBinary = createBinary(Filepath);
219   if (!ExpectedBinary) {
220     // FIXME: Report this better.
221     WithColor::defaultWarningHandler(ExpectedBinary.takeError());
222     return;
223   }
224   OwningBinary<Binary> OBinary = std::move(*ExpectedBinary);
225   Binary &Binary = *OBinary.getBinary();
226   size_t Precount = size();
227   if (Archive *A = dyn_cast<Archive>(&Binary))
228     populateFromArchive(A);
229   else if (ObjectFile *O = dyn_cast<ObjectFile>(&Binary))
230     populateFromObject(O);
231   else {
232     WithColor::warning() << "Not an Archive or ObjectFile: '"
233                          << StringRef(Filepath) << "'\n";
234     return;
235   }
236   if (Precount == size())
237     WithColor::warning() << "No symbols found in '" << StringRef(Filepath)
238                          << "'\n";
239   else
240     outs() << "Found " << size() - Precount << " global function symbols in '"
241            << StringRef(Filepath) << "'\n";
242 }
243 
244 int main(int argc, char *argv[]) {
245   InitLLVM X(argc, argv);
246   BumpPtrAllocator A;
247   StringSaver Saver(A);
248   TLICheckerOptTable Tbl;
249   opt::InputArgList Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver,
250                                          [&](StringRef Msg) { fail(Msg); });
251 
252   if (Args.hasArg(OPT_help)) {
253     std::string Usage(argv[0]);
254     Usage += " [options] library-file [library-file...]";
255     Tbl.printHelp(outs(), Usage.c_str(),
256                   "LLVM TargetLibraryInfo versus SDK checker");
257     outs() << "\nPass @FILE as argument to read options or library names from "
258               "FILE.\n";
259     return 0;
260   }
261 
262   TLINames.initialize(Args.getLastArgValue(OPT_triple_EQ));
263 
264   // --dump-tli doesn't require any input files.
265   if (Args.hasArg(OPT_dump_tli)) {
266     TLINames.dump();
267     return 0;
268   }
269 
270   std::vector<std::string> LibList = Args.getAllArgValues(OPT_INPUT);
271   if (LibList.empty()) {
272     WithColor::error() << "No input files\n";
273     exit(EXIT_FAILURE);
274   }
275   StringRef LibDir = Args.getLastArgValue(OPT_libdir_EQ);
276   bool SeparateMode = Args.hasArg(OPT_separate);
277 
278   ReportKind ReportLevel =
279       SeparateMode ? ReportKind::Summary : ReportKind::Discrepancy;
280   if (const opt::Arg *A = Args.getLastArg(OPT_report_EQ)) {
281     ReportLevel = StringSwitch<ReportKind>(A->getValue())
282                       .Case("summary", ReportKind::Summary)
283                       .Case("discrepancy", ReportKind::Discrepancy)
284                       .Case("full", ReportKind::Full)
285                       .Default(ReportKind::Error);
286     if (ReportLevel == ReportKind::Error) {
287       WithColor::error() << "invalid option for --report: " << A->getValue();
288       exit(EXIT_FAILURE);
289     }
290   }
291 
292   for (size_t I = 0; I < LibList.size(); ++I) {
293     // In SeparateMode we report on input libraries individually; otherwise
294     // we do one big combined search.  Reading to the end of LibList here
295     // will cause the outer while loop to terminate cleanly.
296     if (SeparateMode) {
297       SDKNames.clear();
298       SDKNames.populateFromFile(LibDir, LibList[I]);
299       if (SDKNames.empty())
300         continue;
301     } else {
302       do
303         SDKNames.populateFromFile(LibDir, LibList[I]);
304       while (++I < LibList.size());
305       if (SDKNames.empty()) {
306         WithColor::error() << "NO symbols found!\n";
307         break;
308       }
309       outs() << "Found a grand total of " << SDKNames.size()
310              << " library symbols\n";
311     }
312     unsigned TLIdoesSDKdoesnt = 0;
313     unsigned TLIdoesntSDKdoes = 0;
314     unsigned TLIandSDKboth = 0;
315     unsigned TLIandSDKneither = 0;
316     for (auto &TLIName : TLINames) {
317       bool TLIHas = TLIName.second;
318       bool SDKHas = SDKNames.count(TLIName.first) == 1;
319       int Which = int(TLIHas) * 2 + int(SDKHas);
320       switch (Which) {
321       case 0: ++TLIandSDKneither; break;
322       case 1: ++TLIdoesntSDKdoes; break;
323       case 2: ++TLIdoesSDKdoesnt; break;
324       case 3: ++TLIandSDKboth;    break;
325       }
326       // If the results match, report only if user requested a full report.
327       ReportKind Threshold =
328           TLIHas == SDKHas ? ReportKind::Full : ReportKind::Discrepancy;
329       if (Threshold <= ReportLevel) {
330         constexpr char YesNo[2][4] = {"no ", "yes"};
331         constexpr char Indicator[4][3] = {"!!", ">>", "<<", "=="};
332         outs() << Indicator[Which] << " TLI " << YesNo[TLIHas] << " SDK "
333                << YesNo[SDKHas] << ": " << PrintableName(TLIName.first) << '\n';
334       }
335     }
336 
337     assert(TLIandSDKboth + TLIandSDKneither + TLIdoesSDKdoesnt +
338                TLIdoesntSDKdoes ==
339            LibFunc::NumLibFuncs);
340     outs() << "<< Total TLI yes SDK no:  " << TLIdoesSDKdoesnt
341            << "\n>> Total TLI no  SDK yes: " << TLIdoesntSDKdoes
342            << "\n== Total TLI yes SDK yes: " << TLIandSDKboth;
343     if (TLIandSDKboth == 0) {
344       outs() << " *** NO TLI SYMBOLS FOUND";
345       if (SeparateMode)
346         outs() << " in '" << LibList[I] << "'";
347     }
348     outs() << '\n';
349 
350     if (!SeparateMode) {
351       if (TLIdoesSDKdoesnt == 0 && TLIdoesntSDKdoes == 0)
352         outs() << "PASS: LLVM TLI matched SDK libraries successfully.\n";
353       else
354         outs() << "FAIL: LLVM TLI doesn't match SDK libraries.\n";
355     }
356   }
357 }
358