summaryrefslogtreecommitdiff
path: root/source/slang/slang-serialize-fossil.cpp
diff options
context:
space:
mode:
authorTheresa Foley <10618364+tangent-vector@users.noreply.github.com>2025-08-06 19:30:57 -0700
committerGitHub <noreply@github.com>2025-08-07 02:30:57 +0000
commit631a4c37a597fab7599abc472f25d671bfe37751 (patch)
tree799e3cc30021238cea6d6738338876b154b0f5e3 /source/slang/slang-serialize-fossil.cpp
parentb9aad6649e3ba0186e17fc035d4b56f8e6169b59 (diff)
Improve performance of AST deserialization (#7935)
* Improve performance of AST deserialization The primary goal of these changes is to reduce the total time spent in the global session's `loadBuiltinModule()`, which gets called as part of global session creation to load the core module, and thus impacts every invocation of `slangc` and every user of the Slang compiler API. The majority of the time is spent simply deserializing the core module's AST and IR and, of those two, the AST takes significantly longer to load than the IR (in the ballpark of 5x the time). This change is focused on the serialization infrastructure but, given the performance situation described above, the focus is first and foremost on *deserialization* performance for the Slang *AST*, when using the *fossil* format. That focus shows through in the changes that have been implemented. Change serialization framework to use `template` instead of `virtual` ===================================================================== The recently-introduced serialization framework in `slang-serialize.h` was centered around a dynamically-dispatched `ISerializerImpl` interface. As a result, every single invocation of a `serialize(...)` call ultimately went through `virtual` function dispatch. While the overhead of the `virtual` calls themselves does not have a major impact on the total deserialization performance, those calls end up serving as a barrier to further optimization. This change changes operations that used to take a `Serializer const&` (which wraps an `ISerializerImpl*`), to instead declare a template parameter `<typename S>` and take an `S const&`. The main consequence of the change is that `serialize()` functions for user-defined types will need to be template functions, and thus either be defined in headers (alongside the type that they serialize) or else in the specific source file that handles serialization (as is currently being done for the AST-related types in `slang-serialize-ast.cpp`). Note that if we later decide that we want the ability to perform serialization through a dynamically-dispatched interface (e.g., to easily toggle between different serialization back-ends), it will be easier to layer a dynamically-dispatched implementation on top of the statically-dispatched `template` version than the other way around. Generous use of `SLANG_FORCE_INLINE` ==================================== In order to unlock further optimizations, a bunch of operations were marked with `SLANG_FORCE_INLINE`. It is important to note that forcing inlining like this is a big hammer, and needs to be approached with at least a little caution. The simplest cases are: * trivial wrapper function that just delegate to another function * functions that only have a single call site (but exist to keep abstractions clean) Externalize Scope for `begin`/`end` Operations ============================================== The old `ISerializerImpl` interface had a bunch of paired begin/end operations that define the hierarchical structure of data being read. Most serializer implementations (whether for reading or writing) use these operations to help maintain some kind of internal stack for tracking state in the hierarchy. The overhead of maintaining such a stack with something like a `List<T>` amortizes out over many operations, but even that overhead is unnecessary when the begin/end pairs are *already* mirroring the call stack of the code invoking serialization. This change modifies the `ScopedSerializerFoo` types so that they each provide a piece of stack-allocated storage to the serializer back-end's `beginFoo()` and `endFoo()` operations. Currently only the `Fossil::SerialReader` is making use of that facility, but the other implementations of readers and writers in the codebase could be adapted if we ever wanted to. Streamline `Fossil::SerialReader` ================================= The most significant performance gains came from changes to the `Fossil::SerialReader` type, aimed at minimizing the cycles spent in the core `_readValPtr()` routine. That function used to have a large-ish `switch` statement that implemented superficially very different reading logic depending on the outer container/object being read from. The new logic pushes more work back on the `begin` and `end` operations (which get invoked far less frequently than simple scalar/pointer values get read), so that they always set up the state of the reader with direct pointers to the data and layout for the next fossilized value to be read. The remaining work in `_readValPtr()` has been factored into a differnt subroutine - `_advanceCursor()` - that takes responsibility for advancing the data pointer, and updating the various other fields. The `_advanceCursor()` routine is still messier than is ideal, because it has to deal with the various different kinds of logic required for navigating to the next value. Various other conditionals inside the `SerialReader` implementation were streamlined, mostly by collapsing the `State::Type` enumeration down to only represent the cases that are truly semantically distinct. Evaluated: Streamline Layout Rules for Fossil ============================================= One potential approach that I implemented but then reverted (after finding it had little to no performance impact) was changing the fossil format to always write things with 4-byte alignment/granularity. That would mean values smaller than 4 bytes would get inflated to a full 4 bytes, and scalar values larger than 4 bytes get written with only 4-byte alignment (requiring unaligned loads to read them). I found that the only way to take advantage of the simplified layout rules to improve read performance would be to more-or-less eliminate the use of the layout information embedded in the fossil data, which would make it very difficult to validate that the data is correctly structured. Possible Future Work: Further Type Specialization ================================================= As it stands, the biggest overhead remaining on the critical path of `_readValPtr()` is the way the `_advanceCursor()` logic needs to take different approaches depending on the type of the surrounding context (advancing through elements of a container is very different than advancing through fields of a `struct`, for example). The interesting thing to note is that at the use site within a `serialize()` function, it is usually manifestly obvious which case something is in. If the code uses `SLANG_SCOPED_SERIALIZER_ARRAY` it is in a container, while if it uses `SLANG_SCOPED_SERIALIZER_STRUCT` it is in a struct. This means that the contextual information is staticaly available, but just isn't exposed in a way that lets the core reading logic take advantage of it. A logical extension of the work here would be to expand on the `Scope` idea added in this change such that most of the serialization operations (`handleInt32`, `handleString`, etc.) are actually dispatched through the scope, and then have each of the `SLANG_SCOPED_SERIALIZER_...` macros instantiate a *different* scope type (still dependent on the serializer). * fixup * format code * typo --------- Co-authored-by: slangbot <186143334+slangbot@users.noreply.github.com>
Diffstat (limited to 'source/slang/slang-serialize-fossil.cpp')
-rw-r--r--source/slang/slang-serialize-fossil.cpp532
1 files changed, 244 insertions, 288 deletions
diff --git a/source/slang/slang-serialize-fossil.cpp b/source/slang/slang-serialize-fossil.cpp
index 7ce09d283..3a3d8c00f 100644
--- a/source/slang/slang-serialize-fossil.cpp
+++ b/source/slang/slang-serialize-fossil.cpp
@@ -2,6 +2,7 @@
#include "slang-serialize-fossil.h"
#include "../core/slang-blob.h"
+#include "core/slang-performance-profiler.h"
namespace Slang
{
@@ -142,16 +143,17 @@ void SerialWriter::handleString(String& value)
auto size = value.getLength();
if (_shouldEmitPotentiallyIndirectValueWithPointerIndirection())
{
- if (size == 0)
- {
- _writeNull();
- return;
- }
-
- if (auto found = _mapStringToChunk.tryGetValue(value))
+ ChunkBuilder* existingChunk = nullptr;
+ _mapStringToChunk.tryGetValue(value, existingChunk);
+
+ // If we found an existing chunk that holds the string
+ // value in question, we can re-use it. Also, if the
+ // string is empty, we can encode it as a null pointer
+ // in this case, so we don't care if we found a chunk
+ // or not.
+ //
+ if (existingChunk || size == 0)
{
- auto existingChunk = *found;
-
auto ptrLayout =
(ContainerLayoutObj*)_reserveDestinationForWrite(FossilizedValKind::Ptr);
_mergeLayout(ptrLayout->baseLayout, FossilizedValKind::StringObj);
@@ -174,22 +176,22 @@ void SerialWriter::handleString(String& value)
_mapStringToChunk.addIfNotExists(value, chunk);
}
-void SerialWriter::beginArray()
+void SerialWriter::beginArray(Scope&)
{
_pushContainerScope(FossilizedValKind::ArrayObj);
}
-void SerialWriter::endArray()
+void SerialWriter::endArray(Scope&)
{
_popContainerScope();
}
-void SerialWriter::beginDictionary()
+void SerialWriter::beginDictionary(Scope&)
{
_pushContainerScope(FossilizedValKind::DictionaryObj);
}
-void SerialWriter::endDictionary()
+void SerialWriter::endDictionary(Scope&)
{
_popContainerScope();
}
@@ -216,23 +218,23 @@ bool SerialWriter::hasElements()
return false;
}
-void SerialWriter::beginStruct()
+void SerialWriter::beginStruct(Scope&)
{
_pushInlineValueScope(FossilizedValKind::Struct);
}
-void SerialWriter::endStruct()
+void SerialWriter::endStruct(Scope&)
{
_popInlineValueScope();
}
-void SerialWriter::beginVariant()
+void SerialWriter::beginVariant(Scope&)
{
_pushVariantScope();
_pushInlineValueScope(FossilizedValKind::Struct);
}
-void SerialWriter::endVariant()
+void SerialWriter::endVariant(Scope&)
{
_popInlineValueScope();
_popVariantScope();
@@ -246,27 +248,27 @@ void SerialWriter::handleFieldKey(char const* name, Int index)
SLANG_UNUSED(index);
}
-void SerialWriter::beginTuple()
+void SerialWriter::beginTuple(Scope&)
{
_pushInlineValueScope(FossilizedValKind::Tuple);
}
-void SerialWriter::endTuple()
+void SerialWriter::endTuple(Scope&)
{
_popInlineValueScope();
}
-void SerialWriter::beginOptional()
+void SerialWriter::beginOptional(Scope&)
{
_pushIndirectValueScope(FossilizedValKind::OptionalObj);
}
-void SerialWriter::endOptional()
+void SerialWriter::endOptional(Scope&)
{
_popIndirectValueScope();
}
-void SerialWriter::handleSharedPtr(void*& value, Callback callback, void* context)
+void SerialWriter::handleSharedPtr(void*& value, SerializerCallback callback, void* context)
{
// Because we are writing, we only care about the
// pointer that is already present in `value`.
@@ -313,7 +315,7 @@ void SerialWriter::handleSharedPtr(void*& value, Callback callback, void* contex
_commitWrite(ValInfo::relativePtrTo(chunk));
}
-void SerialWriter::handleUniquePtr(void*& value, Callback callback, void* context)
+void SerialWriter::handleUniquePtr(void*& value, SerializerCallback callback, void* context)
{
// We treat all pointers as shared pointers, because there isn't really
// an optimized representation we would want to use for the unique case.
@@ -321,7 +323,10 @@ void SerialWriter::handleUniquePtr(void*& value, Callback callback, void* contex
handleSharedPtr(value, callback, context);
}
-void SerialWriter::handleDeferredObjectContents(void* valuePtr, Callback callback, void* context)
+void SerialWriter::handleDeferredObjectContents(
+ void* valuePtr,
+ SerializerCallback callback,
+ void* context)
{
// Because we are already deferring writing of the *entirety* of
// an object's members as part of how `handleSharedPtr()` works,
@@ -1152,7 +1157,7 @@ SerialReader::SerialReader(
switch (initialState)
{
case InitialStateType::Root:
- _state.type = State::Type::Root;
+ _state.type = State::Type::Object;
break;
case InitialStateType::PseudoPtr:
@@ -1160,9 +1165,9 @@ SerialReader::SerialReader(
break;
}
- _state.baseValPtr = valPtr;
- _state.elementIndex = 0;
- _state.elementCount = 1;
+ _state.dataCursor = valPtr.getDataPtr();
+ _state.layoutCursor = valPtr.getLayout();
+ _state.remainingValueCount = 1;
}
SerialReader::~SerialReader()
@@ -1207,149 +1212,12 @@ SerialReader::~SerialReader()
_context._readerCount--;
}
-Fossil::AnyValPtr SerialReader::readValPtr()
-{
- return _readValPtr();
-}
-
void SerialReader::flush()
{
_flush();
}
-SerializationMode SerialReader::getMode()
-{
- return SerializationMode::Read;
-}
-
-void SerialReader::handleBool(bool& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleInt8(int8_t& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleInt16(int16_t& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleInt32(Int32& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleInt64(Int64& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleUInt8(uint8_t& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleUInt16(uint16_t& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleUInt32(UInt32& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleUInt64(UInt64& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleFloat32(float& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleFloat64(double& value)
-{
- handleSimpleVal(value);
-}
-
-void SerialReader::handleString(String& value)
-{
- auto valPtr = _readPotentiallyIndirectValPtr();
- if (!valPtr)
- {
- value = String();
- }
- else
- {
- value = as<FossilizedStringObj>(valPtr)->get();
- }
-}
-
-void SerialReader::beginArray()
-{
- auto valPtr = _readPotentiallyIndirectValPtr();
- auto arrayPtr = as<FossilizedArrayObjBase>(valPtr);
-
- _pushState();
-
- _state.type = State::Type::Array;
- _state.baseValPtr = arrayPtr;
- _state.elementIndex = 0;
- _state.elementCount = arrayPtr->getElementCount();
-}
-
-void SerialReader::endArray()
-{
- _popState();
-}
-
-void SerialReader::beginDictionary()
-{
- auto valPtr = _readPotentiallyIndirectValPtr();
- auto dictionaryPtr = as<FossilizedDictionaryObjBase>(valPtr);
-
- _pushState();
-
- _state.type = State::Type::Dictionary;
- _state.baseValPtr = dictionaryPtr;
- _state.elementIndex = 0;
- _state.elementCount = dictionaryPtr->getElementCount();
-}
-
-void SerialReader::endDictionary()
-{
- _popState();
-}
-
-bool SerialReader::hasElements()
-{
- return _state.elementIndex < _state.elementCount;
-}
-
-void SerialReader::beginStruct()
-{
- auto valPtr = _readValPtr();
- auto recordPtr = as<FossilizedRecordVal>(valPtr);
-
- _pushState();
-
- _state.type = State::Type::Struct;
- _state.baseValPtr = valPtr;
- _state.elementIndex = 0;
- _state.elementCount = recordPtr->getFieldCount();
-}
-
-void SerialReader::endStruct()
-{
- _popState();
-}
-
-void SerialReader::beginVariant()
+void SerialReader::beginVariant(Scope& scope)
{
auto valPtr = _readPotentiallyIndirectValPtr();
if (auto variantPtr = as<FossilizedVariantObj>(valPtr))
@@ -1357,78 +1225,28 @@ void SerialReader::beginVariant()
auto contentValPtr = getVariantContentPtr(variantPtr);
valPtr = contentValPtr;
}
- auto recordPtr = as<FossilizedRecordVal>(valPtr);
-
- _pushState();
-
- _state.type = State::Type::Struct;
- _state.baseValPtr = recordPtr;
- _state.elementIndex = 0;
- _state.elementCount = recordPtr->getFieldCount();
-}
-
-void SerialReader::endVariant()
-{
- _popState();
-}
-
-void SerialReader::handleFieldKey(char const* name, Int index)
-{
- // For now we are ignoring field keys, and treating
- // structs as basically equivalent to tuples.
- SLANG_UNUSED(name);
- SLANG_UNUSED(index);
-}
-
-void SerialReader::beginTuple()
-{
- auto valPtr = _readValPtr();
- auto recordPtr = as<FossilizedRecordVal>(valPtr);
-
- _pushState();
-
- _state.type = State::Type::Tuple;
- _state.baseValPtr = recordPtr;
- _state.elementIndex = 0;
- _state.elementCount = recordPtr->getFieldCount();
-}
-
-void SerialReader::endTuple()
-{
- _popState();
-}
-
-void SerialReader::beginOptional()
-{
- auto valPtr = _readIndirectValPtr();
- auto optionalPtr = as<FossilizedOptionalObjBase>(valPtr);
-
- _pushState();
+ auto recordPtr = expectNonNullValOfType<FossilizedRecordVal>(valPtr);
- _state.type = State::Type::Optional;
- _state.baseValPtr = optionalPtr;
- _state.elementIndex = 0;
- _state.elementCount = Count(optionalPtr->hasValue());
+ _pushRecordState(scope, recordPtr);
}
-void SerialReader::endOptional()
+void SerialReader::handleSharedPtr(void*& value, SerializerCallback callback, void* context)
{
- _popState();
-}
+ Fossil::AnyValPtr valPtr = _readValPtr();
-void SerialReader::handleSharedPtr(void*& value, Callback callback, void* context)
-{
Fossil::AnyValPtr targetValPtr;
-
if (_state.type == State::Type::PseudoPtr)
{
- _state.type = State::Type::Root;
- targetValPtr = _readValPtr();
+ // TODO(tfoley): Having to include the `PseudoPtr` case here
+ // is frustrating, because it was only introduced to deal
+ // with a wrinkle related to on-demand AST deserialization,
+ // and ideally shouldn't be needed at all.
+
+ targetValPtr = valPtr;
}
else
{
- auto valPtr = _readValPtr();
- auto ptrPtr = as<FossilizedPtr<void>>(valPtr);
+ auto ptrPtr = expectNonNullValOfType<FossilizedPtr<void>>(valPtr);
targetValPtr = ptrPtr->getTargetValPtr();
}
@@ -1521,11 +1339,12 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* contex
// reading whatever comes after the pointer
// we were invoked to read.
//
- _pushState();
+ Scope callbackScope;
+ _pushState(callbackScope);
_state.type = State::Type::Object;
- _state.baseValPtr = objectInfo->fossilizedObjectPtr;
- _state.elementIndex = 0;
- _state.elementCount = 1;
+ _state.dataCursor = targetValPtr.getDataPtr();
+ _state.layoutCursor = targetValPtr.getLayout();
+ _state.remainingValueCount = 1;
// Note that we are passing the address of `objectInfo.ptr`,
// and `objectInfo` is a reference to an element of the
@@ -1543,22 +1362,17 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* contex
//
callback(&objectInfo->resurrectedObjectPtr, this, context);
- _popState();
+ _popState(callbackScope);
objectInfo->state = ObjectState::ReadingComplete;
value = objectInfo->resurrectedObjectPtr;
}
-void SerialReader::handleUniquePtr(void*& value, Callback callback, void* context)
-{
- // We treat all pointers as shared pointers, because there isn't really
- // an optimized representation we would want to use for the unique case.
- //
- handleSharedPtr(value, callback, context);
-}
-
-void SerialReader::handleDeferredObjectContents(void* valuePtr, Callback callback, void* context)
+void SerialReader::handleDeferredObjectContents(
+ void* valuePtr,
+ SerializerCallback callback,
+ void* context)
{
// Unlike the case in `SerialWriter::handleDeferredObjectContents()`,
// we very much *do* want to delay invoking the callback until later.
@@ -1604,57 +1418,100 @@ void SerialReader::_flush()
}
}
-Fossil::AnyValPtr SerialReader::_readValPtr()
+void SerialReader::_advanceCursor()
{
- switch (_state.type)
+ auto dataPtr = _state.dataCursor;
+
+ // The state also tracks the number of values that
+ // can still be read in the current scope. This value
+ // is used both to drive the loop when an application
+ // is reading a collection, and as a validation check
+ // here.
+ //
+ auto remainingValueCount = _state.remainingValueCount;
+ SLANG_SERIALIZE_FOSSIL_VALIDATE(remainingValueCount > 0);
+
+ // At minimum, we need to update the state to reflect
+ // the new number of values that remain.
+ //
+ remainingValueCount--;
+ _state.remainingValueCount = remainingValueCount;
+
+ // At this point, there is no need to update the
+ // state further, unless values remain. While
+ // we expect the "no values remain" case to be
+ // the less common one, we still guard all of
+ // this logic because we already need to branch
+ // to handle the case of record fields.
+ //
+ if (remainingValueCount != 0)
{
- case State::Type::Root:
- case State::Type::Object:
- case State::Type::PseudoPtr:
- SLANG_ASSERT(_state.elementCount == 1);
- SLANG_ASSERT(_state.elementIndex == 0);
- _state.elementIndex++;
- return _state.baseValPtr;
-
- case State::Type::Struct:
- case State::Type::Tuple:
+ // The primary task in this case is to update
+ // the data pointer to point to the next value
+ // in the stream.
+ //
+ auto nextDataPtr = (char*)dataPtr;
+
+ // If we are reading from a record (struct or tuple)
+ // then we will have an additional piece of state
+ // indicating the field that the data cursor was
+ // pointing at.
+ //
+ if (auto fieldInfoPtr = _state.fieldCursor)
{
- SLANG_ASSERT(_state.elementIndex < _state.elementCount);
- auto index = _state.elementIndex++;
+ // We know, because `remainingValueCount` is non-zero,
+ // that there is at least one more field after this
+ // one, so we can increment the `fieldInfoPtr` to
+ // get a pointer to the next field layout in the
+ // record layout.
+ //
+ auto nextFieldInfoPtr = fieldInfoPtr + 1;
- auto recordPtr = as<FossilizedRecordVal>(_state.baseValPtr);
- return getAddress(recordPtr->getField(index));
- }
+ // The layout information for a record type stores
+ // the offset of each field, so we can compute
+ // the relative offset between two fields as the
+ // difference between their offsets in the layout.
+ //
+ auto offsetToNextField = nextFieldInfoPtr->offset - fieldInfoPtr->offset;
- case State::Type::Optional:
- {
- SLANG_ASSERT(_state.elementCount == 1);
- SLANG_ASSERT(_state.elementIndex == 0);
+ // Adding that relative offset to the data pointer
+ // will navigate us to the next field.
+ //
+ nextDataPtr += offsetToNextField;
+ _state.fieldCursor = nextFieldInfoPtr;
- auto optionalPtr = as<FossilizedOptionalObjBase>(_state.baseValPtr);
- return getAddress(optionalPtr->getValue());
- }
+ // The info for the next field stores a (relative)
+ // pointer to its data layout, so we can write
+ // that into our state so that it is available
+ // for the next read operation.
- case State::Type::Array:
- case State::Type::Dictionary:
+ _state.layoutCursor = nextFieldInfoPtr->layout;
+ }
+ else
{
- SLANG_ASSERT(_state.elementIndex < _state.elementCount);
- auto index = _state.elementIndex++;
-
- auto containerPtr = as<FossilizedContainerObjBase>(_state.baseValPtr);
- return Fossil::ValPtr(containerPtr->getElement(index));
+ // The other case where more than one value can
+ // be read in the same state is when a collection
+ // is being read. In that case the setup logic
+ // will have already stored the stride between
+ // elements into the state, and we can use that
+ // to increment the data pointer.
+ //
+ // (In the container case the layout pointer doesn't
+ // need to change, since all of the elements are
+ // required to have the same layout).
+ //
+ auto dataStride = _state.dataStride;
+ nextDataPtr += dataStride;
}
- default:
- SLANG_UNEXPECTED("unhandled case");
- break;
+ _state.dataCursor = nextDataPtr;
}
}
-Fossil::AnyValPtr SerialReader::_readIndirectValPtr()
+SLANG_FORCE_INLINE Fossil::AnyValPtr SerialReader::_readIndirectValPtr()
{
auto baseValPtr = _readValPtr();
- auto basePtrPtr = as<FossilizedPtr<void>>(baseValPtr);
+ auto basePtrPtr = expectNonNullValOfType<FossilizedPtr<void>>(baseValPtr);
auto targetValPtr = basePtrPtr->getTargetValPtr();
return targetValPtr;
@@ -1663,25 +1520,124 @@ Fossil::AnyValPtr SerialReader::_readIndirectValPtr()
Fossil::AnyValPtr SerialReader::_readPotentiallyIndirectValPtr()
{
- auto baseValPtr = _readValPtr();
- if (auto basePtrPtr = as<FossilizedPtr<void>>(baseValPtr))
+ auto stateType = _state.type;
+ auto valPtr = _readValPtr();
+ if (stateType != State::Type::Object)
{
- auto targetValRef = basePtrPtr->getTargetValRef();
- return Fossil::ValPtr(targetValRef);
+ auto ptrPtr = expectNonNullValOfType<FossilizedPtr<void>>(valPtr);
+ valPtr = ptrPtr->getTargetValPtr();
}
- return baseValPtr;
+ return valPtr;
}
-void SerialReader::_pushState()
+void SerialReader::beginTuple(Scope& scope)
{
- _stack.add(_state);
+ auto valPtr = _readValPtr();
+ auto recordPtr = expectNonNullValOfType<FossilizedRecordVal>(valPtr);
+
+ _pushRecordState(scope, recordPtr);
}
-void SerialReader::_popState()
+void SerialReader::beginOptional(Scope& scope)
{
- SLANG_ASSERT(_stack.getCount() != 0);
- _state = _stack.getLast();
- _stack.removeLast();
+ auto valPtr = _readIndirectValPtr();
+ auto optionalPtr = expectPossiblyNullValOfType<FossilizedOptionalObjBase>(valPtr);
+ bool hasValue = optionalPtr->hasValue();
+
+ _pushState(scope);
+
+ _state.type = State::Type::Object;
+ if (hasValue)
+ {
+ auto heldValPtr = getAddress(optionalPtr->getValue());
+
+ _state.remainingValueCount = 1;
+ _state.dataCursor = heldValPtr.getDataPtr();
+ _state.layoutCursor = heldValPtr.getLayout();
+ }
+}
+
+void SerialReader::handleString(String& value)
+{
+ auto valPtr = _readPotentiallyIndirectValPtr();
+ auto stringPtr = expectPossiblyNullValOfType<FossilizedStringObj>(valPtr);
+ if (!stringPtr)
+ {
+ value = String();
+ }
+ else
+ {
+ value = stringPtr->get();
+ }
+}
+
+void SerialReader::_pushContainerState(
+ Scope& scope,
+ Fossil::ValPtr<FossilizedContainerObjBase> containerObjPtr)
+{
+ // The elements of a container object start immediately
+ // at its in-memory address, so we can use the pointer
+ // to the container object as the pointer to its data.
+ //
+ auto containerDataPtr = containerObjPtr.getDataPtr();
+ auto containerLayout = containerObjPtr.getLayout();
+
+ auto elementCount = (uint32_t)containerObjPtr->getElementCount();
+
+ FossilizedValLayout const* elementLayout = containerLayout->elementLayout;
+ auto elementStride = containerLayout->elementStride;
+
+ _pushState(scope);
+
+ _state.type = State::Type::Container;
+ _state.dataCursor = containerDataPtr;
+ _state.layoutCursor = elementLayout;
+ _state.dataStride = elementStride;
+ _state.remainingValueCount = elementCount;
+}
+
+void SerialReader::_pushRecordState(Scope& scope, Fossil::ValPtr<FossilizedRecordVal> recordPtr)
+{
+ auto recordDataPtr = recordPtr.getDataPtr();
+ auto recordLayout = recordPtr.getLayout();
+
+ auto fieldCount = recordLayout->fieldCount;
+
+ _pushState(scope);
+ _state.type = State::Type::Record;
+ _state.dataCursor = recordDataPtr;
+ _state.remainingValueCount = fieldCount;
+ if (fieldCount != 0)
+ {
+ auto fieldInfo = recordLayout->getField(0);
+ _state.layoutCursor = fieldInfo->layout;
+ _state.fieldCursor = fieldInfo;
+ }
+}
+
+
+void SerialReader::beginArray(Scope& scope)
+{
+ auto valPtr = _readPotentiallyIndirectValPtr();
+ auto arrayPtr = expectPossiblyNullValOfType<FossilizedArrayObjBase>(valPtr);
+
+ _pushContainerState(scope, arrayPtr);
+}
+
+void SerialReader::beginDictionary(Scope& scope)
+{
+ auto valPtr = _readPotentiallyIndirectValPtr();
+ auto dictionaryPtr = expectPossiblyNullValOfType<FossilizedDictionaryObjBase>(valPtr);
+
+ _pushContainerState(scope, dictionaryPtr);
+}
+
+void SerialReader::beginStruct(Scope& scope)
+{
+ auto valPtr = _readValPtr();
+ auto recordPtr = expectNonNullValOfType<FossilizedRecordVal>(valPtr);
+
+ _pushRecordState(scope, recordPtr);
}
} // namespace Fossil