feat: add va_list version of wrenCall and fix GC bug with return values
- Introduce wrenCallVarArgs as an explicit va_list variant, allowing variadic functions to forward their arguments directly without re-parsing. - Fix a garbage collection issue in wrenCall where return values could be prematurely collected by ensuring proper root handling. - Refactor the call API test to avoid re-entering the VM by moving test execution into an afterLoad callback instead of a foreign method.
This commit is contained in:
+6
-1
@@ -12,6 +12,7 @@ static WrenVM* vm;
|
||||
|
||||
static WrenBindForeignMethodFn bindMethodFn = NULL;
|
||||
static WrenBindForeignClassFn bindClassFn = NULL;
|
||||
static WrenForeignMethodFn afterLoadFn = NULL;
|
||||
|
||||
static uv_loop_t* loop;
|
||||
|
||||
@@ -204,6 +205,8 @@ void runFile(const char* path)
|
||||
|
||||
WrenInterpretResult result = wrenInterpret(vm, path, source);
|
||||
|
||||
if (afterLoadFn != NULL) afterLoadFn(vm);
|
||||
|
||||
if (result == WREN_RESULT_SUCCESS)
|
||||
{
|
||||
uv_run(loop, UV_RUN_DEFAULT);
|
||||
@@ -260,8 +263,10 @@ uv_loop_t* getLoop()
|
||||
}
|
||||
|
||||
void setTestCallbacks(WrenBindForeignMethodFn bindMethod,
|
||||
WrenBindForeignClassFn bindClass)
|
||||
WrenBindForeignClassFn bindClass,
|
||||
WrenForeignMethodFn afterLoad)
|
||||
{
|
||||
bindMethodFn = bindMethod;
|
||||
bindClassFn = bindClass;
|
||||
afterLoadFn = afterLoad;
|
||||
}
|
||||
|
||||
+2
-1
@@ -23,6 +23,7 @@ uv_loop_t* getLoop();
|
||||
// Used by the API test executable to let it wire up its own foreign functions.
|
||||
// This must be called before calling [createVM()].
|
||||
void setTestCallbacks(WrenBindForeignMethodFn bindMethod,
|
||||
WrenBindForeignClassFn bindClass);
|
||||
WrenBindForeignClassFn bindClass,
|
||||
void (*afterLoad)(WrenVM* vm));
|
||||
|
||||
#endif
|
||||
|
||||
+11
-5
@@ -1,6 +1,7 @@
|
||||
#ifndef wren_h
|
||||
#define wren_h
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
@@ -223,13 +224,18 @@ WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
|
||||
// release the value. If the passed argument is NULL, this becomes a
|
||||
// Wren NULL.
|
||||
//
|
||||
// [method] must have been created by a call to [wrenGetMethod]. If [result] is
|
||||
// not `NULL`, the return value of the method will be stored in a new
|
||||
// [WrenValue] that [result] will point to. Don't forget to release it, when
|
||||
// done with it.
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method, WrenValue** result,
|
||||
// [method] must have been created by a call to [wrenGetMethod]. If
|
||||
// [returnValue] is not `NULL`, the return value of the method will be stored
|
||||
// in a new [WrenValue] that [returnValue] will point to. Don't forget to
|
||||
// release it, when done with it.
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, ...);
|
||||
|
||||
WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, va_list args);
|
||||
|
||||
// Releases the reference stored in [value]. After calling this, [value] can no
|
||||
// longer be used.
|
||||
void wrenReleaseValue(WrenVM* vm, WrenValue* value);
|
||||
|
||||
+56
-36
@@ -71,9 +71,6 @@ WrenVM* wrenNewVM(WrenConfiguration* config)
|
||||
wrenPopRoot(vm); // name.
|
||||
|
||||
wrenInitializeCore(vm);
|
||||
#if WREN_USE_LIB_IO
|
||||
wrenLoadIOLibrary(vm);
|
||||
#endif
|
||||
#if WREN_USE_LIB_META
|
||||
wrenLoadMetaLibrary(vm);
|
||||
#endif
|
||||
@@ -1316,19 +1313,30 @@ WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
|
||||
return method;
|
||||
}
|
||||
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method, WrenValue** result,
|
||||
WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, argTypes);
|
||||
WrenInterpretResult result = wrenCallVarArgs(vm, method, returnValue,
|
||||
argTypes, args);
|
||||
va_end(args);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WrenInterpretResult wrenCallVarArgs(WrenVM* vm, WrenValue* method,
|
||||
WrenValue** returnValue,
|
||||
const char* argTypes, va_list args)
|
||||
{
|
||||
// TODO: Validate that the number of arguments matches what the method
|
||||
// expects.
|
||||
|
||||
|
||||
ASSERT(IS_FIBER(method->value), "Value must come from wrenGetMethod().");
|
||||
ObjFiber* fiber = AS_FIBER(method->value);
|
||||
|
||||
|
||||
// Push the arguments.
|
||||
va_list argList;
|
||||
va_start(argList, argTypes);
|
||||
|
||||
for (const char* argType = argTypes; *argType != '\0'; argType++)
|
||||
{
|
||||
Value value = NULL_VAL;
|
||||
@@ -1336,53 +1344,65 @@ WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method, WrenValue** result,
|
||||
{
|
||||
case 'a':
|
||||
{
|
||||
const char* bytes = va_arg(argList, const char*);
|
||||
int length = va_arg(argList, int);
|
||||
const char* bytes = va_arg(args, const char*);
|
||||
int length = va_arg(args, int);
|
||||
value = wrenNewString(vm, bytes, (size_t)length);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'b': value = BOOL_VAL(va_arg(argList, int)); break;
|
||||
case 'd': value = NUM_VAL(va_arg(argList, double)); break;
|
||||
case 'i': value = NUM_VAL((double)va_arg(argList, int)); break;
|
||||
case 'n': value = NULL_VAL; va_arg(argList, void*); break;
|
||||
case 'b': value = BOOL_VAL(va_arg(args, int)); break;
|
||||
case 'd': value = NUM_VAL(va_arg(args, double)); break;
|
||||
case 'i': value = NUM_VAL((double)va_arg(args, int)); break;
|
||||
case 'n': value = NULL_VAL; va_arg(args, void*); break;
|
||||
case 's':
|
||||
value = wrenStringFormat(vm, "$", va_arg(argList, const char*));
|
||||
value = wrenStringFormat(vm, "$", va_arg(args, const char*));
|
||||
break;
|
||||
|
||||
|
||||
case 'v':
|
||||
{
|
||||
// Allow a NULL value pointer for Wren null.
|
||||
WrenValue* wrenValue = va_arg(argList, WrenValue*);
|
||||
WrenValue* wrenValue = va_arg(args, WrenValue*);
|
||||
if (wrenValue != NULL) value = wrenValue->value;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
default:
|
||||
ASSERT(false, "Unknown argument type.");
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
*fiber->stackTop++ = value;
|
||||
}
|
||||
|
||||
va_end(argList);
|
||||
|
||||
|
||||
Value receiver = fiber->stack[0];
|
||||
Obj* fn = fiber->frames[0].fn;
|
||||
|
||||
// TODO: How does this handle a runtime error occurring?
|
||||
runInterpreter(vm, fiber);
|
||||
|
||||
if (result != NULL) *result = wrenCaptureValue(vm, fiber->stack[0]);
|
||||
|
||||
// Reset the fiber to get ready for the next call.
|
||||
wrenResetFiber(vm, fiber, fn);
|
||||
|
||||
// Push the receiver back on the stack.
|
||||
*fiber->stackTop++ = receiver;
|
||||
|
||||
return WREN_RESULT_SUCCESS;
|
||||
wrenPushRoot(vm, (Obj*)fn);
|
||||
|
||||
WrenInterpretResult result = runInterpreter(vm, fiber);
|
||||
|
||||
if (result == WREN_RESULT_SUCCESS)
|
||||
{
|
||||
if (returnValue != NULL)
|
||||
{
|
||||
// Make sure the return value doesn't get collected while capturing it.
|
||||
fiber->stackTop++;
|
||||
*returnValue = wrenCaptureValue(vm, fiber->stack[0]);
|
||||
}
|
||||
|
||||
// Reset the fiber to get ready for the next call.
|
||||
wrenResetFiber(vm, fiber, fn);
|
||||
|
||||
// Push the receiver back on the stack.
|
||||
*fiber->stackTop++ = receiver;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (returnValue != NULL) *returnValue = NULL;
|
||||
}
|
||||
|
||||
wrenPopRoot(vm);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
WrenValue* wrenCaptureValue(WrenVM* vm, Value value)
|
||||
|
||||
Reference in New Issue
Block a user