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