VirtualMachine.cpp 21 KB

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