Garbage Collection 26
This commit is contained in:
parent
dc4e6d33b2
commit
6d5dff6e3d
10 changed files with 444 additions and 211 deletions
14
dub.sdl
14
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" {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
134
src/clox/gc.d
Normal 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);
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue