Path: blob/main/contrib/llvm-project/llvm/lib/DebugInfo/MSF/MappedBlockStream.cpp
35266 views
//===- MappedBlockStream.cpp - Reads stream data from an MSF file ---------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "llvm/DebugInfo/MSF/MappedBlockStream.h"9#include "llvm/ADT/ArrayRef.h"10#include "llvm/DebugInfo/MSF/MSFCommon.h"11#include "llvm/Support/BinaryStreamWriter.h"12#include "llvm/Support/Error.h"13#include "llvm/Support/MathExtras.h"14#include <algorithm>15#include <cassert>16#include <cstdint>17#include <cstring>18#include <utility>19#include <vector>2021using namespace llvm;22using namespace llvm::msf;2324namespace {2526template <typename Base> class MappedBlockStreamImpl : public Base {27public:28template <typename... Args>29MappedBlockStreamImpl(Args &&... Params)30: Base(std::forward<Args>(Params)...) {}31};3233} // end anonymous namespace3435using Interval = std::pair<uint64_t, uint64_t>;3637static Interval intersect(const Interval &I1, const Interval &I2) {38return std::make_pair(std::max(I1.first, I2.first),39std::min(I1.second, I2.second));40}4142MappedBlockStream::MappedBlockStream(uint32_t BlockSize,43const MSFStreamLayout &Layout,44BinaryStreamRef MsfData,45BumpPtrAllocator &Allocator)46: BlockSize(BlockSize), StreamLayout(Layout), MsfData(MsfData),47Allocator(Allocator) {}4849std::unique_ptr<MappedBlockStream> MappedBlockStream::createStream(50uint32_t BlockSize, const MSFStreamLayout &Layout, BinaryStreamRef MsfData,51BumpPtrAllocator &Allocator) {52return std::make_unique<MappedBlockStreamImpl<MappedBlockStream>>(53BlockSize, Layout, MsfData, Allocator);54}5556std::unique_ptr<MappedBlockStream> MappedBlockStream::createIndexedStream(57const MSFLayout &Layout, BinaryStreamRef MsfData, uint32_t StreamIndex,58BumpPtrAllocator &Allocator) {59assert(StreamIndex < Layout.StreamMap.size() && "Invalid stream index");60MSFStreamLayout SL;61SL.Blocks = Layout.StreamMap[StreamIndex];62SL.Length = Layout.StreamSizes[StreamIndex];63return std::make_unique<MappedBlockStreamImpl<MappedBlockStream>>(64Layout.SB->BlockSize, SL, MsfData, Allocator);65}6667std::unique_ptr<MappedBlockStream>68MappedBlockStream::createDirectoryStream(const MSFLayout &Layout,69BinaryStreamRef MsfData,70BumpPtrAllocator &Allocator) {71MSFStreamLayout SL;72SL.Blocks = Layout.DirectoryBlocks;73SL.Length = Layout.SB->NumDirectoryBytes;74return createStream(Layout.SB->BlockSize, SL, MsfData, Allocator);75}7677std::unique_ptr<MappedBlockStream>78MappedBlockStream::createFpmStream(const MSFLayout &Layout,79BinaryStreamRef MsfData,80BumpPtrAllocator &Allocator) {81MSFStreamLayout SL(getFpmStreamLayout(Layout));82return createStream(Layout.SB->BlockSize, SL, MsfData, Allocator);83}8485Error MappedBlockStream::readBytes(uint64_t Offset, uint64_t Size,86ArrayRef<uint8_t> &Buffer) {87// Make sure we aren't trying to read beyond the end of the stream.88if (auto EC = checkOffsetForRead(Offset, Size))89return EC;9091if (tryReadContiguously(Offset, Size, Buffer))92return Error::success();9394auto CacheIter = CacheMap.find(Offset);95if (CacheIter != CacheMap.end()) {96// Try to find an alloc that was large enough for this request.97for (auto &Entry : CacheIter->second) {98if (Entry.size() >= Size) {99Buffer = Entry.slice(0, Size);100return Error::success();101}102}103}104105// We couldn't find a buffer that started at the correct offset (the most106// common scenario). Try to see if there is a buffer that starts at some107// other offset but overlaps the desired range.108for (auto &CacheItem : CacheMap) {109Interval RequestExtent = std::make_pair(Offset, Offset + Size);110111// We already checked this one on the fast path above.112if (CacheItem.first == Offset)113continue;114// If the initial extent of the cached item is beyond the ending extent115// of the request, there is no overlap.116if (CacheItem.first >= Offset + Size)117continue;118119// We really only have to check the last item in the list, since we append120// in order of increasing length.121if (CacheItem.second.empty())122continue;123124auto CachedAlloc = CacheItem.second.back();125// If the initial extent of the request is beyond the ending extent of126// the cached item, there is no overlap.127Interval CachedExtent =128std::make_pair(CacheItem.first, CacheItem.first + CachedAlloc.size());129if (RequestExtent.first >= CachedExtent.first + CachedExtent.second)130continue;131132Interval Intersection = intersect(CachedExtent, RequestExtent);133// Only use this if the entire request extent is contained in the cached134// extent.135if (Intersection != RequestExtent)136continue;137138uint64_t CacheRangeOffset =139AbsoluteDifference(CachedExtent.first, Intersection.first);140Buffer = CachedAlloc.slice(CacheRangeOffset, Size);141return Error::success();142}143144// Otherwise allocate a large enough buffer in the pool, memcpy the data145// into it, and return an ArrayRef to that. Do not touch existing pool146// allocations, as existing clients may be holding a pointer which must147// not be invalidated.148uint8_t *WriteBuffer = static_cast<uint8_t *>(Allocator.Allocate(Size, 8));149if (auto EC = readBytes(Offset, MutableArrayRef<uint8_t>(WriteBuffer, Size)))150return EC;151152if (CacheIter != CacheMap.end()) {153CacheIter->second.emplace_back(WriteBuffer, Size);154} else {155std::vector<CacheEntry> List;156List.emplace_back(WriteBuffer, Size);157CacheMap.insert(std::make_pair(Offset, List));158}159Buffer = ArrayRef<uint8_t>(WriteBuffer, Size);160return Error::success();161}162163Error MappedBlockStream::readLongestContiguousChunk(uint64_t Offset,164ArrayRef<uint8_t> &Buffer) {165// Make sure we aren't trying to read beyond the end of the stream.166if (auto EC = checkOffsetForRead(Offset, 1))167return EC;168169uint64_t First = Offset / BlockSize;170uint64_t Last = First;171172while (Last < getNumBlocks() - 1) {173if (StreamLayout.Blocks[Last] != StreamLayout.Blocks[Last + 1] - 1)174break;175++Last;176}177178uint64_t OffsetInFirstBlock = Offset % BlockSize;179uint64_t BytesFromFirstBlock = BlockSize - OffsetInFirstBlock;180uint64_t BlockSpan = Last - First + 1;181uint64_t ByteSpan = BytesFromFirstBlock + (BlockSpan - 1) * BlockSize;182183ArrayRef<uint8_t> BlockData;184uint64_t MsfOffset = blockToOffset(StreamLayout.Blocks[First], BlockSize);185if (auto EC = MsfData.readBytes(MsfOffset, BlockSize, BlockData))186return EC;187188BlockData = BlockData.drop_front(OffsetInFirstBlock);189Buffer = ArrayRef<uint8_t>(BlockData.data(), ByteSpan);190return Error::success();191}192193uint64_t MappedBlockStream::getLength() { return StreamLayout.Length; }194195bool MappedBlockStream::tryReadContiguously(uint64_t Offset, uint64_t Size,196ArrayRef<uint8_t> &Buffer) {197if (Size == 0) {198Buffer = ArrayRef<uint8_t>();199return true;200}201// Attempt to fulfill the request with a reference directly into the stream.202// This can work even if the request crosses a block boundary, provided that203// all subsequent blocks are contiguous. For example, a 10k read with a 4k204// block size can be filled with a reference if, from the starting offset,205// 3 blocks in a row are contiguous.206uint64_t BlockNum = Offset / BlockSize;207uint64_t OffsetInBlock = Offset % BlockSize;208uint64_t BytesFromFirstBlock = std::min(Size, BlockSize - OffsetInBlock);209uint64_t NumAdditionalBlocks =210alignTo(Size - BytesFromFirstBlock, BlockSize) / BlockSize;211212uint64_t RequiredContiguousBlocks = NumAdditionalBlocks + 1;213uint64_t E = StreamLayout.Blocks[BlockNum];214for (uint64_t I = 0; I < RequiredContiguousBlocks; ++I, ++E) {215if (StreamLayout.Blocks[I + BlockNum] != E)216return false;217}218219// Read out the entire block where the requested offset starts. Then drop220// bytes from the beginning so that the actual starting byte lines up with221// the requested starting byte. Then, since we know this is a contiguous222// cross-block span, explicitly resize the ArrayRef to cover the entire223// request length.224ArrayRef<uint8_t> BlockData;225uint64_t FirstBlockAddr = StreamLayout.Blocks[BlockNum];226uint64_t MsfOffset = blockToOffset(FirstBlockAddr, BlockSize);227if (auto EC = MsfData.readBytes(MsfOffset, BlockSize, BlockData)) {228consumeError(std::move(EC));229return false;230}231BlockData = BlockData.drop_front(OffsetInBlock);232Buffer = ArrayRef<uint8_t>(BlockData.data(), Size);233return true;234}235236Error MappedBlockStream::readBytes(uint64_t Offset,237MutableArrayRef<uint8_t> Buffer) {238uint64_t BlockNum = Offset / BlockSize;239uint64_t OffsetInBlock = Offset % BlockSize;240241// Make sure we aren't trying to read beyond the end of the stream.242if (auto EC = checkOffsetForRead(Offset, Buffer.size()))243return EC;244245uint64_t BytesLeft = Buffer.size();246uint64_t BytesWritten = 0;247uint8_t *WriteBuffer = Buffer.data();248while (BytesLeft > 0) {249uint64_t StreamBlockAddr = StreamLayout.Blocks[BlockNum];250251ArrayRef<uint8_t> BlockData;252uint64_t Offset = blockToOffset(StreamBlockAddr, BlockSize);253if (auto EC = MsfData.readBytes(Offset, BlockSize, BlockData))254return EC;255256const uint8_t *ChunkStart = BlockData.data() + OffsetInBlock;257uint64_t BytesInChunk = std::min(BytesLeft, BlockSize - OffsetInBlock);258::memcpy(WriteBuffer + BytesWritten, ChunkStart, BytesInChunk);259260BytesWritten += BytesInChunk;261BytesLeft -= BytesInChunk;262++BlockNum;263OffsetInBlock = 0;264}265266return Error::success();267}268269void MappedBlockStream::invalidateCache() { CacheMap.shrink_and_clear(); }270271void MappedBlockStream::fixCacheAfterWrite(uint64_t Offset,272ArrayRef<uint8_t> Data) const {273// If this write overlapped a read which previously came from the pool,274// someone may still be holding a pointer to that alloc which is now invalid.275// Compute the overlapping range and update the cache entry, so any276// outstanding buffers are automatically updated.277for (const auto &MapEntry : CacheMap) {278// If the end of the written extent precedes the beginning of the cached279// extent, ignore this map entry.280if (Offset + Data.size() < MapEntry.first)281continue;282for (const auto &Alloc : MapEntry.second) {283// If the end of the cached extent precedes the beginning of the written284// extent, ignore this alloc.285if (MapEntry.first + Alloc.size() < Offset)286continue;287288// If we get here, they are guaranteed to overlap.289Interval WriteInterval = std::make_pair(Offset, Offset + Data.size());290Interval CachedInterval =291std::make_pair(MapEntry.first, MapEntry.first + Alloc.size());292// If they overlap, we need to write the new data into the overlapping293// range.294auto Intersection = intersect(WriteInterval, CachedInterval);295assert(Intersection.first <= Intersection.second);296297uint64_t Length = Intersection.second - Intersection.first;298uint64_t SrcOffset =299AbsoluteDifference(WriteInterval.first, Intersection.first);300uint64_t DestOffset =301AbsoluteDifference(CachedInterval.first, Intersection.first);302::memcpy(Alloc.data() + DestOffset, Data.data() + SrcOffset, Length);303}304}305}306307WritableMappedBlockStream::WritableMappedBlockStream(308uint32_t BlockSize, const MSFStreamLayout &Layout,309WritableBinaryStreamRef MsfData, BumpPtrAllocator &Allocator)310: ReadInterface(BlockSize, Layout, MsfData, Allocator),311WriteInterface(MsfData) {}312313std::unique_ptr<WritableMappedBlockStream>314WritableMappedBlockStream::createStream(uint32_t BlockSize,315const MSFStreamLayout &Layout,316WritableBinaryStreamRef MsfData,317BumpPtrAllocator &Allocator) {318return std::make_unique<MappedBlockStreamImpl<WritableMappedBlockStream>>(319BlockSize, Layout, MsfData, Allocator);320}321322std::unique_ptr<WritableMappedBlockStream>323WritableMappedBlockStream::createIndexedStream(const MSFLayout &Layout,324WritableBinaryStreamRef MsfData,325uint32_t StreamIndex,326BumpPtrAllocator &Allocator) {327assert(StreamIndex < Layout.StreamMap.size() && "Invalid stream index");328MSFStreamLayout SL;329SL.Blocks = Layout.StreamMap[StreamIndex];330SL.Length = Layout.StreamSizes[StreamIndex];331return createStream(Layout.SB->BlockSize, SL, MsfData, Allocator);332}333334std::unique_ptr<WritableMappedBlockStream>335WritableMappedBlockStream::createDirectoryStream(336const MSFLayout &Layout, WritableBinaryStreamRef MsfData,337BumpPtrAllocator &Allocator) {338MSFStreamLayout SL;339SL.Blocks = Layout.DirectoryBlocks;340SL.Length = Layout.SB->NumDirectoryBytes;341return createStream(Layout.SB->BlockSize, SL, MsfData, Allocator);342}343344std::unique_ptr<WritableMappedBlockStream>345WritableMappedBlockStream::createFpmStream(const MSFLayout &Layout,346WritableBinaryStreamRef MsfData,347BumpPtrAllocator &Allocator,348bool AltFpm) {349// We only want to give the user a stream containing the bytes of the FPM that350// are actually valid, but we want to initialize all of the bytes, even those351// that come from reserved FPM blocks where the entire block is unused. To do352// this, we first create the full layout, which gives us a stream with all353// bytes and all blocks, and initialize everything to 0xFF (all blocks in the354// file are unused). Then we create the minimal layout (which contains only a355// subset of the bytes previously initialized), and return that to the user.356MSFStreamLayout MinLayout(getFpmStreamLayout(Layout, false, AltFpm));357358MSFStreamLayout FullLayout(getFpmStreamLayout(Layout, true, AltFpm));359auto Result =360createStream(Layout.SB->BlockSize, FullLayout, MsfData, Allocator);361if (!Result)362return Result;363std::vector<uint8_t> InitData(Layout.SB->BlockSize, 0xFF);364BinaryStreamWriter Initializer(*Result);365while (Initializer.bytesRemaining() > 0)366cantFail(Initializer.writeBytes(InitData));367return createStream(Layout.SB->BlockSize, MinLayout, MsfData, Allocator);368}369370Error WritableMappedBlockStream::readBytes(uint64_t Offset, uint64_t Size,371ArrayRef<uint8_t> &Buffer) {372return ReadInterface.readBytes(Offset, Size, Buffer);373}374375Error WritableMappedBlockStream::readLongestContiguousChunk(376uint64_t Offset, ArrayRef<uint8_t> &Buffer) {377return ReadInterface.readLongestContiguousChunk(Offset, Buffer);378}379380uint64_t WritableMappedBlockStream::getLength() {381return ReadInterface.getLength();382}383384Error WritableMappedBlockStream::writeBytes(uint64_t Offset,385ArrayRef<uint8_t> Buffer) {386// Make sure we aren't trying to write beyond the end of the stream.387if (auto EC = checkOffsetForWrite(Offset, Buffer.size()))388return EC;389390uint64_t BlockNum = Offset / getBlockSize();391uint64_t OffsetInBlock = Offset % getBlockSize();392393uint64_t BytesLeft = Buffer.size();394uint64_t BytesWritten = 0;395while (BytesLeft > 0) {396uint64_t StreamBlockAddr = getStreamLayout().Blocks[BlockNum];397uint64_t BytesToWriteInChunk =398std::min(BytesLeft, getBlockSize() - OffsetInBlock);399400const uint8_t *Chunk = Buffer.data() + BytesWritten;401ArrayRef<uint8_t> ChunkData(Chunk, BytesToWriteInChunk);402uint64_t MsfOffset = blockToOffset(StreamBlockAddr, getBlockSize());403MsfOffset += OffsetInBlock;404if (auto EC = WriteInterface.writeBytes(MsfOffset, ChunkData))405return EC;406407BytesLeft -= BytesToWriteInChunk;408BytesWritten += BytesToWriteInChunk;409++BlockNum;410OffsetInBlock = 0;411}412413ReadInterface.fixCacheAfterWrite(Offset, Buffer);414415return Error::success();416}417418Error WritableMappedBlockStream::commit() { return WriteInterface.commit(); }419420421