summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/bytecode.cpp871
-rw-r--r--source/slang/bytecode.h187
-rw-r--r--source/slang/compiler.cpp5
-rw-r--r--source/slang/emit.cpp1
-rw-r--r--source/slang/ir.cpp50
-rw-r--r--source/slang/ir.h5
-rw-r--r--source/slang/lower-to-ir.cpp199
-rw-r--r--source/slang/slang.natvis17
-rw-r--r--source/slang/slang.vcxproj4
-rw-r--r--source/slang/slang.vcxproj.filters4
-rw-r--r--source/slang/vm.cpp1080
-rw-r--r--source/slang/vm.h19
12 files changed, 2417 insertions, 25 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
diff --git a/source/slang/bytecode.h b/source/slang/bytecode.h
new file mode 100644
index 000000000..e073763cf
--- /dev/null
+++ b/source/slang/bytecode.h
@@ -0,0 +1,187 @@
+// bytecode.h
+#ifndef SLANG_BYTECODE_H_INCLUDED
+#define SLANG_BYTECODE_H_INCLUDED
+
+// This file defines a "bytecode" format for storing shader code
+// that has been generated via the Slang IR. The bytecode has
+// two main goals, that can end up in a bit of conflict:
+//
+// 1) It is the official serialized form of the Slang IR, and
+// so it is of some importance that constructs in the IR be
+// able to round-trip through the bytecode.
+//
+// 2) It should support being directly executed/interpreted,
+// so that Slang code can be executed on CPUs when
+// performance isn't critical (or when a JIT just isn't
+// an option).
+//
+
+#include "../core/basic.h"
+
+namespace Slang
+{
+template<typename T>
+struct BytecodeGenerationPtr;
+
+// A "pointer" stored in a serialized bytecode file, which
+// is represented as a byte offset relative to itself.
+//
+template<typename T>
+struct BCPtr
+{
+ typedef int32_t RawVal;
+
+ RawVal rawVal;
+
+ BCPtr() : rawVal(0) {}
+
+ BCPtr(T* ptr)
+ : rawVal(0)
+ {
+ *this = ptr;
+ }
+
+ BCPtr(BCPtr<T> const& ptr)
+ : rawVal(0)
+ {
+ *this = ptr.getPtr();
+ }
+
+ void operator=(BCPtr<T> const& ptr)
+ {
+ *this = ptr.getPtr();
+ }
+
+ void operator=(T* ptr)
+ {
+ if (ptr)
+ {
+ rawVal = (char*)ptr - (char*)this;
+ }
+ else
+ {
+ rawVal = 0;
+ }
+ }
+
+ operator T*() const { return getPtr(); }
+
+ T* getPtr() const
+ {
+ if(!rawVal) return nullptr;
+ return (T*)((char const*)this + rawVal);
+ }
+};
+
+struct BCType
+{
+ uint32_t op;
+};
+
+struct BCSymbol
+{
+ // The opcode that was used to define
+ // this symbol; used to categorize things
+ uint32_t op;
+
+ // The type of the symbol is represent
+ // as an index into the global-scope symbol
+ // list of the module.
+ //
+ // Note: This currently precludes having
+ // a register with a type that is not
+ // statically determined, but that is
+ // probably okay.
+ uint32_t typeGlobalID;
+
+ // The name of this symbol (which might
+ // be a mangled name at some point,
+ // so it is really only meant to be
+ // used for linkage...)
+ BCPtr<char> name;
+};
+
+typedef uint8_t BCOp;
+
+struct BCReg : BCSymbol
+{
+ // The index of the variable/register
+ // that should be stored immediately
+ // preceding this one.
+ uint32_t previousVarIndexPlusOne;
+};
+
+struct BCConst
+{
+ // The ID of the symbol in the global
+ // scope that we are trying to refer
+ // to.
+ //
+ // TODO: eventually, if we have general
+ // nesting, then this might be the
+ // entry in the outer scope that
+ // is being referenced.
+ uint32_t globalID;
+};
+
+struct BCBlock
+{
+ // The start of the bytecode for this block
+ BCPtr<BCOp> code;
+
+ // The list of parameters of the block
+ uint32_t paramCount;
+ BCPtr<BCReg> params;
+};
+
+struct BCFunc : BCSymbol
+{
+ // A list of "registers" used to hold
+ // intermediate values during execution
+ // of this function.
+ uint32_t regCount;
+ BCPtr<BCReg> regs;
+
+ // The basic blocks of the function
+ uint32_t blockCount;
+ BCPtr<BCBlock> blocks;
+
+ // A list of "constants" which are values
+ // from the global scope that this function
+ // wants to be able to refer to. We could
+ // just encode global values directly,
+ // but this would make the encoding less dense.
+ uint32_t constCount;
+ BCPtr<BCConst> consts;
+
+ // Data for "nested" symbols (e.g., a function
+ // nested inside this function).
+ uint32_t nestedSymbolCount;
+ BCPtr<BCPtr<BCSymbol>> nestedSymbols;
+};
+
+// A module is encoded more or less like a function.
+struct BCModule : BCFunc
+{
+};
+
+struct BCHeader
+{
+ char magic[8];
+ uint32_t version;
+
+ // TODO: probably want a section-based file
+ // format so that we can add/remove different
+ // kinds of data without having to revise
+ // the schema here.
+
+ // The bytecode representation of the module
+ BCPtr<BCModule> module;
+};
+
+
+
+}
+
+
+#endif
diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp
index 25ae460c7..b8830c0b4 100644
--- a/source/slang/compiler.cpp
+++ b/source/slang/compiler.cpp
@@ -500,10 +500,7 @@ namespace Slang
#endif
List<uint8_t> emitSlangIRForEntryPoint(
- EntryPointRequest* entryPoint)
- {
- SLANG_UNIMPLEMENTED_X("Slang IR Binary Generation");
- }
+ EntryPointRequest* entryPoint);
String emitSlangIRAssemblyForEntryPoint(
EntryPointRequest* entryPoint);
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index 25a0b5323..6231b4ca1 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -5287,6 +5287,7 @@ String emitEntryPoint(
auto lowered = lowerEntryPointToIR(entryPoint, programLayout, target);
+ // debugging:
// dumpIR(lowered);
// TODO: do we want to emit directly from IR, or translate the
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index c48880cab..2c6395903 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -33,6 +33,11 @@ namespace Slang
return IROp(kIROp_Invalid);
}
+ IROpInfo getIROpInfo(IROp op)
+ {
+ return kIROpInfos[op];
+ }
+
//
void IRUse::init(IRValue* u, IRValue* v)
@@ -91,13 +96,23 @@ namespace Slang
IRParam* IRParam::getNextParam()
{
+ // TODO: this is written as a search because we don't
+ // currently do the careful thing and emit parameters
+ // before any other members of a block.
+ //
+ // This should change on the emit side, instead.
+
auto next = nextInst;
- if(!next) return nullptr;
- if(next->op != kIROp_Param)
- return nullptr;
+ for (;;)
+ {
+ if (!next) return nullptr;
- return (IRParam*) next;
+ if(next->op == kIROp_Param)
+ return (IRParam*) next;
+
+ next = next->nextInst;
+ }
}
//
@@ -1392,6 +1407,24 @@ namespace Slang
// fprintf(context->file, "%llu", (unsigned long long)val);
}
+ static void dump(
+ IRDumpContext* context,
+ IntegerLiteralValue val)
+ {
+ context->builder->append(val);
+
+// fprintf(context->file, "%llu", (unsigned long long)val);
+ }
+
+ static void dump(
+ IRDumpContext* context,
+ FloatingPointLiteralValue val)
+ {
+ context->builder->append(val);
+
+// fprintf(context->file, "%llu", (unsigned long long)val);
+ }
+
static void dumpIndent(
IRDumpContext* context)
{
@@ -1412,7 +1445,7 @@ namespace Slang
else if(inst->id)
{
dump(context, "%");
- dump(context, inst->id);
+ dump(context, (UInt) inst->id);
}
else
{
@@ -1722,5 +1755,12 @@ namespace Slang
return sb;
}
+ void dumpIR(IRModule* module)
+ {
+ String ir = getSlangIRAssembly(module);
+ fprintf(stderr, "%s\n", ir.Buffer());
+ fflush(stderr);
+ }
+
}
diff --git a/source/slang/ir.h b/source/slang/ir.h
index 9b271b09b..bc73ffb66 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -119,6 +119,9 @@ struct IROpInfo
IROpFlags flags;
};
+// Look up the info for an op
+IROpInfo getIROpInfo(IROp op);
+
// A use of another value/inst within an IR operation
struct IRUse
{
@@ -323,6 +326,8 @@ struct IRModule : IRParentInst
void printSlangIRAssembly(StringBuilder& builder, IRModule* module);
String getSlangIRAssembly(IRModule* module);
+void dumpIR(IRModule* module);
+
}
diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp
index 5ee6d5460..6f64a2215 100644
--- a/source/slang/lower-to-ir.cpp
+++ b/source/slang/lower-to-ir.cpp
@@ -1,6 +1,8 @@
// lower.cpp
#include "lower-to-ir.h"
+#include "../../slang.h"
+
#include "ir.h"
#include "ir-insts.h"
#include "type-layout.h"
@@ -354,6 +356,73 @@ LoweredValInfo emitCompoundAssignOp(
return LoweredValInfo::ptr(leftPtr);
}
+IRInst* getOneValOfType(
+ IRGenContext* context,
+ IRType* type)
+{
+ switch(type->op)
+ {
+ case kIROp_Int32Type:
+ case kIROp_UInt32Type:
+ return context->irBuilder->getIntValue(type, 1);
+
+ case kIROp_Float32Type:
+ return context->irBuilder->getFloatValue(type, 1.0);
+
+ default:
+ SLANG_UNEXPECTED("inc/dec type");
+ return nullptr;
+ }
+}
+
+LoweredValInfo emitPreOp(
+ IRGenContext* context,
+ IRType* type,
+ IROp op,
+ UInt argCount,
+ IRValue* const* args)
+{
+ auto builder = context->irBuilder;
+
+ assert(argCount == 1);
+ auto argPtr = args[0];
+
+ auto preVal = builder->emitLoad(argPtr);
+
+ IRInst* oneVal = getOneValOfType(context, type);
+
+ IRInst* innerArgs[] = { preVal, oneVal };
+ auto innerOp = builder->emitIntrinsicInst(type, op, 2, innerArgs);
+
+ builder->emitStore(argPtr, innerOp);
+
+ return LoweredValInfo::simple(preVal);
+}
+
+LoweredValInfo emitPostOp(
+ IRGenContext* context,
+ IRType* type,
+ IROp op,
+ UInt argCount,
+ IRValue* const* args)
+{
+ auto builder = context->irBuilder;
+
+ assert(argCount == 1);
+ auto argPtr = args[0];
+
+ auto preVal = builder->emitLoad(argPtr);
+
+ IRInst* oneVal = getOneValOfType(context, type);
+
+ IRInst* innerArgs[] = { preVal, oneVal };
+ auto innerOp = builder->emitIntrinsicInst(type, op, 2, innerArgs);
+
+ builder->emitStore(argPtr, innerOp);
+
+ return LoweredValInfo::ptr(argPtr);
+}
+
// Given a `DeclRef` for something callable, along with a bunch of
// arguments, emit an appropriate call to it.
LoweredValInfo emitCallToDeclRef(
@@ -444,6 +513,18 @@ LoweredValInfo emitCallToDeclRef(
#undef CASE
+#define CASE(COMPOUND, OP) \
+ case COMPOUND: return emitPreOp(context, type, OP, argCount, args)
+ CASE(kIRPseudoOp_PreInc, kIROp_Add);
+ CASE(kIRPseudoOp_PreDec, kIROp_Sub);
+#undef CASE
+
+#define CASE(COMPOUND, OP) \
+ case COMPOUND: return emitPostOp(context, type, OP, argCount, args)
+ CASE(kIRPseudoOp_PostInc, kIROp_Add);
+ CASE(kIRPseudoOp_PostDec, kIROp_Sub);
+#undef CASE
+
default:
SLANG_UNIMPLEMENTED_X("IR pseudo-op");
break;
@@ -1603,6 +1684,65 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor>
insertBlock(breakLabel);
}
+ void visitWhileStmt(WhileStmt* stmt)
+ {
+ // Generating IR for `while` statement is similar to a
+ // `for` statement, but without a lot of the complications.
+
+ auto builder = getBuilder();
+
+ // We will create blocks for the various places
+ // we need to jump to inside the control flow,
+ // including the blocks that will be referenced
+ // by `continue` or `break` statements.
+ auto loopHead = createBlock();
+ auto bodyLabel = createBlock();
+ auto breakLabel = createBlock();
+
+ // A `continue` inside a `while` loop always
+ // jumps to the head of hte loop.
+ auto continueLabel = loopHead;
+
+ // TODO: register appropriate targets for
+ // break/continue statements.
+
+ // Emit the branch that will start out loop,
+ // and then insert the block for the head.
+
+ auto loopInst = builder->emitLoop(
+ loopHead,
+ breakLabel,
+ continueLabel);
+
+ addLoopDecorations(loopInst, stmt);
+
+ insertBlock(loopHead);
+
+ // Now that we are within the header block, we
+ // want to emit the expression for the loop condition:
+ if (auto condExpr = stmt->Predicate)
+ {
+ auto irCondition = getSimpleVal(context,
+ lowerRValueExpr(context, condExpr));
+
+ // Now we want to `break` if the loop condition is false.
+ builder->emitLoopTest(
+ irCondition,
+ bodyLabel,
+ breakLabel);
+ }
+
+ // Emit the body of the loop
+ insertBlock(bodyLabel);
+ lowerStmt(context, stmt->Statement);
+
+ // At the end of the body we need to jump back to the top.
+ builder->emitBranch(loopHead);
+
+ // Finally we insert the label that a `break` will jump to
+ insertBlock(breakLabel);
+ }
+
void visitExpressionStmt(ExpressionStmt* stmt)
{
// The statement evaluates an expression
@@ -1993,17 +2133,61 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo>
for( auto paramDecl : declForParameters->GetParameters() )
{
IRType* irParamType = lowerSimpleType(context, paramDecl->getType());
- paramTypes.Add(irParamType);
- IRParam* irParam = subBuilder->emitParam(irParamType);
+ LoweredValInfo paramVal;
- subBuilder->addHighLevelDeclDecoration(irParam, paramDecl);
+ if (paramDecl->HasModifier<OutModifier>()
+ || paramDecl->HasModifier<InOutModifier>())
+ {
+ // The parameter is being used for input/output purposes,
+ // so it will lower to an actual parameter with a pointer type.
+ //
+ // TODO: Is this the best representation we can use?
- DeclRef<ParamDecl> paramDeclRef = makeDeclRef(paramDecl.Ptr());
+ auto irPtrType = subBuilder->getPtrType(irParamType);
+ paramTypes.Add(irPtrType);
+
+ IRParam* irParamPtr = subBuilder->emitParam(irPtrType);
+ subBuilder->addHighLevelDeclDecoration(irParamPtr, paramDecl);
- LoweredValInfo irParamVal = LoweredValInfo::simple(irParam);
+ paramVal = LoweredValInfo::ptr(irParamPtr);
- subContext->shared->declValues.Add(paramDeclRef, irParamVal);
+ // TODO: We might want to copy the pointed-to value into
+ // a temporary at the start of the function, and then copy
+ // back out at the end, so that we don't have to worry
+ // about things like aliasing in the function body.
+ //
+ // For now we will just use the storage that was passed
+ // in by the caller, knowing that our current lowering
+ // at call sites will guarantee a fresh/unique location.
+ }
+ else
+ {
+ // Simple case of a by-value input parameter.
+ // But note that HLSL allows an input parameter
+ // to be used as a local variable inside of a
+ // function body, so we need to introduce a temporary
+ // and then copy over to it...
+ //
+ // TODO: we could skip this step if we knew
+ // the parameter was marked `const` or similar.
+
+ paramTypes.Add(irParamType);
+
+ IRParam* irParam = subBuilder->emitParam(irParamType);
+ subBuilder->addHighLevelDeclDecoration(irParam, paramDecl);
+ paramVal = LoweredValInfo::simple(irParam);
+
+ auto irLocal = subBuilder->emitVar(irParamType);
+ auto localVal = LoweredValInfo::ptr(irLocal);
+
+ assign(subContext, localVal, paramVal);
+
+ paramVal = localVal;
+ }
+
+ DeclRef<ParamDecl> paramDeclRef = makeDeclRef(paramDecl.Ptr());
+ subContext->shared->declValues.Add(paramDeclRef, paramVal);
}
auto irResultType = lowerSimpleType(context, declForReturnType->ReturnType);
@@ -2235,4 +2419,5 @@ String emitSlangIRAssemblyForEntryPoint(
return getSlangIRAssembly(irModule);
}
-}
+
+} // namespace Slang
diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis
index 6a9f0cbbc..7a7f7fe0e 100644
--- a/source/slang/slang.natvis
+++ b/source/slang/slang.natvis
@@ -1,14 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010">
- <type Name="Slang::CFGNode">
- <DisplayString>{{CFG Basic Block}}</DisplayString>
+
+ <Type Name="Slang::BCPtr&lt;*&gt;">
+ <SmartPointer Usage="Minimal">rawVal ? ($T1*)((char*)this + rawVal) : ($T1*)0</SmartPointer>
+ <DisplayString Condition="rawVal == 0">BCPtr nullptr</DisplayString>
+ <DisplayString Condition="rawVal != 0">BCPtr {*($T1*)((char*)this + rawVal)}</DisplayString>
<Expand>
- <LinkedListItems>
- <Size>kvPairs.FCount</Size>
- <HeadPointer>kvPairs.FHead</HeadPointer>
- <NextPointer>pNext</NextPointer>
- <ValueNode>Value</ValueNode>
- </LinkedListItems>
+ <ExpandedItem>rawVal ? ($T1*)((char*)this + rawVal) : ($T1*)0</ExpandedItem>
</Expand>
- </type>
+ </Type>
+
</AutoVisualizer> \ No newline at end of file
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index 08a448a6d..c83561b65 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -165,6 +165,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\slang.h" />
+ <ClInclude Include="bytecode.h" />
<ClInclude Include="compiler.h" />
<ClInclude Include="decl-defs.h" />
<ClInclude Include="diagnostic-defs.h" />
@@ -200,8 +201,10 @@
<ClInclude Include="type-layout.h" />
<ClInclude Include="val-defs.h" />
<ClInclude Include="visitor.h" />
+ <ClInclude Include="vm.h" />
</ItemGroup>
<ItemGroup>
+ <ClCompile Include="bytecode.cpp" />
<ClCompile Include="check.cpp" />
<ClCompile Include="compiler.cpp" />
<ClCompile Include="diagnostics.cpp" />
@@ -224,6 +227,7 @@
<ClCompile Include="syntax.cpp" />
<ClCompile Include="token.cpp" />
<ClCompile Include="type-layout.cpp" />
+ <ClCompile Include="vm.cpp" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\core\core.vcxproj">
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index 111b23d36..ac4585082 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -40,6 +40,8 @@
<ClInclude Include="lower-to-ir.h" />
<ClInclude Include="ir-inst-defs.h" />
<ClInclude Include="ir-insts.h" />
+ <ClInclude Include="bytecode.h" />
+ <ClInclude Include="vm.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="check.cpp" />
@@ -64,6 +66,8 @@
<ClCompile Include="name.cpp" />
<ClCompile Include="ir.cpp" />
<ClCompile Include="lower-to-ir.cpp" />
+ <ClCompile Include="bytecode.cpp" />
+ <ClCompile Include="vm.cpp" />
</ItemGroup>
<ItemGroup>
<CustomBuild Include="core.meta.slang" />
diff --git a/source/slang/vm.cpp b/source/slang/vm.cpp
new file mode 100644
index 000000000..70315e701
--- /dev/null
+++ b/source/slang/vm.cpp
@@ -0,0 +1,1080 @@
+#include "vm.h"
+
+// Implementation of the Slang bytecode VM
+//
+
+#include "bytecode.h"
+#include "ir.h"
+
+#include "../../slang.h"
+
+namespace Slang
+{
+
+// Representation of a type during VM execution
+struct VMTypeImpl
+{
+ uint32_t op;
+
+ // Size and alignment of instances of this
+ // type.
+ UInt size;
+ UInt alignment;
+};
+struct VMType
+{
+ VMTypeImpl* impl;
+
+ UInt getSize() { return impl->size; }
+ UInt getAlignment() { return impl->alignment; }
+};
+
+struct VMPtrTypeImpl : VMTypeImpl
+{
+ VMType base;
+ uint32_t addressSpace;
+};
+
+struct VMReg
+{
+ // Type that the register is meant to hold
+ VMType type;
+
+ // offset of the variable inside the frame
+ size_t offset;
+};
+
+struct VMConst
+{
+ // Type of the constant
+ VMType type;
+
+ // Operand address to use
+ void* ptr;
+};
+
+struct VMFrame;
+
+// Information about a function after it has been
+// loaded into the VM.
+struct VMFunc
+{
+ // The parent module that this function belongs to
+ VMFrame* module;
+ BCFunc* bcFunc;
+
+ VMReg* regs;
+ VMConst* consts;
+
+ size_t frameSize;
+};
+
+struct VMFrame
+{
+ // The function from which this frame was spawned
+ VMFunc* func;
+
+ // The parent frame on the call stack of the
+ // current thread.
+ VMFrame* parent;
+
+ // The instruction pointer within this frame
+ BCOp* ip;
+
+ // Registers are stored after this point.
+};
+
+struct VMModule : VMFunc
+{
+};
+
+UInt decodeUInt(BCOp** ioPtr)
+{
+ BCOp* ptr = *ioPtr;
+
+ UInt value = *ptr++;
+ if( value >= 128 )
+ {
+ SLANG_UNEXPECTED("deal with this later");
+ }
+
+ *ioPtr = ptr;
+ return value;
+}
+
+Int decodeSInt(BCOp** ioPtr)
+{
+ UInt uVal = decodeUInt(ioPtr);
+ if( uVal & 1 )
+ {
+ return Int(~(uVal >> 1));
+ }
+ else
+ {
+ return Int(uVal >> 1);
+ }
+}
+
+void* getRegPtrImpl(VMFrame* frame, UInt id)
+{
+ VMFunc* vmFunc = frame->func;
+ VMReg* vmReg = &vmFunc->regs[id];
+ size_t offset = vmReg->offset;
+
+ return (void*)((char*)frame + offset);
+}
+
+void* getOperandPtrImpl(VMFrame* frame, Int id)
+{
+ if( id >= 0 )
+ {
+ // This ID represents a local variable/register
+ // of the current call frame, and should
+ // be used to index into a table of such values.
+
+ return getRegPtrImpl(frame, id);
+ }
+ else
+ {
+ // This ID represents a global value that has
+ // been imported into the current func, and
+ // should be looked up via indirection into
+ // the current module.
+
+ VMFunc* vmFunc = frame->func;
+ VMConst* vmConst = &vmFunc->consts[~id];
+ return vmConst->ptr;
+ }
+}
+
+VMType getOperandTypeImpl(VMFrame* frame, Int id)
+{
+ if( id >= 0 )
+ {
+ return frame->func->regs[id].type;
+ }
+ else
+ {
+ return frame->func->consts[~id].type;
+ }
+}
+struct VMPtrAndType
+{
+ void* ptr;
+ VMType type;
+};
+
+VMPtrAndType decodeOperandPtrAndType(VMFrame* frame, BCOp** ioIP)
+{
+ Int id = decodeSInt(ioIP);
+
+ VMPtrAndType ptrAndType;
+ ptrAndType.ptr = getOperandPtrImpl(frame, id);
+ ptrAndType.type = getOperandTypeImpl(frame, id);
+ return ptrAndType;
+}
+
+void* decodeOperandPtrImpl(VMFrame* frame, BCOp** ioIP)
+{
+ Int id = decodeSInt(ioIP);
+ return getOperandPtrImpl(frame, id);
+}
+
+
+template<typename T>
+T* decodeOperandPtr(VMFrame* frame, BCOp** ioIP)
+{
+ return (T*)decodeOperandPtrImpl(frame, ioIP);
+}
+
+
+template<typename T>
+T& decodeOperand(VMFrame* frame, BCOp** ioIP)
+{
+ return *decodeOperandPtr<T>(frame, ioIP);
+}
+
+VMFunc* loadVMFunc(
+ BCFunc* bcFunc,
+ VMFrame* vmModuleInstance);
+
+struct VMSizeAlign
+{
+ UInt size;
+ UInt align;
+};
+
+VMSizeAlign getVMSymbolSize(BCSymbol* symbol)
+{
+ VMSizeAlign result;
+ result.size = sizeof(void*);
+ result.align = sizeof(void*);
+ switch( symbol->op )
+ {
+ default:
+ SLANG_UNEXPECTED("op");
+ break;
+
+ case kIROp_TypeType:
+ break;
+
+ case kIROp_Func:
+ {
+ BCFunc* func = (BCFunc*) symbol;
+ result.size = sizeof(VMFunc)
+ + func->regCount * sizeof(VMReg)
+ + func->constCount * sizeof(VMConst);
+ }
+ break;
+ }
+
+ return result;
+}
+
+VMFunc* loadVMFunc(
+ BCFunc* bcFunc,
+ VMFrame* vmModuleInstance)
+{
+ UInt regCount = bcFunc->regCount;
+ UInt constCount = bcFunc->constCount;
+ UInt vmFuncSize = sizeof(VMFunc)
+ + regCount * sizeof(VMReg)
+ + constCount * sizeof(VMConst);
+
+ VMFunc* vmFunc = (VMFunc*) malloc(vmFuncSize);
+ VMReg* vmRegs = (VMReg*) (vmFunc + 1);
+ VMConst* vmConsts = (VMConst*) (vmRegs + regCount);
+
+ vmFunc->module = vmModuleInstance;
+ vmFunc->bcFunc = bcFunc;
+ vmFunc->regs = vmRegs;
+ vmFunc->consts = vmConsts;
+
+ UInt offset = sizeof(VMFrame);
+ for( UInt rr = 0; rr < regCount; ++rr )
+ {
+ BCReg* bcReg = &bcFunc->regs[rr];
+ auto typeGlobalID = bcReg->typeGlobalID;
+
+ // HACK: when we are loading a module itself, we might
+ // not yet know the size for the things it defines
+ // (since the module itself might define the type of
+ // one of its symbols), so for now we hack it and
+ // assume everything at module level is 16 bytes or less.
+ //
+ // TODO: this also seems like it will cause problems
+ // in other contexts (any time the type of a register
+ // would depend on an earlier instruction in the same
+ // scope) so this needs careful thought.
+ VMType vmType = { nullptr };
+ UInt regSize = 16;
+ UInt regAlign = 8;
+
+ if (vmModuleInstance)
+ {
+ // We expect the type to come from the outer module, so
+ // that we can allocate space for it as we go.
+ vmType = *(VMType*)getRegPtrImpl(vmModuleInstance, typeGlobalID);
+
+ regSize = vmType.getSize();
+ regAlign = vmType.getAlignment();
+ }
+
+ offset = (offset + (regAlign-1)) & ~(regAlign-1);
+
+ size_t regOffset = offset;
+ offset += regSize;
+
+ vmRegs[rr].type = vmType;
+ vmRegs[rr].offset = regOffset;
+ }
+ vmFunc->frameSize = offset;
+
+ for( UInt cc = 0; cc < constCount; ++cc )
+ {
+ auto globalID = bcFunc->consts[cc].globalID;
+ auto globalPtr = getRegPtrImpl(vmModuleInstance, globalID);
+ vmFunc->consts[cc].ptr = globalPtr;
+ vmFunc->consts[cc].type = vmModuleInstance->func->regs[globalID].type;
+ }
+
+ return vmFunc;
+}
+
+VMFrame* createFrame(VMFunc* vmFunc)
+{
+ VMFrame* vmFrame = (VMFrame*) malloc(vmFunc->frameSize);
+ vmFrame->func = vmFunc;
+ vmFrame->ip = vmFunc->bcFunc->blocks[0].code;
+ return vmFrame;
+}
+
+void dumpVMFrame(VMFrame* vmFrame)
+{
+ fflush(stderr);
+
+ // We want to walk the VM frame and dump the
+ // state of all of its logical registers.
+ // For now this is made easier by having
+ // no overlapping register assignments...
+ VMFunc* vmFunc = vmFrame->func;
+ BCFunc* bcFunc = vmFunc->bcFunc;
+ UInt regCount = bcFunc->regCount;
+
+ for (UInt rr = 0; rr < regCount; ++rr)
+ {
+ VMType regType = getOperandTypeImpl(vmFrame, rr);
+ void* regData = getRegPtrImpl(vmFrame, rr);
+
+ char const* name = bcFunc->regs[rr].name;
+
+ // Use the type to print the data...
+
+ fprintf(stderr, "0x%p: ", regData);
+
+ fprintf(stderr, "%%%u ", rr);
+ if (name)
+ {
+ fprintf(stderr, "\"%s\" ", name);
+ }
+ if (regType.impl)
+ {
+ switch (regType.impl->op)
+ {
+ case kIROp_TypeType:
+ // TODO: we could recursively go and print types...
+ fprintf(stderr, ": Type = ???");
+ break;
+
+ case kIROp_readWriteStructuredBufferType:
+ fprintf(stderr, ": RWStructuredBuffer<???> = ???");
+ break;
+
+ case kIROp_structuredBufferType:
+ fprintf(stderr, ": StructuredBuffer<???> = ???");
+ break;
+
+ case kIROp_BoolType:
+ fprintf(stderr, ": Bool = %s", *(bool*)regData ? "true" : "false");
+ break;
+
+ case kIROp_Int32Type:
+ fprintf(stderr, ": Int32 = %d", *(int32_t*)regData);
+ break;
+
+ case kIROp_UInt32Type:
+ fprintf(stderr, ": UInt32 = %u", *(uint32_t*)regData);
+ break;
+
+ case kIROp_PtrType:
+ {
+ fprintf(stderr, ": Ptr<???> = 0x%p", *(void**)regData);
+ }
+ break;
+
+ default:
+ fprintf(stderr, "<unknown>");
+ break;
+ }
+ }
+ else
+ {
+ fprintf(stderr, ": <null>");
+ }
+ fprintf(stderr, "; // ");
+ fprintf(stderr, "%s", getIROpInfo((IROp) bcFunc->regs[rr].op).name);
+ fprintf(stderr, "\n");
+
+ // Okay, now we need to use the type
+ // stored in the VM register to tell
+ // us how to print things.
+ }
+
+ IROp op = IROp(*vmFrame->ip);
+ IROpInfo opInfo = getIROpInfo(op);
+ fprintf(stderr, "NEXT op: %s\n", opInfo.name);
+
+ fflush(stderr);
+}
+
+struct VM
+{
+};
+
+VM* createVM()
+{
+ VM* vm = new VM();
+ return vm;
+}
+
+struct VMThread
+{
+ // The currently executing call frame
+ VMFrame* frame;
+};
+
+void resumeThread(
+ VMThread* vmThread);
+
+VMFrame* loadVMModuleInstance(
+ VM* vm,
+ void const* bytecode,
+ size_t bytecodeSize)
+{
+ BCHeader* bcHeader = (BCHeader*) bytecode;
+
+ BCModule* bcModule = bcHeader->module;
+
+ UInt vmModuleSize = sizeof(VMModule) + bcModule->regCount * sizeof(VMReg);
+
+ VMModule* vmModule = (VMModule*) loadVMFunc(bcModule, nullptr);
+
+ // Create a frame to store the loaded symbols, and execute it
+ // to initialize them.
+ VMFrame* vmModuleInstance = createFrame(vmModule);
+ vmModuleInstance->parent = nullptr;
+ vmModule->module = vmModuleInstance;
+
+
+ VMThread thread;
+ thread.frame = vmModuleInstance;
+
+ resumeThread(&thread);
+
+ return vmModuleInstance;
+}
+
+void* findGlobalSymbolPtr(
+ VMFrame* moduleInstance,
+ char const* name)
+{
+ // Okay, we need to search through the available
+ // "registers" looking for one that gives us a
+ // name match.
+ //
+ BCFunc* bcFunc = moduleInstance->func->bcFunc;
+ UInt regCount = bcFunc->regCount;
+ for (UInt rr = 0; rr < regCount; ++rr)
+ {
+ BCReg* bcReg = &bcFunc->regs[rr];
+
+ char const* symbolName = bcReg->name;
+ if (!symbolName)
+ continue;
+
+ if(strcmp(symbolName, name) == 0)
+ return getRegPtrImpl(moduleInstance, rr);
+ }
+
+ return nullptr;
+}
+
+VMThread* createThread(
+ VM* vm)
+{
+ VMThread* thread = new VMThread();
+ thread->frame = nullptr;
+ return thread;
+}
+
+void beginCall(
+ VMThread* vmThread,
+ VMFunc* vmFunc)
+{
+ VMFrame* vmFrame = createFrame(vmFunc);
+
+ vmFrame->parent = vmThread->frame;
+ vmThread->frame = vmFrame;
+}
+
+void setArg(
+ VMThread* vmThread,
+ UInt argIndex,
+ void const* data,
+ size_t size)
+{
+ // TODO: need all kinds of validation here...
+
+ void* dest = getRegPtrImpl(vmThread->frame, argIndex);
+ memcpy(dest, data, size);
+}
+
+void resumeThread(
+ VMThread* vmThread)
+{
+ auto frame = vmThread->frame;
+ auto ip = frame->ip;
+
+ for(;;)
+ {
+#if 0
+ // debugging:
+ frame->ip = ip;
+ dumpVMFrame(frame);
+#endif
+
+ auto op = (IROp) decodeUInt(&ip);
+ switch( op )
+ {
+ case kIROp_TypeType:
+ {
+ // The type of types
+ Int argCount = decodeUInt(&ip);
+ void* arg0Ptr = decodeOperandPtr<void>(frame, &ip);
+ VMType* destPtr = decodeOperandPtr<VMType>(frame, &ip);
+
+ auto typeImpl = new VMTypeImpl();
+ typeImpl->op = op;
+ typeImpl->size = sizeof(VMType);
+ typeImpl->alignment = alignof(VMType);
+
+ VMType type = { typeImpl };
+ *destPtr = type;
+ }
+ break;
+
+ case kIROp_BlockType:
+ case kIROp_Int32Type:
+ case kIROp_UInt32Type:
+ case kIROp_Float32Type:
+ case kIROp_BoolType:
+ case kIROp_VoidType:
+ case kIROp_FuncType: // TODO: we should in principle handle function types here
+ {
+ // Case to handle types without arguments.
+ UInt argCount = decodeUInt(&ip);
+ for( UInt aa = 0; aa < argCount; ++aa )
+ {
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ }
+ VMType* destPtr = decodeOperandPtr<VMType>(frame, &ip);
+
+ auto typeImpl = new VMTypeImpl();
+ typeImpl->op = op;
+
+ UInt size = 1;
+ UInt align = 0;
+ switch (op)
+ {
+ case kIROp_BlockType: size = sizeof(void*); break;
+ case kIROp_Int32Type: size = sizeof(int32_t); break;
+ case kIROp_UInt32Type: size = sizeof(uint32_t); break;
+ case kIROp_Float32Type: size = sizeof(float); break;
+ case kIROp_BoolType: size = sizeof(bool); break;
+ case kIROp_VoidType: size = 0; align = 1; break;
+ default:
+ break;
+ }
+ if (!align) align = size;
+ typeImpl->size = size;
+ typeImpl->alignment = align;
+
+ VMType type = { typeImpl };
+ *destPtr = type;
+ }
+ break;
+
+ case kIROp_PtrType:
+ {
+ // Case to handle types without arguments.
+ UInt argCount = decodeUInt(&ip);
+ VMType* typeTypePtr = decodeOperandPtr<VMType>(frame, &ip);
+ VMType baseType = decodeOperand<VMType>(frame, &ip);
+ int32_t addressSpace = decodeOperand<int32_t>(frame, &ip);
+ VMType* destPtr = decodeOperandPtr<VMType>(frame, &ip);
+
+
+
+ auto typeImpl = new VMPtrTypeImpl();
+ typeImpl->op = op;
+ typeImpl->size = sizeof(void*);
+ typeImpl->alignment = alignof(void*);
+ typeImpl->base = baseType;
+ typeImpl->addressSpace = addressSpace;
+
+ VMType type = { typeImpl };
+ *destPtr = type;
+ }
+ break;
+
+ case kIROp_readWriteStructuredBufferType:
+ case kIROp_structuredBufferType:
+ {
+ // Case to handle types without arguments.
+ UInt argCount = decodeUInt(&ip);
+ VMType* typeTypePtr = decodeOperandPtr<VMType>(frame, &ip);
+ VMType baseType = decodeOperand<VMType>(frame, &ip);
+ VMType* destPtr = decodeOperandPtr<VMType>(frame, &ip);
+
+
+ // TODO: give these their own representations!
+ auto typeImpl = new VMPtrTypeImpl();
+ typeImpl->op = op;
+ typeImpl->base = baseType;
+ typeImpl->size = sizeof(void*);
+ typeImpl->alignment = sizeof(void*);
+
+ VMType type = { typeImpl };
+ *destPtr = type;
+ }
+ break;
+
+
+ case kIROp_IntLit:
+ {
+ VMType type = decodeOperand<VMType>(frame, &ip);
+ UInt uVal = decodeUInt(&ip);
+ void* destPtr = decodeOperandPtr<void>(frame, &ip);
+
+ switch( type.impl->op )
+ {
+ case kIROp_Int32Type:
+ *(int32_t*)destPtr = int32_t(uVal);
+ break;
+
+ case kIROp_UInt32Type:
+ *(uint32_t*)destPtr = uint32_t(uVal);
+ break;
+
+ default:
+ SLANG_UNEXPECTED("integer type");
+ break;
+ }
+
+ }
+ break;
+
+ case kIROp_FloatLit:
+ {
+ VMType type = decodeOperand<VMType>(frame, &ip);
+
+ static const UInt size = sizeof(IRFloatingPointValue);
+ IRFloatingPointValue value;
+ memcpy(&value, ip, size);
+ ip += size;
+ void* destPtr = decodeOperandPtr<void>(frame, &ip);
+
+ switch( type.impl->op )
+ {
+ case kIROp_Float32Type:
+ *(float*)destPtr = float(value);
+ break;
+
+ default:
+ SLANG_UNEXPECTED("float type");
+ break;
+ }
+ }
+ break;
+
+ case kIROp_boolConst:
+ {
+ bool val = (*ip++) != 0;
+ bool* destPtr = decodeOperandPtr<bool>(frame, &ip);
+ *destPtr = val;
+ }
+ break;
+
+ case kIROp_Func:
+ {
+ UInt nestedID = decodeUInt(&ip);
+ void* destPtr = decodeOperandPtr<void>(frame, &ip);
+
+ BCSymbol* bcSymbol = frame->func->bcFunc->nestedSymbols[nestedID];
+ BCFunc* bcFunc = (BCFunc*)bcSymbol;
+ VMFunc* vmFunc = loadVMFunc(bcFunc, frame->func->module);
+
+ *(VMFunc**)destPtr = vmFunc;
+ }
+ break;
+
+ case kIROp_Var:
+ {
+ // This instruction represents the `alloca` for a variable of some type.
+
+ UInt argCount = decodeUInt(&ip);
+ void* argPtrs[16] = { 0 };
+ for( UInt aa = 0; aa < argCount; ++aa )
+ {
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ argPtrs[aa] = argPtr;
+ }
+
+ void** destPtr = decodeOperandPtr<void*>(frame, &ip);
+
+ // For now this is a bit silly and simple: the
+ // storage for the variable we are "allocating"
+ // should be right after outer destination, so we can
+ // set it pretty easily.
+
+ *destPtr = destPtr + 1;
+ }
+ break;
+
+ case kIROp_Store:
+ {
+ // An ordinary memory store
+ VMType type = decodeOperand<VMType>(frame, &ip);
+ void* dest = decodeOperand<void*>(frame, &ip);
+ void* src = decodeOperandPtr<void>(frame, &ip);
+
+ memcpy(dest, src, type.getSize());
+ }
+ break;
+
+ case kIROp_Load:
+ {
+ // An ordinary memory store
+ VMType type = decodeOperand<VMType>(frame, &ip);
+ void* src = decodeOperand<void*>(frame, &ip);
+ void* dest = decodeOperandPtr<void>(frame, &ip);
+
+ memcpy(dest, src, type.getSize());
+ }
+ break;
+
+ case kIROp_BufferLoad:
+ {
+ UInt argCount = decodeUInt(&ip);
+ void* argPtrs[16] = { 0 };
+ for( UInt aa = 0; aa < argCount; ++aa )
+ {
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ argPtrs[aa] = argPtr;
+ }
+
+ void* dest = decodeOperandPtr<void>(frame, &ip);
+
+ VMType type = *(VMType*)argPtrs[0];
+ char* bufferData = *(char**)argPtrs[1];
+ uint32_t index = *(uint32_t*)argPtrs[2];
+
+ auto size = type.getSize();
+ char* elementData = bufferData + index*size;
+ memcpy(dest, elementData, size);
+ }
+ break;
+
+ case kIROp_BufferStore:
+ {
+ UInt argCount = decodeUInt(&ip);
+
+ VMType* resultTypePtr = decodeOperandPtr<VMType>(frame, &ip);
+ char* bufferData = decodeOperand<char*>(frame, &ip);
+ uint32_t index = decodeOperand<uint32_t>(frame, &ip);
+
+ auto srcPtrAndType = decodeOperandPtrAndType(frame, &ip);
+ void* srcPtr = srcPtrAndType.ptr;
+ VMType type = srcPtrAndType.type;
+
+ auto size = type.getSize();
+ char* elementData = bufferData + index*size;
+ memcpy(elementData, srcPtr, size);
+ }
+ break;
+ case kIROp_Call:
+ {
+ UInt operandCount = decodeUInt(&ip);
+ VMType type = decodeOperand<VMType>(frame, &ip);
+ VMFunc* func = decodeOperand<VMFunc*>(frame, &ip);
+
+ // Okay, we need to create a frame to prepare the call
+ VMFrame* newFrame = createFrame(func);
+ newFrame->parent = frame;
+
+ // Remaining arguments should populate the
+ // first N registers of the callee
+ UInt argCount = operandCount - 2;
+ for( UInt aa = 0; aa < argCount; ++aa )
+ {
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ void* regPtr = getRegPtrImpl(newFrame, aa);
+
+ VMType regType = func->regs[aa].type;
+ memcpy(regPtr, argPtr, regType.getSize());
+ }
+
+ // Note that we do *not* try to read off
+ // the destination operand, and instead
+ // leave that to be done during the
+ // return sequence.
+ //
+
+ // Save the IP we were using in teh current function.
+ //
+ frame->ip = ip;
+
+ // Now switch over to the callee:
+ //
+ frame = newFrame;
+ ip = newFrame->ip;
+ }
+ break;
+
+ case kIROp_ReturnVoid:
+ {
+ // Easy case: just jump to the parent frame,
+ // without having to worry about operands.
+ VMFrame* newFrame = frame->parent;
+ vmThread->frame = newFrame;
+
+ // HACK: we need to know when we are done.
+ // TODO: We should probably have the bottom
+ // of the stack for a thread always point
+ // to a special bytecode sequence that
+ // forces a `yield` op that can handle
+ // the exit from the interpreter, rather
+ // than always take a branch here.
+ if (!newFrame)
+ return;
+
+ frame = newFrame;
+ ip = frame->ip;
+
+ }
+ break;
+
+ case kIROp_ReturnVal:
+ {
+ UInt argCount = decodeUInt(&ip);
+ void* typePtr = decodeOperandPtr<void>(frame, &ip);
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+
+ VMFrame* oldFrame = frame;
+ VMFrame* newFrame = frame->parent;
+ vmThread->frame = newFrame;
+
+ // Note: see the comments above about
+ // this branch.
+ if (!newFrame)
+ return;
+
+ frame = newFrame;
+ ip = frame->ip;
+
+ auto destPtrAndType = decodeOperandPtrAndType(frame, &ip);
+ void* destPtr = destPtrAndType.ptr;
+ VMType type = destPtrAndType.type;
+
+ memcpy(destPtr, argPtr, type.getSize());
+ }
+ break;
+
+ case kIROp_loop:
+ case kIROp_unconditionalBranch:
+ case kIROp_break:
+ case kIROp_continue:
+ {
+ // For now our encoding is very regular, so we can decode without
+ // knowing too much about an instruction...
+
+ UInt argCount = decodeUInt(&ip);
+ void* typePtr = decodeOperandPtr<void>(frame, &ip);
+ Int destinationBlock = decodeSInt(&ip);
+ for( UInt aa = 2; aa < argCount; ++aa )
+ {
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ }
+
+ // TODO: we need to deal with the case of
+ // passing arguments to the block, which
+ // means copying between the registers...
+ //
+ ip = frame->func->bcFunc->blocks[destinationBlock].code;
+ }
+ break;
+
+ case kIROp_loopTest:
+ case kIROp_if:
+ case kIROp_ifElse:
+ case kIROp_conditionalBranch:
+ {
+ // For now our encoding is very regular, so we can decode without
+ // knowing too much about an instruction...
+
+ UInt argCount = decodeUInt(&ip);
+ void* typePtr = decodeOperandPtr<void>(frame, &ip);
+ bool* condition = decodeOperandPtr<bool>(frame, &ip);
+ Int trueBlockID = decodeSInt(&ip);
+ Int falseBlockID = decodeSInt(&ip);
+ for( UInt aa = 4; aa < argCount; ++aa )
+ {
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ }
+
+ Int destinationBlock = *condition ? trueBlockID : falseBlockID;
+
+ // TODO: we need to deal with the case of
+ // passing arguments to the block, which
+ // means copying between the registers...
+ //
+ ip = frame->func->bcFunc->blocks[destinationBlock].code;
+ }
+ break;
+
+ case kIROp_Greater:
+ {
+ // For now our encoding is very regular, so we can decode without
+ // knowing too much about an instruction...
+
+ UInt argCount = decodeUInt(&ip);
+ void* argPtrs[16] = { 0 };
+ VMType resultType = decodeOperand<VMType>(frame, &ip);
+ auto leftOpnd = decodeOperandPtrAndType(frame, &ip);
+ auto type = leftOpnd.type;
+ auto leftPtr = leftOpnd.ptr;
+ void* rightPtr = decodeOperandPtr<void>(frame, &ip);
+
+ bool* destPtr = decodeOperandPtr<bool>(frame, &ip);
+
+ switch (type.impl->op)
+ {
+ case kIROp_Int32Type:
+ *destPtr = *(int32_t*)leftPtr > *(int32_t*)rightPtr;
+ break;
+
+ default:
+ SLANG_UNEXPECTED("comparison op case");
+ break;
+ }
+ }
+ break;
+
+ case kIROp_Mul:
+ {
+ UInt argCount = decodeUInt(&ip);
+ VMType type = decodeOperand<VMType>(frame, &ip);
+ void* leftPtr = decodeOperandPtr<void>(frame, &ip);
+ void* rightPtr = decodeOperandPtr<void>(frame, &ip);
+
+ void* destPtr = decodeOperandPtr<void>(frame, &ip);
+
+ switch (type.impl->op)
+ {
+ case kIROp_Int32Type:
+ *(int32_t*)destPtr = *(int32_t*)leftPtr * *(int32_t*)rightPtr;
+ break;
+
+ default:
+ SLANG_UNEXPECTED("comparison op case");
+ break;
+ }
+ }
+ break;
+
+ case kIROp_Sub:
+ {
+ UInt argCount = decodeUInt(&ip);
+ VMType type = decodeOperand<VMType>(frame, &ip);
+ void* leftPtr = decodeOperandPtr<void>(frame, &ip);
+ void* rightPtr = decodeOperandPtr<void>(frame, &ip);
+
+ void* destPtr = decodeOperandPtr<void>(frame, &ip);
+
+ switch (type.impl->op)
+ {
+ case kIROp_Int32Type:
+ *(int32_t*)destPtr = *(int32_t*)leftPtr - *(int32_t*)rightPtr;
+ break;
+
+ default:
+ SLANG_UNEXPECTED("comparison op case");
+ break;
+ }
+ }
+ break;
+
+ default:
+ {
+ // For now our encoding is very regular, so we can decode without
+ // knowing too much about an instruction...
+
+ UInt argCount = decodeUInt(&ip);
+ void* argPtrs[16] = { 0 };
+ for( UInt aa = 0; aa < argCount; ++aa )
+ {
+ void* argPtr = decodeOperandPtr<void>(frame, &ip);
+ argPtrs[aa] = argPtr;
+ }
+
+ SLANG_UNEXPECTED("unknown bytecode op");
+ return;
+ }
+ break;
+ }
+ }
+
+}
+
+
+
+
+SLANG_API void SlangVMThread_resume(
+ SlangVMThread* thread)
+{
+ Slang::resumeThread(
+ (Slang::VMThread*) thread);
+}
+
+} // namespace Slang
+
+SLANG_API SlangVM* SlangVM_create()
+{
+ return (SlangVM*) Slang::createVM();
+}
+
+SLANG_API SlangVMModule* SlangVMModule_load(
+ SlangVM* vm,
+ void const* bytecode,
+ size_t bytecodeSize)
+{
+ return (SlangVMModule*) Slang::loadVMModuleInstance(
+ (Slang::VM*) vm,
+ bytecode,
+ bytecodeSize);
+}
+
+SLANG_API void* SlangVMModule_findGlobalSymbolPtr(
+ SlangVMModule* module,
+ char const* name)
+{
+ return (SlangVMFunc*) Slang::findGlobalSymbolPtr(
+ (Slang::VMFrame*) module,
+ name);
+}
+
+SLANG_API SlangVMThread* SlangVMThread_create(
+ SlangVM* vm)
+{
+ return (SlangVMThread*)Slang::createThread(
+ (Slang::VM*) vm);
+}
+
+SLANG_API void SlangVMThread_beginCall(
+ SlangVMThread* thread,
+ SlangVMFunc* func)
+{
+ Slang::beginCall(
+ (Slang::VMThread*) thread,
+ (Slang::VMFunc*) func);
+}
+
+SLANG_API void SlangVMThread_setArg(
+ SlangVMThread* thread,
+ SlangUInt argIndex,
+ void const* data,
+ size_t size)
+{
+ Slang::setArg(
+ (Slang::VMThread*) thread,
+ argIndex,
+ data,
+ size);
+}
+
+SLANG_API void SlangVMThread_resume(
+ SlangVMThread* thread)
+{
+ Slang::resumeThread(
+ (Slang::VMThread*) thread);
+}
diff --git a/source/slang/vm.h b/source/slang/vm.h
new file mode 100644
index 000000000..e67fec43a
--- /dev/null
+++ b/source/slang/vm.h
@@ -0,0 +1,19 @@
+// vm.h
+#ifndef SLANG_VM_H_INCLUDED
+#define SLANG_VM_H_INCLUDED
+
+// This file defines a virtual machine for executing
+// code that has been converted to Slang bytecode.
+//
+
+#include "../core/basic.h"
+
+namespace Slang
+{
+
+
+
+}
+
+
+#endif