summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2017-09-21 10:21:34 -0700
committerGitHub <noreply@github.com>2017-09-21 10:21:34 -0700
commit0116717524291491d09d924b236aabada4d40d07 (patch)
tree30604c3a670b29d7c63a46d22ef8f94fbe1f174b
parent10b62eecd94be53eca4ac2555af860f864966d76 (diff)
Initial work on a "VM" for Slang code (#189)
At a high level, this commit adds two things: 1. A "bytecode" format for serializing Slang IR instructions and related structure (functions, "registers") 2. A virtual machine that can load and then execute code in that bytecode format. The reason for kicking off this work right now is that we *need* a way to run tests on Slang code generation that doesn't rely on having a GPU present (given that our CI runs on VM instances without GPUs), nor on textual comparison to the output of other compilers. With these features I've implemented a slapdash `slang-eval-test` test fixture that can run a (trivial) compute shader to very our compilation flow through to bytecode. Some key design constraints/challenges: - The bytecode format should be "position independent" so that a user can just load a blob of data and then inspect it without having to deserialize into another format, allocate memory, etc. Eventually the bytecode format might be a replacement for out current reflection API (we used to base reflection off a similar format, but the cost/benefit wasn't there at the time and we switched to just using the AST). - The VM should be able to execute bytecode functions without doing any per-operation translation, JIT, etc. (translation of more coarse-grained symbols is okay). For now the VM is just being used to run tests, but eventually I'd like it to be viable for: - Running Slang-based code in the context of the compiler itself. This starts with stuff like constant-folding in the front-end, but could expand to more general metaprogramming features. - Running Slang-based ocde within a runtime application (e.g., a game engine) that wants to be able to run things like "parameter shader" code, or even just evaluate compute-like code on CPU (e.g., when supporting particles on both CPU and GPU). - Finally, the bytecode format should ideally be able to round-trip back to the IR without unacceptable loss of information. This requirement and the previous one play off of each other, because things like a traditional SSA phi operation is ugly when you have to actually *execute* it. This doesn't matter right now when we don't have SSA yet, but it might be part of the decision-making here. The actual implementation is centralized in `bytecode.{h,cpp}` and `vm.{h.cpp}`. Big picture notes: - The space of opcodes is shared between IR and bytecode (BC), with the hope that this makes translation of operations between the two easy. - The actual bytecode instruction stream relies on a variable-length encoding for integer values, including opcodes and operand numbers, so that the common case is single-byte encoding. - In the long term I intend to have a rule that if you use a single-byte encoding for an opcode, then all operands are required to use single-byte encodings too. Operations that need multi-byte operands would then be forced to use a multi-byte encoding of the op, and would be sent down a slower path in the interpeter. - The "bytecode"'s outer structure is based on ordinary data structures linked with pointers, but they are "relative pointers" so the actual structure is position-independent. - There are two main kinds of operands: registers and "constants." An operand is a signed integer where non-negatie values indicate registers (with `index == operandVal`) and negative values indicate constants (with `index == ~operandVal`). - Registers are stored in the "stack frame" for a VM function call, and each has a fixed offset based on the size of the type and those that come before it. Conceptually, registers are allowed to overlap if they aren't live at the same time, and we manage this with a simple stack model: every register is supposed to identify the register that comes directly before it (this isn't implemented yet). - "Constants" are more realistically a representation of "captured" values, but they are currently also how constants come in. Basically we can use a compact range of indices in the bytecode for a function, and each of these indices indirectly refers to some value in the next outer scope. - The actual encoding of bytecode instructions right now is largely ad-hoc and very wasteful (we encode the type on everything, and we also encode everything as if it had varargs). - In some cases, an instruction needs to know the types of the values involved (e.g., because it needs to load an array element, which means copying a number of bytes based on the size). The way the VM works we have types attached to our registers, so we currently get sneaky and look at those types in some ops. Longer term is makes sense to encode the required type info directly in the BC. - There's a whole lot of hand-waving going on with how the actual top-level bytecode module gets loaded, because of the way we currently treat the top-level module as an instruction stream in the IR. This means that we try to represent the loaded module as a "stack frame" for a call to the module as a function, but that approach as serious problems, and isn't realistically what we want to do.
-rw-r--r--slang.h34
-rw-r--r--slang.sln11
-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
-rw-r--r--tests/ir/factorial.slang23
-rw-r--r--tests/ir/factorial.slang.expected13
-rw-r--r--tests/ir/loop.slang.expected109
-rw-r--r--tools/eval-test/eval-test.vcxproj164
-rw-r--r--tools/eval-test/eval-test.vcxproj.filters22
-rw-r--r--tools/eval-test/main.cpp132
-rw-r--r--tools/slang-test/main.cpp73
21 files changed, 2948 insertions, 75 deletions
diff --git a/slang.h b/slang.h
index 161e703f5..cbdddde4b 100644
--- a/slang.h
+++ b/slang.h
@@ -374,6 +374,37 @@ extern "C"
int entryPointIndex,
size_t* outSize);
+ typedef struct SlangVM SlangVM;
+ typedef struct SlangVMModule SlangVMModule;
+ typedef struct SlangVMFunc SlangVMFunc;
+ typedef struct SlangVMThread SlangVMThread;
+
+ SLANG_API SlangVM* SlangVM_create();
+
+ SLANG_API SlangVMModule* SlangVMModule_load(
+ SlangVM* vm,
+ void const* bytecode,
+ size_t bytecodeSize);
+
+ SLANG_API void* SlangVMModule_findGlobalSymbolPtr(
+ SlangVMModule* module,
+ char const* name);
+
+ SLANG_API SlangVMThread* SlangVMThread_create(
+ SlangVM* vm);
+
+ SLANG_API void SlangVMThread_beginCall(
+ SlangVMThread* thread,
+ SlangVMFunc* func);
+
+ SLANG_API void SlangVMThread_setArg(
+ SlangVMThread* thread,
+ SlangUInt argIndex,
+ void const* data,
+ size_t size);
+
+ SLANG_API void SlangVMThread_resume(
+ SlangVMThread* thread);
/* Note(tfoley): working on new reflection interface...
*/
@@ -980,6 +1011,7 @@ namespace slang
#include "source/core/slang-string.cpp"
#include "source/core/stream.cpp"
#include "source/core/text-io.cpp"
+#include "source/slang/bytecode.cpp"
#include "source/slang/diagnostics.cpp"
#include "source/slang/emit.cpp"
#include "source/slang/ir.cpp"
@@ -1002,7 +1034,7 @@ namespace slang
#include "source/slang/type-layout.cpp"
#include "source/slang/reflection.cpp"
#include "source/slang/slang.cpp"
-
+#include "source/slang/vm.cpp"
#endif
diff --git a/slang.sln b/slang.sln
index a999062a3..2c672886e 100644
--- a/slang.sln
+++ b/slang.sln
@@ -28,6 +28,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-glslang", "source\sla
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "slang-generate", "tools\slang-generate\slang-generate.vcxproj", "{66174227-8541-41FC-A6DF-4764FC66F78E}"
EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "eval-test", "tools\eval-test\eval-test.vcxproj", "{205FCAB9-A13F-4980-86FA-F6221A7095EE}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
@@ -100,6 +102,14 @@ Global
{66174227-8541-41FC-A6DF-4764FC66F78E}.Release|Win32.Build.0 = Release|Win32
{66174227-8541-41FC-A6DF-4764FC66F78E}.Release|x64.ActiveCfg = Release|x64
{66174227-8541-41FC-A6DF-4764FC66F78E}.Release|x64.Build.0 = Release|x64
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|Win32.ActiveCfg = Debug|Win32
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|Win32.Build.0 = Debug|Win32
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|x64.ActiveCfg = Debug|x64
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Debug|x64.Build.0 = Debug|x64
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|Win32.ActiveCfg = Release|Win32
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|Win32.Build.0 = Release|Win32
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|x64.ActiveCfg = Release|x64
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE}.Release|x64.Build.0 = Release|x64
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -109,5 +119,6 @@ Global
{0C768A18-1D25-4000-9F37-DA5FE99E3B64} = {74C5F0DC-93BB-4BF3-AC65-8C65491570F7}
{96610759-07B9-4EEB-A974-5C634A2E742B} = {74C5F0DC-93BB-4BF3-AC65-8C65491570F7}
{66174227-8541-41FC-A6DF-4764FC66F78E} = {74C5F0DC-93BB-4BF3-AC65-8C65491570F7}
+ {205FCAB9-A13F-4980-86FA-F6221A7095EE} = {74C5F0DC-93BB-4BF3-AC65-8C65491570F7}
EndGlobalSection
EndGlobal
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
diff --git a/tests/ir/factorial.slang b/tests/ir/factorial.slang
new file mode 100644
index 000000000..0ceff29bd
--- /dev/null
+++ b/tests/ir/factorial.slang
@@ -0,0 +1,23 @@
+//TEST:EVAL:
+
+StructuredBuffer<int> input;
+RWStructuredBuffer<int> output;
+
+int factorial(int n)
+{
+ int result = 1;
+ while(n > 0)
+ {
+ result *= n;
+ n--;
+ }
+ return result;
+}
+
+[numthreads(1, 1, 1)]
+void main(
+ uint tid : SV_DispatchThreadIndex)
+{
+ output[tid] = factorial(input[tid]);
+}
+
diff --git a/tests/ir/factorial.slang.expected b/tests/ir/factorial.slang.expected
new file mode 100644
index 000000000..105a87de9
--- /dev/null
+++ b/tests/ir/factorial.slang.expected
@@ -0,0 +1,13 @@
+result code = 0
+standard error = {
+}
+standard output = {
+outputData[0] = 1
+outputData[1] = 1
+outputData[2] = 2
+outputData[3] = 6
+outputData[4] = 24
+outputData[5] = 120
+outputData[6] = 720
+outputData[7] = 5040
+}
diff --git a/tests/ir/loop.slang.expected b/tests/ir/loop.slang.expected
index 2dc091b0e..2b8bff732 100644
--- a/tests/ir/loop.slang.expected
+++ b/tests/ir/loop.slang.expected
@@ -2,66 +2,77 @@ result code = 0
standard error = {
}
standard output = {
-let %41 : Ptr<Array<Vec<Float32,4>,64>,1> = var()
-let %68 : Ptr<StructuredBuffer<Vec<Float32,4>>,0> = var()
-let %243 : Ptr<RWStructuredBuffer<Vec<Float32,4>>,0> = var()
+let %63 : Ptr<Array<Vec<Float32,4>,64>,1> = var()
+let %90 : Ptr<StructuredBuffer<Vec<Float32,4>>,0> = var()
+let %268 : Ptr<RWStructuredBuffer<Vec<Float32,4>>,0> = var()
func %1(
param %7 : UInt32,
- param %10 : UInt32,
- param %13 : UInt32)
+ param %18 : UInt32,
+ param %28 : UInt32)
{
block %4:
- let %47 : Ptr<Vec<Float32,4>,0> = getElementPtr(%41, %10)
- let %69 : StructuredBuffer<Vec<Float32,4>> = load(%68)
- let %72 : Vec<Float32,4> = bufferLoad(%69, %7)
- store(%47, %72)
- let %81 : Ptr<UInt32,0> = var()
- let %89 : UInt32 = construct(1)
- store(%81, %89)
- loop(%94, %100, %103)
+ let %13 : Ptr<UInt32,0> = var()
+ store(%13, %7)
+ let %23 : Ptr<UInt32,0> = var()
+ store(%23, %18)
+ let %33 : Ptr<UInt32,0> = var()
+ store(%33, %28)
+ let %64 : UInt32 = load(%23)
+ let %69 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %64)
+ let %91 : StructuredBuffer<Vec<Float32,4>> = load(%90)
+ let %94 : UInt32 = load(%13)
+ let %95 : Vec<Float32,4> = bufferLoad(%91, %94)
+ store(%69, %95)
+ let %104 : Ptr<UInt32,0> = var()
+ let %112 : UInt32 = construct(1)
+ store(%104, %112)
+ loop(%117, %123, %126)
-block %94:
- let %110 : UInt32 = load(%81)
- let %119 : UInt32 = construct(64)
- let %120 : Bool = cmpLT(%110, %119)
- loopTest(%120, %97, %100)
+block %117:
+ let %133 : UInt32 = load(%104)
+ let %142 : UInt32 = construct(64)
+ let %143 : Bool = cmpLT(%133, %142)
+ loopTest(%143, %120, %123)
-block %97:
+block %120:
GroupMemoryBarrierWithGroupSync()
- let %147 : Ptr<Vec<Float32,4>,0> = getElementPtr(%41, %10)
- let %152 : Ptr<Vec<Float32,4>,0> = var()
- let %153 : Vec<Float32,4> = load(%147)
- store(%152, %153)
- let %174 : UInt32 = load(%81)
- let %175 : UInt32 = sub(%10, %174)
- let %180 : Ptr<Vec<Float32,4>,0> = getElementPtr(%41, %175)
- let %181 : Vec<Float32,4> = load(%180)
- let %182 : Vec<Float32,4> = load(%152)
- let %183 : Vec<Float32,4> = add(%182, %181)
- store(%152, %183)
- let %186 : Vec<Float32,4> = load(%152)
- store(%147, %186)
- unconditionalBranch(%103)
+ let %166 : UInt32 = load(%23)
+ let %171 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %166)
+ let %176 : Ptr<Vec<Float32,4>,0> = var()
+ let %177 : Vec<Float32,4> = load(%171)
+ store(%176, %177)
+ let %196 : UInt32 = load(%23)
+ let %199 : UInt32 = load(%104)
+ let %200 : UInt32 = sub(%196, %199)
+ let %205 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, %200)
+ let %206 : Vec<Float32,4> = load(%205)
+ let %207 : Vec<Float32,4> = load(%176)
+ let %208 : Vec<Float32,4> = add(%207, %206)
+ store(%176, %208)
+ let %211 : Vec<Float32,4> = load(%176)
+ store(%171, %211)
+ unconditionalBranch(%126)
-block %103:
- let %199 : Ptr<UInt32,0> = var()
- let %200 : UInt32 = load(%81)
- store(%199, %200)
- let %211 : UInt32 = construct(1)
- let %212 : UInt32 = load(%199)
- let %213 : UInt32 = shl(%212, %211)
- store(%199, %213)
- let %216 : UInt32 = load(%199)
- store(%81, %216)
- unconditionalBranch(%94)
+block %126:
+ let %224 : Ptr<UInt32,0> = var()
+ let %225 : UInt32 = load(%104)
+ store(%224, %225)
+ let %236 : UInt32 = construct(1)
+ let %237 : UInt32 = load(%224)
+ let %238 : UInt32 = shl(%237, %236)
+ store(%224, %238)
+ let %241 : UInt32 = load(%224)
+ store(%104, %241)
+ unconditionalBranch(%117)
-block %100:
+block %123:
GroupMemoryBarrierWithGroupSync()
- let %244 : RWStructuredBuffer<Vec<Float32,4>> = load(%243)
- let %260 : Ptr<Vec<Float32,4>,0> = getElementPtr(%41, 0)
- let %261 : Vec<Float32,4> = load(%260)
- bufferStore(%244, %7, %261)
+ let %269 : RWStructuredBuffer<Vec<Float32,4>> = load(%268)
+ let %272 : UInt32 = load(%13)
+ let %286 : Ptr<Vec<Float32,4>,0> = getElementPtr(%63, 0)
+ let %287 : Vec<Float32,4> = load(%286)
+ bufferStore(%269, %272, %287)
return_void()
}
}
diff --git a/tools/eval-test/eval-test.vcxproj b/tools/eval-test/eval-test.vcxproj
new file mode 100644
index 000000000..83f25b1df
--- /dev/null
+++ b/tools/eval-test/eval-test.vcxproj
@@ -0,0 +1,164 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup Label="ProjectConfigurations">
+ <ProjectConfiguration Include="Debug|Win32">
+ <Configuration>Debug</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|Win32">
+ <Configuration>Release</Configuration>
+ <Platform>Win32</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Debug|x64">
+ <Configuration>Debug</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ <ProjectConfiguration Include="Release|x64">
+ <Configuration>Release</Configuration>
+ <Platform>x64</Platform>
+ </ProjectConfiguration>
+ </ItemGroup>
+ <PropertyGroup Label="Globals">
+ <ProjectGuid>{205FCAB9-A13F-4980-86FA-F6221A7095EE}</ProjectGuid>
+ <Keyword>Win32Proj</Keyword>
+ <RootNamespace>evaltest</RootNamespace>
+ <WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
+ <ProjectName>slang-eval-test</ProjectName>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>true</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+ <ConfigurationType>Application</ConfigurationType>
+ <UseDebugLibraries>false</UseDebugLibraries>
+ <PlatformToolset>v140</PlatformToolset>
+ <WholeProgramOptimization>true</WholeProgramOptimization>
+ <CharacterSet>Unicode</CharacterSet>
+ </PropertyGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+ <ImportGroup Label="ExtensionSettings">
+ </ImportGroup>
+ <ImportGroup Label="Shared">
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+ <Import Project="..\..\build\slang-build.props" />
+ </ImportGroup>
+ <PropertyGroup Label="UserMacros" />
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <LinkIncremental>true</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <LinkIncremental>false</LinkIncremental>
+ <IncludePath>$(SolutionDir);$(IncludePath)</IncludePath>
+ </PropertyGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+ <ClCompile>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <WarningLevel>Level3</WarningLevel>
+ <Optimization>Disabled</Optimization>
+ <PreprocessorDefinitions>_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+ <ClCompile>
+ <WarningLevel>Level3</WarningLevel>
+ <PrecompiledHeader>
+ </PrecompiledHeader>
+ <Optimization>MaxSpeed</Optimization>
+ <FunctionLevelLinking>true</FunctionLevelLinking>
+ <IntrinsicFunctions>true</IntrinsicFunctions>
+ <PreprocessorDefinitions>NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+ </ClCompile>
+ <Link>
+ <SubSystem>Console</SubSystem>
+ <EnableCOMDATFolding>true</EnableCOMDATFolding>
+ <OptimizeReferences>true</OptimizeReferences>
+ <GenerateDebugInformation>true</GenerateDebugInformation>
+ </Link>
+ </ItemDefinitionGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp" />
+ </ItemGroup>
+ <ItemGroup>
+ <ProjectReference Include="..\..\source\slang\slang.vcxproj">
+ <Project>{db00da62-0533-4afd-b59f-a67d5b3a0808}</Project>
+ </ProjectReference>
+ </ItemGroup>
+ <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+ <ImportGroup Label="ExtensionTargets">
+ </ImportGroup>
+</Project> \ No newline at end of file
diff --git a/tools/eval-test/eval-test.vcxproj.filters b/tools/eval-test/eval-test.vcxproj.filters
new file mode 100644
index 000000000..0d8d9e457
--- /dev/null
+++ b/tools/eval-test/eval-test.vcxproj.filters
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+ <ItemGroup>
+ <Filter Include="Source Files">
+ <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
+ <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
+ </Filter>
+ <Filter Include="Header Files">
+ <UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
+ <Extensions>h;hh;hpp;hxx;hm;inl;inc;xsd</Extensions>
+ </Filter>
+ <Filter Include="Resource Files">
+ <UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
+ <Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
+ </Filter>
+ </ItemGroup>
+ <ItemGroup>
+ <ClCompile Include="main.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ </ItemGroup>
+</Project> \ No newline at end of file
diff --git a/tools/eval-test/main.cpp b/tools/eval-test/main.cpp
new file mode 100644
index 000000000..f8736a07b
--- /dev/null
+++ b/tools/eval-test/main.cpp
@@ -0,0 +1,132 @@
+// main.cpp
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <slang.h>
+
+int main(
+ int argc,
+ char** argv)
+{
+ // TODO: parse arguments
+
+ assert(argc >= 2);
+ char const* inputPath = argv[1];
+
+ // Slurp in the input file, so that we can compile and run it
+ FILE* inputFile = fopen(inputPath, "rb");
+ assert(inputFile);
+
+ fseek(inputFile, 0, SEEK_END);
+ size_t inputSize = ftell(inputFile);
+ fseek(inputFile, 0, SEEK_SET);
+
+ char* inputText = (char*) malloc(inputSize + 1);
+ fread(inputText, inputSize, 1, inputFile);
+ inputText[inputSize] = 0;
+ fclose(inputFile);
+
+ // TODO: scan through the text to find comments,
+ // that instruct us how to generate input and
+ // consume output when running the test.
+
+ //
+
+ SlangSession* session = spCreateSession(nullptr);
+ SlangCompileRequest* request = spCreateCompileRequest(session);
+
+ spSetCodeGenTarget(
+ request,
+ SLANG_IR);
+
+ int translationUnitIndex = spAddTranslationUnit(
+ request,
+ SLANG_SOURCE_LANGUAGE_SLANG,
+ nullptr);
+
+ spAddTranslationUnitSourceString(
+ request,
+ translationUnitIndex,
+ inputPath,
+ inputText);
+
+ int entryPointIndex = spAddEntryPoint(
+ request,
+ translationUnitIndex,
+ "main",
+ spFindProfile(session, "cs_5_0"));
+
+ if( spCompile(request) != 0 )
+ {
+ char const* output = spGetDiagnosticOutput(request);
+ fputs(output, stderr);
+ exit(1);
+ }
+
+ // Things compiled, so now we need to run them...
+
+ // Extract the bytecode
+ size_t bytecodeSize = 0;
+ void const* bytecode = spGetEntryPointCode(request, entryPointIndex, &bytecodeSize);
+
+ // Now we need to create an execution context to go and run the bytecode we got
+
+ SlangVM* vm = SlangVM_create();
+
+ SlangVMModule* vmModule = SlangVMModule_load(
+ vm,
+ bytecode,
+ bytecodeSize);
+
+ SlangVMFunc* vmFunc = *(SlangVMFunc**)SlangVMModule_findGlobalSymbolPtr(
+ vmModule,
+ "main");
+
+ int32_t*& inputArg = **(int32_t***)SlangVMModule_findGlobalSymbolPtr(
+ vmModule,
+ "input");
+
+ int32_t*& outputArg = **(int32_t***)SlangVMModule_findGlobalSymbolPtr(
+ vmModule,
+ "output");
+
+ SlangVMThread* vmThread = SlangVMThread_create(
+ vm);
+
+ int32_t inputData[8] = { 0, 1, 2, 3, 4, 5, 6, 7 };
+ int32_t outputData[8] = { 0 };
+
+ inputArg = inputData;
+ outputArg = outputData;
+
+ // TODO: set arguments based on specification from the user...
+ for (uint32_t threadID = 0; threadID < 8; ++threadID)
+ {
+#if 0
+ fprintf(stderr, "\n\nthreadID = %u\n\n", threadID);
+ fflush(stderr);
+#endif
+
+ SlangVMThread_beginCall(vmThread, vmFunc);
+
+ SlangVMThread_setArg(
+ vmThread,
+ 0,
+ &threadID,
+ sizeof(threadID));
+
+ SlangVMThread_resume(vmThread);
+ }
+
+ for (uint32_t ii = 0; ii < 8; ++ii)
+ {
+ fprintf(stdout, "outputData[%u] = %d\n", ii, outputData[ii]);
+ }
+
+ spDestroyCompileRequest(request);
+ spDestroySession(session);
+
+ return 0;
+}
diff --git a/tools/slang-test/main.cpp b/tools/slang-test/main.cpp
index 6ee255571..563fc5dab 100644
--- a/tools/slang-test/main.cpp
+++ b/tools/slang-test/main.cpp
@@ -671,6 +671,78 @@ TestResult runSimpleTest(TestInput& input)
return result;
}
+TestResult runEvalTest(TestInput& input)
+{
+ // We are going to load and evaluate the code
+
+ auto filePath = input.filePath;
+ auto outputStem = input.outputStem;
+
+ OSProcessSpawner spawner;
+
+ spawner.pushExecutablePath(String(options.binDir) + "slang-eval-test.exe");
+ spawner.pushArgument(filePath);
+
+ for( auto arg : input.testOptions->args )
+ {
+ spawner.pushArgument(arg);
+ }
+
+ if (spawnAndWait(outputStem, spawner) != kOSError_None)
+ {
+ return kTestResult_Fail;
+ }
+
+ String actualOutput = getOutput(spawner);
+
+ String expectedOutputPath = outputStem + ".expected";
+ String expectedOutput;
+ try
+ {
+ expectedOutput = Slang::File::ReadAllText(expectedOutputPath);
+ }
+ catch (Slang::IOException)
+ {
+ }
+
+ // If no expected output file was found, then we
+ // expect everything to be empty
+ if (expectedOutput.Length() == 0)
+ {
+ expectedOutput = "result code = 0\nstandard error = {\n}\nstandard output = {\n}\n";
+ }
+
+ TestResult result = kTestResult_Pass;
+
+ // Otherwise we compare to the expected output
+ if (actualOutput != expectedOutput)
+ {
+ result = kTestResult_Fail;
+ }
+
+ // If the test failed, then we write the actual output to a file
+ // so that we can easily diff it from the command line and
+ // diagnose the problem.
+ if (result == kTestResult_Fail)
+ {
+ String actualOutputPath = outputStem + ".actual";
+ Slang::File::WriteAllText(actualOutputPath, actualOutput);
+
+ if (options.outputMode == kOutputMode_AppVeyor)
+ {
+ fprintf(stderr, "ERROR:\n"
+ "EXPECTED{{{\n%s}}}\n"
+ "ACTUAL{{{\n%s}}}\n",
+ expectedOutput.Buffer(),
+ actualOutput.Buffer());
+ fflush(stderr);
+ }
+ }
+
+ return result;
+}
+
+
TestResult runCrossCompilerTest(TestInput& input)
{
// need to execute the stand-alone Slang compiler on the file
@@ -1164,6 +1236,7 @@ TestResult runTest(
{ "COMPARE_HLSL_GLSL_RENDER", &runHLSLAndGLSLComparisonTest },
{ "COMPARE_GLSL", &runGLSLComparisonTest },
{ "CROSS_COMPILE", &runCrossCompilerTest },
+ { "EVAL", &runEvalTest },
{ nullptr, nullptr },
};