mirror of
https://github.com/wren-lang/wren.git
synced 2026-01-11 06:08:41 +01:00
Get finalizers working.
This commit is contained in:
@ -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
|
||||
|
||||
@ -1089,7 +1089,7 @@ void wrenFreeObj(WrenVM* vm, Obj* obj)
|
||||
}
|
||||
|
||||
case OBJ_FOREIGN:
|
||||
// TODO: Call finalizer.
|
||||
wrenFinalizeForeign(vm, (ObjForeign*)obj);
|
||||
break;
|
||||
|
||||
case OBJ_LIST:
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user