`t[i].cap` (t a `[N][]u8` / `[N]str`) miscompiled in BOTH stages, divergently — the read-side sibling of #20's store fix. cgexpr on the indexed element leaves the full {ptr,len,cap} header (AX/BX/CX via cgslicehdr), but the `.cap` field-selector never shuffled CX→AX: cstage's typed pseudo-field else-branch handled only .ptr/.len, so `.cap` fell through returning AX=.ptr; wwstage's cgdot non-ident catch-all likewise handled only .ptr/.len, emitting no read (stale AX). `t[i].len` already worked (BX→AX shuffle) — only `.cap` was missing. Fix mirrors the .len shuffle: add the .cap CX→AX arm in both stages. The shuffle fires ONLY for a typed slice/str base (TY_SLICE/TY_STR after NAMED-chase); an untyped str literal (`"abc".cap`) leaves only AX=ptr/BX=len and must return AX unshuffled — keeping the wwstage catch-all byte-identical with cstage, whose cap-shuffle lives in the typed branch, not the untyped catch-all. Validated direct `t[i].cap` (slice + str, elements 0/1) against the whole-element-copy oracle (`let q=t[i]; q.cap`, made correct by #20), plus .len-after-index regression pins, in test 683; dual-stage runtime + byte-id (36/36 ok). combined.ww regenerated.
This commit is contained in:
@@ -7991,12 +7991,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
areg(D_AX));
|
||||
} else {
|
||||
/* Evaluate the str/slice expression — leaves
|
||||
* (AX=ptr, BX=len) for str. .ptr returns AX,
|
||||
* .len shuffles BX→AX. */
|
||||
* the full (AX=ptr, BX=len, CX=cap) header
|
||||
* (cgslicehdr) for an indexed element / non-ident
|
||||
* base. .ptr returns AX, .len shuffles BX→AX,
|
||||
* .cap shuffles CX→AX. The .cap shuffle is the
|
||||
* #13 read-fix (sibling of the #20 store): pre-fix
|
||||
* the else-arm handled only .len, so `t[i].cap`
|
||||
* fell through returning AX=.ptr. */
|
||||
cgexpr(c, n->lhs, locals);
|
||||
if (lenfld)
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
areg(D_AX));
|
||||
else if (capfld)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
areg(D_AX));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -22611,6 +22611,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tBX, AX\n");
|
||||
return;
|
||||
};
|
||||
// .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.
|
||||
if (streq(fld, "cap")) {
|
||||
cgexpr(c, lhs);
|
||||
if (lhs != nil) {
|
||||
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
|
||||
|| lu.kind == tykind.TY_STR)) {
|
||||
emitline("\tMOVQ\tCX, AX\n");
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Chained struct-field-via-ptr-via-ptr access:
|
||||
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
||||
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
||||
|
||||
@@ -2887,6 +2887,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tBX, AX\n");
|
||||
return;
|
||||
};
|
||||
// .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.
|
||||
if (streq(fld, "cap")) {
|
||||
cgexpr(c, lhs);
|
||||
if (lhs != nil) {
|
||||
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
|
||||
|| lu.kind == tykind.TY_STR)) {
|
||||
emitline("\tMOVQ\tCX, AX\n");
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Chained struct-field-via-ptr-via-ptr access:
|
||||
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
||||
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
||||
|
||||
@@ -22611,6 +22611,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tBX, AX\n");
|
||||
return;
|
||||
};
|
||||
// .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.
|
||||
if (streq(fld, "cap")) {
|
||||
cgexpr(c, lhs);
|
||||
if (lhs != nil) {
|
||||
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
|
||||
|| lu.kind == tykind.TY_STR)) {
|
||||
emitline("\tMOVQ\tCX, AX\n");
|
||||
};
|
||||
};
|
||||
return;
|
||||
};
|
||||
// Chained struct-field-via-ptr-via-ptr access:
|
||||
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
||||
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
||||
|
||||
@@ -21,13 +21,16 @@
|
||||
* for `is_str_el || is_slice_el`; wwstage adds isslicel (TY_SLICE ->
|
||||
* esz = esubti.size, fixing the stride) and the matching 3-word store.
|
||||
*
|
||||
* Cap is validated via a WHOLE-ELEMENT COPY (`let q = t[i]; q.cap`), NOT
|
||||
* a direct `t[i].cap` read: the `.cap` field-extract on an INDEXED slice/
|
||||
* str element is a SEPARATE, still-open bug (returns .ptr on cstage,
|
||||
* emits no read on wwstage — divergent), the "cgindex truncating-to-ptr"
|
||||
* sibling cited at cgen.c:9039-9040, filed as task #13. A whole-element
|
||||
* copy reads the stored cap through the (correct) ident-load path, so it
|
||||
* exercises THIS fix's stored cap word without hitting #13.
|
||||
* Cap is validated BOTH ways: a WHOLE-ELEMENT COPY (`let q = t[i]; q.cap`)
|
||||
* AND a DIRECT `t[i].cap` read. The direct read was a SEPARATE bug (task
|
||||
* #13, the #20 store's read-sibling): the `.cap` field-extract on an
|
||||
* INDEXED slice/str element returned .ptr on cstage (it shuffled only
|
||||
* .len BX→AX, leaving AX=.ptr for cap) and emitted NO read on wwstage
|
||||
* (the cgdot non-ident catch-all handled only .ptr/.len) — divergent.
|
||||
* Fix (BOTH stages, byte-id): cgexpr leaves the full {ptr,len,cap} header
|
||||
* via cgslicehdr for an indexed element, so .cap shuffles CX→AX, the twin
|
||||
* of the .len BX→AX shuffle. The direct-cap rows must equal the
|
||||
* whole-element-copy oracle (which #20 made correct).
|
||||
*
|
||||
* Mutation-sanity (the per-word coverage): the .len rows fail if the
|
||||
* store drops the .len word (the pre-fix slice 1-word store), and the
|
||||
@@ -128,6 +131,52 @@ static const struct row rows[] = {
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
/* #13 direct-read rows: `t[i].cap` straight (no copy via a let).
|
||||
* Must equal the slice_cap_copy / str_cap_copy oracle. Pre-fix:
|
||||
* cstage returned .ptr (a heap/stack address, != 7/6), wwstage
|
||||
* emitted no read (stale AX) — and the two diverged. */
|
||||
{ "slice_cap_direct0",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\treturn t[0].cap: i32;\n"
|
||||
"};\n",
|
||||
7 },
|
||||
|
||||
{ "slice_cap_direct1",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet hb: [8]u8;\n"
|
||||
"\tlet a: []u8; a.ptr = &hb[0]; a.len = 3; a.cap = 7;\n"
|
||||
"\tlet b: []u8; b.ptr = &hb[0]; b.len = 2; b.cap = 6;\n"
|
||||
"\tlet t: [2][]u8 = [a, b];\n"
|
||||
"\treturn t[1].cap: i32;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
|
||||
{ "str_cap_direct0",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: str = \"abcde\";\n"
|
||||
"\tlet b: str = \"xy\";\n"
|
||||
"\tlet t: [2]str = [a, b];\n"
|
||||
"\treturn t[0].cap: i32;\n"
|
||||
"};\n",
|
||||
5 },
|
||||
|
||||
{ "str_cap_direct1",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: str = \"abcde\";\n"
|
||||
"\tlet b: str = \"xy\";\n"
|
||||
"\tlet t: [2]str = [a, b];\n"
|
||||
"\treturn t[1].cap: i32;\n"
|
||||
"};\n",
|
||||
2 },
|
||||
|
||||
{ "slice_ptr",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
|
||||
Reference in New Issue
Block a user