Raw File
bytecode.cpp
#include "bytecode.h"

// Implementation of the Slang bytecode (BC)
// (most notably including conversion from IR to BC)

#include "compiler.h"
#include "ir.h"
#include "ir-insts.h"
#include "lower-to-ir.h"

namespace Slang
{
struct SharedBytecodeGenerationContext;

// Representation of a `BCPtr<T>` during actual bytecode generation.
// This representation is to deal with the fact that the actual
// storage for the bytecode data might get reallocated during emission
// so that we need to be careful and not work with raw `BCPtr<T>`.
template<typename T>
struct BytecodeGenerationPtr
{
    UInt                                offset;
    SharedBytecodeGenerationContext*    sharedContext;

    BytecodeGenerationPtr()
        : sharedContext(nullptr)
        , offset(0)
    {}


    BytecodeGenerationPtr(
        SharedBytecodeGenerationContext*    sharedContext,
        UInt                                offset)
        : sharedContext(sharedContext)
        , offset(offset)
    {}

    BytecodeGenerationPtr(
        BytecodeGenerationPtr<T> const& ptr)
        : sharedContext(ptr.sharedContext)
        , offset(ptr.offset)
    {}

    template<typename U>
    BytecodeGenerationPtr(
        BytecodeGenerationPtr<U> const& ptr,
        typename EnableIf<IsConvertible<T*, U*>::Value, void>::type * = 0)
        : sharedContext(ptr.sharedContext)
        , offset(ptr.offset)
    {}

    template<typename U>
    BytecodeGenerationPtr<U> bitCast() const
    {
        return BytecodeGenerationPtr<U>(sharedContext, offset);
    }

    operator BCPtr<T>() const
    {
        return BCPtr<T>(getPtr());
    }

    T* operator->() const
    {
        return getPtr();
    }

    T& operator*() const
    {
        return *getPtr();
    }

    T& operator[](UInt index) const
    {
        return getPtr()[index];
    }

    BytecodeGenerationPtr<T> operator+(Int index) const
    {
        Int delta = index * sizeof(T);
        UInt newOffset = offset + delta;
        return BytecodeGenerationPtr<T>(
            sharedContext,
            newOffset);
    }

    T* getPtr() const;
};

#if 0
template<typename T>
void BCPtr<T>::operator=(BytecodeGenerationPtr<T> const& ptr)
{
    fprintf(stderr, "0x%p: operator=BGP 0x%p\n", this, ptr.getPtr());
    *this = ptr.getPtr();
}
#endif

struct SharedBytecodeGenerationContext
{
    // The final generated bytecode stream
    List<uint8_t>   bytecode;

    // Map from an IR value to a global entity
    // that encodes it:
    Dictionary<IRInst*, BCConst> mapValueToGlobal;

    // Types that have been emitted
    List<BytecodeGenerationPtr<BCType>> bcTypes;
    Dictionary<IRType*, UInt> mapTypeToID;

    // Compile-time constant values that need
    // to be emitted...
    List<IRInst*>  constants;
};

struct BytecodeGenerationContext
{
    SharedBytecodeGenerationContext*    shared;

    // The bytecode of the current symbol being
    // output.
    List<uint8_t>   currentBytecode;

    // The function that is in scope for this context
    IRFunc* currentIRFunc;

    // Counter for global symbols that have been assigned
    // so that they can be used by this function
    List<BCConst>  remappedGlobalSymbols;

