Add API to call Wren method from C code.

This gives you a simple, efficient way to invoke a method on
some Wren object from C code, passing in arguments.

The basic API is in place and works, but there's still lots to do:

- Lots of error handling.
- Documentation.
- Tests!
This commit is contained in:
Bob Nystrom 2015-02-28 13:31:15 -08:00
parent 6b05610c6a
commit 876c2d9208
8 changed files with 245 additions and 16 deletions

View File

@ -6,6 +6,11 @@
typedef struct WrenVM WrenVM;
// A handle to a method, bound to a receiver.
//
// This is used to call a Wren method on some object from C code.
typedef struct WrenMethod WrenMethod;
// A generic allocation function that handles all explicit memory management
// used by Wren. It's used like so:
//
@ -118,6 +123,34 @@ void wrenFreeVM(WrenVM* vm);
WrenInterpretResult wrenInterpret(WrenVM* vm, const char* sourcePath,
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].
//
// 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 by calling
// [wrenReleaseMethod].
WrenMethod* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
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 orde. The allowed types are:
//
// - "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.
void wrenCall(WrenVM* vm, WrenMethod* method, const char* argTypes, ...);
// Releases memory associated with [method]. After calling this, [method] can
// no longer be used.
void wrenReleaseMethod(WrenVM* vm, WrenMethod* method);
// TODO: Figure out how these interact with modules.
// Defines a foreign method implemented by the host application. Looks for a

View File

@ -128,6 +128,16 @@ static int runFile(WrenVM* vm, const char* path)
break;
}
/*
WrenMethod* method = wrenGetMethod(vm, "main", "Foo", "bar(_,_,_)");
wrenCall(vm, method, "nsd", NULL, "some string", 78.9);
wrenCall(vm, method, "iii", 1, 2, 3);
wrenCall(vm, method, "nsd", NULL, "another string", 78.9);
wrenCall(vm, method, "iii", 4, 5, 6);
wrenReleaseMethod(vm, method);
*/
wrenFreeVM(vm);
free(source);

View File

@ -108,6 +108,20 @@
// field index*.
#define MAX_FIELDS 255
// Use the VM's allocator to allocate an object of [type].
#define ALLOCATE(vm, type) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
// Use the VM's allocator to allocate an object of [mainType] containing a
// flexible array of [count] objects of [arrayType].
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
((mainType*)wrenReallocate(vm, NULL, 0, \
sizeof(mainType) + sizeof(arrayType) * count))
// Use the VM's allocator to allocate an array of [count] elements of [type].
#define ALLOCATE_ARRAY(vm, type, count) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * count))
// The Microsoft compiler does not support the "inline" modifier when compiling
// as plain C.
#if defined( _MSC_VER ) && !defined(__cplusplus)

View File

@ -1527,11 +1527,9 @@ static void finishParameterList(Compiler* compiler, Signature* signature)
while (match(compiler, TOKEN_COMMA));
}
// Gets the symbol for a method [name]. If [length] is 0, it will be calculated
// from a null-terminated [name].
// Gets the symbol for a method [name] with [length].
static int methodSymbol(Compiler* compiler, const char* name, int length)
{
if (length == 0) length = (int)strlen(name);
return wrenSymbolTableEnsure(compiler->parser->vm,
&compiler->parser->vm->methodNames, name, length);
}

View File

@ -23,16 +23,6 @@
DEFINE_BUFFER(Value, Value);
DEFINE_BUFFER(Method, Method);
#define ALLOCATE(vm, type) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type)))
#define ALLOCATE_FLEX(vm, mainType, arrayType, count) \
((mainType*)wrenReallocate(vm, NULL, 0, \
sizeof(mainType) + sizeof(arrayType) * count))
#define ALLOCATE_ARRAY(vm, type, count) \
((type*)wrenReallocate(vm, NULL, 0, sizeof(type) * count))
static void initObj(WrenVM* vm, Obj* obj, ObjType type, ObjClass* classObj)
{
obj->type = type;
@ -145,6 +135,13 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
ObjFiber* fiber = ALLOCATE(vm, ObjFiber);
initObj(vm, &fiber->obj, OBJ_FIBER, vm->fiberClass);
wrenResetFiber(fiber, fn);
return fiber;
}
void wrenResetFiber(ObjFiber* fiber, Obj* fn)
{
// Push the stack frame for the function.
fiber->stackTop = fiber->stack;
fiber->numFrames = 1;
@ -164,8 +161,6 @@ ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn)
{
frame->ip = ((ObjClosure*)fn)->fn->bytecode;
}
return fiber;
}
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
@ -795,7 +790,10 @@ static void markFn(WrenVM* vm, ObjFn* fn)
wrenMarkValue(vm, fn->constants[i]);
}
wrenMarkObj(vm, (Obj*)fn->debug->sourcePath);
if (fn->debug->sourcePath != NULL)
{
wrenMarkObj(vm, (Obj*)fn->debug->sourcePath);
}
// Keep track of how much memory is still in use.
vm->bytesAllocated += sizeof(ObjFn);

View File

@ -646,6 +646,9 @@ ObjClosure* wrenNewClosure(WrenVM* vm, ObjFn* fn);
// closure.
ObjFiber* wrenNewFiber(WrenVM* vm, Obj* fn);
// Resets [fiber] back to an initial state where it is ready to invoke [fn].
void wrenResetFiber(ObjFiber* fiber, Obj* fn);
// TODO: The argument list here is getting a bit gratuitous.
// Creates a new function object with the given code and constants. The new
// function will take over ownership of [bytecode] and [sourceLines]. It will

View File

