#include "slang-emit-vm.h"

#include "slang-ir-call-graph.h"
#include "slang-ir-layout.h"
#include "slang-ir-util.h"
#include "slang-rich-diagnostics.h"

using namespace slang;

namespace Slang
{
class ByteCodeEmitter
{
public:
    Dictionary<IRInst*, String> mapInstToName;
    Dictionary<String, int> mapNameToUniqueId;
    Dictionary<IRInst*, VMOperand> mapInstToOperand;
    Dictionary<UnownedStringSlice, VMOperand> mapStringToOperand;
    struct ConstKey
    {
        uint64_t value;
        uint32_t size;
        bool operator==(const ConstKey& other) const
        {
            return value == other.value && size == other.size;
        }
        bool operator!=(const ConstKey& other) const { return !(*this == other); }
        HashCode getHashCode() const { return combineHash(value, size); }
    };
    Dictionary<ConstKey, VMOperand> mapConstantIntToOperand;
    Dictionary<IRFunc*, int> mapFuncToId;

    // Global parameters already reported via diagnoseGlobalParam, so the same
    // unsupported parameter is diagnosed once rather than once per reference.
    HashSet<IRGlobalParam*> diagnosedGlobalParams;

    VMByteCodeBuilder& byteCodeBuilder;
    CodeGenContext* codeGenContext;

    ByteCodeEmitter(VMByteCodeBuilder& builder, CodeGenContext* codeGenContext)
        : byteCodeBuilder(builder), codeGenContext(codeGenContext)
    {
    }

    String getName(IRInst* inst)
    {
        String name;
        if (mapInstToName.tryGetValue(inst, name))
            return name;

        if (auto nameDecor = inst->findDecoration<IRNameHintDecoration>())
        {
            name = nameDecor->getName();
        }
        else if (auto linkageDecor = inst->findDecoration<IRLinkageDecoration>())
        {
            name = linkageDecor->getMangledName();
        }
        else
        {
            name = getIROpInfo(inst->getOp()).name;
        }
        if (int* id = mapNameToUniqueId.tryGetValue(name))
        {
            (*id)++;
            name = name + "_" + String(*id);
        }
        else
        {
            mapNameToUniqueId[name] = 0;
        }
        mapInstToName[inst] = name;
        return name;
    }

    struct InstRelocationEntry
    {
        Index offsetToOperand;
        IRBlock* block;
    };

    template<typename T>
    static T alignUp(T value, T alignment)
    {
        return (value + alignment - 1) / alignment * alignment;
    }

    VMOperand allocReg(VMByteCodeFunctionBuilder& funcBuilder, size_t size, size_t alignment)
    {
        VMOperand operand;
        operand.sectionId = kSlangByteCodeSectionWorkingSet;
        operand.offset = funcBuilder.workingSetSizeInBytes;
        funcBuilder.workingSetSizeInBytes =
            alignUp(funcBuilder.workingSetSizeInBytes, (uint32_t)alignment);
        operand.offset = funcBuilder.workingSetSizeInBytes;
        operand.size = size;
        funcBuilder.workingSetSizeInBytes += (uint32_t)size;
        return operand;
    }

    VMOperand ensureWorkingsetMemory(VMByteCodeFunctionBuilder& funcBuilder, IRInst* inst)
    {
        VMOperand operand;

        if (mapInstToOperand.tryGetValue(inst, operand))
            return operand;

        IRSizeAndAlignment sizeAlignment = {};
        getNaturalSizeAndAlignment(
            codeGenContext->getTargetReq(),
            inst->getDataType(),
            &sizeAlignment);
        operand = allocReg(funcBuilder, sizeAlignment.size, sizeAlignment.alignment);
        mapInstToOperand[inst] = operand;
        return operand;
    }

    VMOperand addStringLiteral(UnownedStringSlice str)
    {
        if (auto operand = mapStringToOperand.tryGetValue(str))
            return *operand;
        VMOperand operand;
        operand.sectionId = kSlangByteCodeSectionStrings;
        operand.offset = (uint32_t)byteCodeBuilder.stringOffsets.getCount();

        byteCodeBuilder.stringOffsets.add((uint32_t)byteCodeBuilder.constantSection.getCount());
        byteCodeBuilder.constantSection.addRange((uint8_t*)str.begin(), str.getLength());
        byteCodeBuilder.constantSection.add(0);
        operand.setType(OperandDataType::String);
        operand.size = 0;
        mapStringToOperand[str] = operand;
        return operand;
    }

    void alignConstSection(int alignment)
    {
        int rem = (int)byteCodeBuilder.constantSection.getCount() % alignment;
        if (rem != 0)
        {
            int paddingSize = alignment - rem;
            for (int i = 0; i < paddingSize; i++)
            {
                byteCodeBuilder.constantSection.add(0);
            }
        }
    }

    template<typename IntType>
    VMOperand addConstantValue(IntType value)
    {
        ConstKey key;
        key.value = value;
        key.size = (uint32_t)sizeof(IntType);
        if (auto operand = mapConstantIntToOperand.tryGetValue(key))
            return *operand;
        VMOperand operand;
        operand.sectionId = kSlangByteCodeSectionConstants;
        // align constantSection
        alignConstSection((int)sizeof(IntType));
        operand.offset = (uint32_t)byteCodeBuilder.constantSection.getCount();
        byteCodeBuilder.constantSection.addRange((uint8_t*)&value, sizeof(value));
        mapConstantIntToOperand[key] = operand;

        operand.size = sizeof(IntType);
        if (operand.size == 4)
            operand.setType(OperandDataType::Int32);
        else if (operand.size == 8)
            operand.setType(OperandDataType::Int64);
        else
            operand.setType(OperandDataType::General);
        return operand;
    }

