forked from Mirror/wren
Start sketching in the FFI.
This commit is contained in:
@ -23,6 +23,8 @@ typedef struct WrenVM WrenVM;
|
|||||||
// [oldSize] will be zero. It should return NULL.
|
// [oldSize] will be zero. It should return NULL.
|
||||||
typedef void* (*WrenReallocateFn)(void* memory, size_t oldSize, size_t newSize);
|
typedef void* (*WrenReallocateFn)(void* memory, size_t oldSize, size_t newSize);
|
||||||
|
|
||||||
|
typedef void (*WrenNativeMethodFn)(WrenVM* vm);
|
||||||
|
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
// The callback Wren will use to allocate, reallocate, and deallocate memory.
|
// The callback Wren will use to allocate, reallocate, and deallocate memory.
|
||||||
@ -80,4 +82,25 @@ void wrenFreeVM(WrenVM* vm);
|
|||||||
// TODO: Define error codes.
|
// TODO: Define error codes.
|
||||||
int wrenInterpret(WrenVM* vm, const char* source);
|
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
|
#endif
|
||||||
|
|||||||
@ -45,6 +45,20 @@
|
|||||||
// Set this to true to log memory operations as they occur.
|
// Set this to true to log memory operations as they occur.
|
||||||
#define WREN_TRACE_MEMORY false
|
#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
|
// Assertions are used to validate program invariants. They indicate things the
|
||||||
// program expects to be true about its internal state during execution. If an
|
// program expects to be true about its internal state during execution. If an
|
||||||
// assertion fails, there is a bug in Wren.
|
// assertion fails, there is a bug in Wren.
|
||||||
|
|||||||
@ -12,11 +12,6 @@
|
|||||||
// parsing/code generation. This minimizes the number of explicit forward
|
// parsing/code generation. This minimizes the number of explicit forward
|
||||||
// declarations needed.
|
// 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
|
// 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
|
// 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.
|
// maximum number of variables in scope at one time, and spans block scopes.
|
||||||
@ -35,15 +30,6 @@
|
|||||||
// argument.
|
// argument.
|
||||||
#define MAX_CONSTANTS (256)
|
#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
|
typedef enum
|
||||||
{
|
{
|
||||||
TOKEN_LEFT_PAREN,
|
TOKEN_LEFT_PAREN,
|
||||||
|
|||||||
@ -190,12 +190,17 @@ typedef struct
|
|||||||
|
|
||||||
typedef enum
|
typedef enum
|
||||||
{
|
{
|
||||||
|
// TODO: Unify these three:
|
||||||
|
|
||||||
// A primitive method implemented in C that immediately returns a value.
|
// A primitive method implemented in C that immediately returns a value.
|
||||||
METHOD_PRIMITIVE,
|
METHOD_PRIMITIVE,
|
||||||
|
|
||||||
// A built-in method that modifies the fiber directly.
|
// A built-in method that modifies the fiber directly.
|
||||||
METHOD_FIBER,
|
METHOD_FIBER,
|
||||||
|
|
||||||
|
// A externally-defined C method.
|
||||||
|
METHOD_FOREIGN,
|
||||||
|
|
||||||
// A normal user-defined method.
|
// A normal user-defined method.
|
||||||
METHOD_BLOCK,
|
METHOD_BLOCK,
|
||||||
|
|
||||||
@ -213,6 +218,7 @@ typedef struct
|
|||||||
{
|
{
|
||||||
Primitive primitive;
|
Primitive primitive;
|
||||||
FiberPrimitive fiberPrimitive;
|
FiberPrimitive fiberPrimitive;
|
||||||
|
WrenNativeMethodFn native;
|
||||||
|
|
||||||
// May be a [ObjFn] or [ObjClosure].
|
// May be a [ObjFn] or [ObjClosure].
|
||||||
Obj* fn;
|
Obj* fn;
|
||||||
|
|||||||
102
src/wren_vm.c
102
src/wren_vm.c
@ -64,6 +64,9 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
|
|||||||
vm->globals[i] = NULL_VAL;
|
vm->globals[i] = NULL_VAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vm->nativeCallSlot = NULL;
|
||||||
|
vm->nativeCallNumArgs = 0;
|
||||||
|
|
||||||
wrenInitializeCore(vm);
|
wrenInitializeCore(vm);
|
||||||
return 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
|
// The main bytecode interpreter loop. This is where the magic happens. It is
|
||||||
// also, as you can imagine, highly performance critical.
|
// also, as you can imagine, highly performance critical.
|
||||||
static Value interpret(WrenVM* vm, ObjFiber* fiber)
|
static Value interpret(WrenVM* vm, ObjFiber* fiber)
|
||||||
@ -762,6 +786,10 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case METHOD_FOREIGN:
|
||||||
|
callForeign(vm, fiber, method, numArgs);
|
||||||
|
break;
|
||||||
|
|
||||||
case METHOD_BLOCK:
|
case METHOD_BLOCK:
|
||||||
STORE_FRAME();
|
STORE_FRAME();
|
||||||
wrenCallFunction(fiber, method->fn, numArgs);
|
wrenCallFunction(fiber, method->fn, numArgs);
|
||||||
@ -847,6 +875,10 @@ static Value interpret(WrenVM* vm, ObjFiber* fiber)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case METHOD_FOREIGN:
|
||||||
|
callForeign(vm, fiber, method, numArgs);
|
||||||
|
break;
|
||||||
|
|
||||||
case METHOD_BLOCK:
|
case METHOD_BLOCK:
|
||||||
STORE_FRAME();
|
STORE_FRAME();
|
||||||
wrenCallFunction(fiber, method->fn, numArgs);
|
wrenCallFunction(fiber, method->fn, numArgs);
|
||||||
@ -1202,3 +1234,73 @@ void unpinObj(WrenVM* vm)
|
|||||||
{
|
{
|
||||||
vm->pinned = vm->pinned->previous;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@ -244,6 +244,14 @@ struct WrenVM
|
|||||||
|
|
||||||
// The externally-provided function used to allocate memory.
|
// The externally-provided function used to allocate memory.
|
||||||
WrenReallocateFn reallocate;
|
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.
|
// A generic allocation function that handles all explicit memory management.
|
||||||
|
|||||||
Reference in New Issue
Block a user