w6c+wwstage: len() of tuple-element slice reads .len not .ptr (#235)

len() special-cased only a plain N_IDENT slice operand (load .len at
BP+off+8) and an array operand (fold $alen); every other shape fell back
to a bare cgexpr(operand), which for a slice leaves AX=.ptr. A tuple-
element read (t.N) loads only AX=.ptr, so len(t.N) on a slice/str tuple
element returned the slice's .ptr word AS its length — a silent
miscompile, gate-blind because the bootstrap never does len() on a
slice-typed tuple element (sibling of the #234/#237 tuple-sret cluster).

Both stages: detect a slice/str tuple-element len() operand and load the
element's .len word directly at BP + element_off + 8, mirroring the
N_IDENT slice arm and the tuple-field-offset walk (element_off sums
preceding element sizes through the type table). Byte-identical asm
(rule 10). The separate tuple-element-read full-header gap is #238; a
leading-scalar mixed-tuple has its own pre-existing sret-layout cs/ww
divergence, filed apart from #235.

Test 903_tuple_elem_slice_len_run: 4 slice/str-only tuple rows (two/
three slices, str+slice, slice+str; distinct lengths), build+run both
drivers + cs==ww byte-id. 12/12 ok.
This commit is contained in:
2026-06-01 18:17:07 +09:00
parent 20fe5419d2
commit 6acddc3a82
6 changed files with 428 additions and 0 deletions

View File

@@ -3809,6 +3809,51 @@ fn cgcall(c: *cgen, n: *node) void = {
return;
};
};
// #235: len() of a tuple-element slice/str
// (`len(t.N)`). The tuple-element read leaves
// only AX=.ptr — no slice-header sibling (that
// gap is #238) — so the cgexpr fallback below
// returned .ptr AS the length. Load the element's
// .len word directly at BP + element_off + 8,
// mirroring the N_IDENT slice arm above and the
// tuple-field-offset walk (cgenexpr.ww N_TTUPLE).
if ((u.kind == tykind.TY_SLICE
|| u.kind == tykind.TY_STR)
&& a.kind == nkind.N_DOT
&& a.lhs != nil
&& a.lhs.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, a.lhs.str);
if (lc != nil) {
let tn: *node = lc.tnode;
for (tn != nil && tn.kind == nkind.N_TNAME) {
tn = aliaslookup(c, tn.str);
};
if (tn != nil) {
if (tn.kind == nkind.N_TTUPLE) {
let idx: i32 = fldnumidx(a.str);
if (idx >= 0) {
let tp: *node = tn.list;
let foff: i32 = 0;
let i: i32 = 0;
for (i < idx) {
if (tp == nil) { i = idx; }
else {
foff += slotsize(c, tp.lhs);
tp = tp.next;
i += 1;
};
};
if (tp != nil) {
emitline("\tMOVQ\t");
emitoff((lc.off + foff + 8): i64);
emitline("(BP), AX\n");
return;
};
};
};
};
};
};
if (u.kind == tykind.TY_ARRAY) {
emitline("\tMOVQ\t$");
emitint(u.alen: i64);