1 | //===- TpiStreamBuilder.cpp - -------------------------------------------===// |
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 | #include "llvm/DebugInfo/PDB/Native/TpiStreamBuilder.h" |
10 | #include "llvm/ADT/ArrayRef.h" |
11 | #include "llvm/ADT/STLExtras.h" |
12 | #include "llvm/DebugInfo/CodeView/RecordSerialization.h" |
13 | #include "llvm/DebugInfo/CodeView/TypeIndex.h" |
14 | #include "llvm/DebugInfo/MSF/MSFBuilder.h" |
15 | #include "llvm/DebugInfo/MSF/MappedBlockStream.h" |
16 | #include "llvm/DebugInfo/PDB/Native/RawTypes.h" |
17 | #include "llvm/Support/Allocator.h" |
18 | #include "llvm/Support/BinaryByteStream.h" |
19 | #include "llvm/Support/BinaryStreamWriter.h" |
20 | #include "llvm/Support/Endian.h" |
21 | #include "llvm/Support/Error.h" |
22 | #include "llvm/Support/TimeProfiler.h" |
23 | #include <algorithm> |
24 | #include <cstdint> |
25 | #include <numeric> |
26 | |
27 | using namespace llvm; |
28 | using namespace llvm::msf; |
29 | using namespace llvm::pdb; |
30 | using namespace llvm::support; |
31 | |
32 | TpiStreamBuilder::TpiStreamBuilder(MSFBuilder &Msf, uint32_t StreamIdx) |
33 | : Msf(Msf), Allocator(Msf.getAllocator()), Header(nullptr), Idx(StreamIdx) { |
34 | } |
35 | |
36 | TpiStreamBuilder::~TpiStreamBuilder() = default; |
37 | |
38 | void TpiStreamBuilder::(PdbRaw_TpiVer Version) { |
39 | VerHeader = Version; |
40 | } |
41 | |
42 | void TpiStreamBuilder::updateTypeIndexOffsets(ArrayRef<uint16_t> Sizes) { |
43 | // If we just crossed an 8KB threshold, add a type index offset. |
44 | for (uint16_t Size : Sizes) { |
45 | size_t NewSize = TypeRecordBytes + Size; |
46 | constexpr size_t EightKB = 8 * 1024; |
47 | if (NewSize / EightKB > TypeRecordBytes / EightKB || TypeRecordCount == 0) { |
48 | TypeIndexOffsets.push_back( |
49 | x: {.Type: codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex + |
50 | TypeRecordCount), |
51 | .Offset: ulittle32_t(TypeRecordBytes)}); |
52 | } |
53 | ++TypeRecordCount; |
54 | TypeRecordBytes = NewSize; |
55 | } |
56 | } |
57 | |
58 | void TpiStreamBuilder::addTypeRecord(ArrayRef<uint8_t> Record, |
59 | std::optional<uint32_t> Hash) { |
60 | assert(((Record.size() & 3) == 0) && |
61 | "The type record's size is not a multiple of 4 bytes which will " |
62 | "cause misalignment in the output TPI stream!" ); |
63 | assert(Record.size() <= codeview::MaxRecordLength); |
64 | uint16_t OneSize = (uint16_t)Record.size(); |
65 | updateTypeIndexOffsets(Sizes: ArrayRef(&OneSize, 1)); |
66 | |
67 | TypeRecBuffers.push_back(x: Record); |
68 | // FIXME: Require it. |
69 | if (Hash) |
70 | TypeHashes.push_back(x: *Hash); |
71 | } |
72 | |
73 | void TpiStreamBuilder::addTypeRecords(ArrayRef<uint8_t> Types, |
74 | ArrayRef<uint16_t> Sizes, |
75 | ArrayRef<uint32_t> Hashes) { |
76 | // Ignore empty type buffers. There should be no hashes or sizes in this case. |
77 | if (Types.empty()) { |
78 | assert(Sizes.empty() && Hashes.empty()); |
79 | return; |
80 | } |
81 | |
82 | assert(((Types.size() & 3) == 0) && |
83 | "The type record's size is not a multiple of 4 bytes which will " |
84 | "cause misalignment in the output TPI stream!" ); |
85 | assert(Sizes.size() == Hashes.size() && "sizes and hashes should be in sync" ); |
86 | assert(std::accumulate(Sizes.begin(), Sizes.end(), 0U) == Types.size() && |
87 | "sizes of type records should sum to the size of the types" ); |
88 | updateTypeIndexOffsets(Sizes); |
89 | |
90 | TypeRecBuffers.push_back(x: Types); |
91 | llvm::append_range(C&: TypeHashes, R&: Hashes); |
92 | } |
93 | |
94 | Error TpiStreamBuilder::finalize() { |
95 | if (Header) |
96 | return Error::success(); |
97 | |
98 | TpiStreamHeader *H = Allocator.Allocate<TpiStreamHeader>(); |
99 | |
100 | H->Version = VerHeader; |
101 | H->HeaderSize = sizeof(TpiStreamHeader); |
102 | H->TypeIndexBegin = codeview::TypeIndex::FirstNonSimpleIndex; |
103 | H->TypeIndexEnd = H->TypeIndexBegin + TypeRecordCount; |
104 | H->TypeRecordBytes = TypeRecordBytes; |
105 | |
106 | H->HashStreamIndex = HashStreamIndex; |
107 | H->HashAuxStreamIndex = kInvalidStreamIndex; |
108 | H->HashKeySize = sizeof(ulittle32_t); |
109 | H->NumHashBuckets = MaxTpiHashBuckets - 1; |
110 | |
111 | // Recall that hash values go into a completely different stream identified by |
112 | // the `HashStreamIndex` field of the `TpiStreamHeader`. Therefore, the data |
113 | // begins at offset 0 of this independent stream. |
114 | H->HashValueBuffer.Off = 0; |
115 | H->HashValueBuffer.Length = calculateHashBufferSize(); |
116 | |
117 | // We never write any adjustments into our PDBs, so this is usually some |
118 | // offset with zero length. |
119 | H->HashAdjBuffer.Off = H->HashValueBuffer.Off + H->HashValueBuffer.Length; |
120 | H->HashAdjBuffer.Length = 0; |
121 | |
122 | H->IndexOffsetBuffer.Off = H->HashAdjBuffer.Off + H->HashAdjBuffer.Length; |
123 | H->IndexOffsetBuffer.Length = calculateIndexOffsetSize(); |
124 | |
125 | Header = H; |
126 | return Error::success(); |
127 | } |
128 | |
129 | uint32_t TpiStreamBuilder::calculateSerializedLength() { |
130 | return sizeof(TpiStreamHeader) + TypeRecordBytes; |
131 | } |
132 | |
133 | uint32_t TpiStreamBuilder::calculateHashBufferSize() const { |
134 | assert((TypeRecordCount == TypeHashes.size() || TypeHashes.empty()) && |
135 | "either all or no type records should have hashes" ); |
136 | return TypeHashes.size() * sizeof(ulittle32_t); |
137 | } |
138 | |
139 | uint32_t TpiStreamBuilder::calculateIndexOffsetSize() const { |
140 | return TypeIndexOffsets.size() * sizeof(codeview::TypeIndexOffset); |
141 | } |
142 | |
143 | Error TpiStreamBuilder::finalizeMsfLayout() { |
144 | uint32_t Length = calculateSerializedLength(); |
145 | if (auto EC = Msf.setStreamSize(Idx, Size: Length)) |
146 | return EC; |
147 | |
148 | uint32_t HashStreamSize = |
149 | calculateHashBufferSize() + calculateIndexOffsetSize(); |
150 | |
151 | if (HashStreamSize == 0) |
152 | return Error::success(); |
153 | |
154 | auto ExpectedIndex = Msf.addStream(Size: HashStreamSize); |
155 | if (!ExpectedIndex) |
156 | return ExpectedIndex.takeError(); |
157 | HashStreamIndex = *ExpectedIndex; |
158 | if (!TypeHashes.empty()) { |
159 | ulittle32_t *H = Allocator.Allocate<ulittle32_t>(Num: TypeHashes.size()); |
160 | MutableArrayRef<ulittle32_t> HashBuffer(H, TypeHashes.size()); |
161 | for (uint32_t I = 0; I < TypeHashes.size(); ++I) { |
162 | HashBuffer[I] = TypeHashes[I] % (MaxTpiHashBuckets - 1); |
163 | } |
164 | ArrayRef<uint8_t> Bytes( |
165 | reinterpret_cast<const uint8_t *>(HashBuffer.data()), |
166 | calculateHashBufferSize()); |
167 | HashValueStream = |
168 | std::make_unique<BinaryByteStream>(args&: Bytes, args: llvm::endianness::little); |
169 | } |
170 | return Error::success(); |
171 | } |
172 | |
173 | Error TpiStreamBuilder::commit(const msf::MSFLayout &Layout, |
174 | WritableBinaryStreamRef Buffer) { |
175 | llvm::TimeTraceScope timeScope("Commit TPI stream" ); |
176 | if (auto EC = finalize()) |
177 | return EC; |
178 | |
179 | auto InfoS = WritableMappedBlockStream::createIndexedStream(Layout, MsfData: Buffer, |
180 | StreamIndex: Idx, Allocator); |
181 | |
182 | BinaryStreamWriter Writer(*InfoS); |
183 | if (auto EC = Writer.writeObject(Obj: *Header)) |
184 | return EC; |
185 | |
186 | for (auto Rec : TypeRecBuffers) { |
187 | assert(!Rec.empty() && "Attempting to write an empty type record shifts " |
188 | "all offsets in the TPI stream!" ); |
189 | assert(((Rec.size() & 3) == 0) && |
190 | "The type record's size is not a multiple of 4 bytes which will " |
191 | "cause misalignment in the output TPI stream!" ); |
192 | if (auto EC = Writer.writeBytes(Buffer: Rec)) |
193 | return EC; |
194 | } |
195 | |
196 | if (HashStreamIndex != kInvalidStreamIndex) { |
197 | auto HVS = WritableMappedBlockStream::createIndexedStream( |
198 | Layout, MsfData: Buffer, StreamIndex: HashStreamIndex, Allocator); |
199 | BinaryStreamWriter HW(*HVS); |
200 | if (HashValueStream) { |
201 | if (auto EC = HW.writeStreamRef(Ref: *HashValueStream)) |
202 | return EC; |
203 | } |
204 | |
205 | for (auto &IndexOffset : TypeIndexOffsets) { |
206 | if (auto EC = HW.writeObject(Obj: IndexOffset)) |
207 | return EC; |
208 | } |
209 | } |
210 | |
211 | return Error::success(); |
212 | } |
213 | |