summaryrefslogtreecommitdiffstats
path: root/source/slang/bytecode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'source/slang/bytecode.cpp')
-rw-r--r--source/slang/bytecode.cpp871
1 files changed, 871 insertions, 0 deletions
diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp
new file mode 100644
index 000000000..52086c76b
--- /dev/null
+++ b/source/slang/bytecode.cpp
@@ -0,0 +1,871 @@
+#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
+{
+ SharedBytecodeGenerationContext* sharedContext;
+ UInt offset;
+
+ 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)
+ {}
+
+ operator BCPtr<T>()
+ {
+ return BCPtr<T>(getPtr());
+ }
+
+ T* operator->()
+ {
+ return getPtr();
+ }
+
+ T& operator*()
+ {
+ return *getPtr()
+ }
+
+ T& operator[](UInt index)
+ {
+ return getPtr()[index];
+ }
+
+ BytecodeGenerationPtr<T> operator+(Int index)
+ {
+ return BytecodeGenerationPtr<T>(
+ sharedContext,
+ offset + index*sizeof(T));
+ }
+
+ 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 a global symbol to its global ID
+ Dictionary<IRInst*, Int> mapGlobalSymbolToGLobalID;
+};
+
+struct BytecodeGenerationContext
+{
+ SharedBytecodeGenerationContext* shared;
+
+ // 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;
+
+ // Map an instruction to the ID for its auxiliary
+ // symbol data
+ Dictionary<IRInst*, UInt> mapInstToNestedID;
+};
+
+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 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->shared->bytecode.Add(value);
+}
+
+void encodeUInt(
+ BytecodeGenerationContext* context,
+ UInt value)
+{
+ if( value < 128 )
+ {
+ encodeUInt8(context, 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);
+}
+
+Int getLocalID(
+ BytecodeGenerationContext* context,
+ IRInst* inst)
+{
+ Int localID = 0;
+ if( context->mapInstToLocalID.TryGetValue(inst, localID) )
+ {
+ return localID;
+ }
+
+ Int globalID = 0;
+ if( context->shared->mapGlobalSymbolToGLobalID.TryGetValue(inst, globalID) )
+ {
+ BCConst bcConst;
+ bcConst.globalID = globalID;
+
+ UInt remappedSymbolIndex = context->remappedGlobalSymbols.Count();
+ context->remappedGlobalSymbols.Add(bcConst);
+
+ localID = ~remappedSymbolIndex;
+ context->mapInstToLocalID.Add(inst, localID);
+ return localID;
+ }
+
+ SLANG_UNEXPECTED("no ID for inst");
+ return -9999;
+}
+
+void encodeOperand(
+ BytecodeGenerationContext* context,
+ IRInst* operand)
+{
+ auto id = getLocalID(context, operand);
+ encodeSInt(context, id);
+}
+
+bool opHasResult(IRInst* inst)
+{
+ auto type = inst->getType();
+ if( !type || type->op != kIROp_VoidType )
+ {
+ return true;
+ }
+ return false;
+}
+
+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 argCount = inst->getArgCount();
+ encodeUInt(context, inst->op);
+ encodeUInt(context, argCount);
+ for( UInt aa = 0; aa < argCount; ++aa )
+ {
+ encodeOperand(context, inst->getArg(aa));
+ }
+
+ auto type = inst->getType();
+ if( type && type->op == kIROp_VoidType )
+ {
+ // 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->getType());
+
+ // TODO: probably want distinct encodings
+ // for signed vs. unsigned here.
+ encodeUInt(context, UInt(ii->u.intVal));
+
+ // destination:
+ encodeOperand(context, inst);
+ }
+ break;
+
+ case kIROp_FloatLit:
+ {
+ auto ii = (IRConstant*) inst;
+ encodeUInt(context, ii->op);
+ encodeOperand(context, ii->getType());
+
+ static const UInt size = sizeof(IRFloatingPointValue);
+ unsigned char buffer[size];
+ memcpy(buffer, &ii->u.floatVal, sizeof(buffer));
+
+ for(UInt ii = 0; ii < size; ++ii)
+ {
+ encodeUInt8(context, buffer[ii]);
+ }
+
+ // destination:
+ encodeOperand(context, inst);
+ }
+ break;
+
+ case kIROp_boolConst:
+ {
+ auto ii = (IRConstant*) inst;
+ encodeUInt(context, ii->op);
+ encodeUInt(context, ii->u.intVal ? 1 : 0);
+
+ // destination:
+ encodeOperand(context, inst);
+ }
+ break;
+
+ 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;
+
+ case kIROp_Store:
+ {
+ encodeUInt(context, inst->op);
+
+ // We need to encode the type being stored, to make
+ // our lives easier.
+ encodeOperand(context, inst->getArg(2)->getType());
+ encodeOperand(context, inst->getArg(1));
+ encodeOperand(context, inst->getArg(2));
+ }
+ break;
+
+ case kIROp_Load:
+ {
+ encodeUInt(context, inst->op);
+ encodeOperand(context, inst->getType());
+ encodeOperand(context, inst->getArg(1));
+ encodeOperand(context, inst);
+ }
+ break;
+ }
+}
+
+Int getIDForGlobalSymbol(
+ BytecodeGenerationContext* context,
+ IRInst* inst)
+{
+ Int globalID;
+ if(context->shared->mapGlobalSymbolToGLobalID.TryGetValue(inst, globalID))
+ return globalID;
+
+ SLANG_UNEXPECTED("no such ID");
+}
+
+uint32_t getTypeForGlobalSymbol(
+ BytecodeGenerationContext* context,
+ IRInst* inst)
+{
+ auto type = inst->getType();
+ if(!type)
+ return 0;
+
+ return getIDForGlobalSymbol(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,
+ IRInst* 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<ParameterBlockReflectionName>())
+ {
+ return allocateString(context, reflectionNameMod->name);
+ }
+ else if(auto name = decl->nameAndLoc.name)
+ {
+ return allocateString(context, name);
+ }
+ }
+
+ return BytecodeGenerationPtr<char>();
+}
+
+BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst(
+ BytecodeGenerationContext* context,
+ IRInst* inst)
+{
+ switch( inst->op )
+ {
+ case kIROp_Func:
+ {
+ auto irFunc = (IRFunc*) inst;
+ BytecodeGenerationPtr<BCFunc> bcFunc = allocate<BCFunc>(context);
+
+ bcFunc->op = inst->op;
+ bcFunc->typeGlobalID = getTypeForGlobalSymbol(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 = 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->firstChild; ii; ii = ii->nextInst )
+ {
+ 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 = 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 = 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 just the parameters first, to ensure they
+ // are always the first N registers of a block.
+ //
+ // This means the parameters of the function itself
+ // are always the first N registers in the overall list.
+ //
+ bcBlocks[blockID].params = bcRegs + regCounter;
+ for( auto ii = bb->firstChild; ii; ii = ii->nextInst )
+ {
+ if(ii->op != kIROp_Param)
+ continue;
+
+ Int localID = regCounter++;
+ subContext->mapInstToLocalID.Add(ii, localID);
+
+ bcRegs[localID].op = ii->op;
+ bcRegs[localID].name = tryGenerateNameForSymbol(context, ii);
+ bcRegs[localID].previousVarIndexPlusOne = localID;
+ bcRegs[localID].typeGlobalID = getTypeForGlobalSymbol(context, ii);
+ }
+
+ // Now loop over the non-parameter instructions and
+ // allocate actual register locations to them.
+ for( auto ii = bb->firstChild; ii; ii = ii->nextInst )
+ {
+ switch(ii->op)
+ {
+ case kIROp_Param:
+ // Already handled.
+ break;
+
+ default:
+ // For 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;
+ bcRegs[localID].name = tryGenerateNameForSymbol(context, ii);
+ bcRegs[localID].previousVarIndexPlusOne = localID;
+ bcRegs[localID].typeGlobalID = getTypeForGlobalSymbol(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;
+ bcRegs[localID].name = tryGenerateNameForSymbol(context, ii);
+ bcRegs[localID].previousVarIndexPlusOne = localID;
+ bcRegs[localID].typeGlobalID = getTypeForGlobalSymbol(context, ii);
+
+ bcRegs[localID+1].op = ii->op;
+ bcRegs[localID+1].previousVarIndexPlusOne = localID+1;
+ bcRegs[localID+1].typeGlobalID = getIDForGlobalSymbol(context,
+ ((IRPtrType*) ii->getType())->getValueType());
+ }
+ break;
+ }
+ }
+ }
+
+ // Now that we've allocated our blocks and our registers
+ // we can go through the actual process of emitting instructions. Hooray!
+ blockCounter = 0;
+ for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() )
+ {
+ UInt blockID = blockCounter++;
+ bcBlocks[blockID].code = getPtr<BCOp>(context);
+
+
+ for( auto ii = bb->firstChild; ii; ii = ii->nextInst )
+ {
+ // 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;
+ }
+ }
+ }
+
+ // 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 = constCount;
+ bcFunc->consts = bcConsts;
+
+ for( UInt cc = 0; cc < constCount; ++cc )
+ {
+ bcConsts[cc] = subContext->remappedGlobalSymbols[cc];
+ }
+
+ return bcFunc;
+ }
+ break;
+
+ default:
+ // Most instructions don't need a custom representation.
+ return BytecodeGenerationPtr<BCSymbol>();
+ }
+}
+
+BytecodeGenerationPtr<BCModule> generateBytecodeForModule(
+ BytecodeGenerationContext* context,
+ IRModule* irModule)
+{
+ // The module will get encoded much like a function,
+ // and then that function will be "invoked" to load
+ // the module.
+ //
+ auto bcModule = allocate<BCModule>(context);
+ bcModule->op = irModule->op;
+ bcModule->typeGlobalID = 0;
+
+ // The logical function that the module representats
+ // will only have a single block, so we can allocate it now.
+ //
+ auto bcBlock = allocate<BCBlock>(context);
+ bcBlock->paramCount = 0;
+ bcBlock->params = BytecodeGenerationPtr<BCReg>();
+
+ bcModule->blockCount = 1;
+ bcModule->blocks = bcBlock;
+
+ // Because the module is the top-most level, there is
+ // no need for it to have "constants" that represent
+ // values imported from the next outer scope.
+ //
+ bcModule->constCount = 0;
+ bcModule->consts = BytecodeGenerationPtr<BCConst>();
+
+ // 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 regCount = 0;
+ for( auto inst = irModule->firstChild; inst; inst = inst->nextInst )
+ {
+ if(!opHasResult(inst))
+ continue;
+
+ Int globalID = Int(regCount++);
+
+ context->shared->mapGlobalSymbolToGLobalID.Add(inst, globalID);
+
+ // In the global scope, global IDs are also the local IDs
+ context->mapInstToLocalID.Add(inst, globalID);
+ }
+
+ auto bcRegs = allocateArray<BCReg>(context, regCount);
+
+ bcModule->regCount = regCount;
+ bcModule->regs = bcRegs;
+
+ // Now lets walk through and initialize all those bytecode
+ // register representations so that we can use them.
+ UInt regCounter= 0;
+ for( auto inst = irModule->firstChild; inst; inst = inst->nextInst )
+ {
+ if(!opHasResult(inst))
+ continue;
+
+ UInt regIndex = *context->mapInstToLocalID.TryGetValue(inst);
+
+ BytecodeGenerationPtr<char> name = tryGenerateNameForSymbol(context, inst);
+
+ bcRegs[regIndex].op = inst->op;
+ bcRegs[regIndex].name = name;
+ bcRegs[regIndex].typeGlobalID = getTypeForGlobalSymbol(context, inst);
+ bcRegs[regIndex].previousVarIndexPlusOne = regIndex;
+ }
+
+ // Some instructions represent "nested" symbols that will need
+ // custom handling, and we will represent those here.
+ List<BytecodeGenerationPtr<BCSymbol>> nestedSymbols;
+ for( auto inst = irModule->firstChild; inst; inst = inst->nextInst )
+ {
+ UInt regIndex = *context->mapInstToLocalID.TryGetValue(inst);
+
+ auto bcSymbol = generateBytecodeSymbolForInst(context, inst);
+ if (!bcSymbol.getPtr())
+ continue;
+
+ UInt nestedSymbolID = nestedSymbols.Count();
+ nestedSymbols.Add(bcSymbol);
+
+ context->mapInstToNestedID.Add(inst, nestedSymbolID);
+
+ bcSymbol->name = bcRegs[regIndex].name;
+ }
+
+ auto nestedSymbolCount = nestedSymbols.Count();
+ auto bcNestedSymbols = allocateArray<BCPtr<BCSymbol>>(context, nestedSymbolCount);
+
+ bcModule->nestedSymbolCount = nestedSymbolCount;
+ bcModule->nestedSymbols = bcNestedSymbols;
+ for (UInt ii = 0; ii < nestedSymbolCount; ++ii)
+ {
+ bcNestedSymbols[ii] = nestedSymbols[ii];
+ }
+
+
+ // Finally, we can go through and emit the actual code for
+ // the initialization step.
+ bcBlock->code = getPtr<BCOp>(context);
+ for( auto inst = irModule->firstChild; inst; inst = inst->nextInst )
+ {
+ // Generate bytecode for global-scope inst
+ generateBytecodeForInst(context, inst);
+ }
+ // Need to encode a terminator here, just to keep the encoding valid
+ encodeUInt(context, kIROp_ReturnVoid);
+
+#if 0
+
+ // Now we can go through and generate the bytecode object
+ // that will represent each of these global symbols
+
+ List<BytecodeGenerationPtr<BCSymbol>> globalSymbols;
+
+ for( auto inst = irModule->firstChild; inst; inst = inst->nextInst )
+ {
+ // Generate bytecode for global-scope inst
+ auto globalSymbol = generateBytecodeForGlobalSymbol(context, inst);
+ globalSymbols.Add(globalSymbol);
+ }
+#endif
+
+ return bcModule;
+}
+
+void generateBytecodeStream(
+ BytecodeGenerationContext* context,
+ IRModule* irModule)
+{
+ // 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;
+
+ // HACK: ensure that a NULL pointer in an operand field can
+ // be encoded.
+ context->shared->mapGlobalSymbolToGLobalID.Add(nullptr, -1);
+
+ header->module = generateBytecodeForModule(context, irModule);
+}
+
+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;
+}
+
+} // namespace Slang