summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorTheresa Foley <10618364+tangent-vector@users.noreply.github.com>2025-05-20 21:55:39 -0700
committerGitHub <noreply@github.com>2025-05-21 04:55:39 +0000
commit9059093bc764e901a9c4aaeb12471bf32028874f (patch)
tree7058871ce0ec4397b6e8996506357e41ebb2517d /source
parent52d70f37f66d8fc34bc142386490bdcde0fc7db0 (diff)
Generalize serialization system used for AST (#7126)
This change takes the new approach to serialization that was used for the AST and generalizes it in a few ways: * The new approach is no longer tangled up with the RIFF format. The serialization system supports multiple different implementations of the underlying format. The existing RIFF format is now supported as one back-end, but support for others will follow in subsequent changes. * The new approach is no longer deeply specialized to AST serialization. The old code had things like serialization for `List`s and `Dictionary`s, but it was embedded inside the `AST{Encoding|Decoding}Context`, and thus couldn't be leveraged for other serialization tasks. This change factors out a completely AST-independent `Serializer` implementation, with an `ASTSerializer` layered on top of it to provide the additional context needed. * There is less duplication of code between reading and writing of serialized data. The old code had both the `ASTEncodingContext` and `ASTDecodingContext`, with serialization logic for most types being implemented in both, but with the constraint that those implementations needed to be kept in sync to avoid serialization-related runtime failures. A key property of the revamped approach is that a single `serialize()` method for a type implements both the reading and writing directions of serialization.
Diffstat (limited to 'source')
-rw-r--r--source/core/slang-riff.h14
-rw-r--r--source/slang/slang-ast-base.h4
-rw-r--r--source/slang/slang-ast-decl.h2
-rw-r--r--source/slang/slang-ast-dump.cpp2
-rw-r--r--source/slang/slang-ast-support-types.h2
-rw-r--r--source/slang/slang-serialize-ast.cpp1932
-rw-r--r--source/slang/slang-serialize-ast.h4
-rw-r--r--source/slang/slang-serialize-container.cpp149
-rw-r--r--source/slang/slang-serialize-riff.cpp897
-rw-r--r--source/slang/slang-serialize-riff.h431
-rw-r--r--source/slang/slang-serialize.h1308
11 files changed, 2929 insertions, 1816 deletions
diff --git a/source/core/slang-riff.h b/source/core/slang-riff.h
index 9c533aeb8..0f820c747 100644
--- a/source/core/slang-riff.h
+++ b/source/core/slang-riff.h
@@ -1007,11 +1007,15 @@ private:
ChunkBuilder* _currentChunk = nullptr;
};
-#define SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(CURSOR, TYPE) \
- ::Slang::RIFF::BuildCursor::ScopedDataChunk _scopedRIFFBuilderDataChunk(CURSOR, TYPE)
-
-#define SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(CURSOR, TYPE) \
- ::Slang::RIFF::BuildCursor::ScopedListChunk _scopedRIFFBuilderListChunk(CURSOR, TYPE)
+#define SLANG_SCOPED_RIFF_BUILDER_DATA_CHUNK(CURSOR, TYPE) \
+ ::Slang::RIFF::BuildCursor::ScopedDataChunk SLANG_CONCAT( \
+ _scopedRIFFBuilderDataChunk, \
+ __LINE__)(CURSOR, TYPE)
+
+#define SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(CURSOR, TYPE) \
+ ::Slang::RIFF::BuildCursor::ScopedListChunk SLANG_CONCAT( \
+ _scopedRIFFBuilderListChunk, \
+ __LINE__)(CURSOR, TYPE)
} // namespace RIFF
diff --git a/source/slang/slang-ast-base.h b/source/slang/slang-ast-base.h
index 5affcb756..47ebc8a9b 100644
--- a/source/slang/slang-ast-base.h
+++ b/source/slang/slang-ast-base.h
@@ -41,7 +41,7 @@ class NodeBase
/// The type of the node. ASTNodeType(-1) is an invalid node type, and shouldn't appear on any
/// correctly constructed (through ASTBuilder) NodeBase derived class.
/// The actual type is set when constructed on the ASTBuilder.
- FIDDLE() ASTNodeType astNodeType = ASTNodeType(-1);
+ ASTNodeType astNodeType = ASTNodeType(-1);
#ifdef _DEBUG
SLANG_UNREFLECTED int32_t _debugUID = 0;
@@ -752,7 +752,7 @@ public:
FIDDLE() NameLoc nameAndLoc;
FIDDLE() CapabilitySet inferredCapabilityRequirements;
- FIDDLE() RefPtr<MarkupEntry> markup;
+ RefPtr<MarkupEntry> markup;
Name* getName() const { return nameAndLoc.name; }
SourceLoc getNameLoc() const { return nameAndLoc.loc; }
diff --git a/source/slang/slang-ast-decl.h b/source/slang/slang-ast-decl.h
index 6fb281247..46d9147a0 100644
--- a/source/slang/slang-ast-decl.h
+++ b/source/slang/slang-ast-decl.h
@@ -758,7 +758,7 @@ class DerivativeRequirementDecl : public FunctionDeclBase
// A reference to a synthesized decl representing a differentiable function requirement, this decl
// will be a child in the orignal function.
FIDDLE()
-class DerivativeRequirementReferenceDecl : public FunctionDeclBase
+class DerivativeRequirementReferenceDecl : public Decl
{
FIDDLE(...)
FIDDLE() DerivativeRequirementDecl* referencedDecl;
diff --git a/source/slang/slang-ast-dump.cpp b/source/slang/slang-ast-dump.cpp
index 24b10344d..7f6f7796c 100644
--- a/source/slang/slang-ast-dump.cpp
+++ b/source/slang/slang-ast-dump.cpp
@@ -775,6 +775,8 @@ struct ASTDumpAccess
%for _,T in ipairs(Slang.NodeBase.subclasses) do
static void dump_($T * node, ASTDumpContext & context)
{
+ SLANG_UNUSED(node);
+ SLANG_UNUSED(context);
% if T.directSuperClass then
dump_(static_cast<$(T.directSuperClass)*>(node), context);
% end
diff --git a/source/slang/slang-ast-support-types.h b/source/slang/slang-ast-support-types.h
index b5ebe1884..6f82a534a 100644
--- a/source/slang/slang-ast-support-types.h
+++ b/source/slang/slang-ast-support-types.h
@@ -1564,7 +1564,7 @@ FIDDLE() namespace Slang
Flavor m_flavor;
DeclRef<Decl> m_declRef;
- RefPtr<RefObject> m_obj;
+ RefPtr<WitnessTable> m_obj;
Val* m_val = nullptr;
};
diff --git a/source/slang/slang-serialize-ast.cpp b/source/slang/slang-serialize-ast.cpp
index 1ef532ad1..9a61dbc5a 100644
--- a/source/slang/slang-serialize-ast.cpp
+++ b/source/slang/slang-serialize-ast.cpp
@@ -5,6 +5,7 @@
#include "slang-compiler.h"
#include "slang-diagnostics.h"
#include "slang-mangle.h"
+#include "slang-serialize-riff.h"
namespace Slang
{
@@ -13,654 +14,512 @@ namespace Slang
//
NodeBase* parseSimpleSyntax(Parser* parser, void* userData);
+//
+// Many of the types used in the AST can be serialized using
+// just the `Serializer` type, so we will handle all of those first.
+//
-struct ASTEncodingContext
+void serialize(Serializer const& serializer, ASTNodeType& value)
{
-private:
- Encoder* encoder;
- struct UnhandledCase
- {
- };
-
- typedef Int DeclID;
- Dictionary<Decl*, DeclID> mapDeclToID;
- List<Decl*> decls;
-
- struct ImportedDeclInfo
- {
- Int moduleIndex = -1;
- Decl* decl;
- };
- List<ImportedDeclInfo> importedDecls;
-
- typedef Int ValID;
- Dictionary<Val*, ValID> mapValToID;
- List<Val*> vals;
-
- ModuleDecl* _module = nullptr;
-
- SerialSourceLocWriter* _sourceLocWriter = nullptr;
+ serializeEnum(serializer, value);
+}
-public:
- ASTEncodingContext(Encoder* encoder, ModuleDecl* module, SerialSourceLocWriter* sourceLocWriter)
- : encoder(encoder), _module(module), _sourceLocWriter(sourceLocWriter)
- {
- }
+void serialize(Serializer const& serializer, TypeTag& value)
+{
+ serializeEnum(serializer, value);
+}
- template<typename T>
- void encodeASTNodeContent(T* node)
- {
- Encoder::WithObject withObject(encoder);
+void serialize(Serializer const& serializer, BaseType& value)
+{
+ serializeEnum(serializer, value);
+}
- ASTNodeDispatcher<T, void>::dispatch(node, [&](auto n) { _encodeDataOf(n); });
- }
+void serialize(Serializer const& serializer, TryClauseType& value)
+{
+ serializeEnum(serializer, value);
+}
- void flush()
- {
- auto containerChunk = encoder->getRIFFChunk();
+void serialize(Serializer const& serializer, DeclVisibility& value)
+{
+ serializeEnum(serializer, value);
+}
- RIFF::ChunkBuilder* declChunk = nullptr;
- RIFF::ChunkBuilder* importedDeclChunk = nullptr;
- RIFF::ChunkBuilder* valChunk = nullptr;
- {
- Encoder::WithArray withList(encoder);
- declChunk = encoder->getRIFFChunk();
- }
- {
- Encoder::WithArray withList(encoder);
- importedDeclChunk = encoder->getRIFFChunk();
- }
- {
- Encoder::WithArray withList(encoder);
- valChunk = encoder->getRIFFChunk();
- }
- Int declIndex = 0;
- Int importedDeclIndex = 0;
- Int valIndex = 0;
+void serialize(Serializer const& serializer, BuiltinRequirementKind& value)
+{
+ serializeEnum(serializer, value);
+}
- bool done = false;
- do
- {
- done = true;
- while (declIndex < decls.getCount())
- {
- done = false;
- encoder->setRIFFChunk(declChunk);
- encodeASTNodeContent(decls[declIndex++]);
- }
- while (importedDeclIndex < importedDecls.getCount())
- {
- done = false;
- encoder->setRIFFChunk(importedDeclChunk);
- encodeImportedDecl(importedDecls[importedDeclIndex++]);
- }
- while (valIndex < vals.getCount())
- {
- done = false;
- encoder->setRIFFChunk(valChunk);
- encodeASTNodeContent(vals[valIndex++]);
- }
- } while (!done);
+void serialize(Serializer const& serializer, ImageFormat& value)
+{
+ serializeEnum(serializer, value);
+}
- encoder->setRIFFChunk(containerChunk);
- }
+void serialize(Serializer const& serializer, PreferRecomputeAttribute::SideEffectBehavior& value)
+{
+ serializeEnum(serializer, value);
+}
- ModuleDecl* findModuleForDecl(Decl* decl)
- {
- for (auto d = decl; d; d = d->parentDecl)
- {
- if (auto m = as<ModuleDecl>(d))
- return m;
- }
- return nullptr;
- }
+void serialize(Serializer const& serializer, TreatAsDifferentiableExpr::Flavor& value)
+{
+ serializeEnum(serializer, value);
+}
- ModuleDecl* findModuleDeclWasImportedFrom(Decl* decl)
- {
- auto declModule = findModuleForDecl(decl);
- if (declModule == nullptr)
- return nullptr;
- if (declModule == _module)
- return nullptr;
- return declModule;
- }
+void serialize(Serializer const& serializer, LogicOperatorShortCircuitExpr::Flavor& value)
+{
+ serializeEnum(serializer, value);
+}
- DeclID getDeclID(Decl* decl)
- {
- SLANG_ASSERT(decl != nullptr);
+void serialize(Serializer const& serializer, RequirementWitness::Flavor& value)
+{
+ serializeEnum(serializer, value);
+}
- if (auto found = mapDeclToID.tryGetValue(decl))
- return *found;
+void serialize(Serializer const& serializer, CapabilityAtom& value)
+{
+ serializeEnum(serializer, value);
+}
- // We need to detect whether the declaration is an
- // imported one, or one from this module itself.
- //
- // Imported declarations need to be handled very
- // differently, since they'll involve resolving
- // references to those other modules, and the
- // declarations within them.
- //
- if (auto importedFromModule = findModuleDeclWasImportedFrom(decl))
- {
- DeclID importedFromModuleDeclID = 0;
- if (decl != importedFromModule)
- {
- importedFromModuleDeclID = getDeclID(importedFromModule);
- }
+void serialize(Serializer const& serializer, DeclAssociationKind& value)
+{
+ serializeEnum(serializer, value);
+}
- DeclID id = ~importedDecls.getCount();
- mapDeclToID.add(decl, id);
+void serialize(Serializer const& serializer, TokenType& value)
+{
+ serializeEnum(serializer, value);
+}
- ImportedDeclInfo info;
- info.moduleIndex = ~importedFromModuleDeclID;
- info.decl = decl;
- importedDecls.add(info);
+void serialize(Serializer const& serializer, ValNodeOperandKind& value)
+{
+ serializeEnum(serializer, value);
+}
- return id;
- }
- else
- {
- DeclID id = decls.getCount();
- decls.add(decl);
- mapDeclToID.add(decl, id);
+void serialize(Serializer const& serializer, SPIRVAsmOperand::Flavor& value)
+{
+ serializeEnum(serializer, value);
+}
- return id;
- }
- }
+void serialize(Serializer const& serializer, MatrixCoord& value)
+{
+ SLANG_SCOPED_SERIALIZER_TUPLE(serializer);
+ serialize(serializer, value.row);
+ serialize(serializer, value.col);
+}
- void encodePtr(Decl* decl)
+void serializePtr(Serializer const& serializer, DiagnosticInfo const*& value, DiagnosticInfo const*)
+{
+ Int32 id = 0;
+ if (isWriting(serializer))
{
- DeclID id = getDeclID(decl);
- encoder->encode(id);
+ id = value->id;
+ serialize(serializer, id);
}
-
- ValID getValID(Val* val)
+ else
{
- SLANG_ASSERT(val != nullptr);
-
- if (auto found = mapValToID.tryGetValue(val))
- return *found;
-
- // In order to ensure that values can be fully constructed
- // from the get-go (so that they will get cached correctly),
- // we conspire to ensure that every value is preceded by
- // all of its operands.
- //
- for (auto operand : val->m_operands)
- {
- switch (operand.kind)
- {
- default:
- break;
-
- case ValNodeOperandKind::ValNode:
- if (auto operandNode = operand.values.nodeOperand)
- {
- SLANG_ASSERT(as<Val>(operandNode));
- getValID(static_cast<Val*>(operandNode));
- }
- break;
-
- case ValNodeOperandKind::ASTNode:
- if (auto operandNode = operand.values.nodeOperand)
- {
- SLANG_ASSERT(as<Decl>(operandNode));
- getDeclID(static_cast<Decl*>(operandNode));
- }
- break;
- }
- }
- auto resolved = val->resolve();
- if (resolved != val)
- {
- getValID(resolved);
- }
-
- ValID id = vals.getCount();
- vals.add(val);
- mapValToID.add(val, id);
- return id;
+ serialize(serializer, id);
+ value = getDiagnosticsLookup()->getDiagnosticById(id);
}
+}
- void encodePtr(Val* val)
+void serialize(Serializer const& serializer, SemanticVersion& value)
+{
+ auto raw = value.getRawValue();
+ serialize(serializer, raw);
+ value = SemanticVersion::fromRaw(raw);
+}
+
+void serialize(Serializer const& serializer, SyntaxClass<NodeBase>& value)
+{
+ ASTNodeType raw;
+ if (isWriting(serializer))
{
- ValID id = getValID(val);
- encoder->encode(id);
+ raw = value.getTag();
}
-
- void encodeImportedDecl(ImportedDeclInfo const& info)
+ serialize(serializer, raw);
+ if (isReading(serializer))
{
- Encoder::WithKeyValuePair withPair(encoder);
- encode(info.moduleIndex);
- auto decl = info.decl;
- if (auto importedModuleDecl = as<ModuleDecl>(decl))
- {
- SLANG_ASSERT(info.moduleIndex == -1);
- encode(importedModuleDecl->getName());
- }
- else
- {
- auto mangledName = getMangledName(getCurrentASTBuilder(), decl);
- encode(mangledName);
- }
+ value = SyntaxClass<NodeBase>(raw);
}
+}
- void encodePtr(Modifier* modifier) { encodeASTNodeContent(modifier); }
- void encodePtr(Expr* expr) { encodeASTNodeContent(expr); }
- void encodePtr(Stmt* stmt) { encodeASTNodeContent(stmt); }
+//
+// Many types in the AST need additional context (beyond
+// what the `Serializer` has) in order to serialize
+// themselves or their members.
+//
+// We define a custom serializer interface to capture
+// the cases that can't be handled by a `Serializer`
+// alone.
+//
- void encodePtr(Name* name) { encode(name->text); }
+/// 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;
- void encodePtr(MarkupEntry* entry)
- {
- // TODO: is this case needed?
- SLANG_UNUSED(entry);
- }
+ // 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.
- void encodePtr(DeclAssociationList* list)
- {
- // We serialize this as if it were a simple list
- // of key-value pairs because... well... that's
- // what it amounts to in practice.
- //
- Encoder::WithArray withArray(encoder);
- for (auto association : list->associations)
- {
- Encoder::WithKeyValuePair withPair(encoder);
- encode(association->kind);
- encode(association->decl);
- }
- }
+ virtual ISerializerImpl* getBaseSerializer() = 0;
+};
- void encodePtr(CandidateExtensionList* list) { encode(list->candidateExtensions); }
+/// Specialization of `Serializer_` for AST serialization.
+template<>
+struct Serializer_<ASTSerializerImpl> : SerializerBase<ASTSerializerImpl>
+{
+public:
+ using SerializerBase::SerializerBase;
- void encodePtr(WitnessTable* witnessTable)
- {
- Encoder::WithObject withObject(encoder);
- encode(witnessTable->baseType);
- encode(witnessTable->witnessedType);
- encode(witnessTable->isExtern);
-
- // 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).
- //
- encode(witnessTable->m_requirementDictionary);
- }
+ //
+ // In order to allow an `ASTSerializer` to be used with
+ // functions that expect an ordinary `Serializer`, we
+ // implement an implicit conversion operator.
+ //
- void encodeValue(RequirementWitness const& witness)
- {
- Encoder::WithKeyValuePair withPair(encoder);
- encodeEnum(witness.m_flavor);
- switch (witness.m_flavor)
- {
- case RequirementWitness::Flavor::none:
- break;
+ operator Serializer() const { return Serializer(get()->getBaseSerializer()); }
+};
- case RequirementWitness::Flavor::declRef:
- encode(witness.m_declRef);
- break;
+/// Context type for AST serialization.
+using ASTSerializer = Serializer_<ASTSerializerImpl>;
- case RequirementWitness::Flavor::val:
- encode(witness.m_val);
- break;
+template<typename T>
+void serializeObject(ASTSerializer const& serializer, T*& value, NodeBase*)
+{
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ serializer->handleASTNode(*(NodeBase**)&value);
+}
- case RequirementWitness::Flavor::witnessTable:
- encode((WitnessTable*)witness.m_obj.Ptr());
- break;
- }
- }
+void serializeObjectContents(ASTSerializer const& serializer, NodeBase* value, NodeBase*)
+{
+ serializer->handleASTNodeContents(value);
+}
- void encodePtr(DiagnosticInfo* info) { encode(Int(info->id)); }
+template<typename T>
+void serialize(ASTSerializer const& serializer, DeclRef<T>& value)
+{
+ serialize(serializer, value.declRefBase);
+}
- void encodePtr(DeclBase* declBase)
+void serialize(ASTSerializer const& serializer, SourceLoc& value)
+{
+ serializer->handleSourceLoc(value);
+}
+
+void serialize(ASTSerializer const& serializer, RequirementWitness& value)
+{
+ SLANG_SCOPED_SERIALIZER_TAGGED_UNION(serializer);
+ serialize(serializer, value.m_flavor);
+ switch (value.m_flavor)
{
- if (auto decl = as<Decl>(declBase))
- {
- encodePtr(decl);
- }
- else
- {
- encodeASTNodeContent(declBase);
- }
- }
+ case RequirementWitness::Flavor::none:
+ break;
- void encodeValue(UnhandledCase);
+ case RequirementWitness::Flavor::declRef:
+ serialize(serializer, value.m_declRef);
+ break;
- void encodeValue(String const& value) { encoder->encode(value); }
+ case RequirementWitness::Flavor::val:
+ serialize(serializer, value.m_val);
+ break;
- void encodeValue(Token const& value)
- {
- encode(value.type);
- encode(TokenFlags(value.flags & ~TokenFlag::Name));
- encode(value.loc);
- if (value.hasContent())
- encoder->encodeString(value.getContent());
- else
- encode(nullptr);
+ case RequirementWitness::Flavor::witnessTable:
+ serialize(serializer, value.m_obj);
+ break;
}
+}
- void encodeValue(NameLoc const& value) { encode(value.name); }
-
- void encodeValue(SemanticVersion value) { encoder->encode(value.getRawValue()); }
+void serialize(ASTSerializer const& serializer, WitnessTable& value)
+{
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ serialize(serializer, value.baseType);
+ serialize(serializer, value.witnessedType);
+ serialize(serializer, value.isExtern);
+
+ // 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);
+}
- void encodeValue(CapabilitySet const& value)
+void serialize(Serializer const& serializer, CapabilityAtomSet& value)
+{
+ SLANG_SCOPED_SERIALIZER_ARRAY(serializer);
+ if (isWriting(serializer))
{
- // While the `CapabilityTargetSets` type is a dictionary,
- // in practice each entry already embeds its own key
- // (the target atom), so we can encode this as just
- // an array of the `CapabilityTargetSet` values.
- //
- Encoder::WithArray withArray(encoder);
- for (auto pair : value.getCapabilityTargetSets())
+ for (auto rawAtom : value)
{
- encode(pair.second);
+ auto atom = CapabilityAtom(rawAtom);
+ serialize(serializer, atom);
}
}
-
- void encodeValue(CapabilityTargetSet const& value)
+ else
{
- Encoder::WithKeyValuePair withPair(encoder);
- encode(value.target);
-
- // Similar to the case for the `CapabilityTargetSets` above,
- // each `CapabilityStageSet` already includes the stage atom,
- // so we can simply encode the values from the dictionary.
- //
- Encoder::WithArray withArray(encoder);
- for (auto pair : value.shaderStageSets)
+ while (hasElements(serializer))
{
- encode(pair.second);
+ CapabilityAtom atom;
+ serialize(serializer, atom);
+ value.add(UInt(atom));
}
}
+}
- void encodeValue(CapabilityStageSet const& value)
- {
- Encoder::WithKeyValuePair withPair(encoder);
- encode(value.stage);
- encode(value.atomSet);
- }
+void serialize(Serializer const& serializer, CapabilityStageSet& value)
+{
+ serialize(serializer, value.atomSet);
+}
- void encodeValue(CapabilityAtomSet const& value)
+void serialize(Serializer const& serializer, CapabilityTargetSet& value)
+{
+ serialize(serializer, value.shaderStageSets);
+
+ // The value for each entry in `shaderStageSets` have
+ // a `stage` field that is redundant with the key for
+ // that entry. Rather than serialize the key as part
+ // of the `CapabilityStageSet` type, we instead copy
+ // it over from the key to the value in the case where
+ // we are reading.
+ //
+ if (isReading(serializer))
{
- Encoder::WithArray withArray(encoder);
- for (auto rawAtom : value)
- {
- encode(CapabilityAtom(rawAtom));
- }
+ for (auto& p : value.shaderStageSets)
+ p.second.stage = p.first;
}
+}
- template<typename T>
- void encodeValue(std::optional<T> const& value)
+void serialize(Serializer const& serializer, CapabilitySet& value)
+{
+ serialize(serializer, value.getCapabilityTargetSets());
+
+ // The value for each entry in `getCapabilityTargetSets()` have
+ // a `target` field that is redundant with the key for
+ // that entry. Rather than serialize the key as part
+ // of the `CapabilityTargetSet` type, we instead copy
+ // it over from the key to the value in the case where
+ // we are reading.
+ //
+ if (isReading(serializer))
{
- if (value)
- encodeValue(*value);
- else
- encoder->encode(nullptr);
+ for (auto& p : value.getCapabilityTargetSets())
+ p.second.target = p.first;
}
+}
+
+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);
+}
- void encodeValue(SyntaxClass<NodeBase> const& value) { encode(value.getTag()); }
+void serialize(ASTSerializer const& serializer, DeclAssociationList& value)
+{
+ serialize(serializer, value.associations);
+}
- template<typename T>
- void encodeValue(DeclRef<T> const& value)
+void serialize(ASTSerializer const& serializer, Modifiers& value)
+{
+ SLANG_SCOPED_SERIALIZER_ARRAY(serializer);
+ if (isWriting(serializer))
{
- encode((DeclRefBase*)value);
+ for (auto modifier : value)
+ {
+ serialize(serializer, modifier);
+ }
}
-
- void encodeValue(ValNodeOperand value)
+ else
{
- Encoder::WithKeyValuePair withPair(encoder);
+ Modifier** link = &value.first;
- encodeEnum(value.kind);
- switch (value.kind)
+ while (hasElements(serializer))
{
- case ValNodeOperandKind::ConstantValue:
- encode(value.values.intOperand);
- break;
-
- case ValNodeOperandKind::ValNode:
- encode(static_cast<Val*>(value.values.nodeOperand));
- break;
+ Modifier* modifier = nullptr;
+ serialize(serializer, modifier);
- case ValNodeOperandKind::ASTNode:
- {
- if (auto decl = as<Decl>(value.values.nodeOperand))
- {
- encode(decl);
- }
- else
- {
- SLANG_UNEXPECTED("AST node operand of `Val` was expected to be a `Decl`");
- }
- }
- break;
+ *link = modifier;
+ link = &modifier->next;
}
}
+}
- void encodeValue(TypeExp value) { encode(value.type); }
+void serialize(ASTSerializer const& serializer, TypeExp& value)
+{
+ serialize(serializer, value.type);
+}
- void encodeValue(QualType value)
- {
- Encoder::WithObject withObject(encoder);
- encode(value.type);
- encode(value.isLeftValue);
- encode(value.hasReadOnlyOnTarget);
- encode(value.isWriteOnly);
- }
+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);
+}
- void encodeValue(MatrixCoord value)
- {
- Encoder::WithObject withObject(encoder);
- encode(value.row);
- encode(value.col);
- }
+void serialize(ASTSerializer const& serializer, Token& value)
+{
+ serializer->handleToken(value);
+}
- void encodeValue(SPIRVAsmOperand::Flavor const& value) { encodeEnum(value); }
+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);
+}
- void encodeValue(SPIRVAsmOperand const& value)
- {
- Encoder::WithObject withObject(encoder);
- encode(value.flavor);
- encode(value.token);
- encode(value.expr);
- encode(value.bitwiseOrWith);
- encode(value.knownValue);
- encode(value.wrapInId);
- encode(value.type);
- }
+void serialize(ASTSerializer const& serializer, SPIRVAsmInst& value)
+{
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ serialize(serializer, value.opcode);
+ serialize(serializer, value.operands);
+}
- void encodeValue(SPIRVAsmInst const& value)
+void serialize(ASTSerializer const& serializer, ValNodeOperand& value)
+{
+ SLANG_SCOPED_SERIALIZER_TAGGED_UNION(serializer);
+ serialize(serializer, value.kind);
+ switch (value.kind)
{
- Encoder::WithObject withObject(encoder);
- encode(value.opcode);
- encode(value.operands);
- }
+ case ValNodeOperandKind::ConstantValue:
+ serialize(serializer, value.values.intOperand);
+ break;
-
- template<typename T, typename = std::enable_if_t<std::is_same_v<T, bool>>>
- void encodeValue(T value)
- {
- encoder->encodeBool(value);
+ case ValNodeOperandKind::ValNode:
+ case ValNodeOperandKind::ASTNode:
+ serialize(serializer, value.values.nodeOperand);
+ break;
}
+}
- void encodeValue(Int32 value) { encoder->encode(value); }
- void encodeValue(UInt32 value) { encoder->encode(value); }
- void encodeValue(Int64 value) { encoder->encode(value); }
- void encodeValue(UInt64 value) { encoder->encode(value); }
- void encodeValue(float value) { encoder->encode(value); }
- void encodeValue(double value) { encoder->encode(value); }
-
- void encodeValue(uint8_t value) { encoder->encode(UInt32(value)); }
-
- void encodeValue(std::nullptr_t) { encoder->encode(nullptr); }
+void serializeObject(ASTSerializer const& serializer, Name*& value, Name*)
+{
+ serializer->handleName(value);
+}
- template<typename T>
- void encodeEnum(T value)
- {
- encoder->encode(Int32(value));
- }
+void serialize(ASTSerializer const& serializer, NameLoc& value)
+{
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ serialize(serializer, value.name);
+ serialize(serializer, value.loc);
+}
- void encodeValue(DeclVisibility value) { encodeEnum(value); }
- void encodeValue(BaseType value) { encodeEnum(value); }
- void encodeValue(BuiltinRequirementKind value) { encodeEnum(value); }
- void encodeValue(ASTNodeType value) { encodeEnum(value); }
- void encodeValue(ImageFormat value) { encodeEnum(value); }
- void encodeValue(TypeTag value) { encodeEnum(value); }
- void encodeValue(TryClauseType value) { encodeEnum(value); }
- void encodeValue(CapabilityAtom value) { encodeEnum(value); }
- void encodeValue(DeclAssociationKind value) { encodeEnum(value); }
- void encodeValue(TokenType value) { encodeEnum(value); }
-
- void encodeValue(SourceLoc value)
- {
- if (!_sourceLocWriter)
- {
- encoder->encode(nullptr);
- }
- else
- {
- auto intermediate = _sourceLocWriter->addSourceLoc(value);
- encoder->encode(intermediate);
+#if 0 // FIDDLE TEMPLATE:
+%for _,T in ipairs(Slang.NodeBase.subclasses) do
+void _serializeASTNodeContents(ASTSerializer const& serializer, $T* value)
+{
+ SLANG_UNUSED(serializer);
+ SLANG_UNUSED(value);
+% if T.directSuperClass then
+ _serializeASTNodeContents(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 0
+#include "slang-serialize-ast.cpp.fiddle"
+#endif // FIDDLE END
- template<typename T>
- void encodeValue(T const* ptr)
- {
- if (!ptr)
- {
- encoder->encode(nullptr);
- }
- else
- {
- encodePtr(const_cast<T*>(ptr));
- }
- }
+void serializeASTNodeContents(ASTSerializer const& serializer, NodeBase* node)
+{
+ ASTNodeDispatcher<NodeBase, void>::dispatch(
+ node,
+ [&](auto n) { _serializeASTNodeContents(serializer, n); });
+}
- template<typename T>
- void encodeValue(RefPtr<T> const& ptr)
- {
- if (!ptr)
- {
- encoder->encode(nullptr);
- }
- else
- {
- encodePtr(ptr.Ptr());
- }
- }
+enum class PseudoASTNodeType
+{
+ None,
+ ImportedModule,
+ ImportedDecl,
+};
- void encodeValue(Modifiers const& modifiers)
- {
- Encoder::WithArray withArray(encoder);
- for (auto m : const_cast<Modifiers&>(modifiers))
- {
- encode(m);
- }
- }
+static PseudoASTNodeType _getPseudoASTNodeType(ASTNodeType type)
+{
+ return int(type) < 0 ? PseudoASTNodeType(~int(type)) : PseudoASTNodeType::None;
+}
+
+static ASTNodeType _getAsASTNodeType(PseudoASTNodeType type)
+{
+ return ASTNodeType(~int(type));
+}
- template<typename T, int N>
- void encodeValue(ShortList<T, N> const& array)
+struct ASTEncodingContext : ASTSerializerImpl
+{
+public:
+ ASTEncodingContext(
+ RIFF::BuildCursor& cursor,
+ ModuleDecl* module,
+ SerialSourceLocWriter* sourceLocWriter)
+ : _writer(cursor.getCurrentChunk()), _module(module), _sourceLocWriter(sourceLocWriter)
{
- Encoder::WithArray withArray(encoder);
- for (auto element : array)
- {
- encode(element);
- }
}
+private:
+ RIFFSerialWriter _writer;
+ ModuleDecl* _module = nullptr;
+ SerialSourceLocWriter* _sourceLocWriter = nullptr;
- template<typename T>
- void encode(List<T> const& array)
- {
- Encoder::WithArray withArray(encoder);
- for (auto element : array)
- {
- encode(element);
- }
- }
+ virtual ISerializerImpl* getBaseSerializer() override { return &_writer; }
- template<typename T, size_t N>
- void encode(T const (&array)[N])
- {
- Encoder::WithArray withArray(encoder);
- for (auto element : array)
- {
- encode(element);
- }
- }
+ 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;
- template<typename K, typename V>
- void encode(OrderedDictionary<K, V> const& dictionary)
- {
- Encoder::WithArray withArray(encoder);
- for (auto p : dictionary)
- {
- Encoder::WithKeyValuePair withPair(encoder);
- encode(p.key);
- encode(p.value);
- }
- }
+ void _writeImportedModule(ModuleDecl* moduleDecl);
+ void _writeImportedDecl(Decl* decl, ModuleDecl* importedFromModuleDecl);
- template<typename K, typename V>
- void encode(Dictionary<K, V> const& dictionary)
+ ModuleDecl* _findModuleForDecl(Decl* decl)
{
- Encoder::WithArray withArray(encoder);
- for (auto p : dictionary)
+ for (auto d = decl; d; d = d->parentDecl)
{
- Encoder::WithKeyValuePair withPair(encoder);
- encode(p.first);
- encode(p.second);
+ if (auto m = as<ModuleDecl>(d))
+ return m;
}
+ return nullptr;
}
- template<typename T>
- void encode(T const& value)
+ ModuleDecl* _findModuleDeclWasImportedFrom(Decl* decl)
{
- encodeValue(value);
- }
-
- // for each class of node, we generate
- // code to recursively serialize each
- // of its fields.
-
-#if 0 // FIDDLE TEMPLATE:
-%for _,T in ipairs(Slang.NodeBase.subclasses) do
- void _encodeDataOf($T* obj)
- {
-%if T.directSuperClass then
- _encodeDataOf(static_cast<$(T.directSuperClass)*>(obj));
-%end
-%for _,f in ipairs(T.directFields) do
- encode(obj->$f);
-%end
+ auto declModule = _findModuleForDecl(decl);
+ if (declModule == nullptr)
+ return nullptr;
+ if (declModule == _module)
+ return nullptr;
+ return declModule;
}
-%end
-#else // FIDDLE OUTPUT:
-#define FIDDLE_GENERATED_OUTPUT_ID 0
-#include "slang-serialize-ast.cpp.fiddle"
-#endif // FIDDLE END
};
-void writeSerializedModuleAST(
- Encoder* encoder,
- ModuleDecl* moduleDecl,
- SerialSourceLocWriter* sourceLocWriter)
-{
- Encoder::WithObject withObject(encoder);
-
- // TODO: we should have a more careful pass here,
- // where we only encode the public declarations
- //
-
- ASTEncodingContext context(encoder, moduleDecl, sourceLocWriter);
- context.getDeclID(moduleDecl);
- context.flush();
-}
-
-struct ASTDecodingContext
+struct ASTDecodingContext : ASTSerializerImpl
{
public:
ASTDecodingContext(
@@ -673,284 +532,60 @@ public:
: _linkage(linkage)
, _astBuilder(astBuilder)
, _sink(sink)
- , _baseChunk(as<RIFF::ListChunk>(baseChunk))
, _sourceLocReader(sourceLocReader)
, _requestingSourceLoc(requestingSourceLoc)
+ , _riffReader(baseChunk)
{
}
+private:
Linkage* _linkage = nullptr;
+ ASTBuilder* _astBuilder = nullptr;
DiagnosticSink* _sink = nullptr;
SerialSourceLocReader* _sourceLocReader = nullptr;
SourceLoc _requestingSourceLoc;
+ RIFFSerialReader _riffReader;
- SlangResult decodeAll()
- {
- auto cursor = _baseChunk->getChildren().begin();
-
- // There are a few different top-level chunks that
- // hold different arrays that we need in order
- // to decode the entire module hierarchy.
- //
- // Basically, these lists correspond to the kinds
- // of nodes in the AST hierarchy for which back-references
- // are allowed (all other nodes should, barring
- // weird corner cases, form a single tree-structured
- // ownership hierarchy, rooted at the `ModuleDecl`.
- //
-
- // First there is the list that actually encodes
- // for the declarations in the module, including
- // the `ModuleDecl` itself, which should be the
- // first entry in the list.
- //
- auto declChunk = *cursor;
- ++cursor;
-
- // Next there is a list of all the declarations
- // referenced inside of the module that need to
- // be imported in from outside.
- //
- auto importedDeclChunk = *cursor;
- ++cursor;
+ virtual ISerializerImpl* getBaseSerializer() override { return &_riffReader; }
- // Then there are all the `Val`-derived nodes that
- // are needed by the module, which will need to be
- // deduplicated so that they are unique within the
- // current compilation context.
- //
- auto valChunk = *cursor;
- ++cursor;
+ 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;
- // The process of decoding the module is then spread
- // over a number of steps.
- //
- // The first step is to process all of the imported
- // declarations, so that other nodes can refer to
- // them.
- //
- SLANG_RETURN_ON_FAIL(decodeImportedDecls(importedDeclChunk));
-
- // Next we process the declarations that are within
- // the module itself, first creating an "empty shell"
- // of each declaration that has the right size in
- // memory (and the right `ASTNodeType` tag), so that
- // we can wire up references to it (including circular
- // references)... so long as nothing here tries to
- // look *inside* the empty shell along the way.
- //
- SLANG_RETURN_ON_FAIL(createEmptyShells(declChunk));
-
- // Once all the `Decl`s that might be needed have
- // been allocated, we can process all the `Val`s
- // that might reference those`Decl`s (and one another).
- //
- // The nature of the `Val` representation ensures
- // that there cannot be cirularities in the references
- // between `Val`s, and the encoding process will have
- // sorted the entries so that a `Val` only ever appears
- // *after* its operands.
- //
- SLANG_RETURN_ON_FAIL(decodeVals(valChunk));
+ ModuleDecl* _readImportedModule();
+ NodeBase* _readImportedDecl();
- // Once all the back-reference-able objects have been
- // instantiated in memory, we can go back through the
- // `Decl`s in the module and fill in those empty shells.
- //
- SLANG_RETURN_ON_FAIL(fillEmptyShells(declChunk));
-
- // As a final pass, we perform any special cleanup actions
- // that might be required to make the output valid for consumers.
- //
- // For example, this is where we set the `DeclCheckState` of everything
- // we are loading to reflect the fact that everything we deserialize
- // is (supposed to be) fully cheked.
- //
- SLANG_RETURN_ON_FAIL(cleanUpNodes());
-
-
- return SLANG_OK;
- }
-
- typedef Int DeclID;
- Decl* getDeclByID(DeclID id)
+ void _cleanUpASTNode(NodeBase* node)
{
- if (id >= 0)
- {
- return _decls[id];
- }
- else
+ if (auto expr = as<Expr>(node))
{
- return _importedDecls[~id];
+ expr->checked = true;
}
- }
-
-private:
- struct UnhandledCase
- {
- };
-
- ASTBuilder* _astBuilder = nullptr;
- RIFF::ListChunk const* _baseChunk = nullptr;
-
- List<Decl*> _decls;
- List<Decl*> _importedDecls;
- List<Val*> _vals;
-
- typedef Int ValID;
- Val* getValByID(ValID id) { return _vals[id]; }
-
- SlangResult decodeImportedDecls(RIFF::Chunk const* importedDeclChunk)
- {
- Decoder decoder(importedDeclChunk);
-
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
+ else if (auto decl = as<Decl>(node))
{
- Decoder::WithKeyValuePair withPair(decoder);
-
- Int moduleIndex;
- decode(moduleIndex, decoder);
+ decl->checkState = DeclCheckState::CapabilityChecked;
- if (moduleIndex == -1)
+ if (auto genericDecl = as<GenericDecl>(node))
{
- Name* moduleName = nullptr;
- decode(moduleName, decoder);
-
- Decl* importedModule = getImportedModule(moduleName);
- _importedDecls.add(importedModule);
+ _assignGenericParameterIndices(genericDecl);
}
- else
+ else if (auto syntaxDecl = as<SyntaxDecl>(node))
{
- auto importedFromModuleDecl = as<ModuleDecl>(_importedDecls[moduleIndex]);
- auto importedFromModule = importedFromModuleDecl->module;
-
- String mangledName;
- decode(mangledName, decoder);
-
- auto importedNode =
- importedFromModule->findExportFromMangledName(mangledName.getUnownedSlice());
- auto importedDecl = as<Decl>(importedNode);
- _importedDecls.add(importedDecl);
+ syntaxDecl->parseCallback = &parseSimpleSyntax;
+ syntaxDecl->parseUserData = (void*)syntaxDecl->syntaxClass.getInfo();
}
- }
- return SLANG_OK;
- }
-
- ModuleDecl* getImportedModule(Name* moduleName)
- {
- Module* module = _linkage->findOrImportModule(moduleName, _requestingSourceLoc, _sink);
- if (!module)
- {
- SLANG_ABORT_COMPILATION("failed to load an imported module during deserialization");
- }
-
- return module->getModuleDecl();
- }
-
- SlangResult decodeVals(RIFF::Chunk const* valChunk)
- {
- Decoder decoder(valChunk);
-
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- Val* val = decodeValNode(decoder);
- _vals.add(val);
- }
- return SLANG_OK;
- }
-
- SlangResult createEmptyShells(RIFF::Chunk const* declChunk)
- {
- Decoder decoder(declChunk);
-
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- ASTNodeType nodeType;
-
- // Each of the declarations is expected to take
- // the form of an object with a first field
- // that holds the node type.
- //
+ else if (auto namespaceLikeDecl = as<NamespaceDeclBase>(node))
{
- Decoder::WithObject withObject(decoder);
- decode(nodeType, decoder);
+ auto declScope = _astBuilder->create<Scope>();
+ declScope->containerDecl = namespaceLikeDecl;
+ namespaceLikeDecl->ownedScope = declScope;
}
-
- auto emptyShell = createEmptyShell(nodeType);
- auto declEmptyShell = as<Decl>(emptyShell);
- _decls.add(declEmptyShell);
}
-
- return SLANG_OK;
}
- Val* decodeValNode(Decoder& decoder)
- {
- Decoder::WithObject withObject(decoder);
-
- ASTNodeType nodeType;
- decode(nodeType, decoder);
-
- ValNodeDesc desc;
- desc.type = SyntaxClass<NodeBase>(nodeType);
-
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- ValNodeOperand operand;
- decode(operand, decoder);
- desc.operands.add(operand);
- }
-
- desc.init();
-
- auto val = _astBuilder->_getOrCreateImpl(_Move(desc));
-
- // Values created during deserialization are
- // not expected to ever resolve further, because
- // they should be coming from fully checked code.
- //
- // val->resolve();
- // val->_setUnique();
-
- return val;
- }
-
- NodeBase* createEmptyShell(ASTNodeType nodeType)
- {
- return SyntaxClass<NodeBase>(nodeType).createInstance(_astBuilder);
- }
-
- SlangResult fillEmptyShells(RIFF::Chunk const* declChunk)
- {
- Index declIndex = 0;
-
- Decoder decoder(declChunk);
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- auto declEmptyShell = _decls[declIndex++];
- decodeASTNodeContent(declEmptyShell, decoder);
- }
-
- return SLANG_OK;
- }
-
- SlangResult cleanUpNodes()
- {
- for (auto decl : _decls)
- {
- decl->checkState = DeclCheckState::CapabilityChecked;
- }
-
- return SLANG_OK;
- }
-
-
- void assignGenericParameterIndices(GenericDecl* genericDecl)
+ void _assignGenericParameterIndices(GenericDecl* genericDecl)
{
int parameterCounter = 0;
for (auto m : genericDecl->members)
@@ -965,576 +600,314 @@ private:
}
}
}
+};
+//
+// 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.
+//
- void cleanUpASTNode(NodeBase* node)
- {
- if (auto expr = as<Expr>(node))
- {
- expr->checked = true;
- }
- else 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 decodeASTNodeContent(NodeBase* node, Decoder& decoder)
- {
- Decoder::WithObject withObject(decoder);
-
- ASTNodeDispatcher<NodeBase, void>::dispatch(
- node,
- [&](auto n) { _decodeDataOf(n, decoder); });
+//
+// AST{Encoding|Decoding}Context::handleName()
+//
- cleanUpASTNode(node);
- }
+void ASTEncodingContext::handleName(Name*& value)
+{
+ serialize(ASTSerializer(this), value->text);
+}
- DeclID decodeDeclID(Decoder& decoder)
- {
- DeclID result = decoder.decode<DeclID>();
- return result;
- }
+void ASTDecodingContext::handleName(Name*& value)
+{
+ String text;
+ serialize(ASTSerializer(this), text);
+ value = _astBuilder->getNamePool()->getName(text);
+}
- ValID decodeValID(Decoder& decoder)
- {
- ValID result = decoder.decode<ValID>();
- return result;
- }
+//
+// AST{Encoding|Decoding}Context::handleSourceLoc()
+//
- template<typename T>
- void decodeASTNode(T*& node, Decoder& decoder)
+void ASTEncodingContext::handleSourceLoc(SourceLoc& value)
+{
+ ASTSerializer serializer(this);
+ SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
+ if (_sourceLocWriter != nullptr)
{
- ASTNodeType nodeType;
- auto saved = decoder.getCursor();
- {
- Decoder::WithObject withObject(decoder);
- decode(nodeType, decoder);
- }
- decoder.setCursor(saved);
-
- auto shell = createEmptyShell(nodeType);
- decodeASTNodeContent(shell, decoder);
-
- node = as<T>(shell);
+ auto rawValue = _sourceLocWriter->addSourceLoc(value);
+ serialize(serializer, rawValue);
}
+}
- void decodePtr(Name*& name, Decoder& decoder, Name*)
+void ASTDecodingContext::handleSourceLoc(SourceLoc& value)
+{
+ ASTSerializer serializer(this);
+ SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
+ if (hasElements(serializer))
{
- String text;
- decode(text, decoder);
-
- name = _astBuilder->getNamePool()->getName(text);
- }
+ SerialSourceLocData::SourceLoc rawValue;
+ serialize(serializer, rawValue);
- void decodePtr(DeclAssociationList*& outList, Decoder& decoder, DeclAssociationList*)
- {
- // Mirroring the encoding logic, we decode this
- // as a list of key-value pairs.
- //
- auto list = RefPtr(new DeclAssociationList());
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
+ if (_sourceLocReader)
{
- auto association = RefPtr(new DeclAssociation());
-
- Decoder::WithKeyValuePair withPair(decoder);
- decode(association->kind, decoder);
- decode(association->decl, decoder);
-
- list->associations.add(association);
+ value = _sourceLocReader->getSourceLoc(rawValue);
}
-
- outList = list.detach();
}
+}
- void decodePtr(DiagnosticInfo const*& info, Decoder& decoder, DiagnosticInfo const*)
- {
- Int id;
- decode(id, decoder);
- info = getDiagnosticsLookup()->getDiagnosticById(id);
- }
-
- void decodePtr(MarkupEntry*& markupEntry, Decoder&, MarkupEntry*)
- {
- // TODO: is this case needed?
- markupEntry = nullptr;
- }
-
- void decodePtr(CandidateExtensionList*& list, Decoder& decoder, CandidateExtensionList*)
- {
- auto result = RefPtr(new CandidateExtensionList());
- decode(result->candidateExtensions, decoder);
- list = result.detach();
- }
-
- void decodePtr(WitnessTable*& witnessTable, Decoder& decoder, WitnessTable*)
- {
- Decoder::WithObject withObject(decoder);
- auto wt = RefPtr(new WitnessTable());
- decode(wt->baseType, decoder);
- decode(wt->witnessedType, decoder);
- decode(wt->isExtern, decoder);
- decode(wt->m_requirementDictionary, decoder);
- witnessTable = wt.detach();
- }
-
- void decodeValue(RequirementWitness& witness, Decoder& decoder)
- {
- Decoder::WithKeyValuePair withPair(decoder);
- decodeEnum(witness.m_flavor, decoder);
- switch (witness.m_flavor)
- {
- case RequirementWitness::Flavor::none:
- break;
-
- case RequirementWitness::Flavor::declRef:
- decode(witness.m_declRef, decoder);
- break;
-
- case RequirementWitness::Flavor::val:
- decode(witness.m_val, decoder);
- break;
+//
+// AST{Encoding|Decoding}Context::handleToken()
+//
- case RequirementWitness::Flavor::witnessTable:
- {
- RefPtr<WitnessTable> object;
- decode(object, decoder);
- witness.m_obj = object;
- }
- break;
- }
- }
+void ASTDecodingContext::handleToken(Token& value)
+{
+ ASTSerializer serializer(this);
- template<typename T>
- void decodePtr(T*& node, Decoder& decoder, Val*)
- {
- ValID id = decodeValID(decoder);
- node = static_cast<T*>(getValByID(id));
- }
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ serialize(serializer, value.type);
+ serialize(serializer, value.loc);
- template<typename T>
- void decodePtr(T*& node, Decoder& decoder, Decl*)
- {
- DeclID id = decodeDeclID(decoder);
- node = static_cast<T*>(getDeclByID(id));
- }
+ serialize(serializer, value.flags);
- template<typename T>
- void decodePtr(T*& node, Decoder& decoder, DeclBase*)
{
- // This case is a bit of a hack. We need
- // to identify whether we are looking at
- // an indirection to a `Decl` (which would
- // be serialized as an integer `DeclID`),
- // or something else derived from `DeclBase`.
- //
- switch (decoder.getTag())
+ SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
+ if (hasElements(serializer))
{
- default:
- decodeASTNode(node, decoder);
- break;
-
- case SerialBinary::kInt32FourCC:
- case SerialBinary::kInt64FourCC:
- case SerialBinary::kUInt32FourCC:
- case SerialBinary::kUInt64FourCC:
- {
- DeclID id = decodeDeclID(decoder);
- node = static_cast<T*>(getDeclByID(id));
- }
- break;
- }
- }
-
- template<typename T>
- void decodePtr(T*& node, Decoder& decoder, NodeBase*)
- {
- decodeASTNode(node, decoder);
- }
+ String content;
+ serialize(serializer, content);
-
- void decodeValue(UnhandledCase, Decoder& decoder);
-
- void decodeValue(String& value, Decoder& decoder) { value = decoder.decodeString(); }
-
- void decodeValue(Token& value, Decoder& decoder)
- {
- decode(value.type, decoder);
- decode(value.flags, decoder);
- decode(value.loc, decoder);
- if (decoder.decodeNull())
- {
- }
- else
- {
- Name* name = nullptr;
- decode(name, decoder);
+ // 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.
+ //
+ // 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 decodeValue(NameLoc& value, Decoder& decoder) { decode(value.name, decoder); }
-
- void decodeValue(SemanticVersion& value, Decoder& decoder)
- {
- SemanticVersion::RawValue rawValue = decoder.decode<SemanticVersion::RawValue>();
- value.setRawValue(rawValue);
- }
-
- void decodeValue(CapabilitySet& value, Decoder& decoder)
- {
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- CapabilityTargetSet targetSet;
- decode(targetSet, decoder);
- value.getCapabilityTargetSets()[targetSet.target] = targetSet;
- }
- }
-
- void decodeValue(CapabilityTargetSet& value, Decoder& decoder)
- {
- Decoder::WithKeyValuePair withPair(decoder);
- decode(value.target, decoder);
+void ASTEncodingContext::handleToken(Token& value)
+{
+ ASTSerializer serializer(this);
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- CapabilityStageSet stageSet;
- decode(stageSet, decoder);
- value.shaderStageSets[stageSet.stage] = stageSet;
- }
- }
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ serialize(serializer, value.type);
+ serialize(serializer, value.loc);
- void decodeValue(CapabilityStageSet& value, Decoder& decoder)
- {
- Decoder::WithKeyValuePair withPair(decoder);
- decode(value.stage, decoder);
- decode(value.atomSet, decoder);
- }
+ TokenFlags flags = TokenFlags(value.flags & ~TokenFlag::Name);
+ serialize(serializer, flags);
- void decodeValue(CapabilityAtomSet& value, Decoder& decoder)
{
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- CapabilityAtom atom;
- decode(atom, decoder);
- value.add(UInt(atom));
- }
- }
-
- template<typename T>
- void decodeValue(std::optional<T>& outValue, Decoder& decoder)
- {
- if (decoder.decodeNull())
- {
- outValue.reset();
- }
- else
+ SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
+ if (value.hasContent())
{
- T value;
- decode(value, decoder);
- outValue = value;
+ String content = value.getContent();
+ serialize(serializer, content);
}
}
+}
- void decodeValue(SyntaxClass<NodeBase>& syntaxClass, Decoder& decoder)
- {
- ASTNodeType nodeType;
- decode(nodeType, decoder);
- syntaxClass = SyntaxClass<NodeBase>(nodeType);
- }
-
- template<typename T>
- void decodeValue(DeclRef<T>& declRef, Decoder& decoder)
- {
- decode(declRef.declRefBase, decoder);
- }
+//
+// AST{Encoding|Decoding}Context::handleASTNode()
+//
- void decodeValue(ValNodeOperand& value, Decoder& decoder)
+void ASTEncodingContext::handleASTNode(NodeBase*& node)
+{
+ if (auto decl = as<Decl>(node))
{
- Decoder::WithKeyValuePair withPair(decoder);
-
- decodeEnum(value.kind, decoder);
- switch (value.kind)
+ if (auto importedFromModule = _findModuleDeclWasImportedFrom(decl))
{
- case ValNodeOperandKind::ConstantValue:
- decode(value.values.intOperand, decoder);
- break;
-
- case ValNodeOperandKind::ValNode:
+ if (decl == importedFromModule)
{
- Val* val = nullptr;
- decode(val, decoder);
- value.values.nodeOperand = val;
+ _writeImportedModule(importedFromModule);
+ return;
}
- break;
-
- case ValNodeOperandKind::ASTNode:
+ else
{
- Decl* decl = nullptr;
- decode(decl, decoder);
- value.values.nodeOperand = decl;
+ _writeImportedDecl(decl, importedFromModule);
+ return;
}
- break;
}
}
- void decodeValue(TypeExp& value, Decoder& decoder) { decode(value.type, decoder); }
+ ASTSerializer serializer(this);
- void decodeValue(QualType& value, Decoder& decoder)
+ if (auto val = as<Val>(node))
{
- Decoder::WithObject withObject(decoder);
- decode(value.type, decoder);
- decode(value.isLeftValue, decoder);
- decode(value.hasReadOnlyOnTarget, decoder);
- decode(value.isWriteOnly, decoder);
- }
+ val = val->resolve();
- void decodeValue(MatrixCoord& value, Decoder& decoder)
- {
- Decoder::WithObject withObject(decoder);
- decode(value.row, decoder);
- decode(value.col, decoder);
+ // 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);
}
-
- void decodeValue(SPIRVAsmOperand::Flavor& value, Decoder& decoder)
+ else
{
- decodeEnum(value, decoder);
+ serialize(serializer, node->astNodeType);
+ deferSerializeObjectContents(serializer, node);
}
+}
- void decodeValue(SPIRVAsmOperand& value, Decoder& decoder)
- {
- Decoder::WithObject withObject(decoder);
- decode(value.flavor, decoder);
- decode(value.token, decoder);
- decode(value.expr, decoder);
- decode(value.bitwiseOrWith, decoder);
- decode(value.knownValue, decoder);
- decode(value.wrapInId, decoder);
- decode(value.type, decoder);
- }
+void ASTDecodingContext::handleASTNode(NodeBase*& outNode)
+{
+ ASTSerializer serializer(this);
- void decodeValue(SPIRVAsmInst& value, Decoder& decoder)
+ ASTNodeType typeTag;
+ serialize(serializer, typeTag);
+ switch (_getPseudoASTNodeType(typeTag))
{
- Decoder::WithObject withObject(decoder);
- decode(value.opcode, decoder);
- decode(value.operands, decoder);
- }
+ default:
+ break;
+ case PseudoASTNodeType::ImportedModule:
+ outNode = _readImportedModule();
+ return;
- template<typename T>
- void decodeEnum(T& value, Decoder& decoder)
- {
- value = T(decoder.decode<Int32>());
+ case PseudoASTNodeType::ImportedDecl:
+ outNode = _readImportedDecl();
+ return;
}
- template<typename T>
- void decodeSimpleValue(T& value, Decoder& decoder)
+ auto syntaxClass = SyntaxClass<NodeBase>(typeTag);
+ if (syntaxClass.isSubClassOf<Val>())
{
- value = decoder.decode<T>();
- }
+ // Subclasses of `Val` are deduplicated as part
+ // of creation, so we need to read in their
+ // operands before we can create them, rather
+ // than allocating the object up front and
+ // then deserializing its content into it later.
- void decodeValue(bool& value, Decoder& decoder) { value = decoder.decodeBool(); }
- void decodeValue(Int32& value, Decoder& decoder) { decodeSimpleValue(value, decoder); }
- void decodeValue(Int64& value, Decoder& decoder) { decodeSimpleValue(value, decoder); }
- void decodeValue(UInt32& value, Decoder& decoder) { decodeSimpleValue(value, decoder); }
- void decodeValue(UInt64& value, Decoder& decoder) { decodeSimpleValue(value, decoder); }
- void decodeValue(float& value, Decoder& decoder) { decodeSimpleValue(value, decoder); }
- void decodeValue(double& value, Decoder& decoder) { decodeSimpleValue(value, decoder); }
+ ValNodeDesc desc;
+ desc.type = syntaxClass;
+ serialize(serializer, desc.operands);
- void decodeValue(uint8_t& value, Decoder& decoder)
- {
- value = uint8_t(decoder.decode<UInt32>());
- }
+ desc.init();
- void decodeValue(DeclVisibility& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(BaseType& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(BuiltinRequirementKind& value, Decoder& decoder)
- {
- decodeEnum(value, decoder);
- }
- void decodeValue(ASTNodeType& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(ImageFormat& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(TypeTag& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(TryClauseType& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(CapabilityAtom& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(PreferRecomputeAttribute::SideEffectBehavior& value, Decoder& decoder)
- {
- decodeEnum(value, decoder);
+ auto node = _astBuilder->_getOrCreateImpl(std::move(desc));
+ outNode = node;
}
- void decodeValue(LogicOperatorShortCircuitExpr::Flavor& value, Decoder& decoder)
+ else
{
- decodeEnum(value, decoder);
- }
- void decodeValue(TreatAsDifferentiableExpr::Flavor& value, Decoder& decoder)
- {
- decodeEnum(value, decoder);
- }
- void decodeValue(DeclAssociationKind& value, Decoder& decoder) { decodeEnum(value, decoder); }
- void decodeValue(TokenType& value, Decoder& decoder) { decodeEnum(value, decoder); }
+ auto node = syntaxClass.createInstance(_astBuilder);
+ outNode = node;
-
- void decodeValue(SourceLoc& value, Decoder& decoder)
- {
- if (!decoder.decodeNull())
- {
- SerialSourceLocData::SourceLoc intermediate;
- decoder.decode(intermediate);
-
- if (_sourceLocReader)
- {
- auto sourceLoc = _sourceLocReader->getSourceLoc(intermediate);
- value = sourceLoc;
- }
- }
+ deferSerializeObjectContents(serializer, node);
}
+}
- template<typename T>
- void decodeValue(T*& ptr, Decoder& decoder)
- {
- if (decoder.decodeNull())
- ptr = nullptr;
- else
- decodePtr(ptr, decoder, (T*)nullptr);
- }
+//
+// AST{Encoding|Decoding}Context::handleASTNodeContents()
+//
- template<typename T>
- void decodeValue(RefPtr<T>& ptr, Decoder& decoder)
- {
- if (decoder.decodeNull())
- ptr = nullptr;
- else
- {
- // Hi Future Tess,
- //
- // The next step here is decoding logic for `WitnessTable`s.
- //
+void ASTEncodingContext::handleASTNodeContents(NodeBase* node)
+{
+ ASTSerializer serializer(this);
+ serializeASTNodeContents(serializer, node);
+}
- decodePtr(*ptr.writeRef(), decoder, (T*)nullptr);
- }
- }
+void ASTDecodingContext::handleASTNodeContents(NodeBase* node)
+{
+ ASTSerializer serializer(this);
+ serializeASTNodeContents(serializer, node);
- void decodeValue(Modifiers& modifiers, Decoder& decoder)
- {
- Modifier** link = &modifiers.first;
+ _cleanUpASTNode(node);
+}
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- Modifier* modifier = nullptr;
- decode(modifier, decoder);
+//
+// AST{Encoding|Decoding}Context::_{write|read}ImportedModule()
+//
- *link = modifier;
- link = &modifier->next;
- }
- }
+void ASTEncodingContext::_writeImportedModule(ModuleDecl* moduleDecl)
+{
+ ASTNodeType type = _getAsASTNodeType(PseudoASTNodeType::ImportedModule);
+ auto moduleName = moduleDecl->getName();
- template<typename T, int N>
- void decodeValue(ShortList<T, N>& array, Decoder& decoder)
- {
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- T element;
- decode(element, decoder);
- array.add(element);
- }
- }
+ ASTSerializer serializer(this);
+ serialize(serializer, type);
+ serialize(serializer, moduleName);
+}
+ModuleDecl* ASTDecodingContext::_readImportedModule()
+{
+ ASTSerializer serializer(this);
- template<typename T>
- void decode(List<T>& array, Decoder& decoder)
+ Name* moduleName = nullptr;
+ serialize(serializer, moduleName);
+ auto module = _linkage->findOrImportModule(moduleName, _requestingSourceLoc, _sink);
+ if (!module)
{
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- T element;
- decode(element, decoder);
- array.add(element);
- }
+ SLANG_ABORT_COMPILATION("failed to load an imported module during AST deserialization");
}
+ return module->getModuleDecl();
+}
- template<typename T, size_t N>
- void decode(T (&array)[N], Decoder& decoder)
- {
- Decoder::WithArray withArray(decoder);
- for (auto& element : array)
- {
- decode(element, decoder);
- }
- }
+//
+// AST{Encoding|Decoding}Context::_{write|read}ImportedModule()
+//
- template<typename K, typename V>
- void decode(OrderedDictionary<K, V>& dictionary, Decoder& decoder)
- {
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- Decoder::WithKeyValuePair withPair(decoder);
+void ASTEncodingContext::_writeImportedDecl(Decl* decl, ModuleDecl* importedFromModuleDecl)
+{
+ ASTNodeType type = _getAsASTNodeType(PseudoASTNodeType::ImportedDecl);
+ auto mangledName = getMangledName(getCurrentASTBuilder(), decl);
- K key;
- V value;
- decode(key, decoder);
- decode(value, decoder);
+ ASTSerializer serializer(this);
+ serialize(serializer, type);
+ serialize(serializer, importedFromModuleDecl);
+ serialize(serializer, mangledName);
+}
- dictionary.add(key, value);
- }
- }
+NodeBase* ASTDecodingContext::_readImportedDecl()
+{
+ ASTSerializer serializer(this);
- template<typename K, typename V>
- void decode(Dictionary<K, V>& dictionary, Decoder& decoder)
- {
- Decoder::WithArray withArray(decoder);
- while (decoder.hasElements())
- {
- Decoder::WithKeyValuePair withPair(decoder);
+ ModuleDecl* importedFromModuleDecl = nullptr;
+ String mangledName;
- K key;
- V value;
- decode(key, decoder);
- decode(value, decoder);
+ serialize(serializer, importedFromModuleDecl);
+ serialize(serializer, mangledName);
- dictionary.add(key, value);
- }
+ auto importedFromModule = importedFromModuleDecl->module;
+ if (!importedFromModule)
+ {
+ return nullptr;
}
- template<typename T>
- void decode(T& outValue, Decoder& decoder)
+ auto importedDecl =
+ importedFromModule->findExportFromMangledName(mangledName.getUnownedSlice());
+ if (!importedDecl)
{
- decodeValue(outValue, decoder);
+ SLANG_ABORT_COMPILATION(
+ "failed to load an imported declaration during AST deserialization");
}
+ return importedDecl;
+}
-#if 0 // FIDDLE TEMPLATE:
-%for _,T in ipairs(Slang.NodeBase.subclasses) do
- void _decodeDataOf($T* obj, Decoder& decoder)
- {
-% if T.directSuperClass then
- _decodeDataOf(static_cast<$(T.directSuperClass)*>(obj), decoder);
-% end
-% for _,f in ipairs(T.directFields) do
- decode(obj->$f, decoder);
-% end
- }
-%end
-#else // FIDDLE OUTPUT:
-#define FIDDLE_GENERATED_OUTPUT_ID 1
-#include "slang-serialize-ast.cpp.fiddle"
-#endif // FIDDLE END
-};
+//
+// {write|read}SerializedModuleAST()
+//
+
+void writeSerializedModuleAST(
+ RIFF::BuildCursor& cursor,
+ ModuleDecl* moduleDecl,
+ SerialSourceLocWriter* sourceLocWriter)
+{
+ // TODO: we might want to have a more careful pass here,
+ // where we only encode the public declarations.
+
+ ASTEncodingContext context(cursor, moduleDecl, sourceLocWriter);
+ serialize(ASTSerializer(&context), moduleDecl);
+}
ModuleDecl* readSerializedModuleAST(
Linkage* linkage,
@@ -1546,9 +919,10 @@ ModuleDecl* readSerializedModuleAST(
{
ASTDecodingContext
context(linkage, astBuilder, sink, chunk, sourceLocReader, requestingSourceLoc);
- context.decodeAll();
- auto node = context.getDeclByID(0);
- auto moduleDecl = as<ModuleDecl>(node);
+
+ ModuleDecl* moduleDecl = nullptr;
+ serialize(ASTSerializer(&context), moduleDecl);
return moduleDecl;
}
+
} // namespace Slang
diff --git a/source/slang/slang-serialize-ast.h b/source/slang/slang-serialize-ast.h
index 45c799e9c..86ba6e772 100644
--- a/source/slang/slang-serialize-ast.h
+++ b/source/slang/slang-serialize-ast.h
@@ -11,8 +11,10 @@
namespace Slang
{
+class Linkage;
+
void writeSerializedModuleAST(
- Encoder* encoder,
+ RIFF::BuildCursor& cursor,
ModuleDecl* moduleDecl,
SerialSourceLocWriter* sourceLocWriter);
diff --git a/source/slang/slang-serialize-container.cpp b/source/slang/slang-serialize-container.cpp
index 665775dc5..ba8c74ca4 100644
--- a/source/slang/slang-serialize-container.cpp
+++ b/source/slang/slang-serialize-container.cpp
@@ -26,7 +26,7 @@ private:
RefPtr<SerialSourceLocWriter> _sourceLocWriter;
RIFF::Builder _riff;
- Encoder _encoder;
+ RIFF::BuildCursor _cursor;
public:
ModuleEncodingContext(SerialContainerUtil::WriteOptions const& options, Stream* stream)
@@ -37,12 +37,12 @@ public:
_sourceLocWriter = new SerialSourceLocWriter(options.sourceManager);
}
- _encoder = Encoder(_riff);
+ _cursor = RIFF::BuildCursor(_riff);
}
~ModuleEncodingContext()
{
- _encoder = Encoder(_riff.getRootChunk());
+ _cursor = RIFF::BuildCursor(_riff.getRootChunk());
encodeFinalPieces();
_riff.writeTo(_stream);
}
@@ -53,7 +53,7 @@ public:
// is simply a matter of encoding the module for each
// of the translation units that got compiled.
//
- Encoder::WithKeyValuePair withArray(&_encoder, SerialBinary::kModuleListFourCc);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, SerialBinary::kModuleListFourCc);
for (TranslationUnitRequest* translationUnit : frontEndReq->translationUnits)
{
SLANG_RETURN_ON_FAIL(encode(translationUnit->module));
@@ -63,14 +63,14 @@ public:
SlangResult encode(FrontEndCompileRequest* frontEndReq)
{
- Encoder::WithObject withObject(&_encoder, SerialBinary::kContainerFourCc);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, SerialBinary::kContainerFourCc);
SLANG_RETURN_ON_FAIL(encodeModuleList(frontEndReq));
return SLANG_OK;
}
SlangResult encode(EndToEndCompileRequest* request)
{
- Encoder::WithObject withObject(&_encoder, SerialBinary::kContainerFourCc);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, SerialBinary::kContainerFourCc);
// Encoding an end-to-end compile request starts with the same
// work as for a front-end request: we encode each of
@@ -99,7 +99,7 @@ public:
auto sink = request->getSink();
auto program = request->getSpecializedGlobalAndEntryPointsComponentType();
{
- Encoder::WithArray withArray(&_encoder);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, SerialBinary::kArrayFourCC);
for (auto target : linkage->targets)
{
@@ -112,7 +112,7 @@ public:
// and we need to encode information about each of them.
//
{
- Encoder::WithArray withArray(&_encoder, SerialBinary::kEntryPointListFourCc);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, SerialBinary::kEntryPointListFourCc);
auto entryPointCount = program->getEntryPointCount();
for (Index ii = 0; ii < entryPointCount; ++ii)
@@ -142,34 +142,41 @@ public:
SLANG_RETURN_ON_FAIL(
writer.write(irModule, _sourceLocWriter, _options.optionFlags, &serialData));
- SLANG_RETURN_ON_FAIL(IRSerialWriter::writeTo(serialData, _encoder));
+ SLANG_RETURN_ON_FAIL(IRSerialWriter::writeTo(serialData, _cursor));
return SLANG_OK;
}
- void encode(Name* name) { _encoder.encode(name->text); }
+ void encodeData(void const* data, size_t size, FourCC type)
+ {
+ _cursor.addDataChunk(type, data, size);
+ }
- void encode(String const& value) { _encoder.encode(value); }
+ void encode(String const& value, FourCC type = SerialBinary::kStringFourCC)
+ {
+ encodeData(value.getBuffer(), value.getLength(), type);
+ }
- void encode(uint32_t value) { _encoder.encode(UInt(value)); }
+ void encode(Name* name, FourCC type = SerialBinary::kNameFourCC) { encode(name->text, type); }
- void encodeData(void const* data, size_t size) { _encoder.encodeData(data, size); }
+
+ void encode(uint32_t value, FourCC type = SerialBinary::kUInt32FourCC)
+ {
+ encodeData(&value, sizeof(value), type);
+ }
SlangResult encode(EntryPoint* entryPoint, String const& entryPointMangledName)
{
- Encoder::WithObject withObject(&_encoder, SerialBinary::kEntryPointFourCc);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, SerialBinary::kEntryPointFourCc);
{
- Encoder::WithObject withProperty(&_encoder, SerialBinary::kNameFourCC);
- encode(entryPoint->getName());
+ encode(entryPoint->getName(), SerialBinary::kNameFourCC);
}
{
- Encoder::WithObject withProperty(&_encoder, SerialBinary::kProfileFourCC);
- encode(entryPoint->getProfile().raw);
+ encode(entryPoint->getProfile().raw, SerialBinary::kProfileFourCC);
}
{
- Encoder::WithObject withProperty(&_encoder, SerialBinary::kMangledNameFourCC);
- encode(entryPointMangledName);
+ encode(entryPointMangledName, SerialBinary::kMangledNameFourCC);
}
return SLANG_OK;
@@ -181,7 +188,7 @@ public:
if (!(_options.optionFlags & (SerialOptionFlag::IRModule | SerialOptionFlag::ASTModule)))
return SLANG_OK;
- Encoder::WithObject withModule(&_encoder, SerialBinary::kModuleFourCC);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, SerialBinary::kModuleFourCC);
// The first piece that we write for a module is its header.
// The header is intended to provide information that can be
@@ -194,15 +201,14 @@ public:
// sense to serialize it separately from all the rest.
//
{
- Encoder::WithObject withProperty(&_encoder, SerialBinary::kNameFourCC);
- _encoder.encodeString(module->getNameObj()->text);
+ encode(module->getNameObj(), SerialBinary::kNameFourCC);
}
// The header includes a digest of all the compile options and
// the files that the compiled result depended on.
//
auto digest = module->computeDigest();
- _encoder.encodeData(PropertyKeys<Module>::Digest, digest.data, sizeof(digest.data));
+ _cursor.addDataChunk(PropertyKeys<Module>::Digest, digest.data, sizeof(digest.data));
// The header includes an array of the paths of all of the
// files that the compiled result depended on.
@@ -221,7 +227,7 @@ public:
IRSerialWriter writer;
SLANG_RETURN_ON_FAIL(
writer.write(irModule, _sourceLocWriter, _options.optionFlags, &serialData));
- SLANG_RETURN_ON_FAIL(IRSerialWriter::writeTo(serialData, _encoder));
+ SLANG_RETURN_ON_FAIL(IRSerialWriter::writeTo(serialData, _cursor));
}
}
@@ -232,9 +238,8 @@ public:
{
if (auto moduleDecl = module->getModuleDecl())
{
- Encoder::WithKeyValuePair withKey(&_encoder, PropertyKeys<Module>::ASTModule);
-
- writeSerializedModuleAST(&_encoder, moduleDecl, _sourceLocWriter);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, PropertyKeys<Module>::ASTModule);
+ writeSerializedModuleAST(_cursor, moduleDecl, _sourceLocWriter);
}
}
@@ -243,7 +248,7 @@ public:
SlangResult encodeModuleDependencyPaths(Module* module)
{
- Encoder::WithObject withProperty(&_encoder, PropertyKeys<Module>::FileDependencies);
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, PropertyKeys<Module>::FileDependencies);
// TODO(tfoley): This is some of the most complicated logic
// in the encoding system, because it tries to translate
@@ -317,7 +322,6 @@ public:
}
Path::getCanonical(linkageRoot, linkageRoot);
- Encoder::WithArray withArray(&_encoder);
for (auto file : fileDependencies)
{
if (file->getPathInfo().hasFoundPath())
@@ -334,26 +338,26 @@ public:
auto relativeModulePath =
Path::getRelativePath(linkageRoot, canonicalModulePath);
- _encoder.encodeString(relativeModulePath);
+ encode(relativeModulePath);
}
else
{
// For all other dependnet files, store them as relative paths with respect
// to the module's path.
canonicalFilePath = Path::getRelativePath(moduleDir, canonicalFilePath);
- _encoder.encodeString(canonicalFilePath);
+ encode(canonicalFilePath);
}
}
else
{
// If the module is coming from string instead of an actual file, store it as
// is.
- _encoder.encodeString(canonicalModulePath);
+ encode(canonicalModulePath);
}
}
else
{
- _encoder.encodeString(file->getPathInfo().getMostUniqueIdentity());
+ encode(file->getPathInfo().getMostUniqueIdentity());
}
}
@@ -369,18 +373,19 @@ public:
SerialSourceLocData debugData;
_sourceLocWriter->write(&debugData);
- debugData.writeTo(_encoder);
+ debugData.writeTo(_cursor);
}
// Write the container string table
if (_containerStringPool.getAdded().getCount() > 0)
{
- Encoder::WithKeyValuePair withKey(&_encoder, SerialBinary::kStringTableFourCc);
-
List<char> encodedTable;
SerialStringTableUtil::encodeStringTable(_containerStringPool, encodedTable);
- _encoder.encodeData(encodedTable.getBuffer(), encodedTable.getCount());
+ _cursor.addDataChunk(
+ SerialBinary::kStringTableFourCc,
+ encodedTable.getBuffer(),
+ encodedTable.getCount());
}
return SLANG_OK;
@@ -425,14 +430,15 @@ public:
String StringChunk::getValue() const
{
- return Decoder(this).decodeString();
+ return String(UnownedStringSlice((char const*)getPayload(), getPayloadSize()));
}
RIFF::ChunkList<StringChunk> ModuleChunk::getFileDependencies() const
{
- Decoder decoder(this);
- Decoder::WithProperty withProperty(decoder, PropertyKeys<Module>::FileDependencies);
- return as<RIFF::ListChunk>(decoder.getCurrentChunk())->getChildren().cast<StringChunk>();
+ auto found = findListChunk(PropertyKeys<Module>::FileDependencies);
+ if (!found)
+ return RIFF::ChunkList<StringChunk>();
+ return found->getChildren().cast<StringChunk>();
}
ModuleChunk const* ModuleChunk::find(RIFF::ListChunk const* baseChunk)
@@ -453,14 +459,12 @@ SHA1::Digest ModuleChunk::getDigest() const
String ModuleChunk::getName() const
{
- // TODO(tfoley): This kind of logic needs a way
- // to be greatly simplified, so that we don't
- // have to express such complicated logic for
- // simply extracting a single string property...
- //
- Decoder decoder(this);
- Decoder::WithProperty withProperty(decoder, SerialBinary::kNameFourCC);
- return decoder.decodeString();
+ auto found = findDataChunk(SerialBinary::kNameFourCC);
+ if (!found)
+ {
+ SLANG_UNEXPECTED("module chunk had no name");
+ }
+ return static_cast<StringChunk const*>(found)->getValue();
}
@@ -506,41 +510,32 @@ RIFF::ChunkList<EntryPointChunk> ContainerChunk::getEntryPoints() const
String EntryPointChunk::getMangledName() const
{
- // TODO(tfoley): This kind of logic needs a way
- // to be greatly simplified, so that we don't
- // have to express such complicated logic for
- // simply extracting a single string property...
- //
- Decoder decoder(this);
- Decoder::WithProperty withProperty(decoder, SerialBinary::kMangledNameFourCC);
- return decoder.decodeString();
+ auto found = findDataChunk(SerialBinary::kMangledNameFourCC);
+ if (!found)
+ {
+ SLANG_UNEXPECTED("entry point chunk had no mangled name");
+ }
+ return static_cast<StringChunk const*>(found)->getValue();
}
String EntryPointChunk::getName() const
{
- // TODO(tfoley): This kind of logic needs a way
- // to be greatly simplified, so that we don't
- // have to express such complicated logic for
- // simply extracting a single string property...
- //
- Decoder decoder(this);
- Decoder::WithProperty withProperty(decoder, SerialBinary::kNameFourCC);
- return decoder.decodeString();
+ auto found = findDataChunk(SerialBinary::kNameFourCC);
+ if (!found)
+ {
+ SLANG_UNEXPECTED("entry point chunk had no name");
+ }
+ return static_cast<StringChunk const*>(found)->getValue();
}
Profile EntryPointChunk::getProfile() const
{
- // TODO(tfoley): This kind of logic needs a way
- // to be greatly simplified, so that we don't
- // have to express such complicated logic for
- // simply extracting a single string property...
- //
- Decoder decoder(this);
- Decoder::WithProperty withProperty(decoder, SerialBinary::kProfileFourCC);
-
- Profile::RawVal rawVal;
- decoder.decode(rawVal);
-
+ auto found = findDataChunk(SerialBinary::kProfileFourCC);
+ if (!found)
+ {
+ SLANG_UNEXPECTED("entry point chunk had no profile");
+ }
+ auto rawVal = found->readPayloadAs<Profile::RawVal>();
return Profile(rawVal);
}
diff --git a/source/slang/slang-serialize-riff.cpp b/source/slang/slang-serialize-riff.cpp
new file mode 100644
index 000000000..01b39e825
--- /dev/null
+++ b/source/slang/slang-serialize-riff.cpp
@@ -0,0 +1,897 @@
+// slang-serialize-riff.cpp
+#include "slang-serialize-riff.h"
+
+namespace Slang
+{
+
+//
+// RIFFSerialWriter
+//
+
+RIFFSerialWriter::RIFFSerialWriter(RIFF::ChunkBuilder* chunk, FourCC type)
+ : _cursor(chunk)
+{
+ _initialize(type);
+}
+
+RIFFSerialWriter::RIFFSerialWriter(RIFF::Builder& riff, FourCC type)
+ : _cursor(riff)
+{
+ _initialize(type);
+}
+
+RIFFSerialWriter::~RIFFSerialWriter()
+{
+ // We need to flush any pending operations to
+ // write objects into the object definition list chunk.
+ //
+ _flush();
+}
+
+SerializationMode RIFFSerialWriter::getMode()
+{
+ return SerializationMode::Write;
+}
+
+void RIFFSerialWriter::handleBool(bool& value)
+{
+ _cursor.addDataChunk(value ? RIFFSerial::kTrueFourCC : RIFFSerial::kFalseFourCC, nullptr, 0);
+}
+
+void RIFFSerialWriter::handleInt8(int8_t& value)
+{
+ _writeInt(value);
+}
+
+void RIFFSerialWriter::handleInt16(int16_t& value)
+{
+ _writeInt(value);
+}
+
+void RIFFSerialWriter::handleInt32(Int32& value)
+{
+ _writeInt(value);
+}
+
+void RIFFSerialWriter::handleInt64(Int64& value)
+{
+ _writeInt(value);
+}
+
+void RIFFSerialWriter::handleUInt8(uint8_t& value)
+{
+ _writeUInt(value);
+}
+
+void RIFFSerialWriter::handleUInt16(uint16_t& value)
+{
+ _writeUInt(value);
+}
+
+void RIFFSerialWriter::handleUInt32(UInt32& value)
+{
+ _writeUInt(value);
+}
+
+void RIFFSerialWriter::handleUInt64(UInt64& value)
+{
+ _writeUInt(value);
+}
+
+void RIFFSerialWriter::handleFloat32(float& value)
+{
+ _writeFloat(value);
+}
+
+void RIFFSerialWriter::handleFloat64(double& value)
+{
+ _writeFloat(value);
+}
+
+void RIFFSerialWriter::handleString(String& value)
+{
+ _cursor.addDataChunk(RIFFSerial::kStringFourCC, value.getBuffer(), value.getLength());
+}
+
+void RIFFSerialWriter::_writeInt(Int64 value)
+{
+ // We pick a 32-bit representation if it can
+ // faithfully represent the value, and a 64-bit
+ // representation otherwise.
+ //
+ if (Int32(value) == value)
+ {
+ auto v = Int32(value);
+ _cursor.addDataChunk(RIFFSerial::kInt32FourCC, &v, sizeof(v));
+ }
+ else
+ {
+ _cursor.addDataChunk(RIFFSerial::kInt64FourCC, &value, sizeof(value));
+ }
+}
+
+void RIFFSerialWriter::_writeUInt(UInt64 value)
+{
+ // We pick a 32-bit representation if it can
+ // faithfully represent the value, and a 64-bit
+ // representation otherwise.
+ //
+ if (UInt32(value) == value)
+ {
+ auto v = UInt32(value);
+ _cursor.addDataChunk(RIFFSerial::kUInt32FourCC, &v, sizeof(v));
+ }
+ else
+ {
+ _cursor.addDataChunk(RIFFSerial::kUInt64FourCC, &value, sizeof(value));
+ }
+}
+
+void RIFFSerialWriter::_writeFloat(double value)
+{
+ // We pick a 32-bit representation if it can
+ // faithfully represent the value, and a 64-bit
+ // representation otherwise.
+ //
+ if (float(value) == value)
+ {
+ auto v = float(value);
+ _cursor.addDataChunk(RIFFSerial::kFloat32FourCC, &v, sizeof(v));
+ }
+ else
+ {
+ _cursor.addDataChunk(RIFFSerial::kFloat64FourCC, &value, sizeof(value));
+ }
+}
+
+void RIFFSerialWriter::beginArray()
+{
+ _cursor.beginListChunk(RIFFSerial::kArrayFourCC);
+}
+
+void RIFFSerialWriter::endArray()
+{
+ _cursor.endChunk();
+}
+
+void RIFFSerialWriter::beginDictionary()
+{
+ _cursor.beginListChunk(RIFFSerial::kDictionaryFourCC);
+}
+
+void RIFFSerialWriter::endDictionary()
+{
+ _cursor.endChunk();
+}
+
+bool RIFFSerialWriter::hasElements()
+{
+ return false;
+}
+
+void RIFFSerialWriter::beginStruct()
+{
+ _cursor.beginListChunk(RIFFSerial::kStructFourCC);
+}
+
+void RIFFSerialWriter::handleFieldKey(char const* name, Int index)
+{
+ // For now we are ignoring field keys, and treating
+ // structs as basically equivalent to tuples.
+ SLANG_UNUSED(name);
+ SLANG_UNUSED(index);
+}
+
+void RIFFSerialWriter::endStruct()
+{
+ _cursor.endChunk();
+}
+
+void RIFFSerialWriter::beginTuple()
+{
+ _cursor.beginListChunk(RIFFSerial::kTupleFourCC);
+}
+
+void RIFFSerialWriter::endTuple()
+{
+ _cursor.endChunk();
+}
+
+void RIFFSerialWriter::beginOptional()
+{
+ _cursor.beginListChunk(RIFFSerial::kOptionalFourCC);
+}
+
+void RIFFSerialWriter::endOptional()
+{
+ _cursor.endChunk();
+}
+
+void RIFFSerialWriter::handleSharedPtr(void*& value, Callback callback, void* userData)
+{
+ // Because we are writing, we only care about the
+ // pointer that is already present in `value`.
+ //
+ void* ptr = value;
+
+ // The first special case we check for is a null pointer,
+ // which we can serialize as an inline value.
+ //
+ if (ptr == nullptr)
+ {
+ _cursor.addDataChunk(RIFFSerial::kNullFourCC, nullptr, 0);
+ return;
+ }
+
+ // Next, we check to see if we have encountered this
+ // pointer before, in which case we've already allocated
+ // an index for it in the object definition list, and
+ // we can simply write a reference to that index.
+ //
+ if (auto found = _mapPtrToObjectIndex.tryGetValue(ptr))
+ {
+ auto objectIndex = *found;
+ _writeObjectReference(objectIndex);
+ return;
+ }
+
+ // If we have a non-null pointer that we haven't seen
+ // before, then we will allocate a new entry in the
+ // object definition list, and the pointer itself
+ // will be written as a reference to that entry.
+ //
+ auto objectIndex = ObjectIndex(_objects.getCount());
+ _mapPtrToObjectIndex.add(ptr, objectIndex);
+ _writeObjectReference(objectIndex);
+
+ // At this point we've correctly written the *reference*
+ // to the object (and will be able to write further
+ // references later if we see an identical pointer),
+ // but we also need to make sure that the *definition*
+ // of the object gets written into the object definition
+ // list chunk.
+ //
+ // The `callback` that was passed in can be used to
+ // write out the members of the object, but if we
+ // simply invoked it here and now we would be at risk
+ // of introducing unbounded recursion in cases where
+ // the object graph contains very long pointer chains.
+ //
+ // (Note that we are not at risk of *infinite* recursion,
+ // because we have already cached the index for the
+ // object into `_mapPtrToObjectIndex`)
+ //
+ // We will simply add an entry to our `_objects` array
+ // to represent the to-be-written object, and store
+ // the pointer and callback there so that we can write
+ // everything out later, in `_flush()`.
+ //
+ ObjectInfo objectInfo;
+ objectInfo.ptr = ptr;
+ objectInfo.callback = callback;
+ objectInfo.userData = userData;
+ _objects.add(objectInfo);
+}
+
+void RIFFSerialWriter::handleUniquePtr(void*& value, Callback callback, void* userData)
+{
+ // We treat all pointers as shared pointers, because there isn't really
+ // an optimized representation we would want to use for the unique case.
+ //
+ handleSharedPtr(value, callback, userData);
+}
+
+void RIFFSerialWriter::handleDeferredObjectContents(
+ void* valuePtr,
+ Callback callback,
+ void* userData)
+{
+ // Because we are already deferring writing of the *entirety* of
+ // an object's members as part of how `handleSharedPtr()` works,
+ // we don't need to implement deferral at this juncture.
+ //
+ // (In practice the `handleDeferredObjectContents()` operation is
+ // more for the benefit of reading than writing).
+ //
+ callback(valuePtr, userData);
+}
+
+void RIFFSerialWriter::_writeObjectReference(ObjectIndex index)
+{
+ _cursor.addDataChunk(RIFFSerial::kObjectReferenceFourCC, &index, sizeof(index));
+}
+
+void RIFFSerialWriter::_initialize(FourCC type)
+{
+ // The entire content that we write will be nested
+ // in a single list chunk, with the type that was
+ // passed in.
+ //
+ _cursor.beginListChunk(type);
+
+ // The first child chunk needs to be the object
+ // definition list chunk, so we create it up front.
+ //
+ _objectDefinitionListChunk = _cursor.addListChunk(RIFFSerial::kObjectDefinitionListFourCC);
+}
+
+void RIFFSerialWriter::_flush()
+{
+ // At this point we might have zero or more object
+ // waiting to be written into the object definition list
+ // chunk, and we need to make sure that they all
+ // get a chance to write their content out.
+ //
+ _cursor.setCurrentChunk(_objectDefinitionListChunk);
+
+ // Note that we do *not* compute `_objects.getCount()` outside
+ // of the loop here, because writing out one object definition
+ // could cause other objects to be referenced, which could
+ // in turn add more entries to `_objects` that need to be
+ // written out.
+ //
+ while (_writtenObjectDefinitionCount < _objects.getCount())
+ {
+ auto objectIndex = _writtenObjectDefinitionCount++;
+ auto objectInfo = _objects[objectIndex];
+
+ // We shouldn't ever be putting a null pointer into the
+ // object definition list; there is logic in `handleSharedPtr()`
+ // that explicitly checks for a null pointer and does an
+ // early-exit in that case.
+ //
+ SLANG_ASSERT(objectInfo.ptr);
+
+ // The callback that was passed into `handleSharedPtr()` should
+ // be able to write out the value of the pointed-to object.
+ //
+ // Note that we are passing the *address* of `objectInfo.ptr`
+ // and not just its *value*, because this callback is used
+ // for both reading and writing, and in the reading case it
+ // needs to be invoked on a pointer-pointer (e.g., a `T**` when
+ // serializing an object pointer `T*`) so that the callee
+ // can set the pointed-to pointer to whatever object it
+ // allocates or finds.
+ //
+ objectInfo.callback(&objectInfo.ptr, objectInfo.userData);
+
+ // TODO(tfoley): There is an important invariant here that
+ // the callback had better only write *one* value, but
+ // that is not currently being enforced.
+ }
+}
+
+//
+// RIFFSerialReader
+//
+
+RIFFSerialReader::RIFFSerialReader(RIFF::Chunk const* chunk, FourCC type)
+ : _cursor(chunk)
+{
+ _initialize(type);
+}
+
+RIFFSerialReader::~RIFFSerialReader()
+{
+ _flush();
+}
+
+SerializationMode RIFFSerialReader::getMode()
+{
+ return SerializationMode::Read;
+}
+
+void RIFFSerialReader::handleBool(bool& value)
+{
+ switch (_peekChunkType())
+ {
+ case RIFFSerial::kTrueFourCC:
+ _advanceCursor();
+ value = true;
+ break;
+
+ case RIFFSerial::kFalseFourCC:
+ _advanceCursor();
+ value = false;
+ break;
+
+ default:
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ break;
+ }
+}
+
+void RIFFSerialReader::handleInt8(int8_t& value)
+{
+ value = int8_t(_readInt());
+}
+
+void RIFFSerialReader::handleInt16(int16_t& value)
+{
+ value = int16_t(_readInt());
+}
+
+void RIFFSerialReader::handleInt32(Int32& value)
+{
+ value = Int32(_readInt());
+}
+
+void RIFFSerialReader::handleInt64(Int64& value)
+{
+ value = Int64(_readInt());
+}
+
+void RIFFSerialReader::handleUInt8(uint8_t& value)
+{
+ value = uint8_t(_readUInt());
+}
+
+void RIFFSerialReader::handleUInt16(uint16_t& value)
+{
+ value = uint16_t(_readUInt());
+}
+
+void RIFFSerialReader::handleUInt32(UInt32& value)
+{
+ value = UInt32(_readUInt());
+}
+
+void RIFFSerialReader::handleUInt64(UInt64& value)
+{
+ value = UInt64(_readUInt());
+}
+
+void RIFFSerialReader::handleFloat32(float& value)
+{
+ value = float(_readFloat());
+}
+
+void RIFFSerialReader::handleFloat64(double& value)
+{
+ value = double(_readFloat());
+}
+
+void RIFFSerialReader::handleString(String& value)
+{
+ if (_peekChunkType() != RIFFSerial::kStringFourCC)
+ {
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ return;
+ }
+
+ auto dataChunk = as<RIFF::DataChunk>(_cursor);
+ if (!dataChunk)
+ {
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ return;
+ }
+
+ auto size = dataChunk->getPayloadSize();
+
+ value = String();
+ value.appendRepeatedChar(' ', size);
+ dataChunk->writePayloadInto((char*)value.getBuffer(), size);
+
+ _advanceCursor();
+}
+
+void RIFFSerialReader::beginArray()
+{
+ _beginListChunk(RIFFSerial::kArrayFourCC);
+}
+
+void RIFFSerialReader::endArray()
+{
+ _endListChunk();
+}
+
+
+void RIFFSerialReader::beginDictionary()
+{
+ _beginListChunk(RIFFSerial::kDictionaryFourCC);
+}
+
+void RIFFSerialReader::endDictionary()
+{
+ _endListChunk();
+}
+
+bool RIFFSerialReader::hasElements()
+{
+ return _cursor.get() != nullptr;
+}
+
+void RIFFSerialReader::beginStruct()
+{
+ _beginListChunk(RIFFSerial::kStructFourCC);
+}
+
+void RIFFSerialReader::handleFieldKey(char const* name, Int index)
+{
+ // For now we are ignoring field keys, and treating
+ // structs as basically equivalent to tuples.
+ SLANG_UNUSED(name);
+ SLANG_UNUSED(index);
+}
+
+void RIFFSerialReader::endStruct()
+{
+ _endListChunk();
+}
+
+void RIFFSerialReader::beginTuple()
+{
+ _beginListChunk(RIFFSerial::kTupleFourCC);
+}
+
+void RIFFSerialReader::endTuple()
+{
+ _endListChunk();
+}
+
+void RIFFSerialReader::beginOptional()
+{
+ _beginListChunk(RIFFSerial::kOptionalFourCC);
+}
+
+void RIFFSerialReader::endOptional()
+{
+ _endListChunk();
+}
+
+RIFFSerialReader::ObjectIndex RIFFSerialReader::_readObjectReference()
+{
+ if (_peekChunkType() != RIFFSerial::kObjectReferenceFourCC)
+ {
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ UNREACHABLE_RETURN(false);
+ }
+
+ auto objectIndex = _readDataChunk<ObjectIndex>();
+ SLANG_ASSERT(objectIndex >= 0 && objectIndex < _objects.getCount());
+ return objectIndex;
+}
+
+void RIFFSerialReader::handleSharedPtr(void*& value, Callback callback, void* userData)
+{
+ // The logic here largely mirrors what appears in
+ // `RIFFSerialWriter::handleSharedPtr`.
+ //
+ // We first check for an explicitly written null pointer.
+ // If we find one our work is very easy.
+ //
+ if (_peekChunkType() == RIFFSerial::kNullFourCC)
+ {
+ _advanceCursor();
+ value = nullptr;
+ return;
+ }
+
+ // Otherwise, we expect to find a reference to
+ // an object index.
+ //
+ // Note that `_readObjectReference()` already asserts
+ // that the index is in-bounds, so we don't repeat
+ // that test here.
+ //
+ auto objectIndex = _readObjectReference();
+
+ // Now we need to check if we've previously read in
+ // a reference to the same object.
+ //
+ auto& objectInfo = _objects[objectIndex];
+ if (objectInfo.state != ObjectState::Unread)
+ {
+ // We've seen this object before, although it
+ // is still possible that we are in the middle
+ // of reading it as part of an invocation
+ // of `handleSharedPtr()` further up the call
+ // stack.
+ //
+ // If a non-nullpointer value has already been
+ // written into the `objectInfo`, then that means
+ // the callback that was run for the prior (or
+ // in-flight) read operation has already allocated
+ // or found an object and written it out.
+ // In that case we will trust the value.
+ //
+ if (objectInfo.ptr == nullptr)
+ {
+ // It is possible that the pointer is null because
+ // the callback that was invoked explicitly *chose*
+ // to yield a null pointer (perhaps the application
+ // is choosing not to deserialize some optional
+ // piece of state).
+ //
+ // However, if there is still a callback in-flight
+ // to read this object, and the pointer is null,
+ // then we have reached a circular reference,
+ // and need to signal an error.
+ //
+ if (objectInfo.state == ObjectState::ReadingInProgress)
+ {
+ SLANG_UNEXPECTED("circularity detected in RIFF deserialization");
+ }
+ }
+ value = objectInfo.ptr;
+ return;
+ }
+
+ // At this point we are reading a reference to an
+ // object index that has not yet been read at all.
+ //
+ SLANG_ASSERT(objectInfo.state == ObjectState::Unread);
+
+ // We cannot return from this function until we have
+ // stored a pointer into `value`, to represent the
+ // deserialized object.
+ //
+ // Thus we will set ourselves up to start reading
+ // from the relevant object definition, and invoke
+ // the callback that was passed in.
+ //
+ // Calling into user-defined serialization logic from
+ // within this function creates the possibility of
+ // unbounded/infinite recursion, so it is vital that
+ // the user is properly using `deferSerializeObjectContents()`
+ // to delay reading data that isn't immediately
+ // necessary.
+ //
+ // We will still set the `objectInfo.state` to reflect
+ // this in-flight operation so that we can detect
+ // a cirularity if one occurs at runtime.
+ //
+ objectInfo.state = ObjectState::ReadingInProgress;
+
+ // We save/restore the current cursor around
+ // the callback, because we need to be able
+ // to return to the current state to continue
+ // reading whatever comes after the pointer
+ // we were invoked to read.
+ //
+ _pushCursor();
+ _cursor = objectInfo.definitionChunk;
+
+ // Note that we are passing the address of `objectInfo.ptr`,
+ // and `objectInfo` is a reference to an element of the
+ // `_objects` array. Thus whenever the `callback` stores
+ // a pointer into that output parameter, the value it writes
+ // will automatically be visible to any subsequent calls
+ // to `handleSharedPtr()`, even if they occur before
+ // `callback` returns.
+ //
+ // Thus a "true" circularity can only occur if the callback
+ // recursively reads a reference to the same object again
+ // *before* it allocates the in-memory representation of
+ // that objects and stores a pointer to it into the output
+ // parameter.
+ //
+ callback(&objectInfo.ptr, userData);
+
+ _popCursor();
+
+ objectInfo.state = ObjectState::ReadingComplete;
+
+ value = objectInfo.ptr;
+}
+
+void RIFFSerialReader::handleUniquePtr(void*& value, Callback callback, void* userData)
+{
+ // We treat all pointers as shared pointers, because there isn't really
+ // an optimized representation we would want to use for the unique case.
+ //
+ handleSharedPtr(value, callback, userData);
+}
+
+void RIFFSerialReader::handleDeferredObjectContents(
+ void* valuePtr,
+ Callback callback,
+ void* userData)
+{
+ // Unlike the case in `RIFFSerialWriter::handleDeferredObjectContents()`,
+ // we very much *do* want to delay invoking the callback until later.
+ //
+ // There is a kind of symmetry going on, where the writer delays the
+ // callback passed to `handleSharedPtr()`, but *not* the callback
+ // passed to `handleDeferredObjectContents()`, while the reader
+ // does the opposite: immediately calls the callback in `handleSharedPtr()`
+ // but delays calling it here.
+
+ // We make sure to save the current `_cursor` value along with
+ // the arguments that will be passed into the callback, so that
+ // we can restore the reader to this state before invoking
+ // the callbak in `_flush()`.
+
+ DeferredAction deferredAction;
+ deferredAction.savedCursor = _cursor;
+ deferredAction.valuePtr = valuePtr;
+ deferredAction.callback = callback;
+ deferredAction.userData = userData;
+
+ _deferredActions.add(deferredAction);
+}
+
+void RIFFSerialReader::_initialize(FourCC type)
+{
+ // All of the content will have been serialized as a single RIFF
+ // list chunk (possibly a root chunk if this content comprises
+ // an entire file), with the given `type`.
+ //
+ _beginListChunk(type);
+
+ // The first child chunk should be the object definition list
+ // chunk, and we will proactively read through all of the
+ // entries in that chunk to build up the `_objects` array.
+ //
+ // This operation takes linear time in the number of serialized
+ // objects, independent of their size, because the RIFF chunk
+ // headers allow us to skip over the content of each of these
+ // object-definition chunks.
+ //
+ _beginListChunk(RIFFSerial::kObjectDefinitionListFourCC);
+ while (auto objectDefinitionChunk = _cursor.get())
+ {
+ ObjectInfo objectInfo;
+ objectInfo.definitionChunk = objectDefinitionChunk;
+ _objects.add(objectInfo);
+
+ _advanceCursor();
+ }
+ _endListChunk();
+}
+
+void RIFFSerialReader::_flush()
+{
+ // We need to flush any actions that were deferred
+ // and are still pending.
+ //
+ while (_deferredActions.getCount() != 0)
+ {
+ // TODO: For simplicity we are using the `_deferredActions`
+ // array as a stack (LIFO), but it would be good to
+ // check whether there is a menaingful difference in how
+ // large the array would need to grow for a FIFO vs. LIFO,
+ // and pick the better option.
+ //
+ auto deferredAction = _deferredActions.getLast();
+ _deferredActions.removeLast();
+
+ _cursor = deferredAction.savedCursor;
+ deferredAction.callback(deferredAction.valuePtr, deferredAction.userData);
+ }
+}
+
+FourCC RIFFSerialReader::_peekChunkType()
+{
+ auto chunk = _cursor.get();
+ if (!chunk)
+ return 0;
+ return chunk->getType();
+}
+
+Int64 RIFFSerialReader::_readInt()
+{
+ switch (_peekChunkType())
+ {
+ case RIFFSerial::kInt64FourCC:
+ return _readDataChunk<Int64>();
+ case RIFFSerial::kInt32FourCC:
+ return _readDataChunk<Int32>();
+
+ case RIFFSerial::kUInt32FourCC:
+ return _readDataChunk<UInt32>();
+
+ case RIFFSerial::kUInt64FourCC:
+ {
+ auto uintValue = _readDataChunk<UInt64>();
+ if (Int64(uintValue) < 0)
+ {
+ SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF");
+ }
+ return Int64(uintValue);
+ }
+
+ default:
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ UNREACHABLE_RETURN(0);
+ }
+}
+
+UInt64 RIFFSerialReader::_readUInt()
+{
+ switch (_peekChunkType())
+ {
+ case RIFFSerial::kUInt64FourCC:
+ return _readDataChunk<UInt64>();
+ case RIFFSerial::kUInt32FourCC:
+ return _readDataChunk<UInt32>();
+
+ case RIFFSerial::kInt32FourCC:
+ case RIFFSerial::kInt64FourCC:
+ {
+ auto intValue = _readInt();
+ if (intValue < 0)
+ {
+ SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF");
+ }
+ return UInt64(intValue);
+ }
+
+ default:
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ UNREACHABLE_RETURN(0);
+ }
+}
+
+double RIFFSerialReader::_readFloat()
+{
+ switch (_peekChunkType())
+ {
+ case RIFFSerial::kFloat32FourCC:
+ return _readDataChunk<float>();
+ case RIFFSerial::kFloat64FourCC:
+ return _readDataChunk<double>();
+
+ default:
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ UNREACHABLE_RETURN(0);
+ }
+}
+
+void RIFFSerialReader::_readDataChunk(void* outData, size_t dataSize)
+{
+ auto dataChunk = as<RIFF::DataChunk>(_cursor);
+ if (!dataChunk)
+ {
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ return;
+ }
+ auto size = dataChunk->getPayloadSize();
+ if (size < dataSize)
+ {
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ return;
+ }
+ dataChunk->writePayloadInto(outData, dataSize);
+ _advanceCursor();
+}
+
+
+void RIFFSerialReader::_beginListChunk(FourCC type)
+{
+ auto listChunk = as<RIFF::ListChunk>(_cursor);
+ if (!listChunk || listChunk->getType() != type)
+ {
+ SLANG_UNEXPECTED("invalid format in RIFF");
+ }
+
+ _advanceCursor();
+ _pushCursor();
+
+ _cursor = listChunk->getFirstChild();
+}
+
+void RIFFSerialReader::_endListChunk()
+{
+ _popCursor();
+}
+
+void RIFFSerialReader::_advanceCursor()
+{
+ _cursor = _cursor.getNextSibling();
+}
+
+void RIFFSerialReader::_pushCursor()
+{
+ _stack.add(_cursor);
+}
+
+void RIFFSerialReader::_popCursor()
+{
+ SLANG_ASSERT(_stack.getCount() != 0);
+ _cursor = _stack.getLast();
+ _stack.removeLast();
+}
+
+
+} // namespace Slang
diff --git a/source/slang/slang-serialize-riff.h b/source/slang/slang-serialize-riff.h
new file mode 100644
index 000000000..a464a5ded
--- /dev/null
+++ b/source/slang/slang-serialize-riff.h
@@ -0,0 +1,431 @@
+// slang-serialize-riff.h
+#ifndef SLANG_SERIALIZE_RIFF_H
+#define SLANG_SERIALIZE_RIFF_H
+
+//
+// This file provides implementations of `ISerializerImpl` that
+// serialize hierarchical data in a RIFF-based format.
+//
+// This implementation can be seen as an adapter between the
+// `Slang::Serializer` and `Slang::RIFF` subsystems, and also
+// serves an an example of how to write a complete reader/writer
+// pair for a new serialization format.
+//
+
+#include "../core/slang-riff.h"
+#include "slang-serialize.h"
+
+namespace Slang
+{
+
+namespace RIFFSerial
+{
+//
+// Each value in the hierarchy will be ended as a RIFF chunk.
+// The type of the chunk, and whether it is a list or data
+// chunk will depend on the kind of value.
+//
+// All of the serialized data will be encapsulated in a single
+// list chunk. This chunk can have its `FourCC` customized,
+// but a default is also provided.
+//
+
+/// Default type for root chunk of a serialized object graph.
+static const FourCC::RawValue kRootFourCC = SLANG_FOUR_CC('r', 'o', 'o', 't');
+
+//
+// Simple numeric values are stored as data chunks.
+// Rather than go down to the granularity of 16- and
+// 8-bit integers, we stick to 32- and 64-bit values
+// only, since the overhead of a RIFF chunk header
+// is already 64 bits (so the savings would be
+// minimal).
+//
+
+static const FourCC::RawValue kInt32FourCC = SLANG_FOUR_CC('i', '3', '2', ' ');
+static const FourCC::RawValue kInt64FourCC = SLANG_FOUR_CC('i', '6', '4', ' ');
+
+static const FourCC::RawValue kUInt32FourCC = SLANG_FOUR_CC('u', '3', '2', ' ');
+static const FourCC::RawValue kUInt64FourCC = SLANG_FOUR_CC('u', '6', '4', ' ');
+
+static const FourCC::RawValue kFloat32FourCC = SLANG_FOUR_CC('f', '3', '2', ' ');
+static const FourCC::RawValue kFloat64FourCC = SLANG_FOUR_CC('f', '6', '4', ' ');
+
+//
+// Boolean values are stored as empty chunks, with a unique
+// type tag for each of the two possible values.
+//
+
+static const FourCC::RawValue kTrueFourCC = SLANG_FOUR_CC('t', 'r', 'u', 'e');
+static const FourCC::RawValue kFalseFourCC = SLANG_FOUR_CC('f', 'a', 'l', 's');
+
+//
+// Strings are stored as a data chunk, with the payload of
+// that chunk holding the bytes of the UTF-8 encoded string.
+// The length of the string is stored as part of the chunk
+// header.
+//
+// We also define a `FourCC` for raw data chunks, in anticipation
+// of support for raw data being added to `ISerializerImpl` as
+// an analogue of strings.
+//
+
+static const FourCC::RawValue kStringFourCC = SLANG_FOUR_CC('s', 't', 'r', ' ');
+static const FourCC::RawValue kDataFourCC = SLANG_FOUR_CC('d', 'a', 't', 'a');
+
+//
+// Containers (arrays, dictionaries, optionals, tuples, and structs)
+// are stored as list chunks, with their elements as child chunks.
+//
+
+static const FourCC::RawValue kArrayFourCC = SLANG_FOUR_CC('a', 'r', 'r', 'y');
+static const FourCC::RawValue kDictionaryFourCC = SLANG_FOUR_CC('d', 'i', 'c', 't');
+static const FourCC::RawValue kStructFourCC = SLANG_FOUR_CC('s', 't', 'r', 'c');
+static const FourCC::RawValue kTupleFourCC = SLANG_FOUR_CC('t', 'p', 'l', 'e');
+static const FourCC::RawValue kOptionalFourCC = SLANG_FOUR_CC('o', 'p', 't', '?');
+
+//
+// Null pointer values are simply stored as an empty data chunk with
+// a distinct type.
+//
+
+static const FourCC::RawValue kNullFourCC = SLANG_FOUR_CC('n', 'u', 'l', 'l');
+
+//
+// Non-null pointers are stored as a data chunk that references a
+// serialized object by its `ObjectIndex`.
+//
+
+using ObjectIndex = Int32;
+
+static const FourCC::RawValue kObjectReferenceFourCC = SLANG_FOUR_CC('o', 'b', 'j', 'r');
+
+//
+// All of the objects transitively referenced in the serialized object
+// graph are stored in a list chunk of object definitions, with one
+// chunk per object. The object definitions themselves are ordinary
+// values using any of the cases above.
+//
+
+static const FourCC::RawValue kObjectDefinitionListFourCC = SLANG_FOUR_CC('o', 'b', 'j', 's');
+
+//
+// The first child of the root chunk will be the object definition list
+// chunk, and that will be followed by zero or more "root values" that
+// have been serialized.
+//
+
+} // namespace RIFFSerial
+
+/// Serializer implementation for writing to a tree of RIFF chunks.
+struct RIFFSerialWriter : ISerializerImpl
+{
+public:
+ /// Construct a writer to append to the given RIFF `chunk`.
+ ///
+ /// The object graph will be serialized into a child chunk
+ /// of `chunk`, as a list chunk with the given `type`.
+ ///
+ RIFFSerialWriter(RIFF::ChunkBuilder* chunk, FourCC type = RIFFSerial::kRootFourCC);
+
+
+ /// Construct a writer to write an entire RIFF file.
+ ///
+ /// The object graph will be serialized as the root chunk
+ /// of `riff`, with the given `type`.
+ ///
+ RIFFSerialWriter(RIFF::Builder& riff, FourCC type = RIFFSerial::kRootFourCC);
+
+ /// Finalize writing.
+ ///
+ /// Any pending operations needed to write the entire object
+ /// graph will be flushed.
+ ///
+ ~RIFFSerialWriter();
+
+private:
+ RIFFSerialWriter() = delete;
+
+ /// Cursor for where in the RIFF hierarchy we are writing.
+ RIFF::BuildCursor _cursor;
+
+ /// Representation of an index into the object list.
+ using ObjectIndex = RIFFSerial::ObjectIndex;
+
+ /// Information about an object that should be
+ /// added to the object definition list.
+ struct ObjectInfo
+ {
+ /// Pointer to the in-memory C++ object.
+ void* ptr;
+
+ /// Callback that can be invoked to serialize the object's data.
+ Callback callback;
+
+ /// User-data pointer for `callback`
+ void* userData;
+ };
+
+ /// The chunk where object definitions are listed.
+ RIFF::ListChunkBuilder* _objectDefinitionListChunk = nullptr;
+
+ /// Information on the objects that have been referenced,
+ /// and which need their definitions to be serialized into
+ /// the object definition list chunk.
+ ///
+ List<ObjectInfo> _objects;
+ Index _writtenObjectDefinitionCount = 0;
+
+ /// Maps the address of an in-memory C++ object to the
+ /// corresponding entry in `_objects`, if any.
+ Dictionary<void*, ObjectIndex> _mapPtrToObjectIndex;
+
+
+ void _initialize(FourCC type);
+ void _flush();
+
+ void _writeInt(Int64 value);
+ void _writeUInt(UInt64 value);
+ void _writeFloat(double value);
+
+ void _writeObjectReference(ObjectIndex index);
+
+private:
+ //
+ // The following declarations are the requirements
+ // of the `ISerializerImpl` interface:
+ //
+
+ virtual SerializationMode getMode() override;
+
+ virtual void handleBool(bool& value) override;
+
+ virtual void handleInt8(int8_t& value) override;
+ virtual void handleInt16(int16_t& value) override;
+ virtual void handleInt32(Int32& value) override;
+ virtual void handleInt64(Int64& value) override;
+
+ virtual void handleUInt8(uint8_t& value) override;
+ virtual void handleUInt16(uint16_t& value) override;
+ virtual void handleUInt32(UInt32& value) override;
+ virtual void handleUInt64(UInt64& value) override;
+
+ virtual void handleFloat32(float& value) override;
+ virtual void handleFloat64(double& value) override;
+
+ virtual void handleString(String& value) override;
+
+ virtual void beginArray() override;
+ virtual void endArray() override;
+
+ virtual void beginDictionary() override;
+ virtual void endDictionary() override;
+
+ virtual bool hasElements() override;
+
+ virtual void beginStruct() override;
+ virtual void handleFieldKey(char const* name, Int index) override;
+ virtual void endStruct() override;
+
+ virtual void beginTuple() override;
+ virtual void endTuple() override;
+
+ 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 handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData)
+ override;
+};
+
+/// Serializer implementation for reading from a tree of RIFF chunks.
+struct RIFFSerialReader : ISerializerImpl
+{
+public:
+ /// Construct a reader to read data from the given `chunk`.
+ ///
+ /// Will validate that the given `chunk` is a list chunk
+ /// matching the expected `type`.
+ ///
+ RIFFSerialReader(RIFF::Chunk const* chunk, FourCC type = RIFFSerial::kRootFourCC);
+
+ /// Finalize the reader.
+ ///
+ /// This will flush any outstanding operations that
+ /// might be pending.
+ ///
+ ~RIFFSerialReader();
+
+private:
+ /// Representation of a read cursor in the serialized RIFF data.
+ using Cursor = RIFF::BoundsCheckedChunkPtr;
+
+ /// Current cursor in the serialized RIFF data.
+ Cursor _cursor;
+
+ void _advanceCursor();
+
+ /// A stack of saved cursors, reflecting the nesting
+ /// hierarchy of container chunks being read from.
+ ///
+ List<Cursor> _stack;
+
+ void _pushCursor();
+ void _popCursor();
+
+ /// Representation of an index into the object list.
+ using ObjectIndex = RIFFSerial::ObjectIndex;
+
+ /// State of a serialized object that may or may not have been read already.
+ enum class ObjectState
+ {
+ Unread,
+ ReadingInProgress,
+ ReadingComplete,
+ };
+
+ /// Information about a serialized object in the object definition list.
+ struct ObjectInfo
+ {
+ /// State of the object.
+ ///
+ ObjectState state = ObjectState::Unread;
+
+ /// Pointer to an in-memory C++ object representing the serialized object.
+ ///
+ /// Should only be accessed with consideration of what `state` is:
+ ///
+ /// * If `state` is `Unread`, then this should always be null.
+ ///
+ /// * If `state` is `ReadingComplete`, then this should be be
+ /// a valid pointer to the in-memory representation of the serialized object
+ /// (or null, if client code chose to deserialize it into a null pointer
+ /// for some reason).
+ ///
+ /// * If `state` is `ReadingInProgress`, then this might be a null pointer,
+ /// indicating that the logic to deserialize the object is currently
+ /// running (but has not yet allocated a representation and set it), or
+ /// it might be non-null indicating that the in-memory representation
+ /// has been allocated.
+ ///
+ /// Even if `ptr` is non-null, it may not be safe to access the
+ /// contents of the pointed-to object, because there may be deferred
+ /// operations pending to read some or all of its members.
+ ///
+ void* ptr = nullptr;
+
+ /// The chunk that holds the definition of this object.
+ RIFF::Chunk const* definitionChunk = nullptr;
+ };
+
+ /// All of the objects from the object definition list chunk.
+ List<ObjectInfo> _objects;
+
+ /// A serialization action that has been deferred.
+ ///
+ /// Deferred actions are typically used to put off recursively
+ /// reading all of the members of an object, thus avoiding
+ /// the potential for unbounded or even infinite recursion.
+ ///
+ struct DeferredAction
+ {
+ /// The in-memory object that the action should apply to.
+ void* valuePtr;
+
+ /// The value of `_cursor` at the time this action was deferred.
+ Cursor savedCursor;
+
+ /// The callback to apply to read data into the `valuePtr`
+ Callback callback;
+
+ /// The user-data pointer for the `callback`.
+ void* userData;
+ };
+
+ /// Deferred actions that are still pending.
+ ///
+ /// As long as this array is non-empty, the contents of
+ /// in-memory objects read from the serialized data should
+ /// not be inspected/used.
+ ///
+ List<DeferredAction> _deferredActions;
+
+ void _initialize(FourCC type);
+ void _flush();
+
+ FourCC _peekChunkType();
+
+ Int64 _readInt();
+ UInt64 _readUInt();
+ double _readFloat();
+
+ ObjectIndex _readObjectReference();
+
+ void _readDataChunk(void* outData, size_t dataSize);
+
+ template<typename T>
+ T _readDataChunk()
+ {
+ T value;
+ _readDataChunk(&value, sizeof(value));
+ return value;
+ }
+
+ void _beginListChunk(FourCC type);
+ void _endListChunk();
+
+private:
+ //
+ // The following declarations are the requirements
+ // of the `ISerializerImpl` interface:
+ //
+
+ virtual SerializationMode getMode() override;
+
+ virtual void handleBool(bool& value) override;
+
+ virtual void handleInt8(int8_t& value) override;
+ virtual void handleInt16(int16_t& value) override;
+ virtual void handleInt32(Int32& value) override;
+ virtual void handleInt64(Int64& value) override;
+
+ virtual void handleUInt8(uint8_t& value) override;
+ virtual void handleUInt16(uint16_t& value) override;
+ virtual void handleUInt32(UInt32& value) override;
+ virtual void handleUInt64(UInt64& value) override;
+
+ virtual void handleFloat32(float& value) override;
+ virtual void handleFloat64(double& value) override;
+
+ virtual void handleString(String& value) override;
+
+ virtual void beginArray() override;
+ virtual void endArray() override;
+
+ virtual void beginDictionary() override;
+ virtual void endDictionary() override;
+
+ virtual bool hasElements() override;
+
+ virtual void beginStruct() override;
+ virtual void handleFieldKey(char const* name, Int index) override;
+ virtual void endStruct() override;
+
+ virtual void beginTuple() override;
+ virtual void endTuple() override;
+
+ 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 handleDeferredObjectContents(void* valuePtr, Callback callback, void* userData)
+ override;
+};
+
+} // namespace Slang
+
+#endif
diff --git a/source/slang/slang-serialize.h b/source/slang/slang-serialize.h
index 4b864fc94..d76ab8338 100644
--- a/source/slang/slang-serialize.h
+++ b/source/slang/slang-serialize.h
@@ -2,570 +2,978 @@
#ifndef SLANG_SERIALIZE_H
#define SLANG_SERIALIZE_H
-// #include <type_traits>
-
-#include "../compiler-core/slang-name.h"
-#include "../core/slang-byte-encode-util.h"
-#include "../core/slang-riff.h"
-#include "../core/slang-stream.h"
-#include "slang-serialize-types.h"
+// This file defines an API for serialization.
+//
+// The API is intended to support multiple serialization formats,
+// and to work with complicated object graphs that may include
+// shared pointers, circular references, and so on.
+//
+// For anybody who don't want to dig into the details, the short
+// version is that if you have a user-defined type like:
+//
+// // my-thing.h
+// ...
+//
+// struct MyThing
+// {
+// float a;
+// List<OtherThing> otherThings;
+// SomeObject* object;
+// };
+//
+// then you can declare serialization support for your type
+// with something like:
+//
+// // my-thing.h
+// ...
+// #include "slang-serialize.h"
+// ...
+//
+// struct MyThing { ... }
+//
+// void serialize(Serializer const& serializer, MyThing& value);
+//
+// and then implement that support with something like:
+//
+// // my-thing.cpp
+// #include "my-thing.h"
+//
+// ...
+//
+// void serialize(Serializer const& serializer, MyThing& value)
+// {
+// SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+// serialize(serializer, value.a);
+// serialize(serializer, value.otherThings);
+// serialize(serializer, value.object);
+// }
+//
+// That's it. So long as the `OtherThing` and `SomeObject` types used
+// in the declaration of `MyType` already implemented serialization
+// support, your new type should be fully serializable.
+//
+
+#include "../core/slang-basic.h"
+
+#include <optional>
namespace Slang
{
-class Linkage;
-
-/*
-A discussion of the serialization system design can be found in
-
-docs/design/serialization.md
-*/
+//
+// A central design choice of this serialization system is that
+// both reading and writing of serialized data for a type are
+// implemented using a single function. This choice makes it
+// easier for a developer to be certain that the reading and
+// writing code for a type are consistent with one another.
+//
+// In some cases, however, a serialization function may need
+// to know whether it is reading or writing serialized data.
+// For that reason, we define a simple `enum` to represent
+// the different modes of operation.
+//
+
+/// Whether serialized data is being read or written.
+enum class SerializationMode
+{
+ Read,
+ Write,
+};
-// Predeclare
-typedef uint32_t SerialSourceLoc;
-class NodeBase;
-class Val;
-struct ValNodeDesc;
+//
+// In order to support different serialized formats, and to
+// abstract over the difference between reading and writing,
+// we define a base interface for serialization. This interface
+// is somewhat user-unfriendly, and is *not* intended for
+// ordinary code to interface with directly.
+//
+
+/// Base interface for serialization.
+///
+/// Can be used for both reading and writing of serialized data.
+///
+struct ISerializerImpl
+{
+ /// Get the mode that this serializer is operating in (reading or writing).
+ virtual SerializationMode getMode() = 0;
+
+ /// Handle a boolean value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleBool(bool& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleInt8(int8_t& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleInt16(int16_t& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleInt32(Int32& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleInt64(Int64& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleUInt8(uint8_t& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleUInt16(uint16_t& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleUInt32(UInt32& value) = 0;
+
+ /// Handle an integer value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleUInt64(UInt64& value) = 0;
+
+ /// Handle a floating-point value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleFloat32(float& value) = 0;
+
+ /// Handle a floating-point value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleFloat64(double& value) = 0;
+
+ /// Handle a string value.
+ ///
+ /// If the serializer is writing, then `value` will be
+ /// written to the serialized format.
+ ///
+ /// If the serializer is reading, then `value` will be
+ /// set to the value read from the serialized format.
+ ///
+ virtual void handleString(String& value) = 0;
+
+ /// Begin serializing an array value.
+ ///
+ /// An array should be used to serialize an
+ /// unkeyed homogeneous collection of a varying
+ /// number of elements.
+ ///
+ /// This operation must be properly paired with a
+ /// call to `endArray()`.
+ ///
+ /// When writing, the values serialized between `beginArray()`
+ /// and `endArray()` will be written as the elements of a
+ /// serialized array.
+ ///
+ /// When reading, the user should call `hasElements()` to
+ /// test whether there are elements remaining to be read,
+ /// and serialize values in a loop until `hasElements()`
+ /// returns `false`.
+ ///
+ virtual void beginArray() = 0;
+
+ /// End serializing an array value.
+ virtual void endArray() = 0;
+
+ /// Begin serializing an optional value.
+ ///
+ /// An optional should be used to serialize a
+ /// collection that logically has either zero
+ /// or one element.
+ ///
+ /// This operation must be properly paired with a
+ /// call to `endOptional()`.
+ ///
+ /// When writing, a value serialized between `beginOptional()`
+ /// and `endOptional()` will be written as the value of
+ /// the serialized optional. If no value is serialized,
+ /// then the optional will be empty.
+ ///
+ /// When reading, the user should call `hasElements()` to
+ /// test whether the serialized optional has a value and,
+ /// if it does, read the value before calling `endOptional()`.
+ ///
+ virtual void beginOptional() = 0;
+
+ /// End serializing an optional value.
+ virtual void endOptional() = 0;
+
+ /// Begin serializing a dictionary value.
+ ///
+ /// A dictionary should be used to serialize a
+ /// keyed homogeneous collection of a varying
+ /// number of elements. The elements of a dictioanry
+ /// are key-value pairs (that is, two-element tuples).
+ ///
+ /// Formats are required to support dictionaries with
+ /// any serializable type as the key, not just strings.
+ ///
+ /// This operation must be properly paired with a
+ /// call to `endDictionary()`.
+ ///
+ /// When writing, the values serialized between `beginDictionary()`
+ /// and `endDictionary()` will be written as the elements of a
+ /// serialized dictionary.
+ ///
+ /// When reading, the user should call `hasElements()` to
+ /// test whether there are elements remaining to be read,
+ /// and serialize values in a loop until `hasElements()`
+ /// returns `false`.
+ ///
+ virtual void beginDictionary() = 0;
+
+ /// End serializing a dictionary value.
+ virtual void endDictionary() = 0;
+
+ /// Check whether there are elements remaining to be read
+ /// from a serialized container.
+ ///
+ /// It is invalid to call this function except between paired
+ /// `beginArray()`/`endArray()`, beginDictionary()`/`endDictionary()`,
+ /// or `beginOptional()`/`endOptional()` calls.
+ ///
+ virtual bool hasElements() = 0;
+
+ /// Begin serializing a tuple value.
+ ///
+ /// A tuple should be used to serialize an
+ /// unkeyed heterogeneous collection of a fixed
+ /// number of elements.
+ ///
+ /// It is up to the concrete implementation whether calls
+ /// to `hasElements()` are allowed between `beginTuple()`
+ /// and `endTuple()`.
+ ///
+ virtual void beginTuple() = 0;
+
+ /// End serializing a tuple value.
+ virtual void endTuple() = 0;
+
+
+ /// Begin serializing a struct value.
+ ///
+ /// A struct should be used to serialize an
+ /// keyed heterogeneous collection of a fixed
+ /// number of elements.
+ ///
+ /// The value of each struct field should be
+ /// preceded by a call to `handleFieldKey()`,
+ /// which specifies the field being serialized.
+ ///
+ /// It is up to the concrete implementation whether
+ /// a fields can be read in a different order than
+ /// they were written, and how to handle attempts
+ /// to read a field that was not written.
+ ///
+ virtual void beginStruct() = 0;
+
+ /// End serializing a struct value.
+ virtual void endStruct() = 0;
+
+ /// Set the key for the next struct field to be serialized.
+ ///
+ /// If no name is available for the field, `name` may be `nullptr`.
+ ///
+ /// If no index is available for the field, `index` may be `-1`.
+ ///
+ /// A user must pass either a valid `name` or `index.
+ ///
+ 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);
+
+ /// Handle a pointer value that is expected to be unique.
+ ///
+ /// A unique pointer is logically similar to an optional value.
+ ///
+ /// If the pointer value being read/written is null, then
+ /// the function returns without invoking `callback`.
+ ///
+ /// When reading, if the serialized value is non-null,
+ /// then the callback will be invoked as `callback(&value, userData)`.
+ /// The callback is expected to read the members of the pointed-to
+ /// type and set `value` to some object (whether newly constructed
+ /// or looked up).
+ ///
+ /// 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
+ /// holding a copy of the `value` that was passed in. The callback
+ /// is expected to write the members of the pointed-to type.
+ ///
+ /// If the `callback` is invoked at some later point, rather than
+ /// immediately, the concrete serializer implementation is responsible
+ /// 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;
+
+ /// Handle a pointer value that may have multiple references.
+ ///
+ /// This operation is similar to `handleUniquePtr` with the following
+ /// differences:
+ ///
+ /// * When writing, if the same pointer value has been seen before,
+ /// the `callback` will not be invoked, and instead an additional
+ /// reference to the previously-serialized value will be written.
+ ///
+ /// * When reading, if the serialized value has been read before,
+ /// 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;
+
+ /// Defer serialization of the contents of an object.
+ ///
+ /// Used to delay serialization of members of an object that
+ /// could cause infinite recursion if serialized eagerly.
+ ///
+ /// This operation should only be used in the body of a callback
+ /// passed to `handleUniquePtr()` or `handleSharedPtr()`.
+ ///
+ /// This operation schedules the given `callback` to be called
+ /// at some later point a `callback(value, userData)`, 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;
+};
-struct Encoder
+//
+// Rather than interact with instances of `ISerializerImpl` directly,
+// most client code will use a wrapper type that amounts to a kind
+// of smart pointer.
+//
+// While the `ISerializerImpl` interface can cover a wide range of
+// types that need to be serialized, it is common for types to require
+// more specific context to be available in order to perform serialization.
+// For example, code might need access to a factory object in order
+// 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.
+//
+
+/// Base type for serialization contexts.
+///
+/// The type parameter `T` should be a type of object that
+/// holds the context information needed.
+///
+template<typename T>
+struct SerializerBase
{
public:
- Encoder() {}
-
- Encoder(RIFF::Builder& riff)
- : _cursor(riff)
+ SerializerBase() = default;
+ SerializerBase(T* ptr)
+ : _ptr(ptr)
{
}
- Encoder(RIFF::ListChunkBuilder* chunk)
- : _cursor(chunk)
- {
- }
+ T* get() const { return _ptr; }
+ T* operator->() const { return get(); }
- void beginArray(FourCC typeCode) { _cursor.beginListChunk(typeCode); }
+private:
+ T* _ptr = nullptr;
+};
- void beginArray() { beginArray(SerialBinary::kArrayFourCC); }
+/// A serialization context.
+///
+/// The type parameter `T` should be a type of object that
+/// holds the context information needed.
+///
+template<typename T>
+struct Serializer_ : SerializerBase<T>
+{
+ using SerializerBase<T>::SerializerBase;
+};
- void endArray() { _cursor.endChunk(); }
+/// Default serialization context.
+using Serializer = Serializer_<ISerializerImpl>;
- void beginObject(FourCC typeCode) { _cursor.beginListChunk(typeCode); }
+//
+// We define namespace-scope functions that mirror some
+// of the operations of `ISerializerImpl`, so that they
+// can be invoked on any type that is contextually
+// convertible to a `Serializer`. This allows users
+// to define their own serialization context types while
+// still being able to take advantage of the utility
+// operations in this file for serializing basic types,
+// arrays, dictionaries, etc.
+//
- void beginObject() { beginObject(SerialBinary::kObjectFourCC); }
- void endObject() { _cursor.endChunk(); }
+/// Get the mode of `serializer`.
+inline SerializationMode getMode(Serializer const& serializer)
+{
+ return serializer->getMode();
+}
- void beginKeyValuePair(FourCC keyCode) { _cursor.beginListChunk(keyCode); }
+/// Check if `serializer` is reading serialized data.
+inline bool isReading(Serializer const& serializer)
+{
+ return getMode(serializer) == SerializationMode::Read;
+}
- void beginKeyValuePair() { beginKeyValuePair(SerialBinary::kPairFourCC); }
+/// Check if `serializer` is writing serialized data.
+inline bool isWriting(Serializer const& serializer)
+{
+ return getMode(serializer) == SerializationMode::Write;
+}
- void endKeyValuePair() { _cursor.endChunk(); }
+/// Check if `serializer` has more container elements.
+inline bool hasElements(Serializer const& serializer)
+{
+ return serializer->hasElements();
+}
- void encodeData(FourCC typeCode, void const* data, size_t size)
- {
- _cursor.addDataChunk(typeCode, data, size);
- }
+inline void serialize(Serializer const& serializer, bool& value)
+{
+ serializer->handleBool(value);
+}
- void encodeData(void const* data, size_t size)
- {
- encodeData(SerialBinary::kDataFourCC, data, size);
- }
+inline void serialize(Serializer const& serializer, int8_t& value)
+{
+ serializer->handleInt8(value);
+}
- void encode(std::nullptr_t) { encodeData(SerialBinary::kNullFourCC, nullptr, 0); }
+inline void serialize(Serializer const& serializer, int16_t& value)
+{
+ serializer->handleInt16(value);
+}
- void encodeBool(bool value)
- {
- encodeData(value ? SerialBinary::kTrueFourCC : SerialBinary::kFalseFourCC, nullptr, 0);
- }
+inline void serialize(Serializer const& serializer, Int32& value)
+{
+ serializer->handleInt32(value);
+}
- void encodeInt(Int64 value)
- {
- if (Int32(value) == value)
- {
- auto v = Int32(value);
- encodeData(SerialBinary::kInt32FourCC, &v, sizeof(v));
- }
- else
- {
- encodeData(SerialBinary::kInt64FourCC, &value, sizeof(value));
- }
- }
+inline void serialize(Serializer const& serializer, Int64& value)
+{
+ serializer->handleInt64(value);
+}
+inline void serialize(Serializer const& serializer, uint8_t& value)
+{
+ serializer->handleUInt8(value);
+}
- void encodeUInt(UInt64 value)
- {
- if (UInt32(value) == value)
- {
- auto v = UInt32(value);
- encodeData(SerialBinary::kUInt32FourCC, &v, sizeof(v));
- }
- else
- {
- encodeData(SerialBinary::kUInt64FourCC, &value, sizeof(value));
- }
- }
+inline void serialize(Serializer const& serializer, uint16_t& value)
+{
+ serializer->handleUInt16(value);
+}
- void encode(Int32 value) { encodeInt(value); }
- void encode(Int64 value) { encodeInt(value); }
+inline void serialize(Serializer const& serializer, UInt32& value)
+{
+ serializer->handleUInt32(value);
+}
- void encode(UInt32 value) { encodeUInt(value); }
- void encode(UInt64 value) { encodeUInt(value); }
+inline void serialize(Serializer const& serializer, UInt64& value)
+{
+ serializer->handleUInt64(value);
+}
- void encode(float value) { encodeData(SerialBinary::kFloat32FourCC, &value, sizeof(value)); }
+inline void serialize(Serializer const& serializer, float& value)
+{
+ serializer->handleFloat32(value);
+}
- void encode(double value) { encodeData(SerialBinary::kFloat64FourCC, &value, sizeof(value)); }
+inline void serialize(Serializer const& serializer, double& value)
+{
+ serializer->handleFloat64(value);
+}
- void encodeString(String const& value)
+inline void serialize(Serializer const& serializer, String& value)
+{
+ serializer->handleString(value);
+}
+
+/// Serialize an `enum` value via an intermediate integer type.
+///
+/// This function serializes a value of `EnumType`, by
+/// converting it to/from the given `RawType` for storage
+/// in the serialized format.
+///
+template<typename RawType = Int32, typename EnumType>
+void serializeEnum(Serializer const& serializer, EnumType& value)
+{
+ auto raw = RawType(value);
+ serialize(serializer, raw);
+ value = EnumType(raw);
+}
+
+//
+// We define a suite of simple RAII types to help users
+// maintain the proper pairing of begin/end operations
+// when interacting with an `ISerializerImpl`, and for
+// each of those types we define a macro to simplify
+// introducing a coresponding scope.
+//
+
+struct ScopedSerializerArray
+{
+public:
+ ScopedSerializerArray(Serializer const& serializer)
+ : _serializer(serializer)
{
- Int size = value.getLength();
- encodeData(SerialBinary::kStringFourCC, value.getBuffer(), size);
+ serializer->beginArray();
}
+ ~ScopedSerializerArray() { _serializer->endArray(); }
- void encode(String const& value) { encodeString(value); }
-
- struct WithArray
- {
- public:
- WithArray(Encoder* encoder)
- : _encoder(encoder)
- {
- encoder->beginArray();
- }
-
- WithArray(Encoder* encoder, FourCC typeCode)
- : _encoder(encoder)
- {
- encoder->beginArray(typeCode);
- }
-
- ~WithArray() { _encoder->endArray(); }
-
- private:
- Encoder* _encoder;
- };
+private:
+ Serializer _serializer;
+};
- struct WithObject
+struct ScopedSerializerDictionary
+{
+public:
+ ScopedSerializerDictionary(Serializer const& serializer)
+ : _serializer(serializer)
{
- public:
- WithObject(Encoder* encoder)
- : _encoder(encoder)
- {
- encoder->beginObject();
- }
-
- WithObject(Encoder* encoder, FourCC typeCode)
- : _encoder(encoder)
- {
- encoder->beginObject(typeCode);
- }
+ serializer->beginDictionary();
+ }
- ~WithObject() { _encoder->endObject(); }
+ ~ScopedSerializerDictionary() { _serializer->endDictionary(); }
- private:
- Encoder* _encoder;
- };
+private:
+ Serializer _serializer;
+};
- struct WithKeyValuePair
+struct ScopedSerializerStruct
+{
+public:
+ ScopedSerializerStruct(Serializer const& serializer)
+ : _serializer(serializer)
{
- public:
- WithKeyValuePair(Encoder* encoder)
- : _encoder(encoder)
- {
- encoder->beginKeyValuePair();
- }
-
- WithKeyValuePair(Encoder* encoder, FourCC typeCode)
- : _encoder(encoder)
- {
- encoder->beginKeyValuePair(typeCode);
- }
-
- ~WithKeyValuePair() { _encoder->endKeyValuePair(); }
+ serializer->beginStruct();
+ }
- private:
- Encoder* _encoder;
- };
+ ~ScopedSerializerStruct() { _serializer->endStruct(); }
private:
- RIFF::BuildCursor _cursor;
+ Serializer _serializer;
+};
+struct ScopedSerializerTuple
+{
public:
- operator RIFF::BuildCursor&() { return _cursor; }
+ ScopedSerializerTuple(Serializer const& serializer)
+ : _serializer(serializer)
+ {
+ serializer->beginTuple();
+ }
- RIFF::ChunkBuilder* getRIFFChunk() { return _cursor.getCurrentChunk(); }
+ ~ScopedSerializerTuple() { _serializer->endTuple(); }
- void setRIFFChunk(RIFF::ChunkBuilder* chunk) { _cursor.setCurrentChunk(chunk); }
+private:
+ Serializer _serializer;
};
-struct Decoder
+struct ScopedSerializerOptional
{
public:
- Decoder(RIFF::Chunk const* chunk)
- : _cursor(chunk)
+ ScopedSerializerOptional(Serializer const& serializer)
+ : _serializer(serializer)
{
+ serializer->beginOptional();
}
- bool decodeBool()
- {
- switch (getTag())
- {
- case SerialBinary::kTrueFourCC:
- _advanceCursor();
- return true;
- case SerialBinary::kFalseFourCC:
- _advanceCursor();
- return false;
-
- default:
- SLANG_UNEXPECTED("invalid format in RIFF");
- UNREACHABLE_RETURN(false);
- }
- }
-
- String decodeString()
- {
- if (getTag() != SerialBinary::kStringFourCC)
- {
- SLANG_UNEXPECTED("invalid format in RIFF");
- UNREACHABLE_RETURN("");
- }
-
- auto dataChunk = as<RIFF::DataChunk>(_cursor);
- if (!dataChunk)
- {
- SLANG_UNEXPECTED("invalid format in RIFF");
- UNREACHABLE_RETURN("");
- }
+ ~ScopedSerializerOptional() { _serializer->endOptional(); }
- auto size = dataChunk->getPayloadSize();
+private:
+ Serializer _serializer;
+};
- String value;
- value.appendRepeatedChar(' ', size);
- dataChunk->writePayloadInto((char*)value.getBuffer(), size);
- _advanceCursor();
- return value;
+#define SLANG_SCOPED_SERIALIZER_ARRAY(SERIALIZER) \
+ ::Slang::ScopedSerializerArray SLANG_CONCAT(_scopedSerializerArray, __LINE__)(SERIALIZER)
+
+#define SLANG_SCOPED_SERIALIZER_DICTIONARY(SERIALIZER) \
+ ::Slang::ScopedSerializerDictionary SLANG_CONCAT(_scopedSerializerDictionary, __LINE__)( \
+ SERIALIZER)
+
+#define SLANG_SCOPED_SERIALIZER_OPTIONAL(SERIALIZER) \
+ ::Slang::ScopedSerializerOptional SLANG_CONCAT(_scopedSerializerOptional, __LINE__)(SERIALIZER)
+
+#define SLANG_SCOPED_SERIALIZER_STRUCT(SERIALIZER) \
+ ::Slang::ScopedSerializerStruct SLANG_CONCAT(_scopedSerializerStruct, __LINE__)(SERIALIZER)
+
+#define SLANG_SCOPED_SERIALIZER_TAGGED_UNION(SERIALIZER) \
+ ::Slang::ScopedSerializerStruct SLANG_CONCAT(_scopedSerializerStruct, __LINE__)(SERIALIZER)
+
+#define SLANG_SCOPED_SERIALIZER_TUPLE(SERIALIZER) \
+ ::Slang::ScopedSerializerTuple SLANG_CONCAT(_scopedSerializerTuple, __LINE__)(SERIALIZER)
+
+//
+// Containers like arrays and dictionaries are more
+// difficult to serialize than typical user-defined
+// types for a few reasons:
+//
+// * They typically need to have distinct code paths
+// for reading and writing, so they don't benefit
+// much from having a unified read/write abstraction.
+//
+// * They need to be written as templates, to abstract
+// over the element type, and thus need to be
+// defined in headers.
+//
+// * Because the element type might require a more
+// specialized type of serialization context, they
+// also need to be templated on the type of the
+// serializer itself.
+//
+// With all that said, the definitions themselves
+// are fairly straightforward. All we have to do is
+// branch on whether we are reading or writing and
+// either iterate over the serialized data to fill
+// the collection (when reading), or iterate over
+// the collection to serialize its elements (when
+// writing).
+//
+
+template<typename S, typename T>
+void serialize(S const& serializer, List<T>& value)
+{
+ SLANG_SCOPED_SERIALIZER_ARRAY(serializer);
+ if (isWriting(serializer))
+ {
+ for (auto element : value)
+ serialize(serializer, element);
}
-
- void decodeData(FourCC typeTag, void* outData, size_t dataSize)
+ else
{
- if (getTag() == typeTag)
+ value.clear();
+ while (hasElements(serializer))
{
- auto dataChunk = as<RIFF::DataChunk>(_cursor);
- if (dataChunk)
- {
- auto payloadSize = dataChunk->getPayloadSize();
- if (payloadSize >= dataSize)
- {
- dataChunk->writePayloadInto(outData, dataSize);
- _advanceCursor();
- return;
- }
- }
+ T element;
+ serialize(serializer, element);
+ value.add(element);
}
-
- SLANG_UNEXPECTED("invalid format in RIFF");
}
+}
- template<typename T>
- T _decodeSimpleValue(FourCC typeTag)
+template<typename S, typename T, size_t N>
+void serialize(S const& serializer, T (&value)[N])
+{
+ SLANG_SCOPED_SERIALIZER_ARRAY(serializer);
+ if (isWriting(serializer))
{
- T value;
- decodeData(typeTag, &value, sizeof(value));
- return value;
+ for (auto element : value)
+ serialize(serializer, element);
}
-
- Int64 decodeInt()
+ else
{
- switch (getTag())
+ size_t index = 0;
+ while (hasElements(serializer))
{
- case SerialBinary::kInt64FourCC:
- return _decodeSimpleValue<Int64>(getTag());
- case SerialBinary::kInt32FourCC:
- return _decodeSimpleValue<Int32>(getTag());
-
- case SerialBinary::kUInt32FourCC:
- return _decodeSimpleValue<UInt32>(getTag());
+ T element;
+ serialize(serializer, element);
- case SerialBinary::kUInt64FourCC:
+ if (index >= N)
{
- auto uintValue = _decodeSimpleValue<UInt64>(getTag());
- if (Int64(uintValue) < 0)
- {
- SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF");
- }
- return Int64(uintValue);
+ SLANG_UNEXPECTED("serialized array too large");
}
-
- default:
- SLANG_UNEXPECTED("invalid format in RIFF");
- UNREACHABLE_RETURN(0);
+ value[index++] = element;
}
}
+}
- UInt64 decodeUInt()
+template<typename S, typename T, int N>
+void serialize(S const& serializer, ShortList<T, N>& value)
+{
+ SLANG_SCOPED_SERIALIZER_ARRAY(serializer);
+ if (isWriting(serializer))
{
- switch (getTag())
- {
- case SerialBinary::kUInt64FourCC:
- return _decodeSimpleValue<UInt64>(getTag());
- case SerialBinary::kUInt32FourCC:
- return _decodeSimpleValue<UInt32>(getTag());
-
- case SerialBinary::kInt32FourCC:
- case SerialBinary::kInt64FourCC:
- {
- auto intValue = decodeInt();
- if (intValue < 0)
- {
- SLANG_UNEXPECTED("signed/unsigned mismatch in RIFF");
- }
- return UInt64(intValue);
- }
-
- default:
- SLANG_UNEXPECTED("invalid format in RIFF");
- UNREACHABLE_RETURN(0);
- }
+ for (auto element : value)
+ serialize(serializer, element);
}
-
- double decodeFloat()
+ else
{
- switch (getTag())
+ value.clear();
+ while (hasElements(serializer))
{
- case SerialBinary::kFloat32FourCC:
- return _decodeSimpleValue<float>(getTag());
- case SerialBinary::kFloat64FourCC:
- return _decodeSimpleValue<double>(getTag());
-
- default:
- SLANG_UNEXPECTED("invalid format in RIFF");
- UNREACHABLE_RETURN(0);
+ T element;
+ serialize(serializer, element);
+ value.add(element);
}
}
+}
- Int32 decodeInt32() { return Int32(decodeInt()); }
- Int64 decodeInt64() { return decodeInt(); }
-
- UInt32 decodeUInt32() { return UInt32(decodeUInt()); }
- UInt64 decodeUInt64() { return decodeUInt(); }
-
- float decodeFloat32() { return float(decodeFloat()); }
- double decodeFloat64() { return decodeFloat(); }
-
- FourCC getTag() { return _cursor ? _cursor->getType() : FourCC(0); }
-
- Int32 _decodeImpl(Int32*) { return decodeInt32(); }
- UInt32 _decodeImpl(UInt32*) { return decodeUInt32(); }
-
- Int64 _decodeImpl(Int64*) { return decodeInt64(); }
- UInt64 _decodeImpl(UInt64*) { return decodeUInt64(); }
-
- float _decodeImpl(float*) { return decodeFloat32(); }
- double _decodeImpl(double*) { return decodeFloat64(); }
-
- template<typename T>
- T decode()
- {
- return _decodeImpl((T*)nullptr);
- }
-
- template<typename T>
- void decode(T& outValue)
- {
- outValue = _decodeImpl((T*)nullptr);
- }
-
- void beginArray(FourCC typeCode = SerialBinary::kArrayFourCC)
+template<typename S, typename T>
+void serialize(S const& serializer, std::optional<T>& value)
+{
+ SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
+ if (isWriting(serializer))
{
- auto listChunk = as<RIFF::ListChunk>(_cursor);
- if (!listChunk)
+ if (value.has_value())
{
- SLANG_UNEXPECTED("invalid format in RIFF");
+ serialize(serializer, *value);
}
-
- if (listChunk->getType() != typeCode)
- {
- SLANG_UNEXPECTED("invalid format in RIFF");
- }
-
- _cursor = listChunk->getFirstChild();
}
-
- void beginObject(FourCC typeCode = SerialBinary::kObjectFourCC)
+ else
{
- auto listChunk = as<RIFF::ListChunk>(_cursor);
- if (!listChunk)
- {
- SLANG_UNEXPECTED("invalid format in RIFF");
- }
-
- if (listChunk->getType() != typeCode)
+ value.reset();
+ if (hasElements(serializer))
{
- SLANG_UNEXPECTED("invalid format in RIFF");
+ value.emplace();
+ serialize(serializer, *value);
}
-
- _cursor = listChunk->getFirstChild();
}
+}
- void beginKeyValuePair(FourCC typeCode = SerialBinary::kPairFourCC)
- {
- auto listChunk = as<RIFF::ListChunk>(_cursor);
- if (!listChunk)
- {
- SLANG_UNEXPECTED("invalid format in RIFF");
- }
+template<typename S, typename K, typename V>
+void serialize(S const& serializer, KeyValuePair<K, V>& value)
+{
+ SLANG_SCOPED_SERIALIZER_TUPLE(serializer);
+ serialize(serializer, value.key);
+ serialize(serializer, value.value);
+}
- if (listChunk->getType() != typeCode)
- {
- SLANG_UNEXPECTED("invalid format in RIFF");
- }
+template<typename S, typename K, typename V>
+void serialize(S const& serializer, std::pair<K, V>& value)
+{
+ SLANG_SCOPED_SERIALIZER_TUPLE(serializer);
+ serialize(serializer, value.first);
+ serialize(serializer, value.second);
+}
- _cursor = listChunk->getFirstChild();
+template<typename S, typename K, typename V>
+void serialize(S const& serializer, Dictionary<K, V>& value)
+{
+ SLANG_SCOPED_SERIALIZER_DICTIONARY(serializer);
+ if (isWriting(serializer))
+ {
+ for (auto pair : value)
+ serialize(serializer, pair);
}
-
- void beginProperty(FourCC propertyCode)
+ else
{
- auto listChunk = as<RIFF::ListChunk>(_cursor);
- if (!listChunk)
- {
- SLANG_UNEXPECTED("invalid format in RIFF");
- }
-
- auto found = listChunk->findListChunk(propertyCode);
- if (!found)
+ value.clear();
+ while (hasElements(serializer))
{
- SLANG_UNEXPECTED("invalid format in RIFF");
+ KeyValuePair<K, V> pair;
+ serialize(serializer, pair);
+ value.add(pair.key, pair.value);
}
-
- _cursor = found->getFirstChild();
}
+}
- bool hasElements() { return _cursor != nullptr; }
-
- bool isNull()
+template<typename S, typename K, typename V>
+void serialize(S const& serializer, OrderedDictionary<K, V>& value)
+{
+ SLANG_SCOPED_SERIALIZER_DICTIONARY(serializer);
+ if (isWriting(serializer))
{
- if (_cursor == nullptr)
- return true;
- if (getTag() == SerialBinary::kNullFourCC)
- return true;
- return false;
+ for (auto pair : value)
+ serialize(serializer, pair);
}
-
- bool decodeNull()
+ else
{
- if (!isNull())
- return false;
-
- if (_cursor != nullptr)
+ value.clear();
+ while (hasElements(serializer))
{
- _advanceCursor();
+ KeyValuePair<K, V> pair;
+ serialize(serializer, pair);
+ value.add(pair.key, pair.value);
}
- return true;
}
+}
+
+//
+// Serialization of pointers is the most complicated part of
+// the whole system. Dealing with pointers means contending with:
+//
+// * Multiply-referenced objects, or even cycles in the object graph.
+//
+// * Polymoprhic types, where a `Derived*` might get serialized
+// through a `Base*` pointer.
+//
+// * Types that require going through a factory function of
+// some kind as part of their creation (perhaps to implement
+// deduplication/caching).
+//
+// Our handling of pointers is thus broken down into several
+// different steps/layers:
+//
+// * An ordinary overload of `serialize(s,v)` is used to intercept
+// pointer types `T*` and dispatched out to `serializePtr(s,v,(T*)nullptr)`.
+// Passing the additional `T*` argument allows different overloads
+// of `serializePtr` to intercept entire type hierarchies, while
+// still allowing for a fallback case.
+//
+// * Implementations of `serializePtr` are typically expected to
+// invoke either `serializeUniquePtr` or `serializeSharedPtr`, which
+// handle calling into the `ISerializerImpl` methods with appropriate
+// callbacks.
+//
+// * The `handleUniquePtr()` or `handleSharedPtr()` operation on
+// `ISerializerImpl` is expected to handle null pointers, or previously-
+// encountered pointers in the shared case, and then invoke the
+// callback to handle things when it can't early-out.
+//
+// * The callbacks will end up calling `serializeObject(s,v,(T*)nullptr)`,
+// which is another customization point. The default implementation
+// will call `new T()` when reading, so types that need more complicated
+// creation logic should intercept this specialization point.
+//
+// * An implementation of `serializeObject()` should strive to serialize
+// the bare minimum of members required to actually allocate the object
+// (in the case where serialized data is being read), and then call
+// `deferSerializeObjectContents()` to schedule the remainder of
+// the data to be serialized. Maintaining that policy helps ensure
+// that cycles in the object graph don't create problems.
+//
+// * `serializeObjectContents()` is the final customization point. By
+// default it simply takes a `T* value` and does `serialize(..., *value)`
+// to serialize the pointed-to `T` value. A custom implementation
+// should serialize whatever members of the object weren't handled
+// as part of the corresponding `serializeObject()` implementation.
+//
+
+template<typename S, typename T>
+void serializeObjectContents(S const& serializer, T* value, void*)
+{
+ serialize(serializer, *value);
+}
- using Cursor = RIFF::BoundsCheckedChunkPtr;
-
- struct WithArray
- {
- public:
- WithArray(Decoder& decoder)
- : _decoder(decoder)
- {
- _saved = decoder._cursor;
- decoder.beginArray();
- }
-
- WithArray(Decoder& decoder, FourCC typeCode)
- : _decoder(decoder)
- {
- _saved = decoder._cursor;
- decoder.beginArray(typeCode);
- }
-
- ~WithArray() { _decoder._cursor = _saved.getNextSibling(); }
-
- private:
- Cursor _saved;
- Decoder& _decoder;
- };
-
- struct WithObject
- {
- public:
- WithObject(Decoder& decoder)
- : _decoder(decoder)
- {
- _saved = decoder._cursor;
- decoder.beginObject();
- }
-
- WithObject(Decoder& decoder, FourCC typeCode)
- : _decoder(decoder)
- {
- _saved = decoder._cursor;
- decoder.beginObject(typeCode);
- }
-
- ~WithObject() { _decoder._cursor = _saved.getNextSibling(); }
-
- private:
- Cursor _saved;
- Decoder& _decoder;
- };
-
- struct WithKeyValuePair
- {
- public:
- WithKeyValuePair(Decoder& decoder)
- : _decoder(decoder)
- {
- _saved = decoder._cursor;
- decoder.beginKeyValuePair();
- }
-
- WithKeyValuePair(Decoder& decoder, FourCC typeCode)
- : _decoder(decoder)
- {
- _saved = decoder._cursor;
- _decoder.beginKeyValuePair(typeCode);
- }
-
- ~WithKeyValuePair() { _decoder._cursor = _saved.getNextSibling(); }
-
- private:
- Cursor _saved;
- Decoder& _decoder;
- };
+template<typename S, typename T>
+void _serializeObjectContentsCallback(void* valuePtr, void* userData)
+{
+ auto serializerImpl = (S*)userData;
+ auto value = (T*)valuePtr;
+ serializeObjectContents(Serializer_<S>(serializerImpl), value, (T*)nullptr);
+}
- struct WithProperty
+template<typename S, typename T>
+void deferSerializeObjectContents(Serializer_<S> const& serializer, T* value)
+{
+ ((Serializer)serializer)
+ ->handleDeferredObjectContents(
+ value,
+ _serializeObjectContentsCallback<S, T>,
+ serializer.get());
+}
+
+template<typename S, typename T>
+void serializeObject(S const& serializer, T*& value, void*)
+{
+ if (isReading(serializer))
{
- public:
- WithProperty(Decoder& decoder, FourCC typeCode)
- : _decoder(decoder)
- {
- _saved = decoder._cursor;
- _decoder.beginProperty(typeCode);
- }
-
- ~WithProperty() { _decoder._cursor = _saved.getNextSibling(); }
+ value = new T();
+ }
+ deferSerializeObjectContents(serializer, value);
+}
- private:
- Cursor _saved;
- Decoder& _decoder;
- };
+template<typename S, typename T>
+void _serializeObjectCallback(void* valuePtr, void* userData)
+{
+ auto serializerImpl = (S*)userData;
+ auto& value = *(T**)valuePtr;
+ serializeObject(Serializer_<S>(serializerImpl), value, (T*)nullptr);
+}
- Cursor getCursor() const { return _cursor; }
- void setCursor(Cursor const& cursor) { _cursor = cursor; }
+template<typename S, typename T>
+void serializeSharedPtr(Serializer_<S> const& serializer, T*& value)
+{
+ ((Serializer)serializer)
+ ->handleSharedPtr(*(void**)&value, _serializeObjectCallback<S, T>, serializer.get());
+}
- RIFF::Chunk const* getCurrentChunk() const { return getCursor(); }
+template<typename S, typename T>
+void serializeUniquePtr(Serializer_<S> const& serializer, T*& value)
+{
+ ((Serializer)serializer)
+ ->handleUniquePtr(*(void**)&value, _serializeObjectCallback<S, T>, serializer.get());
+}
-private:
- void _advanceCursor() { _cursor = _cursor.getNextSibling(); }
+template<typename S, typename T>
+void serializePtr(S const& serializer, T*& value, void*)
+{
+ serializeSharedPtr(serializer, value);
+}
- Cursor _cursor;
-};
+template<typename S, typename T>
+void serialize(S const& serializer, T*& value)
+{
+ serializePtr(serializer, value, (T*)nullptr);
+}
+template<typename S, typename T>
+void serialize(S const& serializer, RefPtr<T>& value)
+{
+ T* raw = value;
+ serialize(serializer, raw);
+ value = raw;
+}
} // namespace Slang