forked from OSchip/llvm-project
1184 lines
44 KiB
C++
1184 lines
44 KiB
C++
//===- DebugTypes.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#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/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"
|
|
|
|
using namespace llvm;
|
|
using namespace llvm::codeview;
|
|
using namespace lld;
|
|
using namespace lld::coff;
|
|
|
|
namespace {
|
|
class TypeServerIpiSource;
|
|
|
|
// The TypeServerSource class represents a PDB type server, a file referenced by
|
|
// OBJ files compiled with MSVC /Zi. A single PDB can be shared by several OBJ
|
|
// files, therefore there must be only once instance per OBJ lot. The file path
|
|
// is discovered from the dependent OBJ's debug type stream. The
|
|
// TypeServerSource object is then queued and loaded by the COFF Driver. The
|
|
// debug type stream for such PDB files will be merged first in the final PDB,
|
|
// before any dependent OBJ.
|
|
class TypeServerSource : public TpiSource {
|
|
public:
|
|
explicit TypeServerSource(COFFLinkerContext &ctx, PDBInputFile *f)
|
|
: TpiSource(ctx, PDB, nullptr), pdbInputFile(f) {
|
|
if (f->loadErr && *f->loadErr)
|
|
return;
|
|
pdb::PDBFile &file = f->session->getPDBFile();
|
|
auto expectedInfo = file.getPDBInfoStream();
|
|
if (!expectedInfo)
|
|
return;
|
|
Guid = expectedInfo->getGuid();
|
|
auto it = ctx.typeServerSourceMappings.emplace(Guid, this);
|
|
assert(it.second);
|
|
(void)it;
|
|
}
|
|
|
|
Error mergeDebugT(TypeMerger *m) override;
|
|
|
|
void loadGHashes() override;
|
|
void remapTpiWithGHashes(GHashState *g) override;
|
|
|
|
bool isDependency() const override { return true; }
|
|
|
|
PDBInputFile *pdbInputFile = nullptr;
|
|
|
|
// TpiSource for IPI stream.
|
|
TypeServerIpiSource *ipiSrc = nullptr;
|
|
|
|
// The PDB signature GUID.
|
|
codeview::GUID Guid;
|
|
};
|
|
|
|
// Companion to TypeServerSource. Stores the index map for the IPI stream in the
|
|
// PDB. Modeling PDBs with two sources for TPI and IPI helps establish the
|
|
// invariant of one type index space per source.
|
|
class TypeServerIpiSource : public TpiSource {
|
|
public:
|
|
explicit TypeServerIpiSource(COFFLinkerContext &ctx)
|
|
: TpiSource(ctx, PDBIpi, nullptr) {}
|
|
|
|
friend class TypeServerSource;
|
|
|
|
// All of the TpiSource methods are no-ops. The parent TypeServerSource
|
|
// handles both TPI and IPI.
|
|
Error mergeDebugT(TypeMerger *m) override { return Error::success(); }
|
|
void loadGHashes() override {}
|
|
void remapTpiWithGHashes(GHashState *g) override {}
|
|
bool isDependency() const override { return true; }
|
|
};
|
|
|
|
// This class represents the debug type stream of an OBJ file that depends on a
|
|
// PDB type server (see TypeServerSource).
|
|
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;
|
|
|
|
// No need to load ghashes from /Zi objects.
|
|
void loadGHashes() override {}
|
|
void remapTpiWithGHashes(GHashState *g) override;
|
|
|
|
// Information about the PDB type server dependency, that needs to be loaded
|
|
// in before merging this OBJ.
|
|
TypeServer2Record typeServerDependency;
|
|
};
|
|
|
|
// This class represents the debug type stream of a Microsoft precompiled
|
|
// headers OBJ (PCH OBJ). This OBJ kind needs to be merged first in the output
|
|
// PDB, before any other OBJs that depend on this. Note that only MSVC generate
|
|
// such files, clang does not.
|
|
class PrecompSource : public TpiSource {
|
|
public:
|
|
PrecompSource(COFFLinkerContext &ctx, ObjFile *f) : TpiSource(ctx, PCH, f) {
|
|
if (!f->pchSignature || !*f->pchSignature)
|
|
fatal(toString(f) +
|
|
" claims to be a PCH object, but does not have a valid signature");
|
|
auto it = ctx.precompSourceMappings.emplace(*f->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) + ")");
|
|
}
|
|
|
|
void loadGHashes() override;
|
|
|
|
bool isDependency() const override { return true; }
|
|
};
|
|
|
|
// This class represents the debug type stream of an OBJ file that depends on a
|
|
// Microsoft precompiled headers OBJ (see PrecompSource).
|
|
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:
|
|
// Information about the Precomp OBJ dependency, that needs to be loaded in
|
|
// before merging this OBJ.
|
|
PrecompRecord precompDependency;
|
|
};
|
|
} // namespace
|
|
|
|
TpiSource::TpiSource(COFFLinkerContext &ctx, TpiKind k, ObjFile *f)
|
|
: ctx(ctx), kind(k), tpiSrcIdx(ctx.tpiSourceList.size()), file(f) {
|
|
ctx.addTpiSource(this);
|
|
}
|
|
|
|
// Vtable key method.
|
|
TpiSource::~TpiSource() {
|
|
// Silence any assertions about unchecked errors.
|
|
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) {
|
|
// Type server sources come in pairs: the TPI stream, and the IPI stream.
|
|
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;
|
|
|
|
// This can be an item index or a type index. Choose the appropriate map.
|
|
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 (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) {
|
|
// TODO: Handle errors similar to symbols.
|
|
SmallVector<TiReference, 32> typeRefs;
|
|
discoverTypeIndices(CVType(rec), typeRefs);
|
|
remapRecord(rec, typeRefs);
|
|
}
|
|
|
|
bool TpiSource::remapTypesInSymbolRecord(MutableArrayRef<uint8_t> rec) {
|
|
// Discover type index references in the record. Skip it if we don't
|
|
// know where they are.
|
|
SmallVector<TiReference, 32> typeRefs;
|
|
if (!discoverTypeIndicesInSymbol(rec, typeRefs))
|
|
return false;
|
|
remapRecord(rec, typeRefs);
|
|
return true;
|
|
}
|
|
|
|
// A COFF .debug$H section is currently a clang extension. This function checks
|
|
// if a .debug$H section is in a format that we expect / understand, so that we
|
|
// can ignore any sections which are coincidentally also named .debug$H but do
|
|
// not contain a format we recognize.
|
|
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::SHA1_8) &&
|
|
(debugH.size() % 8 == 0);
|
|
}
|
|
|
|
static Optional<ArrayRef<uint8_t>> getDebugH(ObjFile *file) {
|
|
SectionChunk *sec =
|
|
SectionChunk::findByName(file->getDebugChunks(), ".debug$H");
|
|
if (!sec)
|
|
return llvm::None;
|
|
ArrayRef<uint8_t> contents = sec->getContents();
|
|
if (!canUseDebugH(contents))
|
|
return None;
|
|
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};
|
|
}
|
|
|
|
// Merge .debug$T for a generic object file.
|
|
Error TpiSource::mergeDebugT(TypeMerger *m) {
|
|
assert(!config->debugGHashes &&
|
|
"use remapTpiWithGHashes when ghash is enabled");
|
|
|
|
CVTypeArray types;
|
|
BinaryStreamReader reader(file->debugTypes, support::little);
|
|
cantFail(reader.readArray(types, reader.getLength()));
|
|
|
|
// When dealing with PCH.OBJ, some indices were already merged.
|
|
unsigned nbHeadIndices = indexMapStorage.size();
|
|
|
|
if (auto err = mergeTypeAndIdRecords(
|
|
m->idTable, m->typeTable, indexMapStorage, types, file->pchSignature))
|
|
fatal("codeview::mergeTypeAndIdRecords failed: " +
|
|
toString(std::move(err)));
|
|
|
|
// In an object, there is only one mapping for both types and items.
|
|
tpiMap = indexMapStorage;
|
|
ipiMap = indexMapStorage;
|
|
|
|
if (config->showSummary) {
|
|
nbTypeRecords = indexMapStorage.size() - nbHeadIndices;
|
|
nbTypeRecordsBytes = reader.getLength();
|
|
// Count how many times we saw each type record in our input. This
|
|
// calculation requires a second pass over the type records to classify each
|
|
// record as a type or index. This is slow, but this code executes when
|
|
// collecting statistics.
|
|
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++];
|
|
// Type merging may fail, so a complex source type may become the simple
|
|
// NotTranslated type, which cannot be used as an array index.
|
|
if (dstIdx.isSimple())
|
|
continue;
|
|
SmallVectorImpl<uint32_t> &counts =
|
|
isIdRecord(ty.kind()) ? m->ipiCounts : m->tpiCounts;
|
|
++counts[dstIdx.toArrayIndex()];
|
|
}
|
|
}
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
// Merge types from a type server PDB.
|
|
Error TypeServerSource::mergeDebugT(TypeMerger *m) {
|
|
assert(!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;
|
|
}
|
|
|
|
// Merge TPI first, because the IPI stream will reference type indices.
|
|
if (auto err = mergeTypeRecords(m->typeTable, indexMapStorage,
|
|
expectedTpi->typeArray()))
|
|
fatal("codeview::mergeTypeRecords failed: " + toString(std::move(err)));
|
|
tpiMap = indexMapStorage;
|
|
|
|
// Merge IPI.
|
|
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 (config->showSummary) {
|
|
nbTypeRecords = tpiMap.size() + ipiMap.size();
|
|
nbTypeRecordsBytes =
|
|
expectedTpi->typeArray().getUnderlyingStream().getLength() +
|
|
(maybeIpi ? maybeIpi->typeArray().getUnderlyingStream().getLength()
|
|
: 0);
|
|
|
|
// Count how many times we saw each type record in our input. If a
|
|
// destination type index is present in the source to destination type index
|
|
// map, that means we saw it once in the input. Add it to our histogram.
|
|
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;
|
|
auto it = ctx.typeServerSourceMappings.find(tsId);
|
|
if (it != ctx.typeServerSourceMappings.end()) {
|
|
tsSrc = (TypeServerSource *)it->second;
|
|
} else {
|
|
// The file failed to load, lookup by name
|
|
PDBInputFile *pdb = PDBInputFile::findFromRecordPath(ctx, tsPath, file);
|
|
if (!pdb)
|
|
return createFileError(tsPath, errorCodeToError(std::error_code(
|
|
ENOENT, std::generic_category())));
|
|
// If an error occurred during loading, throw it now
|
|
if (pdb->loadErr && *pdb->loadErr)
|
|
return createFileError(tsPath, std::move(*pdb->loadErr));
|
|
|
|
tsSrc = (TypeServerSource *)pdb->debugTypesObj;
|
|
|
|
// Just because a file with a matching name was found and it was an actual
|
|
// PDB file doesn't mean it matches. For it to match the InfoStream's GUID
|
|
// must match the GUID specified in the TypeServer2 record.
|
|
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();
|
|
|
|
// Reuse the type index map of the type server.
|
|
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.equals(path2);
|
|
#endif
|
|
}
|
|
|
|
// Find by name an OBJ provided on the command line
|
|
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);
|
|
|
|
// Compare based solely on the file name (link.exe behavior)
|
|
if (equalsPath(currentFileName, fileNameOnly))
|
|
return (PrecompSource *)kv.second;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
PrecompSource *UsePrecompSource::findPrecompSource(ObjFile *file,
|
|
PrecompRecord &pr) {
|
|
// Cross-compile warning: given that Clang doesn't generate LF_PRECOMP
|
|
// records, we assume the OBJ comes from a Windows build of cl.exe. Thusly,
|
|
// the paths embedded in the OBJs are in the Windows format.
|
|
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;
|
|
}
|
|
// Lookup by name
|
|
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 (pr.getSignature() != file->pchSignature)
|
|
return createFileError(
|
|
toString(file),
|
|
make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch));
|
|
|
|
if (pr.getSignature() != *precomp->file->pchSignature)
|
|
return createFileError(
|
|
toString(precomp->file),
|
|
make_error<pdb::PDBError>(pdb::pdb_error_code::no_matching_pch));
|
|
|
|
return precomp;
|
|
}
|
|
|
|
/// Merges a precompiled headers TPI map into the current TPI map. The
|
|
/// precompiled headers object will also be loaded and remapped in the
|
|
/// process.
|
|
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());
|
|
// Use the previously remapped index map from the precompiled headers.
|
|
indexMapStorage.insert(indexMapStorage.begin(), precompSrc->tpiMap.begin(),
|
|
precompSrc->tpiMap.begin() +
|
|
precompDependency.getTypesCount());
|
|
|
|
return Error::success();
|
|
}
|
|
|
|
Error UsePrecompSource::mergeDebugT(TypeMerger *m) {
|
|
// This object was compiled with /Yu, so process the corresponding
|
|
// precompiled headers object (/Yc) first. Some type indices in the current
|
|
// object are referencing data in the precompiled headers object, so we need
|
|
// both to be loaded.
|
|
if (Error e = mergeInPrecompHeaderObj())
|
|
return e;
|
|
|
|
return TpiSource::mergeDebugT(m);
|
|
}
|
|
|
|
//===----------------------------------------------------------------------===//
|
|
// Parellel GHash type merging implementation.
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
void TpiSource::loadGHashes() {
|
|
if (Optional<ArrayRef<uint8_t>> debugH = getDebugH(file)) {
|
|
ghashes = getHashesFromDebugH(*debugH);
|
|
ownedGHashes = false;
|
|
} else {
|
|
CVTypeArray types;
|
|
BinaryStreamReader reader(file->debugTypes, support::little);
|
|
cantFail(reader.readArray(types, reader.getLength()));
|
|
assignGHashesFromVector(GloballyHashedType::hashTypes(types));
|
|
}
|
|
|
|
fillIsItemIndexFromDebugT();
|
|
}
|
|
|
|
// Copies ghashes from a vector into an array. These are long lived, so it's
|
|
// worth the time to copy these into an appropriately sized vector to reduce
|
|
// memory usage.
|
|
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 = makeArrayRef(hashes, hashVec.size());
|
|
ownedGHashes = true;
|
|
}
|
|
|
|
// Faster way to iterate type records. forEachTypeChecked is faster than
|
|
// iterating CVTypeArray. It avoids virtual readBytes calls in inner loops.
|
|
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();
|
|
}));
|
|
}
|
|
|
|
// Walk over file->debugTypes and fill in the isItemIndex bit vector.
|
|
// TODO: Store this information in .debug$H so that we don't have to recompute
|
|
// it. This is the main bottleneck slowing down parallel ghashing with one
|
|
// thread over single-threaded ghashing.
|
|
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) {
|
|
// Decide if the merged type goes into TPI or IPI.
|
|
bool isItem = isIdRecord(ty.kind());
|
|
MergedInfo &merged = isItem ? mergedIpi : mergedTpi;
|
|
|
|
// Copy the type into our mutable buffer.
|
|
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 = makeMutableArrayRef(&merged.recs[offset], newSize);
|
|
memcpy(newRec.data(), ty.data().data(), newSize);
|
|
|
|
// Fix up the record prefix and padding bytes if it required resizing.
|
|
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);
|
|
}
|
|
|
|
// Remap the type indices in the new record.
|
|
remapTypesInTypeRecord(newRec);
|
|
uint32_t pdbHash = check(pdb::hashTypeRecord(CVType(newRec)));
|
|
merged.recSizes.push_back(static_cast<uint16_t>(newSize));
|
|
merged.recHashes.push_back(pdbHash);
|
|
|
|
// Retain a mapping from PDB function id to PDB function type. This mapping is
|
|
// used during symbol processing to rewrite S_GPROC32_ID symbols to S_GPROC32
|
|
// symbols.
|
|
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) {
|
|
// Re-sort the list of unique types by index.
|
|
if (kind == PDB)
|
|
assert(std::is_sorted(uniqueTypes.begin(), uniqueTypes.end()));
|
|
else
|
|
llvm::sort(uniqueTypes);
|
|
|
|
// Accumulate all the unique types into one buffer in mergedTypes.
|
|
uint32_t ghashIndex = 0;
|
|
auto nextUniqueIndex = uniqueTypes.begin();
|
|
assert(mergedTpi.recs.empty());
|
|
assert(mergedIpi.recs.empty());
|
|
|
|
// Pre-compute the number of elements in advance to avoid std::vector resizes.
|
|
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);
|
|
|
|
// Do the actual type merge.
|
|
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(config->debugGHashes && "ghashes must be enabled");
|
|
fillMapFromGHashes(g);
|
|
tpiMap = indexMapStorage;
|
|
ipiMap = indexMapStorage;
|
|
mergeUniqueTypeRecords(file->debugTypes);
|
|
// TODO: Free all unneeded ghash resources now that we have a full index map.
|
|
|
|
if (config->showSummary) {
|
|
nbTypeRecords = ghashes.size();
|
|
nbTypeRecordsBytes = file->debugTypes.size();
|
|
}
|
|
}
|
|
|
|
// PDBs do not actually store global hashes, so when merging a type server
|
|
// PDB we have to synthesize global hashes. To do this, we first synthesize
|
|
// global hashes for the TPI stream, since it is independent, then we
|
|
// synthesize hashes for the IPI stream, using the hashes for the TPI stream
|
|
// as inputs.
|
|
void TypeServerSource::loadGHashes() {
|
|
// Don't hash twice.
|
|
if (!ghashes.empty())
|
|
return;
|
|
pdb::PDBFile &pdbFile = pdbInputFile->session->getPDBFile();
|
|
|
|
// Hash TPI stream.
|
|
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());
|
|
|
|
// Hash IPI stream, which depends on TPI ghashes.
|
|
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));
|
|
|
|
// The IPI stream isItemIndex bitvector should be all ones.
|
|
ipiSrc->isItemIndex.resize(ipiSrc->ghashes.size());
|
|
ipiSrc->isItemIndex.set(0, ipiSrc->ghashes.size());
|
|
}
|
|
|
|
// Flatten discontiguous PDB type arrays to bytes so that we can use
|
|
// forEachTypeChecked instead of CVTypeArray iteration. Copying all types from
|
|
// type servers is faster than iterating all object files compiled with /Z7 with
|
|
// CVTypeArray, which has high overheads due to the virtual interface of
|
|
// BinaryStream::readBytes.
|
|
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;
|
|
}
|
|
|
|
// Merge types from a type server PDB.
|
|
void TypeServerSource::remapTpiWithGHashes(GHashState *g) {
|
|
assert(config->debugGHashes && "ghashes must be enabled");
|
|
|
|
// IPI merging depends on TPI, so do TPI first, then do IPI. No need to
|
|
// propagate errors, those should've been handled during ghash loading.
|
|
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 (config->showSummary) {
|
|
nbTypeRecords = ipiSrc->ghashes.size();
|
|
nbTypeRecordsBytes = ipi.typeArray().getUnderlyingStream().getLength();
|
|
}
|
|
}
|
|
|
|
if (config->showSummary) {
|
|
nbTypeRecords += ghashes.size();
|
|
nbTypeRecordsBytes += tpi.typeArray().getUnderlyingStream().getLength();
|
|
}
|
|
}
|
|
|
|
void UseTypeServerSource::remapTpiWithGHashes(GHashState *g) {
|
|
// No remapping to do with /Zi objects. Simply use the index map from the type
|
|
// server. Errors should have been reported earlier. Symbols from this object
|
|
// will be ignored.
|
|
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) {
|
|
// Remember the index of the LF_ENDPRECOMP record so it can be excluded from
|
|
// the PDB. There must be an entry in the list of ghashes so that the type
|
|
// indexes of the following records in the /Yc PCH object line up.
|
|
if (ty.kind() == LF_ENDPRECOMP)
|
|
endPrecompGHashIdx = ghashIdx;
|
|
|
|
hashVec.push_back(GloballyHashedType::hashType(ty, hashVec, hashVec));
|
|
isItemIndex.push_back(isIdRecord(ty.kind()));
|
|
++ghashIdx;
|
|
});
|
|
assignGHashesFromVector(std::move(hashVec));
|
|
}
|
|
|
|
void UsePrecompSource::loadGHashes() {
|
|
PrecompSource *pchSrc = findPrecompSource(file, precompDependency);
|
|
if (!pchSrc)
|
|
return;
|
|
|
|
// To compute ghashes of a /Yu object file, we need to build on the the
|
|
// ghashes of the /Yc PCH object. After we are done hashing, discard the
|
|
// ghashes from the PCH source so we don't unnecessarily try to deduplicate
|
|
// them.
|
|
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);
|
|
// This object was compiled with /Yu, so process the corresponding
|
|
// precompiled headers object (/Yc) first. Some type indices in the current
|
|
// object are referencing data in the precompiled headers object, so we need
|
|
// both to be loaded.
|
|
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 (config->showSummary) {
|
|
nbTypeRecords = ghashes.size();
|
|
nbTypeRecordsBytes = file->debugTypes.size();
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
/// A concurrent hash table for global type hashing. It is based on this paper:
|
|
/// Concurrent Hash Tables: Fast and General(?)!
|
|
/// https://dl.acm.org/doi/10.1145/3309206
|
|
///
|
|
/// This hash table is meant to be used in two phases:
|
|
/// 1. concurrent insertions
|
|
/// 2. concurrent reads
|
|
/// It does not support lookup, deletion, or rehashing. It uses linear probing.
|
|
///
|
|
/// The paper describes storing a key-value pair in two machine words.
|
|
/// Generally, the values stored in this map are type indices, and we can use
|
|
/// those values to recover the ghash key from a side table. This allows us to
|
|
/// shrink the table entries further at the cost of some loads, and sidesteps
|
|
/// the need for a 128 bit atomic compare-and-swap operation.
|
|
///
|
|
/// During insertion, a priority function is used to decide which insertion
|
|
/// should be preferred. This ensures that the output is deterministic. For
|
|
/// ghashing, lower tpiSrcIdx values (earlier inputs) are preferred.
|
|
///
|
|
class GHashCell;
|
|
struct GHashTable {
|
|
GHashCell *table = nullptr;
|
|
uint32_t tableSize = 0;
|
|
|
|
GHashTable() = default;
|
|
~GHashTable();
|
|
|
|
/// Initialize the table with the given size. Because the table cannot be
|
|
/// resized, the initial size of the table must be large enough to contain all
|
|
/// inputs, or insertion may not be able to find an empty cell.
|
|
void init(uint32_t newTableSize);
|
|
|
|
/// Insert the cell with the given ghash into the table. Return the insertion
|
|
/// position in the table. It is safe for the caller to store the insertion
|
|
/// position because the table cannot be resized.
|
|
uint32_t insert(COFFLinkerContext &ctx, GloballyHashedType ghash,
|
|
GHashCell newCell);
|
|
};
|
|
|
|
/// A ghash table cell for deduplicating types from TpiSources.
|
|
class GHashCell {
|
|
uint64_t data = 0;
|
|
|
|
public:
|
|
GHashCell() = default;
|
|
|
|
// Construct data most to least significant so that sorting works well:
|
|
// - isItem
|
|
// - tpiSrcIdx
|
|
// - ghashIdx
|
|
// Add one to the tpiSrcIdx so that the 0th record from the 0th source has a
|
|
// non-zero representation.
|
|
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) {}
|
|
|
|
// The empty cell is all zeros.
|
|
bool isEmpty() const { return data == 0ULL; }
|
|
|
|
/// Extract the tpiSrcIdx.
|
|
uint32_t getTpiSrcIdx() const {
|
|
return ((uint32_t)(data >> 32U) & 0x7FFFFFFF) - 1;
|
|
}
|
|
|
|
/// Extract the index into the ghash array of the TpiSource.
|
|
uint32_t getGHashIdx() const { return (uint32_t)data; }
|
|
|
|
bool isItem() const { return data & (1ULL << 63U); }
|
|
|
|
/// Get the ghash key for this cell.
|
|
GloballyHashedType getGHash(const COFFLinkerContext &ctx) const {
|
|
return ctx.tpiSourceList[getTpiSrcIdx()]->ghashes[getGHashIdx()];
|
|
}
|
|
|
|
/// The priority function for the cell. The data is stored such that lower
|
|
/// tpiSrcIdx and ghashIdx values are preferred, which means that type record
|
|
/// from earlier sources are more likely to prevail.
|
|
friend inline bool operator<(const GHashCell &l, const GHashCell &r) {
|
|
return l.data < r.data;
|
|
}
|
|
};
|
|
} // namespace
|
|
|
|
namespace lld {
|
|
namespace coff {
|
|
/// This type is just a wrapper around GHashTable with external linkage so it
|
|
/// can be used from a header.
|
|
struct GHashState {
|
|
GHashTable table;
|
|
};
|
|
} // namespace coff
|
|
} // namespace lld
|
|
|
|
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");
|
|
|
|
// FIXME: The low bytes of SHA1 have low entropy for short records, which
|
|
// type records are. Swap the byte order for better entropy. A better ghash
|
|
// won't need this.
|
|
uint32_t startIdx =
|
|
ByteSwap_64(*reinterpret_cast<uint64_t *>(&ghash)) % tableSize;
|
|
|
|
// Do a linear probe starting at startIdx.
|
|
uint32_t idx = startIdx;
|
|
while (true) {
|
|
// Run a compare and swap loop. There are four cases:
|
|
// - cell is empty: CAS into place and return
|
|
// - cell has matching key, earlier priority: do nothing, return
|
|
// - cell has matching key, later priority: CAS into place and return
|
|
// - cell has non-matching key: hash collision, probe next cell
|
|
auto *cellPtr = reinterpret_cast<std::atomic<GHashCell> *>(&table[idx]);
|
|
GHashCell oldCell(cellPtr->load());
|
|
while (oldCell.isEmpty() || oldCell.getGHash(ctx) == ghash) {
|
|
// Check if there is an existing ghash entry with a higher priority
|
|
// (earlier ordering). If so, this is a duplicate, we are done.
|
|
if (!oldCell.isEmpty() && oldCell < newCell)
|
|
return idx;
|
|
// Either the cell is empty, or our value is higher priority. Try to
|
|
// compare and swap. If it succeeds, we are done.
|
|
if (cellPtr->compare_exchange_weak(oldCell, newCell))
|
|
return idx;
|
|
// If the CAS failed, check this cell again.
|
|
}
|
|
|
|
// Advance the probe. Wrap around to the beginning if we run off the end.
|
|
++idx;
|
|
idx = idx == tableSize ? 0 : idx;
|
|
if (idx == startIdx) {
|
|
// If this becomes an issue, we could mark failure and rehash from the
|
|
// beginning with a bigger table. There is no difference between rehashing
|
|
// internally and starting over.
|
|
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() {
|
|
// Load ghashes. Do type servers and PCH objects first.
|
|
{
|
|
ScopedTimer t1(ctx.loadGHashTimer);
|
|
parallelForEach(dependencySources,
|
|
[&](TpiSource *source) { source->loadGHashes(); });
|
|
parallelForEach(objectSources,
|
|
[&](TpiSource *source) { source->loadGHashes(); });
|
|
}
|
|
|
|
ScopedTimer t2(ctx.mergeGHashTimer);
|
|
GHashState ghashState;
|
|
|
|
// Estimate the size of hash table needed to deduplicate ghashes. This *must*
|
|
// be larger than the number of unique types, or hash table insertion may not
|
|
// be able to find a vacant slot. Summing the input types guarantees this, but
|
|
// it is a gross overestimate. The table size could be reduced to save memory,
|
|
// but it would require implementing rehashing, and this table is generally
|
|
// small compared to total memory usage, at eight bytes per input type record,
|
|
// and most input type records are larger than eight bytes.
|
|
size_t tableSize = 0;
|
|
for (TpiSource *source : ctx.tpiSourceList)
|
|
tableSize += source->ghashes.size();
|
|
|
|
// Cap the table size so that we can use 32-bit cell indices. Type indices are
|
|
// also 32-bit, so this is an inherent PDB file format limit anyway.
|
|
tableSize =
|
|
std::min(size_t(INT32_MAX) - TypeIndex::FirstNonSimpleIndex, tableSize);
|
|
ghashState.table.init(static_cast<uint32_t>(tableSize));
|
|
|
|
// Insert ghashes in parallel. During concurrent insertion, we cannot observe
|
|
// the contents of the hash table cell, but we can remember the insertion
|
|
// position. Because the table does not rehash, the position will not change
|
|
// under insertion. After insertion is done, the value of the cell can be read
|
|
// to retrieve the final PDB type index.
|
|
parallelForEachN(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));
|
|
|
|
// Store the ghash cell index as a type index in indexMapStorage. Later
|
|
// we will replace it with the PDB type index.
|
|
source->indexMapStorage[i] = TypeIndex::fromArrayIndex(cellIdx);
|
|
}
|
|
});
|
|
|
|
// Collect all non-empty cells and sort them. This will implicitly assign
|
|
// destination type indices, and partition the entries into type records and
|
|
// item records. It arranges types in this order:
|
|
// - type records
|
|
// - source 0, type 0...
|
|
// - source 1, type 1...
|
|
// - item records
|
|
// - source 0, type 1...
|
|
// - source 1, type 0...
|
|
std::vector<GHashCell> entries;
|
|
for (const GHashCell &cell :
|
|
makeArrayRef(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));
|
|
|
|
// Find out how many type and item indices there are.
|
|
auto mid =
|
|
std::lower_bound(entries.begin(), entries.end(), 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));
|
|
|
|
// Make a list of the "unique" type records to merge for each tpi source. Type
|
|
// merging will skip indices not on this list. Store the destination PDB type
|
|
// index for these unique types in the tpiMap for each source. The entries for
|
|
// non-unique types will be filled in prior to type merging.
|
|
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());
|
|
|
|
// Update the ghash table to store the destination PDB type index in the
|
|
// table.
|
|
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);
|
|
}
|
|
|
|
// In parallel, remap all types.
|
|
for_each(dependencySources, [&](TpiSource *source) {
|
|
source->remapTpiWithGHashes(&ghashState);
|
|
});
|
|
parallelForEach(objectSources, [&](TpiSource *source) {
|
|
source->remapTpiWithGHashes(&ghashState);
|
|
});
|
|
|
|
// Build a global map of from function ID to function type.
|
|
for (TpiSource *source : ctx.tpiSourceList) {
|
|
for (auto idToType : source->funcIdToType)
|
|
funcIdToType.insert(idToType);
|
|
source->funcIdToType.clear();
|
|
}
|
|
|
|
clearGHashes();
|
|
}
|
|
|
|
void TypeMerger::sortDependencies() {
|
|
// Order dependencies first, but preserve the existing order.
|
|
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 = makeArrayRef(ctx.tpiSourceList.data(), numDeps);
|
|
objectSources = makeArrayRef(ctx.tpiSourceList.data() + numDeps, numObjs);
|
|
}
|
|
|
|
/// Given the index into the ghash table for a particular type, return the type
|
|
/// index for that type in the output PDB.
|
|
static TypeIndex loadPdbTypeIndexFromCell(GHashState *g,
|
|
uint32_t ghashCellIdx) {
|
|
GHashCell cell = g->table.table[ghashCellIdx];
|
|
return TypeIndex::fromArrayIndex(cell.getGHashIdx());
|
|
}
|
|
|
|
/// Free heap allocated ghashes.
|
|
void TypeMerger::clearGHashes() {
|
|
for (TpiSource *src : ctx.tpiSourceList) {
|
|
if (src->ownedGHashes)
|
|
delete[] src->ghashes.data();
|
|
src->ghashes = {};
|
|
src->isItemIndex.clear();
|
|
src->uniqueTypes.clear();
|
|
}
|
|
}
|
|
|
|
// Fill in a TPI or IPI index map using ghashes. For each source type, use its
|
|
// ghash to lookup its final type index in the PDB, and store that in the map.
|
|
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());
|
|
}
|
|
}
|