feat(phase-7): -O0 split + 4 matches (42 real) + libcd.h — session B checkpoint

- per-file -O0 mechanism: split text c-subseg into boot@-O0 (src/boot.c) + 800@-O2
  via splat resegmentation + Makefile target-specific CC1FLAGS; the boot/main/
  game-mode-dispatch module (0x80010000-0x800123F0) is -O0, not the pinned -O2
  (per-module compiler mixing, SETUP §5.5). Regression-gated byte-identical.
- 4 byte-matches (38->42 real, make check BYTE-IDENTICAL throughout): GameModeDispatch
  (-O0 register-ptr far member), DebugMenuHandler (-O0 reserved-slot local), CdQueueBusy
  (-O2 if/else order + branch polarity), CdReadRequest (-O2 early-return fall-through)
- PsyQ infra: include/psyq/libcd.h (CdlLOC/CdlFILE from the .gdt) + 4 named symbols
  (CdSearchFile/CdPosToInt/CdIntToPos/VSync) in symbols.us.txt; func_* stubs renamed
- loader cluster non-jtbl COMPLETE (6 matched + 2 drafted): NON_MATCHING drafts of
  LoaderInitFileTable + ResourceLoadStateMachine (logically faithful, residuals in-source)
- tooling: progress.py/difficulty.py fixed for the multi-file split (glob src/*.c, all
  asm dirs, skip forward-decls); deterministic; 42 real / 4 NM
- flywheel: matching-cookbook §6 (-O0 detection + idioms), §7 (PsyQ types/symbols), T4
- build byte-identical 143dbb89f34491258bbc27810d0a12ec8b43a8dd from a full clean cycle
This commit is contained in:
Drew T
2026-06-14 19:37:05 -06:00
parent 379185a799
commit 8ffb7f0607
12 changed files with 652 additions and 178 deletions
+8
View File
@@ -208,6 +208,14 @@ build/src/%.o: src/%.c
@echo " CC $@"
@set -o pipefail; $(CPP) $(CPPFLAGS) $< | $(CC1_PSX) $(CC1FLAGS) | $(VENV_PY) $(MASPSX) --aspsx-version=$(ASPSX_VERSION) $(MASPSX_FLAGS) | $(AS) $(ASFLAGS) -o $@
# Per-module optimization override (SETUP §5.5 — per-module compiler mixing). The boot/
# main/game-mode-dispatch module (src/boot.c, vram 0x80010000-0x800123F0) was compiled at
# -O0, NOT the -O2 game-code default: frame-pointer setup + unfolded large-offset loads
# are the evidence (GameModeDispatch byte-matches only at -O0). gcc 2.7.2 has no
# per-function optimize pragma, so opt level is per-file. Target-specific CC1FLAGS (the
# pattern recipe reads $(CC1FLAGS), so this overrides it for just build/src/boot.o):
build/src/boot.o: CC1FLAGS := -quiet -O0 -G0 -mips1 -mcpu=3000 -mgas -msoft-float -fgnu-linker
# link (the .ld pulls in the .o by path) + objcopy to the raw PS-X EXE image.
$(OUT): $(OBJS) $(LD_SCRIPT)
@set -e
+8 -1
View File
@@ -66,6 +66,13 @@ segments:
# no-op at every real boundary here.
align: 4
subsegments:
- [0x800, c, 800] # text — Phase 6: c-scaffold (src/800.c INCLUDE_ASM stubs); matched fns replace stubs
# Per-module optimization mixing (SETUP §5.5): the boot/main/game-mode-dispatch
# module (vram 0x80010000-0x800123F0) was compiled at -O0; the rest of the text at
# -O2. Split into two c-subsegments so the Makefile applies per-file flags
# (build/src/boot.o is overridden to -O0). The split is byte-identical at 100%
# INCLUDE_ASM (Phase-7 regression gate) — opt level only affects matched C, not stubs.
# Boundary = func_800123F0 (vram 0x800123F0 -> file vram-0x8000F800 = 0x2BF0).
- [0x800, c, boot] # -O0 boot module -> src/boot.c (vram 0x80010000-0x800123F0)
- [0x2BF0, c, 800] # -O2 remainder -> src/800.c (vram 0x800123F0-0x800629DC); name "800" kept (legacy) so the 38 matched fns + their asm/nonmatchings/800 paths don't migrate; NB start is now 0x2BF0, not 0x800
- [0x531DC, data, 531DC] # data — psxexeinfo boundary; round-trips byte-identical
- [0x65000]
+4
View File
@@ -977,3 +977,7 @@ resLoad_result = 0x800C6D10; // data
resLoad_curId = 0x800C6D34; // data
lzss_state = 0x800C7D24; // data
listCdBuffer = 0x80180000; // data
CdIntToPos = 0x80043A18; // func PsyQ libcd (Ghidra-confirmed, Phase 7)
CdPosToInt = 0x80043B1C; // func PsyQ libcd (Ghidra-confirmed, Phase 7)
CdSearchFile = 0x80045374; // func PsyQ libcd (Ghidra-confirmed, Phase 7)
VSync = 0x8004239C; // func PsyQ libetc (Ghidra-confirmed, Phase 7)
+2 -2
View File
@@ -1,8 +1,8 @@
# Unmatched difficulty inventory (generated by tools/difficulty.py — harvest queue)
unmatched functions : 1968
unmatched functions : 1962
trivial (<=5 ins) : 291
non-jtbl leaves : 1095 (best harvest targets)
non-jtbl leaves : 1093 (best harvest targets)
jump-table funcs : 84 (deferred — need the rodata-island workflow, Task 2')
## Easiest 120 unmatched (score asc) — the work queue
+89
View File
@@ -73,6 +73,21 @@ Open example: `func_80015A74` residual (counter init vs hoisted magic constant).
`lb` (sign-extend). Pick the load width/sign that matches the asm, then layer `& 0xff` (I2) /
casts as needed.
### T4 — Branch polarity: invert the source condition to flip gcc's chosen branch
Two source forms can be logically identical but emit **opposite** branches:
`if (x & m) return A; return B;` vs `if ((x & m) == 0) return B; return A;`. gcc -O2 picks one
polarity (beqz vs bnez); it may be the opposite of the target. Symptom in asm-differ: the right
structure but a lone **beqz↔bnez flip with the two return constants swapped** between the branch
and its `j`/delay slot. Fix: rewrite the condition with the other polarity. Also: which arm of an
`if/else` becomes the fall-through follows source order — put the target's fall-through block in the
`if`, the branched-to block in the `else` (e.g. `if (a != b){…} else {…}` if the `==` block sits
last). Example: `CdQueueBusy` (1405 → 210 via if/else order, 210 → 0 via the 0x20 polarity flip).
Likewise multi-exit functions: write the **success/main return LAST** (it becomes the fall-through
into the shared epilogue) and error cases as **early `return`s** (they branch in). Reversing this —
`if (ok) { … return good; } return 0;` — makes `return 0` the fall-through and duplicates the
`j epilogue`/`move v0,zero` tail. Example: `CdReadRequest` (305 → 0 by flipping to `if (busy) return 0;
… return cdReq_result;`).
---
## §3 When a diff is pure scheduling → decomp-permuter (harness built, Phase 6)
@@ -118,3 +133,77 @@ as decomp-permuter candidates rather than hand-grinding.
- **Hoisted-invariant vs IV-init ordering** — a loop-invariant load scheduled before/after the
counter init; not reachable by C-source changes (permuter stuck at base). Needs `PERM_*` or
insight. Example: `func_80015A74` (uint→BCD). See §3.
---
## §6 Per-module optimization mixing — the -O0 boot module (Phase 7)
**Finding (2026-06-14):** the EXE mixes optimization levels per original translation unit (the
§5.5 Xenogears-style mixing, now concrete). The **boot/main/game-mode-dispatch module** — a clean
contiguous block at **vram 0x80010000–0x800123F0** (~50 funcs: `start`, `main`, `GameModeDispatch`,
`DebugMenuHandler`, the game-mode handlers) — was compiled at **-O0**. Everything from 0x800123F0
onward (every match so far + the file-loader cluster) is **-O2**. **Always opt-fingerprint a new
function before writing C** — the pinned `-O2` is NOT global.
### Detecting the opt level (do this first)
gcc 2.7.2 **-O0 keeps a frame pointer**: `addu $fp,$sp,$zero` (`21F0A003`) in the prologue +
`addu $sp,$fp,$zero` in the epilogue; **-O2 omits it**. Grep the target `.s`:
`grep -l 21F0A003 asm/nonmatchings/<seg>/<fn>.s` → hit = **-O0**, miss = -O2. Module scan: classify
every `.s` by that signature, sort by address; the contiguous -O0 run is the module (the boot block
is the one early -O0 run). Other -O0 tells: redundant `move`/`addu rd,rs,$zero` copies; a `nop` after
every load (no load-delay scheduling); single-use values parked in callee-saved `s0..`; large
constant member offsets left **unfolded** (`la $reg,sym` + `lhu off($reg)`), where -O2 folds
`sym+off` into one load.
### -O0 idiom — far struct member via a `register` base pointer
Target: `lui s0,%hi(BASE); addiu s0,s0,%lo(BASE); lui at,1; addu at,s0,at; lhu v0,-0x5c52(at)` =
load a u16 at `BASE + 0xA3AE`. The `lui 1 / addu / -0x5c52` is just `as` expanding a register-relative
load whose offset (0xA3AE) exceeds 0x7FFF (%hi=1, %lo=-0x5C52). C — a **`register`-qualified pointer**
to the base, then offset-deref:
```c
extern u8 BASE[];
register u8 *p = BASE;
... *(u16 *)(p + 0xA3AE) ... /* base stays in a callee-saved reg; offset left unfolded */
```
`register` is **load-bearing**: drop it and -O0 spills the pointer (extra sw/lw, bigger frame); and
-O1/-O2 fold it all back to `lhu sym+off` (no base reg, no frame pointer). Plain `BASE[idx]` or
`((struct*)BASE)->m` also **fold** at -O0 → wrong. Example: `GameModeDispatch` (0x80010B40) =
`gameModeHandlerTable[*(u16*)(p+0xA3AE)]()` — byte-exact (asm-differ 0).
### -O0 idiom — a reserved (unstored) local sets the frame size
A named local the original declares but our toolchain wouldn't store (e.g. a call result the
original checks directly) still **reserves its 8-byte stack slot** at -O0, enlarging the frame.
If a near-match differs **only** by frame size + a uniform save-offset shift (every instruction
identical), add the missing local as a **declaration-only** `int x;` + `(void)x;` — no store, no
load, no `-Wall` noise, no code, just the slot. (Assigning the result to the local instead emits a
`sw`/`lw` pair the target lacks.) Example: `DebugMenuHandler` (0x80011144) — `if (CdReadRequest(...)
!= 0)` with a reserved `int iVar1;` → frame 0x20 (score 42 → 0).
### Build mechanism — per-file opt override (splat resegmentation)
One original .c = one opt level; you can't mix within a compile unit, and gcc 2.7.2 has no
per-function optimize pragma. **Split the module into its own splat c-subsegment** and give that
object its flags:
- `config/splat.us.exe.yaml`: split the text subseg at the module boundary (a function start; file
off = vram − 0x8000F800). Boot module = `[0x800, c, boot] → src/boot.c`; the rest stays
`[0x2BF0, c, 800] → src/800.c` (name kept to avoid migrating matched C).
- `Makefile`: target-specific override — `build/src/boot.o: CC1FLAGS := …-O0…` (the pattern recipe
reads `$(CC1FLAGS)`, so this overrides just that object).
- **Regression gate:** the split must rebuild **byte-identical at 100% INCLUDE_ASM** before any -O0 C
is added (opt level only affects matched C, not stubs). Verified for the boot split.
Reuse this hook for any future module whose flags differ (another opt level, bare `divu`, etc.).
---
## §7 PsyQ SDK types & symbols (the library-call prerequisite)
A function that calls PsyQ library routines needs both the SDK **types** and the library **symbols**:
- **Types:** pull the EXACT layout from Ghidra's imported `.gdt` (`mcp__ghidra__types get <Name>`,
category e.g. `/LIBCD.H`) — never guess offsets (G1). Declare in `include/psyq/<lib>.h` (it
`#include "common.h"` for u8/u32, guard-safe). Verify sizes with a compile-time assert:
`typedef char a[sizeof(T)==N ? 1 : -1];`.
- **Symbols:** PsyQ fns are already named in the Ghidra DB but absent from our exported
`config/symbols.us.txt`, so the build shows them as `func_<addr>`. Add `Name = 0xADDR; // func`
to `symbols.us.txt` (R15) AND rename the matching `INCLUDE_ASM("…", func_<addr>)` stub(s) in
`src/` to the canonical name (splat won't rewrite a committed `.c`). Re-extract →
**byte-identical** (label-only change). No matched C may already reference the old name.
- Done Phase 7: `include/psyq/libcd.h` (CdlLOC 4B, CdlFILE 24B + CdSearchFile/CdPosToInt/CdIntToPos
protos) + the 4 libcd/libetc symbols — unlocks the file-loader cluster. Same pattern for
libgpu/libgte/libspu as they come up.
+7 -7
View File
@@ -1,17 +1,17 @@
# BFM matching progress (generated by tools/progress.py — authoritative)
REAL substantive matches : 38 <- the Gen1-exit >=25 bar counts THIS
NON_MATCHING (near-miss) : 2
REAL substantive matches : 42 <- the Gen1-exit >=25 bar counts THIS
NON_MATCHING (near-miss) : 4
splat-auto empty no-ops : 42
INCLUDE_ASM stubs : 1968
INCLUDE_ASM stubs : 1962
data blobs (excluded) : 4
----------------------------------------
matchable functions : 2050
REAL / matchable : 38 / 2050 = 1.85%
incl. empties : 80 / 2050 = 3.90%
REAL / matchable : 42 / 2050 = 2.05%
incl. empties : 84 / 2050 = 4.10%
REAL matches: LoaderResetReadState ResourceGetCdLoc func_80012AB0 func_800168B4 func_80018F20 func_80019018 func_80019198 func_80019378 func_80019388 func_80019398 func_800193A8 func_8001AA78 func_8001AA88 func_8001B22C func_8001B374 func_8001B384 func_8001B85C func_8001BE20 func_8001BFA0 func_8001BFE8 func_8001D0E8 func_80029254 func_80029264 func_80029504 func_80029514 func_8002953C func_8002954C func_8002957C func_8002958C func_80029FD4 func_8002A26C func_8002A27C func_8002A4B8 func_8002A4C8 func_8002A728 func_8002A738 func_8002A998 func_8002A9A8
NON_MATCHING: func_80015A74 func_80016714
REAL matches: CdQueueBusy CdReadRequest DebugMenuHandler GameModeDispatch LoaderResetReadState ResourceGetCdLoc func_80012AB0 func_800168B4 func_80018F20 func_80019018 func_80019198 func_80019378 func_80019388 func_80019398 func_800193A8 func_8001AA78 func_8001AA88 func_8001B22C func_8001B374 func_8001B384 func_8001B85C func_8001BE20 func_8001BFA0 func_8001BFE8 func_8001D0E8 func_80029254 func_80029264 func_80029504 func_80029514 func_8002953C func_8002954C func_8002957C func_8002958C func_80029FD4 func_8002A26C func_8002A27C func_8002A4B8 func_8002A4C8 func_8002A728 func_8002A738 func_8002A998 func_8002A9A8
NON_MATCHING: LoaderInitFileTable ResourceLoadStateMachine func_80015A74 func_80016714
build SHA1: 143dbb89f34491258bbc27810d0a12ec8b43a8dd (byte-identical)
+34
View File
@@ -0,0 +1,34 @@
/* psyq/libcd.h — PSY-Q libcd (CD-ROM) types & prototypes used by the file loader.
*
* Struct layouts are the authoritative ones from the imported PsyQ 4.0 .gdt
* (Ghidra category /LIBCD.H) — verified offsets/sizes, not guessed (G1). Function
* prototypes use the canonical PsyQ signatures; their symbols are pinned in
* config/symbols.us.txt (and already named in the Ghidra DB). Grow this as more
* libcd surface is matched. Companion to common.h (provides the u8/u32 typedefs).
*/
#ifndef PSYQ_LIBCD_H
#define PSYQ_LIBCD_H
#include "common.h"
/* CD logical location — minute/second/sector are BCD. 4 bytes. (/LIBCD.H) */
typedef struct {
u8 minute; /* +0x00 */
u8 second; /* +0x01 */
u8 sector; /* +0x02 */
u8 track; /* +0x03 */
} CdlLOC;
/* CD file descriptor (CdSearchFile result / directory entry). 24 bytes. (/LIBCD.H) */
typedef struct {
CdlLOC pos; /* +0x00 file start location */
u32 size; /* +0x04 file size in bytes */
char name[16]; /* +0x08 file name */
} CdlFILE;
/* libcd prototypes (symbols in config/symbols.us.txt). */
extern CdlFILE *CdSearchFile(CdlFILE *fp, char *name); /* 0x80045374 */
extern int CdPosToInt(CdlLOC *p); /* 0x80043B1C */
extern CdlLOC *CdIntToPos(int i, CdlLOC *p); /* 0x80043A18 */
#endif /* PSYQ_LIBCD_H */
+6 -2
View File
@@ -23,9 +23,12 @@ rodata-island foundation + LZSS match are DEFERRED to a focused sub-project afte
## Task checklist (REORDERED — execution order top to bottom)
- [x] **Task 3 — Report machinery** ✓ DONE. `tools/{progress,difficulty,dup_report}.py` + `make report` (deterministic) + `make sig-refresh`. Authoritative baseline **14 real / 42 empty / 2 NM / 1992 stubs / 2050 matchable** (REAL/matchable=0.68%); empties audit 42/42 clean; dup leverage 8 h_exact + 44 h_norm redundant; harvest queue 1119 non-jtbl leaves + 313 trivial. Digests → `docs/{progress,difficulty,duplicates}.md`.
- [x] **Task 4 — Harvest to ≥25 real** ✓ DONE. **36 real matches** (14 + 22 trivial accessor leaves: getters/setters of globals, one mask, one two-store), build BYTE-IDENTICAL → all 22 byte-perfect. **≥25 bar PASS** (margin 36/25). `.run/harvest.py` = the applier. Dedup-collapse skipped (the sig dup groups are mostly PsyQ library fragments/epilogues, not real funcs). LZSS still required (Task 2′).
- [~] **Task 5 — Loader cluster** PARTIAL. Matched: **ResourceGetCdLoc** (15, table lookup), **LoaderResetReadState** (32, store seq) → byte-identical. Triage of the rest:
- [x] **Task 5.5 — Per-file -O0 split mechanism** ✓ DONE (session B, Drew-approved). MAJOR FINDING: per-module opt mixing is real (§5.5). The **boot/main/game-mode-dispatch module @ vram 0x80010000–0x800123F0** (~50 funcs incl. start/main/GameModeDispatch/DebugMenuHandler) is **-O0**, not the pinned -O2; everything ≥0x800123F0 is -O2. Mechanism: split text c-subseg → `[0x800,c,boot]→src/boot.c` (-O0 via Makefile target-specific CC1FLAGS) + `[0x2BF0,c,800]→src/800.c` (-O2, matched fns untouched). Regression gate PASS (byte-identical at 100% INCLUDE_ASM). Cookbook §6 added (detect via `21F0A003` frame-ptr sig; `register`-pointer far-member idiom). Reusable for every future flag-divergent module.
- [x] **Task 5.6 — PsyQ CD struct header** ✓ DONE (session B). `include/psyq/libcd.h` (CdlLOC 4B + CdlFILE 24B, offsets from Ghidra `/LIBCD.H`, G1) + 3 libcd protos; added 4 PsyQ symbols to `config/symbols.us.txt` (CdSearchFile/CdPosToInt/CdIntToPos/VSync — Ghidra-confirmed, R15) and renamed their `func_*` stubs in src/800.c. Build BYTE-IDENTICAL (label-only); header compiles, sizeof asserts hold. Unlocks the file-loader cluster (LoaderInitFileTable, ResourceLoadStateMachine, the jtbl loaders). Cookbook §7 added (PsyQ types/symbols pattern). **NB symbols.us.txt now has 983 lines (4 appended, un-sorted — re-merge note for R15).**
- [~] **Task 5 — Loader cluster** PARTIAL (42 real total; non-jtbl 5/7 matched). Matched (score 0, byte-identical): **ResourceGetCdLoc** (15), **LoaderResetReadState** (32), **GameModeDispatch** (29, -O0 `register`-ptr far member), **DebugMenuHandler** (30, -O0, reserved-slot local), **CdQueueBusy** (35, -O2 — if/else order + branch polarity), **CdReadRequest** (53, -O2 — early-return fall-through). Triage of the rest:
- **jtbl → Task 2′ (rodata-gated):** CdReadStateMachine (385), CdReadSectorReadyCB (424, sole LZSS caller), SaveLoadRoutine (1139), StreamLoadStateMachine (459).
- **non-jtbl, need Ghidra+iteration:** GameModeDispatch (29, base+offset addr + callee-saved across jalr), DebugMenuHandler (30), CdReadRequest (53), LoaderInitFileTable (133), ResourceLoadStateMachine (211), CdQueueBusy (35). Match or NON_MATCHING-draft in continuation.
- **non-jtbl COMPLETE (6 matched + 2 drafted):** **LoaderInitFileTable** (133) + **ResourceLoadStateMachine** (211) → **NON_MATCHING-drafted** (logically faithful, libcd.h-typed; per-function residual notes in src/800.c — LoaderInit: regalloc/loop-invariant hoisting + name=base-0x14; ResourceLoad: block placement (state 0→1→2→3 layout vs nested-if) + reserved-local frame 0x38 vs 0x28 + func_8002D4C8 arg & 0xFFFF). **NON_MATCHING count now 4.** Byte-match both in a later structural/permuter pass.
- **Report tooling fixed for the multi-file split:** `tools/{progress,difficulty}.py` now glob all `src/*.c` + search all `asm/nonmatchings/*` subdirs, and `progress.py` skips `extern …(…);` forward-declarations (they were swallowing the next fn + double-counting). Deterministic; 42 real / 1964 stubs consistent across both. (Note for PhaseEnd.)
- [ ] **Task 2′ — Focused LZSS + rodata-island sub-project** (DEFERRED hard task; Gen1-exit LZSS gate). **Max.** See investigation findings below.
- [ ] **Task 6 — Gen1-exit close-out** (README, checklist, ≥3-session zero-regression evidence). **xHigh.**
- [ ] **Task 7 — PhaseEnd_Phase7** (Gen1 synthesis, milestone gate). **Max · Tier 1.**
@@ -36,6 +39,7 @@ NOTE: Gen1 exit needs ≥3 SESSIONS of green `make check` — cannot complete th
## Per-session `make check` green log (≥3 sessions needed for the milestone)
- 2026-06-14 (session A): `make check` → `143dbb89… BYTE-IDENTICAL` ✓ — baseline restored + reproducibility fix, reports built, **38 real matches** (22 accessor leaves + ResourceGetCdLoc + LoaderResetReadState), build byte-identical throughout. [need ≥2 more sessions]
- 2026-06-14 (session B): `make check` → `143dbb89… BYTE-IDENTICAL` ✓ — **per-file -O0 split mechanism** (src/boot.c + Makefile per-file flags); **4 real matches** (GameModeDispatch, DebugMenuHandler, CdQueueBusy, CdReadRequest) → **42 real**; **PsyQ libcd.h infra** (CdlLOC/CdlFILE + 4 named symbols, unlocks the loader cluster); **LoaderInitFileTable + ResourceLoadStateMachine NON_MATCHING-drafted** (→ 4 NM) — **Task 5 non-jtbl loaders COMPLETE** (6 matched + 2 drafted); report tooling fixed (multi-file); cookbook §6/§7/T4. Build byte-identical throughout. [need ≥1 more session]
---
+276 -116
View File
@@ -1,114 +1,5 @@
#include "common.h"
INCLUDE_ASM("asm/nonmatchings/800", start);
INCLUDE_ASM("asm/nonmatchings/800", func_800100A0);
INCLUDE_ASM("asm/nonmatchings/800", __do_global_dtors);
INCLUDE_ASM("asm/nonmatchings/800", main);
INCLUDE_ASM("asm/nonmatchings/800", func_8001096C);
INCLUDE_ASM("asm/nonmatchings/800", func_8001099C);
INCLUDE_ASM("asm/nonmatchings/800", func_80010A08);
INCLUDE_ASM("asm/nonmatchings/800", func_80010A98);
INCLUDE_ASM("asm/nonmatchings/800", func_80010AE0);
INCLUDE_ASM("asm/nonmatchings/800", func_80010B10);
INCLUDE_ASM("asm/nonmatchings/800", GameModeDispatch);
INCLUDE_ASM("asm/nonmatchings/800", func_80010BB4);
INCLUDE_ASM("asm/nonmatchings/800", func_80010C7C);
INCLUDE_ASM("asm/nonmatchings/800", func_80010CEC);
INCLUDE_ASM("asm/nonmatchings/800", func_80010D60);
INCLUDE_ASM("asm/nonmatchings/800", func_80010DA0);
INCLUDE_ASM("asm/nonmatchings/800", func_80010DE0);
INCLUDE_ASM("asm/nonmatchings/800", func_80010E14);
INCLUDE_ASM("asm/nonmatchings/800", func_80010E48);
INCLUDE_ASM("asm/nonmatchings/800", func_80010E7C);
INCLUDE_ASM("asm/nonmatchings/800", func_80010ED4);
INCLUDE_ASM("asm/nonmatchings/800", func_80010F80);
INCLUDE_ASM("asm/nonmatchings/800", func_800110CC);
INCLUDE_ASM("asm/nonmatchings/800", DebugMenuHandler);
INCLUDE_ASM("asm/nonmatchings/800", func_800111BC);
INCLUDE_ASM("asm/nonmatchings/800", func_80011220);
INCLUDE_ASM("asm/nonmatchings/800", func_8001125C);
INCLUDE_ASM("asm/nonmatchings/800", func_800112A8);
INCLUDE_ASM("asm/nonmatchings/800", func_800112C8);
INCLUDE_ASM("asm/nonmatchings/800", func_80011320);
INCLUDE_ASM("asm/nonmatchings/800", func_80011350);
INCLUDE_ASM("asm/nonmatchings/800", func_80011380);
INCLUDE_ASM("asm/nonmatchings/800", func_80011680);
INCLUDE_ASM("asm/nonmatchings/800", func_800116E0);
INCLUDE_ASM("asm/nonmatchings/800", func_80011778);
INCLUDE_ASM("asm/nonmatchings/800", func_80011818);
INCLUDE_ASM("asm/nonmatchings/800", func_800118AC);
INCLUDE_ASM("asm/nonmatchings/800", func_80011928);
INCLUDE_ASM("asm/nonmatchings/800", func_80011998);
INCLUDE_ASM("asm/nonmatchings/800", func_800119F0);
INCLUDE_ASM("asm/nonmatchings/800", func_80011A3C);
INCLUDE_ASM("asm/nonmatchings/800", func_80011ADC);
INCLUDE_ASM("asm/nonmatchings/800", func_80011B7C);
INCLUDE_ASM("asm/nonmatchings/800", func_80011C10);
INCLUDE_ASM("asm/nonmatchings/800", func_80011C8C);
INCLUDE_ASM("asm/nonmatchings/800", func_80011CFC);
INCLUDE_ASM("asm/nonmatchings/800", func_80011D54);
INCLUDE_ASM("asm/nonmatchings/800", func_80011DA0);
INCLUDE_ASM("asm/nonmatchings/800", func_80011DCC);
INCLUDE_ASM("asm/nonmatchings/800", func_80011DF4);
INCLUDE_ASM("asm/nonmatchings/800", func_80011E24);
INCLUDE_ASM("asm/nonmatchings/800", func_80011E84);
INCLUDE_ASM("asm/nonmatchings/800", func_80011EB4);
INCLUDE_ASM("asm/nonmatchings/800", func_800120DC);
INCLUDE_ASM("asm/nonmatchings/800", func_8001212C);
#include "psyq/libcd.h"
INCLUDE_ASM("asm/nonmatchings/800", func_800123F0);
@@ -631,7 +522,113 @@ void func_800193A8(s32 arg0) {
INCLUDE_ASM("asm/nonmatchings/800", func_800193B8);
#ifdef NON_MATCHING
extern void LoaderResetReadState(void);
extern int VSync(int mode);
extern CdlFILE D_80063058;
extern char cdpath_DEBUG_BIN[];
extern u8 D_80062C38; /* path table entry[0].file (CdlFILE), entries stride 0x30; names at -0x14 */
extern u8 D_80062C68; /* path entry[1].file.pos (CdlLOC), the 8 .CD files, stride 0x30 */
extern s32 debugBinPresent;
extern s32 D_800747D4;
extern s32 D_80063074;
extern s32 D_800747D8;
extern u8 cdFileLocTable[]; /* out: {CdlLOC pos; u32 size} per sub-file, 8B stride */
extern u8 D_800AE834; /* cdFileLocTable + 4 (the size field) */
extern s32 listCdBuffer; /* LIST.CD content: 8B records {value; size} */
extern u8 D_80180004; /* listCdBuffer + 4 (the size field) */
/* file-loader directory resolver (boot @0x800101fc): probe \DEBUG.BIN;1, resolve the 21
* CdPathTable entries via CdSearchFile, read LIST.CD (0xE40 B) into listCdBuffer, then build
* cdFileLocTable (CdlLOC+size per sub-file) over the 8 .CD files. Phase 3 T2.
* NON_MATCHING: logically faithful, structurally close (138 vs 133 ins) but not byte-exact.
* Residual is register allocation / loop-invariant hoisting — the target keeps &D_80063058,
* &D_80062C38, &D_80062C68 in callee-saved regs and derives the name arg as (base - 0x14)
* rather than a separate symbol; a multi-iteration / decomp-permuter target for a later pass. */
void LoaderInitFileTable(void) {
CdlFILE *res;
int tries;
int pathOff;
int pathN;
int base;
int n;
int fileIdx;
int bufIdx;
int j;
int outer;
int locOff;
int dstOff;
int srcOff;
int *pCount;
int *pOff;
CdlLOC *p;
LoaderResetReadState();
tries = 0;
do {
res = CdSearchFile(&D_80063058, cdpath_DEBUG_BIN);
tries++;
if (res != (CdlFILE *)-1) {
break;
}
} while (tries < 0x10);
pathN = 0;
pathOff = 0;
debugBinPresent = (res != (CdlFILE *)0);
D_800747D4 = 1;
D_80063074 = 0;
D_800747D8 = 0;
do {
do {
res = CdSearchFile((CdlFILE *)(&D_80062C38 + pathOff), (char *)(&D_80062C38 - 0x14 + pathOff));
} while ((u32)((int)res + 1) < 2);
pathN++;
pathOff += 0x30;
} while (pathN < 0x15);
do {
fileIdx = 0;
n = CdReadRequest(&D_80062C38, &listCdBuffer, 0xE40, 0);
if (n != 0) {
break;
}
VSync(0);
} while (1);
bufIdx = 0;
outer = 0;
locOff = 0;
pCount = &listCdBuffer;
do {
base = CdPosToInt((CdlLOC *)(&D_80062C68 + locOff));
n = *pCount;
pCount += 2;
bufIdx++;
j = 0;
if (n > 0) {
dstOff = fileIdx * 8;
p = (CdlLOC *)(cdFileLocTable + dstOff);
srcOff = bufIdx * 8;
pOff = &listCdBuffer + bufIdx * 2;
do {
if (*pOff != 0) {
CdIntToPos(*pOff + base, p);
*(s32 *)(&D_800AE834 + dstOff) = *(s32 *)(&D_80180004 + srcOff);
}
pOff += 2;
srcOff += 8;
pCount += 2;
bufIdx++;
p += 2;
dstOff += 8;
j++;
fileIdx++;
} while (j < n);
}
outer++;
locOff += 0x30;
} while (outer < 8);
}
#else
INCLUDE_ASM("asm/nonmatchings/800", LoaderInitFileTable);
#endif
INCLUDE_ASM("asm/nonmatchings/800", func_80019930);
@@ -668,7 +665,44 @@ void LoaderResetReadState(void) {
INCLUDE_ASM("asm/nonmatchings/800", func_80019A10);
INCLUDE_ASM("asm/nonmatchings/800", CdReadRequest);
extern s32 CdQueueBusy(void);
extern void CdReadStateMachine(int);
extern s32 cdReq_curSector;
extern void *cdReq_dest;
extern s32 cdReq_size;
extern void *cdReq_cdlFile;
extern s32 cdReq_result;
extern s32 D_800AE720;
extern s32 D_800AE724;
/* Read-request dispatcher (Phase 3 T2). Refuse while CdQueueBusy(); dedup on the request's
* start sector (*cdlFile) vs the in-flight cdReq_curSector; stash dest/size/cdlFile/mode into
* the control block, set the "first read" flag D_800AE720 = (mode == 0), drive the state
* machine, return cdReq_result. */
s32 CdReadRequest(int *cdlFile, void *dest, s32 size, s32 mode) {
s32 sector;
if (CdQueueBusy() != 0) {
return 0;
}
if (cdReq_curSector == 0) {
sector = *cdlFile;
} else {
sector = *cdlFile;
if (sector != cdReq_curSector) {
return 0;
}
}
cdReq_dest = dest;
cdReq_size = size;
cdReq_cdlFile = cdlFile;
D_800AE724 = mode;
D_800AE720 = 0;
cdReq_curSector = sector;
if (mode == 0) {
D_800AE720 = 1;
}
CdReadStateMachine(0);
return cdReq_result;
}
INCLUDE_ASM("asm/nonmatchings/800", CdReadStateMachine);
@@ -739,7 +773,107 @@ void func_8001B384(void) {
INCLUDE_ASM("asm/nonmatchings/800", func_8001B394);
#ifdef NON_MATCHING
extern int func_8001A114(void);
extern void func_8001B710(void);
extern void func_8002D4C8(int arg0, int arg1);
extern void func_80036D58(int arg0);
extern int StreamLoadStateMachine(int arg0, void *loc, int n);
extern s32 CdQueueBusy(void); /* defined later in this file */
extern s32 ResourceGetCdLoc(s16 arg0); /* defined later in this file */
extern s32 resLoad_state;
extern s32 resLoad_curId;
extern s32 resLoad_lastId;
extern s32 resLoad_loadedFileIdx;
extern s32 resLoad_result;
extern s32 cdReq_curSector;
extern u8 resourceIdMap[]; /* 6B entries {s16 fileIdx; s16 D_8006313A; s16 D_8006313C} */
extern u8 D_8006313A;
extern u8 D_8006313C;
extern u8 cdFileLocTable[];
extern s32 D_800AE6F4;
extern s32 D_800AE70C;
/* loads a resource by resLoad_curId via resourceIdMap; load-once cache
* (resLoad_lastId/resLoad_loadedFileIdx). field0<0 -> non-CD path func_80036D58; else
* ResourceGetCdLoc -> StreamLoadStateMachine -> func_8002D4C8 post-process. Polled on
* resLoad_state; done flag resLoad_result. Phase 3 T3.
* NON_MATCHING: logically faithful (Ghidra-derived), body close but not byte-exact. Residuals:
* (1) block placement — the target lays state blocks 0->1->2->3 in order under a top beq-chain
* dispatch (order 1,0,2,3); this nested-if emits the state-2/3 block inline. (2) frame 0x38 vs
* 0x28 (~16B reserved locals the original keeps). (3) func_8002D4C8 arg is field2 & 0xFFFF.
* A switch() risks a rodata jump table (target has none). A later structural / permuter pass. */
void ResourceLoadStateMachine(void) {
int result;
result = 0;
if (resLoad_state != 1) {
if (resLoad_state != 0) {
if (resLoad_state == 2) {
D_800AE6F4 = 0;
D_800AE70C = 0;
resLoad_state = 3;
} else if (resLoad_state != 3) {
goto done;
}
if (func_8001A114() != 0) {
resLoad_state = 0;
}
goto done;
}
if (resLoad_curId == resLoad_lastId) {
resLoad_result = 1;
return;
}
if (*(s16 *)(resourceIdMap + resLoad_curId * 6) == resLoad_loadedFileIdx &&
*(s16 *)(&D_8006313C + resLoad_curId * 6) != 0) {
func_8002D4C8(*(s16 *)(&D_8006313C + resLoad_curId * 6), 0);
resLoad_lastId = resLoad_curId;
resLoad_result = 1;
return;
}
if (CdQueueBusy() != 0) {
goto done;
}
if (*(s16 *)(resourceIdMap + resLoad_curId * 6) < 0) {
func_80036D58(*(s16 *)(&D_8006313A + resLoad_curId * 6));
resLoad_result = 1;
return;
}
if (ResourceGetCdLoc((s16)resLoad_curId) == 0) {
resLoad_result = 1;
return;
}
if (cdReq_curSector != 0 &&
*(s32 *)(cdFileLocTable + *(s16 *)(resourceIdMap + resLoad_curId * 6) * 8) != cdReq_curSector) {
goto done;
}
cdReq_curSector = *(s32 *)(cdFileLocTable + *(s16 *)(resourceIdMap + resLoad_curId * 6) * 8);
resLoad_state++;
}
result = StreamLoadStateMachine(*(s16 *)(&D_8006313A + resLoad_curId * 6),
cdFileLocTable + *(s16 *)(resourceIdMap + resLoad_curId * 6) * 8, 0x10);
if (result == 2) {
resLoad_state++;
result = 0;
}
done:
if (result != 0) {
resLoad_loadedFileIdx = *(s16 *)(resourceIdMap + resLoad_curId * 6);
if (*(s16 *)(&D_8006313C + resLoad_curId * 6) > 0) {
func_8002D4C8(*(s16 *)(&D_8006313C + resLoad_curId * 6), 0);
resLoad_lastId = resLoad_curId;
if (resLoad_curId == 0x3D) {
func_8001B710();
}
}
resLoad_state = 0;
cdReq_curSector = 0;
}
resLoad_result = result;
}
#else
INCLUDE_ASM("asm/nonmatchings/800", ResourceLoadStateMachine);
#endif
INCLUDE_ASM("asm/nonmatchings/800", func_8001B710);
@@ -1652,7 +1786,33 @@ INCLUDE_ASM("asm/nonmatchings/800", func_80034B0C);
INCLUDE_ASM("asm/nonmatchings/800", func_80034B3C);
INCLUDE_ASM("asm/nonmatchings/800", CdQueueBusy);
extern s32 D_8006AEF8;
extern s32 D_8006AEFC;
extern u8 D_80076214;
extern u8 D_8007620C;
/* CD read-queue status: head==tail (D_8006AEF8/FC equal) => idle; flags D_80076214 (a
* pending/error byte) and D_8007620C (bit7/bit5) classify the busy/result state.
* Returns 0=idle done, 8=had pending, 2/4/1=busy variants. */
s32 CdQueueBusy(void) {
if (D_8006AEF8 != D_8006AEFC) {
if (D_80076214 == 0) {
if (D_8007620C & 0x80) {
return 2;
}
if (D_8007620C & 0x20) {
return 4;
}
return 1;
}
} else {
D_8007620C = 0;
if (D_80076214 == 0) {
return 0;
}
}
D_80076214 = 0;
return 8;
}
INCLUDE_ASM("asm/nonmatchings/800", func_80034C24);
@@ -2350,7 +2510,7 @@ INCLUDE_ASM("asm/nonmatchings/800", func_800422E8);
INCLUDE_ASM("asm/nonmatchings/800", func_80042374);
INCLUDE_ASM("asm/nonmatchings/800", func_8004239C);
INCLUDE_ASM("asm/nonmatchings/800", VSync);
INCLUDE_ASM("asm/nonmatchings/800", VSYNC_OBJ_84);
@@ -2490,9 +2650,9 @@ INCLUDE_ASM("asm/nonmatchings/800", func_800439D4);
INCLUDE_ASM("asm/nonmatchings/800", func_800439F8);
INCLUDE_ASM("asm/nonmatchings/800", func_80043A18);
INCLUDE_ASM("asm/nonmatchings/800", CdIntToPos);
INCLUDE_ASM("asm/nonmatchings/800", func_80043B1C);
INCLUDE_ASM("asm/nonmatchings/800", CdPosToInt);
INCLUDE_ASM("asm/nonmatchings/800", func_80043B9C);
@@ -2558,7 +2718,7 @@ INCLUDE_ASM("asm/nonmatchings/800", callback);
INCLUDE_ASM("asm/nonmatchings/800", BIOS_OBJ_1728);
INCLUDE_ASM("asm/nonmatchings/800", func_80045374);
INCLUDE_ASM("asm/nonmatchings/800", CdSearchFile);
INCLUDE_ASM("asm/nonmatchings/800", ISO9660_OBJ_F8);
+142
View File
@@ -0,0 +1,142 @@
#include "common.h"
INCLUDE_ASM("asm/nonmatchings/boot", start);
INCLUDE_ASM("asm/nonmatchings/boot", func_800100A0);
INCLUDE_ASM("asm/nonmatchings/boot", __do_global_dtors);
INCLUDE_ASM("asm/nonmatchings/boot", main);
INCLUDE_ASM("asm/nonmatchings/boot", func_8001096C);
INCLUDE_ASM("asm/nonmatchings/boot", func_8001099C);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010A08);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010A98);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010AE0);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010B10);
/* (*gameModeHandlerTable[gameMode])() — 18 handlers @0x800629F4 (entry [7] =
* DebugMenuHandler). gameMode is a u16 at D_800AF630 + 0xA3AE (= 0x800B99DE) in the
* main state block. NOTE: `register` is REQUIRED to match — at -O0 (this module) it
* keeps the struct base in a callee-saved reg with no stack spill, and the 0xA3AE
* (>0x7FFF) member offset then assembles to the +0x10000/-0x5C52 split. */
extern void (*gameModeHandlerTable[])(void);
extern u8 D_800AF630[];
void GameModeDispatch(void) {
register u8 *p = D_800AF630;
gameModeHandlerTable[*(u16 *)(p + 0xA3AE)]();
}
INCLUDE_ASM("asm/nonmatchings/boot", func_80010BB4);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010C7C);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010CEC);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010D60);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010DA0);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010DE0);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010E14);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010E48);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010E7C);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010ED4);
INCLUDE_ASM("asm/nonmatchings/boot", func_80010F80);
INCLUDE_ASM("asm/nonmatchings/boot", func_800110CC);
extern void func_8001A9F8(int);
extern int CdReadRequest(void *dst, void *src, int arg2, int arg3);
extern void func_80010AE0(int);
extern void func_80011778(void);
extern void func_80015310(void);
extern u8 D_800AE888;
extern void *loadDestPtrTable;
/* gameMode 7 handler (gameModeHandlerTable[7]) — the TCRF L3 debug menu. Streams the
* room-select overlay (cdFileLocTable[11] -> 0x800CEDF8) via CdReadRequest, then inits it
* (func_80011778 / func_80015310). Retail: only room-select works. Phase 3 T8. */
void DebugMenuHandler(void) {
int iVar1; /* -O0 reserves this local's 8-byte slot (frame 0x20); the original
* checks CdReadRequest's result directly (no store) — assigning iVar1
* here would emit a store the target lacks, so it stays declaration-only. */
func_8001A9F8(0);
if (CdReadRequest(&D_800AE888, loadDestPtrTable, 0, 0) != 0) {
func_80010AE0(0x3E0);
func_80011778();
func_80015310();
}
(void)iVar1;
}
INCLUDE_ASM("asm/nonmatchings/boot", func_800111BC);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011220);
INCLUDE_ASM("asm/nonmatchings/boot", func_8001125C);
INCLUDE_ASM("asm/nonmatchings/boot", func_800112A8);
INCLUDE_ASM("asm/nonmatchings/boot", func_800112C8);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011320);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011350);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011380);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011680);
INCLUDE_ASM("asm/nonmatchings/boot", func_800116E0);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011778);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011818);
INCLUDE_ASM("asm/nonmatchings/boot", func_800118AC);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011928);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011998);
INCLUDE_ASM("asm/nonmatchings/boot", func_800119F0);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011A3C);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011ADC);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011B7C);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011C10);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011C8C);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011CFC);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011D54);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011DA0);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011DCC);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011DF4);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011E24);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011E84);
INCLUDE_ASM("asm/nonmatchings/boot", func_80011EB4);
INCLUDE_ASM("asm/nonmatchings/boot", func_800120DC);
INCLUDE_ASM("asm/nonmatchings/boot", func_8001212C);
+23 -15
View File
@@ -11,11 +11,17 @@ Usage: tools/difficulty.py [TOP] (TOP = how many easy rows in the md digest, d
import re, sys, pathlib
ROOT = pathlib.Path(__file__).resolve().parent.parent
SRC = ROOT / "src" / "800.c"
ASM = ROOT / "asm" / "nonmatchings" / "800"
SRCS = sorted((ROOT / "src").glob("*.c")) # every c-segment (src/boot.c, src/800.c, ...)
ASM_ROOT = ROOT / "asm" / "nonmatchings" # per-segment subdirs (boot/, 800/, ...)
MD = ROOT / "docs" / "difficulty.md"
CSV = ROOT / ".run" / "difficulty.csv"
def find_s(name):
"""Locate <name>.s in any asm/nonmatchings/<seg>/ subdir."""
for p in sorted(ASM_ROOT.glob(f"*/{name}.s")):
return p
return None
INSTR = re.compile(r'^\s*/\*\s*[0-9A-Fa-f]+\s+([0-9A-Fa-f]+)\s+[0-9A-Fa-f]+\s*\*/\s+([a-z][a-z0-9.]*)')
BRANCH = re.compile(r'^b(eq|ne|gez|gtz|lez|ltz|nez|eqz|c1t|c1f|gezal|ltzal)?z?$')
@@ -24,22 +30,24 @@ def is_data_blob(txt):
def unmatched_stubs():
"""INCLUDE_ASM names that are real functions (exclude data-blobs) and not inside NON_MATCHING."""
lines = SRC.read_text().split('\n'); n = len(lines); i = 0; out = []
while i < n:
s = lines[i].strip()
if s.startswith('#ifdef NON_MATCHING'):
while i < n and not lines[i].strip().startswith('#endif'):
i += 1
i += 1; continue
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
if m:
out.append(m.group(1))
i += 1
out = []
for src in SRCS:
lines = src.read_text().split('\n'); n = len(lines); i = 0
while i < n:
s = lines[i].strip()
if s.startswith('#ifdef NON_MATCHING'):
while i < n and not lines[i].strip().startswith('#endif'):
i += 1
i += 1; continue
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
if m:
out.append(m.group(1))
i += 1
return out
def analyze(name):
p = ASM / f"{name}.s"
if not p.exists(): return None
p = find_s(name)
if p is None: return None
txt = p.read_text()
if is_data_blob(txt): return None # not a function
nins = branches = ncalls = 0; last_vaddr = None; jtbl = False
+53 -35
View File
@@ -13,12 +13,18 @@ Usage:
import re, sys, hashlib, pathlib
ROOT = pathlib.Path(__file__).resolve().parent.parent
SRC = ROOT / "src" / "800.c"
ASM = ROOT / "asm" / "nonmatchings" / "800"
SRCS = sorted((ROOT / "src").glob("*.c")) # every c-segment (src/boot.c, src/800.c, ...)
ASM_ROOT = ROOT / "asm" / "nonmatchings" # per-segment subdirs (boot/, 800/, ...)
OUT = ROOT / "docs" / "progress.md"
BUILD = ROOT / "build" / "us" / "SLUS_007.26"
CHECK = ROOT / "config" / "check.us.sha"
def find_s(name):
"""Locate <name>.s in any asm/nonmatchings/<seg>/ subdir (segments: boot, 800, ...)."""
for p in sorted(ASM_ROOT.glob(f"*/{name}.s")):
return p
return None
INSTR = re.compile(r'^\s*/\*\s*[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s+[0-9A-Fa-f]+\s*\*/\s+[a-z]')
def strip_comments(s):
@@ -27,16 +33,16 @@ def strip_comments(s):
def is_data_blob(name):
"""A .s with a code label (glabel/jlabel) is a function; data-only (dlabel, no code) is a blob."""
p = ASM / f"{name}.s"
if not p.exists():
p = find_s(name)
if p is None:
return False
txt = p.read_text()
return ('glabel' not in txt and 'jlabel' not in txt and 'dlabel' in txt)
def asm_is_trivial(name):
"""True iff the function's asm is exactly {jr, nop} (the empty-no-op shape splat emits void{} for)."""
p = ASM / f"{name}.s"
if not p.exists():
p = find_s(name)
if p is None:
return None
mnem = []
for ln in p.read_text().splitlines():
@@ -48,37 +54,49 @@ def asm_is_trivial(name):
SIG = re.compile(r'^\s*[A-Za-z_][\w \t\*]*\b([A-Za-z_]\w*)\s*\(')
def classify():
lines = SRC.read_text().split('\n')
n = len(lines); i = 0
real, empty, nonmatching, stubs, blobs = [], [], [], [], []
while i < n:
s = lines[i].strip()
if s.startswith('#ifdef NON_MATCHING'):
blk = []
while i < n and not lines[i].strip().startswith('#endif'):
blk.append(lines[i]); i += 1
i += 1
m = re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(blk))
if m: nonmatching.append(m.group(1))
continue
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
if m:
(blobs if is_data_blob(m.group(1)) else stubs).append(m.group(1)); i += 1; continue
if s.startswith('INCLUDE_RODATA'):
i += 1; continue
fm = SIG.match(lines[i])
if fm and '(' in lines[i]:
start = i; depth = 0; opened = False
while i < n:
c = strip_comments(lines[i]); depth += c.count('{') - c.count('}')
if '{' in c: opened = True
for src in SRCS:
lines = src.read_text().split('\n')
n = len(lines); i = 0
while i < n:
s = lines[i].strip()
if s.startswith('#ifdef NON_MATCHING'):
blk = []
while i < n and not lines[i].strip().startswith('#endif'):
blk.append(lines[i]); i += 1
i += 1
if opened and depth <= 0: break
body = '\n'.join(lines[start:i])
a, b = body.index('{'), body.rindex('}')
(real if strip_comments(body[a+1:b]).strip() else empty).append(fm.group(1))
continue
i += 1
m = re.search(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', '\n'.join(blk))
if m: nonmatching.append(m.group(1))
continue
m = re.match(r'INCLUDE_ASM\("[^"]+",\s*(\w+)\)', s)
if m:
(blobs if is_data_blob(m.group(1)) else stubs).append(m.group(1)); i += 1; continue
if s.startswith('INCLUDE_RODATA'):
i += 1; continue
fm = SIG.match(lines[i])
if fm and '(' in lines[i]:
# Definition ({ ... }) vs forward declaration (ends ;)? Scan to the first { or ;.
# extern/prototype lines (e.g. `extern s32 CdQueueBusy(void);`) are NOT functions.
j = i; kind = None
while j < n:
c = strip_comments(lines[j])
br = c.find('{'); sm = c.find(';')
if br != -1 and (sm == -1 or br < sm): kind = 'def'; break
if sm != -1: kind = 'decl'; break
j += 1
if kind != 'def':
i = j + 1; continue # skip the declaration
start = i; depth = 0; opened = False
while i < n:
c = strip_comments(lines[i]); depth += c.count('{') - c.count('}')
if '{' in c: opened = True
i += 1
if opened and depth <= 0: break
body = '\n'.join(lines[start:i])
a, b = body.index('{'), body.rindex('}')
(real if strip_comments(body[a+1:b]).strip() else empty).append(fm.group(1))
continue
i += 1
return real, empty, nonmatching, stubs, blobs
def main():