Lowering: LEA.HI with a constant base and no carry (40 vertex programs drawn again) #1437

Merged
Joey-Magnus merged 1 commit from fix/lea-hi-constant into main 2026-11-10 04:37:32 +00:00
Joey-Magnus commented 2026-11-10 02:28:01 +00:00 (Migrated from github.com)

What

LEA.HI Rd, Ra, c[bank][offset], RZ, s with no .X and no carry out (native op 0xa11) stopped the Ampere -> Maxwell lowering with "native constant LEA carry mode". A program that does not lower is never drawn, so 40 of OoT's vertex programs were missing. All three failing encodings in the log have words[2] = 0x078fd8ff, for example 0d007a11 00c03100 078fd8ff 000fe400 (programs e9274029dc836a07, 2d279e3ea5e7499f and others).

The instruction is the constant plus the high word of (Ra << s), that is c + (Ra >> (32 - s)). It now lowers the same way as the register form's high word (0x211):

  • the constant goes into R251, as every constant operand does (operand(1, false));
  • SHR R250 = Ra >> (32 - s), arithmetic when .SX32 (bit 73) is set;
  • IADD Rd = R250 + R251.

LEA.HI.X.SX32 keeps its existing lowering. A carry out, a high source (Rc not RZ) or a zero shift is still refused.

Tested

  • --prepare-shaders on the full shader list (Linux, RTX 4070 Ti, fresh caches): 83 -> 43 programs that do not lower. All 40 "native constant LEA carry mode" programs now lower and compile, and the other failure kinds are unchanged (17 formatted surface load, 3 integer min/max, 3 texel fetch, 3 scratch registers, and so on).
  • In game: the Water Temple save loads and draws normally with the new hookshot_vk.so.
  • tests/test_lower_lea_hi_constant.py (new, registered in run_all.py like the ATOMG test) checks the encodings from the log, an .SX32 case and the forms that must still be refused. It fails without this change. The full tests/run_all.py passes: 190 passed, 19 skipped for missing tools.

🤖 Generated with Claude Code

## What `LEA.HI Rd, Ra, c[bank][offset], RZ, s` with no `.X` and no carry out (native op `0xa11`) stopped the Ampere -> Maxwell lowering with "native constant LEA carry mode". A program that does not lower is never drawn, so 40 of OoT's vertex programs were missing. All three failing encodings in the log have `words[2] = 0x078fd8ff`, for example `0d007a11 00c03100 078fd8ff 000fe400` (programs e9274029dc836a07, 2d279e3ea5e7499f and others). The instruction is the constant plus the high word of `(Ra << s)`, that is `c + (Ra >> (32 - s))`. It now lowers the same way as the register form's high word (`0x211`): - the constant goes into R251, as every constant operand does (`operand(1, false)`); - `SHR R250 = Ra >> (32 - s)`, arithmetic when `.SX32` (bit 73) is set; - `IADD Rd = R250 + R251`. `LEA.HI.X.SX32` keeps its existing lowering. A carry out, a high source (Rc not RZ) or a zero shift is still refused. ## Tested - `--prepare-shaders` on the full shader list (Linux, RTX 4070 Ti, fresh caches): **83 -> 43** programs that do not lower. All 40 "native constant LEA carry mode" programs now lower and compile, and the other failure kinds are unchanged (17 formatted surface load, 3 integer min/max, 3 texel fetch, 3 scratch registers, and so on). - In game: the Water Temple save loads and draws normally with the new `hookshot_vk.so`. - `tests/test_lower_lea_hi_constant.py` (new, registered in `run_all.py` like the ATOMG test) checks the encodings from the log, an `.SX32` case and the forms that must still be refused. It fails without this change. The full `tests/run_all.py` passes: 190 passed, 19 skipped for missing tools. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
NotRexed commented 2026-11-10 03:49:41 +00:00 (Migrated from github.com)

This landed in the v2.0.12 branch (merged there for testing; it reaches main with that release), so closing. Thanks!

This landed in the v2.0.12 branch (merged there for testing; it reaches `main` with that release), so closing. Thanks!
NotRexed commented 2026-11-10 03:55:36 +00:00 (Migrated from github.com)

Reopened: closing this was a mistake on my side. It was tested in the v2.0.12 branch, and it will be merged into main here, so it shows as merged. Sorry for the noise.

Reopened: closing this was a mistake on my side. It was tested in the v2.0.12 branch, and it will be merged into main here, so it shows as merged. Sorry for the noise.
hckrman101 (Migrated from github.com) approved these changes 2026-11-10 04:17:55 +00:00
hckrman101 (Migrated from github.com) left a comment

Thanks, this looks right to me.

I decoded the log encoding (words[2] = 0x078fd8ff): Rc = RZ, bit 80 HI set, bits 81-83 = PT (no carry out), no .X (74), no .SX32 (73), s = 0x1b, and the carry-in field (87-90) is the unused !PT. With Rc = RZ the high word of (Rc:Ra) << s is Ra >> (32 - s), logical, and with .SX32 the implied high half is Ra's sign, so it becomes the arithmetic shift. That matches what the 0x211 high-word path and the existing LEA.HI.X.SX32 branch already emit. s is limited to 1..31 by the != 0 check, so 32 - s stays in 1..31 for the Maxwell SHR immediate, and the IADD wraps at 32 bits like the native add. No predicate is written: anything with a carry out (or .X, a non-RZ Rc, bit 72, or bits 62-63 negate/abs on the constant) misses the mask and still falls through to the refusal, because bit 80 is set. Ra is read by the SHR before Rd is written, so Rd == Ra is fine.

The test checks the right things (the two log encodings, the .SX32 decode with s = 8 -> SHR 24, .X.SX32 still taking its own branch, and the three refusals), and it's registered in ARGS in tests/run_all.py.

Nit: the case 0xa11 header comment still says "LEA with constant base and carry predicate", and this branch has neither. Could say "LEA / LEA.HI with constant base" now.

CI was still pending when I looked.

Thanks, this looks right to me. I decoded the log encoding (`words[2] = 0x078fd8ff`): Rc = RZ, bit 80 HI set, bits 81-83 = PT (no carry out), no .X (74), no .SX32 (73), s = 0x1b, and the carry-in field (87-90) is the unused !PT. With Rc = RZ the high word of `(Rc:Ra) << s` is `Ra >> (32 - s)`, logical, and with .SX32 the implied high half is Ra's sign, so it becomes the arithmetic shift. That matches what the 0x211 high-word path and the existing LEA.HI.X.SX32 branch already emit. s is limited to 1..31 by the `!= 0` check, so `32 - s` stays in 1..31 for the Maxwell SHR immediate, and the IADD wraps at 32 bits like the native add. No predicate is written: anything with a carry out (or .X, a non-RZ Rc, bit 72, or bits 62-63 negate/abs on the constant) misses the mask and still falls through to the refusal, because bit 80 is set. Ra is read by the SHR before Rd is written, so `Rd == Ra` is fine. The test checks the right things (the two log encodings, the .SX32 decode with s = 8 -> SHR 24, .X.SX32 still taking its own branch, and the three refusals), and it's registered in `ARGS` in `tests/run_all.py`. Nit: the `case 0xa11` header comment still says "LEA with constant base and carry predicate", and this branch has neither. Could say "LEA / LEA.HI with constant base" now. CI was still pending when I looked.
Commenting is not possible because the repository is archived.
No description provided.