@@ -50,9 +50,32 @@ the ordinary single-threaded hot trace unchanged.
5050After MT activation, supported table-length recording emits:
5151
5252- a generated-frame ` TMPREF IN1 ` ` TValue ` root for the table;
53- - a nonthrowing ` CALLS ` to ` lj_tab_len_rooted_try ()` ; and
53+ - on x64, a nonthrowing ` CALLS ` to ` lj_tab_len_forjit_try ()` ; and
5454- a guard that side exits when the result is ` LJ_TAB_LEN_RETRY ` .
5555
56+ The generated-only helper cross-checks the dispatch ` tg_hint ` against the
57+ universe-aware current TG, requires the exact ` tg->tmptv ` root, live JIT entry,
58+ physical actor and state ownership, and enters an owner-written table-vector
59+ epoch before acquiring either structural root. JLOOP keeps ` jit_base `
60+ published across ordinary generated calls, so destructive GC2 reclamation
61+ cannot begin and ` tmptv ` remains an enumerated exact root. The per-TG epoch
62+ independently prevents an old array or node generation from being reclaimed
63+ after it is captured. Root, paired generation, carrier, JIT and owner facts are
64+ all rechecked before returning the scalar result.
65+
66+ This narrower ABI removes the global SMR reader and counted table-body lease
67+ from the generated hot path. The general ` lj_tab_len_rooted_try() ` remains
68+ unchanged for adversarial C roots and non-x64 generated fallback; direct or
69+ interpreter calls to the x64 ABI fail closed.
70+
71+ The common dense/no-hash shape also has an exact bounded shortcut: after the
72+ paired snapshot it accepts ` asize-1 ` only when the final positive array slot is
73+ a valid non-nil Lua value, then performs the same paired-current check. Empty
74+ tables are exact zero. A boundary hole or any hash part uses the full widening
75+ and binary search; internal/malformed observations request retry. FINCLAIM
76+ classification and the owner-written epoch operations are inline on this hot
77+ path, while their existing external entry points remain available elsewhere.
78+
5679The side exit replays the current bytecode in the interpreter. The bounded
5780helper has made no Lua-visible mutation, so replay does not duplicate an
5881effect. This lowering is shared by direct table ` # ` , Lua 5.2-compatible
@@ -72,8 +95,10 @@ The focused C fixture covers:
7295- table-root replacement;
7396- exact-owner loss and an invalid owner;
7497- bounded generated-code retry, side exit, and interpreter replay; and
75- - balanced SMR, lease, root-descriptor, and dynamic-root-anchor accounting,
76- with no table wait-counter movement and no helper allocation.
98+ - direct-call rejection by the generated-only ABI;
99+ - exact nested table-read depth/epoch restoration; and
100+ - balanced SMR, vector epoch, lease, root-descriptor, and dynamic-root-anchor
101+ accounting, with no table wait-counter movement and no helper allocation.
77102
78103The JIT IR gate checks all live traces. It requires stock ` IR_ALEN ` and no
79104rooted helper before MT, then requires ` TMPREF IN1 ` plus the rooted ` CALLS ` and
@@ -104,11 +129,35 @@ correctness and hot-path performance.
104129The important common-case property is exact: pre-MT traces retain stock
105130` IR_ALEN ` , so this change adds no generated-code instruction to that path.
106131
107- A preliminary single-run active-MT microbenchmark measured roughly 64 ns per
108- ` #table ` , versus roughly 2.8 ns on the pre-MT stock lowering (about 23x). That
109- is an absolute bounded cost, not performance parity, and it remains explicit
110- optimization debt. The result is not a release benchmark or a substitute for
111- the required reproducible three-sample performance gate.
132+ A nine-sample, core-pinned 20-million-iteration microbenchmark measured
133+ 2.461 ns per pre-MT ` #table ` and 9.332 ns for the x64 active-MT generated
134+ path. The committed general rooted ABI measured 64.125 ns in the same run, so
135+ the generated-only epoch path is about 6.87x faster than the first bounded
136+ implementation and 3.79x the stock pre-MT lowering. A nearby active-MT rooted
137+ point read remained 64.361 ns, confirming that the length improvement is the
138+ removal of general lease/SMR machinery rather than a benchmark artifact.
139+
140+ This is a reproducible microbenchmark result, not yet the complete b1.2.1
141+ three-sample suite gate. The remaining roughly 6.9 ns absolute delta is explicit
142+ parity debt and the 3.79x ratio is still above the planned 3x gate, but the
143+ former 25x cliff is gone and incremental optimization is now realistic.
144+
145+ ## Linux validation
146+
147+ The final production-source diff fingerprint was
148+ ` 849fcdffb449db02d4803478a92c386f497a443ff33b4b0c19abe291b8814df7 ` .
149+ Clean focused M5 and M6 gates passed, followed by 20 repeated C-fixture runs
150+ and five repeated JIT-IR runs. A six-writer/two-GC-worker length-and-resize
151+ stress with 8,192 resize rounds and 4,096 observer rounds passed 30/30, and the
152+ broader resize, traced-read, weak-finalizer, remote-stack-GC and worker-
153+ activation matrix passed.
154+
155+ Both GCC and Clang built the helper/assertion profile cleanly with
156+ ` -Wall -Werror ` . Clang ASan and UBSan each passed a strict helper build, the
157+ focused fixture 20/20, the heavy concurrent stress 10/10, and a mixed
158+ length/JIT-read/weak-finalizer/remote-GC run. The normal default build was then
159+ restored. This evidence is deliberately Linux/x64-only; the complete macOS and
160+ Windows pass remains deferred until b1.2.1 is otherwise release-ready.
112161
113162## Remaining work
114163
@@ -123,5 +172,12 @@ mechanical proof that no ordinary length caller reaches a peer-dependent wait.
123172For structural mutation, the next larger table step remains a persistent,
124173helpable new-key publication descriptor covering collision-chain ` KEYLOCK `
125174and ` FINREG ` states, followed by resize/range descriptors. Those designs should
126- also provide the stable generation facts needed to replace the current
127- active-MT helper cost with cheaper validated fast paths.
175+ also provide the stable old/successor facts needed for exact interpreter
176+ completion while a resize owner is paused.
177+
178+ A yield-and-redispatch loop is not such a mechanism. During resize the current
179+ vectors remain ` RETIRING ` until owner-only migration completes, and a forwarded
180+ source value can exist only in the paused owner's C local before successor
181+ installation. Exact completion therefore requires a published resize/migration
182+ descriptor with value-preserving per-slot intent, helpable successor
183+ accounting, and idempotent root publication.
0 commit comments