VirtualMachine.h 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. // zlib open source license
  2. //
  3. // Copyright (c) 2019 David Forsgren Piuva
  4. //
  5. // This software is provided 'as-is', without any express or implied
  6. // warranty. In no event will the authors be held liable for any damages
  7. // arising from the use of this software.
  8. //
  9. // Permission is granted to anyone to use this software for any purpose,
  10. // including commercial applications, and to alter it and redistribute it
  11. // freely, subject to the following restrictions:
  12. //
  13. // 1. The origin of this software must not be misrepresented; you must not
  14. // claim that you wrote the original software. If you use this software
  15. // in a product, an acknowledgment in the product documentation would be
  16. // appreciated but is not required.
  17. //
  18. // 2. Altered source versions must be plainly marked as such, and must not be
  19. // misrepresented as being the original software.
  20. //
  21. // 3. This notice may not be removed or altered from any source
  22. // distribution.
  23. #ifndef DFPSR_VIRTUAL_MACHINE
  24. #define DFPSR_VIRTUAL_MACHINE
  25. #include <stdint.h>
  26. #include "../math/FixedPoint.h"
  27. #include "../collection/Array.h"
  28. #include "../collection/List.h"
  29. // Flags
  30. //#define VIRTUAL_MACHINE_PROFILE // Enable profiling
  31. //#define VIRTUAL_MACHINE_DEBUG_PRINT // Enable debug printing (will affect profiling)
  32. //#define VIRTUAL_MACHINE_DEBUG_FULL_CONTENT // Allow debug printing to show the full content of images
  33. namespace dsr {
  34. #define MAX_TYPE_COUNT 4
  35. // Forward declarations
  36. struct VirtualMachine;
  37. struct VMTypeDef;
  38. enum class AccessType {
  39. Any,
  40. Hidden,
  41. Input,
  42. Output
  43. };
  44. static ReadableString getName(AccessType access) {
  45. switch(access) {
  46. case AccessType::Any: return U"Any";
  47. case AccessType::Hidden: return U"Hidden";
  48. case AccessType::Input: return U"Input";
  49. case AccessType::Output: return U"Output";
  50. default: return U"?";
  51. }
  52. }
  53. // Types used in machine instuctions
  54. enum class ArgumentType {
  55. Unused,
  56. Immediate,
  57. Reference
  58. };
  59. // Types
  60. // TODO: Make the use of FixedPoint optional in VirtualMachine
  61. using DataType = int32_t;
  62. static const DataType DataType_FixedPoint = 0;
  63. struct Variable {
  64. String name;
  65. AccessType access;
  66. const VMTypeDef* typeDescription;
  67. int32_t typeLocalIndex; // The zero-based local index among the members of the same type in the method
  68. bool global; // A flag that generates negative global indices for referring to global variables in method zero
  69. Variable(const String& name, AccessType access, const VMTypeDef* typeDescription, int32_t typeLocalIndex, bool global)
  70. : name(name), access(access), typeDescription(typeDescription), typeLocalIndex(typeLocalIndex), global(global) {}
  71. int32_t getGlobalIndex() {
  72. int32_t result = this->global ? (-this->typeLocalIndex - 1) : this->typeLocalIndex;
  73. return result;
  74. }
  75. int32_t getStackIndex(int32_t framePointer) {
  76. int32_t result = this->global ? this->typeLocalIndex : this->typeLocalIndex + framePointer;
  77. return result;
  78. }
  79. };
  80. // Virtual Machine Argument
  81. struct VMA {
  82. const ArgumentType argType = ArgumentType::Unused;
  83. const DataType dataType;
  84. const FixedPoint value;
  85. explicit VMA(FixedPoint value)
  86. : argType(ArgumentType::Immediate), dataType(DataType_FixedPoint), value(value) {}
  87. VMA(DataType dataType, int32_t globalIndex)
  88. : argType(ArgumentType::Reference), dataType(dataType), value(FixedPoint::fromMantissa(globalIndex)) {}
  89. };
  90. struct ArgSig {
  91. ReadableString name;
  92. bool byValue;
  93. // TODO: Replace with pointers to type definitions (const VMTypeDef*)
  94. DataType dataType;
  95. ArgSig(const ReadableString& name, bool byValue, DataType dataType)
  96. : name(name), byValue(byValue), dataType(dataType) {}
  97. bool matches(ArgumentType argType, DataType dataType) const {
  98. if (this->byValue && this->dataType == DataType_FixedPoint) {
  99. return dataType == this->dataType && (argType == ArgumentType::Immediate || argType == ArgumentType::Reference);
  100. } else {
  101. return dataType == this->dataType && argType == ArgumentType::Reference;
  102. }
  103. }
  104. };
  105. template <typename T>
  106. struct MemoryPlane {
  107. Array<T> stack;
  108. explicit MemoryPlane(int32_t size) : stack(size, T()) {}
  109. T& accessByStackIndex(int32_t stackIndex) {
  110. return this->stack[stackIndex];
  111. }
  112. T& accessByGlobalIndex(int32_t globalIndex, int32_t framePointer) {
  113. int32_t stackIndex = globalIndex < 0 ? -(globalIndex + 1) : framePointer + globalIndex;
  114. return this->stack[stackIndex];
  115. }
  116. T& getRef(const VMA& arg, int32_t framePointer) {
  117. assert(arg.argType == ArgumentType::Reference);
  118. return this->accessByGlobalIndex(arg.value.getMantissa(), framePointer);
  119. }
  120. };
  121. struct CallState {
  122. int32_t methodIndex = 0;
  123. int32_t programCounter = 0;
  124. int32_t stackPointer[MAX_TYPE_COUNT] = {};
  125. int32_t framePointer[MAX_TYPE_COUNT] = {};
  126. };
  127. // A planar memory system with one stack and frame pointer for each type of memory.
  128. // This is possible because the virtual machine only operates on types known in compile-time.
  129. // The planar stack system:
  130. // * Removes the need to manually initialize and align classes in generic memory.
  131. // * Encapsulates any effects of endianness or signed integer representations in the physical hardware.
  132. // Because there cannot be accidental reintepretation when the type is known in compile-time.
  133. class PlanarMemory {
  134. public:
  135. CallState current;
  136. List<CallState> callStack;
  137. virtual ~PlanarMemory() {}
  138. // Store in memory
  139. virtual void store(int targetStackIndex, const VMA& sourceArg, int sourceFramePointer, DataType type) = 0;
  140. // Load from memory
  141. virtual void load(int sourceStackIndex, const VMA& targetArg, int targetFramePointer, DataType type) = 0;
  142. };
  143. // Lambdas without capture is used to create function pointers without objects
  144. inline void MachineOperationTemplate(VirtualMachine& machine, PlanarMemory&, const List<VMA>& args) {}
  145. using MachineOperation = decltype(&MachineOperationTemplate);
  146. struct MachineWord {
  147. MachineOperation operation;
  148. List<VMA> args;
  149. MachineWord(MachineOperation operation, const List<VMA>& args)
  150. : operation(operation), args(args) {}
  151. explicit MachineWord(MachineOperation operation)
  152. : operation(operation) {}
  153. };
  154. struct InsSig {
  155. public:
  156. ReadableString name;
  157. int targetCount; // Number of first arguments to present as results
  158. List<ArgSig> arguments;
  159. MachineOperation operation;
  160. InsSig(const ReadableString& name, int targetCount, MachineOperation operation)
  161. : name(name), targetCount(targetCount), operation(operation) {}
  162. private:
  163. void addArguments() {}
  164. template <typename... ARGS>
  165. void addArguments(const ArgSig& head, ARGS... tail) {
  166. this->arguments.push(head);
  167. this->addArguments(tail...);
  168. }
  169. public:
  170. template <typename... ARGS>
  171. static InsSig create(const ReadableString& name, int targetCount, MachineOperation operation, ARGS... args) {
  172. InsSig result = InsSig(name, targetCount, operation);
  173. result.addArguments(args...);
  174. return result;
  175. }
  176. bool matches(const ReadableString& name, List<VMA> resolvedArguments) const {
  177. if (resolvedArguments.length() != this->arguments.length()) {
  178. return false;
  179. } else if (!string_caseInsensitiveMatch(this->name, name)) {
  180. return false;
  181. } else {
  182. for (int i = 0; i < this->arguments.length(); i++) {
  183. if (!this->arguments[i].matches(resolvedArguments[i].argType, resolvedArguments[i].dataType)) {
  184. return false;
  185. }
  186. }
  187. return true;
  188. }
  189. }
  190. };
  191. // Types
  192. inline void initializeTemplate(VirtualMachine& machine, int globalIndex, const ReadableString& defaultValue) {}
  193. using VMT_Initializer = decltype(&initializeTemplate);
  194. inline void debugPrintTemplate(PlanarMemory& memory, Variable& variable, int globalIndex, int32_t* framePointer, bool fullContent) {}
  195. using VMT_DebugPrinter = decltype(&debugPrintTemplate);
  196. struct VMTypeDef {
  197. ReadableString name;
  198. DataType dataType;
  199. bool allowDefaultValue;
  200. VMT_Initializer initializer;
  201. VMT_DebugPrinter debugPrinter;
  202. VMTypeDef(const ReadableString& name, DataType dataType, bool allowDefaultValue, VMT_Initializer initializer, VMT_DebugPrinter debugPrinter)
  203. : name(name), dataType(dataType), allowDefaultValue(allowDefaultValue), initializer(initializer), debugPrinter(debugPrinter) {}
  204. };
  205. struct Method {
  206. String name;
  207. // Global instruction space
  208. const int32_t startAddress; // Index to machineWords
  209. int32_t instructionCount = 0; // Number of machine words (safer than return statements in case of memory corruption)
  210. // Unified local space
  211. int32_t inputCount = 0; // Number of inputs declared at the start of locals
  212. int32_t outputCount = 0; // Number of output declared directly after the inputs
  213. // TODO: Merge into a state
  214. bool declaredNonInput = false; // Goes true when a non-input is declared
  215. bool declaredLocals = false; // Goes true when a local is declared
  216. List<Variable> locals; // locals[0..inputCount-1] are the inputs, while locals[inputCount..inputCount+outputCount-1] are the outputs
  217. // Type-specific spaces
  218. int32_t count[MAX_TYPE_COUNT] = {};
  219. // Look-up table from a combination of type and type-local indices to unified-local indices
  220. List<int32_t> unifiedLocalIndices[MAX_TYPE_COUNT];
  221. Method(const String& name, int32_t startAddress, int32_t machineTypeCount) : name(name), startAddress(startAddress) {
  222. // Increase MAX_TYPE_COUNT if it's not enough
  223. assert(machineTypeCount <= MAX_TYPE_COUNT);
  224. }
  225. Variable* getLocal(const ReadableString& name) {
  226. for (int i = 0; i < this->locals.length(); i++) {
  227. if (string_caseInsensitiveMatch(this->locals[i].name, name)) {
  228. return &this->locals[i];
  229. }
  230. }
  231. return nullptr;
  232. }
  233. };
  234. // A virtual machine for efficient media processing.
  235. struct VirtualMachine {
  236. // Methods
  237. List<Method> methods;
  238. // Memory
  239. std::shared_ptr<PlanarMemory> memory;
  240. // Instruction types
  241. const InsSig* machineInstructions; int32_t machineInstructionCount;
  242. const InsSig* getMachineInstructionFromFunction(MachineOperation functionPointer) {
  243. for (int s = 0; s < this->machineInstructionCount; s++) {
  244. if (this->machineInstructions[s].operation == functionPointer) {
  245. return &this->machineInstructions[s];
  246. }
  247. }
  248. return nullptr;
  249. }
  250. // Instruction instances
  251. List<MachineWord> machineWords;
  252. // Types
  253. const VMTypeDef* machineTypes; int32_t machineTypeCount;
  254. const VMTypeDef* getMachineType(const ReadableString& name) {
  255. for (int s = 0; s < this->machineTypeCount; s++) {
  256. if (string_caseInsensitiveMatch(this->machineTypes[s].name, name)) {
  257. return &this->machineTypes[s];
  258. }
  259. }
  260. return nullptr;
  261. }
  262. const VMTypeDef* getMachineType(DataType dataType) {
  263. for (int s = 0; s < this->machineTypeCount; s++) {
  264. if (this->machineTypes[s].dataType == dataType) {
  265. return &this->machineTypes[s];
  266. }
  267. }
  268. return nullptr;
  269. }
  270. // Constructor
  271. VirtualMachine(const ReadableString& code, const std::shared_ptr<PlanarMemory>& memory,
  272. const InsSig* machineInstructions, int32_t machineInstructionCount,
  273. const VMTypeDef* machineTypes, int32_t machineTypeCount);
  274. int findMethod(const ReadableString& name);
  275. Variable* getResource(const ReadableString& name, int methodIndex);
  276. /*
  277. Indices
  278. Global index: (Identifier) The value stores in the mantissas of machine instructions to refer to things
  279. These are translated into stack indices for run-time lookups
  280. Useful for storing in compile-time when there's no stack nor frame-pointer for mapping to any real memory address
  281. Relative to the frame-pointer, so it cannot access anything else then globals (using negative indices) and locals (using natural indices)
  282. Stack index: (Pointer) The absolute index of a variable at run-time
  283. Indices to the type's own stack in the machine
  284. A frame pointer is needed to create them, but the memory of calling methods can be accessed using stack indices
  285. Type local index: (Frame-pointer offset) The local index of a variable with a type among the same type
  286. Quick at finding a stack index for the type's own stack
  287. Useful to store in variables and convert into global and stack indices
  288. For compile-time generation and run-time variable access
  289. Unified local index: (Variable) The index of a variable's debug information
  290. Indices to unifiedLocalIndices in methods
  291. Can be used to find the name of the variable for debugging
  292. Unlike the type local index, the unified index knows the type
  293. */
  294. static int globalToTypeLocalIndex(int globalIndex) {
  295. return globalIndex < 0 ? -(globalIndex + 1) : globalIndex;
  296. }
  297. static int typeLocalToGlobalIndex(bool isGlobal, int typeLocalIndex) {
  298. return isGlobal ? -(typeLocalIndex + 1) : typeLocalIndex;
  299. }
  300. void addMachineWord(MachineOperation operation, const List<VMA>& args);
  301. void addMachineWord(MachineOperation operation);
  302. void addReturnInstruction();
  303. void addCallInstructions(const List<String>& arguments);
  304. void interpretCommand(const ReadableString& operation, const List<VMA>& resolvedArguments);
  305. Variable* declareVariable_aux(const VMTypeDef& typeDef, int methodIndex, AccessType access, const ReadableString& name, bool initialize, const ReadableString& defaultValueText);
  306. Variable* declareVariable(int methodIndex, AccessType access, const ReadableString& type, const ReadableString& name, bool initialize, const ReadableString& defaultValueText);
  307. VMA VMAfromText(int methodIndex, const ReadableString& content);
  308. void interpretMachineWord(const ReadableString& command, const List<String>& arguments);
  309. // Run-time debug printing
  310. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  311. Variable* getDebugInfo(DataType dataType, int globalIndex, int methodIndex) {
  312. if (globalIndex < 0) { methodIndex = 0; } // Go to the global method if it's a global index
  313. Method* method = &this->methods[methodIndex];
  314. int typeLocalIndex = globalToTypeLocalIndex(globalIndex);
  315. int unifiedLocalIndex = method->unifiedLocalIndices[dataType][typeLocalIndex];
  316. return &(method->locals[unifiedLocalIndex]);
  317. }
  318. void debugArgument(const VMA& data, int methodIndex, int32_t* framePointer, bool fullContent) {
  319. if (data.argType == ArgumentType::Immediate) {
  320. printText(data.value);
  321. } else {
  322. int globalIndex = data.value.getMantissa();
  323. Variable* variable = getDebugInfo(data.dataType, globalIndex, methodIndex);
  324. const VMTypeDef* typeDefinition = getMachineType(data.dataType);
  325. #ifndef VIRTUAL_MACHINE_DEBUG_FULL_CONTENT
  326. fullContent = false;
  327. #endif
  328. if (typeDefinition) {
  329. typeDefinition->debugPrinter(*(this->memory.get()), *variable, globalIndex, framePointer, fullContent);
  330. if (globalIndex < 0) {
  331. printText(" @gi(", globalIndex, ")");
  332. } else {
  333. printText(" @gi(", globalIndex, ")+fp(", framePointer[typeDefinition->dataType], ")");
  334. }
  335. } else {
  336. printText("?");
  337. }
  338. }
  339. }
  340. void debugPrintVariables(int methodIndex, int32_t* framePointer, const ReadableString& indentation) {
  341. Method* method = &this->methods[methodIndex];
  342. for (int i = 0; i < method->locals.length(); i++) {
  343. Variable* variable = &method->locals[i];
  344. printText(indentation, "* ", getName(variable->access), " ");
  345. const VMTypeDef* typeDefinition = getMachineType(variable->typeDescription->dataType);
  346. if (typeDefinition) {
  347. typeDefinition->debugPrinter(*(this->memory.get()), *variable, variable->getGlobalIndex(), framePointer, false);
  348. } else {
  349. printText("?");
  350. }
  351. printText("\n");
  352. }
  353. }
  354. void debugPrintMethod(int methodIndex, int32_t* framePointer, const ReadableString& indentation) {
  355. printText(" ", this->methods[methodIndex].name, ":\n");
  356. for (int t = 0; t < this->machineTypeCount; t++) {
  357. printText(" FramePointer[", t, "] = ", framePointer[t], " Count[", t, "] = ", this->methods[methodIndex].count[t], "\n");
  358. }
  359. debugPrintVariables(methodIndex, framePointer, indentation);
  360. printText("\n");
  361. }
  362. void debugPrintMemory() {
  363. int methodIndex = this->memory->current.methodIndex;
  364. printText("\nMemory:\n");
  365. if (methodIndex > 0) {
  366. int32_t globalFramePointer[MAX_TYPE_COUNT] = {};
  367. debugPrintMethod(0, globalFramePointer, U" ");
  368. }
  369. for (int i = 0; i < memory->callStack.length(); i++) {
  370. debugPrintMethod(memory->callStack[i].methodIndex, memory->callStack[i].framePointer, U" ");
  371. }
  372. debugPrintMethod(methodIndex, this->memory->current.framePointer, U" ");
  373. }
  374. #endif
  375. void executeMethod(int methodIndex);
  376. int32_t getResourceStackIndex(const ReadableString& name, int methodIndex, DataType dataType, AccessType access = AccessType::Any) {
  377. Variable* variable = getResource(name, methodIndex);
  378. if (variable) {
  379. if (variable->typeDescription->dataType != dataType) {
  380. throwError("The machine's resource named \"", variable->name, "\" had the unexpected type \"", variable->typeDescription->name, "\"!\n");
  381. } else if (access != variable->access && access != AccessType::Any) {
  382. throwError("The machine's resource named \"", variable->name, "\" is not delared as \"", getName(access), "\"!\n");
  383. } else {
  384. return variable->getStackIndex(this->memory->current.framePointer[dataType]);
  385. }
  386. } else {
  387. throwError("The machine cannot find any resource named \"", name, "\"!\n");
  388. }
  389. return -1;
  390. }
  391. };
  392. }
  393. #endif