ww: cgen trap batch (def-str field, chained-ptr write, scalar+str tuple ABI)

This commit is contained in:
2026-05-11 20:49:21 +09:00
parent 2ac15e4e99
commit c5f30f2fce
14 changed files with 1629 additions and 67 deletions

View File

@@ -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) {
cgexpr(c, e1, *locals);
ins1(c, A_PUSHQ, areg(D_AX));
cgexpr(c, e0, *locals);
ins1(c, A_POPQ, areg(D_DX));
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));