| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427 |
- // zlib open source license
- //
- // Copyright (c) 2019 David Forsgren Piuva
- //
- // This software is provided 'as-is', without any express or implied
- // warranty. In no event will the authors be held liable for any damages
- // arising from the use of this software.
- //
- // Permission is granted to anyone to use this software for any purpose,
- // including commercial applications, and to alter it and redistribute it
- // freely, subject to the following restrictions:
- //
- // 1. The origin of this software must not be misrepresented; you must not
- // claim that you wrote the original software. If you use this software
- // in a product, an acknowledgment in the product documentation would be
- // appreciated but is not required.
- //
- // 2. Altered source versions must be plainly marked as such, and must not be
- // misrepresented as being the original software.
- //
- // 3. This notice may not be removed or altered from any source
- // distribution.
- #ifndef DFPSR_VIRTUAL_MACHINE
- #define DFPSR_VIRTUAL_MACHINE
- #include <cstdint>
- #include "../math/FixedPoint.h"
- #include "../collection/Array.h"
- #include "../collection/List.h"
- // Flags
- //#define VIRTUAL_MACHINE_PROFILE // Enable profiling
- //#define VIRTUAL_MACHINE_DEBUG_PRINT // Enable debug printing (will affect profiling)
- //#define VIRTUAL_MACHINE_DEBUG_FULL_CONTENT // Allow debug printing to show the full content of images
- namespace dsr {
- // TODO: Can this be a template argument?
- #define MAX_TYPE_COUNT 3
- // Forward declarations
- struct VirtualMachine;
- struct VMTypeDef;
- enum class AccessType {
- Any,
- Hidden,
- Input,
- Output
- };
- static ReadableString getName(AccessType access) {
- switch(access) {
- case AccessType::Any: return U"Any";
- case AccessType::Hidden: return U"Hidden";
- case AccessType::Input: return U"Input";
- case AccessType::Output: return U"Output";
- default: return U"?";
- }
- }
- // Types used in machine instuctions
- enum class ArgumentType {
- Unused,
- Immediate,
- Reference
- };
- // Types
- // TODO: Make the use of FixedPoint optional in VirtualMachine
- using DataType = int32_t;
- static const DataType DataType_FixedPoint = 0;
- struct Variable {
- String name;
- AccessType access;
- const VMTypeDef* typeDescription;
- int32_t typeLocalIndex; // The zero-based local index among the members of the same type in the method
- bool global; // A flag that generates negative global indices for referring to global variables in method zero
- Variable(const String& name, AccessType access, const VMTypeDef* typeDescription, int32_t typeLocalIndex, bool global)
- : name(name), access(access), typeDescription(typeDescription), typeLocalIndex(typeLocalIndex), global(global) {}
- int32_t getGlobalIndex() {
- int32_t result = this->global ? (-this->typeLocalIndex - 1) : this->typeLocalIndex;
- return result;
- }
- int32_t getStackIndex(int32_t framePointer) {
- int32_t result = this->global ? this->typeLocalIndex : this->typeLocalIndex + framePointer;
- return result;
- }
- };
- // Virtual Machine Argument
- struct VMA {
- const ArgumentType argType = ArgumentType::Unused;
- const DataType dataType;
- const FixedPoint value;
- explicit VMA(FixedPoint value)
- : argType(ArgumentType::Immediate), dataType(DataType_FixedPoint), value(value) {}
- VMA(DataType dataType, int32_t globalIndex)
- : argType(ArgumentType::Reference), dataType(dataType), value(FixedPoint::fromMantissa(globalIndex)) {}
- };
- struct ArgSig {
- ReadableString name;
- bool byValue;
- // TODO: Replace with pointers to type definitions (const VMTypeDef*)
- DataType dataType;
- ArgSig(const ReadableString& name, bool byValue, DataType dataType)
- : name(name), byValue(byValue), dataType(dataType) {}
- bool matches(ArgumentType argType, DataType dataType) const {
- if (this->byValue && this->dataType == DataType_FixedPoint) {
- return dataType == this->dataType && (argType == ArgumentType::Immediate || argType == ArgumentType::Reference);
- } else {
- return dataType == this->dataType && argType == ArgumentType::Reference;
- }
- }
- };
- template <typename T>
- struct MemoryPlane {
- Array<T> stack;
- explicit MemoryPlane(int32_t size) : stack(size, T()) {}
- T& accessByStackIndex(int32_t stackIndex) {
- return this->stack[stackIndex];
- }
- // globalIndex uses the negative values starting from -1 to access global memory, and from 0 and up to access local variables on top of the type's own frame pointer.
- T& accessByGlobalIndex(int32_t globalIndex, int32_t framePointer) {
- int32_t stackIndex = (globalIndex < 0) ? -(globalIndex + 1) : (framePointer + globalIndex);
- return this->stack[stackIndex];
- }
- T& getRef(const VMA& arg, int32_t framePointer) {
- assert(arg.argType == ArgumentType::Reference);
- return this->accessByGlobalIndex(arg.value.getMantissa(), framePointer);
- }
- };
- struct CallState {
- int32_t methodIndex = 0;
- int32_t programCounter = 0;
- int32_t stackPointer[MAX_TYPE_COUNT] = {};
- int32_t framePointer[MAX_TYPE_COUNT] = {};
- };
- // A planar memory system with one stack and frame pointer for each type of memory.
- // This is possible because the virtual machine only operates on types known in compile-time.
- // The planar stack system:
- // * Removes the need to manually initialize and align classes in generic memory.
- // * Encapsulates any effects of endianness or signed integer representations in the physical hardware.
- // Because there cannot be accidental reintepretation when the type is known in compile-time.
- class PlanarMemory {
- public:
- CallState current;
- List<CallState> callStack;
- virtual ~PlanarMemory() {}
- // Store in memory
- virtual void store(int targetStackIndex, const VMA& sourceArg, int sourceFramePointer, DataType type) = 0;
- // Load from memory
- virtual void load(int sourceStackIndex, const VMA& targetArg, int targetFramePointer, DataType type) = 0;
- };
- // Lambdas without capture is used to create function pointers without objects
- inline void MachineOperationTemplate(VirtualMachine& machine, PlanarMemory&, const List<VMA>& args) {}
- using MachineOperation = decltype(&MachineOperationTemplate);
- struct MachineWord {
- MachineOperation operation;
- List<VMA> args;
- MachineWord(MachineOperation operation, const List<VMA>& args)
- : operation(operation), args(args) {}
- explicit MachineWord(MachineOperation operation)
- : operation(operation) {}
- };
- struct InsSig {
- public:
- ReadableString name;
- int targetCount; // Number of first arguments to present as results
- List<ArgSig> arguments;
- MachineOperation operation;
- InsSig(const ReadableString& name, int targetCount, MachineOperation operation)
- : name(name), targetCount(targetCount), operation(operation) {}
- private:
- void addArguments() {}
- template <typename... ARGS>
- void addArguments(const ArgSig& head, ARGS... tail) {
- this->arguments.push(head);
- this->addArguments(tail...);
- }
- public:
- template <typename... ARGS>
- static InsSig create(const ReadableString& name, int targetCount, MachineOperation operation, ARGS... args) {
- InsSig result = InsSig(name, targetCount, operation);
- result.addArguments(args...);
- return result;
- }
- bool matches(const ReadableString& name, List<VMA> resolvedArguments) const {
- if (resolvedArguments.length() != this->arguments.length()) {
- return false;
- } else if (!string_caseInsensitiveMatch(this->name, name)) {
- return false;
- } else {
- for (int i = 0; i < this->arguments.length(); i++) {
- if (!this->arguments[i].matches(resolvedArguments[i].argType, resolvedArguments[i].dataType)) {
- return false;
- }
- }
- return true;
- }
- }
- };
- // Types
- inline void initializeTemplate(VirtualMachine& machine, int globalIndex, const ReadableString& defaultValue) {}
- using VMT_Initializer = decltype(&initializeTemplate);
- inline void debugPrintTemplate(PlanarMemory& memory, Variable& variable, int globalIndex, int32_t* framePointer, bool fullContent) {}
- using VMT_DebugPrinter = decltype(&debugPrintTemplate);
- struct VMTypeDef {
- ReadableString name;
- DataType dataType;
- bool allowDefaultValue;
- VMT_Initializer initializer;
- VMT_DebugPrinter debugPrinter;
- VMTypeDef(const ReadableString& name, DataType dataType, bool allowDefaultValue, VMT_Initializer initializer, VMT_DebugPrinter debugPrinter)
- : name(name), dataType(dataType), allowDefaultValue(allowDefaultValue), initializer(initializer), debugPrinter(debugPrinter) {}
- };
- struct Method {
- String name;
- // Global instruction space
- const int32_t startAddress = 0; // Index to machineWords
- int32_t instructionCount = 0; // Number of machine words (safer than return statements in case of memory corruption)
- // Unified local space
- int32_t inputCount = 0; // Number of inputs declared at the start of locals
- int32_t outputCount = 0; // Number of output declared directly after the inputs
- // TODO: Merge into a state
- bool declaredNonInput = false; // Goes true when a non-input is declared
- bool declaredLocals = false; // Goes true when a local is declared
- List<Variable> locals; // locals[0..inputCount-1] are the inputs, while locals[inputCount..inputCount+outputCount-1] are the outputs
- // Type-specific spaces
- int32_t count[MAX_TYPE_COUNT] = {};
- // Look-up table from a combination of type and type-local indices to unified-local indices
- List<int32_t> unifiedLocalIndices[MAX_TYPE_COUNT];
- Method(const String& name, int32_t startAddress, int32_t machineTypeCount) : name(name), startAddress(startAddress) {
- // Increase MAX_TYPE_COUNT if it's not enough
- assert(machineTypeCount <= MAX_TYPE_COUNT);
- }
- Variable* getLocal(const ReadableString& name) {
- for (int i = 0; i < this->locals.length(); i++) {
- if (string_caseInsensitiveMatch(this->locals[i].name, name)) {
- return &this->locals[i];
- }
- }
- return nullptr;
- }
- };
- // A virtual machine for efficient media processing.
- struct VirtualMachine {
- // Methods
- List<Method> methods;
- // Memory
- Handle<PlanarMemory> memory;
- // Instruction types
- const InsSig* machineInstructions; int32_t machineInstructionCount;
- const InsSig* getMachineInstructionFromFunction(MachineOperation functionPointer) {
- for (int s = 0; s < this->machineInstructionCount; s++) {
- if (this->machineInstructions[s].operation == functionPointer) {
- return &this->machineInstructions[s];
- }
- }
- return nullptr;
- }
- // Instruction instances
- List<MachineWord> machineWords;
- // Types
- const VMTypeDef* machineTypes; int32_t machineTypeCount;
- const VMTypeDef* getMachineType(const ReadableString& name) {
- for (int s = 0; s < this->machineTypeCount; s++) {
- if (string_caseInsensitiveMatch(this->machineTypes[s].name, name)) {
- return &this->machineTypes[s];
- }
- }
- return nullptr;
- }
- const VMTypeDef* getMachineType(DataType dataType) {
- for (int s = 0; s < this->machineTypeCount; s++) {
- if (this->machineTypes[s].dataType == dataType) {
- return &this->machineTypes[s];
- }
- }
- return nullptr;
- }
- // Constructor
- VirtualMachine(const ReadableString& code, const Handle<PlanarMemory>& memory,
- const InsSig* machineInstructions, int32_t machineInstructionCount,
- const VMTypeDef* machineTypes, int32_t machineTypeCount);
- int findMethod(const ReadableString& name);
- Variable* getResource(const ReadableString& name, int methodIndex);
- /*
- Indices
- Global index: (Identifier) The value stores in the mantissas of machine instructions to refer to things
- These are translated into stack indices for run-time lookups
- Useful for storing in compile-time when there's no stack nor frame-pointer for mapping to any real memory address
- Relative to the frame-pointer, so it cannot access anything else then globals (using negative indices) and locals (using natural indices)
- Stack index: (Pointer) The absolute index of a variable at run-time
- Indices to the type's own stack in the machine
- A frame pointer is needed to create them, but the memory of calling methods can be accessed using stack indices
- Type local index: (Frame-pointer offset) The local index of a variable with a type among the same type
- Quick at finding a stack index for the type's own stack
- Useful to store in variables and convert into global and stack indices
- For compile-time generation and run-time variable access
- Unified local index: (Variable) The index of a variable's debug information
- Indices to unifiedLocalIndices in methods
- Can be used to find the name of the variable for debugging
- Unlike the type local index, the unified index knows the type
- */
- static int globalToTypeLocalIndex(int globalIndex) {
- return globalIndex < 0 ? -(globalIndex + 1) : globalIndex;
- }
- static int typeLocalToGlobalIndex(bool isGlobal, int typeLocalIndex) {
- return isGlobal ? -(typeLocalIndex + 1) : typeLocalIndex;
- }
- void addMachineWord(MachineOperation operation, const List<VMA>& args);
- void addMachineWord(MachineOperation operation);
- void addReturnInstruction();
- void addCallInstructions(const List<String>& arguments);
- void interpretCommand(const ReadableString& operation, const List<VMA>& resolvedArguments);
- Variable* declareVariable_aux(const VMTypeDef& typeDef, int methodIndex, AccessType access, const ReadableString& name, bool initialize, const ReadableString& defaultValueText);
- Variable* declareVariable(int methodIndex, AccessType access, const ReadableString& type, const ReadableString& name, bool initialize, const ReadableString& defaultValueText);
- VMA VMAfromText(int methodIndex, const ReadableString& content);
- void interpretMachineWord(const ReadableString& command, const List<String>& arguments);
- // Run-time debug printing
- #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
- Variable* getDebugInfo(DataType dataType, int globalIndex, int methodIndex) {
- if (globalIndex < 0) { methodIndex = 0; } // Go to the global method if it's a global index
- Method* method = &this->methods[methodIndex];
- int typeLocalIndex = globalToTypeLocalIndex(globalIndex);
- int unifiedLocalIndex = method->unifiedLocalIndices[dataType][typeLocalIndex];
- return &(method->locals[unifiedLocalIndex]);
- }
- void debugArgument(const VMA& data, int methodIndex, int32_t* framePointer, bool fullContent) {
- if (data.argType == ArgumentType::Immediate) {
- printText(data.value);
- } else {
- int globalIndex = data.value.getMantissa();
- Variable* variable = getDebugInfo(data.dataType, globalIndex, methodIndex);
- const VMTypeDef* typeDefinition = getMachineType(data.dataType);
- #ifndef VIRTUAL_MACHINE_DEBUG_FULL_CONTENT
- fullContent = false;
- #endif
- if (typeDefinition) {
- typeDefinition->debugPrinter(*(this->memory.getUnsafe()), *variable, globalIndex, framePointer, fullContent);
- if (globalIndex < 0) {
- printText(U" @gi(", globalIndex, U")");
- } else {
- printText(U" @gi(", globalIndex, U") + fp(", framePointer[typeDefinition->dataType], U")");
- }
- } else {
- printText(U"?");
- }
- }
- }
- void debugPrintVariables(int methodIndex, int32_t* framePointer, const ReadableString& indentation) {
- Method* method = &this->methods[methodIndex];
- for (int i = 0; i < method->locals.length(); i++) {
- Variable* variable = &method->locals[i];
- printText(indentation, U"* ", getName(variable->access), U" ");
- const VMTypeDef* typeDefinition = getMachineType(variable->typeDescription->dataType);
- if (typeDefinition) {
- typeDefinition->debugPrinter(*(this->memory.getUnsafe()), *variable, variable->getGlobalIndex(), framePointer, false);
- } else {
- printText(U"?");
- }
- printText(U"\n");
- }
- }
- void debugPrintMethod(int methodIndex, int32_t* framePointer, int32_t* stackPointer, const ReadableString& indentation) {
- printText(" ", this->methods[methodIndex].name, ":\n");
- for (int t = 0; t < this->machineTypeCount; t++) {
- printText(U" FramePointer[", t, "] = ", framePointer[t], U" Count[", t, "] = ", this->methods[methodIndex].count[t], U" StackPointer[", t, "] = ", stackPointer[t], U"\n");
- }
- debugPrintVariables(methodIndex, framePointer, indentation);
- printText(U"\n");
- }
- void debugPrintMemory() {
- int methodIndex = this->memory->current.methodIndex;
- printText(U"\nMemory:\n");
- // Global memory is at the bottom of the stack.
- int32_t globalFramePointer[MAX_TYPE_COUNT] = {};
- debugPrintMethod(0, globalFramePointer, this->methods[0].count, U" ");
- // Stack memory for each calling method.
- for (int i = 0; i < memory->callStack.length(); i++) {
- debugPrintMethod(memory->callStack[i].methodIndex, memory->callStack[i].framePointer, memory->callStack[i].stackPointer, U" ");
- }
- // Stack memory for the current method, which is not in the call stack because that would be slow to access.
- debugPrintMethod(methodIndex, this->memory->current.framePointer, this->memory->current.stackPointer, U" ");
- }
- #endif
- void executeMethod(int methodIndex);
- int32_t getResourceStackIndex(const ReadableString& name, int methodIndex, DataType dataType, AccessType access = AccessType::Any) {
- Variable* variable = getResource(name, methodIndex);
- if (variable) {
- if (variable->typeDescription->dataType != dataType) {
- throwError("The machine's resource named \"", variable->name, "\" had the unexpected type \"", variable->typeDescription->name, "\"!\n");
- } else if (access != variable->access && access != AccessType::Any) {
- throwError("The machine's resource named \"", variable->name, "\" is not delared as \"", getName(access), "\"!\n");
- } else {
- return variable->getStackIndex(this->memory->current.framePointer[dataType]);
- }
- } else {
- throwError("The machine cannot find any resource named \"", name, "\"!\n");
- }
- return -1;
- }
- };
- }
- #endif
|