diff options
| author | Tim Foley <tfoleyNV@users.noreply.github.com> | 2018-12-10 12:42:15 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-12-10 12:42:15 -0800 |
| commit | b2997170df7cc2703de714a946a38dc35058e7f8 (patch) | |
| tree | bbd19208ba07a2f45a2c25f28f6cf77be16f0b49 /source | |
| parent | 32f57c30cfce1681f5fe617e4fe230e88eb7b840 (diff) | |
Remove the "VM" and "bytecode" features (#745)
* Remove the "VM" and "bytecode" features
The "bytecode" in `bc.{h,cpp}` was an initial attempt at a serialized encoding for the Slang IR, but we now have the `ir-serialize.{h,cpp}` approach which was has been kept up to date much better.
Similarly, the "VM" in `vm.{h,cpp}` was intended to be a system for interpreting Slang code in the bytecode format directly (so that you could load and evaluate code in a Slang module in a lightweight fashion). This never got used past a single test, which we eventually disabled.
There are good ideas in some of this code, but at this point the implementations have bit-rotted to a point where trying to maintain it is more costly than it would be to re-created it if/when we ever decide these features are important again.
* fixup: remove slang-eval-test from Makefile
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/bytecode.cpp | 1071 | ||||
| -rw-r--r-- | source/slang/bytecode.h | 253 | ||||
| -rw-r--r-- | source/slang/compiler.cpp | 13 | ||||
| -rw-r--r-- | source/slang/ir.cpp | 15 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj | 4 | ||||
| -rw-r--r-- | source/slang/slang.vcxproj.filters | 14 | ||||
| -rw-r--r-- | source/slang/vm.cpp | 1180 | ||||
| -rw-r--r-- | source/slang/vm.h | 19 |
8 files changed, 15 insertions, 2554 deletions
diff --git a/source/slang/bytecode.cpp b/source/slang/bytecode.cpp deleted file mode 100644 index 7ceb06cdc..000000000 --- a/source/slang/bytecode.cpp +++ /dev/null @@ -1,1071 +0,0 @@ -#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 -{ - UInt offset; - SharedBytecodeGenerationContext* sharedContext; - - 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) - {} - - template<typename U> - BytecodeGenerationPtr<U> bitCast() const - { - return BytecodeGenerationPtr<U>(sharedContext, offset); - } - - operator BCPtr<T>() const - { - return BCPtr<T>(getPtr()); - } - - T* operator->() const - { - return getPtr(); - } - - T& operator*() const - { - return *getPtr(); - } - - T& operator[](UInt index) const - { - return getPtr()[index]; - } - - BytecodeGenerationPtr<T> operator+(Int index) const - { - Int delta = index * sizeof(T); - UInt newOffset = offset + delta; - return BytecodeGenerationPtr<T>( - sharedContext, - newOffset); - } - - 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 an IR value to a global entity - // that encodes it: - Dictionary<IRInst*, BCConst> mapValueToGlobal; - - // Types that have been emitted - List<BytecodeGenerationPtr<BCType>> bcTypes; - Dictionary<IRType*, UInt> mapTypeToID; - - // Compile-time constant values that need - // to be emitted... - List<IRInst*> constants; -}; - -struct BytecodeGenerationContext -{ - SharedBytecodeGenerationContext* shared; - - // The bytecode of the current symbol being - // output. - List<uint8_t> currentBytecode; - - // 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; -}; - -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 (BCPtr<void>::RawVal)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->currentBytecode.Add(value); -} - -void encodeUInt( - BytecodeGenerationContext* context, - UInt value) -{ - if( value < 128 ) - { - encodeUInt8(context, (uint8_t)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); -} - -BCConst getGlobalValue( - BytecodeGenerationContext* context, - IRInst* value) -{ - { - BCConst bcConst; - if (context->shared->mapValueToGlobal.TryGetValue(value, bcConst)) - return bcConst; - } - // Next we need to check for things that can be mapped to - // global IDs on the fly. - - switch( value->op ) - { - case kIROp_IntLit: - { - UInt constID = context->shared->constants.Count(); - context->shared->constants.Add(value); - - BCConst bcConst; - bcConst.flavor = kBCConstFlavor_Constant; - bcConst.id = (uint32_t)constID; - - context->shared->mapValueToGlobal.Add(value, bcConst); - - return bcConst; - } - break; - - default: - break; - } - - SLANG_UNEXPECTED("no ID for inst"); - { - UNREACHABLE(BCConst bcConst); - UNREACHABLE(bcConst.flavor = (BCConstFlavor)-1); - UNREACHABLE(bcConst.id = -9999); - UNREACHABLE_RETURN(bcConst); - } -} - -Int getLocalID( - BytecodeGenerationContext* context, - IRInst* value) -{ - Int localID = 0; - if( context->mapInstToLocalID.TryGetValue(value, localID) ) - { - return localID; - } - - BCConst bcConst = getGlobalValue(context, value); - UInt remappedSymbolIndex = context->remappedGlobalSymbols.Count(); - context->remappedGlobalSymbols.Add(bcConst); - - localID = ~remappedSymbolIndex; - context->mapInstToLocalID.Add(value, localID); - return localID; -} - -void encodeOperand( - BytecodeGenerationContext* context, - IRInst* operand) -{ - auto id = getLocalID(context, operand); - encodeSInt(context, id); -} - -uint32_t getTypeID( - BytecodeGenerationContext* context, - IRType* type); - -void encodeOperand( - BytecodeGenerationContext* context, - IRType* type) -{ - encodeUInt(context, getTypeID(context, type)); -} - -bool opHasResult(IRInst* inst) -{ - auto type = inst->getDataType(); - if (!type) return false; - - // As a bit of a hack right now, we need to check whether - // the function returns the distinguished `Void` type, - // since that is conceptually the same as "not returning - // a value." - if(type->op == kIROp_VoidType) - return false; - - return true; -} - -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 operandCount = inst->getOperandCount(); - encodeUInt(context, inst->op); - encodeOperand(context, inst->getDataType()); - encodeUInt(context, operandCount); - for( UInt aa = 0; aa < operandCount; ++aa ) - { - encodeOperand(context, inst->getOperand(aa)); - } - - if (!opHasResult(inst)) - { - // 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->getDataType()); - - // TODO: probably want distinct encodings - // for signed vs. unsigned here. - encodeUInt(context, UInt(ii->value.intVal)); - - // destination: - encodeOperand(context, inst); - } - break; - - case kIROp_FloatLit: - { - auto cInst = (IRConstant*) inst; - encodeUInt(context, cInst->op); - encodeOperand(context, cInst->getDataType()); - - static const UInt size = sizeof(IRFloatingPointValue); - unsigned char buffer[size]; - memcpy(buffer, &cInst->value.floatVal, sizeof(buffer)); - - for(UInt ii = 0; ii < size; ++ii) - { - encodeUInt8(context, buffer[ii]); - } - - // destination: - encodeOperand(context, inst); - } - break; - - case kIROp_BoolLit: - { - auto ii = (IRConstant*) inst; - encodeUInt(context, ii->op); - encodeUInt(context, ii->value.intVal ? 1 : 0); - - // destination: - encodeOperand(context, inst); - } - break; - -#if 0 - 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; -#endif - - case kIROp_Store: - { - encodeUInt(context, inst->op); - - // We need to encode the type being stored, to make - // our lives easier. - encodeOperand(context, inst->getOperand(1)->getDataType()); - encodeOperand(context, inst->getOperand(0)); - encodeOperand(context, inst->getOperand(1)); - } - break; - - case kIROp_Load: - { - encodeUInt(context, inst->op); - encodeOperand(context, inst->getDataType()); - encodeOperand(context, inst->getOperand(0)); - encodeOperand(context, inst); - } - break; - } -} - -BytecodeGenerationPtr<BCType> emitBCType( - BytecodeGenerationContext* context, - IRType* type, - IROp op, - BytecodeGenerationPtr<uint8_t> const* args, - UInt argCount) -{ - UInt size = sizeof(BCType) - + argCount * sizeof(BCPtr<void>); - - BytecodeGenerationPtr<uint8_t> bcAllocation( - context->shared, - allocateRaw(context, size, alignof(BCPtr<void>))); - - BytecodeGenerationPtr<BCType> bcType = bcAllocation.bitCast<BCType>(); - auto bcArgs = (bcType + 1).bitCast<BCPtr<uint8_t>>(); - - bcType->op = op; - bcType->argCount = (uint32_t)argCount; - - for(UInt aa = 0; aa < argCount; ++aa) - { - bcArgs[aa] = args[aa]; - } - - UInt id = context->shared->bcTypes.Count(); - context->shared->mapTypeToID.Add(type, id); - context->shared->bcTypes.Add(bcType); - bcType->id = (uint32_t)id; - - return bcType; -} - -BytecodeGenerationPtr<BCType> emitBCVarArgType( - BytecodeGenerationContext* context, - IRType* type, - IROp op, - List<BytecodeGenerationPtr<uint8_t>> args) -{ - return emitBCType(context, type, op, args.Buffer(), args.Count()); -} - -BytecodeGenerationPtr<BCType> emitBCType( - BytecodeGenerationContext* context, - IRType* type, - IROp op) -{ - return emitBCType(context, type, op, nullptr, 0); -} - -BytecodeGenerationPtr<BCType> emitBCType( - BytecodeGenerationContext* context, - IRType* type); - -// Emit a `BCType` representation for the given `Type` -BytecodeGenerationPtr<BCType> emitBCTypeImpl( - BytecodeGenerationContext* context, - IRType* type) -{ - // A NULL type is interpreted as equivalent to `Void` for now. - if( !type ) - { - return emitBCType(context, type, kIROp_VoidType); - } - - List<BytecodeGenerationPtr<uint8_t>> operands; - UInt operandCount = type->getOperandCount(); - for (UInt ii = 0; ii < operandCount; ++ii) - { - operands.Add(emitBCType(context, (IRType*) type->getOperand(ii)).bitCast<uint8_t>()); - } - return emitBCVarArgType(context, type, type->op, operands); -} - -BytecodeGenerationPtr<BCType> emitBCType( - BytecodeGenerationContext* context, - IRType* type) -{ - auto canonical = type->getCanonicalType(); - UInt id = 0; - if(context->shared->mapTypeToID.TryGetValue(canonical, id)) - { - return context->shared->bcTypes[id]; - } - - BytecodeGenerationPtr<BCType> bcType = emitBCTypeImpl(context, canonical); - return bcType; -} - -uint32_t getTypeID( - BytecodeGenerationContext* context, - IRType* type) -{ - // We have a type, and we need to emit it (if we haven't - // already) and return its index in the global type table. - BytecodeGenerationPtr<BCType> bcType = emitBCType(context, type); - return bcType->id; -} - -uint32_t getTypeIDForGlobalSymbol( - BytecodeGenerationContext* context, - IRInst* inst) -{ - auto type = inst->getDataType(); - if(!type) - return 0; - - return getTypeID(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, - IRGlobalValue* 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<ParameterGroupReflectionName>()) - { - return allocateString(context, reflectionNameMod->name); - } - else if(auto name = decl->nameAndLoc.name) - { - return allocateString(context, name); - } - } - - return BytecodeGenerationPtr<char>(); -} - -// Generate a `BCSymbol` that can represent a global value. -BytecodeGenerationPtr<BCSymbol> generateBytecodeSymbolForInst( - BytecodeGenerationContext* context, - IRGlobalValue* inst) -{ - switch( inst->op ) - { - case kIROp_Func: - { - auto irFunc = (IRFunc*) inst; - BytecodeGenerationPtr<BCFunc> bcFunc = allocate<BCFunc>(context); - - bcFunc->op = inst->op; - bcFunc->typeID = getTypeIDForGlobalSymbol(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 = (uint32_t)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->getFirstInst(); ii; ii = ii->getNextInst() ) - { - 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 = (uint32_t)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 = (uint32_t)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 the instruction in the block, to allocate registers - // for them. The parameters of a block will always be the first - // N instructions in the block, so they will always get the - // first N registers in that block. Similarly, the entry block - // is always the first block, so that the parameters of the function - // will always be the first N registers. - // - bcBlocks[blockID].params = bcRegs + regCounter; - for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) - { - switch(ii->op) - { - default: - // For a parameter, or 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; -#if 0 - bcRegs[localID].name = tryGenerateNameForSymbol(context, ii); -#endif - bcRegs[localID].previousVarIndexPlusOne = (uint32_t)localID; - bcRegs[localID].typeID = getTypeIDForGlobalSymbol(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; -#if 0 - bcRegs[localID].name = tryGenerateNameForSymbol(context, ii); -#endif - bcRegs[localID].previousVarIndexPlusOne = (uint32_t)localID; - bcRegs[localID].typeID = getTypeIDForGlobalSymbol(context, ii); - - bcRegs[localID+1].op = ii->op; - bcRegs[localID+1].previousVarIndexPlusOne = (uint32_t)localID+1; - bcRegs[localID+1].typeID = getTypeID(context, - (as<IRPtrType>(ii->getDataType()))->getValueType()); - } - break; - } - } - } - assert((UInt)regCounter == regCount); - - // Now that we've allocated our blocks and our registers - // we can go through the actual process of emitting instructions. Hooray! - blockCounter = 0; - - // Offset of each basic block from the start of the code - // for the current funciton. - List<UInt> blockOffsets; - for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() ) - { - blockCounter++; - - // Get local bytecode offset for current block. - UInt blockOffset = subContext->currentBytecode.Count(); - blockOffsets.Add( blockOffset ); - - for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) - { - // 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; - } - } - } - - - // We've collected bytecode for the instruction stream - // into a sub-context, so we can now append that code. - UInt byteCount = subContext->currentBytecode.Count(); - BytecodeGenerationPtr<uint8_t> bytes = allocateArray<uint8_t>(context, byteCount); - memcpy(&bytes[0], subContext->currentBytecode.Buffer(), byteCount); - - // Now that we've allocated the storage, we can write - // the bytecode pointers into the blocks. - blockCounter = 0; - for( auto bb = irFunc->getFirstBlock(); bb; bb = bb->getNextBlock() ) - { - UInt blockID = blockCounter++; - bcBlocks[blockID].code = bytes + blockOffsets[blockID]; - } - - // 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 = (uint32_t)constCount; - bcFunc->consts = bcConsts; - - for( UInt cc = 0; cc < constCount; ++cc ) - { - bcConsts[cc] = subContext->remappedGlobalSymbols[cc]; - } - - return bcFunc; - } - break; - - case kIROp_GlobalVar: - case kIROp_GlobalConstant: - { - auto bcVar = allocate<BCSymbol>(context); - - bcVar->op = inst->op; - bcVar->typeID = getTypeID(context, inst->getFullType()); - - // TODO: actually need to intialize with body instructions - - return bcVar; - } - break; - - default: - // Most instructions don't need a custom representation. - return BytecodeGenerationPtr<BCSymbol>(); - } -} - -BytecodeGenerationPtr<BCModule> generateBytecodeForModule( - BytecodeGenerationContext* context, - IRModule* irModule) -{ - if (!irModule) - { - // Not IR module? Then return a null pointer. - return BytecodeGenerationPtr<BCModule>(); - } - - // A module in the bytecode is mostly just a list of the - // global symbols in the module. - // - // TODO: we need to be careful and recognize the distinction - // between the global symbols in the *AST* module, vs. those - // symbols which are effectively global in the *IR* module. - // - // We probably need to store these distinctly, since we - // need the AST global symbols for reflection, and then - // also to reconstruct the AST on load when importing a - // serialized module. We then need the global IR symbols - // to use when linking, to quickly resolve things without - // needing any semantic knowledge of nesting at the AST level. - // - auto bcModule = allocate<BCModule>(context); - - // 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 symbolCount = 0; - for(auto ii : irModule->getGlobalInsts()) - { - auto gv = as<IRGlobalValue>(ii); - if (!gv) - continue; - - Int globalID = Int(symbolCount++); - - // Ensure that local code inside functions can see these symbols - BCConst bcConst; - bcConst.flavor = kBCConstFlavor_GlobalSymbol; - bcConst.id = (uint32_t)globalID; - context->shared->mapValueToGlobal.Add(gv, bcConst); - - // In the global scope, global IDs are also the local IDs - context->mapInstToLocalID.Add(gv, globalID); - } - - auto bcSymbols = allocateArray<BCPtr<BCSymbol>>(context, symbolCount); - - bcModule->symbolCount = (uint32_t)symbolCount; - bcModule->symbols = bcSymbols; - - for(auto ii : irModule->getGlobalInsts()) - { - auto gv = as<IRGlobalValue>(ii); - if (!gv) - continue; - - UInt symbolIndex = *context->mapInstToLocalID.TryGetValue(gv); - - auto bcSymbol = generateBytecodeSymbolForInst(context, gv); - if (!bcSymbol.getPtr()) - continue; - - auto name = tryGenerateNameForSymbol(context, gv); - bcSymbol->name = name; - - bcSymbols[symbolIndex] = bcSymbol; - } - - // At this point we should have identified all the literals we need: - UInt constantCount = context->shared->constants.Count(); - auto bcConstants = allocateArray<BCConstant>(context, constantCount); - bcModule->constantCount = (uint32_t)constantCount; - bcModule->constants = bcConstants; - - for(UInt cc = 0; cc < constantCount; ++cc) - { - auto irConstant = (IRConstant*) context->shared->constants[cc]; - bcConstants[cc].op = irConstant->op; - bcConstants[cc].typeID = getTypeID(context, irConstant->getFullType()); - - switch(irConstant->op) - { - case kIROp_IntLit: - { - auto ptr = allocate<IRIntegerValue>(context); - *ptr = irConstant->value.intVal; - bcConstants[cc].ptr = ptr.bitCast<uint8_t>(); - } - break; - - default: - break; - } - - } - - // At this point we should have collected all the types we need: - UInt typeCount = context->shared->bcTypes.Count(); - auto bcTypes = allocateArray<BCPtr<BCType>>(context, typeCount); - bcModule->typeCount = (uint32_t)typeCount; - bcModule->types = bcTypes; - - for(UInt tt = 0; tt < typeCount; ++tt) - { - bcTypes[tt] = context->shared->bcTypes[tt]; - } - - - return bcModule; -} - -void generateBytecodeContainer( - BytecodeGenerationContext* context, - CompileRequest* compileReq) -{ - // 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; - - // TODO: Need to generate BC representation of all the public/exported - // declrations in the translation units, so that they can be used to - // resolve depenencies downstream. - - // TODO: Need to dump BC representation of compiled kernel codes - // for each specified code-generation target. - - List<BytecodeGenerationPtr<BCModule>> bcModulesList; - for (auto translationUnitReq : compileReq->translationUnits) - { - auto bcModule = generateBytecodeForModule(context, translationUnitReq->irModule); - bcModulesList.Add(bcModule); - } - - UInt bcModuleCount = bcModulesList.Count(); - header->moduleCount = (uint32_t)bcModuleCount; - - auto bcModules = allocateArray<BCPtr<BCModule>>(context, bcModuleCount); - header->modules = bcModules; - for(UInt ii = 0; ii < bcModuleCount; ++ii) - { - bcModules[ii] = bcModulesList[ii]; - } -} - -void generateBytecodeForCompileRequest( - CompileRequest* compileReq) -{ - SharedBytecodeGenerationContext sharedContext; - - BytecodeGenerationContext context; - context.shared = &sharedContext; - - generateBytecodeContainer(&context, compileReq); - - compileReq->generatedBytecode = sharedContext.bytecode; -} - -// TODO: Need to support IR emit at the whole-module/compile-request -// level, and not just for individual entry points. -#if 0 -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; -} -#endif - -} // namespace Slang diff --git a/source/slang/bytecode.h b/source/slang/bytecode.h deleted file mode 100644 index f1ad52c32..000000000 --- a/source/slang/bytecode.h +++ /dev/null @@ -1,253 +0,0 @@ -// 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 = (RawVal)((char*)ptr - (char*)this); - } - else - { - rawVal = 0; - } - } - - operator T*() const { return getPtr(); } - T* operator->() const { return getPtr(); } - - T* getPtr() const - { - if(!rawVal) return nullptr; - return (T*)((char const*)this + rawVal); - } -}; - -// Representation of a "type-level" value in -// the bytecode fiel. This corresponds to -// the AST-level notion of a `Val` -struct BCVal -{ - // The opcode used to define this value - uint32_t op; - - // The ID of the type within its module - uint32_t id; -}; - -struct BCType : BCVal -{ - // TODO: avoid having to encode this? - uint32_t argCount; - - // type-specific operands follow - - // - - BCPtr<BCVal>* getArgs() { return (BCPtr<BCVal>*) (this +1); } - - BCVal* getArg(UInt index) { return getArgs()[index]; } -}; - -struct BCPtrType : BCType -{ - BCPtr<BCType> valueType; -}; - -struct BCFuncType : BCType -{ - BCPtr<BCType> resultType; - BCPtr<BCType> paramTypes[1]; - - BCType* getResultType() { return resultType; } - - UInt getParamCount() { return argCount - 1; } - BCType* getParamType(UInt index) { return paramTypes[index]; } -}; - -struct BCConstant : BCVal -{ - uint32_t typeID; - BCPtr<uint8_t> ptr; -}; - -struct BCSymbol -{ - // The opcode that was used to define - // this symbol; used to categorize things - uint32_t op; - - // The index (in the module's type table) - // of the type of the symbol: - uint32_t typeID; - - // 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; -}; - -enum BCConstFlavor -{ - kBCConstFlavor_GlobalSymbol, - kBCConstFlavor_Constant, -}; - -struct BCConst -{ - // The flavor of bytecode constant we - // are dealing with. - uint32_t flavor; - uint32_t id; -}; - -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; -}; - -struct BCModule -{ - // The symbols (functions, global variables, etc.) - // that have been declared in the module. - uint32_t symbolCount; - BCPtr<BCPtr<BCSymbol>> symbols; - - // The types that are used by this module, stored - // in a single array so that they can be conveniently - // mapped to another representation in one go. - // - // Instructions in a bytecode instruction sequence - // might reference these types by index. - uint32_t typeCount; - BCPtr<BCPtr<BCType>> types; - - // True compile-time constants go here: - uint32_t constantCount; - BCPtr<BCConstant> constants; -}; - -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. - - // TODO: should include AST declaration structure - // here, which can be used for refleciton, and - // also loaded to resolve dependencies when - // compiling other modules. - - // TODO: Include the original entry point requests? - - // Zero or more IR modules, corresponding to - // the translation units of the original compile - // request. - uint32_t moduleCount; - BCPtr<BCPtr<BCModule>> modules; - - // TODO: should enumerate targets here, and - // include reflection layout info + compiled - // entry points for each target. -}; - -class CompileRequest; -void generateBytecodeForCompileRequest( - CompileRequest* compileReq); - -} - - -#endif diff --git a/source/slang/compiler.cpp b/source/slang/compiler.cpp index 37e9827f8..0f333aa3d 100644 --- a/source/slang/compiler.cpp +++ b/source/slang/compiler.cpp @@ -5,7 +5,6 @@ #include "../core/slang-io.h" #include "../core/slang-string-util.h" -#include "bytecode.h" #include "compiler.h" #include "lexer.h" #include "lower-to-ir.h" @@ -1079,18 +1078,6 @@ SlangResult dissassembleDXILUsingDXC( generateOutputForTarget(targetReq); } - // If we are being asked to generate code in a container - // format, then we are now in a position to do so. - switch (compileRequest->containerFormat) - { - default: - break; - - case ContainerFormat::SlangModule: - generateBytecodeForCompileRequest(compileRequest); - break; - } - // If we are in command-line mode, we might be expected to actually // write output to one or more files here. diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp index 2f16f4ebc..c0715fca3 100644 --- a/source/slang/ir.cpp +++ b/source/slang/ir.cpp @@ -2617,7 +2617,20 @@ namespace Slang } } - bool opHasResult(IRInst* inst); + bool opHasResult(IRInst* inst) + { + auto type = inst->getDataType(); + if (!type) return false; + + // As a bit of a hack right now, we need to check whether + // the function returns the distinguished `Void` type, + // since that is conceptually the same as "not returning + // a value." + if(type->op == kIROp_VoidType) + return false; + + return true; + } bool instHasUses(IRInst* inst) { diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj index 6bcaac3da..c502780df 100644 --- a/source/slang/slang.vcxproj +++ b/source/slang/slang.vcxproj @@ -171,7 +171,6 @@ </ItemDefinitionGroup> <ItemGroup> <ClInclude Include="..\..\slang.h" /> - <ClInclude Include="bytecode.h" /> <ClInclude Include="compiler.h" /> <ClInclude Include="core.meta.slang.h" /> <ClInclude Include="decl-defs.h" /> @@ -222,10 +221,8 @@ <ClInclude Include="type-system-shared.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" /> @@ -262,7 +259,6 @@ <ClCompile Include="token.cpp" /> <ClCompile Include="type-layout.cpp" /> <ClCompile Include="type-system-shared.cpp" /> - <ClCompile Include="vm.cpp" /> </ItemGroup> <ItemGroup> <None Include="slang.natvis" /> diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters index ce76ec77e..dc5630504 100644 --- a/source/slang/slang.vcxproj.filters +++ b/source/slang/slang.vcxproj.filters @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="utf-8"?> +<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <ItemGroup> <Filter Include="Header Files"> @@ -12,9 +12,6 @@ <ClInclude Include="..\..\slang.h"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="bytecode.h"> - <Filter>Header Files</Filter> - </ClInclude> <ClInclude Include="compiler.h"> <Filter>Header Files</Filter> </ClInclude> @@ -165,14 +162,8 @@ <ClInclude Include="visitor.h"> <Filter>Header Files</Filter> </ClInclude> - <ClInclude Include="vm.h"> - <Filter>Header Files</Filter> - </ClInclude> </ItemGroup> <ItemGroup> - <ClCompile Include="bytecode.cpp"> - <Filter>Source Files</Filter> - </ClCompile> <ClCompile Include="check.cpp"> <Filter>Source Files</Filter> </ClCompile> @@ -281,9 +272,6 @@ <ClCompile Include="type-system-shared.cpp"> <Filter>Source Files</Filter> </ClCompile> - <ClCompile Include="vm.cpp"> - <Filter>Source Files</Filter> - </ClCompile> </ItemGroup> <ItemGroup> <None Include="slang.natvis"> diff --git a/source/slang/vm.cpp b/source/slang/vm.cpp deleted file mode 100644 index 0f79c763b..000000000 --- a/source/slang/vm.cpp +++ /dev/null @@ -1,1180 +0,0 @@ -#include "vm.h" - -// Implementation of the Slang bytecode VM -// - -#include "bytecode.h" -#include "ir.h" - -#include "../../slang.h" - -namespace Slang -{ - -struct VMValImpl -{ - // opcode used to construct the value - uint32_t op; -}; - -// Representation of a type during VM execution -struct VMTypeImpl : VMValImpl -{ - // number of arguments to the type - uint32_t argCount; - - // Size and alignment of instances of this - // type. - UInt size; - UInt alignment; - - // operands follow -}; - -struct VMVal -{ - VMValImpl* impl; - - VMValImpl* getImpl() { return impl; } -}; - -struct VMType : VMVal -{ - VMTypeImpl* getImpl() { return (VMTypeImpl*) impl; } - UInt getSize() { return getImpl()->size; } - UInt getAlignment() { return getImpl()->alignment; } -}; - -struct VMPtrTypeImpl : VMTypeImpl -{ - VMType base; -}; - -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 VMModule; - -// Information about a function after it has been -// loaded into the VM. -struct VMFunc -{ - // The parent module that this function belongs to - VMModule* 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 VM; - -struct VMModule -{ - BCModule* bcModule; - VM* vm; - void** symbols; - VMType* types; -}; - -UInt decodeUInt(BCOp** ioPtr) -{ - BCOp* ptr = *ioPtr; - - UInt value = *ptr++; - if( value < 128 ) - { - *ioPtr = ptr; - return value; - } - - // Slower path for variable-length encoding - - UInt result = 0; - for(;;) - { - value = value & 0x7F; - result = (result << 7) | value; - - if(value < 127) - { - *ioPtr = ptr; - return value; - } - - value = *ptr++; - } -} - -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); -} - -VMType decodeType(VMFrame* frame, BCOp** ioIP) -{ - UInt id = decodeUInt(ioIP); - return frame->func->module->types[id]; -} - -VMFunc* loadVMFunc( - BCFunc* bcFunc, - VMModule* vmModule); - -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_TypeKind: - break; - - case kIROp_Func: - { - BCFunc* func = (BCFunc*) symbol; - result.size = sizeof(VMFunc) - + func->regCount * sizeof(VMReg) - + func->constCount * sizeof(VMConst); - } - break; - } - - return result; -} - -VMType getType( - VMModule* vmModule, - uint32_t typeID) -{ - return vmModule->types[typeID]; -} - -void* getGlobalPtr( - VMModule* vmModule, - uint32_t globalID) -{ - return vmModule->symbols[globalID]; -} - -VMType getGlobalType( - VMModule* vmModule, - uint32_t globalID) -{ - return getType(vmModule, vmModule->bcModule->symbols[globalID]->typeID); -} - -VMFunc* loadVMFunc( - BCFunc* bcFunc, - VMModule* vmModule) -{ - 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 = vmModule; - 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 bcTypeID = bcReg->typeID; - - // We expect the type to come from the outer module, so - // that we can allocate space for it as we go. - auto vmType = getType(vmModule, bcTypeID); - - auto regSize = vmType.getSize(); - auto 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 ) - { - BCConst bcConst = bcFunc->consts[cc]; - switch( bcConst.flavor ) - { - case kBCConstFlavor_GlobalSymbol: - { - auto globalID = bcConst.id; - vmFunc->consts[cc].ptr = &vmModule->symbols[globalID]; - vmFunc->consts[cc].type = getGlobalType(vmModule, globalID); - } - break; - - case kBCConstFlavor_Constant: - { - auto constID = bcConst.id; - auto constInfo = &vmModule->bcModule->constants[constID]; - vmFunc->consts[cc].ptr = constInfo->ptr; - #if 0 - fprintf(stderr, "CONSANT[%d] : [%p]\n", (int)cc, vmFunc->consts[cc].ptr); - fprintf(stderr, "BC [%p] : %d\n", &constInfo->ptr, (int)constInfo->ptr.rawVal); - #endif - vmFunc->consts[cc].type = getType(vmModule, constInfo->typeID); - } - break; - } - - - } - - 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 ", (unsigned int) rr); - if (name) - { - fprintf(stderr, "\"%s\" ", name); - } - if (regType.impl) - { - switch (regType.impl->op) - { - case kIROp_TypeKind: - // TODO: we could recursively go and print types... - fprintf(stderr, ": Type = ???"); - break; - - case kIROp_HLSLRWStructuredBufferType: - fprintf(stderr, ": RWStructuredBuffer<??\?> = ???"); - break; - - case kIROp_HLSLStructuredBufferType: - fprintf(stderr, ": StructuredBuffer<??\?> = ???"); - break; - - case kIROp_BoolType: - fprintf(stderr, ": Bool = %s", *(bool*)regData ? "true" : "false"); - break; - - case kIROp_IntType: - fprintf(stderr, ": Int32 = %d", *(int32_t*)regData); - break; - - case kIROp_UIntType: - fprintf(stderr, ": UInt32 = %u", *(uint32_t*)regData); - break; - - case kIROp_PtrType: - { - fprintf(stderr, ": Ptr<?> = [%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); - -void computeTypeSizeAlign( - VMTypeImpl* impl) -{ - UInt size = 0; - UInt alignment = 0; - switch(impl->op) - { - case kIROp_VoidType: - size = 0; - break; - - case kIROp_BoolType: - size = 1; - break; - - case kIROp_IntType: - case kIROp_UIntType: - case kIROp_FloatType: - size = 4; - break; - - case kIROp_FuncType: - case kIROp_PtrType: - case kIROp_HLSLRWStructuredBufferType: - case kIROp_HLSLStructuredBufferType: - size = sizeof(void*); - break; - - default: - SLANG_UNIMPLEMENTED_X("type sizing"); - UNREACHABLE(impl->size = 0); - break; - } - - if(!alignment) - alignment = size; - if(!alignment) - alignment = 1; - - impl->size = size; - impl->alignment = alignment; -} - -VMType getType( - VM* /*vm*/, - VMTypeImpl* typeImpl) -{ - // TODO: need to look up an existing type that matches... - - UInt argCount = typeImpl->argCount; - UInt size = sizeof(VMTypeImpl) + argCount*sizeof(VMType); - - VMTypeImpl* impl = (VMTypeImpl*) malloc(size); - memcpy(impl, typeImpl, size); - - computeTypeSizeAlign(impl); - - VMType type; - type.impl = impl; - return type; -} - -VMVal getVal( - VMModule* vmModule, - UInt index) -{ - return vmModule->types[index]; -} - -VMType loadVMType( - VMModule* vmModule, - BCType* bcType) -{ - // Need to load type from BC format to VM - IROp op = (IROp) bcType->op; - switch(bcType->op) - { - case kIROp_PtrType: - { - // TODO: need to do some caching! - BCPtrType* bcPtrType = (BCPtrType*) bcType; - - VMPtrTypeImpl vmPtrTypeImpl; - vmPtrTypeImpl.op = op; - vmPtrTypeImpl.argCount = 1; - vmPtrTypeImpl.size = sizeof(void*); - vmPtrTypeImpl.alignment = sizeof(void*); - vmPtrTypeImpl.base = getType(vmModule, bcPtrType->valueType->id); - - auto vmPtrType = getType(vmModule->vm, &vmPtrTypeImpl); - return vmPtrType; - } - break; - - default: - { - UInt argCount = bcType->argCount; - - UInt size = sizeof(VMTypeImpl) + argCount * sizeof(VMVal); - - VMTypeImpl* impl = (VMTypeImpl*) alloca(size); - memset(impl, 0, size); - impl->op = bcType->op; - impl->argCount = (uint32_t)argCount; - - VMVal* args = (VMVal*) (impl + 1); - for(UInt aa = 0; aa < argCount; ++aa) - { - args[aa] = getVal(vmModule, bcType->getArg(aa)->id); - } - - return getType(vmModule->vm, impl); - } - - UNREACHABLE(SLANG_UNEXPECTED("unimplemented")); - UNREACHABLE_RETURN(VMType()); - break; - } -} - -void* allocateImpl(VM* /*vm*/, UInt size, UInt /*align*/) -{ - void* ptr = malloc(size); - memset(ptr, 0, size); - return ptr; -} - -void* allocate(VM* vm, VMType type) -{ - return allocateImpl(vm, type.getSize(), type.getAlignment()); -} - -template<typename T> -T* allocate(VM* vm) -{ - return allocateImpl(vm, sizeof(T), alignof(T)); -} - -void* loadVMSymbol( - VMModule* vmModule, - BCSymbol* bcSymbol) -{ - // Need to load type from BC format to VM - - auto vm = vmModule->vm; - - switch(bcSymbol->op) - { - case kIROp_GlobalVar: - { - auto type = getType(vmModule, bcSymbol->typeID); - assert(type.impl->op == kIROp_PtrType); - - VMPtrTypeImpl* ptrTypeImpl = (VMPtrTypeImpl*) type.impl; - auto valueType = ptrTypeImpl->base; - - void* varValue = allocate(vm, valueType); - void** varPtr = (void**) allocate(vm, type); - - - *varPtr = varValue; - - return varPtr; - } - break; - - case kIROp_GlobalConstant: - { - auto type = getType(vmModule, bcSymbol->typeID); - void* valPtr = allocate(vm, type); - return valPtr; - } - break; - - case kIROp_Func: - { - auto bcFunc = (BCFunc*) bcSymbol; - VMFunc* vmFunc = loadVMFunc(bcFunc, vmModule); - return vmFunc; - } - break; - - default: - return nullptr; - } -} - -VMModule* loadVMModuleInstance( - VM* vm, - void const* bytecode, - size_t /*bytecodeSize*/) -{ - BCHeader* bcHeader = (BCHeader*) bytecode; - - UInt bcModuleCount = bcHeader->moduleCount; - if (bcModuleCount == 0) - return nullptr; - - BCModule* bcModule = bcHeader->modules[0]; - - UInt symbolCount = bcModule->symbolCount; - UInt typeCount = bcModule->typeCount; - - UInt vmModuleSize = sizeof(VMModule) - + symbolCount * sizeof(void*) - + typeCount * sizeof(VMType); - - VMModule* vmModule = (VMModule*)malloc(vmModuleSize); - memset(vmModule, 0, vmModuleSize); - - void** vmSymbols = (void**)(vmModule + 1); - VMType* vmTypes = (VMType*)(vmSymbols + symbolCount); - - vmModule->bcModule = bcModule; - vmModule->vm = vm; - vmModule->symbols = vmSymbols; - vmModule->types = vmTypes; - - // Initialize types before symbols, since the symbols - // will all have types... - for(UInt tt = 0; tt < typeCount; ++tt) - { - BCType* bcType = bcModule->types[tt]; - vmTypes[tt] = loadVMType(vmModule, bcType); - } - - // Now we need to initialize all the VM-level symbols - // from their BC-level equivalents. - for(UInt ss = 0; ss < symbolCount; ++ss) - { - BCSymbol* bcSymbol = bcModule->symbols[ss]; - vmSymbols[ss] = loadVMSymbol(vmModule, bcSymbol); - } - - return vmModule; -} - -void* findGlobalSymbolPtr( - VMModule* module, - char const* name) -{ - // Okay, we need to search through the available - // symbols, looking for one that gives us a name - // match. - - BCModule* bcModule = module->bcModule; - UInt symbolCount = bcModule->symbolCount; - for(UInt ss = 0; ss < symbolCount; ++ss) - { - BCSymbol* bcSymbol = bcModule->symbols[ss]; - - char const* symbolName = bcSymbol->name; - if (!symbolName) - continue; - - if(strcmp(symbolName, name) == 0) - return getGlobalPtr(module, (uint32_t)ss); - } - - 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_Var: - { - // This instruction represents the `alloca` for a variable of some type. - - VMType type = decodeType(frame, &ip); - 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 = decodeType(frame, &ip); - void* dest = decodeOperand<void*>(frame, &ip); - void* src = decodeOperandPtr<void>(frame, &ip); - -#if 0 - fprintf(stderr, "STORE *[%p] = [%p] // size: %d\n", - dest, - src, - (int) type.getSize()); -#endif - - memcpy(dest, src, type.getSize()); - } - break; - - case kIROp_Load: - { - // An ordinary memory store - VMType type = decodeType(frame, &ip); - void* src = decodeOperand<void*>(frame, &ip); - void* dest = decodeOperandPtr<void>(frame, &ip); - - memcpy(dest, src, type.getSize()); - } - break; - - case kIROp_Call: - { - VMType type = decodeType(frame, &ip); - UInt operandCount = decodeUInt(&ip); - - // First operand is the callee function - 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 - 1; - 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: - { - VMType instType = decodeType(frame, &ip); - /*UInt argCount =*/ decodeUInt(&ip); - void* argPtr = decodeOperandPtr<void>(frame, &ip); - - 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: - { - // For now our encoding is very regular, so we can decode without - // knowing too much about an instruction... - - VMType type = decodeType(frame, &ip); - UInt argCount = decodeUInt(&ip); - Int destinationBlockIndex = decodeSInt(&ip); - - auto func = frame->func; - auto& destinationBlock = func->bcFunc->blocks[destinationBlockIndex]; - - // We may be passing arguments through to the destination - // block, so any remaining operands of the branch instruction - // need to be used to fill in the parameter registers of - // the destination block. - - UInt paramCount = destinationBlock.paramCount; - - // There might be additional operands, because some branches - // also include information on merge points and break/continue labels. - UInt remainingArgCount = argCount - 1; - assert(remainingArgCount >= paramCount); - - UInt extraArgCount = remainingArgCount - paramCount; - - for (UInt ee = 0; ee < extraArgCount; ++ee) - { - decodeOperandPtr<void>(frame, &ip); - } - - auto baseRegIndex = destinationBlock.params - func->bcFunc->regs; - for( UInt pp = 0; pp < paramCount; ++pp ) - { - auto regIndex = baseRegIndex + pp; - - void* argPtr = decodeOperandPtr<void>(frame, &ip); - void* regPtr = getRegPtrImpl(frame, regIndex); - - VMType regType = func->regs[regIndex].type; - memcpy(regPtr, argPtr, regType.getSize()); - } - - // Now simply jump to the destination block. - // - ip = destinationBlock.code; - } - break; - - case kIROp_ifElse: - case kIROp_conditionalBranch: - { - // For now our encoding is very regular, so we can decode without - // knowing too much about an instruction... - - VMType type = decodeType(frame, &ip); - UInt argCount = decodeUInt(&ip); - bool* condition = decodeOperandPtr<bool>(frame, &ip); - Int trueBlockID = decodeSInt(&ip); - Int falseBlockID = decodeSInt(&ip); - for( UInt aa = 4; aa < argCount; ++aa ) - { - 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... - - VMType resultType = decodeType(frame, &ip); - /*UInt argCount = */decodeUInt(&ip); - //void* argPtrs[16] = { 0 }; - 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_IntType: - *destPtr = *(int32_t*)leftPtr > *(int32_t*)rightPtr; - break; - - default: - SLANG_UNEXPECTED("comparison op case"); - break; - } - } - break; - - case kIROp_Mul: - { - VMType type = decodeType(frame, &ip); - /*UInt argCount = */decodeUInt(&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_IntType: - *(int32_t*)destPtr = *(int32_t*)leftPtr * *(int32_t*)rightPtr; - break; - - default: - SLANG_UNEXPECTED("comparison op case"); - break; - } - } - break; - - case kIROp_Sub: - { - VMType type = decodeType(frame, &ip); - /*UInt argCount = */decodeUInt(&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_IntType: - *(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... - - VMType type = decodeType(frame, &ip); - 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::VMModule*) 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 deleted file mode 100644 index e67fec43a..000000000 --- a/source/slang/vm.h +++ /dev/null @@ -1,19 +0,0 @@ -// 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 |
