2
0

VirtualMachine.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  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. #include "VirtualMachine.h"
  24. #include "../api/timeAPI.h"
  25. using namespace dsr;
  26. VirtualMachine::VirtualMachine(const ReadableString& code, const std::shared_ptr<PlanarMemory>& memory,
  27. const InsSig* machineInstructions, int32_t machineInstructionCount,
  28. const VMTypeDef* machineTypes, int32_t machineTypeCount)
  29. : memory(memory), machineInstructions(machineInstructions), machineInstructionCount(machineInstructionCount),
  30. machineTypes(machineTypes), machineTypeCount(machineTypeCount) {
  31. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  32. printText("Starting media machine.\n");
  33. #endif
  34. this->methods.pushConstruct(U"<init>", 0, this->machineTypeCount);
  35. List<ReadableString> lines = string_split(code, U'\n');
  36. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  37. printText("Reading assembly.\n");
  38. #endif
  39. List<ReadableString> arguments; // Re-using the buffer for in-place splitting
  40. for (int l = 0; l < lines.length(); l++) {
  41. ReadableString currentLine = lines[l];
  42. // If the line has a comment, then skip everything from #
  43. int commentIndex = currentLine.findFirst(U'#');
  44. if (commentIndex > -1) {
  45. currentLine = currentLine.before(commentIndex);
  46. }
  47. currentLine = string_removeOuterWhiteSpace(currentLine);
  48. int colonIndex = currentLine.findFirst(U':');
  49. if (colonIndex > -1) {
  50. ReadableString command = string_removeOuterWhiteSpace(currentLine.before(colonIndex));
  51. ReadableString argumentLine = currentLine.after(colonIndex);
  52. string_split_inPlace(arguments, argumentLine, U',');
  53. this->interpretMachineWord(command, arguments);
  54. } else if (currentLine.length() > 0) {
  55. throwError("Unexpected line \"", currentLine, "\".\n");
  56. }
  57. }
  58. // Calling "<init>" to execute global commands
  59. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  60. printText("Initializing global machine state.\n");
  61. #endif
  62. this->executeMethod(0);
  63. }
  64. int VirtualMachine::findMethod(const ReadableString& name) {
  65. for (int i = 0; i < this->methods.length(); i++) {
  66. if (string_caseInsensitiveMatch(this->methods[i].name, name)) {
  67. return i;
  68. }
  69. }
  70. return -1;
  71. }
  72. Variable* VirtualMachine::getResource(const ReadableString& name, int methodIndex) {
  73. Variable* result = this->methods[methodIndex].getLocal(name);
  74. if (result) {
  75. // If found, take the local variable
  76. return result;
  77. } else if (methodIndex > 0) {
  78. // If not found but having another scope, look for global variables in the global initiation method
  79. return getResource(name, 0);
  80. } else {
  81. return nullptr;
  82. }
  83. }
  84. void VirtualMachine::addMachineWord(MachineOperation operation, const List<VMA>& args) {
  85. this->machineWords.pushConstruct(operation, args);
  86. this->methods[this->methods.length() - 1].instructionCount++;
  87. }
  88. void VirtualMachine::addMachineWord(MachineOperation operation) {
  89. this->machineWords.pushConstruct(operation);
  90. this->methods[this->methods.length() - 1].instructionCount++;
  91. }
  92. void VirtualMachine::interpretCommand(const ReadableString& operation, const List<VMA>& resolvedArguments) {
  93. // Compare the input with overloads
  94. for (int s = 0; s < machineInstructionCount; s++) {
  95. if (machineInstructions[s].matches(operation, resolvedArguments)) {
  96. this->addMachineWord(machineInstructions[s].operation, resolvedArguments);
  97. return;
  98. }
  99. }
  100. // TODO: Allow asking the specific machine type what the given types are called.
  101. String message = string_combine(U"\nError! ", operation, U" does not match any overload for the given arguments:\n");
  102. for (int s = 0; s < machineInstructionCount; s++) {
  103. const InsSig* signature = &machineInstructions[s];
  104. if (string_caseInsensitiveMatch(signature->name, operation)) {
  105. string_append(message, " * ", signature->name, "(");
  106. for (int a = 0; a < signature->arguments.length(); a++) {
  107. if (a > 0) {
  108. string_append(message, ", ");
  109. }
  110. const ArgSig* argument = &signature->arguments[a];
  111. string_append(message, argument->name);
  112. }
  113. string_append(message, ")\n");
  114. }
  115. }
  116. throwError(message);
  117. }
  118. // TODO: Inline into declareVariable
  119. Variable* VirtualMachine::declareVariable_aux(const VMTypeDef& typeDef, int methodIndex, AccessType access, const ReadableString& name, bool initialize, const ReadableString& defaultValueText) {
  120. // Make commonly used data more readable
  121. bool global = methodIndex == 0;
  122. Method* currentMethod = &this->methods[methodIndex];
  123. // Assert correctness
  124. if (global && (access == AccessType::Input || access == AccessType::Output)) {
  125. throwError("Cannot declare inputs or outputs globally!\n");
  126. }
  127. // Count how many variables the method has of each type
  128. currentMethod->count[typeDef.dataType]++;
  129. this->methods[methodIndex].unifiedLocalIndices[typeDef.dataType].push(this->methods[methodIndex].locals.length());
  130. // Count inputs for calling the method
  131. if (access == AccessType::Input) {
  132. if (this->methods[methodIndex].declaredNonInput) {
  133. throwError("Cannot declare input \"", name, "\" after a non-input has been declared. Declare inputs, outputs and locals in order.\n");
  134. }
  135. this->methods[methodIndex].inputCount++;
  136. } else if (access == AccessType::Output) {
  137. if (this->methods[methodIndex].declaredLocals) {
  138. throwError("Cannot declare output \"", name, "\" after a local has been declared. Declare inputs, outputs and locals in order.\n");
  139. }
  140. this->methods[methodIndex].outputCount++;
  141. this->methods[methodIndex].declaredNonInput = true;
  142. } else if (access == AccessType::Hidden) {
  143. this->methods[methodIndex].declaredLocals = true;
  144. this->methods[methodIndex].declaredNonInput = true;
  145. }
  146. // Declare the variable so that code may find the type and index by name
  147. int typeLocalIndex = currentMethod->count[typeDef.dataType] - 1;
  148. int globalIndex = typeLocalToGlobalIndex(global, typeLocalIndex);
  149. this->methods[methodIndex].locals.pushConstruct(name, access, &typeDef, typeLocalIndex, global);
  150. if (initialize && access != AccessType::Input) {
  151. // Generate instructions for assigning the variable's initial value
  152. typeDef.initializer(*this, globalIndex, defaultValueText);
  153. }
  154. return &this->methods[methodIndex].locals.last();
  155. }
  156. Variable* VirtualMachine::declareVariable(int methodIndex, AccessType access, const ReadableString& typeName, const ReadableString& name, bool initialize, const ReadableString& defaultValueText) {
  157. if (this->getResource(name, methodIndex)) {
  158. throwError("A resource named \"", name, "\" already exists! Be aware that resource names are case insensitive.\n");
  159. return nullptr;
  160. } else {
  161. // Loop over type definitions to find a match
  162. const VMTypeDef* typeDef = getMachineType(typeName);
  163. if (typeDef) {
  164. if (defaultValueText.length() > 0 && !typeDef->allowDefaultValue) {
  165. throwError("The variable \"", name, "\" doesn't have an immediate constructor for \"", typeName, "\".\n");
  166. }
  167. return this->declareVariable_aux(*typeDef, methodIndex, access, name, initialize, defaultValueText);
  168. } else {
  169. throwError("Cannot declare variable of unknown type \"", typeName, "\"!\n");
  170. return nullptr;
  171. }
  172. }
  173. }
  174. VMA VirtualMachine::VMAfromText(int methodIndex, const ReadableString& content) {
  175. DsrChar first = content[0];
  176. DsrChar second = content[1];
  177. if (first == U'-' && second >= U'0' && second <= U'9') {
  178. return VMA(FixedPoint::fromText(content));
  179. } else if (first >= U'0' && first <= U'9') {
  180. return VMA(FixedPoint::fromText(content));
  181. } else {
  182. int leftIndex = content.findFirst(U'<');
  183. int rightIndex = content.findLast(U'>');
  184. if (leftIndex > -1 && rightIndex > -1) {
  185. ReadableString name = string_removeOuterWhiteSpace(content.before(leftIndex));
  186. ReadableString typeName = string_removeOuterWhiteSpace(content.inclusiveRange(leftIndex + 1, rightIndex - 1));
  187. ReadableString remainder = string_removeOuterWhiteSpace(content.after(rightIndex));
  188. if (remainder.length() > 0) {
  189. throwError("No code allowed after > for in-place temp declarations!\n");
  190. }
  191. Variable* resource = this->declareVariable(methodIndex, AccessType::Hidden, typeName, name, false, U"");
  192. if (resource) {
  193. return VMA(resource->typeDescription->dataType, resource->getGlobalIndex());
  194. } else {
  195. throwError("The resource \"", name, "\" could not be declared as \"", typeName, "\"!\n");
  196. return VMA(FixedPoint());
  197. }
  198. } else if (leftIndex > -1) {
  199. throwError("Using < without > for in-place temp allocation.\n");
  200. return VMA(FixedPoint());
  201. } else if (rightIndex > -1) {
  202. throwError("Using > without < for in-place temp allocation.\n");
  203. return VMA(FixedPoint());
  204. } else {
  205. Variable* resource = getResource(content, methodIndex);
  206. if (resource) {
  207. return VMA(resource->typeDescription->dataType, resource->getGlobalIndex());
  208. } else {
  209. throwError("The resource \"", content, "\" could not be found! Make sure that it's declared before being used.\n");
  210. return VMA(FixedPoint());
  211. }
  212. }
  213. }
  214. }
  215. static ReadableString getArg(const List<ReadableString>& arguments, int32_t index) {
  216. if (index < 0 || index >= arguments.length()) {
  217. return U"";
  218. } else {
  219. return string_removeOuterWhiteSpace(arguments[index]);
  220. }
  221. }
  222. void VirtualMachine::addReturnInstruction() {
  223. addMachineWord([](VirtualMachine& machine, PlanarMemory& memory, const List<VMA>& args) {
  224. if (memory.callStack.length() > 0) {
  225. // Return to caller
  226. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  227. printText("Returning from \"", machine.methods[memory.current.methodIndex].name, "\" to caller \"", machine.methods[memory.callStack.last().methodIndex].name, "\"\n");
  228. machine.debugPrintMemory();
  229. #endif
  230. memory.current = memory.callStack.last();
  231. memory.callStack.pop();
  232. memory.current.programCounter++;
  233. } else {
  234. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  235. printText("Returning from \"", machine.methods[memory.current.methodIndex].name, "\"\n");
  236. #endif
  237. // Leave the virtual machine
  238. memory.current.programCounter = -1;
  239. }
  240. });
  241. }
  242. void VirtualMachine::addCallInstructions(const List<ReadableString>& arguments) {
  243. if (arguments.length() < 1) {
  244. throwError("Cannot make a call without the name of a method!\n");
  245. }
  246. // TODO: Allow calling methods that aren't defined yet.
  247. int currentMethodIndex = this->methods.length() - 1;
  248. int calledMethodIndex = findMethod(string_removeOuterWhiteSpace(arguments[0]));
  249. // Check the total number of arguments
  250. Method* calledMethod = &this->methods[calledMethodIndex];
  251. if (arguments.length() - 1 != calledMethod->outputCount + calledMethod->inputCount) {
  252. throwError("Wrong argument count to \"", calledMethod->name, "\"! Call arguments should start with the method to call, continue with output references and end with inputs.\n");
  253. }
  254. // Split assembler arguments into separate input and output arguments for machine instructions
  255. List<VMA> inputArguments;
  256. List<VMA> outputArguments;
  257. inputArguments.push(VMA(FixedPoint::fromMantissa(calledMethodIndex)));
  258. outputArguments.push(VMA(FixedPoint::fromMantissa(calledMethodIndex)));
  259. int outputCount = 0;
  260. for (int a = 1; a < arguments.length(); a++) {
  261. ReadableString content = string_removeOuterWhiteSpace(arguments[a]);
  262. if (content.length() > 0) {
  263. if (outputCount < calledMethod->outputCount) {
  264. outputArguments.push(this->VMAfromText(currentMethodIndex, getArg(arguments, a)));
  265. outputCount++;
  266. } else {
  267. inputArguments.push(this->VMAfromText(currentMethodIndex, getArg(arguments, a)));
  268. }
  269. }
  270. }
  271. // Check types
  272. for (int a = 1; a < outputArguments.length(); a++) {
  273. // Output
  274. Variable* variable = &calledMethod->locals[a - 1 + calledMethod->inputCount];
  275. if (outputArguments[a].argType != ArgumentType::Reference) {
  276. throwError("Output argument for \"", variable->name, "\" in \"", calledMethod->name, "\" must be a reference to allow writing its result!\n");
  277. } else if (outputArguments[a].dataType != variable->typeDescription->dataType) {
  278. throwError("Output argument for \"", variable->name, "\" in \"", calledMethod->name, "\" must have the type \"", variable->typeDescription->name, "\"!\n");
  279. }
  280. }
  281. for (int a = 1; a < inputArguments.length(); a++) {
  282. // Input
  283. Variable* variable = &calledMethod->locals[a - 1];
  284. if (inputArguments[a].dataType != variable->typeDescription->dataType) {
  285. throwError("Input argument for \"", variable->name, "\" in \"", calledMethod->name, "\" must have the type \"", variable->typeDescription->name, "\"!\n");
  286. }
  287. }
  288. addMachineWord([](VirtualMachine& machine, PlanarMemory& memory, const List<VMA>& args) {
  289. // Get the method to call
  290. int calledMethodIndex = args[0].value.getMantissa();
  291. int oldMethodIndex = memory.current.methodIndex;
  292. Method* calledMethod = &machine.methods[calledMethodIndex];
  293. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  294. printText("Calling \"", calledMethod->name, "\".\n");
  295. #endif
  296. // Calculate new frame pointers
  297. int32_t newFramePointer[MAX_TYPE_COUNT] = {};
  298. int32_t newStackPointer[MAX_TYPE_COUNT] = {};
  299. for (int t = 0; t < MAX_TYPE_COUNT; t++) {
  300. newFramePointer[t] = memory.current.stackPointer[t];
  301. newStackPointer[t] = memory.current.stackPointer[t] + machine.methods[oldMethodIndex].count[t];
  302. }
  303. // Assign inputs
  304. for (int a = 1; a < args.length(); a++) {
  305. Variable* target = &calledMethod->locals[a - 1];
  306. DataType typeIndex = target->typeDescription->dataType;
  307. int targetStackIndex = target->getStackIndex(newFramePointer[typeIndex]);
  308. memory.store(targetStackIndex, args[a], memory.current.framePointer[typeIndex], typeIndex);
  309. }
  310. // Jump into the method
  311. memory.callStack.push(memory.current);
  312. memory.current.methodIndex = calledMethodIndex;
  313. memory.current.programCounter = machine.methods[calledMethodIndex].startAddress;
  314. for (int t = 0; t < MAX_TYPE_COUNT; t++) {
  315. memory.current.framePointer[t] = newFramePointer[t];
  316. memory.current.stackPointer[t] = newStackPointer[t];
  317. }
  318. }, inputArguments);
  319. // Get results from the method
  320. addMachineWord([](VirtualMachine& machine, PlanarMemory& memory, const List<VMA>& args) {
  321. int calledMethodIndex = args[0].value.getMantissa();
  322. Method* calledMethod = &machine.methods[calledMethodIndex];
  323. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  324. printText("Writing results after call to \"", calledMethod->name, "\":\n");
  325. #endif
  326. // Assign outputs
  327. for (int a = 1; a < args.length(); a++) {
  328. Variable* source = &calledMethod->locals[a - 1 + calledMethod->inputCount];
  329. DataType typeIndex = source->typeDescription->dataType;
  330. int sourceStackIndex = source->getStackIndex(memory.current.stackPointer[typeIndex]);
  331. memory.load(sourceStackIndex, args[a], memory.current.framePointer[typeIndex], typeIndex);
  332. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  333. printText(" ");
  334. machine.debugArgument(VMA(typeIndex, source->getGlobalIndex()), calledMethodIndex, memory.current.stackPointer, false);
  335. printText(" -> ");
  336. machine.debugArgument(args[a], memory.current.methodIndex, memory.current.framePointer, false);
  337. printText("\n");
  338. #endif
  339. }
  340. // TODO: Decrease reference counts for images by zeroing memory above the new stack-pointer
  341. // Avoiding temporary memory leaks and making sure that no cloning is needed for operations that clone if needed
  342. // Planar memory will receive a new memset operation for a range of stack indices for a given type
  343. memory.current.programCounter++;
  344. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  345. machine.debugPrintMemory();
  346. #endif
  347. }, outputArguments);
  348. }
  349. void VirtualMachine::interpretMachineWord(const ReadableString& command, const List<ReadableString>& arguments) {
  350. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  351. printText("interpretMachineWord @", this->machineWords.length(), " ", command, "(");
  352. for (int a = 0; a < arguments.length(); a++) {
  353. if (a > 0) { printText(", "); }
  354. printText(getArg(arguments, a));
  355. }
  356. printText(")\n");
  357. #endif
  358. if (string_caseInsensitiveMatch(command, U"Begin")) {
  359. if (this->methods.length() == 1) {
  360. // When more than one function exists, the init method must end with a return instruction
  361. // Otherwise it would start executing instructions in another method and crash
  362. this->addReturnInstruction();
  363. }
  364. this->methods.pushConstruct(getArg(arguments, 0), this->machineWords.length(), this->machineTypeCount);
  365. } else if (string_caseInsensitiveMatch(command, U"Temp")) {
  366. for (int a = 1; a < arguments.length(); a++) {
  367. this->declareVariable(methods.length() - 1, AccessType::Hidden, getArg(arguments, 0), getArg(arguments, a), false, U"");
  368. }
  369. } else if (string_caseInsensitiveMatch(command, U"Hidden")) {
  370. this->declareVariable(methods.length() - 1, AccessType::Hidden, getArg(arguments, 0), getArg(arguments, 1), true, getArg(arguments, 2));
  371. } else if (string_caseInsensitiveMatch(command, U"Input")) {
  372. this->declareVariable(methods.length() - 1, AccessType::Input, getArg(arguments, 0), getArg(arguments, 1), true, getArg(arguments, 2));
  373. } else if (string_caseInsensitiveMatch(command, U"Output")) {
  374. this->declareVariable(methods.length() - 1, AccessType::Output, getArg(arguments, 0), getArg(arguments, 1), true, getArg(arguments, 2));
  375. } else if (string_caseInsensitiveMatch(command, U"End")) {
  376. this->addReturnInstruction();
  377. } else if (string_caseInsensitiveMatch(command, U"Call")) {
  378. this->addCallInstructions(arguments);
  379. } else {
  380. int methodIndex = this->methods.length() - 1;
  381. List<VMA> resolvedArguments;
  382. for (int a = 0; a < arguments.length(); a++) {
  383. ReadableString content = string_removeOuterWhiteSpace(arguments[a]);
  384. if (content.length() > 0) {
  385. resolvedArguments.push(this->VMAfromText(methodIndex, getArg(arguments, a)));
  386. }
  387. }
  388. this->interpretCommand(command, resolvedArguments);
  389. }
  390. }
  391. void VirtualMachine::executeMethod(int methodIndex) {
  392. Method* rootMethod = &this->methods[methodIndex];
  393. #ifdef VIRTUAL_MACHINE_PROFILE
  394. if (rootMethod->instructionCount < 1) {
  395. // TODO: Assert that each method ends with a return or jump instruction after compiling
  396. printText("Cannot call \"", rootMethod->name, "\", because it doesn't have any instructions.\n");
  397. return;
  398. }
  399. #endif
  400. // Create a new current state
  401. this->memory->current.methodIndex = methodIndex;
  402. this->memory->current.programCounter = rootMethod->startAddress;
  403. for (int t = 0; t < this->machineTypeCount; t++) {
  404. int framePointer = this->methods[0].count[t];
  405. this->memory->current.framePointer[t] = framePointer;
  406. this->memory->current.stackPointer[t] = framePointer + this->methods[methodIndex].count[t];
  407. }
  408. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  409. this->debugPrintMemory();
  410. #endif
  411. #ifdef VIRTUAL_MACHINE_PROFILE
  412. printText("Calling \"", rootMethod->name, "\":\n");
  413. double startTime = time_getSeconds();
  414. #endif
  415. // Execute until the program counter is out of bound (-1)
  416. while (true) {
  417. int32_t pc = this->memory->current.programCounter;
  418. if (pc < 0 || pc >= this->machineWords.length()) {
  419. // Return statements will set the program counter to -1 if there are no more callers saved in the stack
  420. if (pc != -1) {
  421. throwError("Unexpected program counter! @", pc, " outside of 0..", (this->machineWords.length() - 1), "\n");
  422. }
  423. break;
  424. }
  425. MachineWord* word = &this->machineWords[pc];
  426. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  427. const InsSig* signature = getMachineInstructionFromFunction(word->operation);
  428. if (signature) {
  429. printText("Executing @", pc, " ", signature->name, "(");
  430. for (int a = signature->targetCount; a < word->args.length(); a++) {
  431. if (a > signature->targetCount) {
  432. printText(", ");
  433. }
  434. debugArgument(word->args[a], this->memory->current.methodIndex, this->memory->current.framePointer, false);
  435. }
  436. printText(")");
  437. }
  438. word->operation(*this, *(this->memory.get()), word->args);
  439. if (signature) {
  440. if (signature->targetCount > 0) {
  441. printText(" -> ");
  442. for (int a = 0; a < signature->targetCount; a++) {
  443. if (a > 0) {
  444. printText(", ");
  445. }
  446. debugArgument(word->args[a], this->memory->current.methodIndex, this->memory->current.framePointer, true);
  447. }
  448. }
  449. }
  450. printText("\n");
  451. #else
  452. word->operation(*this, *(this->memory.get()), word->args);
  453. #endif
  454. }
  455. #ifdef VIRTUAL_MACHINE_PROFILE
  456. double endTime = time_getSeconds();
  457. printText("Done calling \"", rootMethod->name, "\" after ", (endTime - startTime) * 1000000.0, " microseconds.\n");
  458. #ifdef VIRTUAL_MACHINE_DEBUG_PRINT
  459. printText(" (debug prints are active)\n");
  460. #endif
  461. #endif
  462. }