1 //===- TpiStreamBuilder.cpp - -------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
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"
27 using namespace llvm::msf
;
28 using namespace llvm::pdb
;
29 using namespace llvm::support
;
31 TpiStreamBuilder::TpiStreamBuilder(MSFBuilder
&Msf
, uint32_t StreamIdx
)
32 : Msf(Msf
), Allocator(Msf
.getAllocator()), Header(nullptr), Idx(StreamIdx
) {
35 TpiStreamBuilder::~TpiStreamBuilder() = default;
37 void TpiStreamBuilder::setVersionHeader(PdbRaw_TpiVer Version
) {
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 {codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex
+
50 ulittle32_t(TypeRecordBytes
)});
53 TypeRecordBytes
= NewSize
;
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(ArrayRef(&OneSize
, 1));
66 TypeRecBuffers
.push_back(Record
);
69 TypeHashes
.push_back(*Hash
);
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.
77 assert(Sizes
.empty() && Hashes
.empty());
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
);
89 TypeRecBuffers
.push_back(Types
);
90 llvm::append_range(TypeHashes
, Hashes
);
93 Error
TpiStreamBuilder::finalize() {
95 return Error::success();
97 TpiStreamHeader
*H
= Allocator
.Allocate
<TpiStreamHeader
>();
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
;
105 H
->HashStreamIndex
= HashStreamIndex
;
106 H
->HashAuxStreamIndex
= kInvalidStreamIndex
;
107 H
->HashKeySize
= sizeof(ulittle32_t
);
108 H
->NumHashBuckets
= MaxTpiHashBuckets
- 1;
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();
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;
121 H
->IndexOffsetBuffer
.Off
= H
->HashAdjBuffer
.Off
+ H
->HashAdjBuffer
.Length
;
122 H
->IndexOffsetBuffer
.Length
= calculateIndexOffsetSize();
125 return Error::success();
128 uint32_t TpiStreamBuilder::calculateSerializedLength() {
129 return sizeof(TpiStreamHeader
) + TypeRecordBytes
;
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
);
138 uint32_t TpiStreamBuilder::calculateIndexOffsetSize() const {
139 return TypeIndexOffsets
.size() * sizeof(codeview::TypeIndexOffset
);
142 Error
TpiStreamBuilder::finalizeMsfLayout() {
143 uint32_t Length
= calculateSerializedLength();
144 if (auto EC
= Msf
.setStreamSize(Idx
, Length
))
147 uint32_t HashStreamSize
=
148 calculateHashBufferSize() + calculateIndexOffsetSize();
150 if (HashStreamSize
== 0)
151 return Error::success();
153 auto ExpectedIndex
= Msf
.addStream(HashStreamSize
);
155 return ExpectedIndex
.takeError();
156 HashStreamIndex
= *ExpectedIndex
;
157 if (!TypeHashes
.empty()) {
158 ulittle32_t
*H
= Allocator
.Allocate
<ulittle32_t
>(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);
163 ArrayRef
<uint8_t> Bytes(
164 reinterpret_cast<const uint8_t *>(HashBuffer
.data()),
165 calculateHashBufferSize());
167 std::make_unique
<BinaryByteStream
>(Bytes
, llvm::support::little
);
169 return Error::success();
172 Error
TpiStreamBuilder::commit(const msf::MSFLayout
&Layout
,
173 WritableBinaryStreamRef Buffer
) {
174 if (auto EC
= finalize())
177 auto InfoS
= WritableMappedBlockStream::createIndexedStream(Layout
, Buffer
,
180 BinaryStreamWriter
Writer(*InfoS
);
181 if (auto EC
= Writer
.writeObject(*Header
))
184 for (auto Rec
: TypeRecBuffers
) {
185 assert(!Rec
.empty() && "Attempting to write an empty type record shifts "
186 "all offsets in the TPI stream!");
187 assert(((Rec
.size() & 3) == 0) &&
188 "The type record's size is not a multiple of 4 bytes which will "
189 "cause misalignment in the output TPI stream!");
190 if (auto EC
= Writer
.writeBytes(Rec
))
194 if (HashStreamIndex
!= kInvalidStreamIndex
) {
195 auto HVS
= WritableMappedBlockStream::createIndexedStream(
196 Layout
, Buffer
, HashStreamIndex
, Allocator
);
197 BinaryStreamWriter
HW(*HVS
);
198 if (HashValueStream
) {
199 if (auto EC
= HW
.writeStreamRef(*HashValueStream
))
203 for (auto &IndexOffset
: TypeIndexOffsets
) {
204 if (auto EC
= HW
.writeObject(IndexOffset
))
209 return Error::success();