cgen: address-of fn name emits LEAQ via mafn (#180)

Pre-fix the N_UN TK_AMP arm fell through silently when the operand
was an N_IDENT naming a top-level function — the let/def cascade
had no TY_FN branch, so the store at the assign site picked up
whatever AX held from prior code (commonly a stale arg register).
A subsequent (*f)(...) jumped through that junk and segfaulted.

Cstage: cmd/w6c/cgen.c N_UN TK_AMP IDENT adds a TY_FN arm before
the let/def cascade, mirror of the read-arm at line 2330 — same
mafn(opnd->str, c->cur_mod) shape. Wwstage twin in selfhost/cmd/
wcc/cgenexpr.ww cgun TK_AMP IDENT uses the analogous predicate
fnretlookup(c, nm) != nil + emitfnname(c, nm, c.curmod), matching
the cstage emit on byte-id. Both stages must land together per
rule-10 (cstage-only breaks 990-997 byte-id gates).

Combined.ww regenerated for selfhost/cmd/{w6c,wwdump}/main.combined
.ww per #110 freshness gate.

Probe: test/wcc/764_amp_fn_ident.c, 6 rows table-driven —
minimal / branched-callee / alias-chain / fn-with-args / fn-tuple
-return / cross-module. Rows 1-5 gate both stages (run + .s LEAQ
check + cs.s == ww.s byte-id); row 6 cross-module is cstage-only
because wwstage bails asserttyped on `&mod.fn` (sibling project
#184, filed). Per drew option (b) the probe exercises the address
-of without (*f)(7) — deref-call runtime coverage stays with
project #181's probe once the wwstage asserttyped bail on
N_CALL(*f) is lifted.

Phase 1 cross-mod verdict = FINE for cstage (LEAQ emits via the
already-present N_DOT TK_AMP branch at cgen.c:2477-2493); WWSTAGE
fails asserttyped on the same shape → project #184.
This commit is contained in:
2026-05-28 18:50:10 +09:00
parent caca68eb72
commit 5478695922
6 changed files with 439 additions and 0 deletions

View File

@@ -2435,8 +2435,23 @@ cgexpr(Cg *c, Node *n, Local *locals)
Node *opnd = n->lhs;
if (opnd && opnd->kind == N_IDENT) {
int off = localfind(locals, opnd->str);
Type *ot = opnd->type;
Type *ou = (ot && ot->kind == TY_NAMED)
? ot->under : ot;
if (off != 0) {
ins2(c, A_LEAQ, amem(D_BP, off), areg(D_AX));
} else if (ou && ou->kind == TY_FN) {
/* #180: address-of a top-level fn name.
* Twin of the N_IDENT TY_FN read-arm at
* line 2330 (mafn with c->cur_mod hint).
* Previously this fell through silently —
* the AX-store at the assign site picked
* up whatever AX held from prior code, so
* `let f = &add1; (*f)(7)` jumped through
* stale AX. */
ins2(c, A_LEAQ,
mafn(c, opnd->str, c->cur_mod),
areg(D_AX));
} else if (let_islet(opnd->str)
|| def_isstructdef(opnd->str)
|| def_isarraydef(opnd->str)