From 7bb40d924f1146a2914a69b3fb7e095ca32156cc Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sun, 24 May 2026 22:39:04 +0900 Subject: [PATCH] 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). --- cmd/w6c/cgen.c | 81 ++++++---------------------- selfhost/cmd/w6c/main.combined.ww | 79 ++++++--------------------- selfhost/cmd/wcc/cgenexpr.ww | 79 ++++++--------------------- selfhost/cmd/wwdump/main.combined.ww | 79 ++++++--------------------- 4 files changed, 62 insertions(+), 256 deletions(-) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 8cbcc59e..a480ff78 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 48a28919..588bf88f 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index b0a221c7..b3740056 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 56741412..e938c123 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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);