369 lines · cpp
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-exception6//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 OPTION34};35 36#define OPTTABLE_STR_TABLE_CODE37#include "Opts.inc"38#undef OPTTABLE_STR_TABLE_CODE39 40#define OPTTABLE_PREFIXES_TABLE_CODE41#include "Opts.inc"42#undef OPTTABLE_PREFIXES_TABLE_CODE43 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 OPTION49};50 51class TLICheckerOptTable : public opt::GenericOptTable {52public:53 TLICheckerOptTable()54 : GenericOptTable(OptionStrTable, OptionPrefixesTable, InfoTable) {}55};56} // end anonymous namespace57 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 easy67// 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(NameOrErr.takeError());93 ChildName = "<file index: " + std::to_string(Index) + ">";94 }95 96 WithColor::warning() << ArchiveFilename << "(" << ChildName97 << "): 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(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 of115 // 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(static_cast<LibFunc>(FI)))118 ++NumAvailable;119 }120 121 outs() << "TLI knows " << (LibFunc::End_LibFunc - LibFunc::Begin_LibFunc)122 << " symbols, " << NumAvailable << " available for '" << TargetTriple123 << "'\n";124}125 126static void dumpTLIEntries(const TargetLibraryInfo &TLI) {127 // Assume this gets called after initialize(), so we have the above line of128 // 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(LF);132 StringRef FuncName = TargetLibraryInfo::getStandardName(LF);133 134 outs() << (IsAvailable ? " " : "not ") << "available: ";135 printPrintableName(outs(), FuncName) << '\n';136 }137}138 139// Store all the exported symbol names we found in the input libraries.140// We use a map to get hashed lookup speed; the bool is meaningless.141class SDKNameMap : public StringMap<bool> {142 void maybeInsertSymbol(const SymbolRef &S, const ObjectFile &O);143 void populateFromObject(ObjectFile *O);144 void populateFromArchive(Archive *A);145 146public:147 void populateFromFile(StringRef LibDir, StringRef LibName);148};149static SDKNameMap SDKNames;150 151// Insert defined global function symbols into the map if valid.152void SDKNameMap::maybeInsertSymbol(const SymbolRef &S, const ObjectFile &O) {153 SymbolRef::Type Type = unwrapIgnoreError(S.getType());154 uint32_t Flags = unwrapIgnoreError(S.getFlags());155 section_iterator Section = unwrapIgnoreError(S.getSection(),156 /*Default=*/O.section_end());157 bool IsRegularFunction = Type == SymbolRef::ST_Function &&158 (Flags & SymbolRef::SF_Global) &&159 Section != O.section_end();160 bool IsIFunc =161 Type == SymbolRef::ST_Other && (Flags & SymbolRef::SF_Indirect);162 if (IsRegularFunction || IsIFunc) {163 StringRef Name = unwrapIgnoreError(S.getName());164 insert({ Name, true });165 }166}167 168// Given an ObjectFile, extract the global function symbols.169void SDKNameMap::populateFromObject(ObjectFile *O) {170 // FIXME: Support other formats.171 if (!O->isELF()) {172 WithColor::warning() << O->getFileName()173 << ": only ELF-format files are supported\n";174 return;175 }176 const auto *ELF = cast<ELFObjectFileBase>(O);177 178 if (ELF->getEType() == ELF::ET_REL) {179 for (const auto &S : ELF->symbols())180 maybeInsertSymbol(S, *O);181 } else {182 for (const auto &S : ELF->getDynamicSymbolIterators())183 maybeInsertSymbol(S, *O);184 }185}186 187// Unpack an archive and populate from the component object files.188// This roughly imitates dumpArchive() from llvm-objdump.cpp.189void SDKNameMap::populateFromArchive(Archive *A) {190 Error Err = Error::success();191 int Index = -1;192 for (const auto &C : A->children(Err)) {193 ++Index;194 Expected<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary();195 if (!ChildOrErr) {196 if (auto E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError())) {197 // Issue a generic warning.198 consumeError(std::move(E));199 reportArchiveChildIssue(C, Index, A->getFileName());200 }201 continue;202 }203 if (ObjectFile *O = dyn_cast<ObjectFile>(&*ChildOrErr.get()))204 populateFromObject(O);205 // Ignore non-object archive members.206 }207 if (Err)208 WithColor::defaultErrorHandler(std::move(Err));209}210 211// Unpack a library file and extract the global function names.212void SDKNameMap::populateFromFile(StringRef LibDir, StringRef LibName) {213 // Pick an arbitrary but reasonable default size.214 SmallString<255> Filepath(LibDir);215 sys::path::append(Filepath, LibName);216 if (!sys::fs::exists(Filepath)) {217 WithColor::warning() << StringRef(Filepath) << ": not found\n";218 return;219 }220 outs() << "\nLooking for symbols in '" << StringRef(Filepath) << "'\n";221 auto ExpectedBinary = createBinary(Filepath);222 if (!ExpectedBinary) {223 // FIXME: Report this better.224 WithColor::defaultWarningHandler(ExpectedBinary.takeError());225 return;226 }227 OwningBinary<Binary> OBinary = std::move(*ExpectedBinary);228 Binary &Binary = *OBinary.getBinary();229 size_t Precount = size();230 if (Archive *A = dyn_cast<Archive>(&Binary))231 populateFromArchive(A);232 else if (ObjectFile *O = dyn_cast<ObjectFile>(&Binary))233 populateFromObject(O);234 else {235 WithColor::warning() << StringRef(Filepath)236 << ": not an archive or object file\n";237 return;238 }239 if (Precount == size())240 WithColor::warning() << StringRef(Filepath) << ": no symbols found\n";241 else242 outs() << "Found " << size() - Precount << " global function symbols in '"243 << StringRef(Filepath) << "'\n";244}245 246int main(int argc, char *argv[]) {247 InitLLVM X(argc, argv);248 BumpPtrAllocator A;249 StringSaver Saver(A);250 TLICheckerOptTable Tbl;251 opt::InputArgList Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver,252 [&](StringRef Msg) { fail(Msg); });253 254 if (Args.hasArg(OPT_help)) {255 std::string Usage(argv[0]);256 Usage += " [options] library-file [library-file...]";257 Tbl.printHelp(outs(), Usage.c_str(),258 "LLVM TargetLibraryInfo versus SDK checker");259 outs() << "\nPass @FILE as argument to read options or library names from "260 "FILE.\n";261 return 0;262 }263 264 StringRef TripleStr = Args.getLastArgValue(OPT_triple_EQ);265 Triple TargetTriple(TripleStr);266 TargetLibraryInfoImpl TLII(TargetTriple);267 TargetLibraryInfo TLI(TLII);268 269 reportNumberOfEntries(TLI, TripleStr);270 271 // --dump-tli doesn't require any input files.272 if (Args.hasArg(OPT_dump_tli)) {273 dumpTLIEntries(TLI);274 return 0;275 }276 277 std::vector<std::string> LibList = Args.getAllArgValues(OPT_INPUT);278 if (LibList.empty())279 fail("no input files\n");280 StringRef LibDir = Args.getLastArgValue(OPT_libdir_EQ);281 bool SeparateMode = Args.hasArg(OPT_separate);282 283 ReportKind ReportLevel =284 SeparateMode ? ReportKind::Summary : ReportKind::Discrepancy;285 if (const opt::Arg *A = Args.getLastArg(OPT_report_EQ)) {286 ReportLevel = StringSwitch<ReportKind>(A->getValue())287 .Case("summary", ReportKind::Summary)288 .Case("discrepancy", ReportKind::Discrepancy)289 .Case("full", ReportKind::Full)290 .Default(ReportKind::Error);291 if (ReportLevel == ReportKind::Error)292 fail(Twine("invalid option for --report: ", StringRef(A->getValue())));293 }294 295 for (size_t I = 0; I < LibList.size(); ++I) {296 // In SeparateMode we report on input libraries individually; otherwise297 // we do one big combined search. Reading to the end of LibList here298 // will cause the outer while loop to terminate cleanly.299 if (SeparateMode) {300 SDKNames.clear();301 SDKNames.populateFromFile(LibDir, LibList[I]);302 if (SDKNames.empty())303 continue;304 } else {305 do306 SDKNames.populateFromFile(LibDir, LibList[I]);307 while (++I < LibList.size());308 if (SDKNames.empty()) {309 WithColor::error() << "NO symbols found!\n";310 break;311 }312 outs() << "Found a grand total of " << SDKNames.size()313 << " library symbols\n";314 }315 unsigned TLIdoesSDKdoesnt = 0;316 unsigned TLIdoesntSDKdoes = 0;317 unsigned TLIandSDKboth = 0;318 unsigned TLIandSDKneither = 0;319 320 for (unsigned FI = LibFunc::Begin_LibFunc; FI != LibFunc::End_LibFunc;321 ++FI) {322 LibFunc LF = static_cast<LibFunc>(FI);323 324 StringRef TLIName = TLI.getStandardName(LF);325 bool TLIHas = TLI.has(LF);326 bool SDKHas = SDKNames.count(TLIName) == 1;327 int Which = int(TLIHas) * 2 + int(SDKHas);328 switch (Which) {329 case 0: ++TLIandSDKneither; break;330 case 1: ++TLIdoesntSDKdoes; break;331 case 2: ++TLIdoesSDKdoesnt; break;332 case 3: ++TLIandSDKboth; break;333 }334 // If the results match, report only if user requested a full report.335 ReportKind Threshold =336 TLIHas == SDKHas ? ReportKind::Full : ReportKind::Discrepancy;337 if (Threshold <= ReportLevel) {338 constexpr char YesNo[2][4] = {"no ", "yes"};339 constexpr char Indicator[4][3] = {"!!", ">>", "<<", "=="};340 outs() << Indicator[Which] << " TLI " << YesNo[TLIHas] << " SDK "341 << YesNo[SDKHas] << ": ";342 printPrintableName(outs(), TLIName);343 outs() << '\n';344 }345 }346 347 assert(TLIandSDKboth + TLIandSDKneither + TLIdoesSDKdoesnt +348 TLIdoesntSDKdoes ==349 LibFunc::End_LibFunc - LibFunc::Begin_LibFunc);350 (void) TLIandSDKneither;351 outs() << "<< Total TLI yes SDK no: " << TLIdoesSDKdoesnt352 << "\n>> Total TLI no SDK yes: " << TLIdoesntSDKdoes353 << "\n== Total TLI yes SDK yes: " << TLIandSDKboth;354 if (TLIandSDKboth == 0) {355 outs() << " *** NO TLI SYMBOLS FOUND";356 if (SeparateMode)357 outs() << " in '" << LibList[I] << "'";358 }359 outs() << '\n';360 361 if (!SeparateMode) {362 if (TLIdoesSDKdoesnt == 0 && TLIdoesntSDKdoes == 0)363 outs() << "PASS: LLVM TLI matched SDK libraries successfully.\n";364 else365 outs() << "FAIL: LLVM TLI doesn't match SDK libraries.\n";366 }367 }368}369