diff --git a/dub.sdl b/dub.sdl index 2c95751..43c4538 100644 --- a/dub.sdl +++ b/dub.sdl @@ -9,6 +9,7 @@ sourcePaths configuration "clox" { buildOptions "betterC" sourcePaths "src/clox" + versions "nanBoxing" } configuration "clox-dbg" { @@ -18,6 +19,8 @@ configuration "clox-dbg" { sourcePaths "src/clox" buildRequirements "requireBoundsCheck" "requireContracts" + versions "nanBoxing" + debugVersions "printCode" "traceExec" /* debugVersions "stressGC" */ diff --git a/src/clox/container/table.d b/src/clox/container/table.d index c989dcb..2add2e5 100644 --- a/src/clox/container/table.d +++ b/src/clox/container/table.d @@ -8,6 +8,7 @@ debug import std.stdio : writeln; import clox.memory; import clox.memorydbg; import clox.obj; +import clox.util; import clox.value; import clox.container.dynarray; import conf = clox.conf; @@ -29,6 +30,7 @@ struct Table(K, V){ Entry[] pool; size_t alive; this(size_t size){ + size = npow2(size); pool = allocatePool(size); } void free(){ @@ -69,21 +71,21 @@ struct Table(K, V){ if(alive == 0) return null; Hash hash = getHash(key); - size_t index = hash % pool.length; + size_t index = hash & ((pool.length) - 1); while(true){ const entry = pool[index]; if(entry.isAlive && entry.key == key) return &pool[index]; if(entry.isFree) break; - index = (index+1) % pool.length; + index = (index+1) & ((pool.length) - 1); } return null; } void opIndexAssign(V val, in K key){ checkResize(); Hash hash = getHash(key); - size_t index = hash % pool.length; + size_t index = hash & ((pool.length) - 1); while(true){ const Entry* entry = &pool[index]; if(entry.isAlive && entry.key == key) @@ -92,7 +94,7 @@ struct Table(K, V){ alive++; break; } - index = (index+1) % pool.length; + index = (index+1) & ((pool.length) - 1); } pool[index] = Entry(cast(K)key, val); } diff --git a/src/clox/util.d b/src/clox/util.d index c8acdb4..ad2d5fd 100644 --- a/src/clox/util.d +++ b/src/clox/util.d @@ -70,3 +70,18 @@ version(D_BetterC) {} else string prettyPtr(void* ptr){ return "\033[38;2;" ~ colours[0].to!string ~ ";" ~ colours[1].to!string ~ ";" ~ colours[2].to!string ~ "m" ~ ptr.to!string ~ "\033[0m"; } +size_t npow2(size_t n){ + n--; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + return n + 1; +} + +// https://github.com/dlang/phobos/blob/832cc465998b1ea77051cd3fd014b544442a4f8c/std/conv.d#L6396 +pragma(inline, true) ref T bitCast(T, S)(ref S value) if (T.sizeof <= S.sizeof){ + return *cast(T*) &value; +} + diff --git a/src/clox/value.d b/src/clox/value.d index 5a079f9..4b66f2a 100644 --- a/src/clox/value.d +++ b/src/clox/value.d @@ -4,41 +4,88 @@ import core.stdc.stdio; import clox.memory; import clox.obj; +import clox.util; struct Value{ enum Type{ None, Bool, Nil, Number, Obj } - Type type; - private union Un{ - bool boolean; - double number; - Obj* obj; - } - private Un un; + version(nanBoxing){ + private enum QNAN = ulong(0x7ffc000000000000); + private enum SIGN_BIT = ulong(0x8000000000000000); + enum Tag{ + Nil = 1, False = 2, True = 3 + } + ulong data; + static Value from(double val) => Value(val.bitCast!ulong); + static Value from(bool val) => Value(val ? QNAN | Tag.True : QNAN | Tag.False); + static Value from(T)(T* val) if(__traits(compiles, val.obj)) => Value(SIGN_BIT | QNAN | val.bitCast!ulong); + static Value nil() pure => Value(QNAN | Tag.Nil); + + Type type() const pure{ + if(isType(Type.Number)) + return Type.Number; + if(isType(Type.Bool)) + return Type.Bool; + if(isType(Type.Nil)) + return Type.Nil; + if(isType(Type.Obj)) + return Type.Obj; + assert(0); + } + + bool isType(Type type) const pure{ + final switch(type){ + case Type.Number: return (data & QNAN) != QNAN; + case Type.Nil: return data == nil.data; + case Type.Bool: return data == (QNAN | Tag.True) || data == (QNAN | Tag.False); + case Type.Obj: return (data & (QNAN | SIGN_BIT)) == (QNAN | SIGN_BIT); + case Type.None: assert(0); + } + } + + auto as(Type type)() const pure{ + static if(type != Type.None) + assert(isType(type)); + static if(type == Type.Number) return data.bitCast!double; + static if(type == Type.Bool) return data == (QNAN | Tag.True); + static if(type == Type.Obj) return cast(Obj*)(data & ~(SIGN_BIT | QNAN)); + static if(type == Type.None) return this; + } + + } else { + private union Un{ + bool boolean; + double number; + Obj* obj; + } + private Type type; + private Un un; + static Value from(double val) => Value(Type.Number, Un(number: val)); + static Value from(bool val) => Value(Type.Bool, Un(boolean: val)); + static Value from(T)(T* val) if(__traits(compiles, val.obj)) => Value(Type.Obj, Un(obj: cast(Obj*)val)); + static Value from(Value val) => val; + static Value nil() pure => Value(Type.Nil); + + bool isType(Type type) const pure => this.type == type; + auto as(Type type)() const pure{ + static if(type != Type.None) + assert(isType(type)); + static if(type == Type.Number) return un.number; + static if(type == Type.Bool) return un.boolean; + static if(type == Type.Obj) return un.obj; + static if(type == Type.None) return this; + } - bool isType(Type type) const pure => this.type == type; - bool isType(Obj.Type type) const pure => this.type == Type.Obj && asObj.isType(type); - auto as(Type type)() const pure{ - static if(type != Type.None) - assert(this.type == type); - static if(type == Type.Number) return un.number; - static if(type == Type.Bool) return un.boolean; - static if(type == Type.Obj) return un.obj; - static if(type == Type.None) return this; } + bool isType(Obj.Type type) const pure => isType(Type.Obj) && asObj.isType(type); + bool asBoolean() const pure => as!(Type.Bool); double asNumber() const pure => as!(Type.Number); Obj* asObj() const pure => cast(Obj*)as!(Type.Obj); - static Value from(T: double)(T val) => Value(Type.Number, Un(number: val)); - static Value from(T: bool)(T val) => Value(Type.Bool, Un(boolean: val)); - static Value from(T)(T* val) if(__traits(compiles, val.obj)) => Value(Type.Obj, Un(obj: cast(Obj*)val)); - static Value from(T: Value)(T val) => val; - static Value nil() pure => Value(Type.Nil); - - bool isFalsey() const pure => (type == Type.Bool && asBoolean == false) || type == Type.Nil; + bool isFalsey() const pure => (isType(Type.Bool) && asBoolean == false) || isType(Type.Nil); bool isTruthy() const pure => !isFalsey; bool opEquals(Value rhs) const pure{ if(rhs.type != type) diff --git a/src/clox/vm.d b/src/clox/vm.d index 10122da..2097031 100644 --- a/src/clox/vm.d +++ b/src/clox/vm.d @@ -108,7 +108,6 @@ struct VM{ Value readConstant() => chunk.constants[readVarUint()]; Obj.String* readString() => readConstant().asObj.asString; bool checkBinaryType(alias type)() => peek(0).isType(type) && peek(1).isType(type); - bool checkSameType()() => peek(0).type == peek(1).type; int binaryOp(string op, alias check, string checkMsg, alias pre)(){ if(!check){ runtimeError(checkMsg.ptr); @@ -234,7 +233,7 @@ struct VM{ } static foreach(k, op; [ OpCode.Greater: ">", OpCode.Less: "<", OpCode.GreaterEqual: ">=", OpCode.LessEqual: "<=" ]){ case k: - if(binaryOp!(op, checkSameType, "Operands must be of the same type.", Value.Type.None)) + if(binaryOp!(op, checkBinaryType!(Value.Type.Number), "Operands must be of the same type.", Value.Type.None)) return InterpretResult.RuntimeError; break opSwitch; } diff --git a/test/all.d b/test/all.d index a85a1fa..967795b 100755 --- a/test/all.d +++ b/test/all.d @@ -20,6 +20,8 @@ int main(){ "./test/while.lox".match("1 2 3 2 1 0 1 1 2 3 2 1 0 1 ".replace(' ', '\n')); "./test/fields.lox".match("0 10 ".replace(' ', '\n')); + "./test/nan!=nan.lox".match("false ".replace(' ', '\n')); + "./test/ops.lox".match("1\n2\n3\n4\n5\n6\n7\ntrue\nfalse\ntrue\ntrue\nhello, world\n"); "./test/shortcircuit.lox".match("true\nAAAA!\nAAAA!\nAAAA?\n"); "./test/closure.lox".match("1\n2\n"); diff --git a/test/nan!=nan.lox b/test/nan!=nan.lox new file mode 100644 index 0000000..d3d9ab1 --- /dev/null +++ b/test/nan!=nan.lox @@ -0,0 +1,4 @@ + +var nan = 0 / 0; +print nan == nan; +