    // Map an instruction to its ID for use local
    // to the current context
    Dictionary<IRInst*, Int> mapInstToLocalID;
};

template<typename T>
T* BytecodeGenerationPtr<T>::getPtr() const
{
    if(!sharedContext) return nullptr;
    return (T*)(sharedContext->bytecode.Buffer() + offset);
}


BCPtr<void>::RawVal allocateRaw(
    BytecodeGenerationContext*  context,
    size_t                      size,
    size_t                      alignment)
{
    size_t currentOffset = context->shared->bytecode.Count();

    size_t beginOffset = (currentOffset + (alignment-1)) & ~(alignment-1);

    size_t endOffset = beginOffset + size;

    for(size_t ii = currentOffset; ii < endOffset; ++ii)
        context->shared->bytecode.Add(0);

    return (BCPtr<void>::RawVal)beginOffset;
}

template<typename T>
BytecodeGenerationPtr<T> allocate(
    BytecodeGenerationContext*  context)
{
    return BytecodeGenerationPtr<T>(context->shared, allocateRaw(context, sizeof(T), alignof(T)));
}

template<typename T>
BytecodeGenerationPtr<T> allocateArray(
    BytecodeGenerationContext*  context,
    UInt                        count)
{
    return BytecodeGenerationPtr<T>(context->shared, allocateRaw(context, count * sizeof(T), alignof(T)));
}

template<typename T>
BytecodeGenerationPtr<T> getPtr(
    BytecodeGenerationContext*  context)
{
    return BytecodeGenerationPtr<T>(context->shared, context->shared->bytecode.Count());
}


void encodeUInt8(
    BytecodeGenerationContext*  context,
    uint8_t                     value)
{
    context->currentBytecode.Add(value);
}

void encodeUInt(
    BytecodeGenerationContext*  context,
    UInt                        value)
{
    if( value < 128 )
    {
        encodeUInt8(context, (uint8_t)value);
        return;
    }

    uint8_t bytes[16];
    UInt count = 0;

    for(;;)
    {
        UInt index = count++;
        bytes[index] = value & 0x7F;
        value = value >> 7;
        if (!value)
            break;

        bytes[index] |= 0x80;
    }

    UInt index = count;
    while (index--)
    {
        encodeUInt8(context, bytes[index]);
    }
}

void encodeSInt(
    BytecodeGenerationContext*  context,
    Int                         value)
{
    UInt uValue;
    if( value < 0 )
    {
        uValue = (~UInt(value) << 1) | 1;
    }
    else
    {
        uValue = UInt(value) << 1;
    }

    encodeUInt(context, uValue);
}

BCConst getGlobalValue(
    BytecodeGenerationContext*  context,
    IRInst*                     value)
{
    {
        BCConst bcConst;
        if (context->shared->mapValueToGlobal.TryGetValue(value, bcConst))
            return bcConst;
    }
    // Next we need to check for things that can be mapped to
    // global IDs on the fly.

    switch( value->op )
    {
    case kIROp_IntLit:
        {
            UInt constID = context->shared->constants.Count();
            context->shared->constants.Add(value);

            BCConst bcConst;
            bcConst.flavor = kBCConstFlavor_Constant;
            bcConst.id = (uint32_t)constID;

            context->shared->mapValueToGlobal.Add(value, bcConst);

            return bcConst;
        }
        break;

    default:
        break;
    }

    SLANG_UNEXPECTED("no ID for inst");
    {
        UNREACHABLE(BCConst bcConst);
        UNREACHABLE(bcConst.flavor = (BCConstFlavor)-1);
        UNREACHABLE(bcConst.id = -9999);
        UNREACHABLE_RETURN(bcConst);
    }
}

Int getLocalID(
    BytecodeGenerationContext*  context,
    IRInst*                     value)
{
    Int localID = 0;
    if( context->mapInstToLocalID.TryGetValue(value, localID) )
    {
        return localID;
    }

    BCConst bcConst = getGlobalValue(context, value);
    UInt remappedSymbolIndex = context->remappedGlobalSymbols.Count();
    context->remappedGlobalSymbols.Add(bcConst);

    localID = ~remappedSymbolIndex;
    context->mapInstToLocalID.Add(value, localID);
    return localID;
}

void encodeOperand(
    BytecodeGenerationContext*  context,
    IRInst*                     operand)
{
    auto id = getLocalID(context, operand);
    encodeSInt(context, id);
}

uint32_t getTypeID(
    BytecodeGenerationContext*  context,
    IRType*                     type);

void encodeOperand(
    BytecodeGenerationContext*  context,
    IRType*                     type)
{
    encodeUInt(context, getTypeID(context, type));
}

bool opHasResult(IRInst* inst)
{
    auto type = inst->getDataType();
    if (!type) return false;

    // As a bit of a hack right now, we need to check whether
    // the function returns the distinguished `Void` type,
    // since that is conceptually the same as "not returning
    // a value."
    if(type->op == kIROp_VoidType)
        return false;

    return true;
}

void generateBytecodeForInst(
    BytecodeGenerationContext*  context,
    IRInst*                     inst)
{
    // We are generating bytecode for a local instruction
    // inside a function or similar context.
    switch( inst->op )
    {
    default:
        {
            // As a default case, we will assume that bytecode ops
            // and the IR's internal opcodes are the same, and then
            // encode the necessary extra info:
            //

            auto operandCount = inst->getOperandCount();
            encodeUInt(context, inst->op);
            encodeOperand(context, inst->getDataType());
            encodeUInt(context, operandCount);
            for( UInt aa = 0; aa < operandCount; ++aa )
            {
                encodeOperand(context, inst->getOperand(aa));
            }

            if (!opHasResult(inst))
            {
                // This instructions has no type, so don't emit a destination
            }
            else
            {
                // The instruction can be encoded
                // as its own operand for the destination.
                encodeOperand(context, inst);
            }
        }
        break;

    case kIROp_ReturnVoid:
        // Trivial encoding here
        encodeUInt(context, inst->op);
        break;

    case kIROp_IntLit:
        {
            auto ii = (IRConstant*) inst;
            encodeUInt(context, ii->op);
            encodeOperand(context, ii->getDataType());

            // TODO: probably want distinct encodings
            // for signed vs. unsigned here.
            encodeUInt(context, UInt(ii->value.intVal));

            // destination:
            encodeOperand(context, inst);
        }
        break;

    case kIROp_FloatLit:
        {
            auto cInst = (IRConstant*) inst;
            encodeUInt(context, cInst->op);
            encodeOperand(context, cInst->getDataType());

            static const UInt size = sizeof(IRFloatingPointValue);
            unsigned char buffer[size];
            memcpy(buffer, &cInst->value.floatVal, sizeof(buffer));

            for(UInt ii = 0; ii < size; ++ii)
            {
                encodeUInt8(context, buffer[ii]);
            }

            // destination:
            encodeOperand(context, inst);
        }
        break;

    case kIROp_BoolLit:
        {
            auto ii = (IRConstant*) inst;
            encodeUInt(context, ii->op);
            encodeUInt(context, ii->value.intVal ? 1 : 0);

            // destination:
            encodeOperand(context, inst);
        }
        break;

#if 0
    case kIROp_Func:
        {
            encodeUInt(context, inst->op);

            // We just want to encode the ID for the function
            // symbol data, and then do the rest on the decode side
            UInt nestedID = 0;
            context->mapInstToNestedID.TryGetValue(inst, nestedID);
            encodeUInt(context, nestedID);

            // destination:
            encodeOperand(context, inst);
        }
        break;
#endif

    case kIROp_Store:
        {
            encodeUInt(context, inst->op);

            // We need to encode the type being stored, to make
            // our lives easier.
            encodeOperand(context, inst->getOperand(1)->getDataType());
            encodeOperand(context, inst->getOperand(0));
            encodeOperand(context, inst->getOperand(1));
        }
        break;

    case kIROp_Load:
        {
            encodeUInt(context, inst->op);
            encodeOperand(context, inst->getDataType());
            encodeOperand(context, inst->getOperand(0));
            encodeOperand(context, inst);
        }
        break;
    }
}

BytecodeGenerationPtr<BCType> emitBCType(
    BytecodeGenerationContext*              context,
    IRType*                                 type,
    IROp                                    op,
    BytecodeGenerationPtr<uint8_t> const*   args,
    UInt                                    argCount)
{
    UInt size = sizeof(BCType)
        + argCount * sizeof(BCPtr<void>);

    BytecodeGenerationPtr<uint8_t> bcAllocation(
        context->shared,
        allocateRaw(context, size, alignof(BCPtr<void>)));

    BytecodeGenerationPtr<BCType> bcType = bcAllocation.bitCast<BCType>();
    auto bcArgs = (bcType + 1).bitCast<BCPtr<uint8_t>>();

    bcType->op = op;
    bcType->argCount = (uint32_t)argCount;

    for(UInt aa = 0; aa < argCount; ++aa)
    {
        bcArgs[aa] = args[aa];
    }

    UInt id = context->shared->bcTypes.Count();
    context->shared->mapTypeToID.Add(type, id);
    context->shared->bcTypes.Add(bcType);
    bcType->id = (uint32_t)id;

    return bcType;
}

BytecodeGenerationPtr<BCType> emitBCVarArgType(
    BytecodeGenerationContext*              context,
    IRType*                                 type,
    IROp                                    op,
    List<BytecodeGenerationPtr<uint8_t>>    args)
{
    return emitBCType(context, type, op, args.Buffer(), args.Count());
}

BytecodeGenerationPtr<BCType> emitBCType(
    BytecodeGenerationContext*  context,
    IRType*                     type,
    IROp                        op)
{
    return emitBCType(context, type, op, nullptr, 0);
}

BytecodeGenerationPtr<BCType> emitBCType(
    BytecodeGenerationContext*  context,
    IRType*                     type);

// Emit a `BCType` representation for the given `Type`
BytecodeGenerationPtr<BCType> emitBCTypeImpl(
    BytecodeGenerationContext*  context,
    IRType*                     type)
{
    // A NULL type is interpreted as equivalent to `Void` for now.
    if( !type )
    {
        return emitBCType(context, type, kIROp_VoidType);
    }

    List<BytecodeGenerationPtr<uint8_t>> operands;
    UInt operandCount = type->getOperandCount();
    for (UInt ii = 0; ii < operandCount; ++ii)
    {
        operands.Add(emitBCType(context, (IRType*) type->getOperand(ii)).bitCast<uint8_t>());
    }
    return emitBCVarArgType(context, type, type->op, operands);
}

BytecodeGenerationPtr<BCType> emitBCType(
    BytecodeGenerationContext*  context,
    IRType*                     type)
{
    auto canonical = type->getCanonicalType();
    UInt id = 0;
    if(context->shared->mapTypeToID.TryGetValue(canonical, id))
    {
        return context->shared->bcTypes[id];
    }

    BytecodeGenerationPtr<BCType> bcType = emitBCTypeImpl(context, canonical);
    return bcType;
}

uint32_t getTypeID(
    BytecodeGenerationContext*  context,
    IRType*                     type)
{
    // We have a type, and we need to emit it (if we haven't
    // already) and return its index in the global type table.
    BytecodeGenerationPtr<BCType> bcType = emitBCType(context, type);
    return bcType->id;
}

uint32_t getTypeIDForGlobalSymbol(
    BytecodeGenerationContext*  context,
    IRInst*                     inst)
{
    auto type = inst->getDataType();
    if(!type)
        return 0;

    return getTypeID(context, type);
}

BytecodeGenerationPtr<char> allocateString(
    BytecodeGenerationContext*  context,
    char const*                 data,
    UInt                        size)
{
    BytecodeGenerationPtr<char> ptr = allocateArray<char>(context, size + 1);
    memcpy(ptr.getPtr(), data, size);
    return ptr;
}

BytecodeGenerationPtr<char> allocateString(
    BytecodeGenerationContext*  context,
    String const&               str)
{
    return allocateString(context,
        str.Buffer(),
        str.Length());
}

BytecodeGenerationPtr<char> allocateString(
    BytecodeGenerationContext*  context,
    Name*                       name)
{
    return allocateString(context, name->text);
}

BytecodeGenerationPtr<char> tryGenerateNameForSymbol(
    BytecodeGenerationContext*      context,
    IRGlobalValue*                  inst)
{
    // TODO: this is gross, and the IR should probably have
    // a more direct means of querying a name for a symbol.
    if (auto highLevelDeclDecoration = inst->findDecoration<IRHighLevelDeclDecoration>())
    {
        auto decl = highLevelDeclDecoration->decl;
        if (auto reflectionNameMod = decl->FindModifier<ParameterGroupReflectionName>())
        {
            return allocateString(context, reflectionNameMod->name);
        }
        else if(auto name = decl->nameAndLoc.name)
        {
            return allocateString(context, name);
        }
    }

    return BytecodeGenerationPtr<char>();
}

// Generate a `BCSymbol` that can represent a global value.
BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst(
    BytecodeGenerationContext*  context,
    IRGlobalValue*              inst)
{
    switch( inst->op )
    {
    case kIROp_Func:
        {
            auto irFunc = (IRFunc*) inst;
            BytecodeGenerationPtr<BCFunc> bcFunc = allocate<BCFunc>(context);

            bcFunc->op = inst->op;
            bcFunc->typeID = getTypeIDForGlobalSymbol(context, inst);

            BytecodeGenerationContext   subContextStorage;
            BytecodeGenerationContext*  subContext = &subContextStorage;
            subContext->shared = context->shared;
            subContext->currentIRFunc = irFunc;

            // First we need to enumerate our basic blocks, so that they
            // can reference one another (basic blocks can forward reference
            // blocks that haven't been seen yet).
            //
            // Note: we allow the IDs of blocks to overlap with ordinary
            // "register" numbers, because there is no case where an operand
            // could be either a block or an ordinary register.
            //
            UInt blockCounter = 0;
            for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() )
            {
                Int blockID = Int(blockCounter++);
                subContext->mapInstToLocalID.Add(bb, blockID);
            }
            UInt blockCount = blockCounter;

            // Allocate the array of block objects to be stored in the
            // bytecode file.
            auto bcBlocks = allocateArray<BCBlock>(context, blockCount);
            bcFunc->blockCount = (uint32_t)blockCount;
            bcFunc->blocks = bcBlocks;

            // Now loop through the blocks again, and allocate the storage
            // for any parameters, variables, or registers used inside
            // each block.
            //
            // We'll count in a first pass, and then fill things in
            // using a second pass
            Int regCounter = 0;
            blockCounter = 0;
            for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() )
            {
                UInt blockID = blockCounter++;
                UInt paramCount = 0;

                for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
                {
                    switch( ii->op )
                    {
                    default:
                        // Default behavior: if an op has a result,
                        // then it needs a register to store it.
                        if(opHasResult(ii))
                        {
                            regCounter++;
                        }
                        break;

                    case kIROp_Param:
                        // A parameter always uses a register.
                        regCounter++;
                        //
                        // We also want to keep a count of the parameters themselves.
                        paramCount++;
                        break;

                    case kIROp_Var:
                        // A `var` (`alloca`) node needs two registers:
                        // one to hold the actual storage, and another
                        // to hold the pointer.
                        regCounter += 2;
                        break;
                    }
                }

                bcBlocks[blockID].paramCount = (uint32_t)paramCount;
            }

            // Okay, we've counted how many registers we need for each block,
            // and now we can allocate the contiguous array we will use.
            UInt regCount = regCounter;
            auto bcRegs = allocateArray<BCReg>(context, regCount);

            bcFunc->regCount = (uint32_t)regCount;
            bcFunc->regs = bcRegs;

            // Now we will loop over things again to fill in the information
            // on all these registers.

            regCounter = 0;
            blockCounter = 0;
            for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() )
            {
                UInt blockID = blockCounter++;

                // Loop over the instruction in the block, to allocate registers
                // for them. The parameters of a block will always be the first
                // N instructions in the block, so they will always get the
                // first N registers in that block. Similarly, the entry block
                // is always the first block, so that the parameters of the function
                // will always be the first N registers.
                //
                bcBlocks[blockID].params = bcRegs + regCounter;
                for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
                {
                    switch(ii->op)
                    {
                    default:
                        // For a parameter, or an ordinary instruction with
                        // a result, allocate it here.
                        if( opHasResult(ii) )
                        {
                            Int localID = regCounter++;
                            subContext->mapInstToLocalID.Add(ii, localID);

                            bcRegs[localID].op = ii->op;
#if 0
                            bcRegs[localID].name = tryGenerateNameForSymbol(context, ii);
#endif
                            bcRegs[localID].previousVarIndexPlusOne = (uint32_t)localID;
                            bcRegs[localID].typeID = getTypeIDForGlobalSymbol(context, ii);
                        }
                        break;

                    case kIROp_Var:
                        // As handled in the earlier loop, we are
                        // allocating *two* locations for each `var`
                        // instruction. The first of these will be
                        // the actual pointer value, while the second
                        // will be the storage for the variable value.
                        {
                            Int localID = regCounter;
                            regCounter += 2;

                            subContext->mapInstToLocalID.Add(ii, localID);
                            bcRegs[localID].op = ii->op;
#if 0
                            bcRegs[localID].name = tryGenerateNameForSymbol(context, ii);
#endif
                            bcRegs[localID].previousVarIndexPlusOne = (uint32_t)localID;
                            bcRegs[localID].typeID = getTypeIDForGlobalSymbol(context, ii);

                            bcRegs[localID+1].op = ii->op;
                            bcRegs[localID+1].previousVarIndexPlusOne = (uint32_t)localID+1;
                            bcRegs[localID+1].typeID = getTypeID(context,
                                (as<IRPtrType>(ii->getDataType()))->getValueType());
                        }
                        break;
                    }
                }
            }
            assert((UInt)regCounter == regCount);

