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
+2
View File
@@ -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;
}
+49 -152
View File
@@ -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)
{