    VMOperand addConstantValue(IRConstant* inst)
    {
        // Zero-initialize for the same reason ensureInst does: the VoidLit and PtrLit
        // arms below never call setType, so without this their operand would carry an
        // indeterminate `type` (and `padding`) that writeInst copies byte-for-byte
        // into the code buffer. Zeroing makes the unset `type` a well-defined General.
        VMOperand operand = {};
        operand.sectionId = kSlangByteCodeSectionConstants;

        // Align constantSection.
        IRSizeAndAlignment sizeAlignment;
        getNaturalSizeAndAlignment(
            codeGenContext->getTargetReq(),
            inst->getDataType(),
            &sizeAlignment);
        alignConstSection(sizeAlignment.alignment);

        // Reserve-then-fill contract: offset and size are recorded here, before the
        // switch. Every arm that falls through to the shared `return operand` below must
        // then append exactly `operand.size` bytes to constantSection. (StringLit is the
        // exception: it returns early with a separate strings-section operand and does not
        // use this reservation.) An arm that under-fills leaves the reserved slot unbacked,
        // which is the #11375/#11402 bug: the next constant overlaps this one, and a
        // trailing under-filled constant reads past the section end at run time.
        operand.offset = (uint32_t)byteCodeBuilder.constantSection.getCount();
        operand.size = sizeAlignment.size;

        switch (inst->getOp())
        {
        case kIROp_StringLit:
            {
                return addStringLiteral(static_cast<IRStringLit*>(inst)->getStringSlice());
            }
        case kIROp_IntLit:
            {
                int64_t value = static_cast<IRIntLit*>(inst)->getValue();
                byteCodeBuilder.constantSection.addRange((uint8_t*)&value, sizeAlignment.size);
                operand.setType(OperandDataType::General);
                if (sizeAlignment.size != 64)
                {
                    operand.setType(OperandDataType::Int32);
                }
                break;
            }
        case kIROp_FloatLit:
            {
                auto value = static_cast<IRFloatLit*>(inst)->getValue();
                if (inst->getDataType()->getOp() == kIROp_HalfType)
                {
                    auto halfValue = FloatToHalf((float)value);
                    byteCodeBuilder.constantSection.addRange(
                        (uint8_t*)&halfValue,
                        sizeof(halfValue));
                }
                else if (inst->getDataType()->getOp() == kIROp_FloatType)
                {
                    float floatValue = (float)value;
                    byteCodeBuilder.constantSection.addRange(
                        (uint8_t*)&floatValue,
                        sizeof(floatValue));
                    operand.setType(OperandDataType::Float32);
                }
                else
                {
                    byteCodeBuilder.constantSection.addRange((uint8_t*)&value, sizeof(value));
                    operand.setType(OperandDataType::Float64);
                }
                break;
            }
        case kIROp_PtrLit:
            {
                int64_t value = static_cast<IRIntLit*>(inst)->getValue();
                byteCodeBuilder.constantSection.addRange((uint8_t*)&value, sizeof(value));
                break;
            }
        case kIROp_BoolLit:
            {
                // Widen to int64_t and append the low `sizeAlignment.size` bytes. This is
                // correct only on a little-endian host, where the 0/1 value lives in the
                // low bytes regardless of the width the type-layout pass picks for bool,
                // and only while that width does not exceed sizeof(int64_t) (asserted).
                SLANG_ASSERT(sizeAlignment.size <= (IRIntegerValue)sizeof(int64_t));
                int64_t value = static_cast<IRBoolLit*>(inst)->getValue() ? 1 : 0;
                byteCodeBuilder.constantSection.addRange((uint8_t*)&value, sizeAlignment.size);
                // OperandDataType has no boolean tag. Bool is laid out in 4 bytes, so tagging
                // it Int32 would make the disassembler render it as an integer (`i32(...)`);
                // General keeps it an untyped `const:` rather than mislabeling it.
                operand.setType(OperandDataType::General);
                break;
            }
        case kIROp_VoidLit:
            // A void constant is zero bytes wide, so the reserved slot is already the
            // right (empty) size and there is nothing to append.
            break;
        default:
            {
                // The only IRConstant op (the `Constant` block in slang-ir-insts.lua) not
                // handled above is BlobLit, which VM emission does not support. This is a
                // live guard, not just future-proofing: rather than reserve a slot and
                // leave it unbacked (the #11375 bug), fail loudly at emit time. Name the
                // op so a future unsupported constant is identified in the report.
                StringBuilder sb;
                sb << "unhandled IRConstant op in VM emitter: " << getIROpInfo(inst->getOp()).name;
                SLANG_UNEXPECTED(sb.getBuffer());
            }
        }
        return operand;
    }

    VMOperand ensureInst(IRInst* inst)
    {
        // Zero-initialize: the global-parameter branch below diagnoses and returns
        // without computing a real operand, so it must start from a well-defined
        // value rather than indeterminate stack bytes (which writeInst would copy
        // byte-for-byte into the code buffer).
        VMOperand operand = {};
        if (mapInstToOperand.tryGetValue(inst, operand))
            return operand;

        if (auto constantInst = as<IRConstant>(inst))
        {
            operand = addConstantValue(constantInst);
            mapInstToOperand[inst] = operand;
        }
        else if (auto constantVector = as<IRMakeVector>(inst))
        {
            SLANG_ASSERT(constantVector->getOperandCount() > 0);
            operand = ensureInst(constantVector->getOperand(0));
            for (UInt i = 1; i < constantVector->getOperandCount(); i++)
            {
                ensureInst(constantVector->getOperand(i));
            }
            operand.size *= (uint32_t)constantVector->getOperandCount();
            mapInstToOperand[inst] = operand;
        }
        else if (auto globalParam = findReferencedGlobalParam(inst))
        {
            // A global parameter (possibly reached through a global-scope instruction
            // such as a load of the parameter) has arrived at value emission. The
            // interpreter cannot represent it, so report the limitation and return the
            // (unused) default operand; emission fails once an error has been reported.
            diagnoseGlobalParam(globalParam);
            mapInstToOperand[inst] = operand;
        }
        else
        {
            SLANG_UNEXPECTED("unsupported global inst for vm bytecode emit");
        }
        return operand;
    }

    // Return the global shader parameter that `inst` is, or that it references
    // through its operands (transitively), or null if there is none. Used to detect
    // values the interpreter cannot represent: a `uniform float` used in a function
    // body, for example, lowers to a global-scope `load` of the parameter, so the
    // unsupported value reaching the emitter is the load rather than the parameter
    // itself.
    IRGlobalParam* findReferencedGlobalParam(IRInst* inst)
    {
        HashSet<IRInst*> visited;
        return findReferencedGlobalParam(inst, visited);
    }

