A `let t: [N][]u8 = [a, b]` / `[N]str` literal init lowered each
element's {ptr,len,cap} header into AX/BX/CX (cgexpr) but stored only
some words: a slice element fell through to the scalar 1-word MOVQ
(dropping .len AND .cap), a str element stored 2 words (dropping .cap,
latent). Each element is 24B (post-#1) and must be copied whole.
wwstage was worse — a slice element matched no esz branch, so esz
stayed the 8 sentinel: the per-element stride collapsed (element i+1
overwrote element i's tail), the -96-vs-80 cs!=ww frame divergence.
This is the str/slice arm of the #270 aggregate-element-store family.
struct/array/tuple already copy correctly via the #270-1c is_agg
multi-word path; str/slice were the documented follow-up (cgen.c:9037,
cgenstmt.ww deferral). They can't join is_agg (that path word-copies
from a source slot and rejects non-ident/structlit elements, whereas
str/slice elements are commonly exprs cgexpr lowers into registers) —
the correct mechanism is the existing register header store, extended.
Fix (BOTH stages, converged byte-identical): cstage adds
is_slice_el = type_isslice(esub) and stores 3 words (incl CX->base+16,
the cap) for `is_str_el || is_slice_el`, in the main loop and the
repeat-fill. wwstage adds isslicel (esubti.kind == TY_SLICE -> esz =
esubti.size, fixing the stride) and the matching 3-word store. Closes
[N][]u8 (the bug) and the latent [N]str cap-drop in one branch.
The latent str cap-drop is now stored, but the indexed-element `.cap`
READ (`t[i].cap`) stays broken — a distinct cgindex/dot-selector bug,
cs!=ww divergent, filed as task #13. The new test validates the stored
cap via a whole-element copy (`let q = t[i]; q.cap`), which reads
through the correct ident-load path. [N]tagged literal init is the
remaining sibling (is_agg excludes TY_TAGGED), task #12.
Test 683_arr_strslice_elem: table-driven, dual-stage runtime + asm
byte-id; slice/str .len, 3-element stride-24, cap-via-copy, .ptr deref,
plus a [N]struct regression pin proving the is_agg path is untouched.
This commit is contained in:
@@ -29518,16 +29518,17 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// after the last value (an nkind.N_FIELD with str=="...") fills the
|
||||
// remaining slots up to the declared length with that value.
|
||||
//
|
||||
// str element (16B = ptr+len) needs both halves stored. cgstrlit
|
||||
// / cgident leave a str as (AX=ptr, BX=len) and a single MOVQ
|
||||
// from AX would leave .len as whatever the stack held — silent
|
||||
// miscompile. Worse, primsize("str") returns 0 so esz would fall
|
||||
// back to 8, also collapsing the per-element stride (element i+1
|
||||
// would overwrite element i's would-be .len half). Detect the
|
||||
// str-element case up front so both esz and the store path are
|
||||
// right. (primsize's default-to-8-on-zero pattern is brittle for
|
||||
// composites generally; same gap blocks slice / struct / tuple /
|
||||
// tagged element arrays — tracked as a follow-up.)
|
||||
// str/slice element (24B = ptr+len+cap, post-#1) needs all 3
|
||||
// words stored. cgstrlit / cgident leave it as (AX=ptr, BX=len,
|
||||
// CX=cap) and a single MOVQ from AX would leave .len/.cap as
|
||||
// whatever the stack held — silent miscompile. Worse,
|
||||
// primsize("str") returns 0 so esz would fall back to 8, also
|
||||
// collapsing the per-element stride (element i+1 would overwrite
|
||||
// element i's would-be .len half). Detect the str/slice element
|
||||
// case up front so both esz and the store path are right.
|
||||
// (primsize's default-to-8-on-zero pattern is brittle for
|
||||
// composites generally. The str/slice element now stores all 3
|
||||
// words; [N]tagged element arrays still hit the gap, task #12.)
|
||||
if (rhs.kind == nkind.N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
@@ -29559,6 +29560,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
|| esubti.kind == tykind.TY_ARRAY
|
||||
|| esubti.kind == tykind.TY_TUPLE);
|
||||
if (isagg) { esz = esubti.size: i32; };
|
||||
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
|
||||
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
|
||||
// branch above, so esz stayed the 8 sentinel (wrong stride,
|
||||
// the -96-vs-80 cs!=ww frame divergence) and the scalar
|
||||
// store dropped .len/.cap. Size it from the stamped tinfo
|
||||
// and route it through the 3-word header store below.
|
||||
let isslicel: bool = esubti != nil
|
||||
&& esubti.kind == tykind.TY_SLICE;
|
||||
if (isslicel) { esz = esubti.size: i32; };
|
||||
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
|
||||
// neither a builtin prim (primsize=0 above, so esz stayed
|
||||
// the 8 sentinel) nor an aggregate, so the scalar store kept
|
||||
@@ -29648,13 +29658,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
}; };
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
if (isstrel) {
|
||||
if (isstrel || isslicel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + idx * esz + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
@@ -29692,13 +29705,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
if (isstrel) {
|
||||
if (isstrel || isslicel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + idx * esz + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
|
||||
@@ -1732,16 +1732,17 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// after the last value (an nkind.N_FIELD with str=="...") fills the
|
||||
// remaining slots up to the declared length with that value.
|
||||
//
|
||||
// str element (16B = ptr+len) needs both halves stored. cgstrlit
|
||||
// / cgident leave a str as (AX=ptr, BX=len) and a single MOVQ
|
||||
// from AX would leave .len as whatever the stack held — silent
|
||||
// miscompile. Worse, primsize("str") returns 0 so esz would fall
|
||||
// back to 8, also collapsing the per-element stride (element i+1
|
||||
// would overwrite element i's would-be .len half). Detect the
|
||||
// str-element case up front so both esz and the store path are
|
||||
// right. (primsize's default-to-8-on-zero pattern is brittle for
|
||||
// composites generally; same gap blocks slice / struct / tuple /
|
||||
// tagged element arrays — tracked as a follow-up.)
|
||||
// str/slice element (24B = ptr+len+cap, post-#1) needs all 3
|
||||
// words stored. cgstrlit / cgident leave it as (AX=ptr, BX=len,
|
||||
// CX=cap) and a single MOVQ from AX would leave .len/.cap as
|
||||
// whatever the stack held — silent miscompile. Worse,
|
||||
// primsize("str") returns 0 so esz would fall back to 8, also
|
||||
// collapsing the per-element stride (element i+1 would overwrite
|
||||
// element i's would-be .len half). Detect the str/slice element
|
||||
// case up front so both esz and the store path are right.
|
||||
// (primsize's default-to-8-on-zero pattern is brittle for
|
||||
// composites generally. The str/slice element now stores all 3
|
||||
// words; [N]tagged element arrays still hit the gap, task #12.)
|
||||
if (rhs.kind == nkind.N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
@@ -1773,6 +1774,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
|| esubti.kind == tykind.TY_ARRAY
|
||||
|| esubti.kind == tykind.TY_TUPLE);
|
||||
if (isagg) { esz = esubti.size: i32; };
|
||||
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
|
||||
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
|
||||
// branch above, so esz stayed the 8 sentinel (wrong stride,
|
||||
// the -96-vs-80 cs!=ww frame divergence) and the scalar
|
||||
// store dropped .len/.cap. Size it from the stamped tinfo
|
||||
// and route it through the 3-word header store below.
|
||||
let isslicel: bool = esubti != nil
|
||||
&& esubti.kind == tykind.TY_SLICE;
|
||||
if (isslicel) { esz = esubti.size: i32; };
|
||||
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
|
||||
// neither a builtin prim (primsize=0 above, so esz stayed
|
||||
// the 8 sentinel) nor an aggregate, so the scalar store kept
|
||||
@@ -1862,13 +1872,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
}; };
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
if (isstrel) {
|
||||
if (isstrel || isslicel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + idx * esz + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
@@ -1906,13 +1919,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
if (isstrel) {
|
||||
if (isstrel || isslicel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + idx * esz + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
|
||||
@@ -29518,16 +29518,17 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// after the last value (an nkind.N_FIELD with str=="...") fills the
|
||||
// remaining slots up to the declared length with that value.
|
||||
//
|
||||
// str element (16B = ptr+len) needs both halves stored. cgstrlit
|
||||
// / cgident leave a str as (AX=ptr, BX=len) and a single MOVQ
|
||||
// from AX would leave .len as whatever the stack held — silent
|
||||
// miscompile. Worse, primsize("str") returns 0 so esz would fall
|
||||
// back to 8, also collapsing the per-element stride (element i+1
|
||||
// would overwrite element i's would-be .len half). Detect the
|
||||
// str-element case up front so both esz and the store path are
|
||||
// right. (primsize's default-to-8-on-zero pattern is brittle for
|
||||
// composites generally; same gap blocks slice / struct / tuple /
|
||||
// tagged element arrays — tracked as a follow-up.)
|
||||
// str/slice element (24B = ptr+len+cap, post-#1) needs all 3
|
||||
// words stored. cgstrlit / cgident leave it as (AX=ptr, BX=len,
|
||||
// CX=cap) and a single MOVQ from AX would leave .len/.cap as
|
||||
// whatever the stack held — silent miscompile. Worse,
|
||||
// primsize("str") returns 0 so esz would fall back to 8, also
|
||||
// collapsing the per-element stride (element i+1 would overwrite
|
||||
// element i's would-be .len half). Detect the str/slice element
|
||||
// case up front so both esz and the store path are right.
|
||||
// (primsize's default-to-8-on-zero pattern is brittle for
|
||||
// composites generally. The str/slice element now stores all 3
|
||||
// words; [N]tagged element arrays still hit the gap, task #12.)
|
||||
if (rhs.kind == nkind.N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
@@ -29559,6 +29560,15 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
|| esubti.kind == tykind.TY_ARRAY
|
||||
|| esubti.kind == tykind.TY_TUPLE);
|
||||
if (isagg) { esz = esubti.size: i32; };
|
||||
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
|
||||
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
|
||||
// branch above, so esz stayed the 8 sentinel (wrong stride,
|
||||
// the -96-vs-80 cs!=ww frame divergence) and the scalar
|
||||
// store dropped .len/.cap. Size it from the stamped tinfo
|
||||
// and route it through the 3-word header store below.
|
||||
let isslicel: bool = esubti != nil
|
||||
&& esubti.kind == tykind.TY_SLICE;
|
||||
if (isslicel) { esz = esubti.size: i32; };
|
||||
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
|
||||
// neither a builtin prim (primsize=0 above, so esz stayed
|
||||
// the 8 sentinel) nor an aggregate, so the scalar store kept
|
||||
@@ -29648,13 +29658,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
}; };
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
if (isstrel) {
|
||||
if (isstrel || isslicel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + idx * esz + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
@@ -29692,13 +29705,16 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
if (isstrel) {
|
||||
if (isstrel || isslicel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + idx * esz + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isfloatel) {
|
||||
emitline("\t");
|
||||
emitline(fmov);
|
||||
|
||||
Reference in New Issue
Block a user