            // Now that we've allocated our blocks and our registers
            // we can go through the actual process of emitting instructions. Hooray!
            blockCounter = 0;

            // Offset of each basic block from the start of the code
            // for the current funciton.
            List<UInt> blockOffsets;
            for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() )
            {
                blockCounter++;

                // Get local bytecode offset for current block.
                UInt blockOffset = subContext->currentBytecode.Count();
                blockOffsets.Add( blockOffset );

                for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() )
                {
                    // What we do with each instruction depends a bit on the
                    // kind of instruction it is.
                    switch( ii->op )
                    {
                    default:
                        // For most instructions we just emit their bytecode
                        // ops directly.
                        generateBytecodeForInst(subContext, ii);
                        break;

                    case kIROp_Param:
                        // Don't actually emit code for these, because
                        // there isn't really anything to *execute*
                        //
                        // Note that we *do* allow the `var` nodes
                        // to be executed, just because they need
                        // to set up a register with the pointer value.
                        break;
                    }
                }
            }


            // We've collected bytecode for the instruction stream
            // into a sub-context, so we can now append that code.
            UInt byteCount = subContext->currentBytecode.Count();
            BytecodeGenerationPtr<uint8_t> bytes = allocateArray<uint8_t>(context, byteCount);
            memcpy(&bytes[0], subContext->currentBytecode.Buffer(), byteCount);

            // Now that we've allocated the storage, we can write
            // the bytecode pointers into the blocks.
            blockCounter = 0;
            for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() )
            {
                UInt blockID = blockCounter++;
                bcBlocks[blockID].code = bytes + blockOffsets[blockID];
            }

            // Finally, after emitting all the instructions we can
            // build a table of global symbols taht need to be
            // imported into the current function as constants.
            UInt constCount = subContext->remappedGlobalSymbols.Count();
            auto bcConsts = allocateArray<BCConst>(context, constCount);

            bcFunc->constCount = (uint32_t)constCount;
            bcFunc->consts = bcConsts;

            for( UInt cc = 0; cc < constCount; ++cc )
            {
                bcConsts[cc] = subContext->remappedGlobalSymbols[cc];
            }

            return bcFunc;
        }
        break;

    case kIROp_GlobalVar:
    case kIROp_GlobalConstant:
        {
            auto bcVar = allocate<BCSymbol>(context);

            bcVar->op = inst->op;
            bcVar->typeID = getTypeID(context, inst->getFullType());

            // TODO: actually need to intialize with body instructions

            return bcVar;
        }
        break;

    default:
        // Most instructions don't need a custom representation.
        return BytecodeGenerationPtr<BCSymbol>();
    }
}

