From c513e1fbbdcf659533cbaff040769d724c31936c Mon Sep 17 00:00:00 2001 From: Drew T <50529377+Druthulu@users.noreply.github.com> Date: Fri, 4 Sep 2026 23:28:59 -0600 Subject: [PATCH] =?UTF-8?q?feat(phase-32):=20T1a=20(2)=20=E2=80=94=20resid?= =?UTF-8?q?ent:=20func=5F800D128C=20(243=20ins)=20BANKED=20byte-identical?= =?UTF-8?q?=208e17e02f=20via=20the=20raw=20splice=20+=20a=205-piece=20carv?= =?UTF-8?q?e;=20three=20instrument=20fixes=20(=C2=A7498)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BANK: the stored S71 closeness-0 draft spliced into src/resident/resident_jr_800D128C.c; jtbl_carve --func carved jtbl_80113FB8 (119 entries, 1 pad word trimmed) + jtbl_80114198 into [0x451c0, .rodata, resident_jr_800D128C] + [0x453c4, data, tail3]; JTBL_PADS 0,4; make extract + make build BINARY=resident -j8 rc 0, sha 8e17e02ff8954d07c979449198f7e1645046b353 == check (R53). pads_audit ok/ok; interleave_check ALIGNED n=5; verbatim_check --strict 5==5. Resident stubs 2 -> 1 (func_800D06E8 remains). - WHY THE GATE SAID DIFF (parallel_gate banked 0/DIFF on an rtu_match MATCH): jtbl_carve.set_overlays_var regenerated resident_JTBL_INTERLEAVE from the carve set and DROPPED the resident's `--pre hdr.rodata.o` (§8f leading-rodata sandwich); make extract refused (ld_interleave: hdr.rodata.o would be parked with .text), the build linked the STALE script (249,252 differing bytes from file offset 0x4), and harvest_verify._jtbl_prep_one never read the post-carve extract's exit code (R49/R61). - FIXES (R35/R40/R57): jtbl_carve._merge_pre carries an existing --pre forward (idempotent; overlays unchanged, 4-shape unit control); harvest_verify refuses loudly on a failed post-carve extract and restores the snapshot (CARVE refusal, NOT a draft verdict); interleave_check's anchor accepts a leading --pre (was a false DRIFT n=0 on the resident; control ov_SC02_017 ALIGNED n=44 unchanged). - cookbook §498 (+ the stale-asm-after-a-failed-extract sequencing law); SETUP rows for all three --- config/overlays.mk | 5 +- config/splat.resident.yaml | 6 +- docs/SETUP.md | 3 + docs/cookbook-index.md | 10 +- docs/matching-cookbook.md | 30 ++++ src/resident/resident_jr_800D128C.c | 253 +++++++++++++++++++++++++++- tools/harvest_verify.py | 14 +- tools/interleave_check.py | 4 +- tools/jtbl_carve.py | 17 ++ 9 files changed, 332 insertions(+), 10 deletions(-) diff --git a/config/overlays.mk b/config/overlays.mk index 19d3dec9a..247842731 100644 --- a/config/overlays.mk +++ b/config/overlays.mk @@ -5124,6 +5124,7 @@ ov_SC03_107_UNDEF_SYMS := build/ov_SC03_107/undefined_syms_auto.txt ov_SC03_107_UNDEF_FUNCS := build/ov_SC03_107/undefined_funcs_auto.txt # --- resident (engine blob) — §8e jtbl pad spec for the 0x450e0..0x451ac .rodata carve --- -build/src/resident/resident_jr_800D00E4.o: JTBL_PADS := 0,0,0 # §8e pads (jtbl_carve.py) tables=+0x0,+0x3c,+0xb4 # --- resident (engine blob) — §8f leading-rodata sandwich: rodata(hdr) -> text -> data -> rodata(carve) -> data --- -resident_JTBL_INTERLEAVE := --pre hdr.rodata.o --order tail.data.o,resident_jr_800D00E4.o,tail2.data.o # Phase-26 §8 jtbl-rodata carve +resident_JTBL_INTERLEAVE := --pre hdr.rodata.o --order tail.data.o,resident_jr_800D00E4.o,tail2.data.o,resident_jr_800D128C.o,tail3.data.o # Phase-26 §8 jtbl-rodata carve +build/src/resident/resident_jr_800D00E4.o: JTBL_PADS := 0,0,0 # §8e pads (jtbl_carve.py) tables=+0x0,+0x3c,+0xb4 +build/src/resident/resident_jr_800D128C.o: JTBL_PADS := 0,4 # §8e pads (jtbl_carve.py) tables=+0x0,+0x1e0 diff --git a/config/splat.resident.yaml b/config/splat.resident.yaml index 94d3e6360..a286fc2f5 100644 --- a/config/splat.resident.yaml +++ b/config/splat.resident.yaml @@ -86,7 +86,9 @@ segments: - [0x4, c, resident] - [0x12ec, c, resident_jr_800D00E4] - [0x2494, c, resident_jr_800D128C] - - [0x4610, data, tail] # data tail: vram 0x800D3408..0x80113ED8 (pre-carve) + - [0x4610, data, tail] - [0x450e0, .rodata, resident_jr_800D00E4] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py) - - [0x451ac, data, tail2] # post-carve data tail -> EOF + - [0x451ac, data, tail2] + - [0x451c0, .rodata, resident_jr_800D128C] # Phase-26 §8 jtbl-rodata carve (jtbl_carve.py) + - [0x453c4, data, tail3] - [0x5935C] # EOF (365,404 B) -> end vram 0x80128154 diff --git a/docs/SETUP.md b/docs/SETUP.md index 43321cb50..cf7e5ad48 100644 --- a/docs/SETUP.md +++ b/docs/SETUP.md @@ -781,6 +781,9 @@ Every script under `tools/` (plus the two report make-targets), grouped by purpo | | `tools/dedup_propagate.py` — `_split_masked` memo | **(P31 S75)** `find_site` re-ran `cdecl._mask` (4 regex passes, one `re.S`) + two `splitlines()` over the ENTIRE concatenated source on EVERY call, though the mask is a pure function of the text and the caller loops over every function in the binary. Measured on `ov_SC01_005` (2.50 MB, 2,503 fns): 6.5 + 38.9 + 4.6 = **54 ms per call before any searching**. Memoized via `lru_cache(maxsize=8)` — callers already hold one text object per binary and CPython caches a str's hash, so a repeat lookup is a pointer compare. **2x on that loop (58.3 -> 33.0 ms/call), NC identical results on 120 addresses.** Honest scope: that loop is only ~2.4 min of a 30-min run; the profiler puts 43% of `--check-only` in `family_remap._alias_decl_for` (107 s / 1,312 calls), which is the real target and is NOT fixed here. | | | `tools/main_diff_locate.py` — `classify()` | **(P31 S75, cookbook §447)** The `TABLE REJECT` class was **unreachable for main** and demanded purity. (a) It summed bytes whose object string contains `(.rodata)`, but main's `section_order` is `[.rodata, .text, .data, .bss]` — its rodata sits BELOW `.text` and its jump tables live in `.data` objects, so the test could never fire on the one binary with the most jump-table functions left. Now matches `(.data)` OR `(.rodata)`. (b) It required `ro == outside`, so a few bytes of perturbed code dropped the verdict through to `PLUMBING REJECT` and its §376 declaration advice. Now **dominance-based** (≥60%), reporting the split and naming which part is the table problem and which the declaration problem. Measured on `SaveLoadRoutine` (1,165 ins, the §434 wall): body BYTE-IDENTICAL, 3,787 of 3,989 differing bytes (94.9%) in `.data` jump tables, 202 (5.1%) in `.text`, built image 4 bytes SHORT — verdict moved `PLUMBING REJECT` → `TABLE REJECT (MIXED)`. The §376 chain had been run on it twice and fixed nothing, because it addresses the 5%. NC over all five pre-existing verdict shapes: 5 of 6 unchanged. | | | **`tools/asm_in_c.py`** (NEW) | **(P31 S75, cookbook §448)** Finds every function that is **assembly posing as C** — a §265 file-scope `__asm__` body (class A) or a C function whose body is only asm statements (class B) — while correctly EXCLUDING the §3a cross-jump barrier, which is what `__asm__` means in 3,182 of the 4,224 sources. Measured: **199 functions, 154 of them GAME CODE, 171 in main**, largest `SaveLoadRoutine` (1,165 ins). These were in NO `progress.py` bucket, so main's REAL% was overstated (45.88% → **42.15%** once counted; `progress.py` gained a `VERBATIM __asm__ bodies` line that counts them byte-identical but NEVER as REAL). Design is the point: **three independent detectors that must agree**, disagreement reported as a defect (it caught `jtbl_*` being claimed as functions); **SDK-ness derived from the 14 shipped PsyQ archives via `nm`** (2,227 symbols), not a hand list (which had mis-classified `VectorNormalSS`/`SquareRoot12`/`OuterProduct12` as game code); coverage asserted; and **`--selftest`** with a known-true case of EVERY spelling — hand counts went 116→112→108→178→199 because the sources use both `".ent\tNAME\n"` and `".ent NAME\n"`, and a bare `".ent\t"` fragment yields a phantom function called `t`. Run `--selftest` before believing the number. | +| | `tools/jtbl_carve.py` — `_merge_pre` carries `--pre` forward | **(P32 T1a, cookbook §498)** `set_overlays_var` rebuilt `_JTBL_INTERLEAVE` from the carve set alone and DROPPED the binary's `--pre ` clause (the resident's §8f leading-rodata sandwich, `--pre hdr.rodata.o` — the one binary in the fleet with a `--pre`). `make extract` then refused (`ld_interleave: hdr.rodata.o … would be parked with .text`), the build linked the STALE script, and the gate booked the draft as DIFF (249,252 differing bytes of artefact). `_merge_pre(existing_line, args)` now carries an existing `--pre` into the regenerated value; idempotent; a line without `--pre` is unchanged (every overlay). Unit control on 4 shapes. | +| | `tools/interleave_check.py` — `--pre`-tolerant anchor | **(P32 T1a, cookbook §498)** The `--order` anchor was `:= --order`, so a line carrying `--pre ` first (the resident) parsed as n=0 and printed a false DRIFT (rc 0 — a soft alarm). Now `:= (?:--pre \S+ )?--order`; `--fix` already preserved the prefix (it replaces only the order token). Control: ov_SC02_017 ALIGNED n=44 unchanged; resident ALIGNED n=5. | +| | `tools/harvest_verify.py` — post-carve extract rc checked | **(P32 T1a, cookbook §498)** `_jtbl_prep_one` ran `make extract` after a successful carve and IGNORED its exit code (R49/R61): a refused layout left the stale linker script in place and the whole-binary gate reported the draft as DIFF. Now a failed re-extract prints `!! extract-after-carve FAILED … CARVE refusal, NOT a draft verdict`, restores the carve snapshot, re-extracts, and returns refusal (CARVE-REFUSED class). | | | `tools/jr_isolate_all.py` — `typedef struct Tag Alias;` keyed by the ALIAS | **(P32 T1b, cookbook §497)** The carried-type dedupe keyed every block by the names its regexes found; for a bodiless `typedef struct Rec801806C8_s Rec801806C8;` that was the TAG, so the typedef block and the tag's own `struct Rec801806C8_s {...} __attribute__((packed, aligned(1)));` definition collided under one key with different bodies and the R43 "CONFLICTING bodies — a rename is needed" refusal fired on legal C (ov_SC02_017). `_type_names` now keys such a line by the alias (`_TYPEDEF_TAG_ALIAS`), and the `carried` set learns the alias too; `typedef struct X X;` (alias == tag) keeps the old key so a second one still dedupes/refuses. Unit control on 7 block shapes; the refusing overlay's dry-run is CLEAN (2 region files) with no source rename. | | | `tools/jr_isolate_all.py` — include-derived provided types | **(P32 T1a, cookbook §496)** The carried-type test consulted `_engine_types()` (engine_types.h + common.h) for EVERY TU, assuming each region `#include`s engine_core.h. Overlays do; the resident, the `md_*` modules and main's TUs include only `common.h`. A resident file-local typedef whose NAME engine_types.h also defines (`CdFileLoc`) was therefore NOT carried into the new regions ("the shared headers already define it") → `parse error before cdFileLocTable` in both region TUs, build rc 2 while the stale binary on disk read GREEN (R53). Now `_provided_types(header)` derives the provided set from the TU's own `#include` lines (engine_core.h ⇒ engine_types.h + common.h — engine_core's OWN typedefs live inside `DEFINE_func_*` macro bodies and are provided only where invoked; `common.h` ⇒ common.h only) and `_file_scope_decls(items, provided)` uses it at both decision points; `_engine_types()` kept for legacy callers. R39 controls: an overlay header yields exactly the legacy set (1,197 names); the resident header's set lacks `CdFileLoc`. Positive control: the resident 3-region split builds byte-identical (`8e17e02f…`). | | | `tools/jr_isolate_all.py` — boundary derivation | **(P31 S74, cookbook §441)** `_region_emit_start()`: the yaml offset for a region is derived from the region's **CONTENT** — min of item addresses and of every `.globl`/`.ent` its text names that resolves inside the object — and taken as `min(cut, emit)`, so a boundary can only move DOWN. Reason: **a §265 verbatim `__asm__` body is not one of `parse_overlay_c`'s four addressed-anchor forms, so it attaches to the NEXT anchor as PREAMBLE — and preamble is assumed byte-neutral when it emits bytes.** A cut at `func_800D0268` would have moved 0x168 bytes of three other functions into the new object while the yaml claimed the region started higher. Where no verbatim asm is in play it equals the cut, so every existing isolate is unchanged. Also: an item-less CLOSING region used to emit a duplicate `- [off, c, …]` line (the empty-region skip covered only region 0, and `_partition`'s empty `footer` made the closing region look non-empty). | diff --git a/docs/cookbook-index.md b/docs/cookbook-index.md index c50056845..98e3b86c1 100644 --- a/docs/cookbook-index.md +++ b/docs/cookbook-index.md @@ -2,7 +2,7 @@ > **Generated by `tools/cookbook_index.py` — do not hand-edit** (R33). Regenerate after adding a cookbook section. > -> `docs/matching-cookbook.md` is ~716 KB / 1165 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. +> `docs/matching-cookbook.md` is ~716 KB / 1166 sections. Grepping it blind is how three P30 wave-1 agents each "discovered" an idiom that was already written down. **Start here, then read the section.** A section appears under every symptom it addresses. **How to use:** name what you SEE in the diff (a stolen delay slot, an extra `la`, a swapped register pair, a `conflicting types` error), find that symptom below, read those sections first. If nothing fits, THEN grind — and add a section when you win. @@ -771,7 +771,7 @@ - **§487** — ★★★ — THE PSX LOADER'S PER-VERSION SIGNATURE SETS ARE A FREE PROVENANCE ORACLE: main's "WALL" BAND IS LIBPAD 4.2.1 (P31 S78) L36150 - **§495** — ★★★ — THE VERBATIM END-STATE (P31 S80 #10): TWO DEF-SIDE DECLARATION WALLS, A "BANK" THAT WAS THE ASSEMBLY, AND A GATE THAT DROPPED A BANK ON EXIT 0 L36620 -### jump tables & switches (71) +### jump tables & switches (72) - **§8** — rodata island (compiler jump tables) — the `.data→.rodata→.data` sandwich (Phase 7) L339 - **§8a** — rodata island in a flat OVERLAY — the tail sandwich, per matched jr-function (Phase 26 — PoC PROVEN) L361 @@ -844,6 +844,7 @@ - **§3-B.** — A SINGLE-SET LOCAL'S VALUE IS VISIBLE AT A SWITCH JOIN, AND THAT ERASES A ZERO-EXTENSION (CONFIRMED: `ov_SC03_105:func_801806F8`, 241 ins, banked from closeness 235) L34975 - **§3-D.** — AN OFFLINE JTBL-RODATA PLACEMENT AUDIT (CLAIMED: `md_MAIN_034:func_800CB00C` — did NOT bank, and that is the point) L35008 - **§491** — ★★ — THE MECHANICAL LEFTOVERS (P31 S79 #6): A PHANTOM STUB, TWO JTBL TWINS, ONE EXACT CLONE — AND THREE TOOL GAPS THE BANKS EXPOSED L36346 +- **§498** — ★★★ — A CARVE THAT DROPS THE BINARY'S `--pre` CLAUSE, AND A GATE THAT IGNORED THE EXTRACT'S EXIT CODE: HOW A BYTE-CORRECT DRAFT WAS BOOKED "DIFF" (P32 T1a; resident `func_800D128C` BANKED 243 ins after two instrument fixes) L36718 ### optimisation level (-O0/-O2) (26) @@ -1090,7 +1091,7 @@ - **§494** — ★★★ — TEN BANKS FROM ONE-AGENT-PER-FUNCTION DRAFTING (P31 S79/S80 #9): THE IDIOMS, THE PLUMBING, AND THE THREE WAYS AN AGENT'S "MATCH" WAS NOT ONE L36480 - **§496** — ★★ — A CARRIED-TYPE TEST THAT ASSUMES THE OVERLAY INCLUDE SET SILENTLY DROPS A RESIDENT TYPEDEF (P32 T1a; byte-proven, resident `func_800D128C` isolation) L36661 -### build graph, splat & the harness (214) +### build graph, splat & the harness (215) - **§4** — Flag/toolchain gotchas L190 - **Build** — mechanism — per-file opt override (splat resegmentation) L307 @@ -1306,6 +1307,7 @@ - **§492** — ★★ — "C-PLUMBING" WAS THREE DIFFERENT THINGS (P31 S79 #7): A RAW SPLICE THE GATE'S LADDER BROKE, TWO -O0 BODIES THE CHECKER COMPILED AT -O2, AND TWO DRAFTS THAT BELONGED TO OTHER OVERLAYS L36392 - **§493** — ★★ — THE PERMUTER ROUTE END-TO-END, AND THE THREE PLUMBING STEPS BETWEEN A SCORE-0 WINNER AND THE MAIN GATE (P31 S79 #8) L36420 - **§495** — ★★★ — THE VERBATIM END-STATE (P31 S80 #10): TWO DEF-SIDE DECLARATION WALLS, A "BANK" THAT WAS THE ASSEMBLY, AND A GATE THAT DROPPED A BANK ON EXIT 0 L36620 +- **§498** — ★★★ — A CARVE THAT DROPS THE BINARY'S `--pre` CLAUSE, AND A GATE THAT IGNORED THE EXTRACT'S EXIT CODE: HOW A BYTE-CORRECT DRAFT WAS BOOKED "DIFF" (P32 T1a; resident `func_800D128C` BANKED 243 ins after two instrument fixes) L36718 ### process, measurement & doctrine (141) @@ -2959,6 +2961,7 @@ - **§495** — ★★★ — THE VERBATIM END-STATE (P31 S80 #10): TWO DEF-SIDE DECLARATION WALLS, A "BANK" THAT WAS THE ASSEMBLY, AND A GATE THAT DROPPED A BANK ON EXIT 0 L36620 - **§496** — ★★ — A CARRIED-TYPE TEST THAT ASSUMES THE OVERLAY INCLUDE SET SILENTLY DROPS A RESIDENT TYPEDEF (P32 T1a; byte-proven, resident `func_800D128C` isolation) L36661 - **§497** — ★ — A BODILESS `typedef struct Tag Alias;` DEFINES THE ALIAS, NOT THE TAG: THE CARRIER'S FALSE "CONFLICTING BODIES" REFUSAL (P32 T1b; ov_SC02_017 `func_80186C64` isolation) L36695 +- **§498** — ★★★ — A CARVE THAT DROPS THE BINARY'S `--pre` CLAUSE, AND A GATE THAT IGNORED THE EXTRACT'S EXIT CODE: HOW A BYTE-CORRECT DRAFT WAS BOOKED "DIFF" (P32 T1a; resident `func_800D128C` BANKED 243 ins after two instrument fixes) L36718 --- @@ -4136,3 +4139,4 @@ Notes routinely quote that as a section id. This table resolves it. Grep bait: ` | L36620 | §495 | ★★★ — THE VERBATIM END-STATE (P31 S80 #10): TWO DEF-SIDE DECLARATION WALLS, A "BANK" THAT | | L36661 | §496 | ★★ — A CARRIED-TYPE TEST THAT ASSUMES THE OVERLAY INCLUDE SET SILENTLY DROPS A RESIDENT TY | | L36695 | §497 | ★ — A BODILESS `typedef struct Tag Alias;` DEFINES THE ALIAS, NOT THE TAG: THE CARRIER'S F | +| L36718 | §498 | ★★★ — A CARVE THAT DROPS THE BINARY'S `--pre` CLAUSE, AND A GATE THAT IGNORED THE EXTRACT' | diff --git a/docs/matching-cookbook.md b/docs/matching-cookbook.md index f8938e4e5..0a1c346e5 100644 --- a/docs/matching-cookbook.md +++ b/docs/matching-cookbook.md @@ -36714,3 +36714,33 @@ the overlay's dry-run went REFUSED → CLEAN (2 region files) with the source un **Tell + generalization.** A "CONFLICTING bodies" refusal whose two bodies are a struct DEFINITION and a bodiless `typedef struct ;` is this class — no rename, the carrier is wrong. Sibling of §496 (the provided-type set assumed the overlay include set): the carrier's failures are recogniser/namespace assumptions. + +#### §498 ★★★ — A CARVE THAT DROPS THE BINARY'S `--pre` CLAUSE, AND A GATE THAT IGNORED THE EXTRACT'S EXIT CODE: HOW A BYTE-CORRECT DRAFT WAS BOOKED "DIFF" (P32 T1a; resident `func_800D128C` BANKED 243 ins after two instrument fixes) + +**The verdicts.** `rtu_match` MATCH (243/243) in the real region TU; `parallel_gate` → `func_800D128C DIFF`, banked 0, +53 s, rc 0, nothing merged. The in-tree reproduction (raw splice, §492a) showed why: `jtbl_carve --func` wrote the +5-piece carve and REGENERATED `resident_JTBL_INTERLEAVE` as `--order tail.data.o,…,tail3.data.o` — without the +committed line's `--pre hdr.rodata.o`. `make extract` refused (`ld_interleave --order: 1 extracted asm piece(s) are in +neither --order nor --pre … hdr.rodata.o`), exited 1, and `make build` then linked against the STALE 3-piece script: +sha `59b44f0f…`, **249,252 of 365,404 bytes differing from file offset 0x4** — the image shifted, not the function. The +gate's worker did the same and reported the failure as a draft verdict, because `_jtbl_prep_one` never read the +post-carve `make extract`'s exit code (the file-offset-0x4 shift is the tell: a DIFF that starts at the first code +byte is never the draft). + +**Two fixes, both in our layer (R35/R40/R49/R61).** (1) `jtbl_carve._merge_pre`: `--order` is derived from the +CARVE SET, `--pre` is a property of the binary's LAYOUT (the resident's §8f leading data word emitted as +`hdr.rodata.o`); the rewrite now carries an existing `--pre` forward (idempotent; overlays unchanged — 4-shape unit +control). (2) `harvest_verify._jtbl_prep_one`: a failed post-carve re-extract now restores the snapshot and refuses +loudly (`CARVE refusal, NOT a draft verdict`) instead of letting the build judge a stale script. With both, the same +draft carved (`JTBL_PADS 0,4`; tables at +0x0/+0x1e0 of the new `.rodata` piece at file 0x451c0, pads `tail3` at +0x453c4) and built **byte-identical** (`8e17e02f…`). + +**Sequencing law that bit twice in one task.** After a FAILED extract, `asm/` is half-regenerated for the config that +failed (splat writes before ld_interleave refuses), so the next `jtbl_carve` sees "table not found in the raw data asm — +already carved / stale asm?" — re-extract with the restored config FIRST, then carve. And a build after a failed +extract is never a measurement (R53): the previous binary or a stale script is what you are hashing. + +**Tell + generalization.** Any binary whose `JTBL_INTERLEAVE` carries `--pre` (today: the resident) was un-carvable by +the gate since the §8f sandwich landed — the "NON-CONTIGUOUS carve" refusal of `frontier-p32.md` §1a was the FIRST wall, +this was the second, and neither was the C. Sibling of §496/§497 (the isolation carrier's assumptions): the three +resident blockers were all tooling that had only ever met overlays. diff --git a/src/resident/resident_jr_800D128C.c b/src/resident/resident_jr_800D128C.c index f8bfa4103..910a585c9 100644 --- a/src/resident/resident_jr_800D128C.c +++ b/src/resident/resident_jr_800D128C.c @@ -193,7 +193,258 @@ extern s32 func_800D128C(s32 arg0, s32 arg1); extern s32 func_800D11F0(s32 arg0); /* ==== end §8b carried decl layer ==== */ -INCLUDE_ASM("asm/resident/nonmatchings/resident_jr_800D128C", func_800D128C); +/* func_800D128C — two-stage switch dispatch (arg0 -> a "kind" code, then the kind + * -> the actual object call + a message id). 243 ins, jtbl_80113FB8 (119 entries, + * cases 1..119) + jtbl_80114198 (9 entries, cases 0..8). + * + * SIGNATURE: resident.c:1693 already declares this at FILE scope as + * extern s32 func_800D128C(s32 arg0, s32 arg1); + * so the definition must be (s32, s32) and the narrowing is done in the body — + * `(u8)arg0` is the `andi $a0,$a0,0xFF` before the range check, `(s16)arg1` the + * `sll/sra 16` pair in case 107. (SYS law 2: copy the TU's declaration exactly.) + * + * THREE ZERO-BYTE CONSTRUCTS BELOW ARE LOAD-BEARING — do not "clean them up": + * + * 1+2. §5a/§336 cross-jump barriers. Three arms end in the identical suffix + * [li $a2,1][jal func_8014BB24][li $s0,0x96][j .L800D154C] (case 1 / case 117) + * or just [li $s0,0x96][j] (case 16). find_cross_jump walks BACKWARD from the + * converging jump, and for case 16 the CODE_LABEL clause (jump.c:2402 + * `if (GET_CODE (i1) == CODE_LABEL) { --minimum; break; }`) drops the 2-insn + * floor to 1 — so gcc merges all three and the function comes out 4 ins short. + * The barrier goes BETWEEN the call and the `val =` (not after it), or reorg + * can no longer steal `li $s0,0x96` into the `j`'s delay slot and you get a nop. + * THE TWO BARRIERS MUST NOT BE SPELLED THE SAME: two identical ASM_INPUTs are + * `rtx_renumbered_equal_p`, so they match EACH OTHER and cross_jump merges the + * two arms through them (measured: closeness 105, case 1 folded into case 16). + * + * 3. The switch-2 index copy `addu $v1,$s1,$zero`. `switch (ret)` alone compiles + * to `sltiu $v0,$s1,9` with no copy: expand_end_case folds `ret - 0` away, so + * the index IS ret's pseudo. A plain `sel = ret;` does not survive either — + * CSE canonicalises the uses back onto `ret` and flow deletes the copy. The + * empty volatile asm re-DEFINES `sel` so CSE cannot fold it back; the copy then + * survives to regalloc, where local-alloc gives the single-block `sel` a + * caller-saved reg ($v1) while global-alloc must give `ret` a call-saved one + * ($s1, it is live across the calls in the arms that leave ret == 3). + * + * §162: case 3's `j .L800D1334` is BACKWARD into case 13's body — compiler tail-merge + * is always forward, so that edge is a source-level `goto`. + */ + +extern void func_8014BB24(s32, s32, s32); +extern void func_8014BCC0(s32, s32); +extern void func_8014BD24(s32, s32); +extern void func_8014B944(s32, s32, s32); +extern void func_8014BC0C(s32, s32); +extern void func_8014B2A8(void); +extern void func_8002D4C8(s32, s32); +extern u16 D_80126B58; + +s32 func_800D128C(s32 arg0, s32 arg1) { + s32 ret = 3; + s32 flag = 1; + s32 id = 0; + s32 obj = (s32)&D_80126B58; + s32 val; + s32 sel; + + switch ((u8)arg0) { + case 9: + func_8014BD24(obj, 5); + val = 0; + ret = 1; + break; + case 106: + func_8014BB24(obj, 0x12C, 1); + val = 0x12C; + break; + case 1: + func_8014BB24(obj, 0x96, 1); + __asm__ __volatile__(""); /* §5a cross-jump barrier — load-bearing */ + val = 0x96; + break; + case 2: + func_8014BCC0(obj, 5); + ret = 4; + /* fallthrough */ + case 13: + lab1334: + val = 0x19; + break; + case 3: + func_8014BCC0(obj, 0x19); + ret = 5; + goto lab1334; /* §162: the backward j is a source goto */ + case 17: + func_8014BCC0(obj, 5); + ret = 4; + val = 0xA; + break; + case 18: + func_8014BCC0(obj, 0xA); + ret = 5; + val = 0xA; + break; + case 12: + func_8014BCC0(obj, 5); + ret = 5; + val = 2; + break; + case 11: + func_8014BB24(obj, 0x1E, 1); + val = 0xA; + break; + case 8: + func_8014B944(obj, 0x500000, 1); + val = 5; + ret = 2; + break; + case 109: + func_8014B944(obj, 0x900000, 1); + val = 5; + ret = 2; + break; + case 4: + ret = 7; + /* fallthrough */ + case 15: + val = 0x50; + break; + case 5: + ret = 7; + /* fallthrough */ + case 16: + __asm__ __volatile__("" ::: "memory"); /* §5a barrier — MUST differ from case 1's */ + val = 0x96; + break; + case 107: + if ((s16)arg1 >= 0x13) { + val = 0x14; + } else { + val = (s16)arg1 + 2; + } + val = val * 25; + func_8014BD24(obj, val); + ret = 7; + break; + case 6: + func_8014BD24(obj, 5); + ret = 7; + val = 0xFA; + break; + case 7: + func_8014BD24(obj, 5); + ret = 7; + val = 0x3E7; + break; + case 83: + func_8014BB24(obj, 0xA, 1); + val = 0xA; + break; + case 84: + func_8014BCC0(obj, 5); + ret = 5; + val = 5; + break; + case 85: + func_8014BB24(obj, 0x32, 1); + val = 0x32; + break; + case 112: + func_8014BC0C(obj, 0x19); + ret = 7; + val = 0x3E7; + break; + case 76: + case 113: + ret = 7; + val = 5; + break; + case 114: + func_8014BB24(obj, 0x64, 1); + /* fallthrough */ + case 14: + val = 0x32; + break; + case 115: + func_8014BB24(obj, 0x96, 1); + /* fallthrough */ + case 105: + val = 0x64; + break; + case 116: + func_8014BB24(obj, 0x96, 1); + val = 0xC8; + break; + case 117: + func_8014BB24(obj, 0xC8, 1); + val = 0x96; + break; + case 118: + func_8014BB24(obj, 0xFA, 1); + /* fallthrough */ + case 104: + val = 0x12C; + break; + case 119: + func_8014BB24(obj, 0x1F4, 1); + val = 0x1F4; + break; + case 87: + val = 0xA; + ret = 1; + break; + default: + ret = 0; + break; + } + + sel = ret; + __asm__ __volatile__("" : "=r"(sel) : "0"(sel)); /* keeps `addu $v1,$s1,$zero` alive */ + switch (sel) { + case 0: + id = 0x45F; + flag = 0; + break; + case 1: + id = 0x464; + func_8014B2A8(); + func_8014BB24(obj, val, 1); + break; + case 2: + func_8014BD24(obj, val); + id = 0x464; + break; + case 3: + func_8014BD24(obj, val); + id = 0x45C; + break; + case 4: + func_8014BD24(obj, val); + id = 0x463; + break; + case 5: + func_8014BD24(obj, val); + id = 0x465; + break; + case 7: + func_8014BB24(obj, val, 1); + id = 0x45C; + break; + case 8: + func_8014BCC0(obj, val); + id = 0x45C; + break; + default: + flag = 0; + break; + } + + if ((u16)id != 0) { + func_8002D4C8((u16)id, 0); + } + return flag; +} + s16 func_800D1658(s32 arg0) { extern int func_800291B4(int); diff --git a/tools/harvest_verify.py b/tools/harvest_verify.py index f9a5f6f81..68f59c883 100644 --- a/tools/harvest_verify.py +++ b/tools/harvest_verify.py @@ -635,7 +635,19 @@ def _jtbl_prep_one(fn): if not done: _jtbl_restore(snap) return False, None - _sh(['make', '--no-print-directory', 'extract', 'BINARY=%s' % a.binary]) + # R49/R61 (P32 T1a, cookbook §498): the post-carve re-extract's exit code was IGNORED. When the carve + # writes a layout ld_interleave refuses (measured: a regenerated JTBL_INTERLEAVE that dropped the + # resident's `--pre hdr.rodata.o`), `make extract` fails, the build then links the STALE script, and + # the draft is booked as DIFF — an instrument failure wearing a codegen verdict. Refuse loudly instead. + _rx = _sh(['make', '--no-print-directory', 'extract', 'BINARY=%s' % a.binary]) + if _rx.returncode: + _last = ((_rx.stdout or '') + (_rx.stderr or '')).strip().splitlines()[-3:] or [''] + print(' [jtbl] !! extract-after-carve FAILED %s (rc %d) — carve config restored; this is a CARVE ' + 'refusal, NOT a draft verdict: %s' % (done[0], _rx.returncode, ' | '.join(l[:100] for l in _last))) + _jtbl_restore(snap) + if _sh(['make', '--no-print-directory', 'extract', 'BINARY=%s' % a.binary]).returncode: + print(' [jtbl] !! RESTORE INCOMPLETE: re-extract FAILED after undoing the carve') + return False, None _reload_corpus() print(' [jtbl] carved %s' % done[0]) return True, snap diff --git a/tools/interleave_check.py b/tools/interleave_check.py index 7de302c56..d8c67a6bd 100644 --- a/tools/interleave_check.py +++ b/tools/interleave_check.py @@ -7,7 +7,9 @@ import re,sys ov=sys.argv[1] fix='--fix' in sys.argv[2:] mk=open('config/overlays.mk').read() -m=re.search(r'^%s_JTBL_INTERLEAVE := --order (\S+)'%ov,mk,re.M) +# P32 T1a (cookbook §498): a binary may carry `--pre ` BEFORE `--order` (the resident's §8f leading-rodata +# sandwich, `--pre hdr.rodata.o`); the old anchor `:= --order` read such a line as n=0 and reported a false DRIFT. +m=re.search(r'^%s_JTBL_INTERLEAVE := (?:--pre \S+ )?--order (\S+)'%ov,mk,re.M) order=m.group(1).split(',') if m else [] y=open('config/splat.%s.yaml'%ov).read() yseq=[] diff --git a/tools/jtbl_carve.py b/tools/jtbl_carve.py index 292b8ca18..447cdecbf 100644 --- a/tools/jtbl_carve.py +++ b/tools/jtbl_carve.py @@ -1214,10 +1214,27 @@ def apply(ov, funcs): + (f"; JTBL_PADS = {multi}" if multi else "")) +def _merge_pre(existing_line, args): + """Carry the binary's `--pre ` clause forward into a regenerated JTBL_INTERLEAVE value (P32 T1a, + cookbook §498). `build_carve` derives `--order` from the CARVE SET; `--pre` is a property of the + binary's LAYOUT — the resident's §8f leading-rodata sandwich emits its leading data word as + `hdr.rodata.o` and ld_interleave must place it BEFORE the text — so a rewrite that rebuilds the value + from the carve set alone silently DROPS it: measured on the resident, `make extract` then refused + ("hdr.rodata.o … would be parked with .text"), the build linked a stale script, and the gate booked the + draft as DIFF (249,252 differing bytes of pure artefact). Idempotent: an `args` that already names + `--pre` is returned unchanged; a line with no `--pre` adds nothing (every overlay, byte-neutral).""" + if existing_line is None or "--pre" in args: + return args + m = re.search(r"--pre\s+(\S+)", existing_line) + return f"--pre {m.group(1)} {args}" if m else args + + def set_overlays_var(ov, args): mk = os.path.join(REPO, "config/overlays.mk") txt = open(mk).read() _mk_base = txt + _cur = re.search(rf"^{re.escape(ov)}_JTBL_INTERLEAVE.*$", txt, re.M) + args = _merge_pre(_cur.group(0) if _cur else None, args) var = f"{ov}_JTBL_INTERLEAVE := {args} # Phase-26 §8 jtbl-rodata carve" if re.search(rf"^{re.escape(ov)}_JTBL_INTERLEAVE\b", txt, re.M): txt = re.sub(rf"^{re.escape(ov)}_JTBL_INTERLEAVE.*$", var, txt, count=1, flags=re.M)