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