From 1c7ddf07adb69e9b6deb950f174e4e966cc4b666 Mon Sep 17 00:00:00 2001 From: Thakee Nathees Date: Tue, 3 May 2022 16:45:30 +0530 Subject: [PATCH] memory trace for vmRealloc implemented. a little python script have written to check for memory leaks from the trace report. Which is at scripts/leak_detect.py --- scripts/leak_detect.py | 75 ++++++++++++++++++++++++++++++++++++++++++ src/pk_debug.c | 2 +- src/pk_internal.h | 24 +++++++++++--- src/pk_public.c | 4 +-- src/pk_value.c | 73 +++++++++++++++++++++++++++------------- src/pk_vm.c | 34 +++++++++++++++++-- 6 files changed, 179 insertions(+), 33 deletions(-) create mode 100644 scripts/leak_detect.py diff --git a/scripts/leak_detect.py b/scripts/leak_detect.py new file mode 100644 index 0000000..b33281a --- /dev/null +++ b/scripts/leak_detect.py @@ -0,0 +1,75 @@ +## +## Copyright (c) 2020-2022 Thakee Nathees +## Copyright (c) 2021-2022 Pocketlang Contributors +## Distributed Under The MIT License +## + +## A quick script to detect memory leaks, from the trace report. +## To get the trace report redefine TRACE_MEMORY as 1 at the +## pk_internal.h and compile pocketlang. + +import sys + +def detect_leak(): + trace_log_id = "[memory trace]" + + total_bytes = 0 ## Totally allocated bytes. + mem = dict() ## key = address, value = bytes. + + trace_log_file_path = sys.argv[1] + + with open(trace_log_file_path, 'r') as f: + for line in f.readlines(): + if line.startswith(trace_log_id): + line = line[len(trace_log_id) + 1:].strip() + type, data = split_and_strip(line, ':') + + addr, bytes = split_and_strip(data, '=') + bytes = bytes.replace(' bytes', '') + + if type == "malloc": + bytes = int(bytes) + assert(bytes >= 0); total_bytes += bytes + mem[addr] = bytes + + elif type == "free": + bytes = int(bytes) + assert(bytes <= 0); total_bytes += bytes + mem.pop(addr) + + elif type == "realloc": + oldp, newp = split_and_strip(addr, '->') + olds, news = split_and_strip(bytes, '->') + olds = int(olds); news = int(news) + total_bytes += (news - olds) + assert(mem[oldp] == olds) + mem.pop(oldp) + mem[newp] = news + + success = True + if total_bytes != 0: + print_err(f"Memory leak detected - {total_bytes} bytes were never freed.") + success = False + + if len(mem) != 0: + print_err("Memory leak detected - some addresses were never freed.") + for addr in mem: + print(f" {addr} : {mem[addr]} bytes") + success = False + + if success: + print("No leaks were detected.") + else: + sys.exit(1) + + +def split_and_strip(string, delim): + return map(lambda s: s.strip(), string.split(delim)) + + +def print_err(msg): + print("Error:", msg, file=sys.stderr) + + +if __name__ == "__main__": + detect_leak() diff --git a/src/pk_debug.c b/src/pk_debug.c index 83e4d75..042f508 100644 --- a/src/pk_debug.c +++ b/src/pk_debug.c @@ -179,7 +179,7 @@ static void _reportStackFrame(PKVM* vm, CallFrame* frame) { // reducing it by 1. But stack overflows are occure before executing // any instruction of that function, so the instruction_index possibly // be -1 (set it to zero in that case). - int instruction_index = frame->ip - fn->fn->opcodes.data - 1; + int instruction_index = (int) (frame->ip - fn->fn->opcodes.data) - 1; if (instruction_index == -1) instruction_index++; int line = fn->fn->oplines.data[instruction_index]; diff --git a/src/pk_internal.h b/src/pk_internal.h index 5283488..546450a 100644 --- a/src/pk_internal.h +++ b/src/pk_internal.h @@ -50,6 +50,10 @@ // Dump the stack values and the globals. #define DUMP_STACK 0 +// Trace memory allocations. Enable this and redirect the trace dump to a file +// and run the script leak_detect.py to check for memory leaks. +#define TRACE_MEMORY 0 + // Nan-Tagging could be disable for debugging/portability purposes. See "var.h" // header for more information on Nan-tagging. #define VAR_NAN_TAGGING 1 @@ -79,20 +83,30 @@ // Allocate object of [type] using the vmRealloc function. #define ALLOCATE(vm, type) \ - ((type*)vmRealloc(vm, NULL, 0, sizeof(type))) + ((type*)vmRealloc(vm, NULL, 0, sizeof(type))) // Allocate object of [type] which has a dynamic tail array of type [tail_type] // with [count] entries. #define ALLOCATE_DYNAMIC(vm, type, count, tail_type) \ - ((type*)vmRealloc(vm, NULL, 0, sizeof(type) + sizeof(tail_type) * (count))) + ((type*)vmRealloc(vm, NULL, 0, sizeof(type) + sizeof(tail_type) * (count))) // Allocate [count] amount of object of [type] array. #define ALLOCATE_ARRAY(vm, type, count) \ - ((type*)vmRealloc(vm, NULL, 0, sizeof(type) * (count))) + ((type*)vmRealloc(vm, NULL, 0, sizeof(type) * (count))) // Deallocate a pointer allocated by vmRealloc before. -#define DEALLOCATE(vm, pointer) \ - vmRealloc(vm, pointer, 0, 0) +#define DEALLOCATE(vm, pointer, type) \ + vmRealloc(vm, pointer, sizeof(type), 0) + +// Deallocate object of [type] which has a dynamic tail array of type +// [tail_type] with [count] entries. +#define DEALLOCATE_DYNAMIC(vm, pointer, type, count, tail_type) \ + ((type*)vmRealloc(vm, pointer, \ + sizeof(type) + sizeof(tail_type) * (count), 0)) + +// Deallocate [count] amount of object of [type] array. +#define DEALLOCATE_ARRAY(vm, pointer, type, count) \ + ((type*)vmRealloc(vm, pointer, sizeof(type) * (count), 0)) /*****************************************************************************/ /* REUSABLE INTERNAL MACROS */ diff --git a/src/pk_public.c b/src/pk_public.c index db57551..e1d5fa2 100644 --- a/src/pk_public.c +++ b/src/pk_public.c @@ -129,7 +129,7 @@ void pkFreeVM(PKVM* vm) { // before freeing the VM. ASSERT(vm->handles == NULL, "Not all handles were released."); - DEALLOCATE(vm, vm); + DEALLOCATE(vm, vm, PKVM); } void* pkGetUserData(const PKVM* vm) { @@ -247,7 +247,7 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) { if (handle->prev) handle->prev->next = handle->next; // Free the handle. - DEALLOCATE(vm, handle); + DEALLOCATE(vm, handle, PkHandle); } PkResult pkCompileModule(PKVM* vm, PkHandle* module_handle, PkStringPtr source, diff --git a/src/pk_value.c b/src/pk_value.c index 9089729..cd7a9bf 100644 --- a/src/pk_value.c +++ b/src/pk_value.c @@ -847,7 +847,7 @@ static void _mapResize(PKVM* vm, Map* self, uint32_t capacity) { _mapInsertEntry(self, old_entries[i].key, old_entries[i].value); } - DEALLOCATE(vm, old_entries); + DEALLOCATE_ARRAY(vm, old_entries, MapEntry, old_capacity); } Var mapGet(Map* self, Var key) { @@ -871,7 +871,7 @@ void mapSet(PKVM* vm, Map* self, Var key, Var value) { } void mapClear(PKVM* vm, Map* self) { - DEALLOCATE(vm, self->entries); + DEALLOCATE_ARRAY(vm, self->entries, MapEntry, self->capacity); self->entries = NULL; self->capacity = 0; self->count = 0; @@ -930,60 +930,87 @@ void freeObject(PKVM* vm, Object* self) { // will won't be freed here instead they haven't marked at all, and will be // removed at the sweeping phase of the garbage collection. switch (self->type) { - case OBJ_STRING: - break; + case OBJ_STRING: { + String* str = (String*) self; + DEALLOCATE_DYNAMIC(vm, str, String, str->capacity, char); + return; + }; - case OBJ_LIST: + case OBJ_LIST: { pkVarBufferClear(&(((List*)self)->elements), vm); - break; + DEALLOCATE(vm, self, List); + return; + } - case OBJ_MAP: - DEALLOCATE(vm, ((Map*)self)->entries); - break; + case OBJ_MAP: { + Map* map = (Map*)self; + DEALLOCATE_ARRAY(vm, map->entries, MapEntry, map->capacity); + DEALLOCATE(vm, self, Map); + return; + } - case OBJ_RANGE: - break; + case OBJ_RANGE: { + DEALLOCATE(vm, self, Range); + return; + } case OBJ_MODULE: { Module* module = (Module*)self; pkVarBufferClear(&module->globals, vm); pkUintBufferClear(&module->global_names, vm); pkVarBufferClear(&module->constants, vm); - } break; + DEALLOCATE(vm, self, Module); + return; + } case OBJ_FUNC: { Function* func = (Function*)self; if (!func->is_native) { pkByteBufferClear(&func->fn->opcodes, vm); pkUintBufferClear(&func->fn->oplines, vm); - DEALLOCATE(vm, func->fn); + DEALLOCATE(vm, func->fn, Fn); } - } break; + DEALLOCATE(vm, self, Function); + return; + }; - case OBJ_CLOSURE: - case OBJ_UPVALUE: - break; + case OBJ_CLOSURE: { + DEALLOCATE_DYNAMIC(vm, self, Closure, + ((Closure*)self)->fn->upvalue_count, Upvalue*); + return; + } + + case OBJ_UPVALUE: { + DEALLOCATE(vm, self, Upvalue); + return; + } case OBJ_FIBER: { Fiber* fiber = (Fiber*)self; - DEALLOCATE(vm, fiber->stack); - DEALLOCATE(vm, fiber->frames); - } break; + DEALLOCATE_ARRAY(vm, fiber->stack, Var, fiber->stack_size); + DEALLOCATE_ARRAY(vm, fiber->frames, CallFrame, fiber->frame_capacity); + DEALLOCATE(vm, fiber, Fiber); + return; + } case OBJ_CLASS: { Class* cls = (Class*)self; pkClosureBufferClear(&cls->methods, vm); - } break; + DEALLOCATE(vm, cls, Class); + return; + } case OBJ_INST: { Instance* inst = (Instance*)self; if (inst->cls->delete_fn != NULL) { inst->cls->delete_fn(inst->native); } - } break; + DEALLOCATE(vm, inst, Instance); + return; + } } - DEALLOCATE(vm, self); + UNREACHABLE(); } uint32_t moduleAddConstant(PKVM* vm, Module* module, Var value) { diff --git a/src/pk_vm.c b/src/pk_vm.c index 01c6778..b8799ea 100644 --- a/src/pk_vm.c +++ b/src/pk_vm.c @@ -22,8 +22,6 @@ PkHandle* vmNewHandle(PKVM* vm, Var value) { void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size) { - // TODO: Debug trace allocations here. - // Track the total allocated memory of the VM to trigger the GC. // if vmRealloc is called for freeing, the old_size would be 0 since // deallocated bytes are traced by garbage collector. @@ -33,7 +31,39 @@ void* vmRealloc(PKVM* vm, void* memory, size_t old_size, size_t new_size) { vmCollectGarbage(vm); } +#if TRACE_MEMORY + + void* ptr = vm->config.realloc_fn(memory, new_size, vm->config.user_data); + do { + // Deallocation of the VM itself cannot be traced. + if (memory == vm) break; + if (memory == NULL && new_size == 0) { //< Just nothing. + ASSERT(old_size == 0, OOPS); + break; + } + + if (old_size == 0 && new_size > 0) { // New allocation. + ASSERT(memory == NULL, OOPS); + printf("[memory trace] malloc : %p = %+li bytes\n", + ptr, (long) new_size); + } else if (new_size == 0) { // Free. + ASSERT(memory != NULL && old_size != 0, OOPS); + printf("[memory trace] free : %p = -%li bytes\n", + memory, (long) old_size); + } else { // Realloc. + ASSERT(old_size != 0 && new_size != 0 && memory != NULL, OOPS); + printf("[memory trace] realloc : %p -> %p = %+li -> %+li bytes\n", + memory, ptr, (long) (old_size), (long) (new_size)); + } + + } while (false); + + return ptr; + +#else return vm->config.realloc_fn(memory, new_size, vm->config.user_data); +#endif + } void vmPushTempRef(PKVM* vm, Object* obj) {