diff options
Diffstat (limited to 'source')
| -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 |
12 files changed, 2417 insertions, 25 deletions
diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp new file mode 100644 index 000000000..52086c76b --- /dev/null +++ b/source/slang/bytecode.cpp @@ -0,0 +1,871 @@ +#include "bytecode.h" + +// Implementation of the Slang bytecode (BC) +// (most notably including conversion from IR to BC) + +#include "compiler.h" +#include "ir.h" +#include "ir-insts.h" +#include "lower-to-ir.h" + +namespace Slang +{ +struct SharedBytecodeGenerationContext; + +// Representation of a `BCPtr<T>` during actual bytecode generation. +// This representation is to deal with the fact that the actual +// storage for the bytecode data might get reallocated during emission +// so that we need to be careful and not work with raw `BCPtr<T>`. +template<typename T> +struct BytecodeGenerationPtr +{ + SharedBytecodeGenerationContext* sharedContext; + UInt offset; + + BytecodeGenerationPtr() + : sharedContext(nullptr) + , offset(0) + {} + + + BytecodeGenerationPtr( + SharedBytecodeGenerationContext* sharedContext, + UInt offset) + : sharedContext(sharedContext) + , offset(offset) + {} + + BytecodeGenerationPtr( + BytecodeGenerationPtr<T> const& ptr) + : sharedContext(ptr.sharedContext) + , offset(ptr.offset) + {} + + template<typename U> + BytecodeGenerationPtr( + BytecodeGenerationPtr<U> const& ptr, + typename EnableIf<IsConvertible<T*, U*>::Value, void>::type * = 0) + : sharedContext(ptr.sharedContext) + , offset(ptr.offset) + {} + + operator BCPtr<T>() + { + return BCPtr<T>(getPtr()); + } + + T* operator->() + { + return getPtr(); + } + + T& operator*() + { + return *getPtr() + } + + T& operator[](UInt index) + { + return getPtr()[index]; + } + + BytecodeGenerationPtr<T> operator+(Int index) + { + return BytecodeGenerationPtr<T>( + sharedContext, + offset + index*sizeof(T)); + } + + T* getPtr() const; +}; + +#if 0 +template<typename T> +void BCPtr<T>::operator=(BytecodeGenerationPtr<T> const& ptr) +{ + fprintf(stderr, "0x%p: operator=BGP 0x%p\n", this, ptr.getPtr()); + *this = ptr.getPtr(); +} +#endif + +struct SharedBytecodeGenerationContext +{ + // The final generated bytecode stream + List<uint8_t> bytecode; + + // Map from a global symbol to its global ID + Dictionary<IRInst*, Int> mapGlobalSymbolToGLobalID; +}; + +struct BytecodeGenerationContext +{ + SharedBytecodeGenerationContext* shared; + + // The function that is in scope for this context + IRFunc* currentIRFunc; + + // Counter for global symbols that have been assigned + // so that they can be used by this function + List<BCConst> remappedGlobalSymbols; + + // Map an instruction to its ID for use local + // to the current context + Dictionary<IRInst*, Int> mapInstToLocalID; + + // Map an instruction to the ID for its auxiliary + // symbol data + Dictionary<IRInst*, UInt> mapInstToNestedID; +}; + +template<typename T> +T* BytecodeGenerationPtr<T>::getPtr() const +{ + if(!sharedContext) return nullptr; + return (T*)(sharedContext->bytecode.Buffer() + offset); +} + + +BCPtr<void>::RawVal allocateRaw( + BytecodeGenerationContext* context, + size_t size, + size_t alignment) +{ + size_t currentOffset = context->shared->bytecode.Count(); + + size_t beginOffset = (currentOffset + (alignment-1)) & ~(alignment-1); + + size_t endOffset = beginOffset + size; + + for(size_t ii = currentOffset; ii < endOffset; ++ii) + context->shared->bytecode.Add(0); + + return beginOffset; +} + +template<typename T> +BytecodeGenerationPtr<T> allocate( + BytecodeGenerationContext* context) +{ + return BytecodeGenerationPtr<T>(context->shared, allocateRaw(context, sizeof(T), alignof(T))); +} + +template<typename T> +BytecodeGenerationPtr<T> allocateArray( + BytecodeGenerationContext* context, + UInt count) +{ + return BytecodeGenerationPtr<T>(context->shared, allocateRaw(context, count * sizeof(T), alignof(T))); +} + +template<typename T> +BytecodeGenerationPtr<T> getPtr( + BytecodeGenerationContext* context) +{ + return BytecodeGenerationPtr<T>(context->shared, context->shared->bytecode.Count()); +} + + +void encodeUInt8( + BytecodeGenerationContext* context, + uint8_t value) +{ + context->shared->bytecode.Add(value); +} + +void encodeUInt( + BytecodeGenerationContext* context, + UInt value) +{ + if( value < 128 ) + { + encodeUInt8(context, value); + return; + } + + uint8_t bytes[16]; + UInt count = 0; + + for(;;) + { + UInt index = count++; + bytes[index] = value & 0x7F; + value = value >> 7; + if (!value) + break; + + bytes[index] |= 0x80; + } + + UInt index = count; + while (index--) + { + encodeUInt8(context, bytes[index]); + } +} + +void encodeSInt( + BytecodeGenerationContext* context, + Int value) +{ + UInt uValue; + if( value < 0 ) + { + uValue = (~UInt(value) << 1) | 1; + } + else + { + uValue = UInt(value) << 1; + } + + encodeUInt(context, uValue); +} + +Int getLocalID( + BytecodeGenerationContext* context, + IRInst* inst) +{ + Int localID = 0; + if( context->mapInstToLocalID.TryGetValue(inst, localID) ) + { + return localID; + } + + Int globalID = 0; + if( context->shared->mapGlobalSymbolToGLobalID.TryGetValue(inst, globalID) ) + { + BCConst bcConst; + bcConst.globalID = globalID; + + UInt remappedSymbolIndex = context->remappedGlobalSymbols.Count(); + context->remappedGlobalSymbols.Add(bcConst); + + localID = ~remappedSymbolIndex; + context->mapInstToLocalID.Add(inst, localID); + return localID; + } + + SLANG_UNEXPECTED("no ID for inst"); + return -9999; +} + +void encodeOperand( + BytecodeGenerationContext* context, + IRInst* operand) +{ + auto id = getLocalID(context, operand); + encodeSInt(context, id); +} + +bool opHasResult(IRInst* inst) +{ + auto type = inst->getType(); + if( !type || type->op != kIROp_VoidType ) + { + return true; + } + return false; +} + +void generateBytecodeForInst( + BytecodeGenerationContext* context, + IRInst* inst) +{ + // We are generating bytecode for a local instruction + // inside a function or similar context. + switch( inst->op ) + { + default: + { + // As a default case, we will assume that bytecode ops + // and the IR's internal opcodes are the same, and then + // encode the necessary extra info: + // + + auto argCount = inst->getArgCount(); + encodeUInt(context, inst->op); + encodeUInt(context, argCount); + for( UInt aa = 0; aa < argCount; ++aa ) + { + encodeOperand(context, inst->getArg(aa)); + } + + auto type = inst->getType(); + if( type && type->op == kIROp_VoidType ) + { + // This instructions has no type, so don't emit a destination + } + else + { + // The instruction can be encoded + // as its own operand for the destination. + encodeOperand(context, inst); + } + } + break; + + case kIROp_ReturnVoid: + // Trivial encoding here + encodeUInt(context, inst->op); + break; + + case kIROp_IntLit: + { + auto ii = (IRConstant*) inst; + encodeUInt(context, ii->op); + encodeOperand(context, ii->getType()); + + // TODO: probably want distinct encodings + // for signed vs. unsigned here. + encodeUInt(context, UInt(ii->u.intVal)); + + // destination: + encodeOperand(context, inst); + } + break; + + case kIROp_FloatLit: + { + auto ii = (IRConstant*) inst; + encodeUInt(context, ii->op); + encodeOperand(context, ii->getType()); + + static const UInt size = sizeof(IRFloatingPointValue); + unsigned char buffer[size]; + memcpy(buffer, &ii->u.floatVal, sizeof(buffer)); + + for(UInt ii = 0; ii < size; ++ii) + { + encodeUInt8(context, buffer[ii]); + } + + // destination: + encodeOperand(context, inst); + } + break; + + case kIROp_boolConst: + { + auto ii = (IRConstant*) inst; + encodeUInt(context, ii->op); + encodeUInt(context, ii->u.intVal ? 1 : 0); + + // destination: + encodeOperand(context, inst); + } + break; + + case kIROp_Func: + { + encodeUInt(context, inst->op); + + // We just want to encode the ID for the function + // symbol data, and then do the rest on the decode side + UInt nestedID = 0; + context->mapInstToNestedID.TryGetValue(inst, nestedID); + encodeUInt(context, nestedID); + + // destination: + encodeOperand(context, inst); + } + break; + + case kIROp_Store: + { + encodeUInt(context, inst->op); + + // We need to encode the type being stored, to make + // our lives easier. + encodeOperand(context, inst->getArg(2)->getType()); + encodeOperand(context, inst->getArg(1)); + encodeOperand(context, inst->getArg(2)); + } + break; + + case kIROp_Load: + { + encodeUInt(context, inst->op); + encodeOperand(context, inst->getType()); + encodeOperand(context, inst->getArg(1)); + encodeOperand(context, inst); + } + break; + } +} + +Int getIDForGlobalSymbol( + BytecodeGenerationContext* context, + IRInst* inst) +{ + Int globalID; + if(context->shared->mapGlobalSymbolToGLobalID.TryGetValue(inst, globalID)) + return globalID; + + SLANG_UNEXPECTED("no such ID"); +} + +uint32_t getTypeForGlobalSymbol( + BytecodeGenerationContext* context, + IRInst* inst) +{ + auto type = inst->getType(); + if(!type) + return 0; + + return getIDForGlobalSymbol(context, type); +} + +BytecodeGenerationPtr<char> allocateString( + BytecodeGenerationContext* context, + char const* data, + UInt size) +{ + BytecodeGenerationPtr<char> ptr = allocateArray<char>(context, size + 1); + memcpy(ptr.getPtr(), data, size); + return ptr; +} + +BytecodeGenerationPtr<char> allocateString( + BytecodeGenerationContext* context, + String const& str) +{ + return allocateString(context, + str.Buffer(), + str.Length()); +} + +BytecodeGenerationPtr<char> allocateString( + BytecodeGenerationContext* context, + Name* name) +{ + return allocateString(context, name->text); +} + +BytecodeGenerationPtr<char> tryGenerateNameForSymbol( + BytecodeGenerationContext* context, + IRInst* inst) +{ + // TODO: this is gross, and the IR should probably have + // a more direct means of querying a name for a symbol. + if (auto highLevelDeclDecoration = inst->findDecoration<IRHighLevelDeclDecoration>()) + { + auto decl = highLevelDeclDecoration->decl; + if (auto reflectionNameMod = decl->FindModifier<ParameterBlockReflectionName>()) + { + return allocateString(context, reflectionNameMod->name); + } + else if(auto name = decl->nameAndLoc.name) + { + return allocateString(context, name); + } + } + + return BytecodeGenerationPtr<char>(); +} + +BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst( + BytecodeGenerationContext* context, + IRInst* inst) +{ + switch( inst->op ) + { + case kIROp_Func: + { + auto irFunc = (IRFunc*) inst; + BytecodeGenerationPtr<BCFunc> bcFunc = allocate<BCFunc>(context); + + bcFunc->op = inst->op; + bcFunc->typeGlobalID = getTypeForGlobalSymbol(context, inst); + + BytecodeGenerationContext subContextStorage; + BytecodeGenerationContext* subContext = &subContextStorage; + subContext->shared = context->shared; + subContext->currentIRFunc = irFunc; + + // First we need to enumerate our basic blocks, so that they + // can reference one another (basic blocks can forward reference + // blocks that haven't been seen yet). + // + // Note: we allow the IDs of blocks to overlap with ordinary + // "register" numbers, because there is no case where an operand + // could be either a block or an ordinary register. + // + UInt blockCounter = 0; + for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + Int blockID = Int(blockCounter++); + subContext->mapInstToLocalID.Add(bb, blockID); + } + UInt blockCount = blockCounter; + + // Allocate the array of block objects to be stored in the + // bytecode file. + auto bcBlocks = allocateArray<BCBlock>(context, blockCount); + bcFunc->blockCount = blockCount; + bcFunc->blocks = bcBlocks; + + // Now loop through the blocks again, and allocate the storage + // for any parameters, variables, or registers used inside + // each block. + // + // We'll count in a first pass, and then fill things in + // using a second pass + Int regCounter = 0; + blockCounter = 0; + for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + UInt blockID = blockCounter++; + UInt paramCount = 0; + for( auto ii = bb->firstChild; ii; ii = ii->nextInst ) + { + switch( ii->op ) + { + default: + // Default behavior: if an op has a result, + // then it needs a register to store it. + if(opHasResult(ii)) + { + regCounter++; + } + break; + + case kIROp_Param: + // A parameter always uses a register. + regCounter++; + // + // We also want to keep a count of the parameters themselves. + paramCount++; + break; + + + case kIROp_Var: + // A `var` (`alloca`) node needs two registers: + // one to hold the actual storage, and another + // to hold the pointer. + regCounter += 2; + break; + } + } + + bcBlocks[blockID].paramCount = paramCount; + } + + // Okay, we've counted how many registers we need for each block, + // and now we can allocate the contiguous array we will use. + UInt regCount = regCounter; + auto bcRegs = allocateArray<BCReg>(context, regCount); + + bcFunc->regCount = regCount; + bcFunc->regs = bcRegs; + + // Now we will loop over things again to fill in the information + // on all these registers. + + regCounter = 0; + blockCounter = 0; + for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + UInt blockID = blockCounter++; + + // Loop over just the parameters first, to ensure they + // are always the first N registers of a block. + // + // This means the parameters of the function itself + // are always the first N registers in the overall list. + // + bcBlocks[blockID].params = bcRegs + regCounter; + for( auto ii = bb->firstChild; ii; ii = ii->nextInst ) + { + if(ii->op != kIROp_Param) + continue; + + Int localID = regCounter++; + subContext->mapInstToLocalID.Add(ii, localID); + + bcRegs[localID].op = ii->op; + bcRegs[localID].name = tryGenerateNameForSymbol(context, ii); + bcRegs[localID].previousVarIndexPlusOne = localID; + bcRegs[localID].typeGlobalID = getTypeForGlobalSymbol(context, ii); + } + + // Now loop over the non-parameter instructions and + // allocate actual register locations to them. + for( auto ii = bb->firstChild; ii; ii = ii->nextInst ) + { + switch(ii->op) + { + case kIROp_Param: + // Already handled. + break; + + default: + // For an ordinary instruction with a result, + // allocate it here. + if( opHasResult(ii) ) + { + Int localID = regCounter++; + subContext->mapInstToLocalID.Add(ii, localID); + + bcRegs[localID].op = ii->op; + bcRegs[localID].name = tryGenerateNameForSymbol(context, ii); + bcRegs[localID].previousVarIndexPlusOne = localID; + bcRegs[localID].typeGlobalID = getTypeForGlobalSymbol(context, ii); + } + break; + + case kIROp_Var: + { + // As handled in the earlier loop, we are + // allocating *two* locations for each `var` + // instruction. The first of these will be + // the actual pointer value, while the second + // will be the storage for the variable value. + Int localID = regCounter; + regCounter += 2; + + subContext->mapInstToLocalID.Add(ii, localID); + bcRegs[localID].op = ii->op; + bcRegs[localID].name = tryGenerateNameForSymbol(context, ii); + bcRegs[localID].previousVarIndexPlusOne = localID; + bcRegs[localID].typeGlobalID = getTypeForGlobalSymbol(context, ii); + + bcRegs[localID+1].op = ii->op; + bcRegs[localID+1].previousVarIndexPlusOne = localID+1; + bcRegs[localID+1].typeGlobalID = getIDForGlobalSymbol(context, + ((IRPtrType*) ii->getType())->getValueType()); + } + break; + } + } + } + + // Now that we've allocated our blocks and our registers + // we can go through the actual process of emitting instructions. Hooray! + blockCounter = 0; + for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + UInt blockID = blockCounter++; + bcBlocks[blockID].code = getPtr<BCOp>(context); + + + for( auto ii = bb->firstChild; ii; ii = ii->nextInst ) + { + // What we do with each instruction depends a bit on the + // kind of instruction it is. + switch( ii->op ) + { + default: + // For most instructions we just emit their bytecode + // ops directly. + generateBytecodeForInst(subContext, ii); + break; + + case kIROp_Param: + // Don't actually emit code for these, because + // there isn't really anything to *execute* + // + // Note that we *do* allow the `var` nodes + // to be executed, just because they need + // to set up a register with the pointer value. + break; + } + } + } + + // Finally, after emitting all the instructions we can + // build a table of global symbols taht need to be + // imported into the current function as constants. + UInt constCount = subContext->remappedGlobalSymbols.Count(); + auto bcConsts = allocateArray<BCConst>(context, constCount); + + bcFunc->constCount = constCount; + bcFunc->consts = bcConsts; + + for( UInt cc = 0; cc < constCount; ++cc ) + { + bcConsts[cc] = subContext->remappedGlobalSymbols[cc]; + } + + return bcFunc; + } + break; + + default: + // Most instructions don't need a custom representation. + return BytecodeGenerationPtr<BCSymbol>(); + } +} + +BytecodeGenerationPtr<BCModule> generateBytecodeForModule( + BytecodeGenerationContext* context, + IRModule* irModule) +{ + // The module will get encoded much like a function, + // and then that function will be "invoked" to load + // the module. + // + auto bcModule = allocate<BCModule>(context); + bcModule->op = irModule->op; + bcModule->typeGlobalID = 0; + + // The logical function that the module representats + // will only have a single block, so we can allocate it now. + // + auto bcBlock = allocate<BCBlock>(context); + bcBlock->paramCount = 0; + bcBlock->params = BytecodeGenerationPtr<BCReg>(); + + bcModule->blockCount = 1; + bcModule->blocks = bcBlock; + + // Because the module is the top-most level, there is + // no need for it to have "constants" that represent + // values imported from the next outer scope. + // + bcModule->constCount = 0; + bcModule->consts = BytecodeGenerationPtr<BCConst>(); + + // We need to compute how many "registers" to allocate + // for the module, where the registers represent the + // values being computed at the global scope. + UInt regCount = 0; + for( auto inst = irModule->firstChild; inst; inst = inst->nextInst ) + { + if(!opHasResult(inst)) + continue; + + Int globalID = Int(regCount++); + + context->shared->mapGlobalSymbolToGLobalID.Add(inst, globalID); + + // In the global scope, global IDs are also the local IDs + context->mapInstToLocalID.Add(inst, globalID); + } + + auto bcRegs = allocateArray<BCReg>(context, regCount); + + bcModule->regCount = regCount; + bcModule->regs = bcRegs; + + // Now lets walk through and initialize all those bytecode + // register representations so that we can use them. + UInt regCounter= 0; + for( auto inst = irModule->firstChild; inst; inst = inst->nextInst ) + { + if(!opHasResult(inst)) + continue; + + UInt regIndex = *context->mapInstToLocalID.TryGetValue(inst); + + BytecodeGenerationPtr<char> name = tryGenerateNameForSymbol(context, inst); + + bcRegs[regIndex].op = inst->op; + bcRegs[regIndex].name = name; + bcRegs[regIndex].typeGlobalID = getTypeForGlobalSymbol(context, inst); + bcRegs[regIndex].previousVarIndexPlusOne = regIndex; + } + + // Some instructions represent "nested" symbols that will need + // custom handling, and we will represent those here. + List<BytecodeGenerationPtr<BCSymbol>> nestedSymbols; + for( auto inst = irModule->firstChild; inst; inst = inst->nextInst ) + { + UInt regIndex = *context->mapInstToLocalID.TryGetValue(inst); + + auto bcSymbol = generateBytecodeSymbolForInst(context, inst); + if (!bcSymbol.getPtr()) + continue; + + UInt nestedSymbolID = nestedSymbols.Count(); + nestedSymbols.Add(bcSymbol); + + context->mapInstToNestedID.Add(inst, nestedSymbolID); + + bcSymbol->name = bcRegs[regIndex].name; + } + + auto nestedSymbolCount = nestedSymbols.Count(); + auto bcNestedSymbols = allocateArray<BCPtr<BCSymbol>>(context, nestedSymbolCount); + + bcModule->nestedSymbolCount = nestedSymbolCount; + bcModule->nestedSymbols = bcNestedSymbols; + for (UInt ii = 0; ii < nestedSymbolCount; ++ii) + { + bcNestedSymbols[ii] = nestedSymbols[ii]; + } + + + // Finally, we can go through and emit the actual code for + // the initialization step. + bcBlock->code = getPtr<BCOp>(context); + for( auto inst = irModule->firstChild; inst; inst = inst->nextInst ) + { + // Generate bytecode for global-scope inst + generateBytecodeForInst(context, inst); + } + // Need to encode a terminator here, just to keep the encoding valid + encodeUInt(context, kIROp_ReturnVoid); + +#if 0 + + // Now we can go through and generate the bytecode object + // that will represent each of these global symbols + + List<BytecodeGenerationPtr<BCSymbol>> globalSymbols; + + for( auto inst = irModule->firstChild; inst; inst = inst->nextInst ) + { + // Generate bytecode for global-scope inst + auto globalSymbol = generateBytecodeForGlobalSymbol(context, inst); + globalSymbols.Add(globalSymbol); + } +#endif + + return bcModule; +} + +void generateBytecodeStream( + BytecodeGenerationContext* context, + IRModule* irModule) +{ + // Header must be the very first thing in the bytecode stream + BytecodeGenerationPtr<BCHeader> header = allocate<BCHeader>(context); + + memcpy(header->magic, "slang\0bc", sizeof(header->magic)); + header->version = 0; + + // HACK: ensure that a NULL pointer in an operand field can + // be encoded. + context->shared->mapGlobalSymbolToGLobalID.Add(nullptr, -1); + + header->module = generateBytecodeForModule(context, irModule); +} + +List<uint8_t> emitSlangIRForEntryPoint( + EntryPointRequest* entryPoint) +{ + auto compileRequest = entryPoint->compileRequest; + auto irModule = lowerEntryPointToIR( + entryPoint, + compileRequest->layout.Ptr(), + // TODO: we need to pick the target more carefully here + CodeGenTarget::HLSL); + +#if 0 + String irAsm = getSlangIRAssembly(irModule); + fprintf(stderr, "%s\n", irAsm.Buffer()); +#endif + + // Now we need to encode that IR into a binary format + // for transmission/serialization/etc. + + SharedBytecodeGenerationContext sharedContext; + + BytecodeGenerationContext context; + context.shared = &sharedContext; + + generateBytecodeStream(&context, irModule); + + return sharedContext.bytecode; +} + +} // namespace Slang diff --git a/source/slang/bytecode.h b/source/slang/bytecode.h new file mode 100644 index 000000000..e073763cf --- /dev/null +++ b/source/slang/bytecode.h @@ -0,0 +1,187 @@ +// bytecode.h +#ifndef SLANG_BYTECODE_H_INCLUDED +#define SLANG_BYTECODE_H_INCLUDED + +// This file defines a "bytecode" format for storing shader code +// that has been generated via the Slang IR. The bytecode has +// two main goals, that can end up in a bit of conflict: +// +// 1) It is the official serialized form of the Slang IR, and +// so it is of some importance that constructs in the IR be +// able to round-trip through the bytecode. +// +// 2) It should support being directly executed/interpreted, +// so that Slang code can be executed on CPUs when +// performance isn't critical (or when a JIT just isn't +// an option). +// + +#include "../core/basic.h" + +namespace Slang +{ +template<typename T> +struct BytecodeGenerationPtr; + +// A "pointer" stored in a serialized bytecode file, which +// is represented as a byte offset relative to itself. +// +template<typename T> +struct BCPtr +{ + typedef int32_t RawVal; + + RawVal rawVal; + + BCPtr() : rawVal(0) {} + + BCPtr(T* ptr) + : rawVal(0) + { + *this = ptr; + } + + BCPtr(BCPtr<T> const& ptr) + : rawVal(0) + { + *this = ptr.getPtr(); + } + + void operator=(BCPtr<T> const& ptr) + { + *this = ptr.getPtr(); + } + + void operator=(T* ptr) + { + if (ptr) + { + rawVal = (char*)ptr - (char*)this; + } + else + { + rawVal = 0; + } + } + + operator T*() const { return getPtr(); } + + T* getPtr() const + { + if(!rawVal) return nullptr; + return (T*)((char const*)this + rawVal); + } +}; + +struct BCType +{ + uint32_t op; +}; + +struct BCSymbol +{ + // The opcode that was used to define + // this symbol; used to categorize things + uint32_t op; + + // The type of the symbol is represent + // as an index into the global-scope symbol + // list of the module. + // + // Note: This currently precludes having + // a register with a type that is not + // statically determined, but that is + // probably okay. + uint32_t typeGlobalID; + + // The name of this symbol (which might + // be a mangled name at some point, + // so it is really only meant to be + // used for linkage...) + BCPtr<char> name; +}; + +typedef uint8_t BCOp; + +struct BCReg : BCSymbol +{ + // The index of the variable/register + // that should be stored immediately + // preceding this one. + uint32_t previousVarIndexPlusOne; +}; + +struct BCConst +{ + // The ID of the symbol in the global + // scope that we are trying to refer + // to. + // + // TODO: eventually, if we have general + // nesting, then this might be the + // entry in the outer scope that + // is being referenced. + uint32_t globalID; +}; + +struct BCBlock +{ + // The start of the bytecode for this block + BCPtr<BCOp> code; + + // The list of parameters of the block + uint32_t paramCount; + BCPtr<BCReg> params; +}; + +struct BCFunc : BCSymbol +{ + // A list of "registers" used to hold + // intermediate values during execution + // of this function. + uint32_t regCount; + BCPtr<BCReg> regs; + + // The basic blocks of the function + uint32_t blockCount; + BCPtr<BCBlock> blocks; + + // A list of "constants" which are values + // from the global scope that this function + // wants to be able to refer to. We could + // just encode global values directly, + // but this would make the encoding less dense. + uint32_t constCount; + BCPtr<BCConst> consts; + + // Data for "nested" symbols (e.g., a function + // nested inside this function). + uint32_t nestedSymbolCount; + BCPtr<BCPtr<BCSymbol>> nestedSymbols; +}; + +// A module is encoded more or less like a function. +struct BCModule : BCFunc +{ +}; + +struct BCHeader +{ + char magic[8]; + uint32_t version; + + // TODO: probably want a section-based file + // format so that we can add/remove different + // kinds of data without having to revise + // the schema here. + + // The bytecode representation of the module + BCPtr<BCModule> module; +}; + + + +} + + +#endif diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index 25ae460c7..b8830c0b4 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -500,10 +500,7 @@ namespace Slang #endif List<uint8_t> emitSlangIRForEntryPoint( - EntryPointRequest* entryPoint) - { - SLANG_UNIMPLEMENTED_X("Slang IR Binary Generation"); - } + EntryPointRequest* entryPoint); String emitSlangIRAssemblyForEntryPoint( EntryPointRequest* entryPoint); diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp index 25a0b5323..6231b4ca1 100644 --- a/source/slang/emit.cpp +++ b/source/slang/emit.cpp @@ -5287,6 +5287,7 @@ String emitEntryPoint( auto lowered = lowerEntryPointToIR(entryPoint, programLayout, target); + // debugging: // dumpIR(lowered); // TODO: do we want to emit directly from IR, or translate the diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index c48880cab..2c6395903 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -33,6 +33,11 @@ namespace Slang return IROp(kIROp_Invalid); } + IROpInfo getIROpInfo(IROp op) + { + return kIROpInfos[op]; + } + // void IRUse::init(IRValue* u, IRValue* v) @@ -91,13 +96,23 @@ namespace Slang IRParam* IRParam::getNextParam() { + // TODO: this is written as a search because we don't + // currently do the careful thing and emit parameters + // before any other members of a block. + // + // This should change on the emit side, instead. + auto next = nextInst; - if(!next) return nullptr; - if(next->op != kIROp_Param) - return nullptr; + for (;;) + { + if (!next) return nullptr; - return (IRParam*) next; + if(next->op == kIROp_Param) + return (IRParam*) next; + + next = next->nextInst; + } } // @@ -1392,6 +1407,24 @@ namespace Slang // fprintf(context->file, "%llu", (unsigned long long)val); } + static void dump( + IRDumpContext* context, + IntegerLiteralValue val) + { + context->builder->append(val); + +// fprintf(context->file, "%llu", (unsigned long long)val); + } + + static void dump( + IRDumpContext* context, + FloatingPointLiteralValue val) + { + context->builder->append(val); + +// fprintf(context->file, "%llu", (unsigned long long)val); + } + static void dumpIndent( IRDumpContext* context) { @@ -1412,7 +1445,7 @@ namespace Slang else if(inst->id) { dump(context, "%"); - dump(context, inst->id); + dump(context, (UInt) inst->id); } else { @@ -1722,5 +1755,12 @@ namespace Slang return sb; } + void dumpIR(IRModule* module) + { + String ir = getSlangIRAssembly(module); + fprintf(stderr, "%s\n", ir.Buffer()); + fflush(stderr); + } + } diff --git a/source/slang/ir.h b/source/slang/ir.h index 9b271b09b..bc73ffb66 100644 --- a/source/slang/ir.h +++ b/source/slang/ir.h @@ -119,6 +119,9 @@ struct IROpInfo IROpFlags flags; }; +// Look up the info for an op +IROpInfo getIROpInfo(IROp op); + // A use of another value/inst within an IR operation struct IRUse { @@ -323,6 +326,8 @@ struct IRModule : IRParentInst void printSlangIRAssembly(StringBuilder& builder, IRModule* module); String getSlangIRAssembly(IRModule* module); +void dumpIR(IRModule* module); + } diff --git a/source/slang/lower-to-ir.cpp b/source/slang/lower-to-ir.cpp index 5ee6d5460..6f64a2215 100644 --- a/source/slang/lower-to-ir.cpp +++ b/source/slang/lower-to-ir.cpp @@ -1,6 +1,8 @@ // lower.cpp #include "lower-to-ir.h" +#include "../../slang.h" + #include "ir.h" #include "ir-insts.h" #include "type-layout.h" @@ -354,6 +356,73 @@ LoweredValInfo emitCompoundAssignOp( return LoweredValInfo::ptr(leftPtr); } +IRInst* getOneValOfType( + IRGenContext* context, + IRType* type) +{ + switch(type->op) + { + case kIROp_Int32Type: + case kIROp_UInt32Type: + return context->irBuilder->getIntValue(type, 1); + + case kIROp_Float32Type: + return context->irBuilder->getFloatValue(type, 1.0); + + default: + SLANG_UNEXPECTED("inc/dec type"); + return nullptr; + } +} + +LoweredValInfo emitPreOp( + IRGenContext* context, + IRType* type, + IROp op, + UInt argCount, + IRValue* const* args) +{ + auto builder = context->irBuilder; + + assert(argCount == 1); + auto argPtr = args[0]; + + auto preVal = builder->emitLoad(argPtr); + + IRInst* oneVal = getOneValOfType(context, type); + + IRInst* innerArgs[] = { preVal, oneVal }; + auto innerOp = builder->emitIntrinsicInst(type, op, 2, innerArgs); + + builder->emitStore(argPtr, innerOp); + + return LoweredValInfo::simple(preVal); +} + +LoweredValInfo emitPostOp( + IRGenContext* context, + IRType* type, + IROp op, + UInt argCount, + IRValue* const* args) +{ + auto builder = context->irBuilder; + + assert(argCount == 1); + auto argPtr = args[0]; + + auto preVal = builder->emitLoad(argPtr); + + IRInst* oneVal = getOneValOfType(context, type); + + IRInst* innerArgs[] = { preVal, oneVal }; + auto innerOp = builder->emitIntrinsicInst(type, op, 2, innerArgs); + + builder->emitStore(argPtr, innerOp); + + return LoweredValInfo::ptr(argPtr); +} + // Given a `DeclRef` for something callable, along with a bunch of // arguments, emit an appropriate call to it. LoweredValInfo emitCallToDeclRef( @@ -444,6 +513,18 @@ LoweredValInfo emitCallToDeclRef( #undef CASE +#define CASE(COMPOUND, OP) \ + case COMPOUND: return emitPreOp(context, type, OP, argCount, args) + CASE(kIRPseudoOp_PreInc, kIROp_Add); + CASE(kIRPseudoOp_PreDec, kIROp_Sub); +#undef CASE + +#define CASE(COMPOUND, OP) \ + case COMPOUND: return emitPostOp(context, type, OP, argCount, args) + CASE(kIRPseudoOp_PostInc, kIROp_Add); + CASE(kIRPseudoOp_PostDec, kIROp_Sub); +#undef CASE + default: SLANG_UNIMPLEMENTED_X("IR pseudo-op"); break; @@ -1603,6 +1684,65 @@ struct StmtLoweringVisitor : StmtVisitor<StmtLoweringVisitor> insertBlock(breakLabel); } + void visitWhileStmt(WhileStmt* stmt) + { + // Generating IR for `while` statement is similar to a + // `for` statement, but without a lot of the complications. + + auto builder = getBuilder(); + + // We will create blocks for the various places + // we need to jump to inside the control flow, + // including the blocks that will be referenced + // by `continue` or `break` statements. + auto loopHead = createBlock(); + auto bodyLabel = createBlock(); + auto breakLabel = createBlock(); + + // A `continue` inside a `while` loop always + // jumps to the head of hte loop. + auto continueLabel = loopHead; + + // TODO: register appropriate targets for + // break/continue statements. + + // Emit the branch that will start out loop, + // and then insert the block for the head. + + auto loopInst = builder->emitLoop( + loopHead, + breakLabel, + continueLabel); + + addLoopDecorations(loopInst, stmt); + + insertBlock(loopHead); + + // Now that we are within the header block, we + // want to emit the expression for the loop condition: + if (auto condExpr = stmt->Predicate) + { + auto irCondition = getSimpleVal(context, + lowerRValueExpr(context, condExpr)); + + // Now we want to `break` if the loop condition is false. + builder->emitLoopTest( + irCondition, + bodyLabel, + breakLabel); + } + + // Emit the body of the loop + insertBlock(bodyLabel); + lowerStmt(context, stmt->Statement); + + // At the end of the body we need to jump back to the top. + builder->emitBranch(loopHead); + + // Finally we insert the label that a `break` will jump to + insertBlock(breakLabel); + } + void visitExpressionStmt(ExpressionStmt* stmt) { // The statement evaluates an expression @@ -1993,17 +2133,61 @@ struct DeclLoweringVisitor : DeclVisitor<DeclLoweringVisitor, LoweredValInfo> for( auto paramDecl : declForParameters->GetParameters() ) { IRType* irParamType = lowerSimpleType(context, paramDecl->getType()); - paramTypes.Add(irParamType); - IRParam* irParam = subBuilder->emitParam(irParamType); + LoweredValInfo paramVal; - subBuilder->addHighLevelDeclDecoration(irParam, paramDecl); + if (paramDecl->HasModifier<OutModifier>() + || paramDecl->HasModifier<InOutModifier>()) + { + // The parameter is being used for input/output purposes, + // so it will lower to an actual parameter with a pointer type. + // + // TODO: Is this the best representation we can use? - DeclRef<ParamDecl> paramDeclRef = makeDeclRef(paramDecl.Ptr()); + auto irPtrType = subBuilder->getPtrType(irParamType); + paramTypes.Add(irPtrType); + + IRParam* irParamPtr = subBuilder->emitParam(irPtrType); + subBuilder->addHighLevelDeclDecoration(irParamPtr, paramDecl); - LoweredValInfo irParamVal = LoweredValInfo::simple(irParam); + paramVal = LoweredValInfo::ptr(irParamPtr); - subContext->shared->declValues.Add(paramDeclRef, irParamVal); + // TODO: We might want to copy the pointed-to value into + // a temporary at the start of the function, and then copy + // back out at the end, so that we don't have to worry + // about things like aliasing in the function body. + // + // For now we will just use the storage that was passed + // in by the caller, knowing that our current lowering + // at call sites will guarantee a fresh/unique location. + } + else + { + // Simple case of a by-value input parameter. + // But note that HLSL allows an input parameter + // to be used as a local variable inside of a + // function body, so we need to introduce a temporary + // and then copy over to it... + // + // TODO: we could skip this step if we knew + // the parameter was marked `const` or similar. + + paramTypes.Add(irParamType); + + IRParam* irParam = subBuilder->emitParam(irParamType); + subBuilder->addHighLevelDeclDecoration(irParam, paramDecl); + paramVal = LoweredValInfo::simple(irParam); + + auto irLocal = subBuilder->emitVar(irParamType); + auto localVal = LoweredValInfo::ptr(irLocal); + + assign(subContext, localVal, paramVal); + + paramVal = localVal; + } + + DeclRef<ParamDecl> paramDeclRef = makeDeclRef(paramDecl.Ptr()); + subContext->shared->declValues.Add(paramDeclRef, paramVal); } auto irResultType = lowerSimpleType(context, declForReturnType->ReturnType); @@ -2235,4 +2419,5 @@ String emitSlangIRAssemblyForEntryPoint( return getSlangIRAssembly(irModule); } -} + +} // namespace Slang diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis index 6a9f0cbbc..7a7f7fe0e 100644 --- a/source/slang/slang.natvis +++ b/source/slang/slang.natvis @@ -1,14 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> <AutoVisualizer xmlns="http://schemas.microsoft.com/vstudio/debugger/natvis/2010"> - <type Name="Slang::CFGNode"> - <DisplayString>{{CFG Basic Block}}</DisplayString> + + <Type Name="Slang::BCPtr<*>"> + <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 |
