1
0
forked from Mirror/wren

Start sketching in the FFI.

This commit is contained in:
Bob Nystrom
2013-12-29 10:06:35 -08:00
parent 667d393fbb
commit 63d1255566
6 changed files with 153 additions and 14 deletions

View File

@ -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

View File

@ -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.

View File

@ -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,

View File

@ -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;

View File

@ -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;
}

View File

@ -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.