cstage+selfhost+test: full-element store for [N]str array literals (#21)
[N]str array literals wrote only the .ptr half of each element.
cstage used esz=16 from `lu->sub->size` and a single per-element
MOVQ → .len trailed uninitialized stack residue. Wwstage was worse:
primsize("str")=0 fell through to esz=8, so element i+1's ptr-MOVQ
clobbered element i's .len slot, scrambling everything.
Worker-18 sidestepped during #18 by rewriting array primer rows to
[N]i64.
cstage cgen.c N_ARRLIT TY_STR branch: emit AX → base+i*16 then
BX → base+i*16+8. Repeat-`...` path mirrored. type_isstr handles
TY_UNTYPED_STR + TY_NAMED-aliased-str.
Wwstage cgenstmt.ww: isstrel flag conditionally drives the two-MOVQ
store in both the per-element walk and the repeat fill. The dispatch
loop was refactored to unify FIELD/ellipsis branches via isellip,
cleaning up the duplicated arms.
Wwstage cgenutil.ww slotsize/letslotsize: TNAME-"str" element gets
esz=16, replacing the primsize=0 → 8B fallback. Without this the
frame collapsed to 24B for [3]str.
Slice (24B), struct, tuple, tagged element arrays have the same root
cause but distinct width/layout concerns — deferred to #35 per rob.
Test 711 (arrlit_str_full): 7 rows × 2 stages = 14 fixtures —
str_lens_3el, str_ptrs_3el, str_repeat_5el (TK_ELLIPSIS), bool_3el,
rune_3el, i32_3el, i64_3el. Rune relies on the pre-existing esz==4
→ MOVL path (incidental correctness); sibling slot types pinned as
regression nets.
Followups filed: #34 (wwstage cgindex truncate on [N]str bare-let
read side, surfaced by this fix), #35 (composite element types),
#36 (primsize-returns-0-default-to-8 cleanup).
This commit is contained in:
@@ -7556,8 +7556,15 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
// Composite primitive: `str` is 16B
|
||||
// (ptr+len) — primsize returns 0 for
|
||||
// it, so it'd slot 8B without this.
|
||||
if (streq(elemn.str, "str")) {
|
||||
esz = 16;
|
||||
} else {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
};
|
||||
let cnt: i32 = 0;
|
||||
@@ -7665,8 +7672,14 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let en: str = elemn.str;
|
||||
// `str` is a composite primitive (ptr+len, 16B);
|
||||
// primsize returns 0 for it, so without this
|
||||
// explicit case a `[N]str` would slot 8B/elem,
|
||||
// collapsing the per-element stride and losing
|
||||
// every .len half.
|
||||
if (streq(en, "str")) { esz = 16; };
|
||||
let ps: i32 = primsize(en);
|
||||
if (ps > 0) { esz = ps; }
|
||||
if (esz == 8) { if (ps > 0) { esz = ps; }
|
||||
else {
|
||||
// Named struct / aliased type: size off
|
||||
// the structinfo if present, else follow
|
||||
@@ -7683,7 +7696,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
esz = slotsize(c, al);
|
||||
};
|
||||
}; };
|
||||
};
|
||||
}; };
|
||||
} else { if (elemn.kind == nkind.N_TTAGGED) {
|
||||
// Tagged-union element: full slot (8 tag +
|
||||
// padded max payload). Matches C cgen's
|
||||
@@ -15274,13 +15287,30 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// using the right width for the element type. Trailing `...`
|
||||
// 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.)
|
||||
if (rhs.kind == nkind.N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
let isstrel: bool = false;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
if (streq(elemn.str, "str")) {
|
||||
esz = 16;
|
||||
isstrel = true;
|
||||
} else {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
};
|
||||
let mop: str = tnodestoreop(c, elemn, esz);
|
||||
@@ -15288,33 +15318,37 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
let repeat: bool = false;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
let isellip: bool = false;
|
||||
if (e.kind == nkind.N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
repeat = true;
|
||||
e = nil;
|
||||
isellip = true;
|
||||
};
|
||||
};
|
||||
if (isellip) {
|
||||
e = nil;
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
if (isstrel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
// AX still holds the last stored value; fill remaining
|
||||
// slots up to the declared length with it.
|
||||
// AX (and BX for str) still holds the last stored value;
|
||||
// fill remaining slots up to the declared length with it.
|
||||
if (repeat) {
|
||||
let total: i32 = idx;
|
||||
if (n.lhs != nil) {
|
||||
@@ -15327,11 +15361,20 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
if (isstrel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -503,13 +503,30 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// using the right width for the element type. Trailing `...`
|
||||
// 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.)
|
||||
if (rhs.kind == nkind.N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
let isstrel: bool = false;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
if (streq(elemn.str, "str")) {
|
||||
esz = 16;
|
||||
isstrel = true;
|
||||
} else {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
};
|
||||
let mop: str = tnodestoreop(c, elemn, esz);
|
||||
@@ -517,33 +534,37 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
let repeat: bool = false;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
let isellip: bool = false;
|
||||
if (e.kind == nkind.N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
repeat = true;
|
||||
e = nil;
|
||||
isellip = true;
|
||||
};
|
||||
};
|
||||
if (isellip) {
|
||||
e = nil;
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
if (isstrel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
// AX still holds the last stored value; fill remaining
|
||||
// slots up to the declared length with it.
|
||||
// AX (and BX for str) still holds the last stored value;
|
||||
// fill remaining slots up to the declared length with it.
|
||||
if (repeat) {
|
||||
let total: i32 = idx;
|
||||
if (n.lhs != nil) {
|
||||
@@ -556,11 +577,20 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
if (isstrel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1346,8 +1346,15 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
// Composite primitive: `str` is 16B
|
||||
// (ptr+len) — primsize returns 0 for
|
||||
// it, so it'd slot 8B without this.
|
||||
if (streq(elemn.str, "str")) {
|
||||
esz = 16;
|
||||
} else {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
};
|
||||
let cnt: i32 = 0;
|
||||
@@ -1455,8 +1462,14 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let en: str = elemn.str;
|
||||
// `str` is a composite primitive (ptr+len, 16B);
|
||||
// primsize returns 0 for it, so without this
|
||||
// explicit case a `[N]str` would slot 8B/elem,
|
||||
// collapsing the per-element stride and losing
|
||||
// every .len half.
|
||||
if (streq(en, "str")) { esz = 16; };
|
||||
let ps: i32 = primsize(en);
|
||||
if (ps > 0) { esz = ps; }
|
||||
if (esz == 8) { if (ps > 0) { esz = ps; }
|
||||
else {
|
||||
// Named struct / aliased type: size off
|
||||
// the structinfo if present, else follow
|
||||
@@ -1473,7 +1486,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
esz = slotsize(c, al);
|
||||
};
|
||||
}; };
|
||||
};
|
||||
}; };
|
||||
} else { if (elemn.kind == nkind.N_TTAGGED) {
|
||||
// Tagged-union element: full slot (8 tag +
|
||||
// padded max payload). Matches C cgen's
|
||||
|
||||
@@ -7556,8 +7556,15 @@ export fn letslotsize(c: *cgen, n: *node) i32 = {
|
||||
let esz: i32 = 8;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
// Composite primitive: `str` is 16B
|
||||
// (ptr+len) — primsize returns 0 for
|
||||
// it, so it'd slot 8B without this.
|
||||
if (streq(elemn.str, "str")) {
|
||||
esz = 16;
|
||||
} else {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
};
|
||||
let cnt: i32 = 0;
|
||||
@@ -7665,8 +7672,14 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let en: str = elemn.str;
|
||||
// `str` is a composite primitive (ptr+len, 16B);
|
||||
// primsize returns 0 for it, so without this
|
||||
// explicit case a `[N]str` would slot 8B/elem,
|
||||
// collapsing the per-element stride and losing
|
||||
// every .len half.
|
||||
if (streq(en, "str")) { esz = 16; };
|
||||
let ps: i32 = primsize(en);
|
||||
if (ps > 0) { esz = ps; }
|
||||
if (esz == 8) { if (ps > 0) { esz = ps; }
|
||||
else {
|
||||
// Named struct / aliased type: size off
|
||||
// the structinfo if present, else follow
|
||||
@@ -7683,7 +7696,7 @@ fn slotsize(c: *cgen, typn: *node) i32 = {
|
||||
esz = slotsize(c, al);
|
||||
};
|
||||
}; };
|
||||
};
|
||||
}; };
|
||||
} else { if (elemn.kind == nkind.N_TTAGGED) {
|
||||
// Tagged-union element: full slot (8 tag +
|
||||
// padded max payload). Matches C cgen's
|
||||
@@ -15274,13 +15287,30 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
// using the right width for the element type. Trailing `...`
|
||||
// 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.)
|
||||
if (rhs.kind == nkind.N_ARRLIT) {
|
||||
let elemn: *node = n.lhs.lhs;
|
||||
let esz: i32 = 8;
|
||||
let isstrel: bool = false;
|
||||
if (elemn != nil) {
|
||||
if (elemn.kind == nkind.N_TNAME) {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
if (streq(elemn.str, "str")) {
|
||||
esz = 16;
|
||||
isstrel = true;
|
||||
} else {
|
||||
let ps: i32 = primsize(elemn.str);
|
||||
if (ps > 0) { esz = ps; };
|
||||
};
|
||||
};
|
||||
};
|
||||
let mop: str = tnodestoreop(c, elemn, esz);
|
||||
@@ -15288,33 +15318,37 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
let repeat: bool = false;
|
||||
let e: *node = rhs.list;
|
||||
for (e != nil) {
|
||||
let isellip: bool = false;
|
||||
if (e.kind == nkind.N_FIELD) {
|
||||
if (streq(e.str, "...")) {
|
||||
repeat = true;
|
||||
e = nil;
|
||||
isellip = true;
|
||||
};
|
||||
};
|
||||
if (isellip) {
|
||||
e = nil;
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
if (isstrel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
} else {
|
||||
cgexpr(c, e);
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
idx += 1;
|
||||
e = e.next;
|
||||
};
|
||||
};
|
||||
// AX still holds the last stored value; fill remaining
|
||||
// slots up to the declared length with it.
|
||||
// AX (and BX for str) still holds the last stored value;
|
||||
// fill remaining slots up to the declared length with it.
|
||||
if (repeat) {
|
||||
let total: i32 = idx;
|
||||
if (n.lhs != nil) {
|
||||
@@ -15327,11 +15361,20 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
for (idx < total) {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
if (isstrel) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + idx * esz + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\t");
|
||||
emitline(mop);
|
||||
emitline("\tAX, ");
|
||||
emitoff((off + idx * esz): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
idx += 1;
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user