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:
@@ -66,6 +66,7 @@ void callRunTests(WrenVM* vm)
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenSetSlotHandle(vm, 0, callClass);
|
||||
wrenCall(vm, getValue);
|
||||
printf("slots after call: %d\n", wrenGetSlotCount(vm));
|
||||
WrenHandle* value = wrenGetSlotHandle(vm, 0);
|
||||
|
||||
// Different argument types.
|
||||
|
||||
@@ -48,6 +48,7 @@ class Call {
|
||||
// expect: subscript 1 2
|
||||
// expect: subscript set 1 2 3
|
||||
|
||||
// expect: slots after call: 1
|
||||
// expect: two true false
|
||||
// expect: two 1.2 3.4
|
||||
// expect: two string another
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
static void api(WrenVM *vm) {
|
||||
// Grow the slot array. This should trigger the stack to be moved.
|
||||
wrenEnsureSlots(vm, 10);
|
||||
wrenSetSlotNewList(vm, 0);
|
||||
|
||||
for (int i = 1; i < 10; i++)
|
||||
{
|
||||
wrenSetSlotDouble(vm, i, i);
|
||||
wrenInsertInList(vm, 0, -1, i);
|
||||
}
|
||||
}
|
||||
|
||||
WrenForeignMethodFn callCallsForeignBindMethod(const char* signature)
|
||||
{
|
||||
if (strcmp(signature, "static CallCallsForeign.api()") == 0) return api;
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void callCallsForeignRunTests(WrenVM* vm)
|
||||
{
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "./test/api/call_calls_foreign", "CallCallsForeign", 0);
|
||||
WrenHandle* apiClass = wrenGetSlotHandle(vm, 0);
|
||||
WrenHandle *call = wrenMakeCallHandle(vm, "call(_)");
|
||||
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotHandle(vm, 0, apiClass);
|
||||
wrenSetSlotString(vm, 1, "parameter");
|
||||
|
||||
printf("slots before %d\n", wrenGetSlotCount(vm));
|
||||
wrenCall(vm, call);
|
||||
|
||||
// We should have a single slot count for the return.
|
||||
printf("slots after %d\n", wrenGetSlotCount(vm));
|
||||
|
||||
wrenReleaseHandle(vm, call);
|
||||
wrenReleaseHandle(vm, apiClass);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
#include "wren.h"
|
||||
|
||||
WrenForeignMethodFn callCallsForeignBindMethod(const char* signature);
|
||||
void callCallsForeignRunTests(WrenVM* vm);
|
||||
@@ -0,0 +1,17 @@
|
||||
// Regression test for https://github.com/munificent/wren/issues/510.
|
||||
//
|
||||
// Tests that re-entrant API calls are handled correctly. The host uses
|
||||
// `wrenCall()` to invoke `CallCallsForeign.call()`. That in turn calls
|
||||
// `CallCallsForeign.api()`, which goes back through the API.
|
||||
class CallCallsForeign {
|
||||
foreign static api()
|
||||
|
||||
static call(param) {
|
||||
System.print(api())
|
||||
// expect: slots before 2
|
||||
// expect: [1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
System.print(param) // expect: parameter
|
||||
// expect: slots after 1
|
||||
return "result"
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "benchmark.h"
|
||||
#include "call.h"
|
||||
#include "call_calls_foreign.h"
|
||||
#include "call_wren_call_root.h"
|
||||
#include "error.h"
|
||||
#include "get_variable.h"
|
||||
@@ -42,6 +43,9 @@ static WrenForeignMethodFn bindForeignMethod(
|
||||
method = benchmarkBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = callCallsForeignBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
method = errorBindMethod(fullName);
|
||||
if (method != NULL) return method;
|
||||
|
||||
@@ -102,6 +106,10 @@ static void afterLoad(WrenVM* vm)
|
||||
{
|
||||
callRunTests(vm);
|
||||
}
|
||||
else if (strstr(testName, "/call_calls_foreign.wren") != NULL)
|
||||
{
|
||||
callCallsForeignRunTests(vm);
|
||||
}
|
||||
else if (strstr(testName, "/call_wren_call_root.wren") != NULL)
|
||||
{
|
||||
callWrenCallRootRunTests(vm);
|
||||
|
||||
@@ -10,7 +10,7 @@ void resetStackAfterCallAbortRunTests(WrenVM* vm)
|
||||
WrenHandle* testClass = wrenGetSlotHandle(vm, 0);
|
||||
|
||||
WrenHandle* abortFiber = wrenMakeCallHandle(vm, "abortFiber()");
|
||||
WrenHandle* afterConstruct = wrenMakeCallHandle(vm, "afterAbort(_,_)");
|
||||
WrenHandle* afterAbort = wrenMakeCallHandle(vm, "afterAbort(_,_)");
|
||||
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenSetSlotHandle(vm, 0, testClass);
|
||||
@@ -20,9 +20,9 @@ void resetStackAfterCallAbortRunTests(WrenVM* vm)
|
||||
wrenSetSlotHandle(vm, 0, testClass);
|
||||
wrenSetSlotDouble(vm, 1, 1.0);
|
||||
wrenSetSlotDouble(vm, 2, 2.0);
|
||||
wrenCall(vm, afterConstruct);
|
||||
wrenCall(vm, afterAbort);
|
||||
|
||||
wrenReleaseHandle(vm, testClass);
|
||||
wrenReleaseHandle(vm, abortFiber);
|
||||
wrenReleaseHandle(vm, afterConstruct);
|
||||
wrenReleaseHandle(vm, afterAbort);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user