w6c+wcc: widen struct/tagged-subset, parse ... spread
Three tagged-union gaps:
1. Struct-payload widening was broken at every site (call, let,
assign, return, struct-field init). cg_widen_tagged_store now
materialises str / scalar / struct-lit / struct-ident / tagged
payloads at slot+8+field_off and writes the tag last. Call sites
route through cg_widen_tagged_push (scratch slot + push high→low).
2. Tagged → wider tagged widening forwarded the source tag verbatim.
cg_widen_tag_remap emits a CMPQ-chain switch that translates each
source variant index to the destination's, then zero-pads to the
wider slot. type_eq grew a TY_TAGGED arm (was returning 1 for any
two unions); type_assignable now accepts variant-subset and
rejects the rest.
3. `(...inner | T)` spread parses (cmd/wcc/parse.c, lib/ww/parse).
Marks Node.op = TK_ELLIPSIS; resolve_type unwraps NAMED + flattens
when the spread bit is set so aliases inline like Hare's
tagged_type unwrap flag.
Selfhost mirror: spread parser ported. Cgen widen helpers not yet
mirrored — wwstage stays byte-identical to cstage on the existing
test corpus, but will emit wrong asm if user code uses the new
patterns (probe sp2 shows the divergence).
700_e2e: 9 new rows covering call/let/assign/return × struct +
tagged subset, plus the spread-flatten case.
This commit is contained in:
481
cmd/w6c/cgen.c
481
cmd/w6c/cgen.c
@@ -771,6 +771,9 @@ localfind(Local *head, const char *name)
|
|||||||
|
|
||||||
static void cgexpr(Cg*, Node*, Local*);
|
static void cgexpr(Cg*, Node*, Local*);
|
||||||
static void cgstmt(Cg*, Node*, Local**, int*);
|
static void cgstmt(Cg*, Node*, Local**, int*);
|
||||||
|
static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int);
|
||||||
|
static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int);
|
||||||
|
static void cg_widen_tag_remap(Cg*, Type*, Type*, int);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cgexpr_int(Cg *c, long long v)
|
cgexpr_int(Cg *c, long long v)
|
||||||
@@ -778,6 +781,232 @@ cgexpr_int(Cg *c, long long v)
|
|||||||
ins2(c, A_MOVQ, aimm(v), areg(D_AX));
|
ins2(c, A_MOVQ, aimm(v), areg(D_AX));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* cg_widen_tag_remap — when widening from one tagged union to another,
|
||||||
|
* rewrite the source's variant tag at BP+slot_off+0 to use the dst
|
||||||
|
* union's variant indices. No-op when src and dst index orders coincide.
|
||||||
|
*
|
||||||
|
* Mirrors Hare's tagged-subset assignment: a value of type (A|B) flows
|
||||||
|
* into (A|B|C) by re-tagging the discriminator to the position the
|
||||||
|
* variant occupies in the wider union. Both must already match by
|
||||||
|
* cg_variant_match — the checker enforces that.
|
||||||
|
*
|
||||||
|
* Emits a CMPQ-chain switch over the source tag because w6a has no
|
||||||
|
* CMOVQ encoding. The chain is linear in nvariants; in practice tagged
|
||||||
|
* unions are small. */
|
||||||
|
static void
|
||||||
|
cg_widen_tag_remap(Cg *c, Type *du, Type *su, int slot_off)
|
||||||
|
{
|
||||||
|
if (du == NULL || du->kind != TY_TAGGED) return;
|
||||||
|
if (su == NULL || su->kind != TY_TAGGED) return;
|
||||||
|
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 != idx) { identity = 0; break; }
|
||||||
|
}
|
||||||
|
if (identity) return;
|
||||||
|
const char *done = mklabel(c, "remap_done");
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, slot_off + 0), areg(D_AX));
|
||||||
|
idx = 0;
|
||||||
|
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;
|
||||||
|
ins2(c, A_CMPQ, aimm(idx), areg(D_AX));
|
||||||
|
ins1(c, A_JNE, abranch(next));
|
||||||
|
ins2(c, A_MOVQ, aimm(di), areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
|
||||||
|
ins1(c, A_JMP, abranch(done));
|
||||||
|
label(c, next);
|
||||||
|
}
|
||||||
|
label(c, done);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cg_widen_tagged_store — write the tagged-union slot bytes for `src`
|
||||||
|
* into BP+slot_off, sized to `sz` (8 for nullable fold, else 16/24+).
|
||||||
|
* Used by call-site widening (via cg_widen_tagged_push) and by the
|
||||||
|
* let/assign/return/struct-field-init paths.
|
||||||
|
*
|
||||||
|
* Branches by source shape (tagged_arg_size > 0 source counts as a
|
||||||
|
* tagged subset — possibly with different variant indices):
|
||||||
|
* - nullable: dst is folded (*T|void); store pointer at +0.
|
||||||
|
* - tagged ident: byte-copy slot words then remap tag at +0.
|
||||||
|
* - tagged expression: cgexpr leaves AX=tag, DX=val0, [CX=val1] —
|
||||||
|
* spill into slot then remap.
|
||||||
|
* - struct ident: zero-fill, byte-copy struct words to +8.
|
||||||
|
* - struct literal: zero-fill, store each field at slot+8+field_off.
|
||||||
|
* - str: cgexpr leaves AX=ptr, BX=len.
|
||||||
|
* - scalar: cgexpr leaves AX; store at +8 with zero pad. */
|
||||||
|
static void
|
||||||
|
cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||||
|
int slot_off, int sz)
|
||||||
|
{
|
||||||
|
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
|
||||||
|
if (du == NULL || du->kind != TY_TAGGED) return;
|
||||||
|
if (du->nullable) {
|
||||||
|
cgexpr(c, src, *locals_p);
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Type *st = src ? src->type : NULL;
|
||||||
|
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||||
|
/* Tagged → tagged subset: copy slot words then tag-remap. */
|
||||||
|
if (su && su->kind == TY_TAGGED) {
|
||||||
|
int ssz = (int)su->size;
|
||||||
|
if (src->kind == N_IDENT) {
|
||||||
|
int soff = localfind(*locals_p, src->str);
|
||||||
|
for (int k = 0; k < ssz; k += 8) {
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, soff + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + k));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cgexpr(c, src, *locals_p);
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + 0));
|
||||||
|
if (ssz > 8)
|
||||||
|
ins2(c, A_MOVQ, areg(D_DX),
|
||||||
|
amem(D_BP, slot_off + 8));
|
||||||
|
if (ssz > 16)
|
||||||
|
ins2(c, A_MOVQ, areg(D_CX),
|
||||||
|
amem(D_BP, slot_off + 16));
|
||||||
|
}
|
||||||
|
if (ssz < sz) {
|
||||||
|
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||||
|
for (int k = ssz; k < sz; k += 8)
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + k));
|
||||||
|
}
|
||||||
|
cg_widen_tag_remap(c, du, su, slot_off);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Struct payload: zero the whole slot, then write fields/words
|
||||||
|
* at slot+8+ — keeping the tag word at slot+0 from the zero-fill,
|
||||||
|
* then patch it with the variant tag. */
|
||||||
|
if (su && su->kind == TY_STRUCT) {
|
||||||
|
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||||
|
for (int k = 0; k < sz; k += 8)
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + k));
|
||||||
|
int tag = cg_tag_for_variant(du, st);
|
||||||
|
if (src->kind == N_IDENT) {
|
||||||
|
int soff = localfind(*locals_p, src->str);
|
||||||
|
int ssz = (int)su->size;
|
||||||
|
int k = 0;
|
||||||
|
while (k + 8 <= ssz) {
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, soff + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + 8 + k));
|
||||||
|
k += 8;
|
||||||
|
}
|
||||||
|
if (k < ssz) {
|
||||||
|
/* Tail word: load with the right width to
|
||||||
|
* avoid stepping past the source slot. The
|
||||||
|
* zero-fill above means trailing slop is
|
||||||
|
* already clean. */
|
||||||
|
int tail = ssz - k;
|
||||||
|
int lop = (tail == 4) ? A_MOVL :
|
||||||
|
(tail == 1 ? A_MOVB : A_MOVQ);
|
||||||
|
ins2(c, lop,
|
||||||
|
amem(D_BP, soff + k), areg(D_AX));
|
||||||
|
ins2(c, lop, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + 8 + k));
|
||||||
|
}
|
||||||
|
} else if (src->kind == N_STRUCTLIT) {
|
||||||
|
for (Node *f = src->list; f; f = f->next) {
|
||||||
|
u64 foff = 0;
|
||||||
|
int fsz = 8;
|
||||||
|
Type *ftype = NULL;
|
||||||
|
for (Tfield *fl = su->fields; fl; fl = fl->next) {
|
||||||
|
if (strcmp(fl->name, f->str) == 0) {
|
||||||
|
foff = fl->offset;
|
||||||
|
fsz = (int)(fl->type ? fl->type->size : 8);
|
||||||
|
ftype = fl->type;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cgexpr(c, f->lhs, *locals_p);
|
||||||
|
int sl_isf32 = 0;
|
||||||
|
if (fld_isfloat(ftype, &sl_isf32)) {
|
||||||
|
int mov = sl_isf32 ? A_MOVSS : A_MOVSD;
|
||||||
|
ins2(c, mov, areg(D_X0),
|
||||||
|
amem(D_BP, slot_off + 8 + (int)foff));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Type *fu = (ftype && ftype->kind == TY_NAMED)
|
||||||
|
? ftype->under : ftype;
|
||||||
|
if (fu && fu->kind == TY_STR) {
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + 8 + (int)foff + 0));
|
||||||
|
ins2(c, A_MOVQ, areg(D_BX),
|
||||||
|
amem(D_BP, slot_off + 8 + (int)foff + 8));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int op = A_MOVQ;
|
||||||
|
if (fsz == 1) op = A_MOVB;
|
||||||
|
else if (fsz == 4) op = A_MOVL;
|
||||||
|
ins2(c, op, areg(D_AX),
|
||||||
|
amem(D_BP, slot_off + 8 + (int)foff));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||||
|
amem(D_BP, slot_off + 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* str payload: AX=ptr, BX=len from cgexpr. */
|
||||||
|
if (type_isstr(st) || (su && su->kind == TY_STR)) {
|
||||||
|
cgexpr(c, src, *locals_p);
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
|
||||||
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, slot_off + 16));
|
||||||
|
int tag = cg_tag_for_variant(du, st);
|
||||||
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||||
|
amem(D_BP, slot_off + 0));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* Scalar / pointer / etc. The high slot word (when sz > 16) is
|
||||||
|
* left untouched here — match dispatches on the tag word first
|
||||||
|
* and only the str branch reads slot+16, so leaving the pad
|
||||||
|
* uninitialised in let/assign matches the pre-refactor asm.
|
||||||
|
* cg_widen_tagged_push pre-zeroes the scratch slot before
|
||||||
|
* calling us, so the call-site push still sees clean pad. */
|
||||||
|
cgexpr(c, src, *locals_p);
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, slot_off + 8));
|
||||||
|
int tag = cg_tag_for_variant(du, st);
|
||||||
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag), amem(D_BP, slot_off + 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* cg_widen_tagged_push — call-site widening. Materialise the tagged
|
||||||
|
* value in a stack scratch slot then push slot words high→low so the
|
||||||
|
* arg-register pop drain sees tag first, then payload words. */
|
||||||
|
static void
|
||||||
|
cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||||
|
{
|
||||||
|
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
|
||||||
|
if (du && du->nullable) {
|
||||||
|
/* Single 8B slot: just push the pointer/null. */
|
||||||
|
cgexpr(c, src, *locals_p);
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const char *scr_name = mklabel(c, "argscr");
|
||||||
|
int scr = local_alloc(c, locals_p, scr_name, sz, cg_frame);
|
||||||
|
/* Zero the scratch slot first so any pad word the store path
|
||||||
|
* leaves untouched (scalar variant in a >16B slot, struct payload
|
||||||
|
* shorter than the slot's value area) reads as 0 on the callee.
|
||||||
|
* The store path then writes the variant bytes over the zeros. */
|
||||||
|
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||||
|
for (int k = 0; k < sz; k += 8)
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, scr + k));
|
||||||
|
cg_widen_tagged_store(c, locals_p, dst, src, scr, sz);
|
||||||
|
int nwords = sz / 8;
|
||||||
|
for (int k = nwords - 1; k >= 0; k--) {
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, scr + k * 8), areg(D_AX));
|
||||||
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
cgexpr(Cg *c, Node *n, Local *locals)
|
cgexpr(Cg *c, Node *n, Local *locals)
|
||||||
{
|
{
|
||||||
@@ -1570,9 +1799,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Plain `r = expr;` where r is a tagged-union local. Mirrors
|
/* Plain `r = expr;` where r is a tagged-union local.
|
||||||
* the let-init path: forward AX:DX[:CX] when rhs is itself
|
* Delegates to cg_widen_tagged_store: covers nullable fold,
|
||||||
* tagged, else synthesise the tag from rhs's static type. */
|
* tagged→tagged (with tag remap), struct payload (ident or
|
||||||
|
* literal), str payload, and scalar payload. */
|
||||||
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
||||||
&& n->lhs->type) {
|
&& n->lhs->type) {
|
||||||
Type *lt = n->lhs->type;
|
Type *lt = n->lhs->type;
|
||||||
@@ -1580,31 +1810,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (lu && lu->kind == TY_TAGGED) {
|
if (lu && lu->kind == TY_TAGGED) {
|
||||||
int off = localfind(locals, n->lhs->str);
|
int off = localfind(locals, n->lhs->str);
|
||||||
if (off == 0) break;
|
if (off == 0) break;
|
||||||
Type *rt = n->rhs ? n->rhs->type : NULL;
|
cg_widen_tagged_store(c, &locals, lu, n->rhs,
|
||||||
if (type_istagged(rt)) {
|
off, (int)lu->size);
|
||||||
cgexpr(c, n->rhs, locals);
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + 0));
|
|
||||||
ins2(c, A_MOVQ, areg(D_DX),
|
|
||||||
amem(D_BP, off + 8));
|
|
||||||
if (lu->size > 16)
|
|
||||||
ins2(c, A_MOVQ, areg(D_CX),
|
|
||||||
amem(D_BP, off + 16));
|
|
||||||
} else {
|
|
||||||
int tag = cg_tag_for_variant(lu, rt);
|
|
||||||
cgexpr(c, n->rhs, locals);
|
|
||||||
if (type_isstr(rt)) {
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + 8));
|
|
||||||
ins2(c, A_MOVQ, areg(D_BX),
|
|
||||||
amem(D_BP, off + 16));
|
|
||||||
} else {
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + 8));
|
|
||||||
}
|
|
||||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
|
||||||
amem(D_BP, off + 0));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2072,9 +2279,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
callee_t->under : callee_t;
|
callee_t->under : callee_t;
|
||||||
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
|
Tparam *callee_params = (cu && cu->kind == TY_FN) ?
|
||||||
cu->params : NULL;
|
cu->params : NULL;
|
||||||
/* widen[i]: param is tagged, arg is a concrete variant.
|
/* widen[i]: param is tagged and arg needs re-layout.
|
||||||
* widen_sz[i]: param's tagged slot size (8/16/24).
|
* - arg is a concrete variant (str/struct/scalar) — wrap
|
||||||
* widen_param[i]: param type (for tag-index lookup). */
|
* in the param's slot shape.
|
||||||
|
* - arg is itself a tagged union of a subset/different
|
||||||
|
* variant set — copy the slot words and remap the tag.
|
||||||
|
* Identical types pass through unchanged. */
|
||||||
int widen[16] = {0};
|
int widen[16] = {0};
|
||||||
int widen_sz[16] = {0};
|
int widen_sz[16] = {0};
|
||||||
Type *widen_param[16] = {0};
|
Type *widen_param[16] = {0};
|
||||||
@@ -2084,18 +2294,27 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (p == NULL) break;
|
if (p == NULL) break;
|
||||||
Type *at = args[i] ? args[i]->type : NULL;
|
Type *at = args[i] ? args[i]->type : NULL;
|
||||||
int psz = tagged_arg_size(p->type);
|
int psz = tagged_arg_size(p->type);
|
||||||
int arg_tagged = tagged_arg_size(at) > 0;
|
if (psz > 0) {
|
||||||
if (psz > 0 && !arg_tagged) {
|
Type *pu = (p->type && p->type->kind == TY_NAMED)
|
||||||
|
? p->type->under : p->type;
|
||||||
|
Type *au = (at && at->kind == TY_NAMED)
|
||||||
|
? at->under : at;
|
||||||
|
int same = (pu == au) || type_eq(p->type, at);
|
||||||
|
if (!same) {
|
||||||
widen[i] = 1;
|
widen[i] = 1;
|
||||||
widen_sz[i] = psz;
|
widen_sz[i] = psz;
|
||||||
widen_param[i] = p->type;
|
widen_param[i] = p->type;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
p = p->next;
|
p = p->next;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* eval right-to-left, push to stack */
|
/* eval right-to-left, push to stack. Each N_IDENT fast-path
|
||||||
|
* is guarded by !widen[i] so the tagged-union widening (which
|
||||||
|
* needs to synthesise tag + payload + pad) takes precedence
|
||||||
|
* over the verbatim slice/struct/tagged-ident loads below. */
|
||||||
for (int i = argcount - 1; i >= 0; i--) {
|
for (int i = argcount - 1; i >= 0; i--) {
|
||||||
if (node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
if (!widen[i] && node_isslice(args[i]) && args[i]->kind == N_IDENT) {
|
||||||
int off = localfind(locals, args[i]->str);
|
int off = localfind(locals, args[i]->str);
|
||||||
/* push cap, len, ptr (top) so pops give ptr,len,cap */
|
/* push cap, len, ptr (top) so pops give ptr,len,cap */
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_AX));
|
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_AX));
|
||||||
@@ -2153,7 +2372,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins1(c, A_PUSHQ, areg(D_CX)); /* ptr */
|
ins1(c, A_PUSHQ, areg(D_CX)); /* ptr */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (node_isstructarg(args[i]) && args[i]->kind == N_IDENT) {
|
if (!widen[i] && node_isstructarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||||
/* load qword(s) directly from the struct's slot */
|
/* load qword(s) directly from the struct's slot */
|
||||||
int off = localfind(locals, args[i]->str);
|
int off = localfind(locals, args[i]->str);
|
||||||
int sz = struct_arg_size(args[i]->type);
|
int sz = struct_arg_size(args[i]->type);
|
||||||
@@ -2165,7 +2384,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
ins1(c, A_PUSHQ, areg(D_AX));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (node_istaggedarg(args[i]) && args[i]->kind == N_IDENT) {
|
if (!widen[i] && node_istaggedarg(args[i]) && args[i]->kind == N_IDENT) {
|
||||||
/* Tagged-union: push each 8B word from the slot.
|
/* Tagged-union: push each 8B word from the slot.
|
||||||
* High word goes first so the popper drains them
|
* High word goes first so the popper drains them
|
||||||
* in low→high order into the arg-register class. */
|
* in low→high order into the arg-register class. */
|
||||||
@@ -2182,41 +2401,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (widen[i]) {
|
if (widen[i]) {
|
||||||
/* Concrete → tagged-union widening at the call
|
/* Concrete → tagged-union widening at the call
|
||||||
* site. Mirrors the let/assign/return widening:
|
* site. Mirrors the let/assign/return widening:
|
||||||
* synthesise tag from the arg's static variant
|
* lay out the value in the parameter's slot
|
||||||
* type, evaluate the arg, lay it out as the
|
* shape, then push high→low so pop drains tag
|
||||||
* parameter's tagged slot, then push high→low so
|
* first.
|
||||||
* pop drains tag first. */
|
*
|
||||||
int tag = cg_tag_for_variant(widen_param[i],
|
* Branches by source shape:
|
||||||
args[i]->type);
|
* - nullable (sz==8): pointer IS the disc.
|
||||||
if (tag < 0) tag = 0;
|
* - str: tag@+0, ptr@+8, len@+16.
|
||||||
int sz = widen_sz[i];
|
* - struct ident: copy struct words then
|
||||||
if (sz == 8) {
|
* prepend tag, zero-pad to slot size.
|
||||||
/* Nullable fold: the pointer value IS the
|
* - struct literal: materialise via a stack
|
||||||
* discriminator — no separate tag word. */
|
* scratch slot — store each field at its
|
||||||
cgexpr(c, args[i], locals);
|
* struct-relative offset (with the +8 tag
|
||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
* shift), zero-fill, then push from slot.
|
||||||
continue;
|
* - tagged source: load src slot words, remap
|
||||||
}
|
* the tag word via cg_widen_tag_remap, pad
|
||||||
cgexpr(c, args[i], locals);
|
* to wider dst slot, push.
|
||||||
if (node_isstr(args[i])) {
|
* - scalar: tag@+0, value@+8, optional pad. */
|
||||||
/* slot 24: [+0]=tag,[+8]=ptr,[+16]=len */
|
cg_widen_tagged_push(c, &locals, widen_param[i],
|
||||||
ins1(c, A_PUSHQ, areg(D_BX));
|
args[i], widen_sz[i]);
|
||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
|
||||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
|
||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
|
||||||
} else {
|
|
||||||
/* Scalar variant: single value word at +8.
|
|
||||||
* Pad a zero high word when the slot is 24B
|
|
||||||
* (some other variant of the union is 16B). */
|
|
||||||
if (sz > 16) {
|
|
||||||
ins2(c, A_XORQ, areg(D_DX),
|
|
||||||
areg(D_DX));
|
|
||||||
ins1(c, A_PUSHQ, areg(D_DX));
|
|
||||||
}
|
|
||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
|
||||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
|
||||||
ins1(c, A_PUSHQ, areg(D_AX));
|
|
||||||
}
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
cgexpr(c, args[i], locals);
|
cgexpr(c, args[i], locals);
|
||||||
@@ -3327,47 +3530,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Tagged-union initialiser. Two shapes:
|
/* Tagged-union initialiser. Delegates to cg_widen_tagged_store,
|
||||||
* 1) rhs already produces a tagged-union value (e.g. a fn
|
* which handles nullable fold, tagged→tagged (with tag remap
|
||||||
* call returning (T | E)). cgexpr leaves AX=tag,
|
* when variant indices differ), struct payload (ident or
|
||||||
* DX=value0[, CX=value1] — copy each into the slot.
|
* literal — field-by-field at slot+8+field_off), str payload,
|
||||||
* 2) rhs is a bare variant value (e.g. `let r: (i64|i32) = 7`).
|
* and scalar payload (with zero-pad to the slot size). */
|
||||||
* Synthesise the tag from rhs's static type and store it
|
|
||||||
* alongside the value. str-typed rhs flows as
|
|
||||||
* (AX=ptr, BX=len) so we store both halves.
|
|
||||||
*
|
|
||||||
* Nullable folded `(*T | void)`: the slot is a single 8B
|
|
||||||
* pointer. Both the value-from-call and bare-variant paths
|
|
||||||
* simplify to "spill AX". void variant stores 0; *T variant
|
|
||||||
* stores the pointer. */
|
|
||||||
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
if (n->rhs && lu && lu->kind == TY_TAGGED) {
|
||||||
Type *rt = n->rhs->type;
|
cg_widen_tagged_store(c, locals, lu, n->rhs, off, sz);
|
||||||
if (lu->nullable) {
|
|
||||||
cgexpr(c, n->rhs, *locals);
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + 0));
|
|
||||||
} else if (type_istagged(rt)) {
|
|
||||||
cgexpr(c, n->rhs, *locals);
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
|
||||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
|
||||||
if (lu->size > 16)
|
|
||||||
ins2(c, A_MOVQ, areg(D_CX),
|
|
||||||
amem(D_BP, off + 16));
|
|
||||||
} else {
|
|
||||||
int tag = cg_tag_for_variant(lu, rt);
|
|
||||||
cgexpr(c, n->rhs, *locals);
|
|
||||||
if (type_isstr(rt)) {
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + 8));
|
|
||||||
ins2(c, A_MOVQ, areg(D_BX),
|
|
||||||
amem(D_BP, off + 16));
|
|
||||||
} else {
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + 8));
|
|
||||||
}
|
|
||||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
|
||||||
amem(D_BP, off + 0));
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* slice expression initialiser: build a {ptr, len, cap} header
|
/* slice expression initialiser: build a {ptr, len, cap} header
|
||||||
@@ -3472,37 +3641,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/* Tagged-union field: synthesise tag from
|
/* Tagged-union field: delegate to the shared
|
||||||
* f->lhs's static type and store value bytes
|
* widening writer. Handles str, scalar, struct
|
||||||
* (1 or 2 words for ≤8B/str variants). */
|
* literal/ident payload, and tagged-subset
|
||||||
|
* forwarding (with tag remap). The field's slot
|
||||||
|
* starts at off+foff inside the struct slot. */
|
||||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||||
? ft->under : ft;
|
? ft->under : ft;
|
||||||
if (fu && fu->kind == TY_TAGGED) {
|
if (fu && fu->kind == TY_TAGGED) {
|
||||||
Type *vt = f->lhs ? f->lhs->type : NULL;
|
cg_widen_tagged_store(c, locals, fu,
|
||||||
int tag = cg_tag_for_variant(fu, vt);
|
f->lhs, off + (int)foff,
|
||||||
cgexpr(c, f->lhs, *locals);
|
(int)fu->size);
|
||||||
if (type_isstr(vt)) {
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + (int)foff + 8));
|
|
||||||
ins2(c, A_MOVQ, areg(D_BX),
|
|
||||||
amem(D_BP, off + (int)foff + 16));
|
|
||||||
} else if (type_istagged(vt)) {
|
|
||||||
/* forwarding a tagged value */
|
|
||||||
ins2(c, A_MOVQ, areg(D_DX),
|
|
||||||
amem(D_BP, off + (int)foff + 8));
|
|
||||||
if (fu->size > 16)
|
|
||||||
ins2(c, A_MOVQ, areg(D_CX),
|
|
||||||
amem(D_BP, off + (int)foff + 16));
|
|
||||||
/* AX already holds the tag */
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + (int)foff + 0));
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
|
||||||
amem(D_BP, off + (int)foff + 8));
|
|
||||||
}
|
|
||||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
|
||||||
amem(D_BP, off + (int)foff + 0));
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
cgexpr(c, f->lhs, *locals);
|
cgexpr(c, f->lhs, *locals);
|
||||||
@@ -3615,18 +3764,25 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
if (rt->kind == TY_NAMED) rt = rt->under;
|
if (rt->kind == TY_NAMED) rt = rt->under;
|
||||||
if (rt && rt->kind == TY_TAGGED) {
|
if (rt && rt->kind == TY_TAGGED) {
|
||||||
Type *vt = n->lhs->type;
|
Type *vt = n->lhs->type;
|
||||||
cgexpr(c, n->lhs, *locals);
|
Type *vu = (vt && vt->kind == TY_NAMED)
|
||||||
|
? vt->under : vt;
|
||||||
|
int istagged = vu && vu->kind == TY_TAGGED;
|
||||||
|
int passthrough = istagged && (vu == rt ||
|
||||||
|
type_eq(vt, cg_ret_type));
|
||||||
|
int isstruct = vu && vu->kind == TY_STRUCT;
|
||||||
if (rt->nullable) {
|
if (rt->nullable) {
|
||||||
/* AX already holds the pointer (or
|
cgexpr(c, n->lhs, *locals);
|
||||||
* 0 if the value was `nil` / `void`).
|
} else if (passthrough) {
|
||||||
* No tag word, no shuffle. */
|
/* same tagged type: forward AX/DX/CX. */
|
||||||
} else if (!type_istagged(vt)) {
|
cgexpr(c, n->lhs, *locals);
|
||||||
|
} else if (!istagged && !isstruct) {
|
||||||
|
/* str / scalar variant: synthesise the
|
||||||
|
* tag in AX and shuffle the value into
|
||||||
|
* DX[/CX]. Direct register path keeps
|
||||||
|
* the asm short — no scratch slot. */
|
||||||
int tag = cg_tag_for_variant(rt, vt);
|
int tag = cg_tag_for_variant(rt, vt);
|
||||||
|
cgexpr(c, n->lhs, *locals);
|
||||||
if (type_isstr(vt)) {
|
if (type_isstr(vt)) {
|
||||||
/* AX=ptr, BX=len → DX=ptr,
|
|
||||||
* CX=len, AX=tag. Move BX
|
|
||||||
* before AX since the tag
|
|
||||||
* MOVQ trashes AX. */
|
|
||||||
ins2(c, A_MOVQ, areg(D_BX),
|
ins2(c, A_MOVQ, areg(D_BX),
|
||||||
areg(D_CX));
|
areg(D_CX));
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
@@ -3637,6 +3793,33 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
}
|
}
|
||||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||||
areg(D_AX));
|
areg(D_AX));
|
||||||
|
} else {
|
||||||
|
/* Struct variant or tagged-subset:
|
||||||
|
* materialise the widened value in a
|
||||||
|
* scratch slot, then load AX/DX/CX
|
||||||
|
* from the slot. Struct literal: field
|
||||||
|
* stores; struct ident: word copy;
|
||||||
|
* tagged subset: copy + tag remap. */
|
||||||
|
int sz = (int)rt->size;
|
||||||
|
const char *scrn = mklabel(c, "retscr");
|
||||||
|
int scr = local_alloc(c, locals, scrn,
|
||||||
|
sz, cg_frame);
|
||||||
|
ins2(c, A_XORQ, areg(D_AX), areg(D_AX));
|
||||||
|
for (int k = 0; k < sz; k += 8)
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, scr + k));
|
||||||
|
cg_widen_tagged_store(c, locals, rt,
|
||||||
|
n->lhs, scr, sz);
|
||||||
|
ins2(c, A_MOVQ, amem(D_BP, scr + 0),
|
||||||
|
areg(D_AX));
|
||||||
|
if (sz > 8)
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, scr + 8),
|
||||||
|
areg(D_DX));
|
||||||
|
if (sz > 16)
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BP, scr + 16),
|
||||||
|
areg(D_CX));
|
||||||
}
|
}
|
||||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
ins1(c, A_POPQ, areg(D_BP));
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
|
|||||||
@@ -323,9 +323,16 @@ resolve_type(Checker *c, Node *n)
|
|||||||
for (Node *e = n->list; e; e = e->next) {
|
for (Node *e = n->list; e; e = e->next) {
|
||||||
Type *vt = resolve_type(c, e);
|
Type *vt = resolve_type(c, e);
|
||||||
if (vt == ty_never) continue;
|
if (vt == ty_never) continue;
|
||||||
if (vt && vt->kind == TY_TAGGED) {
|
int spread = (e->op == TK_ELLIPSIS);
|
||||||
/* flatten anonymous nested tagged */
|
/* `...inner` spread: flatten the variants of the
|
||||||
for (Tparam *src = vt->params; src; src = src->next) {
|
* (possibly NAMED) inner tagged union into the
|
||||||
|
* enclosing union — matches Hare's parse-time
|
||||||
|
* unwrap flag on each tagged_type entry. */
|
||||||
|
Type *vu = spread && vt && vt->kind == TY_NAMED
|
||||||
|
? vt->under : vt;
|
||||||
|
if (vu && vu->kind == TY_TAGGED &&
|
||||||
|
(spread || vt->kind == TY_TAGGED)) {
|
||||||
|
for (Tparam *src = vu->params; src; src = src->next) {
|
||||||
Type *st = src->type;
|
Type *st = src->type;
|
||||||
if (st == ty_never) continue;
|
if (st == ty_never) continue;
|
||||||
if (variant_present(head, st)) continue;
|
if (variant_present(head, st)) continue;
|
||||||
|
|||||||
@@ -292,14 +292,27 @@ parsetype(Parser *p)
|
|||||||
/* Three forms inside the parens:
|
/* Three forms inside the parens:
|
||||||
* (T) — parenthesised single type
|
* (T) — parenthesised single type
|
||||||
* (T, T2, ...) — tuple type
|
* (T, T2, ...) — tuple type
|
||||||
* (T | T2 | ...) — tagged-union type (Hare-style sum) */
|
* (T | T2 | ...) — tagged-union type (Hare-style sum)
|
||||||
|
*
|
||||||
|
* Each tagged variant may be prefixed with `...` to mark
|
||||||
|
* a spread: when the variant resolves to another tagged
|
||||||
|
* union its variants are flattened into the enclosing
|
||||||
|
* union. We tag the spread on Node.op = TK_ELLIPSIS so
|
||||||
|
* resolve_type can distinguish intent (today the checker
|
||||||
|
* flattens any nested tagged unconditionally, matching
|
||||||
|
* Hare's structural-equivalence rule, but the marker is
|
||||||
|
* preserved for future nominal handling). */
|
||||||
advance(p);
|
advance(p);
|
||||||
|
int first_spread = accept(p, TK_ELLIPSIS);
|
||||||
Node *first = parsetype(p);
|
Node *first = parsetype(p);
|
||||||
|
if (first_spread) first->op = TK_ELLIPSIS;
|
||||||
if (accept(p, TK_PIPE)) {
|
if (accept(p, TK_PIPE)) {
|
||||||
Node *t = newnode(p->a, N_TTAGGED, pp);
|
Node *t = newnode(p->a, N_TTAGGED, pp);
|
||||||
Node *head = first, *tail = first;
|
Node *head = first, *tail = first;
|
||||||
for (;;) {
|
for (;;) {
|
||||||
|
int spread = accept(p, TK_ELLIPSIS);
|
||||||
Node *e = parsetype(p);
|
Node *e = parsetype(p);
|
||||||
|
if (spread) e->op = TK_ELLIPSIS;
|
||||||
tail->next = e;
|
tail->next = e;
|
||||||
tail = e;
|
tail = e;
|
||||||
if (!accept(p, TK_PIPE)) break;
|
if (!accept(p, TK_PIPE)) break;
|
||||||
@@ -308,6 +321,10 @@ parsetype(Parser *p)
|
|||||||
t->list = head;
|
t->list = head;
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
if (first_spread) {
|
||||||
|
errorf(pp, "spread '...' only valid before tagged-union variants");
|
||||||
|
p->errs++;
|
||||||
|
}
|
||||||
if (!accept(p, TK_COMMA)) {
|
if (!accept(p, TK_COMMA)) {
|
||||||
expect(p, TK_RPAREN);
|
expect(p, TK_RPAREN);
|
||||||
return first;
|
return first;
|
||||||
|
|||||||
@@ -243,6 +243,19 @@ type_eq(Type *a, Type *b)
|
|||||||
}
|
}
|
||||||
return pa == NULL && pb == NULL;
|
return pa == NULL && pb == NULL;
|
||||||
}
|
}
|
||||||
|
case TY_TAGGED: {
|
||||||
|
/* Tagged unions are structurally equal iff variant lists
|
||||||
|
* match position-by-position. Nullable fold is a per-Type
|
||||||
|
* flag, so equal-up-to-fold types compare not-equal here —
|
||||||
|
* the caller can unwrap intentionally if needed. */
|
||||||
|
if (a->nullable != b->nullable) return 0;
|
||||||
|
Tparam *pa = a->params, *pb = b->params;
|
||||||
|
while (pa && pb) {
|
||||||
|
if (!type_eq(pa->type, pb->type)) return 0;
|
||||||
|
pa = pa->next; pb = pb->next;
|
||||||
|
}
|
||||||
|
return pa == NULL && pb == NULL;
|
||||||
|
}
|
||||||
default: return 1; /* primitives */
|
default: return 1; /* primitives */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -255,11 +268,14 @@ type_assignable(Type *dst, Type *src)
|
|||||||
if (src == ty_never) return 1; /* bottom flows into anything */
|
if (src == ty_never) return 1; /* bottom flows into anything */
|
||||||
if (type_eq(dst, src)) return 1;
|
if (type_eq(dst, src)) return 1;
|
||||||
|
|
||||||
/* Tagged-union variant inclusion: src is one of dst's variants.
|
/* Tagged-union assignment:
|
||||||
* Checked before the untyped branch so untyped literals (e.g.
|
* - concrete → tagged: src must match one of dst's variants.
|
||||||
* 0, "msg") flow through to a variant's typed slot. Unwraps a
|
* - tagged → tagged: src is assignable when every variant of
|
||||||
* named alias on either side so `type result = (T | E);` also
|
* src appears as a variant of dst (Hare-style subset). Tag
|
||||||
* accepts variants and the inverse. */
|
* remap at the use site handles different variant indices.
|
||||||
|
* Checked before the untyped branch so untyped literals flow
|
||||||
|
* through to a variant's typed slot. Unwraps a NAMED alias on
|
||||||
|
* either side so `type result = (T|E);` accepts variants too. */
|
||||||
{
|
{
|
||||||
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
|
Type *du = (dst->kind == TY_NAMED) ? dst->under : dst;
|
||||||
Type *su = (src->kind == TY_NAMED) ? src->under : src;
|
Type *su = (src->kind == TY_NAMED) ? src->under : src;
|
||||||
@@ -269,6 +285,18 @@ type_assignable(Type *dst, Type *src)
|
|||||||
if (type_assignable(p->type, src)) return 1;
|
if (type_assignable(p->type, src)) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
if (du && du->kind == TY_TAGGED &&
|
||||||
|
su && su->kind == TY_TAGGED) {
|
||||||
|
for (Tparam *sp = su->params; sp; sp = sp->next) {
|
||||||
|
int ok = 0;
|
||||||
|
for (Tparam *dp = du->params; dp; dp = dp->next)
|
||||||
|
if (type_eq(dp->type, sp->type)) {
|
||||||
|
ok = 1; break;
|
||||||
|
}
|
||||||
|
if (!ok) return 0;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Untyped → typed: only if the typed kind can hold the value. */
|
/* Untyped → typed: only if the typed kind can hold the value. */
|
||||||
|
|||||||
@@ -255,14 +255,24 @@ fn parsetype(p: *parser) *node = {
|
|||||||
|
|
||||||
if (p.curkind == tkind.TK_LPAREN) {
|
if (p.curkind == tkind.TK_LPAREN) {
|
||||||
// (T) or (T, T, ...) or (T | T | ...)
|
// (T) or (T, T, ...) or (T | T | ...)
|
||||||
|
//
|
||||||
|
// Each tagged variant may be prefixed with `...` to mark a
|
||||||
|
// spread — when the variant resolves to another tagged union
|
||||||
|
// its variants are flattened into the enclosing union. We
|
||||||
|
// tag the spread on node.op = TK_ELLIPSIS so resolve_type
|
||||||
|
// can distinguish intent. Mirrors C parsetype.
|
||||||
advance(p);
|
advance(p);
|
||||||
|
let firstspread: bool = accepttok(p, tkind.TK_ELLIPSIS);
|
||||||
let first: *node = parsetype(p);
|
let first: *node = parsetype(p);
|
||||||
|
if (firstspread) { first.op = tkind.TK_ELLIPSIS; };
|
||||||
if (accepttok(p, tkind.TK_PIPE)) {
|
if (accepttok(p, tkind.TK_PIPE)) {
|
||||||
let n: *node = newnode(p.a, nkind.N_TTAGGED, pf, pl, pc);
|
let n: *node = newnode(p.a, nkind.N_TTAGGED, pf, pl, pc);
|
||||||
let head: *node = first;
|
let head: *node = first;
|
||||||
let tail: *node = first;
|
let tail: *node = first;
|
||||||
for (true) {
|
for (true) {
|
||||||
|
let spread: bool = accepttok(p, tkind.TK_ELLIPSIS);
|
||||||
let e: *node = parsetype(p);
|
let e: *node = parsetype(p);
|
||||||
|
if (spread) { e.op = tkind.TK_ELLIPSIS; };
|
||||||
tail.next = e;
|
tail.next = e;
|
||||||
tail = e;
|
tail = e;
|
||||||
if (!accepttok(p, tkind.TK_PIPE)) { break; };
|
if (!accepttok(p, tkind.TK_PIPE)) { break; };
|
||||||
@@ -271,6 +281,9 @@ fn parsetype(p: *parser) *node = {
|
|||||||
n.list = head;
|
n.list = head;
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
if (firstspread) {
|
||||||
|
errmsg(p, "spread '...' only valid before tagged-union variants");
|
||||||
|
};
|
||||||
if (!accepttok(p, tkind.TK_COMMA)) {
|
if (!accepttok(p, tkind.TK_COMMA)) {
|
||||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
|
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
|
||||||
return first;
|
return first;
|
||||||
|
|||||||
@@ -3952,14 +3952,24 @@ fn parsetype(p: *parser) *node = {
|
|||||||
|
|
||||||
if (p.curkind == tkind.TK_LPAREN) {
|
if (p.curkind == tkind.TK_LPAREN) {
|
||||||
// (T) or (T, T, ...) or (T | T | ...)
|
// (T) or (T, T, ...) or (T | T | ...)
|
||||||
|
//
|
||||||
|
// Each tagged variant may be prefixed with `...` to mark a
|
||||||
|
// spread — when the variant resolves to another tagged union
|
||||||
|
// its variants are flattened into the enclosing union. We
|
||||||
|
// tag the spread on node.op = TK_ELLIPSIS so resolve_type
|
||||||
|
// can distinguish intent. Mirrors C parsetype.
|
||||||
advance(p);
|
advance(p);
|
||||||
|
let firstspread: bool = accepttok(p, tkind.TK_ELLIPSIS);
|
||||||
let first: *node = parsetype(p);
|
let first: *node = parsetype(p);
|
||||||
|
if (firstspread) { first.op = tkind.TK_ELLIPSIS; };
|
||||||
if (accepttok(p, tkind.TK_PIPE)) {
|
if (accepttok(p, tkind.TK_PIPE)) {
|
||||||
let n: *node = newnode(p.a, nkind.N_TTAGGED, pf, pl, pc);
|
let n: *node = newnode(p.a, nkind.N_TTAGGED, pf, pl, pc);
|
||||||
let head: *node = first;
|
let head: *node = first;
|
||||||
let tail: *node = first;
|
let tail: *node = first;
|
||||||
for (true) {
|
for (true) {
|
||||||
|
let spread: bool = accepttok(p, tkind.TK_ELLIPSIS);
|
||||||
let e: *node = parsetype(p);
|
let e: *node = parsetype(p);
|
||||||
|
if (spread) { e.op = tkind.TK_ELLIPSIS; };
|
||||||
tail.next = e;
|
tail.next = e;
|
||||||
tail = e;
|
tail = e;
|
||||||
if (!accepttok(p, tkind.TK_PIPE)) { break; };
|
if (!accepttok(p, tkind.TK_PIPE)) { break; };
|
||||||
@@ -3968,6 +3978,9 @@ fn parsetype(p: *parser) *node = {
|
|||||||
n.list = head;
|
n.list = head;
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
if (firstspread) {
|
||||||
|
errmsg(p, "spread '...' only valid before tagged-union variants");
|
||||||
|
};
|
||||||
if (!accepttok(p, tkind.TK_COMMA)) {
|
if (!accepttok(p, tkind.TK_COMMA)) {
|
||||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
|
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
|
||||||
return first;
|
return first;
|
||||||
|
|||||||
@@ -3952,14 +3952,24 @@ fn parsetype(p: *parser) *node = {
|
|||||||
|
|
||||||
if (p.curkind == tkind.TK_LPAREN) {
|
if (p.curkind == tkind.TK_LPAREN) {
|
||||||
// (T) or (T, T, ...) or (T | T | ...)
|
// (T) or (T, T, ...) or (T | T | ...)
|
||||||
|
//
|
||||||
|
// Each tagged variant may be prefixed with `...` to mark a
|
||||||
|
// spread — when the variant resolves to another tagged union
|
||||||
|
// its variants are flattened into the enclosing union. We
|
||||||
|
// tag the spread on node.op = TK_ELLIPSIS so resolve_type
|
||||||
|
// can distinguish intent. Mirrors C parsetype.
|
||||||
advance(p);
|
advance(p);
|
||||||
|
let firstspread: bool = accepttok(p, tkind.TK_ELLIPSIS);
|
||||||
let first: *node = parsetype(p);
|
let first: *node = parsetype(p);
|
||||||
|
if (firstspread) { first.op = tkind.TK_ELLIPSIS; };
|
||||||
if (accepttok(p, tkind.TK_PIPE)) {
|
if (accepttok(p, tkind.TK_PIPE)) {
|
||||||
let n: *node = newnode(p.a, nkind.N_TTAGGED, pf, pl, pc);
|
let n: *node = newnode(p.a, nkind.N_TTAGGED, pf, pl, pc);
|
||||||
let head: *node = first;
|
let head: *node = first;
|
||||||
let tail: *node = first;
|
let tail: *node = first;
|
||||||
for (true) {
|
for (true) {
|
||||||
|
let spread: bool = accepttok(p, tkind.TK_ELLIPSIS);
|
||||||
let e: *node = parsetype(p);
|
let e: *node = parsetype(p);
|
||||||
|
if (spread) { e.op = tkind.TK_ELLIPSIS; };
|
||||||
tail.next = e;
|
tail.next = e;
|
||||||
tail = e;
|
tail = e;
|
||||||
if (!accepttok(p, tkind.TK_PIPE)) { break; };
|
if (!accepttok(p, tkind.TK_PIPE)) { break; };
|
||||||
@@ -3968,6 +3978,9 @@ fn parsetype(p: *parser) *node = {
|
|||||||
n.list = head;
|
n.list = head;
|
||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
|
if (firstspread) {
|
||||||
|
errmsg(p, "spread '...' only valid before tagged-union variants");
|
||||||
|
};
|
||||||
if (!accepttok(p, tkind.TK_COMMA)) {
|
if (!accepttok(p, tkind.TK_COMMA)) {
|
||||||
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
|
expecttok(p, tkind.TK_RPAREN, "expected ')' after parenthesised type");
|
||||||
return first;
|
return first;
|
||||||
|
|||||||
@@ -828,6 +828,133 @@ static const struct row rows[] = {
|
|||||||
" };\n"
|
" };\n"
|
||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};", 3 },
|
"};", 3 },
|
||||||
|
/* Struct variant of a tagged union at the call site. The arg is
|
||||||
|
* an N_STRUCTLIT, the param is (str|point). Widening at the call
|
||||||
|
* site must zero-fill the scratch slot, store each field at
|
||||||
|
* slot+8+field_off, then push the slot words high→low. */
|
||||||
|
{ "type point = struct { x: i32, y: i32 };\n"
|
||||||
|
"fn classify(r: (str | point)) i32 = {\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 0 - s.len: i32;\n"
|
||||||
|
" case let p: point => return p.x + p.y;\n"
|
||||||
|
" };\n"
|
||||||
|
" return -1;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" return classify(point { x = 10, y = 20 });\n"
|
||||||
|
"};", 30 },
|
||||||
|
/* Struct variant passed as a typed local. Widening copies the
|
||||||
|
* struct words from the local into the scratch slot at +8. */
|
||||||
|
{ "type point = struct { x: i32, y: i32 };\n"
|
||||||
|
"fn classify(r: (str | point)) i32 = {\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 0 - s.len: i32;\n"
|
||||||
|
" case let p: point => return p.x + p.y;\n"
|
||||||
|
" };\n"
|
||||||
|
" return -1;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let p: point = point { x = 11, y = 22 };\n"
|
||||||
|
" return classify(p);\n"
|
||||||
|
"};", 33 },
|
||||||
|
/* let-init of a tagged-union local from a struct literal: the
|
||||||
|
* field stores go into slot+8+field_off in-place; tag patched
|
||||||
|
* last. */
|
||||||
|
{ "type point = struct { x: i32, y: i32 };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let r: (str | point) = point { x = 7, y = 35 };\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 0 - s.len: i32;\n"
|
||||||
|
" case let p: point => return p.x + p.y;\n"
|
||||||
|
" };\n"
|
||||||
|
" return -1;\n"
|
||||||
|
"};", 42 },
|
||||||
|
/* Reassign a tagged-union local to a struct literal. Same path
|
||||||
|
* as let-init but writing into an already-allocated slot. */
|
||||||
|
{ "type point = struct { x: i32, y: i32 };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let r: (str | point) = \"init\";\n"
|
||||||
|
" r = point { x = 100, y = 23 };\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 0 - s.len: i32;\n"
|
||||||
|
" case let p: point => return p.x + p.y;\n"
|
||||||
|
" };\n"
|
||||||
|
" return -1;\n"
|
||||||
|
"};", 123 },
|
||||||
|
/* Return a struct variant of the fn's tagged return type. The
|
||||||
|
* scratch-slot path materialises the struct payload then loads
|
||||||
|
* AX/DX/CX from it. */
|
||||||
|
{ "type point = struct { x: i32, y: i32 };\n"
|
||||||
|
"fn make() (str | point) = {\n"
|
||||||
|
" return point { x = 12, y = 30 };\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let r: (str | point) = make();\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 0 - s.len: i32;\n"
|
||||||
|
" case let p: point => return p.x + p.y;\n"
|
||||||
|
" };\n"
|
||||||
|
" return -1;\n"
|
||||||
|
"};", 42 },
|
||||||
|
/* Widen a smaller tagged union to a wider one across slot sizes
|
||||||
|
* AND remapped variant indices. (i32 | rune) is 16B with i32 at
|
||||||
|
* tag 0; (str | i32 | rune) is 24B with i32 at tag 1. The widen
|
||||||
|
* path copies the slot words, zero-pads to 24B, then runs a
|
||||||
|
* CMPQ-chain switch to remap src tag 0 → dst tag 1. */
|
||||||
|
{ "fn classify(r: (str | i32 | rune)) i32 = {\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 1;\n"
|
||||||
|
" case let n: i32 => return n;\n"
|
||||||
|
" case let c: rune => return c: i32 + 100;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let inner: (i32 | rune) = 42: i32;\n"
|
||||||
|
" return classify(inner);\n"
|
||||||
|
"};", 42 },
|
||||||
|
/* Same shape but the rune variant of inner exercises the tag
|
||||||
|
* remap from src tag 1 → dst tag 2. The rune literal needs an
|
||||||
|
* explicit `: rune` cast — `'A'` is an untyped rune and the
|
||||||
|
* variant search picks the first variant that accepts it (i32,
|
||||||
|
* which also accepts untyped runes). 'A' = 65 + 100 = 165. */
|
||||||
|
{ "fn classify(r: (str | i32 | rune)) i32 = {\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 1;\n"
|
||||||
|
" case let n: i32 => return n;\n"
|
||||||
|
" case let c: rune => return c: i32 + 100;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let inner: (i32 | rune) = 'A': rune;\n"
|
||||||
|
" return classify(inner);\n"
|
||||||
|
"};", 165 },
|
||||||
|
/* let-init of a wider tagged union from a smaller-tagged local. */
|
||||||
|
{ "fn main() i32 = {\n"
|
||||||
|
" let inner: (i32 | rune) = 42: i32;\n"
|
||||||
|
" let r: (str | i32 | rune) = inner;\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let s: str => return 1;\n"
|
||||||
|
" case let n: i32 => return n;\n"
|
||||||
|
" case let c: rune => return c: i32 + 100;\n"
|
||||||
|
" };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};", 42 },
|
||||||
|
/* Spread variant in tagged-union type — `(...inner | T)`. The
|
||||||
|
* checker flattens the spread's variants into the enclosing
|
||||||
|
* union so `outer` has variants {i32, rune, str}. */
|
||||||
|
{ "type inner = (i32 | rune);\n"
|
||||||
|
"type outer = (...inner | str);\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let r: outer = 42: i32;\n"
|
||||||
|
" match (r) {\n"
|
||||||
|
" case let n: i32 => return n;\n"
|
||||||
|
" case let c: rune => return c: i32;\n"
|
||||||
|
" case let s: str => return 0;\n"
|
||||||
|
" };\n"
|
||||||
|
" return -1;\n"
|
||||||
|
"};", 42 },
|
||||||
/* Plan 9-style sentinel error idiom: `def NAME: error = "lit"`
|
/* Plan 9-style sentinel error idiom: `def NAME: error = "lit"`
|
||||||
* inlines as the (ptr, len) pair at use sites. */
|
* inlines as the (ptr, len) pair at use sites. */
|
||||||
{ "type error = str;\n"
|
{ "type error = str;\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user