@ -1,3 +1,4 @@
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
@ -35,6 +36,8 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
WrenVM* vm = (WrenVM*)reallocate(NULL, 0, sizeof(WrenVM));
vm->reallocate = reallocate;
vm->methodHandles = NULL;
vm->foreignCallSlot = NULL;
vm->foreignCallNumArgs = 0;
vm->loadModule = configuration->loadModuleFn;
@ -99,7 +102,14 @@ void wrenFreeVM(WrenVM* vm)
obj = next;
}
// Tell the user if they didn't free any method handles. We don't want to
// just free them here because the host app may still have pointers to them
// that they may try to use. Better to tell them about the bug early.
ASSERT(vm->methodHandles == NULL, "All methods have not been released.");
wrenSymbolTableClear(vm, &vm->methodNames);
// TODO: Make macro for freeing.
wrenReallocate(vm, vm, 0, 0);
}
@ -140,6 +150,14 @@ static void collectGarbage(WrenVM* vm)
// The current fiber.
if (vm->fiber != NULL) wrenMarkObj(vm, (Obj*)vm->fiber);
// The method handles.
for (WrenMethod* handle = vm->methodHandles;
handle != NULL;
handle = handle->next)
{
wrenMarkObj(vm, (Obj*)handle->fiber);
}
// Any object the compiler is using (if there is one).
if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler);
@ -1221,6 +1239,146 @@ static bool runInterpreter(WrenVM* vm)
return false;
}
// Creates an [ObjFn] that invokes a method with [signature] when called.
static ObjFn* makeCallStub(WrenVM* vm, ObjModule* module, const char* signature)
{
int signatureLength = (int)strlen(signature);
// Count the number parameters the method expects.
int numParams = 0;
for (const char* s = signature; *s != '\0'; s++)
{
if (*s == '_') numParams++;
}
int method = wrenSymbolTableEnsure(vm, &vm->methodNames,
signature, signatureLength);
uint8_t* bytecode = ALLOCATE_ARRAY(vm, uint8_t, 5);
bytecode[0] = 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);
return wrenNewFunction(vm, module, NULL, 0, 0, 0, bytecode, 5, NULL,
signature, signatureLength, debugLines);
}
WrenMethod* wrenGetMethod(WrenVM* vm, const char* module, const char* variable,
const char* signature)
{
Value moduleName = wrenNewString(vm, module, strlen(module));
wrenPushRoot(vm, AS_OBJ(moduleName));
uint32_t moduleEntry = wrenMapFind(vm->modules, moduleName);
// TODO: Handle module not being found.
ObjModule* moduleObj = AS_MODULE(vm->modules->entries[moduleEntry].value);
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.
WrenMethod* method = ALLOCATE(vm, WrenMethod);
method->fiber = fiber;
// Store the receiver in the fiber's stack so we can use it later in the call.
*fiber->stackTop++ = moduleObj->variables.data[variableSlot];
// Add it to the front of the linked list of handles.
if (vm->methodHandles != NULL) vm->methodHandles->prev = method;
method->prev = NULL;
method->next = vm->methodHandles;
vm->methodHandles = method;
wrenPopRoot(vm); // fiber.
wrenPopRoot(vm); // fn.
wrenPopRoot(vm); // moduleName.
return method;
}
void wrenCall(WrenVM* vm, WrenMethod* method, const char* argTypes, ...)
{
// TODO: Validate that the number of arguments matches what the method
// expects.
// Push the arguments.
va_list argList;
va_start(argList, argTypes);
for (const char* argType = argTypes; *argType != '\0'; argType++)
{
Value value = NULL_VAL;
switch (*argType)
{
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 's':
{
const char* text = va_arg(argList, const char*);
value = wrenNewString(vm, text, strlen(text));
break;
}
default:
ASSERT(false, "Uknown argument type.");
break;
}
*method->fiber->stackTop++ = value;
}
va_end(argList);
vm->fiber = method->fiber;
Value receiver = method->fiber->stack[0];
Obj* fn = method->fiber->frames[0].fn;
// TODO: How does this handle a runtime error occurring?
runInterpreter(vm);
// Reset the fiber to get ready for the next call.
wrenResetFiber(method->fiber, fn);
// Push the receiver back on the stack.
*method->fiber->stackTop++ = receiver;
}
void wrenReleaseMethod(WrenVM* vm, WrenMethod* method)
{
ASSERT(method != NULL, "NULL method.");
// Update the VM's head pointer if we're releasing the first handle.
if (vm->methodHandles == method) vm->methodHandles = method->next;
// Unlink it from the list.
if (method->prev != NULL) method->prev->next = method->next;
if (method->next != NULL) method->next->prev = method->prev;
// Clear it out. This isn't strictly necessary since we're going to free it,
// but it makes for easier debugging.
method->prev = NULL;
method->next = NULL;
method->fiber = NULL;
wrenReallocate(vm, method, sizeof(WrenMethod), 0);
}
// Execute [source] in the context of the core module.
WrenInterpretResult static loadIntoCore(WrenVM* vm, const char* source)
{

View File

@ -189,6 +189,17 @@ typedef enum
CODE_END
} Code;
struct WrenMethod
{
// The fiber that invokes the method. Its stack is pre-populated with the
// receiver for the method, and it contains a single callframe whose function
// is the bytecode stub to invoke the method.
ObjFiber* fiber;
WrenMethod* prev;
WrenMethod* next;
};
// TODO: Move into wren_vm.c?
struct WrenVM
{
@ -254,6 +265,10 @@ struct WrenVM
// receiver) of the call on the fiber's stack.
Value* foreignCallSlot;
// Pointer to the first node in the linked list of active method handles or
// NULL if there are no handles.
WrenMethod* methodHandles;
// During a foreign function call, this will contain the number of arguments
// to the function.
int foreignCallNumArgs;