// slang-blob-builder.h #ifndef SLANG_BLOB_BUILDER_H #define SLANG_BLOB_BUILDER_H // This file provides utilities for building "blobs" of data // where, for purposes, a blob is a contiguous sequence of // bytes where the interpretation *of* those bytes depends // only on the bytes themselves, and not other factors like // the in-memory address of the blob, or the address/contents // of memory not in the blob. // // Superficially, the task seems simple: just maintain a // dynamically-sized array of bytes and append to it until // you're done. If that's what you need, you're probably // better off just using an `OwnedMemoryStream`. // // The utilities in this file deal with the case where you // want to build some kind of offset-based data structure, // so that parts of the blob will store byte offsets to // other parts, while also being able to build parts of // that structure "out of order", so that the final offset // of a particular piece of data in the blob may not be // known until everything *before* it has been fully built. #include "slang-basic.h" #include "slang-internally-linked-list.h" #include "slang-io.h" #include "slang-memory-arena.h" namespace Slang { inline constexpr bool isPowerOfTwo(Size value) { return value > 0 && (value - 1 & value) == 0; } inline constexpr Size roundUpToAlignment(Size size, Size alignment) { SLANG_ASSERT(isPowerOfTwo(alignment)); auto alignmentMask = Size(alignment) - 1; return (size + alignmentMask) & ~alignmentMask; } class ShardBuilder; class ChunkBuilder; struct BlobBuilder; /// A utility type for composing a binary blob. /// /// A blob builder allows a blob to be composed as a sequence of discrete /// chunks, allowing chunks to be added and written to in any order. /// /// Chunks can contain (relative) pointers to one another, with the correct /// relative offsets being computed as part of writing the entire blob out. /// struct BlobBuilder { public: /// Construct an empty blob builder. BlobBuilder(); /// Write the contents of the blob to the given `stream`. void writeTo(Stream* stream); /// Create a copy of the contents of the blob and assign to `outBlob`. void writeToBlob(ISlangBlob** outBlob); /// Add a new empty chunk to the end of the blob. ChunkBuilder* addChunk(); /// Add a new empty chunk after the given `chunk`. ChunkBuilder* addChunkAfter(ChunkBuilder* chunk); /// Create a chunk that is not initially part of the blob. /// /// The contents of the returned chunk will only become /// part of the full blob if `addChunk()` is called later, /// *or* if the contents of the new chunk are moved into /// another chunk that gets added. /// ChunkBuilder* createUnparentedChunk(); /// Add a chunk to the blob that was initially not part of the blob. void addChunk(ChunkBuilder* chunk); private: InternallyLinkedList _chunks; MemoryArena _arena; friend class ChunkBuilder; friend class ShardBuilder; MemoryArena& _getArena() { return _arena; } Size _calcSizeAndSetCachedChunkOffsets(); Size _writeChunksTo(Stream* stream); }; /// A chunk is a logically contiguous unit, such that data can /// only ever be appended to it. As a result, offsets that are /// relative to the start of a chunk are meaningful, and can be /// used to encode relative offsets for pointers. /// /// Every `ChunkBuilder` is owned by its parent `BlobBuilder`. /// A pointer to a `ChunkBuilder` will only be valid during the /// lifetime of the parent `BlobBuilder`. /// /// Conceptually, a `ChunkBuilder` has the following: /// /// * A minimum *alignment* in bytes (initially 1) /// /// * Zero or more bytes of *content* data (initially empty) /// /// * An optional *prefix* consisting of zero or more bytes (initially absent) /// /// The content may be a mix of raw data (e.g., added via `writeData()`), /// relative pointers to other chunks (`writeRelativePtr()`) and padding /// bytes (`writePaddingToAlignTo()`). /// /// The prefix may only be either a single relative pointer, or a /// single range of raw data bytes. /// /// When the parent blob written out as a flat buffer of bytes, the /// following are guaranteed: /// /// * The content of the chunk will be a contiguous range of bytes /// starting at some offset, and will not overlap any other chunks. /// /// * The byte offset where the chunk contents start will be a multiple /// of the chunk's minimum alignment. /// /// * The bytes of the prefix (if any) will immediately precede the /// bytes of the content. /// class ChunkBuilder : public InternallyLinkedList::Node { public: /// Get the blob builder that this chunk belongs to. BlobBuilder* getParentBlob() const { return _parentBlob; } /// Get the required alignment of this chunk. /// /// The minimum alignment for a chunk starts at 1, /// and may be increased by operations such as /// `setAlignmentToAtLeast()` and `writePaddingToAlinTo()`. /// Size getAlignment() const; /// Potentially increases the required alignment of this chunk. /// /// If the alignment of the chunk is less than `alignment`, /// then it will be increased to `alignment`. /// /// The `alignment` passed in must be a power of two. /// void setAlignmentToAtLeast(Size alignment); /// Get the size in bytes of the content of this chunk. /// /// Note that the size is not necessarily a multiple of the /// alignment of the chunk. /// Size getContentSize() const; /// Write data into the chunk. /// /// The chunk will retain a copy of the data passed in, /// so the `data` pointer only needs to be valid for /// the duration of the call. /// /// Note that this operation does *not* adjust the /// alignment of the chunk in any way. /// /// The data must only contain types that can be copied /// bit-for-bit, and that do not depend on addresses /// in meory. In particular no pointers (absolute or /// relative) should be written. /// void writeData(void const* data, Size size); /// Append padding bytes to this chunk until its content size /// is a multiple of `alignment`. /// /// May also increase the alignment of the chunk, as if /// calling `setAlignmentToAtLeast(alignment)`. /// /// The padding bytes will all be zero. /// void writePaddingToAlignTo(Size alignment); /// Write a relative pointer to the given `targetChunk`. /// /// The type parameter `T` is used to determine the size /// of the relative pointer (should be either 4 or 8 bytes). /// /// The bytes that eventually get written will contain /// the computed offset of `targetChunk` minus the computed /// offset of the first byte of the relative pointer itself. /// /// Acts as is `writePaddingToAlignTo(sizeof(T))` were /// called immediately before. /// template void writeRelativePtr(ChunkBuilder* targetChunk) { _writeRelativePtr(targetChunk, sizeof(T)); } /// Append the contents of another chunk to this one. /// /// This *moves* all of the contents of `chunk` into `this`. /// After the operation completes `chunk` will be an empty /// chunk with one-byte alignment. /// /// This operation is useful when accumulating data that /// needs to be appended to the chunk, but where the correct /// alignment for that data is not yet known; a use can /// effectively create a temporary "sub-chunk" and then append /// it to the main chunk once its correct alignment is known. /// void addContentsOf(ChunkBuilder* chunk); /// Get the size in bytes of the prefix of this chunk, if any. /// /// The prefix will be written so that the *end* offset of the /// prefix data is the same as the starting offset of the /// chunk's content. /// Size getPrefixSize() const; /// Add a prefix to this chunk, consisting of raw data. /// /// This chunk must not already have a prefix. /// void addPrefixData(void const* data, Size size); /// Add a prefix to this chunk, consisting of a relative pointer. /// /// This chunk must not already have a prefix. /// /// Updates the alignment of the chunk as if making a call to /// `setAlignmentToAtLeast(sizeof(T))`. /// template void addPrefixRelativePtr(ChunkBuilder* targetChunk) { _addPrefixRelativePtr(targetChunk, sizeof(T)); } /// Insert a new chunk into the blob, immediately after this chunk. /// ChunkBuilder* addChunkAfter(); private: ChunkBuilder() = delete; ChunkBuilder(ChunkBuilder const&) = delete; ChunkBuilder(ChunkBuilder&&) = delete; friend struct BlobBuilder; friend class ShardBuilder; ChunkBuilder(BlobBuilder* parentBlob); Size _contentSize = 0; Size _contentAlignment = 1; InternallyLinkedList _childShards; BlobBuilder* _parentBlob = nullptr; Size _cachedOffset = ~Size(0); Size _getCachedOffset() { return _cachedOffset; } void _setCachedOffset(Size offset) { _cachedOffset = offset; } void _writeRelativePtr(ChunkBuilder* targetChunk, Size ptrSize); ShardBuilder* _createDataShard(void const* data, Size size); ShardBuilder* _createRelativePtrShard(ChunkBuilder* targetChunk, Size ptrSize); ShardBuilder* _prefixShard = nullptr; void _writeTo(Stream* stream); void _addPrefixRelativePtr(ChunkBuilder* targetChunk, Size ptrSize); }; /// A shard is a unit of contiguously-allocated data that makes /// up part of a chunk. /// /// Shards are *not* meant to be directly manipulated by users; /// they are an implementation detail of `ChunkBuilder`. /// /// Every `ShardBuilder` is owned by its parent `ChunkBuilder`. /// A pointer to a `ShardBuilder` will only be valid during the /// lifetime of the parent `ChunkBuilder`. /// class ShardBuilder : public InternallyLinkedList::Node { public: // There are two kinds of shards that may appear in a chunk: // // * Shards that hold plain data that will be part of the // serialized chunk. // // * Shards that represent a relative pointer to some chunk, // which cannot have their exact binary value determined // until the offsets of chunk/shards have been finalized. enum class Kind { Data, RelativePtr, }; Size getSize() const { return _size; } private: ShardBuilder() = delete; ShardBuilder(ShardBuilder const&) = delete; ShardBuilder(ShardBuilder&&) = delete; friend class ChunkBuilder; ShardBuilder(Kind kind); void _writeTo(Stream* stream, Size selfOffset); /// Kind of this shard (data or relative pointer) Kind _kind = Kind::Data; union { /// Used when `_kind == Kind::Data` struct { void const* ptr; } _data; /// Used when `_kind == Kind::RelativePtr` struct { ChunkBuilder* targetChunk; } _relativePtr; }; // Size of this shard in bytes. Size _size = 0; }; } // namespace Slang #endif