From 57d33acd1f0181454b0fed530c3e64886e116058 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 9 Aug 2026 00:12:21 +0900 Subject: [PATCH] w6c: variant-tag miss is loud, never tag 0 (rule 7) Every variant-index lookup miss (-1) was silently clamped to tag 0 across both stages: tagged return (value + bare-void), widen store (struct/slice/str/float/scalar arms + push fast path + field store), widen tag-remap (identity scan + emit loop), match case compare, is/as typetest, tryprop error remap, and the alloc-nomem propagation (clamped to 1). All misses are checker-rejected upstream today, so the clamps were dead -- but any future checker/cgen seam gap would mis-tag silently (wrong arm, wrong error, false success). Rule 7: each site now hard-stops with a per-construct diagnostic; nullable arms keep their raw -1 by design (a miss encodes the void polarity for `case null`). Corpus asm byte-unchanged; bootstrap fixed point holds. --- cmd/w6c/cgen.c | 89 ++++++++++++++++++++++++++++-------- selfhost/cmd/wcc/cgenexpr.ww | 31 +++++++++++-- selfhost/cmd/wcc/cgenstmt.ww | 24 ++++++++-- selfhost/cmd/wcc/cgenutil.ww | 30 ++++++++++-- 4 files changed, 140 insertions(+), 34 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e41bcab9..b3485c99 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -2671,7 +2671,9 @@ cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off) int identity = 1, idx = 0; for (Tparam *p = su->params; p; p = p->next, idx++) { int di = cg_tag_for_variant(du, p->type); - if (di < 0) di = 0; + if (di < 0) + fatal("tagged widen: source variant missing " + "from dst union (rule 7)"); if (di != idx) { identity = 0; break; } } if (identity) return; @@ -2681,7 +2683,9 @@ cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off) for (Tparam *p = su->params; p; p = p->next, idx++) { const char *next = mklabel(c, "remap_next"); int di = cg_tag_for_variant(du, p->type); - if (di < 0) di = 0; + if (di < 0) + fatal("tagged widen: source variant missing " + "from dst union (rule 7)"); ins2(c, A_CMPQ, aimm(idx), areg(D_AX)); ins1(c, A_JNE, abranch(next)); ins2(c, A_MOVQ, aimm(di), areg(D_AX)); @@ -3079,7 +3083,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src, } foff += wide ? 24 : 8; } - ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), + ins2(c, A_MOVQ, aimm(tag), amem(D_BP, write_off + 0)); if (via_outer) goto copy_out; return; @@ -3149,6 +3153,9 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src, ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + k)); int tag = cg_tag_for_variant(du, st); + if (tag < 0) + fatal("tagged widen: no variant tag for source " + "(rule 7)"); if (src->kind == N_IDENT) { int soff = localfind(*locals_p, src->str); int ssz = (int)su->size; @@ -3198,7 +3205,7 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src, cg_structlit_fill(c, locals_p, su, src, DST_BP, 0, NULL, write_off + 8); } - ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), + ins2(c, A_MOVQ, aimm(tag), amem(D_BP, write_off + 0)); if (via_outer) goto copy_out; return; @@ -3214,7 +3221,10 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src, ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, write_off + 16)); ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, write_off + 24)); int tag = cg_tag_for_variant(du, st); - ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), + if (tag < 0) + fatal("tagged widen: no variant tag for source " + "(rule 7)"); + ins2(c, A_MOVQ, aimm(tag), amem(D_BP, write_off + 0)); if (via_outer) goto copy_out; return; @@ -3246,7 +3256,10 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src, amem(D_BP, write_off + k)); } int tag = cg_tag_for_variant(du, st); - ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), + if (tag < 0) + fatal("tagged widen: no variant tag for source " + "(rule 7)"); + ins2(c, A_MOVQ, aimm(tag), amem(D_BP, write_off + 0)); if (via_outer) goto copy_out; return; @@ -3266,7 +3279,10 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src, amem(D_BP, write_off + k)); } int tag = cg_tag_for_variant(du, st); - ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, write_off + 0)); + if (tag < 0) + fatal("tagged widen: no variant tag for source " + "(rule 7)"); + ins2(c, A_MOVQ, aimm(tag), amem(D_BP, write_off + 0)); copy_out: if (via_outer) { /* cgexpr above clobbered base_reg — reload from spill, then @@ -3318,7 +3334,9 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz) /* Direct-push fast path: str / slice / scalar / pointer. */ cgexpr(c, src, *locals_p); int tag = cg_tag_for_variant(du, st); - if (tag < 0) tag = 0; + if (tag < 0) + fatal("tagged widen: no variant tag for source " + "(rule 7)"); if (type_isstr(st) || (su && su->kind == TY_STR)) { /* str IS []u8: slot 32 [+0]=tag, [+8]=ptr, [+16]=len, * [+24]=cap — same shape as the slice arm below. Push @@ -5983,9 +6001,13 @@ cgexpr(Cg *c, Node *n, Local *locals) areg(D_AX)); int v58tag = cg_tag_for_variant(fu, st); + if (v58tag < 0) + fatal("tagged widen: " + "no variant tag " + "for source " + "(rule 7)"); ins2(c, A_MOVQ, - aimm(v58tag < 0 - ? 0 : v58tag), + aimm(v58tag), amem(D_BX, foff + 0)); ins2(c, A_MOVQ, areg(D_AX), @@ -11165,23 +11187,32 @@ cgexpr(Cg *c, Node *n, Local *locals) * if the tag matches any of the alts, * jump to body; otherwise to the next * case. */ + if (tag < 0) + fatal("tagged match: case type " + "not in union (rule 7)"); char *body = mklabel(c, "match_body"); - ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), + ins2(c, A_CMPQ, aimm(tag), areg(D_AX)); ins1(c, A_JE, abranch(body)); for (Node *alt = cs->list; alt; alt = alt->next) { int atag = cg_tag_for_variant( su, alt->type); + if (atag < 0) + fatal("tagged match: case type " + "not in union (rule 7)"); ins2(c, A_CMPQ, - aimm(atag < 0 ? 0 : atag), + aimm(atag), areg(D_AX)); ins1(c, A_JE, abranch(body)); } ins1(c, A_JMP, abranch(next)); label(c, body); } else { - ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), + if (tag < 0) + fatal("tagged match: case type " + "not in union (rule 7)"); + ins2(c, A_CMPQ, aimm(tag), areg(D_AX)); ins1(c, A_JNE, abranch(next)); } @@ -11348,7 +11379,9 @@ cgexpr(Cg *c, Node *n, Local *locals) for (Tparam *p = u->params; p; p = p->next, i++) { if (!cg_variant_is_error(u, i)) continue; int j = cg_tag_for_variant(r, p->type); - if (j < 0) j = 0; + if (j < 0) + fatal("tryprop: error variant missing " + "from fn return union (rule 7)"); if (j == i) continue; char *skip = mklabel(c, "tryprop_skip"); ins2(c, A_CMPQ, aimm(i), areg(D_AX)); @@ -11586,7 +11619,10 @@ cgexpr(Cg *c, Node *n, Local *locals) ins1(c, A_JNE, abranch(ne)); } else { int tag = cg_tag_for_variant(u, vt); - ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX)); + if (tag < 0) + fatal("tagged typetest: type not in union " + "(rule 7)"); + ins2(c, A_CMPQ, aimm(tag), areg(D_AX)); ins1(c, A_JNE, abranch(ne)); } ins2(c, A_MOVQ, aimm(1), areg(D_AX)); @@ -11709,8 +11745,11 @@ cgexpr(Cg *c, Node *n, Local *locals) break; } int tag = cg_tag_for_variant(u, vt); + if (tag < 0) + fatal("tagged typetest: type not in union " + "(rule 7)"); ins2(c, A_MOVQ, amem(D_BP, sl_off + 0), areg(D_AX)); - ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX)); + ins2(c, A_CMPQ, aimm(tag), areg(D_AX)); ins1(c, A_JE, abranch(ok)); ins2(c, A_MOVQ, aimm(1), areg(D_DI)); ins2(c, A_MOVQ, aimm(60), areg(D_AX)); @@ -13531,7 +13570,9 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) Type *r = cg_ret_type; r = type_chase_named(r); int nidx = cg_tag_for_variant(r, ty_nomem); - if (nidx < 0) nidx = 1; + if (nidx < 0) + fatal("tryprop: no nomem variant " + "in fn return union (rule 7)"); ins2(c, A_MOVQ, aimm(nidx), areg(D_AX)); ins2(c, A_MOVQ, areg(D_BP), areg(D_SP)); ins1(c, A_POPQ, areg(D_BP)); @@ -14130,7 +14171,9 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * slot and the caller reads memory. */ if (cg_sret_retsize(rt) > 0) { int tag = cg_tag_for_variant(rt, ty_void); - if (tag < 0) tag = 0; + if (tag < 0) + fatal("tagged return: no void " + "variant (rule 7)"); ins2(c, A_MOVQ, amem(D_BP, cg_sret_arg_off), areg(D_BX)); @@ -14150,7 +14193,9 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, aimm(0), areg(D_AX)); } else { int tag = cg_tag_for_variant(rt, ty_void); - if (tag < 0) tag = 0; + if (tag < 0) + fatal("tagged return: no void " + "variant (rule 7)"); ins2(c, A_MOVQ, aimm(tag), areg(D_AX)); } ins2(c, A_MOVQ, areg(D_BP), areg(D_SP)); @@ -14266,6 +14311,10 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * caller (e.g. a slice-stride IMULQ) would * land in slot+16 / slot+24. (Task #18.) */ int tag = cg_tag_for_variant(rt, vt); + if (tag < 0) + fatal("tagged return: no variant " + "tag for return value " + "(rule 7)"); int rsz = (int)rt->size; cgexpr(c, n->lhs, *locals); if (type_isslice(vt)) { @@ -14332,7 +14381,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) ins2(c, A_MOVQ, aimm(0), areg(D_R8)); } - ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), + ins2(c, A_MOVQ, aimm(tag), areg(D_AX)); } else { /* Struct variant or tagged-subset: diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index cb18c19f..8e7c8d79 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -544,7 +544,11 @@ fn cgtryprop(c: *cgen, n: *syntax.node) void = { for (p != nil) { if (variantiserror(u, i)) { let j: i32 = flatvariantidxt(r, p.type_, false); - if (j < 0) { j = 0; }; + if (j < 0) { + let mtp: str = "tryprop: error variant missing from fn return union (rule 7)\n"; + os.write(2, mtp.ptr, mtp.len: u64); + os.exit(1); + }; if (j != i) { let skip: str = mklabel(c, "tryprop_skip"); emitline("\tCMPQ\t$"); @@ -796,7 +800,11 @@ fn cgtypetest(c: *cgen, n: *syntax.node) void = { emitline("\tJNE\t"); }; } else { - if (want < 0) { want = 0; }; + if (want < 0) { + let mtt: str = "tagged typetest: type not in union (rule 7)\n"; + os.write(2, mtt.ptr, mtt.len: u64); + os.exit(1); + }; emitline("\tCMPQ\t$"); emitint(want: i64); emitline(", AX\n"); @@ -982,7 +990,11 @@ fn cgtypeassert(c: *cgen, n: *syntax.node) void = { emitlabel(okl); return; }; - if (want < 0) { want = 0; }; + if (want < 0) { + let mtt: str = "tagged typetest: type not in union (rule 7)\n"; + os.write(2, mtt.ptr, mtt.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t"); emitoff(scrutoff: i64); emitline("(BP), AX\n"); @@ -3115,7 +3127,12 @@ fn matcharmwant(c: *cgen, scrutt: *syntax.node, pat: *syntax.node) i32 = { r = flatvariantidxt(scrutt.type_: *syntax.tinfo, pattype, false); }; }; - if (r >= 0) { want = r; }; + if (r < 0) { + let mmt: str = "tagged match: case type not in union (rule 7)\n"; + os.write(2, mmt.ptr, mmt.len: u64); + os.exit(1); + }; + want = r; }; }; return want; @@ -10439,7 +10456,11 @@ fn cgassign(c: *cgen, n: *syntax.node) void = { if (viaptr) { emitline("\tMOVQ\t(BX), BX\n"); }; emitline("\tPOPQ\tAX\n"); let v58tag: i32 = taggedvariantindext(c, fi.tnode.type_: *syntax.tinfo, n.rhs); - if (v58tag < 0) { v58tag = 0; }; + if (v58tag < 0) { + let mws: str = "tagged widen: no variant tag for source (rule 7)\n"; + os.write(2, mws.ptr, mws.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t$"); emitint(v58tag: i64); emitline(", "); diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 71665a7b..efc6b9c4 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1211,8 +1211,12 @@ fn cgreturn(c: *cgen, n: *syntax.node) void = { emitline("\tMOVQ\t$0, R8\n"); }; };};}; + if (idx < 0) { + let mtag: str = "tagged return: no variant tag for return value (rule 7)\n"; + os.write(2, mtag.ptr, mtag.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t$"); - if (idx < 0) { idx = 0; }; emitint(idx: i64); emitline(", AX\n"); emitline("\tMOVQ\tBP, SP\n"); @@ -1847,7 +1851,11 @@ fn cgreturn(c: *cgen, n: *syntax.node) void = { if (sretretsize(c, c.fnret) > 0) { let sa38: i32 = localfind(c, "@sretarg"); let vidx38: i32 = voidvariantindex(c.fnret); - if (vidx38 < 0) { vidx38 = 0; }; + if (vidx38 < 0) { + let mvd: str = "tagged return: no void variant (rule 7)\n"; + os.write(2, mvd.ptr, mvd.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t"); emitoff(sa38: i64); emitline("(BP), BX\n"); @@ -1868,7 +1876,11 @@ fn cgreturn(c: *cgen, n: *syntax.node) void = { emitline("\tMOVQ\t$0, AX\n"); } else { let idx: i32 = voidvariantindex(c.fnret); - if (idx < 0) { idx = 0; }; + if (idx < 0) { + let mvd: str = "tagged return: no void variant (rule 7)\n"; + os.write(2, mvd.ptr, mvd.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t$"); emitint(idx: i64); emitline(", AX\n"); @@ -2301,7 +2313,11 @@ fn cgletbody(c: *cgen, n: *syntax.node, off: i32) void = { nidx2 += 1; }; }; }; - if (nidx < 0) { nidx = 1; }; + if (nidx < 0) { + let mnm: str = "tryprop: no nomem variant in fn return union (rule 7)\n"; + os.write(2, mnm.ptr, mnm.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t$"); emitint(nidx: i64); emitline(", AX\n"); diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 9b930c8f..7222b3da 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -4016,7 +4016,11 @@ fn cgwidentagremap(c: *cgen, du: *syntax.tinfo, su: *syntax.tinfo, slot_off: i32 let idx: i32 = 0; for (p != nil) { let di: i32 = flatvariantidxt(dt, p.type_, false); - if (di < 0) { di = 0; }; + if (di < 0) { + let mrm: str = "tagged widen: source variant missing from dst union (rule 7)\n"; + os.write(2, mrm.ptr, mrm.len: u64); + os.exit(1); + }; if (di != idx) { identity = false; p = nil; } else { p = p.tnext; idx += 1; }; }; @@ -4030,7 +4034,11 @@ fn cgwidentagremap(c: *cgen, du: *syntax.tinfo, su: *syntax.tinfo, slot_off: i32 for (p != nil) { let next: str = mklabel(c, "remap_next"); let di: i32 = flatvariantidxt(dt, p.type_, false); - if (di < 0) { di = 0; }; + if (di < 0) { + let mrm: str = "tagged widen: source variant missing from dst union (rule 7)\n"; + os.write(2, mrm.ptr, mrm.len: u64); + os.exit(1); + }; emitline("\tCMPQ\t$"); emitint(idx: i64); emitline(", AX\n"); @@ -5126,7 +5134,11 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *syntax.tinfo, src: *syntax.node, slot_of zoff += 8; }; let tag: i32 = taggedvariantindext(c, dt, src); - if (tag < 0) { tag = 0; }; + if (tag < 0) { + let mws: str = "tagged widen: no variant tag for source (rule 7)\n"; + os.write(2, mws.ptr, mws.len: u64); + os.exit(1); + }; if (src.kind == syntax.nkind.N_STRUCTLIT) { // #23: delegate to the single fill path. The // inline field loop this replaces was a @@ -5206,7 +5218,11 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *syntax.tinfo, src: *syntax.node, slot_of emitoff((slot_off + 24): i64); emitline("(BP)\n"); let tag: i32 = taggedvariantindext(c, dt, src); - if (tag < 0) { tag = 0; }; + if (tag < 0) { + let mws: str = "tagged widen: no variant tag for source (rule 7)\n"; + os.write(2, mws.ptr, mws.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t$"); emitint(tag: i64); emitline(", "); @@ -5309,7 +5325,11 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *syntax.tinfo, src: *syntax.node, slot_of }; }; let tag: i32 = taggedvariantindext(c, dt, src); - if (tag < 0) { tag = 0; }; + if (tag < 0) { + let mws: str = "tagged widen: no variant tag for source (rule 7)\n"; + os.write(2, mws.ptr, mws.len: u64); + os.exit(1); + }; emitline("\tMOVQ\t$"); emitint(tag: i64); emitline(", ");