summaryrefslogtreecommitdiffstats
path: root/source/core
diff options
context:
space:
mode:
Diffstat (limited to 'source/core')
-rw-r--r--source/core/slang-blob-builder.cpp473
-rw-r--r--source/core/slang-blob-builder.h352
-rw-r--r--source/core/slang-internally-linked-list.cpp2
-rw-r--r--source/core/slang-internally-linked-list.h126
-rw-r--r--source/core/slang-memory-arena.h11
-rw-r--r--source/core/slang-relative-ptr.cpp2
-rw-r--r--source/core/slang-relative-ptr.h97
-rw-r--r--source/core/slang-riff.h68
8 files changed, 1064 insertions, 67 deletions
diff --git a/source/core/slang-blob-builder.cpp b/source/core/slang-blob-builder.cpp
new file mode 100644
index 000000000..10dd4003e
--- /dev/null
+++ b/source/core/slang-blob-builder.cpp
@@ -0,0 +1,473 @@
+// slang-blob-builder.cpp
+#include "slang-blob-builder.h"
+
+namespace Slang
+{
+
+//
+// BlobBuilder
+//
+
+BlobBuilder::BlobBuilder()
+ : _arena(4096)
+{
+}
+
+void BlobBuilder::writeTo(Stream* stream)
+{
+ // First we scan through all the chunks to set their
+ // absolute offsets, which enables us to compute the
+ // correct values for relative pointers when we write
+ // them out.
+ //
+ SLANG_MAYBE_UNUSED
+ Size sizeComputed = _calcSizeAndSetCachedChunkOffsets();
+
+ // Now we can scan through the chunks again and write
+ // the bytes of each of their shards.
+ //
+ SLANG_MAYBE_UNUSED
+ Size sizeWritten = _writeChunksTo(stream);
+
+ SLANG_ASSERT(sizeComputed == sizeWritten);
+}
+
+Size BlobBuilder::_calcSizeAndSetCachedChunkOffsets()
+{
+ Size totalSize = 0;
+ for (auto chunk : _chunks)
+ {
+ auto chunkPrefixSize = chunk->getPrefixSize();
+ auto chunkContentSize = chunk->getContentSize();
+ auto chunkAlignment = chunk->getAlignment();
+
+ // We add the size of the chunk prefix (if any) *before*
+ // aligning the current offset. Doing it this way
+ // means that sometimes the prefix can fit "for free"
+ // in space that would otherwise be padding.
+ //
+ // For example, if the current `totalSize` is 4, the
+ // `chunkAlignment` is 16, and the `chunkPrefixSize` is 8,
+ // then the sequence will be:
+ //
+ // * Add `totalSize += chunkPrefixSize`, resulting in a `totalSize`
+ // of 12.
+ //
+ // * Round the `totalSize` up to the `chunkAlignment`, to compute
+ // a `chunkOffset` of 16.
+ //
+ // The result is that the chunk's content starts at offset 16,
+ // and the prefix can occupy the 8 bytes before that (so the
+ // prefix is at offset 8).
+ //
+ // In the best case this approach can save a few bytes here or
+ // there when `chunkAlignment` is larger than the `chunkPrefixSize`,
+ // and in the worst case it does no harm.
+
+ totalSize += chunkPrefixSize;
+ auto chunkOffset = roundUpToAlignment(totalSize, chunkAlignment);
+
+ chunk->_setCachedOffset(chunkOffset);
+
+ totalSize = chunkOffset + chunkContentSize;
+ }
+ return totalSize;
+}
+
+Size BlobBuilder::_writeChunksTo(Stream* stream)
+{
+ Size totalSize = 0;
+ for (auto chunk : _chunks)
+ {
+ auto chunkPrefixSize = chunk->getPrefixSize();
+ auto chunkContentSize = chunk->getContentSize();
+ auto chunkOffset = chunk->_getCachedOffset();
+
+ SLANG_ASSERT(
+ chunkOffset == roundUpToAlignment(totalSize + chunkPrefixSize, chunk->getAlignment()));
+
+ auto paddingSize = chunkOffset - totalSize;
+
+ // The "padding" before the chunk's content also
+ // includes the space reserved for the chunk's prefix.
+ //
+ // We can thus subtract the prefix size from the number
+ // of pad bytes to write.
+
+ SLANG_ASSERT(paddingSize >= chunkPrefixSize);
+ paddingSize -= chunkPrefixSize;
+
+ while (paddingSize--)
+ {
+ Byte padding = 0;
+ stream->write(&padding, sizeof(padding));
+ }
+
+ // The `ChunkBuilder::_writeTo()` call will write the
+ // prefix *and* the chunk content, so it is appropriate
+ // to call it here even when the total number of bytes
+ // written to the stream is not equal to the `chunkOffset`
+ // (because in that case the total bytes written so far
+ // should be `chunkOffset - chunkPrefixSize`).
+ //
+ chunk->_writeTo(stream);
+
+ totalSize = chunkOffset + chunkContentSize;
+ }
+
+ return totalSize;
+}
+
+void BlobBuilder::writeToBlob(ISlangBlob** outBlob)
+{
+ OwnedMemoryStream stream(FileAccess::Write);
+ writeTo(&stream);
+
+ List<uint8_t> data;
+ stream.swapContents(data);
+
+ *outBlob = ListBlob::moveCreate(data).detach();
+}
+
+ChunkBuilder* BlobBuilder::createUnparentedChunk()
+{
+ auto chunk = new (_arena) ChunkBuilder(this);
+ return chunk;
+}
+
+void BlobBuilder::addChunk(ChunkBuilder* chunk)
+{
+ // TODO(tfoley): it would be good to have a way to assert
+ // that the chunk has not already been added.
+
+ _chunks.add(chunk);
+}
+
+ChunkBuilder* BlobBuilder::addChunk()
+{
+ auto chunk = new (_arena) ChunkBuilder(this);
+ _chunks.add(chunk);
+ return chunk;
+}
+
+ChunkBuilder* BlobBuilder::addChunkAfter(ChunkBuilder* existingChunk)
+{
+ auto newChunk = new (_arena) ChunkBuilder(this);
+ _chunks.insertAfter(existingChunk, newChunk);
+ return newChunk;
+}
+
+//
+// ChunkBuilder
+//
+
+void ChunkBuilder::setAlignmentToAtLeast(Size alignment)
+{
+ SLANG_ASSERT(alignment > 0);
+ SLANG_ASSERT(isPowerOfTwo(alignment));
+
+ _contentAlignment = std::max(_contentAlignment, alignment);
+}
+
+void ChunkBuilder::writePaddingToAlignTo(Size alignment)
+{
+ setAlignmentToAtLeast(alignment);
+
+ auto alignedSize = roundUpToAlignment(_contentSize, alignment);
+
+ auto requiredPaddingSize = alignedSize - _contentSize;
+
+ while (requiredPaddingSize)
+ {
+ Byte padByte = 0;
+ writeData(&padByte, sizeof(padByte));
+ requiredPaddingSize -= sizeof(padByte);
+ }
+}
+
+ChunkBuilder::ChunkBuilder(BlobBuilder* parentBlob)
+ : _parentBlob(parentBlob)
+{
+}
+
+Size ChunkBuilder::getContentSize() const
+{
+ return _contentSize;
+}
+
+Size ChunkBuilder::getPrefixSize() const
+{
+ if (!_prefixShard)
+ return 0;
+
+ return _prefixShard->getSize();
+}
+
+Size ChunkBuilder::getAlignment() const
+{
+ return _contentAlignment;
+}
+
+
+void ChunkBuilder::writeData(void const* data, Size size)
+{
+ // Adding no data should be a no-op.
+ //
+ if (size == 0)
+ return;
+
+
+ // The most interesting implementation detail here
+ // is that we will try to detect cases where we
+ // can re-use an existing `ShardBuilder` by adding the data
+ // to the end of that shard's allocation.
+ //
+ // This is only possible because of the way that
+ // we are using a single `MemoryArena` to allocate
+ // everything, which makes it possible that the
+ // next address the arena would return for an allocation
+ // of `size` bytes is the same as the ending address
+ // of the payload for the last shard of this chunk.
+ //
+ auto& arena = getParentBlob()->_getArena();
+
+ // We start by checking if this chunk already has
+ // a last shard that we could consider appending to.
+ //
+ auto lastShard = _childShards.getLast();
+ if (lastShard && lastShard->_kind == ShardBuilder::Kind::Data)
+ {
+ // If there is a last shard, then we can compute
+ // the end address of its payload, and see if
+ // it is the same as the cursor of the arena
+ // we are allocating from.
+ //
+ auto payload = lastShard->_data.ptr;
+ auto payloadSize = lastShard->_size;
+ auto payloadEnd = (Byte*)payload + payloadSize;
+ if (payloadEnd == arena.getCursor())
+ {
+ // Now that we've confirmed that the shard's
+ // payload ends at an address the arena could
+ // conceivably allocate from, we need to ask
+ // the arena to allocate `size` bytes from
+ // the current block it is using, and see if
+ // doing so succeeds.
+ //
+ if (arena.allocateCurrentUnaligned(size))
+ {
+ // At this point, we've confirmed that we
+ // are in our special case, and the relevant
+ // bytes have been allocated from the arena.
+ //
+ // Now we can simply write the new data at
+ // what used to be the end address for the
+ // shard's payload, and adjust its state
+ // to account for the new allocation.
+ //
+ ::memcpy(payloadEnd, data, size);
+
+ lastShard->_size = payloadSize + size;
+ _contentSize += size;
+ return;
+ }
+ }
+ }
+
+ // If the special case above doesn't apply, we simply
+ // allocate a new shard to hold the data that was
+ // passed in.
+ //
+ auto shard = _createDataShard(data, size);
+ _childShards.add(shard);
+ _contentSize += size;
+}
+
+void ChunkBuilder::addContentsOf(ChunkBuilder* otherChunk)
+{
+ auto otherPrefixShard = otherChunk->_prefixShard;
+ auto otherChunkSize = otherChunk->getContentSize();
+ auto otherChunkAlignment = otherChunk->getAlignment();
+ auto otherChunkShards = otherChunk->_childShards;
+
+ otherChunk->_prefixShard = nullptr;
+ otherChunk->_contentSize = 0;
+ otherChunk->_contentAlignment = 1;
+ otherChunk->_childShards = InternallyLinkedList<ShardBuilder>();
+
+ if (otherPrefixShard)
+ {
+ // If the other chunk included a prefix, then
+ // it only makes sense to append it in the case
+ // where *this* chunk is completely empty.
+
+ SLANG_ASSERT(!_prefixShard);
+ SLANG_ASSERT(!_childShards.getFirst());
+ _prefixShard = otherPrefixShard;
+ }
+
+ writePaddingToAlignTo(otherChunkAlignment);
+ _childShards.append(otherChunkShards);
+ _contentSize += otherChunkSize;
+}
+
+ChunkBuilder* ChunkBuilder::addChunkAfter()
+{
+ return getParentBlob()->addChunkAfter(this);
+}
+
+void ChunkBuilder::_writeRelativePtr(ChunkBuilder* targetChunk, Size ptrSize)
+{
+ SLANG_ASSERT(ptrSize != 0);
+ SLANG_ASSERT(ptrSize <= sizeof(UInt64));
+
+ writePaddingToAlignTo(ptrSize);
+
+ if (!targetChunk)
+ {
+ UInt64 value = 0;
+ writeData(&value, ptrSize);
+ return;
+ }
+
+ auto shard = _createRelativePtrShard(targetChunk, ptrSize);
+ _childShards.add(shard);
+ _contentSize += ptrSize;
+}
+
+void ChunkBuilder::_addPrefixRelativePtr(ChunkBuilder* targetChunk, Size ptrSize)
+{
+ SLANG_ASSERT(targetChunk != nullptr);
+ SLANG_ASSERT(ptrSize != 0);
+
+ SLANG_ASSERT(!_prefixShard);
+
+ setAlignmentToAtLeast(ptrSize);
+
+ auto shard = _createRelativePtrShard(targetChunk, ptrSize);
+ _prefixShard = shard;
+}
+
+void ChunkBuilder::addPrefixData(void const* data, Size size)
+{
+ SLANG_ASSERT(data != nullptr);
+ SLANG_ASSERT(size != 0);
+
+ SLANG_ASSERT(!_prefixShard);
+
+ setAlignmentToAtLeast(size);
+
+ auto shard = _createDataShard(data, size);
+ _prefixShard = shard;
+}
+
+ShardBuilder* ChunkBuilder::_createDataShard(void const* data, Size size)
+{
+ auto& arena = getParentBlob()->_getArena();
+ auto shard = new (arena) ShardBuilder(ShardBuilder::Kind::Data);
+
+ auto shardData = arena.allocateUnaligned(size);
+ ::memcpy(shardData, data, size);
+
+ shard->_data.ptr = shardData;
+ shard->_size = size;
+
+ return shard;
+}
+
+ShardBuilder* ChunkBuilder::_createRelativePtrShard(ChunkBuilder* targetChunk, Size ptrSize)
+{
+ auto& arena = getParentBlob()->_getArena();
+ auto shard = new (arena) ShardBuilder(ShardBuilder::Kind::RelativePtr);
+
+ shard->_relativePtr.targetChunk = targetChunk;
+ shard->_size = ptrSize;
+
+ return shard;
+}
+
+void ChunkBuilder::_writeTo(Stream* stream)
+{
+ auto chunkOffset = _getCachedOffset();
+
+ if (_prefixShard)
+ {
+ // Note that the prefix is written *before* the
+ // starting offset of the chunk's content, so we
+ // compute the appropriate offset to pass down.
+ //
+ auto prefixSize = _prefixShard->getSize();
+ auto prefixOffset = chunkOffset - prefixSize;
+
+ _prefixShard->_writeTo(stream, prefixOffset);
+ }
+
+ auto shardOffset = chunkOffset;
+ for (auto shard : _childShards)
+ {
+ shard->_writeTo(stream, shardOffset);
+ shardOffset += shard->getSize();
+ }
+ SLANG_ASSERT(shardOffset == chunkOffset + getContentSize());
+}
+
+
+//
+// ShardBuilder
+//
+
+ShardBuilder::ShardBuilder(Kind kind)
+ : _kind(kind)
+{
+}
+
+void ShardBuilder::_writeTo(Stream* stream, Size inSelfOffset)
+{
+ switch (_kind)
+ {
+ case Kind::Data:
+ {
+ stream->write(_data.ptr, _size);
+ }
+ break;
+
+ case Kind::RelativePtr:
+ {
+ auto targetChunk = _relativePtr.targetChunk;
+ SLANG_ASSERT(targetChunk);
+
+ auto selfOffset = intptr_t(inSelfOffset);
+ auto targetOffset = intptr_t(targetChunk->_getCachedOffset());
+
+ intptr_t relativeOffset = targetOffset - selfOffset;
+
+ switch (_size)
+ {
+ case sizeof(Int32):
+ {
+ auto value = Int32(relativeOffset);
+ stream->write(&value, sizeof(value));
+ }
+ break;
+
+ case sizeof(Int64):
+ {
+ auto value = Int64(relativeOffset);
+ stream->write(&value, sizeof(value));
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unsupported relative pointer size");
+ break;
+ }
+ }
+ break;
+
+ default:
+ SLANG_UNEXPECTED("unknown Fossil::ShardBuilder::Kind");
+ break;
+ }
+}
+
+} // namespace Slang
diff --git a/source/core/slang-blob-builder.h b/source/core/slang-blob-builder.h
new file mode 100644
index 000000000..0b59ee7ab
--- /dev/null
+++ b/source/core/slang-blob-builder.h
@@ -0,0 +1,352 @@
+// 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<ChunkBuilder> _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<ChunkBuilder>::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<typename T>
+ 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<typename T>
+ 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<ShardBuilder> _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<ShardBuilder>::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
diff --git a/source/core/slang-internally-linked-list.cpp b/source/core/slang-internally-linked-list.cpp
new file mode 100644
index 000000000..27bbd77b0
--- /dev/null
+++ b/source/core/slang-internally-linked-list.cpp
@@ -0,0 +1,2 @@
+// slang-internally-linked-list.cpp
+#include "slang-internally-linked-list.h"
diff --git a/source/core/slang-internally-linked-list.h b/source/core/slang-internally-linked-list.h
new file mode 100644
index 000000000..168885531
--- /dev/null
+++ b/source/core/slang-internally-linked-list.h
@@ -0,0 +1,126 @@
+// slang-internally-linked-list.h
+#ifndef SLANG_INTERNALLY_LINKED_LIST_H
+#define SLANG_INTERNALLY_LINKED_LIST_H
+
+// This file provides support for the idiom of a linked
+// list of values where the "next" pointer is stored in
+// the values themselves (thus requiring no additional
+// allocation for list nodes, at the price of any given
+// value only being able to appear in a single list).
+
+#include "slang-basic.h"
+
+namespace Slang
+{
+
+/// A linked list where the elements are themselves the nodes.
+///
+/// The type parameter `T` should be a type that publicly
+/// inherits from `InternallyLinkedList<T>::Node`.
+///
+template<typename T>
+struct InternallyLinkedList
+{
+public:
+ struct Node
+ {
+ public:
+ Node() {}
+
+ private:
+ friend struct InternallyLinkedList<T>;
+ T* _next = nullptr;
+ };
+
+ struct Iterator
+ {
+ public:
+ Iterator() {}
+
+ Iterator(T* node)
+ : _node(node)
+ {
+ }
+
+ T* operator*() const { return _node; }
+
+ void operator++() { _node = static_cast<Node const*>(_node)->_next; }
+
+ bool operator!=(Iterator const& that) const { return _node != that._node; }
+
+ private:
+ T* _node = nullptr;
+ };
+
+ Iterator begin() { return Iterator(_first); }
+
+ Iterator end() { return Iterator(); }
+
+ T* getFirst() const { return _first; }
+
+ T* getLast() const { return _last; }
+
+ void add(T* element)
+ {
+ SLANG_ASSERT(element != nullptr);
+ if (!_last)
+ {
+ SLANG_ASSERT(_first == nullptr);
+
+ _first = element;
+ _last = element;
+ }
+ else
+ {
+ SLANG_ASSERT(_first != nullptr);
+
+ _last->_next = element;
+ _last = element;
+ }
+ }
+
+ void insertAfter(T* existingElement, T* newElement)
+ {
+ SLANG_ASSERT(existingElement != nullptr);
+ SLANG_ASSERT(newElement != nullptr);
+ if (existingElement == _last)
+ {
+ add(newElement);
+ }
+ else
+ {
+ newElement->_next = existingElement->_next;
+ existingElement->_next = newElement;
+ }
+ }
+
+ void append(InternallyLinkedList<T> const& other)
+ {
+ if (!other._first)
+ {
+ }
+ else if (!_last)
+ {
+ _first = other._first;
+ _last = other._last;
+ }
+ else
+ {
+ SLANG_ASSERT(_first != nullptr);
+
+ _last->_next = other._first;
+ _last = other._last;
+ }
+ }
+
+private:
+ T* _first = nullptr;
+ T* _last = nullptr;
+};
+
+template<typename T>
+using InternallyLinkedListNode = InternallyLinkedList<T>::Node;
+
+} // namespace Slang
+
+#endif
diff --git a/source/core/slang-memory-arena.h b/source/core/slang-memory-arena.h
index 03631befe..714918b9a 100644
--- a/source/core/slang-memory-arena.h
+++ b/source/core/slang-memory-arena.h
@@ -476,4 +476,15 @@ SLANG_FORCE_INLINE void operator delete(void* memory, Slang::MemoryArena& arena)
SLANG_UNUSED(arena);
}
+SLANG_FORCE_INLINE void* operator new[](size_t size, Slang::MemoryArena& arena)
+{
+ return arena.allocate(size);
+}
+
+SLANG_FORCE_INLINE void operator delete[](void* memory, Slang::MemoryArena& arena)
+{
+ SLANG_UNUSED(memory);
+ SLANG_UNUSED(arena);
+}
+
#endif // SLANG_MEMORY_ARENA_H
diff --git a/source/core/slang-relative-ptr.cpp b/source/core/slang-relative-ptr.cpp
new file mode 100644
index 000000000..0c4da2c6e
--- /dev/null
+++ b/source/core/slang-relative-ptr.cpp
@@ -0,0 +1,2 @@
+// slang-relative-ptr.cpp
+#include "slang-relative-ptr.h"
diff --git a/source/core/slang-relative-ptr.h b/source/core/slang-relative-ptr.h
new file mode 100644
index 000000000..6c4bc942c
--- /dev/null
+++ b/source/core/slang-relative-ptr.h
@@ -0,0 +1,97 @@
+// slang-relative-ptr.h
+#ifndef SLANG_RELATIVE_PTR_H
+#define SLANG_RELATIVE_PTR_H
+
+// This file implements a smart pointer type `RelativePtr<T>`
+// that, rather than storing the actual *address* of a value
+// of type `T`, stores the relative offset (in bytes) between
+// the target `T*` and the address of the `RelativePtr<T>`
+// itself.
+//
+// This kind of pointer representation can be useful when
+// implementing "memory-mappable" data structures that can
+// still conveniently represent complicated object graphs.
+
+#include "slang-basic.h"
+
+namespace Slang
+{
+namespace detail
+{
+struct RelativePtr32Traits
+{
+ using Offset = Int32;
+ using UOffset = UInt32;
+};
+
+struct RelativePtr64Traits
+{
+ using Offset = Int64;
+ using UOffset = UInt64;
+};
+} // namespace detail
+
+template<typename T, typename Traits>
+struct RelativePtr
+{
+public:
+ using This = RelativePtr<T, Traits>;
+ using Value = T;
+ using RawPtr = T*;
+ using Offset = typename Traits::Offset;
+ using UOffset = typename Traits::UOffset;
+
+ RelativePtr() = default;
+ RelativePtr(RelativePtr const& ptr) { set(ptr); }
+ RelativePtr(RelativePtr&& ptr) { set(ptr); }
+ RelativePtr(T* ptr) { set(ptr); }
+
+ void operator=(RelativePtr const& ptr) { set(ptr); }
+ void operator=(RelativePtr&& ptr) { set(ptr); }
+ void operator=(T* ptr) { set(ptr); }
+
+ T* get() const
+ {
+ if (_offset == 0)
+ {
+ return nullptr;
+ }
+
+ intptr_t thisAddr = intptr_t(this);
+ intptr_t targetAddr = thisAddr + intptr_t(_offset);
+
+ return (T*)(targetAddr);
+ }
+
+ void set(T* ptr)
+ {
+ if (ptr == nullptr)
+ {
+ _offset = 0;
+ return;
+ }
+
+ intptr_t thisAddr = intptr_t(this);
+ intptr_t targetAddr = intptr_t(ptr);
+ intptr_t offsetVal = targetAddr - thisAddr;
+
+ _offset = Offset(offsetVal);
+ SLANG_ASSERT(intptr_t(_offset) == offsetVal);
+ }
+
+ operator T*() const { return get(); }
+ T* operator->() const { return get(); }
+
+private:
+ Offset _offset = 0;
+};
+
+template<typename T>
+using RelativePtr32 = RelativePtr<T, detail::RelativePtr32Traits>;
+
+template<typename T>
+using RelativePtr64 = RelativePtr<T, detail::RelativePtr64Traits>;
+
+} // namespace Slang
+
+#endif
diff --git a/source/core/slang-riff.h b/source/core/slang-riff.h
index 20b1d7c66..9459c8a55 100644
--- a/source/core/slang-riff.h
+++ b/source/core/slang-riff.h
@@ -16,6 +16,7 @@
//
#include "slang-basic.h"
+#include "slang-internally-linked-list.h"
#include "slang-memory-arena.h"
#include "slang-stream.h"
#include "slang-writer.h"
@@ -564,73 +565,6 @@ T const* as(Chunk const* chunk)
return static_cast<T const*>(chunk);
}
-/// A linked list where the elements are themselves the nodes.
-///
-template<typename T>
-struct InternallyLinkedList
-{
-public:
- struct Node
- {
- public:
- Node() {}
-
- private:
- friend struct InternallyLinkedList<T>;
- T* _next = nullptr;
- };
-
- struct Iterator
- {
- public:
- Iterator() {}
-
- Iterator(T* node)
- : _node(node)
- {
- }
-
- T* operator*() const { return _node; }
-
- void operator++() { _node = static_cast<Node const*>(_node)->_next; }
-
- bool operator!=(Iterator const& that) const { return _node != that._node; }
-
- private:
- T* _node = nullptr;
- };
-
- Iterator begin() { return Iterator(_first); }
-
- Iterator end() { return Iterator(); }
-
- T* getFirst() const { return _first; }
-
- T* getLast() const { return _last; }
-
- void add(T* element)
- {
- if (!_last)
- {
- SLANG_ASSERT(_first == nullptr);
-
- _first = element;
- _last = element;
- }
- else
- {
- SLANG_ASSERT(_first != nullptr);
-
- _last->_next = element;
- _last = element;
- }
- }
-
-private:
- T* _first = nullptr;
- T* _last = nullptr;
-};
-
/// A builder for a chunk in a RIFF.
class ChunkBuilder : public InternallyLinkedList<ChunkBuilder>::Node
{