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

@@ -5974,17 +5974,33 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
* Walk elements in declaration order, store each at off + i*esz
* using the right width for the element type. The trailing
* `...` repeat marker (an N_FIELD with str=="...") fills the
* remaining slots with the last value. */
* remaining slots with the last value.
*
* str element (16B = ptr+len) needs both halves stored: cgexpr
* leaves a str as (AX=ptr, BX=len), and a single MOVQ from AX
* would leave .len as whatever the stack held — silent
* miscompile. The per-element store branches on TY_STR before
* falling through to the scalar MOVB/MOVL/MOVQ path. Slice
* (24B) and struct/tuple/tagged element arrays land in the
* same multi-word-store gap; the read side (cgindex of an
* [N]slice) has its own truncating-to-ptr bug, so slice
* end-to-end repros surface sub-issues — both halves of the
* slice-element fix are tracked as a follow-up. */
if (n->rhs && n->rhs->kind == N_ARRLIT && lu
&& lu->kind == TY_ARRAY) {
int esz = lu->sub ? (int)lu->sub->size : 1;
Type *esub = lu->sub;
int esz = esub ? (int)esub->size : 1;
int is_str_el = type_isstr(esub);
int op = A_MOVQ;
if (esz == 1) op = A_MOVB;
else if (esz == 4) op = A_MOVL;
/* esz == 2 (i16/u16) falls through to MOVQ — over-writes
* by 6B; the next element store rewrites the high half.
* For the last element this trails 6 bytes into the next
* stack slot. Add MOVW to w6a if real i16 arrays land. */
if (!is_str_el) {
if (esz == 1) op = A_MOVB;
else if (esz == 4) op = A_MOVL;
/* esz == 2 (i16/u16) falls through to MOVQ —
* over-writes by 6B; the next element store
* rewrites the high half. For the last element
* this trails 6 bytes into the next stack slot.
* Add MOVW to w6a if real i16 arrays land. */
}
int idx = 0;
Node *last = NULL;
int repeat = 0;
@@ -5995,16 +6011,33 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
break;
}
cgexpr(c, e, *locals);
ins2(c, op, areg(D_AX),
amem(D_BP, off + idx * esz));
int base = off + idx * esz;
if (is_str_el) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, base));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, base + 8));
} else {
ins2(c, op, areg(D_AX),
amem(D_BP, base));
}
last = e;
idx++;
}
if (repeat && last) {
/* fill remaining slots with the value already in AX. */
/* fill remaining slots with the value still in
* AX (and BX for str). */
while (idx < (int)lu->alen) {
ins2(c, op, areg(D_AX),
amem(D_BP, off + idx * esz));
int base = off + idx * esz;
if (is_str_el) {
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BP, base));
ins2(c, A_MOVQ, areg(D_BX),
amem(D_BP, base + 8));
} else {
ins2(c, op, areg(D_AX),
amem(D_BP, base));
}
idx++;
}
}