feat: add wrenEnsureSlots, wrenSetSlotNewList, and wrenInsertInList C API functions for list manipulation

Add three new public C API functions to the Wren embedding API that enable
foreign methods to create and populate lists. wrenEnsureSlots grows the
foreign slot stack to guarantee space for temporary work, wrenSetSlotNewList
stores a new empty list in a slot, and wrenInsertInList inserts a value from
one slot into a list at another slot at a given index (supporting negative
indices for relative positioning). The implementation includes a new internal
ensureStack helper that reallocates the fiber's stack and updates all
pointers (stack top, call frame starts, open upvalues, and foreign stack
start) when the reallocation moves the stack. New test files (lists.c,
lists.h, lists.wren) exercise creation, appending, insertion, and negative
index insertion, and the existing slots test is extended with an ensure test
that verifies slot count growth and slot usability.
This commit is contained in:
Bob Nystrom
2015-12-17 00:28:26 +00:00
parent 7535fed608
commit 23b6980dfd
9 changed files with 210 additions and 53 deletions
+92 -51
View File
@@ -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);
}