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