BytecodeGenerationPtr<BCModule> generateBytecodeForModule(
    BytecodeGenerationContext*  context,
    IRModule*                   irModule)
{
    if (!irModule)
    {
        // Not IR module? Then return a null pointer.
        return BytecodeGenerationPtr<BCModule>();
    }

    // A module in the bytecode is mostly just a list of the
    // global symbols in the module.
    //
    // TODO: we need to be careful and recognize the distinction
    // between the global symbols in the *AST* module, vs. those
    // symbols which are effectively global in the *IR* module.
    //
    // We probably need to store these distinctly, since we
    // need the AST global symbols for reflection, and then
    // also to reconstruct the AST on load when importing a
    // serialized module. We then need the global IR symbols
    // to use when linking, to quickly resolve things without
    // needing any semantic knowledge of nesting at the AST level.
    //
    auto bcModule = allocate<BCModule>(context);

    // We need to compute how many "registers" to allocate
    // for the module, where the registers represent the
    // values being computed at the global scope.
    UInt symbolCount = 0;
    for(auto ii : irModule->getGlobalInsts())
    {
        auto gv = as<IRGlobalValue>(ii);
        if (!gv)
            continue;

        Int globalID = Int(symbolCount++);

        // Ensure that local code inside functions can see these symbols
        BCConst bcConst;
        bcConst.flavor = kBCConstFlavor_GlobalSymbol;
        bcConst.id = (uint32_t)globalID;
        context->shared->mapValueToGlobal.Add(gv, bcConst);

        // In the global scope, global IDs are also the local IDs
        context->mapInstToLocalID.Add(gv, globalID);
    }

    auto bcSymbols = allocateArray<BCPtr<BCSymbol>>(context, symbolCount);

    bcModule->symbolCount = (uint32_t)symbolCount;
    bcModule->symbols = bcSymbols;

    for(auto ii : irModule->getGlobalInsts())
    {
        auto gv = as<IRGlobalValue>(ii);
        if (!gv)
            continue;

        UInt symbolIndex = *context->mapInstToLocalID.TryGetValue(gv);

        auto bcSymbol = generateBytecodeSymbolForInst(context, gv);
        if (!bcSymbol.getPtr())
            continue;

        auto name = tryGenerateNameForSymbol(context, gv);
        bcSymbol->name = name;

        bcSymbols[symbolIndex] = bcSymbol;
    }

    // At this point we should have identified all the literals we need:
    UInt constantCount = context->shared->constants.Count();
    auto bcConstants = allocateArray<BCConstant>(context, constantCount);
    bcModule->constantCount = (uint32_t)constantCount;
    bcModule->constants = bcConstants;

    for(UInt cc = 0; cc < constantCount; ++cc)
    {
        auto irConstant = (IRConstant*) context->shared->constants[cc];
        bcConstants[cc].op = irConstant->op;
        bcConstants[cc].typeID = getTypeID(context, irConstant->getFullType());

        switch(irConstant->op)
        {
        case kIROp_IntLit:
            {
                auto ptr = allocate<IRIntegerValue>(context);
                *ptr = irConstant->value.intVal;
                bcConstants[cc].ptr = ptr.bitCast<uint8_t>();
            }
            break;

        default:
            break;
        }

    }

    // At this point we should have collected all the types we need:
    UInt typeCount = context->shared->bcTypes.Count();
    auto bcTypes = allocateArray<BCPtr<BCType>>(context, typeCount);
    bcModule->typeCount = (uint32_t)typeCount;
    bcModule->types = bcTypes;

    for(UInt tt = 0; tt < typeCount; ++tt)
    {
        bcTypes[tt] = context->shared->bcTypes[tt];
    }


    return bcModule;
}

