Add C API functions for working with lists:

- wrenEnsureSlots()
  Lets you go the foreign slot stack to make room for a temporary work
  area.

- wrenSetSlotNewList()
  Creates a new empty list and stores it in a slot.

- wrenInsertInList()
  Takes a value from one slot and inserts it into the list in another.

Still need more functions like getting elements from a list, removing,
etc. but this at least lets you create, populate, and return lists from
foreign methods.
This commit is contained in:
Bob Nystrom
2015-12-16 16:28:26 -08:00
parent 7fcdcf2f1a
commit 6f37d379f4
9 changed files with 210 additions and 53 deletions

View File

@ -240,6 +240,14 @@ void* wrenAllocateForeign(WrenVM* vm, size_t size);
// Returns the number of slots available to the current foreign method. // Returns the number of slots available to the current foreign method.
int wrenGetSlotCount(WrenVM* vm); 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. // TODO: Update docs.
// The following functions read one of the arguments passed to a foreign call. // 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]. // Stores the numeric [value] in [slot].
void wrenSetSlotDouble(WrenVM* vm, int slot, double value); 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]. // Stores null in [slot].
void wrenSetSlotNull(WrenVM* vm, int 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. // This does not release the handle for the value.
void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* 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 #endif

View File

@ -420,6 +420,48 @@ static bool checkArity(WrenVM* vm, Value value, int numArgs)
return false; 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] // Pushes [function] onto [fiber]'s callstack and invokes it. Expects [numArgs]
// arguments (including the receiver) to be on the top of the stack already. // arguments (including the receiver) to be on the top of the stack already.
// [function] can be an `ObjFn` or `ObjClosure`. // [function] can be an `ObjFn` or `ObjClosure`.
@ -435,50 +477,12 @@ static inline void callFunction(
sizeof(CallFrame) * max); sizeof(CallFrame) * max);
fiber->frameCapacity = max; fiber->frameCapacity = max;
} }
// Grow the stack if needed. // Grow the stack if needed.
int stackSize = (int)(fiber->stackTop - fiber->stack); int stackSize = (int)(fiber->stackTop - fiber->stack);
int needed = stackSize + wrenUpwrapClosure(function)->maxSlots; int needed = stackSize + wrenUpwrapClosure(function)->maxSlots;
ensureStack(vm, fiber, needed);
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;
}
}
}
wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs); wrenAppendCallFrame(vm, fiber, function, fiber->stackTop - numArgs);
} }
@ -1625,23 +1629,39 @@ void wrenPopRoot(WrenVM* vm)
vm->numTempRoots--; 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) int wrenGetSlotCount(WrenVM* vm)
{ {
ASSERT(vm->foreignStackStart != NULL, "Must be in foreign call."); 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 if (isInFinalizer(vm)) return 1;
// 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;
}
return (int)(vm->fiber->stackTop - vm->foreignStackStart); 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. // Ensures that [slot] is a valid index into a foreign method's stack of slots.
static void validateForeignSlot(WrenVM* vm, int slot) 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)); 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) void wrenSetSlotNull(WrenVM* vm, int slot)
{ {
setSlot(vm, slot, NULL_VAL); setSlot(vm, slot, NULL_VAL);
@ -1740,3 +1765,19 @@ void wrenSetSlotValue(WrenVM* vm, int slot, WrenValue* value)
setSlot(vm, slot, value->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);
}

46
test/api/lists.c Normal file
View File

@ -0,0 +1,46 @@
#include <string.h>
#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;
}

3
test/api/lists.h Normal file
View File

@ -0,0 +1,3 @@
#include "wren.h"
WrenForeignMethodFn listsBindMethod(const char* signature);

10
test/api/lists.wren Normal file
View File

@ -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]

View File

