diff options
| -rw-r--r-- | source/core/slang-relative-ptr.h | 18 | ||||
| -rw-r--r-- | source/slang/slang-fossil.cpp | 36 | ||||
| -rw-r--r-- | source/slang/slang-fossil.h | 185 | ||||
| -rw-r--r-- | source/slang/slang-serialize-ast.cpp | 166 | ||||
| -rw-r--r-- | source/slang/slang-serialize-fossil.cpp | 532 | ||||
| -rw-r--r-- | source/slang/slang-serialize-fossil.h | 483 | ||||
| -rw-r--r-- | source/slang/slang-serialize-ir.cpp | 11 | ||||
| -rw-r--r-- | source/slang/slang-serialize-riff.cpp | 69 | ||||
| -rw-r--r-- | source/slang/slang-serialize-riff.h | 144 | ||||
| -rw-r--r-- | source/slang/slang-serialize.h | 422 |
10 files changed, 1203 insertions, 863 deletions
diff --git a/source/core/slang-relative-ptr.h b/source/core/slang-relative-ptr.h index 6c4bc942c..1d47545d5 100644 --- a/source/core/slang-relative-ptr.h +++ b/source/core/slang-relative-ptr.h @@ -41,14 +41,14 @@ public: 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); } + SLANG_FORCE_INLINE RelativePtr() = default; + SLANG_FORCE_INLINE RelativePtr(RelativePtr const& ptr) { set(ptr); } + SLANG_FORCE_INLINE RelativePtr(RelativePtr&& ptr) { set(ptr); } + SLANG_FORCE_INLINE RelativePtr(T* ptr) { set(ptr); } - void operator=(RelativePtr const& ptr) { set(ptr); } - void operator=(RelativePtr&& ptr) { set(ptr); } - void operator=(T* ptr) { set(ptr); } + SLANG_FORCE_INLINE void operator=(RelativePtr const& ptr) { set(ptr); } + SLANG_FORCE_INLINE void operator=(RelativePtr&& ptr) { set(ptr); } + SLANG_FORCE_INLINE void operator=(T* ptr) { set(ptr); } T* get() const { @@ -79,8 +79,8 @@ public: SLANG_ASSERT(intptr_t(_offset) == offsetVal); } - operator T*() const { return get(); } - T* operator->() const { return get(); } + SLANG_FORCE_INLINE operator T*() const { return get(); } + SLANG_FORCE_INLINE T* operator->() const { return get(); } private: Offset _offset = 0; diff --git a/source/slang/slang-fossil.cpp b/source/slang/slang-fossil.cpp index 204430901..b5074eac6 100644 --- a/source/slang/slang-fossil.cpp +++ b/source/slang/slang-fossil.cpp @@ -77,30 +77,6 @@ Fossil::AnyValPtr getRootValue(void const* data, Size size) } // namespace Fossil -Size FossilizedStringObj::getSize() const -{ - auto sizePtr = (FossilUInt*)this - 1; - return Size(*sizePtr); -} - -UnownedTerminatedStringSlice FossilizedStringObj::get() const -{ - auto size = getSize(); - return UnownedTerminatedStringSlice((char*)this, size); -} - -Count FossilizedContainerObjBase::getElementCount() const -{ - auto countPtr = (FossilUInt*)this - 1; - return Size(*countPtr); -} - -FossilizedValLayout* FossilizedVariantObj::getContentLayout() const -{ - auto layoutPtrPtr = (FossilizedPtr<FossilizedValLayout>*)this - 1; - return (*layoutPtrPtr).get(); -} - Fossil::AnyValRef Fossil::ValRef<FossilizedContainerObjBase>::getElement(Index index) const { SLANG_ASSERT(index >= 0); @@ -137,16 +113,4 @@ Fossil::AnyValRef Fossil::ValRef<FossilizedRecordVal>::getField(Index index) con return Fossil::AnyValRef(fieldPtr, fieldInfo->layout); } -#if 0 -FossilizedValRef getVariantContent(FossilizedVariantObjRef variantRef) -{ - return getVariantContent(variantRef.getData()); -} -#endif - -Fossil::AnyValPtr getVariantContentPtr(FossilizedVariantObj* variantPtr) -{ - return Fossil::AnyValPtr(variantPtr->getContentDataPtr(), variantPtr->getContentLayout()); -} - } // namespace Slang diff --git a/source/slang/slang-fossil.h b/source/slang/slang-fossil.h index 8d2465ddb..b5fd9b7b5 100644 --- a/source/slang/slang-fossil.h +++ b/source/slang/slang-fossil.h @@ -69,7 +69,10 @@ public: using Layout = FossilizedPtrLikeLayout; - static bool isMatchingKind(FossilizedValKind kind) { return kind == FossilizedValKind::Ptr; } + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) + { + return kind == FossilizedValKind::Ptr; + } }; static_assert(sizeof(FossilizedPtr<void>) == sizeof(uint32_t)); @@ -150,11 +153,11 @@ public: using Layout = FossilizedValLayout; static const FossilizedValKind kKind = Kind; - T const& get() const { return _value; } + SLANG_FORCE_INLINE T const& get() const { return _value; } - operator T const&() const { return _value; } + SLANG_FORCE_INLINE operator T const&() const { return _value; } - static bool isMatchingKind(FossilizedValKind kind) { return kind == kKind; } + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { return kind == kKind; } private: T _value; @@ -207,11 +210,11 @@ public: using Layout = FossilizedValLayout; static const FossilizedValKind kKind = FossilizedValKind::Bool; - bool get() const { return _value != 0; } + SLANG_FORCE_INLINE bool get() const { return _value != 0; } - operator bool() const { return get(); } + SLANG_FORCE_INLINE operator bool() const { return get(); } - static bool isMatchingKind(FossilizedValKind kind) { return kind == kKind; } + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { return kind == kKind; } private: uint8_t _value; @@ -231,9 +234,9 @@ template<typename LiveType, typename FossilizedAsType> struct FossilizedViaCastVal { public: - LiveType get() const { return LiveType(_value.get()); } + SLANG_FORCE_INLINE LiveType get() const { return LiveType(_value.get()); } - operator LiveType() const { return get(); } + SLANG_FORCE_INLINE operator LiveType() const { return get(); } private: @@ -268,11 +271,11 @@ public: Size getSize() const; UnownedTerminatedStringSlice get() const; - operator UnownedTerminatedStringSlice() const { return get(); } + SLANG_FORCE_INLINE operator UnownedTerminatedStringSlice() const { return get(); } using Layout = FossilizedValLayout; - static bool isMatchingKind(FossilizedValKind kind) + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { return kind == FossilizedValKind::StringObj; } @@ -308,9 +311,9 @@ public: Count getElementCount() const; - void const* getBuffer() const { return this; } + SLANG_FORCE_INLINE void const* getBuffer() const { return this; } - static bool isMatchingKind(FossilizedValKind kind) + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { switch (kind) { @@ -342,7 +345,7 @@ public: struct FossilizedArrayObjBase : FossilizedContainerObjBase { public: - static bool isMatchingKind(FossilizedValKind kind) + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { return kind == FossilizedValKind::ArrayObj; } @@ -419,13 +422,13 @@ struct FossilizedPtrLikeLayout struct FossilizedOptionalObjBase { public: - void* getValue() { return this; } + SLANG_FORCE_INLINE void* getValue() { return this; } - void const* getValue() const { return this; } + SLANG_FORCE_INLINE void const* getValue() const { return this; } using Layout = FossilizedPtrLikeLayout; - static bool isMatchingKind(FossilizedValKind kind) + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { return kind == FossilizedValKind::OptionalObj; } @@ -440,17 +443,17 @@ private: template<typename T> struct FossilizedOptionalObj : FossilizedOptionalObjBase { - T* getValue() { return this; } + SLANG_FORCE_INLINE T* getValue() { return this; } - T const* getValue() const { return this; } + SLANG_FORCE_INLINE T const* getValue() const { return this; } }; template<typename T> struct FossilizedOptional { public: - explicit operator bool() const { return _value.get() != nullptr; } - T const& operator*() const { return *_value.get(); } + SLANG_FORCE_INLINE explicit operator bool() const { return _value.get() != nullptr; } + SLANG_FORCE_INLINE T const& operator*() const { return *_value.get(); } private: FossilizedPtr<T> _value; @@ -475,40 +478,40 @@ static_assert(sizeof(Fossilized<std::optional<double>>) == sizeof(FossilUInt)); struct FossilizedString { public: - Size getSize() const { return _obj ? _obj->getSize() : 0; } + SLANG_FORCE_INLINE Size getSize() const { return _obj ? _obj->getSize() : 0; } - UnownedTerminatedStringSlice get() const + SLANG_FORCE_INLINE UnownedTerminatedStringSlice get() const { return _obj ? _obj->get() : UnownedTerminatedStringSlice(); } - operator UnownedTerminatedStringSlice() const { return get(); } + SLANG_FORCE_INLINE operator UnownedTerminatedStringSlice() const { return get(); } private: FossilizedPtr<FossilizedStringObj> _obj; }; -inline int compare(FossilizedString const& lhs, UnownedStringSlice const& rhs) +SLANG_FORCE_INLINE int compare(FossilizedString const& lhs, UnownedStringSlice const& rhs) { return compare(lhs.get(), rhs); } -inline bool operator==(FossilizedString const& left, UnownedStringSlice const& right) +SLANG_FORCE_INLINE bool operator==(FossilizedString const& left, UnownedStringSlice const& right) { return left.get() == right; } -inline bool operator!=(FossilizedString const& left, UnownedStringSlice const& right) +SLANG_FORCE_INLINE bool operator!=(FossilizedString const& left, UnownedStringSlice const& right) { return left.get() != right; } -inline bool operator==(FossilizedStringObj const& left, UnownedStringSlice const& right) +SLANG_FORCE_INLINE bool operator==(FossilizedStringObj const& left, UnownedStringSlice const& right) { return left.get() == right; } -inline bool operator!=(FossilizedStringObj const& left, UnownedStringSlice const& right) +SLANG_FORCE_INLINE bool operator!=(FossilizedStringObj const& left, UnownedStringSlice const& right) { return left.get() != right; } @@ -531,21 +534,21 @@ template<typename T> struct FossilizedContainer { public: - Count getElementCount() const + SLANG_FORCE_INLINE Count getElementCount() const { if (!_obj) return 0; return _obj->getElementCount(); } - T const* getBuffer() const + SLANG_FORCE_INLINE T const* getBuffer() const { if (!_obj) return nullptr; return (T const*)_obj.get()->getBuffer(); } - T const* begin() const { return getBuffer(); } - T const* end() const { return getBuffer() + getElementCount(); } + SLANG_FORCE_INLINE T const* begin() const { return getBuffer(); } + SLANG_FORCE_INLINE T const* end() const { return getBuffer() + getElementCount(); } private: FossilizedPtr<FossilizedContainerObj<T>> _obj; @@ -555,7 +558,7 @@ template<typename T> struct FossilizedArray : FossilizedContainer<T> { public: - T const& operator[](Index index) const + SLANG_FORCE_INLINE T const& operator[](Index index) const { SLANG_ASSERT(index >= 0 && index < this->getElementCount()); return this->getBuffer()[index]; @@ -611,7 +614,7 @@ struct FossilizedTypeTraits<std::pair<K, V>> struct FossilizedDictionaryObjBase : FossilizedContainerObjBase { public: - static bool isMatchingKind(FossilizedValKind kind) + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { return kind == FossilizedValKind::DictionaryObj; } @@ -677,7 +680,7 @@ struct FossilizedRecordVal public: using Layout = FossilizedRecordLayout; - static bool isMatchingKind(FossilizedValKind kind) + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { switch (kind) { @@ -705,10 +708,10 @@ public: FossilizedValLayout* getContentLayout() const; - void* getContentDataPtr() { return this; } - void const* getContentDataPtr() const { return this; } + SLANG_FORCE_INLINE void* getContentDataPtr() { return this; } + SLANG_FORCE_INLINE void const* getContentDataPtr() const { return this; } - static bool isMatchingKind(FossilizedValKind kind) + SLANG_FORCE_INLINE static bool isMatchingKind(FossilizedValKind kind) { return kind == FossilizedValKind::VariantObj; } @@ -802,11 +805,11 @@ public: /// Construct a null reference. /// - ValRefBase() {} + SLANG_FORCE_INLINE ValRefBase() {} /// Construct a reference to the given `data`, assuming it has the given `layout`. /// - ValRefBase(T* data, Layout const* layout) + SLANG_FORCE_INLINE ValRefBase(T* data, Layout const* layout) : _data(data), _layout(layout) { } @@ -816,34 +819,36 @@ public: /// Only enabled if `U*` is convertible to `T*`. /// template<typename U> - ValRefBase(ValRefBase<U> ref, std::enable_if_t<std::is_convertible_v<U*, T*>, void>* = nullptr) + SLANG_FORCE_INLINE ValRefBase( + ValRefBase<U> ref, + std::enable_if_t<std::is_convertible_v<U*, T*>, void>* = nullptr) : _data(ref.getDataPtr()), _layout((Layout const*)ref.getLayout()) { } /// Get a pointer to the value being referenced. /// - T* getDataPtr() const { return _data; } + SLANG_FORCE_INLINE T* getDataPtr() const { return _data; } /// Get a reference to the value being referenced. /// /// This accessor is disabled in the case where `T` is `void`. /// template<typename U = T> - std::enable_if_t<!std::is_same_v<U, void>, T>& getDataRef() const + SLANG_FORCE_INLINE std::enable_if_t<!std::is_same_v<U, void>, T>& getDataRef() const { return *_data; } /// Get the layout of the value being referenced. /// - Layout const* getLayout() const { return _layout; } + SLANG_FORCE_INLINE Layout const* getLayout() const { return _layout; } /// Get the kind of value being referenced. /// /// This reference must not be null. /// - FossilizedValKind getKind() const + SLANG_FORCE_INLINE FossilizedValKind getKind() const { SLANG_ASSERT(getLayout()); return getLayout()->kind; @@ -881,12 +886,12 @@ public: /// Construct a null pointer. /// - ValPtr() {} - ValPtr(std::nullptr_t) {} + SLANG_FORCE_INLINE ValPtr() {} + SLANG_FORCE_INLINE ValPtr(std::nullptr_t) {} /// Construct a pointer to the given `data`, assuming it has the given `layout`. /// - ValPtr(T* data, TargetLayout const* layout) + SLANG_FORCE_INLINE ValPtr(T* data, TargetLayout const* layout) : _ref(data, layout) { } @@ -897,7 +902,7 @@ public: /// We define it as a constructor as a slightly more preferable alternative /// to overloading prefix `operator&` (which is almost always a Bad Idea) /// - explicit ValPtr(ValRef<T> ref) + SLANG_FORCE_INLINE explicit ValPtr(ValRef<T> ref) : _ref(ref) { } @@ -907,25 +912,27 @@ public: /// Only enabled if `U*` is convertible to `T*`. /// template<typename U> - ValPtr(ValPtr<U> ptr, std::enable_if_t<std::is_convertible_v<U*, T*>, void>* = nullptr) + SLANG_FORCE_INLINE ValPtr( + ValPtr<U> ptr, + std::enable_if_t<std::is_convertible_v<U*, T*>, void>* = nullptr) : _ref(*ptr) { } /// Get a pointer to the value being referenced. /// - T* getDataPtr() const { return _ref.getDataPtr(); } + SLANG_FORCE_INLINE T* getDataPtr() const { return _ref.getDataPtr(); } /// Get the layout of the value being referenced. /// - TargetLayout* getLayout() const { return _ref.getLayout(); } + SLANG_FORCE_INLINE TargetLayout const* getLayout() const { return _ref.getLayout(); } - T* get() const { return _ref.getDataPtr(); } - operator T*() const { return get(); } + SLANG_FORCE_INLINE T* get() const { return _ref.getDataPtr(); } + SLANG_FORCE_INLINE operator T*() const { return get(); } /// Deference this `ValPtr` to get a `ValRef`. /// - ValRef<T> operator*() const { return _ref; } + SLANG_FORCE_INLINE ValRef<T> operator*() const { return _ref; } /// Deference this `ValPtr` for member access. /// @@ -939,7 +946,7 @@ public: /// that behavior is for the `operator->` on `ValPtr` /// to return a pointer to a `ValRef`. /// - ValRef<T> const* operator->() const { return &_ref; } + SLANG_FORCE_INLINE ValRef<T> const* operator->() const { return &_ref; } private: ValRef<T> _ref; @@ -948,7 +955,7 @@ private: /// Get a `ValPtr` pointing to the same value as the given `ref`. /// template<typename T> -inline ValPtr<T> getAddress(ValRef<T> ref) +SLANG_FORCE_INLINE ValPtr<T> getAddress(ValRef<T> ref) { return ValPtr<T>(ref); } @@ -973,10 +980,10 @@ struct ValRef<FossilizedStringObj> : ValRefBase<FossilizedStringObj> public: using ValRefBase<FossilizedStringObj>::ValRefBase; - Size getSize() const { return getDataPtr()->getSize(); } - UnownedTerminatedStringSlice get() const { return getDataPtr()->get(); } + SLANG_FORCE_INLINE Size getSize() const { return getDataPtr()->getSize(); } + SLANG_FORCE_INLINE UnownedTerminatedStringSlice get() const { return getDataPtr()->get(); } - operator UnownedTerminatedStringSlice() const { return get(); } + SLANG_FORCE_INLINE operator UnownedTerminatedStringSlice() const { return get(); } }; @@ -986,7 +993,7 @@ struct ValRef<FossilizedContainerObjBase> : ValRefBase<FossilizedContainerObjBas public: using ValRefBase<FossilizedContainerObjBase>::ValRefBase; - Count getElementCount() const + SLANG_FORCE_INLINE Count getElementCount() const { auto data = this->getDataPtr(); if (!data) @@ -1004,7 +1011,7 @@ struct ValRef<FossilizedArrayObjBase> : ValRefBase<FossilizedArrayObjBase> public: using ValRefBase<FossilizedArrayObjBase>::ValRefBase; - Count getElementCount() const + SLANG_FORCE_INLINE Count getElementCount() const { auto data = this->getDataPtr(); if (!data) @@ -1022,7 +1029,7 @@ struct ValRef<FossilizedDictionaryObjBase> : ValRefBase<FossilizedDictionaryObjB public: using ValRefBase<FossilizedDictionaryObjBase>::ValRefBase; - Count getElementCount() const + SLANG_FORCE_INLINE Count getElementCount() const { auto data = this->getDataPtr(); if (!data) @@ -1038,9 +1045,9 @@ struct ValRef<FossilizedOptionalObjBase> : ValRefBase<FossilizedOptionalObjBase> public: using ValRefBase<FossilizedOptionalObjBase>::ValRefBase; - bool hasValue() const { return this->getDataPtr() != nullptr; } + SLANG_FORCE_INLINE bool hasValue() const { return this->getDataPtr() != nullptr; } - AnyValRef getValue() const + SLANG_FORCE_INLINE AnyValRef getValue() const { SLANG_ASSERT(hasValue()); return AnyValRef(this->getDataPtr(), this->getLayout()->elementLayout.get()); @@ -1053,7 +1060,7 @@ struct ValRef<FossilizedRecordVal> : ValRefBase<FossilizedRecordVal> public: using ValRefBase<FossilizedRecordVal>::ValRefBase; - Count getFieldCount() const { return getLayout()->fieldCount; } + SLANG_FORCE_INLINE Count getFieldCount() const { return getLayout()->fieldCount; } AnyValRef getField(Index index) const; }; @@ -1064,13 +1071,13 @@ struct ValRef<FossilizedPtr<T>> : ValRefBase<FossilizedPtr<T>> public: using ValRefBase<FossilizedPtr<T>>::ValRefBase; - ValRef<T> getTargetValRef() const + SLANG_FORCE_INLINE ValRef<T> getTargetValRef() const { auto ptrPtr = this->getDataPtr(); return ValRef<T>(*ptrPtr, this->getLayout()->elementLayout.get()); } - ValPtr<T> getTargetValPtr() const { return ValPtr<T>(getTargetValRef()); } + SLANG_FORCE_INLINE ValPtr<T> getTargetValPtr() const { return ValPtr<T>(getTargetValRef()); } // ValRef<T> operator*() const; }; @@ -1085,19 +1092,19 @@ public: /// Statically cast a pointer to a fossilized value. /// template<typename T> -ValPtr<T> cast(AnyValPtr valPtr) +SLANG_FORCE_INLINE ValPtr<T> cast(AnyValPtr valPtr) { - if (!valPtr) - return ValPtr<T>(); + // if (!valPtr) + // return ValPtr<T>(); return ValPtr<T>( static_cast<T*>(valPtr.getDataPtr()), - (typename T::Layout*)(valPtr->getLayout())); + (typename T::Layout*)(valPtr.getLayout())); } /// Dynamic cast of a pointer to a fossilized value. /// template<typename T> -ValPtr<T> as(AnyValPtr valPtr) +SLANG_FORCE_INLINE ValPtr<T> as(AnyValPtr valPtr) { if (!valPtr || !T::isMatchingKind(valPtr->getKind())) { @@ -1174,6 +1181,36 @@ Fossil::AnyValPtr getRootValue(ISlangBlob* blob); Fossil::AnyValPtr getRootValue(void const* data, Size size); } // namespace Fossil +SLANG_FORCE_INLINE Size FossilizedStringObj::getSize() const +{ + auto sizePtr = (FossilUInt*)this - 1; + return Size(*sizePtr); +} + +SLANG_FORCE_INLINE UnownedTerminatedStringSlice FossilizedStringObj::get() const +{ + auto size = getSize(); + return UnownedTerminatedStringSlice((char*)this, size); +} + +SLANG_FORCE_INLINE Count FossilizedContainerObjBase::getElementCount() const +{ + auto countPtr = (FossilUInt*)this - 1; + return Size(*countPtr); +} + +SLANG_FORCE_INLINE FossilizedValLayout* FossilizedVariantObj::getContentLayout() const +{ + auto layoutPtrPtr = (FossilizedPtr<FossilizedValLayout>*)this - 1; + return (*layoutPtrPtr).get(); +} + +SLANG_FORCE_INLINE Fossil::AnyValPtr getVariantContentPtr(FossilizedVariantObj* variantPtr) +{ + return Fossil::AnyValPtr(variantPtr->getContentDataPtr(), variantPtr->getContentLayout()); +} + + } // namespace Slang #endif diff --git a/source/slang/slang-serialize-ast.cpp b/source/slang/slang-serialize-ast.cpp index a288b3bd2..be959b8a1 100644 --- a/source/slang/slang-serialize-ast.cpp +++ b/source/slang/slang-serialize-ast.cpp @@ -1,6 +1,7 @@ // slang-serialize-ast.cpp #include "slang-serialize-ast.h" +#include "core/slang-performance-profiler.h" #include "slang-ast-dispatch.h" #include "slang-check.h" #include "slang-compiler.h" @@ -84,7 +85,8 @@ namespace Slang // they are all just getting dumped here in the AST serialization logic, because // it is currenly the only place that cares about this stuff. // -void serialize(Serializer const&, RefObject&) +template<typename S> +void serialize(S const&, RefObject&) { // There's actually no data stored in a `RefObject`, since it only exists // to make reference-counting possible for other types. This function is @@ -136,7 +138,8 @@ struct FossilizedTypeTraits<RefObject> // While we could include this among the types we handle using fiddle, // let's implement it by hand here, starting with the `serialize()` function: // -void serialize(Serializer const& serializer, MatrixCoord& value) +template<typename S> +void serialize(S const& serializer, MatrixCoord& value) { // We start with one of the `SLANG_SCOPED_SERIALIZER_*` // macros, which basically just handles calling @@ -210,7 +213,8 @@ struct FossilizedTypeTraits<MatrixCoord> // as a single scalar value. We'll define our `serialize()` function // so that it serializes that "raw" value instead: // -void serialize(Serializer const& serializer, SemanticVersion& value) +template<typename S> +void serialize(S const& serializer, SemanticVersion& value) { // This function is doing something a little "clever" // handle the fact that it might be used to either @@ -373,33 +377,17 @@ struct ContainerDeclDirectMemberDeclsInfo }; // -// Okay, that's enough examples for now. Let's move on to the next big -// topic... -// // Many types in the AST need additional context information to be able to -// read or write them properly, so instead of passing around the basic -// `Serializer` type (which wraps an `ISerializerImpl`), for those types -// that need extra context we will be passing around an `ASTSerializer` -// (which wraps an `IASTSerializerImpl`, with the latter interface providing -// the callbacks to handle the data types that need special-case behavior. +// read or write them properly, so the concrete serializer type being passed +// around will include an additional "context" type, that will be either +// `ASTSerialReadContext` or `ASTSerialWriteContext`, depending on the mode +// in which serialization is being performed. +// +// We could define a base class or interface with `virtual` functions for +// accessing all of the relevant context, but because we are already +// using template specialization, it is easier to just ensure that the +// relevant context types both provide the required operations. // - -struct ASTSerialContext; -using ASTSerializer = Serializer_<ISerializerImpl, ASTSerialContext>; - -/// Context interface for AST serialization -struct ASTSerialContext : SourceLocSerialContext -{ -public: - virtual void handleASTNode(ASTSerializer const& serializer, NodeBase*& value) = 0; - virtual void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* value) = 0; - virtual void handleName(ASTSerializer const& serializer, Name*& value) = 0; - virtual void handleToken(ASTSerializer const& serializer, Token& value) = 0; - virtual void handleContainerDeclDirectMemberDecls( - ASTSerializer const& serializer, - ContainerDeclDirectMemberDecls& value) = 0; -}; - // // Now that we've covered some of the big-picture structure, and shown @@ -439,7 +427,8 @@ public: %for _,T in ipairs(enumTypeNames) do /// Serialize a `value` of type `$T`. -void serialize(Serializer const& serializer, $T& value) +template<typename S> +void serialize(S const& serializer, $T& value) { serializeEnum(serializer, value); } @@ -518,7 +507,8 @@ struct Fossilized_$T; SLANG_DECLARE_FOSSILIZED_TYPE($T, Fossilized_$T); /// Serialize a `$T` -void serialize(ASTSerializer const& serializer, $T& value); +template<typename S> +void serialize(S const& serializer, $T& value); %end #else // FIDDLE OUTPUT: #define FIDDLE_GENERATED_OUTPUT_ID 1 @@ -545,7 +535,8 @@ struct Fossilized_$T; SLANG_DECLARE_FOSSILIZED_TYPE($T, Fossilized_$T); /// Serialize the content of a `$T` -void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value); +template<typename S> +void _serializeASTNodeContents(S const& serializer, $T* value); %end #else // FIDDLE OUTPUT: #define FIDDLE_GENERATED_OUTPUT_ID 2 @@ -566,9 +557,11 @@ void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value); /// lower-level serialization operations to an underlying /// `ISerializerImpl`. /// -struct ASTSerialWriteContext : ASTSerialContext +struct ASTSerialWriteContext : SourceLocSerialContext { public: + using ASTSerializer = Serializer<Fossil::SerialWriter, ASTSerialWriteContext>; + /// Construct a context for writing a serialized AST. /// /// * `module` is the module that is being serialized, and will be @@ -587,21 +580,23 @@ private: ModuleDecl* _module = nullptr; SerialSourceLocWriter* _sourceLocWriter = nullptr; +public: // // For the most part, this type just implements the methods // of the `IASTSerializerImpl` interface, and then has some // support routines needed by those implementations. // - virtual void handleName(ASTSerializer const& serializer, Name*& value) override; - virtual void handleToken(ASTSerializer const& serializer, Token& value) override; - virtual void handleASTNode(ASTSerializer const& serializer, NodeBase*& node) override; - virtual void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node) override; - virtual void handleContainerDeclDirectMemberDecls( + void handleName(ASTSerializer const& serializer, Name*& value); + void handleToken(ASTSerializer const& serializer, Token& value); + void handleASTNode(ASTSerializer const& serializer, NodeBase*& node); + void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node); + void handleContainerDeclDirectMemberDecls( ASTSerializer const& serializer, - ContainerDeclDirectMemberDecls& value) override; - virtual SerialSourceLocWriter* getSourceLocWriter() override { return _sourceLocWriter; } + ContainerDeclDirectMemberDecls& value); + SerialSourceLocWriter* getSourceLocWriter() { return _sourceLocWriter; } +private: void _writeImportedModule(ASTSerializer const& serializer, ModuleDecl* moduleDecl); void _writeImportedDecl( ASTSerializer const& serializer, @@ -653,9 +648,11 @@ private: /// contexts could result in the same declaration getting turned /// into multiple distinct `Decl*`s. /// -struct ASTSerialReadContext : public ASTSerialContext, public RefObject +struct ASTSerialReadContext : public SourceLocSerialContext, public RefObject { public: + using ASTSerializer = Serializer<Fossil::SerialReader, ASTSerialReadContext>; + /// Construct an AST deserialization context. /// /// The `linkage`, `astBuilder`, and `sink` arguments must @@ -729,6 +726,7 @@ private: Count _deserializedTopLevelDeclCount = 0; #endif +public: // // Much like the `ASTSerialWriter`, for the most part this // type just implements the `IASTSerializer` interface, @@ -736,15 +734,16 @@ private: // implementations. // - virtual void handleName(ASTSerializer const& serializer, Name*& value) override; - virtual void handleToken(ASTSerializer const& serializer, Token& value) override; - virtual void handleASTNode(ASTSerializer const& serializer, NodeBase*& outNode) override; - virtual void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node) override; - virtual void handleContainerDeclDirectMemberDecls( + void handleName(ASTSerializer const& serializer, Name*& value); + void handleToken(ASTSerializer const& serializer, Token& value); + void handleASTNode(ASTSerializer const& serializer, NodeBase*& outNode); + void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node); + void handleContainerDeclDirectMemberDecls( ASTSerializer const& serializer, - ContainerDeclDirectMemberDecls& value) override; - virtual SerialSourceLocReader* getSourceLocReader() override { return _sourceLocReader; } + ContainerDeclDirectMemberDecls& value); + SerialSourceLocReader* getSourceLocReader() { return _sourceLocReader; } +private: ModuleDecl* _readImportedModule(ASTSerializer const& serializer); NodeBase* _readImportedDecl(ASTSerializer const& serializer); @@ -770,7 +769,8 @@ private: SLANG_DECLARE_FOSSILIZED_AS(Name, String); -void serializeObject(ASTSerializer const& serializer, Name*& value, Name*) +template<typename S> +void serializeObject(S const& serializer, Name*& value, Name*) { serializer.getContext()->handleName(serializer, value); } @@ -807,7 +807,8 @@ struct FossilizedTypeTraits<Token> }; }; -void serialize(ASTSerializer const& serializer, Token& value) +template<typename S> +void serialize(S const& serializer, Token& value) { serializer.getContext()->handleToken(serializer, value); } @@ -883,8 +884,8 @@ void ASTSerialReadContext::handleToken(ASTSerializer const& serializer, Token& v // serialize any pointers to AST nodes. // -template<typename T> -void serializeObject(ASTSerializer const& serializer, T*& value, NodeBase*) +template<typename S, typename T> +SLANG_FORCE_INLINE void serializeObject(S const& serializer, T*& value, NodeBase*) { // The general-purpose serialization layer defines // a variant as akin to a struct, but where the @@ -912,7 +913,8 @@ void serializeObject(ASTSerializer const& serializer, T*& value, NodeBase*) // object in the reading direction. // -void serializeObjectContents(ASTSerializer const& serializer, NodeBase* value, NodeBase*) +template<typename S> +SLANG_FORCE_INLINE void serializeObjectContents(S const& serializer, NodeBase* value, NodeBase*) { serializer.getContext()->handleASTNodeContents(serializer, value); } @@ -930,7 +932,8 @@ void serializeObjectContents(ASTSerializer const& serializer, NodeBase* value, N SLANG_DECLARE_FOSSILIZED_AS(ContainerDeclDirectMemberDecls, ContainerDeclDirectMemberDeclsInfo); -void serialize(ASTSerializer const& serializer, ContainerDeclDirectMemberDecls& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, ContainerDeclDirectMemberDecls& value) { serializer.getContext()->handleContainerDeclDirectMemberDecls(serializer, value); } @@ -943,7 +946,8 @@ void serialize(ASTSerializer const& serializer, ContainerDeclDirectMemberDecls& SLANG_DECLARE_FOSSILIZED_AS(DiagnosticInfo const*, Int32); -void serializePtr(Serializer const& serializer, DiagnosticInfo const*& value, DiagnosticInfo const*) +template<typename S> +void serializePtr(S const& serializer, DiagnosticInfo const*& value, DiagnosticInfo const*) { Int32 id = 0; if (isWriting(serializer)) @@ -964,8 +968,8 @@ void serializePtr(Serializer const& serializer, DiagnosticInfo const*& value, Di // and we'll serialize it as such. // -template<typename T> -void serialize(ASTSerializer const& serializer, DeclRef<T>& value) +template<typename S, typename T> +void serialize(S const& serializer, DeclRef<T>& value) { serialize(serializer, value.declRefBase); } @@ -987,7 +991,8 @@ struct FossilizedTypeTraits<DeclRef<T>> SLANG_DECLARE_FOSSILIZED_AS(SyntaxClass<NodeBase>, ASTNodeType); -void serialize(Serializer const& serializer, SyntaxClass<NodeBase>& value) +template<typename S> +void serialize(S const& serializer, SyntaxClass<NodeBase>& value) { ASTNodeType raw = ASTNodeType(0); if (isWriting(serializer)) @@ -1011,7 +1016,8 @@ void serialize(Serializer const& serializer, SyntaxClass<NodeBase>& value) SLANG_DECLARE_FOSSILIZED_AS(Modifiers, List<Modifier*>); -void serialize(ASTSerializer const& serializer, Modifiers& value) +template<typename S> +void serialize(S const& serializer, Modifiers& value) { SLANG_SCOPED_SERIALIZER_ARRAY(serializer); @@ -1059,7 +1065,8 @@ void serialize(ASTSerializer const& serializer, Modifiers& value) // SLANG_DECLARE_FOSSILIZED_AS_MEMBER(TypeExp, type); -void serialize(ASTSerializer const& serializer, TypeExp& value) +template<typename S> +void serialize(S const& serializer, TypeExp& value) { serialize(serializer, value.type); } @@ -1071,7 +1078,8 @@ void serialize(ASTSerializer const& serializer, TypeExp& value) SLANG_DECLARE_FOSSILIZED_AS_MEMBER(CandidateExtensionList, candidateExtensions); -void serialize(ASTSerializer const& serializer, CandidateExtensionList& value) +template<typename S> +void serialize(S const& serializer, CandidateExtensionList& value) { serialize(serializer, value.candidateExtensions); } @@ -1079,7 +1087,8 @@ void serialize(ASTSerializer const& serializer, CandidateExtensionList& value) SLANG_DECLARE_FOSSILIZED_AS_MEMBER(DeclAssociationList, associations); -void serialize(ASTSerializer const& serializer, DeclAssociationList& value) +template<typename S> +void serialize(S const& serializer, DeclAssociationList& value) { serialize(serializer, value.associations); } @@ -1109,7 +1118,8 @@ SLANG_DECLARE_FOSSILIZED_AS(CapabilityTargetSet, CapabilityStageSets); // SLANG_DECLARE_FOSSILIZED_AS(CapabilitySet, CapabilityTargetSets); -void serialize(Serializer const& serializer, CapabilityAtomSet& value) +template<typename S> +void serialize(S const& serializer, CapabilityAtomSet& value) { SLANG_SCOPED_SERIALIZER_ARRAY(serializer); if (isWriting(serializer)) @@ -1131,12 +1141,14 @@ void serialize(Serializer const& serializer, CapabilityAtomSet& value) } } -void serialize(Serializer const& serializer, CapabilityStageSet& value) +template<typename S> +void serialize(S const& serializer, CapabilityStageSet& value) { serialize(serializer, value.atomSet); } -void serialize(Serializer const& serializer, CapabilityTargetSet& value) +template<typename S> +void serialize(S const& serializer, CapabilityTargetSet& value) { serialize(serializer, value.shaderStageSets); @@ -1154,7 +1166,8 @@ void serialize(Serializer const& serializer, CapabilityTargetSet& value) } } -void serialize(Serializer const& serializer, CapabilitySet& value) +template<typename S> +void serialize(S const& serializer, CapabilitySet& value) { serialize(serializer, value.getCapabilityTargetSets()); @@ -1190,7 +1203,8 @@ struct FossilizedTypeTraits<RequirementWitness> }; }; -void serialize(ASTSerializer const& serializer, RequirementWitness& value) +template<typename S> +void serialize(S const& serializer, RequirementWitness& value) { SLANG_SCOPED_SERIALIZER_VARIANT(serializer); serialize(serializer, value.m_flavor); @@ -1228,7 +1242,8 @@ struct FossilizedTypeTraits<ValNodeOperand> }; }; -void serialize(ASTSerializer const& serializer, ValNodeOperand& value) +template<typename S> +void serialize(S const& serializer, ValNodeOperand& value) { SLANG_SCOPED_SERIALIZER_VARIANT(serializer); serialize(serializer, value.kind); @@ -1289,7 +1304,8 @@ struct Fossilized_$T }; /// Serialize a `value` of type `$T` -void serialize(ASTSerializer const& serializer, $T& value) +template<typename S> +void serialize(S const& serializer, $T& value) { SLANG_UNUSED(value); SLANG_SCOPED_SERIALIZER_STRUCT(serializer); @@ -1346,7 +1362,8 @@ struct Fossilized_$T }; /// Serialize the contents of an AST node of type `$T` -void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value) +template<typename S> +void _serializeASTNodeContents(S const& serializer, $T* value) { SLANG_UNUSED(serializer); SLANG_UNUSED(value); @@ -1372,7 +1389,8 @@ void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value) // functions, and dispatches to the correct one based on the type of the given node. // -void serializeASTNodeContents(ASTSerializer const& serializer, NodeBase* node) +template<typename S> +void serializeASTNodeContents(S const& serializer, NodeBase* node) { ASTNodeDispatcher<NodeBase, void>::dispatch( node, @@ -1894,7 +1912,7 @@ void ASTSerialReadContext::handleContainerDeclDirectMemberDecls( // `ISerializerImpl`, whereas we *know* it has a more // specific type, which we want to make use of. // - ISerializerImpl* readerImpl = serializer.getImpl(); + auto readerImpl = serializer.getImpl(); auto fossilReader = static_cast<Fossil::SerialReader*>(readerImpl); // auto fossilizedInfo = @@ -1927,6 +1945,8 @@ void writeSerializedModuleAST( ModuleDecl* moduleDecl, SerialSourceLocWriter* sourceLocWriter) { + SLANG_PROFILE; + // TODO: we might want to have a more careful pass here, // where we only encode the public declarations. @@ -1965,7 +1985,7 @@ void writeSerializedModuleAST( // Fossil::SerialWriter writer(blobBuilder); ASTSerialWriteContext context(moduleDecl, sourceLocWriter); - ASTSerializer serializer(&writer, &context); + ASTSerialWriteContext::ASTSerializer serializer(&writer, &context); // Once we have our `serializer`, we can finally invoke // `serialize()` on the `ASTModuleInfo` to cause everything @@ -2030,6 +2050,8 @@ ModuleDecl* readSerializedModuleAST( SerialSourceLocReader* sourceLocReader, SourceLoc requestingSourceLoc) { + SLANG_PROFILE; + // We expect the `chunk` that was passed in to be a RIFF // data chunk (matching what was written in `writeSerializedModuleAST()`, // and to be proper fossil-format data. 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 diff --git a/source/slang/slang-serialize-fossil.h b/source/slang/slang-serialize-fossil.h index 930719935..184831a39 100644 --- a/source/slang/slang-serialize-fossil.h +++ b/source/slang/slang-serialize-fossil.h @@ -21,8 +21,69 @@ namespace Slang namespace Fossil { +// Deserializing data is an important place where security issues +// can arise, so it is usually important to perform validation +// checks throughout the process, and fail fast rather than +// risk reading mal-formed data. +// +// However, validation typically comes at a performance cost, +// and one of the key cases for serialization in Slang is loading +// the core module from the `slang.dll` binary itself. In order +// to measure how much performance is being lost to validation +// checks, we provide a define that is intended to enable or +// disable validation during deserialization. +// +#define SLANG_SERIALIZE_FOSSIL_ENABLE_VALIDATION_CHECKS 1 + +#if SLANG_SERIALIZE_FOSSIL_ENABLE_VALIDATION_CHECKS +#define SLANG_SERIALIZE_FOSSIL_VALIDATE(CONDITION) \ + do \ + { \ + if (!(CONDITION)) \ + SLANG_UNEXPECTED("invalid format encountered in serialized data"); \ + } while (0) +#else +#define SLANG_SERIALIZE_FOSSIL_VALIDATE(CONDITION) SLANG_ASSERT(CONDITION) +#endif + +// A commonly-occuring kind of validation check when reading +// data in fossil format is asserting that some expected +// piece of data is both *present* and has the expected +// type/layout. +// +template<typename T> +SLANG_FORCE_INLINE ValPtr<T> expectNonNullValOfType(AnyValPtr valPtr) +{ +#if SLANG_SERIALIZE_FOSSIL_ENABLE_VALIDATION_CHECKS + if (auto resultPtr = as<T>(valPtr)) + return resultPtr; + SLANG_UNEXPECTED("invalid format encountered in serialized data"); +#else + return cast<T>(valPtr); +#endif +} + +// Some types can be encoded via a null pointer when they +// are in their "default" state. For example, an empty +// dictionary or array may be encoded as a null pointer +// to a `FossilizedContainerObj`. We still expect a match +// on the *format* in such cases. +// +template<typename T> +SLANG_FORCE_INLINE ValPtr<T> expectPossiblyNullValOfType(AnyValPtr valPtr) +{ +#if SLANG_SERIALIZE_FOSSIL_ENABLE_VALIDATION_CHECKS + auto layout = valPtr.getLayout(); + if (!layout || !T::isMatchingKind(layout->kind)) + { + SLANG_UNEXPECTED("invalid format encountered in serialized data"); + } +#endif + return cast<T>(valPtr); +} + /// Serializer implementation for writing objects to a fossil-format blob. -struct SerialWriter : ISerializerImpl +struct SerialWriter { public: SerialWriter(ChunkBuilder* chunk); @@ -75,12 +136,9 @@ private: { } - virtual ~LayoutObj() {} - FossilizedValKind getKind() const { return kind; } Size getSize() const { return size; } - Size getAlignment() const { return alignment; } FossilizedValKind kind; Size size = 0; @@ -133,6 +191,18 @@ private: class SimpleLayoutObj : public LayoutObj { public: + SimpleLayoutObj(FossilizedValKind kind, Size size, Size alignment) + : LayoutObj(kind, size, alignment) + { + } + + // + // Note that for `SimpleLayoutObj` the default + // alignment is the same as the `size`, while + // for the base `LayoutObj` the default is + // one-byte alignment. + // + SimpleLayoutObj(FossilizedValKind kind, Size size) : LayoutObj(kind, size, size) { @@ -259,7 +329,7 @@ private: LayoutObj* ptrLayout = nullptr; /// Callback information used by the ISerializer interface. - Callback callback = nullptr; + SerializerCallback callback = nullptr; void* context = nullptr; }; @@ -501,62 +571,73 @@ private: void _pushState(LayoutObj* layout); void _popState(); -private: +public: // // The following declarations are the requirements // of the `ISerializerImpl` interface: // - virtual SerializationMode getMode() override; + SerializationMode getMode(); + + void handleBool(bool& value); - virtual void handleBool(bool& value) override; + void handleInt8(int8_t& value); + void handleInt16(int16_t& value); + void handleInt32(Int32& value); + void handleInt64(Int64& value); - virtual void handleInt8(int8_t& value) override; - virtual void handleInt16(int16_t& value) override; - virtual void handleInt32(Int32& value) override; - virtual void handleInt64(Int64& value) override; + void handleUInt8(uint8_t& value); + void handleUInt16(uint16_t& value); + void handleUInt32(UInt32& value); + void handleUInt64(UInt64& value); - virtual void handleUInt8(uint8_t& value) override; - virtual void handleUInt16(uint16_t& value) override; - virtual void handleUInt32(UInt32& value) override; - virtual void handleUInt64(UInt64& value) override; + void handleFloat32(float& value); + void handleFloat64(double& value); - virtual void handleFloat32(float& value) override; - virtual void handleFloat64(double& value) override; + void handleString(String& value); - virtual void handleString(String& value) override; + struct Scope + { + // The `SerialWriter` implementation for fossil + // does not currently take advantage of the `Scope` + // facility provided by the serialization framework. + // + // If we *do* want to modify it to use that facility, + // then we can look to the `SerialReader` implementation + // for an example of how it can be used. + }; - virtual void beginArray() override; - virtual void endArray() override; - virtual void beginOptional() override; - virtual void endOptional() override; + void beginArray(Scope& scope); + void endArray(Scope& scope); - virtual void beginDictionary() override; - virtual void endDictionary() override; + void beginOptional(Scope& scope); + void endOptional(Scope& scope); - virtual bool hasElements() override; + void beginDictionary(Scope& scope); + void endDictionary(Scope& scope); - virtual void beginTuple() override; - virtual void endTuple() override; + bool hasElements(); - virtual void beginStruct() override; - virtual void endStruct() override; + void beginTuple(Scope& scope); + void endTuple(Scope& scope); - virtual void beginVariant() override; - virtual void endVariant() override; + void beginStruct(Scope& scope); + void endStruct(Scope& scope); - virtual void handleFieldKey(char const* name, Int index) override; + void beginVariant(Scope& scope); + void endVariant(Scope& scope); - virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; + void handleFieldKey(char const* name, Int index); - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) - override; + void handleSharedPtr(void*& value, SerializerCallback callback, void* context); + void handleUniquePtr(void*& value, SerializerCallback callback, void* context); + + void handleDeferredObjectContents(void* valuePtr, SerializerCallback callback, void* context); }; /// Serializer implementation for reading objects from a fossil-format blob. -struct SerialReader : ISerializerImpl +struct SerialReader { public: struct ReadContext; @@ -595,57 +676,91 @@ private: /// A state that the reader can be in. struct State { + // Note: the exact order of declaration for the fields + // here can end up impacting the overall performance of + // deserialization, so be mindful when making changes. + /// Type of state; related to the kind of value being read from. /// + /// Most of these values are determined by the `FossilizedValKind` + /// of the parent/container that was pushed to create this state, + /// but the correspondance is not one-to-one. For example, + /// both `FossilizedValKind::ArrayObj` and `::DictinaryObj` map + /// to `State::Type::Container`. + /// + /// Reducing the number of distinct cases tracked here minimizes + /// the complexity of branches on the type of state. + /// enum class Type { - Root, - Array, - Dictionary, - Optional, - Tuple, - Struct, Object, - + Container, + Record, PseudoPtr, }; - /// The type of state. - Type type = Type::Root; + /// The pointers to the data and layout of the fossilized + /// value that will be read next. + /// + void* dataCursor = nullptr; + FossilizedValLayout const* layoutCursor = nullptr; - /// The fossilized value (data and layout) that is being read from. + /// The field layout information for the next value to be read. /// - /// Depending on the `type` of state, this might either be the next value - /// that will be read (e.g., for the `Root` case), or it might be - /// a container that is a parent of the next value to be read. + /// This value is only used when `type == State::Type::Record`. + /// In that case, the `fieldCursor` can be used to locate + /// the layout for the next field (if their is one). /// - Fossil::AnyValPtr baseValPtr; + FossilizedRecordElementLayout const* fieldCursor = nullptr; - /// Index of next element to read. + /// The number of values that can still be read in this state. /// - /// This is used in the case where `baseValue` is some kind of - /// container or record. + /// If the `remainingValueCount` field is zero, then the contents + /// of the remaining fields are irrelevant (and may hold stale + /// values rather than being cleared correctly). /// - Index elementIndex = 0; + uint32_t remainingValueCount = 0; - /// Total number of values that can be read. + /// A stride (in bytes) between values. /// - /// If `baseValue` is a container, this is the element count. - /// If `baseValue` is a tuple/struct, this is the field count. - /// If `baseValue` is an optional, this is either zero or one. - /// If this state is a singleton case like `Root`, will be one. + /// This value is only used when `type == State::Type::Container`. + /// It should be set to zero for any other type of state. /// - Count elementCount = 0; + uint32_t dataStride = 0; + + /// The type of state. + Type type = Type::Object; }; /// The current state. State _state; - /// Stack of saved states. - List<State> _stack; + SLANG_FORCE_INLINE State& getState() { return _state; } + +public: + // + // The serialization protocol allows the back-end + // implementation to define a `Scope` type that will + // be passed into the paired `begin` and `end` operations. + // + // We define a scope type that is able to store a saved + // copy of the state of the `SerialReader`, which allows + // us to maintain a stack of states that gets stored on + // the run-time call stack, instead of requiring a + // heap-allocated container here. + // + + struct Scope + { + private: + friend struct SerialReader; + + State savedState; + }; +private: // - // Like other `ISerializerImpl`s for reading, we track objects + // Like other serializer implementations for reading, we track objects // that are in the process of being read in, to avoid possible // unbounded recursion (and detect circularities when they // occur). @@ -682,12 +797,15 @@ private: State savedState; - Callback callback; + SerializerCallback callback; void* context; }; - void _pushState(); - void _popState(); + void _pushState(Scope& scope); + void _popState(Scope& scope); + + void _pushContainerState(Scope& scope, Fossil::ValPtr<FossilizedContainerObjBase> containerPtr); + void _pushRecordState(Scope& scope, Fossil::ValPtr<FossilizedRecordVal> recordPtr); /// Execute all deferred actions that are still pending. @@ -699,6 +817,10 @@ private: /// Fossil::AnyValPtr _readValPtr(); + /// Advance the read cursor. + /// + void _advanceCursor(); + /// Read an indirect value. /// /// This is the case for things like optionals, that are @@ -717,10 +839,16 @@ private: template<typename T> - void handleSimpleVal(T& value) + SLANG_FORCE_INLINE T _readSimpleVal() { auto valPtr = _readValPtr(); - value = as<Fossilized<T>>(valPtr)->getDataRef(); + return expectNonNullValOfType<Fossilized<T>>(valPtr)->getDataRef(); + } + + template<typename T> + SLANG_FORCE_INLINE void _handleSimpleVal(T& value) + { + value = _readSimpleVal<T>(); } public: @@ -739,62 +867,213 @@ public: }; -private: +public: // // The following declarations are the requirements // of the `ISerializerImpl` interface: // - virtual SerializationMode getMode() override; + SerializationMode getMode(); - virtual void handleBool(bool& value) override; + void handleBool(bool& value); - virtual void handleInt8(int8_t& value) override; - virtual void handleInt16(int16_t& value) override; - virtual void handleInt32(Int32& value) override; - virtual void handleInt64(Int64& value) override; + void handleInt8(int8_t& value); + void handleInt16(int16_t& value); + void handleInt32(Int32& value); + void handleInt64(Int64& value); - virtual void handleUInt8(uint8_t& value) override; - virtual void handleUInt16(uint16_t& value) override; - virtual void handleUInt32(UInt32& value) override; - virtual void handleUInt64(UInt64& value) override; + void handleUInt8(uint8_t& value); + void handleUInt16(uint16_t& value); + void handleUInt32(UInt32& value); + void handleUInt64(UInt64& value); - virtual void handleFloat32(float& value) override; - virtual void handleFloat64(double& value) override; + void handleFloat32(float& value); + void handleFloat64(double& value); - virtual void handleString(String& value) override; + void handleString(String& value); - virtual void beginArray() override; - virtual void endArray() override; + void beginArray(Scope& scope); + void endArray(Scope& scope); - virtual void beginDictionary() override; - virtual void endDictionary() override; + void beginDictionary(Scope& scope); + void endDictionary(Scope& scope); - virtual bool hasElements() override; + bool hasElements(); - virtual void beginStruct() override; - virtual void endStruct() override; + void beginStruct(Scope& scope); + void endStruct(Scope& scope); - virtual void beginVariant() override; - virtual void endVariant() override; + void beginVariant(Scope& scope); + void endVariant(Scope& scope); - virtual void handleFieldKey(char const* name, Int index) override; + void handleFieldKey(char const* name, Int index); - virtual void beginTuple() override; - virtual void endTuple() override; + void beginTuple(Scope& scope); + void endTuple(Scope& scope); - virtual void beginOptional() override; - virtual void endOptional() override; + void beginOptional(Scope& scope); + void endOptional(Scope& scope); - virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; + void handleSharedPtr(void*& value, SerializerCallback callback, void* context); + void handleUniquePtr(void*& value, SerializerCallback callback, void* context); - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) - override; + void handleDeferredObjectContents(void* valuePtr, SerializerCallback callback, void* context); }; using ReadContext = SerialReader::ReadContext; +SLANG_FORCE_INLINE Fossil::AnyValPtr SerialReader::readValPtr() +{ + return _readValPtr(); +} + +SLANG_FORCE_INLINE SerializationMode SerialReader::getMode() +{ + return SerializationMode::Read; +} + +SLANG_FORCE_INLINE void SerialReader::handleBool(bool& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleInt8(int8_t& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleInt16(int16_t& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleInt32(Int32& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleInt64(Int64& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleUInt8(uint8_t& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleUInt16(uint16_t& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleUInt32(UInt32& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleUInt64(UInt64& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleFloat32(float& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::handleFloat64(double& value) +{ + _handleSimpleVal(value); +} + +SLANG_FORCE_INLINE void SerialReader::endArray(Scope& scope) +{ + _popState(scope); +} + +SLANG_FORCE_INLINE void SerialReader::endDictionary(Scope& scope) +{ + _popState(scope); +} + +SLANG_FORCE_INLINE bool SerialReader::hasElements() +{ + return getState().remainingValueCount != 0; +} + +SLANG_FORCE_INLINE void SerialReader::endStruct(Scope& scope) +{ + _popState(scope); +} + +SLANG_FORCE_INLINE void SerialReader::endVariant(Scope& scope) +{ + _popState(scope); +} + +SLANG_FORCE_INLINE 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); +} + +SLANG_FORCE_INLINE void SerialReader::endTuple(Scope& scope) +{ + _popState(scope); +} + +SLANG_FORCE_INLINE void SerialReader::endOptional(Scope& scope) +{ + _popState(scope); +} + +SLANG_FORCE_INLINE void SerialReader::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. + // + handleSharedPtr(value, callback, context); +} + +SLANG_FORCE_INLINE void SerialReader::_pushState(Scope& scope) +{ + scope.savedState = _state; + _state = State(); +} + +SLANG_FORCE_INLINE void SerialReader::_popState(Scope& scope) +{ + _state = scope.savedState; +} + +SLANG_FORCE_INLINE Fossil::AnyValPtr SerialReader::_readValPtr() +{ + SLANG_ASSERT(_state.remainingValueCount > 0); + + // The rest of the `SerialReader` implementation conspires + // to set things up so that the `dataCursor` and `layoutCursor` + // stored in `_state` will always represent the next value + // to be read, so the logic to determine the result of + // this function is trivial. + // + auto dataPtr = _state.dataCursor; + auto layoutPtr = _state.layoutCursor; + + // Currently, the logic to advance the cursor(s) to the next + // value is more complicated, and thus it isn't included in + // the inlined part of the function. + // + _advanceCursor(); + + return AnyValPtr(dataPtr, layoutPtr); +} + } // namespace Fossil } // namespace Slang diff --git a/source/slang/slang-serialize-ir.cpp b/source/slang/slang-serialize-ir.cpp index 52ec0e3f2..594953d7e 100644 --- a/source/slang/slang-serialize-ir.cpp +++ b/source/slang/slang-serialize-ir.cpp @@ -4,6 +4,7 @@ #include "core/slang-blob-builder.h" #include "core/slang-common.h" #include "core/slang-dictionary.h" +#include "core/slang-performance-profiler.h" #include "slang-ir-insts-stable-names.h" #include "slang-ir-insts.h" #include "slang-ir-validate.h" @@ -269,11 +270,11 @@ struct IRSerialWriteContext; // Specialize to the reader/writer for the specific backend we're targeting // instead of ISerializerImpl to avoid some virtual function calls #if USE_RIFF -using IRWriteSerializer = Serializer_<RIFFSerialWriter, IRSerialWriteContext>; -using IRReadSerializer = Serializer_<RIFFSerialReader, IRSerialReadContext>; +using IRWriteSerializer = Serializer<RIFFSerialWriter, IRSerialWriteContext>; +using IRReadSerializer = Serializer<RIFFSerialReader, IRSerialReadContext>; #else -using IRWriteSerializer = Serializer_<Fossil::SerialWriter, IRSerialWriteContext>; -using IRReadSerializer = Serializer_<Fossil::SerialReader, IRSerialReadContext>; +using IRWriteSerializer = Serializer<Fossil::SerialWriter, IRSerialWriteContext>; +using IRReadSerializer = Serializer<Fossil::SerialReader, IRSerialReadContext>; #endif struct IRSerialWriteContext : SourceLocSerialContext @@ -848,6 +849,8 @@ Result readSerializedModuleIR( SerialSourceLocReader* sourceLocReader, RefPtr<IRModule>& outIRModule) { + SLANG_PROFILE; + SLANG_RETURN_ON_FAIL(readSerializedModuleIR_(chunk, session, sourceLocReader, outIRModule)); // diff --git a/source/slang/slang-serialize-riff.cpp b/source/slang/slang-serialize-riff.cpp index fb8acd2bd..ff16dbf53 100644 --- a/source/slang/slang-serialize-riff.cpp +++ b/source/slang/slang-serialize-riff.cpp @@ -144,22 +144,22 @@ void RIFFSerialWriter::_writeFloat(double value) } } -void RIFFSerialWriter::beginArray() +void RIFFSerialWriter::beginArray(Scope&) { _cursor.beginListChunk(RIFFSerial::kArrayFourCC); } -void RIFFSerialWriter::endArray() +void RIFFSerialWriter::endArray(Scope&) { _cursor.endChunk(); } -void RIFFSerialWriter::beginDictionary() +void RIFFSerialWriter::beginDictionary(Scope&) { _cursor.beginListChunk(RIFFSerial::kDictionaryFourCC); } -void RIFFSerialWriter::endDictionary() +void RIFFSerialWriter::endDictionary(Scope&) { _cursor.endChunk(); } @@ -169,24 +169,24 @@ bool RIFFSerialWriter::hasElements() return false; } -void RIFFSerialWriter::beginStruct() +void RIFFSerialWriter::beginStruct(Scope&) { _cursor.beginListChunk(RIFFSerial::kStructFourCC); } -void RIFFSerialWriter::endStruct() +void RIFFSerialWriter::endStruct(Scope&) { _cursor.endChunk(); } -void RIFFSerialWriter::beginVariant() +void RIFFSerialWriter::beginVariant(Scope& scope) { - beginStruct(); + beginStruct(scope); } -void RIFFSerialWriter::endVariant() +void RIFFSerialWriter::endVariant(Scope& scope) { - endStruct(); + endStruct(scope); } void RIFFSerialWriter::handleFieldKey(char const* name, Int index) @@ -197,27 +197,27 @@ void RIFFSerialWriter::handleFieldKey(char const* name, Int index) SLANG_UNUSED(index); } -void RIFFSerialWriter::beginTuple() +void RIFFSerialWriter::beginTuple(Scope&) { _cursor.beginListChunk(RIFFSerial::kTupleFourCC); } -void RIFFSerialWriter::endTuple() +void RIFFSerialWriter::endTuple(Scope&) { _cursor.endChunk(); } -void RIFFSerialWriter::beginOptional() +void RIFFSerialWriter::beginOptional(Scope&) { _cursor.beginListChunk(RIFFSerial::kOptionalFourCC); } -void RIFFSerialWriter::endOptional() +void RIFFSerialWriter::endOptional(Scope&) { _cursor.endChunk(); } -void RIFFSerialWriter::handleSharedPtr(void*& value, Callback callback, void* context) +void RIFFSerialWriter::handleSharedPtr(void*& value, SerializerCallback callback, void* context) { // Because we are writing, we only care about the // pointer that is already present in `value`. @@ -283,7 +283,7 @@ void RIFFSerialWriter::handleSharedPtr(void*& value, Callback callback, void* co _objects.add(objectInfo); } -void RIFFSerialWriter::handleUniquePtr(void*& value, Callback callback, void* context) +void RIFFSerialWriter::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. @@ -293,7 +293,7 @@ void RIFFSerialWriter::handleUniquePtr(void*& value, Callback callback, void* co void RIFFSerialWriter::handleDeferredObjectContents( void* valuePtr, - Callback callback, + SerializerCallback callback, void* context) { // Because we are already deferring writing of the *entirety* of @@ -485,23 +485,22 @@ void RIFFSerialReader::handleString(String& value) _advanceCursor(); } -void RIFFSerialReader::beginArray() +void RIFFSerialReader::beginArray(Scope&) { _beginListChunk(RIFFSerial::kArrayFourCC); } -void RIFFSerialReader::endArray() +void RIFFSerialReader::endArray(Scope&) { _endListChunk(); } - -void RIFFSerialReader::beginDictionary() +void RIFFSerialReader::beginDictionary(Scope&) { _beginListChunk(RIFFSerial::kDictionaryFourCC); } -void RIFFSerialReader::endDictionary() +void RIFFSerialReader::endDictionary(Scope&) { _endListChunk(); } @@ -511,24 +510,24 @@ bool RIFFSerialReader::hasElements() return _cursor.get() != nullptr; } -void RIFFSerialReader::beginStruct() +void RIFFSerialReader::beginStruct(Scope&) { _beginListChunk(RIFFSerial::kStructFourCC); } -void RIFFSerialReader::endStruct() +void RIFFSerialReader::endStruct(Scope&) { _endListChunk(); } -void RIFFSerialReader::beginVariant() +void RIFFSerialReader::beginVariant(Scope& scope) { - beginStruct(); + beginStruct(scope); } -void RIFFSerialReader::endVariant() +void RIFFSerialReader::endVariant(Scope& scope) { - endStruct(); + endStruct(scope); } void RIFFSerialReader::handleFieldKey(char const* name, Int index) @@ -539,22 +538,22 @@ void RIFFSerialReader::handleFieldKey(char const* name, Int index) SLANG_UNUSED(index); } -void RIFFSerialReader::beginTuple() +void RIFFSerialReader::beginTuple(Scope&) { _beginListChunk(RIFFSerial::kTupleFourCC); } -void RIFFSerialReader::endTuple() +void RIFFSerialReader::endTuple(Scope&) { _endListChunk(); } -void RIFFSerialReader::beginOptional() +void RIFFSerialReader::beginOptional(Scope&) { _beginListChunk(RIFFSerial::kOptionalFourCC); } -void RIFFSerialReader::endOptional() +void RIFFSerialReader::endOptional(Scope&) { _endListChunk(); } @@ -572,7 +571,7 @@ RIFFSerialReader::ObjectIndex RIFFSerialReader::_readObjectReference() return objectIndex; } -void RIFFSerialReader::handleSharedPtr(void*& value, Callback callback, void* context) +void RIFFSerialReader::handleSharedPtr(void*& value, SerializerCallback callback, void* context) { // The logic here largely mirrors what appears in // `RIFFSerialWriter::handleSharedPtr`. @@ -695,7 +694,7 @@ void RIFFSerialReader::handleSharedPtr(void*& value, Callback callback, void* co value = objectInfo.ptr; } -void RIFFSerialReader::handleUniquePtr(void*& value, Callback callback, void* userData) +void RIFFSerialReader::handleUniquePtr(void*& value, SerializerCallback callback, void* userData) { // We treat all pointers as shared pointers, because there isn't really // an optimized representation we would want to use for the unique case. @@ -705,7 +704,7 @@ void RIFFSerialReader::handleUniquePtr(void*& value, Callback callback, void* us void RIFFSerialReader::handleDeferredObjectContents( void* valuePtr, - Callback callback, + SerializerCallback callback, void* context) { // Unlike the case in `RIFFSerialWriter::handleDeferredObjectContents()`, diff --git a/source/slang/slang-serialize-riff.h b/source/slang/slang-serialize-riff.h index 256c185d4..b305113fa 100644 --- a/source/slang/slang-serialize-riff.h +++ b/source/slang/slang-serialize-riff.h @@ -118,7 +118,7 @@ static const FourCC::RawValue kObjectDefinitionListFourCC = SLANG_FOUR_CC('o', ' } // namespace RIFFSerial /// Serializer implementation for writing to a tree of RIFF chunks. -struct RIFFSerialWriter : ISerializerImpl +struct RIFFSerialWriter { public: /// Construct a writer to append to the given RIFF `chunk`. @@ -160,7 +160,7 @@ private: void* ptr; /// Callback that can be invoked to serialize the object's data. - Callback callback; + SerializerCallback callback; /// Context pointer for `callback` void* context; @@ -196,56 +196,62 @@ private: // of the `ISerializerImpl` interface: // - virtual SerializationMode getMode() override; + SerializationMode getMode(); - virtual void handleBool(bool& value) override; + void handleBool(bool& value); - virtual void handleInt8(int8_t& value) override; - virtual void handleInt16(int16_t& value) override; - virtual void handleInt32(Int32& value) override; - virtual void handleInt64(Int64& value) override; + void handleInt8(int8_t& value); + void handleInt16(int16_t& value); + void handleInt32(Int32& value); + void handleInt64(Int64& value); - virtual void handleUInt8(uint8_t& value) override; - virtual void handleUInt16(uint16_t& value) override; - virtual void handleUInt32(UInt32& value) override; - virtual void handleUInt64(UInt64& value) override; + void handleUInt8(uint8_t& value); + void handleUInt16(uint16_t& value); + void handleUInt32(UInt32& value); + void handleUInt64(UInt64& value); - virtual void handleFloat32(float& value) override; - virtual void handleFloat64(double& value) override; + void handleFloat32(float& value); + void handleFloat64(double& value); - virtual void handleString(String& value) override; + void handleString(String& value); - virtual void beginArray() override; - virtual void endArray() override; + struct Scope + { + // The RIFF serialization back-end is currently + // not taking advantage of the `Scope` facility + // in the serialization framework. + }; + + void beginArray(Scope&); + void endArray(Scope&); - virtual void beginDictionary() override; - virtual void endDictionary() override; + void beginDictionary(Scope&); + void endDictionary(Scope&); - virtual bool hasElements() override; + bool hasElements(); - virtual void beginStruct() override; - virtual void endStruct() override; + void beginStruct(Scope&); + void endStruct(Scope&); - virtual void beginVariant() override; - virtual void endVariant() override; + void beginVariant(Scope&); + void endVariant(Scope&); - virtual void handleFieldKey(char const* name, Int index) override; + void handleFieldKey(char const* name, Int index); - virtual void beginTuple() override; - virtual void endTuple() override; + void beginTuple(Scope&); + void endTuple(Scope&); - virtual void beginOptional() override; - virtual void endOptional() override; + void beginOptional(Scope&); + void endOptional(Scope&); - virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; + void handleSharedPtr(void*& value, SerializerCallback callback, void* context); + void handleUniquePtr(void*& value, SerializerCallback callback, void* context); - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) - override; + void handleDeferredObjectContents(void* valuePtr, SerializerCallback callback, void* context); }; /// Serializer implementation for reading from a tree of RIFF chunks. -struct RIFFSerialReader : ISerializerImpl +struct RIFFSerialReader { public: /// Construct a reader to read data from the given `chunk`. @@ -355,7 +361,7 @@ private: Cursor savedCursor; /// The callback to apply to read data into the `valuePtr` - Callback callback; + SerializerCallback callback; /// The context pointer for the `callback`. void* context; @@ -399,52 +405,58 @@ private: // of the `ISerializerImpl` interface: // - virtual SerializationMode getMode() override; + SerializationMode getMode(); - virtual void handleBool(bool& value) override; + void handleBool(bool& value); - virtual void handleInt8(int8_t& value) override; - virtual void handleInt16(int16_t& value) override; - virtual void handleInt32(Int32& value) override; - virtual void handleInt64(Int64& value) override; + void handleInt8(int8_t& value); + void handleInt16(int16_t& value); + void handleInt32(Int32& value); + void handleInt64(Int64& value); - virtual void handleUInt8(uint8_t& value) override; - virtual void handleUInt16(uint16_t& value) override; - virtual void handleUInt32(UInt32& value) override; - virtual void handleUInt64(UInt64& value) override; + void handleUInt8(uint8_t& value); + void handleUInt16(uint16_t& value); + void handleUInt32(UInt32& value); + void handleUInt64(UInt64& value); - virtual void handleFloat32(float& value) override; - virtual void handleFloat64(double& value) override; + void handleFloat32(float& value); + void handleFloat64(double& value); - virtual void handleString(String& value) override; + void handleString(String& value); + + struct Scope + { + // The RIFF serialization back-end is currently + // not taking advantage of the `Scope` facility + // in the serialization framework. + }; - virtual void beginArray() override; - virtual void endArray() override; + void beginArray(Scope&); + void endArray(Scope&); - virtual void beginDictionary() override; - virtual void endDictionary() override; + void beginDictionary(Scope&); + void endDictionary(Scope&); - virtual bool hasElements() override; + bool hasElements(); - virtual void beginStruct() override; - virtual void endStruct() override; + void beginStruct(Scope&); + void endStruct(Scope&); - virtual void beginVariant() override; - virtual void endVariant() override; + void beginVariant(Scope&); + void endVariant(Scope&); - virtual void handleFieldKey(char const* name, Int index) override; + void handleFieldKey(char const* name, Int index); - virtual void beginTuple() override; - virtual void endTuple() override; + void beginTuple(Scope&); + void endTuple(Scope&); - virtual void beginOptional() override; - virtual void endOptional() override; + void beginOptional(Scope&); + void endOptional(Scope&); - virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; + void handleSharedPtr(void*& value, SerializerCallback callback, void* context); + void handleUniquePtr(void*& value, SerializerCallback callback, void* context); - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) - override; + void handleDeferredObjectContents(void* valuePtr, SerializerCallback callback, void* context); }; } // namespace Slang diff --git a/source/slang/slang-serialize.h b/source/slang/slang-serialize.h index 261f3f2d6..4aad5a4f2 100644 --- a/source/slang/slang-serialize.h +++ b/source/slang/slang-serialize.h @@ -21,26 +21,11 @@ // SomeObject* object; // }; // -// then you can declare serialization support for your type +// then you can implement serialization support for your type // with something like: // -// // my-thing.h -// ... -// #include "slang-serialize.h" -// ... -// -// struct MyThing { ... } -// -// void serialize(Serializer const& serializer, MyThing& value); -// -// and then implement that support with something like: -// -// // my-thing.cpp -// #include "my-thing.h" -// -// ... -// -// void serialize(Serializer const& serializer, MyThing& value) +// template<typename S> +// void serialize(S const& serializer, MyThing& value) // { // SLANG_SCOPED_SERIALIZER_STRUCT(serializer); // serialize(serializer, value.a); @@ -81,17 +66,56 @@ enum class SerializationMode }; // -// In order to support different serialized formats, and to -// abstract over the difference between reading and writing, -// we define a base interface for serialization. This interface -// is somewhat user-unfriendly, and is *not* intended for -// ordinary code to interface with directly. +// Complex pointer-based object graphs can be challenging for +// a serialization system. Naively written recursive serialization +// logic can lead to extremely deep call graphs at runtime +// (potentially overflowing the stack), and when deserialization +// needs to interact with systems for caching and/or deduplicating +// objects, the ordering constraints imposed by those systems +// must be respected. +// +// This serialization system relies on callback functions in +// a few places to allow serialization actions to be queued up or +// otherwise rescheduled to avoid these problems. +// + +/// Callback type used by the serialization infrastructure. +/// +/// Users typically will never define callbacks manually; the +/// system creates and manages them behind the scenes, based +/// on user-defined types. +/// +typedef void (*SerializerCallback)(void* valuePtr, void* impl, void* context); + +// +// In order to achieve the best possible performance, +// serialization functions need to be statically specialized +// to the particular data format being used. To that +// end, concrete serialization formats are expected to +// model the right concept (provide the right methods, +// type declarations, etc.), but there is not a single +// base class or interface that they all *must* inherit from. +// +// However, to help illustrate the requirements for a +// complete serializer implementation, we will declare an +// interface that helps illustrate the outline of what +// a serializer implementation needs to provide. +// +// Note: this interface is somewhat user-unfriendly. It +// is *not* intended to be something that user code would +// usually interact with directly, and instead is intended +// to define only the operations that a serialization +// back-end must support. // /// Base interface for serialization. /// /// Can be used for both reading and writing of serialized data. /// +/// Note that implementations of serialization back-ends do +/// *not* need to inherit from this type; it currently serves +/// only to define the requirements. +/// struct ISerializerImpl { /// Get the mode that this serializer is operating in (reading or writing). @@ -217,6 +241,35 @@ struct ISerializerImpl /// virtual void handleString(String& value) = 0; + // + // Every concrete serializer implementation should define + // its own `Scope` type, which will be passed as the + // operand to the various paired `begin`/`end` operations + // below. Code that is calling into a serializer takes + // responsibility for maintaining the `Scope` for the + // duration of the `begin`/`end` pair, and passing the + // same data in to `end` that was passed into `begin`, + // without making any modifications of its own. + // + // An implementation may be able to use the `Scope` to + // store information related to the hierarchy of `begin` + // and `end` operations, and thus avoid needing to + // maintain its own heap-allocated stack structure. + // + struct Scope + { + // The implementation of `Scope` in this placeholder + // interface is intended to define a kind of upper + // bound on the size of reasonable scope representations. + // + // The intention is that any concrete implementation + // could be wrapped up as an `ISerializerImpl`, so + // long as its `Scope` fits within the storage limit + // defined here. + // + void* storage[8]; + }; + /// Begin serializing an array value. /// /// An array should be used to serialize an @@ -235,10 +288,10 @@ struct ISerializerImpl /// and serialize values in a loop until `hasElements()` /// returns `false`. /// - virtual void beginArray() = 0; + virtual void beginArray(Scope&) = 0; /// End serializing an array value. - virtual void endArray() = 0; + virtual void endArray(Scope&) = 0; /// Begin serializing an optional value. /// @@ -258,10 +311,10 @@ struct ISerializerImpl /// test whether the serialized optional has a value and, /// if it does, read the value before calling `endOptional()`. /// - virtual void beginOptional() = 0; + virtual void beginOptional(Scope&) = 0; /// End serializing an optional value. - virtual void endOptional() = 0; + virtual void endOptional(Scope&) = 0; /// Begin serializing a dictionary value. /// @@ -285,10 +338,10 @@ struct ISerializerImpl /// and serialize values in a loop until `hasElements()` /// returns `false`. /// - virtual void beginDictionary() = 0; + virtual void beginDictionary(Scope&) = 0; /// End serializing a dictionary value. - virtual void endDictionary() = 0; + virtual void endDictionary(Scope&) = 0; /// Check whether there are elements remaining to be read /// from a serialized container. @@ -309,10 +362,10 @@ struct ISerializerImpl /// to `hasElements()` are allowed between `beginTuple()` /// and `endTuple()`. /// - virtual void beginTuple() = 0; + virtual void beginTuple(Scope&) = 0; /// End serializing a tuple value. - virtual void endTuple() = 0; + virtual void endTuple(Scope&) = 0; /// Begin serializing a struct value. @@ -330,10 +383,10 @@ struct ISerializerImpl /// they were written, and how to handle attempts /// to read a field that was not written. /// - virtual void beginStruct() = 0; + virtual void beginStruct(Scope&) = 0; /// End serializing a struct value. - virtual void endStruct() = 0; + virtual void endStruct(Scope&) = 0; /// Begin serializing a variant value. /// @@ -354,10 +407,10 @@ struct ISerializerImpl /// struct always including the same members in the same /// order. /// - virtual void beginVariant() = 0; + virtual void beginVariant(Scope&) = 0; /// End serializing a variant value. - virtual void endVariant() = 0; + virtual void endVariant(Scope&) = 0; /// Set the key for the next struct field to be serialized. /// @@ -433,11 +486,12 @@ struct ISerializerImpl }; // -// Rather than interact with instances of `ISerializerImpl` directly, +// Rather than interface with serialization back-ends directly, // most client code will use a wrapper type that amounts to a kind // of smart pointer. // -// While the `ISerializerImpl` interface can cover a wide range of +// While serialization back-ends providing operations comparable to +// `ISerializerImpl` above can cover a wide range of // types that need to be serialized, it is common for types to require // more specific *context* to be available in order to perform serialization. // For example, code might need access to a factory object in order @@ -451,35 +505,40 @@ struct ISerializerImpl /// Base type for serialization contexts. /// -/// The type parameter `Impl` should be a type that derives from -/// `ISerializerImpl`, and the `Context` type parameter can be any -/// type that passes along additional context information needed. +/// The type parameter `I` should be the serialization back-end +/// implementation (e.g., `ISerializerImpl`), while the `C` type +/// parameter can be any type that carries additional context +/// information needed. /// -template<typename Impl, typename Context> -struct SerializerBase +template<typename I, typename C> +struct Serializer { public: - SerializerBase() = default; - SerializerBase(Impl* impl, Context* context = nullptr) + using Impl = I; + using Context = C; + + SLANG_FORCE_INLINE Serializer() = default; + SLANG_FORCE_INLINE Serializer(Impl* impl, Context* context = nullptr) : _impl(impl), _context(context) { } - template<typename I, typename C> - SerializerBase( - SerializerBase<I, C> const& serializer, + template<typename SourceImpl, typename SourceContext> + SLANG_FORCE_INLINE Serializer( + Serializer<SourceImpl, SourceContext> const& serializer, std::enable_if_t< - std::is_convertible_v<I*, Impl*> && std::is_convertible_v<C*, Context*>, + std::is_convertible_v<SourceImpl*, Impl*> && + std::is_convertible_v<SourceContext*, Context*>, void>* = nullptr) : _impl(serializer.getImpl()), _context(serializer.getContext()) { } - Impl* getImpl() const { return _impl; } - Context* getContext() const { return _context; } + SLANG_FORCE_INLINE Impl* getImpl() const { return _impl; } + SLANG_FORCE_INLINE Context* getContext() const { return _context; } - Impl* get() const { return _impl; } - Impl* operator->() const { return get(); } + SLANG_FORCE_INLINE Impl* get() const { return _impl; } + SLANG_FORCE_INLINE Impl* operator->() const { return get(); } private: @@ -487,113 +546,109 @@ private: Context* _context = nullptr; }; -/// A serialization context. -/// -/// The type parameter `Impl` should be a type that derives from -/// `ISerializerImpl`, and the `Context` type parameter can be any -/// type that passes along additional context information needed. -/// -template<typename Impl, typename Context> -struct Serializer_ : SerializerBase<Impl, Context> -{ - using SerializerBase<Impl, Context>::SerializerBase; -}; - -/// Default serialization context. -using Serializer = Serializer_<ISerializerImpl, void>; - // // We define namespace-scope functions that mirror some -// of the operations of `ISerializerImpl`, so that they -// can be invoked on any type that is contextually -// convertible to a `Serializer`. This allows users -// to define their own serialization context types while -// still being able to take advantage of the utility -// operations in this file for serializing basic types, -// arrays, dictionaries, etc. +// of the operations of `ISerializerImpl`, to insulate +// user-defined `serialize()` function implementations +// a bit from the details of the serializer concept/interface. // - /// Get the mode of `serializer`. -inline SerializationMode getMode(Serializer const& serializer) +template<typename S> +SLANG_FORCE_INLINE SerializationMode getMode(S const& serializer) { return serializer->getMode(); } /// Check if `serializer` is reading serialized data. -inline bool isReading(Serializer const& serializer) +template<typename S> +SLANG_FORCE_INLINE bool isReading(S const& serializer) { return getMode(serializer) == SerializationMode::Read; } /// Check if `serializer` is writing serialized data. -inline bool isWriting(Serializer const& serializer) +template<typename S> +SLANG_FORCE_INLINE bool isWriting(S const& serializer) { return getMode(serializer) == SerializationMode::Write; } /// Check if `serializer` has more container elements. -inline bool hasElements(Serializer const& serializer) +template<typename S> +SLANG_FORCE_INLINE bool hasElements(S const& serializer) { return serializer->hasElements(); } -inline void serialize(Serializer const& serializer, bool& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, bool& value) { serializer->handleBool(value); } -inline void serialize(Serializer const& serializer, int8_t& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, int8_t& value) { serializer->handleInt8(value); } -inline void serialize(Serializer const& serializer, int16_t& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, int16_t& value) { serializer->handleInt16(value); } -inline void serialize(Serializer const& serializer, Int32& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, Int32& value) { serializer->handleInt32(value); } -inline void serialize(Serializer const& serializer, Int64& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, Int64& value) { serializer->handleInt64(value); } -inline void serialize(Serializer const& serializer, uint8_t& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, uint8_t& value) { serializer->handleUInt8(value); } -inline void serialize(Serializer const& serializer, uint16_t& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, uint16_t& value) { serializer->handleUInt16(value); } -inline void serialize(Serializer const& serializer, UInt32& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, UInt32& value) { serializer->handleUInt32(value); } -inline void serialize(Serializer const& serializer, UInt64& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, UInt64& value) { serializer->handleUInt64(value); } -inline void serialize(Serializer const& serializer, float& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, float& value) { serializer->handleFloat32(value); } -inline void serialize(Serializer const& serializer, double& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, double& value) { serializer->handleFloat64(value); } -inline void serialize(Serializer const& serializer, String& value) +template<typename S> +SLANG_FORCE_INLINE void serialize(S const& serializer, String& value) { serializer->handleString(value); } @@ -604,8 +659,8 @@ inline void serialize(Serializer const& serializer, String& value) /// converting it to/from the given `RawType` for storage /// in the serialized format. /// -template<typename RawType = Int32, typename EnumType> -void serializeEnum(Serializer const& serializer, EnumType& value) +template<typename RawType = Int32, typename S, typename EnumType> +SLANG_FORCE_INLINE void serializeEnum(S const& serializer, EnumType& value) { auto raw = RawType(value); serialize(serializer, raw); @@ -619,136 +674,152 @@ void serializeEnum(Serializer const& serializer, EnumType& value) // each of those types we define a macro to simplify // introducing a coresponding scope. // +// These types handle the details of properly allocating +// and retaining a `Scope` value appropriate to the +// serializer implementation, and passing it into both +// the `begin` and `end` operations. +// +template<typename S> struct ScopedSerializerArray { public: - ScopedSerializerArray(Serializer const& serializer) + SLANG_FORCE_INLINE ScopedSerializerArray(S const& serializer) : _serializer(serializer) { - serializer->beginArray(); + serializer->beginArray(_scope); } - ~ScopedSerializerArray() { _serializer->endArray(); } + SLANG_FORCE_INLINE ~ScopedSerializerArray() { _serializer->endArray(_scope); } private: - Serializer _serializer; + S _serializer; + typename S::Impl::Scope _scope; }; +template<typename S> struct ScopedSerializerDictionary { public: - ScopedSerializerDictionary(Serializer const& serializer) + SLANG_FORCE_INLINE ScopedSerializerDictionary(S const& serializer) : _serializer(serializer) { - serializer->beginDictionary(); + serializer->beginDictionary(_scope); } - ~ScopedSerializerDictionary() { _serializer->endDictionary(); } + SLANG_FORCE_INLINE ~ScopedSerializerDictionary() { _serializer->endDictionary(_scope); } private: - Serializer _serializer; + S _serializer; + typename S::Impl::Scope _scope; }; +template<typename S> struct ScopedSerializerStruct { public: - ScopedSerializerStruct(Serializer const& serializer) + SLANG_FORCE_INLINE ScopedSerializerStruct(S const& serializer) : _serializer(serializer) { - serializer->beginStruct(); + serializer->beginStruct(_scope); } - ~ScopedSerializerStruct() { _serializer->endStruct(); } + SLANG_FORCE_INLINE ~ScopedSerializerStruct() { _serializer->endStruct(_scope); } private: - Serializer _serializer; + S _serializer; + typename S::Impl::Scope _scope; }; +template<typename S> struct ScopedSerializerVariant { public: - ScopedSerializerVariant(Serializer const& serializer) + SLANG_FORCE_INLINE ScopedSerializerVariant(S const& serializer) : _serializer(serializer) { - serializer->beginVariant(); + serializer->beginVariant(_scope); } - ~ScopedSerializerVariant() { _serializer->endVariant(); } + SLANG_FORCE_INLINE ~ScopedSerializerVariant() { _serializer->endVariant(_scope); } private: - Serializer _serializer; + S _serializer; + typename S::Impl::Scope _scope; }; +template<typename S> struct ScopedSerializerTuple { public: - ScopedSerializerTuple(Serializer const& serializer) + SLANG_FORCE_INLINE ScopedSerializerTuple(S const& serializer) : _serializer(serializer) { - serializer->beginTuple(); + serializer->beginTuple(_scope); } - ~ScopedSerializerTuple() { _serializer->endTuple(); } + SLANG_FORCE_INLINE ~ScopedSerializerTuple() { _serializer->endTuple(_scope); } private: - Serializer _serializer; + S _serializer; + typename S::Impl::Scope _scope; }; +template<typename S> struct ScopedSerializerOptional { public: - ScopedSerializerOptional(Serializer const& serializer) + SLANG_FORCE_INLINE ScopedSerializerOptional(S const& serializer) : _serializer(serializer) { - serializer->beginOptional(); + serializer->beginOptional(_scope); } - ~ScopedSerializerOptional() { _serializer->endOptional(); } + SLANG_FORCE_INLINE ~ScopedSerializerOptional() { _serializer->endOptional(_scope); } private: - Serializer _serializer; + S _serializer; + typename S::Impl::Scope _scope; }; -#define SLANG_SCOPED_SERIALIZER_ARRAY(SERIALIZER) \ - ::Slang::ScopedSerializerArray SLANG_CONCAT(_scopedSerializerArray, __LINE__)(SERIALIZER) +#define SLANG_SCOPED_SERIALIZER_ARRAY(SERIALIZER) \ + ::Slang::ScopedSerializerArray<std::remove_reference_t<decltype(SERIALIZER)>> SLANG_CONCAT( \ + _scopedSerializerArray, \ + __LINE__)(SERIALIZER) -#define SLANG_SCOPED_SERIALIZER_DICTIONARY(SERIALIZER) \ - ::Slang::ScopedSerializerDictionary SLANG_CONCAT(_scopedSerializerDictionary, __LINE__)( \ - SERIALIZER) +#define SLANG_SCOPED_SERIALIZER_DICTIONARY(SERIALIZER) \ + ::Slang::ScopedSerializerDictionary<std::remove_reference_t<decltype(SERIALIZER)>> \ + SLANG_CONCAT(_scopedSerializerDictionary, __LINE__)(SERIALIZER) -#define SLANG_SCOPED_SERIALIZER_OPTIONAL(SERIALIZER) \ - ::Slang::ScopedSerializerOptional SLANG_CONCAT(_scopedSerializerOptional, __LINE__)(SERIALIZER) +#define SLANG_SCOPED_SERIALIZER_OPTIONAL(SERIALIZER) \ + ::Slang::ScopedSerializerOptional<std::remove_reference_t<decltype(SERIALIZER)>> SLANG_CONCAT( \ + _scopedSerializerOptional, \ + __LINE__)(SERIALIZER) -#define SLANG_SCOPED_SERIALIZER_STRUCT(SERIALIZER) \ - ::Slang::ScopedSerializerStruct SLANG_CONCAT(_scopedSerializerStruct, __LINE__)(SERIALIZER) +#define SLANG_SCOPED_SERIALIZER_STRUCT(SERIALIZER) \ + ::Slang::ScopedSerializerStruct<std::remove_reference_t<decltype(SERIALIZER)>> SLANG_CONCAT( \ + _scopedSerializerStruct, \ + __LINE__)(SERIALIZER) -#define SLANG_SCOPED_SERIALIZER_VARIANT(SERIALIZER) \ - ::Slang::ScopedSerializerVariant SLANG_CONCAT(_scopedSerializerVariant, __LINE__)(SERIALIZER) +#define SLANG_SCOPED_SERIALIZER_VARIANT(SERIALIZER) \ + ::Slang::ScopedSerializerVariant<std::remove_reference_t<decltype(SERIALIZER)>> SLANG_CONCAT( \ + _scopedSerializerVariant, \ + __LINE__)(SERIALIZER) -#define SLANG_SCOPED_SERIALIZER_TUPLE(SERIALIZER) \ - ::Slang::ScopedSerializerTuple SLANG_CONCAT(_scopedSerializerTuple, __LINE__)(SERIALIZER) +#define SLANG_SCOPED_SERIALIZER_TUPLE(SERIALIZER) \ + ::Slang::ScopedSerializerTuple<std::remove_reference_t<decltype(SERIALIZER)>> SLANG_CONCAT( \ + _scopedSerializerTuple, \ + __LINE__)(SERIALIZER) // // Containers like arrays and dictionaries are more // difficult to serialize than typical user-defined -// types for a few reasons: -// -// * They typically need to have distinct code paths -// for reading and writing, so they don't benefit -// much from having a unified read/write abstraction. -// -// * They need to be written as templates, to abstract -// over the element type, and thus need to be -// defined in headers. -// -// * Because the element type might require a more -// specialized type of serialization context, they -// also need to be templated on the type of the -// serializer itself. -// -// With all that said, the definitions themselves +// types because they typically need to have distinct +// code paths for reading and writing, so they don't +// benefit much from having a unified read/write abstraction. +/// +// That said, the definitions themselves // are fairly straightforward. All we have to do is // branch on whether we are reading or writing and // either iterate over the serialized data to fill @@ -856,7 +927,7 @@ void serialize(S const& serializer, KeyValuePair<K, V>& value) } template<typename S, typename K, typename V> -void serialize(S const& serializer, std::pair<K, V>& value) +SLANG_FORCE_INLINE void serialize(S const& serializer, std::pair<K, V>& value) { SLANG_SCOPED_SERIALIZER_TUPLE(serializer); serialize(serializer, value.first); @@ -864,7 +935,7 @@ void serialize(S const& serializer, std::pair<K, V>& value) } template<typename S, typename K, typename V> -void serialize(S const& serializer, Dictionary<K, V>& value) +SLANG_FORCE_INLINE void serialize(S const& serializer, Dictionary<K, V>& value) { SLANG_SCOPED_SERIALIZER_DICTIONARY(serializer); if (isWriting(serializer)) @@ -957,27 +1028,26 @@ void serialize(S const& serializer, OrderedDictionary<K, V>& value) // template<typename S, typename T> -void serializeObjectContents(S const& serializer, T* value, void*) +SLANG_FORCE_INLINE void serializeObjectContents(S const& serializer, T* value, void*) { serialize(serializer, *value); } -template<typename I, typename C, typename T> +template<typename S, typename T> void _serializeObjectContentsCallback(void* valuePtr, void* impl, void* context) { - Serializer_<I, C> serializer((I*)impl, (C*)context); + S serializer((typename S::Impl*)impl, (typename S::Context*)context); auto value = (T*)valuePtr; serializeObjectContents(serializer, value, (T*)nullptr); } -template<typename I, typename C, typename T> -void deferSerializeObjectContents(Serializer_<I, C> const& serializer, T* value) +template<typename S, typename T> +SLANG_FORCE_INLINE void deferSerializeObjectContents(S const& serializer, T* value) { - ((Serializer)serializer) - ->handleDeferredObjectContents( - value, - _serializeObjectContentsCallback<I, C, T>, - serializer.getContext()); + serializer->handleDeferredObjectContents( + value, + _serializeObjectContentsCallback<S, T>, + serializer.getContext()); } template<typename S, typename T> @@ -990,48 +1060,46 @@ void serializeObject(S const& serializer, T*& value, void*) deferSerializeObjectContents(serializer, value); } -template<typename I, typename C, typename T> +template<typename S, typename T> void _serializeObjectCallback(void* valuePtr, void* impl, void* context) { - Serializer_<I, C> serializer((I*)impl, (C*)context); + S serializer((typename S::Impl*)impl, (typename S::Context*)context); auto& value = *(T**)valuePtr; serializeObject(serializer, value, (T*)nullptr); } -template<typename I, typename C, typename T> -void serializeSharedPtr(Serializer_<I, C> const& serializer, T*& value) +template<typename S, typename T> +SLANG_FORCE_INLINE void serializeSharedPtr(S const& serializer, T*& value) { - ((Serializer)serializer) - ->handleSharedPtr( - *(void**)&value, - _serializeObjectCallback<I, C, T>, - serializer.getContext()); + serializer->handleSharedPtr( + *(void**)&value, + _serializeObjectCallback<S, T>, + serializer.getContext()); } -template<typename I, typename C, typename T> -void serializeUniquePtr(Serializer_<I, C> const& serializer, T*& value) +template<typename S, typename T> +SLANG_FORCE_INLINE void serializeUniquePtr(S const& serializer, T*& value) { - ((Serializer)serializer) - ->handleUniquePtr( - *(void**)&value, - _serializeObjectCallback<I, C, T>, - serializer.getContext()); + serializer->handleUniquePtr( + *(void**)&value, + _serializeObjectCallback<S, T>, + serializer.getContext()); } template<typename S, typename T> -void serializePtr(S const& serializer, T*& value, void*) +SLANG_FORCE_INLINE void serializePtr(S const& serializer, T*& value, void*) { serializeSharedPtr(serializer, value); } template<typename S, typename T> -void serialize(S const& serializer, T*& value) +SLANG_FORCE_INLINE void serialize(S const& serializer, T*& value) { serializePtr(serializer, value, (T*)nullptr); } template<typename S, typename T> -void serialize(S const& serializer, RefPtr<T>& value) +SLANG_FORCE_INLINE void serialize(S const& serializer, RefPtr<T>& value) { T* raw = value; serialize(serializer, raw); |
