Garbage Collection 26

This commit is contained in:
nazrin 2025-06-12 06:07:25 +00:00
parent dc4e6d33b2
commit 6d5dff6e3d
10 changed files with 444 additions and 211 deletions

14
dub.sdl
View file

@ -9,18 +9,22 @@ sourcePaths
configuration "clox" {
buildOptions "betterC"
sourcePaths "src/clox"
buildRequirements "requireContracts"
}
configuration "clox-dbg" {
buildRequirements "requireBoundsCheck" "requireContracts"
debugVersions "memTrace"
debugVersions "printCode"
debugVersions "traceExec"
dependency "colored" version="~>0.0.33"
dflags "-checkaction=C"
libs "libbacktrace"
sourcePaths "src/clox"
buildRequirements "requireBoundsCheck" "requireContracts"
debugVersions "printCode"
debugVersions "traceExec"
/* debugVersions "stressGC" */
/* debugVersions "disableGC" */
debugVersions "memTrace"
/* debugVersions "logGC" "memTrace" */
}
configuration "jlox" {

View file

@ -2,13 +2,14 @@ module clox.compiler;
import core.stdc.stdio;
import clox.scanner;
import clox.parser;
import clox.chunk;
import clox.dbg;
import clox.vm;
import clox.gc;
import clox.obj;
import clox.parser;
import clox.scanner;
import clox.value;
import clox.vm;
import clox.container.dynarray;
import clox.container.varint;
@ -91,4 +92,11 @@ struct Compiler{
}
}
void markCompilerRoots(){
Compiler* compiler = currentCompiler;
while(compiler !is null){
mark(cast(Obj*)compiler.func);
compiler = compiler.enclosing;
}
}

View file

