the reference tables, the install guard and the vendored forks stop naming the maintainer's analysis

The notes and comments say what is true of the 1.12.1 client and cite the
byte address; the record numbers, the dates and the private repository's
file paths go.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-23 18:47:48 +07:00
co-authored by Claude Fable 5.1
parent 7e4cf3a054
commit 23c14aaec1
8 changed files with 192 additions and 279 deletions
+47 -69
View File
@@ -1,82 +1,67 @@
# benilla's `lua-src` fork — what differs from upstream, and how to check
# benilla's `lua-src` fork: what differs from upstream, and how to check
Upstream: [`mlua-rs/lua-src-rs`](https://github.com/mlua-rs/lua-src-rs), MIT, version `550.0.0` —
Upstream: [`mlua-rs/lua-src-rs`](https://github.com/mlua-rs/lua-src-rs), MIT, version `550.0.0`,
the version mlua 0.11 resolves to. Wired in through `[patch.crates-io]` in the workspace root.
## Why a fork exists at all
**benilla's Lua has to accept exactly the grammar the 1.12.1 client's Lua accepts — no more and no
less.** Every hunk below is that one rule: three restore 5.0 constructs 5.1 deleted, three delete
5.1 constructs 5.0 never had. A grammar difference in either direction is invisible to every
instrument we own until an addon trips over it, and the corpus *asks about* the difference
directly (decisions 1208, 2101).
benilla's Lua has to accept exactly the grammar the 1.12.1 client's Lua accepts, no more and no
less. Every hunk below is that one rule: three restore 5.0 constructs 5.1 deleted, three delete 5.1
constructs 5.0 never had. A grammar difference in either direction is invisible to every instrument
until an addon trips over it, and the addon corpus asks about the difference directly.
The 1.12 addon corpus is Lua **5.0** code. It uses the iterator-less generic-for:
The 1.12 addon corpus is Lua 5.0 code. It uses the iterator-less generic-for:
```lua
for k, v in someTable do ... end -- no pairs(), no iterator function
```
**183 of 218 corpus addons are reached by it** (118 carry it, 65 inherit it through a declared
dependency), across 1,163 sites. On stock Lua 5.1 it raises `attempt to call a table value`, and it
is the **first session-start error for 60 of 218 addons — three quarters of everything that breaks
once the client is actually running**, eleven times the next cause.
183 of 218 corpus addons are reached by it (118 carry it, 65 inherit it through a declared
dependency), across 1,163 sites. On stock Lua 5.1 it raises `attempt to call a table value`, the
first session-start error for 60 of the 218.
Lua 5.1 removed it at the **opcode** level, which is why no layer above the VM can reach it. Every
other dialect gap this project has closed was reachable from inside Lua — `table.setn` is a library
function, `bit` is a library, `print` is a global (decisions 1194, 1196). This one is not:
Lua 5.1 removed it at the opcode level, which is why no layer above the VM can reach it: a `__call`
metamethod would need a per-type default metatable for tables, which 5.1 does not have; rewriting
the chunk source means parsing Lua with a regex; and `lua-src` ships 5.1 through 5.5, every one of
which removed it.
- a `__call` metamethod would work, but the tables are the addon's own and Lua 5.1 has no
per-*type* default metatable for tables;
- rewriting the chunk source means parsing Lua with a regex to tell `in t do` from `in pairs(t) do`,
and getting that wrong changes behaviour silently;
- there is no `lua50` backend — `lua-src` ships 5.1 through 5.5 and every one of them removed it.
## What is not being done
## What is NOT being done
**benilla is not adopting Lua 5.0.** We stay on 5.1.5 and restore the behaviour of a single opcode
benilla is not adopting Lua 5.0. It stays on 5.1.5 and restores the behaviour of a single opcode
that Lua itself shipped and labelled `/* for compatibility only */`. Every other 5.1 fix, and the
whole of mlua's surface, stay exactly as upstream. That is a far smaller commitment than a version
downgrade, and it is the entire reason this is a one-hunk fork rather than a vendored 5.0.
whole of mlua's surface, stay exactly as upstream.
## The delta, and the command that proves it
**Current state: eight hunks, in three files.** `src/lib.rs` additionally differs by having Lua
5.2/5.3/5.4/5.5 stripped from the `Version` enum (with their source trees deleted) — benilla builds
5.1 and only 5.1, and a fork that still *offered* the other four would answer a request for one with
a missing directory at build time instead of a compile error.
Eight hunks in three files. `src/lib.rs` additionally differs by having Lua 5.2/5.3/5.4/5.5 stripped
from the `Version` enum (with their source trees deleted): benilla builds 5.1 and only 5.1, and a
fork that still offered the other four would answer a request for one with a missing directory at
build time instead of a compile error.
**Five hunks restore what 5.1 changed or removed:**
Five hunks restore what 5.1 changed or removed:
| file | hunk | why | record |
|---|---|---|---|
| `lvm.c` | 5.0's `OP_TFORPREP` table→`next` substitution, folded into the top of `OP_TFORLOOP` | `for k, v in someTable do` raised "attempt to call a table value"; it was the first session-start error for **60** corpus addons | 1215 |
| `luaconf.h` | `LUA_COMPAT_LSTR` `1` → `2` | 5.1 kept 5.0's nesting machinery and put an advisory error in front of it; two corpus addons died on "nesting of `[[...]]` is deprecated" | — |
| `luaconf.h` | `LUA_QL(x)` back to 5.0's `` `x' `` from 5.1's `'x'` | every Lua error message quotes a program element, and `WoW.exe`'s own `.rdata` carries all five formats in the 5.0 spelling (`` bad argument #%d to `%s' (%s) ``, `` attempt to %s %s `%s' (a %s value) ``, `` %s:%d: %s near `%s' `` at the client's `0x87217c`, …). ~80 corpus addons ship AceLibrary, whose `argCheck` parses its own caller back out of `debugstack()` with a pattern that requires the backquote | 2122 |
| `lparser.c` | 5.0's compat-semicolon skip restored at the top of `constructor()`'s field loop — 5.0's own line, verbatim, comment included | one extra `;` after a field separator inside a table constructor (`Back_Title = AL["Factions"];;`, ×20 in AtlasLoot's `ButtonRegistry.lua`) parses on the client and died here with "unexpected symbol near `;`"; statement-level `;;` stays rejected by both dialects | 1315 |
| `lparser.c` | `recfield`'s `cc->nh++` moved back INSIDE its `TK_NAME` arm — 5.0's own placement | a `[expr] = value` constructor field must credit **neither** `OP_NEWTABLE` size hint, so the table is born on the dummy node, the first store rehashes, and the dense integer keys land in an ARRAY part that `next` walks ascending. 5.1 pre-sizes a node vector instead and leaves them in the hash, where `next` is slot order — so every list in every saved-variables file came back scrambled (Bagnon's keyring drew first) | 2111 |
| file | hunk | why |
|---|---|---|
| `lvm.c` | 5.0's `OP_TFORPREP` table-to-`next` substitution, folded into the top of `OP_TFORLOOP` | `for k, v in someTable do` raised "attempt to call a table value" |
| `luaconf.h` | `LUA_COMPAT_LSTR` `1` to `2` | 5.1 kept 5.0's `[[...]]` nesting machinery and put an advisory error in front of it; two corpus addons died on "nesting of `[[...]]` is deprecated" |
| `luaconf.h` | `LUA_QL(x)` back to 5.0's `` `x' `` from 5.1's `'x'` | every Lua error message quotes a program element, and `WoW.exe`'s own `.rdata` carries all five formats in the 5.0 spelling |
| `lparser.c` | 5.0's compat-semicolon skip restored at the top of `constructor()`'s field loop, 5.0's own line verbatim | one extra `;` after a field separator inside a table constructor (`Back_Title = AL["Factions"];;`, which AtlasLoot writes) is accepted by the client and was a parse error here |
| `lparser.c` | `recfield`'s `cc->nh++` moved back inside its `TK_NAME` arm, 5.0's own placement | a `[expr] = value` constructor field credits neither `OP_NEWTABLE` size hint in 5.0, so the table is born on the dummy node |
**Three delete what 5.1 added** — all in `lparser.c`, all byte-read out of the client's own parser
Three delete what 5.1 added, all in `lparser.c`, all byte-read out of the client's own parser
(`simpleexp 0x6fd240`, `getunopr 0x6fe0a0`, `getbinopr 0x6fe0c0`), all landing on 5.0's own
`unexpected symbol` at `prefixexp 0x6fde40`:
| hunk | why | record |
|---|---|---|
| `simpleexp`'s `case TK_DOTS` deleted — `...` is no longer an expression | the Ace2 corpus **asks** which interpreter it is on by compiling `return function(...) return ... end`; 170 `loadstring` sites in the 219-addon corpus are that question and 92 library files gate on it. Answering `true` put Cartographer on its client-2.0 branch, hooking `CloseSpecialWindows` — a name 1.12 does not have. Also restores 5.0's rule that **every** vararg function gets its `arg` table | 1208, 2101 |
| `getunopr`'s `case '#'` deleted — no length operator | the client's `getunopr` tests exactly two tokens (`-`, `not`) and its `OPR_NOUNOPR` is **2**: a three-member enum, 5.0's. 5.0 asks a table with `table.getn` and a string with `string.len` | 2101 |
| `getbinopr`'s `case '%'` deleted — no modulo operator | the client's `getbinopr` switch is based at `'*'` (0x2A), so `%` (0x25) is below its range and reaches `OPR_NOBINOPR` = **14**: a fifteen-member `BinOpr`, 5.0's. 5.0 spells it `math.mod` | 2101 |
| hunk | why |
|---|---|
| `simpleexp`'s `case TK_DOTS` deleted: `...` is not an expression | the Ace2 corpus asks which interpreter it is on by compiling `return function(...) return ... end`; 170 `loadstring` sites in the corpus are that question, and 92 library files in 24 folders branch on the answer |
| `getunopr`'s `case '#'` deleted: no length operator | the client's `getunopr` tests exactly two tokens (`-`, `not`) and its `OPR_NOUNOPR` is 2, a three-member enum; 5.0 asks a table with `table.getn` and a string with `string.len` |
| `getbinopr`'s `case '%'` deleted: no modulo operator | the client's `getbinopr` switch is based at `'*'` (0x2A), so `%` (0x25) is below its range and reaches `OPR_NOBINOPR` = 14, a fifteen-member `BinOpr`; 5.0 spells it `math.mod` |
The ordering was deliberate and it held: the fork landed **inert**, byte-identical to upstream, so
the build pipeline was proven transparent *before* any behavioural payload went into it — and when
the suite then moved, the patch was the only thing that could have moved it. Every hunk is measured
against the corpus rather than argued: the long-string one alone took loaded 134 → 139 and
survivors 99 → 104.
**The deletions are safe because nothing the reference runs uses those constructs, and that was
scanned rather than assumed** (2101 §4): a comment/string-stripping scan of the extracted 1.12
FrameXML (177 files), GlueXML, Blizzard's own addons and the director's installed AddOns folder
finds **zero** sites of all three. Our own engine Lua did use them and was rewritten into the 5.0
spellings; the rule now is that no chunk of ours may use a construct the client's parser rejects.
The deletions are safe because nothing the reference runs uses those constructs: a comment- and
string-stripping scan of the 1.12 FrameXML (177 files), GlueXML, Blizzard's own addons and the
corpus finds zero sites of all three. No chunk of benilla's own Lua may use a construct the client's
parser rejects.
To verify the Lua sources against upstream at any time:
@@ -86,20 +71,13 @@ diff -r third_party/lua-src/lua-5.1.5 \
~/.cargo/registry/src/*/lua-src-550.0.0/lua-5.1.5
```
That must print **exactly** the eight hunks in the tables above and nothing else. If it ever prints
more, something crept in.
That must print exactly the eight hunks in the tables above and nothing else.
## Status of the payload
## The generic-for, in detail
**Landed.** The reverse-engineering answer this was deliberately blocked on came back from
`wow-5875-re` (`system/ui/scratch/lua-generic-for.md`) and is what the `lvm.c` comment cites, detail
by detail: a **bare type-tag** test that never consults a metatable (so a table carrying `__call`
still gets `next`), the **global** `next` read raw and fetched fresh at every loop entry (so an addon
assigning `next = myfn` changes every later generic-for in the session), and userdata *not*
substituted.
Waiting was the right call twice over. Checking stock 5.0 first corrected two assumptions the
project had already written down — the substitution happens in `OP_TFORPREP`, an opcode 5.1 deleted,
*not* in `OP_TFORLOOP`; and the test is a bare `ttistable` with no `__call` check. Stock 5.0 was
corroboration, never the authority: the client links 5.0 but may be modified, and only the byte-read
of the client settles what it actually does.
The `lvm.c` comment carries the three details that are the client's own, each byte-read there: the
substitution test is a bare type-tag equality that never consults a metatable (a table carrying
`__call` still gets `next`); the callee is the global `next`, read raw and fetched fresh at every loop
entry (an addon assigning `next = myfn` changes every later generic-for in the session); and userdata
is not substituted. The substitution happens in `OP_TFORPREP`, an opcode 5.1 deleted, so the top of
`OP_TFORLOOP` is where 5.1 can host it.
+46 -61
View File
@@ -509,22 +509,17 @@ static void constructor (LexState *ls, expdesc *t) {
checknext(ls, '{');
do {
lua_assert(cc.v.k == VVOID || cc.tostore > 0);
/* BENILLA: Lua 5.0's constructor compat-semicolon, restored (decision 1315;
** see third_party/lua-src/BENILLA.md). 5.0 opened this loop with exactly the
** line below -- its own comment read "compatibility only", a Lua 4.0 leftover
** (4.0 separated a constructor's list part from its record part with `;`).
** 5.1 deleted the line, so one extra `;` after a field separator -- the
** `Back_Title = AL["Factions"];;` shape AtlasLoot's ButtonRegistry.lua writes
** twenty times -- became "unexpected symbol near ';'" here while the 1.12.1
** client (Lua 5.0) accepts it. Statement-level `;;` stays rejected by both,
** which is the half wow-5875-re byte-verified first (system/ui/scratch/
** lua-dialect.md 9.1), and byte-verified in the client itself rather than
** inferred from stock 5.0: constructor is 0x6fd430, and its loop head
** 0x6fd4a0 calls testnext(ls,';') WITHOUT testing EAX -- a skip, not a
** separator check -- while the loop's continuation at 0x6fd4f5/0x6fd505 does
** test it. Of the 16 call sites of testnext 0x6fccf0 image-wide, the only
** other consumer of `;' is chunk 0x6fcccc, at STATEMENT level, with no such
** head skip. This line restores the constructor context only. */
/* BENILLA: Lua 5.0's constructor compat-semicolon, restored (see
** third_party/lua-src/BENILLA.md). 5.0 opened this loop with exactly the
** line below, a Lua 4.0 leftover; 5.1 deleted it, so one extra `;' after a
** field separator (`Back_Title = AL["Factions"];;', which AtlasLoot writes)
** raised "unexpected symbol near ';'" while the 1.12.1 client accepts it.
** Statement-level `;;' stays rejected by both. Byte-read in the client:
** constructor is 0x6fd430, and its loop head 0x6fd4a0 calls testnext(ls,';')
** without testing EAX (a skip, not a separator check), while the loop's
** continuation at 0x6fd4f5/0x6fd505 does test it; of testnext 0x6fccf0's 16
** call sites, the only other `;' consumer is chunk 0x6fcccc, at statement
** level, with no such head skip. */
testnext(ls, ';'); /* compatibility only */
if (ls->t.token == '}') break;
closelistfield(fs, &cc);
@@ -766,39 +761,32 @@ static void simpleexp (LexState *ls, expdesc *v) {
init_exp(v, VFALSE, 0);
break;
}
/* BENILLA: 5.1's vararg EXPRESSION arm, deleted (decision 2101; see
/* BENILLA: 5.1's vararg EXPRESSION arm, deleted (see
** third_party/lua-src/BENILLA.md). Lua 5.0 has no grammar for `...' as a
** value -- a vararg function reads its extra arguments out of the implicit
** `arg' table -- and `...' as an expression is the construct the Ace2 corpus
** uses to ASK which interpreter it is running:
** value: a vararg function reads its extra arguments out of the implicit
** `arg' table. `...' as an expression is how the Ace2 corpus asks which
** interpreter it is on:
**
** local lua51 = loadstring("return function(...) return ... end") and true or false
**
** 170 loadstring sites across the 219-addon corpus are that question, and 92
** library files in 24 folders gate on it (decision 1208). Answering it wrong
** makes every one of them take a client-2.0 branch on a 1.12 client -- which
** is how Cartographer came to hook `CloseSpecialWindows', a name 1.12 does
** not have.
** and 92 library files in 24 folders branch on the answer, taking a
** client-2.0 path on a 1.12 client when it is wrong.
**
** Byte-verified in the client itself, not inferred from stock 5.0. simpleexp
** is 0x6fd240; it switches on `token - 0x7B' bounded at 0xA3, indexes the
** byte table at 0x6fd338 and jumps through the 8-entry table at 0x6fd318.
** TK_DOTS is 0x118 (TK_NAME is 0x116 -- wow-5875-re system/ui/scratch/
** lua-dialect.md 9), so its index byte is 0x9D, whose value is 7: the DEFAULT
** arm, 0x6fd30a, which tail-jumps to primaryexp 0x6fdb00. The seven real arms
** are `{', TK_FALSE, TK_FUNCTION, TK_NIL, TK_TRUE, TK_NUMBER and TK_STRING --
** stock Lua 5.0's simpleexp exactly, with no vararg case.
** Byte-read in the client: simpleexp is 0x6fd240; it switches on
** `token - 0x7B' bounded at 0xA3, indexes the byte table at 0x6fd338 and
** jumps through the 8-entry table at 0x6fd318. TK_DOTS is 0x118, so its
** index byte is 0x9D, whose value is 7: the DEFAULT arm, 0x6fd30a, which
** tail-jumps to primaryexp 0x6fdb00. The seven real arms are `{', TK_FALSE,
** TK_FUNCTION, TK_NIL, TK_TRUE, TK_NUMBER and TK_STRING: stock Lua 5.0's
** simpleexp exactly, with no vararg case. So `...' as a value reaches
** prefixexp 0x6fde40, whose head accepts only `(' or TK_NAME and otherwise
** raises "unexpected symbol" (0x871e80); deleting the arm reproduces that
** error here, verbatim.
**
** So `...' as a value reaches prefixexp 0x6fde40, whose head accepts only `('
** or TK_NAME and otherwise raises "unexpected symbol" (0x871e80) -- the same
** reject site lua-dialect.md 9 documents for a stray `;'. Deleting the arm
** reproduces that error here, verbatim.
**
** It also restores 5.0's `arg' rule for free: the deleted line cleared
** VARARG_NEEDSARG for any function that mentioned `...', so `arg' was not
** built for it. With the arm gone, NEEDSARG is never cleared and EVERY vararg
** function gets its `arg' table -- which is what 5.0 does and what all 177
** files of the shipped 1.12 FrameXML read. */
** It also restores 5.0's `arg' rule: the deleted line cleared
** VARARG_NEEDSARG for any function that mentioned `...', so with the arm
** gone every vararg function gets its `arg' table, which is what 5.0 does
** and what the shipped 1.12 FrameXML reads. */
case '{': { /* constructor */
constructor(ls, v);
return;
@@ -821,15 +809,14 @@ static UnOpr getunopr (int op) {
switch (op) {
case TK_NOT: return OPR_NOT;
case '-': return OPR_MINUS;
/* BENILLA: 5.1's `#' length operator, deleted (decision 2101). 5.0 asks a
** table for its size with table.getn/getn and a string with string.len; the
** operator does not exist and neither does its __len metamethod.
** Byte-verified: getunopr is 0x6fe0a0, a leaf with exactly TWO token tests
** -- `cmp ecx,0x2D' (`-') then `cmp ecx,0x10E' (TK_NOT), then setne/inc -- so
** its OPR_NOUNOPR is 2, i.e. the enum has three members, not 5.1's four. The
** metamethod-name pool at 0x871896 agrees: no __len (wow-5875-re
** system/ui/scratch/lua-dialect.md 1, discriminator 4). A `#' therefore
** reaches simpleexp's default arm and raises "unexpected symbol near `#'". */
/* BENILLA: 5.1's `#' length operator, deleted. 5.0 asks a table for its
** size with table.getn and a string with string.len; the operator does not
** exist and neither does its __len metamethod. Byte-read in the client:
** getunopr is 0x6fe0a0, a leaf with exactly two token tests, `cmp ecx,0x2D'
** (`-') then `cmp ecx,0x10E' (TK_NOT), so its OPR_NOUNOPR is 2: a
** three-member enum, not 5.1's four. The metamethod-name pool at 0x871896
** has no __len. A `#' therefore reaches simpleexp's default arm and raises
** "unexpected symbol near `#'". */
default: return OPR_NOUNOPR;
}
}
@@ -841,16 +828,14 @@ static BinOpr getbinopr (int op) {
case '-': return OPR_SUB;
case '*': return OPR_MUL;
case '/': return OPR_DIV;
/* BENILLA: 5.1's `%' modulo operator, deleted (decision 2101). 5.0 spells it
** math.mod (and the bare global `mod'), which this VM already publishes.
** Byte-verified: getbinopr is 0x6fe0c0 and its switch is based at `ecx-0x2A'
** -- `*', the LOWEST token it handles. `%' is 0x25, below the base, so it is
/* BENILLA: 5.1's `%' modulo operator, deleted. 5.0 spells it math.mod
** (and the bare global `mod'), which this VM publishes. Byte-read in the
** client: getbinopr is 0x6fe0c0 and its switch is based at `ecx-0x2A' (`*',
** the lowest token it handles). `%' is 0x25, below the base, so it is
** outside the range test (`cmp eax,0xF2; ja') and falls to the default,
** 0x6fe129, `mov eax,0xE' = OPR_NOBINOPR 14 -- a 15-member BinOpr, 5.0's, not
** 5.1's 16-member one with OPR_MOD. Had 5.1's `%' case been present the
** switch would have been based at 0x25. The metamethod pool has no __mod
** either (wow-5875-re system/ui/scratch/lua-dialect.md 1, discriminator 4).
** priority[] is indexed by the BinOpr enum, which is untouched -- only the
** 0x6fe129, `mov eax,0xE' = OPR_NOBINOPR 14: a 15-member BinOpr, 5.0's, not
** 5.1's 16-member one with OPR_MOD. The metamethod pool has no __mod either.
** priority[] is indexed by the BinOpr enum, which is untouched; only the
** token that reaches OPR_MOD is gone. */
case '^': return OPR_POW;
case TK_CONCAT: return OPR_CONCAT;
+3 -4
View File
@@ -201,7 +201,7 @@
** CHANGE it if you want a different appearance.
*/
/*
** BENILLA (decision 2122): 1.12's Lua is 5.0, which quotes every program element in an error
** BENILLA: 1.12's Lua is 5.0, which quotes every program element in an error
** message with a BACKQUOTE and an apostrophe. 5.1 introduced this macro and made it two
** apostrophes. Read out of WoW.exe's own .rdata, which carries all five of the formats this macro
** feeds, in the 5.0 spelling:
@@ -368,9 +368,8 @@
** off the advisory error when nesting [[...]].
*/
/* BENILLA: 2, the 5.0 behaviour. 1.12.1 ships Lua 5.0, where `[[ ... [[ ... ]] ... ]]`
** nests; 5.1 kept the machinery and put an advisory error in front of it. Two corpus
** addons stop loading on "nesting of [[...]] is deprecated" — a 5.1 opinion about a
** dialect we are not targeting. Same family as the OP_TFORPREP restoration (1215).
** nests; 5.1 kept the machinery and put an advisory error in front of it, on which two
** corpus addons stop loading ("nesting of [[...]] is deprecated").
*/
#define LUA_COMPAT_LSTR 2
+22 -29
View File
@@ -680,42 +680,35 @@ void luaV_execute (lua_State *L, int nexeccalls) {
}
case OP_TFORLOOP: {
StkId cb = ra + 3; /* call base */
/* BENILLA: Lua 5.0's OP_TFORPREP, folded into the loop opcode.
** See third_party/lua-src/BENILLA.md and benilla decision 1215.
/* BENILLA: Lua 5.0's OP_TFORPREP, folded into the loop opcode (see
** third_party/lua-src/BENILLA.md).
**
** 5.0 ran this ONCE at loop entry in its own opcode (index 30, 157 bytes, verified in
** the 1.12.1 client at 0x6f94a2): when a generic-for's generator is a table, the table
** becomes the STATE and the generator becomes the global `next`. 5.1 deleted the opcode,
** so `for k, v in someTable do` -- which 183 of 218 corpus addons are reached by, and
** which is the first session-start error for 60 of them -- raises "attempt to call a
** table value".
** 5.0 ran this once at loop entry in its own opcode (index 30, 157 bytes, at
** 0x6f94a2 in the 1.12.1 client): when a generic-for's generator is a table, the
** table becomes the STATE and the generator becomes the global `next`. 5.1 deleted
** the opcode, so `for k, v in someTable do`, which most 1.12 addons use, raised
** "attempt to call a table value".
**
** Placing it here is timing-exact rather than approximate: 5.1's parser emits a JMP
** straight to this instruction, so the top of TFORLOOP IS 5.0's prep point, before the
** first call. It is self-clearing -- after one pass `ra` holds a function, so the test
** fails on every later iteration -- which is what makes an entry-only rule safe to
** express as a per-iteration check.
** The top of TFORLOOP is 5.0's prep point exactly: 5.1's parser emits a JMP straight
** to this instruction, before the first call. The check is self-clearing, since after
** one pass `ra` holds a function, so an entry-only rule is safe as a per-iteration
** test.
**
** Three details are the client's, not conveniences, each byte-verified in wow-5875-re
** system/ui/scratch/lua-generic-for.md:
** - the test is a BARE TYPE TAG equality. The handler makes no metatable access at
** all, so a table carrying __call still gets `next` and never reaches tryfuncTM.
** "not callable" and "no __call" are different conditions and both diverge here.
** - the callee is the GLOBAL `next`, read RAW (no __index) from the thread's own
** Three details are the client's, each byte-read there:
** - the test is a BARE TYPE TAG equality. The handler makes no metatable access, so
** a table carrying __call still gets `next` and never reaches tryfuncTM.
** - the callee is the GLOBAL `next`, read raw (no __index) from the thread's own
** globals table, fetched fresh at every loop entry. An addon that assigns
** `next = myfn` therefore changes every later generic-for in the session. That is
** observable, and it is reproduced rather than improved on.
** `next = myfn` therefore changes every later generic-for in the session.
** - userdata is NOT substituted (tag mismatch), so it still reaches __call, and a
** nil/string/number generator still raises on iteration 1, not at entry.
**
** KNOWN, MEASURED DIVERGENCE THIS DOES NOT CLOSE. 5.0's generic-for has two hidden
** locals, not three: ra+2 is the first user variable AND the control, so assigning to it
** inside the body changes what the next iteration is asked for. 5.1 gives the control
** its own slot, and this patch does not move it -- that is a register-layout property
** owned by the code generator, not by this handler. Measured before shipping
** (scripts/generic-for-control-census.py): 95 addons contain the shape, concentrated in
** two files nearly everything embeds -- Dewdrop-2.0.lua (123 sites) and AceEvent-2.0.lua
** (113). Recorded rather than hidden; closing it is a separate change.
** Deviation from 1.12 this does not close: 5.0's generic-for has two hidden locals,
** not three, so ra+2 is the first user variable AND the control, and assigning to it
** inside the body changes what the next iteration asks for. 5.1 gives the control its
** own slot, a register-layout property of the code generator, not of this handler.
** 95 corpus addons contain the shape, concentrated in Dewdrop-2.0.lua (123 sites) and
** AceEvent-2.0.lua (113).
*/
if (ttistable(ra)) {
setobjs2s(L, ra+1, ra);