summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2025-07-08 10:36:52 +0800
committerGitHub <noreply@github.com>2025-07-08 02:36:52 +0000
commit69947dec841ea46e68ccdccae45a1080fcaea01c (patch)
treef8208b40d2baab7e914b396d04f08d30ffb63105 /source
parent3865a6596afca1c193eb17bbb74008077096e7c3 (diff)
Use fossil for IR serialization (#7619)
* bottleneck ir module reading and writing * compute/simple working * more complex tests working * neaten * factor out SourceLoc serialization * document changes * Appease clang * Correct name serialization * remove unnecessary code * neaten * neaten
Diffstat (limited to 'source')
-rw-r--r--source/slang/slang-ir.h3
-rw-r--r--source/slang/slang-serialize-ast.cpp91
-rw-r--r--source/slang/slang-serialize-container.cpp103
-rw-r--r--source/slang/slang-serialize-container.h10
-rw-r--r--source/slang/slang-serialize-ir-types.h3
-rw-r--r--source/slang/slang-serialize-ir.cpp983
-rw-r--r--source/slang/slang-serialize-ir.h127
-rw-r--r--source/slang/slang-serialize-source-loc.h76
-rw-r--r--source/slang/slang-serialize-types.h7
-rw-r--r--source/slang/slang.cpp4
10 files changed, 483 insertions, 924 deletions
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index bd16eb1b9..c29a52ff7 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -2442,6 +2442,9 @@ public:
ContainerPool& getContainerPool() { return m_containerPool; }
private:
+ friend struct IRSerialReadContext;
+ friend struct IRSerialWriteContext;
+
IRModule() = delete;
/// Ctor
diff --git a/source/slang/slang-serialize-ast.cpp b/source/slang/slang-serialize-ast.cpp
index 1c05e3ce2..325aa3244 100644
--- a/source/slang/slang-serialize-ast.cpp
+++ b/source/slang/slang-serialize-ast.cpp
@@ -7,10 +7,12 @@
#include "slang-diagnostics.h"
#include "slang-mangle.h"
#include "slang-parser.h"
-#include "slang-serialize-ast.cpp.fiddle"
#include "slang-serialize-fossil.h"
#include "slang-serialize-riff.h"
+//
+#include "slang-serialize-ast.cpp.fiddle"
+
#define SLANG_ENABLE_AST_DESERIALIZATION_STATS 0
#define SLANG_DISABLE_ON_DEMAND_AST_DESERIALIZATION 1
@@ -345,13 +347,12 @@ struct ASTSerialContext;
using ASTSerializer = Serializer_<ISerializerImpl, ASTSerialContext>;
/// Context interface for AST serialization
-struct ASTSerialContext
+struct ASTSerialContext : SourceLocSerialContext
{
public:
virtual void handleASTNode(ASTSerializer const& serializer, NodeBase*& value) = 0;
virtual void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* value) = 0;
virtual void handleName(ASTSerializer const& serializer, Name*& value) = 0;
- virtual void handleSourceLoc(ASTSerializer const& serializer, SourceLoc& value) = 0;
virtual void handleToken(ASTSerializer const& serializer, Token& value) = 0;
virtual void handleContainerDeclDirectMemberDecls(
ASTSerializer const& serializer,
@@ -552,13 +553,13 @@ private:
//
virtual void handleName(ASTSerializer const& serializer, Name*& value) override;
- virtual void handleSourceLoc(ASTSerializer const& serializer, SourceLoc& value) override;
virtual void handleToken(ASTSerializer const& serializer, Token& value) override;
virtual void handleASTNode(ASTSerializer const& serializer, NodeBase*& node) override;
virtual void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node) override;
virtual void handleContainerDeclDirectMemberDecls(
ASTSerializer const& serializer,
ContainerDeclDirectMemberDecls& value) override;
+ virtual SerialSourceLocWriter* getSourceLocWriter() override { return _sourceLocWriter; }
void _writeImportedModule(ASTSerializer const& serializer, ModuleDecl* moduleDecl);
void _writeImportedDecl(
@@ -695,13 +696,13 @@ private:
//
virtual void handleName(ASTSerializer const& serializer, Name*& value) override;
- virtual void handleSourceLoc(ASTSerializer const& serializer, SourceLoc& value) override;
virtual void handleToken(ASTSerializer const& serializer, Token& value) override;
virtual void handleASTNode(ASTSerializer const& serializer, NodeBase*& outNode) override;
virtual void handleASTNodeContents(ASTSerializer const& serializer, NodeBase* node) override;
virtual void handleContainerDeclDirectMemberDecls(
ASTSerializer const& serializer,
ContainerDeclDirectMemberDecls& value) override;
+ virtual SerialSourceLocReader* getSourceLocReader() override { return _sourceLocReader; }
ModuleDecl* _readImportedModule(ASTSerializer const& serializer);
NodeBase* _readImportedDecl(ASTSerializer const& serializer);
@@ -711,86 +712,6 @@ private:
};
//
-// Let's look at a concrete example of how the `ASTSerialReadContext`
-// and `ASTSerialWriteContext` get applied to handle one of the types
-// that needs them for additional context.
-//
-// The `serialize()` function for `SourceLoc` is declared to take
-// an `ASTSerializer` argument instead of a simple `Serializer`:
-//
-void serialize(ASTSerializer const& serializer, SourceLoc& value)
-{
- // Its body is trivial, because the actual handling of `SourceLoc`
- // serialization is delegated to the `ASTSerialWriteContext` and
- // `ASTSerialReadContext`.
- //
- serializer.getContext()->handleSourceLoc(serializer, value);
-}
-
-void ASTSerialWriteContext::handleSourceLoc(ASTSerializer const& serializer, SourceLoc& value)
-{
- // Writing of source location information can be disabled by
- // compiler options, and in that case the `_sourceLocWriter`
- // may be null.
- //
- // In order to handle that possibility, we serialize a `SourceLoc`
- // as an optional value, dependent on whether we have a
- // `_sourceLocWriter` that can be used.
- //
- SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
- if (_sourceLocWriter != nullptr)
- {
- // The `SourceLoc` type is implemented under the hood as an
- // integer offset that can only be decoded using the specific
- // `SourceManager` that created it.
- //
- // The source location writer handles the task of translating
- // the under-the-hood representation to a single integer value
- // (represented as `SerialSourceLocData::SourceLoc`) that can
- // be decoded on the other side using other data that the
- // source location writer will write out as part of its own
- // representation (all of which goes into the dedicated debug
- // data chunk, distinct from the AST).
- //
- SerialSourceLocData::SourceLoc rawValue = _sourceLocWriter->addSourceLoc(value);
- serialize(serializer, rawValue);
- }
-}
-
-void ASTSerialReadContext::handleSourceLoc(ASTSerializer const& serializer, SourceLoc& value)
-{
- // Because the source location was *written* as an optional,
- // we clearly need to *read* it as one.
- //
- SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
- if (hasElements(serializer))
- {
- SerialSourceLocData::SourceLoc rawValue;
- serialize(serializer, rawValue);
-
- // Even if the serialized optional had a value, it is
- // possible that the debug-data chunk got stripped from
- // the compiled module file, in which case we wouldn't
- // have access to the data needed to decode it.
- //
- // In that case, the `_sourceLocReader` member would be
- // null, so we handle that possibility here.
- //
- if (auto sourceLocReader = _sourceLocReader)
- {
- value = sourceLocReader->getSourceLoc(rawValue);
- }
- }
-}
-
-// Now that we've seen the relevant serialization logic, it is clear that
-// a `SourceLoc` gets fossilized the same way that an optional wrapping
-// an integer (of type `SerialSourceLocData::SourceLoc`) would.
-//
-SLANG_DECLARE_FOSSILIZED_AS(SourceLoc, std::optional<SerialSourceLocData::SourceLoc>);
-
-
-//
// Earlier we generated forward declarations for all of the types
// that we'll be able to handle with fiddle, but there are still a
// large number of types that we currently have to hand-write the
diff --git a/source/slang/slang-serialize-container.cpp b/source/slang/slang-serialize-container.cpp
index 1d7f6d438..d4d1fc3ad 100644
--- a/source/slang/slang-serialize-container.cpp
+++ b/source/slang/slang-serialize-container.cpp
@@ -12,6 +12,7 @@
#include "slang-serialize-ast.h"
#include "slang-serialize-ir.h"
#include "slang-serialize-source-loc.h"
+#include "slang-serialize-types.h"
namespace Slang
{
@@ -137,11 +138,10 @@ public:
IRModule* irModule = targetProgram->getOrCreateIRModuleForLayout(sink);
// Okay, we need to serialize this target program and its IR too...
- IRSerialData serialData;
- IRSerialWriter writer;
-
- SLANG_RETURN_ON_FAIL(writer.write(irModule, _sourceLocWriter, &serialData));
- SLANG_RETURN_ON_FAIL(IRSerialWriter::writeTo(serialData, _cursor));
+ {
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, PropertyKeys<IRModule>::IRModule);
+ writeSerializedModuleIR(_cursor, irModule, _sourceLocWriter);
+ }
return SLANG_OK;
}
@@ -216,10 +216,8 @@ public:
//
if (auto irModule = module->getIRModule())
{
- IRSerialData serialData;
- IRSerialWriter writer;
- SLANG_RETURN_ON_FAIL(writer.write(irModule, _sourceLocWriter, &serialData));
- SLANG_RETURN_ON_FAIL(IRSerialWriter::writeTo(serialData, _cursor));
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(_cursor, PropertyKeys<IRModule>::IRModule);
+ writeSerializedModuleIR(_cursor, irModule, _sourceLocWriter);
}
// If we have AST information available, then we serialize it here.
@@ -457,11 +455,11 @@ String ModuleChunk::getName() const
IRModuleChunk const* ModuleChunk::findIR() const
{
- auto foundChunk = findListChunk(IRSerialBinary::kIRModuleFourCc);
- if (!foundChunk)
+ auto foundProperty = findListChunk(PropertyKeys<IRModule>::IRModule);
+ if (!foundProperty)
return nullptr;
- return static_cast<IRModuleChunk const*>(foundChunk);
+ return static_cast<IRModuleChunk const*>(foundProperty->getFirstChild().get());
}
ASTModuleChunk const* ModuleChunk::findAST() const
@@ -576,37 +574,38 @@ SlangResult readSourceLocationsFromDebugChunk(
return SLANG_OK;
}
-SlangResult decodeModuleIR(
- RefPtr<IRModule>& outIRModule,
- IRModuleChunk const* chunk,
- Session* session,
- SerialSourceLocReader* sourceLocReader)
+static void calcModuleInstructionList(IRModule* module, List<IRInst*>& instsOut)
{
- // IR serialization still uses the older approach, where
- // data gets deserialized from the RIFF into an intermediate
- // data structure (`IRSerialData`), and then the actual
- // in-memory structures are created based on the intermediate.
- //
- // Thus we start by running the `IRSerialReader::readContainer`
- // logic to get the `IRSerialData` representation.
- //
- // TODO(tfoley): This should all get streamlined so that we
- // are deserializing IR nodes directly from the format written
- // into the RIFF.
- //
- IRSerialData serialData;
- SLANG_RETURN_ON_FAIL(IRSerialReader::readFrom(chunk, &serialData));
+ // We reserve 0 for null
+ instsOut.setCount(1);
+ instsOut[0] = nullptr;
- // Next we read the actual IR representation out from the
- // `serialData`. This is the step that may pull source-location
- // information from the provided `sourceLocReader`.
- //
- IRSerialReader reader;
- SLANG_RETURN_ON_FAIL(reader.read(serialData, session, sourceLocReader, outIRModule));
+ // Stack for parentInst
+ List<IRInst*> parentInstStack;
- return SLANG_OK;
+ IRModuleInst* moduleInst = module->getModuleInst();
+ parentInstStack.add(moduleInst);
+
+ // Add to list
+ instsOut.add(moduleInst);
+
+ // Traverse all of the instructions
+ while (parentInstStack.getCount())
+ {
+ // If it's in the stack it is assumed it is already in the inst map
+ IRInst* parentInst = parentInstStack.getLast();
+ parentInstStack.removeLast();
+
+ IRInstListBase childrenList = parentInst->getDecorationsAndChildren();
+ for (IRInst* child : childrenList)
+ {
+ instsOut.add(child);
+ parentInstStack.add(child);
+ }
+ }
}
+
/* static */ SlangResult SerialContainerUtil::verifyIRSerialize(
IRModule* module,
Session* session,
@@ -615,9 +614,7 @@ SlangResult decodeModuleIR(
// Verify if we can stream out with raw source locs
List<IRInst*> originalInsts;
- IRSerialWriter::calcInstructionList(module, originalInsts);
-
- IRSerialData irData;
+ calcModuleInstructionList(module, originalInsts);
OwnedMemoryStream memoryStream(FileAccess::ReadWrite);
@@ -627,7 +624,6 @@ SlangResult decodeModuleIR(
// Need to put all of this in a module chunk
SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, SerialBinary::kModuleFourCC);
-
RefPtr<SerialSourceLocWriter> sourceLocWriter;
if (options.sourceManagerToUseWhenSerializingSourceLocs)
@@ -637,11 +633,9 @@ SlangResult decodeModuleIR(
}
{
- // Write IR out to `irData`
- IRSerialWriter writer;
- SLANG_RETURN_ON_FAIL(writer.write(module, sourceLocWriter, &irData));
+ SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, PropertyKeys<IRModule>::IRModule);
+ writeSerializedModuleIR(cursor, module, sourceLocWriter);
}
- SLANG_RETURN_ON_FAIL(IRSerialWriter::writeTo(irData, cursor));
// Write the debug info Riff container
if (sourceLocWriter)
@@ -703,25 +697,12 @@ SlangResult decodeModuleIR(
return SLANG_FAIL;
}
- {
- IRSerialData irReadData;
- IRSerialReader reader;
- SLANG_RETURN_ON_FAIL(reader.readFrom(irChunk, &irReadData));
-
- // Check the stream read data is the same
- if (irData != irReadData)
- {
- SLANG_ASSERT(!"Streamed in data doesn't match");
- return SLANG_FAIL;
- }
-
- SLANG_RETURN_ON_FAIL(reader.read(irData, session, sourceLocReader, irReadModule));
- }
+ readSerializedModuleIR(irChunk, session, sourceLocReader, irReadModule);
}
}
List<IRInst*> readInsts;
- IRSerialWriter::calcInstructionList(irReadModule, readInsts);
+ calcModuleInstructionList(irReadModule, readInsts);
if (readInsts.getCount() != originalInsts.getCount())
{
diff --git a/source/slang/slang-serialize-container.h b/source/slang/slang-serialize-container.h
index 1608b085c..2a3ecef75 100644
--- a/source/slang/slang-serialize-container.h
+++ b/source/slang/slang-serialize-container.h
@@ -68,7 +68,9 @@ public:
String getValue() const;
};
-struct IRModuleChunk;
+struct IRModuleChunk : RIFF::ListChunk
+{
+};
struct ASTModuleChunk : RIFF::ListChunk
{
@@ -129,12 +131,6 @@ SlangResult readSourceLocationsFromDebugChunk(
SourceManager* sourceManager,
RefPtr<SerialSourceLocReader>& outReader);
-SlangResult decodeModuleIR(
- RefPtr<IRModule>& outIRModule,
- IRModuleChunk const* chunk,
- Session* session,
- SerialSourceLocReader* sourceLocReader);
-
} // namespace Slang
#endif
diff --git a/source/slang/slang-serialize-ir-types.h b/source/slang/slang-serialize-ir-types.h
index d4c9dd9d7..dea45d72f 100644
--- a/source/slang/slang-serialize-ir-types.h
+++ b/source/slang/slang-serialize-ir-types.h
@@ -19,9 +19,6 @@ class Name;
struct IRSerialBinary
{
- /// IR module list
- static const FourCC::RawValue kIRModuleFourCc = SLANG_FOUR_CC('S', 'i', 'm', 'd');
-
/* NOTE! All FourCC that can be compressed must start with capital 'S', because compressed
version is the same FourCC with the 'S' replaced with 's' */
diff --git a/source/slang/slang-serialize-ir.cpp b/source/slang/slang-serialize-ir.cpp
index 093643d09..52a0a6537 100644
--- a/source/slang/slang-serialize-ir.cpp
+++ b/source/slang/slang-serialize-ir.cpp
@@ -1,761 +1,436 @@
// slang-serialize-ir.cpp
#include "slang-serialize-ir.h"
-#include "../core/slang-byte-encode-util.h"
-#include "../core/slang-math.h"
-#include "../core/slang-text-io.h"
+#include "core/slang-blob-builder.h"
#include "slang-ir-insts.h"
+#include "slang-ir-validate.h"
+#include "slang-serialize-fossil.h"
+#include "slang-serialize-source-loc.h"
+#include "slang-serialize.h"
namespace Slang
{
-static bool _isConstant(IROp opIn)
+//
+// We wrap everything up in an IRModuleInfo, to prepare for the case in which
+// we want to serialize some sidecar information to help with on-demand loading
+// or backwards compat
+//
+struct IRModuleInfo
{
- const int op = (kIROpMask_OpMask & opIn);
- return op >= kIROp_FirstConstant && op <= kIROp_LastConstant;
-}
+ RefPtr<IRModule> module;
+};
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! IRSerialWriter !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+//
+// We need some small amount of additional context to serialize IR Modules, keep track of that here
+//
+struct IRSerialContext;
+using IRSerializer = Serializer_<ISerializerImpl, IRSerialContext>;
-void IRSerialWriter::_addInstruction(IRInst* inst)
+struct IRSerialContext : SourceLocSerialContext
{
- // It cannot already be in the map
- SLANG_ASSERT(!m_instMap.containsKey(inst));
-
- // Add to the map
- m_instMap.add(inst, Ser::InstIndex(m_insts.getCount()));
- m_insts.add(inst);
-}
+public:
+ virtual void handleIRModule(IRSerializer const& serializer, IRModule*& value) = 0;
+ virtual void handleName(IRSerializer const& serializer, Name*& value) = 0;
+};
-Result IRSerialWriter::_calcDebugInfo(SerialSourceLocWriter* sourceLocWriter)
+struct IRSerialWriteContext : IRSerialContext
{
- // We need to find the unique source Locs
- // We are not going to store SourceLocs directly, because there may be multiple views mapping
- // down to the same underlying source file
-
- // First find all the unique locs
- struct InstLoc
+ IRSerialWriteContext(SerialSourceLocWriter* sourceLocWriter)
+ : _sourceLocWriter(sourceLocWriter)
{
- typedef InstLoc ThisType;
-
- SLANG_FORCE_INLINE bool operator<(const ThisType& rhs) const
- {
- return sourceLoc < rhs.sourceLoc ||
- (sourceLoc == rhs.sourceLoc && instIndex < rhs.instIndex);
- }
-
- uint32_t instIndex;
- uint32_t sourceLoc;
- };
-
- // Find all of the source locations and their associated instructions
- List<InstLoc> instLocs;
- const Index numInsts = m_insts.getCount();
- for (Index i = 1; i < numInsts; i++)
- {
- IRInst* srcInst = m_insts[i];
- if (!srcInst->sourceLoc.isValid())
- {
- continue;
- }
- InstLoc instLoc;
- instLoc.instIndex = uint32_t(i);
- instLoc.sourceLoc = uint32_t(srcInst->sourceLoc.getRaw());
- instLocs.add(instLoc);
}
- // Sort them
- instLocs.sort();
+ virtual void handleIRModule(IRSerializer const& serializer, IRModule*& value) override;
+ virtual void handleName(IRSerializer const& serializer, Name*& value) override;
+ virtual SerialSourceLocWriter* getSourceLocWriter() override { return _sourceLocWriter; }
- // Look for runs
- const InstLoc* startInstLoc = instLocs.begin();
- const InstLoc* endInstLoc = instLocs.end();
+ SerialSourceLocWriter* _sourceLocWriter;
+};
- while (startInstLoc < endInstLoc)
+struct IRSerialReadContext : IRSerialContext, RefObject
+{
+ IRSerialReadContext(Session* session, SerialSourceLocReader* sourceLocReader)
+ : _session(session), _sourceLocReader(sourceLocReader)
{
- const uint32_t startSourceLoc = startInstLoc->sourceLoc;
-
- // Find the run with the same source loc
-
- const InstLoc* curInstLoc = startInstLoc + 1;
- uint32_t curInstIndex = startInstLoc->instIndex + 1;
-
- // Find the run size with same source loc and run of instruction indices
- for (; curInstLoc < endInstLoc && curInstLoc->sourceLoc == startSourceLoc &&
- curInstLoc->instIndex == curInstIndex;
- ++curInstLoc, ++curInstIndex)
- {
- }
-
- // Add the run
+ }
+ virtual void handleIRModule(IRSerializer const& serializer, IRModule*& value) override;
+ virtual void handleName(IRSerializer const& serializer, Name*& value) override;
+ virtual SerialSourceLocReader* getSourceLocReader() override { return _sourceLocReader; }
- IRSerialData::SourceLocRun sourceLocRun;
- sourceLocRun.m_numInst = curInstIndex - startInstLoc->instIndex;
- ;
- sourceLocRun.m_startInstIndex = IRSerialData::InstIndex(startInstLoc->instIndex);
- sourceLocRun.m_sourceLoc =
- sourceLocWriter->addSourceLoc(SourceLoc::fromRaw(startSourceLoc));
+ // Used to allocate an IRModule
+ Session* _session;
- m_serialData->m_debugSourceLocRuns.add(sourceLocRun);
+ //
+ SerialSourceLocReader* _sourceLocReader;
- // Next
- startInstLoc = curInstLoc;
- }
+ // The module in which we will allocate our instructions
+ RefPtr<IRModule> _module;
+};
- return SLANG_OK;
+// IROps are serialized as integers
+SLANG_DECLARE_FOSSILIZED_AS(IROp, FossilUInt);
+void serialize(Serializer const& serializer, IROp& value)
+{
+ serializeEnum(serializer, value);
}
-Result IRSerialWriter::write(
- IRModule* module,
- SerialSourceLocWriter* sourceLocWriter,
- IRSerialData* serialData)
+/// Serialize a `value` of type `IRModuleInfo`, currently no extra information
+/// besides the IRModule
+SLANG_DECLARE_FOSSILIZED_AS_MEMBER(IRModuleInfo, module);
+void serialize(IRSerializer const& serializer, IRModuleInfo& value)
{
- typedef Ser::Inst::PayloadType PayloadType;
-
- m_serialData = serialData;
-
- serialData->clear();
-
- // We reserve 0 for null
- m_insts.clear();
- m_insts.add(nullptr);
-
- // Reset
- m_instMap.clear();
- m_decorations.clear();
-
- // Stack for parentInst
- List<IRInst*> parentInstStack;
-
- IRModuleInst* moduleInst = module->getModuleInst();
- parentInstStack.add(moduleInst);
-
- // Add to the map
- _addInstruction(moduleInst);
-
- // Traverse all of the instructions
- while (parentInstStack.getCount())
- {
- // If it's in the stack it is assumed it is already in the inst map
- IRInst* parentInst = parentInstStack.getLast();
- parentInstStack.removeLast();
- SLANG_ASSERT(m_instMap.containsKey(parentInst));
-
- // Okay we go through each of the children in order. If they are IRInstParent derived, we
- // add to stack to process later cos we want breadth first so the order of children is the
- // same as their index order, meaning we don't need to store explicit indices
- const Ser::InstIndex startChildInstIndex = Ser::InstIndex(m_insts.getCount());
-
- IRInstListBase childrenList = parentInst->getDecorationsAndChildren();
- for (IRInst* child : childrenList)
- {
- // This instruction can't be in the map...
- SLANG_ASSERT(!m_instMap.containsKey(child));
-
- _addInstruction(child);
-
- parentInstStack.add(child);
- }
+ serialize(serializer, value.module);
+}
- // If it had any children, then store the information about it
- if (Ser::InstIndex(m_insts.getCount()) != startChildInstIndex)
- {
- Ser::InstRun run;
- run.m_parentIndex = m_instMap[parentInst];
- run.m_startInstIndex = startChildInstIndex;
- run.m_numChildren = Ser::SizeType(m_insts.getCount() - int(startChildInstIndex));
+//
+// Serialized linked list of child instructions as regular lists, we can fix up
+// the pointers on deserialization
+//
+SLANG_DECLARE_FOSSILIZED_AS(IRInstListBase, List<IRInst*>);
- m_serialData->m_childRuns.add(run);
- }
- }
+void serialize(IRSerializer const& serializer, IRInstListBase& value)
+{
+ SLANG_SCOPED_SERIALIZER_ARRAY(serializer);
-#if 0
+ if (isWriting(serializer))
{
- List<IRInst*> workInsts;
- calcInstructionList(module, workInsts);
- SLANG_ASSERT(workInsts.getCount() == m_insts.getCount());
- for (UInt i = 0; i < workInsts.getCount(); ++i)
+ for (auto inst : value)
{
- SLANG_ASSERT(workInsts[i] == m_insts[i]);
+ serialize(serializer, inst);
}
}
-#endif
-
- // Set to the right size
- m_serialData->m_insts.setCount(m_insts.getCount());
- // Clear all instructions
- memset(m_serialData->m_insts.begin(), 0, sizeof(Ser::Inst) * m_serialData->m_insts.getCount());
-
- // Need to set up the actual instructions
+ else
{
- const Index numInsts = m_insts.getCount();
+ IRInst* first = nullptr;
+ IRInst* prev = nullptr;
- for (Index i = 1; i < numInsts; ++i)
+ while (hasElements(serializer))
{
- IRInst* srcInst = m_insts[i];
- Ser::Inst& dstInst = m_serialData->m_insts[i];
-
- dstInst.m_op = uint16_t(srcInst->getOp() & kIROpMask_OpMask);
- dstInst.m_payloadType = PayloadType::Empty;
-
- dstInst.m_resultTypeIndex = getInstIndex(srcInst->getFullType());
+ IRInst* inst = nullptr;
+ serialize(serializer, inst);
+ first = first ? first : inst;
- IRConstant* irConst = as<IRConstant>(srcInst);
- if (irConst)
- {
- switch (srcInst->getOp())
- {
- // Special handling for the ir const derived types
- case kIROp_BlobLit:
- {
- // Blobs are serialized into string table like strings
- auto stringLit = static_cast<IRBlobLit*>(srcInst);
- dstInst.m_payloadType = PayloadType::String_1;
- dstInst.m_payload.m_stringIndices[0] =
- getStringIndex(stringLit->getStringSlice());
- break;
- }
- case kIROp_StringLit:
- {
- auto stringLit = static_cast<IRStringLit*>(srcInst);
- dstInst.m_payloadType = PayloadType::String_1;
- dstInst.m_payload.m_stringIndices[0] =
- getStringIndex(stringLit->getStringSlice());
- break;
- }
- case kIROp_IntLit:
- {
- dstInst.m_payloadType = PayloadType::Int64;
- dstInst.m_payload.m_int64 = irConst->value.intVal;
- break;
- }
- case kIROp_PtrLit:
- {
- dstInst.m_payloadType = PayloadType::Int64;
- dstInst.m_payload.m_int64 = (intptr_t)irConst->value.ptrVal;
- break;
- }
- case kIROp_FloatLit:
- {
- dstInst.m_payloadType = PayloadType::Float64;
- dstInst.m_payload.m_float64 = irConst->value.floatVal;
- break;
- }
- case kIROp_BoolLit:
- {
- dstInst.m_payloadType = PayloadType::UInt32;
- dstInst.m_payload.m_uint32 = irConst->value.intVal ? 1 : 0;
- break;
- }
- case kIROp_VoidLit:
- {
- dstInst.m_payloadType = PayloadType::Empty;
- break;
- }
- default:
- {
- SLANG_RELEASE_ASSERT(!"Unhandled constant type");
- return SLANG_FAIL;
- }
- }
- continue;
- }
-
- // ModuleInst is different, in so far as it holds a pointer to IRModule, but we don't
- // need to save that off in a special way, so can just use regular path
-
- const int numOperands = int(srcInst->operandCount);
- Ser::InstIndex* dstOperands = nullptr;
-
- if (numOperands <= Ser::Inst::kMaxOperands)
- {
- // Checks the compile below is valid
- SLANG_COMPILE_TIME_ASSERT(
- PayloadType(0) == PayloadType::Empty &&
- PayloadType(1) == PayloadType::Operand_1 &&
- PayloadType(2) == PayloadType::Operand_2);
-
- dstInst.m_payloadType = PayloadType(numOperands);
- dstOperands = dstInst.m_payload.m_operands;
- }
- else
+ if (prev)
{
- dstInst.m_payloadType = PayloadType::OperandExternal;
-
- int operandArrayBaseIndex = int(m_serialData->m_externalOperands.getCount());
- m_serialData->m_externalOperands.setCount(operandArrayBaseIndex + numOperands);
-
- dstOperands = m_serialData->m_externalOperands.begin() + operandArrayBaseIndex;
-
- auto& externalOperands = dstInst.m_payload.m_externalOperand;
- externalOperands.m_arrayIndex = Ser::ArrayIndex(operandArrayBaseIndex);
- externalOperands.m_size = Ser::SizeType(numOperands);
+ prev->next = inst;
}
- for (int j = 0; j < numOperands; ++j)
- {
- const Ser::InstIndex dstInstIndex = getInstIndex(srcInst->getOperand(j));
- dstOperands[j] = dstInstIndex;
- }
+ inst->prev = prev;
+ prev = inst;
}
+ if (prev)
+ {
+ prev->next = nullptr;
+ }
+ value = IRInstListBase(first, prev);
}
-
- // Convert strings into a string table
- {
- SerialStringTableUtil::encodeStringTable(m_stringSlicePool, serialData->m_stringTable);
- }
-
- if (sourceLocWriter)
- {
- _calcDebugInfo(sourceLocWriter);
- }
-
- m_serialData = nullptr;
- return SLANG_OK;
-}
-
-Result _writeInstArrayChunk(
- FourCC chunkId,
- const List<IRSerialData::Inst>& array,
- RIFF::BuildCursor& cursor)
-{
- if (array.getCount() == 0)
- {
- return SLANG_OK;
- }
-
- return SerialRiffUtil::writeArrayChunk(chunkId, array, cursor);
}
-/* static */ Result IRSerialWriter::writeTo(const IRSerialData& data, RIFF::BuildCursor& cursor)
+//
+// Initializing an IRUse requires a small bit of special setup, handle that
+// here
+//
+void serializeUse(IRSerializer const& serializer, IRInst* user, IRUse& use)
{
- SLANG_SCOPED_RIFF_BUILDER_LIST_CHUNK(cursor, Bin::kIRModuleFourCc);
-
- SLANG_RETURN_ON_FAIL(_writeInstArrayChunk(Bin::kInstFourCc, data.m_insts, cursor));
- SLANG_RETURN_ON_FAIL(
- SerialRiffUtil::writeArrayChunk(Bin::kChildRunFourCc, data.m_childRuns, cursor));
- SLANG_RETURN_ON_FAIL(SerialRiffUtil::writeArrayChunk(
- Bin::kExternalOperandsFourCc,
- data.m_externalOperands,
- cursor));
- SLANG_RETURN_ON_FAIL(SerialRiffUtil::writeArrayChunk(
- SerialBinary::kStringTableFourCc,
- data.m_stringTable,
- cursor));
-
- SLANG_RETURN_ON_FAIL(SerialRiffUtil::writeArrayChunk(
- Bin::kUInt32RawSourceLocFourCc,
- data.m_rawSourceLocs,
- cursor));
-
- if (data.m_debugSourceLocRuns.getCount())
+ SLANG_ASSERT(user);
+ IRInst* used = isWriting(serializer) ? use.get() : nullptr;
+ serialize(serializer, used);
+ if (isReading(serializer))
{
- SerialRiffUtil::writeArrayChunk(
- Bin::kDebugSourceLocRunFourCc,
- data.m_debugSourceLocRuns,
- cursor);
+ use.init(user, used);
}
-
- return SLANG_OK;
}
-/* static */ void IRSerialWriter::calcInstructionList(IRModule* module, List<IRInst*>& instsOut)
+template<typename T>
+void serializeObject(IRSerializer const& serializer, T*& inst, IRInst*)
{
- // We reserve 0 for null
- instsOut.setCount(1);
- instsOut[0] = nullptr;
+ // Each IR instruction has:
+ //
+ // * An opcode
+ // * Zero or more operands
+ // * Zero or more children
+ //
+ // Most instructions are entirely defined by those properties.
+ //
+ // The instructions that represent simple constants (integers, strings, etc.) are
+ // unique in that they have "payload" data that holds their value, instead of having
+ // any operands.
+ //
+ // Note that as a result of the serialization strategy used by fossil, it
+ // is not possible for the deserialization logic to interact with any
+ // systems for deduplication or simplification of instructions.
- // Stack for parentInst
- List<IRInst*> parentInstStack;
+ SLANG_SCOPED_SERIALIZER_VARIANT(serializer);
- IRModuleInst* moduleInst = module->getModuleInst();
- parentInstStack.add(moduleInst);
+ //
+ // Since we're calling deferSerializeObjectContents at the end of this
+ // function we need only serialize/deserialize enough to allocate the
+ // instruction itself,
+ //
+ // For most instructions this is simply the operand count, however for a
+ // couple of exceptions (IRModuleInst and anything under IRConstant) we
+ // may need to allocate more space, so first find out what sort of
+ // instruction it is.
+ //
+ IROp op = isWriting(serializer) ? inst->m_op : kIROp_Invalid;
+ uint32_t operandCount = isWriting(serializer) ? inst->operandCount : ~0;
+ serialize(serializer, op);
+ serialize(serializer, operandCount);
- // Add to list
- instsOut.add(moduleInst);
+ //
+ // If it's a string literal, the data is stored inline, so we need to know
+ // the length of the string in order to allocate, handle that here, and we
+ // may as well just read the whole string for convenience.
+ //
+ String stringLitString;
+ if (op == kIROp_StringLit || op == kIROp_BlobLit)
+ {
+ if (isWriting(serializer))
+ {
+ stringLitString = cast<IRConstant>(inst)->getStringSlice();
+ }
+ serialize(serializer, stringLitString);
+ }
- // Traverse all of the instructions
- while (parentInstStack.getCount())
+ //
+ // Now we have read/written everything we need in order to allocate the inst, do so
+ // This will involve calculating the allocation size for constants also
+ //
+ if (isReading(serializer))
{
- // If it's in the stack it is assumed it is already in the inst map
- IRInst* parentInst = parentInstStack.getLast();
- parentInstStack.removeLast();
+ const auto readContext = static_cast<IRSerialReadContext*>(serializer.getContext());
- IRInstListBase childrenList = parentInst->getDecorationsAndChildren();
- for (IRInst* child : childrenList)
+ // We need to handle the special case instructions which aren't just defined by operands and
+ // children, IRModuleInst and IRConstants
+ size_t minSizeInBytes = 0;
+ switch (op)
{
- instsOut.add(child);
- parentInstStack.add(child);
+ case kIROp_ModuleInst:
+ minSizeInBytes = offsetof(IRModuleInst, module) +
+ sizeof(IRModuleInst::module); // NOLINT(bugprone-sizeof-expression)
+ break;
+ case kIROp_BoolLit:
+ case kIROp_IntLit:
+ case kIROp_FloatLit:
+ case kIROp_PtrLit:
+ case kIROp_VoidLit:
+ minSizeInBytes = offsetof(IRConstant, value) + sizeof(IRConstant::value);
+ break;
+ case kIROp_StringLit:
+ case kIROp_BlobLit:
+ minSizeInBytes = offsetof(IRConstant, value) +
+ offsetof(IRConstant::StringValue, chars) + stringLitString.getLength();
+ break;
+ }
+ inst = cast<T>(readContext->_module->_allocateInst(op, operandCount, minSizeInBytes));
+ if (op == kIROp_StringLit || op == kIROp_BlobLit)
+ {
+ const auto c = cast<IRConstant>(inst);
+ char* dstChars = c->value.stringVal.chars;
+ c->value.stringVal.numChars = uint32_t(stringLitString.getLength());
+ memcpy(dstChars, stringLitString.getBuffer(), stringLitString.getLength());
}
}
-}
-
-// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! IRSerialReader !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
-static Result _readInstArrayChunk(RIFF::DataChunk const* chunk, List<IRSerialData::Inst>& arrayOut)
-{
- SerialRiffUtil::ListResizerForType<IRSerialData::Inst> resizer(arrayOut);
- return SerialRiffUtil::readArrayChunk(chunk, resizer);
+ // We've allocated the object, we can leave the rest for later
+ deferSerializeObjectContents(serializer, inst);
}
-/* static */ Result IRSerialReader::readFrom(
- IRModuleChunk const* irModuleChunk,
- IRSerialData* outData)
+template<typename T>
+void serializeObjectContents(IRSerializer const& serializer, T*& value, IRInst*)
{
- typedef IRSerialBinary Bin;
-
- outData->clear();
-
- for (auto chunk : irModuleChunk->getChildren())
+ //
+ // This is all that's necessary for normal instructions
+ // We serialize the source location, type, operands and children
+ //
+ serialize(serializer, value->sourceLoc);
+ serializeUse(serializer, value, value->typeUse);
+ for (Index i = 0; i < value->operandCount; ++i)
{
- auto dataChunk = as<RIFF::DataChunk>(chunk);
- if (!dataChunk)
- {
- continue;
- }
+ serializeUse(serializer, value, value->getOperands()[i]);
+ }
+ // There's an overload for this call further up in this file
+ serialize(serializer, value->m_decorationsAndChildren);
- switch (dataChunk->getType())
+ //
+ // IRConstants require a little special handling
+ // IRModuleInst also has some extra information, but it's just a pointer to
+ // the IRModule value, and this is handled at the top level
+ //
+ if (const auto constant = as<IRConstant>(value))
+ {
+ switch (value->m_op)
{
- case Bin::kInstFourCc:
- {
- SLANG_RETURN_ON_FAIL(_readInstArrayChunk(dataChunk, outData->m_insts));
- break;
- }
- case Bin::kChildRunFourCc:
- {
- SLANG_RETURN_ON_FAIL(
- SerialRiffUtil::readArrayChunk(dataChunk, outData->m_childRuns));
- break;
- }
- case Bin::kExternalOperandsFourCc:
- {
- SLANG_RETURN_ON_FAIL(
- SerialRiffUtil::readArrayChunk(dataChunk, outData->m_externalOperands));
- break;
- }
- case SerialBinary::kStringTableFourCc:
+ case kIROp_BoolLit:
+ case kIROp_IntLit:
{
- SLANG_RETURN_ON_FAIL(
- SerialRiffUtil::readArrayChunk(dataChunk, outData->m_stringTable));
- break;
+ serialize(serializer, constant->value.intVal);
}
- case Bin::kUInt32RawSourceLocFourCc:
+ break;
+ case kIROp_FloatLit:
{
- SLANG_RETURN_ON_FAIL(
- SerialRiffUtil::readArrayChunk(dataChunk, outData->m_rawSourceLocs));
- break;
+ serialize(serializer, constant->value.intVal);
}
- case Bin::kDebugSourceLocRunFourCc:
+ break;
+ case kIROp_PtrLit:
{
- SLANG_RETURN_ON_FAIL(
- SerialRiffUtil::readArrayChunk(dataChunk, outData->m_debugSourceLocRuns));
- break;
+ // Clang gets upset using intptr_t here, due to long and long
+ // long being distinct types
+ auto i = reinterpret_cast<UInt64>(constant->value.ptrVal);
+ serialize(serializer, i);
+ constant->value.ptrVal = reinterpret_cast<void*>(i);
}
+ break;
+ case kIROp_StringLit:
+ case kIROp_BlobLit:
+ // Since we had to read the string anyway to get the length in
+ // serializeObject for this instruction, the string contents
+ // have already been filled in, nothing more to do here.
+ break;
+ case kIROp_VoidLit:
+ break;
default:
- {
- break;
- }
+ SLANG_UNREACHABLE("unhandled constant");
}
}
-
- return SLANG_OK;
}
-Result IRSerialReader::read(
- const IRSerialData& data,
- Session* session,
- SerialSourceLocReader* sourceLocReader,
- RefPtr<IRModule>& outModule)
+//
+// Handlers for IRModule, there is a little extra setup to do once top level
+// entries are deserialized to set up m_mapMangledNameToGlobalInst, this is
+// done at the end of readSerializedModuleIR
+//
+void serializeObject(IRSerializer const& serializer, IRModule*& value, IRModule*)
{
- // Only used in debug builds
- [[maybe_unused]] typedef Ser::Inst::PayloadType PayloadType;
+ serializer.getContext()->handleIRModule(serializer, value);
+}
- m_serialData = &data;
+void IRSerialWriteContext::handleIRModule(IRSerializer const& serializer, IRModule*& value)
+{
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ serialize(serializer, value->m_name);
+ serialize(serializer, value->m_moduleInst);
+}
- auto module = IRModule::create(session);
- outModule = module;
- m_module = module;
+void IRSerialReadContext::handleIRModule(IRSerializer const& serializer, IRModule*& value)
+{
+ SLANG_SCOPED_SERIALIZER_STRUCT(serializer);
+ value = new IRModule{_session};
+ SLANG_ASSERT(!_module);
+ _module = value;
+ serialize(serializer, value->m_name);
+ serialize(serializer, value->m_moduleInst);
+ value->m_moduleInst->module = value;
+}
- // Convert m_stringTable into StringSlicePool.
- SerialStringTableUtil::decodeStringTable(
- data.m_stringTable.getBuffer(),
- data.m_stringTable.getCount(),
- m_stringTable);
+//
+// Serialize Names via the name pool on the session, this is used just for the
+// IRModule name member.
+//
+void serializeObject(IRSerializer const& serializer, Name*& value, Name*)
+{
+ serializer.getContext()->handleName(serializer, value);
+}
- // Each IR instruction has:
- //
- // * An opcode
- // * Zero or more operands
- // * Zero or more children
- //
- // Most instructions are entirely defined by those properties.
- //
- // The instructions that represent simple constants (integers, strings, etc.) are
- // unique in that they have "payload" data that holds their value, instead of having
- // any operands.
- //
- // The deserialization logic here is set up to handle an arbitrary configuration
- // of IR instructions, which means it can handle cases where:
- //
- // * An instruction earlier in the serialized stream might refer to an instruction
- // later in the stream, as one of its operands or (transitive) children.
- //
- // * An instruction in the stream transitively depends on itself via operand
- // and/or child relationships.
- //
- // In order to handle these cases, deserialization proceeds in multiple passes.
- // In the first pass, `IRInst`s are allocated for each instruction in the stream,
- // based on their memory requirements (number of operands in the ordinary case
- // and payload size in the case of simple constants). Subsequent passes then
- // fill in the operands and/or children.
- //
- // Note that as a result of the strategy used here, it is not possible for the
- // deserialization logic to interact with any systems for deduplication or
- // simplification of instructions. An alternative version of the deserializer that
- // uses the `IRBuilder` interface instead might be possible, but would need a
- // plan for how to handle forward and/or circular references in the IR module.
+void IRSerialWriteContext::handleName(IRSerializer const& serializer, Name*& value)
+{
+ serialize(serializer, value->text);
+}
- // Add all the instructions
- List<IRInst*> insts;
+void IRSerialReadContext::handleName(IRSerializer const& serializer, Name*& value)
+{
+ String text;
+ serialize(serializer, text);
+ value = _session->getNamePool()->getName(text);
+}
- const Index numInsts = data.m_insts.getCount();
+//
+// {write,read}SerializedModuleIR()
+//
- SLANG_ASSERT(numInsts > 0);
+void writeSerializedModuleIR(
+ RIFF::BuildCursor& cursor,
+ IRModule* irModule,
+ SerialSourceLocWriter* sourceLocWriter)
+{
+ // The flow here is very similar to writeSerializedModuleAST which is very
+ // well documented.
- insts.setCount(numInsts);
- insts[0] = nullptr;
+ IRModuleInfo moduleInfo{.module = irModule};
- // 0 holds null
- // 1 holds the IRModuleInst
+ BlobBuilder blobBuilder;
{
- // Check that insts[1] is the module inst
- const Ser::Inst& srcInst = data.m_insts[1];
- SLANG_RELEASE_ASSERT(srcInst.m_op == kIROp_ModuleInst);
- SLANG_ASSERT(srcInst.m_payloadType == PayloadType::Empty);
-
- // The root IR instruction for the module will already have
- // been created as part of creating `module` above.
- //
- auto moduleInst = module->getModuleInst();
-
- // Set the IRModuleInst
- insts[1] = moduleInst;
+ Fossil::SerialWriter writer(blobBuilder);
+ IRSerialWriteContext context{sourceLocWriter};
+ IRSerializer serializer(&writer, &context);
+ serialize(serializer, moduleInfo);
}
- for (Index i = 2; i < numInsts; ++i)
- {
- const Ser::Inst& srcInst = data.m_insts[i];
-
- const IROp op((IROp)srcInst.m_op);
-
- if (_isConstant(op))
- {
- // Handling of constants
-
- // Calculate the minimum object size (ie not including the payload of value)
- const size_t prefixSize = SLANG_OFFSET_OF(IRConstant, value);
+ ComPtr<ISlangBlob> blob;
+ blobBuilder.writeToBlob(blob.writeRef());
- // All IR constants have zero operands.
- Int operandCount = 0;
-
- IRConstant* irConst = nullptr;
- switch (op)
- {
- case kIROp_BoolLit:
- {
- // TODO: Most of these cases could use the templated `_allocateInst<T>`
- // *if* we had distinct `IRConstant` subtypes to represent these
- // cases and their subtype-specific payloads.
-
- SLANG_ASSERT(srcInst.m_payloadType == PayloadType::UInt32);
- irConst = static_cast<IRConstant*>(module->_allocateInst(
- op,
- operandCount,
- prefixSize + sizeof(IRIntegerValue)));
- irConst->value.intVal = srcInst.m_payload.m_uint32 != 0;
- break;
- }
- case kIROp_IntLit:
- {
- SLANG_ASSERT(srcInst.m_payloadType == PayloadType::Int64);
- irConst = static_cast<IRConstant*>(module->_allocateInst(
- op,
- operandCount,
- prefixSize + sizeof(IRIntegerValue)));
- irConst->value.intVal = srcInst.m_payload.m_int64;
- break;
- }
- case kIROp_PtrLit:
- {
- SLANG_ASSERT(srcInst.m_payloadType == PayloadType::Int64);
- irConst = static_cast<IRConstant*>(
- module->_allocateInst(op, operandCount, prefixSize + sizeof(void*)));
- irConst->value.ptrVal = (void*)(intptr_t)srcInst.m_payload.m_int64;
- break;
- }
- case kIROp_FloatLit:
- {
- SLANG_ASSERT(srcInst.m_payloadType == PayloadType::Float64);
- irConst = static_cast<IRConstant*>(module->_allocateInst(
- op,
- operandCount,
- prefixSize + sizeof(IRFloatingPointValue)));
- irConst->value.floatVal = srcInst.m_payload.m_float64;
- break;
- }
- case kIROp_VoidLit:
- {
- SLANG_ASSERT(srcInst.m_payloadType == PayloadType::Empty);
- irConst = static_cast<IRConstant*>(
- module->_allocateInst(op, operandCount, prefixSize));
- break;
- }
- case kIROp_BlobLit:
- case kIROp_StringLit:
- {
- SLANG_ASSERT(srcInst.m_payloadType == PayloadType::String_1);
-
- const UnownedStringSlice slice = m_stringTable.getSlice(
- StringSlicePool::Handle(srcInst.m_payload.m_stringIndices[0]));
-
- const size_t sliceSize = slice.getLength();
- const size_t instSize =
- prefixSize + SLANG_OFFSET_OF(IRConstant::StringValue, chars) + sliceSize;
-
- irConst =
- static_cast<IRConstant*>(module->_allocateInst(op, operandCount, instSize));
-
- IRConstant::StringValue& dstString = irConst->value.stringVal;
-
- dstString.numChars = uint32_t(sliceSize);
- // Turn into pointer to avoid warning of array overrun
- char* dstChars = dstString.chars;
- // Copy the chars
- memcpy(dstChars, slice.begin(), sliceSize);
- break;
- }
- default:
- {
- SLANG_ASSERT(!"Unknown constant type");
- return SLANG_FAIL;
- }
- }
-
- insts[i] = irConst;
- }
- else
- {
- int numOperands = srcInst.getNumOperands();
- insts[i] = module->_allocateInst(op, numOperands);
- }
- }
+ void const* data = blob->getBufferPointer();
+ size_t size = blob->getBufferSize();
+ cursor.addDataChunk(PropertyKeys<IRModule>::IRModule, data, size);
+}
- // Patch up the operands
- for (Index i = 1; i < numInsts; ++i)
+//
+// Read a module, this currently does not do any on-demand loading
+//
+void readSerializedModuleIR(
+ RIFF::Chunk const* chunk,
+ Session* session,
+ SerialSourceLocReader* sourceLocReader,
+ RefPtr<IRModule>& outIRModule)
+{
+ auto dataChunk = as<RIFF::DataChunk>(chunk);
+ if (!dataChunk)
{
- const Ser::Inst& srcInst = data.m_insts[i];
- const IROp op((IROp)srcInst.m_op);
- SLANG_UNUSED(op);
-
- IRInst* dstInst = insts[i];
-
- // Set the result type
- if (srcInst.m_resultTypeIndex != Ser::InstIndex(0))
- {
- IRInst* resultInst = insts[int(srcInst.m_resultTypeIndex)];
- // NOTE! Counter intuitively the IRType* paramter may not be IRType* derived for example
- // IRGlobalGenericParam is valid, but isn't IRType* derived
-
- // SLANG_RELEASE_ASSERT(as<IRType>(resultInst));
- dstInst->setFullType(static_cast<IRType*>(resultInst));
- }
-
- // if (!isParentDerived(op))
- {
- const Ser::InstIndex* srcOperandIndices;
- const int numOperands = data.getOperands(srcInst, &srcOperandIndices);
-
- auto dstOperands = dstInst->getOperands();
-
- for (int j = 0; j < numOperands; j++)
- {
- dstOperands[j].init(dstInst, insts[int(srcOperandIndices[j])]);
- }
- }
+ SLANG_UNEXPECTED("invalid format for serialized module IR");
}
- // Patch up the children
+ Fossil::AnyValPtr rootValPtr =
+ Fossil::getRootValue(dataChunk->getPayload(), dataChunk->getPayloadSize());
+ if (!rootValPtr)
{
- const Index numChildRuns = data.m_childRuns.getCount();
- for (Index i = 0; i < numChildRuns; i++)
- {
- const auto& run = data.m_childRuns[i];
-
- IRInst* inst = insts[int(run.m_parentIndex)];
-
- for (int j = 0; j < int(run.m_numChildren); ++j)
- {
- IRInst* child = insts[j + int(run.m_startInstIndex)];
- SLANG_ASSERT(child->parent == nullptr);
- child->insertAtEnd(inst);
- }
- }
+ SLANG_UNEXPECTED("invalid format for serialized module IR");
}
- // Re-add source locations, if they are defined
- if (m_serialData->m_rawSourceLocs.getCount() == numInsts)
+ IRModuleInfo info;
{
- const Ser::RawSourceLoc* srcLocs = m_serialData->m_rawSourceLocs.begin();
- for (Index i = 1; i < numInsts; ++i)
- {
- IRInst* dstInst = insts[i];
-
- dstInst->sourceLoc.setRaw(Slang::SourceLoc::RawValue(srcLocs[i]));
- }
+ auto sharedDecodingContext = RefPtr(new IRSerialReadContext(session, sourceLocReader));
+ Fossil::ReadContext readContext;
+ Fossil::SerialReader reader(
+ readContext,
+ rootValPtr,
+ Fossil::SerialReader::InitialStateType::Root);
+
+ IRSerializer serializer(&reader, sharedDecodingContext);
+ serialize(serializer, info);
}
+ SLANG_ASSERT(info.module);
- // We now need to apply the runs
- if (sourceLocReader && m_serialData->m_debugSourceLocRuns.getCount())
+ //
+ // Now that everything is loaded, we can traverse the module and fix up the
+ // parents which we didn't do before because due to deferred
+ // deserialization we didn't necessarily have this information handy at the
+ // time.
+ //
+ auto go = [](auto&& go, IRInst* parent, IRInst* inst) -> void
{
- List<IRSerialData::SourceLocRun> sourceRuns(m_serialData->m_debugSourceLocRuns);
- // They are now in source location order
- sourceRuns.sort();
-
- // Just guess initially 0 for the source file that contains the initial run
- SerialSourceLocData::SourceRange range = SerialSourceLocData::SourceRange::getInvalid();
- int fix = 0;
-
- const Index numRuns = sourceRuns.getCount();
- for (Index i = 0; i < numRuns; ++i)
- {
- const auto& run = sourceRuns[i];
-
- // Work out the fixed source location
- SourceLoc sourceLoc;
- if (run.m_sourceLoc)
- {
- if (!range.contains(run.m_sourceLoc))
- {
- fix = sourceLocReader->calcFixSourceLoc(run.m_sourceLoc, range);
- }
- sourceLoc = sourceLocReader->calcFixedLoc(run.m_sourceLoc, fix, range);
- }
-
- // Write to all the instructions
- SLANG_ASSERT(Index(uint32_t(run.m_startInstIndex) + run.m_numInst) <= insts.getCount());
- IRInst** dstInsts = insts.getBuffer() + int(run.m_startInstIndex);
-
- const int runSize = int(run.m_numInst);
- for (int j = 0; j < runSize; ++j)
- {
- dstInsts[j]->sourceLoc = sourceLoc;
- }
- }
- }
-
- outModule->buildMangledNameToGlobalInstMap();
+ inst->parent = parent;
+ for (const auto child : inst->getDecorationsAndChildren())
+ go(go, inst, child);
+ };
+ go(go, nullptr, info.module->getModuleInst());
- return SLANG_OK;
+ //
+ // Module is finally valid (or at least as much as it was going it) and
+ // ready to be used
+ //
+ info.module->buildMangledNameToGlobalInstMap();
+ outIRModule = info.module;
}
} // namespace Slang
diff --git a/source/slang/slang-serialize-ir.h b/source/slang/slang-serialize-ir.h
index f6ab7e9ad..0ac188df7 100644
--- a/source/slang/slang-serialize-ir.h
+++ b/source/slang/slang-serialize-ir.h
@@ -1,121 +1,24 @@
-// slang-serialize-ir.h
-#ifndef SLANG_SERIALIZE_IR_H_INCLUDED
-#define SLANG_SERIALIZE_IR_H_INCLUDED
+#pragma once
-#include "../core/slang-riff.h"
-#include "slang-ir.h"
-#include "slang-serialize-ir-types.h"
-#include "slang-serialize-source-loc.h"
-
-// For TranslationUnitRequest
-// and FrontEndCompileRequest::ExtraEntryPointInfo
-#include "slang-compiler.h"
+#include "core/slang-riff.h"
namespace Slang
{
-struct IRModuleChunk : RIFF::ListChunk
-{
-};
-
-struct IRSerialWriter
-{
- typedef IRSerialData Ser;
- typedef IRSerialBinary Bin;
-
- Result write(
- IRModule* module,
- SerialSourceLocWriter* sourceLocWriter,
- IRSerialData* serialData);
-
- /// Write to a container
- static Result writeTo(const IRSerialData& data, RIFF::BuildCursor& cursor);
-
- /// Get an instruction index from an instruction
- Ser::InstIndex getInstIndex(IRInst* inst) const
- {
- return inst ? Ser::InstIndex(m_instMap.getValue(inst)) : Ser::InstIndex(0);
- }
-
- /// Get a slice from an index
- UnownedStringSlice getStringSlice(Ser::StringIndex index) const
- {
- return m_stringSlicePool.getSlice(StringSlicePool::Handle(index));
- }
- /// Get index from string representations
- Ser::StringIndex getStringIndex(StringRepresentation* string)
- {
- return Ser::StringIndex(m_stringSlicePool.add(string));
- }
- Ser::StringIndex getStringIndex(const UnownedStringSlice& slice)
- {
- return Ser::StringIndex(m_stringSlicePool.add(slice));
- }
- Ser::StringIndex getStringIndex(Name* name)
- {
- return name ? getStringIndex(name->text) : SerialStringData::kNullStringIndex;
- }
- Ser::StringIndex getStringIndex(const char* chars)
- {
- return Ser::StringIndex(m_stringSlicePool.add(chars));
- }
- Ser::StringIndex getStringIndex(const String& string)
- {
- return Ser::StringIndex(m_stringSlicePool.add(string.getUnownedSlice()));
- }
-
- StringSlicePool& getStringPool() { return m_stringSlicePool; }
-
- IRSerialWriter()
- : m_serialData(nullptr), m_stringSlicePool(StringSlicePool::Style::Default)
- {
- }
-
- /// Produces an instruction list which is in same order as written through IRSerialWriter
- static void calcInstructionList(IRModule* module, List<IRInst*>& instsOut);
-protected:
- void _addInstruction(IRInst* inst);
- Result _calcDebugInfo(SerialSourceLocWriter* sourceLocWriter);
+struct IRModule;
+class Session;
+class SerialSourceLocReader;
+class SerialSourceLocWriter;
- List<IRInst*> m_insts; ///< Instructions in same order as stored in the
+void writeSerializedModuleIR(
+ RIFF::BuildCursor& cursor,
+ IRModule* moduleDecl,
+ SerialSourceLocWriter* sourceLocWriter);
- List<IRDecoration*>
- m_decorations; ///< Holds all decorations in order of the instructions as found
- List<IRInst*> m_instWithFirstDecoration; ///< All decorations are held in this order after all
- ///< the regular instructions
-
- Dictionary<IRInst*, Ser::InstIndex> m_instMap; ///< Map an instruction to an instruction index
-
- StringSlicePool m_stringSlicePool;
- IRSerialData* m_serialData; ///< Where the data is stored
-};
-
-struct IRSerialReader
-{
- typedef IRSerialData Ser;
-
- /// Read a stream to fill in dataOut IRSerialData
- static Result readFrom(IRModuleChunk const* irModuleChunk, IRSerialData* outData);
-
- /// Read a module from serial data
- Result read(
- const IRSerialData& data,
- Session* session,
- SerialSourceLocReader* sourceLocReader,
- RefPtr<IRModule>& outModule);
-
- IRSerialReader()
- : m_serialData(nullptr), m_module(nullptr), m_stringTable(StringSlicePool::Style::Default)
- {
- }
-
-protected:
- StringSlicePool m_stringTable;
-
- const IRSerialData* m_serialData;
- IRModule* m_module;
-};
+void readSerializedModuleIR(
+ RIFF::Chunk const* chunk,
+ Session* session,
+ SerialSourceLocReader* sourceLocReader,
+ RefPtr<IRModule>& outIRModule);
} // namespace Slang
-
-#endif
diff --git a/source/slang/slang-serialize-source-loc.h b/source/slang/slang-serialize-source-loc.h
index c6ef982c1..ea2a06d96 100644
--- a/source/slang/slang-serialize-source-loc.h
+++ b/source/slang/slang-serialize-source-loc.h
@@ -8,6 +8,7 @@
#include "../core/slang-riff.h"
#include "../core/slang-string-slice-pool.h"
#include "slang-serialize-types.h"
+#include "slang-serialize.h"
namespace Slang
{
@@ -233,6 +234,81 @@ public:
Dictionary<SourceFile*, RefPtr<Source>> m_sourceFileMap;
};
+// A class a custom serialization context can inherit from to enable
+// serializing SourceLoc
+struct SourceLocSerialContext
+{
+ virtual SerialSourceLocReader* getSourceLocReader() { return nullptr; }
+ virtual SerialSourceLocWriter* getSourceLocWriter() { return nullptr; }
+};
+
+template<typename S>
+void serialize(S const& serializer, SourceLoc& value)
+{
+ static_assert(
+ std::is_convertible_v<decltype(serializer.getContext()), SourceLocSerialContext*>,
+ "getContext() must return a SourceLocSerialContext*");
+
+ SourceLocSerialContext* context = serializer.getContext();
+
+ // Writing of source location information can be disabled by
+ // compiler options, and in that case the `_sourceLocWriter`
+ // may be null.
+ //
+ // In order to handle that possibility, we serialize a `SourceLoc`
+ // as an optional value, dependent on whether we have a
+ // `_sourceLocWriter` that can be used.
+ //
+ SLANG_SCOPED_SERIALIZER_OPTIONAL(serializer);
+ if (isWriting(serializer))
+ {
+ // The `SourceLoc` type is implemented under the hood as an
+ // integer offset that can only be decoded using the specific
+ // `SourceManager` that created it.
+ //
+ // The source location writer handles the task of translating
+ // the under-the-hood representation to a single integer value
+ // (represented as `SerialSourceLocData::SourceLoc`) that can
+ // be decoded on the other side using other data that the
+ // source location writer will write out as part of its own
+ // representation (all of which goes into the dedicated debug
+ // data chunk, distinct from the AST/IR module).
+ //
+ if (const auto sourceLocWriter = context->getSourceLocWriter())
+ {
+ SerialSourceLocData::SourceLoc rawValue = sourceLocWriter->addSourceLoc(value);
+ serialize(serializer, rawValue);
+ }
+ }
+ else
+ {
+ if (hasElements(serializer))
+ {
+ SerialSourceLocData::SourceLoc rawValue;
+ serialize(serializer, rawValue);
+
+ // Even if the serialized optional had a value, it is
+ // possible that the debug-data chunk got stripped from
+ // the compiled module file, in which case we wouldn't
+ // have access to the data needed to decode it.
+ //
+ // In that case, the `_sourceLocReader` member would be
+ // null, so we handle that possibility here.
+ //
+ if (const auto sourceLocReader = context->getSourceLocReader())
+ {
+ value = sourceLocReader->getSourceLoc(rawValue);
+ }
+ }
+ }
+}
+
+// Now that we've seen the relevant serialization logic, it is clear that
+// a `SourceLoc` gets fossilized the same way that an optional wrapping
+// an integer (of type `SerialSourceLocData::SourceLoc`) would.
+//
+SLANG_DECLARE_FOSSILIZED_AS(SourceLoc, std::optional<SerialSourceLocData::SourceLoc>);
+
} // namespace Slang
#endif
diff --git a/source/slang/slang-serialize-types.h b/source/slang/slang-serialize-types.h
index be49be1ea..ca54848c2 100644
--- a/source/slang/slang-serialize-types.h
+++ b/source/slang/slang-serialize-types.h
@@ -5,6 +5,7 @@
#include "../core/slang-array-view.h"
#include "../core/slang-riff.h"
#include "../core/slang-string-slice-pool.h"
+#include "slang-ir.h"
// #include "slang-name.h"
// #include "slang-source-loc.h"
@@ -109,6 +110,12 @@ struct PropertyKeys<Module>
static const FourCC::RawValue FileDependencies = SLANG_FOUR_CC('f', 'd', 'e', 'p');
};
+template<>
+struct PropertyKeys<IRModule>
+{
+ static const FourCC::RawValue IRModule = SLANG_FOUR_CC('i', 'r', ' ', ' ');
+};
+
// For types/FourCC that work for serializing in general (not just IR).
struct SerialBinary
{
diff --git a/source/slang/slang.cpp b/source/slang/slang.cpp
index e76ff7436..b616eb555 100644
--- a/source/slang/slang.cpp
+++ b/source/slang/slang.cpp
@@ -763,7 +763,7 @@ SlangResult Session::_readBuiltinModule(
// to deserialize the IR module.
//
RefPtr<IRModule> irModule;
- SLANG_RETURN_ON_FAIL(decodeModuleIR(irModule, irChunk, this, sourceLocReader));
+ readSerializedModuleIR(irChunk, this, sourceLocReader, irModule);
irModule->setName(module->getNameObj());
module->setIRModule(irModule);
@@ -6801,7 +6801,7 @@ SlangResult Linkage::loadSerializedModuleContents(
module->setModuleDecl(moduleDecl);
RefPtr<IRModule> irModule;
- SLANG_RETURN_ON_FAIL(decodeModuleIR(irModule, irChunk, session, sourceLocReader));
+ readSerializedModuleIR(irChunk, session, sourceLocReader, irModule);
module->setIRModule(irModule);
// The handling of file dependencies is complicated, because of