diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index c9a74c03..b9a4923c 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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)); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index cd1c0d7c..d84a8bdf 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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"); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 0c445f9a..1d9970d3 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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"); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index e8cefe0b..459e3a6a 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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"); diff --git a/test/wcc/949_f6_header_run.c b/test/wcc/949_f6_header_run.c index 4b7bb01d..975ed4d6 100644 --- a/test/wcc/949_f6_header_run.c +++ b/test/wcc/949_f6_header_run.c @@ -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