diff options
Diffstat (limited to 'llvm/lib/DebugInfo/PDB/Native/GSIStreamBuilder.cpp')
-rw-r--r-- | llvm/lib/DebugInfo/PDB/Native/GSIStreamBuilder.cpp | 446 |
1 files changed, 281 insertions, 165 deletions
diff --git a/llvm/lib/DebugInfo/PDB/Native/GSIStreamBuilder.cpp b/llvm/lib/DebugInfo/PDB/Native/GSIStreamBuilder.cpp index 432f1e9b24d3..4e58489f1401 100644 --- a/llvm/lib/DebugInfo/PDB/Native/GSIStreamBuilder.cpp +++ b/llvm/lib/DebugInfo/PDB/Native/GSIStreamBuilder.cpp @@ -5,10 +5,14 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// +// +// The data structures defined in this file are based on the reference +// implementation which is available at +// https://github.com/Microsoft/microsoft-pdb/blob/master/PDB/dbi/gsi.cpp +// +//===----------------------------------------------------------------------===// #include "llvm/DebugInfo/PDB/Native/GSIStreamBuilder.h" - -#include "llvm/ADT/DenseSet.h" #include "llvm/DebugInfo/CodeView/RecordName.h" #include "llvm/DebugInfo/CodeView/SymbolDeserializer.h" #include "llvm/DebugInfo/CodeView/SymbolRecord.h" @@ -20,6 +24,7 @@ #include "llvm/DebugInfo/PDB/Native/Hash.h" #include "llvm/Support/BinaryItemStream.h" #include "llvm/Support/BinaryStreamWriter.h" +#include "llvm/Support/Parallel.h" #include "llvm/Support/xxhash.h" #include <algorithm> #include <vector> @@ -29,53 +34,91 @@ using namespace llvm::msf; using namespace llvm::pdb; using namespace llvm::codeview; +// Helper class for building the public and global PDB hash table buckets. struct llvm::pdb::GSIHashStreamBuilder { - struct SymbolDenseMapInfo { - static inline CVSymbol getEmptyKey() { - static CVSymbol Empty; - return Empty; - } - static inline CVSymbol getTombstoneKey() { - static CVSymbol Tombstone( - DenseMapInfo<ArrayRef<uint8_t>>::getTombstoneKey()); - return Tombstone; - } - static unsigned getHashValue(const CVSymbol &Val) { - return xxHash64(Val.RecordData); - } - static bool isEqual(const CVSymbol &LHS, const CVSymbol &RHS) { - return LHS.RecordData == RHS.RecordData; - } - }; + // Sum of the size of all public or global records. + uint32_t RecordByteSize = 0; - std::vector<CVSymbol> Records; - uint32_t StreamIndex; - llvm::DenseSet<CVSymbol, SymbolDenseMapInfo> SymbolHashes; std::vector<PSHashRecord> HashRecords; + + // The hash bitmap has `ceil((IPHR_HASH + 1) / 32)` words in it. The + // reference implementation builds a hash table with IPHR_HASH buckets in it. + // The last bucket is used to link together free hash table cells in a linked + // list, but it is always empty in the compressed, on-disk format. However, + // the bitmap must have a bit for it. std::array<support::ulittle32_t, (IPHR_HASH + 32) / 32> HashBitmap; + std::vector<support::ulittle32_t> HashBuckets; uint32_t calculateSerializedLength() const; - uint32_t calculateRecordByteSize() const; Error commit(BinaryStreamWriter &Writer); - void finalizeBuckets(uint32_t RecordZeroOffset); - template <typename T> void addSymbol(const T &Symbol, MSFBuilder &Msf) { - T Copy(Symbol); - addSymbol(SymbolSerializer::writeOneSymbol(Copy, Msf.getAllocator(), - CodeViewContainer::Pdb)); - } - void addSymbol(const CVSymbol &Symbol) { - if (Symbol.kind() == S_UDT || Symbol.kind() == S_CONSTANT) { - auto Iter = SymbolHashes.insert(Symbol); - if (!Iter.second) - return; - } + void finalizePublicBuckets(); + void finalizeGlobalBuckets(uint32_t RecordZeroOffset); + + // Assign public and global symbol records into hash table buckets. + // Modifies the list of records to store the bucket index, but does not + // change the order. + void finalizeBuckets(uint32_t RecordZeroOffset, + MutableArrayRef<BulkPublic> Globals); +}; - Records.push_back(Symbol); +// DenseMapInfo implementation for deduplicating symbol records. +struct llvm::pdb::SymbolDenseMapInfo { + static inline CVSymbol getEmptyKey() { + static CVSymbol Empty; + return Empty; + } + static inline CVSymbol getTombstoneKey() { + static CVSymbol Tombstone( + DenseMapInfo<ArrayRef<uint8_t>>::getTombstoneKey()); + return Tombstone; + } + static unsigned getHashValue(const CVSymbol &Val) { + return xxHash64(Val.RecordData); + } + static bool isEqual(const CVSymbol &LHS, const CVSymbol &RHS) { + return LHS.RecordData == RHS.RecordData; } }; +namespace { +LLVM_PACKED_START +struct PublicSym32Layout { + RecordPrefix Prefix; + PublicSym32Header Pub; + // char Name[]; +}; +LLVM_PACKED_END +} // namespace + +// Calculate how much memory this public needs when serialized. +static uint32_t sizeOfPublic(const BulkPublic &Pub) { + uint32_t NameLen = Pub.NameLen; + NameLen = std::min(NameLen, + uint32_t(MaxRecordLength - sizeof(PublicSym32Layout) - 1)); + return alignTo(sizeof(PublicSym32Layout) + NameLen + 1, 4); +} + +static CVSymbol serializePublic(uint8_t *Mem, const BulkPublic &Pub) { + // Assume the caller has allocated sizeOfPublic bytes. + uint32_t NameLen = std::min( + Pub.NameLen, uint32_t(MaxRecordLength - sizeof(PublicSym32Layout) - 1)); + size_t Size = alignTo(sizeof(PublicSym32Layout) + NameLen + 1, 4); + assert(Size == sizeOfPublic(Pub)); + auto *FixedMem = reinterpret_cast<PublicSym32Layout *>(Mem); + FixedMem->Prefix.RecordKind = static_cast<uint16_t>(codeview::S_PUB32); + FixedMem->Prefix.RecordLen = static_cast<uint16_t>(Size - 2); + FixedMem->Pub.Flags = Pub.Flags; + FixedMem->Pub.Offset = Pub.Offset; + FixedMem->Pub.Segment = Pub.Segment; + char *NameMem = reinterpret_cast<char *>(FixedMem + 1); + memcpy(NameMem, Pub.Name, NameLen); + // Zero the null terminator and remaining bytes. + memset(&NameMem[NameLen], 0, Size - sizeof(PublicSym32Layout) - NameLen); + return CVSymbol(makeArrayRef(reinterpret_cast<uint8_t *>(Mem), Size)); +} + uint32_t GSIHashStreamBuilder::calculateSerializedLength() const { uint32_t Size = sizeof(GSIHashHeader); Size += HashRecords.size() * sizeof(PSHashRecord); @@ -84,13 +127,6 @@ uint32_t GSIHashStreamBuilder::calculateSerializedLength() const { return Size; } -uint32_t GSIHashStreamBuilder::calculateRecordByteSize() const { - uint32_t Size = 0; - for (const auto &Sym : Records) - Size += Sym.length(); - return Size; -} - Error GSIHashStreamBuilder::commit(BinaryStreamWriter &Writer) { GSIHashHeader Header; Header.VerSignature = GSIHashHeader::HdrSignature; @@ -115,70 +151,134 @@ static bool isAsciiString(StringRef S) { } // See `caseInsensitiveComparePchPchCchCch` in gsi.cpp -static bool gsiRecordLess(StringRef S1, StringRef S2) { +static int gsiRecordCmp(StringRef S1, StringRef S2) { size_t LS = S1.size(); size_t RS = S2.size(); // Shorter strings always compare less than longer strings. if (LS != RS) - return LS < RS; + return LS - RS; // If either string contains non ascii characters, memcmp them. if (LLVM_UNLIKELY(!isAsciiString(S1) || !isAsciiString(S2))) - return memcmp(S1.data(), S2.data(), LS) < 0; + return memcmp(S1.data(), S2.data(), LS); // Both strings are ascii, perform a case-insenstive comparison. - return S1.compare_lower(S2.data()) < 0; + return S1.compare_lower(S2.data()); +} + +void GSIStreamBuilder::finalizePublicBuckets() { + PSH->finalizeBuckets(0, Publics); } -void GSIHashStreamBuilder::finalizeBuckets(uint32_t RecordZeroOffset) { - std::array<std::vector<std::pair<StringRef, PSHashRecord>>, IPHR_HASH + 1> - TmpBuckets; +void GSIStreamBuilder::finalizeGlobalBuckets(uint32_t RecordZeroOffset) { + // Build up a list of globals to be bucketed. Use the BulkPublic data + // structure for this purpose, even though these are global records, not + // public records. Most of the same fields are required: + // - Name + // - NameLen + // - SymOffset + // - BucketIdx + // The dead fields are Offset, Segment, and Flags. + std::vector<BulkPublic> Records; + Records.resize(Globals.size()); uint32_t SymOffset = RecordZeroOffset; - for (const CVSymbol &Sym : Records) { - PSHashRecord HR; - // Add one when writing symbol offsets to disk. See GSI1::fixSymRecs. - HR.Off = SymOffset + 1; - HR.CRef = 1; // Always use a refcount of 1. - - // Hash the name to figure out which bucket this goes into. - StringRef Name = getSymbolName(Sym); - size_t BucketIdx = hashStringV1(Name) % IPHR_HASH; - TmpBuckets[BucketIdx].push_back(std::make_pair(Name, HR)); - SymOffset += Sym.length(); + for (size_t I = 0, E = Globals.size(); I < E; ++I) { + StringRef Name = getSymbolName(Globals[I]); + Records[I].Name = Name.data(); + Records[I].NameLen = Name.size(); + Records[I].SymOffset = SymOffset; + SymOffset += Globals[I].length(); + } + + GSH->finalizeBuckets(RecordZeroOffset, Records); +} + +void GSIHashStreamBuilder::finalizeBuckets( + uint32_t RecordZeroOffset, MutableArrayRef<BulkPublic> Records) { + // Hash every name in parallel. + parallelForEachN(0, Records.size(), [&](size_t I) { + Records[I].setBucketIdx(hashStringV1(Records[I].Name) % IPHR_HASH); + }); + + // Count up the size of each bucket. Then, use an exclusive prefix sum to + // calculate the bucket start offsets. This is C++17 std::exclusive_scan, but + // we can't use it yet. + uint32_t BucketStarts[IPHR_HASH] = {0}; + for (const BulkPublic &P : Records) + ++BucketStarts[P.BucketIdx]; + uint32_t Sum = 0; + for (uint32_t &B : BucketStarts) { + uint32_t Size = B; + B = Sum; + Sum += Size; + } + + // Place globals into the hash table in bucket order. When placing a global, + // update the bucket start. Every hash table slot should be filled. Always use + // a refcount of one for now. + HashRecords.resize(Records.size()); + uint32_t BucketCursors[IPHR_HASH]; + memcpy(BucketCursors, BucketStarts, sizeof(BucketCursors)); + for (int I = 0, E = Records.size(); I < E; ++I) { + uint32_t HashIdx = BucketCursors[Records[I].BucketIdx]++; + HashRecords[HashIdx].Off = I; + HashRecords[HashIdx].CRef = 1; } - // Compute the three tables: the hash records in bucket and chain order, the - // bucket presence bitmap, and the bucket chain start offsets. - HashRecords.reserve(Records.size()); - for (ulittle32_t &Word : HashBitmap) - Word = 0; - for (size_t BucketIdx = 0; BucketIdx < IPHR_HASH + 1; ++BucketIdx) { - auto &Bucket = TmpBuckets[BucketIdx]; - if (Bucket.empty()) - continue; - HashBitmap[BucketIdx / 32] |= 1U << (BucketIdx % 32); - - // Calculate what the offset of the first hash record in the chain would - // be if it were inflated to contain 32-bit pointers. On a 32-bit system, - // each record would be 12 bytes. See HROffsetCalc in gsi.h. - const int SizeOfHROffsetCalc = 12; - ulittle32_t ChainStartOff = - ulittle32_t(HashRecords.size() * SizeOfHROffsetCalc); - HashBuckets.push_back(ChainStartOff); - - // Sort each bucket by memcmp of the symbol's name. It's important that - // we use the same sorting algorithm as is used by the reference - // implementation to ensure that the search for a record within a bucket - // can properly early-out when it detects the record won't be found. The - // algorithm used here corredsponds to the function - // caseInsensitiveComparePchPchCchCch in the reference implementation. - llvm::sort(Bucket, [](const std::pair<StringRef, PSHashRecord> &Left, - const std::pair<StringRef, PSHashRecord> &Right) { - return gsiRecordLess(Left.first, Right.first); - }); - - for (const auto &Entry : Bucket) - HashRecords.push_back(Entry.second); + // Within the buckets, sort each bucket by memcmp of the symbol's name. It's + // important that we use the same sorting algorithm as is used by the + // reference implementation to ensure that the search for a record within a + // bucket can properly early-out when it detects the record won't be found. + // The algorithm used here corresponds to the function + // caseInsensitiveComparePchPchCchCch in the reference implementation. + parallelForEachN(0, IPHR_HASH, [&](size_t I) { + auto B = HashRecords.begin() + BucketStarts[I]; + auto E = HashRecords.begin() + BucketCursors[I]; + if (B == E) + return; + auto BucketCmp = [Records](const PSHashRecord &LHash, + const PSHashRecord &RHash) { + const BulkPublic &L = Records[uint32_t(LHash.Off)]; + const BulkPublic &R = Records[uint32_t(RHash.Off)]; + assert(L.BucketIdx == R.BucketIdx); + int Cmp = gsiRecordCmp(L.getName(), R.getName()); + if (Cmp != 0) + return Cmp < 0; + // This comparison is necessary to make the sorting stable in the presence + // of two static globals with the same name. The easiest way to observe + // this is with S_LDATA32 records. + return L.SymOffset < R.SymOffset; + }; + llvm::sort(B, E, BucketCmp); + + // After we are done sorting, replace the global indices with the stream + // offsets of each global. Add one when writing symbol offsets to disk. + // See GSI1::fixSymRecs. + for (PSHashRecord &HRec : make_range(B, E)) + HRec.Off = Records[uint32_t(HRec.Off)].SymOffset + 1; + }); + + // For each non-empty bucket, push the bucket start offset into HashBuckets + // and set a bit in the hash bitmap. + for (uint32_t I = 0; I < HashBitmap.size(); ++I) { + uint32_t Word = 0; + for (uint32_t J = 0; J < 32; ++J) { + // Skip empty buckets. + uint32_t BucketIdx = I * 32 + J; + if (BucketIdx >= IPHR_HASH || + BucketStarts[BucketIdx] == BucketCursors[BucketIdx]) + continue; + Word |= (1U << J); + + // Calculate what the offset of the first hash record in the chain would + // be if it were inflated to contain 32-bit pointers. On a 32-bit system, + // each record would be 12 bytes. See HROffsetCalc in gsi.h. + const int SizeOfHROffsetCalc = 12; + ulittle32_t ChainStartOff = + ulittle32_t(BucketStarts[BucketIdx] * SizeOfHROffsetCalc); + HashBuckets.push_back(ChainStartOff); + } + HashBitmap[I] = Word; } } @@ -192,7 +292,7 @@ uint32_t GSIStreamBuilder::calculatePublicsHashStreamSize() const { uint32_t Size = 0; Size += sizeof(PublicsStreamHeader); Size += PSH->calculateSerializedLength(); - Size += PSH->Records.size() * sizeof(uint32_t); // AddrMap + Size += Publics.size() * sizeof(uint32_t); // AddrMap // FIXME: Add thunk map and section offsets for incremental linking. return Size; @@ -204,103 +304,90 @@ uint32_t GSIStreamBuilder::calculateGlobalsHashStreamSize() const { Error GSIStreamBuilder::finalizeMsfLayout() { // First we write public symbol records, then we write global symbol records. - uint32_t PSHZero = 0; - uint32_t GSHZero = PSH->calculateRecordByteSize(); - - PSH->finalizeBuckets(PSHZero); - GSH->finalizeBuckets(GSHZero); + finalizePublicBuckets(); + finalizeGlobalBuckets(PSH->RecordByteSize); Expected<uint32_t> Idx = Msf.addStream(calculateGlobalsHashStreamSize()); if (!Idx) return Idx.takeError(); - GSH->StreamIndex = *Idx; + GlobalsStreamIndex = *Idx; + Idx = Msf.addStream(calculatePublicsHashStreamSize()); if (!Idx) return Idx.takeError(); - PSH->StreamIndex = *Idx; + PublicsStreamIndex = *Idx; - uint32_t RecordBytes = - GSH->calculateRecordByteSize() + PSH->calculateRecordByteSize(); + uint32_t RecordBytes = PSH->RecordByteSize + GSH->RecordByteSize; Idx = Msf.addStream(RecordBytes); if (!Idx) return Idx.takeError(); - RecordStreamIdx = *Idx; + RecordStreamIndex = *Idx; return Error::success(); } -static bool comparePubSymByAddrAndName( - const std::pair<const CVSymbol *, const PublicSym32 *> &LS, - const std::pair<const CVSymbol *, const PublicSym32 *> &RS) { - if (LS.second->Segment != RS.second->Segment) - return LS.second->Segment < RS.second->Segment; - if (LS.second->Offset != RS.second->Offset) - return LS.second->Offset < RS.second->Offset; +void GSIStreamBuilder::addPublicSymbols(std::vector<BulkPublic> &&PublicsIn) { + assert(Publics.empty() && PSH->RecordByteSize == 0 && + "publics can only be added once"); + Publics = std::move(PublicsIn); - return LS.second->Name < RS.second->Name; -} - -/// Compute the address map. The address map is an array of symbol offsets -/// sorted so that it can be binary searched by address. -static std::vector<ulittle32_t> computeAddrMap(ArrayRef<CVSymbol> Records) { - // Make a vector of pointers to the symbols so we can sort it by address. - // Also gather the symbol offsets while we're at it. - - std::vector<PublicSym32> DeserializedPublics; - std::vector<std::pair<const CVSymbol *, const PublicSym32 *>> PublicsByAddr; - std::vector<uint32_t> SymOffsets; - DeserializedPublics.reserve(Records.size()); - PublicsByAddr.reserve(Records.size()); - SymOffsets.reserve(Records.size()); + // Sort the symbols by name. PDBs contain lots of symbols, so use parallelism. + parallelSort(Publics, [](const BulkPublic &L, const BulkPublic &R) { + return L.getName() < R.getName(); + }); + // Assign offsets and calculate the length of the public symbol records. uint32_t SymOffset = 0; - for (const CVSymbol &Sym : Records) { - assert(Sym.kind() == SymbolKind::S_PUB32); - DeserializedPublics.push_back( - cantFail(SymbolDeserializer::deserializeAs<PublicSym32>(Sym))); - PublicsByAddr.emplace_back(&Sym, &DeserializedPublics.back()); - SymOffsets.push_back(SymOffset); - SymOffset += Sym.length(); - } - llvm::stable_sort(PublicsByAddr, comparePubSymByAddrAndName); - - // Fill in the symbol offsets in the appropriate order. - std::vector<ulittle32_t> AddrMap; - AddrMap.reserve(Records.size()); - for (auto &Sym : PublicsByAddr) { - ptrdiff_t Idx = std::distance(Records.data(), Sym.first); - assert(Idx >= 0 && size_t(Idx) < Records.size()); - AddrMap.push_back(ulittle32_t(SymOffsets[Idx])); + for (BulkPublic &Pub : Publics) { + Pub.SymOffset = SymOffset; + SymOffset += sizeOfPublic(Pub); } - return AddrMap; -} -uint32_t GSIStreamBuilder::getPublicsStreamIndex() const { - return PSH->StreamIndex; + // Remember the length of the public stream records. + PSH->RecordByteSize = SymOffset; } -uint32_t GSIStreamBuilder::getGlobalsStreamIndex() const { - return GSH->StreamIndex; +void GSIStreamBuilder::addGlobalSymbol(const ProcRefSym &Sym) { + serializeAndAddGlobal(Sym); } -void GSIStreamBuilder::addPublicSymbol(const PublicSym32 &Pub) { - PSH->addSymbol(Pub, Msf); +void GSIStreamBuilder::addGlobalSymbol(const DataSym &Sym) { + serializeAndAddGlobal(Sym); } -void GSIStreamBuilder::addGlobalSymbol(const ProcRefSym &Sym) { - GSH->addSymbol(Sym, Msf); +void GSIStreamBuilder::addGlobalSymbol(const ConstantSym &Sym) { + serializeAndAddGlobal(Sym); } -void GSIStreamBuilder::addGlobalSymbol(const DataSym &Sym) { - GSH->addSymbol(Sym, Msf); +template <typename T> +void GSIStreamBuilder::serializeAndAddGlobal(const T &Symbol) { + T Copy(Symbol); + addGlobalSymbol(SymbolSerializer::writeOneSymbol(Copy, Msf.getAllocator(), + CodeViewContainer::Pdb)); } -void GSIStreamBuilder::addGlobalSymbol(const ConstantSym &Sym) { - GSH->addSymbol(Sym, Msf); +void GSIStreamBuilder::addGlobalSymbol(const codeview::CVSymbol &Symbol) { + // Ignore duplicate typedefs and constants. + if (Symbol.kind() == S_UDT || Symbol.kind() == S_CONSTANT) { + auto Iter = GlobalsSeen.insert(Symbol); + if (!Iter.second) + return; + } + GSH->RecordByteSize += Symbol.length(); + Globals.push_back(Symbol); } -void GSIStreamBuilder::addGlobalSymbol(const codeview::CVSymbol &Sym) { - GSH->addSymbol(Sym); +// Serialize each public and write it. +static Error writePublics(BinaryStreamWriter &Writer, + ArrayRef<BulkPublic> Publics) { + std::vector<uint8_t> Storage; + for (const BulkPublic &Pub : Publics) { + Storage.resize(sizeOfPublic(Pub)); + serializePublic(Storage.data(), Pub); + if (Error E = Writer.writeBytes(Storage)) + return E; + } + return Error::success(); } static Error writeRecords(BinaryStreamWriter &Writer, @@ -318,14 +405,42 @@ Error GSIStreamBuilder::commitSymbolRecordStream( // Write public symbol records first, followed by global symbol records. This // must match the order that we assume in finalizeMsfLayout when computing // PSHZero and GSHZero. - if (auto EC = writeRecords(Writer, PSH->Records)) + if (auto EC = writePublics(Writer, Publics)) return EC; - if (auto EC = writeRecords(Writer, GSH->Records)) + if (auto EC = writeRecords(Writer, Globals)) return EC; return Error::success(); } +static std::vector<support::ulittle32_t> +computeAddrMap(ArrayRef<BulkPublic> Publics) { + // Build a parallel vector of indices into the Publics vector, and sort it by + // address. + std::vector<ulittle32_t> PubAddrMap; + PubAddrMap.reserve(Publics.size()); + for (int I = 0, E = Publics.size(); I < E; ++I) + PubAddrMap.push_back(ulittle32_t(I)); + + auto AddrCmp = [Publics](const ulittle32_t &LIdx, const ulittle32_t &RIdx) { + const BulkPublic &L = Publics[LIdx]; + const BulkPublic &R = Publics[RIdx]; + if (L.Segment != R.Segment) + return L.Segment < R.Segment; + if (L.Offset != R.Offset) + return L.Offset < R.Offset; + // parallelSort is unstable, so we have to do name comparison to ensure + // that two names for the same location come out in a deterministic order. + return L.getName() < R.getName(); + }; + parallelSort(PubAddrMap, AddrCmp); + + // Rewrite the public symbol indices into symbol offsets. + for (ulittle32_t &Entry : PubAddrMap) + Entry = Publics[Entry].SymOffset; + return PubAddrMap; +} + Error GSIStreamBuilder::commitPublicsHashStream( WritableBinaryStreamRef Stream) { BinaryStreamWriter Writer(Stream); @@ -333,7 +448,7 @@ Error GSIStreamBuilder::commitPublicsHashStream( // FIXME: Fill these in. They are for incremental linking. Header.SymHash = PSH->calculateSerializedLength(); - Header.AddrMap = PSH->Records.size() * 4; + Header.AddrMap = Publics.size() * 4; Header.NumThunks = 0; Header.SizeOfThunk = 0; Header.ISectThunkTable = 0; @@ -346,8 +461,9 @@ Error GSIStreamBuilder::commitPublicsHashStream( if (auto EC = PSH->commit(Writer)) return EC; - std::vector<ulittle32_t> AddrMap = computeAddrMap(PSH->Records); - if (auto EC = Writer.writeArray(makeArrayRef(AddrMap))) + std::vector<support::ulittle32_t> PubAddrMap = computeAddrMap(Publics); + assert(PubAddrMap.size() == Publics.size()); + if (auto EC = Writer.writeArray(makeArrayRef(PubAddrMap))) return EC; return Error::success(); @@ -366,7 +482,7 @@ Error GSIStreamBuilder::commit(const msf::MSFLayout &Layout, auto PS = WritableMappedBlockStream::createIndexedStream( Layout, Buffer, getPublicsStreamIndex(), Msf.getAllocator()); auto PRS = WritableMappedBlockStream::createIndexedStream( - Layout, Buffer, getRecordStreamIdx(), Msf.getAllocator()); + Layout, Buffer, getRecordStreamIndex(), Msf.getAllocator()); if (auto EC = commitSymbolRecordStream(*PRS)) return EC; |