@ -1,13 +1,20 @@
module clox.container.dynarray;
public import std.typecons : Flag, Yes, No;
import clox.memory;
struct DynArray(T){
private size_t _count;
private size_t _capacity;
size_t count() => _count;
size_t count(size_t c) => _count = c;
size_t capacity() => _capacity;
// TODO capacity setter
size_t capacity(size_t newCapacity){
ptr = GROW_ARRAY!T(ptr, _capacity, newCapacity);
_capacity = newCapacity;
return newCapacity;
}
T* ptr;
void initialise(){
_count = 0;
@ -15,11 +22,8 @@ struct DynArray(T){
ptr = null;
}
void opOpAssign(string op: "~")(T value){
if(capacity < count + 1){
size_t oldCapacity = capacity;
_capacity = GROW_CAPACITY(oldCapacity);
ptr = GROW_ARRAY!T(ptr, oldCapacity, capacity);
}
if(capacity < count + 1)
capacity = GROW_CAPACITY(_capacity);
ptr[count] = value;
_count++;
}
@ -39,8 +43,8 @@ struct DynArray(T){
return count;
}
auto pop(){
assert(count);
return this[--_count];
assert(count > 0);
return ptr[--_count];
}
void reset(){
_count = 0;

View file

@ -1,166 +1,118 @@
module clox.container.table;
import core.stdc.stdlib : calloc, cfree = free;
import core.stdc.string;
import core.stdc.stdlib;
import std.algorithm : min, max;
import std.traits : isArray;
import clox.memory;
import clox.memorydbg;
import clox.obj;
import clox.value;
import clox.memory;
import clox.container.dynarray;
struct StringStore{
import clox.container.dynarray;
DynArray!(Obj.String*) items;
void initialise(){
items.initialise();
struct Table(K, V){
alias Hash = uint;
enum State{ Empty, Alive, Dead }
struct Entry{
K key;
V val;
State state = State.Alive;
bool isAlive() const => state == State.Alive;
bool isFree() const => state != State.Alive;
void remove(){
state = State.Dead;
}
}
Obj.String* find(const char[] chars, uint hash){
foreach(Obj.String* item; items){
if(item.chars == chars)
return item;
Entry[] pool;
size_t alive;
this(size_t size){
pool = allocatePool(size);
}
void free(){
freePool(pool);
}
private Entry[] allocatePool(size_t size){
Entry[] p = (cast(Entry*)calloc(size, Entry.sizeof))[0 .. size];
debug(memTrace)
registerAlloc(p.ptr, size * Entry.sizeof);
return p;
}
private void freePool(Entry[] p){
debug(memTrace)
registerFree(p.ptr);
cfree(cast(void*)p.ptr);
}
private void checkResize(){
if(alive+1 < pool.length/2)
return;
size_t newSize = pool.length * 2;
auto newTable = Table!(K, V)(newSize);
foreach(Entry entry; pool){
if(entry.isAlive)
newTable[entry.key] = entry.val;
}
freePool(pool);
this = newTable;
}
private Hash getHash(in K key){
static if(isArray!K)
return hashArray(key);
else
return key.hash;
}
private Entry* find(in K key){
if(alive == 0)
return null;
Hash hash = getHash(key);
size_t index = hash % pool.length;
while(true){
const entry = pool[index];
if(entry.isAlive && entry.key == key)
return &pool[index];
if(entry.isFree)
break;
index = (index+1) % pool.length;
}
return null;
}
void opOpAssign(string op: "~")(Obj.String* str){
items ~= str;
}
void free(){
items.free();
}
}
enum TABLE_MAX_LOAD = 0.75;
struct Table{
size_t count, capacity;
Entry* entries;
struct Entry{
Obj.String* key;
Value value;
}
void initialise(){
count = capacity = 0;
entries = null;
}
void free(){
if(entries !is null)
FREE_ARRAY!Entry(entries, capacity);
initialise();
}
private void adjustCapacity(size_t cap){
Entry* ent = ALLOCATE!Entry(cap);
for(int i = 0; i < cap; i++){
ent[i].key = null;
ent[i].value = Value.init;
}
count = 0;
for(int i = 0; i < capacity; i++){
Entry* entry = &entries[i];
if(entry.key is null)
continue;
Entry* dest = findEntry(entries, capacity, entry.key);
dest.key = entry.key;
dest.value = entry.value;
count++;
}
if(entries !is null)
FREE_ARRAY!Entry(entries, capacity);
entries = ent;
capacity = cap;
}
private Entry* findEntry(Entry* entries, size_t capacity, in Obj.String* key){
uint index = key.hash % capacity;
Entry* tombstone = null;
void opIndexAssign(V val, in K key){
checkResize();
Hash hash = getHash(key);
size_t index = hash % pool.length;
while(true){
Entry* entry = &entries[index];
if(entry.key is null){
if(entry.value.isType(Value.Type.None)){
return tombstone !is null ? tombstone : entry;
} else {
if(tombstone is null)
tombstone = entry;
}
} else if(entry.key is key){
return entry;
}
index = (index + 1) % capacity;
const entry = pool[index];
if(entry.isAlive && entry.key == key)
break;
if(entry.isFree)
break;
index = (index+1) % pool.length;
}
alive++;
pool[index] = Entry(cast(K)key, val);
}
bool set(Obj.String* key, Value value){
if(count + 1 > capacity * TABLE_MAX_LOAD){
size_t cap = GROW_CAPACITY(capacity);
adjustCapacity(cap);
}
Entry* entry = findEntry(entries, capacity, key);
bool isNewKey = entry.key == null;
if(isNewKey && entry.value.isType(Value.Type.None))
count++;
entry.key = key;
entry.value = value;
return isNewKey;
V* opIndex(in K key){
Entry* entry = find(key);
if(entry)
return &entry.val;
return null;
}
bool get(in Obj.String* key, out Value value){
if(count == 0)
return false;
Entry* entry = findEntry(entries, capacity, key);
if(entry.key is null)
return false;
value = entry.value;
return true;
}
bool del(in Obj.String* key){
if(count == 0)
return false;
Entry* entry = findEntry(entries, capacity, key);
if(entry.key is null)
return false;
entry.key = null;
entry.value = Value.from(true);
return true;
}
void addAll(Table* from){
for(int i = 0; i < from.capacity; i++){
Entry* entry = &from.entries[i];
if(entry.key !is null)
set(entry.key, entry.value);
void remove(in K key){
Entry* entry = find(key);
if(entry){
entry.remove();
alive--;
}
}
}
unittest{
import clox.vm : vm;
Table tbl;
tbl.initialise();
scope(exit){
tbl.free();
vm.free();
void removeWhite(Table!(char[], Obj.String*) table){
foreach(ref entry; table.pool){
if(entry.state == table.State.Alive && !entry.val.obj.isMarked){
entry.remove();
table.alive--;
}
}
assert(tbl.count == 0);
Obj.String* str = Obj.String.copy("hello");
tbl.set(str, Value.from(true));
assert(tbl.count == 1);
Value val;
assert(tbl.get(str, val));
assert(val.asBoolean == true);
assert(tbl.del(str));
assert(tbl.get(str, val) == false);
assert(val.isType(Value.Type.None));
assert(tbl.count == 1);
foreach(i; 0..25){
Obj.String* str2 = Obj.String.copy("hi 2");
tbl.set(str2, Value.from(i));
assert(tbl.get(str2, val));
assert(val.asNumber == i);
}
assert(tbl.get(str, val) == false);
tbl.set(str, Value.from(true));
assert(tbl.get(str, val));
assert(val.asBoolean == true);
}
uint jenkinsOneAtATimeHash(T)(in T[] key){
@ -175,6 +127,64 @@ uint jenkinsOneAtATimeHash(T)(in T[] key){
hash += hash << 15;
return hash;
}
alias hashArray = jenkinsOneAtATimeHash;
alias hashString = jenkinsOneAtATimeHash;
unittest{
auto tbl = Table!(string, ulong)(2);
scope(exit)
tbl.free();
assert(tbl.pool.length == 2);
tbl.remove("l");
foreach(i; 0 .. 2){
assert(tbl.alive == 0);
tbl["hello"] = i;
tbl.remove("l");
assert(tbl.alive == 1);
assert(*tbl["hello"] == i);
tbl.remove("hello");
assert(tbl.alive == 0);
assert(tbl["hello"] == null);
}
tbl.remove("l");
tbl["l"] = 3;
tbl["m"] = 2;
tbl["s"] = 1;
assert(*tbl["l"] == 3);
assert(*tbl["m"] == 2);
assert(*tbl["s"] == 1);
tbl["m"] = 20;
tbl["l"] = 30;
tbl["s"] = 10;
assert(*tbl["m"] == 20);
assert(*tbl["l"] == 30);
assert(*tbl["s"] == 10);
import std.file, std.algorithm, std.array, std.range;
auto words = readText("/usr/share/dict/words").splitter("\n").take(1000);
foreach(w, word; words.enumerate){
tbl[word] = w;
if(*tbl[word] != w){
assert(0);
}
if(w & 1)
tbl.remove(word);
if(w & 2){
tbl.remove(word);
tbl[word] = w;
}
}
foreach(w, word; words.array.reverse.enumerate){
tbl[word] = w;
if(w & 2){
tbl.remove(word);
tbl[word] = w;
}
if(*tbl[word] != w){
assert(0);
}
}
}

134
src/clox/gc.d Normal file
View file

@ -0,0 +1,134 @@
module clox.gc;
import core.stdc.stdio : printf;
import std.stdio : writeln;
import std.typecons : Tuple, tuple;
import clox.compiler;
import clox.memory;
import clox.obj;
import clox.util;
import clox.value;
import clox.vm;
import clox.container.dynarray;
import clox.container.table;
void collectGarbage(){
debug(disableGC)
return;
debug(stressGC) {} else {
debug(logGC)
writeln("bytesAllocated ", vm.bytesAllocated, ", nextGC ", vm.nextGC);
if(vm.bytesAllocated < vm.nextGC)
return;
}
debug(logGC)
printf(colour!(" -- GC begin\n", Colour.Yellow).ptr);
debug {
static bool isAlreadyRunning = false;
assert(!isAlreadyRunning);
isAlreadyRunning = true;
scope(exit)
isAlreadyRunning = false;
}
markRoots();
traceReferences();
vm.strings.removeWhite();
auto sweepStats = sweep();
debug(logGC)
printf(colour!(" -- GC end %zu/%zu\n", Colour.Yellow).ptr, sweepStats.collected, sweepStats.total);
debug(stressGC) {} else {
vm.nextGC = cast(size_t)(vm.bytesAllocated * 2);
}
}
void mark(Obj* object){
if(object is null || object.isMarked)
return;
debug(logGC)
writeln("\t", object.prettyPtr, colour!(" (-) ", "200", "200", "240"), Obj.toString(object));
object.isMarked = true;
vm.greyStack ~= object;
}
void mark(Value value){
if(value.isType(Value.Type.Obj))
mark(value.asObj);
}
void mark(Table!(Obj.String*, Value) table){
foreach(entry; table.pool){
mark(cast(Obj*)entry.key);
mark(entry.val);
}
}
void mark(T)(DynArray!T darray){
foreach(item; darray[]){
mark(item);
}
}
private void markRoots(){
for(Value* slot = vm.stack.ptr; slot < vm.stackTop; slot++){
mark(*slot);
}
foreach(frame; vm.frames[0 .. vm.frameCount]){
mark(cast(Obj*)frame.closure);
}
for(Obj.Upvalue* upvalue = vm.openUpvalues; upvalue !is null; upvalue = upvalue.next){
mark(cast(Obj*)upvalue);
}
mark(vm.globals);
markCompilerRoots();
}
private void blacken(Obj* object){
debug(logGC)
writeln("\t", object.prettyPtr, colour!(" (#) ", Colour.Black), Obj.toString(object));
final switch(object.type){
case Obj.Type.NativeFunction:
case Obj.Type.String:
break;
case Obj.Type.Upvalue:
mark(object.asUpvalue.closed);
break;
case Obj.Type.Function:
Obj.Function* func = object.asFunc;
mark(cast(Obj*)func.name);
mark(func.chunk.constants);
break;
case Obj.Type.Closure:
Obj.Closure* closure = object.asClosure;
mark(cast(Obj*)closure.func);
foreach(upvalue; closure.upvalues)
mark(cast(Obj*)upvalue);
break;
case Obj.Type.None: assert(0);
}
}
private void traceReferences(){
while(vm.greyStack.count > 0){
Obj* object = vm.greyStack.pop();
blacken(object);
}
}
private auto sweep(){
size_t nCollected, total;
Obj* previous = null;
Obj* object = vm.objects;
while(object !is null){
total++;
if(object.isMarked){
object.isMarked = false;
previous = object;
object = object.next;
} else {
Obj* unreached = object;
object = object.next;
if(previous !is null){
previous.next = object;
} else {
vm.objects = object;
}
freeObject(unreached);
nCollected++;
}
}
return tuple!("collected", "total")(nCollected, total);
}

View file

@ -1,28 +1,38 @@
module clox.memory;
public import std.typecons : Flag, Yes, No;
import core.stdc.stdlib : realloc, free;
import core.stdc.stdio : printf;
import clox.gc;
import clox.memorydbg;
import clox.obj;
import clox.util;
import clox.value;
import clox.vm;
size_t GROW_CAPACITY(size_t capacity) => capacity < 8 ? 8 : capacity * 2;
T* GROW_ARRAY(T)(T* ptr, size_t oldCount, size_t newCount) => cast(T*)reallocate(ptr, T.sizeof * oldCount, T.sizeof * newCount);
T* FREE_ARRAY(T)(T* ptr, size_t oldCount) => reallocate!T(ptr, T.sizeof * oldCount, 0);
T* FREE(T)(T* ptr) => reallocate(ptr, T.sizeof, 0);
void FREE_ARRAY(T)(T* ptr, size_t oldCount) => FREE_ARRAY(ptr, oldCount);
void FREE_ARRAY(T)(ref T* ptr, size_t oldCount){
reallocate(ptr, T.sizeof * oldCount, 0);
ptr = null;
}
void FREE(T)(ref T* ptr){
reallocate(ptr, T.sizeof, 0);
ptr = null;
}
T* ALLOCATE(T)(size_t count) => cast(T*)reallocate!T(null, 0, T.sizeof * count);
T* reallocate(T)(T* ptr, size_t oldSize, size_t newSize){
vm.bytesAllocated += newSize - oldSize;
if(newSize == 0){ // Free
assert(ptr, "Null pointer free");
debug(memTrace){
import std.conv : to;
assert(ptr in allocatedPointers, "Invalid pointer free: " ~ ptr.to!string);
assert(allocatedPointers[ptr].size, "Double free: " ~ ptr.to!string);
totalFrees++;
totalFreedBytes += allocatedPointers[ptr].size;
allocatedPointers[ptr] = Allocation(0);
registerFree(ptr);
}
ptr.free();
return null;
@ -31,30 +41,39 @@ T* reallocate(T)(T* ptr, size_t oldSize, size_t newSize){
assert(result);
debug(memTrace){
if(ptr is null){ // Malloc
totalAllocs++;
allocatedPointers[result] = Allocation(newSize, createBacktrace());
totalAllocBytes += newSize;
registerAlloc(result, newSize);
} else if(ptr !is result){ // Realloc
totalReallocs++;
totalFreedBytes += allocatedPointers[ptr].size;
totalAllocBytes += newSize;
allocatedPointers[result] = Allocation(newSize);
allocatedPointers[ptr] = Allocation(0);
registerRealloc(result, ptr, oldSize, newSize);
}
}
return result;
}
void freeObjects(){
debug(logGC){
import std.stdio : writeln;
size_t count;
scope(exit)
writeln("Freed ", count, " items at the end");
}
Obj* object = vm.objects;
while(object != null){
Obj* next = object.next;
object.freeObject();
object = next;
debug(logGC)
count++;
}
vm.objects = null;
}
void freeObject(Obj* object){
debug(logGC){
import std.stdio;
writeln("\t", object.prettyPtr, colour!(" free ", "200", "200", "200"), Obj.toString(object));
/* auto bt = createBacktrace(3); */
/* bt.prefix = "\t\t"; */
/* writeln(bt); */
}
final switch(object.type){
case Obj.Type.String:
Obj.String* str = cast(Obj.String*)object;
@ -68,7 +87,7 @@ void freeObject(Obj* object){
break;
case Obj.Type.Closure:
Obj.Closure* closure = cast(Obj.Closure*)object;
if(closure.upvalues)
if(closure.func.upvalueCount)
FREE_ARRAY(closure.upvalues.ptr, closure.upvalues.length);
FREE(closure);
break;

View file

@ -41,13 +41,14 @@ debug(memTrace): version(D_BetterC) {} else {
}
struct BackTraceBuilder{
string prefix = "\t";
string[] fileStack;
string[] funcStack;
int[] lineStack;
string toString() const{
import clox.util, std.format;
return zip(fileStack, lineStack, funcStack).map!(a =>
'\t' ~ colour!("%s", Colour.Yellow).format(a[0]) ~ colour!(`:`, Colour.Black) ~ colour!("%d", Colour.Cyan).format(a[1]) ~ ' ' ~ a[2]
prefix ~ colour!("%s", Colour.Yellow).format(a[0]) ~ colour!(`:`, Colour.Black) ~ colour!("%d", Colour.Cyan).format(a[1]) ~ ' ' ~ a[2]
).join("\n");
}
}
@ -58,15 +59,20 @@ debug(memTrace): version(D_BetterC) {} else {
}
struct Allocation{
ulong size;
size_t size;
BackTraceBuilder backtrace;
}
Allocation[void*] allocatedPointers;
ulong totalAllocs, totalReallocs, totalFrees;
ulong totalAllocBytes, totalFreedBytes;
size_t inUse, peakMemoryUsage;
void recalculatePeak(){
peakMemoryUsage = max(peakMemoryUsage, inUse);
}
size_t totalAllocs, totalReallocs, totalFrees;
size_t totalAllocBytes, totalFreedBytes;
static ~this(){
stderr.writefln(colour!("Allocs: %d (%,d bytes), Reallocs: %d, Frees: %d (%,d bytes)", Colour.Black), totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes);
stderr.writefln(colour!("Allocs: %d (%,d bytes), Reallocs: %d, Frees: %d (%,d bytes), Peak: %,d bytes", Colour.Black), totalAllocs, totalAllocBytes, totalReallocs, totalFrees, totalFreedBytes, peakMemoryUsage);
if(totalAllocBytes > totalFreedBytes){
stderr.writefln("Leaked %d bytes!".lightRed.to!string, totalAllocBytes - totalFreedBytes);
@ -77,4 +83,27 @@ debug(memTrace): version(D_BetterC) {} else {
}
}
void registerFree(T)(T* ptr){
totalFrees++;
totalFreedBytes += allocatedPointers[ptr].size;
inUse -= allocatedPointers[ptr].size;
allocatedPointers[ptr] = Allocation(0);
}
void registerAlloc(T)(T* ptr, size_t newSize){
inUse += newSize;
recalculatePeak();
totalAllocs++;
allocatedPointers[ptr] = Allocation(newSize, createBacktrace(3));
totalAllocBytes += newSize;
}
void registerRealloc(T)(T* ptr, T* old, size_t oldSize, size_t newSize){
inUse += newSize - oldSize;
recalculatePeak();
totalReallocs++;
totalFreedBytes += allocatedPointers[old].size;
totalAllocBytes += newSize;
allocatedPointers[ptr] = Allocation(newSize);
allocatedPointers[old] = Allocation(0);
}
}

View file

@ -8,12 +8,14 @@ import clox.container.table;
import clox.memory;
import clox.value;
import clox.vm;
import clox.gc : collectGarbage;
struct Obj{
enum Type{
None, String, Function, Closure, Upvalue, NativeFunction
}
Type type;
bool isMarked;
Obj* next;
bool isType(Type type) const pure => this.type == type;
@ -24,6 +26,7 @@ struct Obj{
static if(type == Type.Function) return cast(Function*)&this;
static if(type == Type.NativeFunction) return cast(NativeFunction*)&this;
static if(type == Type.Closure) return cast(Closure*)&this;
static if(type == Type.Upvalue) return cast(Upvalue*)&this;
static if(type == Type.None) return &this;
}
@ -48,11 +51,18 @@ struct Obj{
Function* asFunc() const pure => as!(Type.Function);
Closure* asClosure() const pure => as!(Type.Closure);
NativeFunction* asNativeFunc() const pure => as!(Type.NativeFunction);
Upvalue* asUpvalue() const pure => as!(Type.Upvalue);
private static T* allocateObject(T)(){
collectGarbage();
Obj* object = reallocate!Obj(null, 0, T.sizeof);
debug(logGC){
import std.stdio, clox.util;
writeln(object.prettyPtr, " allocate ", T.stringof);
}
object.type = T.myType;
object.next = vm.objects;
object.isMarked = false;
vm.objects = object;
return cast(T*)object;
}
@ -63,22 +73,21 @@ struct Obj{
char[] chars;
uint hash;
static String* allocateString(char[] chars, uint hash){
private static String* allocateString(char[] chars, uint hash){
String* str = allocateObject!String();
str.chars = chars;
str.hash = hash;
vm.strings ~= str;
vm.strings[chars] = str;
return str;
}
static String* copy(const char[] chars){
uint strHash = hashString(chars);
Obj.String* interned = vm.strings.find(chars, strHash);
Obj.String** interned = vm.strings[chars];
if(interned !is null)
return interned;
return *interned;
char* heapChars = ALLOCATE!char(chars.length + 1);
heapChars.memcpy(chars.ptr, chars.length);
heapChars[chars.length] = '\0';
return allocateString(heapChars[0 .. chars.length], strHash);
return allocateString(heapChars[0 .. chars.length], hashArray(chars));
}
static String* concat(String* a, String* b){
size_t len = a.chars.length + b.chars.length;
@ -88,14 +97,13 @@ struct Obj{
c[len] = '\0';
return take(c[0 .. len]);
}
static Obj.String* take(char[] c) {
uint strHash = hashString(c);
Obj.String* interned = vm.strings.find(c, strHash);
private static String* take(char[] c){
Obj.String** interned = vm.strings[c];
if(interned !is null){
FREE_ARRAY!char(c.ptr, c.length + 1);
return interned;
return *interned;
}
return allocateString(c, strHash);
return allocateString(c, hashArray(c));
}
}
@ -188,7 +196,7 @@ struct Obj{
static string toString(Obj* obj){
import std.conv, std.format;
final switch(obj.type){
case Type.String: return "%s".format(obj.asString.chars);
case Type.String: return `"%s"`.format(obj.asString.chars);
case Type.Function: return "Function";
case Type.NativeFunction: return "NativeFunction";
case Type.Closure: return "Closure";

View file

@ -59,4 +59,14 @@ char* readStdin(){
const(char)[] parse(const char* cstr) => cstr[0 .. cstr.strlen];
version(D_BetterC) {} else string prettyPtr(void* ptr){
import std.bitmanip, std.conv, std.algorithm;
import clox.container.table : hashArray;
ubyte[size_t.sizeof] ptrBytes = (cast(ubyte*)&ptr)[0 .. size_t.sizeof];
uint hash = hashArray(ptrBytes);
ubyte[4] colours = nativeToLittleEndian(hash);
foreach(ref c; colours)
c = c.max(ubyte(20));
return "\033[38;2;" ~ colours[0].to!string ~ ";" ~ colours[1].to!string ~ ";" ~ colours[2].to!string ~ "m" ~ ptr.to!string ~ "\033[0m";
}

View file

@ -3,13 +3,14 @@ module clox.vm;
import core.stdc.stdio;
import clox.chunk;
import clox.util;
import clox.dbg;
import clox.obj;
import clox.value;
import clox.compiler;
import clox.scanner;
import clox.dbg;
import clox.memory;
import clox.obj;
import clox.scanner;
import clox.util;
import clox.value;
import clox.container.dynarray;
import clox.container.table;
import clox.container.varint;
@ -20,14 +21,15 @@ struct VM{
Value* stackTop;
CallFrame[256] frames;
uint frameCount;
Table globals;
StringStore strings;
Table!(Obj.String*, Value) globals;
Table!(char[], Obj.String*) strings;
Obj.Upvalue* openUpvalues;
Obj* objects;
bool isREPL;
DynArray!(Obj*) greyStack;
size_t bytesAllocated, nextGC = 1024 * 4;
debug(printCode) bool printCode;
debug(traceExec) bool traceExec;
bool dontRun;
bool isREPL, dontRun;
enum InterpretResult{ Ok, CompileError, RuntimeError }
struct CallFrame{
Obj.Closure* closure;
@ -35,8 +37,9 @@ struct VM{
Value* slots;
}
void initialise(){
strings.initialise();
globals.initialise();
strings = typeof(strings)(8);
globals = typeof(globals)(8);
greyStack.initialise();
stackTop = &stack[0];
defineNative("clock", (int argCount, Value* args){
@ -48,6 +51,7 @@ struct VM{
freeObjects();
strings.free();
globals.free();
greyStack.free();
}
void push(Value v){
*(stackTop++) = v;
@ -111,6 +115,7 @@ struct VM{
push(Value.from(mixin("a", op, "b")));
return 0;
}
while(true){
debug(traceExec) if(traceExec){
disassembleInstruction(&chunk(), ip - chunk.code.ptr);
@ -168,25 +173,25 @@ struct VM{
break;
case OpCode.GetGlobal:
Obj.String* name = readString();
Value value;
if(!globals.get(name, value)){
if(Value* value = globals[name]){
push(*value);
} else {
runtimeError("Undefined variable '%s'.", name.chars.ptr);
return InterpretResult.RuntimeError;
}
push(value);
break;
case OpCode.DefineGlobal:
Obj.String* name = readString();
globals.set(name, peek(0));
globals[name] = peek(0);
pop();
break;
case OpCode.SetGlobal:
Obj.String* name = readString();
if(globals.set(name, peek(0))){
globals.del(name);
if(globals[name] is null){
runtimeError("Undefined variable '%s'.", name.chars.ptr);
return InterpretResult.RuntimeError;
}
globals[name] = peek(0);
break;
static foreach(k, op; [ OpCode.Equal: "==", OpCode.NotEqual: "!=" ]){
@ -205,9 +210,11 @@ struct VM{
case k:
static if(k == OpCode.Add){
if(checkBinaryType!(Obj.Type.String)){
Obj.String* b = pop().asObj.asString;
Obj.String* a = pop().asObj.asString;
Obj.String* b = peek(0).asObj.asString;
Obj.String* a = peek(1).asObj.asString;
Obj.String* result = Obj.String.concat(a, b);
pop();
pop();
push(Value.from(result));
break opSwitch;
}
@ -354,7 +361,7 @@ struct VM{
void defineNative(const(char)[] name, Obj.NativeFunction.Sig func){
push(Value.from(Obj.String.copy(name)));
push(Value.from(Obj.NativeFunction.create(func)));
globals.set(stack[0].asObj.asString, vm.stack[1]);
globals[stack[0].asObj.asString] = vm.stack[1];
pop();
pop();
}