ww: add Hare-style is/as postfix ops on tagged unions

`e is T` returns bool (variant tag == T's index); `e as T` unwraps
to T or exit(1) on mismatch. Postfix, same precedence as `:` cast.
TK_IS / N_TYPETEST / N_TYPEASSERT appended at the tail of their
enums so every prior numeric value stays unchanged — the
990_selfhost wwdump-diff stays byte-clean.

Cgen mirrors the match-case slot-based load (tag at +0, value at
+8/+16), so an N_IDENT tagged-union local works just like a
match scrutinee. Selfhost cgen inlines the slot resolution
because the wwstage cgen drops sign bits on `*i32` output
parameters in this position.

Renames `errors.is` -> `errors.equal` (the only naming collision;
the existing comment already noted it shared shape with
strings.equal/bytes.equal).
This commit is contained in:
2026-05-11 23:21:08 +09:00
parent 8899ce5621
commit dd188ca460
16 changed files with 614 additions and 15 deletions

View File

@@ -1936,6 +1936,71 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
break;
}
case N_TYPETEST: {
/* `e is T` — Compare scrutinee tag against T's variant index.
* Result is bool (0/1) in AX. We only need the tag, not the
* value words, so cgexpr's AX-only result is enough for both
* register-returning callers (AX=tag) and N_IDENT locals
* (cgexpr loads slot+0 → AX, which is the tag). */
cgexpr(c, n->lhs, locals);
Type *u = n->lhs ? n->lhs->type : NULL;
if (u && u->kind == TY_NAMED) u = u->under;
Type *vt = n->rhs ? n->rhs->type : NULL;
int tag = cg_tag_for_variant(u, vt);
char *ne = mklabel(c, "is_ne");
char *done = mklabel(c, "is_done");
ins2(c, A_CMPQ, aimm(tag < 0 ? 0 : tag), areg(D_AX));
ins1(c, A_JNE, abranch(ne));
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
ins1(c, A_JMP, abranch(done));
label(c, ne);
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
label(c, done);
break;
}
case N_TYPEASSERT: {
/* `e as T` — abort if tag != T's variant index; otherwise
* unwrap value to T's ABI: scalar/ptr variants land in AX;
* 16B str variants in AX:BX.
*
* We need both tag *and* value words. For an N_IDENT local
* the value lives at slot+8/+16 — cgexpr's single-MOVQ path
* does not load it. Mirror match's pattern: resolve a slot
* offset (existing local or a fresh @asrt_spill) and index
* out tag/value from memory. */
Node *s = n->lhs;
Type *st = s ? s->type : NULL;
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
Type *vt = n->type;
int tag = cg_tag_for_variant(u, vt);
int slot_size = (u && u->kind == TY_TAGGED) ? (int)u->size : 16;
int sl_off = 0;
if (s && s->kind == N_IDENT && s->str) {
sl_off = localfind(locals, s->str);
}
if (sl_off == 0) {
sl_off = localoff(c, &locals, "@asrt_spill",
slot_size, cg_frame);
cgexpr(c, s, locals);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, sl_off + 8));
if (slot_size > 16)
ins2(c, A_MOVQ, areg(D_CX),
amem(D_BP, sl_off + 16));
}
char *ok = mklabel(c, "asrt_ok");
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));
ins1(c, A_JE, abranch(ok));
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
ins0(c, A_SYSCALL);
label(c, ok);
ins2(c, A_MOVQ, amem(D_BP, sl_off + 8), areg(D_AX));
if (type_isstr(vt))
ins2(c, A_MOVQ, amem(D_BP, sl_off + 16), areg(D_BX));
break;
}
case N_CAST: {
int from_f = node_isfloat(n->lhs);
int to_f = cg_isfloat(n->type);