wcc: tuple slice-element read loads the full 24B header, both stages
Reading a slice-typed tuple element (t.0) loaded only the pointer word; len and cap took whatever was left in BX/CX — silent garbage in BOTH stages once anything clobbered the registers between build and read. Load all three header words at the tuple-element arm. Review item #28. Both stages move in one commit: one emission contract; splitting would leave the byte-id gates red between the halves.
This commit is contained in:
@@ -11319,15 +11319,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
areg(D_X0));
|
||||
break;
|
||||
}
|
||||
/* str IS []u8 — load (ptr, len, cap) into
|
||||
* (AX, BX, CX), the canonical slice-header ABI,
|
||||
* so chains like `t.1.len` propagate through the
|
||||
* slice-rhs convention (#1/Phase 3 collapse).
|
||||
* UNLIKE the field arms there is no slice-element
|
||||
* sibling here, so the triple is hand-authored;
|
||||
* base is BP (frame) or CX (global base — CX is
|
||||
* written LAST so it survives the +0/+8 reads). */
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
/* str IS []u8, and a slice is the same 24B
|
||||
* {ptr,len,cap} header — load all three words into
|
||||
* (AX, BX, CX), the canonical slice-header ABI, so
|
||||
* chains like `t.1.len` propagate through the
|
||||
* slice-rhs convention (#1/Phase 3 collapse). #28:
|
||||
* pre-fix the gate was str-only, so a SLICE tuple
|
||||
* element fell to the fldloadop tail (one ptr word;
|
||||
* len/cap stale). base is BP (frame) or CX (global
|
||||
* base — CX written LAST so it survives the +0/+8). */
|
||||
if (fu && (fu->kind == TY_STR || fu->kind == TY_SLICE)) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg, base_disp + foff + 0),
|
||||
areg(D_AX));
|
||||
|
||||
@@ -26646,7 +26646,14 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
if (tp != nil) {
|
||||
let tpt: *node = tp.lhs;
|
||||
if (isstrtype(c, tpt)) {
|
||||
// str IS []u8, and a slice is the same 24B
|
||||
// {ptr,len,cap} header — load all three words
|
||||
// into (AX, BX, CX). #28: pre-fix the gate was
|
||||
// str-only, so a SLICE tuple element fell to
|
||||
// the scalar tail below (one ptr word; len/cap
|
||||
// took stale registers). base is BP (frame),
|
||||
// so the triple order has no clobber risk.
|
||||
if (isstrtype(c, tpt) || isslicetype(c, tpt)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 0): i64);
|
||||
emitline("(BP), AX\n");
|
||||
|
||||
@@ -3495,7 +3495,14 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
if (tp != nil) {
|
||||
let tpt: *node = tp.lhs;
|
||||
if (isstrtype(c, tpt)) {
|
||||
// str IS []u8, and a slice is the same 24B
|
||||
// {ptr,len,cap} header — load all three words
|
||||
// into (AX, BX, CX). #28: pre-fix the gate was
|
||||
// str-only, so a SLICE tuple element fell to
|
||||
// the scalar tail below (one ptr word; len/cap
|
||||
// took stale registers). base is BP (frame),
|
||||
// so the triple order has no clobber risk.
|
||||
if (isstrtype(c, tpt) || isslicetype(c, tpt)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 0): i64);
|
||||
emitline("(BP), AX\n");
|
||||
|
||||
@@ -26646,7 +26646,14 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
};
|
||||
if (tp != nil) {
|
||||
let tpt: *node = tp.lhs;
|
||||
if (isstrtype(c, tpt)) {
|
||||
// str IS []u8, and a slice is the same 24B
|
||||
// {ptr,len,cap} header — load all three words
|
||||
// into (AX, BX, CX). #28: pre-fix the gate was
|
||||
// str-only, so a SLICE tuple element fell to
|
||||
// the scalar tail below (one ptr word; len/cap
|
||||
// took stale registers). base is BP (frame),
|
||||
// so the triple order has no clobber risk.
|
||||
if (isstrtype(c, tpt) || isslicetype(c, tpt)) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff((lc.off + foff + 0): i64);
|
||||
emitline("(BP), AX\n");
|
||||
|
||||
@@ -100,6 +100,45 @@ static const struct row rows[] = {
|
||||
" let v: []u8 = s: []u8;\n"
|
||||
" return v.cap: i32;\n"
|
||||
"};\n", 4, K_RUN, NULL },
|
||||
/* #28 (#263 both-stages): tuple slice-element read `t.0` over a
|
||||
* ([]u8, i64). A clobber() call between the tuple build and the
|
||||
* read leaves junk in BX/CX; pre-fix the str-only arm fell to the
|
||||
* scalar tail (ptr word only), so ys.len read stale BX. Reads len
|
||||
* == 5 (distinct from any stale value) → 0. */
|
||||
{ "tuple_slice_elem",
|
||||
"package main;\n"
|
||||
"fn clobber() i64 = {\n"
|
||||
" let a: i64 = 111;\n"
|
||||
" let b: i64 = 222;\n"
|
||||
" return a + b;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []u8 = [1u8, 2u8, 3u8, 4u8, 5u8];\n"
|
||||
" let t: ([]u8, i64) = (xs, 7);\n"
|
||||
" let junk: i64 = clobber();\n"
|
||||
" let ys: []u8 = t.0;\n"
|
||||
" if (ys.len == 5) { return 0; };\n"
|
||||
" return 1;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
/* #28 sibling: the slice element sits at tuple position 1 (foff != 0:
|
||||
* an i64 occupies slot 0, the []u8 header starts at +8). Exercises the
|
||||
* widened arm's offset arithmetic, which the position-0 row leaves
|
||||
* untested. Same clobber()-between-build-and-read trigger; len == 6. */
|
||||
{ "tuple_slice_elem_pos1",
|
||||
"package main;\n"
|
||||
"fn clobber() i64 = {\n"
|
||||
" let a: i64 = 111;\n"
|
||||
" let b: i64 = 222;\n"
|
||||
" return a + b;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []u8 = [1u8, 2u8, 3u8, 4u8, 5u8, 6u8];\n"
|
||||
" let t: (i64, []u8) = (9, xs);\n"
|
||||
" let junk: i64 = clobber();\n"
|
||||
" let ys: []u8 = t.1;\n"
|
||||
" if (ys.len == 6) { return 0; };\n"
|
||||
" return 1;\n"
|
||||
"};\n", 0, K_RUN, NULL },
|
||||
};
|
||||
|
||||
static int
|
||||
|
||||
Reference in New Issue
Block a user