//===- RuntimeLibcallEmitter.cpp - Properties from RuntimeLibcalls.td -----===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #define DEBUG_TYPE "runtime-libcall-emitter" #include "RuntimeLibcalls.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/Debug.h" #include "llvm/Support/Format.h" #include "llvm/Support/FormatVariadic.h" #include "llvm/Support/raw_ostream.h" #include "llvm/Support/xxhash.h" #include "llvm/TableGen/CodeGenHelpers.h" #include "llvm/TableGen/Error.h" #include "llvm/TableGen/Record.h" #include "llvm/TableGen/SetTheory.h" #include "llvm/TableGen/StringToOffsetTable.h" #include "llvm/TableGen/TableGenBackend.h" using namespace llvm; namespace { // Pair of a RuntimeLibcallPredicate and LibcallCallingConv to use as a map key. struct PredicateWithCC { const Record *Predicate = nullptr; const Record *CallingConv = nullptr; PredicateWithCC() = default; PredicateWithCC(std::pair P) : Predicate(P.first), CallingConv(P.second) {} PredicateWithCC(const Record *P, const Record *C) : Predicate(P), CallingConv(C) {} }; inline bool operator==(PredicateWithCC LHS, PredicateWithCC RHS) { return LHS.Predicate == RHS.Predicate && LHS.CallingConv == RHS.CallingConv; } } // namespace namespace llvm { template <> struct DenseMapInfo { static inline PredicateWithCC getEmptyKey() { return DenseMapInfo< std::pair>::getEmptyKey(); } static inline PredicateWithCC getTombstoneKey() { return DenseMapInfo< std::pair>::getTombstoneKey(); } static unsigned getHashValue(const PredicateWithCC Val) { auto Pair = std::make_pair(Val.Predicate, Val.CallingConv); return DenseMapInfo< std::pair>::getHashValue(Pair); } static bool isEqual(PredicateWithCC LHS, PredicateWithCC RHS) { return LHS == RHS; } }; class RuntimeLibcallEmitter { private: const RecordKeeper &Records; RuntimeLibcalls Libcalls; void emitGetRuntimeLibcallEnum(raw_ostream &OS) const; void emitNameMatchHashTable(raw_ostream &OS, StringToOffsetTable &OffsetTable) const; void emitGetInitRuntimeLibcallNames(raw_ostream &OS) const; void emitSystemRuntimeLibrarySetCalls(raw_ostream &OS) const; public: RuntimeLibcallEmitter(const RecordKeeper &R) : Records(R), Libcalls(R) {} void run(raw_ostream &OS); }; } // End anonymous namespace. void RuntimeLibcallEmitter::emitGetRuntimeLibcallEnum(raw_ostream &OS) const { IfDefEmitter IfDef(OS, "GET_RUNTIME_LIBCALL_ENUM"); OS << "namespace llvm {\n" "namespace RTLIB {\n" "enum Libcall : unsigned short {\n"; for (const RuntimeLibcall &LibCall : Libcalls.getRuntimeLibcallDefList()) { StringRef Name = LibCall.getName(); OS << " " << Name << " = " << LibCall.getEnumVal() << ",\n"; } OS << " UNKNOWN_LIBCALL = " << Libcalls.getRuntimeLibcallDefList().size() << "\n};\n\n" "enum LibcallImpl : unsigned short {\n" " Unsupported = 0,\n"; for (const RuntimeLibcallImpl &LibCall : Libcalls.getRuntimeLibcallImplDefList()) { OS << " impl_" << LibCall.getName() << " = " << LibCall.getEnumVal() << ", // " << LibCall.getLibcallFuncName() << '\n'; } OS << "};\n" << "constexpr size_t NumLibcallImpls = " << Libcalls.getRuntimeLibcallImplDefList().size() + 1 << ";\n" "} // End namespace RTLIB\n" "} // End namespace llvm\n"; } // StringMap uses xxh3_64bits, truncated to uint32_t. static uint64_t hash(StringRef Str) { return static_cast(xxh3_64bits(Str)); } static void emitHashFunction(raw_ostream &OS) { OS << "static inline uint64_t hash(StringRef Str) {\n" " return static_cast(xxh3_64bits(Str));\n" "}\n\n"; } /// Return the table size, maximum number of collisions for the set of hashes static std::pair computePerfectHashParameters(ArrayRef Hashes) { // Chosen based on experimentation with llvm/benchmarks/RuntimeLibcalls.cpp const int SizeOverhead = 4; // Index derived from hash -> number of collisions. DenseMap Table; unsigned NumHashes = Hashes.size(); for (int MaxCollisions = 1;; ++MaxCollisions) { for (unsigned N = NextPowerOf2(NumHashes - 1); N < SizeOverhead * NumHashes; N <<= 1) { Table.clear(); bool NeedResize = false; for (uint64_t H : Hashes) { uint64_t Idx = H % static_cast(N); if (++Table[Idx] > MaxCollisions) { // Need to resize the final table if we increased the collision count. NeedResize = true; break; } } if (!NeedResize) return {N, MaxCollisions}; } } } static std::vector constructPerfectHashTable(ArrayRef Keywords, ArrayRef Hashes, ArrayRef TableValues, int Size, int Collisions, StringToOffsetTable &OffsetTable) { std::vector Lookup(Size * Collisions); for (auto [HashValue, TableValue] : zip(Hashes, TableValues)) { uint64_t Idx = (HashValue % static_cast(Size)) * static_cast(Collisions); bool Found = false; for (int J = 0; J < Collisions; ++J) { unsigned &Entry = Lookup[Idx + J]; if (Entry == 0) { Entry = TableValue; Found = true; break; } } if (!Found) reportFatalInternalError("failure to hash"); } return Lookup; } /// Generate hash table based lookup by name. void RuntimeLibcallEmitter::emitNameMatchHashTable( raw_ostream &OS, StringToOffsetTable &OffsetTable) const { ArrayRef RuntimeLibcallImplDefList = Libcalls.getRuntimeLibcallImplDefList(); std::vector Hashes(RuntimeLibcallImplDefList.size()); std::vector TableValues(RuntimeLibcallImplDefList.size()); DenseSet SeenFuncNames; size_t MaxFuncNameSize = 0; size_t Index = 0; for (const RuntimeLibcallImpl &LibCallImpl : RuntimeLibcallImplDefList) { StringRef ImplName = LibCallImpl.getLibcallFuncName(); if (SeenFuncNames.insert(ImplName).second) { MaxFuncNameSize = std::max(MaxFuncNameSize, ImplName.size()); TableValues[Index] = LibCallImpl.getEnumVal(); Hashes[Index++] = hash(ImplName); } } // Trim excess elements from non-unique entries. Hashes.resize(SeenFuncNames.size()); TableValues.resize(SeenFuncNames.size()); LLVM_DEBUG({ for (const RuntimeLibcallImpl &LibCallImpl : RuntimeLibcallImplDefList) { StringRef ImplName = LibCallImpl.getLibcallFuncName(); if (ImplName.size() == MaxFuncNameSize) { dbgs() << "Maximum runtime libcall name size: " << ImplName << '(' << MaxFuncNameSize << ")\n"; } } }); // Early exiting on the symbol name provides a significant speedup in the miss // case on the set of symbols in a clang binary. Emit this as an inlinable // precondition in the header. // // The empty check is also used to get sensible behavior on anonymous // functions. // // TODO: It may make more sense to split the search by string size more. There // are a few outliers, most call names are small. { IfDefEmitter IfDef(OS, "GET_LOOKUP_LIBCALL_IMPL_NAME_BODY"); OS << " size_t Size = Name.size();\n" " if (Size == 0 || Size > " << MaxFuncNameSize << ")\n" " return enum_seq(RTLIB::Unsupported, RTLIB::Unsupported);\n" " return lookupLibcallImplNameImpl(Name);\n"; } auto [Size, Collisions] = computePerfectHashParameters(Hashes); std::vector Lookup = constructPerfectHashTable(RuntimeLibcallImplDefList, Hashes, TableValues, Size, Collisions, OffsetTable); LLVM_DEBUG(dbgs() << "Runtime libcall perfect hashing parameters: Size = " << Size << ", maximum collisions = " << Collisions << '\n'); IfDefEmitter IfDef(OS, "DEFINE_GET_LOOKUP_LIBCALL_IMPL_NAME"); emitHashFunction(OS); OS << "iota_range RTLIB::RuntimeLibcallsInfo::" "lookupLibcallImplNameImpl(StringRef Name) {\n"; // Emit RTLIB::LibcallImpl values OS << " static constexpr uint16_t HashTableNameToEnum[" << Lookup.size() << "] = {\n"; for (unsigned TableVal : Lookup) OS << " " << TableVal << ",\n"; OS << " };\n\n"; OS << " unsigned Idx = (hash(Name) % " << Size << ") * " << Collisions << ";\n\n" " for (int I = 0; I != " << Collisions << R"(; ++I) { const uint16_t Entry = HashTableNameToEnum[Idx + I]; const uint16_t StrOffset = RuntimeLibcallNameOffsetTable[Entry]; const uint8_t StrSize = RuntimeLibcallNameSizeTable[Entry]; StringRef Str( &RTLIB::RuntimeLibcallsInfo::RuntimeLibcallImplNameTableStorage[StrOffset], StrSize); if (Str == Name) return libcallImplNameHit(Entry, StrOffset); } return enum_seq(RTLIB::Unsupported, RTLIB::Unsupported); } )"; } void RuntimeLibcallEmitter::emitGetInitRuntimeLibcallNames( raw_ostream &OS) const { // Emit the implementation names StringToOffsetTable Table(/*AppendZero=*/true, "RTLIB::RuntimeLibcallsInfo::"); { IfDefEmitter IfDef(OS, "GET_INIT_RUNTIME_LIBCALL_NAMES"); for (const RuntimeLibcallImpl &LibCallImpl : Libcalls.getRuntimeLibcallImplDefList()) Table.GetOrAddStringOffset(LibCallImpl.getLibcallFuncName()); Table.EmitStringTableDef(OS, "RuntimeLibcallImplNameTable"); OS << R"( const uint16_t RTLIB::RuntimeLibcallsInfo::RuntimeLibcallNameOffsetTable[] = { )"; OS << formatv(" {}, // {}\n", Table.GetStringOffset(""), ""); // Unsupported entry for (const RuntimeLibcallImpl &LibCallImpl : Libcalls.getRuntimeLibcallImplDefList()) { StringRef ImplName = LibCallImpl.getLibcallFuncName(); OS << formatv(" {}, // {}\n", Table.GetStringOffset(ImplName), ImplName); } OS << "};\n"; OS << R"( const uint8_t RTLIB::RuntimeLibcallsInfo::RuntimeLibcallNameSizeTable[] = { )"; OS << " 0,\n"; for (const RuntimeLibcallImpl &LibCallImpl : Libcalls.getRuntimeLibcallImplDefList()) OS << " " << LibCallImpl.getLibcallFuncName().size() << ",\n"; OS << "};\n\n"; // Emit the reverse mapping from implementation libraries to RTLIB::Libcall OS << "const RTLIB::Libcall llvm::RTLIB::RuntimeLibcallsInfo::" "ImplToLibcall[RTLIB::NumLibcallImpls] = {\n" " RTLIB::UNKNOWN_LIBCALL, // RTLIB::Unsupported\n"; for (const RuntimeLibcallImpl &LibCallImpl : Libcalls.getRuntimeLibcallImplDefList()) { const RuntimeLibcall *Provides = LibCallImpl.getProvides(); OS << " "; Provides->emitEnumEntry(OS); OS << ", // "; LibCallImpl.emitEnumEntry(OS); OS << '\n'; } OS << "};\n\n"; } emitNameMatchHashTable(OS, Table); } void RuntimeLibcallEmitter::emitSystemRuntimeLibrarySetCalls( raw_ostream &OS) const { OS << "void llvm::RTLIB::RuntimeLibcallsInfo::setTargetRuntimeLibcallSets(" "const llvm::Triple &TT, ExceptionHandling ExceptionModel, " "FloatABI::ABIType FloatABI, EABI EABIVersion, " "StringRef ABIName) {\n"; ArrayRef AllLibs = Records.getAllDerivedDefinitions("SystemRuntimeLibrary"); for (const Record *R : AllLibs) { OS << '\n'; AvailabilityPredicate TopLevelPredicate(R->getValueAsDef("TriplePred")); OS << indent(2); TopLevelPredicate.emitIf(OS); if (const Record *DefaultCCClass = R->getValueAsDef("DefaultLibcallCallingConv")) { StringRef DefaultCC = DefaultCCClass->getValueAsString("CallingConv").trim(); if (!DefaultCC.empty()) { OS << " const CallingConv::ID DefaultCC = " << DefaultCC << ";\n" << " for (CallingConv::ID &Entry : LibcallImplCallingConvs) {\n" " Entry = DefaultCC;\n" " }\n\n"; } } SetTheory Sets; DenseMap, const Record *>> Func2Preds; Sets.addExpander("LibcallImpls", std::make_unique( Libcalls, Func2Preds)); const SetTheory::RecVec *Elements = Sets.expand(R->getValueAsDef("MemberList")); // Sort to get deterministic output SetVector PredicateSorter; PredicateSorter.insert( PredicateWithCC()); // No predicate or CC override first. constexpr unsigned BitsPerStorageElt = 64; DenseMap Pred2Funcs; SmallVector BitsetValues(divideCeil( Libcalls.getRuntimeLibcallImplDefList().size() + 1, BitsPerStorageElt)); for (const Record *Elt : *Elements) { const RuntimeLibcallImpl *LibCallImpl = Libcalls.getRuntimeLibcallImpl(Elt); if (!LibCallImpl) { PrintError(R, "entry for SystemLibrary is not a RuntimeLibcallImpl"); PrintNote(Elt->getLoc(), "invalid entry `" + Elt->getName() + "`"); continue; } size_t BitIdx = LibCallImpl->getEnumVal(); uint64_t BitmaskVal = uint64_t(1) << (BitIdx % BitsPerStorageElt); size_t BitsetIdx = BitIdx / BitsPerStorageElt; auto It = Func2Preds.find(LibCallImpl); if (It == Func2Preds.end()) { BitsetValues[BitsetIdx] |= BitmaskVal; Pred2Funcs[PredicateWithCC()].LibcallImpls.push_back(LibCallImpl); continue; } for (const Record *Pred : It->second.first) { const Record *CC = It->second.second; AvailabilityPredicate SubsetPredicate(Pred); if (SubsetPredicate.isAlwaysAvailable()) BitsetValues[BitsetIdx] |= BitmaskVal; PredicateWithCC Key(Pred, CC); auto &Entry = Pred2Funcs[Key]; Entry.LibcallImpls.push_back(LibCallImpl); Entry.CallingConv = It->second.second; PredicateSorter.insert(Key); } } OS << " static constexpr LibcallImplBitset SystemAvailableImpls({\n" << indent(6); ListSeparator LS; unsigned EntryCount = 0; for (uint64_t Bits : BitsetValues) { if (EntryCount++ == 4) { EntryCount = 1; OS << ",\n" << indent(6); } else OS << LS; OS << format_hex(Bits, 16); } OS << "\n });\n" " AvailableLibcallImpls = SystemAvailableImpls;\n\n"; SmallVector SortedPredicates = PredicateSorter.takeVector(); llvm::sort(SortedPredicates, [](PredicateWithCC A, PredicateWithCC B) { StringRef AName = A.Predicate ? A.Predicate->getName() : ""; StringRef BName = B.Predicate ? B.Predicate->getName() : ""; return AName < BName; }); for (PredicateWithCC Entry : SortedPredicates) { AvailabilityPredicate SubsetPredicate(Entry.Predicate); unsigned IndentDepth = 2; auto It = Pred2Funcs.find(Entry); if (It == Pred2Funcs.end()) continue; if (!SubsetPredicate.isAlwaysAvailable()) { IndentDepth = 4; OS << indent(IndentDepth); SubsetPredicate.emitIf(OS); } LibcallsWithCC &FuncsWithCC = It->second; std::vector &Funcs = FuncsWithCC.LibcallImpls; // Ensure we only emit a unique implementation per libcall in the // selection table. // // FIXME: We need to generate separate functions for // is-libcall-available and should-libcall-be-used to avoid this. // // This also makes it annoying to make use of the default set, since the // entries from the default set may win over the replacements unless // they are explicitly removed. stable_sort(Funcs, [](const RuntimeLibcallImpl *A, const RuntimeLibcallImpl *B) { return A->getProvides()->getEnumVal() < B->getProvides()->getEnumVal(); }); auto UniqueI = llvm::unique( Funcs, [&](const RuntimeLibcallImpl *A, const RuntimeLibcallImpl *B) { if (A->getProvides() == B->getProvides()) { PrintWarning(R->getLoc(), Twine("conflicting implementations for libcall " + A->getProvides()->getName() + ": " + A->getLibcallFuncName() + ", " + B->getLibcallFuncName())); return true; } return false; }); Funcs.erase(UniqueI, Funcs.end()); OS << indent(IndentDepth + 2) << "static const RTLIB::LibcallImpl LibraryCalls"; SubsetPredicate.emitTableVariableNameSuffix(OS); if (FuncsWithCC.CallingConv) OS << '_' << FuncsWithCC.CallingConv->getName(); OS << "[] = {\n"; for (const RuntimeLibcallImpl *LibCallImpl : Funcs) { OS << indent(IndentDepth + 6); LibCallImpl->emitEnumEntry(OS); OS << ", // " << LibCallImpl->getLibcallFuncName() << '\n'; } OS << indent(IndentDepth + 2) << "};\n\n" << indent(IndentDepth + 2) << "for (const RTLIB::LibcallImpl Impl : LibraryCalls"; SubsetPredicate.emitTableVariableNameSuffix(OS); if (FuncsWithCC.CallingConv) OS << '_' << FuncsWithCC.CallingConv->getName(); OS << ") {\n" << indent(IndentDepth + 4) << "setAvailable(Impl);\n"; if (FuncsWithCC.CallingConv) { StringRef CCEnum = FuncsWithCC.CallingConv->getValueAsString("CallingConv"); OS << indent(IndentDepth + 4) << "setLibcallImplCallingConv(Impl, " << CCEnum << ");\n"; } OS << indent(IndentDepth + 2) << "}\n"; OS << '\n'; if (!SubsetPredicate.isAlwaysAvailable()) { OS << indent(IndentDepth); SubsetPredicate.emitEndIf(OS); OS << '\n'; } } OS << indent(4) << "return;\n" << indent(2); TopLevelPredicate.emitEndIf(OS); } // FIXME: This should be a fatal error. A few contexts are improperly relying // on RuntimeLibcalls constructed with fully unknown triples. OS << " LLVM_DEBUG(dbgs() << \"no system runtime library applied to target " "\\'\" << TT.str() << \"\\'\\n\");\n" "}\n\n"; } void RuntimeLibcallEmitter::run(raw_ostream &OS) { emitSourceFileHeader("Runtime LibCalls Source Fragment", OS, Records); emitGetRuntimeLibcallEnum(OS); emitGetInitRuntimeLibcallNames(OS); { IfDefEmitter IfDef(OS, "GET_RUNTIME_LIBCALLS_INFO"); emitSystemRuntimeLibrarySetCalls(OS); } } static TableGen::Emitter::OptClass X("gen-runtime-libcalls", "Generate RuntimeLibcalls");