diff options
-rw-r--r-- | include/ucode/types.h | 4 | ||||
-rw-r--r-- | include/ucode/vm.h | 9 | ||||
-rw-r--r-- | lib.c | 44 | ||||
-rw-r--r-- | main.c | 13 | ||||
-rw-r--r-- | tests/cram/test_basic.t | 4 | ||||
-rw-r--r-- | tests/custom/03_stdlib/60_gc | 107 | ||||
-rw-r--r-- | types.c | 15 | ||||
-rw-r--r-- | vm.c | 59 |
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))) @@ -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 } }; @@ -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 -- @@ -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 */ @@ -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; +} |