diff --git a/src/include/wren.h b/src/include/wren.h index f107f6e3..55aed491 100644 --- a/src/include/wren.h +++ b/src/include/wren.h @@ -240,6 +240,14 @@ void* wrenAllocateForeign(WrenVM* vm, size_t size); // Returns the number of slots available to the current foreign method. int wrenGetSlotCount(WrenVM* vm); +// Ensures that the foreign method stack has at least [numSlots] available for +// use, growing the stack if needed. +// +// Does not shrink the stack if it has more than enough slots. +// +// It is an error to call this from a finalizer. +void wrenEnsureSlots(WrenVM* vm, int numSlots); + // TODO: Update docs. // The following functions read one of the arguments passed to a foreign call. @@ -327,6 +335,9 @@ void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, int length); // Stores the numeric [value] in [slot]. void wrenSetSlotDouble(WrenVM* vm, int slot, double value); +// Stores a new empty list in [slot]. +void wrenSetSlotNewList(WrenVM* vm, int slot); + // Stores null in [slot]. void wrenSetSlotNull(WrenVM* vm, int slot); @@ -343,4 +354,11 @@ void wrenSetSlotString(WrenVM* vm, int slot, const char* text); // This does not release the handle for the value. void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value); +// Takes the value stored at [elementSlot] and inserts it into the list stored +// at [listSlot] at [index]. +// +// As in Wren, negative indexes can be used to insert from the end. To append +// an element, use `-1` for the index. +void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot); + #endif diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 3ce874ff..e8439b63 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -420,6 +420,48 @@ static bool checkArity(WrenVM* vm, Value value, int numArgs) return false; } +// Ensures [fiber]'s stack has at least [needed] slots. +static void ensureStack(WrenVM* vm, ObjFiber* fiber, int needed) +{ + if (fiber->stackCapacity >= needed) return; + + int capacity = wrenPowerOf2Ceil(needed); + + Value* oldStack = fiber->stack; + fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, + sizeof(Value) * fiber->stackCapacity, + sizeof(Value) * capacity); + fiber->stackCapacity = capacity; + + // If the reallocation moves the stack, then we need to shift every pointer + // into the stack to point to its new location. + if (fiber->stack != oldStack) + { + // Top of the stack. + long offset = fiber->stack - oldStack; + fiber->stackTop += offset; + + // Stack pointer for each call frame. + for (int i = 0; i < fiber->numFrames; i++) + { + fiber->frames[i].stackStart += offset; + } + + // Open upvalues. + for (ObjUpvalue* upvalue = fiber->openUpvalues; + upvalue != NULL; + upvalue = upvalue->next) + { + upvalue->value += offset; + } + + if (vm->foreignStackStart != NULL) + { + vm->foreignStackStart += offset; + } + } +} + // Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs] // arguments (including the receiver) to be on the top of the stack already. // [function] can be an `ObjFn` or `ObjClosure`. @@ -435,50 +477,12 @@ static inline void callFunction( sizeof(CallFrame) * max); fiber->frameCapacity = max; } - + // Grow the stack if needed. int stackSize = (int)(fiber->stackTop - fiber->stack); int needed = stackSize + wrenUpwrapClosure(function)->maxSlots; - - if (fiber->stackCapacity < needed) - { - int capacity = wrenPowerOf2Ceil(needed); - - Value* oldStack = fiber->stack; - fiber->stack = (Value*)wrenReallocate(vm, fiber->stack, - sizeof(Value) * fiber->stackCapacity, - sizeof(Value) * capacity); - fiber->stackCapacity = capacity; - - // If the reallocation moves the stack, then we need to shift every pointer - // into the stack to point to its new location. - if (fiber->stack != oldStack) - { - // Top of the stack. - long offset = fiber->stack - oldStack; - fiber->stackTop += offset; - - // Stack pointer for each call frame. - for (int i = 0; i < fiber->numFrames; i++) - { - fiber->frames[i].stackStart += offset; - } - - // Open upvalues. - for (ObjUpvalue* upvalue = fiber->openUpvalues; - upvalue != NULL; - upvalue = upvalue->next) - { - upvalue->value += offset; - } - - if (vm->foreignStackStart != NULL) - { - vm->foreignStackStart += offset; - } - } - } - + ensureStack(vm, fiber, needed); + wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs); } @@ -1625,23 +1629,39 @@ void wrenPopRoot(WrenVM* vm) vm->numTempRoots--; } +// Returns true if the VM is in a foreign method that's a finalizer. +// +// Finalizers don't run in the context of a fiber and have a single magic stack +// slot, so need to be handled a little specially. +static bool isInFinalizer(WrenVM* vm) +{ + return vm->fiber == NULL || + vm->foreignStackStart < vm->fiber->stack || + vm->foreignStackStart > vm->fiber->stackTop; +} + int wrenGetSlotCount(WrenVM* vm) { ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); - // If no fiber is executing or the foreign stack is not in it, we must be in - // a finalizer, in which case the "stack" just has one object, the object - // being finalized. - if (vm->fiber == NULL || - vm->foreignStackStart < vm->fiber->stack || - vm->foreignStackStart > vm->fiber->stackTop) - { - return 1; - } - + if (isInFinalizer(vm)) return 1; return (int)(vm->fiber->stackTop - vm->foreignStackStart); } +void wrenEnsureSlots(WrenVM* vm, int numSlots) +{ + ASSERT(!isInFinalizer(vm), "Cannot grow the stack in a finalizer."); + + int currentSize = (int)(vm->fiber->stackTop - vm->foreignStackStart); + if (currentSize >= numSlots) return; + + // Grow the stack if needed. + int needed = (int)(vm->foreignStackStart - vm->fiber->stack) + numSlots; + ensureStack(vm, vm->fiber, needed); + + vm->fiber->stackTop = vm->foreignStackStart + numSlots; +} + // Ensures that [slot] is a valid index into a foreign method's stack of slots. static void validateForeignSlot(WrenVM* vm, int slot) { @@ -1723,6 +1743,11 @@ void wrenSetSlotDouble(WrenVM* vm, int slot, double value) setSlot(vm, slot, NUM_VAL(value)); } +void wrenSetSlotNewList(WrenVM* vm, int slot) +{ + setSlot(vm, slot, OBJ_VAL(wrenNewList(vm, 0))); +} + void wrenSetSlotNull(WrenVM* vm, int slot) { setSlot(vm, slot, NULL_VAL); @@ -1740,3 +1765,19 @@ void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value) setSlot(vm, slot, value->value); } + +void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) +{ + validateForeignSlot(vm, listSlot); + validateForeignSlot(vm, elementSlot); + ASSERT(IS_LIST(vm->foreignStackStart[listSlot]), "Must insert into a list."); + + ObjList* list = AS_LIST(vm->foreignStackStart[listSlot]); + + // Negative indices count from the end. + if (index < 0) index = list->elements.count + 1 + index; + + ASSERT(index <= list->elements.count, "Index out of bounds."); + + wrenListInsert(vm, list, vm->foreignStackStart[elementSlot], index); +} diff --git a/test/api/lists.c b/test/api/lists.c new file mode 100644 index 00000000..8a2f7a48 --- /dev/null +++ b/test/api/lists.c @@ -0,0 +1,46 @@ +#include + +#include "lists.h" + +static void newList(WrenVM* vm) +{ + wrenSetSlotNewList(vm, 0); +} + +// Helper function to store a double in a slot then insert it into the list at +// slot zero. +static void insertNumber(WrenVM* vm, int index, double value) +{ + wrenSetSlotDouble(vm, 1, value); + wrenInsertInList(vm, 0, index, 1); +} + +static void insert(WrenVM* vm) +{ + wrenSetSlotNewList(vm, 0); + + wrenEnsureSlots(vm, 2); + + // Appending. + insertNumber(vm, 0, 1.0); + insertNumber(vm, 1, 2.0); + insertNumber(vm, 2, 3.0); + + // Inserting. + insertNumber(vm, 0, 4.0); + insertNumber(vm, 1, 5.0); + insertNumber(vm, 2, 6.0); + + // Negative indexes. + insertNumber(vm, -1, 7.0); + insertNumber(vm, -2, 8.0); + insertNumber(vm, -3, 9.0); +} + +WrenForeignMethodFn listsBindMethod(const char* signature) +{ + if (strcmp(signature, "static Lists.newList()") == 0) return newList; + if (strcmp(signature, "static Lists.insert()") == 0) return insert; + + return NULL; +} diff --git a/test/api/lists.h b/test/api/lists.h new file mode 100644 index 00000000..9c74380b --- /dev/null +++ b/test/api/lists.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn listsBindMethod(const char* signature); diff --git a/test/api/lists.wren b/test/api/lists.wren new file mode 100644 index 00000000..64135a5b --- /dev/null +++ b/test/api/lists.wren @@ -0,0 +1,10 @@ +class Lists { + foreign static newList() + foreign static insert() +} + +var list = Lists.newList() +System.print(list is List) // expect: true +System.print(list.count) // expect: 0 + +System.print(Lists.insert()) // expect: [4, 5, 6, 1, 2, 3, 9, 8, 7] diff --git a/test/api/main.c b/test/api/main.c index dbff716a..e9e67768 100644 --- a/test/api/main.c +++ b/test/api/main.c @@ -7,6 +7,7 @@ #include "benchmark.h" #include "call.h" #include "foreign_class.h" +#include "lists.h" #include "slots.h" #include "value.h" @@ -36,6 +37,9 @@ static WrenForeignMethodFn bindForeignMethod( method = foreignClassBindMethod(fullName); if (method != NULL) return method; + method = listsBindMethod(fullName); + if (method != NULL) return method; + method = slotsBindMethod(fullName); if (method != NULL) return method; diff --git a/test/api/slots.c b/test/api/slots.c index 8a2c7308..347b9d75 100644 --- a/test/api/slots.c +++ b/test/api/slots.c @@ -1,3 +1,4 @@ +#include #include #include "slots.h" @@ -73,11 +74,38 @@ static void setSlots(WrenVM* vm) } } +static void ensure(WrenVM* vm) +{ + int before = wrenGetSlotCount(vm); + + wrenEnsureSlots(vm, 20); + + int after = wrenGetSlotCount(vm); + + // Use the slots to make sure they're available. + for (int i = 0; i < 20; i++) + { + wrenSetSlotDouble(vm, i, i); + } + + int sum = 0; + + for (int i = 0; i < 20; i++) + { + sum += (int)wrenGetSlotDouble(vm, i); + } + + char result[100]; + sprintf(result, "%d -> %d (%d)", before, after, sum); + wrenSetSlotString(vm, 0, result); +} + WrenForeignMethodFn slotsBindMethod(const char* signature) { if (strcmp(signature, "static Slots.noSet") == 0) return noSet; if (strcmp(signature, "static Slots.getSlots(_,_,_,_,_)") == 0) return getSlots; if (strcmp(signature, "static Slots.setSlots(_,_,_,_)") == 0) return setSlots; + if (strcmp(signature, "static Slots.ensure()") == 0) return ensure; return NULL; } diff --git a/test/api/slots.wren b/test/api/slots.wren index 119251d5..70d403e8 100644 --- a/test/api/slots.wren +++ b/test/api/slots.wren @@ -1,9 +1,8 @@ class Slots { foreign static noSet - foreign static getSlots(bool, num, string, bytes, value) - foreign static setSlots(a, b, c, d) + foreign static ensure() } // If nothing is set in the return slot, it retains its previous value, the @@ -14,3 +13,5 @@ var value = ["value"] System.print(Slots.getSlots(true, "by\0te", 12.34, "str", value) == value) // expect: true System.print(Slots.setSlots(value, 0, 0, 0) == value) // expect: true + +System.print(Slots.ensure()) // expect: 1 -> 20 (190) diff --git a/util/xcode/wren.xcodeproj/project.pbxproj b/util/xcode/wren.xcodeproj/project.pbxproj index a1fb096b..f5cc72d5 100644 --- a/util/xcode/wren.xcodeproj/project.pbxproj +++ b/util/xcode/wren.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 29729F331BA70A620099CA20 /* io.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29729F301BA70A620099CA20 /* io.wren.inc */; }; 2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; }; 29932D511C20D8C900099DEE /* benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D4F1C20D8C900099DEE /* benchmark.c */; }; + 29932D541C210F8D00099DEE /* lists.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D521C210F8D00099DEE /* lists.c */; }; 29A427341BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; }; 29A427351BDBE435001E6E22 /* wren_opt_meta.c in Sources */ = {isa = PBXBuildFile; fileRef = 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */; }; 29A427361BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */; }; @@ -106,6 +107,8 @@ 2986F6D61ACF93BA00BCE26C /* wren_primitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = ""; }; 29932D4F1C20D8C900099DEE /* benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = ""; }; 29932D501C20D8C900099DEE /* benchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = benchmark.h; path = ../../test/api/benchmark.h; sourceTree = ""; }; + 29932D521C210F8D00099DEE /* lists.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lists.c; path = ../../test/api/lists.c; sourceTree = ""; }; + 29932D531C210F8D00099DEE /* lists.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lists.h; path = ../../test/api/lists.h; sourceTree = ""; }; 29A4272E1BDBE435001E6E22 /* wren_opt_meta.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = wren_opt_meta.c; path = ../../src/optional/wren_opt_meta.c; sourceTree = ""; }; 29A4272F1BDBE435001E6E22 /* wren_opt_meta.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_opt_meta.h; path = ../../src/optional/wren_opt_meta.h; sourceTree = ""; }; 29A427301BDBE435001E6E22 /* wren_opt_meta.wren.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; name = wren_opt_meta.wren.inc; path = ../../src/optional/wren_opt_meta.wren.inc; sourceTree = ""; }; @@ -249,6 +252,8 @@ 293D46951BB43F9900200083 /* call.h */, 29D009A81B7E39A8000CE58C /* foreign_class.c */, 29D009A91B7E39A8000CE58C /* foreign_class.h */, + 29932D521C210F8D00099DEE /* lists.c */, + 29932D531C210F8D00099DEE /* lists.h */, 29D009AA1B7E39A8000CE58C /* slots.c */, 29D009AB1B7E39A8000CE58C /* slots.h */, 29D009AC1B7E39A8000CE58C /* value.c */, @@ -358,6 +363,7 @@ files = ( 29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */, 29729F321BA70A620099CA20 /* io.c in Sources */, + 29932D541C210F8D00099DEE /* lists.c in Sources */, 291647C81BA5EC5E006142EE /* modules.c in Sources */, 29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */, 29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */,