ww: cgen trap batch (def-str field, chained-ptr write, scalar+str tuple ABI)
This commit is contained in:
226
cmd/w6c/cgen.c
226
cmd/w6c/cgen.c
@@ -1038,6 +1038,111 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Chained `<expr>.field = v` where <expr> evaluates to a *struct.
|
||||
* cgexpr on the inner expression already returns the pointer;
|
||||
* we then store at (ptr + field.offset). Without this, only the
|
||||
* single-level N_IDENT base above is wired and shapes like
|
||||
* `r.sym.flag = 1` (where r.sym: *T) silently emit no store —
|
||||
* the read still works because the chained-N_DOT read path is
|
||||
* wired below. (This was trap 1 of the cgen miscompilations.) */
|
||||
if (n->lhs && n->lhs->kind == N_DOT && n->lhs->lhs
|
||||
&& n->lhs->lhs->kind != N_IDENT) {
|
||||
Type *bt = n->lhs->lhs->type;
|
||||
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt;
|
||||
if (bu && bu->kind == TY_PTR && bu->sub) {
|
||||
Type *inner = bu->sub;
|
||||
if (inner->kind == TY_NAMED) inner = inner->under;
|
||||
if (inner && inner->kind == TY_STRUCT) {
|
||||
Tfield *f = NULL;
|
||||
for (Tfield *fl = inner->fields; fl; fl = fl->next)
|
||||
if (strcmp(fl->name, n->lhs->str) == 0)
|
||||
{ f = fl; break; }
|
||||
if (f != NULL) {
|
||||
Type *ft = f->type;
|
||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||
? ft->under : ft;
|
||||
int fsz = (int)(ft ? ft->size : 8);
|
||||
int signed_field = ft && (
|
||||
ft->kind == TY_I8 ||
|
||||
ft->kind == TY_I16 ||
|
||||
ft->kind == TY_I32);
|
||||
int store_op = A_MOVQ;
|
||||
if (fsz == 1) store_op = A_MOVB;
|
||||
else if (fsz == 4) store_op = A_MOVL;
|
||||
int foff = (int)f->offset;
|
||||
if (n->op == TK_ASSIGN) {
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
/* str rhs: (AX=ptr, BX=len). Stash
|
||||
* both, then load the struct ptr
|
||||
* into CX and write both halves. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, n->lhs->lhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
areg(D_CX));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_CX, foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_CX, foff + 8));
|
||||
} else {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, n->lhs->lhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
ins2(c, store_op, areg(D_AX),
|
||||
amem(D_BX, foff));
|
||||
}
|
||||
break;
|
||||
}
|
||||
/* compound op: AX=rhs → push; eval ptr → push;
|
||||
* load old field → AX; pop ptr→BX, rhs→CX;
|
||||
* combine; store. Float/str compound on a
|
||||
* chained pointer-field is not wired. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, n->lhs->lhs, locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
int load_op = A_MOVQ;
|
||||
if (fsz == 1) load_op = A_MOVZBQ;
|
||||
else if (fsz == 4)
|
||||
load_op = signed_field ? A_MOVSXD : A_MOVL;
|
||||
ins2(c, load_op, amem(D_AX, foff),
|
||||
areg(D_AX));
|
||||
ins1(c, A_POPQ, areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
switch (n->op) {
|
||||
case TK_PLUSEQ:
|
||||
ins2(c, A_ADDQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
case TK_MINUSEQ:
|
||||
ins2(c, A_SUBQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
case TK_STAREQ:
|
||||
ins2(c, A_IMULQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
case TK_AMPEQ:
|
||||
ins2(c, A_ANDQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
case TK_PIPEEQ:
|
||||
ins2(c, A_ORQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
case TK_CARETEQ:
|
||||
ins2(c, A_XORQ, areg(D_CX), areg(D_AX));
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
ins2(c, store_op, areg(D_AX),
|
||||
amem(D_BX, foff));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* float assignment to a local */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && node_isfloat(n)) {
|
||||
cgexpr(c, n->rhs, locals); /* X0 */
|
||||
@@ -1887,6 +1992,29 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
&& (lenfld || capfld || ptrfld)) {
|
||||
if (n->lhs->kind == N_IDENT) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off == 0) {
|
||||
/* Not a local — could be `def NAME: str
|
||||
* = "lit"`. Sdef-backed strs aren't laid
|
||||
* out in memory; emit .ptr/.len from the
|
||||
* literal directly, mirroring the bare
|
||||
* N_IDENT branch above. Without this we'd
|
||||
* load BP+8 (return-address slot) as the
|
||||
* "len". */
|
||||
for (Sdef *s = sdefs; s; s = s->next) {
|
||||
if (strcmp(s->name, n->lhs->str) != 0)
|
||||
continue;
|
||||
if (ptrfld) {
|
||||
const char *lab = intern_strlit(c,
|
||||
s->bytes, s->len);
|
||||
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ,
|
||||
aimm((long long)s->len),
|
||||
areg(D_AX));
|
||||
}
|
||||
goto dot_done;
|
||||
}
|
||||
}
|
||||
int delta = ptrfld ? 0 : (lenfld ? 8 : 16);
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + delta),
|
||||
areg(D_AX));
|
||||
@@ -1925,6 +2053,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
if (tp != NULL) {
|
||||
int fsz = (int)(tp->type ? tp->type->size : 8);
|
||||
Type *fu = (tp->type && tp->type->kind == TY_NAMED)
|
||||
? tp->type->under : tp->type;
|
||||
int signed_field = tp->type && (
|
||||
tp->type->kind == TY_I8 ||
|
||||
tp->type->kind == TY_I16 ||
|
||||
@@ -1933,6 +2063,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (fsz == 1) op = A_MOVZBQ;
|
||||
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
/* str element: load (ptr, len) into (AX, BX) so chains
|
||||
* like `t.1.len` propagate through the str-rhs
|
||||
* convention. Without this we'd MOVQ 8B and the .len
|
||||
* shuffle (BX→AX) would surface garbage. */
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 8), areg(D_BX));
|
||||
break;
|
||||
}
|
||||
ins2(c, op, amem(D_BP, off + foff), areg(D_AX));
|
||||
}
|
||||
break;
|
||||
@@ -2225,6 +2364,37 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
||||
break;
|
||||
}
|
||||
/* 24B tuple initialiser for `(scalar, str)` / `(str, scalar)`.
|
||||
* Per the AX:DX:CX return convention: AX = scalar elem,
|
||||
* DX = str.ptr, CX = str.len. The slot is laid out positionally
|
||||
* (e0 at +0, e1 at +8 for scalars; str takes 16B starting at
|
||||
* its position), so we route each register to the slot dictated
|
||||
* by the element's type, not by AX/DX position. */
|
||||
if (n->rhs && lu && lu->kind == TY_TUPLE && sz == 24) {
|
||||
Tparam *p0 = lu->params;
|
||||
Tparam *p1 = p0 ? p0->next : NULL;
|
||||
Type *t0 = p0 ? p0->type : NULL;
|
||||
Type *t1 = p1 ? p1->type : NULL;
|
||||
Type *u0 = (t0 && t0->kind == TY_NAMED) ? t0->under : t0;
|
||||
Type *u1 = (t1 && t1->kind == TY_NAMED) ? t1->under : t1;
|
||||
int e0_str = u0 && u0->kind == TY_STR;
|
||||
int e1_str = u1 && u1->kind == TY_STR;
|
||||
if (e0_str ^ e1_str) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
if (e0_str) {
|
||||
/* layout: str@+0 (16B), scalar@+16. */
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 16));
|
||||
} else {
|
||||
/* layout: scalar@+0 (8B), str@+8 (16B). */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Tagged-union initialiser. Two shapes:
|
||||
* 1) rhs already produces a tagged-union value (e.g. a fn
|
||||
* call returning (T | E)). cgexpr leaves AX=tag,
|
||||
@@ -2496,14 +2666,34 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
if (n->lhs && n->lhs->kind == N_TUPLE) {
|
||||
/* small tuples (≤2 elems, ≤8B each) return in AX:DX */
|
||||
/* 2-tuple ABI:
|
||||
* (scalar, scalar) — AX = e0, DX = e1. (16B, fits SysV.)
|
||||
* (scalar, str) — AX = scalar elem,
|
||||
* DX = str.ptr, CX = str.len. (24B custom.)
|
||||
* (str, scalar) — same regs, type-keyed not position-keyed.
|
||||
*
|
||||
* The 24B convention mirrors the existing tagged-union return
|
||||
* (AX:DX:CX); receive sites destructure off the same regs. */
|
||||
Node *e0 = n->lhs->list;
|
||||
Node *e1 = e0 ? e0->next : NULL;
|
||||
if (e1 && e1->next == NULL) {
|
||||
int e0_is_str = node_isstr(e0);
|
||||
int e1_is_str = node_isstr(e1);
|
||||
if (e0_is_str ^ e1_is_str) {
|
||||
Node *strn = e0_is_str ? e0 : e1;
|
||||
Node *scaln = e0_is_str ? e1 : e0;
|
||||
cgexpr(c, scaln, *locals); /* AX = scalar */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, strn, *locals); /* AX=ptr, BX=len */
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_DX));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
} else {
|
||||
cgexpr(c, e1, *locals);
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, e0, *locals);
|
||||
ins1(c, A_POPQ, areg(D_DX));
|
||||
}
|
||||
} else {
|
||||
/* >2-tuple not yet implemented; fall back to first elem */
|
||||
if (e0) cgexpr(c, e0, *locals);
|
||||
@@ -2681,11 +2871,41 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
case N_MLET: {
|
||||
/* eval rhs; first result in AX, second in DX. Bind both. */
|
||||
/* eval rhs; consume the per-type return-ABI registers.
|
||||
* (scalar, scalar) — AX → l0, DX → l1.
|
||||
* (scalar, str) — AX → scalar slot, (DX, CX) → str slot
|
||||
* as (.ptr, .len). Position-agnostic.
|
||||
* Local sizing comes from each l->type so the str slot gets
|
||||
* the full 16B; without this, only DX would land and the
|
||||
* len half (CX) would have nowhere to go. */
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
ins1(c, A_PUSHQ, areg(D_DX)); /* save 2nd while we store 1st */
|
||||
Node *l0 = n->list;
|
||||
Node *l1 = l0 ? l0->next : NULL;
|
||||
Type *t0 = l0 ? l0->type : NULL;
|
||||
Type *t1 = l1 ? l1->type : NULL;
|
||||
Type *u0 = (t0 && t0->kind == TY_NAMED) ? t0->under : t0;
|
||||
Type *u1 = (t1 && t1->kind == TY_NAMED) ? t1->under : t1;
|
||||
int s0_is_str = u0 && u0->kind == TY_STR;
|
||||
int s1_is_str = u1 && u1->kind == TY_STR;
|
||||
if (l0 && l1 && (s0_is_str ^ s1_is_str)) {
|
||||
int sz0 = s0_is_str ? 16 : 8;
|
||||
int sz1 = s1_is_str ? 16 : 8;
|
||||
int off0 = localoff(c, locals, l0->str, sz0, frame);
|
||||
int off1 = localoff(c, locals, l1->str, sz1, frame);
|
||||
if (s0_is_str) {
|
||||
/* l0 is str: ptr=DX, len=CX. l1 is scalar: l1 = AX. */
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off0 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off0 + 8));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off1));
|
||||
} else {
|
||||
/* l0 is scalar; l1 is str. */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off1 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off1 + 8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_DX)); /* save 2nd while we store 1st */
|
||||
if (l0) {
|
||||
int off = localoff(c, locals, l0->str, 8, frame);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off));
|
||||
|
||||
@@ -10,4 +10,4 @@ This directory is **not** a Hare-style stdlib module — it's a compiler-fronten
|
||||
When porting from `cmd/wcc/*.c`:
|
||||
- Keep the same data layout. The selfhost goal is byte-compatible structures so dump/inspect tools work either way.
|
||||
- Read `cmd/wcc/ww.h` first for canonical field names and ordering.
|
||||
- See `selfhost/CLAUDE.md` for the wwstage cgen traps to avoid (two-level field write, `(scalar,str)` tuple return, `def : str`, undersized `amalloc`).
|
||||
- See `selfhost/CLAUDE.md` for the wwstage cgen traps to avoid (undersized `amalloc`).
|
||||
|
||||
@@ -281,9 +281,17 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_DOT, pf, pl, pc);
|
||||
n.lhs = cur;
|
||||
// Hare-style tuple field access: `t.0`, `t.1`. The
|
||||
// numeric literal becomes the field name string so the
|
||||
// cgen tuple-positional path matches `cmd/wcc/parse.c`.
|
||||
if (p.curkind == TK_INT) {
|
||||
n.str = p.curtext;
|
||||
advance(p);
|
||||
} else {
|
||||
let id: str;
|
||||
expectident(p, &id);
|
||||
n.str = id;
|
||||
};
|
||||
cur = n;
|
||||
continue;
|
||||
};
|
||||
|
||||
@@ -12,6 +12,42 @@ fn parseletlocal(p: *parser) *node = {
|
||||
let is_const: i32 = 0;
|
||||
if (p.curkind == TK_CONST) { is_const = 1; };
|
||||
advance(p);
|
||||
|
||||
// Hare-style tuple destructure: `let (a, b) = expr;`.
|
||||
// Types are optional per binding (matches C parser; Hare itself
|
||||
// doesn't allow types here, but cmd/wcc/parse.c does).
|
||||
if (p.curkind == TK_LPAREN) {
|
||||
advance(p);
|
||||
let m: *node = newnode(p.a, N_MLET, pf, pl, pc);
|
||||
let head: *node = nil;
|
||||
let tail: *node = nil;
|
||||
for (true) {
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
let l: *node = newnode(p.a, N_LET, lpf, lpl, lpc);
|
||||
let id: str;
|
||||
expectbindname(p, &id);
|
||||
l.str = id;
|
||||
if (accepttok(p, TK_COLON)) { l.lhs = parsetype(p); };
|
||||
if (head == nil) { head = l; }
|
||||
else { tail.next = l; };
|
||||
tail = l;
|
||||
if (!accepttok(p, TK_COMMA)) { break; };
|
||||
};
|
||||
expecttok(p, TK_RPAREN, "expected ')' in let destructure");
|
||||
expecttok(p, TK_ASSIGN, "expected '=' after let destructure");
|
||||
m.rhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
m.list = head;
|
||||
if (is_const != 0) {
|
||||
m.op = TK_CONST;
|
||||
let lc: *node = head;
|
||||
for (lc != nil) { lc.op = TK_CONST; lc = lc.next; };
|
||||
};
|
||||
return m;
|
||||
};
|
||||
|
||||
let n: *node = newnode(p.a, N_LET, pf, pl, pc);
|
||||
let id: str;
|
||||
expectbindname(p, &id);
|
||||
@@ -19,6 +55,36 @@ fn parseletlocal(p: *parser) *node = {
|
||||
if (accepttok(p, TK_COLON)) {
|
||||
n.lhs = parsetype(p);
|
||||
};
|
||||
// Comma-multi-let: `let n, s = call();` (ww extension over Hare).
|
||||
// Collects (name, type) pairs, then '=' rhs. Each binding gets
|
||||
// its own N_LET; the wrapping N_MLET carries the rhs.
|
||||
if (p.curkind == TK_COMMA) {
|
||||
let m: *node = newnode(p.a, N_MLET, pf, pl, pc);
|
||||
let head: *node = n;
|
||||
let tail: *node = n;
|
||||
for (accepttok(p, TK_COMMA)) {
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
let l: *node = newnode(p.a, N_LET, lpf, lpl, lpc);
|
||||
let id2: str;
|
||||
expectbindname(p, &id2);
|
||||
l.str = id2;
|
||||
if (accepttok(p, TK_COLON)) { l.lhs = parsetype(p); };
|
||||
tail.next = l;
|
||||
tail = l;
|
||||
};
|
||||
expecttok(p, TK_ASSIGN, "expected '=' after let names");
|
||||
m.rhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
m.list = head;
|
||||
if (is_const != 0) {
|
||||
m.op = TK_CONST;
|
||||
let lc: *node = head;
|
||||
for (lc != nil) { lc.op = TK_CONST; lc = lc.next; };
|
||||
};
|
||||
return m;
|
||||
};
|
||||
if (accepttok(p, TK_ASSIGN)) {
|
||||
n.rhs = parseexpr(p);
|
||||
};
|
||||
|
||||
@@ -4,22 +4,28 @@ Identifiers: Plan 9 style. lowercase, words run together (`newbuf`, `tcpsock`, `
|
||||
|
||||
Syntax and idioms: Hare-shaped. Trailing `;`, `=` after fn/type signatures, `export` for visibility, `match`/`?`/`!` for tagged-union errors. Consult `ref/hare/` for canonical signatures and error-handling patterns before inventing your own.
|
||||
|
||||
The C bootstrap's cgen ("wwstage") has four known silent-miscompilation traps. They produce wrong runtime behavior, not compile errors. When porting C → ww here, default to the workarounds:
|
||||
The C bootstrap's cgen has known silent-miscompilation traps. They produce wrong runtime behavior, not compile errors. When porting C → ww here, default to the workarounds:
|
||||
|
||||
1. **Two-level field write through pointer field doesn't stick.**
|
||||
`r.sym.isdyn = 1` where `r.sym: *T` drops the write. Bind the inner pointer to a local first:
|
||||
```
|
||||
let sym: *lsym = r.sym;
|
||||
sym.isdyn = 1;
|
||||
```
|
||||
1. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
|
||||
|
||||
2. **Tuple return `(scalar, str)` corrupts the str half.** Both ptr and len come back garbage. Split into two functions — one returns the scalar, another returns the str. Plain `str` returns are fine.
|
||||
Fixed (no workaround needed):
|
||||
|
||||
3. **`def NAME: str = "...";` is broken.** The `.len` picks up an unrelated accumulator. Wrap the literal in a nullary fn instead:
|
||||
```
|
||||
fn namestr() str = { return "..."; };
|
||||
```
|
||||
|
||||
4. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
|
||||
- `def NAME: str = "..."` field access. `.len`/`.ptr` on an Sdef ident
|
||||
now inline the literal length / strlit address rather than reading
|
||||
BP+8. See cmd/w6c/cgen.c N_DOT.
|
||||
- Two-level field write through pointer field: `r.sym.isdyn = 1` where
|
||||
`r.sym: *T` now stores. The chained-N_DOT N_ASSIGN branch evaluates
|
||||
the inner pointer and stores at *(ptr + field.offset). See
|
||||
cmd/w6c/cgen.c N_ASSIGN and selfhost/cmd/wcc/cgenexpr.ww cgassign.
|
||||
- Tuple return `(scalar, str)` (24B). Returns now follow an AX:DX:CX
|
||||
convention: AX = scalar elem, DX = str.ptr, CX = str.len. Receive
|
||||
sites all destructure off the same regs regardless of positional
|
||||
order:
|
||||
let n, s = call(); // ww comma form
|
||||
let (n, s) = call(); // Hare-style paren form
|
||||
let t: (i64, str) = call(); // positional t.0 / t.1.len
|
||||
Wwstage parser + cgen are byte-identical to C cgen on these shapes.
|
||||
See cmd/w6c/cgen.c N_RETURN/N_LET/N_DOT/N_MLET, lib/ww/parse/{stmt,
|
||||
expr}.ww and selfhost/cmd/wcc/{cgenstmt,cgenexpr,cgenutil,cgendecl}.ww.
|
||||
|
||||
If a port "should work" but the binary is wrong, suspect these first.
|
||||
|
||||
@@ -2251,9 +2251,17 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_DOT, pf, pl, pc);
|
||||
n.lhs = cur;
|
||||
// Hare-style tuple field access: `t.0`, `t.1`. The
|
||||
// numeric literal becomes the field name string so the
|
||||
// cgen tuple-positional path matches `cmd/wcc/parse.c`.
|
||||
if (p.curkind == TK_INT) {
|
||||
n.str = p.curtext;
|
||||
advance(p);
|
||||
} else {
|
||||
let id: str;
|
||||
expectident(p, &id);
|
||||
n.str = id;
|
||||
};
|
||||
cur = n;
|
||||
continue;
|
||||
};
|
||||
@@ -2374,6 +2382,42 @@ fn parseletlocal(p: *parser) *node = {
|
||||
let is_const: i32 = 0;
|
||||
if (p.curkind == TK_CONST) { is_const = 1; };
|
||||
advance(p);
|
||||
|
||||
// Hare-style tuple destructure: `let (a, b) = expr;`.
|
||||
// Types are optional per binding (matches C parser; Hare itself
|
||||
// doesn't allow types here, but cmd/wcc/parse.c does).
|
||||
if (p.curkind == TK_LPAREN) {
|
||||
advance(p);
|
||||
let m: *node = newnode(p.a, N_MLET, pf, pl, pc);
|
||||
let head: *node = nil;
|
||||
let tail: *node = nil;
|
||||
for (true) {
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
let l: *node = newnode(p.a, N_LET, lpf, lpl, lpc);
|
||||
let id: str;
|
||||
expectbindname(p, &id);
|
||||
l.str = id;
|
||||
if (accepttok(p, TK_COLON)) { l.lhs = parsetype(p); };
|
||||
if (head == nil) { head = l; }
|
||||
else { tail.next = l; };
|
||||
tail = l;
|
||||
if (!accepttok(p, TK_COMMA)) { break; };
|
||||
};
|
||||
expecttok(p, TK_RPAREN, "expected ')' in let destructure");
|
||||
expecttok(p, TK_ASSIGN, "expected '=' after let destructure");
|
||||
m.rhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
m.list = head;
|
||||
if (is_const != 0) {
|
||||
m.op = TK_CONST;
|
||||
let lc: *node = head;
|
||||
for (lc != nil) { lc.op = TK_CONST; lc = lc.next; };
|
||||
};
|
||||
return m;
|
||||
};
|
||||
|
||||
let n: *node = newnode(p.a, N_LET, pf, pl, pc);
|
||||
let id: str;
|
||||
expectbindname(p, &id);
|
||||
@@ -2381,6 +2425,36 @@ fn parseletlocal(p: *parser) *node = {
|
||||
if (accepttok(p, TK_COLON)) {
|
||||
n.lhs = parsetype(p);
|
||||
};
|
||||
// Comma-multi-let: `let n, s = call();` (ww extension over Hare).
|
||||
// Collects (name, type) pairs, then '=' rhs. Each binding gets
|
||||
// its own N_LET; the wrapping N_MLET carries the rhs.
|
||||
if (p.curkind == TK_COMMA) {
|
||||
let m: *node = newnode(p.a, N_MLET, pf, pl, pc);
|
||||
let head: *node = n;
|
||||
let tail: *node = n;
|
||||
for (accepttok(p, TK_COMMA)) {
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
let l: *node = newnode(p.a, N_LET, lpf, lpl, lpc);
|
||||
let id2: str;
|
||||
expectbindname(p, &id2);
|
||||
l.str = id2;
|
||||
if (accepttok(p, TK_COLON)) { l.lhs = parsetype(p); };
|
||||
tail.next = l;
|
||||
tail = l;
|
||||
};
|
||||
expecttok(p, TK_ASSIGN, "expected '=' after let names");
|
||||
m.rhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
m.list = head;
|
||||
if (is_const != 0) {
|
||||
m.op = TK_CONST;
|
||||
let lc: *node = head;
|
||||
for (lc != nil) { lc.op = TK_CONST; lc = lc.next; };
|
||||
};
|
||||
return m;
|
||||
};
|
||||
if (accepttok(p, TK_ASSIGN)) {
|
||||
n.rhs = parseexpr(p);
|
||||
};
|
||||
@@ -4368,6 +4442,23 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
|
||||
// primsize — size in bytes of a primitive type name (or 0 if not
|
||||
// recognised as a primitive — the caller falls back to other paths).
|
||||
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
|
||||
// index, or -1 if not all-digits. Used by cgdot to dispatch
|
||||
// `t.0` / `t.1` against an N_TTUPLE local without pulling in strconv.
|
||||
fn fldnumidx(s: str) i32 = {
|
||||
if (s.len == 0) { return -1; };
|
||||
let r: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let b: u8 = s[i];
|
||||
if (b < 48u8) { return -1; };
|
||||
if (b > 57u8) { return -1; };
|
||||
r = r * 10 + ((b - 48u8): i32);
|
||||
i += 1;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
fn primsize(name: str) i32 = {
|
||||
if (streq(name, "u8")) { return 1; };
|
||||
if (streq(name, "i8")) { return 1; };
|
||||
@@ -4441,7 +4532,19 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (k == N_TFN) { return 8; };
|
||||
if (k == N_TCHAN) { return 8; };
|
||||
if (k == N_TSLICE) { return 24; };
|
||||
if (k == N_TTUPLE) { return 16; };
|
||||
if (k == N_TTUPLE) {
|
||||
// Sum element sizes. Mirrors C cgen which uses raw type
|
||||
// sizes; padding to 8 happens inside slotsize for primitives,
|
||||
// so a `(i64, str)` resolves to 8 + 16 = 24 (matches the C
|
||||
// cgen 24B init / positional-access layout).
|
||||
let total: i32 = 0;
|
||||
let p: *node = typn.list;
|
||||
for (p != nil) {
|
||||
total += slotsize(c, p);
|
||||
p = p.next;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
if (k == N_TTAGGED){ return 24; };
|
||||
if (k == N_TNAME) {
|
||||
let nm: str = typn.str;
|
||||
@@ -5100,6 +5203,49 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Hare-style tuple positional access: `t.0`, `t.1`.
|
||||
// Walk the tuple element type list summing slotsize
|
||||
// (matches the (scalar, str) init layout which puts
|
||||
// the scalar in an 8B slot and the str in 16B). For
|
||||
// a str element, load both halves into (AX, BX) so
|
||||
// chains like `t.1.len` propagate correctly.
|
||||
if (lkind == N_TTUPLE) {
|
||||
let idx: i32 = fldnumidx(fld);
|
||||
if (idx >= 0) {
|
||||
let tp: *node = tn.list;
|
||||
let foff: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
if (tp != nil) {
|
||||
if (isstrtyperaw(tp)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 0): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
return;
|
||||
};
|
||||
let sz: i32 = slotsize(c, tp);
|
||||
let op: str = "MOVQ";
|
||||
if (sz == 1) { op = "MOVZBQ"; }
|
||||
else { if (sz == 4) { op = "MOVL"; }; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
// str/slice pseudo-fields .ptr/.len/.cap on a
|
||||
// direct local: load at slot+delta.
|
||||
let delta: i32 = -1;
|
||||
@@ -5730,6 +5876,71 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
||||
// of dots resolving to a *struct. Mirrors the C cgen branch
|
||||
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
||||
// `local.field = v` and `local.fieldptr.field = v` get wired
|
||||
// (the latter through the IDENT-base branch above) — chains
|
||||
// like `s.last.snext = sy` (lib/ww/sym.ww) silently emit no
|
||||
// store. Only plain `=` is wired here; chained compound on a
|
||||
// pointer-field hasn't surfaced.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_DOT) {
|
||||
let base: *node = lhs.lhs;
|
||||
let fld: str = lhs.str;
|
||||
if (base != nil) {
|
||||
if (base.kind == N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, base);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
if (n.op == TK_ASSIGN) {
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
// str rhs: AX=ptr, BX=len.
|
||||
// Stash both, then load
|
||||
// the struct ptr into CX
|
||||
// and write both halves.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Local-ident target — plain `=` and the simple compound
|
||||
// forms (+= -= *= /=); other compounds fall back to
|
||||
// "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path.
|
||||
@@ -5833,6 +6044,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == N_MASSIGN) { cgmassign(c, n); return; };
|
||||
|
||||
if (k == N_MLET) { cgmlet(c, n); return; };
|
||||
|
||||
if (k == N_BREAK) { cgbreak(c, n); return; };
|
||||
if (k == N_CONTINUE) { cgcontinue(c, n); return; };
|
||||
|
||||
@@ -5851,18 +6064,35 @@ fn cgblock(c: *cgen, n: *node) void = {
|
||||
fn cgreturn(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.lhs;
|
||||
if (rhs != nil) {
|
||||
// Tuple return `return a, b;` — pack as (AX=v0, DX=v1).
|
||||
// Matches C cgen: evaluate v1 first (PUSHQ), then v0
|
||||
// into AX, then POPQ DX. End state: AX = v0, DX = v1.
|
||||
// Tuple return `return a, b;`:
|
||||
// (scalar, scalar) — AX = v0, DX = v1.
|
||||
// (scalar, str) / (str, scalar) — AX = scalar elem,
|
||||
// DX = str.ptr, CX = str.len.
|
||||
// 24B convention mirrors the tagged-union return below; receive
|
||||
// sites destructure off the same regs regardless of position.
|
||||
if (rhs.kind == N_TUPLE) {
|
||||
let v: *node = rhs.list;
|
||||
if (v != nil) {
|
||||
let v2: *node = v.next;
|
||||
if (v2 != nil) {
|
||||
let v0_is_str: bool = nodeisstr(c, v);
|
||||
let v1_is_str: bool = nodeisstr(c, v2);
|
||||
if ((v0_is_str || v1_is_str) && !(v0_is_str && v1_is_str)) {
|
||||
let strn: *node = v;
|
||||
let scaln: *node = v2;
|
||||
if (v1_is_str) { strn = v2; scaln = v; };
|
||||
cgexpr(c, scaln);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, strn);
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
} else {
|
||||
cgexpr(c, v2);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, v);
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
};
|
||||
@@ -5984,6 +6214,50 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// 24B tuple init for `let t: (scalar, str) = call()` /
|
||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX return
|
||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len.
|
||||
// Layout is positional, so we route each register to the
|
||||
// slot dictated by element type, not by AX/DX position.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TTUPLE) {
|
||||
let p0: *node = n.lhs.list;
|
||||
let p1: *node = nil;
|
||||
if (p0 != nil) { p1 = p0.next; };
|
||||
let s0_is_str: bool = isstrtyperaw(p0);
|
||||
let s1_is_str: bool = isstrtyperaw(p1);
|
||||
if (p0 != nil) {
|
||||
if (p1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
cgexpr(c, rhs);
|
||||
if (s0_is_str) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T).
|
||||
// Walk elements in declaration order, store each at off + i*esz
|
||||
// using the right width for the element type. Trailing `...`
|
||||
@@ -6256,6 +6530,109 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Multi-let from a tuple-returning call: `let n, s = call();` or
|
||||
// `let (n, s) = call();`. wwstage has no checker, so each binding's
|
||||
// type is taken from its explicit annotation (l.lhs) when present
|
||||
// or inferred from the called fn's return-type tuple element.
|
||||
//
|
||||
// Per the AX:DX:CX return convention (mirrors C cgen N_MLET):
|
||||
// (scalar, scalar) — AX → l0, DX → l1.
|
||||
// (scalar, str) — AX → scalar slot, (DX, CX) → str slot
|
||||
// as (.ptr, .len). Position-agnostic — the
|
||||
// regs are routed by element type, not by AX/DX.
|
||||
fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs == nil) { return; };
|
||||
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (rhs.kind == N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTUPLE) {
|
||||
p0t = rt.list;
|
||||
if (p0t != nil) { p1t = p0t.next; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
let t0: *node = nil;
|
||||
let t1: *node = nil;
|
||||
if (l0 != nil) { t0 = l0.lhs; };
|
||||
if (l1 != nil) { t1 = l1.lhs; };
|
||||
if (t0 == nil) { t0 = p0t; };
|
||||
if (t1 == nil) { t1 = p1t; };
|
||||
|
||||
let s0_is_str: bool = isstrtyperaw(t0);
|
||||
let s1_is_str: bool = isstrtyperaw(t1);
|
||||
|
||||
cgexpr(c, rhs);
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let sz0: i32 = 8;
|
||||
let sz1: i32 = 8;
|
||||
if (s0_is_str) { sz0 = 16; };
|
||||
if (s1_is_str) { sz1 = 16; };
|
||||
let off0: i32 = localadd(c, l0.str, sz0, t0);
|
||||
let off1: i32 = localadd(c, l1.str, sz1, t1);
|
||||
if (s0_is_str) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if (l0 != nil) {
|
||||
let off: i32 = localadd(c, l0.str, 8, t0);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
if (l1 != nil) {
|
||||
let off: i32 = localadd(c, l1.str, 8, t1);
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgbreak(c: *cgen, n: *node) void = {
|
||||
if (c.looptop > 0) {
|
||||
let lbl: str = c.loopendbuf[c.looptop - 1];
|
||||
@@ -6318,6 +6695,54 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
total += sz;
|
||||
};
|
||||
};
|
||||
// Multi-let from a tuple-returning call: each binding's size
|
||||
// comes from its annotated type (l.lhs) when present, else from
|
||||
// the rhs call's return-tuple element type. Marking via
|
||||
// scanseenmark also dedupes the recursive descent into n.list
|
||||
// so each child isn't counted again at the default 8B.
|
||||
if (n.kind == N_MLET) {
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == N_CALL) {
|
||||
let callee: *node = n.rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTUPLE) {
|
||||
p0t = rt.list;
|
||||
if (p0t != nil) { p1t = p0t.next; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let l: *node = n.list;
|
||||
let pt: *node = p0t;
|
||||
let bidx: i32 = 0;
|
||||
for (l != nil) {
|
||||
if (!scanseenmark(c, l.str)) {
|
||||
let t: *node = l.lhs;
|
||||
if (t == nil) {
|
||||
if (bidx == 0) { t = p0t; };
|
||||
if (bidx == 1) { t = p1t; };
|
||||
};
|
||||
let sz: i32 = 8;
|
||||
if (t != nil) { sz = slotsize(c, t); };
|
||||
if (sz < 8) { sz = 8; };
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
};
|
||||
l = l.next;
|
||||
bidx += 1;
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||
// handles a match as an expression with a by-value locals copy,
|
||||
|
||||
@@ -1345,14 +1345,13 @@ export fn resolve(l: *lnk) i32 = {
|
||||
for (r != nil) {
|
||||
if (r.sym != nil) {
|
||||
if (r.sym.defined == 0) {
|
||||
let sym: *lsym = r.sym;
|
||||
if (sym.isdyn == 0) {
|
||||
if (r.sym.isdyn == 0) {
|
||||
let so: *lso = l.sos;
|
||||
for (so != nil) {
|
||||
if (soprovides(so, sym.name) != 0) {
|
||||
sym.isdyn = 1;
|
||||
sym.dynlib = so;
|
||||
sym.pltidx = l.dynn;
|
||||
if (soprovides(so, r.sym.name) != 0) {
|
||||
r.sym.isdyn = 1;
|
||||
r.sym.dynlib = so;
|
||||
r.sym.pltidx = l.dynn;
|
||||
l.dynn += 1;
|
||||
so = nil; // break
|
||||
} else {
|
||||
|
||||
@@ -35,14 +35,13 @@ export fn resolve(l: *lnk) i32 = {
|
||||
for (r != nil) {
|
||||
if (r.sym != nil) {
|
||||
if (r.sym.defined == 0) {
|
||||
let sym: *lsym = r.sym;
|
||||
if (sym.isdyn == 0) {
|
||||
if (r.sym.isdyn == 0) {
|
||||
let so: *lso = l.sos;
|
||||
for (so != nil) {
|
||||
if (soprovides(so, sym.name) != 0) {
|
||||
sym.isdyn = 1;
|
||||
sym.dynlib = so;
|
||||
sym.pltidx = l.dynn;
|
||||
if (soprovides(so, r.sym.name) != 0) {
|
||||
r.sym.isdyn = 1;
|
||||
r.sym.dynlib = so;
|
||||
r.sym.pltidx = l.dynn;
|
||||
l.dynn += 1;
|
||||
so = nil; // break
|
||||
} else {
|
||||
|
||||
@@ -39,6 +39,54 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
total += sz;
|
||||
};
|
||||
};
|
||||
// Multi-let from a tuple-returning call: each binding's size
|
||||
// comes from its annotated type (l.lhs) when present, else from
|
||||
// the rhs call's return-tuple element type. Marking via
|
||||
// scanseenmark also dedupes the recursive descent into n.list
|
||||
// so each child isn't counted again at the default 8B.
|
||||
if (n.kind == N_MLET) {
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == N_CALL) {
|
||||
let callee: *node = n.rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTUPLE) {
|
||||
p0t = rt.list;
|
||||
if (p0t != nil) { p1t = p0t.next; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let l: *node = n.list;
|
||||
let pt: *node = p0t;
|
||||
let bidx: i32 = 0;
|
||||
for (l != nil) {
|
||||
if (!scanseenmark(c, l.str)) {
|
||||
let t: *node = l.lhs;
|
||||
if (t == nil) {
|
||||
if (bidx == 0) { t = p0t; };
|
||||
if (bidx == 1) { t = p1t; };
|
||||
};
|
||||
let sz: i32 = 8;
|
||||
if (t != nil) { sz = slotsize(c, t); };
|
||||
if (sz < 8) { sz = 8; };
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
};
|
||||
l = l.next;
|
||||
bidx += 1;
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||
// handles a match as an expression with a by-value locals copy,
|
||||
|
||||
@@ -419,6 +419,49 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Hare-style tuple positional access: `t.0`, `t.1`.
|
||||
// Walk the tuple element type list summing slotsize
|
||||
// (matches the (scalar, str) init layout which puts
|
||||
// the scalar in an 8B slot and the str in 16B). For
|
||||
// a str element, load both halves into (AX, BX) so
|
||||
// chains like `t.1.len` propagate correctly.
|
||||
if (lkind == N_TTUPLE) {
|
||||
let idx: i32 = fldnumidx(fld);
|
||||
if (idx >= 0) {
|
||||
let tp: *node = tn.list;
|
||||
let foff: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
if (tp != nil) {
|
||||
if (isstrtyperaw(tp)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 0): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
return;
|
||||
};
|
||||
let sz: i32 = slotsize(c, tp);
|
||||
let op: str = "MOVQ";
|
||||
if (sz == 1) { op = "MOVZBQ"; }
|
||||
else { if (sz == 4) { op = "MOVL"; }; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
// str/slice pseudo-fields .ptr/.len/.cap on a
|
||||
// direct local: load at slot+delta.
|
||||
let delta: i32 = -1;
|
||||
@@ -1049,6 +1092,71 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
||||
// of dots resolving to a *struct. Mirrors the C cgen branch
|
||||
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
||||
// `local.field = v` and `local.fieldptr.field = v` get wired
|
||||
// (the latter through the IDENT-base branch above) — chains
|
||||
// like `s.last.snext = sy` (lib/ww/sym.ww) silently emit no
|
||||
// store. Only plain `=` is wired here; chained compound on a
|
||||
// pointer-field hasn't surfaced.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_DOT) {
|
||||
let base: *node = lhs.lhs;
|
||||
let fld: str = lhs.str;
|
||||
if (base != nil) {
|
||||
if (base.kind == N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, base);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
if (n.op == TK_ASSIGN) {
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
// str rhs: AX=ptr, BX=len.
|
||||
// Stash both, then load
|
||||
// the struct ptr into CX
|
||||
// and write both halves.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Local-ident target — plain `=` and the simple compound
|
||||
// forms (+= -= *= /=); other compounds fall back to
|
||||
// "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path.
|
||||
|
||||
@@ -36,6 +36,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == N_MASSIGN) { cgmassign(c, n); return; };
|
||||
|
||||
if (k == N_MLET) { cgmlet(c, n); return; };
|
||||
|
||||
if (k == N_BREAK) { cgbreak(c, n); return; };
|
||||
if (k == N_CONTINUE) { cgcontinue(c, n); return; };
|
||||
|
||||
@@ -54,18 +56,35 @@ fn cgblock(c: *cgen, n: *node) void = {
|
||||
fn cgreturn(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.lhs;
|
||||
if (rhs != nil) {
|
||||
// Tuple return `return a, b;` — pack as (AX=v0, DX=v1).
|
||||
// Matches C cgen: evaluate v1 first (PUSHQ), then v0
|
||||
// into AX, then POPQ DX. End state: AX = v0, DX = v1.
|
||||
// Tuple return `return a, b;`:
|
||||
// (scalar, scalar) — AX = v0, DX = v1.
|
||||
// (scalar, str) / (str, scalar) — AX = scalar elem,
|
||||
// DX = str.ptr, CX = str.len.
|
||||
// 24B convention mirrors the tagged-union return below; receive
|
||||
// sites destructure off the same regs regardless of position.
|
||||
if (rhs.kind == N_TUPLE) {
|
||||
let v: *node = rhs.list;
|
||||
if (v != nil) {
|
||||
let v2: *node = v.next;
|
||||
if (v2 != nil) {
|
||||
let v0_is_str: bool = nodeisstr(c, v);
|
||||
let v1_is_str: bool = nodeisstr(c, v2);
|
||||
if ((v0_is_str || v1_is_str) && !(v0_is_str && v1_is_str)) {
|
||||
let strn: *node = v;
|
||||
let scaln: *node = v2;
|
||||
if (v1_is_str) { strn = v2; scaln = v; };
|
||||
cgexpr(c, scaln);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, strn);
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
} else {
|
||||
cgexpr(c, v2);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, v);
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
};
|
||||
@@ -187,6 +206,50 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// 24B tuple init for `let t: (scalar, str) = call()` /
|
||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX return
|
||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len.
|
||||
// Layout is positional, so we route each register to the
|
||||
// slot dictated by element type, not by AX/DX position.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TTUPLE) {
|
||||
let p0: *node = n.lhs.list;
|
||||
let p1: *node = nil;
|
||||
if (p0 != nil) { p1 = p0.next; };
|
||||
let s0_is_str: bool = isstrtyperaw(p0);
|
||||
let s1_is_str: bool = isstrtyperaw(p1);
|
||||
if (p0 != nil) {
|
||||
if (p1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
cgexpr(c, rhs);
|
||||
if (s0_is_str) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T).
|
||||
// Walk elements in declaration order, store each at off + i*esz
|
||||
// using the right width for the element type. Trailing `...`
|
||||
@@ -459,6 +522,109 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Multi-let from a tuple-returning call: `let n, s = call();` or
|
||||
// `let (n, s) = call();`. wwstage has no checker, so each binding's
|
||||
// type is taken from its explicit annotation (l.lhs) when present
|
||||
// or inferred from the called fn's return-type tuple element.
|
||||
//
|
||||
// Per the AX:DX:CX return convention (mirrors C cgen N_MLET):
|
||||
// (scalar, scalar) — AX → l0, DX → l1.
|
||||
// (scalar, str) — AX → scalar slot, (DX, CX) → str slot
|
||||
// as (.ptr, .len). Position-agnostic — the
|
||||
// regs are routed by element type, not by AX/DX.
|
||||
fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs == nil) { return; };
|
||||
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (rhs.kind == N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTUPLE) {
|
||||
p0t = rt.list;
|
||||
if (p0t != nil) { p1t = p0t.next; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
let t0: *node = nil;
|
||||
let t1: *node = nil;
|
||||
if (l0 != nil) { t0 = l0.lhs; };
|
||||
if (l1 != nil) { t1 = l1.lhs; };
|
||||
if (t0 == nil) { t0 = p0t; };
|
||||
if (t1 == nil) { t1 = p1t; };
|
||||
|
||||
let s0_is_str: bool = isstrtyperaw(t0);
|
||||
let s1_is_str: bool = isstrtyperaw(t1);
|
||||
|
||||
cgexpr(c, rhs);
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let sz0: i32 = 8;
|
||||
let sz1: i32 = 8;
|
||||
if (s0_is_str) { sz0 = 16; };
|
||||
if (s1_is_str) { sz1 = 16; };
|
||||
let off0: i32 = localadd(c, l0.str, sz0, t0);
|
||||
let off1: i32 = localadd(c, l1.str, sz1, t1);
|
||||
if (s0_is_str) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if (l0 != nil) {
|
||||
let off: i32 = localadd(c, l0.str, 8, t0);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
if (l1 != nil) {
|
||||
let off: i32 = localadd(c, l1.str, 8, t1);
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgbreak(c: *cgen, n: *node) void = {
|
||||
if (c.looptop > 0) {
|
||||
let lbl: str = c.loopendbuf[c.looptop - 1];
|
||||
|
||||
@@ -599,6 +599,23 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
|
||||
// primsize — size in bytes of a primitive type name (or 0 if not
|
||||
// recognised as a primitive — the caller falls back to other paths).
|
||||
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
|
||||
// index, or -1 if not all-digits. Used by cgdot to dispatch
|
||||
// `t.0` / `t.1` against an N_TTUPLE local without pulling in strconv.
|
||||
fn fldnumidx(s: str) i32 = {
|
||||
if (s.len == 0) { return -1; };
|
||||
let r: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let b: u8 = s[i];
|
||||
if (b < 48u8) { return -1; };
|
||||
if (b > 57u8) { return -1; };
|
||||
r = r * 10 + ((b - 48u8): i32);
|
||||
i += 1;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
fn primsize(name: str) i32 = {
|
||||
if (streq(name, "u8")) { return 1; };
|
||||
if (streq(name, "i8")) { return 1; };
|
||||
@@ -672,7 +689,19 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (k == N_TFN) { return 8; };
|
||||
if (k == N_TCHAN) { return 8; };
|
||||
if (k == N_TSLICE) { return 24; };
|
||||
if (k == N_TTUPLE) { return 16; };
|
||||
if (k == N_TTUPLE) {
|
||||
// Sum element sizes. Mirrors C cgen which uses raw type
|
||||
// sizes; padding to 8 happens inside slotsize for primitives,
|
||||
// so a `(i64, str)` resolves to 8 + 16 = 24 (matches the C
|
||||
// cgen 24B init / positional-access layout).
|
||||
let total: i32 = 0;
|
||||
let p: *node = typn.list;
|
||||
for (p != nil) {
|
||||
total += slotsize(c, p);
|
||||
p = p.next;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
if (k == N_TTAGGED){ return 24; };
|
||||
if (k == N_TNAME) {
|
||||
let nm: str = typn.str;
|
||||
|
||||
@@ -2251,9 +2251,17 @@ fn parsepostfix(p: *parser, lhs: *node) *node = {
|
||||
advance(p);
|
||||
let n: *node = newnode(p.a, N_DOT, pf, pl, pc);
|
||||
n.lhs = cur;
|
||||
// Hare-style tuple field access: `t.0`, `t.1`. The
|
||||
// numeric literal becomes the field name string so the
|
||||
// cgen tuple-positional path matches `cmd/wcc/parse.c`.
|
||||
if (p.curkind == TK_INT) {
|
||||
n.str = p.curtext;
|
||||
advance(p);
|
||||
} else {
|
||||
let id: str;
|
||||
expectident(p, &id);
|
||||
n.str = id;
|
||||
};
|
||||
cur = n;
|
||||
continue;
|
||||
};
|
||||
@@ -2374,6 +2382,42 @@ fn parseletlocal(p: *parser) *node = {
|
||||
let is_const: i32 = 0;
|
||||
if (p.curkind == TK_CONST) { is_const = 1; };
|
||||
advance(p);
|
||||
|
||||
// Hare-style tuple destructure: `let (a, b) = expr;`.
|
||||
// Types are optional per binding (matches C parser; Hare itself
|
||||
// doesn't allow types here, but cmd/wcc/parse.c does).
|
||||
if (p.curkind == TK_LPAREN) {
|
||||
advance(p);
|
||||
let m: *node = newnode(p.a, N_MLET, pf, pl, pc);
|
||||
let head: *node = nil;
|
||||
let tail: *node = nil;
|
||||
for (true) {
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
let l: *node = newnode(p.a, N_LET, lpf, lpl, lpc);
|
||||
let id: str;
|
||||
expectbindname(p, &id);
|
||||
l.str = id;
|
||||
if (accepttok(p, TK_COLON)) { l.lhs = parsetype(p); };
|
||||
if (head == nil) { head = l; }
|
||||
else { tail.next = l; };
|
||||
tail = l;
|
||||
if (!accepttok(p, TK_COMMA)) { break; };
|
||||
};
|
||||
expecttok(p, TK_RPAREN, "expected ')' in let destructure");
|
||||
expecttok(p, TK_ASSIGN, "expected '=' after let destructure");
|
||||
m.rhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
m.list = head;
|
||||
if (is_const != 0) {
|
||||
m.op = TK_CONST;
|
||||
let lc: *node = head;
|
||||
for (lc != nil) { lc.op = TK_CONST; lc = lc.next; };
|
||||
};
|
||||
return m;
|
||||
};
|
||||
|
||||
let n: *node = newnode(p.a, N_LET, pf, pl, pc);
|
||||
let id: str;
|
||||
expectbindname(p, &id);
|
||||
@@ -2381,6 +2425,36 @@ fn parseletlocal(p: *parser) *node = {
|
||||
if (accepttok(p, TK_COLON)) {
|
||||
n.lhs = parsetype(p);
|
||||
};
|
||||
// Comma-multi-let: `let n, s = call();` (ww extension over Hare).
|
||||
// Collects (name, type) pairs, then '=' rhs. Each binding gets
|
||||
// its own N_LET; the wrapping N_MLET carries the rhs.
|
||||
if (p.curkind == TK_COMMA) {
|
||||
let m: *node = newnode(p.a, N_MLET, pf, pl, pc);
|
||||
let head: *node = n;
|
||||
let tail: *node = n;
|
||||
for (accepttok(p, TK_COMMA)) {
|
||||
let lpf: str = p.curfile;
|
||||
let lpl: i32 = p.curline;
|
||||
let lpc: i32 = p.curcol;
|
||||
let l: *node = newnode(p.a, N_LET, lpf, lpl, lpc);
|
||||
let id2: str;
|
||||
expectbindname(p, &id2);
|
||||
l.str = id2;
|
||||
if (accepttok(p, TK_COLON)) { l.lhs = parsetype(p); };
|
||||
tail.next = l;
|
||||
tail = l;
|
||||
};
|
||||
expecttok(p, TK_ASSIGN, "expected '=' after let names");
|
||||
m.rhs = parseexpr(p);
|
||||
expecttok(p, TK_SEMI, "expected ';' after let");
|
||||
m.list = head;
|
||||
if (is_const != 0) {
|
||||
m.op = TK_CONST;
|
||||
let lc: *node = head;
|
||||
for (lc != nil) { lc.op = TK_CONST; lc = lc.next; };
|
||||
};
|
||||
return m;
|
||||
};
|
||||
if (accepttok(p, TK_ASSIGN)) {
|
||||
n.rhs = parseexpr(p);
|
||||
};
|
||||
@@ -4368,6 +4442,23 @@ fn structlookup(c: *cgen, name: str) *structinfo = {
|
||||
|
||||
// primsize — size in bytes of a primitive type name (or 0 if not
|
||||
// recognised as a primitive — the caller falls back to other paths).
|
||||
// fldnumidx — parse a tuple field name like "0" / "1" / "12" into an
|
||||
// index, or -1 if not all-digits. Used by cgdot to dispatch
|
||||
// `t.0` / `t.1` against an N_TTUPLE local without pulling in strconv.
|
||||
fn fldnumidx(s: str) i32 = {
|
||||
if (s.len == 0) { return -1; };
|
||||
let r: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < s.len) {
|
||||
let b: u8 = s[i];
|
||||
if (b < 48u8) { return -1; };
|
||||
if (b > 57u8) { return -1; };
|
||||
r = r * 10 + ((b - 48u8): i32);
|
||||
i += 1;
|
||||
};
|
||||
return r;
|
||||
};
|
||||
|
||||
fn primsize(name: str) i32 = {
|
||||
if (streq(name, "u8")) { return 1; };
|
||||
if (streq(name, "i8")) { return 1; };
|
||||
@@ -4441,7 +4532,19 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (k == N_TFN) { return 8; };
|
||||
if (k == N_TCHAN) { return 8; };
|
||||
if (k == N_TSLICE) { return 24; };
|
||||
if (k == N_TTUPLE) { return 16; };
|
||||
if (k == N_TTUPLE) {
|
||||
// Sum element sizes. Mirrors C cgen which uses raw type
|
||||
// sizes; padding to 8 happens inside slotsize for primitives,
|
||||
// so a `(i64, str)` resolves to 8 + 16 = 24 (matches the C
|
||||
// cgen 24B init / positional-access layout).
|
||||
let total: i32 = 0;
|
||||
let p: *node = typn.list;
|
||||
for (p != nil) {
|
||||
total += slotsize(c, p);
|
||||
p = p.next;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
if (k == N_TTAGGED){ return 24; };
|
||||
if (k == N_TNAME) {
|
||||
let nm: str = typn.str;
|
||||
@@ -5100,6 +5203,49 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Hare-style tuple positional access: `t.0`, `t.1`.
|
||||
// Walk the tuple element type list summing slotsize
|
||||
// (matches the (scalar, str) init layout which puts
|
||||
// the scalar in an 8B slot and the str in 16B). For
|
||||
// a str element, load both halves into (AX, BX) so
|
||||
// chains like `t.1.len` propagate correctly.
|
||||
if (lkind == N_TTUPLE) {
|
||||
let idx: i32 = fldnumidx(fld);
|
||||
if (idx >= 0) {
|
||||
let tp: *node = tn.list;
|
||||
let foff: i32 = 0;
|
||||
let i: i32 = 0;
|
||||
for (i < idx) {
|
||||
if (tp == nil) { i = idx; }
|
||||
else {
|
||||
foff += slotsize(c, tp);
|
||||
tp = tp.next;
|
||||
i += 1;
|
||||
};
|
||||
};
|
||||
if (tp != nil) {
|
||||
if (isstrtyperaw(tp)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 0): i64);
|
||||
emitline("(BP), AX\n");
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 8): i64);
|
||||
emitline("(BP), BX\n");
|
||||
return;
|
||||
};
|
||||
let sz: i32 = slotsize(c, tp);
|
||||
let op: str = "MOVQ";
|
||||
if (sz == 1) { op = "MOVZBQ"; }
|
||||
else { if (sz == 4) { op = "MOVL"; }; };
|
||||
emitline("\t");
|
||||
emitline(op);
|
||||
emitline("\t");
|
||||
emitoff((lc.off + foff): i64);
|
||||
emitline("(BP), AX\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
// str/slice pseudo-fields .ptr/.len/.cap on a
|
||||
// direct local: load at slot+delta.
|
||||
let delta: i32 = -1;
|
||||
@@ -5730,6 +5876,71 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
||||
// of dots resolving to a *struct. Mirrors the C cgen branch
|
||||
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
||||
// `local.field = v` and `local.fieldptr.field = v` get wired
|
||||
// (the latter through the IDENT-base branch above) — chains
|
||||
// like `s.last.snext = sy` (lib/ww/sym.ww) silently emit no
|
||||
// store. Only plain `=` is wired here; chained compound on a
|
||||
// pointer-field hasn't surfaced.
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == N_DOT) {
|
||||
let base: *node = lhs.lhs;
|
||||
let fld: str = lhs.str;
|
||||
if (base != nil) {
|
||||
if (base.kind == N_DOT) {
|
||||
let innert: *node = dotinnerstructptr(c, base);
|
||||
if (innert != nil) {
|
||||
let sname: str = innert.str;
|
||||
let si: *structinfo = structlookup(c, sname);
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
if (streq(fi.fname, fld)) {
|
||||
if (n.op == TK_ASSIGN) {
|
||||
if (isstrtype(c, fi.tnode)) {
|
||||
// str rhs: AX=ptr, BX=len.
|
||||
// Stash both, then load
|
||||
// the struct ptr into CX
|
||||
// and write both halves.
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, CX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "CX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((fi.foff + 8): i64, "CX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
let sop: str = fieldstoreop(fi);
|
||||
emitline("\t");
|
||||
emitline(sop);
|
||||
emitline("\tAX, ");
|
||||
emitdispreg(fi.foff: i64, "BX");
|
||||
emitline("\n");
|
||||
return;
|
||||
};
|
||||
};
|
||||
fi = fi.finext;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Local-ident target — plain `=` and the simple compound
|
||||
// forms (+= -= *= /=); other compounds fall back to
|
||||
// "evaluate rhs, replace". Mirrors C cgen's IDENT-assign path.
|
||||
@@ -5833,6 +6044,8 @@ fn cgstmt(c: *cgen, n: *node) void = {
|
||||
|
||||
if (k == N_MASSIGN) { cgmassign(c, n); return; };
|
||||
|
||||
if (k == N_MLET) { cgmlet(c, n); return; };
|
||||
|
||||
if (k == N_BREAK) { cgbreak(c, n); return; };
|
||||
if (k == N_CONTINUE) { cgcontinue(c, n); return; };
|
||||
|
||||
@@ -5851,18 +6064,35 @@ fn cgblock(c: *cgen, n: *node) void = {
|
||||
fn cgreturn(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.lhs;
|
||||
if (rhs != nil) {
|
||||
// Tuple return `return a, b;` — pack as (AX=v0, DX=v1).
|
||||
// Matches C cgen: evaluate v1 first (PUSHQ), then v0
|
||||
// into AX, then POPQ DX. End state: AX = v0, DX = v1.
|
||||
// Tuple return `return a, b;`:
|
||||
// (scalar, scalar) — AX = v0, DX = v1.
|
||||
// (scalar, str) / (str, scalar) — AX = scalar elem,
|
||||
// DX = str.ptr, CX = str.len.
|
||||
// 24B convention mirrors the tagged-union return below; receive
|
||||
// sites destructure off the same regs regardless of position.
|
||||
if (rhs.kind == N_TUPLE) {
|
||||
let v: *node = rhs.list;
|
||||
if (v != nil) {
|
||||
let v2: *node = v.next;
|
||||
if (v2 != nil) {
|
||||
let v0_is_str: bool = nodeisstr(c, v);
|
||||
let v1_is_str: bool = nodeisstr(c, v2);
|
||||
if ((v0_is_str || v1_is_str) && !(v0_is_str && v1_is_str)) {
|
||||
let strn: *node = v;
|
||||
let scaln: *node = v2;
|
||||
if (v1_is_str) { strn = v2; scaln = v; };
|
||||
cgexpr(c, scaln);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, strn);
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
} else {
|
||||
cgexpr(c, v2);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, v);
|
||||
emitline("\tPOPQ\tDX\n");
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, v);
|
||||
};
|
||||
@@ -5984,6 +6214,50 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// 24B tuple init for `let t: (scalar, str) = call()` /
|
||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX return
|
||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len.
|
||||
// Layout is positional, so we route each register to the
|
||||
// slot dictated by element type, not by AX/DX position.
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == N_TTUPLE) {
|
||||
let p0: *node = n.lhs.list;
|
||||
let p1: *node = nil;
|
||||
if (p0 != nil) { p1 = p0.next; };
|
||||
let s0_is_str: bool = isstrtyperaw(p0);
|
||||
let s1_is_str: bool = isstrtyperaw(p1);
|
||||
if (p0 != nil) {
|
||||
if (p1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
cgexpr(c, rhs);
|
||||
if (s0_is_str) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Array literal init: `let xs: [N]T = [a, b, c];` (or [_]T).
|
||||
// Walk elements in declaration order, store each at off + i*esz
|
||||
// using the right width for the element type. Trailing `...`
|
||||
@@ -6256,6 +6530,109 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
|
||||
// Multi-let from a tuple-returning call: `let n, s = call();` or
|
||||
// `let (n, s) = call();`. wwstage has no checker, so each binding's
|
||||
// type is taken from its explicit annotation (l.lhs) when present
|
||||
// or inferred from the called fn's return-type tuple element.
|
||||
//
|
||||
// Per the AX:DX:CX return convention (mirrors C cgen N_MLET):
|
||||
// (scalar, scalar) — AX → l0, DX → l1.
|
||||
// (scalar, str) — AX → scalar slot, (DX, CX) → str slot
|
||||
// as (.ptr, .len). Position-agnostic — the
|
||||
// regs are routed by element type, not by AX/DX.
|
||||
fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs == nil) { return; };
|
||||
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (rhs.kind == N_CALL) {
|
||||
let callee: *node = rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTUPLE) {
|
||||
p0t = rt.list;
|
||||
if (p0t != nil) { p1t = p0t.next; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
let l0: *node = n.list;
|
||||
let l1: *node = nil;
|
||||
if (l0 != nil) { l1 = l0.next; };
|
||||
|
||||
let t0: *node = nil;
|
||||
let t1: *node = nil;
|
||||
if (l0 != nil) { t0 = l0.lhs; };
|
||||
if (l1 != nil) { t1 = l1.lhs; };
|
||||
if (t0 == nil) { t0 = p0t; };
|
||||
if (t1 == nil) { t1 = p1t; };
|
||||
|
||||
let s0_is_str: bool = isstrtyperaw(t0);
|
||||
let s1_is_str: bool = isstrtyperaw(t1);
|
||||
|
||||
cgexpr(c, rhs);
|
||||
|
||||
if (l0 != nil) {
|
||||
if (l1 != nil) {
|
||||
if (s0_is_str != s1_is_str) {
|
||||
let sz0: i32 = 8;
|
||||
let sz1: i32 = 8;
|
||||
if (s0_is_str) { sz0 = 16; };
|
||||
if (s1_is_str) { sz1 = 16; };
|
||||
let off0: i32 = localadd(c, l0.str, sz0, t0);
|
||||
let off1: i32 = localadd(c, l1.str, sz1, t1);
|
||||
if (s0_is_str) {
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
if (l0 != nil) {
|
||||
let off: i32 = localadd(c, l0.str, 8, t0);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
if (l1 != nil) {
|
||||
let off: i32 = localadd(c, l1.str, 8, t1);
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
|
||||
fn cgbreak(c: *cgen, n: *node) void = {
|
||||
if (c.looptop > 0) {
|
||||
let lbl: str = c.loopendbuf[c.looptop - 1];
|
||||
@@ -6318,6 +6695,54 @@ fn scanlocals(c: *cgen, n: *node) i32 = {
|
||||
total += sz;
|
||||
};
|
||||
};
|
||||
// Multi-let from a tuple-returning call: each binding's size
|
||||
// comes from its annotated type (l.lhs) when present, else from
|
||||
// the rhs call's return-tuple element type. Marking via
|
||||
// scanseenmark also dedupes the recursive descent into n.list
|
||||
// so each child isn't counted again at the default 8B.
|
||||
if (n.kind == N_MLET) {
|
||||
let p0t: *node = nil;
|
||||
let p1t: *node = nil;
|
||||
if (n.rhs != nil) {
|
||||
if (n.rhs.kind == N_CALL) {
|
||||
let callee: *node = n.rhs.lhs;
|
||||
if (callee != nil) {
|
||||
let cnm: str;
|
||||
cnm.ptr = nil; cnm.len = 0;
|
||||
if (callee.kind == N_IDENT) { cnm = callee.str; };
|
||||
if (callee.kind == N_DOT) { cnm = callee.str; };
|
||||
if (cnm.len > 0) {
|
||||
let rt: *node = fnretlookup(c, cnm);
|
||||
if (rt != nil) {
|
||||
if (rt.kind == N_TTUPLE) {
|
||||
p0t = rt.list;
|
||||
if (p0t != nil) { p1t = p0t.next; };
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let l: *node = n.list;
|
||||
let pt: *node = p0t;
|
||||
let bidx: i32 = 0;
|
||||
for (l != nil) {
|
||||
if (!scanseenmark(c, l.str)) {
|
||||
let t: *node = l.lhs;
|
||||
if (t == nil) {
|
||||
if (bidx == 0) { t = p0t; };
|
||||
if (bidx == 1) { t = p1t; };
|
||||
};
|
||||
let sz: i32 = 8;
|
||||
if (t != nil) { sz = slotsize(c, t); };
|
||||
if (sz < 8) { sz = 8; };
|
||||
if ((sz & 7) != 0) { sz = (sz + 7) & ~7; };
|
||||
total += sz;
|
||||
};
|
||||
l = l.next;
|
||||
bidx += 1;
|
||||
};
|
||||
};
|
||||
// Match-arm binding (`case let v: T => ...`) gets a slot too.
|
||||
// Crucially we do NOT dedup these against c.locals: C cgen
|
||||
// handles a match as an expression with a by-value locals copy,
|
||||
|
||||
@@ -806,6 +806,69 @@ static const struct row rows[] = {
|
||||
" };\n"
|
||||
" return acc;\n"
|
||||
"};", 16 }, /* 3 + 3 + len(\"no newline\")=10 */
|
||||
/* `(scalar, str)` tuple return: AX:DX:CX convention extends the
|
||||
* tagged-union ABI. AX = scalar, DX = str.ptr, CX = str.len.
|
||||
* Receive sites destructure off the same regs regardless of
|
||||
* positional order. Without the fix, len was lost (only AX:DX
|
||||
* returned), every receive shape gave garbage. */
|
||||
{ "fn split() (i64, str) = { return 42, \"hello\"; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let n, s = split();\n"
|
||||
" return (n: i32) + (s.len: i32);\n"
|
||||
"};", 47 },
|
||||
{ "fn split() (str, i64) = { return \"hello\", 42; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let s, n = split();\n"
|
||||
" return (n: i32) + (s.len: i32);\n"
|
||||
"};", 47 },
|
||||
{ "fn split() (i64, str) = { return 42, \"hello\"; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let t: (i64, str) = split();\n"
|
||||
" return (t.0: i32) + (t.1.len: i32);\n"
|
||||
"};", 47 },
|
||||
/* Hare-style paren tuple-destructure with str element */
|
||||
{ "fn split() (i64, str) = { return 42, \"hello\"; };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let (n, s) = split();\n"
|
||||
" return (n: i32) + (s.len: i32);\n"
|
||||
"};", 47 },
|
||||
/* Chained field write through a pointer field: `r.sym.flag = v`
|
||||
* where `r.sym: *T`. The cgen must evaluate the inner pointer,
|
||||
* then store at *(ptr + field.offset). Without the fix, the
|
||||
* single-level N_IDENT-base path doesn't fire (base is itself
|
||||
* an N_DOT) and the assignment silently emits no instructions.
|
||||
* Compound op + 1-byte field + 3-level chain all exercised. */
|
||||
{ "type inner = struct { tag: u8, pad: u8, flag: i32 };\n"
|
||||
"type outer = struct { sym: *inner };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let i: inner = inner { tag = 0u8, pad = 0u8, flag = 10 };\n"
|
||||
" let r: outer = outer { sym = &i };\n"
|
||||
" r.sym.flag += 32;\n"
|
||||
" r.sym.tag = 5u8;\n"
|
||||
" return r.sym.flag + (r.sym.tag: i32);\n"
|
||||
"};", 47 },
|
||||
{ "type leaf = struct { v: i32 };\n"
|
||||
"type mid = struct { l: *leaf };\n"
|
||||
"type top = struct { m: *mid };\n"
|
||||
"fn main() i32 = {\n"
|
||||
" let lf: leaf = leaf { v = 0 };\n"
|
||||
" let md: mid = mid { l = &lf };\n"
|
||||
" let tp: top = top { m = &md };\n"
|
||||
" tp.m.l.v = 99;\n"
|
||||
" return tp.m.l.v;\n"
|
||||
"};", 99 },
|
||||
/* `def NAME: str = \"lit\"` field access. The Sdef has no stack
|
||||
* slot, so .len/.ptr must inline the literal length / strlit
|
||||
* address; without the fix, .len reads BP+8 (return-address slot)
|
||||
* as garbage. */
|
||||
{ "def MSG: str = \"hello world\";\n"
|
||||
"fn main() i32 = { return MSG.len: i32; };", 11 },
|
||||
{ "use os;\n"
|
||||
"def GREETING: str = \"hi\\n\";\n"
|
||||
"fn main() i32 = {\n"
|
||||
" os.write(1, GREETING.ptr, GREETING.len: u64);\n"
|
||||
" return GREETING.len: i32;\n"
|
||||
"};", 3 },
|
||||
{ NULL, 0 }
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user