void generateBytecodeContainer(
    BytecodeGenerationContext*  context,
    CompileRequest*             compileReq)
{
    // Header must be the very first thing in the bytecode stream
    BytecodeGenerationPtr<BCHeader> header = allocate<BCHeader>(context);

    memcpy(header->magic, "slang\0bc", sizeof(header->magic));
    header->version = 0;

    // TODO: Need to generate BC representation of all the public/exported
    // declrations in the translation units, so that they can be used to
    // resolve depenencies downstream.

    // TODO: Need to dump BC representation of compiled kernel codes
    // for each specified code-generation target.

    List<BytecodeGenerationPtr<BCModule>> bcModulesList;
    for (auto translationUnitReq : compileReq->translationUnits)
    {
        auto bcModule = generateBytecodeForModule(context, translationUnitReq->irModule);
        bcModulesList.Add(bcModule);
    }

    UInt bcModuleCount = bcModulesList.Count();
    header->moduleCount = (uint32_t)bcModuleCount;

    auto bcModules = allocateArray<BCPtr<BCModule>>(context, bcModuleCount);
    header->modules = bcModules;
    for(UInt ii = 0; ii < bcModuleCount; ++ii)
    {
        bcModules[ii] = bcModulesList[ii];
    }
}

void generateBytecodeForCompileRequest(
    CompileRequest* compileReq)
{
    SharedBytecodeGenerationContext sharedContext;

    BytecodeGenerationContext context;
    context.shared = &sharedContext;

    generateBytecodeContainer(&context, compileReq);

    compileReq->generatedBytecode = sharedContext.bytecode;
}

// TODO: Need to support IR emit at the whole-module/compile-request
// level, and not just for individual entry points.
#if 0
List<uint8_t> emitSlangIRForEntryPoint(
    EntryPointRequest*  entryPoint)
{
    auto compileRequest = entryPoint->compileRequest;
    auto irModule = lowerEntryPointToIR(
        entryPoint,
        compileRequest->layout.Ptr(),
        // TODO: we need to pick the target more carefully here
        CodeGenTarget::HLSL);

#if 0
    String irAsm = getSlangIRAssembly(irModule);
    fprintf(stderr, "%s\n", irAsm.Buffer());
#endif

    // Now we need to encode that IR into a binary format
    // for transmission/serialization/etc.

    SharedBytecodeGenerationContext sharedContext;

    BytecodeGenerationContext context;
    context.shared = &sharedContext;

    generateBytecodeStream(&context, irModule);

    return sharedContext.bytecode;
}
#endif

} // namespace Slang
back to top