
This is an attempt to reland #151660 by including a missing STL header found by a buildbot failure. The stable function map could be huge for a large application. Fully loading it is slow and consumes a significant amount of memory, which is unnecessary and drastically slows down compilation especially for non-LTO and distributed-ThinLTO setups. This patch introduces an opt-in lazy loading support for the stable function map. The detailed changes are: - `StableFunctionMap` - The map now stores entries in an `EntryStorage` struct, which includes offsets for serialized entries and a `std::once_flag` for thread-safe lazy loading. - The underlying map type is changed from `DenseMap` to `std::unordered_map` for compatibility with `std::once_flag`. - `contains()`, `size()` and `at()` are implemented to only load requested entries on demand. - Lazy Loading Mechanism - When reading indexed codegen data, if the newly-introduced `-indexed-codegen-data-lazy-loading` flag is set, the stable function map is not fully deserialized up front. The binary format for the stable function map now includes offsets and sizes to support lazy loading. - The safety of lazy loading is guarded by the once flag per function hash. This guarantees that even in a multi-threaded environment, the deserialization for a given function hash will happen exactly once. The first thread to request it performs the load, and subsequent threads will wait for it to complete before using the data. For single-threaded builds, the overhead is negligible (a single check on the once flag). For multi-threaded scenarios, users can omit the flag to retain the previous eager-loading behavior.
291 lines
12 KiB
C++
291 lines
12 KiB
C++
//===-- 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();
|
|
}
|