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