1 modulewren.vm;
2 importwren.core;
3 importwren.common;
4 importwren.opcodes;
5 importwren.value;
6 importwren.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 to13 // 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 error15 // or modifies the running fiber, it returns `false`.16 aliasPrimitive = boolfunction(WrenVM* vm, Value* args);
17 18 // A generic allocation function that handles all explicit memory management19 // used by Wren. It's used like so:20 //21 // - To allocate new memory, [memory] is null and [newSize] is the desired22 // size. It should return the allocated memory or null on failure.23 //24 // - To attempt to grow an existing allocation, [memory] is the memory, and25 // [newSize] is the desired size. It should return [memory] if it was able to26 // 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 will29 // always return [memory].30 //31 // - To free memory, [memory] will be the memory to free and [newSize] will be32 // zero. It should return null.33 aliasWrenReallocateFn = void* function(void* memory, size_tnewSize, void* userData);
34 35 // A function callable from Wren code, but implemented in C.36 aliasWrenForeignMethodFn = voidfunction(WrenVM* vm);
37 38 // A finalizer function for freeing resources owned by an instance of a foreign39 // class. Unlike most foreign methods, finalizers do not have access to the VM40 // and should not interact with it since it's in the middle of a garbage41 // collection.42 aliasWrenFinalizerFn = voidfunction(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 module46 // that contains the import. Typically, this is used to implement relative47 // imports.48 aliasWrenResolveModuleFn = 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 result52 // is handed back to you in this callback, so that you can free memory if appropriate.53 aliasWrenLoadModuleCompleteFn = voidfunction(WrenVM* vm, const(char)* name, WrenLoadModuleResultresult);
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 structWrenLoadModuleResult59 {
60 const(char)* source;
61 WrenLoadModuleCompleteFnonComplete;
62 void* userData;
63 }
64 65 // Loads and returns the source code for the module [name].66 aliasWrenLoadModuleFn = WrenLoadModuleResultfunction(WrenVM* vm, const(char)* name);
67 68 // Returns a pointer to a foreign method on [className] in [module] with69 // [signature].70 aliasWrenBindForeignMethodFn = WrenForeignMethodFnfunction(WrenVM* vm,
71 const(char)* module_, const(char)* className, boolisStatic,
72 const(char)* signature);
73 74 // Displays a string of text to the user.75 aliasWrenWriteFn = voidfunction(WrenVM* vm, const(char)* text);
76 77 enumWrenErrorType78 {
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_TRACE87 }
88 89 // Reports an error to the user.90 //91 // An error detected during compile time is reported by calling this once with92 // [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's97 // [message]. After that, a series of [type] `WREN_ERROR_STACK_TRACE` calls are98 // made for each line in the stack trace. Each of those has the resolved99 // [module] and [line] where the method or function is defined and [message] is100 // the name of the method or function.101 aliasWrenErrorFn = voidfunction(WrenVM* vm, WrenErrorTypetype, const(char)* module_,
102 intline, const(char)* message);
103 104 structWrenForeignClassMethods105 {
106 // The callback invoked when the foreign object is created.107 //108 // This must be provided. Inside the body of this, it must call109 // [wrenSetSlotNewForeign()] exactly once.110 WrenForeignMethodFnallocate;
111 112 // The callback invoked when the garbage collector is about to collect a113 // foreign object's memory.114 //115 // This may be `null` if the foreign class does not need to finalize.116 WrenFinalizerFnfinalize;
117 }
118 119 aliasWrenBindForeignClassFn = WrenForeignClassMethodsfunction(
120 WrenVM* vm, const(char)* module_, const(char)* className);
121 122 structWrenConfiguration123 {
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 WrenReallocateFnreallocateFn;
128 129 // The callback Wren uses to resolve a module name.130 //131 // Some host applications may wish to support "relative" imports, where the132 // meaning of an import string depends on the module that contains it. To133 // support that without baking any policy into Wren itself, the VM gives the134 // host a chance to resolve an import string.135 //136 // Before an import is loaded, it calls this, passing in the name of the137 // module that contains the import and the import string. The host app can138 // look at both of those and produce a new "canonical" string that uniquely139 // identifies the module. This string is then used as the name of the module140 // going forward. It is what is passed to [loadModuleFn], how duplicate141 // imports of the same module are detected, and how the module is reported in142 // stack traces.143 //144 // If you leave this function null, then the original import string is145 // treated as the resolved string.146 //147 // If an import cannot be resolved by the embedder, it should return null and148 // Wren will report that as a runtime error.149 //150 // Wren will take ownership of the string you return and free it for you, so151 // it should be allocated using the same allocation function you provide152 // above.153 WrenResolveModuleFnresolveModuleFn;
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 the158 // embedder to physically locate and read the source code for a module. The159 // first time an import appears, Wren will call this and pass in the name of160 // the module being imported. The method will return a result, which contains161 // 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 the165 // result internally so subsequent imports of the same module will use the166 // previous source and not call this.167 //168 // If a module with the given name could not be found by the embedder, it169 // should return null and Wren will report that as a runtime error.170 WrenLoadModuleFnloadModuleFn;
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 the175 // foreign method's module, class, and signature when the class body is176 // executed. It should return a pointer to the foreign function that will be177 // bound to that method.178 //179 // If the foreign function could not be found, this should return null and180 // Wren will report it as runtime error.181 WrenBindForeignMethodFnbindForeignMethodFn;
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's186 // module and name when the class body is executed. It should return the187 // foreign functions uses to allocate and (optionally) finalize the bytes188 // stored in the foreign object when an instance is created.189 WrenBindForeignClassFnbindForeignClassFn;
190 191 // The callback Wren uses to display text when `System.print()` or the other192 // related functions are called.193 //194 // If this is `null`, Wren discards any printed text.195 WrenWriteFnwriteFn;
196 197 // The callback Wren uses to report errors.198 //199 // When an error occurs, this will be called with the module name, line200 // number, and an error message. If this is `null`, Wren doesn't report any201 // errors.202 WrenErrorFnerrorFn;
203 204 // The number of bytes Wren will allocate before triggering the first garbage205 // collection.206 //207 // If zero, defaults to 10MB.208 size_tinitialHeapSize;
209 210 // After a collection occurs, the threshold for the next collection is211 // determined based on the number of bytes remaining in use. This allows Wren212 // to shrink its memory usage automatically after reclaiming a large amount213 // of memory.214 //215 // This can be used to ensure that the heap does not get too small, which can216 // in turn lead to a large number of collections afterwards as the heap grows217 // back to a usable size.218 //219 // If zero, defaults to 1MB.220 size_tminHeapSize;
221 222 // Wren will resize the heap automatically as the number of bytes223 // remaining in use after a collection changes. This number determines the224 // amount of additional memory Wren will use after a collection, as a225 // percentage of the current heap size.226 //227 // For example, say that this is 50. After a garbage collection, when there228 // are 400 bytes of memory still in use, the next collection will be triggered229 // after a total of 600 bytes are allocated (including the 400 already in230 // use.)231 //232 // Setting this to a smaller number wastes less memory, but triggers more233 // frequent garbage collections.234 //235 // If zero, defaults to 50.236 intheapGrowthPercent;
237 238 // User-defined data associated with the VM.239 void* userData;
240 }
241 242 enumWrenInterpretResult243 {
244 WREN_RESULT_SUCCESS,
245 WREN_RESULT_COMPILE_ERROR,
246 WREN_RESULT_RUNTIME_ERROR247 }
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 level252 // representation type.253 enumWrenType254 {
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_UNKNOWN265 }
266 267 // The maximum number of temporary objects that can be made visible to the GC268 // at one time.269 enumWREN_MAX_TEMP_ROOTS = 8;
270 271 structWrenVM272 {
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 ObjModule290 // for the module.291 ObjMap* modules;
292 293 // The most recently imported module. More specifically, the module whose294 // 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 all302 // memory that was proven live after the last GC, as well as any new bytes303 // that were allocated since then. Does *not* include bytes for objects that304 // were freed since the last GC.305 size_tbytesAllocated;
306 307 // The number of total allocated bytes that will trigger the next GC.308 size_tnextGC;
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 unprocessed314 // objects while a garbage collection pass is in process.315 Obj** gray;
316 intgrayCount;
317 intgrayCapacity;
318 319 // The list of temporary roots. This is for temporary or new objects that are320 // not otherwise reachable but should not be collected.321 //322 // They are organized as a stack of pointers stored in this array. This323 // implies that temporary roots need to have stack semantics: only the most324 // recently pushed object can be released.325 Obj*[WREN_MAX_TEMP_ROOTS] tempRoots;
326 327 intnumTempRoots;
328 329 // Pointer to the first node in the linked list of active handles or null if330 // there are none.331 WrenHandle* handles;
332 333 // Pointer to the bottom of the range of stack slots available for use from334 // the C API. During a foreign method, this will be in the stack of the fiber335 // that is executing a method.336 //337 // If not in a foreign method, this is initially null. If the user requests338 // slots by calling wrenEnsureSlots(), a stack is created and this is339 // initialized.340 Value* apiStack;
341 342 WrenConfigurationconfig;
343 344 // Compiler and debugger data:345 346 // The compiler that is currently compiling code. This is used so that heap347 // allocated objects used by the compiler can be found if a GC is kicked off348 // in the middle of a compile.349 importwren.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 SymbolTablemethodNames;
355 }
356 357 // The behavior of realloc() when the size is 0 is implementation defined. It358 // may return a non-null pointer which must not be dereferenced but nevertheless359 // should be freed. To prevent that, we avoid calling realloc() with a zero360 // size.361 staticvoid* defaultReallocate(void* ptr, size_tnewSize, void* _)
362 {
363 importcore.stdc.stdlib : free, realloc;
364 365 if (newSize == 0)
366 {
367 free(ptr);
368 returnnull;
369 }
370 371 returnrealloc(ptr, newSize);
372 }
373 374 voidwrenInitConfiguration(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 importcore.stdc.string : memset, memcpy;
392 WrenReallocateFnreallocate = &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 pointer408 vm.config.reallocateFn = reallocate;
409 }
410 else411 {
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 returnvm;
427 }
428 429 voidwrenFreeVM(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 free446 // them here because the host app may still have pointers to them that they447 // 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 voidwrenCollectGarbage(WrenVM* vm)
456 {
457 staticif (WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC)
458 {
459 importcore.stdc.stdio : printf;
460 importcore.stdc.time;
461 printf("-- gc --\n");
462 463 size_tbefore = vm.bytesAllocated;
464 doublestartTime = 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 that470 // we can track how much memory is in use without needing to know the size471 // of each *freed* object.472 //473 // This is important because when freeing an unmarked object, we don't always474 // 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 have476 // already been freed.477 vm.bytesAllocated = 0;
478 479 wrenGrayObj(vm, cast(Obj*)vm.modules);
480 481 // Temporary roots.482 for (inti = 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 importwren.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 the506 // 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 else521 {
522 // This object was reached, so unmark it (for the next GC) and move on to523 // the next.524 (*obj).isDark = false;
525 obj = &(*obj).next;
526 }
527 }
528 529 // Calculate the next gc point, this is the current allocation plus530 // 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 staticif (WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC)
535 {
536 doubleelapsed = (cast(double)clock / CLOCKS_PER_SEC) - startTime;
537 // Explicit cast because size_t has different sizes on 32-bit and 64-bit and538 // 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 should552 // 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 new557 // pointer if it had to move it.558 //559 // - To shrink memory, [memory], [oldSize], and [newSize] are the same as above560 // but it will always return [memory].561 //562 // - To free memory, [memory] will be the memory to free and [newSize] and563 // [oldSize] will be zero. It should return null.564 void* wrenReallocate(WrenVM* vm, void* memory, size_toldSize, size_tnewSize)
565 {
566 staticif (WREN_DEBUG_TRACE_MEMORY)
567 {
568 importcore.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 objects574 // are being completely deallocated, we don't track that (since we don't575 // track the original size). Instead, that will be handled while marking576 // during the next GC.577 vm.bytesAllocated += newSize - oldSize;
578 579 staticif (WREN_DEBUG_GC_STRESS)
580 {
581 // Since collecting calls this function to free things, make sure we don't582 // recurse.583 if (newSize > 0) wrenCollectGarbage(vm);
584 }
585 else586 {
587 if (newSize > 0 && vm.bytesAllocated > vm.nextGC) wrenCollectGarbage(vm);
588 }
589 590 returnvm.config.reallocateFn(memory, newSize, vm.config.userData);
591 }
592 593 // Captures the local variable [local] into an [Upvalue]. If that local is594 // already in an upvalue, the existing one will be used. (This is important to595 // ensure that multiple closures closing over the same variable actually see596 // the same variable.) Otherwise, it will create a new open upvalue and add it597 // the fiber's list of upvalues.598 staticObjUpvalue* 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 returnfiber.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 existing611 // 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) returnupvalue;
620 621 // We've walked past this local on the stack, so there must not be an622 // upvalue for it already. Make a new one and link it in in the right623 // 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 else631 {
632 prevUpvalue.next = createdUpvalue;
633 }
634 635 createdUpvalue.next = upvalue;
636 returncreatedUpvalue;
637 }
638 639 // Closes any open upvalues that have been created for stack slots at [last]640 // and above.641 staticvoidcloseUpvalues(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, it660 // falls back to handling the built-in modules.661 staticWrenForeignMethodFnfindForeignMethod(WrenVM* vm,
662 constchar* moduleName,
663 constchar* className,
664 boolisStatic,
665 constchar* signature)
666 {
667 WrenForeignMethodFnmethod = 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 importcore.stdc.string : strcmp;
679 staticif (WREN_OPT_META) {
680 if (strcmp(moduleName, "meta") == 0)
681 {
682 importwren.optional.meta : wrenMetaBindForeignMethod;
683 method = wrenMetaBindForeignMethod(vm, className, isStatic, signature);
684 }
685 }
686 staticif (WREN_OPT_RANDOM) {
687 if (strcmp(moduleName, "random") == 0)
688 {
689 importwren.optional.random : wrenRandomBindForeignMethod;
690 method = wrenRandomBindForeignMethod(vm, className, isStatic, signature);
691 }
692 }
693 }
694 695 returnmethod;
696 }
697 698 // Defines [methodValue] as a method on [classObj].699 //700 // Handles both foreign methods where [methodValue] is a string containing the701 // 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 be704 // found.705 staticvoidbindMethod(WrenVM* vm, intmethodType, intsymbol,
706 ObjModule* module_, ObjClass* classObj, ValuemethodValue)
707 {
708 const(char)* className = classObj.name.value.ptr;
709 if (methodType == Code.CODE_METHOD_STATIC) classObj = classObj.obj.classObj;
710 711 Methodmethod;
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 else730 {
731 importwren.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 staticvoidcallForeign(WrenVM* vm, ObjFiber* fiber,
744 WrenForeignMethodFnforeign, intnumArgs)
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 one752 // 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 that761 // handles the error. If none do, tells the VM to stop.762 staticvoidruntimeError(WrenVM* vm)
763 {
764 assert(wrenHasError(vm.fiber), "Should only call this after an error.");
765 766 ObjFiber* current = vm.fiber;
767 Valueerror = 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 importwren.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 a797 // method with [symbol] on [classObj].798 staticvoidmethodNotFound(WrenVM* vm, ObjClass* classObj, intsymbol)
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 staticObjModule* getModule(WrenVM* vm, Valuename)
808 {
809 ValuemoduleValue = wrenMapGet(vm.modules, name);
810 return !IS_UNDEFINED(moduleValue) ? AS_MODULE(moduleValue) : null;
811 }
812 813 staticObjClosure* compileInModule(WrenVM* vm, Valuename, constchar* source,
814 boolisExpression, boolprintErrors)
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 collect824 // 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 module828 // 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 (inti = 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 importwren.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 returnnull;
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 returnclosure;
858 }
859 860 // Verifies that [superclassValue] is a valid object to inherit from. That861 // 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 and864 // the other limitations foreign classes have.865 //866 // If successful, returns `null`. Otherwise, returns a string for the runtime867 // error message.868 staticValuevalidateSuperclass(WrenVM* vm, Valuename, ValuesuperclassValue,
869 intnumFields)
870 {
871 // Make sure the superclass is a class.872 if (!IS_CLASS(superclassValue))
873 {
874 returnwrenStringFormat(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 methods880 // on these classes assume the instance is one of the other Obj___ types and881 // 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 returnwrenStringFormat(vm,
895 "Class '@' cannot inherit from built-in class '@'.",
896 name, OBJ_VAL(superclass.name));
897 }
898 899 if (superclass.numFields == -1)
900 {
901 returnwrenStringFormat(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 returnwrenStringFormat(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 returnwrenStringFormat(vm,
916 "Class '@' may not have more than 255 fields, including inherited "917 ~ "ones.", name);
918 }
919 920 returnNULL_VAL;
921 }
922 923 staticvoidbindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module_)
924 {
925 WrenForeignClassMethodsmethods;
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 staticif (WREN_OPT_RANDOM) {
941 importcore.stdc.string : strcmp;
942 if (strcmp(module_.name.value.ptr, "random") == 0)
943 {
944 importwren.optional.random : wrenRandomBindForeignClass;
945 methods = wrenRandomBindForeignClass(vm, module_.name.value.ptr,
946 classObj.name.value.ptr);
947 }
948 }
949 }
950 951 Methodmethod;
952 method.type = MethodType.METHOD_FOREIGN;
953 954 // Add the symbol even if there is no allocator so we can ensure that the955 // symbol itself is always in the symbol table.956 intsymbol = 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 the964 // 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 from979 // compile time to runtime, since it now has all the attributes associated980 // with a class, including for methods.981 staticvoidendClass(WrenVM* vm)
982 {
983 // Pull the attributes and class off the stack984 Valueattributes = vm.fiber.stackTop[-2];
985 ValueclassValue = vm.fiber.stackTop[-1];
986 987 // Remove the stack items988 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 superclass997 // should be on top of the fiber's stack. After calling this, the top of the998 // stack will contain the new class.999 //1000 // Aborts the current fiber if an error occurs.1001 staticvoidcreateClass(WrenVM* vm, intnumFields, ObjModule* module_)
1002 {
1003 // Pull the name and superclass off the stack.1004 Valuename = vm.fiber.stackTop[-2];
1005 Valuesuperclass = vm.fiber.stackTop[-1];
1006 1007 // We have two values on the stack and we are going to leave one, so discard1008 // 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 staticvoidcreateForeign(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 intsymbol = 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 voidwrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
1044 {
1045 // TODO: Don't look up every time.1046 intsymbol = 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 WrenFinalizerFnfinalizer = 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 staticValueresolveModule(WrenVM* vm, Valuename)
1067 {
1068 // If the host doesn't care to resolve, leave the name alone.1069 if (vm.config.resolveModuleFn == null) returnname;
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 returnNULL_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)) returnname;
1087 1088 // Copy the string into a Wren String object.1089 name = wrenNewString(vm, resolved);
1090 DEALLOCATE(vm, cast(char*)resolved);
1091 returnname;
1092 }
1093 1094 staticValueimportModule(WrenVM* vm, Valuename)
1095 {
1096 name = resolveModule(vm, name);
1097 1098 // If the module is already loaded, we don't need to do anything.1099 Valueexisting = wrenMapGet(vm.modules, name);
1100 if (!IS_UNDEFINED(existing)) returnexisting;
1101 1102 wrenPushRoot(vm, AS_OBJ(name));
1103 1104 WrenLoadModuleResultresult = 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 importcore.stdc.string : strcmp;
1117 result.onComplete = null;
1118 ObjString* nameString = AS_STRING(name);
1119 staticif (WREN_OPT_META) {
1120 importwren.optional.meta : wrenMetaSource;
1121 if (strcmp(nameString.value.ptr, "meta") == 0) result.source = wrenMetaSource().ptr;
1122 }
1123 staticif (WREN_OPT_RANDOM) {
1124 importwren.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 returnNULL_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 returnNULL_VAL;
1147 }
1148 1149 wrenPopRoot(vm); // name.1150 1151 // Return the closure that executes the module.1152 returnOBJ_VAL(moduleClosure);
1153 }
1154 1155 staticValuegetModuleVariable(WrenVM* vm, ObjModule* module_,
1156 ValuevariableName)
1157 {
1158 ObjString* variable = AS_STRING(variableName);
1159 uintvariableEntry = 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 returnmodule_.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 returnNULL_VAL;
1173 }
1174 1175 staticboolcheckArity(WrenVM* vm, Valuevalue, intnumArgs)
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 because1181 // numArgs includes the receiver, the function itself, which we don't want to1182 // count.1183 if (numArgs - 1 >= fn.arity) returntrue;
1184 1185 vm.fiber.error = CONST_STRING(vm, "Function expects more arguments.");
1186 returnfalse;
1187 }
1188 1189 // The main bytecode interpreter loop. This is where the magic happens. It is1190 // also, as you can imagine, highly performance critical.1191 // Arg... thar be dragons here.1192 staticWrenInterpretResultrunInterpreter(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 loop1199 // but assigned less frequently. Keeping them in locals and updating them when1200 // 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 intnumArgs;
1209 intsymbol;
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 voidPUSH(Valuevalue) {
1217 *fiber.stackTop++ = value;
1218 }
1219 1220 pragma(inline, true)
1221 ValuePOP() {
1222 return (*(--fiber.stackTop));
1223 }
1224 1225 pragma(inline, true)
1226 voidDROP() {
1227 fiber.stackTop--;
1228 }
1229 1230 pragma(inline, true)
1231 ValuePEEK() {
1232 return (*(fiber.stackTop - 1));
1233 }
1234 1235 pragma(inline, true)
1236 ValuePEEK2() {
1237 return (*(fiber.stackTop - 2));
1238 }
1239 1240 pragma(inline, true)
1241 ubyteREAD_BYTE() {
1242 return (*ip++);
1243 }
1244 1245 pragma(inline, true)
1246 ushortREAD_SHORT() {
1247 ip += 2;
1248 returncast(ushort)((ip[-2] << 8) | ip[-1]);
1249 }
1250 1251 // Use this before a CallFrame is pushed to store the local variables back1252 // into the current one.1253 pragma(inline, true)
1254 voidSTORE_FRAME() {
1255 frame.ip = ip;
1256 }
1257 1258 // Use this after a CallFrame has been pushed or popped to refresh the local1259 // variables.1260 pragma(inline, true)
1261 voidLOAD_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 calling1269 // fiber is willing to catch the error, transfers control to it, otherwise1270 // exits the interpreter.1271 stringRUNTIME_ERROR() {
1272 returnq{1273 STORE_FRAME();
1274 runtimeError(vm);
1275 if (vm.fiber == null) returnWrenInterpretResult.WREN_RESULT_RUNTIME_ERROR;
1276 fiber = vm.fiber;
1277 LOAD_FRAME();
1278 gotoloop;
1279 };
1280 }
1281 1282 staticif (WREN_DEBUG_TRACE_INSTRUCTIONS)
1283 {
1284 voidDEBUG_TRACE_INSTRUCTION() {
1285 importwren.dbg : wrenDumpStack, wrenDumpInstruction;
1286 wrenDumpStack(fiber);
1287 wrenDumpInstruction(vm, fn, cast(int)(ip - fn.code.data));
1288 }
1289 }
1290 1291 LOAD_FRAME();
1292 Codeinstruction;
1293 1294 loop:
1295 staticif (WREN_DEBUG_TRACE_INSTRUCTIONS)
1296 {
1297 DEBUG_TRACE_INSTRUCTION();
1298 }
1299 switch (instruction = cast(Code)READ_BYTE()) with(Code)
1300 {
1301 caseCODE_LOAD_LOCAL_0:
1302 caseCODE_LOAD_LOCAL_1:
1303 caseCODE_LOAD_LOCAL_2:
1304 caseCODE_LOAD_LOCAL_3:
1305 caseCODE_LOAD_LOCAL_4:
1306 caseCODE_LOAD_LOCAL_5:
1307 caseCODE_LOAD_LOCAL_6:
1308 caseCODE_LOAD_LOCAL_7:
1309 caseCODE_LOAD_LOCAL_8:
1310 PUSH(stackStart[instruction - CODE_LOAD_LOCAL_0]);
1311 gotoloop;
1312 1313 caseCODE_LOAD_LOCAL:
1314 PUSH(stackStart[READ_BYTE()]);
1315 gotoloop;
1316 1317 caseCODE_LOAD_FIELD_THIS: {
1318 ubytefield = READ_BYTE();
1319 Valuereceiver = 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 gotoloop;
1325 }
1326 1327 caseCODE_POP:
1328 DROP();
1329 gotoloop;
1330 1331 caseCODE_NULL:
1332 PUSH(NULL_VAL);
1333 gotoloop;
1334 1335 caseCODE_FALSE:
1336 PUSH(FALSE_VAL);
1337 gotoloop;
1338 1339 caseCODE_TRUE:
1340 PUSH(TRUE_VAL);
1341 gotoloop;
1342 1343 caseCODE_STORE_LOCAL:
1344 stackStart[READ_BYTE()] = PEEK();
1345 gotoloop;
1346 1347 caseCODE_CONSTANT:
1348 PUSH(fn.constants.data[READ_SHORT()]);
1349 gotoloop;
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 sequence1353 // to handle the bit that is special to super calls makes the non-super1354 // call path noticeably slower.1355 //1356 // Instead, we do this old school using an explicit goto to share code for1357 // everything at the tail end of the call-handling code that is the same1358 // between normal and superclass calls.1359 1360 caseCODE_CALL_0:
1361 caseCODE_CALL_1:
1362 caseCODE_CALL_2:
1363 caseCODE_CALL_3:
1364 caseCODE_CALL_4:
1365 caseCODE_CALL_5:
1366 caseCODE_CALL_6:
1367 caseCODE_CALL_7:
1368 caseCODE_CALL_8:
1369 caseCODE_CALL_9:
1370 caseCODE_CALL_10:
1371 caseCODE_CALL_11:
1372 caseCODE_CALL_12:
1373 caseCODE_CALL_13:
1374 caseCODE_CALL_14:
1375 caseCODE_CALL_15:
1376 caseCODE_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 gotocompleteCall;
1385 1386 caseCODE_SUPER_0:
1387 caseCODE_SUPER_1:
1388 caseCODE_SUPER_2:
1389 caseCODE_SUPER_3:
1390 caseCODE_SUPER_4:
1391 caseCODE_SUPER_5:
1392 caseCODE_SUPER_6:
1393 caseCODE_SUPER_7:
1394 caseCODE_SUPER_8:
1395 caseCODE_SUPER_9:
1396 caseCODE_SUPER_10:
1397 caseCODE_SUPER_11:
1398 caseCODE_SUPER_12:
1399 caseCODE_SUPER_13:
1400 caseCODE_SUPER_14:
1401 caseCODE_SUPER_15:
1402 caseCODE_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 gotocompleteCall;
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 caseMETHOD_PRIMITIVE: {
1427 if (method.as.primitive(vm, args))
1428 {
1429 // The result is now in the first arg slot. Discard the other1430 // 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) returnWrenInterpretResult.WREN_RESULT_SUCCESS;
1439 if (wrenHasError(fiber)) mixin(RUNTIME_ERROR);
1440 LOAD_FRAME();
1441 }
1442 break;
1443 }
1444 1445 caseMETHOD_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 caseMETHOD_FOREIGN: {
1457 callForeign(vm, fiber, method.as.foreign, numArgs);
1458 if (wrenHasError(fiber)) {
1459 mixin(RUNTIME_ERROR);
1460 }
1461 break;
1462 }
1463 1464 caseMETHOD_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 gotoloop;
1475 }
1476 }
1477 1478 caseCODE_LOAD_UPVALUE:
1479 {
1480 ObjUpvalue** upvalues = frame.closure.upvalues.ptr;
1481 PUSH(*upvalues[READ_BYTE()].value);
1482 gotoloop;
1483 }
1484 1485 caseCODE_STORE_UPVALUE:
1486 {
1487 ObjUpvalue** upvalues = frame.closure.upvalues.ptr;
1488 *upvalues[READ_BYTE()].value = PEEK();
1489 gotoloop;
1490 }
1491 1492 caseCODE_LOAD_MODULE_VAR:
1493 PUSH(fn.module_.variables.data[READ_SHORT()]);
1494 gotoloop;
1495 1496 caseCODE_STORE_MODULE_VAR:
1497 fn.module_.variables.data[READ_SHORT()] = PEEK();
1498 gotoloop;
1499 1500 caseCODE_STORE_FIELD_THIS:
1501 {
1502 ubytefield = READ_BYTE();
1503 Valuereceiver = 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 gotoloop;
1509 }
1510 1511 caseCODE_LOAD_FIELD:
1512 {
1513 ubytefield = READ_BYTE();
1514 Valuereceiver = 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 gotoloop;
1520 }
1521 1522 caseCODE_STORE_FIELD:
1523 {
1524 ubytefield = READ_BYTE();
1525 Valuereceiver = 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 gotoloop;
1531 }
1532 1533 caseCODE_JUMP:
1534 {
1535 ushortoffset = READ_SHORT();
1536 ip += offset;
1537 gotoloop;
1538 }
1539 1540 caseCODE_LOOP:
1541 {
1542 // Jump back to the top of the loop.1543 ushortoffset = READ_SHORT();
1544 ip -= offset;
1545 gotoloop;
1546 }
1547 1548 caseCODE_JUMP_IF:
1549 {
1550 ushortoffset = READ_SHORT();
1551 Valuecondition = POP();
1552 1553 if (wrenIsFalsyValue(condition)) ip += offset;
1554 gotoloop;
1555 }
1556 1557 caseCODE_AND:
1558 {
1559 ushortoffset = READ_SHORT();
1560 Valuecondition = PEEK();
1561 1562 if (wrenIsFalsyValue(condition))
1563 {
1564 // Short-circuit the right hand side.1565 ip += offset;
1566 }
1567 else1568 {
1569 // Discard the condition and evaluate the right hand side.1570 DROP();
1571 }
1572 gotoloop;
1573 }
1574 1575 caseCODE_OR:
1576 {
1577 ushortoffset = READ_SHORT();
1578 Valuecondition = PEEK();
1579 1580 if (wrenIsFalsyValue(condition))
1581 {
1582 // Discard the condition and evaluate the right hand side.1583 DROP();
1584 }
1585 else1586 {
1587 // Short-circuit the right hand side.1588 ip += offset;
1589 }
1590 gotoloop;
1591 }
1592 1593 caseCODE_CLOSE_UPVALUE:
1594 {
1595 // Close the upvalue for the local if we have one.1596 closeUpvalues(fiber, fiber.stackTop - 1);
1597 DROP();
1598 gotoloop;
1599 }
1600 1601 caseCODE_RETURN:
1602 {
1603 Valueresult = 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 the1616 // C API can get it.1617 fiber.stack[0] = result;
1618 fiber.stackTop = fiber.stack + 1;
1619 returnWrenInterpretResult.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 else1631 {
1632 // Store the result of the block in the first slot, which is where1633 // the caller expects it.1634 stackStart[0] = result;
1635 1636 // Discard the stack slots for the call frame (leaving one slot for the1637 // result).1638 fiber.stackTop = frame.stackStart + 1;
1639 }
1640 1641 LOAD_FRAME();
1642 gotoloop;
1643 }
1644 1645 caseCODE_CONSTRUCT:
1646 assert(IS_CLASS(stackStart[0]), "'this' should be a class.");
1647 stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
1648 gotoloop;
1649 1650 caseCODE_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 gotoloop;
1657 1658 caseCODE_CLOSURE:
1659 {
1660 // Create the closure and push it on the stack before creating upvalues1661 // 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 (inti = 0; i < fnC.numUpvalues; i++)
1668 {
1669 ubyteisLocal = READ_BYTE();
1670 ubyteindex = 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 else1677 {
1678 // Use the same upvalue as the current call frame.1679 closure.upvalues[i] = frame.closure.upvalues[index];
1680 }
1681 }
1682 gotoloop;
1683 }
1684 1685 caseCODE_END_CLASS:
1686 {
1687 endClass(vm);
1688 if (wrenHasError(fiber))
1689 {
1690 mixin(RUNTIME_ERROR);
1691 }
1692 gotoloop;
1693 }
1694 1695 caseCODE_CLASS:
1696 {
1697 createClass(vm, READ_BYTE(), null);
1698 if (wrenHasError(fiber))
1699 {
1700 mixin(RUNTIME_ERROR);
1701 }
1702 gotoloop;
1703 }
1704 1705 caseCODE_FOREIGN_CLASS:
1706 {
1707 createClass(vm, -1, fn.module_);
1708 if (wrenHasError(fiber)) {
1709 mixin(RUNTIME_ERROR);
1710 }
1711 gotoloop;
1712 }
1713 1714 caseCODE_METHOD_INSTANCE:
1715 caseCODE_METHOD_STATIC:
1716 {
1717 ushortmethodSymbol = READ_SHORT();
1718 ObjClass* methodClassObj = AS_CLASS(PEEK());
1719 ValuemethodValue = PEEK2();
1720 bindMethod(vm, instruction, methodSymbol, fn.module_, methodClassObj, methodValue);
1721 if (wrenHasError(fiber)) {
1722 mixin(RUNTIME_ERROR);
1723 }
1724 DROP();
1725 DROP();
1726 gotoloop;
1727 }
1728 1729 caseCODE_END_MODULE:
1730 {
1731 vm.lastModule = fn.module_;
1732 PUSH(NULL_VAL);
1733 gotoloop;
1734 }
1735 1736 caseCODE_IMPORT_MODULE:
1737 {
1738 // Make a slot on the stack for the module's fiber to place the return1739 // value. It will be popped after this fiber is resumed. Store the1740 // imported module's closure in the slot in case a GC happens when1741 // 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 else1756 {
1757 // The module has already been loaded. Remember it so we can import1758 // variables from it if needed.1759 vm.lastModule = AS_MODULE(PEEK());
1760 }
1761 1762 gotoloop;
1763 }
1764 1765 caseCODE_IMPORT_VARIABLE:
1766 {
1767 Valuevariable = fn.constants.data[READ_SHORT()];
1768 assert(vm.lastModule != null, "Should have already imported module.");
1769 Valueresult = getModuleVariable(vm, vm.lastModule, variable);
1770 if (wrenHasError(fiber)) {
1771 mixin(RUNTIME_ERROR);
1772 }
1773 1774 PUSH(result);
1775 gotoloop;
1776 }
1777 1778 caseCODE_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 importcore.stdc.string : strlen;
1789 assert(signature != null, "Signature cannot be NULL.");
1790 1791 intsignatureLength = cast(int)strlen(signature);
1792 assert(signatureLength > 0, "Signature cannot be empty.");
1793 1794 // Count the number parameters the method expects.1795 intnumParams = 0;
1796 if (signature[signatureLength - 1] == ')')
1797 {
1798 for (inti = 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 (inti = 0; i < signatureLength && signature[i] != ']'; i++)
1808 {
1809 if (signature[i] == '_') numParams++;
1810 }
1811 }
1812 1813 // Add the signatue to the method table.1814 intmethod = wrenSymbolTableEnsure(vm, &vm.methodNames,
1815 signature, signatureLength);
1816 1817 // Create a little stub function that assumes the arguments are on the stack1818 // 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 it1822 // 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 returnvalue;
1835 }
1836 1837 WrenInterpretResultwrenCall(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 need1851 // it. We use this being non-null to tell if re-entrant calls to foreign1852 // methods are happening, so it's important to clear it out now so that you1853 // 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 stub1857 // 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 WrenInterpretResultresult = runInterpreter(vm, vm.fiber);
1862 1863 // If the call didn't abort, then set up the API stack to point to the1864 // 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 returnresult;
1868 }
1869 1870 WrenHandle* wrenMakeHandle(WrenVM* vm, Valuevalue)
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 returnhandle;
1887 }
1888 1889 voidwrenReleaseHandle(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 WrenInterpretResultwrenInterpret(WrenVM* vm, const(char)* module_,
1909 const(char)* source)
1910 {
1911 ObjClosure* closure = wrenCompileSource(vm, module_, source, false, true);
1912 if (closure == null) returnWrenInterpretResult.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 returnrunInterpreter(vm, fiber);
1920 }
1921 1922 ObjClosure* wrenCompileSource(WrenVM* vm, const(char)* module_, const(char)* source,
1923 boolisExpression, boolprintErrors)
1924 {
1925 ValuenameValue = 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 returnclosure;
1937 }
1938 1939 ValuewrenGetModuleVariable(WrenVM* vm, ValuemoduleName, ValuevariableName)
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 returnNULL_VAL;
1947 }
1948 1949 returngetModuleVariable(vm, module_, variableName);
1950 }
1951 1952 ValuewrenFindVariable(WrenVM* vm, ObjModule* module_, constchar* name)
1953 {
1954 importcore.stdc.string : strlen;
1955 intsymbol = wrenSymbolTableFind(&module_.variableNames, name, strlen(name));
1956 returnmodule_.variables.data[symbol];
1957 }
1958 1959 intwrenDeclareVariable(WrenVM* vm, ObjModule* module_, constchar* name,
1960 size_tlength, intline)
1961 {
1962 if (module_.variables.count == MAX_MODULE_VARS) return -2;
1963 1964 // Implicitly defined variables get a "value" that is the line where the1965 // variable is first used. We'll use that later to report an error on the1966 // right line.1967 wrenValueBufferWrite(vm, &module_.variables, NUM_VAL(line));
1968 returnwrenSymbolTableAdd(vm, &module_.variableNames, name, length);
1969 }
1970 1971 intwrenDefineVariable(WrenVM* vm, ObjModule* module_, constchar* name,
1972 size_tlength, Valuevalue, 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 intsymbol = 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 elseif (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 else1999 {
2000 // Already explicitly declared.2001 symbol = -1;
2002 }
2003 2004 if (IS_OBJ(value)) wrenPopRoot(vm);
2005 2006 returnsymbol;
2007 }
2008 2009 staticvoidwrenCallFunction(WrenVM* vm, ObjFiber* fiber,
2010 ObjClosure* closure, intnumArgs)
2011 {
2012 // Grow the call frame array if needed.2013 if (fiber.numFrames + 1 > fiber.frameCapacity)
2014 {
2015 intmax = 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 intstackSize = cast(int)(fiber.stackTop - fiber.stack);
2023 intneeded = stackSize + closure.fn.maxSlots;
2024 wrenEnsureStack(vm, fiber, needed);
2025 2026 wrenAppendCallFrame(vm, fiber, closure, fiber.stackTop - numArgs);
2027 }
2028 2029 // TODO: Inline?2030 voidwrenPushRoot(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 voidwrenPopRoot(WrenVM* vm)
2039 {
2040 assert(vm.numTempRoots > 0, "No temporary roots to release.");
2041 vm.numTempRoots--;
2042 }
2043 2044 ObjClass* wrenGetClass(WrenVM* vm, Valuevalue)
2045 {
2046 returnwrenGetClassInline(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 be2052 // inlined. That means it must be defined in the header, but the wren_value.h2053 // header doesn't have a full definitely of WrenVM yet.2054 staticObjClass* wrenGetClassInline(WrenVM* vm, Valuevalue)
2055 {
2056 if (IS_NUM(value)) returnvm.numClass;
2057 if (IS_OBJ(value)) returnAS_OBJ(value).classObj;
2058 2059 staticif (WREN_NAN_TAGGING) {
2060 switch (GET_TAG(value))
2061 {
2062 caseTAG_FALSE: returnvm.boolClass;
2063 caseTAG_NAN: returnvm.numClass;
2064 caseTAG_NULL: returnvm.nullClass;
2065 caseTAG_TRUE: returnvm.boolClass;
2066 caseTAG_UNDEFINED: assert(0, "Unreachable");
2067 default: assert(0, "Unhandled tag?");
2068 }
2069 } else {
2070 switch (value.type) with(ValueType)
2071 {
2072 caseVAL_FALSE: returnvm.boolClass;
2073 caseVAL_NULL: returnvm.nullClass;
2074 caseVAL_NUM: returnvm.numClass;
2075 caseVAL_TRUE: returnvm.boolClass;
2076 caseVAL_OBJ: returnAS_OBJ(value).classObj;
2077 caseVAL_UNDEFINED: assert(0, "Unreachable");
2078 default: assert(0, "Unreachable");
2079 }
2080 }
2081 }
2082 2083 intwrenGetSlotCount(WrenVM* vm)
2084 {
2085 if (vm.apiStack == null) return0;
2086 2087 returncast(int)(vm.fiber.stackTop - vm.apiStack);
2088 }
2089 2090 voidwrenEnsureSlots(WrenVM* vm, intnumSlots)
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 intcurrentSize = cast(int)(vm.fiber.stackTop - vm.apiStack);
2100 if (currentSize >= numSlots) return;
2101 2102 // Grow the stack if needed.2103 intneeded = 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 staticvoidvalidateApiSlot(WrenVM* vm, intslot)
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 WrenTypewrenGetSlotType(WrenVM* vm, intslot)
2118 {
2119 validateApiSlot(vm, slot);
2120 if (IS_BOOL(vm.apiStack[slot])) returnWrenType.WREN_TYPE_BOOL;
2121 if (IS_NUM(vm.apiStack[slot])) returnWrenType.WREN_TYPE_NUM;
2122 if (IS_FOREIGN(vm.apiStack[slot])) returnWrenType.WREN_TYPE_FOREIGN;
2123 if (IS_LIST(vm.apiStack[slot])) returnWrenType.WREN_TYPE_LIST;
2124 if (IS_MAP(vm.apiStack[slot])) returnWrenType.WREN_TYPE_MAP;
2125 if (IS_NULL(vm.apiStack[slot])) returnWrenType.WREN_TYPE_NULL;
2126 if (IS_STRING(vm.apiStack[slot])) returnWrenType.WREN_TYPE_STRING;
2127 2128 returnWrenType.WREN_TYPE_UNKNOWN;
2129 }
2130 2131 boolwrenGetSlotBool(WrenVM* vm, intslot)
2132 {
2133 validateApiSlot(vm, slot);
2134 assert(IS_BOOL(vm.apiStack[slot]), "Slot must hold a bool.");
2135 2136 returnAS_BOOL(vm.apiStack[slot]);
2137 }
2138 2139 const(char)* wrenGetSlotBytes(WrenVM* vm, intslot, 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 returnstring_.value.ptr;
2147 }
2148 2149 doublewrenGetSlotDouble(WrenVM* vm, intslot)
2150 {
2151 validateApiSlot(vm, slot);
2152 assert(IS_NUM(vm.apiStack[slot]), "Slot must hold a number.");
2153 2154 returnAS_NUM(vm.apiStack[slot]);
2155 }
2156 2157 void* wrenGetSlotForeign(WrenVM* vm, intslot)
2158 {
2159 validateApiSlot(vm, slot);
2160 assert(IS_FOREIGN(vm.apiStack[slot]),
2161 "Slot must hold a foreign instance.");
2162 2163 returnAS_FOREIGN(vm.apiStack[slot]).data.ptr;
2164 }
2165 2166 const(char)* wrenGetSlotString(WrenVM* vm, intslot)
2167 {
2168 validateApiSlot(vm, slot);
2169 assert(IS_STRING(vm.apiStack[slot]), "Slot must hold a string.");
2170 2171 returnAS_CSTRING(vm.apiStack[slot]);
2172 }
2173 2174 WrenHandle* wrenGetSlotHandle(WrenVM* vm, intslot)
2175 {
2176 validateApiSlot(vm, slot);
2177 returnwrenMakeHandle(vm, vm.apiStack[slot]);
2178 }
2179 2180 // Stores [value] in [slot] in the foreign call stack.2181 staticvoidsetSlot(WrenVM* vm, intslot, Valuevalue)
2182 {
2183 validateApiSlot(vm, slot);
2184 vm.apiStack[slot] = value;
2185 }
2186 2187 voidwrenSetSlotBool(WrenVM* vm, intslot, boolvalue)
2188 {
2189 setSlot(vm, slot, BOOL_VAL(value));
2190 }
2191 2192 voidwrenSetSlotBytes(WrenVM* vm, intslot, constchar* bytes, size_tlength)
2193 {
2194 assert(bytes != null, "Byte array cannot be NULL.");
2195 setSlot(vm, slot, wrenNewStringLength(vm, bytes, length));
2196 }
2197 2198 voidwrenSetSlotDouble(WrenVM* vm, intslot, doublevalue)
2199 {
2200 setSlot(vm, slot, NUM_VAL(value));
2201 }
2202 2203 void* wrenSetSlotNewForeign(WrenVM* vm, intslot, intclassSlot, size_tsize)
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 returncast(void*)foreign.data;
2216 }
2217 2218 voidwrenSetSlotNewList(WrenVM* vm, intslot)
2219 {
2220 setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0)));
2221 }
2222 2223 voidwrenSetSlotNewMap(WrenVM* vm, intslot)
2224 {
2225 setSlot(vm, slot, OBJ_VAL(wrenNewMap(vm)));
2226 }
2227 2228 voidwrenSetSlotNull(WrenVM* vm, intslot)
2229 {
2230 setSlot(vm, slot, NULL_VAL);
2231 }
2232 2233 voidwrenSetSlotString(WrenVM* vm, intslot, constchar* text)
2234 {
2235 assert(text != null, "String cannot be NULL.");
2236 2237 setSlot(vm, slot, wrenNewString(vm, text));
2238 }
2239 2240 voidwrenSetSlotHandle(WrenVM* vm, intslot, WrenHandle* handle)
2241 {
2242 assert(handle != null, "Handle cannot be NULL.");
2243 2244 setSlot(vm, slot, handle.value);
2245 }
2246 2247 intwrenGetListCount(WrenVM* vm, intslot)
2248 {
2249 validateApiSlot(vm, slot);
2250 assert(IS_LIST(vm.apiStack[slot]), "Slot must hold a list.");
2251 2252 ValueBufferelements = AS_LIST(vm.apiStack[slot]).elements;
2253 returnelements.count;
2254 }
2255 2256 voidwrenGetListElement(WrenVM* vm, intlistSlot, intindex, intelementSlot)
2257 {
2258 validateApiSlot(vm, listSlot);
2259 validateApiSlot(vm, elementSlot);
2260 assert(IS_LIST(vm.apiStack[listSlot]), "Slot must hold a list.");
2261 2262 ValueBufferelements = AS_LIST(vm.apiStack[listSlot]).elements;
2263 2264 uintusedIndex = wrenValidateIndex(elements.count, index);
2265 assert(usedIndex != uint.max, "Index out of bounds.");
2266 2267 vm.apiStack[elementSlot] = elements.data[usedIndex];
2268 }
2269 2270 voidwrenSetListElement(WrenVM* vm, intlistSlot, intindex, intelementSlot)
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 uintusedIndex = 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 voidwrenInsertInList(WrenVM* vm, intlistSlot, intindex, intelementSlot)
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 intwrenGetMapCount(WrenVM* vm, intslot)
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 returnmap.count;
2308 }
2309 2310 boolwrenGetMapContainsKey(WrenVM* vm, intmapSlot, intkeySlot)
2311 {
2312 importwren.primitive : validateKey;
2313 validateApiSlot(vm, mapSlot);
2314 validateApiSlot(vm, keySlot);
2315 assert(IS_MAP(vm.apiStack[mapSlot]), "Slot must hold a map.");
2316 2317 Valuekey = vm.apiStack[keySlot];
2318 assert(wrenMapIsValidKey(key), "Key must be a value type");
2319 if (!validateKey(vm, key)) returnfalse;
2320 2321 ObjMap* map = AS_MAP(vm.apiStack[mapSlot]);
2322 Valuevalue = wrenMapGet(map, key);
2323 2324 return !IS_UNDEFINED(value);
2325 }
2326 2327 voidwrenGetMapValue(WrenVM* vm, intmapSlot, intkeySlot, intvalueSlot)
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 Valuevalue = wrenMapGet(map, vm.apiStack[keySlot]);
2336 if (IS_UNDEFINED(value)) {
2337 value = NULL_VAL;
2338 }
2339 2340 vm.apiStack[valueSlot] = value;
2341 }
2342 2343 voidwrenSetMapValue(WrenVM* vm, intmapSlot, intkeySlot, intvalueSlot)
2344 {
2345 importwren.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 Valuekey = vm.apiStack[keySlot];
2352 assert(wrenMapIsValidKey(key), "Key must be a value type");
2353 2354 if (!validateKey(vm, key)) {
2355 return;
2356 }
2357 2358 Valuevalue = vm.apiStack[valueSlot];
2359 ObjMap* map = AS_MAP(vm.apiStack[mapSlot]);
2360 2361 wrenMapSet(vm, map, key, value);
2362 }
2363 2364 voidwrenRemoveMapValue(WrenVM* vm, intmapSlot, intkeySlot,
2365 intremovedValueSlot)
2366 {
2367 importwren.primitive : validateKey;
2368 validateApiSlot(vm, mapSlot);
2369 validateApiSlot(vm, keySlot);
2370 assert(IS_MAP(vm.apiStack[mapSlot]), "Slot must hold a map.");
2371 2372 Valuekey = vm.apiStack[keySlot];
2373 if (!validateKey(vm, key)) {
2374 return;
2375 }
2376 2377 ObjMap* map = AS_MAP(vm.apiStack[mapSlot]);
2378 Valueremoved = wrenMapRemoveKey(vm, map, key);
2379 setSlot(vm, removedValueSlot, removed);
2380 }
2381 2382 voidwrenGetVariable(WrenVM* vm, const(char)* module_, const(char)* name,
2383 intslot)
2384 {
2385 importcore.stdc.string : strlen;
2386 2387 assert(module_ != null, "Module cannot be NULL.");
2388 assert(name != null, "Variable name cannot be NULL.");
2389 2390 ValuemoduleName = 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 intvariableSlot = 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 boolwrenHasVariable(WrenVM* vm, const(char)* module_, const(char)* name)
2406 {
2407 importcore.stdc.string : strlen;
2408 2409 assert(module_ != null, "Module cannot be NULL.");
2410 assert(name != null, "Variable name cannot be NULL.");
2411 2412 ValuemoduleName = 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 intvariableSlot = wrenSymbolTableFind(&moduleObj.variableNames,
2422 name, strlen(name));
2423 2424 returnvariableSlot != -1;
2425 }
2426 2427 boolwrenHasModule(WrenVM* vm, const(char)* module_)
2428 {
2429 assert(module_ != null, "Module cannot be NULL.");
2430 2431 ValuemoduleName = wrenStringFormat(vm, "$", module_);
2432 wrenPushRoot(vm, AS_OBJ(moduleName));
2433 2434 ObjModule* moduleObj = getModule(vm, moduleName);
2435 2436 wrenPopRoot(vm); // moduleName.2437 2438 returnmoduleObj != null;
2439 }
2440 2441 voidwrenAbortFiber(WrenVM* vm, intslot)
2442 {
2443 validateApiSlot(vm, slot);
2444 vm.fiber.error = vm.apiStack[slot];
2445 }
2446 2447 void* wrenGetUserData(WrenVM* vm)
2448 {
2449 returnvm.config.userData;
2450 }
2451 2452 voidwrenSetUserData(WrenVM* vm, void* userData)
2453 {
2454 vm.config.userData = userData;
2455 }
2456 2457 staticboolwrenIsLocalName(const(char)* name)
2458 {
2459 returnname[0] >= 'a' && name[0] <= 'z';
2460 }
2461 2462 staticboolwrenIsFalsyValue(Valuevalue)
2463 {
2464 returnIS_FALSE(value) || IS_NULL(value);
2465 }