#include "DebugTypes.h"
#include "COFFLinkerContext.h"
#include "Chunks.h"
#include "Driver.h"
#include "InputFiles.h"
#include "PDB.h"
#include "TypeMerger.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/Memory.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h"
#include "llvm/DebugInfo/CodeView/TypeRecord.h"
#include "llvm/DebugInfo/CodeView/TypeRecordHelpers.h"
#include "llvm/DebugInfo/CodeView/TypeStreamMerger.h"
#include "llvm/DebugInfo/PDB/GenericError.h"
#include "llvm/DebugInfo/PDB/Native/InfoStream.h"
#include "llvm/DebugInfo/PDB/Native/NativeSession.h"
#include "llvm/DebugInfo/PDB/Native/PDBFile.h"
#include "llvm/DebugInfo/PDB/Native/TpiHashing.h"
#include "llvm/DebugInfo/PDB/Native/TpiStream.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Parallel.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TimeProfiler.h"
using namespace llvm;
using namespace llvm::codeview;
using namespace lld;
using namespace lld::coff;
namespace {
class TypeServerIpiSource;
class TypeServerSource : public TpiSource {
public:
explicit TypeServerSource(COFFLinkerContext &ctx, PDBInputFile *f)
: TpiSource(ctx, PDB, nullptr), pdbInputFile(f) {
if (f->loadErrorStr)
return;
pdb::PDBFile &file = f->session->getPDBFile();
auto expectedInfo = file.getPDBInfoStream();
if (!expectedInfo)
return;
Guid = expectedInfo->getGuid();
auto it = ctx.typeServerSourceMappings.emplace(Guid, this);
if (!it.second) {
it.first->second = nullptr;
}
}
Error mergeDebugT(TypeMerger *m) override;
void loadGHashes() override;
void remapTpiWithGHashes(GHashState *g) override;
bool isDependency() const override { return true; }
PDBInputFile *pdbInputFile = nullptr;
TypeServerIpiSource *ipiSrc = nullptr;
codeview::GUID Guid;
};
class TypeServerIpiSource : public TpiSource {
public:
explicit TypeServerIpiSource(COFFLinkerContext &ctx)
: TpiSource(ctx, PDBIpi, nullptr) {}
friend class TypeServerSource;
Error mergeDebugT(TypeMerger *m) override { return Error::success(); }
void loadGHashes() override {}
void remapTpiWithGHashes(GHashState *g) override {}
bool isDependency() const override { return true; }
};
class UseTypeServerSource : public TpiSource {
Expected<TypeServerSource *> getTypeServerSource();
public:
UseTypeServerSource(COFFLinkerContext &ctx, ObjFile *f, TypeServer2Record ts)
: TpiSource(ctx, UsingPDB, f), typeServerDependency(ts) {}
Error mergeDebugT(TypeMerger *m) override;
void loadGHashes() override {}
void remapTpiWithGHashes(GHashState *g) override;
TypeServer2Record typeServerDependency;
};
class PrecompSource : public TpiSource {
public:
PrecompSource(COFFLinkerContext &ctx, ObjFile *f) : TpiSource(ctx, PCH, f) {
registerMapping();
}
Error mergeDebugT(TypeMerger *m) override;
void loadGHashes() override;
bool isDependency() const override { return true; }
private:
void registerMapping();
bool registered = false;
};
class UsePrecompSource : public TpiSource {
public:
UsePrecompSource(COFFLinkerContext &ctx, ObjFile *f, PrecompRecord precomp)
: TpiSource(ctx, UsingPCH, f), precompDependency(precomp) {}
Error mergeDebugT(TypeMerger *m) override;
void loadGHashes() override;
void remapTpiWithGHashes(GHashState *g) override;
private:
Error mergeInPrecompHeaderObj();
PrecompSource *findObjByName(StringRef fileNameOnly);
PrecompSource *findPrecompSource(ObjFile *file, PrecompRecord &pr);
Expected<PrecompSource *> findPrecompMap(ObjFile *file, PrecompRecord &pr);
public:
PrecompRecord precompDependency;
};
}
TpiSource::TpiSource(COFFLinkerContext &ctx, TpiKind k, ObjFile *f)
: ctx(ctx), kind(k), tpiSrcIdx(ctx.tpiSourceList.size()), file(f) {
ctx.addTpiSource(this);
}
TpiSource::~TpiSource() {
consumeError(std::move(typeMergingError));
}
TpiSource *lld::coff::makeTpiSource(COFFLinkerContext &ctx, ObjFile *file) {
return make<TpiSource>(ctx, TpiSource::Regular, file);
}
TpiSource *lld::coff::makeTypeServerSource(COFFLinkerContext &ctx,
PDBInputFile *pdbInputFile) {
auto *tpiSource = make<TypeServerSource>(ctx, pdbInputFile);
if (pdbInputFile->session->getPDBFile().hasPDBIpiStream())
tpiSource->ipiSrc = make<TypeServerIpiSource>(ctx);
return tpiSource;
}
TpiSource *lld::coff::makeUseTypeServerSource(COFFLinkerContext &ctx,
ObjFile *file,
TypeServer2Record ts) {
return make<UseTypeServerSource>(ctx, file, ts);
}
TpiSource *lld::coff::makePrecompSource(COFFLinkerContext &ctx, ObjFile *file) {
return make<PrecompSource>(ctx, file);
}
TpiSource *lld::coff::makeUsePrecompSource(COFFLinkerContext &ctx,
ObjFile *file,
PrecompRecord precomp) {
return make<UsePrecompSource>(ctx, file, precomp);
}
bool TpiSource::remapTypeIndex(TypeIndex &ti, TiRefKind refKind) const {
if (ti.isSimple())
return true;
ArrayRef<TypeIndex> tpiOrIpiMap =
(refKind == TiRefKind::IndexRef) ? ipiMap : tpiMap;
if (ti.toArrayIndex() >= tpiOrIpiMap.size())
return false;
ti = tpiOrIpiMap[ti.toArrayIndex()];
return true;
}
void TpiSource::remapRecord(MutableArrayRef<uint8_t> rec,
ArrayRef<TiReference> typeRefs) {
MutableArrayRef<uint8_t> contents = rec.drop_front(sizeof(RecordPrefix));
for (const TiReference &ref : typeRefs) {
unsigned byteSize = ref.Count * sizeof(TypeIndex);
if (contents.size() < ref.Offset + byteSize)
fatal("symbol record too short");
MutableArrayRef<TypeIndex> indices(
reinterpret_cast<TypeIndex *>(contents.data() + ref.Offset), ref.Count);
for (TypeIndex &ti : indices) {
if (!remapTypeIndex(ti, ref.Kind)) {
if (ctx.config.verbose) {
uint16_t kind =
reinterpret_cast<const RecordPrefix *>(rec.data())->RecordKind;
StringRef fname = file ? file->getName() : "<unknown PDB>";
log("failed to remap type index in record of kind 0x" +
utohexstr(kind) + " in " + fname + " with bad " +
(ref.Kind == TiRefKind::IndexRef ? "item" : "type") +
" index 0x" + utohexstr(ti.getIndex()));
}
ti = TypeIndex(SimpleTypeKind::NotTranslated);
continue;
}
}
}
}
void TpiSource::remapTypesInTypeRecord(MutableArrayRef<uint8_t> rec) {
SmallVector<TiReference, 32> typeRefs;
discoverTypeIndices(CVType(rec), typeRefs);
remapRecord(rec, typeRefs);
}
bool TpiSource::remapTypesInSymbolRecord(MutableArrayRef<uint8_t> rec) {
SmallVector<TiReference, 32> typeRefs;
if (!discoverTypeIndicesInSymbol(rec, typeRefs))
return false;
remapRecord(rec, typeRefs);
return true;
}
static bool canUseDebugH(ArrayRef<uint8_t> debugH) {
if (debugH.size() < sizeof(object::debug_h_header))
return false;
auto *header =
reinterpret_cast<const object::debug_h_header *>(debugH.data());
debugH = debugH.drop_front(sizeof(object::debug_h_header));
return header->Magic == COFF::DEBUG_HASHES_SECTION_MAGIC &&
header->Version == 0 &&
header->HashAlgorithm == uint16_t(GlobalTypeHashAlg::BLAKE3) &&
(debugH.size() % 8 == 0);
}
static std::optional<ArrayRef<uint8_t>> getDebugH(ObjFile *file) {
SectionChunk *sec =
SectionChunk::findByName(file->getDebugChunks(), ".debug$H");
if (!sec)
return std::nullopt;
ArrayRef<uint8_t> contents = sec->getContents();
if (!canUseDebugH(contents))
return std::nullopt;
return contents;
}
static ArrayRef<GloballyHashedType>
getHashesFromDebugH(ArrayRef<uint8_t> debugH) {
assert(canUseDebugH(debugH));
debugH = debugH.drop_front(sizeof(object::debug_h_header));
uint32_t count = debugH.size() / sizeof(GloballyHashedType);
return {reinterpret_cast<const GloballyHashedType *>(debugH.data()), count};
}
Error TpiSource::mergeDebugT(TypeMerger *m) {
assert(!ctx.config.debugGHashes &&
"use remapTpiWithGHashes when ghash is enabled");
CVTypeArray types;
BinaryStreamReader reader(file->debugTypes, llvm::endianness::little);
cantFail(reader.readArray(types, reader.getLength()));
unsigned nbHeadIndices = indexMapStorage.size();
std::optional<PCHMergerInfo> pchInfo;
if (auto err = mergeTypeAndIdRecords(m->idTable, m->typeTable,
indexMapStorage, types, pchInfo))
fatal("codeview::mergeTypeAndIdRecords failed: " +
toString(std::move(err)));
if (pchInfo) {
file->pchSignature = pchInfo->PCHSignature;
endPrecompIdx = pchInfo->EndPrecompIndex;
}
tpiMap = indexMapStorage;
ipiMap = indexMapStorage;
if (ctx.config.showSummary) {
nbTypeRecords = indexMapStorage.size() - nbHeadIndices;
nbTypeRecordsBytes = reader.getLength();
m->tpiCounts.resize(m->getTypeTable().size());
m->ipiCounts.resize(m->getIDTable().size());
uint32_t srcIdx = nbHeadIndices;
for (const CVType &ty : types) {
TypeIndex dstIdx = tpiMap[srcIdx++];
if (dstIdx.isSimple())
continue;
SmallVectorImpl<uint32_t> &counts =
isIdRecord(ty.kind()) ? m->ipiCounts : m->tpiCounts;
++counts[dstIdx.toArrayIndex()];
}
}
return Error::success();
}
Error TypeServerSource::mergeDebugT(TypeMerger *m) {
assert(!ctx.config.debugGHashes &&
"use remapTpiWithGHashes when ghash is enabled");
pdb::PDBFile &pdbFile = pdbInputFile->session->getPDBFile();
Expected<pdb::TpiStream &> expectedTpi = pdbFile.getPDBTpiStream();
if (auto e = expectedTpi.takeError())
fatal("Type server does not have TPI stream: " + toString(std::move(e)));
pdb::TpiStream *maybeIpi = nullptr;
if (pdbFile.hasPDBIpiStream()) {
Expected<pdb::TpiStream &> expectedIpi = pdbFile.getPDBIpiStream();
if (auto e = expectedIpi.takeError())
fatal("Error getting type server IPI stream: " + toString(std::move(e)));
maybeIpi = &*expectedIpi;
}
if (auto err = mergeTypeRecords(m->typeTable, indexMapStorage,
expectedTpi->typeArray()))
fatal("codeview::mergeTypeRecords failed: " + toString(std::move(err)));
tpiMap = indexMapStorage;
if (maybeIpi) {
if (auto err = mergeIdRecords(m->idTable, tpiMap, ipiSrc->indexMapStorage,
maybeIpi->typeArray()))
fatal("codeview::mergeIdRecords failed: " + toString(std::move(err)));
ipiMap = ipiSrc->indexMapStorage;
}
if (ctx.config.showSummary) {
nbTypeRecords = tpiMap.size() + ipiMap.size();
nbTypeRecordsBytes =
expectedTpi->typeArray().getUnderlyingStream().getLength() +
(maybeIpi ? maybeIpi->typeArray().getUnderlyingStream().getLength()
: 0);
m->tpiCounts.resize(m->getTypeTable().size());
m->ipiCounts.resize(m->getIDTable().size());
for (TypeIndex ti : tpiMap)
if (!ti.isSimple())
++m->tpiCounts[ti.toArrayIndex()];
for (TypeIndex ti : ipiMap)
if (!ti.isSimple())
++m->ipiCounts[ti.toArrayIndex()];
}
return Error::success();
}
Expected<TypeServerSource *> UseTypeServerSource::getTypeServerSource() {
const codeview::GUID &tsId = typeServerDependency.getGuid();
StringRef tsPath = typeServerDependency.getName();
TypeServerSource *tsSrc = nullptr;
auto it = ctx.typeServerSourceMappings.find(tsId);
if (it != ctx.typeServerSourceMappings.end()) {
tsSrc = (TypeServerSource *)it->second;
}
if (tsSrc == nullptr) {
PDBInputFile *pdb = PDBInputFile::findFromRecordPath(ctx, tsPath, file);
if (!pdb)
return createFileError(tsPath, errorCodeToError(std::error_code(
ENOENT, std::generic_category())));
if (pdb->loadErrorStr)
return createFileError(
tsPath, make_error<StringError>(*pdb->loadErrorStr,
llvm::inconvertibleErrorCode()));
tsSrc = (TypeServerSource *)pdb->debugTypesObj;
if (tsSrc->Guid != tsId) {
return createFileError(tsPath,
make_error<pdb::PDBError>(
pdb::pdb_error_code::signature_out_of_date));
}
}
return tsSrc;
}
Error UseTypeServerSource::mergeDebugT(TypeMerger *m) {
Expected<TypeServerSource *> tsSrc = getTypeServerSource();
if (!tsSrc)
return tsSrc.takeError();
pdb::PDBFile &pdbSession = (*tsSrc)->pdbInputFile->session->getPDBFile();
auto expectedInfo = pdbSession.getPDBInfoStream();
if (!expectedInfo)
return expectedInfo.takeError();
tpiMap = (*tsSrc)->tpiMap;
ipiMap = (*tsSrc)->ipiMap;
return Error::success();
}
static bool equalsPath(StringRef path1, StringRef path2) {
#if defined(_WIN32)
return path1.equals_insensitive(path2);
#else
return path1 == path2;
#endif
}
PrecompSource *UsePrecompSource::findObjByName(StringRef fileNameOnly) {
SmallString<128> currentPath;
for (auto kv : ctx.precompSourceMappings) {
StringRef currentFileName = sys::path::filename(kv.second->file->getName(),
sys::path::Style::windows);
if (equalsPath(currentFileName, fileNameOnly))
return (PrecompSource *)kv.second;
}
return nullptr;
}
PrecompSource *UsePrecompSource::findPrecompSource(ObjFile *file,
PrecompRecord &pr) {
SmallString<128> prFileName =
sys::path::filename(pr.getPrecompFilePath(), sys::path::Style::windows);
auto it = ctx.precompSourceMappings.find(pr.getSignature());
if (it != ctx.precompSourceMappings.end()) {
return (PrecompSource *)it->second;
}
return findObjByName(prFileName);
}
Expected<PrecompSource *> UsePrecompSource::findPrecompMap(ObjFile *file,
PrecompRecord &pr) {
PrecompSource *precomp = findPrecompSource(file, pr);
if (!precomp)
return createFileError(
pr.getPrecompFilePath(),
make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch));
if (precomp->endPrecompIdx != pr.getTypesCount())
return createFileError(
toString(file),
make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch));
return precomp;
}
Error UsePrecompSource::mergeInPrecompHeaderObj() {
auto e = findPrecompMap(file, precompDependency);
if (!e)
return e.takeError();
PrecompSource *precompSrc = *e;
if (precompSrc->tpiMap.empty())
return Error::success();
assert(precompDependency.getStartTypeIndex() ==
TypeIndex::FirstNonSimpleIndex);
assert(precompDependency.getTypesCount() <= precompSrc->tpiMap.size());
indexMapStorage.insert(indexMapStorage.begin(), precompSrc->tpiMap.begin(),
precompSrc->tpiMap.begin() +
precompDependency.getTypesCount());
return Error::success();
}
Error UsePrecompSource::mergeDebugT(TypeMerger *m) {
if (Error e = mergeInPrecompHeaderObj())
return e;
return TpiSource::mergeDebugT(m);
}
Error PrecompSource::mergeDebugT(TypeMerger *m) {
if (Error e = TpiSource::mergeDebugT(m))
return e;
registerMapping();
return Error::success();
}
void PrecompSource::registerMapping() {
if (registered)
return;
if (file->pchSignature && *file->pchSignature) {
auto it = ctx.precompSourceMappings.emplace(*file->pchSignature, this);
if (!it.second)
fatal("a PCH object with the same signature has already been provided (" +
toString(it.first->second->file) + " and " + toString(file) + ")");
registered = true;
}
}
void TpiSource::loadGHashes() {
if (std::optional<ArrayRef<uint8_t>> debugH = getDebugH(file)) {
ghashes = getHashesFromDebugH(*debugH);
ownedGHashes = false;
} else {
CVTypeArray types;
BinaryStreamReader reader(file->debugTypes, llvm::endianness::little);
cantFail(reader.readArray(types, reader.getLength()));
assignGHashesFromVector(GloballyHashedType::hashTypes(types));
}
fillIsItemIndexFromDebugT();
}
void TpiSource::assignGHashesFromVector(
std::vector<GloballyHashedType> &&hashVec) {
if (hashVec.empty())
return;
GloballyHashedType *hashes = new GloballyHashedType[hashVec.size()];
memcpy(hashes, hashVec.data(), hashVec.size() * sizeof(GloballyHashedType));
ghashes = ArrayRef(hashes, hashVec.size());
ownedGHashes = true;
}
static void forEachTypeChecked(ArrayRef<uint8_t> types,
function_ref<void(const CVType &)> fn) {
checkError(
forEachCodeViewRecord<CVType>(types, [fn](const CVType &ty) -> Error {
fn(ty);
return Error::success();
}));
}
void TpiSource::fillIsItemIndexFromDebugT() {
uint32_t index = 0;
isItemIndex.resize(ghashes.size());
forEachTypeChecked(file->debugTypes, [&](const CVType &ty) {
if (isIdRecord(ty.kind()))
isItemIndex.set(index);
++index;
});
}
void TpiSource::mergeTypeRecord(TypeIndex curIndex, CVType ty) {
bool isItem = isIdRecord(ty.kind());
MergedInfo &merged = isItem ? mergedIpi : mergedTpi;
assert(ty.length() <= codeview::MaxRecordLength);
size_t offset = merged.recs.size();
size_t newSize = alignTo(ty.length(), 4);
merged.recs.resize(offset + newSize);
auto newRec = MutableArrayRef(&merged.recs[offset], newSize);
memcpy(newRec.data(), ty.data().data(), newSize);
if (newSize != ty.length()) {
reinterpret_cast<RecordPrefix *>(newRec.data())->RecordLen = newSize - 2;
for (size_t i = ty.length(); i < newSize; ++i)
newRec[i] = LF_PAD0 + (newSize - i);
}
remapTypesInTypeRecord(newRec);
uint32_t pdbHash = check(pdb::hashTypeRecord(CVType(newRec)));
merged.recSizes.push_back(static_cast<uint16_t>(newSize));
merged.recHashes.push_back(pdbHash);
if (ty.kind() == LF_FUNC_ID || ty.kind() == LF_MFUNC_ID) {
bool success = ty.length() >= 12;
TypeIndex funcId = curIndex;
if (success)
success &= remapTypeIndex(funcId, TiRefKind::IndexRef);
TypeIndex funcType =
*reinterpret_cast<const TypeIndex *>(&newRec.data()[8]);
if (success) {
funcIdToType.push_back({funcId, funcType});
} else {
StringRef fname = file ? file->getName() : "<unknown PDB>";
warn("corrupt LF_[M]FUNC_ID record 0x" + utohexstr(curIndex.getIndex()) +
" in " + fname);
}
}
}
void TpiSource::mergeUniqueTypeRecords(ArrayRef<uint8_t> typeRecords,
TypeIndex beginIndex) {
if (kind == PDB)
assert(llvm::is_sorted(uniqueTypes));
else
llvm::sort(uniqueTypes);
uint32_t ghashIndex = 0;
auto nextUniqueIndex = uniqueTypes.begin();
assert(mergedTpi.recs.empty());
assert(mergedIpi.recs.empty());
unsigned nbTpiRecs = 0;
unsigned nbIpiRecs = 0;
forEachTypeChecked(typeRecords, [&](const CVType &ty) {
if (nextUniqueIndex != uniqueTypes.end() &&
*nextUniqueIndex == ghashIndex) {
assert(ty.length() <= codeview::MaxRecordLength);
size_t newSize = alignTo(ty.length(), 4);
(isIdRecord(ty.kind()) ? nbIpiRecs : nbTpiRecs) += newSize;
++nextUniqueIndex;
}
++ghashIndex;
});
mergedTpi.recs.reserve(nbTpiRecs);
mergedIpi.recs.reserve(nbIpiRecs);
ghashIndex = 0;
nextUniqueIndex = uniqueTypes.begin();
forEachTypeChecked(typeRecords, [&](const CVType &ty) {
if (nextUniqueIndex != uniqueTypes.end() &&
*nextUniqueIndex == ghashIndex) {
mergeTypeRecord(beginIndex + ghashIndex, ty);
++nextUniqueIndex;
}
++ghashIndex;
});
assert(nextUniqueIndex == uniqueTypes.end() &&
"failed to merge all desired records");
assert(uniqueTypes.size() ==
mergedTpi.recSizes.size() + mergedIpi.recSizes.size() &&
"missing desired record");
}
void TpiSource::remapTpiWithGHashes(GHashState *g) {
assert(ctx.config.debugGHashes && "ghashes must be enabled");
fillMapFromGHashes(g);
tpiMap = indexMapStorage;
ipiMap = indexMapStorage;
mergeUniqueTypeRecords(file->debugTypes);
if (ctx.config.showSummary) {
nbTypeRecords = ghashes.size();
nbTypeRecordsBytes = file->debugTypes.size();
}
}
void TypeServerSource::loadGHashes() {
if (!ghashes.empty())
return;
pdb::PDBFile &pdbFile = pdbInputFile->session->getPDBFile();
Expected<pdb::TpiStream &> expectedTpi = pdbFile.getPDBTpiStream();
if (auto e = expectedTpi.takeError())
fatal("Type server does not have TPI stream: " + toString(std::move(e)));
assignGHashesFromVector(
GloballyHashedType::hashTypes(expectedTpi->typeArray()));
isItemIndex.resize(ghashes.size());
if (!pdbFile.hasPDBIpiStream())
return;
Expected<pdb::TpiStream &> expectedIpi = pdbFile.getPDBIpiStream();
if (auto e = expectedIpi.takeError())
fatal("error retrieving IPI stream: " + toString(std::move(e)));
ipiSrc->assignGHashesFromVector(
GloballyHashedType::hashIds(expectedIpi->typeArray(), ghashes));
ipiSrc->isItemIndex.resize(ipiSrc->ghashes.size());
ipiSrc->isItemIndex.set(0, ipiSrc->ghashes.size());
}
static ArrayRef<uint8_t> typeArrayToBytes(const CVTypeArray &types) {
BinaryStreamRef stream = types.getUnderlyingStream();
ArrayRef<uint8_t> debugTypes;
checkError(stream.readBytes(0, stream.getLength(), debugTypes));
return debugTypes;
}
void TypeServerSource::remapTpiWithGHashes(GHashState *g) {
assert(ctx.config.debugGHashes && "ghashes must be enabled");
pdb::PDBFile &pdbFile = pdbInputFile->session->getPDBFile();
pdb::TpiStream &tpi = check(pdbFile.getPDBTpiStream());
fillMapFromGHashes(g);
tpiMap = indexMapStorage;
mergeUniqueTypeRecords(typeArrayToBytes(tpi.typeArray()));
if (pdbFile.hasPDBIpiStream()) {
pdb::TpiStream &ipi = check(pdbFile.getPDBIpiStream());
ipiSrc->indexMapStorage.resize(ipiSrc->ghashes.size());
ipiSrc->fillMapFromGHashes(g);
ipiMap = ipiSrc->indexMapStorage;
ipiSrc->tpiMap = tpiMap;
ipiSrc->ipiMap = ipiMap;
ipiSrc->mergeUniqueTypeRecords(typeArrayToBytes(ipi.typeArray()));
if (ctx.config.showSummary) {
nbTypeRecords = ipiSrc->ghashes.size();
nbTypeRecordsBytes = ipi.typeArray().getUnderlyingStream().getLength();
}
}
if (ctx.config.showSummary) {
nbTypeRecords += ghashes.size();
nbTypeRecordsBytes += tpi.typeArray().getUnderlyingStream().getLength();
}
}
void UseTypeServerSource::remapTpiWithGHashes(GHashState *g) {
Expected<TypeServerSource *> maybeTsSrc = getTypeServerSource();
if (!maybeTsSrc) {
typeMergingError =
joinErrors(std::move(typeMergingError), maybeTsSrc.takeError());
return;
}
TypeServerSource *tsSrc = *maybeTsSrc;
tpiMap = tsSrc->tpiMap;
ipiMap = tsSrc->ipiMap;
}
void PrecompSource::loadGHashes() {
if (getDebugH(file)) {
warn("ignoring .debug$H section; pch with ghash is not implemented");
}
uint32_t ghashIdx = 0;
std::vector<GloballyHashedType> hashVec;
forEachTypeChecked(file->debugTypes, [&](const CVType &ty) {
if (ty.kind() == LF_ENDPRECOMP) {
EndPrecompRecord endPrecomp;
cantFail(TypeDeserializer::deserializeAs<EndPrecompRecord>(
const_cast<CVType &>(ty), endPrecomp));
file->pchSignature = endPrecomp.getSignature();
registerMapping();
endPrecompIdx = ghashIdx;
}
hashVec.push_back(GloballyHashedType::hashType(ty, hashVec, hashVec));
isItemIndex.push_back(isIdRecord(ty.kind()));
++ghashIdx;
});
assignGHashesFromVector(std::move(hashVec));
}
void UsePrecompSource::loadGHashes() {
auto e = findPrecompMap(file, precompDependency);
if (!e) {
warn(toString(e.takeError()));
return;
}
PrecompSource *pchSrc = *e;
std::vector<GloballyHashedType> hashVec =
pchSrc->ghashes.take_front(precompDependency.getTypesCount());
forEachTypeChecked(file->debugTypes, [&](const CVType &ty) {
hashVec.push_back(GloballyHashedType::hashType(ty, hashVec, hashVec));
isItemIndex.push_back(isIdRecord(ty.kind()));
});
hashVec.erase(hashVec.begin(),
hashVec.begin() + precompDependency.getTypesCount());
assignGHashesFromVector(std::move(hashVec));
}
void UsePrecompSource::remapTpiWithGHashes(GHashState *g) {
fillMapFromGHashes(g);
if (Error e = mergeInPrecompHeaderObj()) {
typeMergingError = joinErrors(std::move(typeMergingError), std::move(e));
return;
}
tpiMap = indexMapStorage;
ipiMap = indexMapStorage;
mergeUniqueTypeRecords(file->debugTypes,
TypeIndex(precompDependency.getStartTypeIndex() +
precompDependency.getTypesCount()));
if (ctx.config.showSummary) {
nbTypeRecords = ghashes.size();
nbTypeRecordsBytes = file->debugTypes.size();
}
}
namespace {
class GHashCell;
struct GHashTable {
GHashCell *table = nullptr;
uint32_t tableSize = 0;
GHashTable() = default;
~GHashTable();
void init(uint32_t newTableSize);
uint32_t insert(COFFLinkerContext &ctx, GloballyHashedType ghash,
GHashCell newCell);
};
class GHashCell {
alignas(sizeof(uint64_t)) uint64_t data = 0;
public:
GHashCell() = default;
GHashCell(bool isItem, uint32_t tpiSrcIdx, uint32_t ghashIdx)
: data((uint64_t(isItem) << 63U) | (uint64_t(tpiSrcIdx + 1) << 32ULL) |
ghashIdx) {
assert(tpiSrcIdx == getTpiSrcIdx() && "round trip failure");
assert(ghashIdx == getGHashIdx() && "round trip failure");
}
explicit GHashCell(uint64_t data) : data(data) {}
bool isEmpty() const { return data == 0ULL; }
uint32_t getTpiSrcIdx() const {
return ((uint32_t)(data >> 32U) & 0x7FFFFFFF) - 1;
}
uint32_t getGHashIdx() const { return (uint32_t)data; }
bool isItem() const { return data & (1ULL << 63U); }
GloballyHashedType getGHash(const COFFLinkerContext &ctx) const {
return ctx.tpiSourceList[getTpiSrcIdx()]->ghashes[getGHashIdx()];
}
friend inline bool operator<(const GHashCell &l, const GHashCell &r) {
return l.data < r.data;
}
};
}
namespace lld::coff {
struct GHashState {
GHashTable table;
};
}
GHashTable::~GHashTable() { delete[] table; }
void GHashTable::init(uint32_t newTableSize) {
table = new GHashCell[newTableSize];
memset(table, 0, newTableSize * sizeof(GHashCell));
tableSize = newTableSize;
}
uint32_t GHashTable::insert(COFFLinkerContext &ctx, GloballyHashedType ghash,
GHashCell newCell) {
assert(!newCell.isEmpty() && "cannot insert empty cell value");
uint32_t startIdx =
llvm::byteswap<uint64_t>(*reinterpret_cast<uint64_t *>(&ghash)) %
tableSize;
uint32_t idx = startIdx;
while (true) {
auto *cellPtr = reinterpret_cast<std::atomic<GHashCell> *>(&table[idx]);
GHashCell oldCell(cellPtr->load());
while (oldCell.isEmpty() || oldCell.getGHash(ctx) == ghash) {
if (!oldCell.isEmpty() && oldCell < newCell)
return idx;
if (cellPtr->compare_exchange_weak(oldCell, newCell))
return idx;
}
++idx;
idx = idx == tableSize ? 0 : idx;
if (idx == startIdx) {
report_fatal_error("ghash table is full");
}
}
llvm_unreachable("left infloop");
}
TypeMerger::TypeMerger(COFFLinkerContext &c, llvm::BumpPtrAllocator &alloc)
: typeTable(alloc), idTable(alloc), ctx(c) {}
TypeMerger::~TypeMerger() = default;
void TypeMerger::mergeTypesWithGHash() {
{
llvm::TimeTraceScope timeScope("Load GHASHes");
ScopedTimer t1(ctx.loadGHashTimer);
parallelForEach(dependencySources,
[&](TpiSource *source) { source->loadGHashes(); });
parallelForEach(objectSources,
[&](TpiSource *source) { source->loadGHashes(); });
}
llvm::TimeTraceScope timeScope("Merge types (GHASH)");
ScopedTimer t2(ctx.mergeGHashTimer);
GHashState ghashState;
size_t tableSize = 0;
for (TpiSource *source : ctx.tpiSourceList)
tableSize += source->ghashes.size();
tableSize =
std::min(size_t(INT32_MAX) - TypeIndex::FirstNonSimpleIndex, tableSize);
ghashState.table.init(static_cast<uint32_t>(tableSize));
parallelFor(0, ctx.tpiSourceList.size(), [&](size_t tpiSrcIdx) {
TpiSource *source = ctx.tpiSourceList[tpiSrcIdx];
source->indexMapStorage.resize(source->ghashes.size());
for (uint32_t i = 0, e = source->ghashes.size(); i < e; i++) {
if (source->shouldOmitFromPdb(i)) {
source->indexMapStorage[i] = TypeIndex(SimpleTypeKind::NotTranslated);
continue;
}
GloballyHashedType ghash = source->ghashes[i];
bool isItem = source->isItemIndex.test(i);
uint32_t cellIdx =
ghashState.table.insert(ctx, ghash, GHashCell(isItem, tpiSrcIdx, i));
source->indexMapStorage[i] = TypeIndex::fromArrayIndex(cellIdx);
}
});
std::vector<GHashCell> entries;
for (const GHashCell &cell : ArrayRef(ghashState.table.table, tableSize)) {
if (!cell.isEmpty())
entries.push_back(cell);
}
parallelSort(entries, std::less<GHashCell>());
log(formatv("ghash table load factor: {0:p} (size {1} / capacity {2})\n",
tableSize ? double(entries.size()) / tableSize : 0,
entries.size(), tableSize));
auto mid = llvm::lower_bound(entries, GHashCell(true, 0, 0));
assert((mid == entries.end() || mid->isItem()) &&
(mid == entries.begin() || !std::prev(mid)->isItem()) &&
"midpoint is not midpoint");
uint32_t numTypes = std::distance(entries.begin(), mid);
uint32_t numItems = std::distance(mid, entries.end());
log("Tpi record count: " + Twine(numTypes));
log("Ipi record count: " + Twine(numItems));
for (uint32_t i = 0, e = entries.size(); i < e; ++i) {
auto &cell = entries[i];
uint32_t tpiSrcIdx = cell.getTpiSrcIdx();
TpiSource *source = ctx.tpiSourceList[tpiSrcIdx];
source->uniqueTypes.push_back(cell.getGHashIdx());
uint32_t pdbTypeIndex = i < numTypes ? i : i - numTypes;
uint32_t ghashCellIndex =
source->indexMapStorage[cell.getGHashIdx()].toArrayIndex();
ghashState.table.table[ghashCellIndex] =
GHashCell(cell.isItem(), cell.getTpiSrcIdx(), pdbTypeIndex);
}
for (TpiSource *source : dependencySources)
source->remapTpiWithGHashes(&ghashState);
parallelForEach(objectSources, [&](TpiSource *source) {
source->remapTpiWithGHashes(&ghashState);
});
for (TpiSource *source : ctx.tpiSourceList) {
for (auto idToType : source->funcIdToType)
funcIdToType.insert(idToType);
source->funcIdToType.clear();
}
clearGHashes();
}
void TypeMerger::sortDependencies() {
std::vector<TpiSource *> deps;
std::vector<TpiSource *> objs;
for (TpiSource *s : ctx.tpiSourceList)
(s->isDependency() ? deps : objs).push_back(s);
uint32_t numDeps = deps.size();
uint32_t numObjs = objs.size();
ctx.tpiSourceList = std::move(deps);
ctx.tpiSourceList.insert(ctx.tpiSourceList.end(), objs.begin(), objs.end());
for (uint32_t i = 0, e = ctx.tpiSourceList.size(); i < e; ++i)
ctx.tpiSourceList[i]->tpiSrcIdx = i;
dependencySources = ArrayRef(ctx.tpiSourceList.data(), numDeps);
objectSources = ArrayRef(ctx.tpiSourceList.data() + numDeps, numObjs);
}
static TypeIndex loadPdbTypeIndexFromCell(GHashState *g,
uint32_t ghashCellIdx) {
GHashCell cell = g->table.table[ghashCellIdx];
return TypeIndex::fromArrayIndex(cell.getGHashIdx());
}
void TypeMerger::clearGHashes() {
for (TpiSource *src : ctx.tpiSourceList) {
if (src->ownedGHashes)
delete[] src->ghashes.data();
src->ghashes = {};
src->isItemIndex.clear();
src->uniqueTypes.clear();
}
}
void TpiSource::fillMapFromGHashes(GHashState *g) {
for (size_t i = 0, e = ghashes.size(); i < e; ++i) {
TypeIndex fakeCellIndex = indexMapStorage[i];
if (fakeCellIndex.isSimple())
indexMapStorage[i] = fakeCellIndex;
else
indexMapStorage[i] =
loadPdbTypeIndexFromCell(g, fakeCellIndex.toArrayIndex());
}
}