diff --git a/src/include/wren.h b/src/include/wren.h index 0fbc7362..7792b22d 100644 --- a/src/include/wren.h +++ b/src/include/wren.h @@ -4,6 +4,10 @@ #include #include +// A single virtual machine for executing Wren code. +// +// Wren has no global state, so all state stored by a running interpreter lives +// here. typedef struct WrenVM WrenVM; // A handle to a method, bound to a receiver. @@ -11,6 +15,13 @@ typedef struct WrenVM WrenVM; // This is used to call a Wren method on some object from C code. typedef struct WrenMethod WrenMethod; +// A handle to a Wren object. +// +// This lets code outside of the VM hold a persistent reference to an object. +// After a value is acquired, and until it is released, this ensures the +// garbage collector will not reclaim it. +typedef struct WrenValue WrenValue; + // A generic allocation function that handles all explicit memory management // used by Wren. It's used like so: // @@ -169,6 +180,10 @@ void wrenCall(WrenVM* vm, WrenMethod* method, const char* argTypes, ...); // no longer be used. void wrenReleaseMethod(WrenVM* vm, WrenMethod* method); +// Releases the reference stored in [value]. After calling this, [value] can no +// longer be used. +void wrenReleaseValue(WrenVM* vm, WrenValue* value); + // The following functions read one of the arguments passed to a foreign call. // They may only be called while within a function provided to // [wrenDefineMethod] or [wrenDefineStaticMethod] that Wren has invoked. @@ -205,6 +220,12 @@ double wrenGetArgumentDouble(WrenVM* vm, int index); // function returns, since the garbage collector may reclaim it. const char* wrenGetArgumentString(WrenVM* vm, int index); +// Creates a handle for the value passed as an argument to a foreign call. +// +// This will prevent the object that is referred to from being garbage collected +// until the handle is released by calling [wrenReleaseValue()]. +WrenValue* wrenGetArgumentValue(WrenVM* vm, int index); + // The following functions provide the return value for a foreign method back // to Wren. Like above, they may only be called during a foreign call invoked // by Wren. @@ -228,4 +249,10 @@ void wrenReturnDouble(WrenVM* vm, double value); // [text] will be calculated using `strlen()`. void wrenReturnString(WrenVM* vm, const char* text, int length); +// Provides the return value for a foreign call. +// +// This uses the value referred to by the handle as the return value, but it +// does not release the handle. +void wrenReturnValue(WrenVM* vm, WrenValue* value); + #endif diff --git a/src/vm/wren_vm.c b/src/vm/wren_vm.c index 6b4bf66c..5e26d599 100644 --- a/src/vm/wren_vm.c +++ b/src/vm/wren_vm.c @@ -112,11 +112,12 @@ void wrenFreeVM(WrenVM* vm) obj = next; } - // Tell the user if they didn't free any method handles. We don't want to - // just free them here because the host app may still have pointers to them - // that they may try to use. Better to tell them about the bug early. + // Tell the user if they didn't free any handles. We don't want to just free + // them here because the host app may still have pointers to them that they + // may try to use. Better to tell them about the bug early. ASSERT(vm->methodHandles == NULL, "All methods have not been released."); - + ASSERT(vm->valueHandles == NULL, "All values have not been released."); + wrenSymbolTableClear(vm, &vm->methodNames); DEALLOCATE(vm, vm); @@ -166,6 +167,14 @@ static void collectGarbage(WrenVM* vm) { wrenMarkObj(vm, (Obj*)handle->fiber); } + + // The value handles. + for (WrenValue* value = vm->valueHandles; + value != NULL; + value = value->next) + { + wrenMarkValue(vm, value->value); + } // Any object the compiler is using (if there is one). if (vm->compiler != NULL) wrenMarkCompiler(vm, vm->compiler); @@ -1289,6 +1298,25 @@ void wrenReleaseMethod(WrenVM* vm, WrenMethod* method) DEALLOCATE(vm, method); } +void wrenReleaseValue(WrenVM* vm, WrenValue* value) +{ + ASSERT(value != NULL, "NULL value."); + + // Update the VM's head pointer if we're releasing the first handle. + if (vm->valueHandles == value) vm->valueHandles = value->next; + + // Unlink it from the list. + if (value->prev != NULL) value->prev->next = value->next; + if (value->next != NULL) value->next->prev = value->prev; + + // Clear it out. This isn't strictly necessary since we're going to free it, + // but it makes for easier debugging. + value->prev = NULL; + value->next = NULL; + value->value = NULL_VAL; + DEALLOCATE(vm, value); +} + // Execute [source] in the context of the core module. static WrenInterpretResult loadIntoCore(WrenVM* vm, const char* source) { @@ -1426,11 +1454,16 @@ void wrenPopRoot(WrenVM* vm) vm->numTempRoots--; } -bool wrenGetArgumentBool(WrenVM* vm, int index) +static void validateForeignArgument(WrenVM* vm, int index) { ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); ASSERT(index >= 0, "index cannot be negative."); ASSERT(index < vm->foreignCallNumArgs, "Not that many arguments."); +} + +bool wrenGetArgumentBool(WrenVM* vm, int index) +{ + validateForeignArgument(vm, index); if (!IS_BOOL(*(vm->foreignCallSlot + index))) return false; @@ -1439,9 +1472,7 @@ bool wrenGetArgumentBool(WrenVM* vm, int index) double wrenGetArgumentDouble(WrenVM* vm, int index) { - ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); - ASSERT(index >= 0, "index cannot be negative."); - ASSERT(index < vm->foreignCallNumArgs, "Not that many arguments."); + validateForeignArgument(vm, index); if (!IS_NUM(*(vm->foreignCallSlot + index))) return 0.0; @@ -1450,15 +1481,30 @@ double wrenGetArgumentDouble(WrenVM* vm, int index) const char* wrenGetArgumentString(WrenVM* vm, int index) { - ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); - ASSERT(index >= 0, "index cannot be negative."); - ASSERT(index < vm->foreignCallNumArgs, "Not that many arguments."); + validateForeignArgument(vm, index); if (!IS_STRING(*(vm->foreignCallSlot + index))) return NULL; return AS_CSTRING(*(vm->foreignCallSlot + index)); } +WrenValue* wrenGetArgumentValue(WrenVM* vm, int index) +{ + validateForeignArgument(vm, index); + + // Make a handle for it. + WrenValue* value = ALLOCATE(vm, WrenValue); + value->value = *(vm->foreignCallSlot + index); + + // Add it to the front of the linked list of handles. + if (vm->valueHandles != NULL) vm->valueHandles->prev = value; + value->prev = NULL; + value->next = vm->valueHandles; + vm->valueHandles = value; + + return value; +} + void wrenReturnBool(WrenVM* vm, bool value) { ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); @@ -1486,3 +1532,12 @@ void wrenReturnString(WrenVM* vm, const char* text, int length) *vm->foreignCallSlot = wrenNewString(vm, text, size); vm->foreignCallSlot = NULL; } + +void wrenReturnValue(WrenVM* vm, WrenValue* value) +{ + ASSERT(vm->foreignCallSlot != NULL, "Must be in foreign call."); + ASSERT(value != NULL, "Value cannot be NULL."); + + *vm->foreignCallSlot = value->value; + vm->foreignCallSlot = NULL; +} diff --git a/src/vm/wren_vm.h b/src/vm/wren_vm.h index c34e9155..1dfd3427 100644 --- a/src/vm/wren_vm.h +++ b/src/vm/wren_vm.h @@ -17,6 +17,11 @@ typedef enum #undef OPCODE } Code; +// A handle to a method. +// +// It is a node in the doubly-linked list of currently allocate method handles. +// Each node has a reference to the fiber containing the method stub to call +// the method. struct WrenMethod { // The fiber that invokes the method. Its stack is pre-populated with the @@ -28,6 +33,17 @@ struct WrenMethod WrenMethod* next; }; +// A handle to a value, basically just a linked list of extra GC roots. +// +// Note that even non-heap-allocated values can be stored here. +struct WrenValue +{ + Value value; + + WrenValue* prev; + WrenValue* next; +}; + struct WrenVM { ObjClass* boolClass; @@ -99,6 +115,10 @@ struct WrenVM // Pointer to the first node in the linked list of active method handles or // NULL if there are no handles. WrenMethod* methodHandles; + + // 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. diff --git a/test/api/get_value/get_value.c b/test/api/get_value/get_value.c new file mode 100644 index 00000000..fd02c23a --- /dev/null +++ b/test/api/get_value/get_value.c @@ -0,0 +1,24 @@ +#include + +#include "get_value.h" + +static WrenValue* value; + +static void setValue(WrenVM* vm) +{ + value = wrenGetArgumentValue(vm, 1); +} + +static void getValue(WrenVM* vm) +{ + wrenReturnValue(vm, value); + wrenReleaseValue(vm, value); +} + +WrenForeignMethodFn getValueBindForeign(const char* signature) +{ + if (strcmp(signature, "static Api.value=(_)") == 0) return setValue; + if (strcmp(signature, "static Api.value") == 0) return getValue; + + return NULL; +} diff --git a/test/api/get_value/get_value.h b/test/api/get_value/get_value.h new file mode 100644 index 00000000..8f333fac --- /dev/null +++ b/test/api/get_value/get_value.h @@ -0,0 +1,3 @@ +#include "wren.h" + +WrenForeignMethodFn getValueBindForeign(const char* signature); diff --git a/test/api/get_value/get_value.wren b/test/api/get_value/get_value.wren new file mode 100644 index 00000000..e3e07d4f --- /dev/null +++ b/test/api/get_value/get_value.wren @@ -0,0 +1,14 @@ +class Api { + foreign static value=(value) + foreign static value +} + +Api.value = ["list", "of", "strings"] + +// Do some stuff to trigger a GC (at least when GC stress testing enabled). +var s = "string" +for (i in 1...10) { + s = s + " more" +} + +IO.print(Api.value) // expect: [list, of, strings] diff --git a/test/api/main.c b/test/api/main.c index 5724a058..97eb121e 100644 --- a/test/api/main.c +++ b/test/api/main.c @@ -5,12 +5,13 @@ #include "vm.h" #include "wren.h" +#include "get_value/get_value.h" #include "return_bool/return_bool.h" #include "return_double/return_double.h" #include "return_null/return_null.h" -#define REGISTER_TEST(name) \ - if (strcmp(testName, #name) == 0) return name##BindForeign(fullName) +#define REGISTER_TEST(name, camelCase) \ + if (strcmp(testName, #name) == 0) return camelCase##BindForeign(fullName) // The name of the currently executing API test. const char* testName; @@ -30,9 +31,10 @@ static WrenForeignMethodFn bindForeign( strcat(fullName, "."); strcat(fullName, signature); - REGISTER_TEST(return_bool); - REGISTER_TEST(return_double); - REGISTER_TEST(return_null); + REGISTER_TEST(get_value, getValue); + REGISTER_TEST(return_bool, returnBool); + REGISTER_TEST(return_double, returnDouble); + REGISTER_TEST(return_null, returnNull); fprintf(stderr, "Unknown foreign method '%s' for test '%s'\n", fullName, testName); diff --git a/test/api/return_bool/return_bool.c b/test/api/return_bool/return_bool.c index 160db170..c52dae4c 100644 --- a/test/api/return_bool/return_bool.c +++ b/test/api/return_bool/return_bool.c @@ -12,7 +12,7 @@ static void returnFalse(WrenVM* vm) wrenReturnBool(vm, false); } -WrenForeignMethodFn return_boolBindForeign(const char* signature) +WrenForeignMethodFn returnBoolBindForeign(const char* signature) { if (strcmp(signature, "static Api.returnTrue") == 0) return returnTrue; if (strcmp(signature, "static Api.returnFalse") == 0) return returnFalse; diff --git a/test/api/return_bool/return_bool.h b/test/api/return_bool/return_bool.h index 21175b0c..f20d4986 100644 --- a/test/api/return_bool/return_bool.h +++ b/test/api/return_bool/return_bool.h @@ -1,3 +1,3 @@ #include "wren.h" -WrenForeignMethodFn return_boolBindForeign(const char* signature); +WrenForeignMethodFn returnBoolBindForeign(const char* signature); diff --git a/test/api/return_double/return_double.c b/test/api/return_double/return_double.c index d0f317d7..83d47153 100644 --- a/test/api/return_double/return_double.c +++ b/test/api/return_double/return_double.c @@ -12,7 +12,7 @@ static void returnFloat(WrenVM* vm) wrenReturnDouble(vm, 123.456); } -WrenForeignMethodFn return_doubleBindForeign(const char* signature) +WrenForeignMethodFn returnDoubleBindForeign(const char* signature) { if (strcmp(signature, "static Api.returnInt") == 0) return returnInt; if (strcmp(signature, "static Api.returnFloat") == 0) return returnFloat; diff --git a/test/api/return_double/return_double.h b/test/api/return_double/return_double.h index e16598df..50f49232 100644 --- a/test/api/return_double/return_double.h +++ b/test/api/return_double/return_double.h @@ -1,3 +1,3 @@ #include "wren.h" -WrenForeignMethodFn return_doubleBindForeign(const char* signature); +WrenForeignMethodFn returnDoubleBindForeign(const char* signature); diff --git a/test/api/return_null/return_null.c b/test/api/return_null/return_null.c index d0d47134..41a94d82 100644 --- a/test/api/return_null/return_null.c +++ b/test/api/return_null/return_null.c @@ -7,7 +7,7 @@ static void implicitNull(WrenVM* vm) // Do nothing. } -WrenForeignMethodFn return_nullBindForeign(const char* signature) +WrenForeignMethodFn returnNullBindForeign(const char* signature) { if (strcmp(signature, "static Api.implicitNull") == 0) return implicitNull; diff --git a/test/api/return_null/return_null.h b/test/api/return_null/return_null.h index 8a565904..d2afb9c8 100644 --- a/test/api/return_null/return_null.h +++ b/test/api/return_null/return_null.h @@ -1,3 +1,3 @@ #include "wren.h" -WrenForeignMethodFn return_nullBindForeign(const char* signature); +WrenForeignMethodFn returnNullBindForeign(const char* signature);