    // Worker for findReferencedGlobalParam. `visited` guards against re-walking
    // shared operands in the global-scope graph, which a global initializer can
    // reference from several places.
    IRGlobalParam* findReferencedGlobalParam(IRInst* inst, HashSet<IRInst*>& visited)
    {
        if (auto globalParam = as<IRGlobalParam>(inst))
            return globalParam;
        // Only global-scope instructions are followed here; an operand at function
        // scope is an ordinary value that ensureInst handles through its normal paths.
        if (!as<IRModuleInst>(inst->getParent()))
            return nullptr;
        if (!visited.add(inst))
            return nullptr;
        for (UInt i = 0; i < inst->getOperandCount(); i++)
        {
            if (auto found = findReferencedGlobalParam(inst->getOperand(i), visited))
                return found;
        }
        return nullptr;
    }

    // Report that a global shader parameter is not supported by the interpreter. The
    // interpreter runs on the CPU and has no concept of global parameters or GPU
    // resource types (e.g. RWStructuredBuffer), so a program referencing one cannot
    // be interpreted. This is the single source of truth for the diagnostic; the two
    // detection points below feed into it.
    void diagnoseGlobalParam(IRGlobalParam* globalParam)
    {
        if (!diagnosedGlobalParams.add(globalParam))
            return;
        codeGenContext->getSink()->diagnose(
            Diagnostics::GlobalParamNotSupportedByInterpreter{.name = getName(globalParam)});
    }

    // Report a clean diagnostic if any instruction of a function body has a global
    // parameter as a direct operand, and return true in that case.
    //
    // This complements the check in ensureInst: every emitted *value* flows through
    // ensureInst and is caught there, but a few instruction handlers (and the
    // unimplemented-instruction fallback in emitInst) abort before calling ensureInst
    // on their global-parameter operand. Checking operands up front, before emitting
    // any instruction of the function, catches those cases and lets the failure
    // propagate out of emitVMByteCodeForEntryPoints as a clean error instead of an
    // internal abort.
    bool diagnoseGlobalParamReferences(IRFunc* func)
    {
        bool diagnosed = false;
        for (auto block : func->getBlocks())
        {
            // Mirror emitFunction, which skips unreachable blocks; an operand reference
            // in a block that is never emitted cannot cause an abort.
            if (isUnreachableBlock(block))
                continue;

            for (auto inst : block->getChildren())
            {
                for (UInt i = 0; i < inst->getOperandCount(); i++)
                {
                    // Use findReferencedGlobalParam (not a direct as<IRGlobalParam>) so
                    // this mirrors the detection in ensureInst: an operand can be a
                    // global-scope load of the parameter rather than the parameter
                    // itself, and the consuming instruction's handler may abort before
                    // ensureInst is reached.
                    if (auto globalParam = findReferencedGlobalParam(inst->getOperand(i)))
                    {
                        diagnoseGlobalParam(globalParam);
                        diagnosed = true;
                    }
                }
            }
        }
        return diagnosed;
    }

    void writeInst(
        VMByteCodeFunctionBuilder& funcBuilder,
        VMOp op,
        uint32_t extOp,
        ArrayView<VMOperand> operands)
    {
        VMInstHeader instHeader = {};
        instHeader.opcode = op;
        instHeader.opcodeExtension = extOp;
        instHeader.operandCount = (uint16_t)operands.getCount();
        funcBuilder.instOffsets.add(funcBuilder.code.getCount());
        funcBuilder.code.addRange(reinterpret_cast<uint8_t*>(&instHeader), sizeof(instHeader));
        for (auto operand : operands)
        {
            funcBuilder.code.addRange(reinterpret_cast<uint8_t*>(&operand), sizeof(operand));
        }
    }

    void writeInst(VMByteCodeFunctionBuilder& funcBuilder, VMOp op, uint32_t extOp)
    {
        writeInst(funcBuilder, op, extOp, ArrayView<VMOperand>());
    }

    void writeInst(
        VMByteCodeFunctionBuilder& funcBuilder,
        VMOp op,
        uint32_t extOp,
        VMOperand operand)
    {
        writeInst(funcBuilder, op, extOp, makeArrayViewSingle(operand));
    }

    void writeInst(
        VMByteCodeFunctionBuilder& funcBuilder,
        VMOp op,
        uint32_t extOp,
        VMOperand operand1,
        VMOperand operand2)
    {
        writeInst(funcBuilder, op, extOp, makeArray(operand1, operand2).getView());
    }

    void writeInst(
        VMByteCodeFunctionBuilder& funcBuilder,
        VMOp op,
        uint32_t extOp,
        VMOperand operand1,
        VMOperand operand2,
        VMOperand operand3)
    {
        writeInst(funcBuilder, op, extOp, makeArray(operand1, operand2, operand3).getView());
    }

    uint32_t getExtCode(IRInst* type)
    {
        ArithmeticExtCode extCode = {};
        if (auto vecType = as<IRVectorType>(type))
        {
            extCode.vectorSize = getIntVal(vecType->getElementCount());
            type = vecType->getElementType();
        }
        else if (auto matType = as<IRMatrixType>(type))
        {
            extCode.vectorSize =
                getIntVal(matType->getRowCount()) * getIntVal(matType->getColumnCount());
            type = matType->getElementType();
        }
        switch (type->getOp())
        {
        case kIROp_IntType:
        case kIROp_BoolType:
            extCode.scalarType = kSlangByteCodeScalarTypeSignedInt;
            extCode.scalarBitWidth = 2;
            break;
        case kIROp_Int8Type:
            extCode.scalarType = kSlangByteCodeScalarTypeSignedInt;
            extCode.scalarBitWidth = 0;
            break;
        case kIROp_Int16Type:
            extCode.scalarType = kSlangByteCodeScalarTypeSignedInt;
            extCode.scalarBitWidth = 1;
            break;
        case kIROp_Int64Type:
        case kIROp_IntPtrType:
            extCode.scalarType = kSlangByteCodeScalarTypeSignedInt;
            extCode.scalarBitWidth = 3;
            break;
        case kIROp_UIntType:
            extCode.scalarType = kSlangByteCodeScalarTypeUnsignedInt;
            extCode.scalarBitWidth = 2;
            break;
        case kIROp_UInt8Type:
            extCode.scalarType = kSlangByteCodeScalarTypeUnsignedInt;
            extCode.scalarBitWidth = 0;
            break;
        case kIROp_UInt16Type:
            extCode.scalarType = kSlangByteCodeScalarTypeUnsignedInt;
            extCode.scalarBitWidth = 1;
            break;
        case kIROp_UInt64Type:
        case kIROp_UIntPtrType:
        case kIROp_PtrType:
        case kIROp_OutParamType:
        case kIROp_BorrowInOutParamType:
        case kIROp_RefParamType:
        case kIROp_NativePtrType:
            extCode.scalarType = kSlangByteCodeScalarTypeUnsignedInt;
            extCode.scalarBitWidth = 3;
            break;
        case kIROp_FloatType:
            extCode.scalarType = kSlangByteCodeScalarTypeFloat;
            extCode.scalarBitWidth = 2;
            break;
        case kIROp_HalfType:
            extCode.scalarType = kSlangByteCodeScalarTypeFloat;
            extCode.scalarBitWidth = 1;
            break;
        case kIROp_DoubleType:
            extCode.scalarType = kSlangByteCodeScalarTypeFloat;
            extCode.scalarBitWidth = 3;
            break;
        default:
            SLANG_UNEXPECTED("Unsupported type for arithmetic operation");
        }
        uint32_t result;
        memcpy(&result, &extCode, sizeof(extCode));
        return result;
    }

