diff options
Diffstat (limited to 'source/slang/slang-serialize-ir.cpp')
| -rw-r--r-- | source/slang/slang-serialize-ir.cpp | 983 |
1 files changed, 329 insertions, 654 deletions
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 |
