ww: cgen trap batch (def-str field, chained-ptr write, scalar+str tuple ABI)
This commit is contained in:
234
cmd/w6c/cgen.c
234
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) {
|
||||
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));
|
||||
|
||||
Reference in New Issue
Block a user