mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-06 04:37:47 +08:00
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
This commit is contained in:
parent
1d77ec7adf
commit
1c7ddf07ad
75
scripts/leak_detect.py
Normal file
75
scripts/leak_detect.py
Normal file
@ -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()
|
@ -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];
|
||||
|
@ -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 */
|
||||
|
@ -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,
|
||||
|
@ -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) {
|
||||
|
34
src/pk_vm.c
34
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) {
|
||||
|
Loading…
Reference in New Issue
Block a user