diff options
| -rw-r--r-- | slang.h | 34 | ||||
| -rw-r--r-- | slang.sln | 11 | ||||
| -rw-r--r-- | source/slang/bytecode.cpp | 871 | ||||
| -rw-r--r-- | source/slang/bytecode.h | 187 | ||||
| -rw-r--r-- | source/slang/compiler.cpp | 5 | ||||
| -rw-r--r-- | source/slang/emit.cpp | 1 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 50 | ||||
| -rw-r--r-- | source/slang/ir.h | 5 | ||||
| -rw-r--r-- | source/slang/lower-to-ir.cpp | 199 | ||||
| -rw-r--r-- | source/slang/slang.natvis | 17 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 4 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 4 | ||||
| -rw-r--r-- | source/slang/vm.cpp | 1080 | ||||
| -rw-r--r-- | source/slang/vm.h | 19 | ||||
| -rw-r--r-- | tests/ir/factorial.slang | 23 | ||||
| -rw-r--r-- | tests/ir/factorial.slang.expected | 13 | ||||
| -rw-r--r-- | tests/ir/loop.slang.expected | 109 | ||||
| -rw-r--r-- | tools/eval-test/eval-test.vcxproj | 164 | ||||
| -rw-r--r-- | tools/eval-test/eval-test.vcxproj.filters | 22 | ||||
| -rw-r--r-- | tools/eval-test/main.cpp | 132 | ||||
| -rw-r--r-- | tools/slang-test/main.cpp | 73 |
21 files changed, 2948 insertions, 75 deletions
@@ -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 @@ -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<*>"> + <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 }, }; |
