wwstage: spill non-IDENT scrutinee at cgtypeassert (#200)

cgtypeassert kept scrutoff=0 when the scrutinee wasn't an N_IDENT
(direct call result, arr[i], p.field, ?, paren-wrap of any of those),
so the tag-load fell on (BP) — the saved-BP word — and the payload-
load on +8(BP) — the return address. The wwstage repro returned 220
(garbage from RIP) where cstage returned 42 (impl-e1-resume sibling
of #199/#201).

Mirror cstage cmd/w6c/cgen.c:6300-6316 N_TYPEASSERT non-IDENT arm.
Add an `else` branch after the existing N_IDENT path that resolves
the tagged type via matchscrutt, alloc an @asrt_spill slot via
matchspillsz/localalloc, cgexpr the LHS, then spill the AX/DX/CX
tagged-return-ABI words: AX→+0 (tag), DX→+8 (word0), CX→+16
(word1, guarded on spill > 16). Subsequent tag-check + payload load
indexes off the spill like the IDENT path. Helpers reused from
cgmatch (cgenexpr.ww:1422-1460).

cstage's cgtypeassert omits the cgmatch 4-word R8→+24 spill (rule-10
stage symmetry: rather than diverge into a 32B-payload case the test
suite doesn't exercise, mirror cstage exactly and file the cstage
omission inline). Filed inline: cstage cgtypeassert needs the same
R8→+24 path cgmatch already has (drew's design rationale, blocked
by the rule-10 floor today).

772_typeassert_nonident: 7 rows (call_as_size — the repro, call_as_str
— CX→+16 spill + BX post-load, call_as_namedvoid — void-variant
tag-check fires, payload load is a 0-byte no-op, call_as_fnptr —
fn-ptr variant 8B word0, call_as_u8 / call_as_i16 — narrow scalar
round-trip via MOVQ + MOVQ confirms no truncation, branched_call_as
— runtime-chosen tag). Each row gated on cstage runtime + wwstage
runtime + cs.s == ww.s byte-identity.
This commit is contained in:
2026-05-29 03:52:04 +09:00
parent 4d44242363
commit 6ce292b157
5 changed files with 444 additions and 0 deletions

View File

@@ -387,6 +387,41 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
scrutoff = lc.off;
scrutt = resolvetagged(c, lc.tnode);
};
} else {
// Non-ident scrutinee (call result, arr[i], p.field, ?,
// etc.). Mirror cgmatch's spill (cgenexpr.ww:1422-1460)
// and cstage cmd/w6c/cgen.c:6300-6316: alloc an
// `@asrt_spill` slot sized via matchspillsz, evaluate
// the LHS, then copy the AX/DX/CX[/R8] return-ABI words
// into the slot so the tag-check + payload load indexes
// off memory like the IDENT path. Without this, scrutoff
// stayed 0 and the tag read fell on (BP) — the saved-BP
// word — and the payload read on +8(BP) — the return
// address. Bug #200.
scrutt = matchscrutt(c, lhs);
let spillsz: i32 = matchspillsz(c, scrutt);
scrutoff = localalloc(c, "@asrt_spill", spillsz, nil);
cgexpr(c, lhs);
emitline("\tMOVQ\tAX, ");
emitoff(scrutoff: i64);
emitline("(BP)\n");
if (!isnullabletype(scrutt)) {
emitline("\tMOVQ\tDX, ");
emitoff((scrutoff + 8): i64);
emitline("(BP)\n");
// Mirror cstage cmd/w6c/cgen.c:6313-6315: only CX
// → +16 when slot_size > 16. The 4-word case
// (R8 → +24, slot_size > 24) is the cgmatch shape
// (cgenexpr.ww:1454-1458, cmd/w6c/cgen.c:5988-5990)
// but cstage cgtypeassert omits it; preserve the
// asymmetry rather than diverge from rule 10
// byte-id. Filed inline as a cstage twin task.
if (spillsz > 16) {
emitline("\tMOVQ\tCX, ");
emitoff((scrutoff + 16): i64);
emitline("(BP)\n");
};
};
};
};
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);