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:
2026-05-16 10:38:23 +09:00
parent f906081c8c
commit cbb9fbbb65
7 changed files with 483 additions and 85 deletions

View File

@@ -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