diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 614dbe58..d2b9127a 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -2144,6 +2144,57 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + /* `*p OP= v` — compound assign through a pointer deref. The + * plain-assign branch above only fires for TK_ASSIGN; without + * this, compound ops fall through the switch and emit nothing + * (silent no-op). Evaluate rhs → save, evaluate ptr → BX, load + * *BX (sized + extended), combine with rhs in CX, sized store + * back. Scalar deref targets only — float and aggregate deref + * compounds (rare) still fall through. */ + if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR + && n->op != TK_ASSIGN) { + Type *pt = n->lhs->lhs ? n->lhs->lhs->type : NULL; + Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt; + Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL; + if (vt && vt->kind == TY_NAMED) vt = vt->under; + int sz = vt ? (int)vt->size : 8; + int load_op = A_MOVQ, store_op = A_MOVQ; + int handled = 1; + if (sz == 8) { + load_op = A_MOVQ; store_op = A_MOVQ; + } else if (sz == 4) { + load_op = A_MOVSXD; store_op = A_MOVL; + } else if (sz == 1) { + load_op = A_MOVZBQ; store_op = A_MOVB; + } else { + handled = 0; + } + if (handled) { + cgexpr(c, n->rhs, locals); /* AX = rhs */ + ins1(c, A_PUSHQ, areg(D_AX)); + cgexpr(c, n->lhs->lhs, locals); /* AX = ptr */ + ins2(c, A_MOVQ, areg(D_AX), areg(D_BX)); + ins2(c, load_op, amem(D_BX, 0), areg(D_AX)); + 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; + case TK_LSHIFTEQ: ins2(c, A_SHLQ, areg(D_CX), areg(D_AX)); break; + case TK_RSHIFTEQ: ins2(c, A_SHRQ, areg(D_CX), areg(D_AX)); break; + default: + /* TK_SLASHEQ / unknown: store rhs only, + * matching the IDENT-compound fallback. */ + ins2(c, A_MOVQ, areg(D_CX), areg(D_AX)); + break; + } + ins2(c, store_op, areg(D_AX), amem(D_BX, 0)); + break; + } + } /* Plain `name = strexpr;` for a str-typed local. cgexpr leaves * (AX=ptr, BX=len); store both halves at off+0 and off+8. * Mirrors the let-init shape so reassignment doesn't truncate. diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 334362a9..b8e8f503 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10268,6 +10268,76 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; }; + // `*p OP= v` — compound assign through a pointer deref. The + // plain-assign branch above only fires for TK_ASSIGN; without + // this, compound ops fall through and emit nothing (silent + // no-op — exactly the trap that broke fmt.println). Mirror of + // cmd/w6c/cgen.c's N_UN/TK_STAR compound branch. + if (lhs != nil) { + if (lhs.kind == nkind.N_UN) { + if (lhs.op == tkind.TK_STAR) { + if (n.op != tkind.TK_ASSIGN) { + let inner: *node = lhs.lhs; + let loadop: str = "MOVQ"; + let storeop: str = "MOVQ"; + if (inner != nil) { + if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + if (tn.kind == nkind.N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { + if (pe.kind == nkind.N_TNAME) { + let ps: i32 = primsize(pe.str); + if (ps == 1) { + loadop = "MOVZBQ"; + storeop = "MOVB"; + } else { if (ps == 4) { + if (typenameissigned(pe.str)) { + loadop = "MOVSXD"; + } else { + loadop = "MOVL"; + }; + storeop = "MOVL"; + }; }; + }; + }; + }; + }; + }; + }; + }; + cgexpr(c, n.rhs); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, inner); + emitline("\tMOVQ\tAX, BX\n"); + emitline("\t"); + emitline(loadop); + emitline("\t(BX), AX\n"); + emitline("\tPOPQ\tCX\n"); + let combineop: str = "MOVQ"; + if (n.op == tkind.TK_PLUSEQ) { combineop = "ADDQ"; } + else { if (n.op == tkind.TK_MINUSEQ) { combineop = "SUBQ"; } + else { if (n.op == tkind.TK_STAREQ) { combineop = "IMULQ"; } + else { if (n.op == tkind.TK_AMPEQ) { combineop = "ANDQ"; } + else { if (n.op == tkind.TK_PIPEEQ) { combineop = "ORQ"; } + else { if (n.op == tkind.TK_CARETEQ) { combineop = "XORQ"; } + else { if (n.op == tkind.TK_LSHIFTEQ) { combineop = "SHLQ"; } + else { if (n.op == tkind.TK_RSHIFTEQ) { combineop = "SHRQ"; }; + }; }; }; }; }; }; }; + emitline("\t"); + emitline(combineop); + emitline("\tCX, AX\n"); + emitline("\t"); + emitline(storeop); + emitline("\tAX, (BX)\n"); + return; + }; + }; + }; + }; // Array/slice/ptr index store: `arr[i] = v;`. Element size // from base.tnode picks MOVB vs MOVQ. if (lhs != nil) { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 3e2eaa09..b7cf7eb8 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -2574,6 +2574,76 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; }; + // `*p OP= v` — compound assign through a pointer deref. The + // plain-assign branch above only fires for TK_ASSIGN; without + // this, compound ops fall through and emit nothing (silent + // no-op — exactly the trap that broke fmt.println). Mirror of + // cmd/w6c/cgen.c's N_UN/TK_STAR compound branch. + if (lhs != nil) { + if (lhs.kind == nkind.N_UN) { + if (lhs.op == tkind.TK_STAR) { + if (n.op != tkind.TK_ASSIGN) { + let inner: *node = lhs.lhs; + let loadop: str = "MOVQ"; + let storeop: str = "MOVQ"; + if (inner != nil) { + if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + if (tn.kind == nkind.N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { + if (pe.kind == nkind.N_TNAME) { + let ps: i32 = primsize(pe.str); + if (ps == 1) { + loadop = "MOVZBQ"; + storeop = "MOVB"; + } else { if (ps == 4) { + if (typenameissigned(pe.str)) { + loadop = "MOVSXD"; + } else { + loadop = "MOVL"; + }; + storeop = "MOVL"; + }; }; + }; + }; + }; + }; + }; + }; + }; + cgexpr(c, n.rhs); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, inner); + emitline("\tMOVQ\tAX, BX\n"); + emitline("\t"); + emitline(loadop); + emitline("\t(BX), AX\n"); + emitline("\tPOPQ\tCX\n"); + let combineop: str = "MOVQ"; + if (n.op == tkind.TK_PLUSEQ) { combineop = "ADDQ"; } + else { if (n.op == tkind.TK_MINUSEQ) { combineop = "SUBQ"; } + else { if (n.op == tkind.TK_STAREQ) { combineop = "IMULQ"; } + else { if (n.op == tkind.TK_AMPEQ) { combineop = "ANDQ"; } + else { if (n.op == tkind.TK_PIPEEQ) { combineop = "ORQ"; } + else { if (n.op == tkind.TK_CARETEQ) { combineop = "XORQ"; } + else { if (n.op == tkind.TK_LSHIFTEQ) { combineop = "SHLQ"; } + else { if (n.op == tkind.TK_RSHIFTEQ) { combineop = "SHRQ"; }; + }; }; }; }; }; }; }; + emitline("\t"); + emitline(combineop); + emitline("\tCX, AX\n"); + emitline("\t"); + emitline(storeop); + emitline("\tAX, (BX)\n"); + return; + }; + }; + }; + }; // Array/slice/ptr index store: `arr[i] = v;`. Element size // from base.tnode picks MOVB vs MOVQ. if (lhs != nil) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 465bbabc..9aa2b76c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10268,6 +10268,76 @@ fn cgassign(c: *cgen, n: *node) void = { }; }; }; + // `*p OP= v` — compound assign through a pointer deref. The + // plain-assign branch above only fires for TK_ASSIGN; without + // this, compound ops fall through and emit nothing (silent + // no-op — exactly the trap that broke fmt.println). Mirror of + // cmd/w6c/cgen.c's N_UN/TK_STAR compound branch. + if (lhs != nil) { + if (lhs.kind == nkind.N_UN) { + if (lhs.op == tkind.TK_STAR) { + if (n.op != tkind.TK_ASSIGN) { + let inner: *node = lhs.lhs; + let loadop: str = "MOVQ"; + let storeop: str = "MOVQ"; + if (inner != nil) { + if (inner.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, inner.str); + if (lc != nil) { + let tn: *node = lc.tnode; + if (tn != nil) { + if (tn.kind == nkind.N_TPTR) { + let pe: *node = tn.lhs; + if (pe != nil) { + if (pe.kind == nkind.N_TNAME) { + let ps: i32 = primsize(pe.str); + if (ps == 1) { + loadop = "MOVZBQ"; + storeop = "MOVB"; + } else { if (ps == 4) { + if (typenameissigned(pe.str)) { + loadop = "MOVSXD"; + } else { + loadop = "MOVL"; + }; + storeop = "MOVL"; + }; }; + }; + }; + }; + }; + }; + }; + }; + cgexpr(c, n.rhs); + emitline("\tPUSHQ\tAX\n"); + cgexpr(c, inner); + emitline("\tMOVQ\tAX, BX\n"); + emitline("\t"); + emitline(loadop); + emitline("\t(BX), AX\n"); + emitline("\tPOPQ\tCX\n"); + let combineop: str = "MOVQ"; + if (n.op == tkind.TK_PLUSEQ) { combineop = "ADDQ"; } + else { if (n.op == tkind.TK_MINUSEQ) { combineop = "SUBQ"; } + else { if (n.op == tkind.TK_STAREQ) { combineop = "IMULQ"; } + else { if (n.op == tkind.TK_AMPEQ) { combineop = "ANDQ"; } + else { if (n.op == tkind.TK_PIPEEQ) { combineop = "ORQ"; } + else { if (n.op == tkind.TK_CARETEQ) { combineop = "XORQ"; } + else { if (n.op == tkind.TK_LSHIFTEQ) { combineop = "SHLQ"; } + else { if (n.op == tkind.TK_RSHIFTEQ) { combineop = "SHRQ"; }; + }; }; }; }; }; }; }; + emitline("\t"); + emitline(combineop); + emitline("\tCX, AX\n"); + emitline("\t"); + emitline(storeop); + emitline("\tAX, (BX)\n"); + return; + }; + }; + }; + }; // Array/slice/ptr index store: `arr[i] = v;`. Element size // from base.tnode picks MOVB vs MOVQ. if (lhs != nil) {