cgen: slice-elem store/read -> 3-word via kind-OR (both stages)

The G-cluster gave str its 3-word {ptr,len,cap} store/read at indexed/field/chained sites, but each arm was gated on str only; the slice arm fell through to the 1-word fldstoreop default, dropping len+cap. A []T value stored through arr[i]=, arr[i].f=, *struct.f=, or value-spine o.i.f= (and read back via arr[i] / arr[i].f) silently lost length and capacity.

Widen all six arms (4 stores + 2 read mirrors) with a kind-OR (TY_STR||TY_SLICE / typeisstr||typeisslice), never a size test: str and slice are both 24B, so a width gate would fire on both and mask the missing slice arm. The str kind stays distinct and nominal -- the arm is widened, the kinds are not collapsed. cstage and wwstage mirrored.

Gate-blind class: store and read were both short, so byte-identity and cstage==wwstage stayed green on self-consistent garbage; only a runtime len/cap round-trip exposes it (test 941, table-driven, 4 shapes x 2 stages, fail-before/pass-after on both ww and ww_ww).

Deref store (*p=) and tuple-elem store (N_MLET/N_MASSIGN, distinct DX,CX,R8 return-ABI) are the same bug class but separate folds.
This commit is contained in:
2026-05-25 00:20:16 +09:00
parent 7bb40d924f
commit 451e2ebec9
6 changed files with 387 additions and 115 deletions

View File

