3.6 KiB
§43 — The K&R s16-param definition DISSOLVES the "narrow-param wall" for by-value register args (Phase 25 task A, Fable5 crack of the 369-ins giant func_80166994 ×134, 2026-07-11)
§17/§29 called a def with narrow-scalar by-value params an irreducible wall: it can't be no-proto-relaxed
(K&R default-promotion "changes the ABI") and often can't match the TU's s32 canon-sig prototype → stub it.
A Fable5 giant crack byte-proves that verdict is too broad for s16 (and s32) by-value params.
The refinement — use a K&R definition:
s32 func_X(param_1, param_2, param_3, param_4)
s32 param_1; s16 param_2; s16 param_3; s16 param_4; /* K&R: params declared narrow */
{ ... }
On MIPS all four args arrive in $a0–$a3 as 32-bit words. K&R promotes the s16 params to int for the
PROTOTYPE — ABI-identical to the canon-sig s32(s32,s32,s32,s32) (so no conflicting types, no //@EDIT
for the param types) — while the BODY still treats them as s16, producing the target's lazy per-use
in-place narrow/extend: sll aN,aN,16 ; sra aN,aN,16 on the arg register itself, with the raw values
stashed to callee-saved pseudos first (s3←a1 …) and re-extended per use after calls. The (s16)param_of_s32
cast form CANNOT reproduce this — it extends into fresh v0/v1 temps instead.
Triage tell (read it off the diff): target does sll aN,aN,16 in place on an arg reg + copies the raw
aN elsewhere first ⇒ true s16 param ⇒ K&R form. Extends into v0/v1 temps ⇒ it's a cast-of-s32, keep s32.
The return-type flip pair (void-return value-drop): if the def returns s32 but the ambient decls say
void, gcc-2.7.2 discards return expr; in a void fn (pedwarn) → you lose the target's exit
materializations (addiu v0,zero,1 / addu v0,zero,zero). Fix = flip void→s32 at BOTH:
- (a) the split canon-sig decl (
//@EDIT void func_X(...);||s32 func_X(...);) — a self-fn decl, EXEMPLAR- SPECIFIC (the canon-sig layer put it in ov077; siblings usually have 0 of these → make the split-edit OPTIONAL infamily_sweep --edit-remap: apply where present, never skip — the byte-gate is the arbiter), and - (b) the engine_core.h
DEFINE_func_*externs (ec_edit, once-global, byte-neutral because every caller discards the result — the func_80156044 trampoline precedent, now for a real returning fn).
Zero-footprint body ⇒ ×134-clean: put ALL typedefs + externs block-scope inside the function (a
conflicting file-scope typed extern is a hard cc1 error, exit 33, not a warning). Access a global as
&((Struct *)D_xxx)[i] over an ambient-compatible extern u8 D_xxx[];. No register __asm__ pins → it
propagates ×134 via family_sweep --edit-remap with no cc1-crash (contrast the pin-heavy §42e families
that SIGABRT in sibling TUs — structural cracks are the ×134-safe ones).
Scope (byte-tested = s16 only): proven for s16 by-value register params. u16/u8/s8/float
by-value, and any narrow param accessed via memory (sh/sw width differences), remain §29 walls until
byte-tested. So §29's blanket "narrow-param wall" narrows to "narrow params that aren't s16/s32-by-value in
an arg register."
Flywheel (R16): this idiom is now cheap-Opus-applicable — no Fable5 needed — for any giant whose diff
shows the in-place-sll triage tell. Check each remaining giant for the s16-param class before spending the
Fable5 tier. (Also caught: the prior wave's @stuck: none — MATCH note on func_80166994 was stale/false
— match_one re-ran DIFF 366/369; verify a "MATCH" claim against the bytes, R14, never trust a stale note.)