diff --git a/ops.go b/ops.go index 9e786d5..bae7571 100644 --- a/ops.go +++ b/ops.go @@ -132,12 +132,18 @@ func (ex *executor) slowOp() error { case opSetIndex: return ex.newindex(op, *ex.reg(a), Int(int64(b)), *ex.reg(c)) case opSetField: + if ex.constSet(*ex.reg(a), b, *ex.reg(c)) { + return nil + } return ex.newindex(op, *ex.reg(a), p.k[b], *ex.reg(c)) case opSetTableK: return ex.newindex(op, *ex.reg(a), *ex.reg(b), p.k[c]) case opSetIndexK: return ex.newindex(op, *ex.reg(a), Int(int64(b)), p.k[c]) case opSetFieldK: + if ex.constSet(*ex.reg(a), b, p.k[c]) { + return nil + } return ex.newindex(op, *ex.reg(a), p.k[b], p.k[c]) case opNewTable: // the sizes are hints: clamp them, so that no instruction can ask for a huge allocation @@ -1403,6 +1409,37 @@ func (ex *executor) builtinIter(base, c int) (bool, error) { return true, ex.tick() // a call ticks } +// constSet does obj[K[kc]] = val for a SETFIELD the loop could not do inline, such as one +// that adds a field to a table just built, when the table has no metatable and the write +// needs no resize: it finds the key with the hash computed at compile time, sets it or adds +// it, and records where for the site. It reports false when newindex must do the write. +func (ex *executor) constSet(obj Value, kc int, val Value) bool { + if !obj.isObj(kindTable) || val.IsNil() { + return false + } + t := (*Table)(obj.p) + if t.meta != nil || t.flags&(tabFrozen|tabGuarded) != 0 || len(t.nodes) == 0 { + return false + } + p := ex.st.cl.p + key, h := p.k[kc], p.khash[kc] + if !key.isString() || key.n&lenMask >= 2 && *(*uint16)(key.p) == 0x5f5f { + return false // a key starting with "__" clears the table's caches in set + } + idx := t.findNode(&key, h) + if idx >= 0 { + t.nodes[idx].v = val + } else { + if (t.used+1)*4 > len(t.nodes)*3 || t.memSize() >= maxAlloc/2 { + return false // a resize, which newindex charges for + } + idx = t.insert(key, val, h) + } + learn(&p.hints[ex.aux()], idx) + ex.st.pc++ // the aux word + return true +} + // constField does R[dst] = obj[K[kc]] for GETFIELD and SELF, when obj is a table and the // key is in it or one cached __index hop away, or obj is a string and the key is in the // string library, with the key's compile-time hash. It learns the site's hint and reports diff --git a/table.go b/table.go index 4d8716b..9f4b78e 100644 --- a/table.go +++ b/table.go @@ -380,15 +380,15 @@ func (t *Table) growArray(n int) { } } -// insert places a new key; the caller has made room. -func (t *Table) insert(key, val Value, h uint64) { +// insert places a new key and returns its node index; the caller has made room. +func (t *Table) insert(key, val Value, h uint64) int { mask := uint64(len(t.nodes) - 1) for j := h; ; j++ { nd := &t.nodes[j&mask] if nd.k.p == nil { nd.k, nd.v = key, val t.used++ - return + return int(j & mask) } } } diff --git a/testdata/meta.expected b/testdata/meta.expected index eb749f9..605c67e 100644 --- a/testdata/meta.expected +++ b/testdata/meta.expected @@ -14,3 +14,4 @@ a tail call through __call stays a tail call str:646f6e65 | true | 2 | 3 | str:6 metamethods that are not functions str:6d6574616d6574686f6473207468617420617265206e6f742066756e6374696f6e733a343a20617474656d707420746f2063616c6c206120737472696e672076616c756520286d6574616d6574686f6420276164642729 | true | str:6d6574616d6574686f6473207468617420617265206e6f742066756e6374696f6e733a363a20617474656d707420746f2063616c6c206120626f6f6c65616e2076616c756520286d6574616d6574686f6420276c656e2729 | str:6d6574616d6574686f6473207468617420617265206e6f742066756e6374696f6e733a373a20617474656d707420746f2063616c6c2061206e756d6265722076616c756520286d6574616d6574686f642027636f6e6361742729 | str:63616c6c6564 | str:6d6574616d6574686f6473207468617420617265206e6f742066756e6374696f6e733a31303a20617474656d707420746f2063616c6c2061207461626c652076616c756520286d6574616d6574686f64202765712729 | str:6d6574616d6574686f6473207468617420617265206e6f742066756e6374696f6e733a31323a20617474656d707420746f2063616c6c2061206e756d6265722076616c756520286d6574616d6574686f642027636c6f73652729 | str:6d6574616d6574686f6473207468617420617265206e6f742066756e6374696f6e733a31343a20617474656d707420746f2063616c6c2061206e696c2076616c756520286d6574616d6574686f642027636c6f73652729 | str:617474656d707420746f2063616c6c2061206e756d6265722076616c7565 | str:6e696c | str:7461626c65 __index and __newindex chains that loop false | str:5f5f696e64657820616e64205f5f6e6577696e64657820636861696e732074686174206c6f6f703a363a20275f5f696e6465782720636861696e20746f6f206c6f6e673b20706f737369626c65206c6f6f70 | false | str:5f5f696e64657820616e64205f5f6e6577696e64657820636861696e732074686174206c6f6f703a373a20275f5f6e6577696e6465782720636861696e20746f6f206c6f6e673b20706f737369626c65206c6f6f70 | false | str:275f5f696e6465782720636861696e20746f6f206c6f6e673b20706f737369626c65206c6f6f70 | false | str:275f5f6e6577696e6465782720636861696e20746f6f206c6f6e673b20706f737369626c65206c6f6f70 index errors raised by the library, called from Lua str:275f5f696e6465782720636861696e20746f6f206c6f6e673b20706f737369626c65206c6f6f70 | str:275f5f6e6577696e6465782720636861696e20746f6f206c6f6e673b20706f737369626c65206c6f6f70 | str:617474656d707420746f20696e6465782061206e756d6265722076616c7565 | str:617474656d707420746f20696e6465782061206e756d6265722076616c7565 +fields added to tables just built, with and without __newindex 270 | 11 | 22 | 33 | 44 | 55 | 66 | 6 | str:7121 | 43 diff --git a/testdata/meta.lua b/testdata/meta.lua index 2d0402b..ab75df9 100644 --- a/testdata/meta.lua +++ b/testdata/meta.lua @@ -131,3 +131,33 @@ return err(function() return table.unpack(setmetatable({}, L), 1, 1) end), err(function() table.insert(setmetatable({}, L), 1) end), err(function() for _ in ipairs(5) do end end), err(function() table.insert(setmetatable({}, {__newindex = 5}), 1) end) +--== fields added to tables just built, with and without __newindex +local function new(x, y) return {x = x, y = y} end +local r, sum = {}, 0 +for i = 1, 20 do + local p = new(i, 2 * i) + p.z = i; p.w = -i -- adds, the second one past the size the constructor asked for + p.y = nil; p.y = 3 -- sets a deleted key again + sum = sum + p.x + p.y + p.z + p.w +end +r[#r + 1] = sum +local logged = {} +local mt = {__newindex = function(t, k, v) logged[#logged + 1] = k; rawset(t, k, v * 10) end} +for i = 1, 6 do + local p = setmetatable(new(i, i), mt) + p.x = i -- present: no __newindex + p.z = i -- absent: __newindex + r[#r + 1] = p.x + p.z +end +r[#r + 1] = #logged +local m = new(1, 2) +local o = setmetatable({}, m) +m.__index = function(_, k) return k .. "!" end -- a "__" key added to a table in use as a metatable +r[#r + 1] = o.q +local n = 0 +local t = new(1, 2) +for i = 1, 40 do t["k" .. i] = i end +t.v = 0 +for _ in pairs(t) do n = n + 1 end +r[#r + 1] = n +return table.unpack(r)