mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-10 22:50:58 +08:00
garbage collection implementations.
This commit is contained in:
parent
c601e5cf90
commit
34716b4f6b
@ -40,6 +40,18 @@ typedef struct MSVM MSVM;
|
||||
typedef struct Var Var;
|
||||
#endif
|
||||
|
||||
// A function that'll be called for all the allocation calls by MSVM.
|
||||
//
|
||||
// - To allocate new memory it'll pass NULL to parameter [memory] and the
|
||||
// required size to [new_size]. On failure the return value would be NULL.
|
||||
//
|
||||
// - When reallocating an existing memory if it's grow in place the return
|
||||
// address would be the same as [memory] otherwise a new address.
|
||||
//
|
||||
// - To free an allocated memory pass [memory] and 0 to [new_size]. The
|
||||
// function will return NULL.
|
||||
typedef void* (*MiniScriptReallocFn)(void* memory, size_t new_size, void* user_data);
|
||||
|
||||
// C function pointer which is callable from MiniScript.
|
||||
typedef void (*MiniScriptNativeFn)(MSVM* vm);
|
||||
|
||||
@ -84,6 +96,10 @@ typedef void (*MiniScriptLoadScriptDoneFn) (MSVM* vm, const char* path,
|
||||
|
||||
typedef struct {
|
||||
|
||||
// The callback used to allocate, reallocate, and free. If the function
|
||||
// pointer is NULL it defaults to the VM's realloc(), free() wrappers.
|
||||
MiniScriptReallocFn realloc_fn;
|
||||
|
||||
MiniScriptErrorFn error_fn;
|
||||
MiniScriptWriteFn write_fn;
|
||||
|
||||
@ -95,6 +111,10 @@ typedef struct {
|
||||
|
||||
} MSConfiguration;
|
||||
|
||||
// Initialize the configuration and set ALL of it's values to the defaults.
|
||||
// Call this before setting any particular field of it.
|
||||
void MSInitConfiguration(MSConfiguration* config);
|
||||
|
||||
typedef enum {
|
||||
RESULT_SUCCESS = 0,
|
||||
RESULT_COMPILE_ERROR,
|
||||
|
@ -40,6 +40,7 @@ def generate_files():
|
||||
import buffergen
|
||||
ec = buffergen.gen()
|
||||
|
||||
## .bat files are just for quick rebuild of the buffer templates in windows.
|
||||
if sys.platform == 'win32':
|
||||
with open('src/types/gen.bat', 'w') as f:
|
||||
f.write('python buffergen.py')
|
||||
|
@ -268,7 +268,6 @@ void initializeCore(MSVM* vm) {
|
||||
STD_NEW_SCRIPT("std:list");
|
||||
STD_ADD_FUNCTION("sort", stdListSort, 1);
|
||||
|
||||
|
||||
// std:os script.
|
||||
STD_NEW_SCRIPT("std:os");
|
||||
STD_ADD_FUNCTION("clock", stdOsClock, 0);
|
||||
@ -408,7 +407,7 @@ Var varGetAttrib(MSVM* vm, Var on, String* attrib) {
|
||||
int index = nameTableFind(&scr->function_names, attrib->data,
|
||||
attrib->length);
|
||||
if (index != -1) {
|
||||
// TODO: Assert index (not a runtime error).
|
||||
ASSERT_INDEX(index, scr->functions.count);
|
||||
return VAR_OBJ(scr->functions.data[index]);
|
||||
}
|
||||
|
||||
@ -492,7 +491,7 @@ do { \
|
||||
return;
|
||||
|
||||
case OBJ_USER:
|
||||
ERR_NO_ATTRIB();
|
||||
TODO; //ERR_NO_ATTRIB();
|
||||
return;
|
||||
|
||||
default:
|
||||
|
@ -9,6 +9,14 @@
|
||||
#include "var.h"
|
||||
#include "common.h"
|
||||
|
||||
// Initialize core language, builtin function and "std" scripts.
|
||||
// Note (TODO: refactore required):
|
||||
// Since the builtin function doesn't require any allocation they're
|
||||
// elements of a static `builtins` array but the "std" scripts are `Script`
|
||||
// objects they required memory management and they're bound with the VM.
|
||||
// It contradicts `initializeCore()` to be called for each VM or only once.
|
||||
// 1. Make the scripts share between VMs.
|
||||
// 2. Destroy scripts buffer only when the last VM die.
|
||||
void initializeCore(MSVM* vm);
|
||||
|
||||
// Find the builtin function name and returns it's index in the builtins array
|
||||
|
@ -17,6 +17,8 @@ void nameTableClear(NameTable* self, MSVM* vm) {
|
||||
|
||||
int nameTableAdd(NameTable* self, MSVM* vm, const char* name, size_t length,
|
||||
String** ptr) {
|
||||
// Note: Since stringBuffer won't copy the string we don't have to free the
|
||||
// below string, it'll be managed by the string buffer.
|
||||
String* string = newString(vm, name, (uint32_t)length);
|
||||
|
||||
vmPushTempRef(vm, &string->_super);
|
||||
|
50
src/vm.c
50
src/vm.c
@ -17,6 +17,14 @@
|
||||
// Minimum size of the stack.
|
||||
#define MIN_STACK_SIZE 128
|
||||
|
||||
static void* defaultRealloc(void* memory, size_t new_size, void* user_data) {
|
||||
if (new_size == 0) {
|
||||
free(memory);
|
||||
return NULL;
|
||||
}
|
||||
return realloc(memory, new_size);
|
||||
}
|
||||
|
||||
Fiber* newFiber(MSVM* vm) {
|
||||
Fiber* fiber = ALLOCATE(vm, Fiber);
|
||||
memset(fiber, 0, sizeof(Fiber));
|
||||
@ -26,33 +34,43 @@ Fiber* newFiber(MSVM* vm) {
|
||||
|
||||
void* vmRealloc(MSVM* self, 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.
|
||||
self->bytes_allocated += new_size - old_size;
|
||||
|
||||
// TODO: If vm->bytes_allocated > some_value -> GC();
|
||||
if (new_size > 0 && self->bytes_allocated > self->next_gc) {
|
||||
vmCollectGarbage(self);
|
||||
}
|
||||
|
||||
if (new_size == 0) {
|
||||
free(memory);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return realloc(memory, new_size);
|
||||
return self->config.realloc_fn(memory, new_size, self->config.user_data);
|
||||
}
|
||||
|
||||
void vmInit(MSVM* self, MSConfiguration* config) {
|
||||
memset(self, 0, sizeof(MSVM));
|
||||
self->config = *config;
|
||||
|
||||
self->gray_list_count = 0;
|
||||
self->gray_list_capacity = 8; // TODO: refactor the magic '8' here.
|
||||
self->gray_list = (Object**)self->config.realloc_fn(
|
||||
NULL, sizeof(Object*) * self->gray_list_capacity, NULL);
|
||||
self->next_gc = 1024 * 1024 * 10; // TODO:
|
||||
|
||||
// TODO: no need to initialize if already done by another vm.
|
||||
initializeCore(self);
|
||||
}
|
||||
|
||||
void vmPushTempRef(MSVM* self, Object* obj) {
|
||||
ASSERT(obj != NULL, "Cannot reference to NULL.");
|
||||
ASSERT(self->temp_reference_count < MAX_TEMP_REFERENCE,
|
||||
"Too many temp references");
|
||||
ASSERT(self->temp_reference_count < MAX_TEMP_REFERENCE,
|
||||
"Too many temp references");
|
||||
self->temp_reference[self->temp_reference_count++] = obj;
|
||||
}
|
||||
|
||||
@ -61,6 +79,15 @@ void vmPopTempRef(MSVM* self) {
|
||||
self->temp_reference_count--;
|
||||
}
|
||||
|
||||
void vmCollectGarbage(MSVM* self) {
|
||||
|
||||
// Reset VM's bytes_allocated value and count it again so that we don't
|
||||
// required to know the size of each object that'll be freeing.
|
||||
self->bytes_allocated = 0;
|
||||
|
||||
TODO;
|
||||
}
|
||||
|
||||
void vmAddStdScript(MSVM* self, Script* script) {
|
||||
ASSERT(self->std_count < MAX_SCRIPT_CACHE, OOPS);
|
||||
self->std_scripts[self->std_count++] = script;
|
||||
@ -127,6 +154,18 @@ void vmReportError(MSVM* vm) {
|
||||
ASSERT(false, "TODO: create debug.h");
|
||||
}
|
||||
|
||||
void MSInitConfiguration(MSConfiguration* config) {
|
||||
config->realloc_fn = defaultRealloc;
|
||||
|
||||
// TODO: Handle Null functions before calling them.
|
||||
config->error_fn = NULL;
|
||||
config->write_fn = NULL;
|
||||
|
||||
config->load_script_fn = NULL;
|
||||
config->load_script_done_fn = NULL;
|
||||
config->user_data = NULL;
|
||||
}
|
||||
|
||||
MSVM* msNewVM(MSConfiguration* config) {
|
||||
MSVM* vm = (MSVM*)malloc(sizeof(MSVM));
|
||||
vmInit(vm, config);
|
||||
@ -137,7 +176,8 @@ MSInterpretResult msInterpret(MSVM* vm, const char* file) {
|
||||
Script* script = compileSource(vm, file);
|
||||
if (script == NULL) return RESULT_COMPILE_ERROR;
|
||||
|
||||
// TODO: Check if scripts size is enough.
|
||||
// TODO: The below assertion should be an error report.
|
||||
ASSERT(vm->script_count + 1 < MAX_SCRIPT_CACHE, "Scripts cache out of bound.");
|
||||
vm->scripts[vm->script_count++] = script;
|
||||
return vmRunScript(vm, script);
|
||||
}
|
||||
|
21
src/vm.h
21
src/vm.h
@ -36,15 +36,15 @@ struct Fiber {
|
||||
// body function).
|
||||
Function* func;
|
||||
|
||||
// The stack of the execution holding locals and temps. A heap allocated
|
||||
// Will and grow as needed.
|
||||
// The stack of the execution holding locals and temps. A heap will be
|
||||
// allocated and grow as needed.
|
||||
Var* stack;
|
||||
|
||||
// The stack pointer (%rsp) pointing to the stack top.
|
||||
Var* sp;
|
||||
|
||||
// The stack base pointer of the current frame. It'll be updated before
|
||||
// calling a native function.
|
||||
// calling a native function. (`fiber->ret` === `curr_call_frame->rbp`).
|
||||
Var* ret;
|
||||
|
||||
// Size of the allocated stack.
|
||||
@ -68,8 +68,18 @@ struct MSVM {
|
||||
// The first object in the link list of all heap allocated objects.
|
||||
Object* first;
|
||||
|
||||
// The number of bytes allocated by the vm and not (yet) garbage collected.
|
||||
size_t bytes_allocated;
|
||||
|
||||
// The number of bytes that'll trigger the next GC.
|
||||
size_t next_gc;
|
||||
|
||||
// In the tri coloring scheme gray is the working list. We recursively pop
|
||||
// from the list color it balck and add it's referenced objects to gray_list.
|
||||
Object** gray_list;
|
||||
int gray_list_count;
|
||||
int gray_list_capacity;
|
||||
|
||||
// A stack of temporary object references to ensure that the object
|
||||
// doesn't garbage collected.
|
||||
Object* temp_reference[MAX_TEMP_REFERENCE];
|
||||
@ -81,7 +91,7 @@ struct MSVM {
|
||||
// Current compiler reference to mark it's heap allocated objects.
|
||||
Compiler* compiler;
|
||||
|
||||
// Std scripts array.
|
||||
// Std scripts array. (TODO: assert "std" scripts doesn't have global vars).
|
||||
Script* std_scripts[MAX_SCRIPT_CACHE];
|
||||
|
||||
// Std scripts count.
|
||||
@ -122,6 +132,9 @@ void vmPushTempRef(MSVM* self, Object* obj);
|
||||
// Pop the top most object from temporary reference stack.
|
||||
void vmPopTempRef(MSVM* self);
|
||||
|
||||
// Trigger garbage collection manually.
|
||||
void vmCollectGarbage(MSVM* self);
|
||||
|
||||
// Add a std script to vm when initializing core.
|
||||
void vmAddStdScript(MSVM* self, Script* script);
|
||||
|
||||
|
@ -18,6 +18,8 @@ void writeFunction(MSVM* vm, const char* text) {
|
||||
}
|
||||
|
||||
void loadScriptDone(MSVM* vm, const char* path, void* user_data) {
|
||||
// User data is the allocated source code buffer and it has to be freed
|
||||
// manually since it wasn't allocated by the VM.
|
||||
free(user_data);
|
||||
}
|
||||
|
||||
@ -66,6 +68,7 @@ int main(int argc, char** argv) {
|
||||
const char* source_path = argv[1];
|
||||
|
||||
MSConfiguration config;
|
||||
MSInitConfiguration(&config);
|
||||
config.error_fn = errorPrint;
|
||||
config.write_fn = writeFunction;
|
||||
config.load_script_fn = loadScript;
|
||||
|
Loading…
Reference in New Issue
Block a user