diff --git a/dub.sdl b/dub.sdl index c56910c..b3d0a67 100644 --- a/dub.sdl +++ b/dub.sdl @@ -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" { diff --git a/src/clox/compiler.d b/src/clox/compiler.d index f5080c6..b367ed3 100644 --- a/src/clox/compiler.d +++ b/src/clox/compiler.d @@ -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; + } +} diff --git a/src/clox/container/dynarray.d b/src/clox/container/dynarray.d index f231e7e..a562ec8 100644 --- a/src/clox/container/dynarray.d +++ b/src/clox/container/dynarray.d @@ -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; diff --git a/src/clox/container/table.d b/src/clox/container/table.d index 5cd7862..2958488 100644 --- a/src/clox/container/table.d +++ b/src/clox/container/table.d @@ -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); + } + } +} diff --git a/src/clox/gc.d b/src/clox/gc.d new file mode 100644 index 0000000..c6b7914 --- /dev/null +++ b/src/clox/gc.d @@ -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); +} + diff --git a/src/clox/memory.d b/src/clox/memory.d index 2e1f462..dcec163 100644 --- a/src/clox/memory.d +++ b/src/clox/memory.d @@ -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; diff --git a/src/clox/memorydbg.d b/src/clox/memorydbg.d index c4cab5c..8ab7813 100644 --- a/src/clox/memorydbg.d +++ b/src/clox/memorydbg.d @@ -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); + } + } diff --git a/src/clox/obj.d b/src/clox/obj.d index 1d0a5d3..3e2049a 100644 --- a/src/clox/obj.d +++ b/src/clox/obj.d @@ -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"; diff --git a/src/clox/util.d b/src/clox/util.d index ef2d96e..c8acdb4 100644 --- a/src/clox/util.d +++ b/src/clox/util.d @@ -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"; +} diff --git a/src/clox/vm.d b/src/clox/vm.d index a3d6dfb..8aca37f 100644 --- a/src/clox/vm.d +++ b/src/clox/vm.d @@ -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(); }