From 63d12555663038b41cd240c9dfdcee12f673bb44 Mon Sep 17 00:00:00 2001 From: Bob Nystrom Date: Sun, 29 Dec 2013 10:06:35 -0800 Subject: [PATCH] Start sketching in the FFI. --- include/wren.h | 23 ++++++++++ src/wren_common.h | 14 ++++++ src/wren_compiler.c | 14 ------ src/wren_value.h | 6 +++ src/wren_vm.c | 102 ++++++++++++++++++++++++++++++++++++++++++++ src/wren_vm.h | 8 ++++ 6 files changed, 153 insertions(+), 14 deletions(-) diff --git a/include/wren.h b/include/wren.h index ef061db4..4665431e 100644 --- a/include/wren.h +++ b/include/wren.h @@ -23,6 +23,8 @@ typedef struct WrenVM WrenVM; // [oldSize] will be zero. It should return NULL. typedef void* (*WrenReallocateFn)(void* memory, size_t oldSize, size_t newSize); +typedef void (*WrenNativeMethodFn)(WrenVM* vm); + typedef struct { // The callback Wren will use to allocate, reallocate, and deallocate memory. @@ -80,4 +82,25 @@ void wrenFreeVM(WrenVM* vm); // TODO: Define error codes. int wrenInterpret(WrenVM* vm, const char* source); +// Defines a foreign method implemented by the host application. Looks for a +// global class named [className] to bind the method to. If not found, it will +// be created automatically. +// +// Defines a method on that class named [methodName] accepting [numParams] +// parameters. If a method already exists with that name and arity, it will be +// replaced. When invoked, the method will call [method]. +void wrenDefineMethod(WrenVM* vm, const char* className, + const char* methodName, int numParams, + WrenNativeMethodFn method); + +// Reads an numeric argument for a foreign call. This must only be called within +// a function provided to [wrenDefineMethod]. Retrieves the argument at [index] +// which ranges from 0 to the number of parameters the method expects - 1. +double wrenGetArgumentDouble(WrenVM* vm, int index); + +// Provides a numeric return value for a foreign call. This must only be called +// within a function provided to [wrenDefineMethod]. Once this is called, the +// foreign call is done, and no more arguments can be read or return calls made. +void wrenReturnDouble(WrenVM* vm, double value); + #endif diff --git a/src/wren_common.h b/src/wren_common.h index 43d87d12..49dcf3d8 100644 --- a/src/wren_common.h +++ b/src/wren_common.h @@ -45,6 +45,20 @@ // Set this to true to log memory operations as they occur. #define WREN_TRACE_MEMORY false +// The maximum number of arguments that can be passed to a method. Note that +// this limtation is hardcoded in other places in the VM, in particular, the +// `CODE_CALL_XX` instructions assume a certain maximum number. +#define MAX_PARAMETERS (16) + +// The maximum name of a method, not including the signature. This is an +// arbitrary but enforced maximum just so we know how long the method name +// strings need to be in the parser. +#define MAX_METHOD_NAME (64) + +// The maximum length of a method signature. This includes the name, and the +// extra spaces added to handle arity, and another byte to terminate the string. +#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + MAX_PARAMETERS + 1) + // Assertions are used to validate program invariants. They indicate things the // program expects to be true about its internal state during execution. If an // assertion fails, there is a bug in Wren. diff --git a/src/wren_compiler.c b/src/wren_compiler.c index 6313c72a..6085fd02 100644 --- a/src/wren_compiler.c +++ b/src/wren_compiler.c @@ -12,11 +12,6 @@ // parsing/code generation. This minimizes the number of explicit forward // declarations needed. -// The maximum number of arguments that can be passed to a method. Note that -// this limtation is hardcoded in other places in the VM, in particular, the -// `CODE_CALL_XX` instructions assume a certain maximum number. -#define MAX_PARAMETERS (16) - // The maximum number of local (i.e. non-global) variables that can be declared // in a single function, method, or chunk of top level code. This is the // maximum number of variables in scope at one time, and spans block scopes. @@ -35,15 +30,6 @@ // argument. #define MAX_CONSTANTS (256) -// The maximum name of a method, not including the signature. This is an -// arbitrary but enforced maximum just so we know how long the method name -// strings need to be in the parser. -#define MAX_METHOD_NAME (64) - -// The maximum length of a method signature. This includes the name, and the -// extra spaces added to handle arity, and another byte to terminate the string. -#define MAX_METHOD_SIGNATURE (MAX_METHOD_NAME + MAX_PARAMETERS + 1) - typedef enum { TOKEN_LEFT_PAREN, diff --git a/src/wren_value.h b/src/wren_value.h index 0f5c4387..1e25b765 100644 --- a/src/wren_value.h +++ b/src/wren_value.h @@ -190,12 +190,17 @@ typedef struct typedef enum { + // TODO: Unify these three: + // A primitive method implemented in C that immediately returns a value. METHOD_PRIMITIVE, // A built-in method that modifies the fiber directly. METHOD_FIBER, + // A externally-defined C method. + METHOD_FOREIGN, + // A normal user-defined method. METHOD_BLOCK, @@ -213,6 +218,7 @@ typedef struct { Primitive primitive; FiberPrimitive fiberPrimitive; + WrenNativeMethodFn native; // May be a [ObjFn] or [ObjClosure]. Obj* fn; diff --git a/src/wren_vm.c b/src/wren_vm.c index 7237af41..6ac21ee8 100644 --- a/src/wren_vm.c +++ b/src/wren_vm.c @@ -64,6 +64,9 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration) vm->globals[i] = NULL_VAL; } + vm->nativeCallSlot = NULL; + vm->nativeCallNumArgs = 0; + wrenInitializeCore(vm); return vm; } @@ -567,6 +570,27 @@ static void bindMethod(int methodType, int symbol, ObjClass* classObj, } } +static void callForeign(WrenVM* vm, ObjFiber* fiber, Method* method, int numArgs) +{ + vm->nativeCallSlot = &fiber->stack[fiber->stackSize - numArgs]; + + // Don't include the receiver. + vm->nativeCallNumArgs = numArgs - 1; + + method->native(vm); + + // Discard the stack slots for the arguments (but leave one for + // the result). + fiber->stackSize -= numArgs - 1; + + // If nothing was returned, implicitly return null. + if (vm->nativeCallSlot != NULL) + { + *vm->nativeCallSlot = NULL_VAL; + vm->nativeCallSlot = NULL; + } +} + // The main bytecode interpreter loop. This is where the magic happens. It is // also, as you can imagine, highly performance critical. static Value interpret(WrenVM* vm, ObjFiber* fiber) @@ -762,6 +786,10 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber) break; } + case METHOD_FOREIGN: + callForeign(vm, fiber, method, numArgs); + break; + case METHOD_BLOCK: STORE_FRAME(); wrenCallFunction(fiber, method->fn, numArgs); @@ -847,6 +875,10 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber) break; } + case METHOD_FOREIGN: + callForeign(vm, fiber, method, numArgs); + break; + case METHOD_BLOCK: STORE_FRAME(); wrenCallFunction(fiber, method->fn, numArgs); @@ -1202,3 +1234,73 @@ void unpinObj(WrenVM* vm) { vm->pinned = vm->pinned->previous; } + +void wrenDefineMethod(WrenVM* vm, const char* className, + const char* methodName, int numParams, + WrenNativeMethodFn method) +{ + ASSERT(className != NULL, "Must provide class name."); + + int length = (int)strlen(methodName); + ASSERT(methodName != NULL, "Must provide method name."); + ASSERT(strlen(methodName) < MAX_METHOD_NAME, "Method name too long."); + + ASSERT(numParams >= 0, "numParams cannot be negative."); + ASSERT(numParams <= MAX_PARAMETERS, "Too many parameters."); + + ASSERT(method != NULL, "Must provide method function."); + + // Find or create the class to bind the method to. + int classSymbol = findSymbol(&vm->globalSymbols, + className, strlen(className)); + ObjClass* classObj; + + if (classSymbol != -1) + { + // TODO: Handle name is not class. + classObj = AS_CLASS(vm->globals[classSymbol]); + } + else + { + // The class doesn't already exist, so create it. + // TODO: Allow passing in name for superclass? + classObj = wrenNewClass(vm, vm->objectClass, 0); + classSymbol = addSymbol(vm, &vm->globalSymbols, + className, strlen(className)); + vm->globals[classSymbol] = OBJ_VAL(classObj); + } + + // Create a name for the method, including its arity. + char name[MAX_METHOD_SIGNATURE]; + strncpy(name, methodName, length); + for (int i = 0; i < numParams; i++) + { + name[length++] = ' '; + } + name[length] = '\0'; + + // Bind the method. + int methodSymbol = ensureSymbol(vm, &vm->methods, name, length); + + classObj->methods[methodSymbol].type = METHOD_FOREIGN; + classObj->methods[methodSymbol].native = method; +} + +double wrenGetArgumentDouble(WrenVM* vm, int index) +{ + ASSERT(vm->nativeCallSlot != NULL, "Must be in foreign call."); + ASSERT(index >= 0, "index cannot be negative."); + ASSERT(index < vm->nativeCallNumArgs, "Not that many arguments."); + + // + 1 to shift past the receiver. + // TODO: Check actual value type first. + return AS_NUM(*(vm->nativeCallSlot + index + 1)); +} + +void wrenReturnDouble(WrenVM* vm, double value) +{ + ASSERT(vm->nativeCallSlot != NULL, "Must be in foreign call."); + + *vm->nativeCallSlot = NUM_VAL(value); + vm->nativeCallSlot = NULL; +} diff --git a/src/wren_vm.h b/src/wren_vm.h index 70dcd913..e4d2b68b 100644 --- a/src/wren_vm.h +++ b/src/wren_vm.h @@ -244,6 +244,14 @@ struct WrenVM // The externally-provided function used to allocate memory. WrenReallocateFn reallocate; + + // During a foreign function call, this will point to the first argument (the + // receiver) of the call on the fiber's stack. + Value* nativeCallSlot; + + // During a foreign function call, this will contain the number of arguments + // to the function. + int nativeCallNumArgs; }; // A generic allocation function that handles all explicit memory management.