selfhost+test: nodeisstr handles N_INDEX of [N]str (#34)
wwstage's nodeisstr (cgenutil) didn't recognize N_INDEX-of-[N]str. cgindex emitted only the ptr-half MOVQ when the result was used as a str arg (call, .len access, str streq), so the .len half read stack residue. Surfaced by worker-21 during #21 dev — pre-#21 slotsize=24B masked the read-side defect; post-#21 (16B stride) exposed it. cstage's typed-AST node_isstr handles this naturally; wwstage's untyped pattern walks the base ident's tnode shape. Added N_INDEX arm to nodeisstr: walk the indexed base's tnode through N_TARRAY / N_TSLICE / N_TPTR.lhs, return isstrtype on the element. Mirrors cgindex's own base-type walk byte-for-byte in shape so the two now agree on load-shape decisions. Not covered (separate bugs, separately filed): - N_UN(TK_STAR) of *str — cgun itself never loads .len into BX. - tuple `.1` of str — N_TTUPLE path has its own load shape. - alias-typed base (`type a = [N]str`) — N_TNAME isn't peeled; cgindex doesn't peel it either, so agreement holds. Outside #34 scope. Test 711: 3 new rows — barelet_index_call_arg (streq direct arg), nested_call_index_arg (f(g(argv[i])) — nested-call recursion), barelet_index_len_arg (sister regression-pin for cgindex element stride in bare-let context; pins a different code path that was already correct post-#21). The pre-existing wwstage `..findflag(SB)` symbol-mangling bug in getopttest wwstage build is filed as task #37, not in this commit's scope.
This commit is contained in:
@@ -6731,6 +6731,21 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
|
||||
// nodeisstr — best-effort surface check: does this expression
|
||||
// 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 (struct field / chained / pseudo-fields excluded), 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
|
||||
// help. Tracked alongside the broader cgun-load-shape gap.
|
||||
// - N_DOT to a tuple positional `t.1` of a str element — wwstage's
|
||||
// cgdot loads (AX, BX) but tuple-as-arg has independent issues.
|
||||
fn nodeisstr(c: *cgen, n: *node) bool = {
|
||||
if (n == nil) { return false; };
|
||||
let k: nkind = n.kind;
|
||||
@@ -6758,6 +6773,34 @@ 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.
|
||||
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) {
|
||||
let elem: *node = nil;
|
||||
let bk: nkind = bt.kind;
|
||||
if (bk == nkind.N_TARRAY) { elem = bt.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { elem = bt.lhs; };
|
||||
if (bk == nkind.N_TPTR) { elem = bt.lhs; };
|
||||
if (elem != nil) {
|
||||
return isstrtype(c, elem);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
let base: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
|
||||
@@ -488,6 +488,21 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
|
||||
// nodeisstr — best-effort surface check: does this expression
|
||||
// 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 (struct field / chained / pseudo-fields excluded), 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
|
||||
// help. Tracked alongside the broader cgun-load-shape gap.
|
||||
// - N_DOT to a tuple positional `t.1` of a str element — wwstage's
|
||||
// cgdot loads (AX, BX) but tuple-as-arg has independent issues.
|
||||
fn nodeisstr(c: *cgen, n: *node) bool = {
|
||||
if (n == nil) { return false; };
|
||||
let k: nkind = n.kind;
|
||||
@@ -515,6 +530,34 @@ 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.
|
||||
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) {
|
||||
let elem: *node = nil;
|
||||
let bk: nkind = bt.kind;
|
||||
if (bk == nkind.N_TARRAY) { elem = bt.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { elem = bt.lhs; };
|
||||
if (bk == nkind.N_TPTR) { elem = bt.lhs; };
|
||||
if (elem != nil) {
|
||||
return isstrtype(c, elem);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
let base: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
|
||||
@@ -6731,6 +6731,21 @@ fn nodeisslice(c: *cgen, n: *node) bool = {
|
||||
// nodeisstr — best-effort surface check: does this expression
|
||||
// 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 (struct field / chained / pseudo-fields excluded), 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
|
||||
// help. Tracked alongside the broader cgun-load-shape gap.
|
||||
// - N_DOT to a tuple positional `t.1` of a str element — wwstage's
|
||||
// cgdot loads (AX, BX) but tuple-as-arg has independent issues.
|
||||
fn nodeisstr(c: *cgen, n: *node) bool = {
|
||||
if (n == nil) { return false; };
|
||||
let k: nkind = n.kind;
|
||||
@@ -6758,6 +6773,34 @@ 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.
|
||||
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) {
|
||||
let elem: *node = nil;
|
||||
let bk: nkind = bt.kind;
|
||||
if (bk == nkind.N_TARRAY) { elem = bt.lhs; };
|
||||
if (bk == nkind.N_TSLICE) { elem = bt.lhs; };
|
||||
if (bk == nkind.N_TPTR) { elem = bt.lhs; };
|
||||
if (elem != nil) {
|
||||
return isstrtype(c, elem);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
return false;
|
||||
};
|
||||
if (k == nkind.N_DOT) {
|
||||
let base: *node = n.lhs;
|
||||
let fld: str = n.str;
|
||||
|
||||
Reference in New Issue
Block a user