Compare commits

..

2 Commits

Author SHA1 Message Date
Kayne Ruse 53d3606c7e Found and fixed some obscure leaks in 'Toy_Scope' 2026-05-10 17:35:07 +10:00
Kayne Ruse 83fb5222a2 Found and fixed a memory leak in the rope strings
Also fixed a bit manipulation error in the GC.
2026-05-10 15:45:16 +10:00
11 changed files with 111 additions and 38 deletions
+47
View File
@@ -0,0 +1,47 @@
#include "bucket_inspector.h"
#include <toy_string.h>
#include <stdio.h>
int inspect_bucket(Toy_Bucket** bucketHandle) {
int depth = 0;
//for each bucket
for (Toy_Bucket* iter = (*bucketHandle); iter != NULL; iter = iter->next) {
int occupied = 0;
int released = 0;
unsigned char* ptr = iter->data;
while ((ptr - iter->data < iter->count) && *((int*)ptr) != 0) { //for each partition
if ( ( *((int*)ptr) & 1) == 0) { //is this partition still in use?
occupied++;
//try to print as a string if possible
Toy_String* str = (void*)(ptr + 4);
if (str->info.type == TOY_STRING_LEAF && str->info.length < 255) {
printf("String Leaf (%d bytes, %d refCount): %.*s\n", *((int*)ptr), str->info.refCount, str->info.length, str->leaf.data);
}
else if (str->info.type == TOY_STRING_NODE) {
printf("String Node (%d bytes, %d refCount): ...\n", *((int*)ptr), str->info.refCount);
}
}
else {
released++;
}
//jump distance: ((*((int*)ptr) | 1) ^ 1) + 4
// printf(" jump %d, ", ((*((int*)ptr) | 1) ^ 1) + 4);
ptr += ((*((int*)ptr) | 1) ^ 1) + 4; //OR + XOR to remove the 'free' flag from the size
}
printf("Bucket link %d: count %u, %d occupied, %d released\n", depth, iter->count, occupied, released);
depth++;
}
printf("\n");
return depth;
}
+5
View File
@@ -0,0 +1,5 @@
#pragma once
#include "toy_bucket.h"
int inspect_bucket(Toy_Bucket** bucketHandle);
+17 -4
View File
@@ -1,5 +1,6 @@
#include "ast_inspector.h" #include "ast_inspector.h"
#include "bytecode_inspector.h" #include "bytecode_inspector.h"
#include "bucket_inspector.h"
#include "toy_console_colors.h" #include "toy_console_colors.h"
@@ -279,14 +280,14 @@ static void debugScopePrint(Toy_Scope* scope, int depth) {
printf("\n" TOY_CC_NOTICE "Scope Dump [%d]" TOY_CC_RESET "\n" TOY_CC_NOTICE "%-20s%-20s%-20s" TOY_CC_RESET "\n", depth, "type", "name", "value"); printf("\n" TOY_CC_NOTICE "Scope Dump [%d]" TOY_CC_RESET "\n" TOY_CC_NOTICE "%-20s%-20s%-20s" TOY_CC_RESET "\n", depth, "type", "name", "value");
for (unsigned int i = 0; i < scope->capacity; i++) { for (unsigned int i = 0; i < scope->capacity; i++) {
if (scope->data[i].key.info.length == 0) { if (scope->data[i].key == NULL || scope->data[i].key->info.length == 0) {
continue; continue;
} }
Toy_String k = scope->data[i].key; Toy_String* k = scope->data[i].key;
Toy_Value v = scope->data[i].value; Toy_Value v = scope->data[i].value;
printf("%-10s%-10s%-20s", Toy_getValueTypeAsCString(scope->data[i].type), scope->data[i].constant ? "const" : "", k.leaf.data); printf("%-10s%-10s%-20s", Toy_getValueTypeAsCString(scope->data[i].type), scope->data[i].constant ? "const" : "", k != NULL ? k->leaf.data : "");
//print value //print value
Toy_String* string = Toy_stringifyValue(&stringBucket, Toy_unwrapValue(v)); Toy_String* string = Toy_stringifyValue(&stringBucket, Toy_unwrapValue(v));
@@ -320,7 +321,7 @@ int repl(const char* filepath, bool verbose) {
char inputBuffer[INPUT_BUFFER_SIZE]; char inputBuffer[INPUT_BUFFER_SIZE];
memset(inputBuffer, 0, INPUT_BUFFER_SIZE); memset(inputBuffer, 0, INPUT_BUFFER_SIZE);
Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); Toy_Bucket* bucket = Toy_allocateBucket(TOY_BUCKET_IDEAL); //TODO: gc this
Toy_VM vm; Toy_VM vm;
Toy_initVM(&vm); Toy_initVM(&vm);
@@ -380,14 +381,26 @@ int repl(const char* filepath, bool verbose) {
//run //run
Toy_runVM(&vm); Toy_runVM(&vm);
int depthBeforeGC = 0;
int depthAfterGC = 0;
//print the debug info //print the debug info
if (verbose) { if (verbose) {
debugStackPrint(vm.stack); debugStackPrint(vm.stack);
debugScopePrint(vm.scope, 0); debugScopePrint(vm.scope, 0);
depthBeforeGC = inspect_bucket(&vm.memoryBucket);
} }
//free the memory, and leave the VM ready for the next loop //free the memory, and leave the VM ready for the next loop
Toy_resetVM(&vm, true, true); Toy_resetVM(&vm, true, true);
if (verbose) {
depthAfterGC = inspect_bucket(&vm.memoryBucket);
printf("GC Report: %d -> %d\n", depthBeforeGC, depthAfterGC);
}
free(bytecode); free(bytecode);
printf("%s> ", prompt); //shows the terminal prompt printf("%s> ", prompt); //shows the terminal prompt
+2 -6
View File
@@ -56,13 +56,9 @@ void initStandardLibrary(Toy_VM* vm) {
//declare each pair //declare each pair
for (int i = 0; callbackPairs[i].name; i++) { for (int i = 0; callbackPairs[i].name; i++) {
//cheat Toy_String* key = Toy_createStringLength(&vm->memoryBucket, callbackPairs[i].name, strlen(callbackPairs[i].name));
Toy_String key = (Toy_String){
.leaf = { ._padding = { .type = TOY_STRING_LEAF, .length = strlen(callbackPairs[i].name), .refCount = 1, .cachedHash = 0 }, .data = callbackPairs[i].name }
};
Toy_Function* fn = Toy_createFunctionFromCallback(&(vm->memoryBucket), callbackPairs[i].callback); Toy_Function* fn = Toy_createFunctionFromCallback(&(vm->memoryBucket), callbackPairs[i].callback);
Toy_declareScope(vm->scope, &key, TOY_VALUE_FUNCTION, TOY_VALUE_FROM_FUNCTION(fn), true); Toy_declareScope(vm->scope, key, TOY_VALUE_FUNCTION, TOY_VALUE_FROM_FUNCTION(fn), true);
} }
} }
+7 -1
View File
@@ -1,5 +1,5 @@
/*
fn swap(a, b) { fn swap(a, b) {
return b, a; return b, a;
} }
@@ -11,3 +11,9 @@ var d;
//BUG: still causes a segfault //BUG: still causes a segfault
c, d = swap(a, b); c, d = swap(a, b);
*/
{ var str = "Hello"; var i = 0; while (i < 100) { str = str .. " World"; i++; } }
+1 -1
View File
@@ -88,7 +88,7 @@ TOY_API void Toy_collectBucketGarbage(Toy_Bucket** bucketHandle) {
gc = false; gc = false;
break; break;
} }
ptr += (*((int*)(ptr)) ^ 1) + 4; //XOR to remove the 'free' flag from the size ptr += ((*((int*)ptr) | 1) ^ 1) + 4; //OR + XOR to remove the 'free' flag from the size
} }
//free this link, if its been entirely released //free this link, if its been entirely released
+18 -21
View File
@@ -19,12 +19,12 @@ static Toy_ScopeEntry* lookupScopeEntryPtr(Toy_Scope* scope, Toy_String* key, un
while (true) { while (true) {
//found the entry //found the entry
if (Toy_compareStrings(&(scope->data[probe].key), key) == 0) { if (scope->data[probe].key != NULL && Toy_compareStrings(scope->data[probe].key, key) == 0) {
return &(scope->data[probe]); return &(scope->data[probe]);
} }
//if its an empty slot (didn't find it here) //if its an empty slot (didn't find it here)
if (scope->data[probe].key.info.length == 0) { if (scope->data[probe].key == NULL) {
return recursive ? lookupScopeEntryPtr(scope->next, key, hash, recursive) : NULL; return recursive ? lookupScopeEntryPtr(scope->next, key, hash, recursive) : NULL;
} }
@@ -36,12 +36,12 @@ static Toy_ScopeEntry* lookupScopeEntryPtr(Toy_Scope* scope, Toy_String* key, un
static void probeAndInsert(Toy_Scope* scope, Toy_String* key, Toy_Value value, Toy_ValueType type, bool constant) { static void probeAndInsert(Toy_Scope* scope, Toy_String* key, Toy_Value value, Toy_ValueType type, bool constant) {
//make the entry //make the entry
unsigned int probe = Toy_hashString(key) % scope->capacity; unsigned int probe = Toy_hashString(key) % scope->capacity;
Toy_ScopeEntry entry = (Toy_ScopeEntry){ .key = *key, .value = value, .type = type, .constant = constant, .psl = 1 }; Toy_ScopeEntry entry = (Toy_ScopeEntry){ .key = key, .value = value, .type = type, .constant = constant, .psl = 1 };
//probe //probe
while (true) { while (true) {
//if we're overriding an existing value //if we're overriding an existing value
if (Toy_compareStrings(&(scope->data[probe].key), &(entry.key)) == 0) { if (scope->data[probe].key != NULL && Toy_compareStrings(scope->data[probe].key, entry.key) == 0) {
scope->data[probe] = entry; scope->data[probe] = entry;
scope->maxPsl = entry.psl > scope->maxPsl ? entry.psl : scope->maxPsl; scope->maxPsl = entry.psl > scope->maxPsl ? entry.psl : scope->maxPsl;
return; return;
@@ -94,8 +94,8 @@ static Toy_ScopeEntry* adjustScopeEntries(Toy_Scope* scope, unsigned int newCapa
//for each existing entry in the old array, copy it into the new array //for each existing entry in the old array, copy it into the new array
for (unsigned int i = 0; i < oldCapacity; i++) { for (unsigned int i = 0; i < oldCapacity; i++) {
if (oldEntries[i].key.info.length > 0) { if (oldEntries[i].key != NULL && oldEntries[i].key->info.length > 0) {
probeAndInsert(scope, &(oldEntries[i].key), oldEntries[i].value, oldEntries[i].type, oldEntries[i].constant); probeAndInsert(scope, oldEntries[i].key, oldEntries[i].value, oldEntries[i].type, oldEntries[i].constant);
} }
} }
@@ -134,7 +134,10 @@ Toy_Scope* Toy_popScope(Toy_Scope* scope) {
} }
Toy_private_decrementScopeRefCount(scope); Toy_private_decrementScopeRefCount(scope);
return scope->next;
Toy_Scope* next = scope->next;
Toy_releaseBucketPartition((void*)scope);
return next;
} }
void Toy_declareScope(Toy_Scope* scope, Toy_String* key, Toy_ValueType type, Toy_Value value, bool constant) { void Toy_declareScope(Toy_Scope* scope, Toy_String* key, Toy_ValueType type, Toy_Value value, bool constant) {
@@ -193,6 +196,8 @@ void Toy_assignScope(Toy_Scope* scope, Toy_String* key, Toy_Value value) {
return; return;
} }
Toy_freeValue(entryPtr->value);
entryPtr->value = value; entryPtr->value = value;
} }
@@ -227,29 +232,21 @@ void Toy_private_incrementScopeRefCount(Toy_Scope* scope) {
} }
void Toy_private_decrementScopeRefCount(Toy_Scope* scope) { void Toy_private_decrementScopeRefCount(Toy_Scope* scope) {
Toy_Scope* iter = scope; for (Toy_Scope* iter = scope; iter != NULL; iter = iter->next) {
while (iter) {
iter->refCount--; iter->refCount--;
//clean up our insides if needed
if (iter->refCount == 0) { if (iter->refCount == 0) {
//free the scope entries when this scope is no longer needed //free the data
if (iter->data != NULL) { if (iter->data != NULL) {
for (unsigned int i = 0; i < iter->capacity; i++) { for (unsigned int i = 0; i < iter->capacity; i++) {
if (iter->data[i].psl > 0) { if (iter->data[i].key != NULL) {
Toy_freeString(&(iter->data[i].key)); Toy_freeString(iter->data[i].key);
Toy_freeValue(iter->data[i].value); Toy_freeValue(iter->data[i].value);
} }
} }
free(iter->data); free(iter->data);
} }
//free the scope itself, fixing the iterator for the next loop
Toy_Scope* empty = iter;
iter = iter->next;
Toy_releaseBucketPartition((void*)empty);
}
else {
iter = iter->next;
} }
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@
//keys are leaf-only strings //keys are leaf-only strings
typedef struct Toy_ScopeEntry { typedef struct Toy_ScopeEntry {
Toy_String key; Toy_String* key;
Toy_Value value; Toy_Value value;
Toy_ValueType type; Toy_ValueType type;
unsigned int psl; //psl '0' means empty unsigned int psl; //psl '0' means empty
+8
View File
@@ -20,10 +20,18 @@ static void incrementRefCount(Toy_String* str) {
static void decrementRefCount(Toy_String* str) { static void decrementRefCount(Toy_String* str) {
str->info.refCount--; str->info.refCount--;
if (str->info.type == TOY_STRING_NODE) { if (str->info.type == TOY_STRING_NODE) {
//the parent of this node triggered a decrement across the whole tree
decrementRefCount(str->node.left); decrementRefCount(str->node.left);
decrementRefCount(str->node.right); decrementRefCount(str->node.right);
} }
if (str->info.refCount == 0) { if (str->info.refCount == 0) {
if (str->info.type == TOY_STRING_NODE) {
//THIS node has triggered the decrement, so run this again
decrementRefCount(str->node.left);
decrementRefCount(str->node.right);
}
//mark this memory as unused
Toy_releaseBucketPartition((void*)str); Toy_releaseBucketPartition((void*)str);
} }
} }
+2 -1
View File
@@ -210,7 +210,7 @@ static void processAssign(Toy_VM* vm) {
Toy_Value name = Toy_popStack(&vm->stack); Toy_Value name = Toy_popStack(&vm->stack);
//assign it //assign it
Toy_assignScope(vm->scope, TOY_VALUE_AS_STRING(name), Toy_copyValue(&vm->memoryBucket, value)); //scope now owns the value, doesn't need to be freed Toy_assignScope(vm->scope, TOY_VALUE_AS_STRING(name), Toy_copyValue(&vm->memoryBucket, value));
//in case of chaining, leave a copy on the stack //in case of chaining, leave a copy on the stack
bool chainedAssignment = READ_BYTE(vm); bool chainedAssignment = READ_BYTE(vm);
@@ -220,6 +220,7 @@ static void processAssign(Toy_VM* vm) {
//cleanup //cleanup
Toy_freeValue(name); Toy_freeValue(name);
Toy_freeValue(value);
} }
static void processAssignCompound(Toy_VM* vm) { static void processAssignCompound(Toy_VM* vm) {
+2 -2
View File
@@ -192,8 +192,8 @@ int test_string_concatenation(void) {
free(buffer); free(buffer);
Toy_freeString(result); Toy_freeString(result);
Toy_freeString(first); // Toy_freeString(first); //these do NOT need to be freed manually
Toy_freeString(second); // Toy_freeString(second);
Toy_freeBucket(&bucket); Toy_freeBucket(&bucket);
} }