First pass at implementing foreign classes.

Most of the pieces are there:

- You can declare a foreign class.
- It will call your C function to provide an allocator function.
- Whenever a foreign object is created, it calls the allocator.
- Foreign methods can access the foreign bytes of an object.
- Most of the runtime checking is in place for things like subclassing
  foreign classes.

There is still some loose ends to tie up:

- Finalizers are not called.
- Some of the error-handling could be better.
- The GC doesn't track how much memory a marked foreign object uses.
This commit is contained in:
Bob Nystrom
2015-08-15 12:07:53 -07:00
parent 7a79b8fac6
commit 48bdbc7745
33 changed files with 720 additions and 119 deletions
+36 -7
View File
@@ -252,6 +252,9 @@ typedef struct
// Symbol table for the fields of the class.
SymbolTable* fields;
// True if the class being compiled is a foreign class.
bool isForeign;
// True if the current method being compiled is static.
bool inStatic;
@@ -1938,6 +1941,10 @@ static void field(Compiler* compiler, bool allowAssignment)
{
error(compiler, "Cannot reference a field outside of a class definition.");
}
else if (enclosingClass->isForeign)
{
error(compiler, "Cannot define fields in a foreign class.");
}
else if (enclosingClass->inStatic)
{
error(compiler, "Cannot use an instance field in a static method.");
@@ -2591,6 +2598,8 @@ static int getNumArguments(const uint8_t* bytecode, const Value* constants,
case CODE_LOAD_LOCAL_7:
case CODE_LOAD_LOCAL_8:
case CODE_CONSTRUCT:
case CODE_FOREIGN_CONSTRUCT:
case CODE_FOREIGN_CLASS:
return 0;
case CODE_LOAD_LOCAL:
@@ -2945,7 +2954,8 @@ static void createConstructor(Compiler* compiler, Signature* signature,
initCompiler(&methodCompiler, compiler->parser, compiler, false);
// Allocate the instance.
emit(&methodCompiler, CODE_CONSTRUCT);
emit(&methodCompiler, compiler->enclosingClass->isForeign
? CODE_FOREIGN_CONSTRUCT : CODE_CONSTRUCT);
// Run its initializer.
emitShortArg(&methodCompiler, (Code)(CODE_CALL_0 + signature->arity),
@@ -3072,7 +3082,6 @@ static bool method(Compiler* compiler, ClassCompiler* classCompiler,
static void createDefaultConstructor(Compiler* compiler, int classSlot)
{
Signature signature = { "new", 3, SIG_INITIALIZER, 0 };
int initializerSymbol = signatureSymbol(compiler, &signature);
signature.type = SIG_METHOD;
@@ -3083,8 +3092,8 @@ static void createDefaultConstructor(Compiler* compiler, int classSlot)
}
// Compiles a class definition. Assumes the "class" token has already been
// consumed.
static void classDefinition(Compiler* compiler)
// consumed (along with a possibly preceding "foreign" token).
static void classDefinition(Compiler* compiler, bool isForeign)
{
// Create a variable to store the class in.
int slot = declareNamedVariable(compiler);
@@ -3109,7 +3118,15 @@ static void classDefinition(Compiler* compiler)
// Store a placeholder for the number of fields argument. We don't know
// the value until we've compiled all the methods to see which fields are
// used.
int numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
int numFieldsInstruction = -1;
if (isForeign)
{
emit(compiler, CODE_FOREIGN_CLASS);
}
else
{
numFieldsInstruction = emitByteArg(compiler, CODE_CLASS, 255);
}
// Store it in its name.
defineVariable(compiler, slot);
@@ -3120,6 +3137,7 @@ static void classDefinition(Compiler* compiler)
pushScope(compiler);
ClassCompiler classCompiler;
classCompiler.isForeign = isForeign;
// Set up a symbol table for the class's fields. We'll initially compile
// them to slots starting at zero. When the method is bound to the class, the
@@ -3155,7 +3173,11 @@ static void classDefinition(Compiler* compiler)
}
// Update the class with the number of fields.
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
if (!isForeign)
{
compiler->bytecode.data[numFieldsInstruction] = (uint8_t)fields.count;
}
wrenSymbolTableClear(compiler->parser->vm, &fields);
compiler->enclosingClass = NULL;
@@ -3227,7 +3249,14 @@ void definition(Compiler* compiler)
{
if (match(compiler, TOKEN_CLASS))
{
classDefinition(compiler);
classDefinition(compiler, false);
return;
}
if (match(compiler, TOKEN_FOREIGN))
{
consume(compiler, TOKEN_CLASS, "Expect 'class' after 'foreign'.");
classDefinition(compiler, true);
return;
}
+8 -2
View File
@@ -32,10 +32,13 @@ static void dumpObject(Obj* obj)
{
switch (obj->type)
{
case OBJ_CLASS: printf("[class %p]", obj); break;
case OBJ_CLASS:
printf("[class %s %p]", ((ObjClass*)obj)->name->value, obj);
break;
case OBJ_CLOSURE: printf("[closure %p]", obj); break;
case OBJ_FIBER: printf("[fiber %p]", obj); break;
case OBJ_FN: printf("[fn %p]", obj); break;
case OBJ_FOREIGN: printf("[foreign %p]", obj); break;
case OBJ_INSTANCE: printf("[instance %p]", obj); break;
case OBJ_LIST: printf("[list %p]", obj); break;
case OBJ_MAP: printf("[map %p]", obj); break;
@@ -269,7 +272,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
break;
}
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
case CODE_CONSTRUCT: printf("CODE_CONSTRUCT\n"); break;
case CODE_FOREIGN_CONSTRUCT: printf("CODE_FOREIGN_CONSTRUCT\n"); break;
case CODE_CLASS:
{
@@ -278,6 +282,8 @@ static int dumpInstruction(WrenVM* vm, ObjFn* fn, int i, int* lastLine)
break;
}
case CODE_FOREIGN_CLASS: printf("FOREIGN_CLASS\n"); break;
case CODE_METHOD_INSTANCE:
{
int symbol = READ_SHORT();
+11
View File
@@ -157,11 +157,22 @@ OPCODE(CLOSURE)
// compiler-generated constructor metaclass methods.
OPCODE(CONSTRUCT)
// Creates a new instance of a foreign class.
//
// Assumes the class object is in slot zero, and replaces it with the new
// uninitialized instance of that class. This opcode is only emitted by the
// compiler-generated constructor metaclass methods.
OPCODE(FOREIGN_CONSTRUCT)
// Creates a class. Top of stack is the superclass, or `null` if the class
// inherits Object. Below that is a string for the name of the class. Byte
// [arg] is the number of fields in the class.
OPCODE(CLASS)
// Creates a foreign class. Top of stack is the superclass, or `null` if the
// class inherits Object. Below that is a string for the name of the class.
OPCODE(FOREIGN_CLASS)
// Define a method for symbol [arg]. The class receiving the method is popped
// off the stack, then the function defining the body is popped.
//
+33 -1
View File
@@ -65,7 +65,15 @@ void wrenBindSuperclass(WrenVM* vm, ObjClass* subclass, ObjClass* superclass)
subclass->superclass = superclass;
// Include the superclass in the total number of fields.
subclass->numFields += superclass->numFields;
if (subclass->numFields != -1)
{
subclass->numFields += superclass->numFields;
}
else
{
ASSERT(superclass->numFields == 0,
"A foreign class cannot inherit from a class with fields.");
}
// Inherit methods from its superclass.
for (int i = 0; i < superclass->methods.count; i++)
@@ -166,6 +174,16 @@ void wrenResetFiber(WrenVM* vm, ObjFiber* fiber, Obj* fn)
wrenAppendCallFrame(vm, fiber, fn, fiber->stack);
}
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size)
{
ObjForeign* object = ALLOCATE_FLEX(vm, ObjForeign, uint8_t, size);
initObj(vm, &object->obj, OBJ_FOREIGN, classObj);
// Zero out the bytes.
memset(object->data, 0, size);
return object;
}
ObjFn* wrenNewFunction(WrenVM* vm, ObjModule* module,
const Value* constants, int numConstants,
int numUpvalues, int arity,
@@ -903,6 +921,15 @@ static void markFn(WrenVM* vm, ObjFn* fn)
// TODO: What about the function name?
}
static void markForeign(WrenVM* vm, ObjForeign* foreign)
{
// TODO: Keep track of how much memory the foreign object uses. We can store
// this in each foreign object, but it will balloon the size. We may not want
// that much overhead. One option would be to let the foreign class register
// a C function that returns a size for the object. That way the VM doesn't
// always have to explicitly store it.
}
static void markInstance(WrenVM* vm, ObjInstance* instance)
{
wrenMarkObj(vm, (Obj*)instance->obj.classObj);
@@ -1007,6 +1034,7 @@ void wrenMarkObj(WrenVM* vm, Obj* obj)
case OBJ_CLOSURE: markClosure( vm, (ObjClosure*) obj); break;
case OBJ_FIBER: markFiber( vm, (ObjFiber*) obj); break;
case OBJ_FN: markFn( vm, (ObjFn*) obj); break;
case OBJ_FOREIGN: markForeign( vm, (ObjForeign*) obj); break;
case OBJ_INSTANCE: markInstance(vm, (ObjInstance*)obj); break;
case OBJ_LIST: markList( vm, (ObjList*) obj); break;
case OBJ_MAP: markMap( vm, (ObjMap*) obj); break;
@@ -1059,6 +1087,10 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
DEALLOCATE(vm, fn->debug);
break;
}
case OBJ_FOREIGN:
// TODO: Call finalizer.
break;
case OBJ_LIST:
wrenValueBufferClear(vm, &((ObjList*)obj)->elements);
+11
View File
@@ -51,6 +51,7 @@
#define AS_CLOSURE(value) ((ObjClosure*)AS_OBJ(value)) // ObjClosure*
#define AS_FIBER(v) ((ObjFiber*)AS_OBJ(v)) // ObjFiber*
#define AS_FN(value) ((ObjFn*)AS_OBJ(value)) // ObjFn*
#define AS_FOREIGN(v) ((ObjForeign*)AS_OBJ(v)) // ObjForeign*
#define AS_INSTANCE(value) ((ObjInstance*)AS_OBJ(value)) // ObjInstance*
#define AS_LIST(value) ((ObjList*)AS_OBJ(value)) // ObjList*
#define AS_MAP(value) ((ObjMap*)AS_OBJ(value)) // ObjMap*
@@ -74,6 +75,7 @@
#define IS_CLOSURE(value) (wrenIsObjType(value, OBJ_CLOSURE)) // ObjClosure
#define IS_FIBER(value) (wrenIsObjType(value, OBJ_FIBER)) // ObjFiber
#define IS_FN(value) (wrenIsObjType(value, OBJ_FN)) // ObjFn
#define IS_FOREIGN(value) (wrenIsObjType(value, OBJ_FOREIGN)) // ObjForeign
#define IS_INSTANCE(value) (wrenIsObjType(value, OBJ_INSTANCE)) // ObjInstance
#define IS_RANGE(value) (wrenIsObjType(value, OBJ_RANGE)) // ObjRange
#define IS_STRING(value) (wrenIsObjType(value, OBJ_STRING)) // ObjString
@@ -89,6 +91,7 @@ typedef enum {
OBJ_CLOSURE,
OBJ_FIBER,
OBJ_FN,
OBJ_FOREIGN,
OBJ_INSTANCE,
OBJ_LIST,
OBJ_MAP,
@@ -397,6 +400,12 @@ struct sObjClass
ObjString* name;
};
typedef struct
{
Obj obj;
uint8_t data[FLEXIBLE_ARRAY];
} ObjForeign;
typedef struct
{
Obj obj;
@@ -652,6 +661,8 @@ static inline void wrenAppendCallFrame(WrenVM* vm, ObjFiber* fiber,
frame->ip = wrenGetFrameFunction(frame)->bytecode;
}
ObjForeign* wrenNewForeign(WrenVM* vm, ObjClass* classObj, size_t size);
// 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
+194 -72
View File
@@ -37,6 +37,17 @@ static void* defaultReallocate(void* ptr, size_t newSize)
return realloc(ptr, newSize);
}
void wrenInitConfiguration(WrenConfiguration* configuration)
{
configuration->reallocateFn = NULL;
configuration->loadModuleFn = NULL;
configuration->bindForeignMethodFn = NULL;
configuration->bindForeignClassFn = NULL;
configuration->initialHeapSize = 1024 * 1024 * 10;
configuration->minHeapSize = 1024 * 1024;
configuration->heapGrowthPercent = 50;
}
WrenVM* wrenNewVM(WrenConfiguration* configuration)
{
WrenReallocateFn reallocate = defaultReallocate;
@@ -49,32 +60,19 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration)
memset(vm, 0, sizeof(WrenVM));
vm->reallocate = reallocate;
vm->bindForeign = configuration->bindForeignMethodFn;
vm->bindForeignMethod = configuration->bindForeignMethodFn;
vm->bindForeignClass = configuration->bindForeignClassFn;
vm->loadModule = configuration->loadModuleFn;
vm->nextGC = configuration->initialHeapSize;
vm->minNextGC = configuration->minHeapSize;
// +100 here because the configuration gives us the *additional* size of
// the heap relative to the in-use memory, while heapScalePercent is the
// *total* size of the heap relative to in-use.
vm->heapScalePercent = 100 + configuration->heapGrowthPercent;
wrenSymbolTableInit(&vm->methodNames);
vm->nextGC = 1024 * 1024 * 10;
if (configuration->initialHeapSize != 0)
{
vm->nextGC = configuration->initialHeapSize;
}
vm->minNextGC = 1024 * 1024;
if (configuration->minHeapSize != 0)
{
vm->minNextGC = configuration->minHeapSize;
}
vm->heapScalePercent = 150;
if (configuration->heapGrowthPercent != 0)
{
// +100 here because the configuration gives us the *additional* size of
// the heap relative to the in-use memory, while heapScalePercent is the
// *total* size of the heap relative to in-use.
vm->heapScalePercent = 100 + configuration->heapGrowthPercent;
}
ObjString* name = AS_STRING(CONST_STRING(vm, "core"));
wrenPushRoot(vm, (Obj*)name);
@@ -311,24 +309,21 @@ static WrenForeignMethodFn findForeignMethod(WrenVM* vm,
{
WrenForeignMethodFn fn;
// Let the host try to find it first.
if (vm->bindForeign != NULL)
{
fn = vm->bindForeign(vm, moduleName, className, isStatic, signature);
if (fn != NULL) return fn;
}
// Otherwise, try the built-in libraries.
// Bind foreign methods in the core module.
if (strcmp(moduleName, "core") == 0)
{
#if WREN_USE_LIB_IO
fn = wrenBindIOForeignMethod(vm, className, signature);
if (fn != NULL) return fn;
#endif
ASSERT(fn != NULL, "Failed to bind core module foreign method.");
return fn;
}
// TODO: Report a runtime error on failure to find it.
return NULL;
// For other modules, let the host bind it.
if (vm->bindForeignMethod == NULL) return NULL;
return vm->bindForeignMethod(vm, moduleName, className, isStatic, signature);
}
// Defines [methodValue] as a method on [classObj].
@@ -546,18 +541,23 @@ static bool importVariable(WrenVM* vm, Value moduleName, Value variableName,
return false;
}
// Verifies that [superclass] is a valid object to inherit from. That means it
// must be a class and cannot be the class of any built-in type.
// Verifies that [superclassValue] is a valid object to inherit from. That
// means it must be a class and cannot be the class of any built-in type.
//
// If successful, returns null. Otherwise, returns a string for the runtime
// Also validates that it doesn't result in a class with too many fields and
// the other limitations foreign classes have.
//
// If successful, returns `null`. Otherwise, returns a string for the runtime
// error message.
static Value validateSuperclass(WrenVM* vm, Value name,
Value superclassValue)
static Value validateSuperclass(WrenVM* vm, Value name, Value superclassValue,
int numFields)
{
// Make sure the superclass is a class.
if (!IS_CLASS(superclassValue))
{
return CONST_STRING(vm, "Must inherit from a class.");
return wrenStringFormat(vm,
"Class '@' cannot inherit from a non-class object.",
name);
}
// Make sure it doesn't inherit from a sealed built-in type. Primitive methods
@@ -572,13 +572,125 @@ static Value validateSuperclass(WrenVM* vm, Value name,
superclass == vm->rangeClass ||
superclass == vm->stringClass)
{
return wrenStringFormat(vm, "@ cannot inherit from @.",
return wrenStringFormat(vm,
"Class '@' cannot inherit from built-in class '@'.",
name, OBJ_VAL(superclass->name));
}
if (superclass->numFields == -1)
{
return wrenStringFormat(vm,
"Class '@' cannot inherit from foreign class '@'.",
name, OBJ_VAL(superclass->name));
}
if (numFields == -1 && superclass->numFields > 0)
{
return wrenStringFormat(vm,
"Foreign class '@' may not inherit from a class with fields.",
name);
}
if (superclass->numFields + numFields > MAX_FIELDS)
{
return wrenStringFormat(vm,
"Class '@' may not have more than 255 fields, including inherited "
"ones.", name);
}
return NULL_VAL;
}
static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
{
// TODO: Make this a runtime error?
ASSERT(vm->bindForeignClass != NULL,
"Cannot declare foreign classes without a bindForeignClassFn.");
WrenForeignClassMethods methods = vm->bindForeignClass(
vm, module->name->value, classObj->name->value);
Method method;
method.type = METHOD_FOREIGN;
method.fn.foreign = methods.allocate;
ASSERT(method.fn.foreign != NULL,
"A foreign class must provide an allocate function.");
int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<allocate>", 10);
wrenBindMethod(vm, classObj, symbol, method);
if (methods.finalize != NULL)
{
method.fn.foreign = methods.finalize;
symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>", 10);
wrenBindMethod(vm, classObj, symbol, method);
}
}
// Creates a new class.
//
// If [numFields] is -1, the class is a foreign class. The name and superclass
// should be on top of the fiber's stack. After calling this, the top of the
// stack will contain either the new class or a string if a runtime error
// occurred.
//
// Returns false if the result is an error.
static bool defineClass(WrenVM* vm, ObjFiber* fiber, int numFields,
ObjModule* module)
{
// Pull the name and superclass off the stack.
Value name = fiber->stackTop[-2];
Value superclassValue = fiber->stackTop[-1];
// We have two values on the stack and we are going to leave one, so discard
// the other slot.
fiber->stackTop--;
// Use implicit Object superclass if none given.
ObjClass* superclass = vm->objectClass;
if (!IS_NULL(superclassValue))
{
Value error = validateSuperclass(vm, name, superclassValue, numFields);
if (!IS_NULL(error))
{
fiber->stackTop[-1] = error;
return false;
}
superclass = AS_CLASS(superclassValue);
}
ObjClass* classObj = wrenNewClass(vm, superclass, numFields, AS_STRING(name));
fiber->stackTop[-1] = OBJ_VAL(classObj);
if (numFields == -1) bindForeignClass(vm, classObj, module);
return true;
}
static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
{
ObjClass* classObj = AS_CLASS(stack[0]);
ASSERT(classObj->numFields == -1, "Class must be a foreign class.");
// TODO: Don't look up every time.
int symbol = wrenSymbolTableFind(&vm->methodNames, "<allocate>", 10);
ASSERT(symbol != -1, "Should have defined <allocate> symbol.");
ASSERT(classObj->methods.count > symbol, "Class should have allocator.");
Method* method = &classObj->methods.data[symbol];
ASSERT(method->type == METHOD_FOREIGN, "Allocator should be foreign.");
// Pass the constructor arguments to the allocator as well.
vm->foreignCallSlot = stack;
vm->foreignCallNumArgs = (int)(fiber->stackTop - stack);
method->fn.foreign(vm);
// TODO: Check that allocateForeign was called.
}
// The main bytecode interpreter loop. This is where the magic happens. It is
// also, as you can imagine, highly performance critical. Returns `true` if the
// fiber completed without error.
@@ -1022,6 +1134,11 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
stackStart[0] = wrenNewInstance(vm, AS_CLASS(stackStart[0]));
DISPATCH();
CASE_CODE(FOREIGN_CONSTRUCT):
ASSERT(IS_CLASS(stackStart[0]), "'this' should be a class.");
createForeign(vm, fiber, stackStart);
DISPATCH();
CASE_CODE(CLOSURE):
{
ObjFn* prototype = AS_FN(fn->constants[READ_SHORT()]);
@@ -1057,40 +1174,16 @@ static WrenInterpretResult runInterpreter(WrenVM* vm, register ObjFiber* fiber)
CASE_CODE(CLASS):
{
Value name = PEEK2();
ObjClass* superclass = vm->objectClass;
// Use implicit Object superclass if none given.
if (!IS_NULL(PEEK()))
{
Value error = validateSuperclass(vm, name, PEEK());
if (!IS_NULL(error)) RUNTIME_ERROR(error);
superclass = AS_CLASS(PEEK());
}
int numFields = READ_BYTE();
Value classObj = OBJ_VAL(wrenNewClass(vm, superclass, numFields,
AS_STRING(name)));
// Don't pop the superclass and name off the stack until the subclass is
// done being created, to make sure it doesn't get collected.
DROP();
DROP();
// Now that we know the total number of fields, make sure we don't
// overflow.
if (superclass->numFields + numFields > MAX_FIELDS)
{
RUNTIME_ERROR(wrenStringFormat(vm,
"Class '@' may not have more than 255 fields, including inherited "
"ones.", name));
}
PUSH(classObj);
if (!defineClass(vm, fiber, READ_BYTE(), NULL)) RUNTIME_ERROR(PEEK());
DISPATCH();
}
CASE_CODE(FOREIGN_CLASS):
{
if (!defineClass(vm, fiber, -1, fn->module)) RUNTIME_ERROR(PEEK());
DISPATCH();
}
CASE_CODE(METHOD_INSTANCE):
CASE_CODE(METHOD_STATIC):
{
@@ -1323,6 +1416,20 @@ void wrenReleaseValue(WrenVM* vm, WrenValue* value)
DEALLOCATE(vm, value);
}
void* wrenAllocateForeign(WrenVM* vm, size_t size)
{
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
// TODO: Validate this. It can fail if the user calls this inside another
// foreign method, or calls one of the return functions.
ObjClass* classObj = AS_CLASS(vm->foreignCallSlot[0]);
ObjForeign* foreign = wrenNewForeign(vm, classObj, size);
vm->foreignCallSlot[0] = OBJ_VAL(foreign);
return (void*)foreign->data;
}
// Execute [source] in the context of the core module.
static WrenInterpretResult loadIntoCore(WrenVM* vm, const char* source)
{
@@ -1460,6 +1567,12 @@ void wrenPopRoot(WrenVM* vm)
vm->numTempRoots--;
}
int wrenGetArgumentCount(WrenVM* vm)
{
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
return vm->foreignCallNumArgs;
}
static void validateForeignArgument(WrenVM* vm, int index)
{
ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call.");
@@ -1485,6 +1598,15 @@ double wrenGetArgumentDouble(WrenVM* vm, int index)
return AS_NUM(*(vm->foreignCallSlot + index));
}
void* wrenGetArgumentForeign(WrenVM* vm, int index)
{
validateForeignArgument(vm, index);
if (!IS_FOREIGN(*(vm->foreignCallSlot + index))) return NULL;
return AS_FOREIGN(*(vm->foreignCallSlot + index))->data;
}
const char* wrenGetArgumentString(WrenVM* vm, int index)
{
validateForeignArgument(vm, index);
+4 -1
View File
@@ -125,7 +125,10 @@ struct WrenVM
int foreignCallNumArgs;
// The function used to locate foreign functions.
WrenBindForeignMethodFn bindForeign;
WrenBindForeignMethodFn bindForeignMethod;
// The function used to locate foreign classes.
WrenBindForeignClassFn bindForeignClass;
// The function used to load modules.
WrenLoadModuleFn loadModule;