cgen: deref of *fn skips MOVQ load — pointer IS fn-addr (#185)

Pre-fix the N_UN TK_STAR arm applied the generic pointer-load
`MOVQ (AX), AX` to a *fn operand. cgexpr on the operand already
left AX = fn-addr (post-#180 LEAQ); the spurious second load
read the first instruction word, and the subsequent CALL AX
jumped through that junk address and segfaulted.

Cstage: cmd/w6c/cgen.c N_UN TK_STAR opens with a TY_NAMED-peel
+ TY_FN early-break — leave AX as the fn-addr cgexpr produced.
Wwstage twin in selfhost/cmd/wcc/cgenexpr.ww cgun TK_STAR walks
the n.type_ tinfo chain the same way (TY_NAMED peel then TY_FN
check) and returns before the generic load. Mirrors
ref/harec/src/check.c expr_call's STORAGE_POINTER→STORAGE_FUNCTION
path (harec skips the deref since the pointer IS the address).

Both stages must land together per rule-10 (cstage-only would
break 990-997 byte-id gates — same lesson as #180).

Probe: test/wcc/765_star_fn_deref.c, 5 rows table-driven —
minimal / branched-callee / alias-chain / fn-with-args /
fn-tuple-return. Every row is cstage-only via stage_mask
because wwstage's checker bails asserttyped on `(*f)(...)`
(filed as #181 — N_CALL type_ stamp gap on deref-call); #181's
own probe will lock the wwstage runtime once the bail lifts.
Gate-blind risk (ken's note): byte-id alone cannot catch this
class because both stages drop the SAME instruction
symmetrically, so cs.s == ww.s holds either way. Runtime
exit-code is the only correctness net here.

Combined.ww regenerated for selfhost/cmd/{w6c,wwdump}/main.
combined.ww per #110 freshness gate.
This commit is contained in:
2026-05-28 19:25:53 +09:00
parent 5478695922
commit b86b9d76e2
6 changed files with 283 additions and 0 deletions

View File

@@ -3066,6 +3066,14 @@ fn cgun(c: *cgen, n: *node) void = {
return;
};
if (n.op == tkind.TK_STAR) {
// #185: deref of *fn — the pointer value IS the fn address.
// cgexpr(n.lhs) left AX = fn-addr; a generic MOVQ (AX),AX
// would load the first instruction word and a subsequent
// CALL would segfault. Mirror ref/harec/src/check.c
// expr_call's STORAGE_POINTER→STORAGE_FUNCTION skip.
let rti: *tinfo = n.type_: *tinfo;
for (rti != nil && rti.kind == tykind.TY_NAMED) { rti = rti.under; };
if (rti != nil && rti.kind == tykind.TY_FN) { return; };
// f64/f32 result rides X0 (SSE), not AX — an integer MOVQ
// strands the value off the float ABI and the caller's
// MOVSD X0 reads stale bits (#96). Mirrors the float