llvm-project/llvm/lib/CGData/StableFunctionMapRecord.cpp
Zhaoxuan Jiang 2738828c0e
[Reland] [CGData] Lazy loading support for stable function map (#154491)
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.
2025-08-20 06:15:04 -07:00

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();
}