
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.
299 lines
11 KiB
C++
299 lines
11 KiB
C++
//===-- StableFunctionMap.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 StableFunctionMap class, which
|
|
// manages the mapping of stable function hashes to their metadata. It includes
|
|
// methods for inserting, merging, and finalizing function entries, as well as
|
|
// utilities for handling function names and IDs.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "llvm/CGData/StableFunctionMap.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
#include "llvm/CGData/StableFunctionMapRecord.h"
|
|
#include "llvm/Support/CommandLine.h"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
#define DEBUG_TYPE "stable-function-map"
|
|
|
|
using namespace llvm;
|
|
|
|
static cl::opt<unsigned>
|
|
GlobalMergingMinMerges("global-merging-min-merges",
|
|
cl::desc("Minimum number of similar functions with "
|
|
"the same hash required for merging."),
|
|
cl::init(2), cl::Hidden);
|
|
static cl::opt<unsigned> GlobalMergingMinInstrs(
|
|
"global-merging-min-instrs",
|
|
cl::desc("The minimum instruction count required when merging functions."),
|
|
cl::init(1), cl::Hidden);
|
|
static cl::opt<unsigned> GlobalMergingMaxParams(
|
|
"global-merging-max-params",
|
|
cl::desc(
|
|
"The maximum number of parameters allowed when merging functions."),
|
|
cl::init(std::numeric_limits<unsigned>::max()), cl::Hidden);
|
|
static cl::opt<bool> GlobalMergingSkipNoParams(
|
|
"global-merging-skip-no-params",
|
|
cl::desc("Skip merging functions with no parameters."), cl::init(true),
|
|
cl::Hidden);
|
|
static cl::opt<double> GlobalMergingInstOverhead(
|
|
"global-merging-inst-overhead",
|
|
cl::desc("The overhead cost associated with each instruction when lowering "
|
|
"to machine instruction."),
|
|
cl::init(1.2), cl::Hidden);
|
|
static cl::opt<double> GlobalMergingParamOverhead(
|
|
"global-merging-param-overhead",
|
|
cl::desc("The overhead cost associated with each parameter when merging "
|
|
"functions."),
|
|
cl::init(2.0), cl::Hidden);
|
|
static cl::opt<double>
|
|
GlobalMergingCallOverhead("global-merging-call-overhead",
|
|
cl::desc("The overhead cost associated with each "
|
|
"function call when merging functions."),
|
|
cl::init(1.0), cl::Hidden);
|
|
static cl::opt<double> GlobalMergingExtraThreshold(
|
|
"global-merging-extra-threshold",
|
|
cl::desc("An additional cost threshold that must be exceeded for merging "
|
|
"to be considered beneficial."),
|
|
cl::init(0.0), cl::Hidden);
|
|
|
|
unsigned StableFunctionMap::getIdOrCreateForName(StringRef Name) {
|
|
auto It = NameToId.find(Name);
|
|
if (It != NameToId.end())
|
|
return It->second;
|
|
unsigned Id = IdToName.size();
|
|
assert(Id == NameToId.size() && "ID collision");
|
|
IdToName.emplace_back(Name.str());
|
|
NameToId[IdToName.back()] = Id;
|
|
return Id;
|
|
}
|
|
|
|
std::optional<std::string> StableFunctionMap::getNameForId(unsigned Id) const {
|
|
if (Id >= IdToName.size())
|
|
return std::nullopt;
|
|
return IdToName[Id];
|
|
}
|
|
|
|
void StableFunctionMap::insert(const StableFunction &Func) {
|
|
assert(!Finalized && "Cannot insert after finalization");
|
|
auto FuncNameId = getIdOrCreateForName(Func.FunctionName);
|
|
auto ModuleNameId = getIdOrCreateForName(Func.ModuleName);
|
|
auto IndexOperandHashMap = std::make_unique<IndexOperandHashMapType>();
|
|
for (auto &[Index, Hash] : Func.IndexOperandHashes)
|
|
(*IndexOperandHashMap)[Index] = Hash;
|
|
auto FuncEntry = std::make_unique<StableFunctionEntry>(
|
|
Func.Hash, FuncNameId, ModuleNameId, Func.InstCount,
|
|
std::move(IndexOperandHashMap));
|
|
insert(std::move(FuncEntry));
|
|
}
|
|
|
|
void StableFunctionMap::merge(const StableFunctionMap &OtherMap) {
|
|
assert(!Finalized && "Cannot merge after finalization");
|
|
deserializeLazyLoadingEntries();
|
|
for (auto &[Hash, Funcs] : OtherMap.HashToFuncs) {
|
|
auto &ThisFuncs = HashToFuncs[Hash].Entries;
|
|
for (auto &Func : Funcs.Entries) {
|
|
auto FuncNameId =
|
|
getIdOrCreateForName(*OtherMap.getNameForId(Func->FunctionNameId));
|
|
auto ModuleNameId =
|
|
getIdOrCreateForName(*OtherMap.getNameForId(Func->ModuleNameId));
|
|
auto ClonedIndexOperandHashMap =
|
|
std::make_unique<IndexOperandHashMapType>(*Func->IndexOperandHashMap);
|
|
ThisFuncs.emplace_back(std::make_unique<StableFunctionEntry>(
|
|
Func->Hash, FuncNameId, ModuleNameId, Func->InstCount,
|
|
std::move(ClonedIndexOperandHashMap)));
|
|
}
|
|
}
|
|
}
|
|
|
|
size_t StableFunctionMap::size(SizeType Type) const {
|
|
switch (Type) {
|
|
case UniqueHashCount:
|
|
return HashToFuncs.size();
|
|
case TotalFunctionCount: {
|
|
deserializeLazyLoadingEntries();
|
|
size_t Count = 0;
|
|
for (auto &Funcs : HashToFuncs)
|
|
Count += Funcs.second.Entries.size();
|
|
return Count;
|
|
}
|
|
case MergeableFunctionCount: {
|
|
deserializeLazyLoadingEntries();
|
|
size_t Count = 0;
|
|
for (auto &[Hash, Funcs] : HashToFuncs)
|
|
if (Funcs.Entries.size() >= 2)
|
|
Count += Funcs.Entries.size();
|
|
return Count;
|
|
}
|
|
}
|
|
llvm_unreachable("Unhandled size type");
|
|
}
|
|
|
|
const StableFunctionMap::StableFunctionEntries &
|
|
StableFunctionMap::at(HashFuncsMapType::key_type FunctionHash) const {
|
|
auto It = HashToFuncs.find(FunctionHash);
|
|
if (isLazilyLoaded())
|
|
deserializeLazyLoadingEntry(It);
|
|
return It->second.Entries;
|
|
}
|
|
|
|
void StableFunctionMap::deserializeLazyLoadingEntry(
|
|
HashFuncsMapType::iterator It) const {
|
|
assert(isLazilyLoaded() && "Cannot deserialize non-lazily-loaded map");
|
|
auto &[Hash, Storage] = *It;
|
|
std::call_once(Storage.LazyLoadFlag,
|
|
[this, HashArg = Hash, &StorageArg = Storage]() {
|
|
for (auto Offset : StorageArg.Offsets)
|
|
StableFunctionMapRecord::deserializeEntry(
|
|
reinterpret_cast<const unsigned char *>(Offset),
|
|
HashArg, const_cast<StableFunctionMap *>(this));
|
|
});
|
|
}
|
|
|
|
void StableFunctionMap::deserializeLazyLoadingEntries() const {
|
|
if (!isLazilyLoaded())
|
|
return;
|
|
for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It)
|
|
deserializeLazyLoadingEntry(It);
|
|
}
|
|
|
|
const StableFunctionMap::HashFuncsMapType &
|
|
StableFunctionMap::getFunctionMap() const {
|
|
// Ensure all entries are deserialized before returning the raw map.
|
|
if (isLazilyLoaded())
|
|
deserializeLazyLoadingEntries();
|
|
return HashToFuncs;
|
|
}
|
|
|
|
using ParamLocs = SmallVector<IndexPair>;
|
|
static void
|
|
removeIdenticalIndexPair(StableFunctionMap::StableFunctionEntries &SFS) {
|
|
auto &RSF = SFS[0];
|
|
unsigned StableFunctionCount = SFS.size();
|
|
|
|
SmallVector<IndexPair> ToDelete;
|
|
for (auto &[Pair, Hash] : *(RSF->IndexOperandHashMap)) {
|
|
bool Identical = true;
|
|
for (unsigned J = 1; J < StableFunctionCount; ++J) {
|
|
auto &SF = SFS[J];
|
|
const auto &SHash = SF->IndexOperandHashMap->at(Pair);
|
|
if (Hash != SHash) {
|
|
Identical = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// No need to parameterize them if the hashes are identical across stable
|
|
// functions.
|
|
if (Identical)
|
|
ToDelete.emplace_back(Pair);
|
|
}
|
|
|
|
for (auto &Pair : ToDelete)
|
|
for (auto &SF : SFS)
|
|
SF->IndexOperandHashMap->erase(Pair);
|
|
}
|
|
|
|
static bool isProfitable(const StableFunctionMap::StableFunctionEntries &SFS) {
|
|
unsigned StableFunctionCount = SFS.size();
|
|
if (StableFunctionCount < GlobalMergingMinMerges)
|
|
return false;
|
|
|
|
unsigned InstCount = SFS[0]->InstCount;
|
|
if (InstCount < GlobalMergingMinInstrs)
|
|
return false;
|
|
|
|
double Cost = 0.0;
|
|
SmallSet<stable_hash, 8> UniqueHashVals;
|
|
for (auto &SF : SFS) {
|
|
UniqueHashVals.clear();
|
|
for (auto &[IndexPair, Hash] : *SF->IndexOperandHashMap)
|
|
UniqueHashVals.insert(Hash);
|
|
unsigned ParamCount = UniqueHashVals.size();
|
|
if (ParamCount > GlobalMergingMaxParams)
|
|
return false;
|
|
// Theoretically, if ParamCount is 0, it results in identical code folding
|
|
// (ICF), which we can skip merging here since the linker already handles
|
|
// ICF. This pass would otherwise introduce unnecessary thunks that are
|
|
// merely direct jumps. However, enabling this could be beneficial depending
|
|
// on downstream passes, so we provide an option for it.
|
|
if (GlobalMergingSkipNoParams && ParamCount == 0)
|
|
return false;
|
|
Cost += ParamCount * GlobalMergingParamOverhead + GlobalMergingCallOverhead;
|
|
}
|
|
Cost += GlobalMergingExtraThreshold;
|
|
|
|
double Benefit =
|
|
InstCount * (StableFunctionCount - 1) * GlobalMergingInstOverhead;
|
|
bool Result = Benefit > Cost;
|
|
LLVM_DEBUG(dbgs() << "isProfitable: Hash = " << SFS[0]->Hash << ", "
|
|
<< "StableFunctionCount = " << StableFunctionCount
|
|
<< ", InstCount = " << InstCount
|
|
<< ", Benefit = " << Benefit << ", Cost = " << Cost
|
|
<< ", Result = " << (Result ? "true" : "false") << "\n");
|
|
return Result;
|
|
}
|
|
|
|
void StableFunctionMap::finalize(bool SkipTrim) {
|
|
deserializeLazyLoadingEntries();
|
|
SmallVector<HashFuncsMapType::iterator> ToDelete;
|
|
for (auto It = HashToFuncs.begin(); It != HashToFuncs.end(); ++It) {
|
|
auto &[StableHash, Storage] = *It;
|
|
auto &SFS = Storage.Entries;
|
|
|
|
// Group stable functions by ModuleIdentifier.
|
|
llvm::stable_sort(SFS, [&](const std::unique_ptr<StableFunctionEntry> &L,
|
|
const std::unique_ptr<StableFunctionEntry> &R) {
|
|
return *getNameForId(L->ModuleNameId) < *getNameForId(R->ModuleNameId);
|
|
});
|
|
|
|
// Consider the first function as the root function.
|
|
auto &RSF = SFS[0];
|
|
|
|
bool Invalid = false;
|
|
unsigned StableFunctionCount = SFS.size();
|
|
for (unsigned I = 1; I < StableFunctionCount; ++I) {
|
|
auto &SF = SFS[I];
|
|
assert(RSF->Hash == SF->Hash);
|
|
if (RSF->InstCount != SF->InstCount) {
|
|
Invalid = true;
|
|
break;
|
|
}
|
|
if (RSF->IndexOperandHashMap->size() != SF->IndexOperandHashMap->size()) {
|
|
Invalid = true;
|
|
break;
|
|
}
|
|
for (auto &P : *RSF->IndexOperandHashMap) {
|
|
auto &InstOpndIndex = P.first;
|
|
if (!SF->IndexOperandHashMap->count(InstOpndIndex)) {
|
|
Invalid = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (Invalid) {
|
|
ToDelete.push_back(It);
|
|
continue;
|
|
}
|
|
|
|
if (SkipTrim)
|
|
continue;
|
|
|
|
// Trim the index pair that has the same operand hash across
|
|
// stable functions.
|
|
removeIdenticalIndexPair(SFS);
|
|
|
|
if (!isProfitable(SFS))
|
|
ToDelete.push_back(It);
|
|
}
|
|
for (auto It : ToDelete)
|
|
HashToFuncs.erase(It);
|
|
|
|
Finalized = true;
|
|
}
|