feat: replace wrenGetMethod with slot-based wrenCall API using wrenMakeCallHandle and wrenEnsureSlots
Refactor the C API for invoking Wren methods from C code. Remove the old wrenGetMethod and wrenCallVarArgs functions that used a type-string-based argument marshalling system. Introduce a new slot-based approach where callers first call wrenEnsureSlots to reserve space, then use wrenSetSlot___ functions to place the receiver and arguments on the stack, and finally call wrenCall with a handle created by wrenMakeCallHandle. Results are retrieved via wrenGetSlot___. This change simplifies the VM internals, eliminates varargs parsing overhead, and yields a 185% speed improvement in the call benchmark. Update all internal modules (scheduler, io, timer) and test files to use the new API. Also fix fiber suspension to clear the API stack pointer and adjust the interpreter to store the final fiber result at stack[0] for C API access.
This commit is contained in:
+47
-20
@@ -18,7 +18,10 @@ typedef struct sFileRequestData
|
||||
|
||||
static const int stdinDescriptor = 0;
|
||||
|
||||
// Handle to Stdin.onData_(). Called when libuv provides data on stdin.
|
||||
// Handle to the Stdin class object.
|
||||
static WrenValue* stdinClass = NULL;
|
||||
|
||||
// Handle to an onData_() method call. Called when libuv provides data on stdin.
|
||||
static WrenValue* stdinOnData = NULL;
|
||||
|
||||
// The stream used to read from stdin. Initialized on the first read.
|
||||
@@ -33,7 +36,13 @@ static void shutdownStdin()
|
||||
free(stdinStream);
|
||||
stdinStream = NULL;
|
||||
}
|
||||
|
||||
|
||||
if (stdinClass != NULL)
|
||||
{
|
||||
wrenReleaseValue(getVM(), stdinClass);
|
||||
stdinClass = NULL;
|
||||
}
|
||||
|
||||
if (stdinOnData != NULL)
|
||||
{
|
||||
wrenReleaseValue(getVM(), stdinOnData);
|
||||
@@ -121,8 +130,9 @@ static void directoryListCallback(uv_fs_t* request)
|
||||
bufferLength += length + 1;
|
||||
}
|
||||
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
schedulerResumeBytes(fiber, buffer, bufferLength);
|
||||
schedulerResume(freeRequest(request), true);
|
||||
wrenSetSlotBytes(getVM(), 2, buffer, bufferLength);
|
||||
schedulerFinishResume();
|
||||
free(buffer);
|
||||
}
|
||||
|
||||
@@ -161,8 +171,9 @@ static void fileOpenCallback(uv_fs_t* request)
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
double fd = (double)request->result;
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
schedulerResumeDouble(fiber, fd);
|
||||
schedulerResume(freeRequest(request), true);
|
||||
wrenSetSlotDouble(getVM(), 2, fd);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void fileOpen(WrenVM* vm)
|
||||
@@ -180,8 +191,9 @@ static void fileSizeCallback(uv_fs_t* request)
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
double size = (double)request->statbuf.st_size;
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
schedulerResumeDouble(fiber, size);
|
||||
schedulerResume(freeRequest(request), true);
|
||||
wrenSetSlotDouble(getVM(), 2, size);
|
||||
schedulerFinishResume();
|
||||
}
|
||||
|
||||
void fileSizePath(WrenVM* vm)
|
||||
@@ -195,8 +207,7 @@ static void fileCloseCallback(uv_fs_t* request)
|
||||
{
|
||||
if (handleRequestError(request)) return;
|
||||
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
schedulerResume(fiber);
|
||||
schedulerResume(freeRequest(request), false);
|
||||
}
|
||||
|
||||
void fileClose(WrenVM* vm)
|
||||
@@ -233,12 +244,12 @@ static void fileReadBytesCallback(uv_fs_t* request)
|
||||
FileRequestData* data = (FileRequestData*)request->data;
|
||||
uv_buf_t buffer = data->buffer;
|
||||
|
||||
WrenValue* fiber = freeRequest(request);
|
||||
|
||||
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's
|
||||
// embedding API supported a way to *give* it bytes that were previously
|
||||
// allocated using Wren's own allocator.
|
||||
schedulerResumeBytes(fiber, buffer.base, buffer.len);
|
||||
schedulerResume(freeRequest(request), true);
|
||||
wrenSetSlotBytes(getVM(), 2, buffer.base, buffer.len);
|
||||
schedulerFinishResume();
|
||||
|
||||
// TODO: Likewise, freeing this after we resume is lame.
|
||||
free(buffer.base);
|
||||
@@ -282,25 +293,41 @@ static void allocCallback(uv_handle_t* handle, size_t suggestedSize,
|
||||
static void stdinReadCallback(uv_stream_t* stream, ssize_t numRead,
|
||||
const uv_buf_t* buffer)
|
||||
{
|
||||
WrenVM* vm = getVM();
|
||||
|
||||
if (stdinClass == NULL)
|
||||
{
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "io", "Stdin", 0);
|
||||
stdinClass = wrenGetSlotValue(vm, 0);
|
||||
}
|
||||
|
||||
if (stdinOnData == NULL)
|
||||
{
|
||||
stdinOnData = wrenMakeCallHandle(vm, "onData_(_)");
|
||||
}
|
||||
|
||||
// If stdin was closed, send null to let io.wren know.
|
||||
if (numRead == UV_EOF)
|
||||
{
|
||||
wrenCall(getVM(), stdinOnData, NULL, "v", NULL);
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotValue(vm, 0, stdinClass);
|
||||
wrenSetSlotNull(vm, 1);
|
||||
wrenCall(vm, stdinOnData);
|
||||
|
||||
shutdownStdin();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Handle other errors.
|
||||
|
||||
if (stdinOnData == NULL)
|
||||
{
|
||||
stdinOnData = wrenGetMethod(getVM(), "io", "Stdin", "onData_(_)");
|
||||
}
|
||||
|
||||
// TODO: Having to copy the bytes here is a drag. It would be good if Wren's
|
||||
// embedding API supported a way to *give* it bytes that were previously
|
||||
// allocated using Wren's own allocator.
|
||||
wrenCall(getVM(), stdinOnData, NULL, "a", buffer->base, numRead);
|
||||
wrenEnsureSlots(vm, 2);
|
||||
wrenSetSlotValue(vm, 0, stdinClass);
|
||||
wrenSetSlotBytes(vm, 1, buffer->base, numRead);
|
||||
wrenCall(vm, stdinOnData);
|
||||
|
||||
// TODO: Likewise, freeing this after we resume is lame.
|
||||
free(buffer->base);
|
||||
|
||||
+38
-33
@@ -7,30 +7,19 @@
|
||||
#include "wren.h"
|
||||
#include "vm.h"
|
||||
|
||||
// A handle to the "Scheduler" class object. Used to call static methods on it.
|
||||
static WrenValue* schedulerClass;
|
||||
|
||||
// This method resumes a fiber that is suspended waiting on an asynchronous
|
||||
// operation. The first resumes it with zero arguments, and the second passes
|
||||
// one.
|
||||
static WrenValue* resume;
|
||||
static WrenValue* resumeWithArg;
|
||||
static WrenValue* resume1;
|
||||
static WrenValue* resume2;
|
||||
static WrenValue* resumeError;
|
||||
|
||||
void schedulerCaptureMethods(WrenVM* vm)
|
||||
static void resume(WrenValue* method)
|
||||
{
|
||||
resume = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_)");
|
||||
resumeWithArg = wrenGetMethod(vm, "scheduler", "Scheduler", "resume_(_,_)");
|
||||
resumeError = wrenGetMethod(vm, "scheduler", "Scheduler", "resumeError_(_,_)");
|
||||
}
|
||||
|
||||
static void callResume(WrenValue* resumeMethod, WrenValue* fiber,
|
||||
const char* argTypes, ...)
|
||||
{
|
||||
va_list args;
|
||||
va_start(args, argTypes);
|
||||
WrenInterpretResult result = wrenCallVarArgs(getVM(), resumeMethod, NULL,
|
||||
argTypes, args);
|
||||
va_end(args);
|
||||
|
||||
wrenReleaseValue(getVM(), fiber);
|
||||
WrenInterpretResult result = wrenCall(getVM(), method);
|
||||
|
||||
// If a runtime error occurs in response to an async operation and nothing
|
||||
// catches the error in the fiber, then exit the CLI.
|
||||
@@ -41,34 +30,50 @@ static void callResume(WrenValue* resumeMethod, WrenValue* fiber,
|
||||
}
|
||||
}
|
||||
|
||||
void schedulerResume(WrenValue* fiber)
|
||||
void schedulerCaptureMethods(WrenVM* vm)
|
||||
{
|
||||
callResume(resume, fiber, "v", fiber);
|
||||
wrenEnsureSlots(vm, 1);
|
||||
wrenGetVariable(vm, "scheduler", "Scheduler", 0);
|
||||
schedulerClass = wrenGetSlotValue(vm, 0);
|
||||
|
||||
resume1 = wrenMakeCallHandle(vm, "resume_(_)");
|
||||
resume2 = wrenMakeCallHandle(vm, "resume_(_,_)");
|
||||
resumeError = wrenMakeCallHandle(vm, "resumeError_(_,_)");
|
||||
}
|
||||
|
||||
void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length)
|
||||
void schedulerResume(WrenValue* fiber, bool hasArgument)
|
||||
{
|
||||
callResume(resumeWithArg, fiber, "va", fiber, bytes, length);
|
||||
WrenVM* vm = getVM();
|
||||
wrenEnsureSlots(vm, 2 + (hasArgument ? 1 : 0));
|
||||
wrenSetSlotValue(vm, 0, schedulerClass);
|
||||
wrenSetSlotValue(vm, 1, fiber);
|
||||
wrenReleaseValue(vm, fiber);
|
||||
|
||||
// If we don't need to wait for an argument to be stored on the stack, resume
|
||||
// it now.
|
||||
if (!hasArgument) resume(resume1);
|
||||
}
|
||||
|
||||
void schedulerResumeDouble(WrenValue* fiber, double value)
|
||||
void schedulerFinishResume()
|
||||
{
|
||||
callResume(resumeWithArg, fiber, "vd", fiber, value);
|
||||
}
|
||||
|
||||
void schedulerResumeString(WrenValue* fiber, const char* text)
|
||||
{
|
||||
callResume(resumeWithArg, fiber, "vs", fiber, text);
|
||||
resume(resume2);
|
||||
}
|
||||
|
||||
void schedulerResumeError(WrenValue* fiber, const char* error)
|
||||
{
|
||||
callResume(resumeError, fiber, "vs", fiber, error);
|
||||
schedulerResume(fiber, true);
|
||||
wrenSetSlotString(getVM(), 2, error);
|
||||
resume(resumeError);
|
||||
}
|
||||
|
||||
void schedulerShutdown()
|
||||
{
|
||||
if (resume != NULL) wrenReleaseValue(getVM(), resume);
|
||||
if (resumeWithArg != NULL) wrenReleaseValue(getVM(), resumeWithArg);
|
||||
if (resumeError != NULL) wrenReleaseValue(getVM(), resumeError);
|
||||
// If the module was never loaded, we don't have anything to release.
|
||||
if (schedulerClass == NULL) return;
|
||||
|
||||
WrenVM* vm = getVM();
|
||||
wrenReleaseValue(vm, schedulerClass);
|
||||
wrenReleaseValue(vm, resume1);
|
||||
wrenReleaseValue(vm, resume2);
|
||||
wrenReleaseValue(vm, resumeError);
|
||||
}
|
||||
|
||||
+10
-4
@@ -3,10 +3,16 @@
|
||||
|
||||
#include "wren.h"
|
||||
|
||||
void schedulerResume(WrenValue* fiber);
|
||||
void schedulerResumeBytes(WrenValue* fiber, const char* bytes, size_t length);
|
||||
void schedulerResumeDouble(WrenValue* fiber, double value);
|
||||
void schedulerResumeString(WrenValue* fiber, const char* text);
|
||||
// Sets up the API stack to call one of the resume methods on Scheduler.
|
||||
//
|
||||
// If [hasArgument] is false, this just sets up the stack to have another
|
||||
// argument stored in slot 2 and returns. The module must store the argument
|
||||
// on the stack and then call [schedulerFinishResume] to complete the call.
|
||||
//
|
||||
// Otherwise, the call resumes immediately. Releases [fiber] when called.
|
||||
void schedulerResume(WrenValue* fiber, bool hasArgument);
|
||||
|
||||
void schedulerFinishResume();
|
||||
void schedulerResumeError(WrenValue* fiber, const char* error);
|
||||
|
||||
void schedulerShutdown();
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ static void timerCallback(uv_timer_t* handle)
|
||||
uv_close((uv_handle_t*)handle, timerCloseCallback);
|
||||
|
||||
// Run the fiber that was sleeping.
|
||||
schedulerResume(fiber);
|
||||
schedulerResume(fiber, false);
|
||||
}
|
||||
|
||||
void timerStartTimer(WrenVM* vm)
|
||||
|
||||
Reference in New Issue
Block a user