//===-- llvm-tli-checker.cpp - Compare TargetLibraryInfo to SDK libraries -===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringMap.h" #include "llvm/ADT/Triple.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Config/llvm-config.h" #include "llvm/Demangle/Demangle.h" #include "llvm/Object/Archive.h" #include "llvm/Object/ELFObjectFile.h" #include "llvm/Option/ArgList.h" #include "llvm/Option/Option.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/Path.h" #include "llvm/Support/WithColor.h" using namespace llvm; using namespace llvm::object; // Command-line option boilerplate. namespace { enum ID { OPT_INVALID = 0, // This is not an option ID. #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ OPT_##ID, #include "Opts.inc" #undef OPTION }; #define PREFIX(NAME, VALUE) \ static constexpr StringLiteral NAME##_init[] = VALUE; \ static constexpr ArrayRef NAME(NAME##_init, \ std::size(NAME##_init) - 1); #include "Opts.inc" #undef PREFIX static constexpr opt::OptTable::Info InfoTable[] = { #define OPTION(PREFIX, NAME, ID, KIND, GROUP, ALIAS, ALIASARGS, FLAGS, PARAM, \ HELPTEXT, METAVAR, VALUES) \ { \ PREFIX, NAME, HELPTEXT, \ METAVAR, OPT_##ID, opt::Option::KIND##Class, \ PARAM, FLAGS, OPT_##GROUP, \ OPT_##ALIAS, ALIASARGS, VALUES}, #include "Opts.inc" #undef OPTION }; class TLICheckerOptTable : public opt::GenericOptTable { public: TLICheckerOptTable() : GenericOptTable(InfoTable) {} }; } // end anonymous namespace // We have three levels of reporting. enum class ReportKind { Error, // For argument parsing errors. Summary, // Report counts but not details. Discrepancy, // Report where TLI and the library differ. Full // Report for every known-to-TLI function. }; // Most of the ObjectFile interfaces return an Expected, so make it easy // to ignore errors. template static T unwrapIgnoreError(Expected E, T Default = T()) { if (E) return std::move(*E); // Sink the error and return a nothing value. consumeError(E.takeError()); return Default; } static void fail(const Twine &Message) { WithColor::error() << Message << '\n'; exit(EXIT_FAILURE); } // Some problem occurred with an archive member; complain and continue. static void reportArchiveChildIssue(const object::Archive::Child &C, int Index, StringRef ArchiveFilename) { // First get the member name. std::string ChildName; Expected NameOrErr = C.getName(); if (NameOrErr) ChildName = std::string(NameOrErr.get()); else { // Ignore the name-fetch error, just report the index. consumeError(NameOrErr.takeError()); ChildName = ""; } WithColor::warning() << ArchiveFilename << "(" << ChildName << "): member is not usable\n"; } // Return Name, and if Name is mangled, append "aka" and the demangled name. static std::string getPrintableName(StringRef Name) { std::string OutputName = "'"; OutputName += Name; OutputName += "'"; std::string DemangledName(demangle(Name.str())); if (Name != DemangledName) { OutputName += " aka "; OutputName += DemangledName; } return OutputName; } // Store all the names that TargetLibraryInfo knows about; the bool indicates // whether TLI has it marked as "available" for the target of interest. // This is a vector to preserve the sorted order for better reporting. struct TLINameList : std::vector> { // Record all the TLI info in the vector. void initialize(StringRef TargetTriple); // Print out what we found. void dump(); }; static TLINameList TLINames; void TLINameList::initialize(StringRef TargetTriple) { Triple T(TargetTriple); TargetLibraryInfoImpl TLII(T); TargetLibraryInfo TLI(TLII); reserve(LibFunc::NumLibFuncs); size_t NumAvailable = 0; for (unsigned FI = 0; FI != LibFunc::NumLibFuncs; ++FI) { LibFunc LF = (LibFunc)FI; bool Available = TLI.has(LF); // getName returns names only for available funcs. TLII.setAvailable(LF); emplace_back(TLI.getName(LF), Available); if (Available) ++NumAvailable; } outs() << "TLI knows " << LibFunc::NumLibFuncs << " symbols, " << NumAvailable << " available for '" << TargetTriple << "'\n"; } void TLINameList::dump() { // Assume this gets called after initialize(), so we have the above line of // output as a header. So, for example, no need to repeat the triple. for (auto &TLIName : TLINames) { outs() << (TLIName.second ? " " : "not ") << "available: " << getPrintableName(TLIName.first) << '\n'; } } // Store all the exported symbol names we found in the input libraries. // We use a map to get hashed lookup speed; the bool is meaningless. class SDKNameMap : public StringMap { void maybeInsertSymbol(const SymbolRef &S, const ObjectFile &O); void populateFromObject(ObjectFile *O); void populateFromArchive(Archive *A); public: void populateFromFile(StringRef LibDir, StringRef LibName); }; static SDKNameMap SDKNames; // Insert defined global function symbols into the map if valid. void SDKNameMap::maybeInsertSymbol(const SymbolRef &S, const ObjectFile &O) { SymbolRef::Type Type = unwrapIgnoreError(S.getType()); uint32_t Flags = unwrapIgnoreError(S.getFlags()); section_iterator Section = unwrapIgnoreError(S.getSection(), /*Default=*/O.section_end()); if (Type == SymbolRef::ST_Function && (Flags & SymbolRef::SF_Global) && Section != O.section_end()) { StringRef Name = unwrapIgnoreError(S.getName()); insert({ Name, true }); } } // Given an ObjectFile, extract the global function symbols. void SDKNameMap::populateFromObject(ObjectFile *O) { // FIXME: Support other formats. if (!O->isELF()) { WithColor::warning() << O->getFileName() << ": only ELF-format files are supported\n"; return; } const auto *ELF = cast(O); if (ELF->getEType() == ELF::ET_REL) { for (const auto &S : ELF->symbols()) maybeInsertSymbol(S, *O); } else { for (const auto &S : ELF->getDynamicSymbolIterators()) maybeInsertSymbol(S, *O); } } // Unpack an archive and populate from the component object files. // This roughly imitates dumpArchive() from llvm-objdump.cpp. void SDKNameMap::populateFromArchive(Archive *A) { Error Err = Error::success(); int Index = -1; for (const auto &C : A->children(Err)) { ++Index; Expected> ChildOrErr = C.getAsBinary(); if (!ChildOrErr) { if (auto E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError())) { // Issue a generic warning. consumeError(std::move(E)); reportArchiveChildIssue(C, Index, A->getFileName()); } continue; } if (ObjectFile *O = dyn_cast(&*ChildOrErr.get())) populateFromObject(O); // Ignore non-object archive members. } if (Err) WithColor::defaultErrorHandler(std::move(Err)); } // Unpack a library file and extract the global function names. void SDKNameMap::populateFromFile(StringRef LibDir, StringRef LibName) { // Pick an arbitrary but reasonable default size. SmallString<255> Filepath(LibDir); sys::path::append(Filepath, LibName); if (!sys::fs::exists(Filepath)) { WithColor::warning() << StringRef(Filepath) << ": not found\n"; return; } outs() << "\nLooking for symbols in '" << StringRef(Filepath) << "'\n"; auto ExpectedBinary = createBinary(Filepath); if (!ExpectedBinary) { // FIXME: Report this better. WithColor::defaultWarningHandler(ExpectedBinary.takeError()); return; } OwningBinary OBinary = std::move(*ExpectedBinary); Binary &Binary = *OBinary.getBinary(); size_t Precount = size(); if (Archive *A = dyn_cast(&Binary)) populateFromArchive(A); else if (ObjectFile *O = dyn_cast(&Binary)) populateFromObject(O); else { WithColor::warning() << StringRef(Filepath) << ": not an archive or object file\n"; return; } if (Precount == size()) WithColor::warning() << StringRef(Filepath) << ": no symbols found\n"; else outs() << "Found " << size() - Precount << " global function symbols in '" << StringRef(Filepath) << "'\n"; } int main(int argc, char *argv[]) { InitLLVM X(argc, argv); BumpPtrAllocator A; StringSaver Saver(A); TLICheckerOptTable Tbl; opt::InputArgList Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, [&](StringRef Msg) { fail(Msg); }); if (Args.hasArg(OPT_help)) { std::string Usage(argv[0]); Usage += " [options] library-file [library-file...]"; Tbl.printHelp(outs(), Usage.c_str(), "LLVM TargetLibraryInfo versus SDK checker"); outs() << "\nPass @FILE as argument to read options or library names from " "FILE.\n"; return 0; } TLINames.initialize(Args.getLastArgValue(OPT_triple_EQ)); // --dump-tli doesn't require any input files. if (Args.hasArg(OPT_dump_tli)) { TLINames.dump(); return 0; } std::vector LibList = Args.getAllArgValues(OPT_INPUT); if (LibList.empty()) fail("no input files\n"); StringRef LibDir = Args.getLastArgValue(OPT_libdir_EQ); bool SeparateMode = Args.hasArg(OPT_separate); ReportKind ReportLevel = SeparateMode ? ReportKind::Summary : ReportKind::Discrepancy; if (const opt::Arg *A = Args.getLastArg(OPT_report_EQ)) { ReportLevel = StringSwitch(A->getValue()) .Case("summary", ReportKind::Summary) .Case("discrepancy", ReportKind::Discrepancy) .Case("full", ReportKind::Full) .Default(ReportKind::Error); if (ReportLevel == ReportKind::Error) fail(Twine("invalid option for --report: ", StringRef(A->getValue()))); } for (size_t I = 0; I < LibList.size(); ++I) { // In SeparateMode we report on input libraries individually; otherwise // we do one big combined search. Reading to the end of LibList here // will cause the outer while loop to terminate cleanly. if (SeparateMode) { SDKNames.clear(); SDKNames.populateFromFile(LibDir, LibList[I]); if (SDKNames.empty()) continue; } else { do SDKNames.populateFromFile(LibDir, LibList[I]); while (++I < LibList.size()); if (SDKNames.empty()) { WithColor::error() << "NO symbols found!\n"; break; } outs() << "Found a grand total of " << SDKNames.size() << " library symbols\n"; } unsigned TLIdoesSDKdoesnt = 0; unsigned TLIdoesntSDKdoes = 0; unsigned TLIandSDKboth = 0; unsigned TLIandSDKneither = 0; for (auto &TLIName : TLINames) { bool TLIHas = TLIName.second; bool SDKHas = SDKNames.count(TLIName.first) == 1; int Which = int(TLIHas) * 2 + int(SDKHas); switch (Which) { case 0: ++TLIandSDKneither; break; case 1: ++TLIdoesntSDKdoes; break; case 2: ++TLIdoesSDKdoesnt; break; case 3: ++TLIandSDKboth; break; } // If the results match, report only if user requested a full report. ReportKind Threshold = TLIHas == SDKHas ? ReportKind::Full : ReportKind::Discrepancy; if (Threshold <= ReportLevel) { constexpr char YesNo[2][4] = {"no ", "yes"}; constexpr char Indicator[4][3] = {"!!", ">>", "<<", "=="}; outs() << Indicator[Which] << " TLI " << YesNo[TLIHas] << " SDK " << YesNo[SDKHas] << ": " << getPrintableName(TLIName.first) << '\n'; } } assert(TLIandSDKboth + TLIandSDKneither + TLIdoesSDKdoesnt + TLIdoesntSDKdoes == LibFunc::NumLibFuncs); (void) TLIandSDKneither; outs() << "<< Total TLI yes SDK no: " << TLIdoesSDKdoesnt << "\n>> Total TLI no SDK yes: " << TLIdoesntSDKdoes << "\n== Total TLI yes SDK yes: " << TLIandSDKboth; if (TLIandSDKboth == 0) { outs() << " *** NO TLI SYMBOLS FOUND"; if (SeparateMode) outs() << " in '" << LibList[I] << "'"; } outs() << '\n'; if (!SeparateMode) { if (TLIdoesSDKdoesnt == 0 && TLIdoesntSDKdoes == 0) outs() << "PASS: LLVM TLI matched SDK libraries successfully.\n"; else outs() << "FAIL: LLVM TLI doesn't match SDK libraries.\n"; } } }