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::setVersionHeader(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 {codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex + 50 TypeRecordCount), 51 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(ArrayRef(&OneSize, 1)); 66 67 TypeRecBuffers.push_back(Record); 68 // FIXME: Require it. 69 if (Hash) 70 TypeHashes.push_back(*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(Types); 91 llvm::append_range(TypeHashes, 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, 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(HashStreamSize); 155 if (!ExpectedIndex) 156 return ExpectedIndex.takeError(); 157 HashStreamIndex = *ExpectedIndex; 158 if (!TypeHashes.empty()) { 159 ulittle32_t *H = Allocator.Allocate<ulittle32_t>(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>(Bytes, 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, Buffer, 180 Idx, Allocator); 181 182 BinaryStreamWriter Writer(*InfoS); 183 if (auto EC = Writer.writeObject(*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(Rec)) 193 return EC; 194 } 195 196 if (HashStreamIndex != kInvalidStreamIndex) { 197 auto HVS = WritableMappedBlockStream::createIndexedStream( 198 Layout, Buffer, HashStreamIndex, Allocator); 199 BinaryStreamWriter HW(*HVS); 200 if (HashValueStream) { 201 if (auto EC = HW.writeStreamRef(*HashValueStream)) 202 return EC; 203 } 204 205 for (auto &IndexOffset : TypeIndexOffsets) { 206 if (auto EC = HW.writeObject(IndexOffset)) 207 return EC; 208 } 209 } 210 211 return Error::success(); 212 } 213