summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorJo-Philipp Wich <jo@mein.io>2022-08-12 01:40:49 +0200
committerGitHub <noreply@github.com>2022-08-12 01:40:49 +0200
commit796a9471bfe69007678d83ba279f9b24cfd17ec1 (patch)
tree3c284b97e970c7cd5f17d13f31ba4d7df028bb69
parentd0ae9106307343298c926ed065c19609aa37a001 (diff)
parentb41cb2d7b7cc1a10f4f68f9c8f544f916f448822 (diff)
Merge pull request #99 from jow-/gc-control
Implement periodic GC
-rw-r--r--include/ucode/types.h4
-rw-r--r--include/ucode/vm.h9
-rw-r--r--lib.c44
-rw-r--r--main.c13
-rw-r--r--tests/cram/test_basic.t4
-rw-r--r--tests/custom/03_stdlib/60_gc107
-rw-r--r--types.c15
-rw-r--r--vm.c59
8 files changed, 250 insertions, 5 deletions
diff --git a/include/ucode/types.h b/include/ucode/types.h
index e20f3d6..6041b22 100644
--- a/include/ucode/types.h
+++ b/include/ucode/types.h
@@ -303,8 +303,10 @@ struct uc_vm {
uint8_t u8;
int8_t s8;
} arg;
- size_t spread_values;
+ size_t alloc_refs;
uint8_t trace;
+ uint8_t gc_flags;
+ uint16_t gc_interval;
uc_stringbuf_t *strbuf;
uc_exception_handler_t *exhandler;
FILE *output;
diff --git a/include/ucode/vm.h b/include/ucode/vm.h
index 8562524..161f1ee 100644
--- a/include/ucode/vm.h
+++ b/include/ucode/vm.h
@@ -116,6 +116,12 @@ typedef enum {
ERROR_RUNTIME
} uc_vm_status_t;
+typedef enum {
+ GC_ENABLED = (1 << 0)
+} uc_vm_gc_flags_t;
+
+#define GC_DEFAULT_INTERVAL 1000
+
extern uint32_t insns[__I_MAX];
void uc_vm_init(uc_vm_t *vm, uc_parse_config_t *config);
@@ -139,6 +145,9 @@ void uc_vm_exception_handler_set(uc_vm_t *vm, uc_exception_handler_t *exhandler)
uint32_t uc_vm_trace_get(uc_vm_t *vm);
void uc_vm_trace_set(uc_vm_t *vm, uint32_t level);
+bool uc_vm_gc_start(uc_vm_t *vm, uint16_t interval);
+bool uc_vm_gc_stop(uc_vm_t *vm);
+
uc_exception_type_t uc_vm_call(uc_vm_t *vm, bool mcall, size_t nargs);
void __attribute__((format(printf, 3, 0)))
diff --git a/lib.c b/lib.c
index 7a04cdd..1cbde6d 100644
--- a/lib.c
+++ b/lib.c
@@ -3519,6 +3519,49 @@ uc_hexdec(uc_vm_t *vm, size_t nargs)
return ucv_stringbuf_finish(buf);
}
+static uc_value_t *
+uc_gc(uc_vm_t *vm, size_t nargs)
+{
+ uc_value_t *operation = uc_fn_arg(0);
+ uc_value_t *argument = uc_fn_arg(1);
+ const char *op = NULL;
+ uc_weakref_t *ref;
+ int64_t n;
+
+ if (operation != NULL && ucv_type(operation) != UC_STRING)
+ return NULL;
+
+ op = ucv_string_get(operation);
+
+ if (!op || !strcmp(op, "collect")) {
+ ucv_gc(vm);
+
+ return ucv_boolean_new(true);
+ }
+ else if (!strcmp(op, "start")) {
+ n = argument ? ucv_int64_get(argument) : 0;
+
+ if (errno || n < 0 || n > 0xFFFF)
+ return NULL;
+
+ if (n == 0)
+ n = GC_DEFAULT_INTERVAL;
+
+ return ucv_boolean_new(uc_vm_gc_start(vm, n));
+ }
+ else if (!strcmp(op, "stop")) {
+ return ucv_boolean_new(uc_vm_gc_stop(vm));
+ }
+ else if (!strcmp(op, "count")) {
+ for (n = 0, ref = vm->values.next; ref != &vm->values; ref = ref->next)
+ n++;
+
+ return ucv_uint64_new(n);
+ }
+
+ return NULL;
+}
+
const uc_function_list_t uc_stdlib_functions[] = {
{ "chr", uc_chr },
@@ -3586,6 +3629,7 @@ const uc_function_list_t uc_stdlib_functions[] = {
{ "clock", uc_clock },
{ "hexdec", uc_hexdec },
{ "hexenc", uc_hexenc },
+ { "gc", uc_gc }
};
diff --git a/main.c b/main.c
index 02df3b1..03549fe 100644
--- a/main.c
+++ b/main.c
@@ -54,6 +54,10 @@ print_usage(const char *app)
"-t\n"
" Enable VM execution tracing.\n\n"
+ "-g interval\n"
+ " Perform periodic garbage collection every `interval` object\n"
+ " allocations.\n\n"
+
"-S\n"
" Enable strict mode.\n\n"
@@ -130,6 +134,9 @@ compile(uc_vm_t *vm, uc_source_t *src, FILE *precompile, bool strip, char *inter
goto out;
}
+ if (vm->gc_interval)
+ uc_vm_gc_start(vm, vm->gc_interval);
+
rc = uc_vm_execute(vm, program, &res);
switch (rc) {
@@ -468,7 +475,7 @@ appname(const char *argv0)
int
main(int argc, char **argv)
{
- const char *optspec = "he:tST::RD:F:U:l:L:c::o:s";
+ const char *optspec = "he:tg:ST::RD:F:U:l:L:c::o:s";
char *interp = "/usr/bin/env ucode";
uc_source_t *source = NULL;
FILE *precompile = NULL;
@@ -554,6 +561,10 @@ main(int argc, char **argv)
uc_vm_trace_set(&vm, 1);
break;
+ case 'g':
+ vm.gc_interval = atoi(optarg);
+ break;
+
case 'D':
if (!parse_define_string(optarg, uc_vm_scope_get(&vm))) {
rv = 1;
diff --git a/tests/cram/test_basic.t b/tests/cram/test_basic.t
index 5911ac9..c33dee9 100644
--- a/tests/cram/test_basic.t
+++ b/tests/cram/test_basic.t
@@ -25,6 +25,10 @@ check that ucode provides exepected help:
-t
Enable VM execution tracing.
+ -g interval
+ Perform periodic garbage collection every `interval` object
+ allocations.
+
-S
Enable strict mode.
diff --git a/tests/custom/03_stdlib/60_gc b/tests/custom/03_stdlib/60_gc
new file mode 100644
index 0000000..44e5d9e
--- /dev/null
+++ b/tests/custom/03_stdlib/60_gc
@@ -0,0 +1,107 @@
+The `gc()` function allows controlling the garbage collector of the VM.
+It takes the requested operation as first argument and an optional,
+operation specific second argument.
+
+Returns `null` if the given operation is invalid or if the operation
+specific argument is invalid.
+
+Returns `false` if the requested operation would not result in any
+changes.
+
+Returns `true` if the requested oepration succeeded (e.g. starting
+the GC when it was previously stopped).
+
+Returns an object count if the given operation is `count`.
+
+-- Testcase --
+{%
+ printf("Count #1: %d\n", gc("count"));
+
+ // create an unreachable cyclic structure
+ let o = {};
+ o.cycle = o;
+ o = null;
+
+ printf("Count #2: %d\n", gc("count"));
+
+ // invoking gc without any argument defaults to "collect"
+ gc();
+
+ printf("Count #3: %d\n", gc("count"));
+
+
+ // create another unreachable cyclic structure
+ o = {};
+ o.cycle = o;
+ o = null;
+
+ printf("Count #4: %d\n", gc("count"));
+
+ // invoking gc with explicit collect argument
+ gc("collect");
+
+ printf("Count #5: %d\n", gc("count"));
+%}
+-- End --
+
+-- Expect stdout --
+Count #1: 5
+Count #2: 6
+Count #3: 5
+Count #4: 6
+Count #5: 5
+-- End --
+
+
+Testing enabling the automatic collector.
+
+-- Testcase --
+{%
+ // start GC, trigger every 10 object allocations
+ gc("start", 10);
+
+ for (let i = 0; i < 100; i++) {
+ let o = {};
+ o.cyle = o;
+ o = null;
+
+ if ((i % 10) == 0)
+ printf("Count #%d: %d\n", (i / 10) + 1, gc("count"));
+ }
+
+ // stop GC
+ gc("stop");
+
+ for (let i = 100; i < 200; i++) {
+ let o = {};
+ o.cyle = o;
+ o = null;
+
+ if ((i % 10) == 0)
+ printf("Count #%d: %d\n", (i / 10) + 1, gc("count"));
+ }
+%}
+-- End --
+
+-- Expect stdout --
+Count #1: 6
+Count #2: 12
+Count #3: 12
+Count #4: 12
+Count #5: 12
+Count #6: 12
+Count #7: 12
+Count #8: 12
+Count #9: 12
+Count #10: 12
+Count #11: 12
+Count #12: 22
+Count #13: 32
+Count #14: 42
+Count #15: 52
+Count #16: 62
+Count #17: 72
+Count #18: 82
+Count #19: 92
+Count #20: 102
+-- End --
diff --git a/types.c b/types.c
index 3a3e35c..e05d685 100644
--- a/types.c
+++ b/types.c
@@ -705,8 +705,10 @@ ucv_array_new_length(uc_vm_t *vm, size_t length)
uc_vector_grow(array);
- if (vm)
+ if (vm) {
ucv_ref(&vm->values, &array->ref);
+ vm->alloc_refs++;
+ }
return &array->header;
}
@@ -901,8 +903,10 @@ ucv_object_new(uc_vm_t *vm)
object->header.refcount = 1;
object->table = table;
- if (vm)
+ if (vm) {
ucv_ref(&vm->values, &object->ref);
+ vm->alloc_refs++;
+ }
return &object->header;
}
@@ -1022,8 +1026,10 @@ ucv_closure_new(uc_vm_t *vm, uc_function_t *function, bool arrow_fn)
closure->is_arrow = arrow_fn;
closure->upvals = function->nupvals ? (uc_upvalref_t **)((uintptr_t)closure + ALIGN(sizeof(*closure))) : NULL;
- if (vm)
+ if (vm) {
ucv_ref(&vm->values, &closure->ref);
+ vm->alloc_refs++;
+ }
uc_program_get(function->program);
@@ -2220,6 +2226,9 @@ ucv_gc_common(uc_vm_t *vm, bool final)
for (i = 0; i < vm->restypes.count; i++)
ucv_gc_mark(vm->restypes.entries[i]->proto);
+
+ for (i = 0; i < vm->exports.count; i++)
+ ucv_gc_mark(vm->exports.entries[i]->value);
}
/* unref unreachable objects */
diff --git a/vm.c b/vm.c
index c08f9ce..d8f0074 100644
--- a/vm.c
+++ b/vm.c
@@ -2508,6 +2508,35 @@ uc_vm_insn_dynload(uc_vm_t *vm, uc_vm_insn_t insn)
}
}
+static void
+uc_vm_gc_step(uc_vm_t *vm)
+{
+ size_t curr_count = 0, prev_count = 0;
+ uc_weakref_t *ref;
+
+ if (!(vm->gc_flags & GC_ENABLED))
+ return;
+
+ if (vm->alloc_refs >= vm->gc_interval) {
+ if (vm->trace) {
+ for (ref = vm->values.next; ref != &vm->values; ref = ref->next)
+ prev_count++;
+
+ ucv_gc(vm);
+
+ for (ref = vm->values.next; ref != &vm->values; ref = ref->next)
+ curr_count++;
+
+ fprintf(stderr, "! GC reclaimed %zu object(s)\n", prev_count - curr_count);
+ }
+ else {
+ ucv_gc(vm);
+ }
+
+ vm->alloc_refs = 0;
+ }
+}
+
static uc_value_t *
uc_vm_callframe_pop(uc_vm_t *vm)
{
@@ -2755,6 +2784,7 @@ uc_vm_execute_chunk(uc_vm_t *vm)
case I_POP:
ucv_put(uc_vm_stack_pop(vm));
+ uc_vm_gc_step(vm);
break;
case I_CUPV:
@@ -3028,3 +3058,32 @@ uc_vm_registry_delete(uc_vm_t *vm, const char *key)
{
return ucv_object_delete(vm->registry, key);
}
+
+bool
+uc_vm_gc_start(uc_vm_t *vm, uint16_t interval)
+{
+ bool changed = false;
+
+ if (vm->gc_interval != interval) {
+ vm->gc_interval = interval;
+ changed = true;
+ }
+
+ if (!(vm->gc_flags & GC_ENABLED)) {
+ vm->gc_flags |= GC_ENABLED;
+ changed = true;
+ }
+
+ return changed;
+}
+
+bool
+uc_vm_gc_stop(uc_vm_t *vm)
+{
+ if (!(vm->gc_flags & GC_ENABLED))
+ return false;
+
+ vm->gc_flags &= ~GC_ENABLED;
+
+ return true;
+}