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