w6c_ww: exclude bare str-literal from indexed .cap shuffle (fix #13 symmetry break)
The #13 .cap read-fix gated the wwstage CX→AX shuffle on the base's type being TY_SLICE/TY_STR, on the assumption that a bare string literal types as untyped_str and so misses the gate (matching cstage, whose cap-shuffle lives only in the typed pseudo-field branch). That assumption is false on wwstage: its checker stamps N_STRLIT as `str` (check.ww:2322), not untyped_str as cstage does (check.c:1079). So `"abc".cap` passed the TY_STR gate and emitted a stray `MOVQ CX, AX` on wwstage only — while cstage's untyped catch-all never shuffles it — a rule-10 byte-id break. A string literal's cgexpr loads only AX=ptr/BX=len (cgen.ww N_STRLIT), never a CX cap, so the shuffle was garbage on top of divergent. Exclude N_STRLIT from the gate: `"abc".cap` now returns AX unshuffled on both stages, byte-identical. The typed `t[i].cap` path (lhs N_INDEX) is unaffected. The underlying N_STRLIT type divergence (cstage untyped_str vs wwstage str) is a separate latent checker issue, filed for follow-up; this commit keeps the cgen byte-identical regardless. 683: new BYTEID_ONLY row str_lit_cap_symmetry pins the edge (asm-byte-id asserted; runtime value is a link-time address). 39/39 ok; test-unit 251/251; smoke cs==ww; sizelint clean. combined.ww (w6c + wwdump) regen'd.
This commit is contained in:
@@ -22614,14 +22614,18 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// .cap on a non-ident base (indexed element `t[i].cap`, call,
|
||||
// dot-slice): cgexpr leaves the full {ptr,len,cap} header via
|
||||
// cgslicehdr — shuffle CX→AX. The shuffle fires ONLY for a TYPED
|
||||
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase); an untyped
|
||||
// str literal (`"abc".cap`) leaves only AX=ptr/BX=len and must return
|
||||
// AX unshuffled. Mirrors cstage cgen.c: the cap-shuffle lives in the
|
||||
// typed pseudo-field branch (`u->kind == TY_SLICE/TY_STR`), never the
|
||||
// untyped catch-all. #13 read-fix, sibling of the #20 store.
|
||||
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase) that is NOT
|
||||
// a bare string literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
|
||||
// (cgen.ww), never a CX cap, so `"abc".cap` must return AX unshuffled.
|
||||
// cstage reaches that outcome by typing N_STRLIT as untyped_str
|
||||
// (cgen.c:8456 catch-all, no cap shuffle); the wwstage checker types
|
||||
// N_STRLIT as `str` instead (check.ww:2322 vs cstage check.c:1079 —
|
||||
// divergence filed separately), so the TY_STR kind-gate alone would
|
||||
// wrongly fire. The N_STRLIT exclusion keeps this byte-identical with
|
||||
// cstage. #13 read-fix, sibling of the #20 store.
|
||||
if (streq(fld, "cap")) {
|
||||
cgexpr(c, lhs);
|
||||
if (lhs != nil) {
|
||||
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
|
||||
let lu: *tinfo = lhs.type_: *tinfo;
|
||||
for (lu != nil && lu.kind == tykind.TY_NAMED) { lu = lu.under; };
|
||||
if (lu != nil && (lu.kind == tykind.TY_SLICE
|
||||
|
||||
@@ -2890,14 +2890,18 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// .cap on a non-ident base (indexed element `t[i].cap`, call,
|
||||
// dot-slice): cgexpr leaves the full {ptr,len,cap} header via
|
||||
// cgslicehdr — shuffle CX→AX. The shuffle fires ONLY for a TYPED
|
||||
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase); an untyped
|
||||
// str literal (`"abc".cap`) leaves only AX=ptr/BX=len and must return
|
||||
// AX unshuffled. Mirrors cstage cgen.c: the cap-shuffle lives in the
|
||||
// typed pseudo-field branch (`u->kind == TY_SLICE/TY_STR`), never the
|
||||
// untyped catch-all. #13 read-fix, sibling of the #20 store.
|
||||
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase) that is NOT
|
||||
// a bare string literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
|
||||
// (cgen.ww), never a CX cap, so `"abc".cap` must return AX unshuffled.
|
||||
// cstage reaches that outcome by typing N_STRLIT as untyped_str
|
||||
// (cgen.c:8456 catch-all, no cap shuffle); the wwstage checker types
|
||||
// N_STRLIT as `str` instead (check.ww:2322 vs cstage check.c:1079 —
|
||||
// divergence filed separately), so the TY_STR kind-gate alone would
|
||||
// wrongly fire. The N_STRLIT exclusion keeps this byte-identical with
|
||||
// cstage. #13 read-fix, sibling of the #20 store.
|
||||
if (streq(fld, "cap")) {
|
||||
cgexpr(c, lhs);
|
||||
if (lhs != nil) {
|
||||
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
|
||||
let lu: *tinfo = lhs.type_: *tinfo;
|
||||
for (lu != nil && lu.kind == tykind.TY_NAMED) { lu = lu.under; };
|
||||
if (lu != nil && (lu.kind == tykind.TY_SLICE
|
||||
|
||||
@@ -22614,14 +22614,18 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// .cap on a non-ident base (indexed element `t[i].cap`, call,
|
||||
// dot-slice): cgexpr leaves the full {ptr,len,cap} header via
|
||||
// cgslicehdr — shuffle CX→AX. The shuffle fires ONLY for a TYPED
|
||||
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase); an untyped
|
||||
// str literal (`"abc".cap`) leaves only AX=ptr/BX=len and must return
|
||||
// AX unshuffled. Mirrors cstage cgen.c: the cap-shuffle lives in the
|
||||
// typed pseudo-field branch (`u->kind == TY_SLICE/TY_STR`), never the
|
||||
// untyped catch-all. #13 read-fix, sibling of the #20 store.
|
||||
// slice/str base (kind TY_SLICE/TY_STR after NAMED-chase) that is NOT
|
||||
// a bare string literal: N_STRLIT's cgexpr loads only AX=ptr/BX=len
|
||||
// (cgen.ww), never a CX cap, so `"abc".cap` must return AX unshuffled.
|
||||
// cstage reaches that outcome by typing N_STRLIT as untyped_str
|
||||
// (cgen.c:8456 catch-all, no cap shuffle); the wwstage checker types
|
||||
// N_STRLIT as `str` instead (check.ww:2322 vs cstage check.c:1079 —
|
||||
// divergence filed separately), so the TY_STR kind-gate alone would
|
||||
// wrongly fire. The N_STRLIT exclusion keeps this byte-identical with
|
||||
// cstage. #13 read-fix, sibling of the #20 store.
|
||||
if (streq(fld, "cap")) {
|
||||
cgexpr(c, lhs);
|
||||
if (lhs != nil) {
|
||||
if (lhs != nil && lhs.kind != nkind.N_STRLIT) {
|
||||
let lu: *tinfo = lhs.type_: *tinfo;
|
||||
for (lu != nil && lu.kind == tykind.TY_NAMED) { lu = lu.under; };
|
||||
if (lu != nil && (lu.kind == tykind.TY_SLICE
|
||||
|
||||
@@ -82,6 +82,13 @@ runwait(const char *cmd)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* want == BYTEID_ONLY: the row's runtime value is non-deterministic (a
|
||||
* link-time address), so only the cstage/wwstage asm-byte-id is asserted;
|
||||
* the build must still succeed on both stages. Used for the `"abc".cap`
|
||||
* symmetry edge (a string-literal .cap is meaningless garbage on both
|
||||
* stages, but rule 10 still requires byte-identical asm). */
|
||||
#define BYTEID_ONLY (-2147483647 - 1)
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
@@ -177,6 +184,20 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
/* #13 symmetry edge: `.cap` of a BARE string literal. The wwstage
|
||||
* checker types N_STRLIT as `str` (cstage types it untyped_str), so
|
||||
* the .cap kind-gate would wrongly shuffle CX→AX on wwstage only —
|
||||
* but a literal's cgexpr never loads a CX cap, and cstage never
|
||||
* shuffles it, so both must return AX (ptr) unshuffled. Byte-id only:
|
||||
* the value is a link-time address (non-deterministic). Pre-fix this
|
||||
* row's asm DIFFERED (wwstage had the stray MOVQ CX, AX). */
|
||||
{ "str_lit_cap_symmetry",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\treturn \"abc\".cap: i32;\n"
|
||||
"};\n",
|
||||
BYTEID_ONLY },
|
||||
|
||||
{ "slice_ptr",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
@@ -349,7 +370,12 @@ main(void)
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
/* BYTEID_ONLY rows assert asm byte-id below; here only
|
||||
* the build must succeed (run_driver returns -1 on a
|
||||
* build/run failure). The exit value is ignored. */
|
||||
int bad = rows[i].want == BYTEID_ONLY
|
||||
? (got == -1) : (got != rows[i].want);
|
||||
if (bad) {
|
||||
fprintf(stderr,
|
||||
"arr_strslice_elem[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
|
||||
Reference in New Issue
Block a user