@ -7,6 +7,7 @@
#include "benchmark.h" #include "benchmark.h"
#include "call.h" #include "call.h"
#include "foreign_class.h" #include "foreign_class.h"
#include "lists.h"
#include "slots.h" #include "slots.h"
#include "value.h" #include "value.h"
@ -36,6 +37,9 @@ static WrenForeignMethodFn bindForeignMethod(
method = foreignClassBindMethod(fullName); method = foreignClassBindMethod(fullName);
if (method != NULL) return method; if (method != NULL) return method;
method = listsBindMethod(fullName);
if (method != NULL) return method;
method = slotsBindMethod(fullName); method = slotsBindMethod(fullName);
if (method != NULL) return method; if (method != NULL) return method;

View File

@ -1,3 +1,4 @@
#include <stdio.h>
#include <string.h> #include <string.h>
#include "slots.h" #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) WrenForeignMethodFn slotsBindMethod(const char* signature)
{ {
if (strcmp(signature, "static Slots.noSet") == 0) return noSet; if (strcmp(signature, "static Slots.noSet") == 0) return noSet;
if (strcmp(signature, "static Slots.getSlots(_,_,_,_,_)") == 0) return getSlots; if (strcmp(signature, "static Slots.getSlots(_,_,_,_,_)") == 0) return getSlots;
if (strcmp(signature, "static Slots.setSlots(_,_,_,_)") == 0) return setSlots; if (strcmp(signature, "static Slots.setSlots(_,_,_,_)") == 0) return setSlots;
if (strcmp(signature, "static Slots.ensure()") == 0) return ensure;
return NULL; return NULL;
} }

View File

@ -1,9 +1,8 @@
class Slots { class Slots {
foreign static noSet foreign static noSet
foreign static getSlots(bool, num, string, bytes, value) foreign static getSlots(bool, num, string, bytes, value)
foreign static setSlots(a, b, c, d) foreign static setSlots(a, b, c, d)
foreign static ensure()
} }
// If nothing is set in the return slot, it retains its previous value, the // 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.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.setSlots(value, 0, 0, 0) == value) // expect: true
System.print(Slots.ensure()) // expect: 1 -> 20 (190)

View File

@ -26,6 +26,7 @@
29729F331BA70A620099CA20 /* io.wren.inc in Sources */ = {isa = PBXBuildFile; fileRef = 29729F301BA70A620099CA20 /* io.wren.inc */; }; 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 */; }; 2986F6D71ACF93BA00BCE26C /* wren_primitive.c in Sources */ = {isa = PBXBuildFile; fileRef = 2986F6D51ACF93BA00BCE26C /* wren_primitive.c */; };
29932D511C20D8C900099DEE /* benchmark.c in Sources */ = {isa = PBXBuildFile; fileRef = 29932D4F1C20D8C900099DEE /* benchmark.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 */; }; 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 */; }; 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 */; }; 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 = "<group>"; }; 2986F6D61ACF93BA00BCE26C /* wren_primitive.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = wren_primitive.h; path = ../../src/vm/wren_primitive.h; sourceTree = "<group>"; };
29932D4F1C20D8C900099DEE /* benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = "<group>"; }; 29932D4F1C20D8C900099DEE /* benchmark.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = benchmark.c; path = ../../test/api/benchmark.c; sourceTree = "<group>"; };
29932D501C20D8C900099DEE /* benchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = benchmark.h; path = ../../test/api/benchmark.h; sourceTree = "<group>"; }; 29932D501C20D8C900099DEE /* benchmark.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = benchmark.h; path = ../../test/api/benchmark.h; sourceTree = "<group>"; };
29932D521C210F8D00099DEE /* lists.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = lists.c; path = ../../test/api/lists.c; sourceTree = "<group>"; };
29932D531C210F8D00099DEE /* lists.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = lists.h; path = ../../test/api/lists.h; sourceTree = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
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 = "<group>"; }; 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 = "<group>"; };
@ -249,6 +252,8 @@
293D46951BB43F9900200083 /* call.h */, 293D46951BB43F9900200083 /* call.h */,
29D009A81B7E39A8000CE58C /* foreign_class.c */, 29D009A81B7E39A8000CE58C /* foreign_class.c */,
29D009A91B7E39A8000CE58C /* foreign_class.h */, 29D009A91B7E39A8000CE58C /* foreign_class.h */,
29932D521C210F8D00099DEE /* lists.c */,
29932D531C210F8D00099DEE /* lists.h */,
29D009AA1B7E39A8000CE58C /* slots.c */, 29D009AA1B7E39A8000CE58C /* slots.c */,
29D009AB1B7E39A8000CE58C /* slots.h */, 29D009AB1B7E39A8000CE58C /* slots.h */,
29D009AC1B7E39A8000CE58C /* value.c */, 29D009AC1B7E39A8000CE58C /* value.c */,
@ -358,6 +363,7 @@
files = ( files = (
29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */, 29A427371BDBE435001E6E22 /* wren_opt_meta.wren.inc in Sources */,
29729F321BA70A620099CA20 /* io.c in Sources */, 29729F321BA70A620099CA20 /* io.c in Sources */,
29932D541C210F8D00099DEE /* lists.c in Sources */,
291647C81BA5EC5E006142EE /* modules.c in Sources */, 291647C81BA5EC5E006142EE /* modules.c in Sources */,
29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */, 29DC14A11BBA2FEC008A8274 /* scheduler.c in Sources */,
29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */, 29A427391BDBE435001E6E22 /* wren_opt_random.c in Sources */,