cgen: merge byte-identical str+slice cgassign arms onto kind-gates -- Phase 2 C4.4

Post the str->24B lifts, cgassign had SEPARATE str and slice arms emitting byte-identical 3-word {ptr,len,cap} code. Collapse each identical pair into ONE kind-gated arm (rule 12, sea-of-stars; removes a drift hazard) -- the structural str==[]u8 unification, byte-id-NEUTRAL (each stage's emission unchanged for both str and slice inputs). Pairs: field store s.f=v + ident reassign name=v. cstage gates on the EXACT predicate union (raw kind==TY_STR OR'd with TY_SLICE -- NOT type_isstr, which would also match TY_UNTYPED_STR); ww on isstrtype||isslicetype and letvarisstr||letvarisslice (ww local field/reassign were already merged). Mirrors the in-tree deep-value-chain precedent (cstage 3274). str-only arms with no slice pair (arr[i].field=/chained, G1/G2) untouched.

Verified per-stage PRE==POST byte-identical (focused 5-path fixture + 4 large real combined.ww inputs, both stages); the 5 pairs were byte-identical pre-merge. main.combined.ww regenerated via the canonical make path (md5-stable). A pre-existing global-slice-field-store divergence (g.sl=b: cstage 3-word, wwstage 1-word) surfaced during review -- filed (#26/#10), NOT a C4.4 concern (PRE==POST).
This commit is contained in:
2026-05-24 22:39:04 +09:00
parent 3637993bb2
commit 7bb40d924f
4 changed files with 62 additions and 256 deletions

View File

@@ -2587,42 +2587,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
int is_global = (boff == 0 && !via_ptr
&& let_islet(base->str));
int foff = (int)f->offset;
/* str IS []u8: rhs cgexpr leaves (AX=ptr, BX=len,
* CX=cap); store all three at field+0/+8/+16,
* mirroring the slice-field arm below. Address
* scratch must dodge CX (holds cap), so via_ptr/
* is_global stage the struct base in DX (#1/Phase 3).
* Only plain `=` is wired; compound on a str field is
* not meaningful. */
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
? f->type->under : f->type;
if (n->op == TK_ASSIGN && str_fu && str_fu->kind == TY_STR) {
cgexpr(c, n->rhs, locals);
if (via_ptr) {
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_DX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, foff + 0));
ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, foff + 8));
ins2(c, A_MOVQ, areg(D_CX), amem(D_DX, foff + 16));
} else if (is_global) {
ins2(c, A_LEAQ, masym(c, base->str), areg(D_DX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, foff + 0));
ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, foff + 8));
ins2(c, A_MOVQ, areg(D_CX), amem(D_DX, foff + 16));
} else {
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 0));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, boff + foff + 8));
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, boff + foff + 16));
}
break;
}
/* slice-typed field: rhs cgexpr leaves (AX=ptr,
* BX=len, CX=cap); store all three at
* field+0/+8/+16. Address scratch must dodge CX
* (holds cap), so via_ptr/is_global stage the
* struct base in DX. Without this branch the
/* str/slice field: str IS []u8, so both store the full
* 3-word {ptr,len,cap} that rhs cgexpr leaves in
* (AX,BX,CX) at field+0/+8/+16. Address scratch must
* dodge CX (holds cap), so via_ptr/is_global stage the
* struct base in DX (#1/Phase 3). Without this the
* generic store_op below writes only AX, silently
* dropping .len and .cap. */
if (n->op == TK_ASSIGN && str_fu && str_fu->kind == TY_SLICE) {
* dropping .len/.cap. Only plain `=` is wired; compound
* on a str/slice field is not meaningful. */
if (n->op == TK_ASSIGN && str_fu
&& (str_fu->kind == TY_SLICE || str_fu->kind == TY_STR)) {
cgexpr(c, n->rhs, locals);
if (via_ptr) {
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_DX));
@@ -3850,41 +3826,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
break;
}
}
/* Plain `name = strexpr;` for a str-typed local. str IS []u8:
* cgexpr leaves (AX=ptr, BX=len, CX=cap); store all three at
* off+0/+8/+16, identical to the slice arm below. Top-level
* str globals go through &name(SB) → DI scratch (CX holds cap)
* since the asm has no `name+8(SB)` operand form (#1/Phase 3). */
/* `name = expr;` reassignment of a str/slice/struct local or
* top-level let. */
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
&& n->lhs->type) {
Type *lt = n->lhs->type;
Type *lu = (lt && lt->kind == TY_NAMED) ? lt->under : lt;
if (lu && lu->kind == TY_STR) {
int off = localfind(locals, n->lhs->str);
if (off != 0) {
cgexpr(c, n->rhs, locals);
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
break;
}
if (let_islet(n->lhs->str)) {
cgexpr(c, n->rhs, locals);
ins2(c, A_MOVQ, areg(D_CX), areg(D_DI));
ins2(c, A_LEAQ, masym(c, n->lhs->str),
areg(D_CX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, 0));
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, 8));
ins2(c, A_MOVQ, areg(D_DI), amem(D_CX, 16));
break;
}
break;
}
/* Slice reassignment: cgexpr produces (AX=ptr, BX=len,
* CX=cap). Store all three at off+0/+8/+16 (local) or
* via &name(SB) → DI scratch (global — CX holds the
* cap, so we need a different address register). */
if (lu && lu->kind == TY_SLICE) {
/* str/slice local/let: str IS []u8, so both store the full
* 3-word {ptr,len,cap} from (AX,BX,CX) at off+0/+8/+16
* (local) or via &name(SB) → DI scratch (global — CX holds
* the cap, and the asm has no `name+8(SB)` operand form, so
* a different address register is needed) (#1/Phase 3). */
if (lu && (lu->kind == TY_SLICE || lu->kind == TY_STR)) {
int off = localfind(locals, n->lhs->str);
if (off != 0) {
cgexpr(c, n->rhs, locals);

View File

@@ -18213,33 +18213,12 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tBX, AX\n");
};
};
// str IS []u8: rhs left (AX=ptr,
// BX=len, CX=cap). CX holds cap, so
// stage the struct addr in DX and
// store all three words — identical
// to the slice arm below (#1/Phase 3).
if (n.op == tkind.TK_ASSIGN) {
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "DX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "DX");
emitline("\n");
emitline("\tMOVQ\tCX, ");
emitdispreg((fi.foff + 16): i64, "DX");
emitline("\n");
return;
};
// slice field via *struct: rhs left
// (AX=ptr, BX=len, CX=cap). CX is
// taken, so stage the struct addr
// in DX. Store all three words at
// foff/+8/+16.
if (isslicetype(c, fi.tnode)) {
// str/slice field via *struct: str IS []u8, so both
// store the full 3-word {ptr,len,cap} from (AX,BX,CX).
// CX holds cap, so stage the struct addr in DX and
// store at foff/+8/+16 (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), DX\n");
@@ -18421,29 +18400,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};};
};
cgexpr(c, n.rhs);
// str IS []u8: cgexpr left (AX=ptr,
// BX=len, CX=cap); store all three at
// +0/+8/+16, identical to the slice
// arm below. BP base, no scratch
// reload needed (#1/Phase 3).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((lc.off + fi.foff + 16): i64);
emitline("(BP)\n");
return;
};
// slice field direct: cgexpr left
// (AX=ptr, BX=len, CX=cap); store all
// three at +0/+8/+16. The generic
// fldstoreop below would only write AX,
// dropping .len/.cap.
if (isslicetype(c, fi.tnode)) {
// str/slice field direct: str IS []u8, so both store the
// full 3-word {ptr,len,cap} from (AX,BX,CX) at +0/+8/+16.
// BP base, no scratch reload needed; the generic fldstoreop
// below would write only AX, dropping .len/.cap (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitline("(BP)\n");
@@ -19466,21 +19427,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
cgexpr(c, n.rhs);
if (n.op == tkind.TK_ASSIGN) {
if (letvarisstr(c, nm)) {
// str IS []u8: stash cap in DI before LEAQ
// overwrites CX, then store ptr/len/cap
// identical to the slice arm below
// (#1/Phase 3).
emitline("\tMOVQ\tCX, DI\n");
emitline("\tLEAQ\t");
emitsymname(c, nm);
emitline("(SB), CX\n");
emitline("\tMOVQ\tAX, (CX)\n");
emitline("\tMOVQ\tBX, 8(CX)\n");
emitline("\tMOVQ\tDI, 16(CX)\n");
return;
};
if (letvarisslice(c, nm)) {
// str/slice top-level let: str IS []u8, so both store the
// full 3-word {ptr,len,cap}. Stash cap in DI before LEAQ
// overwrites CX, then store ptr/len/cap via &name(SB)
// (#1/Phase 3).
if (letvarisstr(c, nm) || letvarisslice(c, nm)) {
emitline("\tMOVQ\tCX, DI\n");
emitline("\tLEAQ\t");
emitsymname(c, nm);

View File

@@ -4380,33 +4380,12 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tBX, AX\n");
};
};
// str IS []u8: rhs left (AX=ptr,
// BX=len, CX=cap). CX holds cap, so
// stage the struct addr in DX and
// store all three words — identical
// to the slice arm below (#1/Phase 3).
if (n.op == tkind.TK_ASSIGN) {
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "DX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "DX");
emitline("\n");
emitline("\tMOVQ\tCX, ");
emitdispreg((fi.foff + 16): i64, "DX");
emitline("\n");
return;
};
// slice field via *struct: rhs left
// (AX=ptr, BX=len, CX=cap). CX is
// taken, so stage the struct addr
// in DX. Store all three words at
// foff/+8/+16.
if (isslicetype(c, fi.tnode)) {
// str/slice field via *struct: str IS []u8, so both
// store the full 3-word {ptr,len,cap} from (AX,BX,CX).
// CX holds cap, so stage the struct addr in DX and
// store at foff/+8/+16 (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), DX\n");
@@ -4588,29 +4567,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};};
};
cgexpr(c, n.rhs);
// str IS []u8: cgexpr left (AX=ptr,
// BX=len, CX=cap); store all three at
// +0/+8/+16, identical to the slice
// arm below. BP base, no scratch
// reload needed (#1/Phase 3).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((lc.off + fi.foff + 16): i64);
emitline("(BP)\n");
return;
};
// slice field direct: cgexpr left
// (AX=ptr, BX=len, CX=cap); store all
// three at +0/+8/+16. The generic
// fldstoreop below would only write AX,
// dropping .len/.cap.
if (isslicetype(c, fi.tnode)) {
// str/slice field direct: str IS []u8, so both store the
// full 3-word {ptr,len,cap} from (AX,BX,CX) at +0/+8/+16.
// BP base, no scratch reload needed; the generic fldstoreop
// below would write only AX, dropping .len/.cap (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitline("(BP)\n");
@@ -5633,21 +5594,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
cgexpr(c, n.rhs);
if (n.op == tkind.TK_ASSIGN) {
if (letvarisstr(c, nm)) {
// str IS []u8: stash cap in DI before LEAQ
// overwrites CX, then store ptr/len/cap
// identical to the slice arm below
// (#1/Phase 3).
emitline("\tMOVQ\tCX, DI\n");
emitline("\tLEAQ\t");
emitsymname(c, nm);
emitline("(SB), CX\n");
emitline("\tMOVQ\tAX, (CX)\n");
emitline("\tMOVQ\tBX, 8(CX)\n");
emitline("\tMOVQ\tDI, 16(CX)\n");
return;
};
if (letvarisslice(c, nm)) {
// str/slice top-level let: str IS []u8, so both store the
// full 3-word {ptr,len,cap}. Stash cap in DI before LEAQ
// overwrites CX, then store ptr/len/cap via &name(SB)
// (#1/Phase 3).
if (letvarisstr(c, nm) || letvarisslice(c, nm)) {
emitline("\tMOVQ\tCX, DI\n");
emitline("\tLEAQ\t");
emitsymname(c, nm);

View File

@@ -18213,33 +18213,12 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tBX, AX\n");
};
};
// str IS []u8: rhs left (AX=ptr,
// BX=len, CX=cap). CX holds cap, so
// stage the struct addr in DX and
// store all three words — identical
// to the slice arm below (#1/Phase 3).
if (n.op == tkind.TK_ASSIGN) {
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), DX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(fi.foff: i64, "DX");
emitline("\n");
emitline("\tMOVQ\tBX, ");
emitdispreg((fi.foff + 8): i64, "DX");
emitline("\n");
emitline("\tMOVQ\tCX, ");
emitdispreg((fi.foff + 16): i64, "DX");
emitline("\n");
return;
};
// slice field via *struct: rhs left
// (AX=ptr, BX=len, CX=cap). CX is
// taken, so stage the struct addr
// in DX. Store all three words at
// foff/+8/+16.
if (isslicetype(c, fi.tnode)) {
// str/slice field via *struct: str IS []u8, so both
// store the full 3-word {ptr,len,cap} from (AX,BX,CX).
// CX holds cap, so stage the struct addr in DX and
// store at foff/+8/+16 (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\t");
emitoff(lc.off: i64);
emitline("(BP), DX\n");
@@ -18421,29 +18400,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};};
};
cgexpr(c, n.rhs);
// str IS []u8: cgexpr left (AX=ptr,
// BX=len, CX=cap); store all three at
// +0/+8/+16, identical to the slice
// arm below. BP base, no scratch
// reload needed (#1/Phase 3).
if (isstrtype(c, fi.tnode)) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((lc.off + fi.foff + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((lc.off + fi.foff + 16): i64);
emitline("(BP)\n");
return;
};
// slice field direct: cgexpr left
// (AX=ptr, BX=len, CX=cap); store all
// three at +0/+8/+16. The generic
// fldstoreop below would only write AX,
// dropping .len/.cap.
if (isslicetype(c, fi.tnode)) {
// str/slice field direct: str IS []u8, so both store the
// full 3-word {ptr,len,cap} from (AX,BX,CX) at +0/+8/+16.
// BP base, no scratch reload needed; the generic fldstoreop
// below would write only AX, dropping .len/.cap (#1/Phase 3).
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
emitline("\tMOVQ\tAX, ");
emitoff((lc.off + fi.foff): i64);
emitline("(BP)\n");
@@ -19466,21 +19427,11 @@ fn cgassign(c: *cgen, n: *node) void = {
};
cgexpr(c, n.rhs);
if (n.op == tkind.TK_ASSIGN) {
if (letvarisstr(c, nm)) {
// str IS []u8: stash cap in DI before LEAQ
// overwrites CX, then store ptr/len/cap
// identical to the slice arm below
// (#1/Phase 3).
emitline("\tMOVQ\tCX, DI\n");
emitline("\tLEAQ\t");
emitsymname(c, nm);
emitline("(SB), CX\n");
emitline("\tMOVQ\tAX, (CX)\n");
emitline("\tMOVQ\tBX, 8(CX)\n");
emitline("\tMOVQ\tDI, 16(CX)\n");
return;
};
if (letvarisslice(c, nm)) {
// str/slice top-level let: str IS []u8, so both store the
// full 3-word {ptr,len,cap}. Stash cap in DI before LEAQ
// overwrites CX, then store ptr/len/cap via &name(SB)
// (#1/Phase 3).
if (letvarisstr(c, nm) || letvarisslice(c, nm)) {
emitline("\tMOVQ\tCX, DI\n");
emitline("\tLEAQ\t");
emitsymname(c, nm);