Get finalizers working.

This commit is contained in:
Bob Nystrom
2015-08-31 21:56:21 -07:00
parent 1e0a2e6036
commit 36f3059e48
6 changed files with 103 additions and 10 deletions

View File

@ -167,6 +167,9 @@ WrenVM* wrenNewVM(WrenConfiguration* configuration);
// call to [wrenNewVM].
void wrenFreeVM(WrenVM* vm);
// Immediately run the garbage collector to free unused memory.
void wrenCollectGarbage(WrenVM* vm);
// Runs [source], a string of Wren source code in a new fiber in [vm].
//
// [sourcePath] is a string describing where [source] was located, for use in

View File

@ -1089,7 +1089,7 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
}
case OBJ_FOREIGN:
// TODO: Call finalizer.
wrenFinalizeForeign(vm, (ObjForeign*)obj);
break;
case OBJ_LIST:

View File

@ -125,7 +125,7 @@ void wrenSetCompiler(WrenVM* vm, Compiler* compiler)
vm->compiler = compiler;
}
static void collectGarbage(WrenVM* vm)
void wrenCollectGarbage(WrenVM* vm)
{
#if WREN_DEBUG_TRACE_MEMORY || WREN_DEBUG_TRACE_GC
printf("-- gc --\n");
@ -222,9 +222,9 @@ void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize)
#if WREN_DEBUG_GC_STRESS
// Since collecting calls this function to free things, make sure we don't
// recurse.
if (newSize > 0) collectGarbage(vm);
if (newSize > 0) wrenCollectGarbage(vm);
#else
if (newSize > 0 && vm->bytesAllocated > vm->nextGC) collectGarbage(vm);
if (newSize > 0 && vm->bytesAllocated > vm->nextGC) wrenCollectGarbage(vm);
#endif
return vm->reallocate(memory, newSize);
@ -615,10 +615,13 @@ static void bindForeignClass(WrenVM* vm, ObjClass* classObj, ObjModule* module)
int symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<allocate>", 10);
wrenBindMethod(vm, classObj, symbol, method);
// Add the symbol even if there is no finalizer so we can ensure that the
// symbol itself is always in the symbol table.
symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>", 10);
if (methods.finalize != NULL)
{
method.fn.foreign = methods.finalize;
symbol = wrenSymbolTableEnsure(vm, &vm->methodNames, "<finalize>", 10);
wrenBindMethod(vm, classObj, symbol, method);
}
}
@ -686,6 +689,32 @@ static void createForeign(WrenVM* vm, ObjFiber* fiber, Value* stack)
// TODO: Check that allocateForeign was called.
}
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign)
{
// TODO: Don't look up every time.
int symbol = wrenSymbolTableFind(&vm->methodNames, "<finalize>", 10);
ASSERT(symbol != -1, "Should have defined <finalize> symbol.");
// If there are no finalizers, don't finalize it.
if (symbol == -1) return;
// If the class doesn't have a finalizer, bail out.
ObjClass* classObj = foreign->obj.classObj;
if (symbol >= classObj->methods.count) return;
Method* method = &classObj->methods.data[symbol];
if (method->type == METHOD_NONE) return;
ASSERT(method->type == METHOD_FOREIGN, "Finalizer should be foreign.");
// Pass the constructor arguments to the allocator as well.
Value slot = OBJ_VAL(foreign);
vm->foreignCallSlot = &slot;
vm->foreignCallNumArgs = 1;
method->fn.foreign(vm);
}
// 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.

View File

@ -89,17 +89,17 @@ struct WrenVM
Obj* tempRoots[WREN_MAX_TEMP_ROOTS];
int numTempRoots;
// Pointer to the first node in the linked list of active value handles or
// NULL if there are no handles.
WrenValue* valueHandles;
// Foreign function data:
// During a foreign function call, this will point to the first argument (the
// receiver) of the call on the fiber's stack.
Value* foreignCallSlot;
// Pointer to the first node in the linked list of active value handles or
// NULL if there are no handles.
WrenValue* valueHandles;
// During a foreign function call, this will contain the number of arguments
// to the function.
int foreignCallNumArgs;
@ -143,6 +143,9 @@ struct WrenVM
// [oldSize] will be zero. It should return NULL.
void* wrenReallocate(WrenVM* vm, void* memory, size_t oldSize, size_t newSize);
// Invoke the finalizer for the foreign object referenced by [foreign].
void wrenFinalizeForeign(WrenVM* vm, ObjForeign* foreign);
// Creates a new [WrenValue] for [value].
WrenValue* wrenCaptureValue(WrenVM* vm, Value value);

View File

@ -3,6 +3,18 @@
#include "foreign_class.h"
static int finalized = 0;
static void apiGC(WrenVM* vm)
{
wrenCollectGarbage(vm);
}
static void apiFinalized(WrenVM* vm)
{
wrenReturnDouble(vm, finalized);
}
static void counterAllocate(WrenVM* vm)
{
double* value = (double*)wrenAllocateForeign(vm, sizeof(double));
@ -60,8 +72,20 @@ static void pointToString(WrenVM* vm)
wrenReturnString(vm, result, (int)strlen(result));
}
static void resourceAllocate(WrenVM* vm)
{
wrenAllocateForeign(vm, 0);
}
static void resourceFinalize(WrenVM* vm)
{
finalized++;
}
WrenForeignMethodFn foreignClassBindMethod(const char* signature)
{
if (strcmp(signature, "static Api.gc()") == 0) return apiGC;
if (strcmp(signature, "static Api.finalized") == 0) return apiFinalized;
if (strcmp(signature, "Counter.increment(_)") == 0) return counterIncrement;
if (strcmp(signature, "Counter.value") == 0) return counterValue;
if (strcmp(signature, "Point.translate(_,_,_)") == 0) return pointTranslate;
@ -84,4 +108,11 @@ void foreignClassBindClass(
methods->allocate = pointAllocate;
return;
}
if (strcmp(className, "Resource") == 0)
{
methods->allocate = resourceAllocate;
methods->finalize = resourceFinalize;
return;
}
}

View File

@ -1,3 +1,8 @@
class Api {
foreign static gc()
foreign static finalized
}
// Class with a default constructor.
foreign class Counter {
foreign increment(amount)
@ -46,3 +51,25 @@ var error = Fiber.new {
class Subclass is Point {}
}.try()
IO.print(error) // expect: Class 'Subclass' cannot inherit from foreign class 'Point'.
// Class with a finalizer.
foreign class Resource {}
var resources = [
Resource.new(),
Resource.new(),
Resource.new()
]
Api.gc()
IO.print(Api.finalized) // expect: 0
resources.removeAt(-1)
Api.gc()
IO.print(Api.finalized) // expect: 1
resources.clear()
Api.gc()
IO.print(Api.finalized) // expect: 3