Skip to content

Add new fields to tables without a metatable directly in the SETFIELD slow path - #49

Merged
folbricht merged 1 commit into
mainfrom
setfield-insert
Oct 20, 2026
Merged

folbricht merged 1 commit into
mainfrom
setfield-insert

Conversation

@folbricht

Copy link
Copy Markdown
Owner

A SETFIELD that adds a key misses the loop's site hint, because the key is not in the table yet. That is every field written into a table just built, as in setmetatable({x = x, y = y}, Point), or self.z = z in a constructor. The miss went to newindex, which:

  • looked the key up twice (Get, then findNode in set),
  • hashed it at run time,
  • measured the table before and after to charge memory,
  • then looked it up a third time to learn the hint.

constSet handles these writes in the slow path when the table has no metatable, is not frozen or guarded, and the key does not start with __. It probes with the constant's hash computed at compile time, as constField does for reads. It sets the key if it is there, or adds it with insert when no resize is needed, then learns the hint. Every other case still goes to newindex. With no resize, the table's size doesn't change, so nothing needs to be charged. insert now returns the node index.

The insert is not done inside the loop. Doing it there makes fast larger than the inliner's big-function threshold. Small helpers like setInt and setBool then become calls, and every opcode spills; asmgate catches this.

Effect, fast loop unless noted:

before after
oop, instructions per run 603M 544M (-9.7%)
oop, ms (minimum of 6 interleaved runs) 85.3 77.1 (-9.6%)
oop, checked loop, ms 89.6 81.4 (-9.1%)
binarytrees, json, parse, instructions within ±0.5%

A new case in testdata/meta.lua, with its expected output from C Lua, covers:

  • adds past the constructor's size,
  • setting a deleted key again,
  • __newindex on absent and present keys,
  • a __ key added to a table that is in use as a metatable.

…directly

A SETFIELD that adds a key, as every field of a table just built, missed the
loop's hint and went through newindex, which looked the key up twice, hashed
it at run time and measured the table before and after. constSet probes with
the constant's compile-time hash and inserts when no resize is needed; other
cases still go to newindex. The loop itself is unchanged: an inline insert
there pushes fast past the inliner's big-function threshold.
@folbricht
folbricht merged commit d4f3ef8 into main Oct 20, 2026
17 checks passed
@folbricht
folbricht deleted the setfield-insert branch October 20, 2026 13:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant