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