`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));
|
areg(D_AX));
|
||||||
} else {
|
} else {
|
||||||
/* Evaluate the str/slice expression — leaves
|
/* Evaluate the str/slice expression — leaves
|
||||||
* (AX=ptr, BX=len) for str. .ptr returns AX,
|
* the full (AX=ptr, BX=len, CX=cap) header
|
||||||
* .len shuffles BX→AX. */
|
* (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);
|
cgexpr(c, n->lhs, locals);
|
||||||
if (lenfld)
|
if (lenfld)
|
||||||
ins2(c, A_MOVQ, areg(D_BX),
|
ins2(c, A_MOVQ, areg(D_BX),
|
||||||
areg(D_AX));
|
areg(D_AX));
|
||||||
|
else if (capfld)
|
||||||
|
ins2(c, A_MOVQ, areg(D_CX),
|
||||||
|
areg(D_AX));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22611,6 +22611,26 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\tBX, AX\n");
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
return;
|
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:
|
// Chained struct-field-via-ptr-via-ptr access:
|
||||||
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
||||||
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
// 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");
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
return;
|
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:
|
// Chained struct-field-via-ptr-via-ptr access:
|
||||||
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
||||||
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
// 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");
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
return;
|
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:
|
// Chained struct-field-via-ptr-via-ptr access:
|
||||||
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
// r.sym.val where r: *lrel, .sym: *lsym, .val: u64
|
||||||
// Inner DOT (`r.sym`) returns a *struct (a pointer-to-struct
|
// 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 ->
|
* for `is_str_el || is_slice_el`; wwstage adds isslicel (TY_SLICE ->
|
||||||
* esz = esubti.size, fixing the stride) and the matching 3-word store.
|
* 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
|
* Cap is validated BOTH ways: a WHOLE-ELEMENT COPY (`let q = t[i]; q.cap`)
|
||||||
* a direct `t[i].cap` read: the `.cap` field-extract on an INDEXED slice/
|
* AND a DIRECT `t[i].cap` read. The direct read was a SEPARATE bug (task
|
||||||
* str element is a SEPARATE, still-open bug (returns .ptr on cstage,
|
* #13, the #20 store's read-sibling): the `.cap` field-extract on an
|
||||||
* emits no read on wwstage — divergent), the "cgindex truncating-to-ptr"
|
* INDEXED slice/str element returned .ptr on cstage (it shuffled only
|
||||||
* sibling cited at cgen.c:9039-9040, filed as task #13. A whole-element
|
* .len BX→AX, leaving AX=.ptr for cap) and emitted NO read on wwstage
|
||||||
* copy reads the stored cap through the (correct) ident-load path, so it
|
* (the cgdot non-ident catch-all handled only .ptr/.len) — divergent.
|
||||||
* exercises THIS fix's stored cap word without hitting #13.
|
* 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
|
* 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
|
* store drops the .len word (the pre-fix slice 1-word store), and the
|
||||||
@@ -128,6 +131,52 @@ static const struct row rows[] = {
|
|||||||
"};\n",
|
"};\n",
|
||||||
7 },
|
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",
|
{ "slice_ptr",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"export fn main() i32 = {\n"
|
"export fn main() i32 = {\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user