feat: allow re-entrant wrenCall from foreign methods by resetting apiStack to NULL

The core change in wren_vm.c replaces the save/restore pattern for vm->apiStack with a simple NULL assignment after foreign calls, enabling nested wrenCall invocations from within foreign method handlers. This is accompanied by new test files (call_calls_foreign.c/.h/.wren) that verify the re-entrant call path works correctly, along with Makefile and Xcode project updates to build and run the new tests. A minor typo fix in reset_stack_after_call_abort.c renames a handle variable from 'afterConstruct' to 'afterAbort' for consistency.
This commit is contained in:
Bob Nystrom
2019-02-09 01:09:39 +00:00
parent 4a0e60236e
commit 523e157c6a
10 changed files with 111 additions and 16 deletions
+20 -9
View File
@@ -268,8 +268,8 @@ static ObjUpvalue* captureUpvalue(WrenVM* vm, ObjFiber* fiber, Value* local)
return createdUpvalue;
}
// Closes any open upvates that have been created for stack slots at [last] and
// above.
// Closes any open upvalues that have been created for stack slots at [last]
// and above.
static void closeUpvalues(ObjFiber* fiber, Value* last)
{
while (fiber->openUpvalues != NULL &&
@@ -370,8 +370,7 @@ static void bindMethod(WrenVM* vm, int methodType, int symbol,
static void callForeign(WrenVM* vm, ObjFiber* fiber,
WrenForeignMethodFn foreign, int numArgs)
{
// Save the current state so we can restore it when done.
Value* apiStack = vm->apiStack;
ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call.");
vm->apiStack = fiber->stackTop - numArgs;
foreign(vm);
@@ -379,7 +378,8 @@ static void callForeign(WrenVM* vm, ObjFiber* fiber,
// Discard the stack slots for the arguments and temporaries but leave one
// for the result.
fiber->stackTop = vm->apiStack + 1;
vm->apiStack = apiStack;
vm->apiStack = NULL;
}
// Handles the current fiber having aborted because of an error.
@@ -624,13 +624,12 @@ static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
// Pass the constructor arguments to the allocator as well.
Value* oldApiStack = vm->apiStack;
ASSERT(vm->apiStack == NULL, "Cannot already be in foreign call.");
vm->apiStack = stack;
method->as.foreign(vm);
vm->apiStack = oldApiStack;
// TODO: Check that allocateForeign was called.
vm->apiStack = NULL;
}
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
@@ -1379,12 +1378,24 @@ WrenInterpretResult wrenCall(WrenVM* vm, WrenHandle* method)
ASSERT(vm->fiber->stackTop - vm->fiber->stack >= closure->fn->arity,
"Stack must have enough arguments for method.");
// Clear the API stack. Now that wrenCall() has control, we no longer need
// it. We use this being non-null to tell if re-entrant calls to foreign
// methods are happening, so it's important to clear it out now so that you
// can call foreign methods from within calls to wrenCall().
vm->apiStack = NULL;
// Discard any extra temporary slots. We take for granted that the stub
// function has exactly one slot for each argument.
vm->fiber->stackTop = &vm->fiber->stack[closure->fn->maxSlots];
wrenCallFunction(vm, vm->fiber, closure, 0);
return runInterpreter(vm, vm->fiber);
WrenInterpretResult result = runInterpreter(vm, vm->fiber);
// If the call didn't abort, then set up the API stack to point to the
// beginning of the stack so the host can access the call's return value.
if (vm->fiber != NULL) vm->apiStack = vm->fiber->stack;
return result;
}
WrenHandle* wrenMakeHandle(WrenVM* vm, Value value)