    VMInstHeader translateArithmeticOp(IRInst* inst)
    {
        VMInstHeader opInfo = {};

        switch (inst->getOp())
        {
        case kIROp_Add:
            opInfo.opcode = VMOp::Add;
            break;
        case kIROp_Sub:
            opInfo.opcode = VMOp::Sub;
            break;
        case kIROp_Mul:
            opInfo.opcode = VMOp::Mul;
            break;
        case kIROp_Div:
            opInfo.opcode = VMOp::Div;
            break;
        case kIROp_IRem:
        case kIROp_FRem:
            opInfo.opcode = VMOp::Rem;
            break;
        case kIROp_Neg:
            opInfo.opcode = VMOp::Neg;
            break;
        case kIROp_And:
            opInfo.opcode = VMOp::And;
            break;
        case kIROp_Or:
            opInfo.opcode = VMOp::Or;
            break;
        case kIROp_Not:
            opInfo.opcode = VMOp::Not;
            break;
        case kIROp_BitAnd:
            opInfo.opcode = VMOp::BitAnd;
            break;
        case kIROp_BitOr:
            opInfo.opcode = VMOp::BitOr;
            break;
        case kIROp_BitXor:
            opInfo.opcode = VMOp::BitXor;
            break;
        case kIROp_BitNot:
            opInfo.opcode = VMOp::BitNot;
            break;
        case kIROp_Lsh:
            opInfo.opcode = VMOp::Shl;
            break;
        case kIROp_Rsh:
            opInfo.opcode = VMOp::Shr;
            break;
        case kIROp_Less:
            opInfo.opcode = VMOp::Less;
            break;
        case kIROp_Leq:
            opInfo.opcode = VMOp::Leq;
            break;
        case kIROp_Greater:
            opInfo.opcode = VMOp::Greater;
            break;
        case kIROp_Geq:
            opInfo.opcode = VMOp::Geq;
            break;
        case kIROp_Eql:
            opInfo.opcode = VMOp::Equal;
            break;
        case kIROp_Neq:
            opInfo.opcode = VMOp::Neq;
            break;
        default:
            SLANG_UNEXPECTED("Unsupported operation");
            break;
        }
        opInfo.opcodeExtension = getExtCode(inst->getOperand(0)->getDataType());
        return opInfo;
    }

    void emitCast(VMByteCodeFunctionBuilder& funcBuilder, VMOp op, IRInst* inst)
    {
        auto extCode1 = getExtCode(inst->getDataType());
        auto extCode2 = getExtCode(inst->getOperand(0)->getDataType());
        auto extCode = extCode1 | (extCode2 << 16);
        writeInst(
            funcBuilder,
            op,
            extCode,
            ensureWorkingsetMemory(funcBuilder, inst),
            ensureInst(inst->getOperand(0)));
    }

