| //===- TpiStreamBuilder.cpp - -------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "llvm/DebugInfo/PDB/Native/TpiStreamBuilder.h" |
| #include "llvm/ADT/ArrayRef.h" |
| #include "llvm/ADT/STLExtras.h" |
| #include "llvm/DebugInfo/CodeView/TypeIndex.h" |
| #include "llvm/DebugInfo/CodeView/TypeRecord.h" |
| #include "llvm/DebugInfo/MSF/MSFBuilder.h" |
| #include "llvm/DebugInfo/MSF/MappedBlockStream.h" |
| #include "llvm/DebugInfo/PDB/Native/PDBFile.h" |
| #include "llvm/DebugInfo/PDB/Native/RawError.h" |
| #include "llvm/DebugInfo/PDB/Native/RawTypes.h" |
| #include "llvm/Support/Allocator.h" |
| #include "llvm/Support/BinaryByteStream.h" |
| #include "llvm/Support/BinaryStreamArray.h" |
| #include "llvm/Support/BinaryStreamReader.h" |
| #include "llvm/Support/BinaryStreamWriter.h" |
| #include "llvm/Support/Endian.h" |
| #include "llvm/Support/Error.h" |
| #include <algorithm> |
| #include <cstdint> |
| #include <numeric> |
| |
| using namespace llvm; |
| using namespace llvm::msf; |
| using namespace llvm::pdb; |
| using namespace llvm::support; |
| |
| TpiStreamBuilder::TpiStreamBuilder(MSFBuilder &Msf, uint32_t StreamIdx) |
| : Msf(Msf), Allocator(Msf.getAllocator()), Header(nullptr), Idx(StreamIdx) { |
| } |
| |
| TpiStreamBuilder::~TpiStreamBuilder() = default; |
| |
| void TpiStreamBuilder::setVersionHeader(PdbRaw_TpiVer Version) { |
| VerHeader = Version; |
| } |
| |
| void TpiStreamBuilder::updateTypeIndexOffsets(ArrayRef<uint16_t> Sizes) { |
| // If we just crossed an 8KB threshold, add a type index offset. |
| for (uint16_t Size : Sizes) { |
| size_t NewSize = TypeRecordBytes + Size; |
| constexpr size_t EightKB = 8 * 1024; |
| if (NewSize / EightKB > TypeRecordBytes / EightKB || TypeRecordCount == 0) { |
| TypeIndexOffsets.push_back( |
| {codeview::TypeIndex(codeview::TypeIndex::FirstNonSimpleIndex + |
| TypeRecordCount), |
| ulittle32_t(TypeRecordBytes)}); |
| } |
| ++TypeRecordCount; |
| TypeRecordBytes = NewSize; |
| } |
| } |
| |
| void TpiStreamBuilder::addTypeRecord(ArrayRef<uint8_t> Record, |
| Optional<uint32_t> Hash) { |
| assert(((Record.size() & 3) == 0) && |
| "The type record's size is not a multiple of 4 bytes which will " |
| "cause misalignment in the output TPI stream!"); |
| assert(Record.size() <= codeview::MaxRecordLength); |
| uint16_t OneSize = (uint16_t)Record.size(); |
| updateTypeIndexOffsets(makeArrayRef(&OneSize, 1)); |
| |
| TypeRecBuffers.push_back(Record); |
| // FIXME: Require it. |
| if (Hash) |
| TypeHashes.push_back(*Hash); |
| } |
| |
| void TpiStreamBuilder::addTypeRecords(ArrayRef<uint8_t> Types, |
| ArrayRef<uint16_t> Sizes, |
| ArrayRef<uint32_t> Hashes) { |
| // Ignore empty type buffers. There should be no hashes or sizes in this case. |
| if (Types.empty()) { |
| assert(Sizes.empty() && Hashes.empty()); |
| return; |
| } |
| |
| assert(((Types.size() & 3) == 0) && |
| "The type record's size is not a multiple of 4 bytes which will " |
| "cause misalignment in the output TPI stream!"); |
| assert(Sizes.size() == Hashes.size() && "sizes and hashes should be in sync"); |
| assert(std::accumulate(Sizes.begin(), Sizes.end(), 0U) == Types.size() && |
| "sizes of type records should sum to the size of the types"); |
| updateTypeIndexOffsets(Sizes); |
| |
| TypeRecBuffers.push_back(Types); |
| llvm::append_range(TypeHashes, Hashes); |
| } |
| |
| Error TpiStreamBuilder::finalize() { |
| if (Header) |
| return Error::success(); |
| |
| TpiStreamHeader *H = Allocator.Allocate<TpiStreamHeader>(); |
| |
| H->Version = VerHeader; |
| H->HeaderSize = sizeof(TpiStreamHeader); |
| H->TypeIndexBegin = codeview::TypeIndex::FirstNonSimpleIndex; |
| H->TypeIndexEnd = H->TypeIndexBegin + TypeRecordCount; |
| H->TypeRecordBytes = TypeRecordBytes; |
| |
| H->HashStreamIndex = HashStreamIndex; |
| H->HashAuxStreamIndex = kInvalidStreamIndex; |
| H->HashKeySize = sizeof(ulittle32_t); |
| H->NumHashBuckets = MaxTpiHashBuckets - 1; |
| |
| // Recall that hash values go into a completely different stream identified by |
| // the `HashStreamIndex` field of the `TpiStreamHeader`. Therefore, the data |
| // begins at offset 0 of this independent stream. |
| H->HashValueBuffer.Off = 0; |
| H->HashValueBuffer.Length = calculateHashBufferSize(); |
| |
| // We never write any adjustments into our PDBs, so this is usually some |
| // offset with zero length. |
| H->HashAdjBuffer.Off = H->HashValueBuffer.Off + H->HashValueBuffer.Length; |
| H->HashAdjBuffer.Length = 0; |
| |
| H->IndexOffsetBuffer.Off = H->HashAdjBuffer.Off + H->HashAdjBuffer.Length; |
| H->IndexOffsetBuffer.Length = calculateIndexOffsetSize(); |
| |
| Header = H; |
| return Error::success(); |
| } |
| |
| uint32_t TpiStreamBuilder::calculateSerializedLength() { |
| return sizeof(TpiStreamHeader) + TypeRecordBytes; |
| } |
| |
| uint32_t TpiStreamBuilder::calculateHashBufferSize() const { |
| assert((TypeRecordCount == TypeHashes.size() || TypeHashes.empty()) && |
| "either all or no type records should have hashes"); |
| return TypeHashes.size() * sizeof(ulittle32_t); |
| } |
| |
| uint32_t TpiStreamBuilder::calculateIndexOffsetSize() const { |
| return TypeIndexOffsets.size() * sizeof(codeview::TypeIndexOffset); |
| } |
| |
| Error TpiStreamBuilder::finalizeMsfLayout() { |
| uint32_t Length = calculateSerializedLength(); |
| if (auto EC = Msf.setStreamSize(Idx, Length)) |
| return EC; |
| |
| uint32_t HashStreamSize = |
| calculateHashBufferSize() + calculateIndexOffsetSize(); |
| |
| if (HashStreamSize == 0) |
| return Error::success(); |
| |
| auto ExpectedIndex = Msf.addStream(HashStreamSize); |
| if (!ExpectedIndex) |
| return ExpectedIndex.takeError(); |
| HashStreamIndex = *ExpectedIndex; |
| if (!TypeHashes.empty()) { |
| ulittle32_t *H = Allocator.Allocate<ulittle32_t>(TypeHashes.size()); |
| MutableArrayRef<ulittle32_t> HashBuffer(H, TypeHashes.size()); |
| for (uint32_t I = 0; I < TypeHashes.size(); ++I) { |
| HashBuffer[I] = TypeHashes[I] % (MaxTpiHashBuckets - 1); |
| } |
| ArrayRef<uint8_t> Bytes( |
| reinterpret_cast<const uint8_t *>(HashBuffer.data()), |
| calculateHashBufferSize()); |
| HashValueStream = |
| std::make_unique<BinaryByteStream>(Bytes, llvm::support::little); |
| } |
| return Error::success(); |
| } |
| |
| Error TpiStreamBuilder::commit(const msf::MSFLayout &Layout, |
| WritableBinaryStreamRef Buffer) { |
| if (auto EC = finalize()) |
| return EC; |
| |
| auto InfoS = WritableMappedBlockStream::createIndexedStream(Layout, Buffer, |
| Idx, Allocator); |
| |
| BinaryStreamWriter Writer(*InfoS); |
| if (auto EC = Writer.writeObject(*Header)) |
| return EC; |
| |
| for (auto Rec : TypeRecBuffers) { |
| assert(!Rec.empty() && "Attempting to write an empty type record shifts " |
| "all offsets in the TPI stream!"); |
| assert(((Rec.size() & 3) == 0) && |
| "The type record's size is not a multiple of 4 bytes which will " |
| "cause misalignment in the output TPI stream!"); |
| if (auto EC = Writer.writeBytes(Rec)) |
| return EC; |
| } |
| |
| if (HashStreamIndex != kInvalidStreamIndex) { |
| auto HVS = WritableMappedBlockStream::createIndexedStream( |
| Layout, Buffer, HashStreamIndex, Allocator); |
| BinaryStreamWriter HW(*HVS); |
| if (HashValueStream) { |
| if (auto EC = HW.writeStreamRef(*HashValueStream)) |
| return EC; |
| } |
| |
| for (auto &IndexOffset : TypeIndexOffsets) { |
| if (auto EC = HW.writeObject(IndexOffset)) |
| return EC; |
| } |
| } |
| |
| return Error::success(); |
| } |