From e0ac88c22abcf2ce53e7e7630f365eabb8ef4d01 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Tue, 29 Dec 2015 07:58:47 -0800 Subject: [PATCH] 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. --- src/include/wren.h | 57 +++--------- src/module/io.c | 67 ++++++++++---- src/module/scheduler.c | 71 ++++++++------- src/module/scheduler.h | 14 ++- src/module/timer.c | 2 +- src/vm/wren_core.c | 2 + src/vm/wren_vm.c | 201 ++++++++++------------------------------- test/api/benchmark.c | 21 ++++- test/api/call.c | 78 ++++++++++++---- test/api/call.wren | 9 +- 10 files changed, 239 insertions(+), 283 deletions(-) diff --git a/src/include/wren.h b/src/include/wren.h index a73a69b8..de2b7874 100644 --- a/src/include/wren.h +++ b/src/include/wren.h @@ -189,54 +189,27 @@ void wrenCollectGarbage(WrenVM* vm); // Runs [source], a string of Wren source code in a new fiber in [vm]. WrenInterpretResult wrenInterpret(WrenVM* vm, const char* source); -// Creates a handle that can be used to invoke a method with [signature] on the -// object in [module] currently stored in top-level [variable]. +// Creates a handle that can be used to invoke a method with [signature] on +// using a receiver and arguments that are set up on the stack. // // This handle can be used repeatedly to directly invoke that method from C // code using [wrenCall]. // -// When done with this handle, it must be released using [wrenReleaseValue]. -WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable, - const char* signature); +// When you are done with this handle, it must be released using +// [wrenReleaseValue]. +WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature); -// Calls [method], passing in a series of arguments whose types must match the -// specifed [argTypes]. This is a string where each character identifies the -// type of a single argument, in order. The allowed types are: +// Calls [method], using the receiver and arguments previously set up on the +// stack. // -// - "b" - A C `int` converted to a Wren Bool. -// - "d" - A C `double` converted to a Wren Num. -// - "i" - A C `int` converted to a Wren Num. -// - "s" - A C null-terminated `const char*` converted to a Wren String. Wren -// will allocate its own string and copy the characters from this, so -// you don't have to worry about the lifetime of the string you pass to -// Wren. -// - "a" - An array of bytes converted to a Wren String. This requires two -// consecutive arguments in the argument list: `const char*` pointing -// to the array of bytes, followed by an `int` defining the length of -// the array. This is used when the passed string may contain null -// bytes, or just to avoid the implicit `strlen()` call of "s" if you -// happen to already know the length. -// - "v" - A previously acquired WrenValue*. Passing this in does not implicitly -// release the value. If the passed argument is NULL, this becomes a -// Wren NULL. +// [method] must have been created by a call to [wrenMakeCallHandle]. The +// arguments to the method must be already on the stack. The receiver should be +// in slot 0 with the remaining arguments following it, in order. It is an +// error if the number of arguments provided does not match the method's +// signature. // -// [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); - -// Gets the numeric value of [value]. -// -// It is an error to call this if the value is not a number. -double wrenGetValueDouble(WrenVM* vm, WrenValue* value); -// TODO: Functions for other types. +// After this returns, you can access the return value from slot 0 on the stack. +WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method); // Releases the reference stored in [value]. After calling this, [value] can no // longer be used. @@ -359,7 +332,7 @@ void wrenSetSlotBool(WrenVM* vm, int slot, bool value); // // The bytes are copied to a new string within Wren's heap, so you can free // memory used by them after this is called. -void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, int length); +void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length); // Stores the numeric [value] in [slot]. void wrenSetSlotDouble(WrenVM* vm, int slot, double value); diff --git a/src/module/io.c b/src/module/io.c index 5ec141ab..3300ec40 100644 --- a/src/module/io.c +++ b/src/module/io.c @@ -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); diff --git a/src/module/scheduler.c b/src/module/scheduler.c index 20347eb8..28f4989f 100644 --- a/src/module/scheduler.c +++ b/src/module/scheduler.c @@ -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); } diff --git a/src/module/scheduler.h b/src/module/scheduler.h index e90ce1e4..943d5780 100644 --- a/src/module/scheduler.h +++ b/src/module/scheduler.h @@ -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(); diff --git a/src/module/timer.c b/src/module/timer.c index a9d98eca..406635f0 100644 --- a/src/module/timer.c +++ b/src/module/timer.c @@ -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) diff --git a/src/vm/wren_core.c b/src/vm/wren_core.c index 79ef463a..a5b38dd3 100644 --- a/src/vm/wren_core.c +++ b/src/vm/wren_core.c @@ -150,6 +150,8 @@ DEF_PRIMITIVE(fiber_suspend) { // Switching to a null fiber tells the interpreter to stop and exit. vm->fiber = NULL; + vm->apiStack = NULL; + return false; } diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index eca8eca6..f281a62f 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -1104,18 +1104,23 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) // If the fiber is complete, end it. if (fiber->numFrames == 0) { - // See if there's another fiber to return to. - ObjFiber* callingFiber = fiber->caller; + // See if there's another fiber to return to. If not, we're done. + if (fiber->caller == NULL) + { + // Store the final result value at the beginning of the stack so the + // C API can get it. + fiber->stack[0] = result; + fiber->stackTop = fiber->stack + 1; + return WREN_RESULT_SUCCESS; + } + + ObjFiber* resumingFiber = fiber->caller; fiber->caller = NULL; - - fiber = callingFiber; - vm->fiber = fiber; - - // If not, we're done. - if (fiber == NULL) return WREN_RESULT_SUCCESS; - + fiber = resumingFiber; + vm->fiber = resumingFiber; + // Store the result in the resuming fiber. - *(fiber->stackTop - 1) = result; + fiber->stackTop[-1] = result; } else { @@ -1127,7 +1132,7 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) // result). fiber->stackTop = frame->stackStart + 1; } - + LOAD_FRAME(); DISPATCH(); } @@ -1255,11 +1260,10 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber) #undef READ_SHORT } -// Creates an [ObjFn] that invokes a method with [signature] when called. -static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature) +WrenValue* wrenMakeCallHandle(WrenVM* vm, const char* signature) { int signatureLength = (int)strlen(signature); - + // Count the number parameters the method expects. int numParams = 0; if (signature[signatureLength - 1] == ')') @@ -1270,147 +1274,46 @@ static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature) if (*s == '_') numParams++; } } - + + // Add the signatue to the method table. int method = wrenSymbolTableEnsure(vm, &vm->methodNames, signature, signatureLength); - + + // Create a little stub function that assumes the arguments are on the stack + // and calls the method. uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5); bytecode[0] = (uint8_t)(CODE_CALL_0 + numParams); bytecode[1] = (method >> 8) & 0xff; bytecode[2] = method & 0xff; bytecode[3] = CODE_RETURN; bytecode[4] = CODE_END; - + int* debugLines = ALLOCATE_ARRAY(vm, int, 5); memset(debugLines, 1, 5); + + ObjFn* fn = wrenNewFunction(vm, NULL, NULL, 0, 0, numParams + 1, 0, bytecode, + 5, signature, signatureLength, debugLines); - return wrenNewFunction(vm, module, NULL, 0, 0, numParams + 1, 0, bytecode, 5, - signature, signatureLength, debugLines); + // Wrap the function in a handle. + return wrenCaptureValue(vm, OBJ_VAL(fn)); } -WrenValue* wrenGetMethod(WrenVM* vm, const char* module, const char* variable, - const char* signature) +WrenInterpretResult wrenCall(WrenVM* vm, WrenValue* method) { - Value moduleName = wrenStringFormat(vm, "$", module); - wrenPushRoot(vm, AS_OBJ(moduleName)); - - ObjModule* moduleObj = getModule(vm, moduleName); - // TODO: Handle module not being found. - - int variableSlot = wrenSymbolTableFind(&moduleObj->variableNames, - variable, strlen(variable)); - // TODO: Handle the variable not being found. - - ObjFn* fn = makeCallStub(vm, moduleObj, signature); - wrenPushRoot(vm, (Obj*)fn); - - // Create a single fiber that we can reuse each time the method is invoked. - ObjFiber* fiber = wrenNewFiber(vm, (Obj*)fn); - wrenPushRoot(vm, (Obj*)fiber); - - // Create a handle that keeps track of the function that calls the method. - WrenValue* method = wrenCaptureValue(vm, OBJ_VAL(fiber)); - - // Store the receiver in the fiber's stack so we can use it later in the call. - *fiber->stackTop++ = moduleObj->variables.data[variableSlot]; - - wrenPopRoot(vm); // fiber. - wrenPopRoot(vm); // fn. - wrenPopRoot(vm); // moduleName. - - return method; -} - -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. - for (const char* argType = argTypes; *argType != '\0'; argType++) - { - Value value = NULL_VAL; - switch (*argType) - { - case 'a': - { - 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(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(args, const char*)); - break; - - case 'v': - { - // Allow a NULL value pointer for Wren null. - WrenValue* wrenValue = va_arg(args, WrenValue*); - if (wrenValue != NULL) value = wrenValue->value; - break; - } - - default: - ASSERT(false, "Unknown argument type."); - break; - } - - *fiber->stackTop++ = value; - } - - Value receiver = fiber->stack[0]; - Obj* fn = fiber->frames[0].fn; - 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; + ASSERT(method != NULL, "Method cannot be NULL."); + ASSERT(IS_FN(method->value), "Method must be a method handle."); + ASSERT(vm->fiber != NULL, "Must set up arguments for call first."); + ASSERT(vm->apiStack != NULL, "Must set up arguments for call first."); + ASSERT(vm->fiber->numFrames == 0, "Can not call from a foreign method."); + + ObjFn* fn = AS_FN(method->value); + + ASSERT(vm->fiber->stackTop - vm->fiber->stack >= fn->arity, + "Stack must have enough arguments for method."); + + callFunction(vm, vm->fiber, (Obj*)fn, fn->arity); + + return runInterpreter(vm, vm->fiber); } WrenValue* wrenCaptureValue(WrenVM* vm, Value value) @@ -1428,14 +1331,6 @@ WrenValue* wrenCaptureValue(WrenVM* vm, Value value) return wrappedValue; } -double wrenGetValueDouble(WrenVM* vm, WrenValue* value) -{ - ASSERT(value != NULL, "Value cannot be NULL."); - ASSERT(IS_NUM(value->value), "Value must be a number."); - - return AS_NUM(value->value); -} - void wrenReleaseValue(WrenVM* vm, WrenValue* value) { ASSERT(value != NULL, "Value cannot be NULL."); @@ -1601,7 +1496,7 @@ int wrenGetSlotCount(WrenVM* vm) void wrenEnsureSlots(WrenVM* vm, int numSlots) { // If we don't have a fiber accessible, create one for the API to use. - if (vm->fiber == NULL && vm->apiStack == NULL) + if (vm->apiStack == NULL) { vm->fiber = wrenNewFiber(vm, NULL); vm->apiStack = vm->fiber->stack; @@ -1685,10 +1580,10 @@ void wrenSetSlotBool(WrenVM* vm, int slot, bool value) setSlot(vm, slot, BOOL_VAL(value)); } -void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, int length) +void wrenSetSlotBytes(WrenVM* vm, int slot, const char* bytes, size_t length) { ASSERT(bytes != NULL, "Byte array cannot be NULL."); - setSlot(vm, slot, wrenNewString(vm, bytes, (size_t)length)); + setSlot(vm, slot, wrenNewString(vm, bytes, length)); } void wrenSetSlotDouble(WrenVM* vm, int slot, double value) @@ -1735,6 +1630,8 @@ void wrenInsertInList(WrenVM* vm, int listSlot, int index, int elementSlot) wrenListInsert(vm, list, vm->apiStack[elementSlot], index); } +// TODO: Maybe just have this always return a WrenValue* instead of having to +// deal with slots? void wrenGetVariable(WrenVM* vm, const char* module, const char* name, int slot) { diff --git a/test/api/benchmark.c b/test/api/benchmark.c index 26d8daad..672e97ba 100644 --- a/test/api/benchmark.c +++ b/test/api/benchmark.c @@ -32,21 +32,32 @@ static void call(WrenVM* vm) wrenInterpret(otherVM, testScript); - WrenValue* method = wrenGetMethod(otherVM, "main", "Test", "method(_,_,_,_)"); + WrenValue* method = wrenMakeCallHandle(otherVM, "method(_,_,_,_)"); + + wrenEnsureSlots(otherVM, 1); + wrenGetVariable(otherVM, "main", "Test", 0); + WrenValue* testClass = wrenGetSlotValue(otherVM, 0); double startTime = (double)clock() / CLOCKS_PER_SEC; double result = 0; for (int i = 0; i < iterations; i++) { - WrenValue* resultValue; - wrenCall(otherVM, method, &resultValue, "dddd", 1.0, 2.0, 3.0, 4.0); - result += wrenGetValueDouble(otherVM, resultValue); - wrenReleaseValue(otherVM, resultValue); + wrenEnsureSlots(otherVM, 5); + wrenSetSlotValue(otherVM, 0, testClass); + wrenSetSlotDouble(otherVM, 1, 1.0); + wrenSetSlotDouble(otherVM, 2, 2.0); + wrenSetSlotDouble(otherVM, 3, 3.0); + wrenSetSlotDouble(otherVM, 4, 4.0); + + wrenCall(otherVM, method); + + result += wrenGetSlotDouble(otherVM, 0); } double elapsed = (double)clock() / CLOCKS_PER_SEC - startTime; + wrenReleaseValue(otherVM, testClass); wrenReleaseValue(otherVM, method); wrenFreeVM(otherVM); diff --git a/test/api/call.c b/test/api/call.c index 5207a456..f7d8af8a 100644 --- a/test/api/call.c +++ b/test/api/call.c @@ -5,33 +5,75 @@ void callRunTests(WrenVM* vm) { - WrenValue* noParams = wrenGetMethod(vm, "main", "Call", "noParams"); - WrenValue* zero = wrenGetMethod(vm, "main", "Call", "zero()"); - WrenValue* one = wrenGetMethod(vm, "main", "Call", "one(_)"); - WrenValue* two = wrenGetMethod(vm, "main", "Call", "two(_,_)"); + wrenEnsureSlots(vm, 1); + wrenGetVariable(vm, "main", "Call", 0); + WrenValue* callClass = wrenGetSlotValue(vm, 0); + + WrenValue* noParams = wrenMakeCallHandle(vm, "noParams"); + WrenValue* zero = wrenMakeCallHandle(vm, "zero()"); + WrenValue* one = wrenMakeCallHandle(vm, "one(_)"); + WrenValue* two = wrenMakeCallHandle(vm, "two(_,_)"); // Different arity. - wrenCall(vm, noParams, NULL, ""); - wrenCall(vm, zero, NULL, ""); - wrenCall(vm, one, NULL, "i", 1); - wrenCall(vm, two, NULL, "ii", 1, 2); - - WrenValue* getValue = wrenGetMethod(vm, "main", "Call", "getValue(_)"); + wrenEnsureSlots(vm, 1); + wrenSetSlotValue(vm, 0, callClass); + wrenCall(vm, noParams); + + wrenEnsureSlots(vm, 1); + wrenSetSlotValue(vm, 0, callClass); + wrenCall(vm, zero); + + wrenEnsureSlots(vm, 2); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotDouble(vm, 1, 1.0); + wrenCall(vm, one); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotDouble(vm, 1, 1.0); + wrenSetSlotDouble(vm, 2, 2.0); + wrenCall(vm, two); // Returning a value. - WrenValue* value = NULL; - wrenCall(vm, getValue, &value, "v", NULL); + WrenValue* getValue = wrenMakeCallHandle(vm, "getValue()"); + wrenEnsureSlots(vm, 1); + wrenSetSlotValue(vm, 0, callClass); + wrenCall(vm, getValue); + WrenValue* value = wrenGetSlotValue(vm, 0); // Different argument types. - wrenCall(vm, two, NULL, "bb", true, false); - wrenCall(vm, two, NULL, "dd", 1.2, 3.4); - wrenCall(vm, two, NULL, "ii", 3, 4); - wrenCall(vm, two, NULL, "ss", "string", "another"); - wrenCall(vm, two, NULL, "vv", NULL, value); + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotBool(vm, 1, true); + wrenSetSlotBool(vm, 2, false); + wrenCall(vm, two); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotDouble(vm, 1, 1.2); + wrenSetSlotDouble(vm, 2, 3.4); + wrenCall(vm, two); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotString(vm, 1, "string"); + wrenSetSlotString(vm, 2, "another"); + wrenCall(vm, two); + + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotNull(vm, 1); + wrenSetSlotValue(vm, 2, value); + wrenCall(vm, two); // Truncate a string, or allow null bytes. - wrenCall(vm, two, NULL, "aa", "string", 3, "b\0y\0t\0e", 7); + wrenEnsureSlots(vm, 3); + wrenSetSlotValue(vm, 0, callClass); + wrenSetSlotBytes(vm, 1, "string", 3); + wrenSetSlotBytes(vm, 2, "b\0y\0t\0e", 7); + wrenCall(vm, two); + wrenReleaseValue(vm, callClass); wrenReleaseValue(vm, noParams); wrenReleaseValue(vm, zero); wrenReleaseValue(vm, one); diff --git a/test/api/call.wren b/test/api/call.wren index 35feaa5e..889a7936 100644 --- a/test/api/call.wren +++ b/test/api/call.wren @@ -20,13 +20,7 @@ class Call { System.print("two %(one) %(two)") } - static getValue(value) { - // Return a new value if we aren't given one. - if (value == null) return ["a", "b"] - - // Otherwise print it. - System.print(value) - } + static getValue() { ["a", "b"] } } // expect: noParams @@ -36,7 +30,6 @@ class Call { // expect: two true false // expect: two 1.2 3.4 -// expect: two 3 4 // expect: two string another // expect: two null [a, b] // expect: two str [98, 0, 121, 0, 116, 0, 101]