| //===-- StableFunctionMapRecord.cpp ---------------------------------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This implements the functionality for the StableFunctionMapRecord class, | |
| // including methods for serialization and deserialization of stable function | |
| // maps to and from raw and YAML streams. It also includes utilities for | |
| // managing function entries and their metadata. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/CGData/StableFunctionMapRecord.h" | |
| #include "llvm/Support/EndianStream.h" | |
| #define DEBUG_TYPE "stable-function-map-record" | |
| using namespace llvm; | |
| using namespace llvm::support; | |
| LLVM_YAML_IS_SEQUENCE_VECTOR(IndexPairHash) | |
| LLVM_YAML_IS_SEQUENCE_VECTOR(StableFunction) | |
| namespace llvm { | |
| namespace yaml { | |
| template <> struct MappingTraits<IndexPairHash> { | |
| static void mapping(IO &IO, IndexPairHash &Key) { | |
| IO.mapRequired("InstIndex", Key.first.first); | |
| IO.mapRequired("OpndIndex", Key.first.second); | |
| IO.mapRequired("OpndHash", Key.second); | |
| } | |
| }; | |
| template <> struct MappingTraits<StableFunction> { | |
| static void mapping(IO &IO, StableFunction &Func) { | |
| IO.mapRequired("Hash", Func.Hash); | |
| IO.mapRequired("FunctionName", Func.FunctionName); | |
| IO.mapRequired("ModuleName", Func.ModuleName); | |
| IO.mapRequired("InstCount", Func.InstCount); | |
| IO.mapRequired("IndexOperandHashes", Func.IndexOperandHashes); | |
| } | |
| }; | |
| } // namespace yaml | |
| } // namespace llvm | |
| // Get a sorted vector of StableFunctionEntry pointers. | |
| static SmallVector<const StableFunctionMap::StableFunctionEntry *> | |
| getStableFunctionEntries(const StableFunctionMap &SFM) { | |
| SmallVector<const StableFunctionMap::StableFunctionEntry *> FuncEntries; | |
| for (const auto &P : SFM.getFunctionMap()) | |
| for (auto &Func : P.second.Entries) | |
| FuncEntries.emplace_back(Func.get()); | |
| llvm::stable_sort( | |
| FuncEntries, [&](auto &A, auto &B) { | |
| return std::tuple(A->Hash, SFM.getNameForId(A->ModuleNameId), | |
| SFM.getNameForId(A->FunctionNameId)) < | |
| std::tuple(B->Hash, SFM.getNameForId(B->ModuleNameId), | |
| SFM.getNameForId(B->FunctionNameId)); | |
| }); | |
| return FuncEntries; | |
| } | |
| // Get a sorted vector of IndexOperandHashes. | |
| static IndexOperandHashVecType getStableIndexOperandHashes( | |
| const StableFunctionMap::StableFunctionEntry *FuncEntry) { | |
| IndexOperandHashVecType IndexOperandHashes; | |
| for (auto &[Indices, OpndHash] : *FuncEntry->IndexOperandHashMap) | |
| IndexOperandHashes.emplace_back(Indices, OpndHash); | |
| // The indices are unique, so we can just sort by the first. | |
| llvm::sort(IndexOperandHashes); | |
| return IndexOperandHashes; | |
| } | |
| void StableFunctionMapRecord::serialize( | |
| raw_ostream &OS, std::vector<CGDataPatchItem> &PatchItems) const { | |
| serialize(OS, FunctionMap.get(), PatchItems); | |
| } | |
| void StableFunctionMapRecord::serialize( | |
| raw_ostream &OS, const StableFunctionMap *FunctionMap, | |
| std::vector<CGDataPatchItem> &PatchItems) { | |
| support::endian::Writer Writer(OS, endianness::little); | |
| // Write Names. | |
| ArrayRef<std::string> Names = FunctionMap->getNames(); | |
| Writer.write<uint32_t>(Names.size()); | |
| // Remember the position, write back the total size of Names, so we can skip | |
| // reading them if needed. | |
| const uint64_t NamesByteSizeOffset = Writer.OS.tell(); | |
| Writer.write<uint64_t>(0); | |
| for (auto &Name : Names) | |
| Writer.OS << Name << '\0'; | |
| // Align current position to 4 bytes. | |
| uint32_t Padding = offsetToAlignment(Writer.OS.tell(), Align(4)); | |
| for (uint32_t I = 0; I < Padding; ++I) | |
| Writer.OS << '\0'; | |
| const auto NamesByteSize = | |
| Writer.OS.tell() - NamesByteSizeOffset - sizeof(NamesByteSizeOffset); | |
| PatchItems.emplace_back(NamesByteSizeOffset, &NamesByteSize, 1); | |
| // Write StableFunctionEntries whose pointers are sorted. | |
| auto FuncEntries = getStableFunctionEntries(*FunctionMap); | |
| Writer.write<uint32_t>(FuncEntries.size()); | |
| for (const auto *FuncRef : FuncEntries) | |
| Writer.write<stable_hash>(FuncRef->Hash); | |
| std::vector<uint64_t> IndexOperandHashesOffsets; | |
| IndexOperandHashesOffsets.reserve(FuncEntries.size()); | |
| for (const auto *FuncRef : FuncEntries) { | |
| Writer.write<uint32_t>(FuncRef->FunctionNameId); | |
| Writer.write<uint32_t>(FuncRef->ModuleNameId); | |
| Writer.write<uint32_t>(FuncRef->InstCount); | |
| const uint64_t Offset = Writer.OS.tell(); | |
| IndexOperandHashesOffsets.push_back(Offset); | |
| Writer.write<uint64_t>(0); | |
| } | |
| const uint64_t IndexOperandHashesByteSizeOffset = Writer.OS.tell(); | |
| Writer.write<uint64_t>(0); | |
| for (size_t I = 0; I < FuncEntries.size(); ++I) { | |
| const uint64_t Offset = Writer.OS.tell() - IndexOperandHashesOffsets[I]; | |
| PatchItems.emplace_back(IndexOperandHashesOffsets[I], &Offset, 1); | |
| // Emit IndexOperandHashes sorted from IndexOperandHashMap. | |
| const auto *FuncRef = FuncEntries[I]; | |
| IndexOperandHashVecType IndexOperandHashes = | |
| getStableIndexOperandHashes(FuncRef); | |
| Writer.write<uint32_t>(IndexOperandHashes.size()); | |
| for (auto &IndexOperandHash : IndexOperandHashes) { | |
| Writer.write<uint32_t>(IndexOperandHash.first.first); | |
| Writer.write<uint32_t>(IndexOperandHash.first.second); | |
| Writer.write<stable_hash>(IndexOperandHash.second); | |
| } | |
| } | |
| // Write the total size of IndexOperandHashes. | |
| const uint64_t IndexOperandHashesByteSize = | |
| Writer.OS.tell() - IndexOperandHashesByteSizeOffset - sizeof(uint64_t); | |
| PatchItems.emplace_back(IndexOperandHashesByteSizeOffset, | |
| &IndexOperandHashesByteSize, 1); | |
| } | |
| void StableFunctionMapRecord::deserializeEntry(const unsigned char *Ptr, | |
| stable_hash Hash, | |
| StableFunctionMap *FunctionMap) { | |
| auto FunctionNameId = | |
| endian::readNext<uint32_t, endianness::little, unaligned>(Ptr); | |
| if (FunctionMap->ReadStableFunctionMapNames) | |
| assert(FunctionMap->getNameForId(FunctionNameId) && | |
| "FunctionNameId out of range"); | |
| auto ModuleNameId = | |
| endian::readNext<uint32_t, endianness::little, unaligned>(Ptr); | |
| if (FunctionMap->ReadStableFunctionMapNames) | |
| assert(FunctionMap->getNameForId(ModuleNameId) && | |
| "ModuleNameId out of range"); | |
| auto InstCount = | |
| endian::readNext<uint32_t, endianness::little, unaligned>(Ptr); | |
| // Read IndexOperandHashes to build IndexOperandHashMap | |
| auto CurrentPosition = reinterpret_cast<uintptr_t>(Ptr); | |
| auto IndexOperandHashesOffset = | |
| endian::readNext<uint64_t, endianness::little, unaligned>(Ptr); | |
| auto *IndexOperandHashesPtr = reinterpret_cast<const unsigned char *>( | |
| CurrentPosition + IndexOperandHashesOffset); | |
| auto NumIndexOperandHashes = | |
| endian::readNext<uint32_t, endianness::little, unaligned>( | |
| IndexOperandHashesPtr); | |
| auto IndexOperandHashMap = std::make_unique<IndexOperandHashMapType>(); | |
| for (unsigned J = 0; J < NumIndexOperandHashes; ++J) { | |
| auto InstIndex = endian::readNext<uint32_t, endianness::little, unaligned>( | |
| IndexOperandHashesPtr); | |
| auto OpndIndex = endian::readNext<uint32_t, endianness::little, unaligned>( | |
| IndexOperandHashesPtr); | |
| auto OpndHash = | |
| endian::readNext<stable_hash, endianness::little, unaligned>( | |
| IndexOperandHashesPtr); | |
| assert(InstIndex < InstCount && "InstIndex out of range"); | |
| IndexOperandHashMap->try_emplace({InstIndex, OpndIndex}, OpndHash); | |
| } | |
| // Insert a new StableFunctionEntry into the map. | |
| auto FuncEntry = std::make_unique<StableFunctionMap::StableFunctionEntry>( | |
| Hash, FunctionNameId, ModuleNameId, InstCount, | |
| std::move(IndexOperandHashMap)); | |
| FunctionMap->insert(std::move(FuncEntry)); | |
| } | |
| void StableFunctionMapRecord::deserialize(const unsigned char *&Ptr, | |
| bool Lazy) { | |
| // Assert that Ptr is 4-byte aligned | |
| assert(((uintptr_t)Ptr % 4) == 0); | |
| // Read Names. | |
| auto NumNames = | |
| endian::readNext<uint32_t, endianness::little, unaligned>(Ptr); | |
| // Early exit if there is no name. | |
| if (NumNames == 0) | |
| return; | |
| const auto NamesByteSize = | |
| endian::readNext<uint64_t, endianness::little, unaligned>(Ptr); | |
| const auto NamesOffset = reinterpret_cast<uintptr_t>(Ptr); | |
| if (FunctionMap->ReadStableFunctionMapNames) { | |
| for (unsigned I = 0; I < NumNames; ++I) { | |
| StringRef Name(reinterpret_cast<const char *>(Ptr)); | |
| Ptr += Name.size() + 1; | |
| FunctionMap->getIdOrCreateForName(Name); | |
| } | |
| // Align Ptr to 4 bytes. | |
| Ptr = reinterpret_cast<const uint8_t *>(alignAddr(Ptr, Align(4))); | |
| assert(reinterpret_cast<uintptr_t>(Ptr) - NamesOffset == NamesByteSize && | |
| "NamesByteSize does not match the actual size of names"); | |
| } else { | |
| // skip reading Names by advancing the pointer. | |
| Ptr = reinterpret_cast<const uint8_t *>(NamesOffset + NamesByteSize); | |
| } | |
| // Read StableFunctionEntries. | |
| auto NumFuncs = | |
| endian::readNext<uint32_t, endianness::little, unaligned>(Ptr); | |
| auto FixedSizeFieldsOffset = | |
| reinterpret_cast<uintptr_t>(Ptr) + NumFuncs * sizeof(stable_hash); | |
| constexpr uint32_t FixedSizeFieldsSizePerEntry = | |
| // FunctionNameId | |
| sizeof(uint32_t) + | |
| // ModuleNameId | |
| sizeof(uint32_t) + | |
| // InstCount | |
| sizeof(uint32_t) + | |
| // Relative offset to IndexOperandHashes | |
| sizeof(uint64_t); | |
| for (unsigned I = 0; I < NumFuncs; ++I) { | |
| auto Hash = | |
| endian::readNext<stable_hash, endianness::little, unaligned>(Ptr); | |
| if (Lazy) { | |
| auto It = FunctionMap->HashToFuncs.try_emplace(Hash).first; | |
| StableFunctionMap::EntryStorage &Storage = It->second; | |
| Storage.Offsets.push_back(FixedSizeFieldsOffset); | |
| } else { | |
| deserializeEntry( | |
| reinterpret_cast<const unsigned char *>(FixedSizeFieldsOffset), Hash, | |
| FunctionMap.get()); | |
| } | |
| FixedSizeFieldsOffset += FixedSizeFieldsSizePerEntry; | |
| } | |
| // Update Ptr to the end of the serialized map to meet the expectation of | |
| // CodeGenDataReader. | |
| Ptr = reinterpret_cast<const unsigned char *>(FixedSizeFieldsOffset); | |
| auto IndexOperandHashesByteSize = | |
| endian::readNext<uint64_t, endianness::little, unaligned>(Ptr); | |
| Ptr = reinterpret_cast<const unsigned char *>( | |
| reinterpret_cast<uintptr_t>(Ptr) + IndexOperandHashesByteSize); | |
| } | |
| void StableFunctionMapRecord::deserialize(const unsigned char *&Ptr) { | |
| deserialize(Ptr, /*Lazy=*/false); | |
| } | |
| void StableFunctionMapRecord::lazyDeserialize( | |
| std::shared_ptr<MemoryBuffer> Buffer, uint64_t Offset) { | |
| const auto *Ptr = reinterpret_cast<const unsigned char *>( | |
| reinterpret_cast<uintptr_t>(Buffer->getBufferStart()) + Offset); | |
| deserialize(Ptr, /*Lazy=*/true); | |
| FunctionMap->Buffer = std::move(Buffer); | |
| } | |
| void StableFunctionMapRecord::serializeYAML(yaml::Output &YOS) const { | |
| auto FuncEntries = getStableFunctionEntries(*FunctionMap); | |
| SmallVector<StableFunction> Functions; | |
| for (const auto *FuncEntry : FuncEntries) { | |
| auto IndexOperandHashes = getStableIndexOperandHashes(FuncEntry); | |
| Functions.emplace_back( | |
| FuncEntry->Hash, *FunctionMap->getNameForId(FuncEntry->FunctionNameId), | |
| *FunctionMap->getNameForId(FuncEntry->ModuleNameId), | |
| FuncEntry->InstCount, std::move(IndexOperandHashes)); | |
| } | |
| YOS << Functions; | |
| } | |
| void StableFunctionMapRecord::deserializeYAML(yaml::Input &YIS) { | |
| std::vector<StableFunction> Funcs; | |
| YIS >> Funcs; | |
| for (auto &Func : Funcs) | |
| FunctionMap->insert(Func); | |
| YIS.nextDocument(); | |
| } |