diff --git a/arith.go b/arith.go index fc444cd..3e16e0d 100644 --- a/arith.go +++ b/arith.go @@ -113,6 +113,15 @@ func floatMod(x, y float64) float64 { if y == 0 || math.IsInf(x, 0) || x != x || y != y { return (x * y) / (x * y) } + // whole numbers below 2^53, as code written before Lua had integers takes bits: exact + // as integers, where math.Mod loops over the bits of the quotient + if a, b := int64(x), int64(y); float64(a) == x && float64(b) == y && x < 0x1p53 && x > -0x1p53 && y < 0x1p53 && y > -0x1p53 { + m := intMod(a, b) + if m == 0 { + return math.Copysign(0, x) // as fmod: a zero remainder keeps the dividend's sign + } + return float64(m) + } m := math.Mod(x, y) if (m > 0 && y < 0) || (m < 0 && y != m && y > 0) { m += y diff --git a/bench/asmgate/baseline.txt b/bench/asmgate/baseline.txt index 0d4a421..6003ce6 100644 --- a/bench/asmgate/baseline.txt +++ b/bench/asmgate/baseline.txt @@ -1,8 +1,8 @@ # Stack accesses per hot opcode in (*executor).fast, outside write-barrier slow paths. # Opcodes not listed must have none. Written by go run ./bench/asmgate -update. amd64-v1 head 1 -amd64-v1 opGetField 7 -amd64-v1 opGetTable,opGetIndex 2 +amd64-v1 opGetField 6 +amd64-v1 opGetTable,opGetIndex 3 amd64-v1 opJmp 3 amd64-v1 opSelf 1 amd64-v3 head 2 diff --git a/checked_gen.go b/checked_gen.go index 03b54e0..21b1580 100644 --- a/checked_gen.go +++ b/checked_gen.go @@ -410,6 +410,14 @@ dispatch: idx = uint64(uint8(i>>24)) - 1 } else if key := ®s[uint8(i>>24)]; key.p == tInt() { idx = key.n - 1 + } else if key.p == tFloat() { + // a float with a whole value, as arithmetic in code written for Lua 5.1 makes + // indexes: the integer key it normalises to + var whole bool + if idx, whole = wholeIndex(key.n); !whole { + e = exSlowOp + break dispatch + } } else if key.n>>56 == kindString && !isScalar(key.p) && key.n&lenMask <= 16 && obj.n == kindTable<<56 && !isScalar(obj.p) { // a short string key: hash it and probe inline, as hashString and keyEq would t, n := (*Table)(obj.p), key.n&lenMask @@ -507,6 +515,12 @@ dispatch: idx = uint64(uint8(i>>16)) - 1 } else if key := ®s[uint8(i>>16)]; key.p == tInt() { idx = key.n - 1 + } else if key.p == tFloat() { + var whole bool + if idx, whole = wholeIndex(key.n); !whole { + e = exSlowOp + break dispatch + } } else { e = exSlowOp break dispatch @@ -582,6 +596,29 @@ dispatch: } else { setInt(®s[uint8(i>>8)], uint64(q)) } + } else if (uint8(i) == opMod || uint8(i) == opModK) && (x.p == tFloat() || y.p == tFloat()) && isNum(x) && isNum(y) { + // a float remainder of whole numbers below 2^53, as code written before Lua had + // integers takes bits: exact as integers, where math.Mod loops over the quotient's + // bits. Two integers with a divisor below 1 are the driver's: the result is an integer + fx, fy := toFloat(x), toFloat(y) + a, b := int64(fx), int64(fy) + if float64(a) != fx || float64(b) != fy || b == 0 || fx >= 0x1p53 || fx <= -0x1p53 || fy >= 0x1p53 || fy <= -0x1p53 { + // not whole, too large, NaN, infinite, or a zero divisor. Spelt out: wholeInt + // here spills the loop + e = exSlowOp + break dispatch + } + m := a % b + if m != 0 && (m^b) < 0 { // round towards minus infinity + m += b + } + r := float64(m) + if m == 0 { + // as fmod: a zero remainder keeps the dividend's sign, which is bit 63 of an + // integer and of a float alike + r = math.Float64frombits(x.n & (1 << 63)) + } + setFloat(®s[uint8(i>>8)], r) } else { e = exSlowOp break dispatch diff --git a/exec_test.go b/exec_test.go index a460a6f..ebf59bf 100644 --- a/exec_test.go +++ b/exec_test.go @@ -1020,3 +1020,72 @@ func TestWrapFailureKeepsNothing(t *testing.T) { } release(&rt.pool, ex) } + +// The remainder of floats with whole values, as code written for Lua 5.1 takes bits, is +// computed as integers, inline and in the driver: the same as fmod gives, sign of a zero +// included; values that are not whole, or too large, still go the long way. +func TestFloatModWhole(t *testing.T) { + src := ` + local function same(a, b) return a == b and (a ~= 0 or 1 / a == 1 / b) end + local K = 3.0 + local cases = { + { 7.0, 3.0, 1.0 }, { -7.0, 3.0, 2.0 }, { 7.0, -3.0, -2.0 }, { -7.0, -3.0, -1.0 }, + { 6.0, 3.0, 0.0 }, { -6.0, 3.0, -0.0 }, { 6.0, -3.0, 0.0 }, { -0.0, 3.0, -0.0 }, { 0.0, -3.0, 0.0 }, + { 7, 3.0, 1.0 }, { 7.0, 3, 1.0 }, { -6, 3.0, -0.0 }, { -5, -1.0, -0.0 }, { 6, -3.0, 0.0 }, { -7, 3.0, 2.0 }, + { 5.5, 2.0, 1.5 }, { 7.0, 2.5, 2.0 }, { 2^53, 3.0, 2.0 }, { 2^60, 7.0, 1.0 }, { 16777216.0 * 65536, 256.0, 0.0 }, + { 1e300, 7.0, 1.0 }, -- the last three go the long way, with the values C Lua gives + } + for i, c in ipairs(cases) do + local x, y, want = c[1], c[2], c[3] + local got = x % y + assert(same(got, want), i .. ": " .. tostring(x) .. " % " .. tostring(y) .. " = " .. tostring(got)) + assert(math.type(got) == "float", i) + end + local x = 7.0 + assert(same(x % 3.0, 1.0) and same(-x % 3.0, 2.0) and same((x - 13) % 3.0, -0.0)) -- the constant form, MODK + assert(same(K % 3, 0.0) and same(-K % 3, -0.0)) -- a float register with an integer constant + local nan = 7.0 % 0.0 + assert(nan ~= nan) + local bitbuf, room = 0, 1 -- a bit buffer as inflate keeps one, in floats + for i = 1, 20 do bitbuf, room = bitbuf + (i % 256) * room, room * 256 end + local v = 0 + for _ = 1, 50 do local p = 2 ^ 5; local r = bitbuf % p; v = v * 2 + r; bitbuf = (bitbuf - r) / p end + return v` + for _, checked := range []bool{true, false} { + res, err := runInEnv(t, MustNew(withLoop(checked)), src) + if err != nil || len(res) != 1 { + t.Fatalf("checked=%v: %v %v", checked, res, err) + } + if f, _ := res[0].Float(); f != 5181448545894400 { // as C Lua computes it + t.Errorf("checked=%v: bit buffer gave %v", checked, res[0]) + } + } +} + +// A float key with a whole value finds the integer key it normalises to, inline, in the +// array part and in the hash part; one without, or past the integers, goes the long way. +func TestFloatKeysInline(t *testing.T) { + src := ` + local t = { 10, 20, 30, [0] = 5, [-1] = -5, [100] = 1000, [0.5] = "half", [2^53] = "big", [2^63] = "huge" } + assert(t[1.0] == 10 and t[3.0] == 30 and t[0.0] == 5 and t[-0.0] == 5 and t[-1.0] == -5 and t[100.0] == 1000) + assert(t[0.5] == "half" and t[2^53] == "big" and t[2^63] == "huge" and t[4.0] == nil and t[1e300] == nil) + local nan = 0 / 0 + assert(t[nan] == nil) + t[2.0] = 22 + t[0.0] = 55 + t[100.0] = 1001 + t[4.0] = 40 -- an append, as t[4] is + assert(t[2] == 22 and t[0] == 55 and t[100] == 1001 and t[4] == 40 and #t == 4) + local w = {} + for k = 1, 10 do w[k + 0.0] = k * k end + local s = 0 + for k = 1, 10 do s = s + w[k] end + assert(s == 385 and #w == 10) + return "ok"` + for _, checked := range []bool{true, false} { + res, err := runInEnv(t, MustNew(withLoop(checked)), src) + if err != nil || len(res) != 1 || res[0].String() != "ok" { + t.Errorf("checked=%v: %v %v", checked, res, err) + } + } +} diff --git a/fast.go b/fast.go index 6a9e719..b236c55 100644 --- a/fast.go +++ b/fast.go @@ -44,6 +44,19 @@ func strEqBytes(x, y *Value, n uintptr) bool { return true } +// wholeIndex returns the array index of a float key, from its bits, when its value is whole +// and an integer: the key it normalises to, less one, as normKey gives it. The range is +// tested on the float, as arm64 saturates int64(f) where amd64 gives the minimum. Small +// enough to inline into fast; split in two, it spills the loop. +func wholeIndex(bits uint64) (uint64, bool) { + f := math.Float64frombits(bits) + n := int64(f) + if float64(n) != f || f >= 0x1p63 || f < -0x1p63 { + return 0, false + } + return uint64(n) - 1, true +} + // isNum reports whether v is an integer or a float. Small enough to inline into fast. func isNum(v *Value) bool { return v.p == tInt() || v.p == tFloat() } @@ -508,6 +521,14 @@ dispatch: idx = uint64(uint8(i>>24)) - 1 } else if key := ®s[uint8(i>>24)]; key.p == tInt() { idx = key.n - 1 + } else if key.p == tFloat() { + // a float with a whole value, as arithmetic in code written for Lua 5.1 makes + // indexes: the integer key it normalises to + var whole bool + if idx, whole = wholeIndex(key.n); !whole { + e = exSlowOp + break dispatch + } } else if key.n>>56 == kindString && !isScalar(key.p) && key.n&lenMask <= 16 && obj.n == kindTable<<56 && !isScalar(obj.p) { // a short string key: hash it and probe inline, as hashString and keyEq would t, n := (*Table)(obj.p), key.n&lenMask @@ -605,6 +626,12 @@ dispatch: idx = uint64(uint8(i>>16)) - 1 } else if key := ®s[uint8(i>>16)]; key.p == tInt() { idx = key.n - 1 + } else if key.p == tFloat() { + var whole bool + if idx, whole = wholeIndex(key.n); !whole { + e = exSlowOp + break dispatch + } } else { e = exSlowOp break dispatch @@ -680,6 +707,29 @@ dispatch: } else { setInt(®s[uint8(i>>8)], uint64(q)) } + } else if (uint8(i) == opMod || uint8(i) == opModK) && (x.p == tFloat() || y.p == tFloat()) && isNum(x) && isNum(y) { + // a float remainder of whole numbers below 2^53, as code written before Lua had + // integers takes bits: exact as integers, where math.Mod loops over the quotient's + // bits. Two integers with a divisor below 1 are the driver's: the result is an integer + fx, fy := toFloat(x), toFloat(y) + a, b := int64(fx), int64(fy) + if float64(a) != fx || float64(b) != fy || b == 0 || fx >= 0x1p53 || fx <= -0x1p53 || fy >= 0x1p53 || fy <= -0x1p53 { + // not whole, too large, NaN, infinite, or a zero divisor. Spelt out: wholeInt + // here spills the loop + e = exSlowOp + break dispatch + } + m := a % b + if m != 0 && (m^b) < 0 { // round towards minus infinity + m += b + } + r := float64(m) + if m == 0 { + // as fmod: a zero remainder keeps the dividend's sign, which is bit 63 of an + // integer and of a float alike + r = math.Float64frombits(x.n & (1 << 63)) + } + setFloat(®s[uint8(i>>8)], r) } else { e = exSlowOp break dispatch