1 | //===- DbiStreamBuilder.cpp - PDB Dbi Stream Creation -----------*- C++ -*-===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | // |
9 | // The data structures defined in this file are based on the reference |
10 | // implementation which is available at |
11 | // https://github.com/Microsoft/microsoft-pdb/blob/master/PDB/dbi/gsi.cpp |
12 | // |
13 | //===----------------------------------------------------------------------===// |
14 | |
15 | #include "llvm/DebugInfo/PDB/Native/GSIStreamBuilder.h" |
16 | #include "llvm/DebugInfo/CodeView/RecordName.h" |
17 | #include "llvm/DebugInfo/CodeView/RecordSerialization.h" |
18 | #include "llvm/DebugInfo/CodeView/SymbolRecord.h" |
19 | #include "llvm/DebugInfo/CodeView/SymbolSerializer.h" |
20 | #include "llvm/DebugInfo/MSF/MSFBuilder.h" |
21 | #include "llvm/DebugInfo/MSF/MSFCommon.h" |
22 | #include "llvm/DebugInfo/MSF/MappedBlockStream.h" |
23 | #include "llvm/DebugInfo/PDB/Native/GlobalsStream.h" |
24 | #include "llvm/DebugInfo/PDB/Native/Hash.h" |
25 | #include "llvm/DebugInfo/PDB/Native/RawTypes.h" |
26 | #include "llvm/Support/BinaryItemStream.h" |
27 | #include "llvm/Support/BinaryStreamWriter.h" |
28 | #include "llvm/Support/FormatVariadic.h" |
29 | #include "llvm/Support/Parallel.h" |
30 | #include "llvm/Support/TimeProfiler.h" |
31 | #include "llvm/Support/xxhash.h" |
32 | #include <algorithm> |
33 | #include <vector> |
34 | |
35 | using namespace llvm; |
36 | using namespace llvm::msf; |
37 | using namespace llvm::pdb; |
38 | using namespace llvm::codeview; |
39 | |
40 | // Helper class for building the public and global PDB hash table buckets. |
41 | struct llvm::pdb::GSIHashStreamBuilder { |
42 | // Sum of the size of all public or global records. |
43 | uint64_t RecordByteSize = 0; |
44 | |
45 | std::vector<PSHashRecord> HashRecords; |
46 | |
47 | // The hash bitmap has `ceil((IPHR_HASH + 1) / 32)` words in it. The |
48 | // reference implementation builds a hash table with IPHR_HASH buckets in it. |
49 | // The last bucket is used to link together free hash table cells in a linked |
50 | // list, but it is always empty in the compressed, on-disk format. However, |
51 | // the bitmap must have a bit for it. |
52 | std::array<support::ulittle32_t, (IPHR_HASH + 32) / 32> HashBitmap; |
53 | |
54 | std::vector<support::ulittle32_t> HashBuckets; |
55 | |
56 | uint32_t calculateSerializedLength() const; |
57 | Error commit(BinaryStreamWriter &Writer); |
58 | |
59 | void finalizePublicBuckets(); |
60 | void finalizeGlobalBuckets(uint32_t RecordZeroOffset); |
61 | |
62 | // Assign public and global symbol records into hash table buckets. |
63 | // Modifies the list of records to store the bucket index, but does not |
64 | // change the order. |
65 | void finalizeBuckets(uint32_t RecordZeroOffset, |
66 | MutableArrayRef<BulkPublic> Globals); |
67 | }; |
68 | |
69 | // DenseMapInfo implementation for deduplicating symbol records. |
70 | struct llvm::pdb::SymbolDenseMapInfo { |
71 | static inline CVSymbol getEmptyKey() { |
72 | static CVSymbol Empty; |
73 | return Empty; |
74 | } |
75 | static inline CVSymbol getTombstoneKey() { |
76 | static CVSymbol Tombstone( |
77 | DenseMapInfo<ArrayRef<uint8_t>>::getTombstoneKey()); |
78 | return Tombstone; |
79 | } |
80 | static unsigned getHashValue(const CVSymbol &Val) { |
81 | return xxh3_64bits(data: Val.RecordData); |
82 | } |
83 | static bool isEqual(const CVSymbol &LHS, const CVSymbol &RHS) { |
84 | return LHS.RecordData == RHS.RecordData; |
85 | } |
86 | }; |
87 | |
88 | namespace { |
89 | LLVM_PACKED_START |
90 | struct PublicSym32Layout { |
91 | RecordPrefix Prefix; |
92 | PublicSym32Header Pub; |
93 | // char Name[]; |
94 | }; |
95 | LLVM_PACKED_END |
96 | } // namespace |
97 | |
98 | // Calculate how much memory this public needs when serialized. |
99 | static uint32_t sizeOfPublic(const BulkPublic &Pub) { |
100 | uint32_t NameLen = Pub.NameLen; |
101 | NameLen = std::min(a: NameLen, |
102 | b: uint32_t(MaxRecordLength - sizeof(PublicSym32Layout) - 1)); |
103 | return alignTo(Value: sizeof(PublicSym32Layout) + NameLen + 1, Align: 4); |
104 | } |
105 | |
106 | static CVSymbol serializePublic(uint8_t *Mem, const BulkPublic &Pub) { |
107 | // Assume the caller has allocated sizeOfPublic bytes. |
108 | uint32_t NameLen = std::min( |
109 | a: Pub.NameLen, b: uint32_t(MaxRecordLength - sizeof(PublicSym32Layout) - 1)); |
110 | size_t Size = alignTo(Value: sizeof(PublicSym32Layout) + NameLen + 1, Align: 4); |
111 | assert(Size == sizeOfPublic(Pub)); |
112 | auto *FixedMem = reinterpret_cast<PublicSym32Layout *>(Mem); |
113 | FixedMem->Prefix.RecordKind = static_cast<uint16_t>(codeview::S_PUB32); |
114 | FixedMem->Prefix.RecordLen = static_cast<uint16_t>(Size - 2); |
115 | FixedMem->Pub.Flags = Pub.Flags; |
116 | FixedMem->Pub.Offset = Pub.Offset; |
117 | FixedMem->Pub.Segment = Pub.Segment; |
118 | char *NameMem = reinterpret_cast<char *>(FixedMem + 1); |
119 | memcpy(dest: NameMem, src: Pub.Name, n: NameLen); |
120 | // Zero the null terminator and remaining bytes. |
121 | memset(s: &NameMem[NameLen], c: 0, n: Size - sizeof(PublicSym32Layout) - NameLen); |
122 | return CVSymbol(ArrayRef(Mem, Size)); |
123 | } |
124 | |
125 | uint32_t GSIHashStreamBuilder::calculateSerializedLength() const { |
126 | uint32_t Size = sizeof(GSIHashHeader); |
127 | Size += HashRecords.size() * sizeof(PSHashRecord); |
128 | Size += HashBitmap.size() * sizeof(uint32_t); |
129 | Size += HashBuckets.size() * sizeof(uint32_t); |
130 | return Size; |
131 | } |
132 | |
133 | Error GSIHashStreamBuilder::commit(BinaryStreamWriter &Writer) { |
134 | GSIHashHeader ; |
135 | Header.VerSignature = GSIHashHeader::HdrSignature; |
136 | Header.VerHdr = GSIHashHeader::HdrVersion; |
137 | Header.HrSize = HashRecords.size() * sizeof(PSHashRecord); |
138 | Header.NumBuckets = HashBitmap.size() * 4 + HashBuckets.size() * 4; |
139 | |
140 | if (auto EC = Writer.writeObject(Obj: Header)) |
141 | return EC; |
142 | |
143 | if (auto EC = Writer.writeArray(Array: ArrayRef(HashRecords))) |
144 | return EC; |
145 | if (auto EC = Writer.writeArray(Array: ArrayRef(HashBitmap))) |
146 | return EC; |
147 | if (auto EC = Writer.writeArray(Array: ArrayRef(HashBuckets))) |
148 | return EC; |
149 | return Error::success(); |
150 | } |
151 | |
152 | static bool isAsciiString(StringRef S) { |
153 | return llvm::all_of(Range&: S, P: [](char C) { return unsigned(C) < 0x80; }); |
154 | } |
155 | |
156 | // See `caseInsensitiveComparePchPchCchCch` in gsi.cpp |
157 | static int gsiRecordCmp(StringRef S1, StringRef S2) { |
158 | size_t LS = S1.size(); |
159 | size_t RS = S2.size(); |
160 | // Shorter strings always compare less than longer strings. |
161 | if (LS != RS) |
162 | return (LS > RS) - (LS < RS); |
163 | |
164 | // If either string contains non ascii characters, memcmp them. |
165 | if (LLVM_UNLIKELY(!isAsciiString(S1) || !isAsciiString(S2))) |
166 | return memcmp(s1: S1.data(), s2: S2.data(), n: LS); |
167 | |
168 | // Both strings are ascii, perform a case-insensitive comparison. |
169 | return S1.compare_insensitive(RHS: S2.data()); |
170 | } |
171 | |
172 | void GSIStreamBuilder::finalizePublicBuckets() { |
173 | PSH->finalizeBuckets(RecordZeroOffset: 0, Globals: Publics); |
174 | } |
175 | |
176 | void GSIStreamBuilder::finalizeGlobalBuckets(uint32_t RecordZeroOffset) { |
177 | // Build up a list of globals to be bucketed. Use the BulkPublic data |
178 | // structure for this purpose, even though these are global records, not |
179 | // public records. Most of the same fields are required: |
180 | // - Name |
181 | // - NameLen |
182 | // - SymOffset |
183 | // - BucketIdx |
184 | // The dead fields are Offset, Segment, and Flags. |
185 | std::vector<BulkPublic> Records; |
186 | Records.resize(new_size: Globals.size()); |
187 | uint32_t SymOffset = RecordZeroOffset; |
188 | for (size_t I = 0, E = Globals.size(); I < E; ++I) { |
189 | StringRef Name = getSymbolName(Sym: Globals[I]); |
190 | Records[I].Name = Name.data(); |
191 | Records[I].NameLen = Name.size(); |
192 | Records[I].SymOffset = SymOffset; |
193 | SymOffset += Globals[I].length(); |
194 | } |
195 | |
196 | GSH->finalizeBuckets(RecordZeroOffset, Globals: Records); |
197 | } |
198 | |
199 | void GSIHashStreamBuilder::finalizeBuckets( |
200 | uint32_t RecordZeroOffset, MutableArrayRef<BulkPublic> Records) { |
201 | // Hash every name in parallel. |
202 | parallelFor(Begin: 0, End: Records.size(), Fn: [&](size_t I) { |
203 | Records[I].setBucketIdx(hashStringV1(Str: Records[I].Name) % IPHR_HASH); |
204 | }); |
205 | |
206 | // Count up the size of each bucket. Then, use an exclusive prefix sum to |
207 | // calculate the bucket start offsets. This is C++17 std::exclusive_scan, but |
208 | // we can't use it yet. |
209 | uint32_t BucketStarts[IPHR_HASH] = {0}; |
210 | for (const BulkPublic &P : Records) |
211 | ++BucketStarts[P.BucketIdx]; |
212 | uint32_t Sum = 0; |
213 | for (uint32_t &B : BucketStarts) { |
214 | uint32_t Size = B; |
215 | B = Sum; |
216 | Sum += Size; |
217 | } |
218 | |
219 | // Place globals into the hash table in bucket order. When placing a global, |
220 | // update the bucket start. Every hash table slot should be filled. Always use |
221 | // a refcount of one for now. |
222 | HashRecords.resize(new_size: Records.size()); |
223 | uint32_t BucketCursors[IPHR_HASH]; |
224 | memcpy(dest: BucketCursors, src: BucketStarts, n: sizeof(BucketCursors)); |
225 | for (int I = 0, E = Records.size(); I < E; ++I) { |
226 | uint32_t HashIdx = BucketCursors[Records[I].BucketIdx]++; |
227 | HashRecords[HashIdx].Off = I; |
228 | HashRecords[HashIdx].CRef = 1; |
229 | } |
230 | |
231 | // Within the buckets, sort each bucket by memcmp of the symbol's name. It's |
232 | // important that we use the same sorting algorithm as is used by the |
233 | // reference implementation to ensure that the search for a record within a |
234 | // bucket can properly early-out when it detects the record won't be found. |
235 | // The algorithm used here corresponds to the function |
236 | // caseInsensitiveComparePchPchCchCch in the reference implementation. |
237 | parallelFor(Begin: 0, End: IPHR_HASH, Fn: [&](size_t I) { |
238 | auto B = HashRecords.begin() + BucketStarts[I]; |
239 | auto E = HashRecords.begin() + BucketCursors[I]; |
240 | if (B == E) |
241 | return; |
242 | auto BucketCmp = [Records](const PSHashRecord &LHash, |
243 | const PSHashRecord &RHash) { |
244 | const BulkPublic &L = Records[uint32_t(LHash.Off)]; |
245 | const BulkPublic &R = Records[uint32_t(RHash.Off)]; |
246 | assert(L.BucketIdx == R.BucketIdx); |
247 | int Cmp = gsiRecordCmp(S1: L.getName(), S2: R.getName()); |
248 | if (Cmp != 0) |
249 | return Cmp < 0; |
250 | // This comparison is necessary to make the sorting stable in the presence |
251 | // of two static globals with the same name. The easiest way to observe |
252 | // this is with S_LDATA32 records. |
253 | return L.SymOffset < R.SymOffset; |
254 | }; |
255 | llvm::sort(Start: B, End: E, Comp: BucketCmp); |
256 | |
257 | // After we are done sorting, replace the global indices with the stream |
258 | // offsets of each global. Add one when writing symbol offsets to disk. |
259 | // See GSI1::fixSymRecs. |
260 | for (PSHashRecord &HRec : make_range(x: B, y: E)) |
261 | HRec.Off = Records[uint32_t(HRec.Off)].SymOffset + 1; |
262 | }); |
263 | |
264 | // For each non-empty bucket, push the bucket start offset into HashBuckets |
265 | // and set a bit in the hash bitmap. |
266 | for (uint32_t I = 0; I < HashBitmap.size(); ++I) { |
267 | uint32_t Word = 0; |
268 | for (uint32_t J = 0; J < 32; ++J) { |
269 | // Skip empty buckets. |
270 | uint32_t BucketIdx = I * 32 + J; |
271 | if (BucketIdx >= IPHR_HASH || |
272 | BucketStarts[BucketIdx] == BucketCursors[BucketIdx]) |
273 | continue; |
274 | Word |= (1U << J); |
275 | |
276 | // Calculate what the offset of the first hash record in the chain would |
277 | // be if it were inflated to contain 32-bit pointers. On a 32-bit system, |
278 | // each record would be 12 bytes. See HROffsetCalc in gsi.h. |
279 | const int SizeOfHROffsetCalc = 12; |
280 | ulittle32_t ChainStartOff = |
281 | ulittle32_t(BucketStarts[BucketIdx] * SizeOfHROffsetCalc); |
282 | HashBuckets.push_back(x: ChainStartOff); |
283 | } |
284 | HashBitmap[I] = Word; |
285 | } |
286 | } |
287 | |
288 | GSIStreamBuilder::GSIStreamBuilder(msf::MSFBuilder &Msf) |
289 | : Msf(Msf), PSH(std::make_unique<GSIHashStreamBuilder>()), |
290 | GSH(std::make_unique<GSIHashStreamBuilder>()) {} |
291 | |
292 | GSIStreamBuilder::~GSIStreamBuilder() = default; |
293 | |
294 | uint32_t GSIStreamBuilder::calculatePublicsHashStreamSize() const { |
295 | uint32_t Size = 0; |
296 | Size += sizeof(PublicsStreamHeader); |
297 | Size += PSH->calculateSerializedLength(); |
298 | Size += Publics.size() * sizeof(uint32_t); // AddrMap |
299 | // FIXME: Add thunk map and section offsets for incremental linking. |
300 | |
301 | return Size; |
302 | } |
303 | |
304 | uint32_t GSIStreamBuilder::calculateGlobalsHashStreamSize() const { |
305 | return GSH->calculateSerializedLength(); |
306 | } |
307 | |
308 | Error GSIStreamBuilder::finalizeMsfLayout() { |
309 | // First we write public symbol records, then we write global symbol records. |
310 | finalizePublicBuckets(); |
311 | finalizeGlobalBuckets(RecordZeroOffset: PSH->RecordByteSize); |
312 | |
313 | Expected<uint32_t> Idx = Msf.addStream(Size: calculateGlobalsHashStreamSize()); |
314 | if (!Idx) |
315 | return Idx.takeError(); |
316 | GlobalsStreamIndex = *Idx; |
317 | |
318 | Idx = Msf.addStream(Size: calculatePublicsHashStreamSize()); |
319 | if (!Idx) |
320 | return Idx.takeError(); |
321 | PublicsStreamIndex = *Idx; |
322 | |
323 | uint64_t RecordBytes = PSH->RecordByteSize + GSH->RecordByteSize; |
324 | if (RecordBytes > UINT32_MAX) |
325 | return make_error<StringError>( |
326 | Args: formatv(Fmt: "the public symbols ({0} bytes) and global symbols ({1} bytes) " |
327 | "are too large to fit in a PDB file; " |
328 | "the maximum total is {2} bytes." , |
329 | Vals&: PSH->RecordByteSize, Vals&: GSH->RecordByteSize, UINT32_MAX), |
330 | Args: inconvertibleErrorCode()); |
331 | |
332 | Idx = Msf.addStream(Size: RecordBytes); |
333 | if (!Idx) |
334 | return Idx.takeError(); |
335 | RecordStreamIndex = *Idx; |
336 | return Error::success(); |
337 | } |
338 | |
339 | void GSIStreamBuilder::addPublicSymbols(std::vector<BulkPublic> &&PublicsIn) { |
340 | assert(Publics.empty() && PSH->RecordByteSize == 0 && |
341 | "publics can only be added once" ); |
342 | Publics = std::move(PublicsIn); |
343 | |
344 | // Sort the symbols by name. PDBs contain lots of symbols, so use parallelism. |
345 | parallelSort(R&: Publics, Comp: [](const BulkPublic &L, const BulkPublic &R) { |
346 | return L.getName() < R.getName(); |
347 | }); |
348 | |
349 | // Assign offsets and calculate the length of the public symbol records. |
350 | uint32_t SymOffset = 0; |
351 | for (BulkPublic &Pub : Publics) { |
352 | Pub.SymOffset = SymOffset; |
353 | SymOffset += sizeOfPublic(Pub); |
354 | } |
355 | |
356 | // Remember the length of the public stream records. |
357 | PSH->RecordByteSize = SymOffset; |
358 | } |
359 | |
360 | void GSIStreamBuilder::addGlobalSymbol(const ProcRefSym &Sym) { |
361 | serializeAndAddGlobal(Symbol: Sym); |
362 | } |
363 | |
364 | void GSIStreamBuilder::addGlobalSymbol(const DataSym &Sym) { |
365 | serializeAndAddGlobal(Symbol: Sym); |
366 | } |
367 | |
368 | void GSIStreamBuilder::addGlobalSymbol(const ConstantSym &Sym) { |
369 | serializeAndAddGlobal(Symbol: Sym); |
370 | } |
371 | |
372 | template <typename T> |
373 | void GSIStreamBuilder::serializeAndAddGlobal(const T &Symbol) { |
374 | T Copy(Symbol); |
375 | addGlobalSymbol(SymbolSerializer::writeOneSymbol(Copy, Msf.getAllocator(), |
376 | CodeViewContainer::Pdb)); |
377 | } |
378 | |
379 | void GSIStreamBuilder::addGlobalSymbol(const codeview::CVSymbol &Symbol) { |
380 | // Ignore duplicate typedefs and constants. |
381 | if (Symbol.kind() == S_UDT || Symbol.kind() == S_CONSTANT) { |
382 | auto Iter = GlobalsSeen.insert(V: Symbol); |
383 | if (!Iter.second) |
384 | return; |
385 | } |
386 | GSH->RecordByteSize += Symbol.length(); |
387 | Globals.push_back(x: Symbol); |
388 | } |
389 | |
390 | // Serialize each public and write it. |
391 | static Error writePublics(BinaryStreamWriter &Writer, |
392 | ArrayRef<BulkPublic> Publics) { |
393 | std::vector<uint8_t> Storage; |
394 | for (const BulkPublic &Pub : Publics) { |
395 | Storage.resize(new_size: sizeOfPublic(Pub)); |
396 | serializePublic(Mem: Storage.data(), Pub); |
397 | if (Error E = Writer.writeBytes(Buffer: Storage)) |
398 | return E; |
399 | } |
400 | return Error::success(); |
401 | } |
402 | |
403 | static Error writeRecords(BinaryStreamWriter &Writer, |
404 | ArrayRef<CVSymbol> Records) { |
405 | BinaryItemStream<CVSymbol> ItemStream(llvm::endianness::little); |
406 | ItemStream.setItems(Records); |
407 | BinaryStreamRef RecordsRef(ItemStream); |
408 | return Writer.writeStreamRef(Ref: RecordsRef); |
409 | } |
410 | |
411 | Error GSIStreamBuilder::commitSymbolRecordStream( |
412 | WritableBinaryStreamRef Stream) { |
413 | BinaryStreamWriter Writer(Stream); |
414 | |
415 | // Write public symbol records first, followed by global symbol records. This |
416 | // must match the order that we assume in finalizeMsfLayout when computing |
417 | // PSHZero and GSHZero. |
418 | if (auto EC = writePublics(Writer, Publics)) |
419 | return EC; |
420 | if (auto EC = writeRecords(Writer, Records: Globals)) |
421 | return EC; |
422 | |
423 | return Error::success(); |
424 | } |
425 | |
426 | static std::vector<support::ulittle32_t> |
427 | computeAddrMap(ArrayRef<BulkPublic> Publics) { |
428 | // Build a parallel vector of indices into the Publics vector, and sort it by |
429 | // address. |
430 | std::vector<ulittle32_t> PubAddrMap; |
431 | PubAddrMap.reserve(n: Publics.size()); |
432 | for (int I = 0, E = Publics.size(); I < E; ++I) |
433 | PubAddrMap.push_back(x: ulittle32_t(I)); |
434 | |
435 | auto AddrCmp = [Publics](const ulittle32_t &LIdx, const ulittle32_t &RIdx) { |
436 | const BulkPublic &L = Publics[LIdx]; |
437 | const BulkPublic &R = Publics[RIdx]; |
438 | if (L.Segment != R.Segment) |
439 | return L.Segment < R.Segment; |
440 | if (L.Offset != R.Offset) |
441 | return L.Offset < R.Offset; |
442 | // parallelSort is unstable, so we have to do name comparison to ensure |
443 | // that two names for the same location come out in a deterministic order. |
444 | return L.getName() < R.getName(); |
445 | }; |
446 | parallelSort(R&: PubAddrMap, Comp: AddrCmp); |
447 | |
448 | // Rewrite the public symbol indices into symbol offsets. |
449 | for (ulittle32_t &Entry : PubAddrMap) |
450 | Entry = Publics[Entry].SymOffset; |
451 | return PubAddrMap; |
452 | } |
453 | |
454 | Error GSIStreamBuilder::commitPublicsHashStream( |
455 | WritableBinaryStreamRef Stream) { |
456 | BinaryStreamWriter Writer(Stream); |
457 | PublicsStreamHeader ; |
458 | |
459 | // FIXME: Fill these in. They are for incremental linking. |
460 | Header.SymHash = PSH->calculateSerializedLength(); |
461 | Header.AddrMap = Publics.size() * 4; |
462 | Header.NumThunks = 0; |
463 | Header.SizeOfThunk = 0; |
464 | Header.ISectThunkTable = 0; |
465 | memset(s: Header.Padding, c: 0, n: sizeof(Header.Padding)); |
466 | Header.OffThunkTable = 0; |
467 | Header.NumSections = 0; |
468 | if (auto EC = Writer.writeObject(Obj: Header)) |
469 | return EC; |
470 | |
471 | if (auto EC = PSH->commit(Writer)) |
472 | return EC; |
473 | |
474 | std::vector<support::ulittle32_t> PubAddrMap = computeAddrMap(Publics); |
475 | assert(PubAddrMap.size() == Publics.size()); |
476 | if (auto EC = Writer.writeArray(Array: ArrayRef(PubAddrMap))) |
477 | return EC; |
478 | |
479 | return Error::success(); |
480 | } |
481 | |
482 | Error GSIStreamBuilder::commitGlobalsHashStream( |
483 | WritableBinaryStreamRef Stream) { |
484 | BinaryStreamWriter Writer(Stream); |
485 | return GSH->commit(Writer); |
486 | } |
487 | |
488 | Error GSIStreamBuilder::commit(const msf::MSFLayout &Layout, |
489 | WritableBinaryStreamRef Buffer) { |
490 | llvm::TimeTraceScope timeScope("Commit GSI stream" ); |
491 | auto GS = WritableMappedBlockStream::createIndexedStream( |
492 | Layout, MsfData: Buffer, StreamIndex: getGlobalsStreamIndex(), Allocator&: Msf.getAllocator()); |
493 | auto PS = WritableMappedBlockStream::createIndexedStream( |
494 | Layout, MsfData: Buffer, StreamIndex: getPublicsStreamIndex(), Allocator&: Msf.getAllocator()); |
495 | auto PRS = WritableMappedBlockStream::createIndexedStream( |
496 | Layout, MsfData: Buffer, StreamIndex: getRecordStreamIndex(), Allocator&: Msf.getAllocator()); |
497 | |
498 | if (auto EC = commitSymbolRecordStream(Stream: *PRS)) |
499 | return EC; |
500 | if (auto EC = commitGlobalsHashStream(Stream: *GS)) |
501 | return EC; |
502 | if (auto EC = commitPublicsHashStream(Stream: *PS)) |
503 | return EC; |
504 | return Error::success(); |
505 | } |
506 | |