Re-land "[PDB] Merge types in parallel when using ghashing"
Stored Error objects have to be checked, even if they are success
values.
This reverts commit 8d250ac3cd.
Relands commit 49b3459930655d879b2dc190ff8fe11c38a8be5f..
Original commit message:
-----------------------------------------
This makes type merging much faster (-24% on chrome.dll) when multiple
threads are available, but it slightly increases the time to link (+10%)
when /threads:1 is passed. With only one more thread, the new type
merging is faster (-11%). The output PDB should be identical to what it
was before this change.
To give an idea, here is the /time output placed side by side:
BEFORE | AFTER
Input File Reading: 956 ms | 968 ms
Code Layout: 258 ms | 190 ms
Commit Output File: 6 ms | 7 ms
PDB Emission (Cumulative): 6691 ms | 4253 ms
Add Objects: 4341 ms | 2927 ms
Type Merging: 2814 ms | 1269 ms -55%!
Symbol Merging: 1509 ms | 1645 ms
Publics Stream Layout: 111 ms | 112 ms
TPI Stream Layout: 764 ms | 26 ms trivial
Commit to Disk: 1322 ms | 1036 ms -300ms
----------------------------------------- --------
Total Link Time: 8416 ms 5882 ms -30% overall
The main source of the additional overhead in the single-threaded case
is the need to iterate all .debug$T sections up front to check which
type records should go in the IPI stream. See fillIsItemIndexFromDebugT.
With changes to the .debug$H section, we could pre-calculate this info
and eliminate the need to do this walk up front. That should restore
single-threaded performance back to what it was before this change.
This change will cause LLD to be much more parallel than it used to, and
for users who do multiple links in parallel, it could regress
performance. However, when the user is only doing one link, it's a huge
improvement. In the future, we can use NT worker threads to avoid
oversaturating the machine with work, but for now, this is such an
improvement for the single-link use case that I think we should land
this as is.
Algorithm
----------
Before this change, we essentially used a
DenseMap<GloballyHashedType, TypeIndex> to check if a type has already
been seen, and if it hasn't been seen, insert it now and use the next
available type index for it in the destination type stream. DenseMap
does not support concurrent insertion, and even if it did, the linker
must be deterministic: it cannot produce different PDBs by using
different numbers of threads. The output type stream must be in the same
order regardless of the order of hash table insertions.
In order to create a hash table that supports concurrent insertion, the
table cells must be small enough that they can be updated atomically.
The algorithm I used for updating the table using linear probing is
described in this paper, "Concurrent Hash Tables: Fast and General(?)!":
https://dl.acm.org/doi/10.1145/3309206
The GHashCell in this change is essentially a pair of 32-bit integer
indices: <sourceIndex, typeIndex>. The sourceIndex is the index of the
TpiSource object, and it represents an input type stream. The typeIndex
is the index of the type in the stream. Together, we have something like
a ragged 2D array of ghashes, which can be looked up as:
tpiSources[tpiSrcIndex]->ghashes[typeIndex]
By using these side tables, we can omit the key data from the hash
table, and keep the table cell small. There is a cost to this: resolving
hash table collisions requires many more loads than simply looking at
the key in the same cache line as the insertion position. However, most
supported platforms should have a 64-bit CAS operation to update the
cell atomically.
To make the result of concurrent insertion deterministic, the cell
payloads must have a priority function. Defining one is pretty
straightforward: compare the two 32-bit numbers as a combined 64-bit
number. This means that types coming from inputs earlier on the command
line have a higher priority and are more likely to appear earlier in the
final PDB type stream than types from an input appearing later on the
link line.
After table insertion, the non-empty cells in the table can be copied
out of the main table and sorted by priority to determine the ordering
of the final type index stream. At this point, item and type records
must be separated, either by sorting or by splitting into two arrays,
and I chose sorting. This is why the GHashCell must contain the isItem
bit.
Once the final PDB TPI stream ordering is known, we need to compute a
mapping from source type index to PDB type index. To avoid starting over
from scratch and looking up every type again by its ghash, we save the
insertion position of every hash table insertion during the first
insertion phase. Because the table does not support rehashing, the
insertion position is stable. Using the array of insertion positions
indexed by source type index, we can replace the source type indices in
the ghash table cells with the PDB type indices.
Once the table cells have been updated to contain PDB type indices, the
mapping for each type source can be computed in parallel. Simply iterate
the list of cell positions and replace them with the PDB type index,
since the insertion positions are no longer needed.
Once we have a source to destination type index mapping for every type
source, there are no more data dependencies. We know which type records
are "unique" (not duplicates), and what their final type indices will
be. We can do the remapping in parallel, and accumulate type sizes and
type hashes in parallel by type source.
Lastly, TPI stream layout must be done serially. Accumulate all the type
records, sizes, and hashes, and add them to the PDB.
Differential Revision: https://reviews.llvm.org/D87805
This commit is contained in:
1 parent
722d792499
commit
5519e4da83
18 files changed
+1083
-235
No files matched your search
+749
-96
File diff suppressed because it is too large.
Load diff
+111
-5
@@ -10,32 +10,37 @@
|
||||
#define LLD_COFF_DEBUGTYPES_H
|
||||
|
||||
#include "lld/Common/LLVM.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeIndex.h"
|
||||
#include "llvm/ADT/BitVector.h"
|
||||
#include "llvm/ADT/DenseMap.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeIndexDiscovery.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeRecord.h"
|
||||
#include "llvm/Support/Error.h"
|
||||
#include "llvm/Support/MemoryBuffer.h"
|
||||
|
||||
namespace llvm {
|
||||
namespace codeview {
|
||||
class PrecompRecord;
|
||||
class TypeServer2Record;
|
||||
struct GloballyHashedType;
|
||||
} // namespace codeview
|
||||
namespace pdb {
|
||||
class NativeSession;
|
||||
class TpiStream;
|
||||
}
|
||||
} // namespace llvm
|
||||
|
||||
namespace lld {
|
||||
namespace coff {
|
||||
|
||||
using llvm::codeview::GloballyHashedType;
|
||||
using llvm::codeview::TypeIndex;
|
||||
|
||||
class ObjFile;
|
||||
class PDBInputFile;
|
||||
class TypeMerger;
|
||||
struct GHashState;
|
||||
|
||||
class TpiSource {
|
||||
public:
|
||||
enum TpiKind { Regular, PCH, UsingPCH, PDB, PDBIpi, UsingPDB };
|
||||
enum TpiKind : uint8_t { Regular, PCH, UsingPCH, PDB, PDBIpi, UsingPDB };
|
||||
|
||||
TpiSource(TpiKind k, ObjFile *f);
|
||||
virtual ~TpiSource();
|
||||
@@ -53,21 +58,97 @@ public:
|
||||
/// caller-provided ObjectIndexMap.
|
||||
virtual Error mergeDebugT(TypeMerger *m);
|
||||
|
||||
/// Load global hashes, either by hashing types directly, or by loading them
|
||||
/// from LLVM's .debug$H section.
|
||||
virtual void loadGHashes();
|
||||
|
||||
/// Use global hashes to merge type information.
|
||||
virtual void remapTpiWithGHashes(GHashState *g);
|
||||
|
||||
// Remap a type index in place.
|
||||
bool remapTypeIndex(TypeIndex &ti, llvm::codeview::TiRefKind refKind) const;
|
||||
|
||||
protected:
|
||||
void remapRecord(MutableArrayRef<uint8_t> rec,
|
||||
ArrayRef<llvm::codeview::TiReference> typeRefs);
|
||||
|
||||
void mergeTypeRecord(llvm::codeview::CVType ty);
|
||||
|
||||
// Merge the type records listed in uniqueTypes. beginIndex is the TypeIndex
|
||||
// of the first record in this source, typically 0x1000. When PCHs are
|
||||
// involved, it may start higher.
|
||||
void mergeUniqueTypeRecords(
|
||||
ArrayRef<uint8_t> debugTypes,
|
||||
TypeIndex beginIndex = TypeIndex(TypeIndex::FirstNonSimpleIndex));
|
||||
|
||||
// Use the ghash table to construct a map from source type index to
|
||||
// destination PDB type index. Usable for either TPI or IPI.
|
||||
void fillMapFromGHashes(GHashState *m,
|
||||
llvm::SmallVectorImpl<TypeIndex> &indexMap);
|
||||
|
||||
// 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 assignGHashesFromVector(std::vector<GloballyHashedType> &&hashVec);
|
||||
|
||||
// Walk over file->debugTypes and fill in the isItemIndex bit vector.
|
||||
void fillIsItemIndexFromDebugT();
|
||||
|
||||
public:
|
||||
bool remapTypesInSymbolRecord(MutableArrayRef<uint8_t> rec);
|
||||
|
||||
void remapTypesInTypeRecord(MutableArrayRef<uint8_t> rec);
|
||||
|
||||
/// Is this a dependent file that needs to be processed first, before other
|
||||
/// OBJs?
|
||||
virtual bool isDependency() const { return false; }
|
||||
|
||||
static void forEachSource(llvm::function_ref<void(TpiSource *)> fn);
|
||||
/// Returns true if this type record should be omitted from the PDB, even if
|
||||
/// it is unique. This prevents a record from being added to the input ghash
|
||||
/// table.
|
||||
bool shouldOmitFromPdb(uint32_t ghashIdx) {
|
||||
return ghashIdx == endPrecompGHashIdx;
|
||||
}
|
||||
|
||||
/// All sources of type information in the program.
|
||||
static std::vector<TpiSource *> instances;
|
||||
|
||||
/// Dependency type sources, such as type servers or PCH object files. These
|
||||
/// must be processed before objects that rely on them. Set by
|
||||
/// TpiSources::sortDependencies.
|
||||
static ArrayRef<TpiSource *> dependencySources;
|
||||
|
||||
/// Object file sources. These must be processed after dependencySources.
|
||||
static ArrayRef<TpiSource *> objectSources;
|
||||
|
||||
/// Sorts the dependencies and reassigns TpiSource indices.
|
||||
static void sortDependencies();
|
||||
|
||||
static uint32_t countTypeServerPDBs();
|
||||
static uint32_t countPrecompObjs();
|
||||
|
||||
/// Free heap allocated ghashes.
|
||||
static void clearGHashes();
|
||||
|
||||
/// Clear global data structures for TpiSources.
|
||||
static void clear();
|
||||
|
||||
const TpiKind kind;
|
||||
bool ownedGHashes = true;
|
||||
uint32_t tpiSrcIdx = 0;
|
||||
|
||||
protected:
|
||||
/// The ghash index (zero based, not 0x1000-based) of the LF_ENDPRECOMP record
|
||||
/// in this object, if one exists. This is the all ones value otherwise. It is
|
||||
/// recorded here so that it can be omitted from the final ghash table.
|
||||
uint32_t endPrecompGHashIdx = ~0U;
|
||||
|
||||
public:
|
||||
ObjFile *file;
|
||||
|
||||
/// An error encountered during type merging, if any.
|
||||
Error typeMergingError = Error::success();
|
||||
|
||||
// Storage for tpiMap or ipiMap, depending on the kind of source.
|
||||
llvm::SmallVector<TypeIndex, 0> indexMapStorage;
|
||||
|
||||
@@ -76,6 +157,31 @@ public:
|
||||
// objects.
|
||||
llvm::ArrayRef<TypeIndex> tpiMap;
|
||||
llvm::ArrayRef<TypeIndex> ipiMap;
|
||||
|
||||
/// Array of global type hashes, indexed by TypeIndex. May be calculated on
|
||||
/// demand, or present in input object files.
|
||||
llvm::ArrayRef<llvm::codeview::GloballyHashedType> ghashes;
|
||||
|
||||
/// When ghashing is used, record the mapping from LF_[M]FUNC_ID to function
|
||||
/// type index here. Both indices are PDB indices, not object type indexes.
|
||||
llvm::DenseMap<TypeIndex, TypeIndex> funcIdToType;
|
||||
|
||||
/// Indicates if a type record is an item index or a type index.
|
||||
llvm::BitVector isItemIndex;
|
||||
|
||||
/// A list of all "unique" type indices which must be merged into the final
|
||||
/// PDB. GHash type deduplication produces this list, and it should be
|
||||
/// considerably smaller than the input.
|
||||
std::vector<uint32_t> uniqueTypes;
|
||||
|
||||
struct MergedInfo {
|
||||
std::vector<uint8_t> recs;
|
||||
std::vector<uint16_t> recSizes;
|
||||
std::vector<uint32_t> recHashes;
|
||||
};
|
||||
|
||||
MergedInfo mergedTpi;
|
||||
MergedInfo mergedIpi;
|
||||
};
|
||||
|
||||
TpiSource *makeTpiSource(ObjFile *file);
|
||||
|
||||
+1
-1
@@ -69,13 +69,13 @@ bool link(ArrayRef<const char *> args, bool canExitEarly, raw_ostream &stdoutOS,
|
||||
lld::stderrOS = &stderrOS;
|
||||
|
||||
errorHandler().cleanupCallback = []() {
|
||||
TpiSource::clear();
|
||||
freeArena();
|
||||
ObjFile::instances.clear();
|
||||
PDBInputFile::instances.clear();
|
||||
ImportFile::instances.clear();
|
||||
BitcodeFile::instances.clear();
|
||||
memset(MergeChunk::instances, 0, sizeof(MergeChunk::instances));
|
||||
TpiSource::clear();
|
||||
OutputSection::clear();
|
||||
};
|
||||
|
||||
|
||||
+88
-90
@@ -66,7 +66,8 @@ using llvm::object::coff_section;
|
||||
static ExitOnError exitOnErr;
|
||||
|
||||
static Timer totalPdbLinkTimer("PDB Emission (Cumulative)", Timer::root());
|
||||
|
||||
Timer lld::coff::loadGHashTimer("Global Type Hashing", totalPdbLinkTimer);
|
||||
Timer lld::coff::mergeGHashTimer("GHash Type Merging", totalPdbLinkTimer);
|
||||
static Timer addObjectsTimer("Add Objects", totalPdbLinkTimer);
|
||||
static Timer typeMergingTimer("Type Merging", addObjectsTimer);
|
||||
static Timer symbolMergingTimer("Symbol Merging", addObjectsTimer);
|
||||
@@ -112,8 +113,6 @@ public:
|
||||
/// externally.
|
||||
void addDebug(TpiSource *source);
|
||||
|
||||
bool mergeTypeRecords(TpiSource *source);
|
||||
|
||||
void addDebugSymbols(TpiSource *source);
|
||||
|
||||
void mergeSymbolRecords(TpiSource *source,
|
||||
@@ -250,43 +249,18 @@ static void addTypeInfo(pdb::TpiStreamBuilder &tpiBuilder,
|
||||
});
|
||||
}
|
||||
|
||||
static bool remapTypeIndex(TypeIndex &ti, ArrayRef<TypeIndex> typeIndexMap) {
|
||||
if (ti.isSimple())
|
||||
return true;
|
||||
if (ti.toArrayIndex() >= typeIndexMap.size())
|
||||
return false;
|
||||
ti = typeIndexMap[ti.toArrayIndex()];
|
||||
return true;
|
||||
}
|
||||
|
||||
static void remapTypesInSymbolRecord(ObjFile *file, SymbolKind symKind,
|
||||
MutableArrayRef<uint8_t> recordBytes,
|
||||
TpiSource *source,
|
||||
ArrayRef<TiReference> typeRefs) {
|
||||
MutableArrayRef<uint8_t> contents =
|
||||
recordBytes.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");
|
||||
|
||||
// This can be an item index or a type index. Choose the appropriate map.
|
||||
bool isItemIndex = ref.Kind == TiRefKind::IndexRef;
|
||||
ArrayRef<TypeIndex> typeOrItemMap =
|
||||
isItemIndex ? source->ipiMap : source->tpiMap;
|
||||
|
||||
MutableArrayRef<TypeIndex> tIs(
|
||||
reinterpret_cast<TypeIndex *>(contents.data() + ref.Offset), ref.Count);
|
||||
for (TypeIndex &ti : tIs) {
|
||||
if (!remapTypeIndex(ti, typeOrItemMap)) {
|
||||
log("ignoring symbol record of kind 0x" + utohexstr(symKind) + " in " +
|
||||
file->getName() + " with bad " + (isItemIndex ? "item" : "type") +
|
||||
" index 0x" + utohexstr(ti.getIndex()));
|
||||
ti = TypeIndex(SimpleTypeKind::NotTranslated);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void addGHashTypeInfo(pdb::PDBFileBuilder &builder) {
|
||||
// Start the TPI or IPI stream header.
|
||||
builder.getTpiBuilder().setVersionHeader(pdb::PdbTpiV80);
|
||||
builder.getIpiBuilder().setVersionHeader(pdb::PdbTpiV80);
|
||||
for_each(TpiSource::instances, [&](TpiSource *source) {
|
||||
builder.getTpiBuilder().addTypeRecords(source->mergedTpi.recs,
|
||||
source->mergedTpi.recSizes,
|
||||
source->mergedTpi.recHashes);
|
||||
builder.getIpiBuilder().addTypeRecords(source->mergedIpi.recs,
|
||||
source->mergedIpi.recSizes,
|
||||
source->mergedIpi.recHashes);
|
||||
});
|
||||
}
|
||||
|
||||
static void
|
||||
@@ -329,7 +303,7 @@ static SymbolKind symbolKind(ArrayRef<uint8_t> recordData) {
|
||||
|
||||
/// MSVC translates S_PROC_ID_END to S_END, and S_[LG]PROC32_ID to S_[LG]PROC32
|
||||
static void translateIdSymbols(MutableArrayRef<uint8_t> &recordData,
|
||||
TypeCollection &idTable) {
|
||||
TypeMerger &tMerger, TpiSource *source) {
|
||||
RecordPrefix *prefix = reinterpret_cast<RecordPrefix *>(recordData.data());
|
||||
|
||||
SymbolKind kind = symbolKind(recordData);
|
||||
@@ -356,13 +330,25 @@ static void translateIdSymbols(MutableArrayRef<uint8_t> &recordData,
|
||||
reinterpret_cast<TypeIndex *>(content.data() + refs[0].Offset);
|
||||
// `ti` is the index of a FuncIdRecord or MemberFuncIdRecord which lives in
|
||||
// the IPI stream, whose `FunctionType` member refers to the TPI stream.
|
||||
// Note that LF_FUNC_ID and LF_MEMFUNC_ID have the same record layout, and
|
||||
// Note that LF_FUNC_ID and LF_MFUNC_ID have the same record layout, and
|
||||
// in both cases we just need the second type index.
|
||||
if (!ti->isSimple() && !ti->isNoneType()) {
|
||||
CVType funcIdData = idTable.getType(*ti);
|
||||
ArrayRef<uint8_t> tiBuf = funcIdData.data().slice(8, 4);
|
||||
assert(tiBuf.size() == 4 && "corrupt LF_[MEM]FUNC_ID record");
|
||||
*ti = *reinterpret_cast<const TypeIndex *>(tiBuf.data());
|
||||
if (config->debugGHashes) {
|
||||
auto idToType = source->funcIdToType.find(*ti);
|
||||
if (idToType == source->funcIdToType.end()) {
|
||||
warn(formatv("S_[GL]PROC32_ID record in {0} refers to PDB item "
|
||||
"index {1:X} which is not a LF_[M]FUNC_ID record",
|
||||
source->file->getName(), ti->getIndex()));
|
||||
*ti = TypeIndex(SimpleTypeKind::NotTranslated);
|
||||
} else {
|
||||
*ti = idToType->second;
|
||||
}
|
||||
} else {
|
||||
CVType funcIdData = tMerger.getIDTable().getType(*ti);
|
||||
ArrayRef<uint8_t> tiBuf = funcIdData.data().slice(8, 4);
|
||||
assert(tiBuf.size() == 4 && "corrupt LF_[M]FUNC_ID record");
|
||||
*ti = *reinterpret_cast<const TypeIndex *>(tiBuf.data());
|
||||
}
|
||||
}
|
||||
|
||||
kind = (kind == SymbolKind::S_GPROC32_ID) ? SymbolKind::S_GPROC32
|
||||
@@ -561,22 +547,16 @@ void PDBLinker::mergeSymbolRecords(TpiSource *source,
|
||||
const_cast<uint8_t *>(sym.data().data()), sym.length());
|
||||
}
|
||||
|
||||
// Discover type index references in the record. Skip it if we don't
|
||||
// know where they are.
|
||||
SmallVector<TiReference, 32> typeRefs;
|
||||
if (!discoverTypeIndicesInSymbol(sym, typeRefs)) {
|
||||
log("ignoring unknown symbol record with kind 0x" +
|
||||
utohexstr(sym.kind()));
|
||||
// Re-map all the type index references.
|
||||
if (!source->remapTypesInSymbolRecord(recordBytes)) {
|
||||
log("error remapping types in symbol of kind 0x" +
|
||||
utohexstr(sym.kind()) + ", ignoring");
|
||||
return Error::success();
|
||||
}
|
||||
|
||||
// Re-map all the type index references.
|
||||
remapTypesInSymbolRecord(file, sym.kind(), recordBytes, source,
|
||||
typeRefs);
|
||||
|
||||
// An object file may have S_xxx_ID symbols, but these get converted to
|
||||
// "real" symbols in a PDB.
|
||||
translateIdSymbols(recordBytes, tMerger.getIDTable());
|
||||
translateIdSymbols(recordBytes, tMerger, source);
|
||||
sym = CVSymbol(recordBytes);
|
||||
|
||||
// If this record refers to an offset in the object file's string table,
|
||||
@@ -748,11 +728,15 @@ void DebugSHandler::mergeInlineeLines(
|
||||
const DebugSubsectionRecord &inlineeSubsection) {
|
||||
DebugInlineeLinesSubsectionRef inlineeLines;
|
||||
exitOnErr(inlineeLines.initialize(inlineeSubsection.getRecordData()));
|
||||
if (!source) {
|
||||
warn("ignoring inlinee lines section in file that lacks type information");
|
||||
return;
|
||||
}
|
||||
|
||||
// Remap type indices in inlinee line records in place.
|
||||
for (const InlineeSourceLine &line : inlineeLines) {
|
||||
TypeIndex &inlinee = *const_cast<TypeIndex *>(&line.Header->Inlinee);
|
||||
if (!remapTypeIndex(inlinee, source->ipiMap)) {
|
||||
if (!source->remapTypeIndex(inlinee, TiRefKind::IndexRef)) {
|
||||
log("bad inlinee line record in " + file.getName() +
|
||||
" with bad inlinee index 0x" + utohexstr(inlinee.getIndex()));
|
||||
}
|
||||
@@ -827,20 +811,6 @@ static void warnUnusable(InputFile *f, Error e) {
|
||||
warn(msg);
|
||||
}
|
||||
|
||||
bool PDBLinker::mergeTypeRecords(TpiSource *source) {
|
||||
ScopedTimer t(typeMergingTimer);
|
||||
// Before we can process symbol substreams from .debug$S, we need to process
|
||||
// type information, file checksums, and the string table. Add type info to
|
||||
// the PDB first, so that we can get the map from object file type and item
|
||||
// indices to PDB type and item indices.
|
||||
if (Error e = source->mergeDebugT(&tMerger)) {
|
||||
// If the .debug$T sections fail to merge, assume there is no debug info.
|
||||
warnUnusable(source->file, std::move(e));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Allocate memory for a .debug$S / .debug$F section and relocate it.
|
||||
static ArrayRef<uint8_t> relocateDebugChunk(SectionChunk &debugChunk) {
|
||||
uint8_t *buffer = bAlloc.Allocate<uint8_t>(debugChunk.getSize());
|
||||
@@ -920,9 +890,28 @@ static void createModuleDBI(pdb::PDBFileBuilder &builder, ObjFile *file) {
|
||||
}
|
||||
|
||||
void PDBLinker::addDebug(TpiSource *source) {
|
||||
// Before we can process symbol substreams from .debug$S, we need to process
|
||||
// type information, file checksums, and the string table. Add type info to
|
||||
// the PDB first, so that we can get the map from object file type and item
|
||||
// indices to PDB type and item indices. If we are using ghashes, types have
|
||||
// already been merged.
|
||||
if (!config->debugGHashes) {
|
||||
ScopedTimer t(typeMergingTimer);
|
||||
if (Error e = source->mergeDebugT(&tMerger)) {
|
||||
// If type merging failed, ignore the symbols.
|
||||
warnUnusable(source->file, std::move(e));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If type merging failed, ignore the symbols.
|
||||
if (mergeTypeRecords(source))
|
||||
addDebugSymbols(source);
|
||||
Error typeError = std::move(source->typeMergingError);
|
||||
if (typeError) {
|
||||
warnUnusable(source->file, std::move(typeError));
|
||||
return;
|
||||
}
|
||||
|
||||
addDebugSymbols(source);
|
||||
}
|
||||
|
||||
static pdb::BulkPublic createPublic(Defined *def) {
|
||||
@@ -955,25 +944,31 @@ void PDBLinker::addObjectsToPDB() {
|
||||
for_each(ObjFile::instances,
|
||||
[&](ObjFile *obj) { createModuleDBI(builder, obj); });
|
||||
|
||||
// Merge dependencies
|
||||
TpiSource::forEachSource([&](TpiSource *source) {
|
||||
if (source->isDependency())
|
||||
addDebug(source);
|
||||
});
|
||||
// Reorder dependency type sources to come first.
|
||||
TpiSource::sortDependencies();
|
||||
|
||||
// Merge regular and dependent OBJs
|
||||
TpiSource::forEachSource([&](TpiSource *source) {
|
||||
if (!source->isDependency())
|
||||
addDebug(source);
|
||||
});
|
||||
// Merge type information from input files using global type hashing.
|
||||
if (config->debugGHashes)
|
||||
tMerger.mergeTypesWithGHash();
|
||||
|
||||
// Merge dependencies and then regular objects.
|
||||
for_each(TpiSource::dependencySources,
|
||||
[&](TpiSource *source) { addDebug(source); });
|
||||
for_each(TpiSource::objectSources,
|
||||
[&](TpiSource *source) { addDebug(source); });
|
||||
|
||||
builder.getStringTableBuilder().setStrings(pdbStrTab);
|
||||
t1.stop();
|
||||
|
||||
// Construct TPI and IPI stream contents.
|
||||
ScopedTimer t2(tpiStreamLayoutTimer);
|
||||
addTypeInfo(builder.getTpiBuilder(), tMerger.getTypeTable());
|
||||
addTypeInfo(builder.getIpiBuilder(), tMerger.getIDTable());
|
||||
// Collect all the merged types.
|
||||
if (config->debugGHashes) {
|
||||
addGHashTypeInfo(builder);
|
||||
} else {
|
||||
addTypeInfo(builder.getTpiBuilder(), tMerger.getTypeTable());
|
||||
addTypeInfo(builder.getIpiBuilder(), tMerger.getIDTable());
|
||||
}
|
||||
t2.stop();
|
||||
}
|
||||
|
||||
@@ -1014,8 +1009,8 @@ void PDBLinker::printStats() {
|
||||
"Input OBJ files (expanded from all cmd-line inputs)");
|
||||
print(TpiSource::countTypeServerPDBs(), "PDB type server dependencies");
|
||||
print(TpiSource::countPrecompObjs(), "Precomp OBJ dependencies");
|
||||
print(tMerger.getTypeTable().size() + tMerger.getIDTable().size(),
|
||||
"Merged TPI records");
|
||||
print(builder.getTpiBuilder().getRecordCount(), "Merged TPI records");
|
||||
print(builder.getIpiBuilder().getRecordCount(), "Merged IPI records");
|
||||
print(pdbStrTab.size(), "Output PDB strings");
|
||||
print(globalSymbols, "Global symbol records");
|
||||
print(moduleSymbols, "Module symbol records");
|
||||
@@ -1067,8 +1062,11 @@ void PDBLinker::printStats() {
|
||||
}
|
||||
};
|
||||
|
||||
printLargeInputTypeRecs("TPI", tMerger.tpiCounts, tMerger.getTypeTable());
|
||||
printLargeInputTypeRecs("IPI", tMerger.ipiCounts, tMerger.getIDTable());
|
||||
if (!config->debugGHashes) {
|
||||
// FIXME: Reimplement for ghash.
|
||||
printLargeInputTypeRecs("TPI", tMerger.tpiCounts, tMerger.getTypeTable());
|
||||
printLargeInputTypeRecs("IPI", tMerger.ipiCounts, tMerger.getIDTable());
|
||||
}
|
||||
|
||||
message(buffer);
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ union DebugInfo;
|
||||
}
|
||||
|
||||
namespace lld {
|
||||
class Timer;
|
||||
|
||||
namespace coff {
|
||||
class OutputSection;
|
||||
class SectionChunk;
|
||||
@@ -32,6 +34,10 @@ void createPDB(SymbolTable *symtab,
|
||||
|
||||
llvm::Optional<std::pair<llvm::StringRef, uint32_t>>
|
||||
getFileLineCodeView(const SectionChunk *c, uint32_t addr);
|
||||
|
||||
extern Timer loadGHashTimer;
|
||||
extern Timer mergeGHashTimer;
|
||||
|
||||
} // namespace coff
|
||||
} // namespace lld
|
||||
|
||||
|
||||
+16
-14
@@ -10,45 +10,47 @@
|
||||
#define LLD_COFF_TYPEMERGER_H
|
||||
|
||||
#include "Config.h"
|
||||
#include "llvm/DebugInfo/CodeView/GlobalTypeTableBuilder.h"
|
||||
#include "llvm/DebugInfo/CodeView/MergingTypeTableBuilder.h"
|
||||
#include "llvm/DebugInfo/CodeView/TypeHashing.h"
|
||||
#include "llvm/Support/Allocator.h"
|
||||
#include <atomic>
|
||||
|
||||
namespace lld {
|
||||
namespace coff {
|
||||
|
||||
using llvm::codeview::GloballyHashedType;
|
||||
using llvm::codeview::TypeIndex;
|
||||
|
||||
struct GHashState;
|
||||
|
||||
class TypeMerger {
|
||||
public:
|
||||
TypeMerger(llvm::BumpPtrAllocator &alloc)
|
||||
: typeTable(alloc), idTable(alloc), globalTypeTable(alloc),
|
||||
globalIDTable(alloc) {}
|
||||
TypeMerger(llvm::BumpPtrAllocator &alloc);
|
||||
|
||||
~TypeMerger();
|
||||
|
||||
/// Get the type table or the global type table if /DEBUG:GHASH is enabled.
|
||||
inline llvm::codeview::TypeCollection &getTypeTable() {
|
||||
if (config->debugGHashes)
|
||||
return globalTypeTable;
|
||||
assert(!config->debugGHashes);
|
||||
return typeTable;
|
||||
}
|
||||
|
||||
/// Get the ID table or the global ID table if /DEBUG:GHASH is enabled.
|
||||
inline llvm::codeview::TypeCollection &getIDTable() {
|
||||
if (config->debugGHashes)
|
||||
return globalIDTable;
|
||||
assert(!config->debugGHashes);
|
||||
return idTable;
|
||||
}
|
||||
|
||||
/// Use global hashes to eliminate duplicate types and identify unique type
|
||||
/// indices in each TpiSource.
|
||||
void mergeTypesWithGHash();
|
||||
|
||||
/// Type records that will go into the PDB TPI stream.
|
||||
llvm::codeview::MergingTypeTableBuilder typeTable;
|
||||
|
||||
/// Item records that will go into the PDB IPI stream.
|
||||
llvm::codeview::MergingTypeTableBuilder idTable;
|
||||
|
||||
/// Type records that will go into the PDB TPI stream (for /DEBUG:GHASH)
|
||||
llvm::codeview::GlobalTypeTableBuilder globalTypeTable;
|
||||
|
||||
/// Item records that will go into the PDB IPI stream (for /DEBUG:GHASH)
|
||||
llvm::codeview::GlobalTypeTableBuilder globalIDTable;
|
||||
|
||||
// When showSummary is enabled, these are histograms of TPI and IPI records
|
||||
// keyed by type index.
|
||||
SmallVector<uint32_t, 0> tpiCounts;
|
||||
|
||||
@@ -153,6 +153,13 @@ template <class T> T check(Expected<T> e) {
|
||||
return std::move(*e);
|
||||
}
|
||||
|
||||
// Don't move from Expected wrappers around references.
|
||||
template <class T> T &check(Expected<T &> e) {
|
||||
if (!e)
|
||||
fatal(llvm::toString(e.takeError()));
|
||||
return *e;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T check2(ErrorOr<T> e, llvm::function_ref<std::string()> prefix) {
|
||||
if (auto ec = e.getError())
|
||||
|
||||
@@ -2,7 +2,7 @@ RUN: yaml2obj %p/Inputs/pdb-hashes-1.yaml -o %t.1.obj
|
||||
RUN: yaml2obj %p/Inputs/pdb-hashes-2.yaml -o %t.2.obj
|
||||
RUN: yaml2obj %p/Inputs/pdb-hashes-2-missing.yaml -o %t.2.missing.obj
|
||||
RUN: lld-link /debug %t.1.obj %t.2.obj /entry:main /nodefaultlib /PDB:%t.nohash.pdb
|
||||
RUN: lld-link /debug:ghash %t.1.obj %t.2.obj /entry:main /nodefaultlib /PDB:%t.hash.pdb
|
||||
RUN: lld-link /debug:ghash -verbose %t.1.obj %t.2.obj /entry:main /nodefaultlib /PDB:%t.hash.pdb
|
||||
RUN: lld-link /debug:ghash %t.1.obj %t.2.missing.obj /entry:main /nodefaultlib /PDB:%t.mixed.pdb
|
||||
RUN: llvm-pdbutil dump -types -ids -dont-resolve-forward-refs %t.nohash.pdb | FileCheck %s
|
||||
RUN: llvm-pdbutil dump -types -ids -dont-resolve-forward-refs %t.hash.pdb | FileCheck %s
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
# RUN: yaml2obj %p/Inputs/pdb1.yaml -o %t1.obj
|
||||
# RUN: yaml2obj %p/Inputs/pdb2.yaml -o %t2.obj
|
||||
# RUN: yaml2obj < %p/Inputs/pdb1.yaml > %t1.obj
|
||||
# RUN: yaml2obj < %p/Inputs/pdb2.yaml > %t2.obj
|
||||
|
||||
# RUN: lld-link /debug /pdb:%t.pdb /dll /out:%t.dll /entry:main /nodefaultlib \
|
||||
# RUN: %t1.obj %t2.obj
|
||||
# RUN: llvm-pdbutil dump -symbols %t.pdb | FileCheck %s
|
||||
|
||||
# RUN: lld-link /debug /debug:ghash /pdb:%t.pdb /dll /out:%t.dll /entry:main /nodefaultlib \
|
||||
# RUN: %t1.obj %t2.obj
|
||||
# RUN: llvm-pdbutil dump -symbols %t.pdb | FileCheck %s
|
||||
|
||||
CHECK: Symbols
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
# RUN: yaml2obj %s -o %t1.obj
|
||||
# RUN: yaml2obj %p/Inputs/pdb-type-server-missing-2.yaml -o %t2.obj
|
||||
# RUN: lld-link %t1.obj %t2.obj -out:%t.exe -debug -pdb:%t.pdb -nodefaultlib -entry:main 2>&1 | FileCheck %s -check-prefix=WARN
|
||||
# RUN: lld-link %t1.obj %t2.obj -out:%t.exe -debug:ghash -pdb:%t.pdb -nodefaultlib -entry:main 2>&1 | FileCheck %s -check-prefix=WARN
|
||||
# RUN: lld-link %t1.obj %t2.obj -out:%t.exe -debug -pdb:%t.pdb -nodefaultlib -entry:main /ignore:4099 2>&1 | FileCheck %s -check-prefix=IGNORE -allow-empty
|
||||
# RUN: not lld-link %t1.obj %t2.obj -out:%t.exe -debug -pdb:%t.pdb -nodefaultlib -entry:main /WX 2>&1 | FileCheck %s -check-prefix=ERR
|
||||
# RUN: lld-link %t1.obj %t2.obj -out:%t.exe -debug -pdb:%t.pdb -nodefaultlib -entry:main /ignore:4099 /WX 2>&1 | FileCheck %s -check-prefix=IGNORE-ERR -allow-empty
|
||||
|
||||
@@ -20,7 +20,11 @@ RUN: rm -rf %t && mkdir -p %t && cd %t
|
||||
RUN: yaml2obj %S/Inputs/pdb-type-server-simple-a.yaml -o a.obj
|
||||
RUN: yaml2obj %S/Inputs/pdb-type-server-simple-b.yaml -o b.obj
|
||||
RUN: llvm-pdbutil yaml2pdb %S/Inputs/pdb-type-server-simple-ts.yaml -pdb ts.pdb
|
||||
RUN: lld-link a.obj b.obj -entry:main -debug -out:t.exe -pdb:t.pdb -nodefaultlib /summary | FileCheck %s -check-prefix SUMMARY
|
||||
RUN: lld-link a.obj b.obj -entry:main -debug -out:t.exe -pdb:t.pdb -nodefaultlib -summary | FileCheck %s -check-prefix SUMMARY
|
||||
RUN: llvm-pdbutil dump -symbols -types -ids -globals %t/t.pdb | FileCheck %s
|
||||
|
||||
Re-run with /DEBUG:GHASH
|
||||
RUN: lld-link a.obj b.obj -entry:main -debug:ghash -out:t.exe -pdb:t.pdb -nodefaultlib -summary -verbose
|
||||
RUN: llvm-pdbutil dump -symbols -types -ids -globals %t/t.pdb | FileCheck %s
|
||||
|
||||
|
||||
@@ -101,7 +105,8 @@ SUMMARY-NEXT: ------------------------------------------------------------------
|
||||
SUMMARY-NEXT: 2 Input OBJ files (expanded from all cmd-line inputs)
|
||||
SUMMARY-NEXT: 1 PDB type server dependencies
|
||||
SUMMARY-NEXT: 0 Precomp OBJ dependencies
|
||||
SUMMARY-NEXT: 25 Merged TPI records
|
||||
SUMMARY-NEXT: 9 Merged TPI records
|
||||
SUMMARY-NEXT: 16 Merged IPI records
|
||||
SUMMARY-NEXT: 3 Output PDB strings
|
||||
SUMMARY-NEXT: 4 Global symbol records
|
||||
SUMMARY-NEXT: 14 Module symbol records
|
||||
|
||||
@@ -5,6 +5,7 @@ RUN: lld-link %S/Inputs/precomp.obj %S/Inputs/precomp-a.obj %S/Inputs/precomp-b.
|
||||
RUN: llvm-pdbutil dump -types %t.pdb | FileCheck %s
|
||||
|
||||
RUN: lld-link %S/Inputs/precomp-a.obj %S/Inputs/precomp-invalid.obj %S/Inputs/precomp.obj /nodefaultlib /entry:main /debug /pdb:%t.pdb /out:%t.exe /opt:ref /opt:icf 2>&1 | FileCheck %s -check-prefix FAILURE
|
||||
RUN: lld-link %S/Inputs/precomp-a.obj %S/Inputs/precomp-invalid.obj %S/Inputs/precomp.obj /nodefaultlib /entry:main /debug:ghash /pdb:%t.pdb /out:%t.exe /opt:ref /opt:icf 2>&1 | FileCheck %s -check-prefix FAILURE
|
||||
|
||||
FIXME: The following RUN line should fail, regardless of whether debug info is
|
||||
enabled or not. Normally this would result in an error due to missing _PchSym_
|
||||
@@ -52,12 +53,19 @@ CHECK-NOT: LF_PRECOMP
|
||||
CHECK-NOT: LF_ENDPRECOMP
|
||||
|
||||
|
||||
Re-run with ghash. Eventually, perhaps this will be the default.
|
||||
|
||||
RUN: lld-link %S/Inputs/precomp-a.obj %S/Inputs/precomp-b.obj %S/Inputs/precomp.obj /nodefaultlib /entry:main /debug /debug:ghash /pdb:%t.pdb /out:%t.exe /opt:ref /opt:icf /summary | FileCheck %s -check-prefix SUMMARY
|
||||
RUN: llvm-pdbutil dump -types %t.pdb | FileCheck %s
|
||||
|
||||
|
||||
SUMMARY: Summary
|
||||
SUMMARY-NEXT: --------------------------------------------------------------------------------
|
||||
SUMMARY-NEXT: 3 Input OBJ files (expanded from all cmd-line inputs)
|
||||
SUMMARY-NEXT: 0 PDB type server dependencies
|
||||
SUMMARY-NEXT: 1 Precomp OBJ dependencies
|
||||
SUMMARY-NEXT: 1044 Merged TPI records
|
||||
SUMMARY-NEXT: 874 Merged TPI records
|
||||
SUMMARY-NEXT: 170 Merged IPI records
|
||||
SUMMARY-NEXT: 5 Output PDB strings
|
||||
SUMMARY-NEXT: 167 Global symbol records
|
||||
SUMMARY-NEXT: 20 Module symbol records
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
# RUN: llvm-mc -filetype=obj -triple=x86_64-pc-windows-msvc < %s > %t.obj
|
||||
# RUN: lld-link /DEBUG:FULL /nodefaultlib /entry:main %t.obj /PDB:%t.pdb /OUT:%t.exe
|
||||
# RUN: llvm-pdbutil dump -types -globals -symbols -modi=0 %t.pdb | FileCheck %s
|
||||
# RUN: lld-link /DEBUG:FULL /debug:ghash /nodefaultlib /entry:main %t.obj /PDB:%t.pdb /OUT:%t.exe
|
||||
# RUN: llvm-pdbutil dump -types -globals -symbols -modi=0 %t.pdb | FileCheck %s
|
||||
|
||||
# CHECK: Types (TPI Stream)
|
||||
# CHECK-NEXT: ============================================================
|
||||
|
||||
@@ -86,6 +86,16 @@ struct GloballyHashedType {
|
||||
|
||||
bool empty() const { return *(const uint64_t*)Hash.data() == 0; }
|
||||
|
||||
friend inline bool operator==(const GloballyHashedType &L,
|
||||
const GloballyHashedType &R) {
|
||||
return L.Hash == R.Hash;
|
||||
}
|
||||
|
||||
friend inline bool operator!=(const GloballyHashedType &L,
|
||||
const GloballyHashedType &R) {
|
||||
return !(L.Hash == R.Hash);
|
||||
}
|
||||
|
||||
/// Given a sequence of bytes representing a record, compute a global hash for
|
||||
/// this record. Due to the nature of global hashes incorporating the hashes
|
||||
/// of referenced records, this function requires a list of types and ids
|
||||
@@ -206,7 +216,7 @@ template <> struct DenseMapInfo<codeview::GloballyHashedType> {
|
||||
|
||||
static bool isEqual(codeview::GloballyHashedType LHS,
|
||||
codeview::GloballyHashedType RHS) {
|
||||
return LHS.Hash == RHS.Hash;
|
||||
return LHS == RHS;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -116,13 +116,22 @@ public:
|
||||
|
||||
uint32_t toArrayIndex() const {
|
||||
assert(!isSimple());
|
||||
return getIndex() - FirstNonSimpleIndex;
|
||||
return (getIndex() & ~DecoratedItemIdMask) - FirstNonSimpleIndex;
|
||||
}
|
||||
|
||||
static TypeIndex fromArrayIndex(uint32_t Index) {
|
||||
return TypeIndex(Index + FirstNonSimpleIndex);
|
||||
}
|
||||
|
||||
static TypeIndex fromDecoratedArrayIndex(bool IsItem, uint32_t Index) {
|
||||
return TypeIndex((Index + FirstNonSimpleIndex) |
|
||||
(IsItem ? DecoratedItemIdMask : 0));
|
||||
}
|
||||
|
||||
TypeIndex removeDecoration() {
|
||||
return TypeIndex(Index & ~DecoratedItemIdMask);
|
||||
}
|
||||
|
||||
SimpleTypeKind getSimpleKind() const {
|
||||
assert(isSimple());
|
||||
return static_cast<SimpleTypeKind>(Index & SimpleKindMask);
|
||||
|
||||
@@ -54,16 +54,20 @@ public:
|
||||
|
||||
void setVersionHeader(PdbRaw_TpiVer Version);
|
||||
void addTypeRecord(ArrayRef<uint8_t> Type, Optional<uint32_t> Hash);
|
||||
void addTypeRecords(ArrayRef<uint8_t> Types, ArrayRef<uint16_t> Sizes,
|
||||
ArrayRef<uint32_t> Hashes);
|
||||
|
||||
Error finalizeMsfLayout();
|
||||
|
||||
uint32_t getRecordCount() const { return TypeRecords.size(); }
|
||||
uint32_t getRecordCount() const { return TypeRecordCount; }
|
||||
|
||||
Error commit(const msf::MSFLayout &Layout, WritableBinaryStreamRef Buffer);
|
||||
|
||||
uint32_t calculateSerializedLength();
|
||||
|
||||
private:
|
||||
void updateTypeIndexOffsets(ArrayRef<uint16_t> Sizes);
|
||||
|
||||
uint32_t calculateHashBufferSize() const;
|
||||
uint32_t calculateIndexOffsetSize() const;
|
||||
Error finalize();
|
||||
@@ -71,10 +75,11 @@ private:
|
||||
msf::MSFBuilder &Msf;
|
||||
BumpPtrAllocator &Allocator;
|
||||
|
||||
uint32_t TypeRecordCount = 0;
|
||||
size_t TypeRecordBytes = 0;
|
||||
|
||||
PdbRaw_TpiVer VerHeader = PdbRaw_TpiVer::PdbTpiV80;
|
||||
std::vector<ArrayRef<uint8_t>> TypeRecords;
|
||||
std::vector<ArrayRef<uint8_t>> TypeRecBuffers;
|
||||
std::vector<uint32_t> TypeHashes;
|
||||
std::vector<codeview::TypeIndexOffset> TypeIndexOffsets;
|
||||
uint32_t HashStreamIndex = kInvalidStreamIndex;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "llvm/DebugInfo/CodeView/RecordName.h"
|
||||
|
||||
#include "llvm/ADT/SmallString.h"
|
||||
#include "llvm/ADT/StringExtras.h"
|
||||
#include "llvm/DebugInfo/CodeView/CVSymbolVisitor.h"
|
||||
#include "llvm/DebugInfo/CodeView/CVTypeVisitor.h"
|
||||
#include "llvm/DebugInfo/CodeView/SymbolRecordMapping.h"
|
||||
@@ -77,9 +78,10 @@ Error TypeNameComputer::visitKnownRecord(CVType &CVR, ArgListRecord &Args) {
|
||||
uint32_t Size = Indices.size();
|
||||
Name = "(";
|
||||
for (uint32_t I = 0; I < Size; ++I) {
|
||||
assert(Indices[I] < CurrentTypeIndex);
|
||||
|
||||
Name.append(Types.getTypeName(Indices[I]));
|
||||
if (Indices[I] < CurrentTypeIndex)
|
||||
Name.append(Types.getTypeName(Indices[I]));
|
||||
else
|
||||
Name.append("<unknown 0x" + utohexstr(Indices[I].getIndex()) + ">");
|
||||
if (I + 1 != Size)
|
||||
Name.append(", ");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include "llvm/Support/Error.h"
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <numeric>
|
||||
|
||||
using namespace llvm;
|
||||
using namespace llvm::msf;
|
||||
@@ -41,39 +42,68 @@ void TpiStreamBuilder::setVersionHeader(PdbRaw_TpiVer Version) {
|
||||
VerHeader = Version;
|
||||
}
|
||||
|
||||
void TpiStreamBuilder::updateTypeIndexOffsets(ArrayRef<uint16_t> Sizes) {
|
||||
// If we just crossed an 8KB threshold, add a type index offset.
|
||||
for (uint16_t Size : Sizes) {
|
||||
size_t NewSize = TypeRecordBytes + Size;
|
||||
constexpr size_t EightKB = 8 * 1024;
|
||||
if (NewSize / EightKB > TypeRecordBytes / EightKB || TypeRecordCount == 0) {
|
||||
TypeIndexOffsets.push_back(
|
||||
{codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex +
|
||||
TypeRecordCount),
|
||||
ulittle32_t(TypeRecordBytes)});
|
||||
}
|
||||
++TypeRecordCount;
|
||||
TypeRecordBytes = NewSize;
|
||||
}
|
||||
}
|
||||
|
||||
void TpiStreamBuilder::addTypeRecord(ArrayRef<uint8_t> Record,
|
||||
Optional<uint32_t> Hash) {
|
||||
// If we just crossed an 8KB threshold, add a type index offset.
|
||||
assert(((Record.size() & 3) == 0) &&
|
||||
"The type record's size is not a multiple of 4 bytes which will "
|
||||
"cause misalignment in the output TPI stream!");
|
||||
size_t NewSize = TypeRecordBytes + Record.size();
|
||||
constexpr size_t EightKB = 8 * 1024;
|
||||
if (NewSize / EightKB > TypeRecordBytes / EightKB || TypeRecords.empty()) {
|
||||
TypeIndexOffsets.push_back(
|
||||
{codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex +
|
||||
TypeRecords.size()),
|
||||
ulittle32_t(TypeRecordBytes)});
|
||||
}
|
||||
TypeRecordBytes = NewSize;
|
||||
assert(Record.size() <= codeview::MaxRecordLength);
|
||||
uint16_t OneSize = (uint16_t)Record.size();
|
||||
updateTypeIndexOffsets(makeArrayRef(&OneSize, 1));
|
||||
|
||||
TypeRecords.push_back(Record);
|
||||
TypeRecBuffers.push_back(Record);
|
||||
// FIXME: Require it.
|
||||
if (Hash)
|
||||
TypeHashes.push_back(*Hash);
|
||||
}
|
||||
|
||||
void TpiStreamBuilder::addTypeRecords(ArrayRef<uint8_t> Types,
|
||||
ArrayRef<uint16_t> Sizes,
|
||||
ArrayRef<uint32_t> Hashes) {
|
||||
// Ignore empty type buffers. There should be no hashes or sizes in this case.
|
||||
if (Types.empty()) {
|
||||
assert(Sizes.empty() && Hashes.empty());
|
||||
return;
|
||||
}
|
||||
|
||||
assert(((Types.size() & 3) == 0) &&
|
||||
"The type record's size is not a multiple of 4 bytes which will "
|
||||
"cause misalignment in the output TPI stream!");
|
||||
assert(Sizes.size() == Hashes.size() && "sizes and hashes should be in sync");
|
||||
assert(std::accumulate(Sizes.begin(), Sizes.end(), 0U) == Types.size() &&
|
||||
"sizes of type records should sum to the size of the types");
|
||||
updateTypeIndexOffsets(Sizes);
|
||||
|
||||
TypeRecBuffers.push_back(Types);
|
||||
TypeHashes.insert(TypeHashes.end(), Hashes.begin(), Hashes.end());
|
||||
}
|
||||
|
||||
Error TpiStreamBuilder::finalize() {
|
||||
if (Header)
|
||||
return Error::success();
|
||||
|
||||
TpiStreamHeader *H = Allocator.Allocate<TpiStreamHeader>();
|
||||
|
||||
uint32_t Count = TypeRecords.size();
|
||||
|
||||
H->Version = VerHeader;
|
||||
H->HeaderSize = sizeof(TpiStreamHeader);
|
||||
H->TypeIndexBegin = codeview::TypeIndex::FirstNonSimpleIndex;
|
||||
H->TypeIndexEnd = H->TypeIndexBegin + Count;
|
||||
H->TypeIndexEnd = H->TypeIndexBegin + TypeRecordCount;
|
||||
H->TypeRecordBytes = TypeRecordBytes;
|
||||
|
||||
H->HashStreamIndex = HashStreamIndex;
|
||||
@@ -104,7 +134,7 @@ uint32_t TpiStreamBuilder::calculateSerializedLength() {
|
||||
}
|
||||
|
||||
uint32_t TpiStreamBuilder::calculateHashBufferSize() const {
|
||||
assert((TypeRecords.size() == TypeHashes.size() || TypeHashes.empty()) &&
|
||||
assert((TypeRecordCount == TypeHashes.size() || TypeHashes.empty()) &&
|
||||
"either all or no type records should have hashes");
|
||||
return TypeHashes.size() * sizeof(ulittle32_t);
|
||||
}
|
||||
@@ -155,7 +185,7 @@ Error TpiStreamBuilder::commit(const msf::MSFLayout &Layout,
|
||||
if (auto EC = Writer.writeObject(*Header))
|
||||
return EC;
|
||||
|
||||
for (auto Rec : TypeRecords) {
|
||||
for (auto Rec : TypeRecBuffers) {
|
||||
assert(!Rec.empty() && "Attempting to write an empty type record shifts "
|
||||
"all offsets in the TPI stream!");
|
||||
assert(((Rec.size() & 3) == 0) &&
|
||||
|
||||
Reference in new issue
Block a user