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 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 } // 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 those. 70 template <typename T> T unwrapIgnoreError(Expected<T> E) { 71 if (E) 72 return std::move(*E); 73 // Sink the error and return a nothing value. 74 consumeError(E.takeError()); 75 return T(); 76 } 77 78 static void fail(const Twine &Message) { 79 WithColor::error() << Message << '\n'; 80 exit(EXIT_FAILURE); 81 } 82 83 // Some problem occurred with an archive member; complain and continue. 84 static void reportArchiveChildIssue(const object::Archive::Child &C, int Index, 85 StringRef ArchiveFilename) { 86 // First get the member name. 87 std::string ChildName; 88 Expected<StringRef> NameOrErr = C.getName(); 89 if (NameOrErr) 90 ChildName = std::string(NameOrErr.get()); 91 else { 92 // Ignore the name-fetch error, just report the index. 93 consumeError(NameOrErr.takeError()); 94 ChildName = "<file index: " + std::to_string(Index) + ">"; 95 } 96 97 WithColor::warning() << ArchiveFilename << "(" << ChildName 98 << "): member is not usable\n"; 99 } 100 101 // Return Name, and if Name is mangled, append "aka" and the demangled name. 102 static std::string PrintableName(StringRef Name) { 103 std::string OutputName = "'"; 104 OutputName += Name; 105 OutputName += "'"; 106 if (Name.startswith("_Z") || Name.startswith("??")) { 107 OutputName += " aka "; 108 OutputName += demangle(Name.str()); 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 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: " << PrintableName(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 populateFromObject(ObjectFile *O); 157 void populateFromArchive(Archive *A); 158 159 public: 160 void populateFromFile(StringRef LibDir, StringRef LibName); 161 }; 162 SDKNameMap SDKNames; 163 164 // Given an ObjectFile, extract the global function symbols. 165 void SDKNameMap::populateFromObject(ObjectFile *O) { 166 // FIXME: Support COFF. 167 if (!O->isELF()) { 168 WithColor::warning() << "Only ELF-format files are supported\n"; 169 return; 170 } 171 auto *ELF = cast<const ELFObjectFileBase>(O); 172 173 for (auto I = ELF->getDynamicSymbolIterators().begin(); 174 I != ELF->getDynamicSymbolIterators().end(); ++I) { 175 // We want only global function symbols. 176 SymbolRef::Type Type = unwrapIgnoreError(I->getType()); 177 uint32_t Flags = unwrapIgnoreError(I->getFlags()); 178 StringRef Name = unwrapIgnoreError(I->getName()); 179 if (Type == SymbolRef::ST_Function && (Flags & SymbolRef::SF_Global)) 180 insert({Name, true}); 181 } 182 } 183 184 // Unpack an archive and populate from the component object files. 185 // This roughly imitates dumpArchive() from llvm-objdump.cpp. 186 void SDKNameMap::populateFromArchive(Archive *A) { 187 Error Err = Error::success(); 188 int Index = -1; 189 for (auto &C : A->children(Err)) { 190 ++Index; 191 Expected<std::unique_ptr<object::Binary>> ChildOrErr = C.getAsBinary(); 192 if (!ChildOrErr) { 193 if (auto E = isNotObjectErrorInvalidFileType(ChildOrErr.takeError())) { 194 // Issue a generic warning. 195 consumeError(std::move(E)); 196 reportArchiveChildIssue(C, Index, A->getFileName()); 197 } 198 continue; 199 } 200 if (ObjectFile *O = dyn_cast<ObjectFile>(&*ChildOrErr.get())) 201 populateFromObject(O); 202 // Ignore non-object archive members. 203 } 204 if (Err) 205 WithColor::defaultErrorHandler(std::move(Err)); 206 } 207 208 // Unpack a library file and extract the global function names. 209 void SDKNameMap::populateFromFile(StringRef LibDir, StringRef LibName) { 210 // Pick an arbitrary but reasonable default size. 211 SmallString<255> Filepath(LibDir); 212 sys::path::append(Filepath, LibName); 213 if (!sys::fs::exists(Filepath)) { 214 WithColor::warning() << "Could not find '" << StringRef(Filepath) << "'\n"; 215 return; 216 } 217 outs() << "\nLooking for symbols in '" << StringRef(Filepath) << "'\n"; 218 auto ExpectedBinary = createBinary(Filepath); 219 if (!ExpectedBinary) { 220 // FIXME: Report this better. 221 WithColor::defaultWarningHandler(ExpectedBinary.takeError()); 222 return; 223 } 224 OwningBinary<Binary> OBinary = std::move(*ExpectedBinary); 225 Binary &Binary = *OBinary.getBinary(); 226 size_t Precount = size(); 227 if (Archive *A = dyn_cast<Archive>(&Binary)) 228 populateFromArchive(A); 229 else if (ObjectFile *O = dyn_cast<ObjectFile>(&Binary)) 230 populateFromObject(O); 231 else { 232 WithColor::warning() << "Not an Archive or ObjectFile: '" 233 << StringRef(Filepath) << "'\n"; 234 return; 235 } 236 if (Precount == size()) 237 WithColor::warning() << "No symbols found in '" << StringRef(Filepath) 238 << "'\n"; 239 else 240 outs() << "Found " << size() - Precount << " global function symbols in '" 241 << StringRef(Filepath) << "'\n"; 242 } 243 244 int main(int argc, char *argv[]) { 245 InitLLVM X(argc, argv); 246 BumpPtrAllocator A; 247 StringSaver Saver(A); 248 TLICheckerOptTable Tbl; 249 opt::InputArgList Args = Tbl.parseArgs(argc, argv, OPT_UNKNOWN, Saver, 250 [&](StringRef Msg) { fail(Msg); }); 251 252 if (Args.hasArg(OPT_help)) { 253 std::string Usage(argv[0]); 254 Usage += " [options] library-file [library-file...]"; 255 Tbl.printHelp(outs(), Usage.c_str(), 256 "LLVM TargetLibraryInfo versus SDK checker"); 257 outs() << "\nPass @FILE as argument to read options or library names from " 258 "FILE.\n"; 259 return 0; 260 } 261 262 TLINames.initialize(Args.getLastArgValue(OPT_triple_EQ)); 263 264 // --dump-tli doesn't require any input files. 265 if (Args.hasArg(OPT_dump_tli)) { 266 TLINames.dump(); 267 return 0; 268 } 269 270 std::vector<std::string> LibList = Args.getAllArgValues(OPT_INPUT); 271 if (LibList.empty()) { 272 WithColor::error() << "No input files\n"; 273 exit(EXIT_FAILURE); 274 } 275 StringRef LibDir = Args.getLastArgValue(OPT_libdir_EQ); 276 bool SeparateMode = Args.hasArg(OPT_separate); 277 278 ReportKind ReportLevel = 279 SeparateMode ? ReportKind::Summary : ReportKind::Discrepancy; 280 if (const opt::Arg *A = Args.getLastArg(OPT_report_EQ)) { 281 ReportLevel = StringSwitch<ReportKind>(A->getValue()) 282 .Case("summary", ReportKind::Summary) 283 .Case("discrepancy", ReportKind::Discrepancy) 284 .Case("full", ReportKind::Full) 285 .Default(ReportKind::Error); 286 if (ReportLevel == ReportKind::Error) { 287 WithColor::error() << "invalid option for --report: " << A->getValue(); 288 exit(EXIT_FAILURE); 289 } 290 } 291 292 for (size_t I = 0; I < LibList.size(); ++I) { 293 // In SeparateMode we report on input libraries individually; otherwise 294 // we do one big combined search. Reading to the end of LibList here 295 // will cause the outer while loop to terminate cleanly. 296 if (SeparateMode) { 297 SDKNames.clear(); 298 SDKNames.populateFromFile(LibDir, LibList[I]); 299 if (SDKNames.empty()) 300 continue; 301 } else { 302 do 303 SDKNames.populateFromFile(LibDir, LibList[I]); 304 while (++I < LibList.size()); 305 if (SDKNames.empty()) { 306 WithColor::error() << "NO symbols found!\n"; 307 break; 308 } 309 outs() << "Found a grand total of " << SDKNames.size() 310 << " library symbols\n"; 311 } 312 unsigned TLIdoesSDKdoesnt = 0; 313 unsigned TLIdoesntSDKdoes = 0; 314 unsigned TLIandSDKboth = 0; 315 unsigned TLIandSDKneither = 0; 316 for (auto &TLIName : TLINames) { 317 bool TLIHas = TLIName.second; 318 bool SDKHas = SDKNames.count(TLIName.first) == 1; 319 int Which = int(TLIHas) * 2 + int(SDKHas); 320 switch (Which) { 321 case 0: ++TLIandSDKneither; break; 322 case 1: ++TLIdoesntSDKdoes; break; 323 case 2: ++TLIdoesSDKdoesnt; break; 324 case 3: ++TLIandSDKboth; break; 325 } 326 // If the results match, report only if user requested a full report. 327 ReportKind Threshold = 328 TLIHas == SDKHas ? ReportKind::Full : ReportKind::Discrepancy; 329 if (Threshold <= ReportLevel) { 330 constexpr char YesNo[2][4] = {"no ", "yes"}; 331 constexpr char Indicator[4][3] = {"!!", ">>", "<<", "=="}; 332 outs() << Indicator[Which] << " TLI " << YesNo[TLIHas] << " SDK " 333 << YesNo[SDKHas] << ": " << PrintableName(TLIName.first) << '\n'; 334 } 335 } 336 337 assert(TLIandSDKboth + TLIandSDKneither + TLIdoesSDKdoesnt + 338 TLIdoesntSDKdoes == 339 LibFunc::NumLibFuncs); 340 outs() << "<< Total TLI yes SDK no: " << TLIdoesSDKdoesnt 341 << "\n>> Total TLI no SDK yes: " << TLIdoesntSDKdoes 342 << "\n== Total TLI yes SDK yes: " << TLIandSDKboth; 343 if (TLIandSDKboth == 0) { 344 outs() << " *** NO TLI SYMBOLS FOUND"; 345 if (SeparateMode) 346 outs() << " in '" << LibList[I] << "'"; 347 } 348 outs() << '\n'; 349 350 if (!SeparateMode) { 351 if (TLIdoesSDKdoesnt == 0 && TLIdoesntSDKdoes == 0) 352 outs() << "PASS: LLVM TLI matched SDK libraries successfully.\n"; 353 else 354 outs() << "FAIL: LLVM TLI doesn't match SDK libraries.\n"; 355 } 356 } 357 } 358