Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions arith.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions bench/asmgate/baseline.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
37 changes: 37 additions & 0 deletions checked_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

69 changes: 69 additions & 0 deletions exec_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
50 changes: 50 additions & 0 deletions fast.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() }

Expand Down Expand Up @@ -508,6 +521,14 @@ dispatch:
idx = uint64(uint8(i>>24)) - 1
} else if key := &regs[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
Expand Down Expand Up @@ -605,6 +626,12 @@ dispatch:
idx = uint64(uint8(i>>16)) - 1
} else if key := &regs[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
Expand Down Expand Up @@ -680,6 +707,29 @@ dispatch:
} else {
setInt(&regs[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(&regs[uint8(i>>8)], r)
} else {
e = exSlowOp
break dispatch
Expand Down
Loading