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