Revamp wrenCall to work with slots.

Now, you call wrenEnsureSlots() and then wrenSetSlot___() to set up the
receiver and arguments before the call. Then wrenCall() is passed a
handle to the stub function that makes the call. After that, you can
get the result using wrenGetSlot___().

This is a little more verbose to use, but it's more flexible, simpler,
and much faster in the VM. The call benchmark is 185% of the previous
speed.
This commit is contained in:
Bob Nystrom
2015-12-29 07:58:47 -08:00
parent ed6fad6153
commit e0ac88c22a
10 changed files with 239 additions and 283 deletions
+47 -20
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)