diff options
33 files changed, 4040 insertions, 1233 deletions
diff --git a/source/core/core.natvis b/source/core/core.natvis index f2547b3fe..b9e7009e4 100644 --- a/source/core/core.natvis +++ b/source/core/core.natvis @@ -102,6 +102,14 @@ </Expand> </Type> +<Type Name="Slang::RelativePtr<*,*>"> + <SmartPointer Usage="Minimal">_offset == 0 ? nullptr : ($T1*)((char*)this + _offset)</SmartPointer> + <DisplayString Condition="_offset == 0">{($T1*)0}</DisplayString> + <DisplayString Condition="_offset != 0">{($T1*)((char*)this + _offset)}</DisplayString> + <Expand> + <ExpandedItem>_offset == 0 ? nullptr : ($T1*)((char*)this + _offset)</ExpandedItem> + </Expand> +</Type> <Type Name="Slang::Safe32Ptr<*>"> <Expand> diff --git a/source/core/slang-string.cpp b/source/core/slang-string.cpp index e9804eaa8..a0612ccda 100644 --- a/source/core/slang-string.cpp +++ b/source/core/slang-string.cpp @@ -727,6 +727,22 @@ UnownedStringSlice UnownedStringSlice::subString(Index idx, Index len) const return UnownedStringSlice(m_begin + idx, m_begin + idx + len); } +int compare(UnownedStringSlice const& lhs, UnownedStringSlice const& rhs) +{ + auto lhsSize = lhs.getLength(); + auto rhsSize = rhs.getLength(); + + auto lhsData = lhs.begin(); + auto rhsData = rhs.begin(); + + auto sharedPrefixSize = std::min(lhsSize, rhsSize); + int sharedPrefixCmp = memcmp(lhsData, rhsData, sharedPrefixSize); + if (sharedPrefixCmp != 0) + return sharedPrefixCmp; + + return int(lhsSize - rhsSize); +} + bool UnownedStringSlice::operator==(ThisType const& other) const { // Note that memcmp is undefined when passed in null ptrs, so if we want to handle diff --git a/source/core/slang-string.h b/source/core/slang-string.h index 3da0db6b9..6d84a0c95 100644 --- a/source/core/slang-string.h +++ b/source/core/slang-string.h @@ -218,6 +218,14 @@ protected: char const* m_end; }; +/// Three-way comparison of string slices. +/// +/// * Returns 0 if `lhs == rhs` +/// * Returns a value < 0 if `lhs < rhs` +/// * Returns a value > 0 if `lhs > rhs` +/// +int compare(UnownedStringSlice const& lhs, UnownedStringSlice const& rhs); + // A more convenient way to make slices from *string literals* template<size_t SIZE> SLANG_FORCE_INLINE UnownedStringSlice toSlice(const char (&in)[SIZE]) diff --git a/source/slang/slang-ast-base.h b/source/slang/slang-ast-base.h index 5b77f9d53..ac963861a 100644 --- a/source/slang/slang-ast-base.h +++ b/source/slang/slang-ast-base.h @@ -767,7 +767,7 @@ public: /// method, which ensures that the `_prevInContainerWithSameName` fields /// have been properly set for all declarations in that container. /// - Decl* _prevInContainerWithSameName = nullptr; + FIDDLE() Decl* _prevInContainerWithSameName = nullptr; bool isChecked(DeclCheckState state) const { return checkState >= state; } void setCheckState(DeclCheckState state) diff --git a/source/slang/slang-ast-decl.cpp b/source/slang/slang-ast-decl.cpp index f37ebef48..4d9b8718f 100644 --- a/source/slang/slang-ast-decl.cpp +++ b/source/slang/slang-ast-decl.cpp @@ -51,80 +51,167 @@ bool isInterfaceRequirement(Decl* decl) } // -// ContainerDecl +// ContainerDeclDirectMemberDecls // -List<Decl*> const& ContainerDecl::getDirectMemberDecls() +void ContainerDeclDirectMemberDecls::_initForOnDemandDeserialization( + RefObject* deserializationContext, + void const* deserializationData, + Count declCount) { - return _directMemberDecls.decls; + SLANG_ASSERT(deserializationContext); + SLANG_ASSERT(deserializationData); + + SLANG_ASSERT(!decls.getCount()); + SLANG_ASSERT(!onDemandDeserialization.context); + + onDemandDeserialization.context = deserializationContext; + onDemandDeserialization.data = deserializationData; + + for (Index i = 0; i < declCount; ++i) + decls.add(nullptr); } -Count ContainerDecl::getDirectMemberDeclCount() +void ContainerDeclDirectMemberDecls::_readAllSerializedDecls() const { - return _directMemberDecls.decls.getCount(); + SLANG_ASSERT(isUsingOnDemandDeserialization()); + + // We start by querying each of the contained decls + // by index, which should cause the entire `decls` + // array to be filled in. + // + auto declCount = getDeclCount(); + for (Index i = 0; i < declCount; ++i) + { + auto decl = getDecl(i); + SLANG_UNUSED(decl); + } + + // At this point, we have loaded all the information + // that was in the serialized representation, and + // don't need to keep doing on-demand loading. + // Thus, we clear out the pointer to the serialized + // data (which will cause later calls to + // `isDoingOnDemandSerialization()` to return `false`). + // + // Note that we do *not* clear out the `context` pointer + // used for on-demand deserialization, because in the + // case where we are storing the members of a `ModuleDecl`, + // that context will hold the additional state needed to + // look up declarations by their mangled names, and we + // want to retain that state. The + // `isUsingOnDemandDeserializationForExports()` query + // is based on the `context` pointer only, so it will + // continue to return `true`. + // + onDemandDeserialization.data = nullptr; + + _invalidateLookupAccelerators(); } -Decl* ContainerDecl::getDirectMemberDecl(Index index) +List<Decl*> const& ContainerDeclDirectMemberDecls::getDecls() const { - return _directMemberDecls.decls[index]; + if (isUsingOnDemandDeserialization()) + { + _readAllSerializedDecls(); + } + + return decls; } -Decl* ContainerDecl::getFirstDirectMemberDecl() +Count ContainerDeclDirectMemberDecls::getDeclCount() const { - if (getDirectMemberDeclCount() == 0) - return nullptr; - return getDirectMemberDecl(0); + // Note: in the case of on-demand deserialization, + // the number of elements in the `decls` list + // will be correct, although one or more of the + // pointers in it might be null. + // + return decls.getCount(); } -DeclsOfNameList ContainerDecl::getDirectMemberDeclsOfName(Name* name) +Decl* ContainerDeclDirectMemberDecls::getDecl(Index index) const { - return DeclsOfNameList(findLastDirectMemberDeclOfName(name)); + auto decl = decls[index]; + if (!decl && isUsingOnDemandDeserialization()) + { + decl = _readSerializedDeclAtIndex(index); + decls[index] = decl; + } + return decl; } -Decl* ContainerDecl::findLastDirectMemberDeclOfName(Name* name) +Decl* ContainerDeclDirectMemberDecls::findLastDeclOfName(Name* name) const { - _ensureLookupAcceleratorsAreValid(); - if (auto found = _directMemberDecls.accelerators.mapNameToLastDeclOfThatName.tryGetValue(name)) - return *found; + if (isUsingOnDemandDeserialization()) + { + if (auto found = accelerators.mapNameToLastDeclOfThatName.tryGetValue(name)) + return *found; + + Decl* decl = _readSerializedDeclsOfName(name); + accelerators.mapNameToLastDeclOfThatName.add(name, decl); + return decl; + } + else + { + _ensureLookupAcceleratorsAreValid(); + if (auto found = accelerators.mapNameToLastDeclOfThatName.tryGetValue(name)) + return *found; + } return nullptr; } -Decl* ContainerDecl::getPrevDirectMemberDeclWithSameName(Decl* decl) +Dictionary<Name*, Decl*> ContainerDeclDirectMemberDecls::getMapFromNameToLastDeclOfThatName() const { - SLANG_ASSERT(decl); - SLANG_ASSERT(decl->parentDecl == this); + if (isUsingOnDemandDeserialization()) + { + // If we have been using on-demand deserialization, + // then the `mapNameToLastDeclOfThatName` dictionary + // may not accurately reflect the contained declarations. + // We need to force all of the declarations to be + // deserialized immediately, which will also have + // the effect of invalidating the accelerators so + // that they can be rebuilt to contain complete information. + // + _readAllSerializedDecls(); + } _ensureLookupAcceleratorsAreValid(); - return decl->_prevInContainerWithSameName; + return accelerators.mapNameToLastDeclOfThatName; } -void ContainerDecl::addDirectMemberDecl(Decl* decl) -{ - if (!decl) - return; - decl->parentDecl = this; - _directMemberDecls.decls.add(decl); +List<Decl*> const& ContainerDeclDirectMemberDecls::getTransparentDecls() const +{ + if (isUsingOnDemandDeserialization()) + { + if (accelerators.filteredListOfTransparentDecls.getCount() == 0) + { + _readSerializedTransparentDecls(); + } + } + else + { + _ensureLookupAcceleratorsAreValid(); + } + return accelerators.filteredListOfTransparentDecls; } -List<Decl*> const& ContainerDecl::getTransparentDirectMemberDecls() +bool ContainerDeclDirectMemberDecls::isUsingOnDemandDeserialization() const { - _ensureLookupAcceleratorsAreValid(); - return _directMemberDecls.accelerators.filteredListOfTransparentDecls; + return onDemandDeserialization.data != nullptr; } -bool ContainerDecl::_areLookupAcceleratorsValid() +bool ContainerDeclDirectMemberDecls::_areLookupAcceleratorsValid() const { - return _directMemberDecls.accelerators.declCountWhenLastUpdated == - _directMemberDecls.decls.getCount(); + return accelerators.declCountWhenLastUpdated == decls.getCount(); } -void ContainerDecl::_invalidateLookupAccelerators() +void ContainerDeclDirectMemberDecls::_invalidateLookupAccelerators() const { - _directMemberDecls.accelerators.declCountWhenLastUpdated = -1; + accelerators.declCountWhenLastUpdated = -1; } -void ContainerDecl::_ensureLookupAcceleratorsAreValid() +void ContainerDeclDirectMemberDecls::_ensureLookupAcceleratorsAreValid() const { if (_areLookupAcceleratorsValid()) return; @@ -133,24 +220,28 @@ void ContainerDecl::_ensureLookupAcceleratorsAreValid() // the accelerators are entirely invalidated, and must be rebuilt // from scratch. // - if (_directMemberDecls.accelerators.declCountWhenLastUpdated < 0) + if (accelerators.declCountWhenLastUpdated < 0) { - _directMemberDecls.accelerators.declCountWhenLastUpdated = 0; - _directMemberDecls.accelerators.mapNameToLastDeclOfThatName.clear(); - _directMemberDecls.accelerators.filteredListOfTransparentDecls.clear(); + accelerators.declCountWhenLastUpdated = 0; + accelerators.mapNameToLastDeclOfThatName.clear(); + accelerators.filteredListOfTransparentDecls.clear(); } - // are we a generic? - GenericDecl* genericDecl = as<GenericDecl>(this); - - Count memberCount = _directMemberDecls.decls.getCount(); - Count memberCountWhenLastUpdated = _directMemberDecls.accelerators.declCountWhenLastUpdated; + Count memberCount = decls.getCount(); + Count memberCountWhenLastUpdated = accelerators.declCountWhenLastUpdated; SLANG_ASSERT(memberCountWhenLastUpdated >= 0 && memberCountWhenLastUpdated <= memberCount); + // are we a generic? + GenericDecl* genericDecl = nullptr; + if (memberCount > 0) + { + genericDecl = as<GenericDecl>(decls[0]->parentDecl); + } + for (Index i = memberCountWhenLastUpdated; i < memberCount; ++i) { - Decl* memberDecl = _directMemberDecls.decls[i]; + Decl* memberDecl = decls[i]; // Transparent member declarations will go into a separate list, // so that they can be conveniently queried later for lookup @@ -163,7 +254,7 @@ void ContainerDecl::_ensureLookupAcceleratorsAreValid() // if (memberDecl->hasModifier<TransparentModifier>()) { - _directMemberDecls.accelerators.filteredListOfTransparentDecls.add(memberDecl); + accelerators.filteredListOfTransparentDecls.add(memberDecl); } // Members that don't have a name don't go into the lookup dictionary. @@ -190,9 +281,7 @@ void ContainerDecl::_ensureLookupAcceleratorsAreValid() // all of the overloaded functions with a given name. // Decl* prevMemberWithSameName = nullptr; - _directMemberDecls.accelerators.mapNameToLastDeclOfThatName.tryGetValue( - memberName, - prevMemberWithSameName); + accelerators.mapNameToLastDeclOfThatName.tryGetValue(memberName, prevMemberWithSameName); memberDecl->_prevInContainerWithSameName = prevMemberWithSameName; // Whether or not there was a previous declaration with this @@ -200,18 +289,131 @@ void ContainerDecl::_ensureLookupAcceleratorsAreValid() // with that name encountered so far, and it is what we will // store in the lookup dictionary. // - _directMemberDecls.accelerators.mapNameToLastDeclOfThatName[memberName] = memberDecl; + accelerators.mapNameToLastDeclOfThatName[memberName] = memberDecl; } - _directMemberDecls.accelerators.declCountWhenLastUpdated = memberCount; + accelerators.declCountWhenLastUpdated = memberCount; SLANG_ASSERT(_areLookupAcceleratorsValid()); } + +// +// ContainerDecl +// + +List<Decl*> const& ContainerDecl::getDirectMemberDecls() +{ + return _directMemberDecls.getDecls(); +} + +Count ContainerDecl::getDirectMemberDeclCount() +{ + return _directMemberDecls.getDeclCount(); +} + +Decl* ContainerDecl::getDirectMemberDecl(Index index) +{ + return _directMemberDecls.getDecl(index); +} + +Decl* ContainerDecl::getFirstDirectMemberDecl() +{ + if (getDirectMemberDeclCount() == 0) + return nullptr; + return getDirectMemberDecl(0); +} + +DeclsOfNameList ContainerDecl::getDirectMemberDeclsOfName(Name* name) +{ + return DeclsOfNameList(findLastDirectMemberDeclOfName(name)); +} + +Decl* ContainerDecl::findLastDirectMemberDeclOfName(Name* name) +{ + return _directMemberDecls.findLastDeclOfName(name); +} + +Decl* ContainerDecl::getPrevDirectMemberDeclWithSameName(Decl* decl) +{ + SLANG_ASSERT(decl); + SLANG_ASSERT(decl->parentDecl == this); + + if (isUsingOnDemandDeserializationForDirectMembers()) + { + // Note: in the case of on-demand deserialization, + // we trust that the caller has previously + // invoked `findLastDirectMemberDeclOfName()` + // in order to get `decl` (or an earlier + // entry in the same linked list), so that + // the list threaded through the declarations + // of the same name is already set up. + // + // If that is ever *not* the case, then this + // query would end up returning the wrong results. + + return decl->_prevInContainerWithSameName; + } + else + { + _ensureLookupAcceleratorsAreValid(); + return decl->_prevInContainerWithSameName; + } +} + +void ContainerDecl::addDirectMemberDecl(Decl* decl) +{ + if (isUsingOnDemandDeserializationForDirectMembers()) + { + SLANG_UNEXPECTED("this operation shouldn't be performed on deserialized declarations"); + } + + if (!decl) + return; + + decl->parentDecl = this; + _directMemberDecls.decls.add(decl); +} + +List<Decl*> const& ContainerDecl::getTransparentDirectMemberDecls() +{ + return _directMemberDecls.getTransparentDecls(); +} + +bool ContainerDecl::isUsingOnDemandDeserializationForDirectMembers() +{ + return _directMemberDecls.isUsingOnDemandDeserialization(); +} + +bool ModuleDecl::isUsingOnDemandDeserializationForExports() +{ + return _directMemberDecls.onDemandDeserialization.context != nullptr; +} + +bool ContainerDecl::_areLookupAcceleratorsValid() +{ + return _directMemberDecls._areLookupAcceleratorsValid(); +} + +void ContainerDecl::_invalidateLookupAccelerators() +{ + _directMemberDecls._invalidateLookupAccelerators(); +} + +void ContainerDecl::_ensureLookupAcceleratorsAreValid() +{ + _directMemberDecls._ensureLookupAcceleratorsAreValid(); +} + void ContainerDecl:: _invalidateLookupAcceleratorsBecauseUnscopedEnumAttributeWillBeTurnedIntoTransparentModifier( UnscopedEnumAttribute* unscopedEnumAttr, TransparentModifier* transparentModifier) { + if (isUsingOnDemandDeserializationForDirectMembers()) + { + SLANG_UNEXPECTED("this operation shouldn't be performed on deserialized declarations"); + } + SLANG_ASSERT(unscopedEnumAttr); SLANG_ASSERT(transparentModifier); @@ -225,6 +427,11 @@ void ContainerDecl:: _removeDirectMemberConstructorDeclBecauseSynthesizedAnotherDefaultConstructorInstead( ConstructorDecl* decl) { + if (isUsingOnDemandDeserializationForDirectMembers()) + { + SLANG_UNEXPECTED("this operation shouldn't be performed on deserialized declarations"); + } + SLANG_ASSERT(decl); _invalidateLookupAccelerators(); @@ -237,6 +444,11 @@ void ContainerDecl:: VarDecl* oldDecl, PropertyDecl* newDecl) { + if (isUsingOnDemandDeserializationForDirectMembers()) + { + SLANG_UNEXPECTED("this operation shouldn't be performed on deserialized declarations"); + } + SLANG_ASSERT(oldDecl); SLANG_ASSERT(newDecl); SLANG_ASSERT(index >= 0 && index < getDirectMemberDeclCount()); @@ -251,6 +463,11 @@ void ContainerDecl::_insertDirectMemberDeclAtIndexForBitfieldPropertyBackingMemb Index index, VarDecl* backingVarDecl) { + if (isUsingOnDemandDeserializationForDirectMembers()) + { + SLANG_UNEXPECTED("this operation shouldn't be performed on deserialized declarations"); + } + SLANG_ASSERT(backingVarDecl); SLANG_ASSERT(index >= 0 && index <= getDirectMemberDeclCount()); diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h index c46878945..a92f73e2a 100644 --- a/source/slang/slang-ast-decl.h +++ b/source/slang/slang-ast-decl.h @@ -4,6 +4,7 @@ #include "slang-ast-base.h" #include "slang-ast-decl.h.fiddle" +#include "slang-fossil.h" FIDDLE() namespace Slang @@ -34,23 +35,54 @@ class UnresolvedDecl : public Decl struct ContainerDeclDirectMemberDecls { public: - List<Decl*> const& getDecls() const { return decls; } + List<Decl*> const& getDecls() const; - List<Decl*>& _refDecls() { return decls; } + Count getDeclCount() const; + Decl* getDecl(Index index) const; + + Decl* findLastDeclOfName(Name* name) const; + + Dictionary<Name*, Decl*> getMapFromNameToLastDeclOfThatName() const; + + List<Decl*> const& getTransparentDecls() const; + + bool isUsingOnDemandDeserialization() const; + + void _initForOnDemandDeserialization( + RefObject* deserializationContext, + void const* deserializationData, + Count declCount); private: friend class ContainerDecl; + friend class ModuleDecl; friend struct ASTDumpContext; - List<Decl*> decls; + bool _areLookupAcceleratorsValid() const; + void _invalidateLookupAccelerators() const; + void _ensureLookupAcceleratorsAreValid() const; + + void _readSerializedTransparentDecls() const; + Decl* _readSerializedDeclAtIndex(Index index) const; + Decl* _readSerializedDeclsOfName(Name* name) const; + + void _readAllSerializedDecls() const; + + mutable List<Decl*> decls; - struct + mutable struct { Count declCountWhenLastUpdated = 0; Dictionary<Name*, Decl*> mapNameToLastDeclOfThatName; List<Decl*> filteredListOfTransparentDecls; } accelerators; + + mutable struct + { + RefPtr<RefObject> context; + void const* data = nullptr; + } onDemandDeserialization; }; /// A conceptual list of declarations of the same name, in the same container. @@ -199,6 +231,10 @@ class ContainerDecl : public Decl // void addMember(Decl* member) { addDirectMemberDecl(member); } + /// Is this declaration using on-demand deserialization for its direct members? + /// + bool isUsingOnDemandDeserializationForDirectMembers(); + // // NOTE: The operations after this point are *not* considered part of // the public API of `ContainerDecl`, and new code should not be @@ -737,6 +773,14 @@ class ModuleDecl : public NamespaceDeclBase /// This mapping is filled in during semantic checking, as `ExtensionDecl`s get checked. /// FIDDLE() Dictionary<AggTypeDecl*, RefPtr<CandidateExtensionList>> mapTypeToCandidateExtensions; + + /// Is this module using on-demand deserialization for its exports? + /// + bool isUsingOnDemandDeserializationForExports(); + + /// Find a declaration exported from this module by its `mangledName`. + /// + Decl* _findSerializedDeclByMangledExportName(UnownedStringSlice const& mangledName); }; // Represents a transparent scope of declarations that are defined in a single source file. diff --git a/source/slang/slang-ast-expr.h b/source/slang/slang-ast-expr.h index 177feff15..d6027b2b2 100644 --- a/source/slang/slang-ast-expr.h +++ b/source/slang/slang-ast-expr.h @@ -309,13 +309,17 @@ class StaticMemberExpr : public DeclRefExpr SourceLoc memberOperatorLoc; }; +FIDDLE() struct MatrixCoord { + FIDDLE(...) + bool operator==(const MatrixCoord& rhs) const { return row == rhs.row && col == rhs.col; }; bool operator!=(const MatrixCoord& rhs) const { return !(*this == rhs); }; + // Rows and columns are zero indexed - int row; - int col; + FIDDLE() Int32 row; + FIDDLE() Int32 col; }; FIDDLE() @@ -851,40 +855,41 @@ public: }; // The flavour and token describes how this was parsed - Flavor flavor; + FIDDLE() Flavor flavor; // The single token this came from Token token; // If this was a SlangValue or SlangValueAddr or SlangType, then we also // store the expression, which should be a single VarExpr because we only // parse single idents at the moment - Expr* expr = nullptr; + FIDDLE() Expr* expr = nullptr; // If this is part of a bitwise or expression, this will point to the // remaining operands values in such an expression must be of flavour // Literal or NamedValue - List<SPIRVAsmOperand> bitwiseOrWith = List<SPIRVAsmOperand>(); + FIDDLE() List<SPIRVAsmOperand> bitwiseOrWith = List<SPIRVAsmOperand>(); // If this is a named value then we calculate the value here during // checking. If this is an opcode, then the parser will populate this too // (or set it to 0xffffffff); - SpvWord knownValue = 0xffffffff; + FIDDLE() SpvWord knownValue = 0xffffffff; // Although this might be a constant in the source we should actually pass // it as an id created with OpConstant - bool wrapInId = false; + FIDDLE() bool wrapInId = false; // Once we've checked things, the SlangType and BuiltinVar flavour operands // will have this type populated. - TypeExp type = TypeExp(); + FIDDLE() TypeExp type = TypeExp(); }; FIDDLE() struct SPIRVAsmInst { FIDDLE(...) + public: - SPIRVAsmOperand opcode; - List<SPIRVAsmOperand> operands; + FIDDLE() SPIRVAsmOperand opcode; + FIDDLE() List<SPIRVAsmOperand> operands; }; FIDDLE() diff --git a/source/slang/slang-ast-forward-declarations.h b/source/slang/slang-ast-forward-declarations.h index 717bca1d9..32cfd9ef0 100644 --- a/source/slang/slang-ast-forward-declarations.h +++ b/source/slang/slang-ast-forward-declarations.h @@ -1,10 +1,12 @@ // slang-ast-forward-declarations.h #pragma once +#include "../core/slang-basic.h" + namespace Slang { -enum class ASTNodeType +enum class ASTNodeType : Int32 { #if 0 // FIDDLE TEMPLATE: %for _, T in ipairs(Slang.NodeBase.subclasses) do diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h index d05f24e56..e4002c237 100644 --- a/source/slang/slang-ast-support-types.h +++ b/source/slang/slang-ast-support-types.h @@ -232,10 +232,12 @@ FIDDLE() namespace Slang class Val; // Helper type for pairing up a name and the location where it appeared - struct NameLoc + FIDDLE() struct NameLoc { - Name* name; - SourceLoc loc; + FIDDLE(...) + + FIDDLE() Name* name; + FIDDLE() SourceLoc loc; NameLoc() : name(nullptr) @@ -572,10 +574,11 @@ FIDDLE() namespace Slang struct QualType { FIDDLE(...) - Type* type = nullptr; - bool isLeftValue = false; - bool hasReadOnlyOnTarget = false; - bool isWriteOnly = false; + + FIDDLE() Type* type = nullptr; + FIDDLE() bool isLeftValue = false; + FIDDLE() bool hasReadOnlyOnTarget = false; + FIDDLE() bool isWriteOnly = false; QualType() = default; @@ -1571,16 +1574,16 @@ FIDDLE() namespace Slang void add(Decl* decl, RequirementWitness const& witness); // The type that the witness table witnesses conformance to (e.g. an Interface) - Type* baseType; + FIDDLE() Type* baseType; // The type witnessesd by the witness table (a concrete type). - Type* witnessedType; + FIDDLE() Type* witnessedType; // Whether or not this witness table is an extern declaration. - bool isExtern = false; + FIDDLE() bool isExtern = false; // Cached dictionary for looking up satisfying values. - RequirementDictionary m_requirementDictionary; + FIDDLE() RequirementDictionary m_requirementDictionary; RefPtr<WitnessTable> specialize(ASTBuilder* astBuilder, SubstitutionSet const& subst); }; @@ -1634,8 +1637,9 @@ FIDDLE() namespace Slang class DeclAssociation : public RefObject { FIDDLE(...) - DeclAssociationKind kind; - Decl* decl; + + FIDDLE() DeclAssociationKind kind; + FIDDLE() Decl* decl; }; /// A reference-counted object to hold a list of associated decls for a decl. diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp index f0cd32e74..1a70e25d7 100644 --- a/source/slang/slang-check-decl.cpp +++ b/source/slang/slang-check-decl.cpp @@ -3494,14 +3494,8 @@ struct SemanticsDeclDifferentialConformanceVisitor } }; -/// Recursively register any builtin declarations that need to be attached to the `session`. -/// -/// This function should only be needed for declarations in the core module. -/// -static void _registerBuiltinDeclsRec(Session* session, Decl* decl) +void registerBuiltinDecl(SharedASTBuilder* sharedASTBuilder, Decl* decl) { - SharedASTBuilder* sharedASTBuilder = session->m_sharedASTBuilder; - if (auto builtinMod = decl->findModifier<BuiltinTypeModifier>()) { sharedASTBuilder->registerBuiltinDecl(decl, builtinMod); @@ -3514,6 +3508,25 @@ static void _registerBuiltinDeclsRec(Session* session, Decl* decl) { sharedASTBuilder->registerBuiltinRequirementDecl(decl, builtinRequirement); } +} + + +void registerBuiltinDecl(ASTBuilder* astBuilder, Decl* decl) +{ + registerBuiltinDecl(astBuilder->getSharedASTBuilder(), decl); +} + + +/// Recursively register any builtin declarations that need to be attached to the `session`. +/// +/// This function should only be needed for declarations in the core module. +/// +static void _registerBuiltinDeclsRec(Session* session, Decl* decl) +{ + SharedASTBuilder* sharedASTBuilder = session->m_sharedASTBuilder; + + registerBuiltinDecl(sharedASTBuilder, decl); + if (auto containerDecl = as<ContainerDecl>(decl)) { for (auto childDecl : containerDecl->getDirectMemberDecls()) @@ -3535,6 +3548,42 @@ void registerBuiltinDecls(Session* session, Decl* decl) _registerBuiltinDeclsRec(session, decl); } +void _collectBuiltinDeclsThatNeedRegistrationRec(Decl* decl, List<Decl*>& ioDecls) +{ + if (decl->findModifier<BuiltinTypeModifier>()) + { + ioDecls.add(decl); + } + else if (decl->findModifier<MagicTypeModifier>()) + { + ioDecls.add(decl); + } + else if (decl->findModifier<BuiltinRequirementModifier>()) + { + ioDecls.add(decl); + } + + if (auto containerDecl = as<ContainerDecl>(decl)) + { + for (auto childDecl : containerDecl->getDirectMemberDecls()) + { + if (as<ScopeDecl>(childDecl)) + continue; + + _collectBuiltinDeclsThatNeedRegistrationRec(childDecl, ioDecls); + } + } + if (auto genericDecl = as<GenericDecl>(decl)) + { + _collectBuiltinDeclsThatNeedRegistrationRec(genericDecl->inner, ioDecls); + } +} + +void collectBuiltinDeclsThatNeedRegistration(ModuleDecl* moduleDecl, List<Decl*>& outDecls) +{ + _collectBuiltinDeclsThatNeedRegistrationRec(moduleDecl, outDecls); +} + Type* unwrapArrayType(Type* type) { for (;;) diff --git a/source/slang/slang-check-modifier.cpp b/source/slang/slang-check-modifier.cpp index d135744af..7779957ef 100644 --- a/source/slang/slang-check-modifier.cpp +++ b/source/slang/slang-check-modifier.cpp @@ -2225,7 +2225,6 @@ void SemanticsVisitor::checkRayPayloadStructFields(StructDecl* structDecl) { auto readModifier = fieldVarDecl->findModifier<RayPayloadReadSemantic>(); auto writeModifier = fieldVarDecl->findModifier<RayPayloadWriteSemantic>(); - bool hasReadModifier = readModifier != nullptr; bool hasWriteModifier = writeModifier != nullptr; diff --git a/source/slang/slang-check.h b/source/slang/slang-check.h index f4d86cff9..ebeff1afe 100644 --- a/source/slang/slang-check.h +++ b/source/slang/slang-check.h @@ -21,8 +21,13 @@ class TranslationUnitRequest; bool isGlobalShaderParameter(VarDeclBase* decl); bool isFromCoreModule(Decl* decl); +void registerBuiltinDecl(SharedASTBuilder* sharedASTBuilder, Decl* decl); +void registerBuiltinDecl(ASTBuilder* astBuilder, Decl* decl); + void registerBuiltinDecls(Session* session, Decl* decl); +void collectBuiltinDeclsThatNeedRegistration(ModuleDecl* moduleDecl, List<Decl*>& outDecls); + Type* unwrapArrayType(Type* type); Type* unwrapModifiedType(Type* type); diff --git a/source/slang/slang-compiler.h b/source/slang/slang-compiler.h index 43b51b231..cee80f882 100644 --- a/source/slang/slang-compiler.h +++ b/source/slang/slang-compiler.h @@ -1794,7 +1794,16 @@ public: /// Given a mangled name finds the exported NodeBase associated with this module. /// If not found returns nullptr. - NodeBase* findExportFromMangledName(const UnownedStringSlice& slice); + Decl* findExportedDeclByMangledName(const UnownedStringSlice& mangledName); + + /// Ensure that the any accelerator(s) used for `findExportedDeclByMangledName` + /// have already been built. + /// + void ensureExportLookupAcceleratorBuilt(); + + Count getExportedDeclCount(); + Decl* getExportedDecl(Index index); + UnownedStringSlice getExportedDeclMangledName(Index index); /// Get the ASTBuilder ASTBuilder* getASTBuilder() { return m_astBuilder; } @@ -1906,7 +1915,7 @@ private: // Holds map of exported mangled names to symbols. m_mangledExportPool maps names to indices, // and m_mangledExportSymbols holds the NodeBase* values for each index. StringSlicePool m_mangledExportPool; - List<NodeBase*> m_mangledExportSymbols; + List<Decl*> m_mangledExportSymbols; // Source files that have been pulled into the module with `__include`. Dictionary<SourceFile*, FileDecl*> m_mapSourceFileToFileDecl; @@ -2451,6 +2460,7 @@ public: /// Otherwise, return null. /// RefPtr<Module> findOrLoadSerializedModuleForModuleLibrary( + ISlangBlob* blobHoldingSerializedData, ModuleChunk const* moduleChunk, RIFF::ListChunk const* libraryChunk, DiagnosticSink* sink); @@ -2458,6 +2468,7 @@ public: RefPtr<Module> loadSerializedModule( Name* moduleName, const PathInfo& moduleFilePathInfo, + ISlangBlob* blobHoldingSerializedData, ModuleChunk const* moduleChunk, RIFF::ListChunk const* containerChunk, //< The outer container, if there is one. SourceLoc const& requestingLoc, @@ -2466,6 +2477,7 @@ public: SlangResult loadSerializedModuleContents( Module* module, const PathInfo& moduleFilePathInfo, + ISlangBlob* blobHoldingSerializedData, ModuleChunk const* moduleChunk, RIFF::ListChunk const* containerChunk, //< The outer container, if there is one. DiagnosticSink* sink); diff --git a/source/slang/slang-fossil.cpp b/source/slang/slang-fossil.cpp index e3b2e2c9d..204430901 100644 --- a/source/slang/slang-fossil.cpp +++ b/source/slang/slang-fossil.cpp @@ -25,12 +25,12 @@ const char Fossil::Header::kMagic[16] = { '\n' // byte 15 }; -FossilizedValRef getRootValue(ISlangBlob* blob) +Fossil::AnyValPtr getRootValue(ISlangBlob* blob) { return getRootValue(blob->getBufferPointer(), blob->getBufferSize()); } -FossilizedValRef getRootValue(void const* data, Size size) +Fossil::AnyValPtr getRootValue(void const* data, Size size) { if (!data) { @@ -72,9 +72,7 @@ FossilizedValRef getRootValue(void const* data, Size size) SLANG_UNEXPECTED("bad format for fossil"); } - return FossilizedValRef( - rootValueVariant->getContentData(), - rootValueVariant->getContentLayout()); + return getVariantContentPtr(rootValueVariant); } } // namespace Fossil @@ -85,13 +83,13 @@ Size FossilizedStringObj::getSize() const return Size(*sizePtr); } -UnownedTerminatedStringSlice FossilizedStringObj::getValue() const +UnownedTerminatedStringSlice FossilizedStringObj::get() const { auto size = getSize(); return UnownedTerminatedStringSlice((char*)this, size); } -Count FossilizedContainerObj::getElementCount() const +Count FossilizedContainerObjBase::getElementCount() const { auto countPtr = (FossilUInt*)this - 1; return Size(*countPtr); @@ -103,52 +101,18 @@ FossilizedValLayout* FossilizedVariantObj::getContentLayout() const return (*layoutPtrPtr).get(); } -FossilizedValRef getPtrTarget(FossilizedPtrValRef ptrRef) -{ - auto ptrLayout = ptrRef.getLayout(); - auto ptrPtr = ptrRef.getData(); - return FossilizedValRef(ptrPtr->getTargetData(), ptrLayout->elementLayout); -} - -bool hasValue(FossilizedOptionalObjRef optionalRef) -{ - return optionalRef.getData() != nullptr; -} - -FossilizedValRef getValue(FossilizedOptionalObjRef optionalRef) -{ - auto optionalLayout = optionalRef.getLayout(); - auto valuePtr = optionalRef.getData(); - return FossilizedValRef(valuePtr, optionalLayout->elementLayout); -} - -Count getElementCount(FossilizedContainerObjRef containerRef) -{ - if (!containerRef) - return 0; - - auto containerPtr = containerRef.getData(); - return containerPtr->getElementCount(); -} - -FossilizedValRef getElement(FossilizedContainerObjRef containerRef, Index index) +Fossil::AnyValRef Fossil::ValRef<FossilizedContainerObjBase>::getElement(Index index) const { SLANG_ASSERT(index >= 0); - SLANG_ASSERT(index < getElementCount(containerRef)); + SLANG_ASSERT(index < getElementCount()); - auto containerLayout = containerRef.getLayout(); + auto containerLayout = getLayout(); auto elementLayout = containerLayout->elementLayout.get(); auto elementStride = containerLayout->elementStride; - auto elementsPtr = (Byte*)containerRef.getData(); - auto elementPtr = (FossilizedVal*)(elementsPtr + elementStride * index); - return FossilizedValRef(elementPtr, elementLayout); -} - -Count getFieldCount(FossilizedRecordValRef recordRef) -{ - auto recordLayout = recordRef.getLayout(); - return recordLayout->fieldCount; + auto elementsPtr = (Byte*)getDataPtr(); + auto elementPtr = (void*)(elementsPtr + elementStride * index); + return Fossil::AnyValRef(elementPtr, elementLayout); } FossilizedRecordElementLayout* FossilizedRecordLayout::getField(Index index) const @@ -160,28 +124,29 @@ FossilizedRecordElementLayout* FossilizedRecordLayout::getField(Index index) con return fieldsPtr + index; } - -FossilizedValRef getField(FossilizedRecordValRef recordRef, Index index) +Fossil::AnyValRef Fossil::ValRef<FossilizedRecordVal>::getField(Index index) const { SLANG_ASSERT(index >= 0); - SLANG_ASSERT(index < getFieldCount(recordRef)); + SLANG_ASSERT(index < getFieldCount()); - auto recordLayout = recordRef.getLayout(); - auto field = recordLayout->getField(index); + auto recordLayout = getLayout(); + auto fieldInfo = recordLayout->getField(index); - auto fieldsPtr = (Byte*)recordRef.getData(); - auto fieldPtr = (FossilizedVal*)(fieldsPtr + field->offset); - return FossilizedValRef(fieldPtr, field->layout); + auto fieldsPtr = (Byte*)getDataPtr(); + auto fieldPtr = (void*)(fieldsPtr + fieldInfo->offset); + return Fossil::AnyValRef(fieldPtr, fieldInfo->layout); } +#if 0 FossilizedValRef getVariantContent(FossilizedVariantObjRef variantRef) { return getVariantContent(variantRef.getData()); } +#endif -FossilizedValRef getVariantContent(FossilizedVariantObj* variantPtr) +Fossil::AnyValPtr getVariantContentPtr(FossilizedVariantObj* variantPtr) { - return FossilizedValRef(variantPtr->getContentData(), variantPtr->getContentLayout()); + return Fossil::AnyValPtr(variantPtr->getContentDataPtr(), variantPtr->getContentLayout()); } } // namespace Slang diff --git a/source/slang/slang-fossil.h b/source/slang/slang-fossil.h index dcc12bacb..8d2465ddb 100644 --- a/source/slang/slang-fossil.h +++ b/source/slang/slang-fossil.h @@ -18,8 +18,43 @@ #include "../core/slang-relative-ptr.h" +#include <optional> +#include <type_traits> + namespace Slang { + +struct FossilizedPtrLikeLayout; +struct FossilizedRecordLayout; +struct FossilizedValLayout; + +using FossilInt = int32_t; +using FossilUInt = uint32_t; + +/// Kinds of values that can appear in fossilized data. +enum class FossilizedValKind : FossilUInt +{ + Bool, + Int8, + Int16, + Int32, + Int64, + UInt8, + UInt16, + UInt32, + UInt64, + Float32, + Float64, + StringObj, + ArrayObj, + OptionalObj, + DictionaryObj, + Tuple, + Struct, + Ptr, + VariantObj, +}; + // A key part of the fossil representation is the use of *relative pointers*, // so that a fossilized object graph can be traversed dirctly in memory // without having to deserialize any of the intermediate objects. @@ -27,7 +62,17 @@ namespace Slang // Fossil uses 32-bit relative pointers, to keep the format compact. template<typename T> -using FossilizedPtr = RelativePtr32<T>; +struct FossilizedPtr : RelativePtr32<T> +{ +public: + using RelativePtr32<T>::RelativePtr32; + + using Layout = FossilizedPtrLikeLayout; + + static bool isMatchingKind(FossilizedValKind kind) { return kind == FossilizedValKind::Ptr; } +}; + +static_assert(sizeof(FossilizedPtr<void>) == sizeof(uint32_t)); // Various other parts of the format need to store offsets or counts, // and for consistency we will store them with the same number of @@ -37,78 +82,218 @@ using FossilizedPtr = RelativePtr32<T>; // pointer size down the line, we define type aliases for the // general-purpose integer types that will be used in fossilized data. -using FossilInt = FossilizedPtr<void>::Offset; -using FossilUInt = FossilizedPtr<void>::UOffset; + +static_assert(sizeof(FossilInt) == sizeof(FossilizedPtr<void>)); +static_assert(sizeof(FossilUInt) == sizeof(FossilizedPtr<void>)); // -// The fossil format supports data that is *self-describing*. +// A "live" type can declare what its fossilized representation +// is by specializing the `FossilizedTypeTraits` template. // -// A `FossilizedValLayout` describes the in-memory layout of a fossilized -// value. Given a `FossilizedValLayout` and a pointer to the data -// for a particular value, it is possible to inspect the structure -// of the fossilized data. +// By default, a type is fossilized as an opaque `FossilizedOpaqueVal` +// if no user-defined specialization is provided. +// + +template<typename T> +struct Fossilized_; + +template<typename T> +struct FossilizedTypeTraits +{ + using FossilizedType = Fossilized_<T>; +}; + +template<typename T> +using Fossilized = FossilizedTypeTraits<T>::FossilizedType; + // -// If all you have is a `FossilizedVal*`, then there is no way to access -// its contents without assuming it is of some particular type and casting -// it. +// In many cases, a new C++ type can be fossilized using +// the same representation as some existing type, so we +// allow them to conveniently declare that fact with +// a macro. // -// A `FossilizedVariantObj` is a fossilized value that is self-describing; -// it stores a (relative) pointer to a layout, which can be used to inspect -// its own data/state. + +#define SLANG_DECLARE_FOSSILIZED_AS(TYPE, FOSSILIZED_AS) \ + template<> \ + struct FossilizedTypeTraits<TYPE> \ + { \ + using FossilizedType = Fossilized<FOSSILIZED_AS>; \ + } + +// +// Another common pattern is when some aggregate type +// can simply be fossilized as one of its members. // -struct FossilizedVal; -struct FossilizedValLayout; -struct FossilizedVariantObj; +#define SLANG_DECLARE_FOSSILIZED_AS_MEMBER(TYPE, MEMBER) \ + template<> \ + struct FossilizedTypeTraits<TYPE> \ + { \ + using FossilizedType = Fossilized<decltype(TYPE::MEMBER)>; \ + } -/// Kinds of values that can appear in fossilized data. -enum class FossilizedValKind : FossilUInt +// +// Simple scalar values are fossilized into a wrapper +// `struct` that contains the underlying value. +// +// The reason to impose the wrapper `struct` is that +// it allows us to control the alignment of the type +// in case it turns out that different targets/compilers +// don't apply the same layout to all of the underlying +// scalar types. +// + +template<typename T, FossilizedValKind Kind> +struct FossilizedSimpleVal { - Bool, - Int8, - Int16, - Int32, - Int64, - UInt8, - UInt16, - UInt32, - UInt64, - Float32, - Float64, - String, - Array, - Optional, - Dictionary, - Tuple, - Struct, - Ptr, - Variant, +public: + using Layout = FossilizedValLayout; + static const FossilizedValKind kKind = Kind; + + T const& get() const { return _value; } + + operator T const&() const { return _value; } + + static bool isMatchingKind(FossilizedValKind kind) { return kind == kKind; } + +private: + T _value; }; -/// Layout information about a fossilized value in memory. -/// -/// -/// Every `FossilizedValLayout` stores the kind of the value. -/// Based on that kind, specific additional fields may be -/// available as part of the layout. -/// -struct FossilizedValLayout +#define SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(TYPE, TAG) \ + template<> \ + struct FossilizedTypeTraits<TYPE> \ + { \ + using FossilizedType = FossilizedSimpleVal<TYPE, FossilizedValKind::TAG>; \ + }; + +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(int8_t, Int8) +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(int16_t, Int16) +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(int32_t, Int32) +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(int64_t, Int64) + +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(uint8_t, UInt8) +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(uint16_t, UInt16) +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(uint32_t, UInt32) +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(uint64_t, UInt64) + +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(float, Float32) +SLANG_DECLARE_FOSSILIZED_SIMPLE_TYPE(double, Float64) + +static_assert(sizeof(Fossilized<int8_t>) == 1); +static_assert(sizeof(Fossilized<int16_t>) == 2); +static_assert(sizeof(Fossilized<int32_t>) == 4); +static_assert(sizeof(Fossilized<int64_t>) == 8); + +static_assert(sizeof(Fossilized<uint8_t>) == 1); +static_assert(sizeof(Fossilized<uint16_t>) == 2); +static_assert(sizeof(Fossilized<uint32_t>) == 4); +static_assert(sizeof(Fossilized<uint64_t>) == 8); + +static_assert(sizeof(Fossilized<float>) == 4); +static_assert(sizeof(Fossilized<double>) == 8); + +// +// The `bool` type shouldn't be fossilized as itself, because +// its layout is not guaranteed to be consistent across targets. +// We instead fossilize it as an underlying `uint8_t`, and convert +// on reads. +// + +template<> +struct Fossilized_<bool> { - FossilizedValKind kind; +public: + using Layout = FossilizedValLayout; + static const FossilizedValKind kKind = FossilizedValKind::Bool; + + bool get() const { return _value != 0; } + + operator bool() const { return get(); } + + static bool isMatchingKind(FossilizedValKind kind) { return kind == kKind; } + +private: + uint8_t _value; }; -struct FossilizedPtrLikeLayout +static_assert(sizeof(Fossilized<bool>) == 1); + +// +// Some simple types can be fossilized as one of the +// scalar types above, with explicit casts between +// the "live" type and the "fossilized" type. +// +// A common example of this is `enum` types. +// + +template<typename LiveType, typename FossilizedAsType> +struct FossilizedViaCastVal { - // Note: we aren't using inheritance in the definitions - // of these types, because per the letter of the law in - // C++, a type is only "standard layout" when there is - // only a single type in the inheritance hierarchy that - // has (non-static) data members. +public: + LiveType get() const { return LiveType(_value.get()); } - FossilizedValKind kind; - FossilizedPtr<FossilizedValLayout> elementLayout; + operator LiveType() const { return get(); } + + +private: + Fossilized<FossilizedAsType> _value; +}; + +// +// By default we assume that an `enum` type should be fossilized +// as a signed 32-bit integer. +// +#define SLANG_DECLARE_FOSSILIZED_ENUM(TYPE) \ + template<> \ + struct Fossilized_<TYPE> : FossilizedViaCastVal<TYPE, int32_t> \ + { \ + }; + +// +// For many of the other kinds of types that get fossilized, +// the in-memory encoding will typically be as a (relative) +// pointer to the actual data. +// +// Here we distinguish between the *value* type (e.g., +// `FossilizedString`) that typically gets stored as the +// field of a record/tuple/whatever, and the *object* type +// that the value type is a (relative) pointer to (e.g., +// `FossilizedStringObj`). +// + +struct FossilizedStringObj +{ +public: + Size getSize() const; + UnownedTerminatedStringSlice get() const; + + operator UnownedTerminatedStringSlice() const { return get(); } + + using Layout = FossilizedValLayout; + + static bool isMatchingKind(FossilizedValKind kind) + { + return kind == FossilizedValKind::StringObj; + } + +private: + // Before the `this` address, there is a `FossilUInt` + // with the size of the string in bytes. + // + // At the `this` address there is a nul-terminated + // sequence of `getSize() + 1` bytes. }; +// +// The array and dictionary types are handled largely +// the same as strings, with the added detail that the +// object type is split into a base type without the +// template parameters, and a subtype that has those +// parameters. The base type enables navigating of +// these containers dynamically, based on layout. +// + struct FossilizedContainerLayout { FossilizedValKind kind; @@ -116,320 +301,824 @@ struct FossilizedContainerLayout FossilUInt elementStride; }; -struct FossilizedRecordElementLayout +struct FossilizedContainerObjBase { - FossilizedPtr<FossilizedValLayout> layout; - FossilUInt offset; +public: + using Layout = FossilizedContainerLayout; + + Count getElementCount() const; + + void const* getBuffer() const { return this; } + + static bool isMatchingKind(FossilizedValKind kind) + { + switch (kind) + { + default: + return false; + + case FossilizedValKind::ArrayObj: + case FossilizedValKind::DictionaryObj: + return true; + } + } + +private: + // Before the `this` address, there is a `FossilUInt` + // with the number of elements. + // + // At the `this` address there is a sequence of + // `getCount()` elements. The layout of those elements + // cannot be determined without having a `FossilizedContainerLayout` + // for this container. }; -struct FossilizedRecordLayout +template<typename T> +struct FossilizedContainerObj : FossilizedContainerObjBase { - FossilizedValKind kind; - FossilUInt fieldCount; +public: +}; - // FossilizedRecordElementLayout elements[]; +struct FossilizedArrayObjBase : FossilizedContainerObjBase +{ +public: + static bool isMatchingKind(FossilizedValKind kind) + { + return kind == FossilizedValKind::ArrayObj; + } +}; - FossilizedRecordElementLayout* getField(Index index) const; +template<typename T> +struct FossilizedArrayObj : FossilizedArrayObjBase +{ +}; + +// +// While we defined the core `FossilizedPtr` type above, there is +// some subtlety involved in defining the way that a C++ pointer +// type like `T*` maps to its fossilized representation via +// `Fossilized<T*>`. The reason for this is that the binary layout +// of fossilized data avoids storing redundant pointers-to-pointers, +// so because a `Dictionary<int, float>` would already be stored +// via an indirection in the binary layout, a pointer type +// `Dictionary<int, float> *` would be stored with the exact same +// binary layout. +// +// When computing what `Fossilized<T*>` is, the result will be +// `FossilizedPtr< FossilizedPtrTarget<T> >`. The `FossilizedPtrTarget<T>` +// template uses a set of helpers defined in a `details` namespace +// to compute the correct target type. +// + +namespace details +{ +// +// By default, a `Fossilized<T*>` will just be a `FossilizedPtr<Fossilized<T>>`. +// +template<typename T> +T fossilizedPtrTargetType(T*, void*); +} // namespace details + +template<typename T> +using FossilizedPtrTarget = decltype(details::fossilizedPtrTargetType( + std::declval<Fossilized<T>*>(), + std::declval<Fossilized<T>*>())); + + +template<typename T> +struct FossilizedTypeTraits<T*> +{ + using FossilizedType = FossilizedPtr<FossilizedPtrTarget<T>>; }; -/// A reference to a fossilized value in memory (of type T), and its layout. -/// template<typename T> -struct FossilizedValRef_ +struct FossilizedTypeTraits<RefPtr<T>> +{ + using FossilizedType = FossilizedPtr<FossilizedPtrTarget<T>>; +}; + + +// +// An optional value is effectively just a pointer, with +// the null case being used to represent the absence of +// a value. +// + +struct FossilizedPtrLikeLayout +{ + // Note: we aren't using inheritance in the definitions + // of these types, because per the letter of the law in + // C++, a type is only "standard layout" when there is + // only a single type in the inheritance hierarchy that + // has (non-static) data members. + + FossilizedValKind kind; + FossilizedPtr<FossilizedValLayout> elementLayout; +}; + +struct FossilizedOptionalObjBase { public: - using Val = T; - using Layout = typename T::Layout; + void* getValue() { return this; } - /// Construct a null reference. - /// - FossilizedValRef_() {} + void const* getValue() const { return this; } - /// Construct a reference to the given `data`, assuming it has the given `layout`. - /// - FossilizedValRef_(T* data, Layout* layout) - : _data(data), _layout(layout) - { - } + using Layout = FossilizedPtrLikeLayout; - /// Get the kind of value being referenced. - /// - /// This reference must not be null. - /// - FossilizedValKind getKind() + static bool isMatchingKind(FossilizedValKind kind) { - SLANG_ASSERT(getLayout()); - return getLayout()->kind; + return kind == FossilizedValKind::OptionalObj; } - /// Get the layout of the value being referenced. - /// - Layout* getLayout() { return _layout; } +private: + // An absent optional is encoded as a null pointer + // (so `this` would be null), while a present value + // is encoded as a pointer to that value. Thus the + // held value is at the same address as `this`. +}; - /// Get a pointer to the value being referenced. - /// - T* getData() { return _data; } +template<typename T> +struct FossilizedOptionalObj : FossilizedOptionalObjBase +{ + T* getValue() { return this; } - operator T*() const { return _data; } + T const* getValue() const { return this; } +}; - T* operator->() { return _data; } +template<typename T> +struct FossilizedOptional +{ +public: + explicit operator bool() const { return _value.get() != nullptr; } + T const& operator*() const { return *_value.get(); } private: - T* _data = nullptr; - Layout* _layout = nullptr; + FossilizedPtr<T> _value; }; -using FossilizedValRef = FossilizedValRef_<FossilizedVal>; +template<typename T> +struct FossilizedTypeTraits<std::optional<T>> +{ + using FossilizedType = FossilizedOptional<FossilizedPtrTarget<T>>; +}; -/// A fossilized value in memory. -/// -/// There isn't a lot that can be done with a bare pointer to -/// a `FossilizedVal`. This type is mostly declared to allow -/// us to make it explicit when a pointer points to a fossilized -/// value (even if we don't know anything about its layout). -/// -struct FossilizedVal +static_assert(sizeof(Fossilized<std::optional<double>>) == sizeof(FossilUInt)); + +// +// With all of the various `Fossilized*Obj` cases defined above, +// we can now define the more direct versions of things that +// apply in the common case. For example, `Fossilized<String>` +// simply maps to the `FossilizedString` type, and the parallels +// are similar for arrays and dictionaries. +// + +struct FossilizedString { public: - using Kind = FossilizedValKind; - using Layout = FossilizedValLayout; + Size getSize() const { return _obj ? _obj->getSize() : 0; } - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) + UnownedTerminatedStringSlice get() const { - SLANG_UNUSED(kind); - return true; + return _obj ? _obj->get() : UnownedTerminatedStringSlice(); } -protected: - FossilizedVal() = default; - FossilizedVal(FossilizedVal const&) = default; - FossilizedVal(FossilizedVal&&) = default; - ~FossilizedVal() = default; + operator UnownedTerminatedStringSlice() const { return get(); } + +private: + FossilizedPtr<FossilizedStringObj> _obj; }; -template<typename T, FossilizedValKind kKind> -struct FossilizedSimpleVal : FossilizedVal +inline int compare(FossilizedString const& lhs, UnownedStringSlice const& rhs) +{ + return compare(lhs.get(), rhs); +} + +inline bool operator==(FossilizedString const& left, UnownedStringSlice const& right) +{ + return left.get() == right; +} + +inline bool operator!=(FossilizedString const& left, UnownedStringSlice const& right) +{ + return left.get() != right; +} + +inline bool operator==(FossilizedStringObj const& left, UnownedStringSlice const& right) +{ + return left.get() == right; +} + +inline bool operator!=(FossilizedStringObj const& left, UnownedStringSlice const& right) +{ + return left.get() != right; +} + +#define SLANG_DECLARE_FOSSILIZED_TYPE(LIVE, FOSSILIZED) \ + template<> \ + struct FossilizedTypeTraits<LIVE> \ + { \ + using FossilizedType = FOSSILIZED; \ + } + +SLANG_DECLARE_FOSSILIZED_TYPE(String, FossilizedString); +SLANG_DECLARE_FOSSILIZED_TYPE(UnownedStringSlice, FossilizedString); +SLANG_DECLARE_FOSSILIZED_TYPE(UnownedTerminatedStringSlice, FossilizedString); + +static_assert(std::is_same_v<Fossilized<String>, FossilizedString>); +static_assert(sizeof(Fossilized<String>) == sizeof(FossilUInt)); + +template<typename T> +struct FossilizedContainer { public: - T getValue() const { return _value; } + Count getElementCount() const + { + if (!_obj) + return 0; + return _obj->getElementCount(); + } + T const* getBuffer() const + { + if (!_obj) + return nullptr; + return (T const*)_obj.get()->getBuffer(); + } - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) { return kind == kKind; } + T const* begin() const { return getBuffer(); } + T const* end() const { return getBuffer() + getElementCount(); } private: - T _value; + FossilizedPtr<FossilizedContainerObj<T>> _obj; }; -using FossilizedInt8Val = FossilizedSimpleVal<int8_t, FossilizedValKind::Int8>; -using FossilizedInt16Val = FossilizedSimpleVal<int16_t, FossilizedValKind::Int16>; -using FossilizedInt32Val = FossilizedSimpleVal<int32_t, FossilizedValKind::Int32>; -using FossilizedInt64Val = FossilizedSimpleVal<int64_t, FossilizedValKind::Int64>; +template<typename T> +struct FossilizedArray : FossilizedContainer<T> +{ +public: + T const& operator[](Index index) const + { + SLANG_ASSERT(index >= 0 && index < this->getElementCount()); + return this->getBuffer()[index]; + } +}; -using FossilizedUInt8Val = FossilizedSimpleVal<uint8_t, FossilizedValKind::UInt8>; -using FossilizedUInt16Val = FossilizedSimpleVal<uint16_t, FossilizedValKind::UInt16>; -using FossilizedUInt32Val = FossilizedSimpleVal<uint32_t, FossilizedValKind::UInt32>; -using FossilizedUInt64Val = FossilizedSimpleVal<uint64_t, FossilizedValKind::UInt64>; +template<typename T> +struct FossilizedTypeTraits<List<T>> +{ + using FossilizedType = FossilizedArray<Fossilized<T>>; +}; -using FossilizedFloat32Val = FossilizedSimpleVal<float, FossilizedValKind::Float32>; -using FossilizedFloat64Val = FossilizedSimpleVal<double, FossilizedValKind::Float64>; +template<typename T, int N> +struct FossilizedTypeTraits<ShortList<T, N>> +{ + using FossilizedType = FossilizedArray<Fossilized<T>>; +}; -struct FossilizedBoolVal : FossilizedVal +template<typename T, size_t N> +struct FossilizedTypeTraits<T[N]> { -public: - bool getValue() const { return _value != 0; } + using FossilizedType = FossilizedArray<Fossilized<T>>; +}; - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) { return kind == Kind::Bool; } +static_assert(sizeof(Fossilized<List<int32_t>>) == sizeof(FossilUInt)); -private: - uint8_t _value; +template<typename K, typename V> +struct FossilizedKeyValuePair +{ + using Layout = FossilizedRecordLayout; + K key; + V value; +}; + +template<typename K, typename V> +struct FossilizedTypeTraits<KeyValuePair<K, V>> +{ + using FossilizedType = FossilizedKeyValuePair<Fossilized<K>, Fossilized<V>>; +}; + +template<typename K, typename V> +struct FossilizedTypeTraits<std::pair<K, V>> +{ + using FossilizedType = FossilizedKeyValuePair<Fossilized<K>, Fossilized<V>>; }; -struct FossilizedPtrVal : FossilizedVal +// +// In terms of the encoding, a fossilized dictionary +// is really just an array of key-value pairs, but +// we keep the types distinct to help with clarity. +// + +struct FossilizedDictionaryObjBase : FossilizedContainerObjBase { public: - using Layout = FossilizedPtrLikeLayout; + static bool isMatchingKind(FossilizedValKind kind) + { + return kind == FossilizedValKind::DictionaryObj; + } +}; - FossilizedVal* getTargetData() const { return _value.get(); } +template<typename K, typename V> +struct FossilizedDictionaryObj : FossilizedDictionaryObjBase +{ +}; + +template<typename K, typename V> +struct FossilizedDictionary : FossilizedContainer<FossilizedKeyValuePair<K, V>> +{ +public: + using Entry = FossilizedKeyValuePair<K, V>; +}; - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) { return kind == Kind::Ptr; } +template<typename K, typename V> +struct FossilizedTypeTraits<Dictionary<K, V>> +{ + using FossilizedType = FossilizedDictionary<Fossilized<K>, Fossilized<V>>; +}; -private: - FossilizedPtr<FossilizedVal> _value; +template<typename K, typename V> +struct FossilizedTypeTraits<OrderedDictionary<K, V>> +{ + using FossilizedType = FossilizedDictionary<Fossilized<K>, Fossilized<V>>; }; +static_assert(sizeof(Fossilized<Dictionary<String, String>>) == sizeof(FossilUInt)); -struct FossilizedRecordVal : FossilizedVal +// +// A record (struct or tuple) is stored simply as a sequence of field +// values, and its layout gives the total number of fields as well as +// the offset and layout of each. +// + +struct FossilizedRecordElementLayout +{ + FossilizedPtr<FossilizedValLayout> layout; + FossilUInt offset; +}; + +struct FossilizedRecordLayout +{ + FossilizedValKind kind; + FossilUInt fieldCount; + + // FossilizedRecordElementLayout elements[]; + + FossilizedRecordElementLayout* getField(Index index) const; +}; + +/// Stand-in for a fossilized record of unknown type. +/// +/// Note that user-defined fossilized types should *not* try +/// to inherit from `FossilizedRecordVal`, as doing so can +/// end up breaking the correlation between the binary layout +/// of fossilized data and the matching C++ declarations. +/// +struct FossilizedRecordVal { public: using Layout = FossilizedRecordLayout; - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) + static bool isMatchingKind(FossilizedValKind kind) { switch (kind) { default: return false; - case Kind::Struct: - case Kind::Tuple: + case FossilizedValKind::Struct: + case FossilizedValKind::Tuple: return true; } } }; // -// Some of the following subtypes of `FossilizedVal` are -// named as `Fossilized*Obj` rather than `Fossilized*Val`, -// to indicate that they will only ever be located on the -// other side of a pointer indirection. -// -// E.g., a field of a fossilized struct value should never -// have a layout claiming it to be of kind `String`; instead -// it should show as a field of kind `Ptr`, where the -// pointed-to type is `String`. The same goes for `Optional`, -// `Array`, and `Dictionary`. -// -// This distinction only matters when dealing with things like -// an *optional* string, because instead of an in-memory -// layout like `Ptr -> Optional -> Ptr -> String`, the fossilized -// data will simply store `Ptr -> Optional -> String`. +// A *variant* is a value that can conceptually hold data of any type/layout, +// and stores a pointer to layout information so that the data it holds +// can be navigated dynamically. // -struct FossilizedStringObj : FossilizedVal +struct FossilizedVariantObj { public: - Size getSize() const; - UnownedTerminatedStringSlice getValue() const; + using Layout = FossilizedValLayout; + static const FossilizedValKind kKind = FossilizedValKind::VariantObj; + + FossilizedValLayout* getContentLayout() const; - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) { return kind == Kind::String; } + void* getContentDataPtr() { return this; } + void const* getContentDataPtr() const { return this; } + + static bool isMatchingKind(FossilizedValKind kind) + { + return kind == FossilizedValKind::VariantObj; + } private: - // Before the `this` address, there is a `FossilUInt` - // with the size of the string in bytes. + // Before the `this` address, there is a `FossilizedPtr<FossilizedValLayout>` + // with the layout of the content. // - // At the `this` address there is a nul-terminated - // serquence of `getSize() + 1` bytes. + // The content itself starts at the `this` address, with its + // layout determined by `getContentLayout()`. }; -struct FossilizedOptionalObj : FossilizedVal +struct FossilizedVariant { public: - using Layout = FossilizedPtrLikeLayout; +private: + FossilizedPtr<FossilizedVariantObj> _obj; +}; - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) { return kind == Kind::Optional; } +static_assert(sizeof(FossilizedVariant) == sizeof(FossilUInt)); - FossilizedVal* getValue() { return this; } +// +// Now that all of the relevant types for fossilized data have been defined, +// we can circle back to define the specializations of `FossilizedPtrTargetType` +// for the types that need it. +// - FossilizedVal const* getValue() const { return this; } +namespace details +{ +template<typename X> +FossilizedStringObj fossilizedPtrTargetType(X*, FossilizedString*); -private: - // An absent optional is encoded as a null pointer - // (so `this` would be null), while a present value - // is encoded as a pointer to that value. Thus the - // held value is at the same address as `this`. +template<typename X> +FossilizedVariantObj fossilizedPtrTargetType(X*, FossilizedVariant*); + +template<typename X, typename T> +FossilizedArrayObj<T> fossilizedPtrTargetType(X*, FossilizedArray<T>*); + +template<typename X, typename K, typename V> +FossilizedDictionaryObj<K, V> fossilizedPtrTargetType(X*, FossilizedDictionary<K, V>*); +} // namespace details + +// +// In addition to being able to expose a statically-known +// layout through `Fossilized<T>`, the fossil format also +// allows data to be *self-describing*, by carying its layout +// with it. +// +// A `FossilizedValLayout` describes the in-memory layout of a fossilized +// value. Given a `FossilizedValLayout` and a pointer to the data +// for a particular value, it is possible to inspect the structure +// of the fossilized data. +// +// If all you have is a `void*` to a fossilzied value, then there is no way +// to access its contents without assuming it is of some particular type and +// casting it. +// +// A `FossilizedVariantObj` is a fossilized value that is self-describing; +// it stores a (relative) pointer to a layout, which can be used to inspect +// its own data/state. +// + +struct FossilizedValLayout; +struct FossilizedPtrLikeLayout; +struct FossilizedContainerLayout; +struct FossilizedRecordLayout; +struct FossilizedVariantObj; + +/// Layout information about a fossilized value in memory. +/// +/// +/// Every `FossilizedValLayout` stores the kind of the value. +/// Based on that kind, specific additional fields may be +/// available as part of the layout. +/// +struct FossilizedValLayout +{ + FossilizedValKind kind; }; -struct FossilizedContainerObj : FossilizedVal +namespace Fossil +{ +/// A reference to a fossilized value in memory, along with layout information. +/// +template<typename T, typename L = typename T::Layout> +struct ValRefBase { public: - using Layout = FossilizedContainerLayout; + using Val = T; + using Layout = L; - Count getElementCount() const; + /// Construct a null reference. + /// + ValRefBase() {} - /// Determine if a value with the given `kind` should be allowed to cast to this type. - static bool _isMatchingKind(Kind kind) + /// Construct a reference to the given `data`, assuming it has the given `layout`. + /// + ValRefBase(T* data, Layout const* layout) + : _data(data), _layout(layout) { - switch (kind) - { - default: - return false; + } - case Kind::Array: - case Kind::Dictionary: - return true; - } + /// Construct a copy of `ref`. + /// + /// 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) + : _data(ref.getDataPtr()), _layout((Layout const*)ref.getLayout()) + { } -private: - // Before the `this` address, there is a `FossilUInt` - // with the number of elements. - // - // At the `this` address there is a sequence of - // `getCount()` elements. The layout of those elements - // cannot be determined without having a `FossilizedContainerLayout` - // for this container. + /// Get a pointer to the value being referenced. + /// + 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 + { + return *_data; + } + + /// Get the layout of the value being referenced. + /// + Layout const* getLayout() const { return _layout; } + + /// Get the kind of value being referenced. + /// + /// This reference must not be null. + /// + FossilizedValKind getKind() const + { + SLANG_ASSERT(getLayout()); + return getLayout()->kind; + } + +protected: + T* _data = nullptr; + Layout const* _layout = nullptr; +}; + +/// A reference to a fossilized value in memory, along with layout information. +/// +template<typename T> +struct ValRef : ValRefBase<T> +{ + using ValRefBase<T>::ValRefBase; }; -struct FossilizedVariantObj : FossilizedVal +/// Specialization of `ValRef<T>` for the case where `T` is `void`. +/// +template<> +struct ValRef<void> : ValRefBase<void, FossilizedValLayout> +{ + using ValRefBase<void, FossilizedValLayout>::ValRefBase; +}; + +/// A pointer to a fossilized value in memory, along with layout information. +/// +template<typename T> +struct ValPtr { public: - FossilizedValLayout* getContentLayout() const; + using TargetVal = T; + using TargetLayout = typename ValRef<T>::Layout; + /// Construct a null pointer. + /// + ValPtr() {} + ValPtr(std::nullptr_t) {} - FossilizedVal* getContentData() { return this; } - FossilizedVal const* getContentData() const { return this; } + /// Construct a pointer to the given `data`, assuming it has the given `layout`. + /// + ValPtr(T* data, TargetLayout const* layout) + : _ref(data, layout) + { + } - static bool _isMatchingKind(Kind kind) { return kind == Kind::Variant; } + /// Construct a pointer to the value referenced by `ref`. + /// + /// This constructor is basically equivalent to the address-of operator `&`. + /// 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) + : _ref(ref) + { + } + + /// Construct a copy of `ptr`. + /// + /// 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) + : _ref(*ptr) + { + } + + /// Get a pointer to the value being referenced. + /// + T* getDataPtr() const { return _ref.getDataPtr(); } + + /// Get the layout of the value being referenced. + /// + TargetLayout* getLayout() const { return _ref.getLayout(); } + + T* get() const { return _ref.getDataPtr(); } + operator T*() const { return get(); } + + /// Deference this `ValPtr` to get a `ValRef`. + /// + ValRef<T> operator*() const { return _ref; } + + /// Deference this `ValPtr` for member access. + /// + /// Note that an overloaded `operator->` must return either + /// a pointer or a type that itself overloads `operator->`. + /// Because `ValRef<T>` is not functionally a "smart pointer" + /// to a `T`, the logical behavior here is that we want + /// `someValPtr->foo` to be equvialent to `someValRef.foo`, + /// where `someValRef` is a reference to the same value + /// that `someValPtr` points to. The correct way to get + /// that behavior is for the `operator->` on `ValPtr` + /// to return a pointer to a `ValRef`. + /// + ValRef<T> const* operator->() const { return &_ref; } private: - // Before the `this` address, there is a `FossilizedPtr<FossilizedValLayout>` - // with the layout of the content. - // - // The content itself starts at the `this` address, with its - // layout determined by `getContentLayout()`. + ValRef<T> _ref; }; -/// Dynamic cast of a reference to a fossilized value. +/// Get a `ValPtr` pointing to the same value as the given `ref`. /// -template<typename T, typename U> -FossilizedValRef_<T> as(FossilizedValRef_<U> valRef) +template<typename T> +inline ValPtr<T> getAddress(ValRef<T> ref) { - if (!valRef || !T::_isMatchingKind(valRef.getKind())) - return FossilizedValRef_<T>(); - - return FossilizedValRef_<T>( - static_cast<T*>(valRef.getData()), - reinterpret_cast<typename T::Layout*>(valRef.getLayout())); + return ValPtr<T>(ref); } -using FossilizedInt8ValRef = FossilizedValRef_<FossilizedInt8Val>; -using FossilizedInt16ValRef = FossilizedValRef_<FossilizedInt16Val>; -using FossilizedInt32ValRef = FossilizedValRef_<FossilizedInt32Val>; -using FossilizedInt64ValRef = FossilizedValRef_<FossilizedInt64Val>; -using FossilizedUInt8ValRef = FossilizedValRef_<FossilizedUInt8Val>; -using FossilizedUInt16ValRef = FossilizedValRef_<FossilizedUInt16Val>; -using FossilizedUInt32ValRef = FossilizedValRef_<FossilizedUInt32Val>; -using FossilizedUInt64ValRef = FossilizedValRef_<FossilizedUInt64Val>; -using FossilizedFloat32ValRef = FossilizedValRef_<FossilizedFloat32Val>; -using FossilizedFloat64ValRef = FossilizedValRef_<FossilizedFloat64Val>; -using FossilizedBoolValRef = FossilizedValRef_<FossilizedBoolVal>; -using FossilizedStringObjRef = FossilizedValRef_<FossilizedStringObj>; -using FossilizedPtrValRef = FossilizedValRef_<FossilizedPtrVal>; -using FossilizedOptionalObjRef = FossilizedValRef_<FossilizedOptionalObj>; -using FossilizedContainerObjRef = FossilizedValRef_<FossilizedContainerObj>; -using FossilizedRecordValRef = FossilizedValRef_<FossilizedRecordVal>; -using FossilizedVariantObjRef = FossilizedValRef_<FossilizedVariantObj>; +using AnyValRef = ValRef<void>; +using AnyValPtr = ValPtr<void>; -FossilizedValRef getPtrTarget(FossilizedPtrValRef ptrRef); +// +// In order to make `ValRef<T>` more usable in contexts where we want +// to make use of the knowledge that it refers to a `T`, we define +// various specializations of `ValRef` for the specific types that +// are relevant for decoding serialized data. +// +// Note that we do not need to define any specializations of +// `ValPtr`, because that is ultimately just a wrapper around +// `ValRef`. +// -bool hasValue(FossilizedOptionalObjRef optionalRef); -FossilizedValRef getValue(FossilizedOptionalObjRef optionalRef); +template<> +struct ValRef<FossilizedStringObj> : ValRefBase<FossilizedStringObj> +{ +public: + using ValRefBase<FossilizedStringObj>::ValRefBase; + + Size getSize() const { return getDataPtr()->getSize(); } + UnownedTerminatedStringSlice get() const { return getDataPtr()->get(); } + + operator UnownedTerminatedStringSlice() const { return get(); } +}; -Count getElementCount(FossilizedContainerObjRef containerRef); -FossilizedValRef getElement(FossilizedContainerObjRef containerRef, Index index); -Count getFieldCount(FossilizedRecordValRef recordRef); -FossilizedValRef getField(FossilizedRecordValRef recordRef, Index index); +template<> +struct ValRef<FossilizedContainerObjBase> : ValRefBase<FossilizedContainerObjBase> +{ +public: + using ValRefBase<FossilizedContainerObjBase>::ValRefBase; -FossilizedValRef getVariantContent(FossilizedVariantObjRef variantRef); -FossilizedValRef getVariantContent(FossilizedVariantObj* variantPtr); + Count getElementCount() const + { + auto data = this->getDataPtr(); + if (!data) + return 0; + return data->getElementCount(); + } + AnyValRef getElement(Index index) const; +}; + + +template<> +struct ValRef<FossilizedArrayObjBase> : ValRefBase<FossilizedArrayObjBase> +{ +public: + using ValRefBase<FossilizedArrayObjBase>::ValRefBase; + + Count getElementCount() const + { + auto data = this->getDataPtr(); + if (!data) + return 0; + return data->getElementCount(); + } + + AnyValRef getElement(Index index) const; +}; + + +template<> +struct ValRef<FossilizedDictionaryObjBase> : ValRefBase<FossilizedDictionaryObjBase> +{ +public: + using ValRefBase<FossilizedDictionaryObjBase>::ValRefBase; + + Count getElementCount() const + { + auto data = this->getDataPtr(); + if (!data) + return 0; + return data->getElementCount(); + } + + AnyValRef getElement(Index index) const; +}; +template<> +struct ValRef<FossilizedOptionalObjBase> : ValRefBase<FossilizedOptionalObjBase> +{ +public: + using ValRefBase<FossilizedOptionalObjBase>::ValRefBase; + + bool hasValue() const { return this->getDataPtr() != nullptr; } + + AnyValRef getValue() const + { + SLANG_ASSERT(hasValue()); + return AnyValRef(this->getDataPtr(), this->getLayout()->elementLayout.get()); + } +}; + +template<> +struct ValRef<FossilizedRecordVal> : ValRefBase<FossilizedRecordVal> +{ +public: + using ValRefBase<FossilizedRecordVal>::ValRefBase; + + Count getFieldCount() const { return getLayout()->fieldCount; } + + AnyValRef getField(Index index) const; +}; + +template<typename T> +struct ValRef<FossilizedPtr<T>> : ValRefBase<FossilizedPtr<T>> +{ +public: + using ValRefBase<FossilizedPtr<T>>::ValRefBase; + + ValRef<T> getTargetValRef() const + { + auto ptrPtr = this->getDataPtr(); + return ValRef<T>(*ptrPtr, this->getLayout()->elementLayout.get()); + } + + ValPtr<T> getTargetValPtr() const { return ValPtr<T>(getTargetValRef()); } + + // ValRef<T> operator*() const; +}; + +// +// We support both static and dynamic casting of `ValPtr`s +// to fossilized data. In the dynamic case, the layout +// information associated with the pointer is used to +// determine if the cast is allowed. +// + +/// Statically cast a pointer to a fossilized value. +/// +template<typename T> +ValPtr<T> cast(AnyValPtr valPtr) +{ + if (!valPtr) + return ValPtr<T>(); + return ValPtr<T>( + static_cast<T*>(valPtr.getDataPtr()), + (typename T::Layout*)(valPtr->getLayout())); +} + +/// Dynamic cast of a pointer to a fossilized value. +/// +template<typename T> +ValPtr<T> as(AnyValPtr valPtr) +{ + if (!valPtr || !T::isMatchingKind(valPtr->getKind())) + { + return nullptr; + } + + return ValPtr<T>( + static_cast<T*>(valPtr.getDataPtr()), + (typename T::Layout*)(valPtr->getLayout())); +} + +} // namespace Fossil + +/// Get a dynamically-typed pointer to the content of a fossilized variant. +/// +/// This operation does not require a dynamically-typed `Fossil::ValPtr` +/// or `Fossil::ValRef` as input, because it makes use of the way that +/// a fossilized variant stores a (relative) pointer to the layout of +/// its content. +/// +Fossil::AnyValPtr getVariantContentPtr(FossilizedVariantObj* variantPtr); namespace Fossil { @@ -466,13 +1155,15 @@ struct Header FossilizedPtr<FossilizedVariantObj> rootValue; }; +static_assert(sizeof(Header) == 32); + /// Get the root object from a fossilized blob. /// /// This operation performs some basic validation on the blob to /// ensure that it doesn't seem incorrectly sized or otherwise /// corrupted/malformed. /// -FossilizedValRef getRootValue(ISlangBlob* blob); +Fossil::AnyValPtr getRootValue(ISlangBlob* blob); /// Get the root object from a fossilized blob. /// @@ -480,7 +1171,7 @@ FossilizedValRef getRootValue(ISlangBlob* blob); /// ensure that it doesn't seem incorrectly sized or otherwise /// corrupted/malformed. /// -FossilizedValRef getRootValue(void const* data, Size size); +Fossil::AnyValPtr getRootValue(void const* data, Size size); } // namespace Fossil } // namespace Slang diff --git a/source/slang/slang-module-library.cpp b/source/slang/slang-module-library.cpp index c3f4a1349..df3ae3687 100644 --- a/source/slang/slang-module-library.cpp +++ b/source/slang/slang-module-library.cpp @@ -39,6 +39,7 @@ void* ModuleLibrary::castAs(const Guid& guid) } SlangResult loadModuleLibrary( + ISlangBlob* blobHoldingSerializedData, const Byte* inData, size_t dataSize, String path, @@ -69,8 +70,11 @@ SlangResult loadModuleLibrary( for (auto moduleChunk : container->getModules()) { - auto loadedModule = - linkage->findOrLoadSerializedModuleForModuleLibrary(moduleChunk, container, sink); + auto loadedModule = linkage->findOrLoadSerializedModuleForModuleLibrary( + blobHoldingSerializedData, + moduleChunk, + container, + sink); if (!loadedModule) return SLANG_FAIL; @@ -111,6 +115,7 @@ SlangResult loadModuleLibrary( // Load the module ComPtr<IModuleLibrary> library; SLANG_RETURN_ON_FAIL(loadModuleLibrary( + blob, (const Byte*)blob->getBufferPointer(), blob->getBufferSize(), path, diff --git a/source/slang/slang-module-library.h b/source/slang/slang-module-library.h index 836366e93..2c25a8fb7 100644 --- a/source/slang/slang-module-library.h +++ b/source/slang/slang-module-library.h @@ -46,6 +46,7 @@ public: }; SlangResult loadModuleLibrary( + ISlangBlob* blobHoldingSerializedData, const Byte* inBytes, size_t bytesCount, String Path, diff --git a/source/slang/slang-serialize-ast.cpp b/source/slang/slang-serialize-ast.cpp index c5e54b835..1c05e3ce2 100644 --- a/source/slang/slang-serialize-ast.cpp +++ b/source/slang/slang-serialize-ast.cpp @@ -2,117 +2,985 @@ #include "slang-serialize-ast.h" #include "slang-ast-dispatch.h" +#include "slang-check.h" #include "slang-compiler.h" #include "slang-diagnostics.h" #include "slang-mangle.h" +#include "slang-parser.h" +#include "slang-serialize-ast.cpp.fiddle" #include "slang-serialize-fossil.h" #include "slang-serialize-riff.h" +#define SLANG_ENABLE_AST_DESERIALIZATION_STATS 0 +#define SLANG_DISABLE_ON_DEMAND_AST_DESERIALIZATION 1 + +FIDDLE() namespace Slang { -// TODO(tfoley): have the parser export this, or a utility function -// for initializing a `SyntaxDecl` in the common case. // -NodeBase* parseSimpleSyntax(Parser* parser, void* userData); +// The big picture here is that we will serialize all of the structures +// that make up the AST using the framework in `slang-serialize.h`, +// and the specific *implementation* of serialization from `slang-fossil.h`. +// +// There's a certain amount of work that needs to be done on a per-type basis +// to make all of the serialization magic work the way we want. In order to +// help illustrate what's going on before we grind through all the different +// types, we will start slow and define the needed pieces for a somewhat +// trivial type: `RefObject`. +// + +// +// For the general-purpose serialization framework in `slang-serialize.h`, the +// main requirement is that any type that we want to serialize should have an +// available overload of `serialize()`. +// +// +// In principle, the declarations and definitions of these functions ought to +// be more closely associated with the types that they pertain to, but for now +// 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&) +{ + // There's actually no data stored in a `RefObject`, since it only exists + // to make reference-counting possible for other types. This function is + // primarily useful for cases where we might codegen logic to serialize + // a type by serializing its base class (if it has one) and then its fields. + // If the base class is `RefObject`, we want there to be an available + // overload of `serialize()` to handle that case. +} // -// Many of the types used in the AST can be serialized using -// just the `Serializer` type, so we will handle all of those first. +// In addition to using the general-purpose serialization system, we are +// specifically encoding the AST using the "fossil" format defined in `slang-fossil.h`. +// This format allows us to load the serialized data into memory and easily +// navigate it without having to deserialize any of its content. +// +// There are really two modes in which fossilized data can be navigated: +// +// * As a dynamically-typed graph of nodes, where a reference to a node +// comprises a data pointer and a layout pointer, with the layout +// describing the type and format of the data. +// +// * As a statically-typed data structure, where code can just cast a +// pointer to fossilized data to the type that it knows/expects it to +// have, and then access it like Just Another C++ Type. +// +// In order to enable the second of these modes, we need to do a little +// work to define the mapping from a "live" C++ type to its fossilized +// equivalent. +// +// In cases where a live type can use an existing fossilized type as +// its representation, we can specialize the `FossilizedTypeTraits` template: // -void serialize(Serializer const& serializer, ASTNodeType& value) +template<> +struct FossilizedTypeTraits<RefObject> { - serializeEnum(serializer, value); -} + struct FossilizedType + { + }; +}; -void serialize(Serializer const& serializer, TypeTag& value) +// +// The handling of `RefObject` was trivial, so let's cover a few more +// simple examples in detail before we move on to the rest of the things, +// where we will be using fiddle to generate a lot of the boilerplate +// code we'd otherwise be writing by hand. +// +// The `MatrixCoord` type is a fairly simple `struct` with two fields. +// 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) { - serializeEnum(serializer, value); + // We start with one of the `SLANG_SCOPED_SERIALIZER_*` + // macros, which basically just handles calling + // `ISerializerImpl::beginTuple()` and the start of our + // scope, and `ISerializer::endTuple()` at the end. + // + SLANG_SCOPED_SERIALIZER_TUPLE(serializer); + + // Next, we call `serialize()` on each of the fields + // of our type, in order. Ordinary overload resolution + // will pick the right function to call based on the type + // of the field itself. + // + serialize(serializer, value.row); + serialize(serializer, value.col); + + // Note: this one function handles both the read and write + // directions. For simple types like `MatrixCoord` the logic + // for reading and writing is symettric, and writing it as + // one function ensures that the two paths are kept in sync. + // + // Some of the later examples will perform different logic + // in the read and write cases, and all of those require + // more conscious effort by contributors to keep the two + // paths matching. } -void serialize(Serializer const& serializer, BaseType& value) +// +// Writing the `serialize()` function is one piece of the picture, but +// if we want to be able to navigate a fossilized `MatrixCoord` in +// memory, we need to declare what it's fossilized version will look like. +// +// We do that here by writing an explicit specialization of `FossilizedTypeTraits`: +// +template<> +struct FossilizedTypeTraits<MatrixCoord> { - serializeEnum(serializer, value); -} + // The `MatrixCoord` type can't map directly to any type + // for fossilized data, so we declare it here as a custom + // `struct`. + // + struct FossilizedType + { + // The contents of a fossilized struct will typically + // just be the fossilized representation of each of + // its fields. + // + // We use `decltype()` to access the type of each of + // the fields, and `Fossilized<...>` to map those + // types to their fossilized equivalents. + // + // Note that this type definition must be consistent + // with the implementation of `serialize()` above, + // so it is important to keep the two consistent. + // That requirement of consistency is part of why + // it helps to generate these definitions rather + // than author them by hand. + // + Fossilized<decltype(MatrixCoord::row)> row; + Fossilized<decltype(MatrixCoord::col)> col; + }; +}; -void serialize(Serializer const& serializer, TryClauseType& value) +// +// In some cases we don't really want to serialize a type directly, +// and instead want to translate it to some intermediate format +// that can be serialized more conveniently. +// +// For example, the `SemanticVersion` type conceptually has multiple +// fields, but it is also designed so that it can be encoded conveniently +// 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) { - serializeEnum(serializer, value); + // This function is doing something a little "clever" + // handle the fact that it might be used to either + // *write* a `SemanticVersion` to the serialized format, + // or to *read* one. + // + // In the case where we are writing, the following line + // will copy the `value` we want to write into the + // local variable `raw`, but if we are *reading* instead, + // this operation doesn't so anything useful. + // + // The assumption being made here is that it is safe to + // call `getRawValue()` on any `SemanticVersion`, including + // one that has been default-constructed, because we have + // no guarantee that the incoming `value` represents anything + // useful or even *valid* in the case where we are reading + // (and thus expected to overwrite `value`). + // + SemanticVersion::RawValue raw = value.getRawValue(); + + // Depending on whether we are reading or writing, this next + // line will either write out the value of `raw` that was + // computed above, or it will read serialized data into `raw`, + // and overwrite the useless value from before. + // + serialize(serializer, raw); + + // Finally, we overwrite the `value` by converting `raw` + // back to a `SemanticVersion`. If we are in reading mode, + // this will do exactly what the caller wants/expects. + // If we are in *writing* mode, this line makes a few more + // subtle assumptions: + // + // * It assumes that we can safely round-trip any `SemanticVersion` + // through its `RawValue` without changing its meaning. + // + // * It assumes that the passed-in `value` will never be a + // reference to read-only memory, and that in the case where + // there are other concurrent accesses to `value`, this write + // will not somehow create a difficult-to-debug data hazard + // (e.g., there might be an overload of `operator=` that + // temporarily sets the object into a state that shouldn't be + // obeserved). + // + value = SemanticVersion::fromRaw(raw); + + // In cases where a given type doesn't satsify all the assumptions + // being made above, it is relatively simple to just split the + // logic into distinct cases based on `isReading(serializer)` and + // avoid all the concerns. That conditional involves a virtual + // function call, so in cases where it can easily be avoided, + // we prefer to do the redundant copy-in and copy-out on a local + // variable, like in the code above. } -void serialize(Serializer const& serializer, DeclVisibility& value) +// +// Given the definition of `serialize()` above, it is clear that the +// fossilized representation of `SemanticVersion` would be the same +// as whatever the fossilized representation of `SemanticVersion::RawValue` +// would be. +// +// The fossil header provides some convenient macros for defining that +// one type gets fossilized as another: +// +SLANG_DECLARE_FOSSILIZED_AS(SemanticVersion, SemanticVersion::RawValue); + +// +// While in some cases we want to serialize something via an intermediate +// type that already exists (like for `SemanticVersion` and +// `SemanticVersion::RawValue` above), in other cases we need to *define* +// an intermediate type to store the data we care about in a more +// direct fashion. +// +// When serializing an AST `ModuleDecl`, there are certain pieces +// of data that are implicitly encoded in the object graph under +// that module declaration that are beneficial to make explicit +// in the serialized representation. +// +// As a concrete example, there are various declarations in the Slang +// core module that have to be "registered" with the `SharedASTBuilder` +// being used, so that they can be looked up by a well-defined tag +// (whether an integer or string) by other logic in the compiler. +// Those declarations can be found by doing a recursive search over +// the entire `Decl` hierarchy of a module, but doing such a recursive +// search would force us to load and inspect every single declaration +// in a module as part of deserialization, which would negate any +// possible benefits to supporting on-demand deserialization of those +// declarations. +// +// Thus, we define an intermediate `ASTModuleInfo` type that holds +// the pre-computed information that we want to serialize (and thus +// also represents the data that we will want to navigate in the +// serialized representation). +// +FIDDLE() +struct ASTModuleInfo { - serializeEnum(serializer, value); -} + FIDDLE(...) + + // We still want to serialize the original module declaration, + // and everything it transitvely refers to. + // + FIDDLE() ModuleDecl* moduleDecl; + + // The intermediate type will store an explicit list of all of + // the declarations that we need to register upon loading + // this module (this list is expected to be empty for everything + // other than the core module). + // + FIDDLE() List<Decl*> declsToRegister; + + // Another example of data that we want to store explicitly + // rather than leave implicit in the declaration hierarchy is + // the set of declarations exported from the module, and their + // mangled names. + // + FIDDLE() OrderedDictionary<String, Decl*> mapMangledNameToDecl; +}; + +// +// Another case where we wnat to define an intermediate type is +// the `ContainerDeclDirectMembers` type used to encapsulate +// the list of direct members for a `ContainerDecl` along with +// the acceleration structures used to enable efficient lookup +// of those declarations. +// +FIDDLE() +struct ContainerDeclDirectMemberDeclsInfo +{ + FIDDLE(...) + + // We need to store the ordered list of declarations, + // because many parts of the compiler need to access + // all of the direct members of a container, and the + // order of the direct members often matters (e.g., + // for layout). + // + FIDDLE() List<Decl*> decls; + + // One of the acceleration structures that a `ContainerDecl` + // may build and store is a list of those entries in + // `decls` that are marked as "transparent." This is + // not a commonly-occuring case, used only to support + // a few legacy features, so it is a bit wasteful to + // store such a list on *every* container decl in the + // serialized format, but the format itself isn't + // currently optimized for size, so we consider this + // fine for now. + // + FIDDLE() List<FossilUInt> transparentDeclIndices; + + + // The other main acceleration structure that a `ContainerDecl` + // may build and store is a dictionary to map a string name to + // a declaration of that name (which is then the first node + // in an internally-linked list of *all* the declarations with + // the given name). + // + FIDDLE() OrderedDictionary<String, FossilUInt> mapNameToDeclIndex; +}; + +// +// 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. +// + +struct ASTSerialContext; +using ASTSerializer = Serializer_<ISerializerImpl, ASTSerialContext>; + +/// Context interface for AST serialization +struct ASTSerialContext +{ +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 handleSourceLoc(ASTSerializer const& serializer, SourceLoc& 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 +// a few small examples, we will try to use fiddle to generate the code +// to handle as many of the remaining types as we can. +// +// TODO: It would be great to have more of this logic be driven by information +// that the fiddle tool scraped from the relevant declarations. +// -void serialize(Serializer const& serializer, BuiltinRequirementKind& value) +// +// We start with the easiest case, which is the various `enum` types that +// get stored as part of the AST. +// +#if 0 // FIDDLE TEMPLATE: +% +%local enumTypeNames = { +% "ASTNodeType", +% "TypeTag", +% "BaseType", +% "TryClauseType", +% "DeclVisibility", +% "BuiltinRequirementKind", +% "ImageFormat", +% "PreferRecomputeAttribute::SideEffectBehavior", +% "TreatAsDifferentiableExpr::Flavor", +% "LogicOperatorShortCircuitExpr::Flavor", +% "RequirementWitness::Flavor", +% "CapabilityAtom", +% "DeclAssociationKind", +% "TokenType", +% "ValNodeOperandKind", +% "SPIRVAsmOperand::Flavor", +% "SlangLanguageVersion", +%} +% +%for _,T in ipairs(enumTypeNames) do + +/// Serialize a `value` of type `$T`. +void serialize(Serializer const& serializer, $T& value) { serializeEnum(serializer, value); } -void serialize(Serializer const& serializer, ImageFormat& value) +% -- The `serializeEnum()` function encodes enum values as `FossilUInt`s +% -- so we declare the fossilized representation of these types to match. +% +// Declare fossilized representation of `$T` +SLANG_DECLARE_FOSSILIZED_AS($T, FossilUInt); + +%end +#else // FIDDLE OUTPUT: +#define FIDDLE_GENERATED_OUTPUT_ID 0 +#include "slang-serialize-ast.cpp.fiddle" +#endif // FIDDLE END + +// +// Next we have a few `struct` types that can be serialized just +// based on the information that the fiddle tool is able to +// scrape from their declarations. +// +// The main wrinkle here, as compared to the `enum` handling above, +// is that we will split this logic between two fiddle templates: +// one to generate forward declarations, and another to fill in +// the actual implementations. +// +// The forward declarations are needed to resolve ordering issues +// when types in the AST can transitively reference themselves +// through pointer chains. Because of the way that the `serialize()` +// approach relies on overload resolution, and the `FossilizedTypeTraits` +// approach relies on partial template specialization, it is important +// that the relevant declarations/specializations get seen before +// any use sites are encountered. +// +// TODO: Ideally we would be placing the declarations of the `serialize()` +// functions and the `FossilizedTypeTraits` specializations next to the +// declarations of the types themselves. It would be great if the scraper +// part of the fiddle tool could generate those for `FIDDLE()`-annotated +// types. +// +// The basic idea here is that for each struct type `Foo` that +// we want to serialize, we will forward-declare the `serialize()` +// function, and also forward-declare a type `Fossilized_Foo` +// that will represent a fossilized `Foo` (and we also wire it +// up so that `Fossilized<Foo>` will map to `Fossilized_Foo`). +// +// All of this can be done without ever iterating over the members +// of `Foo`, so we don't run into any ordering issues. +// +#if 0 // FIDDLE TEMPLATE: +% +% -- TODO: This declaration would ideally be `local` in Lua, +% -- but the way that fiddle currently translates the templates +% -- in a C++ file over to Lua puts each distinct template in +% -- its own nested function, which means that their `local` +% -- scopes are distinct. We should see if we can change the +% -- translation so that the code directly nested under a +% -- template like this is in the global scope. +% +%astStructTypes = { +% Slang.QualType, +% Slang.SPIRVAsmOperand, +% Slang.DeclAssociation, +% Slang.NameLoc, +% Slang.WitnessTable, +% Slang.SPIRVAsmInst, +% Slang.ASTModuleInfo, +% Slang.ContainerDeclDirectMemberDeclsInfo, +%} +% +%for _,T in ipairs(astStructTypes) do + +/// Fossilized representation of a `$T` +struct Fossilized_$T; + +SLANG_DECLARE_FOSSILIZED_TYPE($T, Fossilized_$T); + +/// Serialize a `$T` +void serialize(ASTSerializer const& serializer, $T& value); +%end +#else // FIDDLE OUTPUT: +#define FIDDLE_GENERATED_OUTPUT_ID 1 +#include "slang-serialize-ast.cpp.fiddle" +#endif // FIDDLE END + +// +// Now we move on to the AST nodes themselves (subtypes of `NodeBase`). +// +// The handling of these is largely the same as for the struct +// types above, except that the function that handles serializing +// them is called `_serializeASTNodeContents()`, because of the +// logic that we use to handle the polymorphism of `NodeBase`. +// +#if 0 // FIDDLE TEMPLATE: +% +%astNodeClasses = Slang.NodeBase.subclasses +% +%for _,T in ipairs(astNodeClasses) do + +/// Fossilized representation of a `$T` +struct Fossilized_$T; + +SLANG_DECLARE_FOSSILIZED_TYPE($T, Fossilized_$T); + +/// Serialize the content of a `$T` +void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value); +%end +#else // FIDDLE OUTPUT: +#define FIDDLE_GENERATED_OUTPUT_ID 2 +#include "slang-serialize-ast.cpp.fiddle" +#endif // FIDDLE END + + +// +// We will define two different implementations of `IASTSerializerImpl`, one for +// the writing case, and one for the reading case. The writing direction is +// the simpler one, so let's look at it first: +// + +/// Context for writing a Slang AST to a serialized format. +/// +/// This type only provides the contextual information needed +/// to correctly write AST-related types, and delegates the +/// lower-level serialization operations to an underlying +/// `ISerializerImpl`. +/// +struct ASTSerialWriteContext : ASTSerialContext { - serializeEnum(serializer, value); +public: + /// Construct a context for writing a serialized AST. + /// + /// * `module` is the module that is being serialized, and will be + /// used to detect whether declarations are part of the module, + /// or imported from other modules. + /// + /// * `sourceLocWriter` will be used to handle translation of + /// `SourceLoc`s into a format suitable for serialization. + /// + ASTSerialWriteContext(ModuleDecl* module, SerialSourceLocWriter* sourceLocWriter) + : _module(module), _sourceLocWriter(sourceLocWriter) + { + } + +private: + ModuleDecl* _module = nullptr; + SerialSourceLocWriter* _sourceLocWriter = nullptr; + + // + // 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 handleSourceLoc(ASTSerializer const& serializer, SourceLoc& 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( + ASTSerializer const& serializer, + ContainerDeclDirectMemberDecls& value) override; + + void _writeImportedModule(ASTSerializer const& serializer, ModuleDecl* moduleDecl); + void _writeImportedDecl( + ASTSerializer const& serializer, + Decl* decl, + ModuleDecl* importedFromModuleDecl); + + ModuleDecl* _findModuleForDecl(Decl* decl) + { + for (auto d = decl; d; d = d->parentDecl) + { + if (auto m = as<ModuleDecl>(d)) + return m; + } + return nullptr; + } + + ModuleDecl* _findModuleDeclWasImportedFrom(Decl* decl) + { + auto declModule = _findModuleForDecl(decl); + if (declModule == nullptr) + return nullptr; + if (declModule == _module) + return nullptr; + return declModule; + } +}; + +// +// The reading direction is where things get a bit more interesting. +// +// In order to support on-demand deserialization, we need an object +// that persists across multiple deserialization requests, and that +// stores the state about what values have/haven't already been +// deserialized. Concretely, multiple requests to deserialize the +// same serialized declaration had better return the same `Decl*`. +// +// This is the place where we start concretely assuming that the +// AST will be written and read using the fossil format. +// + +/// Context for on-demand AST deserialization. +/// +/// This type owns the mapping from fossilized AST declarations +/// to their live `Decl*` counterparts. +/// +/// A single `ASTDeserializationContext` should be created and +/// maintained for the entire duration during which fossilized +/// declarations might need to be revitalized. Using multiple +/// contexts could result in the same declaration getting turned +/// into multiple distinct `Decl*`s. +/// +struct ASTSerialReadContext : public ASTSerialContext, public RefObject +{ +public: + /// Construct an AST deserialization context. + /// + /// The `linkage`, `astBuilder`, and `sink` arguments must + /// all remain valid for as long as this context will be used. + /// + /// The context will retain the `sourceLocReader` and the + /// `blobHoldingSerializedData`. It is assumed that the + /// `fossilizedModuleInfo` is a pointer into the + /// `blobHoldingSerializedData`, so that keeping the blob + /// alive will ensure that the pointer stays valid. + /// + ASTSerialReadContext( + Linkage* linkage, + ASTBuilder* astBuilder, + DiagnosticSink* sink, + SerialSourceLocReader* sourceLocReader, + SourceLoc requestingSourceLoc, + Fossilized<ASTModuleInfo> const* fossilizedModuleInfo, + ISlangBlob* blobHoldingSerializedData) + : _linkage(linkage) + , _astBuilder(astBuilder) + , _sink(sink) + , _sourceLocReader(sourceLocReader) + , _requestingSourceLoc(requestingSourceLoc) + , _fossilizedModuleInfo(fossilizedModuleInfo) + , _blobHoldingSerializedData(blobHoldingSerializedData) + { + } + + /// Translate a fossilized declaration into a live `Decl*`. + /// + /// If the same `fossilizedDecl` address has been passed to this + /// operation before, it will return the same `Decl*`. + /// + /// Otherwise, this operation will trigger deserialization + /// of the `fossilizedDecl` and return the result. + /// + /// It is assumed that the `fossilizedDecl` comes from the same + /// serialized AST and the same data blob that were passed into + /// the constructor for `ASTDeserializationContext`. + /// + Decl* readFossilizedDecl(Fossilized<Decl>* fossilizedDecl); + + /// Look up an export from the fossilized module, by its mangled name. + /// + /// If a matching export is found in the serialized data, returns a + /// the corresponding declaration as if `readFossilizedDecl()` was + /// invoked on it. + /// + /// If no matching export is found, returns null. + /// + Decl* findExportedDeclByMangledName(UnownedStringSlice const& mangledName); + +private: + Linkage* _linkage = nullptr; + ASTBuilder* _astBuilder = nullptr; + DiagnosticSink* _sink = nullptr; + RefPtr<SerialSourceLocReader> _sourceLocReader = nullptr; + SourceLoc _requestingSourceLoc; + Fossilized<ASTModuleInfo> const* _fossilizedModuleInfo; + ComPtr<ISlangBlob> _blobHoldingSerializedData; + + // + // The actual cache for the mapping from fossilized declaration pointers + // to their revitalized `Decl*`s is maintained by the `Fossil::ReadContext`. + // + + Fossil::ReadContext _readContext; + +#if SLANG_ENABLE_AST_DESERIALIZATION_STATS + Count _deserializedTopLevelDeclCount = 0; +#endif + + // + // Much like the `ASTSerialWriter`, for the most part this + // type just implements the `IASTSerializer` interface, + // plus a small number of utility methods that serve those + // implementations. + // + + virtual void handleName(ASTSerializer const& serializer, Name*& value) override; + virtual void handleSourceLoc(ASTSerializer const& serializer, SourceLoc& 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( + ASTSerializer const& serializer, + ContainerDeclDirectMemberDecls& value) override; + + ModuleDecl* _readImportedModule(ASTSerializer const& serializer); + NodeBase* _readImportedDecl(ASTSerializer const& serializer); + + void _cleanUpASTNode(NodeBase* node); + void _assignGenericParameterIndices(GenericDecl* genericDecl); +}; + +// +// Let's look at a concrete example of how the `ASTSerialReadContext` +// and `ASTSerialWriteContext` get applied to handle one of the types +// that needs them for additional context. +// +// The `serialize()` function for `SourceLoc` is declared to take +// an `ASTSerializer` argument instead of a simple `Serializer`: +// +void serialize(ASTSerializer const& serializer, SourceLoc& value) +{ + // Its body is trivial, because the actual handling of `SourceLoc` + // serialization is delegated to the `ASTSerialWriteContext` and + // `ASTSerialReadContext`. + // + serializer.getContext()->handleSourceLoc(serializer, value); } -void serialize(Serializer const& serializer, PreferRecomputeAttribute::SideEffectBehavior& value) +void ASTSerialWriteContext::handleSourceLoc(ASTSerializer const& serializer, SourceLoc& value) { - serializeEnum(serializer, value); + // Writing of source location information can be disabled by + // compiler options, and in that case the `_sourceLocWriter` + // may be null. + // + // In order to handle that possibility, we serialize a `SourceLoc` + // as an optional value, dependent on whether we have a + // `_sourceLocWriter` that can be used. + // + SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer); + if (_sourceLocWriter != nullptr) + { + // The `SourceLoc` type is implemented under the hood as an + // integer offset that can only be decoded using the specific + // `SourceManager` that created it. + // + // The source location writer handles the task of translating + // the under-the-hood representation to a single integer value + // (represented as `SerialSourceLocData::SourceLoc`) that can + // be decoded on the other side using other data that the + // source location writer will write out as part of its own + // representation (all of which goes into the dedicated debug + // data chunk, distinct from the AST). + // + SerialSourceLocData::SourceLoc rawValue = _sourceLocWriter->addSourceLoc(value); + serialize(serializer, rawValue); + } } -void serialize(Serializer const& serializer, TreatAsDifferentiableExpr::Flavor& value) +void ASTSerialReadContext::handleSourceLoc(ASTSerializer const& serializer, SourceLoc& value) { - serializeEnum(serializer, value); + // Because the source location was *written* as an optional, + // we clearly need to *read* it as one. + // + SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer); + if (hasElements(serializer)) + { + SerialSourceLocData::SourceLoc rawValue; + serialize(serializer, rawValue); + + // Even if the serialized optional had a value, it is + // possible that the debug-data chunk got stripped from + // the compiled module file, in which case we wouldn't + // have access to the data needed to decode it. + // + // In that case, the `_sourceLocReader` member would be + // null, so we handle that possibility here. + // + if (auto sourceLocReader = _sourceLocReader) + { + value = sourceLocReader->getSourceLoc(rawValue); + } + } } -void serialize(Serializer const& serializer, LogicOperatorShortCircuitExpr::Flavor& value) +// Now that we've seen the relevant serialization logic, it is clear that +// a `SourceLoc` gets fossilized the same way that an optional wrapping +// an integer (of type `SerialSourceLocData::SourceLoc`) would. +// +SLANG_DECLARE_FOSSILIZED_AS(SourceLoc, std::optional<SerialSourceLocData::SourceLoc>); + + +// +// Earlier we generated forward declarations for all of the types +// that we'll be able to handle with fiddle, but there are still a +// large number of types that we currently have to hand-write the +// serialization logic for. We'll go over those here. +// + +// +// A `Name` is basically just a string, but we need to handle +// a `Name*` as a pointer, and deal with the possibility that +// it might be null. +// +// TODO: It might be better to customize the serialization of +// `Name*` itself, so that it is handled as an optional string. +// + +SLANG_DECLARE_FOSSILIZED_AS(Name, String); + +void serializeObject(ASTSerializer const& serializer, Name*& value, Name*) { - serializeEnum(serializer, value); + serializer.getContext()->handleName(serializer, value); } -void serialize(Serializer const& serializer, RequirementWitness::Flavor& value) +void ASTSerialWriteContext::handleName(ASTSerializer const& serializer, Name*& value) { - serializeEnum(serializer, value); + serialize(serializer, value->text); } -void serialize(Serializer const& serializer, CapabilityAtom& value) +void ASTSerialReadContext::handleName(ASTSerializer const& serializer, Name*& value) { - serializeEnum(serializer, value); + String text; + serialize(serializer, text); + value = _astBuilder->getNamePool()->getName(text); } -void serialize(Serializer const& serializer, DeclAssociationKind& value) +// +// A `Token` is *almost* an easy type to handle, and +// the declaration for its fossilized representation +// makes it look like it should be a simple `struct` +// that we can let fiddle generate the implementation +// for: +// + +template<> +struct FossilizedTypeTraits<Token> +{ + struct FossilizedType + { + Fossilized<decltype(Token::type)> type; + Fossilized<decltype(Token::loc)> loc; + Fossilized<decltype(Token::flags)> flags; + Fossilized<String> content; + }; +}; + +void serialize(ASTSerializer const& serializer, Token& value) { - serializeEnum(serializer, value); + serializer.getContext()->handleToken(serializer, value); } -void serialize(Serializer const& serializer, TokenType& value) +// +// The cracks start to show when we look at the logic +// for writing a `Token`: +// + +void ASTSerialWriteContext::handleToken(ASTSerializer const& serializer, Token& value) { - serializeEnum(serializer, value); + SLANG_SCOPED_SERIALIZER_STRUCT(serializer); + serialize(serializer, value.type); + serialize(serializer, value.loc); + + // The flags stored in a `Token` have one bit + // (`TokenFlag::Name`) that we don't want + // to have read in on the other side, because + // it relates to some aspects of the underlying + // in-memory representation that don't actually + // relate to the semantic *value* we are serializing. + + TokenFlags flags = TokenFlags(value.flags & ~TokenFlag::Name); + serialize(serializer, flags); + + // The content of a token is basically just a + // string, but it can be encoded in different + // ways, so we extract it here for writing. + // + String content = value.getContent(); + serialize(serializer, content); } -void serialize(Serializer const& serializer, ValNodeOperandKind& value) +// +// The reading logic adds yet more complexity... +// + +void ASTSerialReadContext::handleToken(ASTSerializer const& serializer, Token& value) { - serializeEnum(serializer, value); + SLANG_SCOPED_SERIALIZER_STRUCT(serializer); + serialize(serializer, value.type); + serialize(serializer, value.loc); + + serialize(serializer, value.flags); + + String content; + serialize(serializer, content); + + // Note that we cannot just call `value.setContent(...)` + // and pass in an `UnownedStringSlice` of `content`, + // because the `Token` will not take ownership of its own + // textual content. + // + // Instead, we need to get the text we just loaded + // into something that the `Token` can refer info, + // and the easiest way to accomplish that is to + // represent the text using a `Name`. + // + Name* name = _astBuilder->getNamePool()->getName(content); + value.setName(name); } -void serialize(Serializer const& serializer, SPIRVAsmOperand::Flavor& value) +// +// While we use fiddle to generate a lot of the code related to +// specific subclasses of `NodeBase`, the logic to serialize +// a `NodeBase*` itself needs to be special-cased by intercepting +// the `serializeObject()` customization point provided by +// the serialization system. +// +// We'll cover the implementations of `handleASTNode()` for the +// reading and writing cases later; what matters now is to +// establish this declaration before any code that tries to +// serialize any pointers to AST nodes. +// + +template<typename T> +void serializeObject(ASTSerializer const& serializer, T*& value, NodeBase*) { - serializeEnum(serializer, value); + // The general-purpose serialization layer defines + // a variant as akin to a struct, but where the + // specific number and type of fields that get written + // can vary from value to value, for the same type. + // + // The fossil encoding of a variant is always via indirection, + // as a pointer to a memory region holding the particular + // value, along with a pointer to the layout information + // for that value. + // + // Because `NodeBase` is the base class of a polymorphic + // class hierarchy, we treat all pointers to `NodeBase`-derived + // types as variants for serialization purposes. + // + SLANG_SCOPED_SERIALIZER_VARIANT(serializer); + serializer.getContext()->handleASTNode(serializer, reinterpret_cast<NodeBase*&>(value)); } -void serialize(Serializer const& serializer, SlangLanguageVersion version) +// +// We also intercept the `serializeObjectContents()` customization +// point, which is used to read/write most of the actual members +// of an AST node, whereas the `serializeObject()` step just deals +// with the parts that are necessary to allocate (or find) an +// object in the reading direction. +// + +void serializeObjectContents(ASTSerializer const& serializer, NodeBase* value, NodeBase*) { - serializeEnum(serializer, version); + serializer.getContext()->handleASTNodeContents(serializer, value); } -void serialize(Serializer const& serializer, MatrixCoord& value) +// +// The handling of the members of a `ContainerDecl` is another +// complicated part of the serialization process, so we will +// define the `serialize()` implementation here, but defer its +// implementation until later. +// +// We know, however, that the members will be serialized via +// the intermediate `ContainerDeclDirectMemberDeclsInfo` type +// that was defined earlier in this file. +// + +SLANG_DECLARE_FOSSILIZED_AS(ContainerDeclDirectMemberDecls, ContainerDeclDirectMemberDeclsInfo); + +void serialize(ASTSerializer const& serializer, ContainerDeclDirectMemberDecls& value) { - SLANG_SCOPED_SERIALIZER_TUPLE(serializer); - serialize(serializer, value.row); - serialize(serializer, value.col); + serializer.getContext()->handleContainerDeclDirectMemberDecls(serializer, value); } +// +// Pointers to diagnostics (which can be referenced in attributes +// related to enabling/disabling warnings) get serialized as +// the integer diagnostic ID. +// + +SLANG_DECLARE_FOSSILIZED_AS(DiagnosticInfo const*, Int32); + void serializePtr(Serializer const& serializer, DiagnosticInfo const*& value, DiagnosticInfo const*) { Int32 id = 0; @@ -128,13 +996,35 @@ void serializePtr(Serializer const& serializer, DiagnosticInfo const*& value, Di } } -void serialize(Serializer const& serializer, SemanticVersion& value) + +// +// A `DeclRef<T>` is just a wrapper around a `DeclRefBase*`, +// and we'll serialize it as such. +// + +template<typename T> +void serialize(ASTSerializer const& serializer, DeclRef<T>& value) { - auto raw = value.getRawValue(); - serialize(serializer, raw); - value = SemanticVersion::fromRaw(raw); + serialize(serializer, value.declRefBase); } +template<typename T> +struct FossilizedTypeTraits<DeclRef<T>> +{ + // TODO: This case can't be declared with `SLANG_DECLARE_FOSSILIZED_AS()` + // because of the need for the template parameter `T`. A more advanced + // version of that macro could also allow for template parameters, + // but for now it is okay to just write these cases out long-form. + // + using FossilizedType = Fossilized<DeclRefBase*>; +}; + +// +// A `SyntaxClass<T>` is a wrapper around an `ASTNodeType`: +// + +SLANG_DECLARE_FOSSILIZED_AS(SyntaxClass<NodeBase>, ASTNodeType); + void serialize(Serializer const& serializer, SyntaxClass<NodeBase>& value) { ASTNodeType raw = ASTNodeType(0); @@ -150,118 +1040,112 @@ void serialize(Serializer const& serializer, SyntaxClass<NodeBase>& value) } // -// Many types in the AST need additional context (beyond -// what the `Serializer` has) in order to serialize -// themselves or their members. +// The `Modifiers` type is just a wrapper around the way +// that the `Modifier` type uses an internally-linked list. // -// We define a custom serializer interface to capture -// the cases that can't be handled by a `Serializer` -// alone. +// We serialize `Modifiers` as if they were just using +// the ordinary `List<T>` type (which maybe they should...). // -/// Interface for AST serialization -struct ASTSerializerImpl -{ -public: - virtual void handleASTNode(NodeBase*& value) = 0; - virtual void handleASTNodeContents(NodeBase* value) = 0; - virtual void handleName(Name*& value) = 0; - virtual void handleSourceLoc(SourceLoc& value) = 0; - virtual void handleToken(Token& value) = 0; - - // Note that this type does *not* inherit from `ISerializerImpl`. - // - // We want to decouple the AST-specific context information - // from the lower-level details of the serialization format. - // - // Instead of using inheritance, we expect that any - // `ASTSerializerImpl` will aggregate a lower-level - // serializer, and the interface exposes access to - // that base serializer implementation. - - virtual ISerializerImpl* getBaseSerializer() = 0; -}; +SLANG_DECLARE_FOSSILIZED_AS(Modifiers, List<Modifier*>); -/// Specialization of `Serializer_` for AST serialization. -template<> -struct Serializer_<ASTSerializerImpl> : SerializerBase<ASTSerializerImpl> +void serialize(ASTSerializer const& serializer, Modifiers& value) { -public: - using SerializerBase::SerializerBase; + SLANG_SCOPED_SERIALIZER_ARRAY(serializer); + // Because we are dealing with a list, rather + // than a more mundane aggregate type list + // a struct, we need our logic to distinguish + // between the writing and reading cases. // - // In order to allow an `ASTSerializer` to be used with - // functions that expect an ordinary `Serializer`, we - // implement an implicit conversion operator. - // - - operator Serializer() const { return Serializer(get()->getBaseSerializer()); } -}; + if (isWriting(serializer)) + { + for (auto modifier : value) + { + serialize(serializer, modifier); + } + } + else + { + Modifier** link = &value.first; -/// Context type for AST serialization. -using ASTSerializer = Serializer_<ASTSerializerImpl>; + while (hasElements(serializer)) + { + Modifier* modifier = nullptr; + serialize(serializer, modifier); -template<typename T> -void serializeObject(ASTSerializer const& serializer, T*& value, NodeBase*) -{ - SLANG_SCOPED_SERIALIZER_VARIANT(serializer); - serializer->handleASTNode(*(NodeBase**)&value); + *link = modifier; + link = &modifier->next; + } + } } -void serializeObjectContents(ASTSerializer const& serializer, NodeBase* value, NodeBase*) -{ - serializer->handleASTNodeContents(value); -} +// +// For the purposes of serialization, a `TypeExp` is just +// a wrapper around a `Type*`. +// +// (Under the hood a `TypeExp` has room to store both a +// type *expression* (an `Expr*`) and the `Type*` that +// we compute as a result of checking that type expression. +// For any AST that has passed front-end semantic checking, +// the `Type*` part is expected to be filled in, and the +// `Expr*` part is no longer relevant.) +// +// Here we use another convenience macro to declare that +// the fossilized reprsentation of a `TypeExp` is the same +// as the `TypeExpr::type` member. +// +SLANG_DECLARE_FOSSILIZED_AS_MEMBER(TypeExp, type); -template<typename T> -void serialize(ASTSerializer const& serializer, DeclRef<T>& value) +void serialize(ASTSerializer const& serializer, TypeExp& value) { - serialize(serializer, value.declRefBase); + serialize(serializer, value.type); } -void serialize(ASTSerializer const& serializer, SourceLoc& value) +// +// The `CandidateExtensionList` and `DeclAssociationList` types +// are simple wrappers around a single field. +// + +SLANG_DECLARE_FOSSILIZED_AS_MEMBER(CandidateExtensionList, candidateExtensions); + +void serialize(ASTSerializer const& serializer, CandidateExtensionList& value) { - serializer->handleSourceLoc(value); + serialize(serializer, value.candidateExtensions); } -void serialize(ASTSerializer const& serializer, RequirementWitness& value) + +SLANG_DECLARE_FOSSILIZED_AS_MEMBER(DeclAssociationList, associations); + +void serialize(ASTSerializer const& serializer, DeclAssociationList& value) { - SLANG_SCOPED_SERIALIZER_VARIANT(serializer); - serialize(serializer, value.m_flavor); - switch (value.m_flavor) - { - case RequirementWitness::Flavor::none: - break; + serialize(serializer, value.associations); +} - case RequirementWitness::Flavor::declRef: - serialize(serializer, value.m_declRef); - break; +// +// The various types used to store capabilities on declarations +// are all semantically equivalent to simpler types. +// - case RequirementWitness::Flavor::val: - serialize(serializer, value.m_val); - break; +// A `CapabilityAtomSet` is an optimized representation of a +// set of a `CapabilityAtom`s (which we can encode as just +// a sequence). +// +SLANG_DECLARE_FOSSILIZED_AS(CapabilityAtomSet, List<CapabilityAtom>); - case RequirementWitness::Flavor::witnessTable: - serialize(serializer, value.m_obj); - break; - } -} +// A `CapabilityStateSet` can simply be encoded using its `atomSet` member. +// +SLANG_DECLARE_FOSSILIZED_AS_MEMBER(CapabilityStageSet, atomSet); -void serialize(ASTSerializer const& serializer, WitnessTable& value) -{ - SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.baseType); - serialize(serializer, value.witnessedType); - serialize(serializer, value.isExtern); +// A `CapabilityStageSet` is really just a wrapper around a `CapabilityStageSets` +// (which is itself just a dictionary of `CapabilityStateSet`s). +// +SLANG_DECLARE_FOSSILIZED_AS(CapabilityTargetSet, CapabilityStageSets); - // TODO(tfoley): In theory we should be able to streamline - // this so that we only encode the requirements that we - // absolutely need to (which basically amounts to `associatedtype` - // requirements where the satisfying type is part of the public - // API of the type). - // - serialize(serializer, value.m_requirementDictionary); -} +// A `CapabilitySet` is really just a wrapper around a `CapabilityTargetSets` +// (which is itself just a dictionary of `CapabilityTargetSet`s). +// +SLANG_DECLARE_FOSSILIZED_AS(CapabilitySet, CapabilityTargetSets); void serialize(Serializer const& serializer, CapabilityAtomSet& value) { @@ -326,85 +1210,61 @@ void serialize(Serializer const& serializer, CapabilitySet& value) } } -void serialize(ASTSerializer const& serializer, CandidateExtensionList& value) -{ - serialize(serializer, value.candidateExtensions); -} - -void serialize(ASTSerializer const& serializer, DeclAssociation& value) -{ - SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.kind); - serialize(serializer, value.decl); -} +// +// The `RequirementWitness` type is a variant, where the `m_flavor` +// field determines what data can follow. +// +// For now we will skip declaring those additional members as part +// of the fossilized representation, because we do not have any +// code that wants to navigate them directly on that representation: +// -void serialize(ASTSerializer const& serializer, DeclAssociationList& value) +template<> +struct FossilizedTypeTraits<RequirementWitness> { - serialize(serializer, value.associations); -} + struct FossilizedType + { + Fossilized<decltype(RequirementWitness::m_flavor)> m_flavor; + }; +}; -void serialize(ASTSerializer const& serializer, Modifiers& value) +void serialize(ASTSerializer const& serializer, RequirementWitness& value) { - SLANG_SCOPED_SERIALIZER_ARRAY(serializer); - if (isWriting(serializer)) - { - for (auto modifier : value) - { - serialize(serializer, modifier); - } - } - else + SLANG_SCOPED_SERIALIZER_VARIANT(serializer); + serialize(serializer, value.m_flavor); + switch (value.m_flavor) { - Modifier** link = &value.first; - - while (hasElements(serializer)) - { - Modifier* modifier = nullptr; - serialize(serializer, modifier); - - *link = modifier; - link = &modifier->next; - } - } -} + case RequirementWitness::Flavor::none: + break; -void serialize(ASTSerializer const& serializer, TypeExp& value) -{ - serialize(serializer, value.type); -} + case RequirementWitness::Flavor::declRef: + serialize(serializer, value.m_declRef); + break; -void serialize(ASTSerializer const& serializer, QualType& value) -{ - SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.type); - serialize(serializer, value.isLeftValue); - serialize(serializer, value.hasReadOnlyOnTarget); - serialize(serializer, value.isWriteOnly); -} + case RequirementWitness::Flavor::val: + serialize(serializer, value.m_val); + break; -void serialize(ASTSerializer const& serializer, Token& value) -{ - serializer->handleToken(value); + case RequirementWitness::Flavor::witnessTable: + serialize(serializer, value.m_obj); + break; + } } -void serialize(ASTSerializer const& serializer, SPIRVAsmOperand& value) -{ - SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.flavor); - serialize(serializer, value.token); - serialize(serializer, value.expr); - serialize(serializer, value.bitwiseOrWith); - serialize(serializer, value.knownValue); - serialize(serializer, value.wrapInId); - serialize(serializer, value.type); -} +// +// The `ValNodeOperand` type, used to store the operands of +// a `Val`-derived AST node, is a variant that gets handled +// similarly to `RequirementWitness` above. +// -void serialize(ASTSerializer const& serializer, SPIRVAsmInst& value) +template<> +struct FossilizedTypeTraits<ValNodeOperand> { - SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.opcode); - serialize(serializer, value.operands); -} + struct FossilizedType + { + Fossilized<decltype(ValNodeOperand::kind)> kind; + }; +}; void serialize(ASTSerializer const& serializer, ValNodeOperand& value) { @@ -423,25 +1283,108 @@ void serialize(ASTSerializer const& serializer, ValNodeOperand& value) } } -void serializeObject(ASTSerializer const& serializer, Name*& value, Name*) +// +// Now that we've covered the types that required hand-writing their +// serialization logic, we return to the types that will have their +// serialization logic generated using fiddle. +// +// We start with the ordinary types (everything other than the +// `NodeBase`-derived stuff). +// +// The code being generated here is the same sort of thing that was +// in the hand-written case for types like `MatrixCoord` way earlier +// in this file (in fact, `MatrixCoord` could be handled by this +// logic, and is only hand-written to help illustrate what's going on). +// +// WARNING: The way these declarations are currently being generated +// uses inheritance in the definitions of the `Fossilized_*` types, +// which isn't actually something we can be confident will work correctly +// across compilers. This isn't a problem right now, because there +// doesn't end up being any code that will actually use these generated +// types in the case where there is inhertance going on that would +// break C++ "standard layout" rules. +// +// TODO: If we reach a point where the use of inheritance ends up +// breaking things, then we'll have to do a fair bit more. It might +// seem like we could just turn the `: public Whatever` base into +// a `Whatever super;` field declaration, but that wouldn't give +// us the correct layout in cases where `Whatever` is an empty +// type. +// +#if 0 // FIDDLE TEMPLATE: +%for _,T in ipairs(astStructTypes) do +% TRACE(T) +/// Fossilized representation of a value of type `$T` +struct Fossilized_$T +% if T.directSuperClass then + : public Fossilized<$(T.directSuperClass)> +% else + : public FossilizedRecordVal +% end { - serializer->handleName(value); -} +% for _,f in ipairs(T.directFields) do + Fossilized<decltype($T::$f)> $f; +% end +}; -void serialize(ASTSerializer const& serializer, NameLoc& value) +/// Serialize a `value` of type `$T` +void serialize(ASTSerializer const& serializer, $T& value) { + SLANG_UNUSED(value); SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.name); - serialize(serializer, value.loc); +% if T.directSuperClass then + serialize(serializer, static_cast<$(T.directSuperClass)&>(value)); +% end +% for _,f in ipairs(T.directFields) do + serialize(serializer, value.$f); +% end } +%end +#else // FIDDLE OUTPUT: +#define FIDDLE_GENERATED_OUTPUT_ID 3 +#include "slang-serialize-ast.cpp.fiddle" +#endif // FIDDLE END -void serialize(ASTSerializer const& serializer, ContainerDeclDirectMemberDecls& value) +// +// After the ordinary struct types come the AST node classes. +// As with the declarations, the definitions here aren't all +// that different from how the structs are being handled. +// +// Note that the big "WARNING" on the comment before the struct +// cases also applies to the inheritance here. It just turns out +// that no code (currently) wants to navigate serialized AST +// nodes in memory (so the `Fossilized_*` declarations are largely +// just there to be convenient when debugging). +// +// One wrinkle we deal with here is that the `astNodeType` +// field is not treated as part of the "content" of an AST +// node for the purposes of the `_serializeASTNodeContents()` +// functions, but it needs to be present in the `Fossilized_NodeBase` +// type declaration in order for the layout of these types to +// be correct. We handle that with a small but ugly conditional +// in the logic to define `Fossilized_*`. +// +#if 0 // FIDDLE TEMPLATE: +%for _,T in ipairs(astNodeClasses) do + +/// Fossilized representation of a value of type `$T` +struct Fossilized_$T +% if T.directSuperClass then + : public Fossilized_$(T.directSuperClass) +% else + : public FossilizedVariantObj +% end { - serialize(serializer, value._refDecls()); -} +% if T == Slang.NodeBase then + Fossilized<ASTNodeType> astNodeType; +% end -#if 0 // FIDDLE TEMPLATE: -%for _,T in ipairs(Slang.NodeBase.subclasses) do +% for _,f in ipairs(T.directFields) do + Fossilized<decltype($T::$f)> $f; +% end +}; + +/// Serialize the contents of an AST node of type `$T` void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value) { SLANG_UNUSED(serializer); @@ -452,315 +1395,85 @@ void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value) % for _,f in ipairs(T.directFields) do serialize(serializer, value->$f); % end - } +} %end #else // FIDDLE OUTPUT: -#define FIDDLE_GENERATED_OUTPUT_ID 0 +#define FIDDLE_GENERATED_OUTPUT_ID 4 #include "slang-serialize-ast.cpp.fiddle" #endif // FIDDLE END -void serializeASTNodeContents(ASTSerializer const& serializer, NodeBase* node) -{ - ASTNodeDispatcher<NodeBase, void>::dispatch( - node, - [&](auto n) { _serializeASTNodeContents(serializer, n); }); -} - -enum class PseudoASTNodeType -{ - None, - ImportedModule, - ImportedDecl, -}; - -static PseudoASTNodeType _getPseudoASTNodeType(ASTNodeType type) -{ - return int(type) < 0 ? PseudoASTNodeType(~int(type)) : PseudoASTNodeType::None; -} - -static ASTNodeType _getAsASTNodeType(PseudoASTNodeType type) -{ - return ASTNodeType(~int(type)); -} - -struct ASTEncodingContext : ASTSerializerImpl -{ -public: - ASTEncodingContext( - ISerializerImpl* writer, - ModuleDecl* module, - SerialSourceLocWriter* sourceLocWriter) - : _writer(writer), _module(module), _sourceLocWriter(sourceLocWriter) - { - } - -private: - ISerializerImpl* _writer = nullptr; - ModuleDecl* _module = nullptr; - SerialSourceLocWriter* _sourceLocWriter = nullptr; - - virtual ISerializerImpl* getBaseSerializer() override { return _writer; } - - virtual void handleName(Name*& value) override; - virtual void handleSourceLoc(SourceLoc& value) override; - virtual void handleToken(Token& value) override; - virtual void handleASTNode(NodeBase*& node) override; - virtual void handleASTNodeContents(NodeBase* node) override; - - void _writeImportedModule(ModuleDecl* moduleDecl); - void _writeImportedDecl(Decl* decl, ModuleDecl* importedFromModuleDecl); - - ModuleDecl* _findModuleForDecl(Decl* decl) - { - for (auto d = decl; d; d = d->parentDecl) - { - if (auto m = as<ModuleDecl>(d)) - return m; - } - return nullptr; - } - - ModuleDecl* _findModuleDeclWasImportedFrom(Decl* decl) - { - auto declModule = _findModuleForDecl(decl); - if (declModule == nullptr) - return nullptr; - if (declModule == _module) - return nullptr; - return declModule; - } -}; - -struct ASTDecodingContext : ASTSerializerImpl -{ -public: - ASTDecodingContext( - Linkage* linkage, - ASTBuilder* astBuilder, - DiagnosticSink* sink, - ISerializerImpl* reader, - SerialSourceLocReader* sourceLocReader, - SourceLoc requestingSourceLoc) - : _linkage(linkage) - , _astBuilder(astBuilder) - , _sink(sink) - , _sourceLocReader(sourceLocReader) - , _requestingSourceLoc(requestingSourceLoc) - , _reader(reader) - { - } - -private: - Linkage* _linkage = nullptr; - ASTBuilder* _astBuilder = nullptr; - DiagnosticSink* _sink = nullptr; - SerialSourceLocReader* _sourceLocReader = nullptr; - SourceLoc _requestingSourceLoc; - ISerializerImpl* _reader = nullptr; - - virtual ISerializerImpl* getBaseSerializer() override { return _reader; } - - virtual void handleName(Name*& value) override; - virtual void handleSourceLoc(SourceLoc& value) override; - virtual void handleToken(Token& value) override; - virtual void handleASTNode(NodeBase*& outNode) override; - virtual void handleASTNodeContents(NodeBase* node) override; - - ModuleDecl* _readImportedModule(); - NodeBase* _readImportedDecl(); - - void _cleanUpASTNode(NodeBase* node) - { - if (auto expr = as<Expr>(node)) - { - expr->checked = true; - } - else if (auto decl = as<Decl>(node)) - { - decl->checkState = DeclCheckState::CapabilityChecked; - - if (auto genericDecl = as<GenericDecl>(node)) - { - _assignGenericParameterIndices(genericDecl); - } - else if (auto syntaxDecl = as<SyntaxDecl>(node)) - { - syntaxDecl->parseCallback = &parseSimpleSyntax; - syntaxDecl->parseUserData = (void*)syntaxDecl->syntaxClass.getInfo(); - } - else if (auto namespaceLikeDecl = as<NamespaceDeclBase>(node)) - { - auto declScope = _astBuilder->create<Scope>(); - declScope->containerDecl = namespaceLikeDecl; - namespaceLikeDecl->ownedScope = declScope; - } - } - } - - void _assignGenericParameterIndices(GenericDecl* genericDecl) - { - int parameterCounter = 0; - for (auto m : genericDecl->getDirectMemberDecls()) - { - if (auto typeParam = as<GenericTypeParamDeclBase>(m)) - { - typeParam->parameterIndex = parameterCounter++; - } - else if (auto valParam = as<GenericValueParamDecl>(m)) - { - valParam->parameterIndex = parameterCounter++; - } - } - } -}; - -// -// We are matching up the corresponding `handle*()` operations from the -// `AST{Encoding|Decoding}Context` types here, so that it is easier -// to visually verify that they are serializing the same data with the -// same ordering. // - +// Each of the `_serializeASTNodeContents()` functions handles one class in the hierarchy, +// but we need to be able to dispatch to the correct one based on the run-time type of +// a particular AST node. // -// AST{Encoding|Decoding}Context::handleName() +// The `serializeASTNodeContents()` function is a wrapper around those underscore-prefixed +// functions, and dispatches to the correct one based on the type of the given node. // -void ASTEncodingContext::handleName(Name*& value) -{ - serialize(ASTSerializer(this), value->text); -} - -void ASTDecodingContext::handleName(Name*& value) +void serializeASTNodeContents(ASTSerializer const& serializer, NodeBase* node) { - String text; - serialize(ASTSerializer(this), text); - value = _astBuilder->getNamePool()->getName(text); + ASTNodeDispatcher<NodeBase, void>::dispatch( + node, + [&](auto n) { _serializeASTNodeContents(serializer, n); }); } // -// AST{Encoding|Decoding}Context::handleSourceLoc() -// - -void ASTEncodingContext::handleSourceLoc(SourceLoc& value) -{ - ASTSerializer serializer(this); - SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer); - if (_sourceLocWriter != nullptr) - { - auto rawValue = _sourceLocWriter->addSourceLoc(value); - serialize(serializer, rawValue); - } -} - -void ASTDecodingContext::handleSourceLoc(SourceLoc& value) -{ - ASTSerializer serializer(this); - SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer); - if (hasElements(serializer)) - { - SerialSourceLocData::SourceLoc rawValue; - serialize(serializer, rawValue); - - if (_sourceLocReader) - { - value = _sourceLocReader->getSourceLoc(rawValue); - } - } -} - +// At this point we can get back to the handling of reading/writing actual AST nodes. // -// AST{Encoding|Decoding}Context::handleToken() +// We'll start with the writing logic, because that gives a good idea of +// the overall structure, which the reading logic will need to follow: // -void ASTDecodingContext::handleToken(Token& value) +void ASTSerialWriteContext::handleASTNode(ASTSerializer const& serializer, NodeBase*& node) { - ASTSerializer serializer(this); - - SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.type); - serialize(serializer, value.loc); - - serialize(serializer, value.flags); - + // The first complication that needs to be handled is that when we + // run into a `Decl*` that is being written, we need to check + // whether it comes from an imported module (as opposed to the + // module we are being asked to serialize). + // + if (auto decl = as<Decl>(node)) { - SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer); - if (hasElements(serializer)) + if (auto moduleDeclWasImportedFrom = _findModuleDeclWasImportedFrom(decl)) { - String content; - serialize(serializer, content); - - // An important note here is that we cannot just - // call `value.setContent(...)` and pass in an - // `UnownedStringSlice` of `content`, because the - // `Token` will not take ownership of its own - // textual content. + // If we find that the declaration is imported, then there + // are two sub-cases that we want to handle a bit differently: // - // Instead, we need to get the text we just loaded - // into something that the `Token` can refer info, - // and the easiest way to accomplish that is to - // represent the text using a `Name`. + // * When the `decl` we are writing is itself a module + // (and thus identical to `moduleDeclWasImportedFrom`). // - Name* name = _astBuilder->getNamePool()->getName(content); - value.setName(name); - } - } -} - -void ASTEncodingContext::handleToken(Token& value) -{ - ASTSerializer serializer(this); - - SLANG_SCOPED_SERIALIZER_STRUCT(serializer); - serialize(serializer, value.type); - serialize(serializer, value.loc); - - TokenFlags flags = TokenFlags(value.flags & ~TokenFlag::Name); - serialize(serializer, flags); - - { - SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer); - if (value.hasContent()) - { - String content = value.getContent(); - serialize(serializer, content); - } - } -} - -// -// AST{Encoding|Decoding}Context::handleASTNode() -// - -void ASTEncodingContext::handleASTNode(NodeBase*& node) -{ - if (auto decl = as<Decl>(node)) - { - if (auto importedFromModule = _findModuleDeclWasImportedFrom(decl)) - { - if (decl == importedFromModule) + // * The ordinary case, where `decl` is one of the declarations + // contained in `moduleDeclWasImportedFrom`. + // + if (decl == moduleDeclWasImportedFrom) { - _writeImportedModule(importedFromModule); + _writeImportedModule(serializer, moduleDeclWasImportedFrom); return; } else { - _writeImportedDecl(decl, importedFromModule); + _writeImportedDecl(serializer, decl, moduleDeclWasImportedFrom); return; } } } - ASTSerializer serializer(this); - + // The next complication we need to deal with is that + // for most AST nodes we will want to defer writing + // out their contents until a later step (to avoid + // going into an infinite recursion when there are + // cycles in the object graph), but because of the + // way that AST nodes derived from `Val` are + // deduplicated as part of creation, we can't + // defer reading their operands. + // + // Thus we branch here based on whether we are + // writing a `Val`-derived node, or not. + // if (auto val = as<Val>(node)) { val = val->resolve(); - // On the reading side of things, sublcasses of `Val` - // are deduplicated as part of creation, and will read the - // operands out immediately, so we mirror that approach - // on the writing side to make sure the code is consistent. - // serialize(serializer, val->astNodeType); serialize(serializer, val->m_operands); } @@ -771,26 +1484,79 @@ void ASTEncodingContext::handleASTNode(NodeBase*& node) } } -void ASTDecodingContext::handleASTNode(NodeBase*& outNode) +// +// In order to be able to encode the cases for imported +// modules and declarations, we get a little bit "clever" +// with the representation and store some out-of-range +// values in an `ASTNodeType` to represent these +// additional cases. +// + +enum class PseudoASTNodeType { - ASTSerializer serializer(this); + None, + ImportedModule, + ImportedDecl, +}; + +// All valid `ASTNodeType`s will be non-negative integers, +// so the `PseudoASTNodeType` are encoded into an +// `ASTNodeType` as negative values that are the bitwise +// negation of their value in the `PseudoASTNodeType` enumeration. + +static PseudoASTNodeType _getPseudoASTNodeType(ASTNodeType type) +{ + return Int32(type) < 0 ? PseudoASTNodeType(~Int32(type)) : PseudoASTNodeType::None; +} + +static ASTNodeType _getAsASTNodeType(PseudoASTNodeType type) +{ + return ASTNodeType(~Int32(type)); +} + +// +// With the `PseudoASTNodeType` trickery introduced, +// it is possible to show the reading logic for +// `NodeBase`-derived types: +// - ASTNodeType typeTag = ASTNodeType(0); +void ASTSerialReadContext::handleASTNode(ASTSerializer const& serializer, NodeBase*& outNode) +{ + // We start by reading the `ASTNodeType`, because + // we will dispatch differently based on what + // value we see there. + // + ASTNodeType typeTag = ASTNodeType::NodeBase; serialize(serializer, typeTag); + + // In the case where the `ASTNodeType` is actually + // smuggling in one of our `PseudoASTNodeType` + // values, we can delegate to the correct + // subroutine to handle that case. + // + // These two cases mirror the cases for imported + // modules and declarations in + // `ASTSerialWriter::handleASTNode()`. + // switch (_getPseudoASTNodeType(typeTag)) { default: break; case PseudoASTNodeType::ImportedModule: - outNode = _readImportedModule(); + outNode = _readImportedModule(serializer); return; case PseudoASTNodeType::ImportedDecl: - outNode = _readImportedDecl(); + outNode = _readImportedDecl(serializer); return; } + // Next we check whether the `typeTag` + // indicates that we are looking at a + // subclass of `Val`, because we need + // to handle those differently. + // auto syntaxClass = SyntaxClass<NodeBase>(typeTag); if (syntaxClass.isSubClassOf<Val>()) { @@ -811,6 +1577,11 @@ void ASTDecodingContext::handleASTNode(NodeBase*& outNode) } else { + // In the ordinary case, we can allocate an empty + // shell of an AST node to represent the object, + // and defer actually serializing the contents + // of that object until later. + auto node = syntaxClass.createInstance(_astBuilder); outNode = node; @@ -819,41 +1590,36 @@ void ASTDecodingContext::handleASTNode(NodeBase*& outNode) } // -// AST{Encoding|Decoding}Context::handleASTNodeContents() -// - -void ASTEncodingContext::handleASTNodeContents(NodeBase* node) -{ - ASTSerializer serializer(this); - serializeASTNodeContents(serializer, node); -} - -void ASTDecodingContext::handleASTNodeContents(NodeBase* node) -{ - ASTSerializer serializer(this); - serializeASTNodeContents(serializer, node); - - _cleanUpASTNode(node); -} - -// -// AST{Encoding|Decoding}Context::_{write|read}ImportedModule() +// Imported modules are serialized using one of the +// `PseudoASTNodeType` cases as its tag, and then +// store a single field with the name of the module. // -void ASTEncodingContext::_writeImportedModule(ModuleDecl* moduleDecl) +void ASTSerialWriteContext::_writeImportedModule( + ASTSerializer const& serializer, + ModuleDecl* moduleDecl) { ASTNodeType type = _getAsASTNodeType(PseudoASTNodeType::ImportedModule); auto moduleName = moduleDecl->getName(); - ASTSerializer serializer(this); serialize(serializer, type); serialize(serializer, moduleName); } -ModuleDecl* ASTDecodingContext::_readImportedModule() +ModuleDecl* ASTSerialReadContext::_readImportedModule(ASTSerializer const& serializer) { - ASTSerializer serializer(this); - + // In the reading direction, we need to actually + // kick off the logic to import the module + // that this one depends on. + // + // TODO: It might be cleaner if we changed up + // the representation so that imported modules + // get listed at the top level, as part of + // the `ASTModuleInfo`, and thus allowing the + // process of importing them to be handled + // by logic that isn't deep in the guts of the + // serialization code. + // Name* moduleName = nullptr; serialize(serializer, moduleName); auto module = _linkage->findOrImportModule(moduleName, _requestingSourceLoc, _sink); @@ -865,24 +1631,28 @@ ModuleDecl* ASTDecodingContext::_readImportedModule() } // -// AST{Encoding|Decoding}Context::_{write|read}ImportedModule() +// Imported declarations use a `PseudoASTNodeType` +// to define their type tag, and are then serialized +// like a struct that contains a poitner to the +// module that the declaration was imported from, +// and the mangled name of the specific declaration. // -void ASTEncodingContext::_writeImportedDecl(Decl* decl, ModuleDecl* importedFromModuleDecl) +void ASTSerialWriteContext::_writeImportedDecl( + ASTSerializer const& serializer, + Decl* decl, + ModuleDecl* importedFromModuleDecl) { ASTNodeType type = _getAsASTNodeType(PseudoASTNodeType::ImportedDecl); auto mangledName = getMangledName(getCurrentASTBuilder(), decl); - ASTSerializer serializer(this); serialize(serializer, type); serialize(serializer, importedFromModuleDecl); serialize(serializer, mangledName); } -NodeBase* ASTDecodingContext::_readImportedDecl() +NodeBase* ASTSerialReadContext::_readImportedDecl(ASTSerializer const& serializer) { - ASTSerializer serializer(this); - ModuleDecl* importedFromModuleDecl = nullptr; String mangledName; @@ -896,7 +1666,7 @@ NodeBase* ASTDecodingContext::_readImportedDecl() } auto importedDecl = - importedFromModule->findExportFromMangledName(mangledName.getUnownedSlice()); + importedFromModule->findExportedDeclByMangledName(mangledName.getUnownedSlice()); if (!importedDecl) { SLANG_ABORT_COMPILATION( @@ -906,6 +1676,288 @@ NodeBase* ASTDecodingContext::_readImportedDecl() } // +// Handling the contents of an AST node is mostly the +// same logic between the reading and writing directions. +// The only difference is that when we are reading in +// an AST node there is some cleanup work we have to +// do after reading is complete, in order to make +// the AST node actually usable. +// + +void ASTSerialWriteContext::handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node) +{ + serializeASTNodeContents(serializer, node); +} + +void ASTSerialReadContext::handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node) +{ + serializeASTNodeContents(serializer, node); + + _cleanUpASTNode(node); +} + +void ASTSerialReadContext::_cleanUpASTNode(NodeBase* node) +{ + if (auto expr = as<Expr>(node)) + { + expr->checked = true; + } + else if (auto decl = as<Decl>(node)) + { + decl->checkState = DeclCheckState::CapabilityChecked; + + if (auto genericDecl = as<GenericDecl>(node)) + { + _assignGenericParameterIndices(genericDecl); + } + else if (auto syntaxDecl = as<SyntaxDecl>(node)) + { + syntaxDecl->parseCallback = &parseSimpleSyntax; + syntaxDecl->parseUserData = (void*)syntaxDecl->syntaxClass.getInfo(); + } + else if (auto namespaceLikeDecl = as<NamespaceDeclBase>(node)) + { + auto declScope = _astBuilder->create<Scope>(); + declScope->containerDecl = namespaceLikeDecl; + namespaceLikeDecl->ownedScope = declScope; + } + +#if SLANG_ENABLE_AST_DESERIALIZATION_STATS + if (auto moduleDecl = as<ModuleDecl>(decl->parentDecl)) + { + auto& deserializedCount = _sharedContext->_deserializedTopLevelDeclCount; + deserializedCount++; + + Count totalCount = moduleDecl->getDirectMemberDeclCount(); + + fprintf( + stderr, + "loaded %d / %d direct members of module '%s' (%f%%)\n", + int(deserializedCount), + int(totalCount), + moduleDecl->getName() ? moduleDecl->getName()->text.getBuffer() : "", + float(deserializedCount) * 100.0f / float(totalCount)); + } +#endif + + // TODO(tfoley): If we are disabling on-demand deserialization + // for now (because of other changes that are needed before we + // can enable it), then we will intentionally load all of the + // direct member declarations of a container declarations + // up-front. +#if SLANG_DISABLE_ON_DEMAND_AST_DESERIALIZATION + if (auto containerDecl = as<ContainerDecl>(decl)) + { + auto& directMemberDecls = containerDecl->getDirectMemberDecls(); + SLANG_UNUSED(directMemberDecls); + } +#endif + } +} + +void ASTSerialReadContext::_assignGenericParameterIndices(GenericDecl* genericDecl) +{ + int parameterCounter = 0; + for (auto m : genericDecl->getDirectMemberDecls()) + { + if (auto typeParam = as<GenericTypeParamDeclBase>(m)) + { + typeParam->parameterIndex = parameterCounter++; + } + else if (auto valParam = as<GenericValueParamDecl>(m)) + { + valParam->parameterIndex = parameterCounter++; + } + } +} + + +// +// +// + +template<typename K, typename V> +static void _sortByKey(List<KeyValuePair<K, V>>& array) +{ + array.sort([](KeyValuePair<K, V> const& lhs, KeyValuePair<K, V> const& rhs) + { return lhs.key < rhs.key; }); +} + +static void _collectASTModuleInfo(ModuleDecl* moduleDecl, ASTModuleInfo& moduleInfo) +{ + auto module = moduleDecl->module; + + moduleInfo.moduleDecl = moduleDecl; + collectBuiltinDeclsThatNeedRegistration(moduleDecl, moduleInfo.declsToRegister); + + // We want to store a dictionary of exported declarations + // from the module, mapping from a mangled name to the + // declaration with that name. + // + // In order to accelerate search on the reading side, we will + // conspire to make the entries in the serialized dictionary + // be in sorted order by their keys. + // + List<KeyValuePair<String, Decl*>> exportNameDeclPairs; + + auto exportCount = module->getExportedDeclCount(); + for (Index exportIndex = 0; exportIndex < exportCount; ++exportIndex) + { + auto exportMangledName = String(module->getExportedDeclMangledName(exportIndex)); + auto exportDecl = module->getExportedDecl(exportIndex); + + exportNameDeclPairs.add(KeyValuePair(exportMangledName, exportDecl)); + } + _sortByKey(exportNameDeclPairs); + + for (auto& entry : exportNameDeclPairs) + { + moduleInfo.mapMangledNameToDecl.add(entry.key, entry.value); + } +} + +// +// The `ContainerDeclDirectMemberDecls` type is serialized via the +// intermediate type `ContainerDeclDirectMemberDeclsInfo`. We start +// by defining the logic to collect the required information: +// + +static ContainerDeclDirectMemberDeclsInfo _collectContainerDeclDirectMemberDeclsInfo( + ContainerDeclDirectMemberDecls const& decls) +{ + ContainerDeclDirectMemberDeclsInfo info; + info.decls = decls.getDecls(); + + // In order to ensure that the accelerators that we serialize + // match with those created by the compiler front-end, we + // will pull the data from `decls` via its public API rather + // than try to reconstruct any of that information. + // + // Because the public API of `ContainerDeclDirectMemberDecls` + // traffics in `Decl*`s but we want to serialize indices, + // we will create a dictionary to reverse the mapping so that + // we can serialize out indices. + // + Dictionary<Decl*, FossilUInt> mapDeclToIndex; + Count declCount = info.decls.getCount(); + for (Index i = 0; i < declCount; ++i) + { + auto decl = info.decls[i]; + if (!decl) + continue; + + mapDeclToIndex[decl] = FossilUInt(i); + } + + // With our decl-to-index mapping created, filling + // out the to-be-serialized list of transparent + // declarations is a simple matter. + // + for (auto decl : decls.getTransparentDecls()) + { + if (!decl) + continue; + + auto found = mapDeclToIndex.tryGetValue(decl); + SLANG_ASSERT(found); + + info.transparentDeclIndices.add(*found); + } + + // Handling the name-to-declaration mapping is a bit + // more complicated, simply because we want to store + // the entries of the resulting dictionary in sorted + // order to enable them to be looked up via a binary + // search. Thus we start by creating a list of the + // key-value pairs, which we will then sort. + // + List<KeyValuePair<String, FossilUInt>> nameIndexPairs; + for (auto& entry : decls.getMapFromNameToLastDeclOfThatName()) + { + auto name = entry.first; + if (!name) + continue; + + auto decl = entry.second; + if (!decl) + continue; + + auto found = mapDeclToIndex.tryGetValue(decl); + SLANG_ASSERT(found); + + nameIndexPairs.add(KeyValuePair(name->text, *found)); + } + _sortByKey(nameIndexPairs); + + // The `info.mapNameToDeclIndex` is stored as an `OrderedDictionary`, + // so it will preserve the order in which we insert its entries here. + // + for (auto& entry : nameIndexPairs) + { + info.mapNameToDeclIndex.add(entry.key, entry.value); + } + + return info; +} + +void ASTSerialWriteContext::handleContainerDeclDirectMemberDecls( + ASTSerializer const& serializer, + ContainerDeclDirectMemberDecls& value) +{ + // Writing the members of a container declaration is + // just a matter of collecting the information into + // the intermediate type, and then writing *that*. + + ContainerDeclDirectMemberDeclsInfo info = _collectContainerDeclDirectMemberDeclsInfo(value); + + serialize(serializer, info); +} + +void ASTSerialReadContext::handleContainerDeclDirectMemberDecls( + ASTSerializer const& serializer, + ContainerDeclDirectMemberDecls& value) +{ + // In the reading direction, we will intentionally + // *not* deserialize things the usual way, because + // we want to support deserializing only a subset + // of the direct member declarations of a given + // container, on-demand. + + // We start by reading a pointer to a single fossilized + // value from the underlying `Fossil::Reader` that we + // are using, and cast it to the type that we expect to + // find there. + // + // The underlying reader was passed in as part of the + // `serializer` parameter, but it is only typed as an + // `ISerializerImpl`, whereas we *know* it has a more + // specific type, which we want to make use of. + // + ISerializerImpl* readerImpl = serializer.getImpl(); + auto fossilReader = static_cast<Fossil::SerialReader*>(readerImpl); + // + auto fossilizedInfo = + (Fossilized<ContainerDeclDirectMemberDeclsInfo>*)fossilReader->readValPtr().get(); + + // We can read specific fields out of the `fossilizedInfo` + // without triggering full deserialization. At this point + // we will do exactly that to read the number of direct + // member declarations. + // + auto declCount = fossilizedInfo->decls.getElementCount(); + + // We will set up the `ContainerDeclDirectMemberDecls` to + // be in on-demand deserialization mode, in which it will + // retain a pointer to this context (which is being used for + // the entire AST module), along with a pointer to the + // fossilized information for this specific container's + // member declarations. + // + value._initForOnDemandDeserialization(this, fossilizedInfo, declCount); +} + + +// // {write|read}SerializedModuleAST() // @@ -917,43 +1969,457 @@ void writeSerializedModuleAST( // TODO: we might want to have a more careful pass here, // where we only encode the public declarations. + // Rather than serialize the `ModuleDecl` directly, we instead + // collect the information we want to serialize into an intermediate + // `ASTModuleInfo` value, and then serialize *that*. + // + // This choice allows us to build up some data structures that will + // be very useful when reading the serialized data later, and that + // would not naturally "fall out" of serializing the module more + // directly. + + ASTModuleInfo moduleInfo; + _collectASTModuleInfo(moduleDecl, moduleInfo); + + // At the most basic, we are building a single "blob" of data + // (in the sense of the `ISlangBlob` interface). + // BlobBuilder blobBuilder; { + // The architecture of the serialization system means that + // we need a few steps to set up everything before we can + // actually call `serialize()`: + // + // * We need an implementation of `ISerializerImpl` to do + // the actual writing, which in this case will be a + // `Fossil::SerialWriter`. + // + // * We need the additional context information that many + // of the AST types require in their `serialize()` overloads, + // which will be an `ASTSerialWriteContext`. + // + // * We need to wrap those two values up in an `ASTSerializer` + // (which is more or less just a pair of pointers, to the two + // values described above). + // Fossil::SerialWriter writer(blobBuilder); + ASTSerialWriteContext context(moduleDecl, sourceLocWriter); + ASTSerializer serializer(&writer, &context); + + // Once we have our `serializer`, we can finally invoke + // `serialize()` on the `ASTModuleInfo` to cause everything + // to be recursively written. + // + serialize(serializer, moduleInfo); - ASTEncodingContext context(&writer, moduleDecl, sourceLocWriter); - serialize(ASTSerializer(&context), moduleDecl); + // Note that we wrapped these steps in a scope, because + // it is the destructor for `Fossil::SerialWriter` that + // will actually "flush" any pending serialization operations + // and cause the full blob to be written. } + // We can now grab the serialized data as a single `ISlangBlob`. + // ComPtr<ISlangBlob> blob; blobBuilder.writeToBlob(blob.writeRef()); + // While the AST serialization system is using fossil, the + // overall module serialization is still based on the RIFF + // container format, so we immediately turn around and + // add the blob we just created as a single data chunk in + // the RIFF hierarchy. + // + // TODO: This step copies the entire blob. If that copy + // operation ever becomes a performance concern, we should + // be able to tweak things so that the `BlobBuilder` uses + // the same memory arena that the RIFF builder is using, + // and then employ the `RIFF::BuildCursor::addUnownedData()` + // method to add the data without copying. + // void const* data = blob->getBufferPointer(); size_t size = blob->getBufferSize(); - cursor.addDataChunk(PropertyKeys<Module>::ASTModule, data, size); } +// +// The reading direction is significantly more subtle than the +// writing direction, because we will be traversing some of +// the fossilized data structures without first deserializing +// them into ordinary C++ objects. +// +// In order for this code to work, we need to know that the +// fossilized layout for the types we will access directly +// (such as `ASTModuleInfo`) will exactly match what we expect. +// +// As a small safety measure, we include some static assertions +// about the key properties we expect of the fossilized `ASTModuleInfo`. +// + +static_assert(sizeof(Fossilized<ASTModuleInfo>) == 12); +static_assert(offsetof(Fossilized<ASTModuleInfo>, moduleDecl) == 0); +static_assert(offsetof(Fossilized<ASTModuleInfo>, declsToRegister) == 4); +static_assert(offsetof(Fossilized<ASTModuleInfo>, mapMangledNameToDecl) == 8); + ModuleDecl* readSerializedModuleAST( Linkage* linkage, ASTBuilder* astBuilder, DiagnosticSink* sink, + ISlangBlob* blobHoldingSerializedData, RIFF::Chunk const* chunk, SerialSourceLocReader* sourceLocReader, SourceLoc requestingSourceLoc) { + // 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. + // auto dataChunk = as<RIFF::DataChunk>(chunk); + if (!dataChunk) + { + SLANG_UNEXPECTED("invalid format for serialized module AST"); + } + + Fossil::AnyValPtr rootValPtr = + Fossil::getRootValue(dataChunk->getPayload(), dataChunk->getPayloadSize()); + if (!rootValPtr) + { + SLANG_UNEXPECTED("invalid format for serialized module AST"); + } - auto rootVal = Fossil::getRootValue(dataChunk->getPayload(), dataChunk->getPayloadSize()); + // We don't want to simply mirror the `writeSerializedModuleAST()` logic + // here and deserialize an entire `ASTModuleInfo`. Instead, we will + // traverse the `Fossilized<ASTModuleInfo>` directly, and extract only + // the information we need. + // + // The `rootValPtr` above uses the `Fossil::AnyValPtr` type, which + // is basically a dynamically-typed pointer to fossilized data of + // any type, and carries around its own layout information. We could + // in principle traverse the structure using that type by making dynamic + // queries (and doing so would let us detect various error cases where + // the serialized format might not match what we expect), but instead + // we are going to simply perform an uncheckedcast on that dynamically-typed + // pointer to get out a statically-typed pointer to what we expect to + // find there. + // + Fossilized<ASTModuleInfo>* fossilizedModuleInfo = cast<Fossilized<ASTModuleInfo>>(rootValPtr); + + // We now have enough information to construct an `ASTSerialReadContext`, + // which is the mirror to the `ASTSerialWriteContext`, but which has the + // important difference that the `ASTSerialReadContext` is allowed to + // persist past when this function returns. Thus we cannot allocte the + // read context on the stack like we did for the write context, and + // we instead allocate it as a reference-counted object. + // + auto sharedDecodingContext = RefPtr(new ASTSerialReadContext( + linkage, + astBuilder, + sink, + sourceLocReader, + requestingSourceLoc, + fossilizedModuleInfo, + blobHoldingSerializedData)); + + // The `sharedDecodingContext` will allow us to deserialize individual + // `Decl`s from the AST one-by-one. One declaration that we *know* + // we need right away is the actual `ModuleDecl` (since we need to + // return it from this function). + // + ModuleDecl* moduleDecl = + as<ModuleDecl>(sharedDecodingContext->readFossilizedDecl(fossilizedModuleInfo->moduleDecl)); + SLANG_ASSERT(moduleDecl); + +#if SLANG_ENABLE_AST_DESERIALIZATION_STATS + fprintf( + stderr, + "finished loading the `ModuleDecl` for '%s'\n", + moduleDecl->getName()->text.getBuffer()); +#endif + + // In the case where we are reading one of the builtin modules (e.g. + // the core module), there may be declarations inside that module + // that need to be registered with the `SharedASTBuilder`, because + // parts of the C++ compiler code need to be able to form references + // to those declarations. + // + // We will handle those here by traversing the fossilized equivalent + // of the `ASTModuleInfo::declsToRegister` array, then deserializing + // and registering each entry we find. + // + for (Fossilized<Decl>* fossilizedDecl : fossilizedModuleInfo->declsToRegister) + { + Decl* decl = sharedDecodingContext->readFossilizedDecl(fossilizedDecl); + registerBuiltinDecl(astBuilder, decl); + } - Fossil::SerialReader reader(rootVal); +#if SLANG_ENABLE_AST_DESERIALIZATION_STATS + fprintf( + stderr, + "finished registering builtins for '%s'\n", + moduleDecl->getName()->text.getBuffer()); +#endif - ASTDecodingContext - context(linkage, astBuilder, sink, &reader, sourceLocReader, requestingSourceLoc); + // + // At this point any further data in the serialized AST can be read + // on-demand as needed, via the accessor methods on `ContainerDeclDirectMemberDecls` + // and `ModuleDecl` that are implemented below. + // - ModuleDecl* moduleDecl = nullptr; - serialize(ASTSerializer(&context), moduleDecl); return moduleDecl; } +// +// A key facility that makes on-demand deserialization possible is +// the ability to read individual serialized declarations out of +// a module, just based on a pointer to their fossilized representation. +// + +Decl* ASTSerialReadContext::readFossilizedDecl(Fossilized<Decl>* fossilizedDecl) +{ + // AST nodes are all fossilized as variants, which means that they + // carrying their own layout information. We can exploit this fact + // to get from the raw pointer that was passed in to a `Fossil::AnyValPtr` + // that includes the layout information that a `Fossil::SerialReader` + // needs. + // + Fossil::AnyValPtr contentValPtr = getVariantContentPtr(fossilizedDecl); + + // One subtle issue is that when we call `serialize()` below to read + // a `Decl*`, the `SerialReader` wants to *read* a pointer to the + // serialized object from its current cursor position. But what we have + // is a pointer to the object... not a pointer to a *pointer* to the object. + // + // We thus tweak the `InitialStateType` used for the `SerialReader` to + // tell it that it should treat our `contentValPtr` as if there was + // an additional level of pointer indirection above it. + // + Fossil::SerialReader reader( + _readContext, + contentValPtr, + Fossil::SerialReader::InitialStateType::PseudoPtr); + ASTSerializer serializer(&reader, this); + + Decl* decl = nullptr; + serialize(serializer, decl); + return decl; +} + +// +// We now turn our attention to the various accessors on AST types +// that need to read from the serialized data on-demand. +// +// One key design choice in the current encoding is that the +// fossilized dictionaries that we will use for lookup operations +// are written as ordinary `FossilizedDictionary<K,V>` values (which +// are ultimately just flat arrays of `K`,`V` pairs), but have their +// keys sorted before being written out. +// +// We can thus look up entries in these fossilized dictionaries +// using a binary search. +// + +template<typename T> +T const* _findEntryInFossilizedDictionaryWithSortedKeys( + FossilizedDictionary<FossilizedString, T> const& dictionary, + UnownedStringSlice const& key) +{ + Index lo = 0; + Index hi = dictionary.getElementCount() - 1; + + auto elements = dictionary.getBuffer(); + + while (lo <= hi) + { + Index mid = lo + ((hi - lo) >> 1); + + auto element = elements + mid; + int cmp = compare(element->key, key); + if (cmp == 0) + return &element->value; + + if (cmp < 0) + lo = mid + 1; + else + hi = mid - 1; + } + + return nullptr; +} + +Decl* ModuleDecl::_findSerializedDeclByMangledExportName(UnownedStringSlice const& mangledName) +{ + // Each of the accessors defined in this file should only + // ever be invoked in the case where the corresponding + // AST node is using on-demand deserialization. + // + SLANG_ASSERT(isUsingOnDemandDeserializationForExports()); + + // The `context` pointer stored in the `ContainerDeclDirectMemberDecls` type is + // a raw `RefPtr<RefObject>`, so that the definition of the `ASTSerialReadContext` + // doesn't need to be exposed outside this file. + // + // In order to access the context pointer, we thus need to cast it. + // + auto sharedContext = + as<ASTSerialReadContext>(_directMemberDecls.onDemandDeserialization.context); + + // The `sharedContext` has the information needed to do lookup + // based on mangled names, so we delegate the actual work to it. + // + return sharedContext->findExportedDeclByMangledName(mangledName); +} + +Decl* ASTSerialReadContext::findExportedDeclByMangledName(UnownedStringSlice const& mangledName) +{ + // The read context has retained a pointer to the `Fossilized<ASTModuleInfo>`, + // which allows us to perform a lookup in the serialized `mapMangledNameToDecl` + // without ever deserializing it. + // + auto found = _findEntryInFossilizedDictionaryWithSortedKeys( + _fossilizedModuleInfo->mapMangledNameToDecl, + mangledName); + if (!found) + return nullptr; + + // If the given `mangledName` does indeed map to a pointer to + // a fossilized declaration, then we will read the declaration + // on-demand before returing it. + // + // Note that if we've seen the same declaration before (whether + // via a previous call to `findExportedDeclByMangledName()` or + // through some other path leading to `readFossilizedDecl()`, + // this will return the same `Decl*` that was previously + // deserialized). + // + auto decl = readFossilizedDecl(*found); + return decl; +} + +Decl* ContainerDeclDirectMemberDecls::_readSerializedDeclsOfName(Name* name) const +{ + // All of these accessors on `ContainerDeclDirectMemberDecls` start with + // a similar pattern of asserting that they are only used when on-demand + // deserialization is active, and then casting the `void*` that is stored + // in the AST representation over to the correct fossilized type (a type + // that is only used/visible within this file). + // + SLANG_ASSERT(isUsingOnDemandDeserialization()); + auto& fossilizedInfo = + *(Fossilized<ContainerDeclDirectMemberDeclsInfo>*)onDemandDeserialization.data; + + // TODO: It isn't clear why the compiler will sometimes perform by-name + // lookup using a null name, but it happens and thus the code here + // needs to be defensive against that scenario. + // + if (name == nullptr) + return nullptr; + + // Once we are sure that `name` is valid, the overall logic here + // is quite similar to `findExportedDeclByMangledName()` above: + // we do a lookup in the serialized dictionary by binary search. + // + auto found = _findEntryInFossilizedDictionaryWithSortedKeys( + fossilizedInfo.mapNameToDeclIndex, + name->text.getUnownedSlice()); + if (!found) + return nullptr; + + // Unlike the case for `findExportedDeclByMangledName()`, the + // dictionary stored on a container declaration only holds indices + // rather than pointers. The reason for this is that we want to + // bottleneck deserialization of direct member declarations through + // the by-index accessor, when possilbe. + // + // One thing to note here is that this function is being called + // to get the list of *all* declarations with a given name, but + // it seems to only fetch one. In practice this works fine because + // the `_prevInContainerWithSameName` field in `Decl` is part of + // the state that gets serialized for a `Decl`, so that loading + // the head of the linked list will cause the rest to get + // deserialized eagerly. + // + // TODO: We could avoid serializing the `_prevInContainerWithSameName` + // field on every `Decl` (since it is almost always null), and instead + // use a more complicated lookup structure here. E.g., the dictionary + // entries could either refer to a single declaration by index (the + // common case, we hope) or to a sequence of two or more indices stored + // in some side-band structure (in the case where multiple declarations + // have the same name). For now we are sticking with the simpler + // representation; further complexity would need to be motivated by + // profiling information showing there's a problem to be solved. + // + Index declIndex = *found; + return getDecl(declIndex); +} + +void ContainerDeclDirectMemberDecls::_readSerializedTransparentDecls() const +{ + SLANG_ASSERT(isUsingOnDemandDeserialization()); + auto& fossilizedInfo = + *(Fossilized<ContainerDeclDirectMemberDeclsInfo>*)onDemandDeserialization.data; + + // This particular function works by filling in the `filteredListOfTransparentDecls` + // part of the lookup accelerators. If it has been called once and put anything + // into that array, then `filteredListOfTransparentDecls` should be used instead + // of calling this method again. We enforce this invariant in an attempt to + // avoid overhead that might be associated with this method. + // + SLANG_ASSERT(accelerators.filteredListOfTransparentDecls.getCount() == 0); + + // If this is the first time this method is being called (or, in the very common + // corner case, when there are no transparent decls at all...) we loop over the + // fossilized array holding the indices of the transparent members. + // + for (auto index : fossilizedInfo.transparentDeclIndices) + { + // For each index that is found, we do a by-index query for + // the member and then add it to the list. This is another + // case of us trying to bottleneck access to members through + // the by-index accessor as much as possible. + // + auto decl = getDecl(index); + accelerators.filteredListOfTransparentDecls.add(decl); + } +} + +Decl* ContainerDeclDirectMemberDecls::_readSerializedDeclAtIndex(Index index) const +{ + SLANG_ASSERT(isUsingOnDemandDeserialization()); + auto& fossilizedInfo = + *(Fossilized<ContainerDeclDirectMemberDeclsInfo>*)onDemandDeserialization.data; + + // + // It isn't visible here, but `ContainerDeclDirectMemberDecls::getDecl(index)` + // will automatically cache the decl that we return, so that subsequent queries + // for the same index shouldn't call this method at all (unless we end up + // returning null, for some unexpected reason). + // + + // The logic here is fairly simple: we directly read from the array of (fossilized) + // declaration pointers in the (fossilized) `ContainerDeclDirectMemberDeclsInfo`, + // to get a pointer to the (fossilized) declaration we want. + // + // Note that it is important that the variable here is *not* being declared + // with `auto`. If we were to simply write `auto fossilizedDecl` then the type + // that gets inferred would be `Fossilized<Decl*>` which is a `FossilizedPtr<...>` + // - a 32-bit relative pointer. On systems with a 64-bit address space, it is + // not guaranteed that a 32-bit offset is enough to refer to part of the serialized + // AST blob (in the heap) from this local variable (on the stack). + // + // The two options are to use `auto&` so that we capture a *reference* to the + // fossilized pointer (rather than try to copy it), or to declare the variable + // as an ordinary "live" pointer. + // + Fossilized<Decl>* fossilizedDecl = fossilizedInfo.decls[index]; + + // Once we have a pointer to the (fossilized) declaration that we want, + // we can use the `ASTSerialReadContext` to read it on-demand. Because + // the `context` pointer declared on the actual AST type is untyped + // (to avoid needing to expose `ASTSerialReadContext` outside this file), + // we need to cast the pointer before we can perform the read. + // + auto sharedContext = as<ASTSerialReadContext>(onDemandDeserialization.context); + auto decl = sharedContext->readFossilizedDecl(fossilizedDecl); + return decl; +} + } // namespace Slang diff --git a/source/slang/slang-serialize-ast.h b/source/slang/slang-serialize-ast.h index 86ba6e772..ec4023a62 100644 --- a/source/slang/slang-serialize-ast.h +++ b/source/slang/slang-serialize-ast.h @@ -22,6 +22,7 @@ ModuleDecl* readSerializedModuleAST( Linkage* linkage, ASTBuilder* astBuilder, DiagnosticSink* sink, + ISlangBlob* blobHoldingSerializedData, RIFF::Chunk const* chunk, SerialSourceLocReader* sourceLocReader, SourceLoc requestingSourceLoc); diff --git a/source/slang/slang-serialize-fossil.cpp b/source/slang/slang-serialize-fossil.cpp index da1516399..003f4227a 100644 --- a/source/slang/slang-serialize-fossil.cpp +++ b/source/slang/slang-serialize-fossil.cpp @@ -56,7 +56,7 @@ void SerialWriter::_initialize(ChunkBuilder* chunk) // that the last field of the header is a relative pointer // to the root-value chunk. // - headerChunk->writeRelativePtr<Fossil::RelativePtrOffset>(rootValueChunk); + headerChunk->writeRelativePtr<FossilInt>(rootValueChunk); // The root value should always be a variant, and we want to // set up to write into it in a reasonable way. @@ -140,7 +140,7 @@ void SerialWriter::handleFloat64(double& value) void SerialWriter::handleString(String& value) { auto size = value.getLength(); - if (_shouldEmitWithPointerIndirection(FossilizedValKind::String)) + if (_shouldEmitPotentiallyIndirectValueWithPointerIndirection()) { if (size == 0) { @@ -154,14 +154,14 @@ void SerialWriter::handleString(String& value) auto ptrLayout = (ContainerLayoutObj*)_reserveDestinationForWrite(FossilizedValKind::Ptr); - _mergeLayout(ptrLayout->baseLayout, FossilizedValKind::String); + _mergeLayout(ptrLayout->baseLayout, FossilizedValKind::StringObj); _commitWrite(ValInfo::relativePtrTo(existingChunk)); return; } } - _pushPotentiallyIndirectValueScope(FossilizedValKind::String); + _pushPotentiallyIndirectValueScope(FossilizedValKind::StringObj); auto data = value.getBuffer(); _writeValueRaw(ValInfo::rawData(data, size + 1, 1)); @@ -176,7 +176,7 @@ void SerialWriter::handleString(String& value) void SerialWriter::beginArray() { - _pushContainerScope(FossilizedValKind::Array); + _pushContainerScope(FossilizedValKind::ArrayObj); } void SerialWriter::endArray() @@ -186,7 +186,7 @@ void SerialWriter::endArray() void SerialWriter::beginDictionary() { - _pushContainerScope(FossilizedValKind::Dictionary); + _pushContainerScope(FossilizedValKind::DictionaryObj); } void SerialWriter::endDictionary() @@ -258,7 +258,7 @@ void SerialWriter::endTuple() void SerialWriter::beginOptional() { - _pushIndirectValueScope(FossilizedValKind::Optional); + _pushIndirectValueScope(FossilizedValKind::OptionalObj); } void SerialWriter::endOptional() @@ -266,7 +266,7 @@ void SerialWriter::endOptional() _popIndirectValueScope(); } -void SerialWriter::handleSharedPtr(void*& value, Callback callback, void* userData) +void SerialWriter::handleSharedPtr(void*& value, Callback callback, void* context) { // Because we are writing, we only care about the // pointer that is already present in `value`. @@ -305,7 +305,7 @@ void SerialWriter::handleSharedPtr(void*& value, Callback callback, void* userDa fossilizedObject->ptrLayout = ptrLayout; fossilizedObject->liveObjectPtr = liveObjectPtr; fossilizedObject->callback = callback; - fossilizedObject->userData = userData; + fossilizedObject->context = context; _fossilizedObjects.add(fossilizedObject); _mapLiveObjectPtrToFossilizedObject.add(liveObjectPtr, fossilizedObject); @@ -313,15 +313,15 @@ void SerialWriter::handleSharedPtr(void*& value, Callback callback, void* userDa _commitWrite(ValInfo::relativePtrTo(chunk)); } -void SerialWriter::handleUniquePtr(void*& value, Callback callback, void* userData) +void SerialWriter::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, userData); + handleSharedPtr(value, callback, context); } -void SerialWriter::handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData) +void SerialWriter::handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) { // Because we are already deferring writing of the *entirety* of // an object's members as part of how `handleSharedPtr()` works, @@ -330,7 +330,7 @@ void SerialWriter::handleDeferredObjectContents(void* valuePtr, Callback callbac // (In practice the `handleDeferredObjectContents()` operation is // more for the benefit of reading than writing). // - callback(valuePtr, userData); + callback(valuePtr, this, context); } SerialWriter::LayoutObj* SerialWriter::_createSimpleLayout(FossilizedValKind kind) @@ -356,7 +356,7 @@ SerialWriter::LayoutObj* SerialWriter::_createSimpleLayout(FossilizedValKind kin case FossilizedValKind::Float64: return new (_arena) SimpleLayoutObj(kind, 8); - case FossilizedValKind::String: + case FossilizedValKind::StringObj: return new (_arena) SimpleLayoutObj(kind); default: @@ -369,23 +369,19 @@ SerialWriter::LayoutObj* SerialWriter::_createLayout(FossilizedValKind kind) { switch (kind) { - case FossilizedValKind::Array: - case FossilizedValKind::Optional: - case FossilizedValKind::Dictionary: + case FossilizedValKind::ArrayObj: + case FossilizedValKind::OptionalObj: + case FossilizedValKind::DictionaryObj: return new (_arena) ContainerLayoutObj(kind, nullptr); case FossilizedValKind::Ptr: - return new (_arena) ContainerLayoutObj( - kind, - nullptr, - sizeof(Fossil::RelativePtrOffset), - sizeof(Fossil::RelativePtrOffset)); + return new (_arena) ContainerLayoutObj(kind, nullptr, sizeof(FossilInt), sizeof(FossilInt)); case FossilizedValKind::Struct: case FossilizedValKind::Tuple: return new (_arena) RecordLayoutObj(kind); - case FossilizedValKind::Variant: + case FossilizedValKind::VariantObj: // A variant is being treated like a container in this context, // because it wants to be able to track the layout of what it // ended up holding... @@ -403,7 +399,7 @@ SerialWriter::LayoutObj* SerialWriter::_createLayout(FossilizedValKind kind) case FossilizedValKind::UInt64: case FossilizedValKind::Float32: case FossilizedValKind::Float64: - case FossilizedValKind::String: + case FossilizedValKind::StringObj: { if (auto found = _simpleLayouts.tryGetValue(kind)) return *found; @@ -435,7 +431,7 @@ SerialWriter::LayoutObj* SerialWriter::_mergeLayout(LayoutObj*& dst, FossilizedV // then we want to have a unique layout object for each // instance. // - if (kind == FossilizedValKind::Variant) + if (kind == FossilizedValKind::VariantObj) { auto src = _createLayout(kind); return src; @@ -462,9 +458,9 @@ void SerialWriter::_mergeLayout(LayoutObj*& dst, LayoutObj* src) switch (src->getKind()) { - case FossilizedValKind::Array: - case FossilizedValKind::Optional: - case FossilizedValKind::Dictionary: + case FossilizedValKind::ArrayObj: + case FossilizedValKind::OptionalObj: + case FossilizedValKind::DictionaryObj: case FossilizedValKind::Ptr: { auto dstContainer = (ContainerLayoutObj*)dst; @@ -473,10 +469,10 @@ void SerialWriter::_mergeLayout(LayoutObj*& dst, LayoutObj* src) } break; - case FossilizedValKind::String: + case FossilizedValKind::StringObj: break; - case FossilizedValKind::Variant: + case FossilizedValKind::VariantObj: // Recursive merging should not be applied to variants; // each variant is unique until later deduplication. break; @@ -557,7 +553,7 @@ Size SerialWriter::ValInfo::getAlignment() const switch (kind) { case Kind::RelativePtr: - return sizeof(Fossil::RelativePtrOffset); + return sizeof(FossilInt); case Kind::ContentsOfChunk: return chunk->getAlignment(); @@ -598,13 +594,13 @@ void SerialWriter::_popInlineValueScope() void SerialWriter::_pushVariantScope() { - _pushPotentiallyIndirectValueScope(FossilizedValKind::Variant); + _pushPotentiallyIndirectValueScope(FossilizedValKind::VariantObj); } void SerialWriter::_popVariantScope() { SLANG_ASSERT(_state.layout); - SLANG_ASSERT(_state.layout->kind == FossilizedValKind::Variant); + SLANG_ASSERT(_state.layout->kind == FossilizedValKind::VariantObj); auto variantLayout = (ContainerLayoutObj*)_state.layout; auto valueLayout = variantLayout->baseLayout; SLANG_ASSERT(valueLayout); @@ -631,7 +627,7 @@ void SerialWriter::_popVariantScope() void SerialWriter::_pushPotentiallyIndirectValueScope(FossilizedValKind kind) { - if (_shouldEmitWithPointerIndirection(kind)) + if (_shouldEmitPotentiallyIndirectValueWithPointerIndirection()) { _pushIndirectValueScope(kind); } @@ -647,12 +643,10 @@ ChunkBuilder* SerialWriter::_popPotentiallyIndirectValueScope() // conditional to select between the functions for the // indirect and inline cases. - auto valueLayout = _state.layout; auto valueChunk = _state.chunk; _popState(); - auto valueKind = valueLayout->getKind(); - if (_shouldEmitWithPointerIndirection(valueKind)) + if (_shouldEmitPotentiallyIndirectValueWithPointerIndirection()) { return _writeKnownIndirectValueSharedLogic(valueChunk); } @@ -729,11 +723,14 @@ void SerialWriter::_writeValueRaw(ValInfo const& val) case ValInfo::Kind::RelativePtr: _ensureChunkExists(); - _state.chunk->writeRelativePtr<Fossil::RelativePtrOffset>(val.chunk); + _state.chunk->writeRelativePtr<FossilInt>(val.chunk); break; case ValInfo::Kind::ContentsOfChunk: { + if (!val.chunk) + return; + if (!_state.chunk) { _state.chunk = val.chunk; @@ -751,29 +748,14 @@ void SerialWriter::_writeValueRaw(ValInfo const& val) } } -bool SerialWriter::_shouldEmitWithPointerIndirection(FossilizedValKind kind) +bool SerialWriter::_shouldEmitPotentiallyIndirectValueWithPointerIndirection() { - switch (kind) - { - default: - return false; - - case FossilizedValKind::Optional: - return true; - - case FossilizedValKind::Array: - case FossilizedValKind::Dictionary: - case FossilizedValKind::String: - case FossilizedValKind::Variant: - break; - } - switch (_state.layout->getKind()) { default: return true; - case FossilizedValKind::Optional: + case FossilizedValKind::OptionalObj: case FossilizedValKind::Ptr: return false; } @@ -794,10 +776,10 @@ SerialWriter::LayoutObj*& SerialWriter::_reserveDestinationForWrite() break; case FossilizedValKind::Ptr: - case FossilizedValKind::Optional: - case FossilizedValKind::Array: - case FossilizedValKind::Dictionary: - case FossilizedValKind::Variant: + case FossilizedValKind::OptionalObj: + case FossilizedValKind::ArrayObj: + case FossilizedValKind::DictionaryObj: + case FossilizedValKind::VariantObj: { auto containerLayout = (ContainerLayoutObj*)_state.layout; auto& elementLayout = containerLayout->baseLayout; @@ -849,17 +831,17 @@ void SerialWriter::_commitWrite(ValInfo const& val) } break; - case FossilizedValKind::Optional: + case FossilizedValKind::OptionalObj: case FossilizedValKind::Ptr: - case FossilizedValKind::Array: - case FossilizedValKind::Dictionary: - case FossilizedValKind::Variant: + case FossilizedValKind::ArrayObj: + case FossilizedValKind::DictionaryObj: + case FossilizedValKind::VariantObj: { auto elementIndex = _state.elementCount++; switch (outerKind) { - case FossilizedValKind::Optional: + case FossilizedValKind::OptionalObj: case FossilizedValKind::Ptr: if (elementIndex > 0) { @@ -911,7 +893,10 @@ void SerialWriter::_flush() _state = State(fossilizedObject->ptrLayout, fossilizedObject->chunk); - fossilizedObject->callback(&fossilizedObject->liveObjectPtr, fossilizedObject->userData); + fossilizedObject->callback( + &fossilizedObject->liveObjectPtr, + this, + fossilizedObject->context); } // Once we've written out all the payload data, we can start to work on @@ -921,7 +906,7 @@ void SerialWriter::_flush() for (auto variantInfo : _variants) { auto layoutChunk = _getOrCreateChunkForLayout(variantInfo.layout); - variantInfo.chunk->addPrefixRelativePtr<Fossil::RelativePtrOffset>(layoutChunk); + variantInfo.chunk->addPrefixRelativePtr<FossilInt>(layoutChunk); } } @@ -964,22 +949,22 @@ ChunkBuilder* SerialWriter::_getOrCreateChunkForLayout(LayoutObj* layout) break; case FossilizedValKind::Ptr: - case FossilizedValKind::Optional: + case FossilizedValKind::OptionalObj: { auto containerLayout = (ContainerLayoutObj*)layout; auto elementLayout = containerLayout->baseLayout; auto elementLayoutChunk = _getOrCreateChunkForLayout(elementLayout); - chunk->writeRelativePtr<Fossil::RelativePtrOffset>(elementLayoutChunk); + chunk->writeRelativePtr<FossilInt>(elementLayoutChunk); } break; - case FossilizedValKind::Array: - case FossilizedValKind::Dictionary: + case FossilizedValKind::ArrayObj: + case FossilizedValKind::DictionaryObj: { auto containerLayout = (ContainerLayoutObj*)layout; auto elementLayout = containerLayout->baseLayout; auto elementLayoutChunk = _getOrCreateChunkForLayout(elementLayout); - chunk->writeRelativePtr<Fossil::RelativePtrOffset>(elementLayoutChunk); + chunk->writeRelativePtr<FossilInt>(elementLayoutChunk); UInt32 elementStride = 0; if (elementLayout) @@ -1004,7 +989,7 @@ ChunkBuilder* SerialWriter::_getOrCreateChunkForLayout(LayoutObj* layout) { auto& field = recordLayout->fields[i]; auto fieldLayoutChunk = _getOrCreateChunkForLayout(field.layout); - chunk->writeRelativePtr<Fossil::RelativePtrOffset>(fieldLayoutChunk); + chunk->writeRelativePtr<FossilInt>(fieldLayoutChunk); auto fieldOffset = UInt32(field.offset); chunk->writeData(&fieldOffset, sizeof(fieldOffset)); @@ -1043,9 +1028,9 @@ bool SerialWriter::LayoutObjKey::operator==(LayoutObjKey const& that) const default: break; - case FossilizedValKind::Array: - case FossilizedValKind::Dictionary: - case FossilizedValKind::Optional: + case FossilizedValKind::ArrayObj: + case FossilizedValKind::DictionaryObj: + case FossilizedValKind::OptionalObj: case FossilizedValKind::Ptr: { auto thisContainer = (ContainerLayoutObj*)obj; @@ -1117,9 +1102,9 @@ void SerialWriter::LayoutObjKey::hashInto(Hasher& hasher) const default: break; - case FossilizedValKind::Array: - case FossilizedValKind::Dictionary: - case FossilizedValKind::Optional: + case FossilizedValKind::ArrayObj: + case FossilizedValKind::DictionaryObj: + case FossilizedValKind::OptionalObj: case FossilizedValKind::Ptr: { auto container = (ContainerLayoutObj*)obj; @@ -1152,16 +1137,83 @@ void SerialWriter::LayoutObjKey::hashInto(Hasher& hasher) const // SerialReader // -SerialReader::SerialReader(FossilizedValRef valRef) +SerialReader::SerialReader( + ReadContext& context, + Fossil::AnyValPtr valPtr, + InitialStateType initialState) + : _context(context) { - _state.type = State::Type::Root; - _state.baseValue = valRef; + // We track the number of active `SerialReader`s that + // are working with the same `ReadContext`, and will + // make use of this count in the destructor below. + // + context._readerCount++; + + switch (initialState) + { + case InitialStateType::Root: + _state.type = State::Type::Root; + break; + + case InitialStateType::PseudoPtr: + _state.type = State::Type::PseudoPtr; + break; + } + + _state.baseValPtr = valPtr; _state.elementIndex = 0; _state.elementCount = 1; } SerialReader::~SerialReader() { + // If an application is designed to perform something + // like on-demand deserialization, it may create + // additional `SerialReader`s attached to the same + // `ReadContext`, potentially even in the body of a + // callback that was invoked by an operation on another + // `SerialReader` further up the stack. + // + // If we were to track the deferred actions that get + // enqueued on a per-`SerialReader` basis, and then + // flush them when the given `SerialReader` is destructed, + // it could potentially lead to very deep call stacks. + // + // Instead, we track a single list of deferred actions + // on the `ReadContext`, which means that we need to + // figure out when to actually flush that list. + // + // What is implemented here is a "last one out shuts the door" + // policy. When a `SerialReader` is being destroyed, before + // it decrements the count on the shared `ReadContext`, it + // checks to see if it is the last remaining `SerialReader`, + // in which case it takes responsibility for flushing the deferred + // actions that were enqueued by *all* of the readers. + // + // Note that the ordering here is critical: we check whether + // we are the last reader and, if so, perform the `_flush()` + // operation all *before* decrementing the counter. If we + // were to decrement the count before invoking `_flush()` + // then any nested `SerialReader`s that get created by the + // deferred actions would (incorrectly) believe themselves + // to be the "last one out" and try to perform their own + // `flush()`, which could quickly lead to unbounded + // recursion. + // + if (_context._readerCount == 1) + { + _flush(); + } + _context._readerCount--; +} + +Fossil::AnyValPtr SerialReader::readValPtr() +{ + return _readValPtr(); +} + +void SerialReader::flush() +{ _flush(); } @@ -1172,94 +1224,83 @@ SerializationMode SerialReader::getMode() void SerialReader::handleBool(bool& value) { - auto valRef = _readValRef(); - value = as<FossilizedBoolVal>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleInt8(int8_t& value) { - auto valRef = _readValRef(); - value = as<FossilizedInt8Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleInt16(int16_t& value) { - auto valRef = _readValRef(); - value = as<FossilizedInt16Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleInt32(Int32& value) { - auto valRef = _readValRef(); - value = as<FossilizedInt32Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleInt64(Int64& value) { - auto valRef = _readValRef(); - value = as<FossilizedInt64Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleUInt8(uint8_t& value) { - auto valRef = _readValRef(); - value = as<FossilizedUInt8Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleUInt16(uint16_t& value) { - auto valRef = _readValRef(); - value = as<FossilizedUInt16Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleUInt32(UInt32& value) { - auto valRef = _readValRef(); - value = as<FossilizedUInt32Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleUInt64(UInt64& value) { - auto valRef = _readValRef(); - value = as<FossilizedUInt64Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleFloat32(float& value) { - auto valRef = _readValRef(); - value = as<FossilizedFloat32Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleFloat64(double& value) { - auto valRef = _readValRef(); - value = as<FossilizedFloat64Val>(valRef)->getValue(); + handleSimpleVal(value); } void SerialReader::handleString(String& value) { - auto valRef = _readPotentiallyIndirectValRef(); - if (!valRef) + auto valPtr = _readPotentiallyIndirectValPtr(); + if (!valPtr) { value = String(); } else { - value = as<FossilizedStringObj>(valRef)->getValue(); + value = as<FossilizedStringObj>(valPtr)->get(); } } void SerialReader::beginArray() { - auto valRef = _readPotentiallyIndirectValRef(); - auto arrayRef = as<FossilizedContainerObj>(valRef); + auto valPtr = _readPotentiallyIndirectValPtr(); + auto arrayPtr = as<FossilizedArrayObjBase>(valPtr); _pushState(); _state.type = State::Type::Array; - _state.baseValue = valRef; + _state.baseValPtr = arrayPtr; _state.elementIndex = 0; - _state.elementCount = getElementCount(arrayRef); + _state.elementCount = arrayPtr->getElementCount(); } void SerialReader::endArray() @@ -1269,15 +1310,15 @@ void SerialReader::endArray() void SerialReader::beginDictionary() { - auto valRef = _readPotentiallyIndirectValRef(); - auto dictionaryRef = as<FossilizedContainerObj>(valRef); + auto valPtr = _readPotentiallyIndirectValPtr(); + auto dictionaryPtr = as<FossilizedDictionaryObjBase>(valPtr); _pushState(); _state.type = State::Type::Dictionary; - _state.baseValue = valRef; + _state.baseValPtr = dictionaryPtr; _state.elementIndex = 0; - _state.elementCount = getElementCount(dictionaryRef); + _state.elementCount = dictionaryPtr->getElementCount(); } void SerialReader::endDictionary() @@ -1292,15 +1333,15 @@ bool SerialReader::hasElements() void SerialReader::beginStruct() { - auto valRef = _readValRef(); - auto recordRef = as<FossilizedRecordVal>(valRef); + auto valPtr = _readValPtr(); + auto recordPtr = as<FossilizedRecordVal>(valPtr); _pushState(); _state.type = State::Type::Struct; - _state.baseValue = valRef; + _state.baseValPtr = valPtr; _state.elementIndex = 0; - _state.elementCount = getFieldCount(recordRef); + _state.elementCount = recordPtr->getFieldCount(); } void SerialReader::endStruct() @@ -1310,18 +1351,20 @@ void SerialReader::endStruct() void SerialReader::beginVariant() { - auto valRef = _readPotentiallyIndirectValRef(); - auto variantRef = as<FossilizedVariantObj>(valRef); - - auto contentValRef = getVariantContent(variantRef); - auto contentRecordRef = as<FossilizedRecordVal>(contentValRef); + auto valPtr = _readPotentiallyIndirectValPtr(); + if (auto variantPtr = as<FossilizedVariantObj>(valPtr)) + { + auto contentValPtr = getVariantContentPtr(variantPtr); + valPtr = contentValPtr; + } + auto recordPtr = as<FossilizedRecordVal>(valPtr); _pushState(); _state.type = State::Type::Struct; - _state.baseValue = contentValRef; + _state.baseValPtr = recordPtr; _state.elementIndex = 0; - _state.elementCount = getFieldCount(contentRecordRef); + _state.elementCount = recordPtr->getFieldCount(); } void SerialReader::endVariant() @@ -1339,15 +1382,15 @@ void SerialReader::handleFieldKey(char const* name, Int index) void SerialReader::beginTuple() { - auto valRef = _readValRef(); - auto recordRef = as<FossilizedRecordVal>(valRef); + auto valPtr = _readValPtr(); + auto recordPtr = as<FossilizedRecordVal>(valPtr); _pushState(); _state.type = State::Type::Tuple; - _state.baseValue = valRef; + _state.baseValPtr = recordPtr; _state.elementIndex = 0; - _state.elementCount = getFieldCount(recordRef); + _state.elementCount = recordPtr->getFieldCount(); } void SerialReader::endTuple() @@ -1357,15 +1400,15 @@ void SerialReader::endTuple() void SerialReader::beginOptional() { - auto valRef = _readIndirectValRef(); - auto optionalRef = as<FossilizedOptionalObj>(valRef); + auto valPtr = _readIndirectValPtr(); + auto optionalPtr = as<FossilizedOptionalObjBase>(valPtr); _pushState(); _state.type = State::Type::Optional; - _state.baseValue = valRef; + _state.baseValPtr = optionalPtr; _state.elementIndex = 0; - _state.elementCount = Count(hasValue(optionalRef)); + _state.elementCount = Count(optionalPtr->hasValue()); } void SerialReader::endOptional() @@ -1373,14 +1416,21 @@ void SerialReader::endOptional() _popState(); } -void SerialReader::handleSharedPtr(void*& value, Callback callback, void* userData) +void SerialReader::handleSharedPtr(void*& value, Callback callback, void* context) { - // The fossilized value at our cursor must be a pointer, - // and we can resolve what it is pointing to easily enough. - // - auto valRef = _readValRef(); - auto ptrRef = as<FossilizedPtrVal>(valRef); - auto targetValRef = getPtrTarget(ptrRef); + Fossil::AnyValPtr targetValPtr; + + if (_state.type == State::Type::PseudoPtr) + { + _state.type = State::Type::Root; + targetValPtr = _readValPtr(); + } + else + { + auto valPtr = _readValPtr(); + auto ptrPtr = as<FossilizedPtr<void>>(valPtr); + targetValPtr = ptrPtr->getTargetValPtr(); + } // The logic here largely mirrors what appears in // `SerialWriter::handleSharedPtr`. @@ -1388,7 +1438,7 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* userDa // We first check for an explicitly written null pointer. // If we find one our work is very easy. // - if (!targetValRef) + if (!targetValPtr) { value = nullptr; return; @@ -1397,7 +1447,7 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* userDa // Now we need to check if we've previously read in // a reference to the same object. // - if (auto found = _mapFossilizedObjectPtrToObjectInfo.tryGetValue(targetValRef.getData())) + if (auto found = _context.mapFossilizedObjectPtrToObjectInfo.tryGetValue(targetValPtr.get())) { auto objectInfo = *found; @@ -1440,9 +1490,9 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* userDa // object index that has not yet been read at all. // auto objectInfo = RefPtr(new ObjectInfo()); - _mapFossilizedObjectPtrToObjectInfo.add(targetValRef.getData(), objectInfo); + _context.mapFossilizedObjectPtrToObjectInfo.add(targetValPtr.get(), objectInfo); - objectInfo->fossilizedObjectRef = targetValRef; + objectInfo->fossilizedObjectPtr = targetValPtr; // We cannot return from this function until we have // stored a pointer into `value`, to represent the @@ -1473,7 +1523,7 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* userDa // _pushState(); _state.type = State::Type::Object; - _state.baseValue = objectInfo->fossilizedObjectRef; + _state.baseValPtr = objectInfo->fossilizedObjectPtr; _state.elementIndex = 0; _state.elementCount = 1; @@ -1491,7 +1541,7 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* userDa // that objects and stores a pointer to it into the output // parameter. // - callback(&objectInfo->resurrectedObjectPtr, userData); + callback(&objectInfo->resurrectedObjectPtr, this, context); _popState(); @@ -1500,15 +1550,15 @@ void SerialReader::handleSharedPtr(void*& value, Callback callback, void* userDa value = objectInfo->resurrectedObjectPtr; } -void SerialReader::handleUniquePtr(void*& value, Callback callback, void* userData) +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, userData); + handleSharedPtr(value, callback, context); } -void SerialReader::handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData) +void SerialReader::handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) { // Unlike the case in `SerialWriter::handleDeferredObjectContents()`, // we very much *do* want to delay invoking the callback until later. @@ -1528,9 +1578,9 @@ void SerialReader::handleDeferredObjectContents(void* valuePtr, Callback callbac deferredAction.savedState = _state; deferredAction.resurrectedObjectPtr = valuePtr; deferredAction.callback = callback; - deferredAction.userData = userData; + deferredAction.context = context; - _deferredActions.add(deferredAction); + _context._deferredActions.add(deferredAction); } void SerialReader::_flush() @@ -1538,7 +1588,7 @@ void SerialReader::_flush() // We need to flush any actions that were deferred // and are still pending. // - while (_deferredActions.getCount() != 0) + while (_context._deferredActions.getCount() != 0) { // TODO: For simplicity we are using the `_deferredActions` // array as a stack (LIFO), but it would be good to @@ -1546,15 +1596,15 @@ void SerialReader::_flush() // large the array would need to grow for a FIFO vs. LIFO, // and pick the better option. // - auto deferredAction = _deferredActions.getLast(); - _deferredActions.removeLast(); + auto deferredAction = _context._deferredActions.getLast(); + _context._deferredActions.removeLast(); _state = deferredAction.savedState; - deferredAction.callback(deferredAction.resurrectedObjectPtr, deferredAction.userData); + deferredAction.callback(deferredAction.resurrectedObjectPtr, this, deferredAction.context); } } -FossilizedValRef SerialReader::_readValRef() +Fossil::AnyValPtr SerialReader::_readValPtr() { switch (_state.type) { @@ -1563,7 +1613,7 @@ FossilizedValRef SerialReader::_readValRef() SLANG_ASSERT(_state.elementCount == 1); SLANG_ASSERT(_state.elementIndex == 0); _state.elementIndex++; - return _state.baseValue; + return _state.baseValPtr; case State::Type::Struct: case State::Type::Tuple: @@ -1571,8 +1621,8 @@ FossilizedValRef SerialReader::_readValRef() SLANG_ASSERT(_state.elementIndex < _state.elementCount); auto index = _state.elementIndex++; - auto recordRef = as<FossilizedRecordVal>(_state.baseValue); - return getField(recordRef, index); + auto recordPtr = as<FossilizedRecordVal>(_state.baseValPtr); + return getAddress(recordPtr->getField(index)); } case State::Type::Optional: @@ -1580,8 +1630,8 @@ FossilizedValRef SerialReader::_readValRef() SLANG_ASSERT(_state.elementCount == 1); SLANG_ASSERT(_state.elementIndex == 0); - auto optionalRef = as<FossilizedOptionalObj>(_state.baseValue); - return getValue(optionalRef); + auto optionalPtr = as<FossilizedOptionalObjBase>(_state.baseValPtr); + return getAddress(optionalPtr->getValue()); } case State::Type::Array: @@ -1590,8 +1640,8 @@ FossilizedValRef SerialReader::_readValRef() SLANG_ASSERT(_state.elementIndex < _state.elementCount); auto index = _state.elementIndex++; - auto containerRef = as<FossilizedContainerObj>(_state.baseValue); - return getElement(containerRef, index); + auto containerPtr = as<FossilizedContainerObjBase>(_state.baseValPtr); + return Fossil::ValPtr(containerPtr->getElement(index)); } default: @@ -1600,24 +1650,25 @@ FossilizedValRef SerialReader::_readValRef() } } -FossilizedValRef SerialReader::_readIndirectValRef() +Fossil::AnyValPtr SerialReader::_readIndirectValPtr() { - auto ptrValRef = _readValRef(); - auto ptrRef = as<FossilizedPtrVal>(ptrValRef); + auto baseValPtr = _readValPtr(); + auto basePtrPtr = as<FossilizedPtr<void>>(baseValPtr); - auto valRef = getPtrTarget(ptrRef); - return valRef; + auto targetValPtr = basePtrPtr->getTargetValPtr(); + return targetValPtr; } -FossilizedValRef SerialReader::_readPotentiallyIndirectValRef() +Fossil::AnyValPtr SerialReader::_readPotentiallyIndirectValPtr() { - auto valRef = _readValRef(); - if (auto ptrRef = as<FossilizedPtrVal>(valRef)) + auto baseValPtr = _readValPtr(); + if (auto basePtrPtr = as<FossilizedPtr<void>>(baseValPtr)) { - return getPtrTarget(ptrRef); + auto targetValRef = basePtrPtr->getTargetValRef(); + return Fossil::ValPtr(targetValRef); } - return valRef; + return baseValPtr; } void SerialReader::_pushState() diff --git a/source/slang/slang-serialize-fossil.h b/source/slang/slang-serialize-fossil.h index 0393c5784..930719935 100644 --- a/source/slang/slang-serialize-fossil.h +++ b/source/slang/slang-serialize-fossil.h @@ -260,7 +260,7 @@ private: /// Callback information used by the ISerializer interface. Callback callback = nullptr; - void* userData = nullptr; + void* context = nullptr; }; List<FossilizedObjectInfo*> _fossilizedObjects; @@ -465,10 +465,10 @@ private: void _pushPotentiallyIndirectValueScope(FossilizedValKind kind); ChunkBuilder* _popPotentiallyIndirectValueScope(); - /// Determine if a potentially-indirect value of `kind` should be + /// Determine if a potentially-indirect value of should be /// emitted indirectly, in the current state. /// - bool _shouldEmitWithPointerIndirection(FossilizedValKind kind); + bool _shouldEmitPotentiallyIndirectValueWithPointerIndirection(); /// Helper function to share details between `_popIndirectValueScope` /// and `_popPotentiallyIndirectValueScope`. @@ -548,10 +548,10 @@ private: virtual void handleFieldKey(char const* name, Int index) override; - virtual void handleSharedPtr(void*& value, Callback callback, void* userData) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* userData) override; + virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; + virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData) + virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) override; }; @@ -559,10 +559,39 @@ private: struct SerialReader : ISerializerImpl { public: - SerialReader(FossilizedValRef valRef); + struct ReadContext; + + enum class InitialStateType + { + Root, + PseudoPtr, + }; + + SerialReader( + ReadContext& context, + Fossil::AnyValPtr valPtr, + InitialStateType initialState = InitialStateType::Root); ~SerialReader(); + /// Read a value from the current cursor position. + /// + /// This operation can be used to skip over an entire value + /// that might otherwise need to be read with a sequence of + /// operations of the `ISerializerImpl` interface. + /// + /// The saved pointer can then be used to construct another + /// `Fossil::SerialReader` to read the contents of the value + /// at some later time, or code can simply navigate the + /// data in memory using their own logic. + /// + Fossil::AnyValPtr readValPtr(); + + void flush(); + private: + /// The shared context that this reader is using. + ReadContext& _context; + /// A state that the reader can be in. struct State { @@ -577,6 +606,8 @@ private: Tuple, Struct, Object, + + PseudoPtr, }; /// The type of state. @@ -588,7 +619,7 @@ private: /// 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. /// - FossilizedValRef baseValue; + Fossil::AnyValPtr baseValPtr; /// Index of next element to read. /// @@ -613,15 +644,16 @@ private: /// Stack of saved states. List<State> _stack; - void _pushState(); - void _popState(); - // // Like other `ISerializerImpl`s 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). // + // A key difference here is that the actual mapping is being + // stored in the shared `ReadContext`, rather than in the + // `SerialReader` itself. + // enum class ObjectState { @@ -634,9 +666,8 @@ private: ObjectState state = ObjectState::Unread; void* resurrectedObjectPtr = nullptr; - FossilizedValRef fossilizedObjectRef; + Fossil::AnyValPtr fossilizedObjectPtr; }; - Dictionary<void*, RefPtr<ObjectInfo>> _mapFossilizedObjectPtrToObjectInfo; // // Again, like other `ISerializerImpl`s for reading, we @@ -652,9 +683,12 @@ private: State savedState; Callback callback; - void* userData; + void* context; }; - List<DeferredAction> _deferredActions; + + void _pushState(); + void _popState(); + /// Execute all deferred actions that are still pending. void _flush(); @@ -663,14 +697,14 @@ private: /// /// This is the case for scalars, tuples, and structs. /// - FossilizedValRef _readValRef(); + Fossil::AnyValPtr _readValPtr(); /// Read an indirect value. /// /// This is the case for things like optionals, that are /// always encoded as a pointer. /// - FossilizedValRef _readIndirectValRef(); + Fossil::AnyValPtr _readIndirectValPtr(); /// Read a potentially-indirect value. /// @@ -679,7 +713,31 @@ private: /// /// Otherwise, this will return a reference to the value itself. /// - FossilizedValRef _readPotentiallyIndirectValRef(); + Fossil::AnyValPtr _readPotentiallyIndirectValPtr(); + + + template<typename T> + void handleSimpleVal(T& value) + { + auto valPtr = _readValPtr(); + value = as<Fossilized<T>>(valPtr)->getDataRef(); + } + +public: + struct ReadContext + { + public: + ReadContext() = default; + + private: + friend struct SerialReader; + + Dictionary<void*, RefPtr<ObjectInfo>> mapFossilizedObjectPtrToObjectInfo; + List<DeferredAction> _deferredActions; + + Count _readerCount = 0; + }; + private: // @@ -728,13 +786,15 @@ private: virtual void beginOptional() override; virtual void endOptional() override; - virtual void handleSharedPtr(void*& value, Callback callback, void* userData) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* userData) override; + virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; + virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData) + virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) override; }; +using ReadContext = SerialReader::ReadContext; + } // namespace Fossil } // namespace Slang diff --git a/source/slang/slang-serialize-riff.cpp b/source/slang/slang-serialize-riff.cpp index 469803c2a..fb8acd2bd 100644 --- a/source/slang/slang-serialize-riff.cpp +++ b/source/slang/slang-serialize-riff.cpp @@ -217,7 +217,7 @@ void RIFFSerialWriter::endOptional() _cursor.endChunk(); } -void RIFFSerialWriter::handleSharedPtr(void*& value, Callback callback, void* userData) +void RIFFSerialWriter::handleSharedPtr(void*& value, Callback callback, void* context) { // Because we are writing, we only care about the // pointer that is already present in `value`. @@ -279,22 +279,22 @@ void RIFFSerialWriter::handleSharedPtr(void*& value, Callback callback, void* us ObjectInfo objectInfo; objectInfo.ptr = ptr; objectInfo.callback = callback; - objectInfo.userData = userData; + objectInfo.context = context; _objects.add(objectInfo); } -void RIFFSerialWriter::handleUniquePtr(void*& value, Callback callback, void* userData) +void RIFFSerialWriter::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, userData); + handleSharedPtr(value, callback, context); } void RIFFSerialWriter::handleDeferredObjectContents( void* valuePtr, Callback callback, - void* userData) + void* context) { // Because we are already deferring writing of the *entirety* of // an object's members as part of how `handleSharedPtr()` works, @@ -303,7 +303,7 @@ void RIFFSerialWriter::handleDeferredObjectContents( // (In practice the `handleDeferredObjectContents()` operation is // more for the benefit of reading than writing). // - callback(valuePtr, userData); + callback(valuePtr, this, context); } void RIFFSerialWriter::_writeObjectReference(ObjectIndex index) @@ -363,7 +363,7 @@ void RIFFSerialWriter::_flush() // can set the pointed-to pointer to whatever object it // allocates or finds. // - objectInfo.callback(&objectInfo.ptr, objectInfo.userData); + objectInfo.callback(&objectInfo.ptr, this, objectInfo.context); // TODO(tfoley): There is an important invariant here that // the callback had better only write *one* value, but @@ -572,7 +572,7 @@ RIFFSerialReader::ObjectIndex RIFFSerialReader::_readObjectReference() return objectIndex; } -void RIFFSerialReader::handleSharedPtr(void*& value, Callback callback, void* userData) +void RIFFSerialReader::handleSharedPtr(void*& value, Callback callback, void* context) { // The logic here largely mirrors what appears in // `RIFFSerialWriter::handleSharedPtr`. @@ -686,7 +686,7 @@ void RIFFSerialReader::handleSharedPtr(void*& value, Callback callback, void* us // that objects and stores a pointer to it into the output // parameter. // - callback(&objectInfo.ptr, userData); + callback(&objectInfo.ptr, this, context); _popCursor(); @@ -706,7 +706,7 @@ void RIFFSerialReader::handleUniquePtr(void*& value, Callback callback, void* us void RIFFSerialReader::handleDeferredObjectContents( void* valuePtr, Callback callback, - void* userData) + void* context) { // Unlike the case in `RIFFSerialWriter::handleDeferredObjectContents()`, // we very much *do* want to delay invoking the callback until later. @@ -726,7 +726,7 @@ void RIFFSerialReader::handleDeferredObjectContents( deferredAction.savedCursor = _cursor; deferredAction.valuePtr = valuePtr; deferredAction.callback = callback; - deferredAction.userData = userData; + deferredAction.context = context; _deferredActions.add(deferredAction); } @@ -777,7 +777,7 @@ void RIFFSerialReader::_flush() _deferredActions.removeLast(); _cursor = deferredAction.savedCursor; - deferredAction.callback(deferredAction.valuePtr, deferredAction.userData); + deferredAction.callback(deferredAction.valuePtr, this, deferredAction.context); } } diff --git a/source/slang/slang-serialize-riff.h b/source/slang/slang-serialize-riff.h index 87f83f3f0..256c185d4 100644 --- a/source/slang/slang-serialize-riff.h +++ b/source/slang/slang-serialize-riff.h @@ -162,8 +162,8 @@ private: /// Callback that can be invoked to serialize the object's data. Callback callback; - /// User-data pointer for `callback` - void* userData; + /// Context pointer for `callback` + void* context; }; /// The chunk where object definitions are listed. @@ -237,10 +237,10 @@ private: virtual void beginOptional() override; virtual void endOptional() override; - virtual void handleSharedPtr(void*& value, Callback callback, void* userData) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* userData) override; + virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; + virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData) + virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) override; }; @@ -262,6 +262,19 @@ public: /// ~RIFFSerialReader(); + /// Read a chunk from the current cursor position. + /// + /// This operation can be used to skip over an entire value + /// that might otherwise need to be read with a sequence of + /// operations of the `ISerializerImpl` interface. + /// + /// The saved pointer can then be used to construct another + /// `RIFFSerialReader` to read the contents of the chunk + /// at some later time, or code can simply navigate the + /// chunk in memory using their own logic. + /// + RIFF::Chunk const* readChunk(); + private: /// Representation of a read cursor in the serialized RIFF data. using Cursor = RIFF::BoundsCheckedChunkPtr; @@ -344,8 +357,8 @@ private: /// The callback to apply to read data into the `valuePtr` Callback callback; - /// The user-data pointer for the `callback`. - void* userData; + /// The context pointer for the `callback`. + void* context; }; /// Deferred actions that are still pending. @@ -427,10 +440,10 @@ private: virtual void beginOptional() override; virtual void endOptional() override; - virtual void handleSharedPtr(void*& value, Callback callback, void* userData) override; - virtual void handleUniquePtr(void*& value, Callback callback, void* userData) override; + virtual void handleSharedPtr(void*& value, Callback callback, void* context) override; + virtual void handleUniquePtr(void*& value, Callback callback, void* context) override; - virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData) + virtual void handleDeferredObjectContents(void* valuePtr, Callback callback, void* context) override; }; diff --git a/source/slang/slang-serialize.h b/source/slang/slang-serialize.h index 591f43139..261f3f2d6 100644 --- a/source/slang/slang-serialize.h +++ b/source/slang/slang-serialize.h @@ -370,7 +370,7 @@ struct ISerializerImpl virtual void handleFieldKey(char const* name, Int index) = 0; /// A callback function used to handle serialization of pointers. - typedef void (*Callback)(void* valuePtr, void* userData); + typedef void (*Callback)(void* valuePtr, void* impl, void* context); /// Handle a pointer value that is expected to be unique. /// @@ -387,7 +387,7 @@ struct ISerializerImpl /// /// When writing, if the `value` is non-null, then the callback /// will be invoked, either immediately or at some later point, - /// as `callback(&ptr, userData)` where `ptr` is a variable + /// as `callback(&ptr, this, context)` where `ptr` is a variable /// holding a copy of the `value` that was passed in. The callback /// is expected to write the members of the pointed-to type. /// @@ -396,7 +396,7 @@ struct ISerializerImpl /// for ensuring that its internal state has been restored to /// be compatible with what it was when `handleUniquePtr` was called. /// - virtual void handleUniquePtr(void*& value, Callback callback, void* userData) = 0; + virtual void handleUniquePtr(void*& value, Callback callback, void* context) = 0; /// Handle a pointer value that may have multiple references. /// @@ -411,7 +411,7 @@ struct ISerializerImpl /// the `callback` will not be invoked, and instead `value` will /// be set to the pointer that was previously read. /// - virtual void handleSharedPtr(void*& value, Callback callback, void* userData) = 0; + virtual void handleSharedPtr(void*& value, Callback callback, void* context) = 0; /// Defer serialization of the contents of an object. /// @@ -422,14 +422,14 @@ struct ISerializerImpl /// passed to `handleUniquePtr()` or `handleSharedPtr()`. /// /// This operation schedules the given `callback` to be called - /// at some later point a `callback(value, userData)`, with + /// at some later point a `callback(value, this, context)`, with /// the state of the serializer implementation restored to what /// it was when `handleDeferredObjectContents()` was called. /// /// Some concrete serializer implementations might implement /// this operation by invoking `callback` immediately. /// - virtual void handleDeferredObjectContents(void* value, Callback callback, void* userData) = 0; + virtual void handleDeferredObjectContents(void* value, Callback callback, void* context) = 0; }; // @@ -439,50 +439,68 @@ struct ISerializerImpl // // While the `ISerializerImpl` interface 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. +// more specific *context* to be available in order to perform serialization. // For example, code might need access to a factory object in order // to construct objects of a type being read. // -// To support more specialized serializer implementations, we allow -// the smart pointer used for a serializer to depend on the type -// of the underlying implementation object. +// To support more specialized serializer implementations, the smart +// pointer type used for a serializer actually wraps *two* pointers: +// one for an `ISerializerImpl`-derived type, and one for a context +// type. The smart pointer is templated on both of these types. // /// Base type for serialization contexts. /// -/// The type parameter `T` should be a type of object that -/// holds the context information needed. +/// 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 T> +template<typename Impl, typename Context> struct SerializerBase { public: SerializerBase() = default; - SerializerBase(T* ptr) - : _ptr(ptr) + SerializerBase(Impl* impl, Context* context = nullptr) + : _impl(impl), _context(context) { } - T* get() const { return _ptr; } - T* operator->() const { return get(); } + template<typename I, typename C> + SerializerBase( + SerializerBase<I, C> const& serializer, + std::enable_if_t< + std::is_convertible_v<I*, Impl*> && std::is_convertible_v<C*, Context*>, + void>* = nullptr) + : _impl(serializer.getImpl()), _context(serializer.getContext()) + { + } + + Impl* getImpl() const { return _impl; } + Context* getContext() const { return _context; } + + Impl* get() const { return _impl; } + Impl* operator->() const { return get(); } + private: - T* _ptr = nullptr; + Impl* _impl = nullptr; + Context* _context = nullptr; }; /// A serialization context. /// -/// The type parameter `T` should be a type of object that -/// holds the context information needed. +/// 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 T> -struct Serializer_ : SerializerBase<T> +template<typename Impl, typename Context> +struct Serializer_ : SerializerBase<Impl, Context> { - using SerializerBase<T>::SerializerBase; + using SerializerBase<Impl, Context>::SerializerBase; }; /// Default serialization context. -using Serializer = Serializer_<ISerializerImpl>; +using Serializer = Serializer_<ISerializerImpl, void>; // // We define namespace-scope functions that mirror some @@ -944,22 +962,22 @@ void serializeObjectContents(S const& serializer, T* value, void*) serialize(serializer, *value); } -template<typename S, typename T> -void _serializeObjectContentsCallback(void* valuePtr, void* userData) +template<typename I, typename C, typename T> +void _serializeObjectContentsCallback(void* valuePtr, void* impl, void* context) { - auto serializerImpl = (S*)userData; + Serializer_<I, C> serializer((I*)impl, (C*)context); auto value = (T*)valuePtr; - serializeObjectContents(Serializer_<S>(serializerImpl), value, (T*)nullptr); + serializeObjectContents(serializer, value, (T*)nullptr); } -template<typename S, typename T> -void deferSerializeObjectContents(Serializer_<S> const& serializer, T* value) +template<typename I, typename C, typename T> +void deferSerializeObjectContents(Serializer_<I, C> const& serializer, T* value) { ((Serializer)serializer) ->handleDeferredObjectContents( value, - _serializeObjectContentsCallback<S, T>, - serializer.get()); + _serializeObjectContentsCallback<I, C, T>, + serializer.getContext()); } template<typename S, typename T> @@ -972,26 +990,32 @@ void serializeObject(S const& serializer, T*& value, void*) deferSerializeObjectContents(serializer, value); } -template<typename S, typename T> -void _serializeObjectCallback(void* valuePtr, void* userData) +template<typename I, typename C, typename T> +void _serializeObjectCallback(void* valuePtr, void* impl, void* context) { - auto serializerImpl = (S*)userData; + Serializer_<I, C> serializer((I*)impl, (C*)context); auto& value = *(T**)valuePtr; - serializeObject(Serializer_<S>(serializerImpl), value, (T*)nullptr); + serializeObject(serializer, value, (T*)nullptr); } -template<typename S, typename T> -void serializeSharedPtr(Serializer_<S> const& serializer, T*& value) +template<typename I, typename C, typename T> +void serializeSharedPtr(Serializer_<I, C> const& serializer, T*& value) { ((Serializer)serializer) - ->handleSharedPtr(*(void**)&value, _serializeObjectCallback<S, T>, serializer.get()); + ->handleSharedPtr( + *(void**)&value, + _serializeObjectCallback<I, C, T>, + serializer.getContext()); } -template<typename S, typename T> -void serializeUniquePtr(Serializer_<S> const& serializer, T*& value) +template<typename I, typename C, typename T> +void serializeUniquePtr(Serializer_<I, C> const& serializer, T*& value) { ((Serializer)serializer) - ->handleUniquePtr(*(void**)&value, _serializeObjectCallback<S, T>, serializer.get()); + ->handleUniquePtr( + *(void**)&value, + _serializeObjectCallback<I, C, T>, + serializer.getContext()); } template<typename S, typename T> diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp index 2c76f035c..f2f28e3e3 100644 --- a/source/slang/slang.cpp +++ b/source/slang/slang.cpp @@ -745,6 +745,7 @@ SlangResult Session::_readBuiltinModule( linkage, astBuilder, nullptr, // no sink + fileContents, astChunk, sourceLocReader, SourceLoc()); @@ -755,11 +756,6 @@ SlangResult Session::_readBuiltinModule( moduleDecl->module = module; module->setModuleDecl(moduleDecl); - if (isFromCoreModule(moduleDecl)) - { - registerBuiltinDecls(this, moduleDecl); - } - // After the AST module has been read in, we next look // to deserialize the IR module. // @@ -4198,6 +4194,7 @@ void Linkage::loadParsedModule( } RefPtr<Module> Linkage::findOrLoadSerializedModuleForModuleLibrary( + ISlangBlob* blobHoldingSerializedData, ModuleChunk const* moduleChunk, RIFF::ListChunk const* libraryChunk, DiagnosticSink* sink) @@ -4244,6 +4241,7 @@ RefPtr<Module> Linkage::findOrLoadSerializedModuleForModuleLibrary( return loadSerializedModule( moduleName, modulePathInfo, + blobHoldingSerializedData, moduleChunk, libraryChunk, SourceLoc(), @@ -4253,6 +4251,7 @@ RefPtr<Module> Linkage::findOrLoadSerializedModuleForModuleLibrary( RefPtr<Module> Linkage::loadSerializedModule( Name* moduleName, const PathInfo& moduleFilePathInfo, + ISlangBlob* blobHoldingSerializedData, ModuleChunk const* moduleChunk, RIFF::ListChunk const* containerChunk, SourceLoc const& requestingLoc, @@ -4286,6 +4285,7 @@ RefPtr<Module> Linkage::loadSerializedModule( if (SLANG_FAILED(loadSerializedModuleContents( module, moduleFilePathInfo, + blobHoldingSerializedData, moduleChunk, containerChunk, sink))) @@ -4352,6 +4352,7 @@ RefPtr<Module> Linkage::loadBinaryModuleImpl( RefPtr<Module> module = loadSerializedModule( moduleName, moduleFilePathInfo, + moduleFileContents, moduleChunk, rootChunk, requestingLoc, @@ -5269,7 +5270,27 @@ void Module::_processFindDeclsExportSymbolsRec(Decl* decl) } } -NodeBase* Module::findExportFromMangledName(const UnownedStringSlice& slice) +Decl* Module::findExportedDeclByMangledName(const UnownedStringSlice& mangledName) +{ + // If this module is a serialized module that is being + // deserialized on-demand, then we want to use the + // mangled name mapping that was baked into the serialized + // data, rather than attempt to enumerate all of the declarations + // in the module (as would be done if we proceeded to call + // `ensureExportLookupAcceleratorBuilt()`). + // + if (this->m_moduleDecl->isUsingOnDemandDeserializationForExports()) + { + return m_moduleDecl->_findSerializedDeclByMangledExportName(mangledName); + } + + ensureExportLookupAcceleratorBuilt(); + + const Index index = m_mangledExportPool.findIndex(mangledName); + return (index >= 0) ? m_mangledExportSymbols[index] : nullptr; +} + +void Module::ensureExportLookupAcceleratorBuilt() { // Will be non zero if has been previously attempted if (m_mangledExportSymbols.getCount() == 0) @@ -5284,9 +5305,25 @@ NodeBase* Module::findExportFromMangledName(const UnownedStringSlice& slice) m_mangledExportSymbols.add(nullptr); } } +} - const Index index = m_mangledExportPool.findIndex(slice); - return (index >= 0) ? m_mangledExportSymbols[index] : nullptr; +Count Module::getExportedDeclCount() +{ + ensureExportLookupAcceleratorBuilt(); + + return m_mangledExportPool.getSlicesCount(); +} + +Decl* Module::getExportedDecl(Index index) +{ + ensureExportLookupAcceleratorBuilt(); + return m_mangledExportSymbols[index]; +} + +UnownedStringSlice Module::getExportedDeclMangledName(Index index) +{ + ensureExportLookupAcceleratorBuilt(); + return m_mangledExportPool.getSlices()[index]; } // ComponentType @@ -6657,6 +6694,7 @@ void Linkage::setFileSystem(ISlangFileSystem* inFileSystem) SlangResult Linkage::loadSerializedModuleContents( Module* module, const PathInfo& moduleFilePathInfo, + ISlangBlob* blobHoldingSerializedData, ModuleChunk const* moduleChunk, RIFF::ListChunk const* containerChunk, DiagnosticSink* sink) @@ -6753,6 +6791,7 @@ SlangResult Linkage::loadSerializedModuleContents( this, astBuilder, sink, + blobHoldingSerializedData, astChunk, sourceLocReader, serializedModuleLoc); @@ -7399,8 +7438,10 @@ SlangResult EndToEndCompileRequest::addLibraryReference( // We need to deserialize and add the modules ComPtr<IModuleLibrary> library; + auto libBlob = RawBlob::create((const Byte*)libData, libDataSize); + SLANG_RETURN_ON_FAIL( - loadModuleLibrary((const Byte*)libData, libDataSize, basePath, this, library)); + loadModuleLibrary(libBlob, (const Byte*)libData, libDataSize, basePath, this, library)); // Create an artifact without any name (as one is not provided) auto artifact = diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis index 341a390f2..38ec85b62 100644 --- a/source/slang/slang.natvis +++ b/source/slang/slang.natvis @@ -667,4 +667,55 @@ </Expand> </Type> <!--~UIntSet--> + + <Type Name="Slang::FossilizedPtr<*>"> + <SmartPointer Usage="Minimal">_offset == 0 ? nullptr : ($T1*)((char*)this + _offset)</SmartPointer> + <DisplayString Condition="_offset == 0">{($T1*)0}</DisplayString> + <DisplayString Condition="_offset != 0">{($T1*)((char*)this + _offset)}</DisplayString> + <Expand> + <ExpandedItem>_offset == 0 ? nullptr : ($T1*)((char*)this + _offset)</ExpandedItem> + </Expand> + </Type> + + + <Type Name="Slang::FossilizedString"> + <DisplayString Condition="_obj._offset == 0">""</DisplayString> + <DisplayString Condition="_obj._offset != 0">{((char*)this + _obj._offset),s8}</DisplayString> + <!-- + <Expand> + <ExpandedItem>(_text._offset == 0 ? "" : ((char*)this + _text._offset)),s8</ExpandedItem> + </Expand> + --> + </Type> + + <Type Name="Slang::FossilizedArray<*>"> + <DisplayString Condition="_obj._offset == 0">{{ count = 0 }}</DisplayString> + <DisplayString Condition="_obj._offset != 0">{{ count = {*((UInt32*)this - 1)} }}</DisplayString> + <Expand> + <Item Name="[count]">*((UInt32*)this - 1)</Item> + <ArrayItems> + <Size>*((UInt32*)this - 1)</Size> + <ValuePointer>($T1*)((char*)this + _obj._offset)</ValuePointer> + </ArrayItems> + </Expand> + </Type> + + <Type Name="Slang::FossilizedDictionary<*,*>"> + <DisplayString Condition="_obj._offset == 0">{{ count = 0 }}</DisplayString> + <DisplayString Condition="_obj._offset != 0">{{ count = {*((UInt32*)this - 1)} }}</DisplayString> + <Expand> + <Item Name="[count]">*((UInt32*)this - 1)</Item> + <ArrayItems> + <Size>*((UInt32*)this - 1)</Size> + <!-- + <ValuePointer>($T1*)((char*)this + _elements._offset)</ValuePointer> + --> + <ValuePointer>(Slang::KeyValuePair<$T1,$T2> *) ((char*)this + _obj._offset)</ValuePointer> + <!-- + <ValuePointer>(Slang::KeyValuePair<$T1,$T2>*)((char*)this + _elements._offset)</ValuePointer> + --> + </ArrayItems> + </Expand> + </Type> + </AutoVisualizer> diff --git a/tools/gfx-unit-test/gfx-test-util.cpp b/tools/gfx-unit-test/gfx-test-util.cpp index bd1b2a891..688bf048e 100644 --- a/tools/gfx-unit-test/gfx-test-util.cpp +++ b/tools/gfx-unit-test/gfx-test-util.cpp @@ -69,6 +69,8 @@ Slang::Result loadComputeProgram( programDesc.slangGlobalScope = composedProgram.get(); auto shaderProgram = device->createProgram(programDesc); + if (!shaderProgram) + return SLANG_FAIL; outShaderProgram = shaderProgram; return SLANG_OK; diff --git a/tools/slang-fiddle/slang-fiddle-diagnostic-defs.h b/tools/slang-fiddle/slang-fiddle-diagnostic-defs.h index 204185b1c..df9a8dbe9 100644 --- a/tools/slang-fiddle/slang-fiddle-diagnostic-defs.h +++ b/tools/slang-fiddle/slang-fiddle-diagnostic-defs.h @@ -44,6 +44,13 @@ DIAGNOSTIC( "start line for template not followed by a line marking output with '$0'") DIAGNOSTIC(300002, Error, expectedEndMarker, "expected a template end line ('$0')") +// Template Lua Parsing/Execution + +DIAGNOSTIC(400001, Fatal, scriptLoadError, "failure while loading lua script: $0") + +DIAGNOSTIC(400002, Fatal, scriptExecutionError, "failure while executing lua script: $0") + + // Scraper: Parsing DIAGNOSTIC(500001, Error, unexpected, "unexpected $0, expected $1") diff --git a/tools/slang-fiddle/slang-fiddle-scrape.cpp b/tools/slang-fiddle/slang-fiddle-scrape.cpp index f2b2a39a8..763ef8f48 100644 --- a/tools/slang-fiddle/slang-fiddle-scrape.cpp +++ b/tools/slang-fiddle/slang-fiddle-scrape.cpp @@ -144,6 +144,7 @@ public: break; case TokenType::IntegerLiteral: + case TokenType::StringLiteral: { auto token = read(); return new LiteralExpr(token); @@ -1630,11 +1631,14 @@ int _indexVal(lua_State* L) push(L, classDecl->directBaseType); return 1; } + } + if (auto aggTypeDecl = as<AggTypeDecl>(val)) + { if (strcmp(name, "directFields") == 0) { List<RefPtr<Decl>> fields; - for (auto m : classDecl->members) + for (auto m : aggTypeDecl->members) { if (auto f = as<VarDecl>(m)) fields.add(f); diff --git a/tools/slang-fiddle/slang-fiddle-script.cpp b/tools/slang-fiddle/slang-fiddle-script.cpp index 0bee052d8..246488199 100644 --- a/tools/slang-fiddle/slang-fiddle-script.cpp +++ b/tools/slang-fiddle/slang-fiddle-script.cpp @@ -11,15 +11,11 @@ DiagnosticSink* _sink = nullptr; StringBuilder* _builder = nullptr; Count _templateCounter = 0; -void diagnoseLuaError(lua_State* L) +static void _writeLuaMessage(Severity severity, String const& message) { - size_t size = 0; - char const* buffer = lua_tolstring(L, -1, &size); - String message = UnownedStringSlice(buffer, size); - message = message + "\n"; if (_sink) { - _sink->diagnoseRaw(Severity::Error, message.getBuffer()); + _sink->diagnoseRaw(severity, message.getUnownedSlice()); } else { @@ -27,10 +23,35 @@ void diagnoseLuaError(lua_State* L) } } -int _handleLuaError(lua_State* L) +int _trace(lua_State* L) { - diagnoseLuaError(L); - return lua_error(L); + int argCount = lua_gettop(L); + + for (int i = 0; i < argCount; ++i) + { + lua_pushliteral(L, " "); + luaL_tolstring(L, i + 1, nullptr); + } + lua_concat(L, 2 * argCount); + + size_t size = 0; + char const* buffer = lua_tolstring(L, -1, &size); + + String message; + message.append("fiddle:"); + message.append(UnownedStringSlice(buffer, size)); + message.append("\n"); + + _writeLuaMessage(Severity::Note, message); + return 0; +} + +int _handleLuaErrorRaised(lua_State* L) +{ + lua_pushliteral(L, "\n"); + luaL_traceback(L, L, nullptr, 1); + lua_concat(L, 3); + return 1; } int _original(lua_State* L) @@ -57,15 +78,10 @@ int _splice(lua_State* L) _builder = &spliceBuilder; lua_pushvalue(L, 1); - auto result = lua_pcall(L, 0, 1, 0); + lua_call(L, 0, 1); _builder = savedBuilder; - if (result != LUA_OK) - { - return _handleLuaError(L); - } - // The actual string value follows whatever // got printed to the output (unless it is // nil). @@ -89,11 +105,7 @@ int _template(lua_State* L) _builder->append("\n"); lua_pushvalue(L, 1); - auto result = lua_pcall(L, 0, 0, 0); - if (result != LUA_OK) - { - return _handleLuaError(L); - } + lua_call(L, 0, 0); _builder->append("\n#endif\n"); @@ -110,6 +122,9 @@ void ensureLuaInitialized() L = luaL_newstate(); luaL_openlibs(L); + lua_pushcclosure(L, &_trace, 0); + lua_setglobal(L, "TRACE"); + lua_pushcclosure(L, &_original, 0); lua_setglobal(L, "ORIGINAL"); @@ -132,7 +147,11 @@ lua_State* getLuaState() } -String evaluateScriptCode(String originalFileName, String scriptSource, DiagnosticSink* sink) +String evaluateScriptCode( + SourceLoc loc, + String originalFileName, + String scriptSource, + DiagnosticSink* sink) { StringBuilder builder; _builder = &builder; @@ -142,6 +161,8 @@ String evaluateScriptCode(String originalFileName, String scriptSource, Diagnost String luaChunkName = "@" + originalFileName; + lua_pushcfunction(L, &_handleLuaErrorRaised); + if (LUA_OK != luaL_loadbuffer( L, scriptSource.getBuffer(), @@ -152,17 +173,19 @@ String evaluateScriptCode(String originalFileName, String scriptSource, Diagnost char const* buffer = lua_tolstring(L, -1, &size); String message = UnownedStringSlice(buffer, size); message = message + "\n"; - sink->diagnoseRaw(Severity::Error, message.getBuffer()); + + sink->diagnose(loc, fiddle::Diagnostics::scriptLoadError, message); SLANG_ABORT_COMPILATION("fiddle failed during Lua script loading"); } - if (LUA_OK != lua_pcall(L, 0, 0, 0)) + if (LUA_OK != lua_pcall(L, 0, 0, -2)) { size_t size = 0; char const* buffer = lua_tolstring(L, -1, &size); String message = UnownedStringSlice(buffer, size); message = message + "\n"; - sink->diagnoseRaw(Severity::Error, message.getBuffer()); + + sink->diagnose(loc, fiddle::Diagnostics::scriptExecutionError, message); SLANG_ABORT_COMPILATION("fiddle failed during Lua script execution"); } diff --git a/tools/slang-fiddle/slang-fiddle-script.h b/tools/slang-fiddle/slang-fiddle-script.h index 4b77b32be..98cdfa148 100644 --- a/tools/slang-fiddle/slang-fiddle-script.h +++ b/tools/slang-fiddle/slang-fiddle-script.h @@ -15,5 +15,9 @@ using namespace Slang; lua_State* getLuaState(); -String evaluateScriptCode(String originalFileName, String scriptSource, DiagnosticSink* sink); +String evaluateScriptCode( + SourceLoc loc, + String originalFileName, + String scriptSource, + DiagnosticSink* sink); } // namespace fiddle diff --git a/tools/slang-fiddle/slang-fiddle-template.cpp b/tools/slang-fiddle/slang-fiddle-template.cpp index c70029d6f..b8e07f933 100644 --- a/tools/slang-fiddle/slang-fiddle-template.cpp +++ b/tools/slang-fiddle/slang-fiddle-template.cpp @@ -252,6 +252,7 @@ public: RefPtr<TextTemplateFile> parseTextTemplateFile() { auto textTemplateFile = RefPtr(new TextTemplateFile()); + textTemplateFile->loc = _inputSourceView->getRange().begin; textTemplateFile->originalFileContent = _inputSourceView->getContent(); while (!atEnd()) { @@ -423,6 +424,25 @@ private: _builder.append(" end)"); } + bool isEntirelyWhitespace(UnownedStringSlice const& text) + { + for (auto c : text) + { + switch (c) + { + default: + return false; + + case ' ': + case '\t': + case '\r': + case '\n': + continue; + } + } + return true; + } + void evaluateTextTemplateStmt(TextTemplateStmt* stmt) { if (auto seqStmt = as<TextTemplateSeqStmt>(stmt)) @@ -432,9 +452,17 @@ private: } else if (auto rawStmt = as<TextTemplateRawStmt>(stmt)) { - _builder.append("RAW [==["); - _builder.append(rawStmt->text); - _builder.append("]==]"); + auto rawContent = rawStmt->text; + if (isEntirelyWhitespace(rawContent)) + { + _builder.append(rawContent); + } + else + { + _builder.append("RAW [==["); + _builder.append(rawContent); + _builder.append("]==]"); + } } else if (auto scriptStmt = as<TextTemplateScriptStmt>(stmt)) { @@ -471,7 +499,7 @@ void generateTextTemplateOutputs( TextTemplateScriptCodeEmitter emitter(file); String scriptCode = emitter.emitScriptCodeForTextTemplateFile(); - String output = evaluateScriptCode(originalFileName, scriptCode, sink); + String output = evaluateScriptCode(file->loc, originalFileName, scriptCode, sink); builder.append(output); builder.append("\n"); diff --git a/tools/slang-fiddle/slang-fiddle-template.h b/tools/slang-fiddle/slang-fiddle-template.h index 51798c902..f67166772 100644 --- a/tools/slang-fiddle/slang-fiddle-template.h +++ b/tools/slang-fiddle/slang-fiddle-template.h @@ -62,6 +62,7 @@ public: class TextTemplateFile : public RefObject { public: + SourceLoc loc; UnownedStringSlice originalFileContent; List<RefPtr<TextTemplate>> textTemplates; }; |
