wcc/ww: nodeisslice/nodeisstr gain the N_INDEX arm (stamp-keyed)

A slice/str ELEMENT of an indexed expression passed as a call-arg
pushed one word instead of the 24B/16B header — the predicates had no
N_INDEX arm, so element-typed args fell to the scalar path (review
findings #45/#46). Read the element-type stamp; dual-stage rows in
989_idxarg_run pin cs==ww (red 2/8 on pre-fix binaries).
This commit is contained in:
2026-06-12 00:04:22 +09:00
parent 6afa72afea
commit cbbfd1a4a6
5 changed files with 303 additions and 249 deletions

View File

@@ -1214,6 +1214,17 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
if (k == nkind.N_DOT) {
return typeisslice(n.type_: *tinfo);
};
// #45 (F7-c2): `arr[i]` whose element is a slice. cgindex leaves
// (AX=ptr, BX=len, CX=cap) for a 24B element, but with no N_INDEX
// arm here pushargsrev fell to the scalar default — one PUSHQ AX —
// and the cgcall pop under-drained by 2 words (take(rows[1]) pushed
// 1 word, callee read garbage len). Read the checker-stamped element
// type (exprtype N_INDEX stamps n.type_, check.ww:2777), mirroring
// cstage node_isslice = type_isslice(n->type) and the N_INDEX arms of
// nodeisstr (below) + nodeisunsigned (:1798).
if (k == nkind.N_INDEX) {
return typeisslice(n.type_: *tinfo);
};
return false;
};
@@ -1221,14 +1232,14 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
// evaluate to a str value? Used to drive the call-arg push convention
// (str args take two slots: ptr + len).
//
// TODO(#11): every consumer of "is-str" here reconstructs the answer
// from raw N_kind because wwstage has no typed AST. Each new expression
// shape needs an explicit arm or it silently falls through to false,
// which downstream drops the second slot (BX/len) at the call site.
// A typed AST check (cstage reads n->type) would replace this whole
// function. Covered arms below: N_STRLIT, N_IDENT (local/let-typed),
// N_CALL (return type), N_INDEX (element type of [N]T / []T / *T base),
// N_DOT (reads checker-stamped n.type_ — #56 A.6.3h), N_CAST.
// The value-bearing arms (N_IDENT local, N_INDEX, N_DOT) read the
// checker-stamped n.type_ — the typed-AST check the prior TODO(#11)
// wanted, mirroring cstage node_isstr = type_isstr(n->type). The
// remaining N_kind arms (N_STRLIT, N_CALL, N_CAST) carry their own
// recognizer because the stamp is on a sub-node (callee return / cast
// target), not on `n` itself. Covered: N_STRLIT, N_IDENT (local stamp /
// def stamp), N_CALL (return type), N_INDEX (element stamp — #46 F7-c2),
// N_DOT (n.type_ — #56 A.6.3h), N_CAST.
// Not covered (separate bugs / out of scope):
// - N_UN(TK_STAR) of `*str` — cgun itself emits only `MOVQ (AX), AX`
// and never loads .len into BX; fixing the recognizer alone won't
@@ -1290,82 +1301,17 @@ fn nodeisstr(c: *cgen, n: *node) bool = {
};
return false;
};
// N_INDEX: `arr[i]` whose base is an indexable type carrying a
// str element. cgindex correctly loads (AX=ptr, BX=len) for a
// 16B element; without this arm pushargsrev only pushes AX and
// the call-arg pop reads .len from stack residue. Mirror of
// cstage's node_isstr → type_isstr(n->type), where n->type is
// the resolved element type after check.
// #46 (F7-c2): `arr[i]` whose element is a str. The prior structural
// walk only recognised N_IDENT and N_DOT bases (idxelemtn off the
// base's tnode), so a chained / call / slice base (take(m[1][1]))
// fell through to `return false` → 1-word push, callee read garbage
// .len. Read the checker-stamped element type instead (exprtype
// N_INDEX stamps n.type_, check.ww:2777), mirroring cstage node_isstr
// = type_isstr(n->type) and nodeisunsigned's N_INDEX arm (:1798). The
// base-kind whitelist is gone — every base shape routes through the
// one stamp read.
if (k == nkind.N_INDEX) {
let base: *node = n.lhs;
if (base != nil) {
if (base.kind == nkind.N_IDENT) {
let bt: *node = nil;
let lc: *local = localfindnode(c, base.str);
if (lc != nil) { bt = lc.tnode; }
else { bt = letvartnode(c, base.str); };
if (bt != nil) {
// idxelemtn: `*[N]T` drills to the pointee
// array's element (#61).
let elem: *node = idxelemtn(bt);
if (elem != nil) {
return isstrtype(c, elem);
};
};
};
// N_INDEX through a struct field: e.g. cmd.argsptr[i]
// where argsptr: *str. cgindex correctly loads the
// (ptr, len) pair off the stamped element size; without
// this arm pushargsrev would only push AX and lose .len.
if (base.kind == nkind.N_DOT) {
let fld: str = base.str;
if (streq(fld, "ptr")) { return false; };
if (streq(fld, "len")) { return false; };
if (streq(fld, "cap")) { return false; };
let inner: *node = base.lhs;
if (inner != nil) {
if (inner.kind == nkind.N_IDENT) {
let lc: *local = localfindnode(c, inner.str);
if (lc != nil) {
let tn: *node = lc.tnode;
let sname: str;
sname.ptr = nil; sname.len = 0;
if (tn != nil) {
if (tn.kind == nkind.N_TNAME) { sname = tn.str; };
if (tn.kind == nkind.N_TPTR) {
let pinner: *node = tn.lhs;
if (pinner != nil) {
if (pinner.kind == nkind.N_TNAME) {
sname = pinner.str;
};
};
};
};
if (sname.len > 0) {
let si: *structinfo = structlookup(c, sname);
if (si != nil) {
let fi: *fieldinfo = si.fields;
for (fi != nil) {
if (streq(fi.fname, fld)) {
let ft: *node = fi.tnode;
if (ft != nil) {
// idxelemtn: `*[N]T` drill (#61).
let elem: *node = idxelemtn(ft);
if (elem != nil) {
return isstrtype(c, elem);
};
};
};
fi = fi.finext;
};
};
};
};
};
};
};
};
return false;
return typeisstr(n.type_: *tinfo);
};
// N_DOT: read the checker-stamped n.type_. Struct field, nested
// dot, value-struct hops, and pseudo-fields (.ptr/.len/.cap) all