mirror of
https://github.com/Druthulu/BFM-decomp
synced 2026-09-30 07:31:59 -04:00
feat(phase-29): caller pair banked (57,822 templ ins) via K&R defs — §92's remedy corrected (§99)
func_80175AB8 + func_80175DA8 both banked. R22 clean-fleet 140 passed / 0 failed of 140.
§92 SAID these need "the §17a-1 caller pair, NOT a bare conform" — the diagnosis was right (conforming
a narrow param changes argument promotion at every call site, measured PLUMBING -> DIFF) but the
remedy was the expensive one. The actual fix touches NO declaration: convert the DEFINITION to K&R,
where a narrow param PROMOTES to int (C89 6.3.2.2) and is therefore already compatible with the
fleet's existing `s32` prototype, while still emitting narrow-param codegen. §43 applied to the def
side. T0 draft-only, ZERO blast radius, versus a 524-site fleet conform.
THREE reconcile_tu BUGS SURFACED, ONE OF THEM MINE:
(a) BLIND TO BLOCK SCOPE. split_statements is depth-0 BY DESIGN, and §8d deliberately demotes data
externs into the function body — so the tool saw one statement and no declarations, printing
"reconciled: 0 draft(s), 0 data symbol(s); coverage defects: 0" for a draft cc1 rejected with
`conflicting types for D_8011F7BC`. A silent skip (R32). Fixed: descend one level.
(b) MY BUG, introduced by (a): descending into ANY `{` also enters struct/union/enum definitions, so
MEMBERS parse as declarations and get conformed — `u32 code;` became the TU's
`typedef void (*code)(unsigned short*);` INSIDE the struct, and `p->code` became
`p->(*(u32 *)&code)`. Caught by DIFFING THE TOOL'S OUTPUT AGAINST ITS INPUT before trusting it;
the byte-gate would have said PLUMBING and explained nothing. Guard: function bodies only.
(c) LATENT since the tool was written: _cast_sub matched bare identifiers and rewrote MEMBER ACCESSES
as globals. Unreachable until (a) existed. Guard: (?<![.\w])(?<!->).
cookbook §99.
This commit is contained in:
@@ -7142,3 +7142,52 @@ defining TU's own decl); definition + register pin intact; both drafts re-banked
|
||||
> binary **succeeded** — because it reused objects the clean run rebuilds. *An incremental pass does
|
||||
> not refute a clean-tree failure.* Every step of this diagnosis came from reading the real cc1/ld
|
||||
> error after a genuinely clean rebuild, never from reasoning about what the tool "should" do.
|
||||
|
||||
## §99 — The narrow-param wall is a DEF-side problem with a ZERO-blast-radius fix: convert the definition to K&R (Phase 29 SESSION-22, `func_80175AB8` + `func_80175DA8`)
|
||||
|
||||
**§92 said these two need "the §17a-1 caller pair, NOT a bare conform".** The diagnosis was right —
|
||||
conforming the fleet's `void f(s32)` declarations to the byte-true `void f(s16)` narrows the
|
||||
parameter, changing argument promotion at **every** call site, so the callers emit different code
|
||||
(§92 measured exactly that: PLUMBING before the conform, **DIFF** after). But the prescribed remedy
|
||||
was the expensive one. The actual fix touches **no declaration at all**:
|
||||
|
||||
```c
|
||||
void func_80175AB8(param_1) /* K&R: the narrow param PROMOTES to int (C89 6.3.2.2), */
|
||||
s16 param_1; /* so this is ALREADY compatible with the fleet's `s32` */
|
||||
{ /* prototype — while still emitting the narrow-param codegen. */
|
||||
```
|
||||
|
||||
That is §43 applied to the **definition** side instead of the declaration side. Cost: a draft-only
|
||||
edit (**T0**, zero blast radius) versus a **524-site fleet conform**. Both banked, `d19c9580`,
|
||||
R22 140/140 — **57,822 templated instructions for two draft-local rewrites.**
|
||||
|
||||
> **The law:** when the byte-true signature has a NARROW scalar parameter and the fleet declares it
|
||||
> wide, do NOT move the declarations. Move the DEFINITION to K&R and let C's promotion rule make the
|
||||
> existing prototype correct. Conform only when the disagreement is a POINTER shape (caller-neutral,
|
||||
> §85) or an arity/return change.
|
||||
|
||||
### Two `reconcile_tu` bugs found underneath, one introduced while fixing the other
|
||||
|
||||
**(a) It was blind to BLOCK-SCOPE declarations.** `split_statements` is depth-0 **by design**, so for
|
||||
a draft whose body is one function definition it returns exactly ONE statement and every declaration
|
||||
inside is invisible — and §8d (`scope_data_externs`) *deliberately demotes the data externs to block
|
||||
scope*. C still requires a block-scope `extern` to agree with a file-scope declaration in scope, so
|
||||
the conflict is real: the tool printed `reconciled: 0 draft(s), 0 data symbol(s); coverage defects: 0`
|
||||
for a draft cc1 then rejected with `conflicting types for D_8011F7BC`. Fixed by descending one level.
|
||||
|
||||
**(b) DESCENDING INTO ANY `{` CORRUPTS STRUCTS — a bug I introduced with (a) and caught by diffing
|
||||
the tool's own output against its input.** Struct MEMBERS parse as declarations and get "conformed":
|
||||
```
|
||||
- u32 code; /* 0x04 */ -> typedef void (*code)(unsigned short*);
|
||||
- p->code = *(u32 *)src; -> p->(*(u32 *)&code) = *(u32 *)src;
|
||||
```
|
||||
Guard: descend only into a FUNCTION body (a parameter list before the brace, and not a
|
||||
`typedef|struct|union|enum` head).
|
||||
|
||||
**(c) And it exposed a LATENT one:** `_cast_sub`'s regex matched a bare identifier, so it rewrote
|
||||
**member accesses** as if they were the global. Broken since the tool was written; only reachable
|
||||
once block-scope descent started finding such names. Guard: `(?<![.\w])(?<!->)`.
|
||||
|
||||
> **Process note worth keeping:** (b) was caught because the transform's output was diffed against
|
||||
> its input *before* the result was trusted — not by a gate. The byte-gate would have reported
|
||||
> PLUMBING and told me nothing about *why*, and the corrupted draft looked plausible.
|
||||
|
||||
@@ -6136,3 +6136,25 @@ closure-scope error) — on the tool that banked 543 members today. **Reverted,
|
||||
Restructuring a proven tool with blind string replaces at the end of a long session is how a working
|
||||
thing gets broken. Left as a specified next-session task; `sweep_parallel.py` already exists and is
|
||||
proven, so the work is *wiring*, not invention.
|
||||
|
||||
## ✅ T22 — the caller pair BANKED (57,822 templ ins) with ZERO fleet edits; §92's remedy corrected
|
||||
|
||||
**`func_80175AB8` + `func_80175DA8` both banked.** R22 clean-fleet **140/140**.
|
||||
|
||||
**§92 prescribed the expensive remedy.** Its diagnosis was right (conforming a narrow param changes
|
||||
argument promotion at every call site → DIFF), but the fix needs **no declaration touched**: convert
|
||||
the DEFINITION to **K&R**, where the narrow param promotes to `int` and is therefore already
|
||||
compatible with the fleet's existing `s32` prototype — §43 applied to the def side. **T0, zero blast
|
||||
radius, versus a 524-site fleet conform.** → cookbook **§99**.
|
||||
|
||||
**Three `reconcile_tu` bugs surfaced, and ONE WAS MINE:**
|
||||
- **(a)** blind to **block-scope** declarations (`split_statements` is depth-0 by design, and §8d
|
||||
*deliberately demotes* data externs into the function body) → reported `0 reconciled / 0 coverage
|
||||
defects` for a draft cc1 rejected with `conflicting types for D_8011F7BC`. Fixed: descend one level.
|
||||
- **(b) MY BUG, introduced by (a):** descending into ANY `{` also enters struct definitions, so
|
||||
members parse as declarations and get conformed — it rewrote `u32 code;` into the TU's
|
||||
`typedef void (*code)(...)` INSIDE the struct and mangled `p->code` → `p->(*(u32 *)&code)`.
|
||||
**Caught by diffing the tool's output against its input before trusting it** — the gate would only
|
||||
have said PLUMBING. Guard: descend only into a function body.
|
||||
- **(c)** latent since the tool was written: `_cast_sub` matched bare identifiers, rewriting **member
|
||||
accesses** as globals. Only reachable once (a) existed. Guard: `(?<![.\w])(?<!->)`.
|
||||
|
||||
@@ -2998,9 +2998,278 @@ INCLUDE_ASM("asm/ov_SC01_077/nonmatchings/ov_SC01_077_jr_801734BC", func_801758F
|
||||
|
||||
INCLUDE_ASM("asm/ov_SC01_077/nonmatchings/ov_SC01_077_jr_801734BC", func_801759D8);
|
||||
|
||||
INCLUDE_ASM("asm/ov_SC01_077/nonmatchings/ov_SC01_077_jr_801734BC", func_80175AB8);
|
||||
// @class: regalloc-order
|
||||
// @stuck: none — MATCH (188/188), symcheck SYMS-OK (22 symbols)
|
||||
//
|
||||
// func_80175AB8 — ov_SC01_077 h_seq exemplar (188 ins, ×138 members).
|
||||
// Zero file-scope footprint (§28/§43): typedef + every extern is block-scoped so the body
|
||||
// lifts as one unit for dedup_propagate / family_sweep.
|
||||
//
|
||||
// FOUR byte-proven levers found here (all newly measured this session, closeness 15 -> 0):
|
||||
//
|
||||
// L1 base-pointer opacity WITHOUT killing the birthing boost (sched.c birthing_insn_p).
|
||||
// $s2 must hold &D_8011F7F0 while `arr = base - 0x48` stays a SEPARATE addiu, so the
|
||||
// symbol must not constant-fold. The obvious fence `__asm__("" : "=r"(base) : "0"(base))`
|
||||
// works but sets `base` TWICE => reg_n_sets[base]==2 => birthing_insn_p() returns 0 =>
|
||||
// adjust_priority() skips the max_priority boost => the `la` is scheduled at the very top
|
||||
// instead of after `sll $s3,$v0,2`. Fix: fence a SINGLE-set pseudo —
|
||||
// __asm__("" : "=r"(base) : "0"(&D_8011F7F0));
|
||||
// `base` is now set once (by the asm) and the address temp once (by the `la`), so BOTH keep
|
||||
// the birthing boost and land where the target puts them. (15 -> 5 mismatches.)
|
||||
//
|
||||
// L2 a volatile STORE is a memory barrier that register moves may cross; a
|
||||
// `__asm__ __volatile__("" ::: "memory")` is a FULL barrier that they may not.
|
||||
// sched.c's ASM_OPERANDS case adds a dependence on every reg's last set/use for a volatile
|
||||
// asm, so the memory fence pinned `sw $s1,0x18($s4)` BEFORE the call's `addu $a0,$s1,$zero`.
|
||||
// Writing the store as `*(volatile u32 *)&slot[6] = ...` keeps the store anchored (dropping
|
||||
// the fence entirely let it sink 11 slots) while letting the arg move hoist past it.
|
||||
// (5 -> 3 mismatches.) The SECOND memory fence (before func_80024054) is still required.
|
||||
//
|
||||
// L3 local-alloc.c block_alloc ties operand 0 to the first input operand that DIES
|
||||
// (combine_regs). `sum = r1 + r2` therefore inherits r1's quantity — and r1 crosses a call,
|
||||
// so the whole quantity is forced call-preserved ($s0) and the `move $5,ext` copy-suggestion
|
||||
// is ignored. Neither variable reuse nor an extra post-addu use of r1 breaks the tie
|
||||
// (sched1 runs first and re-sinks the use). A pin whose live range crosses no call does:
|
||||
// register s32 sum __asm__("$5");
|
||||
// (§44-correction: a pin that does not span a `jal` is ×N-safe.) (3 -> 2 mismatches.)
|
||||
//
|
||||
// L4 pinning `sum` to $a1 makes gcc emit it before the $a0 arg move; pinning the $a0 arg too
|
||||
// (`register u32 *a0v __asm__("$4"); a0v = p;`) restores the target order. (2 -> 0.)
|
||||
//
|
||||
// Also note the deliberate MIXED addressing of the same objects: (*(u16 *)&D_8011F7BC)/(*(u16 *)&D_8011F7BE) are read
|
||||
// absolutely at the 3rd call (lui/lhu) and $s2-relatively (-0x34/-0x32) at the 4th/6th/7th. That
|
||||
// is what the target does; writing either form uniformly breaks it.
|
||||
|
||||
#include "common.h"
|
||||
|
||||
void func_80175AB8(param_1)
|
||||
s16 param_1;
|
||||
{
|
||||
typedef struct { s32 g0; s32 pad[2]; } S_AF634; /* size 0x0C */
|
||||
extern S_AF634 D_800AF634[];
|
||||
extern S_AF634 D_800AF638[];
|
||||
|
||||
extern u8 D_8011F7F0;
|
||||
extern u16 D_8011F7B4;
|
||||
extern u16 D_8011F7B6;
|
||||
extern s16 D_8011F7BC;
|
||||
extern s16 D_8011F7BE;
|
||||
extern s32 D_8011F804;
|
||||
extern u16 D_8011F80A;
|
||||
extern u16 D_8011F824;
|
||||
extern u8 D_8011F832;
|
||||
extern u8 D_8011F83A;
|
||||
extern u8 *D_8018A23C[];
|
||||
|
||||
extern u32 *func_80176D94(void *param_1, u32 param_2, s16 param_3_);
|
||||
extern u32 *func_801770E0(void *param_1, u32 param_2, s16 param_3_);
|
||||
extern u32 func_801783D0(s32 a0, s32 a1);
|
||||
extern u32 *func_80177EA4(u32 *param_1, s32 param_2, u32 param_3, s32 param_4);
|
||||
extern u32 *func_80177B5C(u32 *a0, s32 a1, s32 a2, s32 a3, s32 a4);
|
||||
extern void func_80177940(u32 *p, u32 a_, u32 b_, u32 c_);
|
||||
extern u32 *func_80178298(u32 *param_1, u8 *param_2, short param_3, short param_4);
|
||||
extern s32 func_80024054(u8*, u8*);
|
||||
extern s32 func_8005A600(s32, s32, s32, s32, s32);
|
||||
|
||||
u8 local[24];
|
||||
u8 *base;
|
||||
u8 *arr;
|
||||
u32 *slot;
|
||||
u32 *p;
|
||||
register u32 *a0v __asm__("$4");
|
||||
u32 uv;
|
||||
s32 k;
|
||||
s16 q;
|
||||
s32 r1;
|
||||
s32 a2v;
|
||||
register s32 sum __asm__("$5");
|
||||
s32 r2;
|
||||
s32 idx;
|
||||
s16 sv;
|
||||
|
||||
p = (u32 *)(D_800AF638[param_1].g0 + D_800AF634[param_1].g0 * 4);
|
||||
__asm__("" : "=r"(base) : "0"(&D_8011F7F0)); /* L1 */
|
||||
arr = base - 0x48;
|
||||
slot = (u32 *)(param_1 * 4 + (s32)arr);
|
||||
*(volatile u32 *)&slot[6] = (u32)p; /* L2 */
|
||||
p = func_80176D94(p, (s16)(D_8011F7B4 - 0x71), (s16)(D_8011F7B6 + 0x51));
|
||||
p = func_801770E0(p, (s16)(D_8011F7B4 - 0x71), (s16)(D_8011F7B6 + 0x65));
|
||||
p = func_80177EA4(p, func_801783D0(D_8011F804, 0),
|
||||
(s16)((*(u16 *)&D_8011F7BC) + 0x39), (s16)((*(u16 *)&D_8011F7BE) + 0x51));
|
||||
uv = D_8011F824;
|
||||
k = 0x3E7;
|
||||
if (uv < 1000) {
|
||||
k = uv;
|
||||
}
|
||||
sv = (s16)func_801783D0(k, 4);
|
||||
p = func_80177B5C(p, sv, D_8011F832,
|
||||
(s16)(*(u16 *)(base - 0x34) + 0x39),
|
||||
(s16)(*(u16 *)(base - 0x32) + 0x65));
|
||||
q = (s16)D_8011F80A / 15;
|
||||
a2v = (q & 3) * 15;
|
||||
r1 = func_801783D0((s32)(q << 16) >> 18, 8);
|
||||
r2 = func_801783D0(a2v, 0);
|
||||
a0v = p; /* L4 */
|
||||
sum = r1 + r2; /* L3 */
|
||||
p = ((u32 * (*)(u32 *, s32, s32, s32))func_80177940)(
|
||||
a0v, (s16)sum,
|
||||
(s16)(*(u16 *)(base - 0x34) + 0x76),
|
||||
(s16)(*(u16 *)(base - 0x32) + 0x65));
|
||||
idx = D_8011F83A & 0x7F;
|
||||
D_8011F83A = idx;
|
||||
__asm__ __volatile__("" ::: "memory");
|
||||
((void (*)(s32, u8 *))func_80024054)(((s32 *)D_8018A23C)[idx], local);
|
||||
p = func_80178298(p, local,
|
||||
(s16)(*(u16 *)(base - 0x34) + 0x39),
|
||||
(s16)(*(u16 *)(base - 0x32) + 0x5B));
|
||||
func_8005A600((s32)p, 0, 0, 0x15, 0);
|
||||
*p = (((u32)p - 0x14) & 0xFFFFFF) | 0x2000000;
|
||||
slot[8] = (u32)p;
|
||||
p += 5;
|
||||
D_800AF634[param_1].g0 += ((s32)p - (s32)slot[6]) >> 2;
|
||||
}
|
||||
|
||||
|
||||
// @class: schedule
|
||||
// @stuck: none — MATCH (231 ins)
|
||||
#include "common.h"
|
||||
|
||||
extern u8 D_8011F7F0;
|
||||
extern u8 D_800B9A13;
|
||||
typedef struct { s32 g0; s32 pad[2]; } S_AF634; /* size 0x0C */
|
||||
extern S_AF634 D_800AF634[];
|
||||
extern S_AF634 D_800AF638[];
|
||||
extern u16 D_8018A1DC[];
|
||||
extern u16 D_8018A22C[];
|
||||
extern u16 D_8018A238;
|
||||
extern u8 D_8018A2B8[];
|
||||
extern u8 D_8018A2CC[];
|
||||
extern s32 D_8018A2E4[];
|
||||
extern u8 D_800D43D4;
|
||||
extern u8 D_800D4414;
|
||||
extern u8 D_800D45D4;
|
||||
extern u32 *func_8017742C(u32 *a0, s32 a1, s32 a2);
|
||||
extern s32 func_8005A600(s32 a0, s32 a1, s32 a2, s32 a3, s32 a4);
|
||||
extern void func_800183E0(s32 a0);
|
||||
|
||||
typedef struct {
|
||||
u32 tag; /* 0x00 */
|
||||
u32 code; /* 0x04 */
|
||||
u16 x; /* 0x08 */
|
||||
u16 y; /* 0x0A */
|
||||
u32 uv; /* 0x0C */
|
||||
u32 wh; /* 0x10 */
|
||||
} Sp_80175DA8; /* 0x14 */
|
||||
|
||||
void func_80175DA8(param_1)
|
||||
u16 param_1;
|
||||
{
|
||||
u8 *base = &D_8011F7F0;
|
||||
u8 *s = base - 0x48;
|
||||
u16 *src = D_8018A1DC;
|
||||
Sp_80175DA8 *p;
|
||||
s16 i;
|
||||
s32 arg;
|
||||
s32 t;
|
||||
s32 fl;
|
||||
u16 v;
|
||||
|
||||
p = (Sp_80175DA8 *)(D_800AF638[(s16)param_1].g0 + D_800AF634[(s16)param_1].g0 * 4);
|
||||
*(Sp_80175DA8 **)(s + (s16)param_1 * 4 + 0x28) = p;
|
||||
i = 0;
|
||||
do {
|
||||
p->tag = ((u32)(p - 1) & 0xFFFFFF) | 0x4000000;
|
||||
p->code = *(u32 *)src;
|
||||
src += 2;
|
||||
if (i < 2) {
|
||||
p->x = *src++ + *(u16 *)(s + 0xC);
|
||||
p->y = *src++ + *(u16 *)(s + 0xE);
|
||||
} else if (i == 2) {
|
||||
p->x = *src++ + *(u16 *)(s + 0x10);
|
||||
p->y = *src++ + *(u16 *)(s + 0x12);
|
||||
} else {
|
||||
p->x = *src++ + *(u16 *)(s + 0x14);
|
||||
p->y = *src++ + *(u16 *)(s + 0x16);
|
||||
}
|
||||
p->uv = *(u32 *)src;
|
||||
src += 2;
|
||||
p->wh = *(u32 *)src;
|
||||
src += 2;
|
||||
p++;
|
||||
i++;
|
||||
} while (i < 5);
|
||||
|
||||
*(u8 *)(s + 7) = D_800B9A13;
|
||||
p = (Sp_80175DA8 *)func_8017742C((u32 *)p,
|
||||
(s16)(*(u16 *)(s + 0x10) - 0x98),
|
||||
(s16)(*(u16 *)(s + 0x12) + 9));
|
||||
func_8005A600((s32)p, 0, 0, 0x16, 0);
|
||||
p->tag = ((u32)(p - 1) & 0xFFFFFF) | 0x2000000;
|
||||
*(Sp_80175DA8 **)(s + (s16)param_1 * 4 + 0x30) = p;
|
||||
p++;
|
||||
{
|
||||
s32 acc = D_800AF634[(s16)param_1].g0;
|
||||
D_800AF634[(s16)param_1].g0 =
|
||||
acc + (((s32)p - *(s32 *)(s + (s16)param_1 * 4 + 0x28)) >> 2);
|
||||
}
|
||||
|
||||
p = *(Sp_80175DA8 **)(s + (s16)param_1 * 4 + 0x28);
|
||||
if (base[0x48] != 0) {
|
||||
*((u8 *)p + 0xD) = 0;
|
||||
} else {
|
||||
*((u8 *)p + 0xD) = 0xA0;
|
||||
}
|
||||
|
||||
{
|
||||
u8 *q1 = *(u8 **)(s + (s16)param_1 * 4 + 0x28);
|
||||
*(u16 *)(q1 + 0x22) = 0x6CD6;
|
||||
if (base[0x48] & 0x80) {
|
||||
*(u16 *)(q1 + 0x20) = D_8018A238;
|
||||
arg = (s32)&D_800D45D4;
|
||||
} else {
|
||||
u16 *tt = D_8018A22C;
|
||||
s32 k = base[0x48];
|
||||
if (k != 0) {
|
||||
k--;
|
||||
tt += k;
|
||||
}
|
||||
*(u16 *)(q1 + 0x20) = *tt;
|
||||
arg = D_8018A2E4[base[0x48]];
|
||||
}
|
||||
}
|
||||
func_800183E0(arg);
|
||||
|
||||
{
|
||||
u8 *q2 = *(u8 **)(s + (s16)param_1 * 4 + 0x28);
|
||||
t = (s32)(*(u16 *)(base + 0x2E) << 16);
|
||||
if (t != 0) {
|
||||
q2[0x49] = D_8018A2CC[t >> 20];
|
||||
} else {
|
||||
q2[0x49] = 0xA0;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
u8 *q3 = *(u8 **)(s + (s16)param_1 * 4 + 0x28);
|
||||
v = *(u16 *)(base + 0x40);
|
||||
if (v < 100) {
|
||||
q3[0x5D] = D_8018A2B8[v / 5];
|
||||
} else {
|
||||
q3[0x5D] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
fl = *(s16 *)(base + 0x1E) & 0x8000;
|
||||
/* §5a zero-byte sched fence: without it sched1 hoists the `la D_800D43D4`
|
||||
into the lh's load-delay slot, dropping the target's nop (-1 ins). */
|
||||
__asm__("");
|
||||
arg = (s32)&D_800D43D4;
|
||||
if (fl != 0) {
|
||||
arg = (s32)&D_800D4414;
|
||||
}
|
||||
func_800183E0(arg);
|
||||
}
|
||||
|
||||
INCLUDE_ASM("asm/ov_SC01_077/nonmatchings/ov_SC01_077_jr_801734BC", func_80175DA8);
|
||||
|
||||
INCLUDE_ASM("asm/ov_SC01_077/nonmatchings/ov_SC01_077_jr_801734BC", func_80176144);
|
||||
|
||||
|
||||
+48
-2
@@ -95,7 +95,11 @@ def _cast_sub(d, tu):
|
||||
`D_x[i]()` into `((u8 *)D_x)[i]()` — a dormant transform that fixing the parser would ARM. This
|
||||
tool handles the kind natively, which is why it supersedes that one rather than patching it."""
|
||||
sym = d.name
|
||||
rx = re.compile(rf'(&?)\b{re.escape(sym)}\b(\s*\[)?')
|
||||
# `(?<![.\w])(?<!->)` — a MEMBER ACCESS is not this symbol. Without it, a struct field that
|
||||
# happens to share a global's name is rewritten at every use: `p->code` became
|
||||
# `p->(*(u32 *)&code)`, which is not even valid C. Latent since the tool was written and only
|
||||
# reachable once block-scope descent started finding such names (Phase 29 SESSION-22).
|
||||
rx = re.compile(rf'(&?)(?<![.\w])(?<!->){re.escape(sym)}\b(\s*\[)?')
|
||||
c_arr = tu.kind in ('array', 'fnptr_array')
|
||||
e = _elem(d)
|
||||
|
||||
@@ -110,16 +114,58 @@ def _cast_sub(d, tu):
|
||||
return rx, lambda m: f'{m.group(1)}{acc}{m.group(2) or ""}'
|
||||
|
||||
|
||||
def _draft_statements(body):
|
||||
"""Every declaration-bearing statement in the draft, at FILE scope AND at BLOCK scope, as
|
||||
(Stmt-with-ABSOLUTE-spans, is_inner).
|
||||
|
||||
WHY (Phase 29 SESSION-22, byte-witnessed on func_80175AB8). `split_statements` is depth-0 BY
|
||||
DESIGN, so for a draft whose body is one function definition it returns exactly ONE statement —
|
||||
the definition — and every declaration inside it is invisible. That is precisely where the data
|
||||
externs live: §8d (`scope_data_externs`) DEMOTES them to block scope on purpose, because a
|
||||
block-scope extern declares no global for the TU's own later decls to collide with.
|
||||
|
||||
But C still requires a block-scope `extern` to be compatible with a file-scope declaration of the
|
||||
same identifier that is in scope — so the conflict is real, and this tool was structurally blind
|
||||
to it: it reported `reconciled: 0 draft(s), 0 data symbol(s); coverage defects: 0` for a draft
|
||||
cc1 then rejected with `conflicting types for D_8011F7BC`. A silent skip that reads exactly like
|
||||
"nothing to do" (R32).
|
||||
|
||||
One level of descent is enough and is deliberate: the externs sit at the top of the function
|
||||
body, not inside nested blocks, and recursing further would start parsing arbitrary code."""
|
||||
out = []
|
||||
for st in cdecl.split_statements(body):
|
||||
out.append((st, False))
|
||||
m = re.search(r'\{', cdecl._mask(st.text))
|
||||
if not m:
|
||||
continue
|
||||
# DESCEND ONLY INTO A FUNCTION BODY. Descending into ANY `{` also enters struct/union/enum
|
||||
# definitions, whose MEMBERS then parse as declarations and get "conformed" against the TU —
|
||||
# byte-witnessed while writing this: a member `u32 code;` was rewritten to the TU's
|
||||
# declaration of `code` (`typedef void (*code)(unsigned short*);`) INSIDE the struct, and the
|
||||
# member ACCESS `p->code` became `p->(*(u32 *)&code)`. A function definition is distinguished
|
||||
# by a parameter list before the brace and by not being a tag/typedef definition.
|
||||
head = st.text[:m.start()]
|
||||
if re.match(r'\s*(typedef|struct|union|enum)\b', head) or ')' not in head:
|
||||
continue
|
||||
off = st.start + m.end() # first byte INSIDE the body
|
||||
inner_txt = body[off:st.end]
|
||||
for s2 in cdecl.split_statements(inner_txt):
|
||||
out.append((cdecl.Stmt(s2.text, off + s2.start, off + s2.end), True))
|
||||
return out
|
||||
|
||||
|
||||
def fix(body, tu_path, fn):
|
||||
"""Conform the draft's DATA decls to the TU. Returns (new_body, notes)."""
|
||||
full = cdecl.tu_scope(tu_path) # CONFLICT domain: a decl BELOW still conflicts
|
||||
above = set(cdecl.tu_scope(tu_path, above=fn)) # ORDER, for cc1's no-prototype rule
|
||||
|
||||
plan, notes = {}, []
|
||||
for st in cdecl.split_statements(body):
|
||||
for st, inner in _draft_statements(body):
|
||||
try:
|
||||
ds = cdecl.parse(st.text)
|
||||
except cdecl.CDeclError as e:
|
||||
if inner:
|
||||
continue # ordinary CODE inside a function body — not a declaration, not a defect
|
||||
notes.append(f'!! UNPARSED (a coverage defect, not a no-op): {st.text[:60]} -> {e}')
|
||||
continue
|
||||
for d in ds:
|
||||
|
||||
Reference in New Issue
Block a user