1 module wren.vm; 2 import wren.core; 3 import wren.common; 4 import wren.opcodes; 5 import wren.value; 6 import wren.utils; 7 8 @nogc: 9 10 // The type of a primitive function. 11 // 12 // Primitives are similar to foreign functions, but have more direct access to 13 // VM internals. It is passed the arguments in [args]. If it returns a value, 14 // it places it in `args[0]` and returns `true`. If it causes a runtime error 15 // or modifies the running fiber, it returns `false`. 16 alias Primitive = bool function(WrenVM* vm, Value* args); 17 18 // A generic allocation function that handles all explicit memory management 19 // used by Wren. It's used like so: 20 // 21 // - To allocate new memory, [memory] is null and [newSize] is the desired 22 // size. It should return the allocated memory or null on failure. 23 // 24 // - To attempt to grow an existing allocation, [memory] is the memory, and 25 // [newSize] is the desired size. It should return [memory] if it was able to 26 // grow it in place, or a new pointer if it had to move it. 27 // 28 // - To shrink memory, [memory] and [newSize] are the same as above but it will 29 // always return [memory]. 30 // 31 // - To free memory, [memory] will be the memory to free and [newSize] will be 32 // zero. It should return null. 33 alias WrenReallocateFn = void* function(void* memory, size_t newSize, void* userData); 34 35 // A function callable from Wren code, but implemented in C. 36 alias WrenForeignMethodFn = void function(WrenVM* vm); 37 38 // A finalizer function for freeing resources owned by an instance of a foreign 39 // class. Unlike most foreign methods, finalizers do not have access to the VM 40 // and should not interact with it since it's in the middle of a garbage 41 // collection. 42 alias WrenFinalizerFn = void function(void* data); 43 44 // Gives the host a chance to canonicalize the imported module name, 45 // potentially taking into account the (previously resolved) name of the module 46 // that contains the import. Typically, this is used to implement relative 47 // imports. 48 alias WrenResolveModuleFn = const(char)* function(WrenVM* vm, 49 const(char)* importer, const(char)* name); 50 51 // Called after loadModuleFn is called for module [name]. The original returned result 52 // is handed back to you in this callback, so that you can free memory if appropriate. 53 alias WrenLoadModuleCompleteFn = void function(WrenVM* vm, const(char)* name, WrenLoadModuleResult result); 54 55 // The result of a loadModuleFn call. 56 // [source] is the source code for the module, or null if the module is not found. 57 // [onComplete] an optional callback that will be called once Wren is done with the result. 58 struct WrenLoadModuleResult 59 { 60 const(char)* source; 61 WrenLoadModuleCompleteFn onComplete; 62 void* userData; 63 } 64 65 // Loads and returns the source code for the module [name]. 66 alias WrenLoadModuleFn = WrenLoadModuleResult function(WrenVM* vm, const(char)* name); 67 68 // Returns a pointer to a foreign method on [className] in [module] with 69 // [signature]. 70 alias WrenBindForeignMethodFn = WrenForeignMethodFn function(WrenVM* vm, 71 const(char)* module_, const(char)* className, bool isStatic, 72 const(char)* signature); 73 74 // Displays a string of text to the user. 75 alias WrenWriteFn = void function(WrenVM* vm, const(char)* text); 76 77 enum WrenErrorType 78 { 79 // A syntax or resolution error detected at compile time. 80 WREN_ERROR_COMPILE, 81 82 // The error message for a runtime error. 83 WREN_ERROR_RUNTIME, 84 85 // One entry of a runtime error's stack trace. 86 WREN_ERROR_STACK_TRACE 87 } 88 89 // Reports an error to the user. 90 // 91 // An error detected during compile time is reported by calling this once with 92 // [type] `WREN_ERROR_COMPILE`, the resolved name of the [module] and [line] 93 // where the error occurs, and the compiler's error [message]. 94 // 95 // A runtime error is reported by calling this once with [type] 96 // `WREN_ERROR_RUNTIME`, no [module] or [line], and the runtime error's 97 // [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are 98 // made for each line in the stack trace. Each of those has the resolved 99 // [module] and [line] where the method or function is defined and [message] is 100 // the name of the method or function. 101 alias WrenErrorFn = void function(WrenVM* vm, WrenErrorType type, const(char)* module_, 102 int line, const(char)* message); 103 104 struct WrenForeignClassMethods 105 { 106 // The callback invoked when the foreign object is created. 107 // 108 // This must be provided. Inside the body of this, it must call 109 // [wrenSetSlotNewForeign()] exactly once. 110 WrenForeignMethodFn allocate; 111 112 // The callback invoked when the garbage collector is about to collect a 113 // foreign object's memory. 114 // 115 // This may be `null` if the foreign class does not need to finalize. 116 WrenFinalizerFn finalize; 117 } 118 119 alias WrenBindForeignClassFn = WrenForeignClassMethods function( 120 WrenVM* vm, const(char)* module_, const(char)* className); 121 122 struct WrenConfiguration 123 { 124 // The callback Wren will use to allocate, reallocate, and deallocate memory. 125 // 126 // If `null`, defaults to a built-in function that uses `realloc` and `free`. 127 WrenReallocateFn reallocateFn; 128 129 // The callback Wren uses to resolve a module name. 130 // 131 // Some host applications may wish to support "relative" imports, where the 132 // meaning of an import string depends on the module that contains it. To 133 // support that without baking any policy into Wren itself, the VM gives the 134 // host a chance to resolve an import string. 135 // 136 // Before an import is loaded, it calls this, passing in the name of the 137 // module that contains the import and the import string. The host app can 138 // look at both of those and produce a new "canonical" string that uniquely 139 // identifies the module. This string is then used as the name of the module 140 // going forward. It is what is passed to [loadModuleFn], how duplicate 141 // imports of the same module are detected, and how the module is reported in 142 // stack traces. 143 // 144 // If you leave this function null, then the original import string is 145 // treated as the resolved string. 146 // 147 // If an import cannot be resolved by the embedder, it should return null and 148 // Wren will report that as a runtime error. 149 // 150 // Wren will take ownership of the string you return and free it for you, so 151 // it should be allocated using the same allocation function you provide 152 // above. 153 WrenResolveModuleFn resolveModuleFn; 154 155 // The callback Wren uses to load a module. 156 // 157 // Since Wren does not talk directly to the file system, it relies on the 158 // embedder to physically locate and read the source code for a module. The 159 // first time an import appears, Wren will call this and pass in the name of 160 // the module being imported. The method will return a result, which contains 161 // the source code for that module. Memory for the source is owned by the 162 // host application, and can be freed using the onComplete callback. 163 // 164 // This will only be called once for any given module name. Wren caches the 165 // result internally so subsequent imports of the same module will use the 166 // previous source and not call this. 167 // 168 // If a module with the given name could not be found by the embedder, it 169 // should return null and Wren will report that as a runtime error. 170 WrenLoadModuleFn loadModuleFn; 171 172 // The callback Wren uses to find a foreign method and bind it to a class. 173 // 174 // When a foreign method is declared in a class, this will be called with the 175 // foreign method's module, class, and signature when the class body is 176 // executed. It should return a pointer to the foreign function that will be 177 // bound to that method. 178 // 179 // If the foreign function could not be found, this should return null and 180 // Wren will report it as runtime error. 181 WrenBindForeignMethodFn bindForeignMethodFn; 182 183 // The callback Wren uses to find a foreign class and get its foreign methods. 184 // 185 // When a foreign class is declared, this will be called with the class's 186 // module and name when the class body is executed. It should return the 187 // foreign functions uses to allocate and (optionally) finalize the bytes 188 // stored in the foreign object when an instance is created. 189 WrenBindForeignClassFn bindForeignClassFn; 190 191 // The callback Wren uses to display text when `System.print()` or the other 192 // related functions are called. 193 // 194 // If this is `null`, Wren discards any printed text. 195 WrenWriteFn writeFn; 196 197 // The callback Wren uses to report errors. 198 // 199 // When an error occurs, this will be called with the module name, line 200 // number, and an error message. If this is `null`, Wren doesn't report any 201 // errors. 202 WrenErrorFn errorFn; 203 204 // The number of bytes Wren will allocate before triggering the first garbage 205 // collection. 206 // 207 // If zero, defaults to 10MB. 208 size_t initialHeapSize; 209 210 // After a collection occurs, the threshold for the next collection is 211 // determined based on the number of bytes remaining in use. This allows Wren 212 // to shrink its memory usage automatically after reclaiming a large amount 213 // of memory. 214 // 215 // This can be used to ensure that the heap does not get too small, which can 216 // in turn lead to a large number of collections afterwards as the heap grows 217 // back to a usable size. 218 // 219 // If zero, defaults to 1MB. 220 size_t minHeapSize; 221 222 // Wren will resize the heap automatically as the number of bytes 223 // remaining in use after a collection changes. This number determines the 224 // amount of additional memory Wren will use after a collection, as a 225 // percentage of the current heap size. 226 // 227 // For example, say that this is 50. After a garbage collection, when there 228 // are 400 bytes of memory still in use, the next collection will be triggered 229 // after a total of 600 bytes are allocated (including the 400 already in 230 // use.) 231 // 232 // Setting this to a smaller number wastes less memory, but triggers more 233 // frequent garbage collections. 234 // 235 // If zero, defaults to 50. 236 int heapGrowthPercent; 237 238 // User-defined data associated with the VM. 239 void* userData; 240 } 241 242 enum WrenInterpretResult 243 { 244 WREN_RESULT_SUCCESS, 245 WREN_RESULT_COMPILE_ERROR, 246 WREN_RESULT_RUNTIME_ERROR 247 } 248 249 // The type of an object stored in a slot. 250 // 251 // This is not necessarily the object's *class*, but instead its low level 252 // representation type. 253 enum WrenType 254 { 255 WREN_TYPE_BOOL, 256 WREN_TYPE_NUM, 257 WREN_TYPE_FOREIGN, 258 WREN_TYPE_LIST, 259 WREN_TYPE_MAP, 260 WREN_TYPE_NULL, 261 WREN_TYPE_STRING, 262 263 // The object is of a type that isn't accessible by the C API. 264 WREN_TYPE_UNKNOWN 265 } 266 267 // The maximum number of temporary objects that can be made visible to the GC 268 // at one time. 269 enum WREN_MAX_TEMP_ROOTS = 8; 270 271 struct WrenVM 272 { 273 ObjClass* boolClass; 274 ObjClass* classClass; 275 ObjClass* fiberClass; 276 ObjClass* fnClass; 277 ObjClass* listClass; 278 ObjClass* mapClass; 279 ObjClass* nullClass; 280 ObjClass* numClass; 281 ObjClass* objectClass; 282 ObjClass* rangeClass; 283 ObjClass* stringClass; 284 285 // The fiber that is currently running. 286 ObjFiber* fiber; 287 288 // The loaded modules. Each key is an ObjString (except for the main module, 289 // whose key is null) for the module's name and the value is the ObjModule 290 // for the module. 291 ObjMap* modules; 292 293 // The most recently imported module. More specifically, the module whose 294 // code has most recently finished executing. 295 // 296 // Not treated like a GC root since the module is already in [modules]. 297 ObjModule* lastModule; 298 299 // Memory management data: 300 301 // The number of bytes that are known to be currently allocated. Includes all 302 // memory that was proven live after the last GC, as well as any new bytes 303 // that were allocated since then. Does *not* include bytes for objects that 304 // were freed since the last GC. 305 size_t bytesAllocated; 306 307 // The number of total allocated bytes that will trigger the next GC. 308 size_t nextGC; 309 310 // The first object in the linked list of all currently allocated objects. 311 Obj* first; 312 313 // The "gray" set for the garbage collector. This is the stack of unprocessed 314 // objects while a garbage collection pass is in process. 315 Obj** gray; 316 int grayCount; 317 int grayCapacity; 318 319 // The list of temporary roots. This is for temporary or new objects that are 320 // not otherwise reachable but should not be collected. 321 // 322 // They are organized as a stack of pointers stored in this array. This 323 // implies that temporary roots need to have stack semantics: only the most 324 // recently pushed object can be released. 325 Obj*[WREN_MAX_TEMP_ROOTS] tempRoots; 326 327 int numTempRoots; 328 329 // Pointer to the first node in the linked list of active handles or null if 330 // there are none. 331 WrenHandle* handles; 332 333 // Pointer to the bottom of the range of stack slots available for use from 334 // the C API. During a foreign method, this will be in the stack of the fiber 335 // that is executing a method. 336 // 337 // If not in a foreign method, this is initially null. If the user requests 338 // slots by calling wrenEnsureSlots(), a stack is created and this is 339 // initialized. 340 Value* apiStack; 341 342 WrenConfiguration config; 343 344 // Compiler and debugger data: 345 346 // The compiler that is currently compiling code. This is used so that heap 347 // allocated objects used by the compiler can be found if a GC is kicked off 348 // in the middle of a compile. 349 import wren.compiler : Compiler; 350 Compiler* compiler; 351 352 // There is a single global symbol table for all method names on all classes. 353 // Method calls are dispatched directly by index in this table. 354 SymbolTable methodNames; 355 } 356 357 // The behavior of realloc() when the size is 0 is implementation defined. It 358 // may return a non-null pointer which must not be dereferenced but nevertheless 359 // should be freed. To prevent that, we avoid calling realloc() with a zero 360 // size. 361 static void* defaultReallocate(void* ptr, size_t newSize, void* _) 362 { 363 import core.stdc.stdlib : free, realloc; 364 365 if (newSize == 0) 366 { 367 free(ptr); 368 return null; 369 } 370 371 return realloc(ptr, newSize); 372 } 373 374 void wrenInitConfiguration(WrenConfiguration* config) 375 { 376 config.reallocateFn = &defaultReallocate; 377 config.resolveModuleFn = null; 378 config.loadModuleFn = null; 379 config.bindForeignMethodFn = null; 380 config.bindForeignClassFn = null; 381 config.writeFn = null; 382 config.errorFn = null; 383 config.initialHeapSize = 1024 * 1024 * 10; 384 config.minHeapSize = 1024 * 1024; 385 config.heapGrowthPercent = 50; 386 config.userData = null; 387 } 388 389 WrenVM* wrenNewVM(WrenConfiguration* config) 390 { 391 import core.stdc.string : memset, memcpy; 392 WrenReallocateFn reallocate = &defaultReallocate; 393 void* userData = null; 394 if (config != null) { 395 userData = config.userData; 396 reallocate = config.reallocateFn ? config.reallocateFn : &defaultReallocate; 397 } 398 399 WrenVM* vm = cast(WrenVM*)reallocate(null, WrenVM.sizeof, userData); 400 memset(vm, 0, WrenVM.sizeof); 401 402 // Copy the configuration if given one. 403 if (config != null) 404 { 405 memcpy(&vm.config, config, WrenConfiguration.sizeof); 406 // We choose to set this after copying, 407 // rather than modifying the user config pointer 408 vm.config.reallocateFn = reallocate; 409 } 410 else 411 { 412 wrenInitConfiguration(&vm.config); 413 } 414 415 // TODO: Should we allocate and free this during a GC? 416 vm.grayCount = 0; 417 // TODO: Tune this. 418 vm.grayCapacity = 4; 419 vm.gray = cast(Obj**)reallocate(null, vm.grayCapacity * (Obj*).sizeof, userData); 420 vm.nextGC = vm.config.initialHeapSize; 421 422 wrenSymbolTableInit(&vm.methodNames); 423 424 vm.modules = wrenNewMap(vm); 425 wrenInitializeCore(vm); 426 return vm; 427 } 428 429 void wrenFreeVM(WrenVM* vm) 430 { 431 assert(vm.methodNames.count > 0, "VM appears to have already been freed."); 432 433 // Free all of the GC objects. 434 Obj* obj = vm.first; 435 while (obj != null) 436 { 437 Obj* next = obj.next; 438 wrenFreeObj(vm, obj); 439 obj = next; 440 } 441 442 // Free up the GC gray set. 443 vm.gray = cast(Obj**)vm.config.reallocateFn(vm.gray, 0, vm.config.userData); 444 445 // Tell the user if they didn't free any handles. We don't want to just free 446 // them here because the host app may still have pointers to them that they 447 // may try to use. Better to tell them about the bug early. 448 assert(vm.handles == null, "All handles have not been released."); 449 450 wrenSymbolTableClear(vm, &vm.methodNames); 451 452 DEALLOCATE(vm, vm); 453 } 454 455 void wrenCollectGarbage(WrenVM* vm) 456 { 457 static if (WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC) 458 { 459 import core.stdc.stdio : printf; 460 import core.stdc.time; 461 printf("-- gc --\n"); 462 463 size_t before = vm.bytesAllocated; 464 double startTime = cast(double)clock() / CLOCKS_PER_SEC; 465 } 466 467 // Mark all reachable objects. 468 469 // Reset this. As we mark objects, their size will be counted again so that 470 // we can track how much memory is in use without needing to know the size 471 // of each *freed* object. 472 // 473 // This is important because when freeing an unmarked object, we don't always 474 // know how much memory it is using. For example, when freeing an instance, 475 // we need to know its class to know how big it is, but its class may have 476 // already been freed. 477 vm.bytesAllocated = 0; 478 479 wrenGrayObj(vm, cast(Obj*)vm.modules); 480 481 // Temporary roots. 482 for (int i = 0; i < vm.numTempRoots; i++) 483 { 484 wrenGrayObj(vm, vm.tempRoots[i]); 485 } 486 487 // The current fiber. 488 wrenGrayObj(vm, cast(Obj*)vm.fiber); 489 490 // The handles. 491 for (WrenHandle* handle = vm.handles; 492 handle != null; 493 handle = handle.next) 494 { 495 wrenGrayValue(vm, handle.value); 496 } 497 498 // Any object the compiler is using (if there is one). 499 import wren.compiler : wrenMarkCompiler; 500 if (vm.compiler != null) wrenMarkCompiler(vm, vm.compiler); 501 502 // Method names. 503 wrenBlackenSymbolTable(vm, &vm.methodNames); 504 505 // Now that we have grayed the roots, do a depth-first search over all of the 506 // reachable objects. 507 wrenBlackenObjects(vm); 508 509 // Collect the white objects. 510 Obj** obj = &vm.first; 511 while (*obj != null) 512 { 513 if (!((*obj).isDark)) 514 { 515 // This object wasn't reached, so remove it from the list and free it. 516 Obj* unreached = *obj; 517 *obj = unreached.next; 518 wrenFreeObj(vm, unreached); 519 } 520 else 521 { 522 // This object was reached, so unmark it (for the next GC) and move on to 523 // the next. 524 (*obj).isDark = false; 525 obj = &(*obj).next; 526 } 527 } 528 529 // Calculate the next gc point, this is the current allocation plus 530 // a configured percentage of the current allocation. 531 vm.nextGC = vm.bytesAllocated + ((vm.bytesAllocated * vm.config.heapGrowthPercent) / 100); 532 if (vm.nextGC < vm.config.minHeapSize) vm.nextGC = vm.config.minHeapSize; 533 534 static if (WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC) 535 { 536 double elapsed = (cast(double)clock / CLOCKS_PER_SEC) - startTime; 537 // Explicit cast because size_t has different sizes on 32-bit and 64-bit and 538 // we need a consistent type for the format string. 539 printf("GC %lu before, %lu after (%lu collected), next at %lu. Took %.3fms.\n", 540 cast(ulong)before, 541 cast(ulong)vm.bytesAllocated, 542 cast(ulong)(before - vm.bytesAllocated), 543 cast(ulong)vm.nextGC, 544 elapsed*1000.0); 545 } 546 } 547 548 // A generic allocation function that handles all explicit memory management. 549 // It's used like so: 550 // 551 // - To allocate new memory, [memory] is null and [oldSize] is zero. It should 552 // return the allocated memory or null on failure. 553 // 554 // - To attempt to grow an existing allocation, [memory] is the memory, 555 // [oldSize] is its previous size, and [newSize] is the desired size. 556 // It should return [memory] if it was able to grow it in place, or a new 557 // pointer if it had to move it. 558 // 559 // - To shrink memory, [memory], [oldSize], and [newSize] are the same as above 560 // but it will always return [memory]. 561 // 562 // - To free memory, [memory] will be the memory to free and [newSize] and 563 // [oldSize] will be zero. It should return null. 564 void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize) 565 { 566 static if (WREN_DEBUG_TRACE_MEMORY) 567 { 568 import core.stdc.stdio : printf; 569 printf("reallocate %p %lu . %lu", 570 memory, cast(ulong)oldSize, cast(ulong)newSize); 571 } 572 573 // If new bytes are being allocated, add them to the total count. If objects 574 // are being completely deallocated, we don't track that (since we don't 575 // track the original size). Instead, that will be handled while marking 576 // during the next GC. 577 vm.bytesAllocated += newSize - oldSize; 578 579 static if (WREN_DEBUG_GC_STRESS) 580 { 581 // Since collecting calls this function to free things, make sure we don't 582 // recurse. 583 if (newSize > 0) wrenCollectGarbage(vm); 584 } 585 else 586 { 587 if (newSize > 0 && vm.bytesAllocated > vm.nextGC) wrenCollectGarbage(vm); 588 } 589 590 return vm.config.reallocateFn(memory, newSize, vm.config.userData); 591 } 592 593 // Captures the local variable [local] into an [Upvalue]. If that local is 594 // already in an upvalue, the existing one will be used. (This is important to 595 // ensure that multiple closures closing over the same variable actually see 596 // the same variable.) Otherwise, it will create a new open upvalue and add it 597 // the fiber's list of upvalues. 598 static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local) 599 { 600 // If there are no open upvalues at all, we must need a new one. 601 if (fiber.openUpvalues == null) 602 { 603 fiber.openUpvalues = wrenNewUpvalue(vm, local); 604 return fiber.openUpvalues; 605 } 606 607 ObjUpvalue* prevUpvalue = null; 608 ObjUpvalue* upvalue = fiber.openUpvalues; 609 610 // Walk towards the bottom of the stack until we find a previously existing 611 // upvalue or pass where it should be. 612 while (upvalue != null && upvalue.value > local) 613 { 614 prevUpvalue = upvalue; 615 upvalue = upvalue.next; 616 } 617 618 // Found an existing upvalue for this local. 619 if (upvalue != null && upvalue.value == local) return upvalue; 620 621 // We've walked past this local on the stack, so there must not be an 622 // upvalue for it already. Make a new one and link it in in the right 623 // place to keep the list sorted. 624 ObjUpvalue* createdUpvalue = wrenNewUpvalue(vm, local); 625 if (prevUpvalue == null) 626 { 627 // The new one is the first one in the list. 628 fiber.openUpvalues = createdUpvalue; 629 } 630 else 631 { 632 prevUpvalue.next = createdUpvalue; 633 } 634 635 createdUpvalue.next = upvalue; 636 return createdUpvalue; 637 } 638 639 // Closes any open upvalues that have been created for stack slots at [last] 640 // and above. 641 static void closeUpvalues(ObjFiber* fiber, Value* last) 642 { 643 while (fiber.openUpvalues != null && 644 fiber.openUpvalues.value >= last) 645 { 646 ObjUpvalue* upvalue = fiber.openUpvalues; 647 648 // Move the value into the upvalue itself and point the upvalue to it. 649 upvalue.closed = *upvalue.value; 650 upvalue.value = &upvalue.closed; 651 652 // Remove it from the open upvalue list. 653 fiber.openUpvalues = upvalue.next; 654 } 655 } 656 657 // Looks up a foreign method in [moduleName] on [className] with [signature]. 658 // 659 // This will try the host's foreign method binder first. If that fails, it 660 // falls back to handling the built-in modules. 661 static WrenForeignMethodFn findForeignMethod(WrenVM* vm, 662 const char* moduleName, 663 const char* className, 664 bool isStatic, 665 const char* signature) 666 { 667 WrenForeignMethodFn method = null; 668 669 if (vm.config.bindForeignMethodFn != null) 670 { 671 method = vm.config.bindForeignMethodFn(vm, moduleName, className, isStatic, 672 signature); 673 } 674 675 // If the host didn't provide it, see if it's an optional one. 676 if (method == null) 677 { 678 import core.stdc.string : strcmp; 679 static if (WREN_OPT_META) { 680 if (strcmp(moduleName, "meta") == 0) 681 { 682 import wren.optional.meta : wrenMetaBindForeignMethod; 683 method = wrenMetaBindForeignMethod(vm, className, isStatic, signature); 684 } 685 } 686 static if (WREN_OPT_RANDOM) { 687 if (strcmp(moduleName, "random") == 0) 688 { 689 import wren.optional.random : wrenRandomBindForeignMethod; 690 method = wrenRandomBindForeignMethod(vm, className, isStatic, signature); 691 } 692 } 693 } 694 695 return method; 696 } 697 698 // Defines [methodValue] as a method on [classObj]. 699 // 700 // Handles both foreign methods where [methodValue] is a string containing the 701 // method's signature and Wren methods where [methodValue] is a function. 702 // 703 // Aborts the current fiber if the method is a foreign method that could not be 704 // found. 705 static void bindMethod(WrenVM* vm, int methodType, int symbol, 706 ObjModule* module_, ObjClass* classObj, Value methodValue) 707 { 708 const(char)* className = classObj.name.value.ptr; 709 if (methodType == Code.CODE_METHOD_STATIC) classObj = classObj.obj.classObj; 710 711 Method method; 712 if (IS_STRING(methodValue)) 713 { 714 const(char)* name = AS_CSTRING(methodValue); 715 method.type = MethodType.METHOD_FOREIGN; 716 method.as.foreign = findForeignMethod(vm, module_.name.value.ptr, 717 className, 718 methodType == Code.CODE_METHOD_STATIC, 719 name); 720 721 if (method.as.foreign == null) 722 { 723 vm.fiber.error = wrenStringFormat(vm, 724 "Could not find foreign method '@' for class $ in module '$'.".ptr, 725 methodValue, classObj.name.value.ptr, module_.name.value.ptr); 726 return; 727 } 728 } 729 else 730 { 731 import wren.compiler : wrenBindMethodCode; 732 733 method.as.closure = AS_CLOSURE(methodValue); 734 method.type = MethodType.METHOD_BLOCK; 735 736 // Patch up the bytecode now that we know the superclass. 737 wrenBindMethodCode(classObj, method.as.closure.fn); 738 } 739 740 wrenBindMethod(vm, classObj, symbol, method); 741 } 742 743 static void callForeign(WrenVM* vm, ObjFiber* fiber, 744 WrenForeignMethodFn foreign, int numArgs) 745 { 746 assert(vm.apiStack == null, "Cannot already be in foreign call."); 747 vm.apiStack = fiber.stackTop - numArgs; 748 749 foreign(vm); 750 751 // Discard the stack slots for the arguments and temporaries but leave one 752 // for the result. 753 fiber.stackTop = vm.apiStack + 1; 754 755 vm.apiStack = null; 756 } 757 758 // Handles the current fiber having aborted because of an error. 759 // 760 // Walks the call chain of fibers, aborting each one until it hits a fiber that 761 // handles the error. If none do, tells the VM to stop. 762 static void runtimeError(WrenVM* vm) 763 { 764 assert(wrenHasError(vm.fiber), "Should only call this after an error."); 765 766 ObjFiber* current = vm.fiber; 767 Value error = current.error; 768 769 while (current != null) 770 { 771 // Every fiber along the call chain gets aborted with the same error. 772 current.error = error; 773 774 // If the caller ran this fiber using "try", give it the error and stop. 775 if (current.state == FiberState.FIBER_TRY) 776 { 777 // Make the caller's try method return the error message. 778 current.caller.stackTop[-1] = vm.fiber.error; 779 vm.fiber = current.caller; 780 return; 781 } 782 783 // Otherwise, unhook the caller since we will never resume and return to it. 784 ObjFiber* caller = current.caller; 785 current.caller = null; 786 current = caller; 787 } 788 789 // If we got here, nothing caught the error, so show the stack trace. 790 import wren.dbg : wrenDebugPrintStackTrace; 791 wrenDebugPrintStackTrace(vm); 792 vm.fiber = null; 793 vm.apiStack = null; 794 } 795 796 // Aborts the current fiber with an appropriate method not found error for a 797 // method with [symbol] on [classObj]. 798 static void methodNotFound(WrenVM* vm, ObjClass* classObj, int symbol) 799 { 800 vm.fiber.error = wrenStringFormat(vm, "@ does not implement '$'.".ptr, 801 OBJ_VAL(classObj.name), vm.methodNames.data[symbol].value.ptr); 802 } 803 804 // Looks up the previously loaded module with [name]. 805 // 806 // Returns `null` if no module with that name has been loaded. 807 static ObjModule* getModule(WrenVM* vm, Value name) 808 { 809 Value moduleValue = wrenMapGet(vm.modules, name); 810 return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : null; 811 } 812 813 static ObjClosure* compileInModule(WrenVM* vm, Value name, const char* source, 814 bool isExpression, bool printErrors) 815 { 816 // See if the module has already been loaded. 817 ObjModule* module_ = getModule(vm, name); 818 if (module_ == null) 819 { 820 module_ = wrenNewModule(vm, AS_STRING(name)); 821 822 // It's possible for the wrenMapSet below to resize the modules map, 823 // and trigger a GC while doing so. When this happens it will collect 824 // the module we've just created. Once in the map it is safe. 825 wrenPushRoot(vm, cast(Obj*)module_); 826 827 // Store it in the VM's module registry so we don't load the same module 828 // multiple times. 829 wrenMapSet(vm, vm.modules, name, OBJ_VAL(module_)); 830 831 wrenPopRoot(vm); 832 833 // Implicitly import the core module. 834 ObjModule* coreModule = getModule(vm, NULL_VAL); 835 for (int i = 0; i < coreModule.variables.count; i++) 836 { 837 wrenDefineVariable(vm, module_, 838 coreModule.variableNames.data[i].value.ptr, 839 coreModule.variableNames.data[i].length, 840 coreModule.variables.data[i], null); 841 } 842 } 843 844 import wren.compiler : wrenCompile; 845 ObjFn* fn = wrenCompile(vm, module_, source, isExpression, printErrors); 846 if (fn == null) 847 { 848 // TODO: Should we still store the module even if it didn't compile? 849 return null; 850 } 851 852 // Functions are always wrapped in closures. 853 wrenPushRoot(vm, cast(Obj*)fn); 854 ObjClosure* closure = wrenNewClosure(vm, fn); 855 wrenPopRoot(vm); // fn. 856 857 return closure; 858 } 859 860 // Verifies that [superclassValue] is a valid object to inherit from. That 861 // means it must be a class and cannot be the class of any built-in type. 862 // 863 // Also validates that it doesn't result in a class with too many fields and 864 // the other limitations foreign classes have. 865 // 866 // If successful, returns `null`. Otherwise, returns a string for the runtime 867 // error message. 868 static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue, 869 int numFields) 870 { 871 // Make sure the superclass is a class. 872 if (!IS_CLASS(superclassValue)) 873 { 874 return wrenStringFormat(vm, 875 "Class '@' cannot inherit from a non-class object.".ptr, 876 name); 877 } 878 879 // Make sure it doesn't inherit from a sealed built-in type. Primitive methods 880 // on these classes assume the instance is one of the other Obj___ types and 881 // will fail horribly if it's actually an ObjInstance. 882 ObjClass* superclass = AS_CLASS(superclassValue); 883 if (superclass == vm.classClass || 884 superclass == vm.fiberClass || 885 superclass == vm.fnClass || // Includes OBJ_CLOSURE. 886 superclass == vm.listClass || 887 superclass == vm.mapClass || 888 superclass == vm.rangeClass || 889 superclass == vm.stringClass || 890 superclass == vm.boolClass || 891 superclass == vm.nullClass || 892 superclass == vm.numClass) 893 { 894 return wrenStringFormat(vm, 895 "Class '@' cannot inherit from built-in class '@'.", 896 name, OBJ_VAL(superclass.name)); 897 } 898 899 if (superclass.numFields == -1) 900 { 901 return wrenStringFormat(vm, 902 "Class '@' cannot inherit from foreign class '@'.", 903 name, OBJ_VAL(superclass.name)); 904 } 905 906 if (numFields == -1 && superclass.numFields > 0) 907 { 908 return wrenStringFormat(vm, 909 "Foreign class '@' may not inherit from a class with fields.", 910 name); 911 } 912 913 if (superclass.numFields + numFields > MAX_FIELDS) 914 { 915 return wrenStringFormat(vm, 916 "Class '@' may not have more than 255 fields, including inherited " 917 ~ "ones.", name); 918 } 919 920 return NULL_VAL; 921 } 922 923 static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module_) 924 { 925 WrenForeignClassMethods methods; 926 methods.allocate = null; 927 methods.finalize = null; 928 929 // Check the optional built-in module first so the host can override it. 930 931 if (vm.config.bindForeignClassFn != null) 932 { 933 methods = vm.config.bindForeignClassFn(vm, module_.name.value.ptr, 934 classObj.name.value.ptr); 935 } 936 937 // If the host didn't provide it, see if it's a built in optional module. 938 if (methods.allocate == null && methods.finalize == null) 939 { 940 static if (WREN_OPT_RANDOM) { 941 import core.stdc.string : strcmp; 942 if (strcmp(module_.name.value.ptr, "random") == 0) 943 { 944 import wren.optional.random : wrenRandomBindForeignClass; 945 methods = wrenRandomBindForeignClass(vm, module_.name.value.ptr, 946 classObj.name.value.ptr); 947 } 948 } 949 } 950 951 Method method; 952 method.type = MethodType.METHOD_FOREIGN; 953 954 // Add the symbol even if there is no allocator so we can ensure that the 955 // symbol itself is always in the symbol table. 956 int symbol = wrenSymbolTableEnsure(vm, &vm.methodNames, "<allocate>", 10); 957 if (methods.allocate != null) 958 { 959 method.as.foreign = methods.allocate; 960 wrenBindMethod(vm, classObj, symbol, method); 961 } 962 963 // Add the symbol even if there is no finalizer so we can ensure that the 964 // symbol itself is always in the symbol table. 965 symbol = wrenSymbolTableEnsure(vm, &vm.methodNames, "<finalize>", 10); 966 if (methods.finalize != null) 967 { 968 method.as.foreign = cast(WrenForeignMethodFn)methods.finalize; 969 wrenBindMethod(vm, classObj, symbol, method); 970 } 971 } 972 973 // Completes the process for creating a new class. 974 // 975 // The class attributes instance and the class itself should be on the 976 // top of the fiber's stack. 977 // 978 // This process handles moving the attribute data for a class from 979 // compile time to runtime, since it now has all the attributes associated 980 // with a class, including for methods. 981 static void endClass(WrenVM* vm) 982 { 983 // Pull the attributes and class off the stack 984 Value attributes = vm.fiber.stackTop[-2]; 985 Value classValue = vm.fiber.stackTop[-1]; 986 987 // Remove the stack items 988 vm.fiber.stackTop -= 2; 989 990 ObjClass* classObj = AS_CLASS(classValue); 991 classObj.attributes = attributes; 992 } 993 994 // Creates a new class. 995 // 996 // If [numFields] is -1, the class is a foreign class. The name and superclass 997 // should be on top of the fiber's stack. After calling this, the top of the 998 // stack will contain the new class. 999 // 1000 // Aborts the current fiber if an error occurs. 1001 static void createClass(WrenVM* vm, int numFields, ObjModule* module_) 1002 { 1003 // Pull the name and superclass off the stack. 1004 Value name = vm.fiber.stackTop[-2]; 1005 Value superclass = vm.fiber.stackTop[-1]; 1006 1007 // We have two values on the stack and we are going to leave one, so discard 1008 // the other slot. 1009 vm.fiber.stackTop--; 1010 1011 vm.fiber.error = validateSuperclass(vm, name, superclass, numFields); 1012 if (wrenHasError(vm.fiber)) return; 1013 1014 ObjClass* classObj = wrenNewClass(vm, AS_CLASS(superclass), numFields, 1015 AS_STRING(name)); 1016 vm.fiber.stackTop[-1] = OBJ_VAL(classObj); 1017 1018 if (numFields == -1) bindForeignClass(vm, classObj, module_); 1019 } 1020 1021 static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack) 1022 { 1023 ObjClass* classObj = AS_CLASS(stack[0]); 1024 assert(classObj.numFields == -1, "Class must be a foreign class."); 1025 1026 // TODO: Don't look up every time. 1027 int symbol = wrenSymbolTableFind(&vm.methodNames, "<allocate>", 10); 1028 assert(symbol != -1, "Should have defined <allocate> symbol."); 1029 1030 assert(classObj.methods.count > symbol, "Class should have allocator."); 1031 Method* method = &classObj.methods.data[symbol]; 1032 assert(method.type == MethodType.METHOD_FOREIGN, "Allocator should be foreign."); 1033 1034 // Pass the constructor arguments to the allocator as well. 1035 assert(vm.apiStack == null, "Cannot already be in foreign call."); 1036 vm.apiStack = stack; 1037 1038 method.as.foreign(vm); 1039 1040 vm.apiStack = null; 1041 } 1042 1043 void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign) 1044 { 1045 // TODO: Don't look up every time. 1046 int symbol = wrenSymbolTableFind(&vm.methodNames, "<finalize>", 10); 1047 assert(symbol != -1, "Should have defined <finalize> symbol."); 1048 1049 // If there are no finalizers, don't finalize it. 1050 if (symbol == -1) return; 1051 1052 // If the class doesn't have a finalizer, bail out. 1053 ObjClass* classObj = foreign.obj.classObj; 1054 if (symbol >= classObj.methods.count) return; 1055 1056 Method* method = &classObj.methods.data[symbol]; 1057 if (method.type == MethodType.METHOD_NONE) return; 1058 1059 assert(method.type == MethodType.METHOD_FOREIGN, "Finalizer should be foreign."); 1060 1061 WrenFinalizerFn finalizer = cast(WrenFinalizerFn)method.as.foreign; 1062 finalizer(foreign.data.ptr); 1063 } 1064 1065 // Let the host resolve an imported module name if it wants to. 1066 static Value resolveModule(WrenVM* vm, Value name) 1067 { 1068 // If the host doesn't care to resolve, leave the name alone. 1069 if (vm.config.resolveModuleFn == null) return name; 1070 1071 ObjFiber* fiber = vm.fiber; 1072 ObjFn* fn = fiber.frames[fiber.numFrames - 1].closure.fn; 1073 ObjString* importer = fn.module_.name; 1074 1075 const(char)* resolved = vm.config.resolveModuleFn(vm, importer.value.ptr, 1076 AS_CSTRING(name)); 1077 if (resolved == null) 1078 { 1079 vm.fiber.error = wrenStringFormat(vm, 1080 "Could not resolve module '@' imported from '@'.", 1081 name, OBJ_VAL(importer)); 1082 return NULL_VAL; 1083 } 1084 1085 // If they resolved to the exact same string, we don't need to copy it. 1086 if (resolved == AS_CSTRING(name)) return name; 1087 1088 // Copy the string into a Wren String object. 1089 name = wrenNewString(vm, resolved); 1090 DEALLOCATE(vm, cast(char*)resolved); 1091 return name; 1092 } 1093 1094 static Value importModule(WrenVM* vm, Value name) 1095 { 1096 name = resolveModule(vm, name); 1097 1098 // If the module is already loaded, we don't need to do anything. 1099 Value existing = wrenMapGet(vm.modules, name); 1100 if (!IS_UNDEFINED(existing)) return existing; 1101 1102 wrenPushRoot(vm, AS_OBJ(name)); 1103 1104 WrenLoadModuleResult result = WrenLoadModuleResult(null, null, null); 1105 const(char)* source = null; 1106 1107 // Let the host try to provide the module. 1108 if (vm.config.loadModuleFn != null) 1109 { 1110 result = vm.config.loadModuleFn(vm, AS_CSTRING(name)); 1111 } 1112 1113 // If the host didn't provide it, see if it's a built in optional module. 1114 if (result.source == null) 1115 { 1116 import core.stdc.string : strcmp; 1117 result.onComplete = null; 1118 ObjString* nameString = AS_STRING(name); 1119 static if (WREN_OPT_META) { 1120 import wren.optional.meta : wrenMetaSource; 1121 if (strcmp(nameString.value.ptr, "meta") == 0) result.source = wrenMetaSource().ptr; 1122 } 1123 static if (WREN_OPT_RANDOM) { 1124 import wren.optional.random : wrenRandomSource; 1125 if (strcmp(nameString.value.ptr, "random") == 0) result.source = wrenRandomSource().ptr; 1126 } 1127 } 1128 1129 if (result.source == null) 1130 { 1131 vm.fiber.error = wrenStringFormat(vm, "Could not load module '@'.", name); 1132 wrenPopRoot(vm); // name. 1133 return NULL_VAL; 1134 } 1135 1136 ObjClosure* moduleClosure = compileInModule(vm, name, result.source, false, true); 1137 1138 // Now that we're done, give the result back in case there's cleanup to do. 1139 if(result.onComplete) result.onComplete(vm, AS_CSTRING(name), result); 1140 1141 if (moduleClosure == null) 1142 { 1143 vm.fiber.error = wrenStringFormat(vm, 1144 "Could not compile module '@'.", name); 1145 wrenPopRoot(vm); // name. 1146 return NULL_VAL; 1147 } 1148 1149 wrenPopRoot(vm); // name. 1150 1151 // Return the closure that executes the module. 1152 return OBJ_VAL(moduleClosure); 1153 } 1154 1155 static Value getModuleVariable(WrenVM* vm, ObjModule* module_, 1156 Value variableName) 1157 { 1158 ObjString* variable = AS_STRING(variableName); 1159 uint variableEntry = wrenSymbolTableFind(&module_.variableNames, 1160 variable.value.ptr, 1161 variable.length); 1162 1163 // It's a runtime error if the imported variable does not exist. 1164 if (variableEntry != uint.max) 1165 { 1166 return module_.variables.data[variableEntry]; 1167 } 1168 1169 vm.fiber.error = wrenStringFormat(vm, 1170 "Could not find a variable named '@' in module '@'.", 1171 variableName, OBJ_VAL(module_.name)); 1172 return NULL_VAL; 1173 } 1174 1175 static bool checkArity(WrenVM* vm, Value value, int numArgs) 1176 { 1177 assert(IS_CLOSURE(value), "Receiver must be a closure."); 1178 ObjFn* fn = AS_CLOSURE(value).fn; 1179 1180 // We only care about missing arguments, not extras. The "- 1" is because 1181 // numArgs includes the receiver, the function itself, which we don't want to 1182 // count. 1183 if (numArgs - 1 >= fn.arity) return true; 1184 1185 vm.fiber.error = CONST_STRING(vm, "Function expects more arguments."); 1186 return false; 1187 } 1188 1189 // The main bytecode interpreter loop. This is where the magic happens. It is 1190 // also, as you can imagine, highly performance critical. 1191 // Arg... thar be dragons here. 1192 static WrenInterpretResult runInterpreter(WrenVM* vm, ObjFiber* fiber) 1193 { 1194 // Remember the current fiber so we can find it if a GC happens. 1195 vm.fiber = fiber; 1196 fiber.state = FiberState.FIBER_ROOT; 1197 1198 // Hoist these into local variables. They are accessed frequently in the loop 1199 // but assigned less frequently. Keeping them in locals and updating them when 1200 // a call frame has been pushed or popped gives a large speed boost. 1201 CallFrame* frame; 1202 Value* stackStart; 1203 ubyte* ip; 1204 ObjFn* fn; 1205 1206 // These are a part of the CALL args, 1207 // but cannot be defined within the switch statement itself. 1208 int numArgs; 1209 int symbol; 1210 Value* args; 1211 ObjClass* classObj; 1212 Method* method; 1213 1214 // These macros are designed to only be invoked within this function. 1215 pragma(inline, true) 1216 void PUSH(Value value) { 1217 *fiber.stackTop++ = value; 1218 } 1219 1220 pragma(inline, true) 1221 Value POP() { 1222 return (*(--fiber.stackTop)); 1223 } 1224 1225 pragma(inline, true) 1226 void DROP() { 1227 fiber.stackTop--; 1228 } 1229 1230 pragma(inline, true) 1231 Value PEEK() { 1232 return (*(fiber.stackTop - 1)); 1233 } 1234 1235 pragma(inline, true) 1236 Value PEEK2() { 1237 return (*(fiber.stackTop - 2)); 1238 } 1239 1240 pragma(inline, true) 1241 ubyte READ_BYTE() { 1242 return (*ip++); 1243 } 1244 1245 pragma(inline, true) 1246 ushort READ_SHORT() { 1247 ip += 2; 1248 return cast(ushort)((ip[-2] << 8) | ip[-1]); 1249 } 1250 1251 // Use this before a CallFrame is pushed to store the local variables back 1252 // into the current one. 1253 pragma(inline, true) 1254 void STORE_FRAME() { 1255 frame.ip = ip; 1256 } 1257 1258 // Use this after a CallFrame has been pushed or popped to refresh the local 1259 // variables. 1260 pragma(inline, true) 1261 void LOAD_FRAME() { 1262 frame = &fiber.frames[fiber.numFrames - 1]; 1263 stackStart = frame.stackStart; 1264 ip = frame.ip; 1265 fn = frame.closure.fn; 1266 } 1267 1268 // Terminates the current fiber with error string [error]. If another calling 1269 // fiber is willing to catch the error, transfers control to it, otherwise 1270 // exits the interpreter. 1271 string RUNTIME_ERROR() { 1272 return q{ 1273 STORE_FRAME(); 1274 runtimeError(vm); 1275 if (vm.fiber == null) return WrenInterpretResult.WREN_RESULT_RUNTIME_ERROR; 1276 fiber = vm.fiber; 1277 LOAD_FRAME(); 1278 goto loop; 1279 }; 1280 } 1281 1282 static if (WREN_DEBUG_TRACE_INSTRUCTIONS) 1283 { 1284 void DEBUG_TRACE_INSTRUCTION() { 1285 import wren.dbg : wrenDumpStack, wrenDumpInstruction; 1286 wrenDumpStack(fiber); 1287 wrenDumpInstruction(vm, fn, cast(int)(ip - fn.code.data)); 1288 } 1289 } 1290 1291 LOAD_FRAME(); 1292 Code instruction; 1293 1294 loop: 1295 static if (WREN_DEBUG_TRACE_INSTRUCTIONS) 1296 { 1297 DEBUG_TRACE_INSTRUCTION(); 1298 } 1299 switch (instruction = cast(Code)READ_BYTE()) with(Code) 1300 { 1301 case CODE_LOAD_LOCAL_0: 1302 case CODE_LOAD_LOCAL_1: 1303 case CODE_LOAD_LOCAL_2: 1304 case CODE_LOAD_LOCAL_3: 1305 case CODE_LOAD_LOCAL_4: 1306 case CODE_LOAD_LOCAL_5: 1307 case CODE_LOAD_LOCAL_6: 1308 case CODE_LOAD_LOCAL_7: 1309 case CODE_LOAD_LOCAL_8: 1310 PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]); 1311 goto loop; 1312 1313 case CODE_LOAD_LOCAL: 1314 PUSH(stackStart[READ_BYTE()]); 1315 goto loop; 1316 1317 case CODE_LOAD_FIELD_THIS: { 1318 ubyte field = READ_BYTE(); 1319 Value receiver = stackStart[0]; 1320 assert(IS_INSTANCE(receiver), "Receiver should be instance."); 1321 ObjInstance* instance = AS_INSTANCE(receiver); 1322 assert(field < instance.obj.classObj.numFields, "Out of bounds field."); 1323 PUSH(instance.fields[field]); 1324 goto loop; 1325 } 1326 1327 case CODE_POP: 1328 DROP(); 1329 goto loop; 1330 1331 case CODE_NULL: 1332 PUSH(NULL_VAL); 1333 goto loop; 1334 1335 case CODE_FALSE: 1336 PUSH(FALSE_VAL); 1337 goto loop; 1338 1339 case CODE_TRUE: 1340 PUSH(TRUE_VAL); 1341 goto loop; 1342 1343 case CODE_STORE_LOCAL: 1344 stackStart[READ_BYTE()] = PEEK(); 1345 goto loop; 1346 1347 case CODE_CONSTANT: 1348 PUSH(fn.constants.data[READ_SHORT()]); 1349 goto loop; 1350 1351 // The opcodes for doing method and superclass calls share a lot of code. 1352 // However, doing an if() test in the middle of the instruction sequence 1353 // to handle the bit that is special to super calls makes the non-super 1354 // call path noticeably slower. 1355 // 1356 // Instead, we do this old school using an explicit goto to share code for 1357 // everything at the tail end of the call-handling code that is the same 1358 // between normal and superclass calls. 1359 1360 case CODE_CALL_0: 1361 case CODE_CALL_1: 1362 case CODE_CALL_2: 1363 case CODE_CALL_3: 1364 case CODE_CALL_4: 1365 case CODE_CALL_5: 1366 case CODE_CALL_6: 1367 case CODE_CALL_7: 1368 case CODE_CALL_8: 1369 case CODE_CALL_9: 1370 case CODE_CALL_10: 1371 case CODE_CALL_11: 1372 case CODE_CALL_12: 1373 case CODE_CALL_13: 1374 case CODE_CALL_14: 1375 case CODE_CALL_15: 1376 case CODE_CALL_16: 1377 // Add one for the implicit receiver argument. 1378 numArgs = instruction - CODE_CALL_0 + 1; 1379 symbol = READ_SHORT(); 1380 1381 // The receiver is the first argument. 1382 args = fiber.stackTop - numArgs; 1383 classObj = wrenGetClassInline(vm, args[0]); 1384 goto completeCall; 1385 1386 case CODE_SUPER_0: 1387 case CODE_SUPER_1: 1388 case CODE_SUPER_2: 1389 case CODE_SUPER_3: 1390 case CODE_SUPER_4: 1391 case CODE_SUPER_5: 1392 case CODE_SUPER_6: 1393 case CODE_SUPER_7: 1394 case CODE_SUPER_8: 1395 case CODE_SUPER_9: 1396 case CODE_SUPER_10: 1397 case CODE_SUPER_11: 1398 case CODE_SUPER_12: 1399 case CODE_SUPER_13: 1400 case CODE_SUPER_14: 1401 case CODE_SUPER_15: 1402 case CODE_SUPER_16: 1403 { 1404 // Add one for the implicit receiver argument. 1405 numArgs = instruction - CODE_SUPER_0 + 1; 1406 symbol = READ_SHORT(); 1407 1408 // The receiver is the first argument. 1409 args = fiber.stackTop - numArgs; 1410 1411 // The superclass is stored in a constant. 1412 classObj = AS_CLASS(fn.constants.data[READ_SHORT()]); 1413 goto completeCall; 1414 1415 completeCall: 1416 { 1417 // If the class's method table doesn't include the symbol, bail. 1418 if (symbol >= classObj.methods.count || 1419 (method = &classObj.methods.data[symbol]).type == MethodType.METHOD_NONE) 1420 { 1421 methodNotFound(vm, classObj, symbol); 1422 mixin(RUNTIME_ERROR); 1423 } 1424 1425 switch (method.type) with(MethodType) { 1426 case METHOD_PRIMITIVE: { 1427 if (method.as.primitive(vm, args)) 1428 { 1429 // The result is now in the first arg slot. Discard the other 1430 // stack slots. 1431 fiber.stackTop -= numArgs - 1; 1432 } else { 1433 // An error, fiber switch, or call frame change occurred. 1434 STORE_FRAME(); 1435 1436 // If we don't have a fiber to switch to, stop interpreting. 1437 fiber = vm.fiber; 1438 if (fiber == null) return WrenInterpretResult.WREN_RESULT_SUCCESS; 1439 if (wrenHasError(fiber)) mixin(RUNTIME_ERROR); 1440 LOAD_FRAME(); 1441 } 1442 break; 1443 } 1444 1445 case METHOD_FUNCTION_CALL: { 1446 if (!checkArity(vm, args[0], numArgs)) { 1447 mixin(RUNTIME_ERROR); 1448 } 1449 1450 STORE_FRAME(); 1451 method.as.primitive(vm, args); 1452 LOAD_FRAME(); 1453 break; 1454 } 1455 1456 case METHOD_FOREIGN: { 1457 callForeign(vm, fiber, method.as.foreign, numArgs); 1458 if (wrenHasError(fiber)) { 1459 mixin(RUNTIME_ERROR); 1460 } 1461 break; 1462 } 1463 1464 case METHOD_BLOCK: { 1465 STORE_FRAME(); 1466 wrenCallFunction(vm, fiber, cast(ObjClosure*)method.as.closure, numArgs); 1467 LOAD_FRAME(); 1468 break; 1469 } 1470 default: 1471 assert(0, "Unreachable"); 1472 1473 } 1474 goto loop; 1475 } 1476 } 1477 1478 case CODE_LOAD_UPVALUE: 1479 { 1480 ObjUpvalue** upvalues = frame.closure.upvalues.ptr; 1481 PUSH(*upvalues[READ_BYTE()].value); 1482 goto loop; 1483 } 1484 1485 case CODE_STORE_UPVALUE: 1486 { 1487 ObjUpvalue** upvalues = frame.closure.upvalues.ptr; 1488 *upvalues[READ_BYTE()].value = PEEK(); 1489 goto loop; 1490 } 1491 1492 case CODE_LOAD_MODULE_VAR: 1493 PUSH(fn.module_.variables.data[READ_SHORT()]); 1494 goto loop; 1495 1496 case CODE_STORE_MODULE_VAR: 1497 fn.module_.variables.data[READ_SHORT()] = PEEK(); 1498 goto loop; 1499 1500 case CODE_STORE_FIELD_THIS: 1501 { 1502 ubyte field = READ_BYTE(); 1503 Value receiver = stackStart[0]; 1504 assert(IS_INSTANCE(receiver), "Receiver should be instance."); 1505 ObjInstance* instance = AS_INSTANCE(receiver); 1506 assert(field < instance.obj.classObj.numFields, "Out of bounds field."); 1507 instance.fields[field] = PEEK(); 1508 goto loop; 1509 } 1510 1511 case CODE_LOAD_FIELD: 1512 { 1513 ubyte field = READ_BYTE(); 1514 Value receiver = POP(); 1515 assert(IS_INSTANCE(receiver), "Receiver should be instance."); 1516 ObjInstance* instance = AS_INSTANCE(receiver); 1517 assert(field < instance.obj.classObj.numFields, "Out of bounds field."); 1518 PUSH(instance.fields[field]); 1519 goto loop; 1520 } 1521 1522 case CODE_STORE_FIELD: 1523 { 1524 ubyte field = READ_BYTE(); 1525 Value receiver = POP(); 1526 assert(IS_INSTANCE(receiver), "Receiver should be instance."); 1527 ObjInstance* instance = AS_INSTANCE(receiver); 1528 assert(field < instance.obj.classObj.numFields, "Out of bounds field."); 1529 instance.fields[field] = PEEK(); 1530 goto loop; 1531 } 1532 1533 case CODE_JUMP: 1534 { 1535 ushort offset = READ_SHORT(); 1536 ip += offset; 1537 goto loop; 1538 } 1539 1540 case CODE_LOOP: 1541 { 1542 // Jump back to the top of the loop. 1543 ushort offset = READ_SHORT(); 1544 ip -= offset; 1545 goto loop; 1546 } 1547 1548 case CODE_JUMP_IF: 1549 { 1550 ushort offset = READ_SHORT(); 1551 Value condition = POP(); 1552 1553 if (wrenIsFalsyValue(condition)) ip += offset; 1554 goto loop; 1555 } 1556 1557 case CODE_AND: 1558 { 1559 ushort offset = READ_SHORT(); 1560 Value condition = PEEK(); 1561 1562 if (wrenIsFalsyValue(condition)) 1563 { 1564 // Short-circuit the right hand side. 1565 ip += offset; 1566 } 1567 else 1568 { 1569 // Discard the condition and evaluate the right hand side. 1570 DROP(); 1571 } 1572 goto loop; 1573 } 1574 1575 case CODE_OR: 1576 { 1577 ushort offset = READ_SHORT(); 1578 Value condition = PEEK(); 1579 1580 if (wrenIsFalsyValue(condition)) 1581 { 1582 // Discard the condition and evaluate the right hand side. 1583 DROP(); 1584 } 1585 else 1586 { 1587 // Short-circuit the right hand side. 1588 ip += offset; 1589 } 1590 goto loop; 1591 } 1592 1593 case CODE_CLOSE_UPVALUE: 1594 { 1595 // Close the upvalue for the local if we have one. 1596 closeUpvalues(fiber, fiber.stackTop - 1); 1597 DROP(); 1598 goto loop; 1599 } 1600 1601 case CODE_RETURN: 1602 { 1603 Value result = POP(); 1604 fiber.numFrames--; 1605 1606 // Close any upvalues still in scope. 1607 closeUpvalues(fiber, stackStart); 1608 1609 // If the fiber is complete, end it. 1610 if (fiber.numFrames == 0) 1611 { 1612 // See if there's another fiber to return to. If not, we're done. 1613 if (fiber.caller == null) 1614 { 1615 // Store the final result value at the beginning of the stack so the 1616 // C API can get it. 1617 fiber.stack[0] = result; 1618 fiber.stackTop = fiber.stack + 1; 1619 return WrenInterpretResult.WREN_RESULT_SUCCESS; 1620 } 1621 1622 ObjFiber* resumingFiber = fiber.caller; 1623 fiber.caller = null; 1624 fiber = resumingFiber; 1625 vm.fiber = resumingFiber; 1626 1627 // Store the result in the resuming fiber. 1628 fiber.stackTop[-1] = result; 1629 } 1630 else 1631 { 1632 // Store the result of the block in the first slot, which is where 1633 // the caller expects it. 1634 stackStart[0] = result; 1635 1636 // Discard the stack slots for the call frame (leaving one slot for the 1637 // result). 1638 fiber.stackTop = frame.stackStart + 1; 1639 } 1640 1641 LOAD_FRAME(); 1642 goto loop; 1643 } 1644 1645 case CODE_CONSTRUCT: 1646 assert(IS_CLASS(stackStart[0]), "'this' should be a class."); 1647 stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0])); 1648 goto loop; 1649 1650 case CODE_FOREIGN_CONSTRUCT: 1651 assert(IS_CLASS(stackStart[0]), "'this' should be a class."); 1652 createForeign(vm, fiber, stackStart); 1653 if (wrenHasError(fiber)) { 1654 mixin(RUNTIME_ERROR); 1655 } 1656 goto loop; 1657 1658 case CODE_CLOSURE: 1659 { 1660 // Create the closure and push it on the stack before creating upvalues 1661 // so that it doesn't get collected. 1662 ObjFn* fnC = AS_FN(fn.constants.data[READ_SHORT()]); 1663 ObjClosure* closure = wrenNewClosure(vm, fnC); 1664 PUSH(OBJ_VAL(closure)); 1665 1666 // Capture upvalues, if any. 1667 for (int i = 0; i < fnC.numUpvalues; i++) 1668 { 1669 ubyte isLocal = READ_BYTE(); 1670 ubyte index = READ_BYTE(); 1671 if (isLocal) 1672 { 1673 // Make an new upvalue to close over the parent's local variable. 1674 closure.upvalues[i] = captureUpvalue(vm, fiber, frame.stackStart + index); 1675 } 1676 else 1677 { 1678 // Use the same upvalue as the current call frame. 1679 closure.upvalues[i] = frame.closure.upvalues[index]; 1680 } 1681 } 1682 goto loop; 1683 } 1684 1685 case CODE_END_CLASS: 1686 { 1687 endClass(vm); 1688 if (wrenHasError(fiber)) 1689 { 1690 mixin(RUNTIME_ERROR); 1691 } 1692 goto loop; 1693 } 1694 1695 case CODE_CLASS: 1696 { 1697 createClass(vm, READ_BYTE(), null); 1698 if (wrenHasError(fiber)) 1699 { 1700 mixin(RUNTIME_ERROR); 1701 } 1702 goto loop; 1703 } 1704 1705 case CODE_FOREIGN_CLASS: 1706 { 1707 createClass(vm, -1, fn.module_); 1708 if (wrenHasError(fiber)) { 1709 mixin(RUNTIME_ERROR); 1710 } 1711 goto loop; 1712 } 1713 1714 case CODE_METHOD_INSTANCE: 1715 case CODE_METHOD_STATIC: 1716 { 1717 ushort methodSymbol = READ_SHORT(); 1718 ObjClass* methodClassObj = AS_CLASS(PEEK()); 1719 Value methodValue = PEEK2(); 1720 bindMethod(vm, instruction, methodSymbol, fn.module_, methodClassObj, methodValue); 1721 if (wrenHasError(fiber)) { 1722 mixin(RUNTIME_ERROR); 1723 } 1724 DROP(); 1725 DROP(); 1726 goto loop; 1727 } 1728 1729 case CODE_END_MODULE: 1730 { 1731 vm.lastModule = fn.module_; 1732 PUSH(NULL_VAL); 1733 goto loop; 1734 } 1735 1736 case CODE_IMPORT_MODULE: 1737 { 1738 // Make a slot on the stack for the module's fiber to place the return 1739 // value. It will be popped after this fiber is resumed. Store the 1740 // imported module's closure in the slot in case a GC happens when 1741 // invoking the closure. 1742 PUSH(importModule(vm, fn.constants.data[READ_SHORT()])); 1743 if (wrenHasError(fiber)) { 1744 mixin(RUNTIME_ERROR); 1745 } 1746 1747 // If we get a closure, call it to execute the module body. 1748 if (IS_CLOSURE(PEEK())) 1749 { 1750 STORE_FRAME(); 1751 ObjClosure* closure = AS_CLOSURE(PEEK()); 1752 wrenCallFunction(vm, fiber, closure, 1); 1753 LOAD_FRAME(); 1754 } 1755 else 1756 { 1757 // The module has already been loaded. Remember it so we can import 1758 // variables from it if needed. 1759 vm.lastModule = AS_MODULE(PEEK()); 1760 } 1761 1762 goto loop; 1763 } 1764 1765 case CODE_IMPORT_VARIABLE: 1766 { 1767 Value variable = fn.constants.data[READ_SHORT()]; 1768 assert(vm.lastModule != null, "Should have already imported module."); 1769 Value result = getModuleVariable(vm, vm.lastModule, variable); 1770 if (wrenHasError(fiber)) { 1771 mixin(RUNTIME_ERROR); 1772 } 1773 1774 PUSH(result); 1775 goto loop; 1776 } 1777 1778 case CODE_END: 1779 assert(0, "Unreachable"); 1780 1781 default: 1782 assert(0, "Unhandled instruction"); 1783 } 1784 } 1785 1786 WrenHandle* wrenMakeCallHandle(WrenVM* vm, const(char)* signature) 1787 { 1788 import core.stdc.string : strlen; 1789 assert(signature != null, "Signature cannot be NULL."); 1790 1791 int signatureLength = cast(int)strlen(signature); 1792 assert(signatureLength > 0, "Signature cannot be empty."); 1793 1794 // Count the number parameters the method expects. 1795 int numParams = 0; 1796 if (signature[signatureLength - 1] == ')') 1797 { 1798 for (int i = signatureLength - 1; i > 0 && signature[i] != '('; i--) 1799 { 1800 if (signature[i] == '_') numParams++; 1801 } 1802 } 1803 1804 // Count subscript arguments. 1805 if (signature[0] == '[') 1806 { 1807 for (int i = 0; i < signatureLength && signature[i] != ']'; i++) 1808 { 1809 if (signature[i] == '_') numParams++; 1810 } 1811 } 1812 1813 // Add the signatue to the method table. 1814 int method = wrenSymbolTableEnsure(vm, &vm.methodNames, 1815 signature, signatureLength); 1816 1817 // Create a little stub function that assumes the arguments are on the stack 1818 // and calls the method. 1819 ObjFn* fn = wrenNewFunction(vm, null, numParams + 1); 1820 1821 // Wrap the function in a closure and then in a handle. Do this here so it 1822 // doesn't get collected as we fill it in. 1823 WrenHandle* value = wrenMakeHandle(vm, OBJ_VAL(fn)); 1824 value.value = OBJ_VAL(wrenNewClosure(vm, fn)); 1825 1826 wrenByteBufferWrite(vm, &fn.code, cast(ubyte)(Code.CODE_CALL_0 + numParams)); 1827 wrenByteBufferWrite(vm, &fn.code, (method >> 8) & 0xff); 1828 wrenByteBufferWrite(vm, &fn.code, method & 0xff); 1829 wrenByteBufferWrite(vm, &fn.code, Code.CODE_RETURN); 1830 wrenByteBufferWrite(vm, &fn.code, Code.CODE_END); 1831 wrenIntBufferFill(vm, &fn.debug_.sourceLines, 0, 5); 1832 wrenFunctionBindName(vm, fn, signature, signatureLength); 1833 1834 return value; 1835 } 1836 1837 WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method) 1838 { 1839 assert(method != null, "Method cannot be NULL."); 1840 assert(IS_CLOSURE(method.value), "Method must be a method handle."); 1841 assert(vm.fiber != null, "Must set up arguments for call first."); 1842 assert(vm.apiStack != null, "Must set up arguments for call first."); 1843 assert(vm.fiber.numFrames == 0, "Can not call from a foreign method."); 1844 1845 ObjClosure* closure = AS_CLOSURE(method.value); 1846 1847 assert(vm.fiber.stackTop - vm.fiber.stack >= closure.fn.arity, 1848 "Stack must have enough arguments for method."); 1849 1850 // Clear the API stack. Now that wrenCall() has control, we no longer need 1851 // it. We use this being non-null to tell if re-entrant calls to foreign 1852 // methods are happening, so it's important to clear it out now so that you 1853 // can call foreign methods from within calls to wrenCall(). 1854 vm.apiStack = null; 1855 1856 // Discard any extra temporary slots. We take for granted that the stub 1857 // function has exactly one slot for each argument. 1858 vm.fiber.stackTop = &vm.fiber.stack[closure.fn.maxSlots]; 1859 1860 wrenCallFunction(vm, vm.fiber, closure, 0); 1861 WrenInterpretResult result = runInterpreter(vm, vm.fiber); 1862 1863 // If the call didn't abort, then set up the API stack to point to the 1864 // beginning of the stack so the host can access the call's return value. 1865 if (vm.fiber != null) vm.apiStack = vm.fiber.stack; 1866 1867 return result; 1868 } 1869 1870 WrenHandle* wrenMakeHandle(WrenVM* vm, Value value) 1871 { 1872 if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); 1873 1874 // Make a handle for it. 1875 WrenHandle* handle = ALLOCATE!(WrenVM, WrenHandle)(vm); 1876 handle.value = value; 1877 1878 if (IS_OBJ(value)) wrenPopRoot(vm); 1879 1880 // Add it to the front of the linked list of handles. 1881 if (vm.handles != null) vm.handles.prev = handle; 1882 handle.prev = null; 1883 handle.next = vm.handles; 1884 vm.handles = handle; 1885 1886 return handle; 1887 } 1888 1889 void wrenReleaseHandle(WrenVM* vm, WrenHandle* handle) 1890 { 1891 assert(handle != null, "Handle cannot be NULL."); 1892 1893 // Update the VM's head pointer if we're releasing the first handle. 1894 if (vm.handles == handle) vm.handles = handle.next; 1895 1896 // Unlink it from the list. 1897 if (handle.prev != null) handle.prev.next = handle.next; 1898 if (handle.next != null) handle.next.prev = handle.prev; 1899 1900 // Clear it out. This isn't strictly necessary since we're going to free it, 1901 // but it makes for easier debugging. 1902 handle.prev = null; 1903 handle.next = null; 1904 handle.value = NULL_VAL; 1905 DEALLOCATE(vm, handle); 1906 } 1907 1908 WrenInterpretResult wrenInterpret(WrenVM* vm, const(char)* module_, 1909 const(char)* source) 1910 { 1911 ObjClosure* closure = wrenCompileSource(vm, module_, source, false, true); 1912 if (closure == null) return WrenInterpretResult.WREN_RESULT_COMPILE_ERROR; 1913 1914 wrenPushRoot(vm, cast(Obj*)closure); 1915 ObjFiber* fiber = wrenNewFiber(vm, closure); 1916 wrenPopRoot(vm); // closure. 1917 vm.apiStack = null; 1918 1919 return runInterpreter(vm, fiber); 1920 } 1921 1922 ObjClosure* wrenCompileSource(WrenVM* vm, const(char)* module_, const(char)* source, 1923 bool isExpression, bool printErrors) 1924 { 1925 Value nameValue = NULL_VAL; 1926 if (module_ != null) 1927 { 1928 nameValue = wrenNewString(vm, module_); 1929 wrenPushRoot(vm, AS_OBJ(nameValue)); 1930 } 1931 1932 ObjClosure* closure = compileInModule(vm, nameValue, source, 1933 isExpression, printErrors); 1934 1935 if (module_ != null) wrenPopRoot(vm); // nameValue. 1936 return closure; 1937 } 1938 1939 Value wrenGetModuleVariable(WrenVM* vm, Value moduleName, Value variableName) 1940 { 1941 ObjModule* module_ = getModule(vm, moduleName); 1942 if (module_ == null) 1943 { 1944 vm.fiber.error = wrenStringFormat(vm, "Module '@' is not loaded.", 1945 moduleName); 1946 return NULL_VAL; 1947 } 1948 1949 return getModuleVariable(vm, module_, variableName); 1950 } 1951 1952 Value wrenFindVariable(WrenVM* vm, ObjModule* module_, const char* name) 1953 { 1954 import core.stdc.string : strlen; 1955 int symbol = wrenSymbolTableFind(&module_.variableNames, name, strlen(name)); 1956 return module_.variables.data[symbol]; 1957 } 1958 1959 int wrenDeclareVariable(WrenVM* vm, ObjModule* module_, const char* name, 1960 size_t length, int line) 1961 { 1962 if (module_.variables.count == MAX_MODULE_VARS) return -2; 1963 1964 // Implicitly defined variables get a "value" that is the line where the 1965 // variable is first used. We'll use that later to report an error on the 1966 // right line. 1967 wrenValueBufferWrite(vm, &module_.variables, NUM_VAL(line)); 1968 return wrenSymbolTableAdd(vm, &module_.variableNames, name, length); 1969 } 1970 1971 int wrenDefineVariable(WrenVM* vm, ObjModule* module_, const char* name, 1972 size_t length, Value value, int* line) 1973 { 1974 if (module_.variables.count == MAX_MODULE_VARS) return -2; 1975 1976 if (IS_OBJ(value)) wrenPushRoot(vm, AS_OBJ(value)); 1977 1978 // See if the variable is already explicitly or implicitly declared. 1979 int symbol = wrenSymbolTableFind(&module_.variableNames, name, length); 1980 1981 if (symbol == -1) 1982 { 1983 // Brand new variable. 1984 symbol = wrenSymbolTableAdd(vm, &module_.variableNames, name, length); 1985 wrenValueBufferWrite(vm, &module_.variables, value); 1986 } 1987 else if (IS_NUM(module_.variables.data[symbol])) 1988 { 1989 // An implicitly declared variable's value will always be a number. 1990 // Now we have a real definition. 1991 if(line) *line = cast(int)AS_NUM(module_.variables.data[symbol]); 1992 module_.variables.data[symbol] = value; 1993 1994 // If this was a localname we want to error if it was 1995 // referenced before this definition. 1996 if (wrenIsLocalName(name)) symbol = -3; 1997 } 1998 else 1999 { 2000 // Already explicitly declared. 2001 symbol = -1; 2002 } 2003 2004 if (IS_OBJ(value)) wrenPopRoot(vm); 2005 2006 return symbol; 2007 } 2008 2009 static void wrenCallFunction(WrenVM* vm, ObjFiber* fiber, 2010 ObjClosure* closure, int numArgs) 2011 { 2012 // Grow the call frame array if needed. 2013 if (fiber.numFrames + 1 > fiber.frameCapacity) 2014 { 2015 int max = fiber.frameCapacity * 2; 2016 fiber.frames = cast(CallFrame*)wrenReallocate(vm, fiber.frames, 2017 CallFrame.sizeof * fiber.frameCapacity, CallFrame.sizeof * max); 2018 fiber.frameCapacity = max; 2019 } 2020 2021 // Grow the stack if needed. 2022 int stackSize = cast(int)(fiber.stackTop - fiber.stack); 2023 int needed = stackSize + closure.fn.maxSlots; 2024 wrenEnsureStack(vm, fiber, needed); 2025 2026 wrenAppendCallFrame(vm, fiber, closure, fiber.stackTop - numArgs); 2027 } 2028 2029 // TODO: Inline? 2030 void wrenPushRoot(WrenVM* vm, Obj* obj) 2031 { 2032 assert(obj != null, "Cannot root null"); 2033 assert(vm.numTempRoots < WREN_MAX_TEMP_ROOTS, "Too many temporary roots."); 2034 2035 vm.tempRoots[vm.numTempRoots++] = obj; 2036 } 2037 2038 void wrenPopRoot(WrenVM* vm) 2039 { 2040 assert(vm.numTempRoots > 0, "No temporary roots to release."); 2041 vm.numTempRoots--; 2042 } 2043 2044 ObjClass* wrenGetClass(WrenVM* vm, Value value) 2045 { 2046 return wrenGetClassInline(vm, value); 2047 } 2048 2049 // Returns the class of [value]. 2050 // 2051 // Defined here instead of in wren_value.h because it's critical that this be 2052 // inlined. That means it must be defined in the header, but the wren_value.h 2053 // header doesn't have a full definitely of WrenVM yet. 2054 static ObjClass* wrenGetClassInline(WrenVM* vm, Value value) 2055 { 2056 if (IS_NUM(value)) return vm.numClass; 2057 if (IS_OBJ(value)) return AS_OBJ(value).classObj; 2058 2059 static if (WREN_NAN_TAGGING) { 2060 switch (GET_TAG(value)) 2061 { 2062 case TAG_FALSE: return vm.boolClass; 2063 case TAG_NAN: return vm.numClass; 2064 case TAG_NULL: return vm.nullClass; 2065 case TAG_TRUE: return vm.boolClass; 2066 case TAG_UNDEFINED: assert(0, "Unreachable"); 2067 default: assert(0, "Unhandled tag?"); 2068 } 2069 } else { 2070 switch (value.type) with(ValueType) 2071 { 2072 case VAL_FALSE: return vm.boolClass; 2073 case VAL_NULL: return vm.nullClass; 2074 case VAL_NUM: return vm.numClass; 2075 case VAL_TRUE: return vm.boolClass; 2076 case VAL_OBJ: return AS_OBJ(value).classObj; 2077 case VAL_UNDEFINED: assert(0, "Unreachable"); 2078 default: assert(0, "Unreachable"); 2079 } 2080 } 2081 } 2082 2083 int wrenGetSlotCount(WrenVM* vm) 2084 { 2085 if (vm.apiStack == null) return 0; 2086 2087 return cast(int)(vm.fiber.stackTop - vm.apiStack); 2088 } 2089 2090 void wrenEnsureSlots(WrenVM* vm, int numSlots) 2091 { 2092 // If we don't have a fiber accessible, create one for the API to use. 2093 if (vm.apiStack == null) 2094 { 2095 vm.fiber = wrenNewFiber(vm, null); 2096 vm.apiStack = vm.fiber.stack; 2097 } 2098 2099 int currentSize = cast(int)(vm.fiber.stackTop - vm.apiStack); 2100 if (currentSize >= numSlots) return; 2101 2102 // Grow the stack if needed. 2103 int needed = cast(int)(vm.apiStack - vm.fiber.stack) + numSlots; 2104 wrenEnsureStack(vm, vm.fiber, needed); 2105 2106 vm.fiber.stackTop = vm.apiStack + numSlots; 2107 } 2108 2109 // Ensures that [slot] is a valid index into the API's stack of slots. 2110 static void validateApiSlot(WrenVM* vm, int slot) 2111 { 2112 assert(slot >= 0, "Slot cannot be negative."); 2113 assert(slot < wrenGetSlotCount(vm), "Not that many slots."); 2114 } 2115 2116 // Gets the type of the object in [slot]. 2117 WrenType wrenGetSlotType(WrenVM* vm, int slot) 2118 { 2119 validateApiSlot(vm, slot); 2120 if (IS_BOOL(vm.apiStack[slot])) return WrenType.WREN_TYPE_BOOL; 2121 if (IS_NUM(vm.apiStack[slot])) return WrenType.WREN_TYPE_NUM; 2122 if (IS_FOREIGN(vm.apiStack[slot])) return WrenType.WREN_TYPE_FOREIGN; 2123 if (IS_LIST(vm.apiStack[slot])) return WrenType.WREN_TYPE_LIST; 2124 if (IS_MAP(vm.apiStack[slot])) return WrenType.WREN_TYPE_MAP; 2125 if (IS_NULL(vm.apiStack[slot])) return WrenType.WREN_TYPE_NULL; 2126 if (IS_STRING(vm.apiStack[slot])) return WrenType.WREN_TYPE_STRING; 2127 2128 return WrenType.WREN_TYPE_UNKNOWN; 2129 } 2130 2131 bool wrenGetSlotBool(WrenVM* vm, int slot) 2132 { 2133 validateApiSlot(vm, slot); 2134 assert(IS_BOOL(vm.apiStack[slot]), "Slot must hold a bool."); 2135 2136 return AS_BOOL(vm.apiStack[slot]); 2137 } 2138 2139 const(char)* wrenGetSlotBytes(WrenVM* vm, int slot, int* length) 2140 { 2141 validateApiSlot(vm, slot); 2142 assert(IS_STRING(vm.apiStack[slot]), "Slot must hold a string."); 2143 2144 ObjString* string_ = AS_STRING(vm.apiStack[slot]); 2145 *length = string_.length; 2146 return string_.value.ptr; 2147 } 2148 2149 double wrenGetSlotDouble(WrenVM* vm, int slot) 2150 { 2151 validateApiSlot(vm, slot); 2152 assert(IS_NUM(vm.apiStack[slot]), "Slot must hold a number."); 2153 2154 return AS_NUM(vm.apiStack[slot]); 2155 } 2156 2157 void* wrenGetSlotForeign(WrenVM* vm, int slot) 2158 { 2159 validateApiSlot(vm, slot); 2160 assert(IS_FOREIGN(vm.apiStack[slot]), 2161 "Slot must hold a foreign instance."); 2162 2163 return AS_FOREIGN(vm.apiStack[slot]).data.ptr; 2164 } 2165 2166 const(char)* wrenGetSlotString(WrenVM* vm, int slot) 2167 { 2168 validateApiSlot(vm, slot); 2169 assert(IS_STRING(vm.apiStack[slot]), "Slot must hold a string."); 2170 2171 return AS_CSTRING(vm.apiStack[slot]); 2172 } 2173 2174 WrenHandle* wrenGetSlotHandle(WrenVM* vm, int slot) 2175 { 2176 validateApiSlot(vm, slot); 2177 return wrenMakeHandle(vm, vm.apiStack[slot]); 2178 } 2179 2180 // Stores [value] in [slot] in the foreign call stack. 2181 static void setSlot(WrenVM* vm, int slot, Value value) 2182 { 2183 validateApiSlot(vm, slot); 2184 vm.apiStack[slot] = value; 2185 } 2186 2187 void wrenSetSlotBool(WrenVM* vm, int slot, bool value) 2188 { 2189 setSlot(vm, slot, BOOL_VAL(value)); 2190 } 2191 2192 void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) 2193 { 2194 assert(bytes != null, "Byte array cannot be NULL."); 2195 setSlot(vm, slot, wrenNewStringLength(vm, bytes, length)); 2196 } 2197 2198 void wrenSetSlotDouble(WrenVM* vm, int slot, double value) 2199 { 2200 setSlot(vm, slot, NUM_VAL(value)); 2201 } 2202 2203 void* wrenSetSlotNewForeign(WrenVM* vm, int slot, int classSlot, size_t size) 2204 { 2205 validateApiSlot(vm, slot); 2206 validateApiSlot(vm, classSlot); 2207 assert(IS_CLASS(vm.apiStack[classSlot]), "Slot must hold a class."); 2208 2209 ObjClass* classObj = AS_CLASS(vm.apiStack[classSlot]); 2210 assert(classObj.numFields == -1, "Class must be a foreign class."); 2211 2212 ObjForeign* foreign = wrenNewForeign(vm, classObj, size); 2213 vm.apiStack[slot] = OBJ_VAL(foreign); 2214 2215 return cast(void*)foreign.data; 2216 } 2217 2218 void wrenSetSlotNewList(WrenVM* vm, int slot) 2219 { 2220 setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); 2221 } 2222 2223 void wrenSetSlotNewMap(WrenVM* vm, int slot) 2224 { 2225 setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm))); 2226 } 2227 2228 void wrenSetSlotNull(WrenVM* vm, int slot) 2229 { 2230 setSlot(vm, slot, NULL_VAL); 2231 } 2232 2233 void wrenSetSlotString(WrenVM* vm, int slot, const char* text) 2234 { 2235 assert(text != null, "String cannot be NULL."); 2236 2237 setSlot(vm, slot, wrenNewString(vm, text)); 2238 } 2239 2240 void wrenSetSlotHandle(WrenVM* vm, int slot, WrenHandle* handle) 2241 { 2242 assert(handle != null, "Handle cannot be NULL."); 2243 2244 setSlot(vm, slot, handle.value); 2245 } 2246 2247 int wrenGetListCount(WrenVM* vm, int slot) 2248 { 2249 validateApiSlot(vm, slot); 2250 assert(IS_LIST(vm.apiStack[slot]), "Slot must hold a list."); 2251 2252 ValueBuffer elements = AS_LIST(vm.apiStack[slot]).elements; 2253 return elements.count; 2254 } 2255 2256 void wrenGetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) 2257 { 2258 validateApiSlot(vm, listSlot); 2259 validateApiSlot(vm, elementSlot); 2260 assert(IS_LIST(vm.apiStack[listSlot]), "Slot must hold a list."); 2261 2262 ValueBuffer elements = AS_LIST(vm.apiStack[listSlot]).elements; 2263 2264 uint usedIndex = wrenValidateIndex(elements.count, index); 2265 assert(usedIndex != uint.max, "Index out of bounds."); 2266 2267 vm.apiStack[elementSlot] = elements.data[usedIndex]; 2268 } 2269 2270 void wrenSetListElement(WrenVM* vm, int listSlot, int index, int elementSlot) 2271 { 2272 validateApiSlot(vm, listSlot); 2273 validateApiSlot(vm, elementSlot); 2274 assert(IS_LIST(vm.apiStack[listSlot]), "Slot must hold a list."); 2275 2276 ObjList* list = AS_LIST(vm.apiStack[listSlot]); 2277 2278 uint usedIndex = wrenValidateIndex(list.elements.count, index); 2279 assert(usedIndex != uint.max, "Index out of bounds."); 2280 2281 list.elements.data[usedIndex] = vm.apiStack[elementSlot]; 2282 } 2283 2284 void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) 2285 { 2286 validateApiSlot(vm, listSlot); 2287 validateApiSlot(vm, elementSlot); 2288 assert(IS_LIST(vm.apiStack[listSlot]), "Must insert into a list."); 2289 2290 ObjList* list = AS_LIST(vm.apiStack[listSlot]); 2291 2292 // Negative indices count from the end. 2293 // We don't use wrenValidateIndex here because insert allows 1 past the end. 2294 if (index < 0) index = list.elements.count + 1 + index; 2295 2296 assert(index <= list.elements.count, "Index out of bounds."); 2297 2298 wrenListInsert(vm, list, vm.apiStack[elementSlot], index); 2299 } 2300 2301 int wrenGetMapCount(WrenVM* vm, int slot) 2302 { 2303 validateApiSlot(vm, slot); 2304 assert(IS_MAP(vm.apiStack[slot]), "Slot must hold a map."); 2305 2306 ObjMap* map = AS_MAP(vm.apiStack[slot]); 2307 return map.count; 2308 } 2309 2310 bool wrenGetMapContainsKey(WrenVM* vm, int mapSlot, int keySlot) 2311 { 2312 import wren.primitive : validateKey; 2313 validateApiSlot(vm, mapSlot); 2314 validateApiSlot(vm, keySlot); 2315 assert(IS_MAP(vm.apiStack[mapSlot]), "Slot must hold a map."); 2316 2317 Value key = vm.apiStack[keySlot]; 2318 assert(wrenMapIsValidKey(key), "Key must be a value type"); 2319 if (!validateKey(vm, key)) return false; 2320 2321 ObjMap* map = AS_MAP(vm.apiStack[mapSlot]); 2322 Value value = wrenMapGet(map, key); 2323 2324 return !IS_UNDEFINED(value); 2325 } 2326 2327 void wrenGetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) 2328 { 2329 validateApiSlot(vm, mapSlot); 2330 validateApiSlot(vm, keySlot); 2331 validateApiSlot(vm, valueSlot); 2332 assert(IS_MAP(vm.apiStack[mapSlot]), "Slot must hold a map."); 2333 2334 ObjMap* map = AS_MAP(vm.apiStack[mapSlot]); 2335 Value value = wrenMapGet(map, vm.apiStack[keySlot]); 2336 if (IS_UNDEFINED(value)) { 2337 value = NULL_VAL; 2338 } 2339 2340 vm.apiStack[valueSlot] = value; 2341 } 2342 2343 void wrenSetMapValue(WrenVM* vm, int mapSlot, int keySlot, int valueSlot) 2344 { 2345 import wren.primitive : validateKey; 2346 validateApiSlot(vm, mapSlot); 2347 validateApiSlot(vm, keySlot); 2348 validateApiSlot(vm, valueSlot); 2349 assert(IS_MAP(vm.apiStack[mapSlot]), "Must insert into a map."); 2350 2351 Value key = vm.apiStack[keySlot]; 2352 assert(wrenMapIsValidKey(key), "Key must be a value type"); 2353 2354 if (!validateKey(vm, key)) { 2355 return; 2356 } 2357 2358 Value value = vm.apiStack[valueSlot]; 2359 ObjMap* map = AS_MAP(vm.apiStack[mapSlot]); 2360 2361 wrenMapSet(vm, map, key, value); 2362 } 2363 2364 void wrenRemoveMapValue(WrenVM* vm, int mapSlot, int keySlot, 2365 int removedValueSlot) 2366 { 2367 import wren.primitive : validateKey; 2368 validateApiSlot(vm, mapSlot); 2369 validateApiSlot(vm, keySlot); 2370 assert(IS_MAP(vm.apiStack[mapSlot]), "Slot must hold a map."); 2371 2372 Value key = vm.apiStack[keySlot]; 2373 if (!validateKey(vm, key)) { 2374 return; 2375 } 2376 2377 ObjMap* map = AS_MAP(vm.apiStack[mapSlot]); 2378 Value removed = wrenMapRemoveKey(vm, map, key); 2379 setSlot(vm, removedValueSlot, removed); 2380 } 2381 2382 void wrenGetVariable(WrenVM* vm, const(char)* module_, const(char)* name, 2383 int slot) 2384 { 2385 import core.stdc.string : strlen; 2386 2387 assert(module_ != null, "Module cannot be NULL."); 2388 assert(name != null, "Variable name cannot be NULL."); 2389 2390 Value moduleName = wrenStringFormat(vm, "$", module_); 2391 wrenPushRoot(vm, AS_OBJ(moduleName)); 2392 2393 ObjModule* moduleObj = getModule(vm, moduleName); 2394 assert(moduleObj != null, "Could not find module."); 2395 2396 wrenPopRoot(vm); // moduleName. 2397 2398 int variableSlot = wrenSymbolTableFind(&moduleObj.variableNames, 2399 name, strlen(name)); 2400 assert(variableSlot != -1, "Could not find variable."); 2401 2402 setSlot(vm, slot, moduleObj.variables.data[variableSlot]); 2403 } 2404 2405 bool wrenHasVariable(WrenVM* vm, const(char)* module_, const(char)* name) 2406 { 2407 import core.stdc.string : strlen; 2408 2409 assert(module_ != null, "Module cannot be NULL."); 2410 assert(name != null, "Variable name cannot be NULL."); 2411 2412 Value moduleName = wrenStringFormat(vm, "$", module_); 2413 wrenPushRoot(vm, AS_OBJ(moduleName)); 2414 2415 //We don't use wrenHasModule since we want to use the module object. 2416 ObjModule* moduleObj = getModule(vm, moduleName); 2417 assert(moduleObj != null, "Could not find module."); 2418 2419 wrenPopRoot(vm); // moduleName. 2420 2421 int variableSlot = wrenSymbolTableFind(&moduleObj.variableNames, 2422 name, strlen(name)); 2423 2424 return variableSlot != -1; 2425 } 2426 2427 bool wrenHasModule(WrenVM* vm, const(char)* module_) 2428 { 2429 assert(module_ != null, "Module cannot be NULL."); 2430 2431 Value moduleName = wrenStringFormat(vm, "$", module_); 2432 wrenPushRoot(vm, AS_OBJ(moduleName)); 2433 2434 ObjModule* moduleObj = getModule(vm, moduleName); 2435 2436 wrenPopRoot(vm); // moduleName. 2437 2438 return moduleObj != null; 2439 } 2440 2441 void wrenAbortFiber(WrenVM* vm, int slot) 2442 { 2443 validateApiSlot(vm, slot); 2444 vm.fiber.error = vm.apiStack[slot]; 2445 } 2446 2447 void* wrenGetUserData(WrenVM* vm) 2448 { 2449 return vm.config.userData; 2450 } 2451 2452 void wrenSetUserData(WrenVM* vm, void* userData) 2453 { 2454 vm.config.userData = userData; 2455 } 2456 2457 static bool wrenIsLocalName(const(char)* name) 2458 { 2459 return name[0] >= 'a' && name[0] <= 'z'; 2460 } 2461 2462 static bool wrenIsFalsyValue(Value value) 2463 { 2464 return IS_FALSE(value) || IS_NULL(value); 2465 }