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 <algorithm> 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::setVersionHeader(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 {codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex + 49 TypeRecordCount), 50 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(ArrayRef(&OneSize, 1)); 65 66 TypeRecBuffers.push_back(Record); 67 // FIXME: Require it. 68 if (Hash) 69 TypeHashes.push_back(*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(Types); 90 llvm::append_range(TypeHashes, 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, 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(HashStreamSize); 154 if (!ExpectedIndex) 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); 162 } 163 ArrayRef<uint8_t> Bytes( 164 reinterpret_cast<const uint8_t *>(HashBuffer.data()), 165 calculateHashBufferSize()); 166 HashValueStream = 167 std::make_unique<BinaryByteStream>(Bytes, llvm::support::little); 168 } 169 return Error::success(); 170 } 171 172 Error TpiStreamBuilder::commit(const msf::MSFLayout &Layout, 173 WritableBinaryStreamRef Buffer) { 174 if (auto EC = finalize()) 175 return EC; 176 177 auto InfoS = WritableMappedBlockStream::createIndexedStream(Layout, Buffer, 178 Idx, Allocator); 179 180 BinaryStreamWriter Writer(*InfoS); 181 if (auto EC = Writer.writeObject(*Header)) 182 return EC; 183 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)) 191 return EC; 192 } 193 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)) 200 return EC; 201 } 202 203 for (auto &IndexOffset : TypeIndexOffsets) { 204 if (auto EC = HW.writeObject(IndexOffset)) 205 return EC; 206 } 207 } 208 209 return Error::success(); 210 } 211