diff --git a/include/wren.h b/include/wren.h index c1b5257e..eb0dd651 100644 --- a/include/wren.h +++ b/include/wren.h @@ -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 diff --git a/src/main.c b/src/main.c index 7ab3a044..64a0b226 100644 --- a/src/main.c +++ b/src/main.c @@ -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); diff --git a/src/wren_common.h b/src/wren_common.h index 06771d6d..0d80a56e 100644 --- a/src/wren_common.h +++ b/src/wren_common.h @@ -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) diff --git a/src/wren_compiler.c b/src/wren_compiler.c index ac7c1c2e..98814c7f 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -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); } diff --git a/src/wren_value.c b/src/wren_value.c index 4c2d6a87..344f3ef6 100644 --- a/src/wren_value.c +++ b/src/wren_value.c @@ -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); diff --git a/src/wren_value.h b/src/wren_value.h index e150fa8e..05140a1c 100644 --- a/src/wren_value.h +++ b/src/wren_value.h @@ -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 diff --git a/src/wren_vm.c b/src/wren_vm.c index 2672164c..cc73ff42 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -1,3 +1,4 @@ +#include #include #include #include @@ -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) { diff --git a/src/wren_vm.h b/src/wren_vm.h index fac146fb..0e020f4b 100644 --- a/src/wren_vm.h +++ b/src/wren_vm.h @@ -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;