    void emitInst(
        VMByteCodeFunctionBuilder& funcBuilder,
        IRInst* inst,
        List<InstRelocationEntry>& relocations)
    {
        switch (inst->getOp())
        {
        case kIROp_Poison:
        case kIROp_LoadFromUninitializedMemory:
            {
                // We basically handle an undefined value by allocating a
                // temporary and then not initializing it.
                //
                ensureWorkingsetMemory(funcBuilder, inst);
            }
            break;
        case kIROp_Param:
            {
                auto operand = ensureWorkingsetMemory(funcBuilder, inst);
                if (isFirstBlock(inst->getParent()))
                {
                    funcBuilder.parameterOffsets.add(operand.offset);
                    IRSizeAndAlignment sizeAlignment = {};
                    getNaturalSizeAndAlignment(
                        codeGenContext->getTargetReq(),
                        inst->getDataType(),
                        &sizeAlignment);
                    funcBuilder.parameterSize =
                        operand.offset + (uint32_t)sizeAlignment.getStride();
                }
            }
            break;
        case kIROp_Var:
            {
                IRBuilder builder(inst);
                auto type = tryGetPointedToType(&builder, inst->getDataType());
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(codeGenContext->getTargetReq(), type, &sizeAlignment);
                auto varStorage = allocReg(
                    funcBuilder,
                    (size_t)sizeAlignment.size,
                    (size_t)sizeAlignment.alignment);
                writeInst(
                    funcBuilder,
                    VMOp::GetWorkingSetPtr,
                    varStorage.offset,
                    ensureWorkingsetMemory(funcBuilder, inst));
            }
            break;
        case kIROp_Load:
            {
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    inst->getDataType(),
                    &sizeAlignment);
                writeInst(
                    funcBuilder,
                    VMOp::Load,
                    (uint32_t)sizeAlignment.getStride(),
                    ensureWorkingsetMemory(funcBuilder, inst),
                    ensureInst(inst->getOperand(0)));
            }
            break;
        case kIROp_Store:
            {
                auto storeInst = as<IRStore>(inst);
                IRBuilder builder(inst);

                // A HostVM store must have a typed destination. NativePtr/ComPtr are opaque handle
                // values and RawPointer is untyped, so none is a valid store destination here.
                auto pointeeType =
                    tryGetPointedToType(&builder, storeInst->getPtr()->getDataType());
                SLANG_RELEASE_ASSERT(pointeeType);

                // Use the destination type to determine how many bytes the store writes. For
                // example, StringType has no configured HostVM size, but a NativeString field is
                // pointer-sized.
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    pointeeType,
                    &sizeAlignment);
                writeInst(
                    funcBuilder,
                    VMOp::Store,
                    (uint32_t)sizeAlignment.getStride(),
                    ensureInst(storeInst->getPtr()),
                    ensureInst(storeInst->getVal()));
            }
            break;
        case kIROp_Add:
        case kIROp_Sub:
        case kIROp_Mul:
        case kIROp_Div:
        case kIROp_And:
        case kIROp_FRem:
        case kIROp_IRem:
        case kIROp_Or:
        case kIROp_BitAnd:
        case kIROp_BitOr:
        case kIROp_BitXor:
        case kIROp_Lsh:
        case kIROp_Rsh:
        case kIROp_Less:
        case kIROp_Leq:
        case kIROp_Greater:
        case kIROp_Geq:
        case kIROp_Eql:
        case kIROp_Neq:
            {
                auto opInfo = translateArithmeticOp(inst);
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    inst->getDataType(),
                    &sizeAlignment);
                writeInst(
                    funcBuilder,
                    opInfo.opcode,
                    opInfo.opcodeExtension,
                    ensureWorkingsetMemory(funcBuilder, inst),
                    ensureInst(inst->getOperand(0)),
                    ensureInst(inst->getOperand(1)));
            }
            break;
        case kIROp_Neg:
        case kIROp_Not:
        case kIROp_BitNot:
            {
                auto opInfo = translateArithmeticOp(inst);
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    inst->getDataType(),
                    &sizeAlignment);
                writeInst(
                    funcBuilder,
                    opInfo.opcode,
                    opInfo.opcodeExtension,
                    ensureWorkingsetMemory(funcBuilder, inst),
                    ensureInst(inst->getOperand(0)));
            }
            break;
        case kIROp_UnconditionalBranch:
        case kIROp_Loop:
            {
                // Write phi arguments into param registers.
                auto branch = as<IRUnconditionalBranch>(inst);
                auto params = branch->getTargetBlock()->getParams();
                List<IRInst*> paramList;
                for (auto param : params)
                {
                    paramList.add(param);
                }
                if (paramList.getCount() != (Index)branch->getArgCount())
                {
                    SLANG_UNEXPECTED("Invalid number of arguments for branch instruction");
                }
                for (UInt i = 0; i < branch->getArgCount(); i++)
                {
                    auto arg = branch->getArg(i);
                    auto param = paramList[i];
                    auto paramReg = ensureWorkingsetMemory(funcBuilder, param);
                    IRSizeAndAlignment sizeAlignment = {};
                    getNaturalSizeAndAlignment(
                        codeGenContext->getTargetReq(),
                        param->getDataType(),
                        &sizeAlignment);
                    writeInst(
                        funcBuilder,
                        VMOp::Copy,
                        (uint32_t)sizeAlignment.getStride(),
                        paramReg,
                        ensureInst(arg));
                }
                // Write jump inst.
                VMOperand relocOperand = {};
                writeInst(funcBuilder, VMOp::Jump, 0, relocOperand);
                InstRelocationEntry entry;
                entry.block = (IRBlock*)inst->getOperand(0);
                entry.offsetToOperand = funcBuilder.code.getCount() - sizeof(VMOperand);
                relocations.add(entry);
            }
            break;
        case kIROp_IfElse:
            {
                VMOperand relocOperand = {};
                writeInst(
                    funcBuilder,
                    VMOp::JumpIf,
                    0,
                    ensureInst(inst->getOperand(0)),
                    relocOperand,
                    relocOperand);
                InstRelocationEntry entry;
                entry.block = (IRBlock*)inst->getOperand(1);
                entry.offsetToOperand = funcBuilder.code.getCount() - sizeof(VMOperand) * 2;
                relocations.add(entry);
                entry.block = (IRBlock*)inst->getOperand(2);
                entry.offsetToOperand = funcBuilder.code.getCount() - sizeof(VMOperand);
                relocations.add(entry);
            }
            break;
        case kIROp_Call:
            {
                auto callInst = as<IRCall>(inst);
                auto callee = as<IRFunc>(callInst->getCallee());
                UnownedStringSlice def;
                IRInst* intrinsicInst;
                if (findTargetIntrinsicDefinition(
                        callee,
                        codeGenContext->getTargetCaps(),
                        def,
                        intrinsicInst))
                {
                    auto calleeOperand = addStringLiteral(def);
                    List<VMOperand> operands;
                    operands.add(ensureWorkingsetMemory(funcBuilder, inst));
                    operands.add(calleeOperand);
                    for (UInt i = 0; i < callInst->getArgCount(); ++i)
                    {
                        operands.add(ensureInst(callInst->getArg(i)));
                    }
                    writeInst(funcBuilder, VMOp::CallExt, 0, operands.getArrayView());
                    break;
                }
                List<VMOperand> operands;
                int calleeId = -1;
                mapFuncToId.tryGetValue(callee, calleeId);
                SLANG_ASSERT(calleeId != -1);
                VMOperand calleeOperand = {};
                calleeOperand.sectionId = kSlangByteCodeSectionFuncs;
                calleeOperand.offset = calleeId;
                calleeOperand.setType(OperandDataType::Int32);
                operands.add(ensureWorkingsetMemory(funcBuilder, inst));
                operands.add(calleeOperand);
                for (UInt i = 0; i < callInst->getArgCount(); ++i)
                {
                    operands.add(ensureInst(callInst->getArg(i)));
                }
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    inst->getDataType(),
                    &sizeAlignment);
                writeInst(
                    funcBuilder,
                    VMOp::Call,
                    (uint32_t)sizeAlignment.getStride(),
                    operands.getArrayView());
            }
            break;
        case kIROp_MissingReturn:
        case kIROp_Return:
            {
                auto returnInst = as<IRReturn>(inst);
                if (returnInst && returnInst->getVal()->getOp() != kIROp_VoidLit)
                {
                    IRSizeAndAlignment sizeAlignment = {};
                    getNaturalSizeAndAlignment(
                        codeGenContext->getTargetReq(),
                        returnInst->getVal()->getDataType(),
                        &sizeAlignment);
                    writeInst(
                        funcBuilder,
                        VMOp::Ret,
                        (uint32_t)sizeAlignment.getStride(),
                        ensureInst(returnInst->getOperand(0)));
                }
                else
                {
                    writeInst(funcBuilder, VMOp::Ret, 0);
                }
            }
            break;
        case kIROp_GetElementPtr:
            {
                auto getElemInst = as<IRGetElementPtr>(inst);
                auto base = getElemInst->getBase();
                auto index = getElemInst->getIndex();
                IRBuilder builder(inst);
                auto elementType = tryGetPointedToType(&builder, getElemInst->getDataType());
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    elementType,
                    &sizeAlignment);
                auto stride = sizeAlignment.getStride();
                auto baseOperand = ensureInst(base);
                auto indexOperand = ensureInst(index);
                writeInst(
                    funcBuilder,
                    VMOp::GetElementPtr,
                    (uint32_t)stride,
                    ensureWorkingsetMemory(funcBuilder, inst),
                    baseOperand,
                    indexOperand);
            }
            break;
        case kIROp_FieldAddress:
            {
                auto fieldAddrInst = as<IRFieldAddress>(inst);
                auto base = fieldAddrInst->getBase();
                auto fieldKey = (IRStructKey*)fieldAddrInst->getField();
                IRBuilder builder(base);

                auto structType =
                    as<IRStructType>(tryGetPointedToType(&builder, base->getDataType()));
                IRIntegerValue offset = 0;
                auto field = findStructField(structType, fieldKey);
                getNaturalOffset(codeGenContext->getTargetReq(), field, &offset);

                writeInst(
                    funcBuilder,
                    VMOp::Add,
                    getExtCode(inst->getDataType()),
                    ensureWorkingsetMemory(funcBuilder, inst),
                    ensureInst(base),
                    addConstantValue((uint64_t)offset));
            }
            break;
        case kIROp_GetOffsetPtr:
            {
                auto getOffsetPtrInst = as<IRGetOffsetPtr>(inst);
                auto base = getOffsetPtrInst->getBase();
                auto offset = getOffsetPtrInst->getOffset();
                IRSizeAndAlignment sizeAlignment = {};
                IRBuilder builder(inst);
                auto elementType = tryGetPointedToType(&builder, getOffsetPtrInst->getDataType());
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    elementType,
                    &sizeAlignment);
                writeInst(
                    funcBuilder,
                    VMOp::OffsetPtr,
                    (uint32_t)sizeAlignment.getStride(),
                    ensureWorkingsetMemory(funcBuilder, inst),
                    ensureInst(base),
                    ensureInst(offset));
            }
            break;
        case kIROp_FieldExtract:
            {
                auto fieldExtractInst = as<IRFieldExtract>(inst);
                auto base = fieldExtractInst->getBase();
                auto fieldKey = (IRStructKey*)fieldExtractInst->getField();

                auto structType = as<IRStructType>(base->getDataType());
                IRIntegerValue offset = 0;
                auto field = findStructField(structType, fieldKey);
                getNaturalOffset(codeGenContext->getTargetReq(), field, &offset);

                auto baseOperand = ensureInst(base);
                baseOperand.offset += (uint32_t)offset;
                mapInstToOperand[inst] = baseOperand;
            }
            break;
        case kIROp_GetElement:
            {
                auto getElemInst = as<IRGetElement>(inst);
                auto base = getElemInst->getBase();
                auto index = getElemInst->getIndex();
                auto elementType = getElemInst->getDataType();
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    elementType,
                    &sizeAlignment);
                auto stride = sizeAlignment.getStride();
                auto baseOperand = ensureInst(base);
                if (as<IRIntLit>(index))
                {
                    baseOperand.offset += (uint32_t)(stride * getIntVal(index));
                    mapInstToOperand[inst] = baseOperand;
                    break;
                }
                writeInst(
                    funcBuilder,
                    VMOp::GetElement,
                    (uint32_t)stride,
                    ensureWorkingsetMemory(funcBuilder, inst),
                    baseOperand,
                    ensureInst(index));
            }
            break;
        case kIROp_BitCast:
            {
                auto operand = ensureInst(inst->getOperand(0));
                mapInstToOperand[inst] = operand;
            }
            break;
        case kIROp_IntCast:
        case kIROp_CastIntToPtr:
        case kIROp_CastPtrToInt:
        case kIROp_CastIntToFloat:
        case kIROp_CastFloatToInt:
        case kIROp_FloatCast:
            emitCast(funcBuilder, VMOp::Cast, inst);
            break;
        case kIROp_Swizzle:
            {
                auto swizzleInst = as<IRSwizzle>(inst);
                auto base = swizzleInst->getBase();
                auto baseOperand = ensureInst(base);
                auto count = (uint32_t)swizzleInst->getElementCount();
                List<VMOperand> operands;
                operands.add(ensureWorkingsetMemory(funcBuilder, inst));
                operands.add(baseOperand);
                for (UInt i = 0; i < count; ++i)
                {
                    auto index = (uint32_t)getIntVal(swizzleInst->getElementIndex(i));
                    VMOperand operand;
                    operand.sectionId = kSlangByteCodeSectionImmediate;
                    operand.offset = index;
                    operands.add(operand);
                }
                writeInst(
                    funcBuilder,
                    VMOp::Swizzle,
                    getExtCode(inst->getDataType()),
                    operands.getArrayView());
            }
            break;
        case kIROp_MakeArray:
            {
                auto result = ensureWorkingsetMemory(funcBuilder, inst);
                auto arrayType = as<IRArrayTypeBase>(inst->getDataType());
                auto elementType = arrayType->getElementType();
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    elementType,
                    &sizeAlignment);
                auto stride = (uint32_t)sizeAlignment.getStride();
                for (UInt i = 0; i < inst->getOperandCount(); ++i)
                {
                    VMOperand elementOperand = result;
                    elementOperand.offset += (uint32_t)(stride * i);
                    writeInst(
                        funcBuilder,
                        VMOp::Copy,
                        stride,
                        elementOperand,
                        ensureInst(inst->getOperand(i)));
                }
            }
            break;
        case kIROp_MakeArrayFromElement:
            {
                auto result = ensureWorkingsetMemory(funcBuilder, inst);
                auto arrayType = as<IRArrayTypeBase>(inst->getDataType());
                auto elementType = arrayType->getElementType();
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    elementType,
                    &sizeAlignment);
                auto stride = (uint32_t)sizeAlignment.getStride();
                for (Index i = 0; i < getIntVal(arrayType->getElementCount()); ++i)
                {
                    VMOperand elementOperand = result;
                    elementOperand.offset += (uint32_t)(stride * i);
                    writeInst(
                        funcBuilder,
                        VMOp::Copy,
                        stride,
                        elementOperand,
                        ensureInst(inst->getOperand(0)));
                }
            }
            break;
        case kIROp_MakeStruct:
            {
                auto result = ensureWorkingsetMemory(funcBuilder, inst);
                auto structType = as<IRStructType>(inst->getDataType());
                List<IRStructField*> fields;
                for (auto field : structType->getFields())
                {
                    fields.add(field);
                }
                for (UInt i = 0; i < inst->getOperandCount(); ++i)
                {
                    auto field = fields[i];
                    IRIntegerValue offset = 0;
                    getNaturalOffset(codeGenContext->getTargetReq(), field, &offset);
                    IRSizeAndAlignment sizeAlignment = {};
                    getNaturalSizeAndAlignment(
                        codeGenContext->getTargetReq(),
                        field->getFieldType(),
                        &sizeAlignment);
                    VMOperand elementOperand = result;
                    elementOperand.offset += (uint32_t)offset;
                    writeInst(
                        funcBuilder,
                        VMOp::Copy,
                        (uint32_t)sizeAlignment.getStride(),
                        elementOperand,
                        ensureInst(inst->getOperand(i)));
                }
            }
            break;
        case kIROp_MakeVector:
        case kIROp_MakeMatrix:
            {
                auto result = ensureWorkingsetMemory(funcBuilder, inst);
                for (UInt i = 0; i < inst->getOperandCount(); ++i)
                {
                    VMOperand elementOperand = result;
                    IRSizeAndAlignment sizeAlignment = {};
                    getNaturalSizeAndAlignment(
                        codeGenContext->getTargetReq(),
                        inst->getOperand(i)->getDataType(),
                        &sizeAlignment);
                    writeInst(
                        funcBuilder,
                        VMOp::Copy,
                        (uint32_t)sizeAlignment.getStride(),
                        elementOperand,
                        ensureInst(inst->getOperand(i)));
                    result.offset += (uint32_t)sizeAlignment.getStride();
                }
            }
            break;
        case kIROp_MakeVectorFromScalar:
            {
                auto result = ensureWorkingsetMemory(funcBuilder, inst);
                auto vectorType = as<IRVectorType>(inst->getDataType());
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    vectorType->getElementType(),
                    &sizeAlignment);
                auto stride = (uint32_t)sizeAlignment.getStride();
                for (Index i = 0; i < getIntVal(vectorType->getElementCount()); ++i)
                {
                    VMOperand elementOperand = result;
                    elementOperand.offset += (uint32_t)(stride * i);
                    writeInst(
                        funcBuilder,
                        VMOp::Copy,
                        stride,
                        elementOperand,
                        ensureInst(inst->getOperand(0)));
                }
            }
            break;
        case kIROp_MakeMatrixFromScalar:
            {
                auto result = ensureWorkingsetMemory(funcBuilder, inst);
                auto matrixType = as<IRMatrixType>(inst->getDataType());
                IRSizeAndAlignment sizeAlignment = {};
                getNaturalSizeAndAlignment(
                    codeGenContext->getTargetReq(),
                    matrixType->getElementType(),
                    &sizeAlignment);
                auto stride = (uint32_t)sizeAlignment.getStride();
                for (Index i = 0; i < getIntVal(matrixType->getRowCount()); ++i)
                {
                    for (Index j = 0; j < getIntVal(matrixType->getColumnCount()); ++j)
                    {
                        writeInst(
                            funcBuilder,
                            VMOp::Copy,
                            stride,
                            result,
                            ensureInst(inst->getOperand(0)));
                        result.offset += stride;
                    }
                }
            }
            break;
        case kIROp_Printf:
            {
                List<VMOperand> operands;
                operands.add(ensureInst(inst->getOperand(0)));
                auto tuple = inst->getOperand(1);
                if (auto makeTuple = as<IRMakeStruct>(tuple))
                {
                    for (UInt i = 0; i < makeTuple->getOperandCount(); i++)
                    {
                        operands.add(ensureInst(makeTuple->getOperand(i)));
                    }
                }
                else
                {
                    // If not a tuple, it should be a single value.
                    operands.add(ensureInst(tuple));
                }
                writeInst(funcBuilder, VMOp::Print, 0, operands.getArrayView());
            }
            break;
        default:
            SLANG_UNIMPLEMENTED_X("VM bytecode gen for inst.");
        }
    }

    bool isUnreachableBlock(IRBlock* block)
    {
        return as<IRUnreachable>(block->getTerminator()) != nullptr;
    }

    void emitFunction(IRFunc* func)
    {
        // Bail out with a clean diagnostic before emitting any instruction if this
        // function references a global shader parameter, which the interpreter does
        // not support.
        if (diagnoseGlobalParamReferences(func))
            return;

        VMByteCodeFunctionBuilder funcBuilder;
        funcBuilder.name = addStringLiteral(getName(func).getUnownedSlice());

        IRSizeAndAlignment sizeAlignment = {};
        getNaturalSizeAndAlignment(
            codeGenContext->getTargetReq(),
            func->getResultType(),
            &sizeAlignment);
        funcBuilder.resultSize = (uint32_t)sizeAlignment.getStride();

        Dictionary<IRBlock*, Index> mapBlockToByteOffset;
        List<InstRelocationEntry> relocations;

        for (auto block : func->getBlocks())
        {
            if (isUnreachableBlock(block))
                continue;

            mapBlockToByteOffset[block] = funcBuilder.code.getCount();

            for (auto inst : block->getChildren())
            {
                funcBuilder.instOffsets.add(funcBuilder.code.getCount());
                emitInst(funcBuilder, inst, relocations);
            }
        }

        // Apply relocations for jump targets.
        for (auto reloc : relocations)
        {
            Index offset = mapBlockToByteOffset.getValue(reloc.block);
            uint8_t* codePtr = (funcBuilder.code.getBuffer() + reloc.offsetToOperand);
            VMOperand* operand = (VMOperand*)codePtr;
            operand->sectionId = kSlangByteCodeSectionInsts;
            operand->offset = (uint32_t)offset;
        }
        funcBuilder.workingSetSizeInBytes =
            alignUp(funcBuilder.workingSetSizeInBytes, (uint32_t)sizeof(uint64_t));

        byteCodeBuilder.functions.add(funcBuilder);
    }

    void emitEntryPoints(LinkedIR& linkedIR)
    {
        Dictionary<IRInst*, HashSet<IRFunc*>> referencingEntryPoints;
        buildEntryPointReferenceGraph(referencingEntryPoints, linkedIR.module);
        OrderedHashSet<IRFunc*> entryPointSet;
        for (auto entryPoint : linkedIR.entryPoints)
        {
            auto entryPointDecor = entryPoint->findDecoration<IREntryPointDecoration>();
            if (!entryPointDecor)
                continue;
            if (entryPointDecor->getProfile().getStage() != Stage::Dispatch)
                continue;
            entryPointSet.add(entryPoint);
        }

        List<IRFunc*> functionsToEmit;

        // Emit all entrypoints first.
        for (auto entryPoint : entryPointSet)
        {
            // Emit the function for the entry point.
            functionsToEmit.add(entryPoint);
        }

        // Emit remaining funcitons, if they are called by entry points.
        for (auto globalInst : linkedIR.module->getGlobalInsts())
        {
            auto func = as<IRFunc>(globalInst);

            if (!func)
                continue;

            // Skip if already emitted as an entry point.
            if (entryPointSet.contains(func))
                continue;

            HashSet<IRFunc*>* entryPointRefs = referencingEntryPoints.tryGetValue(func);
            if (!entryPointRefs)
                continue;

            // If the function is referenced by any entry point, emit it.
            bool referencedByHostEntryPoint = false;
            for (auto entryPoint : *entryPointRefs)
            {
                if (entryPointSet.contains(entryPoint))
                {
                    referencedByHostEntryPoint = true;
                    break;
                }
            }
            if (referencedByHostEntryPoint)
            {
                functionsToEmit.add(func);
            }
        }

        // Emit all functions.
        for (Index i = 0; i < functionsToEmit.getCount(); i++)
        {
            mapFuncToId[functionsToEmit[i]] = (int)i;
        }
        for (auto func : functionsToEmit)
        {
            emitFunction(func);
        }
    }
};