@@ -14556,12 +14556,15 @@ fn cgindex(c: *cgen, n: *node) void = {
let idx: *node = n.rhs;
let esz: i32 = 8;
let signed_elem: bool = false;
// #1/Phase 3: str=24B collides with slice=24B, so the str-element
// branches below MUST gate on kind (mirroring cstage's elem_is_str),
// not a bare `esz == primtypesize("str")` size check — otherwise a
// []u8 element (also 24B) misfires into the str 2-word load and
// diverges from cstage (#60 collision class; sentinel 754).
// #1/Phase 3: str and slice are both 24B (and a >16B struct is
// 24B+ too), so the header branches below MUST gate on KIND
// (elemisstr/elemisslice, mirroring cstage's elem_is_str||
// elem_is_slice), not a bare `esz == primtypesize("str")` size
// check — a size gate would route a plain >16B struct into the
// 3-word {ptr,len,cap} load and diverge from cstage (#60 collision
// class; sentinel 754).
let elemisstr: bool = false;
let elemisslice: bool = false;
let baselocal: *local = nil;
// Global `[N]T` array or `*T` pointer used as an index base.
// The local-ident lookup above misses it; we need LEAQ name(SB)
@@ -14603,7 +14606,7 @@ fn cgindex(c: *cgen, n: *node) void = {
// cgen.c:3517-18). esz-only — N_DOT-base signedness
// stays unset, as before.
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); };
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); };
} else { if (base.kind == nkind.N_INDEX) {
// #60: chained `names[i][k]` — n.type_ is the checker-
// stamped outer element tinfo (indexresult over the inner
@@ -14653,6 +14656,7 @@ fn cgindex(c: *cgen, n: *node) void = {
};
};
elemisstr = isstrtype(c, etn);
elemisslice = isslicetype(c, etn);
};
};
cgexpr(c, idx);
@@ -14686,10 +14690,11 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element: load the full (ptr, len, cap) header into
// (AX, BX, CX) — str is 24B since #1, so cap must survive.
// Kind-gate on isstrtype, never size==24. Base is BX.
if (elemisstr) {
// str/slice element: load the full (ptr, len, cap) header into
// (AX, BX, CX) — both are 24B since #1, so cap must survive.
// Kind-gate on isstrtype||isslicetype, never size==24 (a >16B
// struct is 24B+ too but takes the struct-copy path). Base is BX.
if (elemisstr || elemisslice) {
cgslicehdr(c, "BX");
return;
};
@@ -14726,9 +14731,9 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element: full (ptr, len, cap) header into (AX, BX, CX);
// str/slice element: full (ptr, len, cap) header into (AX, BX, CX);
// cap must survive (#1). Kind-gate, never size==24. Base BX.
if (elemisstr) {
if (elemisstr || elemisslice) {
cgslicehdr(c, "BX");
return;
};
@@ -14756,10 +14761,10 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element via fallback base: full (ptr, len, cap) header
// str/slice element via fallback base: full (ptr, len, cap) header
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
// size==24. Base AX.
if (elemisstr) {
if (elemisstr || elemisslice) {
cgslicehdr(c, "AX");
return;
};
@@ -15610,8 +15615,8 @@ fn cgdot(c: *cgen, n: *node) void = {
} else {
emitline("\tMOVQ\tBX, AX\n");
};
if (isstrtype(c, fi.tnode)) {
// str IS the 3-word {ptr,len,cap}
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
// str/slice: the 3-word {ptr,len,cap}
// slice header (#1). AX holds the
// element base, so load .ptr (which
// targets AX) LAST. Matches the
@@ -17718,16 +17723,16 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
cgexpr(c, n.rhs); // value → AX
// str IS []u8: spill cap (CX) + len (BX) before
// str/slice: spill cap (CX) + len (BX) before
// computing the index so the post-index store can
// pop all three. #1/Phase 3: str=24B collides with
// pop all three. str=24B (#1/Phase 3) collides with
// slice=24B, so this MUST gate on kind (cstage's
// elem_is_str, cmd/w6c/cgen.c:3576) — not a bare
// `esz == primtypesize("str")` — or a []u8 element
// (also 24B) misfires the str 3-word store and
// diverges from cstage. Write-side mirror of the
// elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581),
// never a bare esz==24: a >16B struct is also >=24B
// but takes the struct-copy path, not this 3-word
// {ptr,len,cap} store. Write-side mirror of the
// cgindex read-path gate (#7/754).
if (isstrtype(c, elemtn)) {
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");
};
@@ -17768,10 +17773,10 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tPOPQ\tAX\n"); // scaled idx
emitline("\tADDQ\tAX, BX\n");
emitline("\tPOPQ\tAX\n"); // value
// str IS []u8: pop the saved len + cap and store
// str/slice: pop the saved len + cap and store
// all three words. Kind-gate, not size — see the
// spill site above (#1/Phase 3, #7/754).
if (isstrtype(c, elemtn)) {
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
emitline("\tMOVQ\tAX, (BX)\n");
emitline("\tPOPQ\tCX\n");
emitline("\tMOVQ\tCX, 8(BX)\n");
@@ -17875,7 +17880,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\n");
return;
};
// str IS []u8: rhs leaves AX=ptr,
// str/slice: rhs leaves AX=ptr,
// BX=len, CX=cap (#1/Phase 3). Spill
// all three across the index/address
// computation (IMULQ's CX scratch
@@ -17883,7 +17888,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// off the str AX/BX/CX convention
// (mirrors s.f=v), then store the full
// triple at foff+0/+8/+16.
if (isstrtype(c, fi.tnode)) {
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");
@@ -18784,8 +18789,8 @@ fn cgassign(c: *cgen, n: *node) void = {
if (streq(tf.name, fld)) {
let ft: *tinfo = tf.type_;
if (n.op == tkind.TK_ASSIGN) {
if (typeisstr(ft)) {
// str IS []u8: rhs leaves AX=ptr,
if (typeisstr(ft) || typeisslice(ft)) {
// str/slice: rhs leaves AX=ptr,
// BX=len, CX=cap (#1/Phase 3). Spill
// all three across the base-expr eval
// (it may clobber any reg), stage the
@@ -18906,8 +18911,8 @@ fn cgassign(c: *cgen, n: *node) void = {
};
return;
};
if (typeisstr(leaftype)) {
// str IS []u8: store ptr/len/cap. cgexpr leaves
if (typeisstr(leaftype) || typeisslice(leaftype)) {
// str/slice: store ptr/len/cap. cgexpr leaves
// CX=cap, so the viacx base goes in DX (not CX) to
// avoid clobbering it — same as the single-dot str
// field store (#1/Phase 3).

View File

@@ -723,12 +723,15 @@ fn cgindex(c: *cgen, n: *node) void = {
let idx: *node = n.rhs;
let esz: i32 = 8;
let signed_elem: bool = false;
// #1/Phase 3: str=24B collides with slice=24B, so the str-element
// branches below MUST gate on kind (mirroring cstage's elem_is_str),
// not a bare `esz == primtypesize("str")` size check — otherwise a
// []u8 element (also 24B) misfires into the str 2-word load and
// diverges from cstage (#60 collision class; sentinel 754).
// #1/Phase 3: str and slice are both 24B (and a >16B struct is
// 24B+ too), so the header branches below MUST gate on KIND
// (elemisstr/elemisslice, mirroring cstage's elem_is_str||
// elem_is_slice), not a bare `esz == primtypesize("str")` size
// check — a size gate would route a plain >16B struct into the
// 3-word {ptr,len,cap} load and diverge from cstage (#60 collision
// class; sentinel 754).
let elemisstr: bool = false;
let elemisslice: bool = false;
let baselocal: *local = nil;
// Global `[N]T` array or `*T` pointer used as an index base.
// The local-ident lookup above misses it; we need LEAQ name(SB)
@@ -770,7 +773,7 @@ fn cgindex(c: *cgen, n: *node) void = {
// cgen.c:3517-18). esz-only — N_DOT-base signedness
// stays unset, as before.
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); };
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); };
} else { if (base.kind == nkind.N_INDEX) {
// #60: chained `names[i][k]` — n.type_ is the checker-
// stamped outer element tinfo (indexresult over the inner
@@ -820,6 +823,7 @@ fn cgindex(c: *cgen, n: *node) void = {
};
};
elemisstr = isstrtype(c, etn);
elemisslice = isslicetype(c, etn);
};
};
cgexpr(c, idx);
@@ -853,10 +857,11 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element: load the full (ptr, len, cap) header into
// (AX, BX, CX) — str is 24B since #1, so cap must survive.
// Kind-gate on isstrtype, never size==24. Base is BX.
if (elemisstr) {
// str/slice element: load the full (ptr, len, cap) header into
// (AX, BX, CX) — both are 24B since #1, so cap must survive.
// Kind-gate on isstrtype||isslicetype, never size==24 (a >16B
// struct is 24B+ too but takes the struct-copy path). Base is BX.
if (elemisstr || elemisslice) {
cgslicehdr(c, "BX");
return;
};
@@ -893,9 +898,9 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element: full (ptr, len, cap) header into (AX, BX, CX);
// str/slice element: full (ptr, len, cap) header into (AX, BX, CX);
// cap must survive (#1). Kind-gate, never size==24. Base BX.
if (elemisstr) {
if (elemisstr || elemisslice) {
cgslicehdr(c, "BX");
return;
};
@@ -923,10 +928,10 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element via fallback base: full (ptr, len, cap) header
// str/slice element via fallback base: full (ptr, len, cap) header
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
// size==24. Base AX.
if (elemisstr) {
if (elemisstr || elemisslice) {
cgslicehdr(c, "AX");
return;
};
@@ -1777,8 +1782,8 @@ fn cgdot(c: *cgen, n: *node) void = {
} else {
emitline("\tMOVQ\tBX, AX\n");
};
if (isstrtype(c, fi.tnode)) {
// str IS the 3-word {ptr,len,cap}
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
// str/slice: the 3-word {ptr,len,cap}
// slice header (#1). AX holds the
// element base, so load .ptr (which
// targets AX) LAST. Matches the
@@ -3885,16 +3890,16 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
cgexpr(c, n.rhs); // value → AX
// str IS []u8: spill cap (CX) + len (BX) before
// str/slice: spill cap (CX) + len (BX) before
// computing the index so the post-index store can
// pop all three. #1/Phase 3: str=24B collides with
// pop all three. str=24B (#1/Phase 3) collides with
// slice=24B, so this MUST gate on kind (cstage's
// elem_is_str, cmd/w6c/cgen.c:3576) — not a bare
// `esz == primtypesize("str")` — or a []u8 element
// (also 24B) misfires the str 3-word store and
// diverges from cstage. Write-side mirror of the
// elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581),
// never a bare esz==24: a >16B struct is also >=24B
// but takes the struct-copy path, not this 3-word
// {ptr,len,cap} store. Write-side mirror of the
// cgindex read-path gate (#7/754).
if (isstrtype(c, elemtn)) {
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");
};
@@ -3935,10 +3940,10 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tPOPQ\tAX\n"); // scaled idx
emitline("\tADDQ\tAX, BX\n");
emitline("\tPOPQ\tAX\n"); // value
// str IS []u8: pop the saved len + cap and store
// str/slice: pop the saved len + cap and store
// all three words. Kind-gate, not size — see the
// spill site above (#1/Phase 3, #7/754).
if (isstrtype(c, elemtn)) {
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
emitline("\tMOVQ\tAX, (BX)\n");
emitline("\tPOPQ\tCX\n");
emitline("\tMOVQ\tCX, 8(BX)\n");
@@ -4042,7 +4047,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\n");
return;
};
// str IS []u8: rhs leaves AX=ptr,
// str/slice: rhs leaves AX=ptr,
// BX=len, CX=cap (#1/Phase 3). Spill
// all three across the index/address
// computation (IMULQ's CX scratch
@@ -4050,7 +4055,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// off the str AX/BX/CX convention
// (mirrors s.f=v), then store the full
// triple at foff+0/+8/+16.
if (isstrtype(c, fi.tnode)) {
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");
@@ -4951,8 +4956,8 @@ fn cgassign(c: *cgen, n: *node) void = {
if (streq(tf.name, fld)) {
let ft: *tinfo = tf.type_;
if (n.op == tkind.TK_ASSIGN) {
if (typeisstr(ft)) {
// str IS []u8: rhs leaves AX=ptr,
if (typeisstr(ft) || typeisslice(ft)) {
// str/slice: rhs leaves AX=ptr,
// BX=len, CX=cap (#1/Phase 3). Spill
// all three across the base-expr eval
// (it may clobber any reg), stage the
@@ -5073,8 +5078,8 @@ fn cgassign(c: *cgen, n: *node) void = {
};
return;
};
if (typeisstr(leaftype)) {
// str IS []u8: store ptr/len/cap. cgexpr leaves
if (typeisstr(leaftype) || typeisslice(leaftype)) {
// str/slice: store ptr/len/cap. cgexpr leaves
// CX=cap, so the viacx base goes in DX (not CX) to
// avoid clobbering it — same as the single-dot str
// field store (#1/Phase 3).

View File

@@ -14556,12 +14556,15 @@ fn cgindex(c: *cgen, n: *node) void = {
let idx: *node = n.rhs;
let esz: i32 = 8;
let signed_elem: bool = false;
// #1/Phase 3: str=24B collides with slice=24B, so the str-element
// branches below MUST gate on kind (mirroring cstage's elem_is_str),
// not a bare `esz == primtypesize("str")` size check — otherwise a
// []u8 element (also 24B) misfires into the str 2-word load and
// diverges from cstage (#60 collision class; sentinel 754).
// #1/Phase 3: str and slice are both 24B (and a >16B struct is
// 24B+ too), so the header branches below MUST gate on KIND
// (elemisstr/elemisslice, mirroring cstage's elem_is_str||
// elem_is_slice), not a bare `esz == primtypesize("str")` size
// check — a size gate would route a plain >16B struct into the
// 3-word {ptr,len,cap} load and diverge from cstage (#60 collision
// class; sentinel 754).
let elemisstr: bool = false;
let elemisslice: bool = false;
let baselocal: *local = nil;
// Global `[N]T` array or `*T` pointer used as an index base.
// The local-ident lookup above misses it; we need LEAQ name(SB)
@@ -14603,7 +14606,7 @@ fn cgindex(c: *cgen, n: *node) void = {
// cgen.c:3517-18). esz-only — N_DOT-base signedness
// stays unset, as before.
let dt: *tinfo = n.type_: *tinfo;
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); };
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); };
} else { if (base.kind == nkind.N_INDEX) {
// #60: chained `names[i][k]` — n.type_ is the checker-
// stamped outer element tinfo (indexresult over the inner
@@ -14653,6 +14656,7 @@ fn cgindex(c: *cgen, n: *node) void = {
};
};
elemisstr = isstrtype(c, etn);
elemisslice = isslicetype(c, etn);
};
};
cgexpr(c, idx);
@@ -14686,10 +14690,11 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element: load the full (ptr, len, cap) header into
// (AX, BX, CX) — str is 24B since #1, so cap must survive.
// Kind-gate on isstrtype, never size==24. Base is BX.
if (elemisstr) {
// str/slice element: load the full (ptr, len, cap) header into
// (AX, BX, CX) — both are 24B since #1, so cap must survive.
// Kind-gate on isstrtype||isslicetype, never size==24 (a >16B
// struct is 24B+ too but takes the struct-copy path). Base is BX.
if (elemisstr || elemisslice) {
cgslicehdr(c, "BX");
return;
};
@@ -14726,9 +14731,9 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element: full (ptr, len, cap) header into (AX, BX, CX);
// str/slice element: full (ptr, len, cap) header into (AX, BX, CX);
// cap must survive (#1). Kind-gate, never size==24. Base BX.
if (elemisstr) {
if (elemisstr || elemisslice) {
cgslicehdr(c, "BX");
return;
};
@@ -14756,10 +14761,10 @@ fn cgindex(c: *cgen, n: *node) void = {
emitline("\tMOVQ\t(BX), AX\n");
return;
};
// str element via fallback base: full (ptr, len, cap) header
// str/slice element via fallback base: full (ptr, len, cap) header
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
// size==24. Base AX.
if (elemisstr) {
if (elemisstr || elemisslice) {
cgslicehdr(c, "AX");
return;
};
@@ -15610,8 +15615,8 @@ fn cgdot(c: *cgen, n: *node) void = {
} else {
emitline("\tMOVQ\tBX, AX\n");
};
if (isstrtype(c, fi.tnode)) {
// str IS the 3-word {ptr,len,cap}
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
// str/slice: the 3-word {ptr,len,cap}
// slice header (#1). AX holds the
// element base, so load .ptr (which
// targets AX) LAST. Matches the
@@ -17718,16 +17723,16 @@ fn cgassign(c: *cgen, n: *node) void = {
};
};
cgexpr(c, n.rhs); // value → AX
// str IS []u8: spill cap (CX) + len (BX) before
// str/slice: spill cap (CX) + len (BX) before
// computing the index so the post-index store can
// pop all three. #1/Phase 3: str=24B collides with
// pop all three. str=24B (#1/Phase 3) collides with
// slice=24B, so this MUST gate on kind (cstage's
// elem_is_str, cmd/w6c/cgen.c:3576) — not a bare
// `esz == primtypesize("str")` — or a []u8 element
// (also 24B) misfires the str 3-word store and
// diverges from cstage. Write-side mirror of the
// elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581),
// never a bare esz==24: a >16B struct is also >=24B
// but takes the struct-copy path, not this 3-word
// {ptr,len,cap} store. Write-side mirror of the
// cgindex read-path gate (#7/754).
if (isstrtype(c, elemtn)) {
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");
};
@@ -17768,10 +17773,10 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\tPOPQ\tAX\n"); // scaled idx
emitline("\tADDQ\tAX, BX\n");
emitline("\tPOPQ\tAX\n"); // value
// str IS []u8: pop the saved len + cap and store
// str/slice: pop the saved len + cap and store
// all three words. Kind-gate, not size — see the
// spill site above (#1/Phase 3, #7/754).
if (isstrtype(c, elemtn)) {
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
emitline("\tMOVQ\tAX, (BX)\n");
emitline("\tPOPQ\tCX\n");
emitline("\tMOVQ\tCX, 8(BX)\n");
@@ -17875,7 +17880,7 @@ fn cgassign(c: *cgen, n: *node) void = {
emitline("\n");
return;
};
// str IS []u8: rhs leaves AX=ptr,
// str/slice: rhs leaves AX=ptr,
// BX=len, CX=cap (#1/Phase 3). Spill
// all three across the index/address
// computation (IMULQ's CX scratch
@@ -17883,7 +17888,7 @@ fn cgassign(c: *cgen, n: *node) void = {
// off the str AX/BX/CX convention
// (mirrors s.f=v), then store the full
// triple at foff+0/+8/+16.
if (isstrtype(c, fi.tnode)) {
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
cgexpr(c, n.rhs);
emitline("\tPUSHQ\tCX\n");
emitline("\tPUSHQ\tBX\n");
@@ -18784,8 +18789,8 @@ fn cgassign(c: *cgen, n: *node) void = {
if (streq(tf.name, fld)) {
let ft: *tinfo = tf.type_;
if (n.op == tkind.TK_ASSIGN) {
if (typeisstr(ft)) {
// str IS []u8: rhs leaves AX=ptr,
if (typeisstr(ft) || typeisslice(ft)) {
// str/slice: rhs leaves AX=ptr,
// BX=len, CX=cap (#1/Phase 3). Spill
// all three across the base-expr eval
// (it may clobber any reg), stage the
@@ -18906,8 +18911,8 @@ fn cgassign(c: *cgen, n: *node) void = {
};
return;
};
if (typeisstr(leaftype)) {
// str IS []u8: store ptr/len/cap. cgexpr leaves
if (typeisstr(leaftype) || typeisslice(leaftype)) {
// str/slice: store ptr/len/cap. cgexpr leaves
// CX=cap, so the viacx base goes in DX (not CX) to
// avoid clobbering it — same as the single-dot str
// field store (#1/Phase 3).