SlangResult emitVMByteCodeForEntryPoints(
    CodeGenContext* codeGenContext,
    LinkedIR& linkedIR,
    VMByteCodeBuilder& byteCode)
{
    auto sink = codeGenContext->getSink();
    int errorCountBefore = sink->getErrorCount();

    ByteCodeEmitter emitter(byteCode, codeGenContext);
    emitter.emitEntryPoints(linkedIR);

    // If emission reported a diagnostic (e.g. an unsupported global parameter),
    // fail cleanly so the emitted bytecode is not used, rather than returning
    // success with malformed output.
    if (sink->getErrorCount() != errorCountBefore)
        return SLANG_FAIL;

    return SLANG_OK;
}

SlangResult VMByteCodeBuilder::serialize(slang::IBlob** outBlob)
{
    OwnedMemoryStream ms(FileAccess::Write);
    ms.write(&kSlangByteCodeFourCC, sizeof(uint32_t));
    ms.write(&kSlangByteCodeVersion, sizeof(uint32_t));

    // Write functions section.
    ms.write(&kSlangByteCodeFunctionsFourCC, sizeof(uint32_t));
    uint32_t functionChunkSizeStart = (uint32_t)ms.getPosition();
    uint32_t zero = 0;
    ms.write(&zero, sizeof(uint32_t)); // Reserve space for function chunk size.

    uint32_t functionCount = (uint32_t)functions.getCount();
    ms.write(&functionCount, sizeof(uint32_t));
    // Reserve space for function offsets.
    auto functionOffsetStart = ms.getPosition();
    for (uint32_t i = 0; i < functionCount; ++i)
    {
        ms.write(&zero, sizeof(uint32_t));
    }
    List<uint32_t> functionOffsets;
    for (uint32_t i = 0; i < functionCount; ++i)
    {
        functionOffsets.add((uint32_t)ms.getPosition());

        auto& function = functions[i];
        VMFuncHeader funcHeader;
        funcHeader.name = function.name;
        funcHeader.codeSize = (uint32_t)function.code.getCount();
        funcHeader.parameterCount = (uint32_t)function.parameterOffsets.getCount();
        funcHeader.workingSetSizeInBytes = function.workingSetSizeInBytes;
        funcHeader.returnValueSizeInBytes = function.resultSize;
        funcHeader.parameterSizeInBytes = function.parameterSize;
        ms.write(&funcHeader, sizeof(funcHeader));
        ms.write(
            function.parameterOffsets.getBuffer(),
            sizeof(uint32_t) * function.parameterOffsets.getCount());

        ms.write(function.code.begin(), funcHeader.codeSize);
    }
    uint32_t functionChunkSize =
        (uint32_t)(ms.getPosition() - functionChunkSizeStart - sizeof(uint32_t));

    // Write kernel Blob section.
    ms.write(&kSlangByteCodeKernelBlobFourCC, sizeof(uint32_t));
    uint32_t kernelBlobSize = (uint32_t)kernelBlob->getBufferSize();
    ms.write(&kernelBlobSize, sizeof(uint32_t));
    ms.write(kernelBlob->getBufferPointer(), kernelBlobSize);

    // Write constant section.
    ms.write(&kSlangByteCodeConstantsFourCC, sizeof(uint32_t));
    uint32_t constanBlobSize = (uint32_t)constantSection.getCount();
    ms.write(&constanBlobSize, sizeof(uint32_t));
    uint32_t stringCount = (uint32_t)stringOffsets.getCount();
    ms.write(&stringCount, sizeof(uint32_t));
    ms.write(stringOffsets.getBuffer(), sizeof(uint32_t) * stringCount);
    ms.write(constantSection.begin(), constanBlobSize);

    auto blob = RawBlob::create(ms.getContents().getBuffer(), ms.getContents().getCount());

    // Patch in the function chunk size.
    uint32_t* functionChunkSizePtr =
        (uint32_t*)((uint8_t*)blob->getBufferPointer() + functionChunkSizeStart);
    *functionChunkSizePtr = functionChunkSize;

    // Patch in the function offsets.
    auto funcOffsetTable = (uint32_t*)((uint8_t*)blob->getBufferPointer() + functionOffsetStart);
    for (uint32_t i = 0; i < functionCount; ++i)
    {
        funcOffsetTable[i] = functionOffsets[i];
    }

    *outBlob = blob.detach();
    return SLANG_OK;
}

} // namespace Slang
