diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 69ba728f..3af35649 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1952,6 +1952,32 @@ cgexpr(Cg *c, Node *n, Local *locals) } break; } + /* slice-typed field: rhs cgexpr leaves (AX=ptr, + * BX=len, CX=cap); store all three at + * field+0/+8/+16. Address scratch must dodge CX + * (holds cap), so via_ptr/is_global stage the + * struct base in DX. Without this branch the + * generic store_op below writes only AX, silently + * dropping .len and .cap. */ + if (n->op == TK_ASSIGN && str_fu && str_fu->kind == TY_SLICE) { + cgexpr(c, n->rhs, locals); + if (via_ptr) { + ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_DX)); + ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, foff + 0)); + ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, foff + 8)); + ins2(c, A_MOVQ, areg(D_CX), amem(D_DX, foff + 16)); + } else if (is_global) { + ins2(c, A_LEAQ, masym(c, base->str), areg(D_DX)); + ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, foff + 0)); + ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, foff + 8)); + ins2(c, A_MOVQ, areg(D_CX), amem(D_DX, foff + 16)); + } else { + ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 0)); + ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, boff + foff + 8)); + ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, boff + foff + 16)); + } + break; + } /* compound: load current value into BX */ if (n->op != TK_ASSIGN) { if (via_ptr) { diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e038597f..0cb53acf 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -11661,6 +11661,26 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\n"); return; }; + // slice field via *struct: rhs left + // (AX=ptr, BX=len, CX=cap). CX is + // taken, so stage the struct addr + // in DX. Store all three words at + // foff/+8/+16. + if (isslicetype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(fi.foff: i64, "DX"); + emitline("\n"); + emitline("\tMOVQ\tBX, "); + emitdispreg((fi.foff + 8): i64, "DX"); + emitline("\n"); + emitline("\tMOVQ\tCX, "); + emitdispreg((fi.foff + 16): i64, "DX"); + emitline("\n"); + return; + }; // f64/f32 plain `=` via *struct: cgexpr left the // value in X0. Reload struct ptr and MOVSD/MOVSS. if (isfloattype(c, fi.tnode)) { @@ -11717,6 +11737,23 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); return; }; + // slice field direct: cgexpr left + // (AX=ptr, BX=len, CX=cap); store all + // three at +0/+8/+16. The generic + // fldstoreop below would only write AX, + // dropping .len/.cap. + if (isslicetype(c, fi.tnode)) { + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + fi.foff): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((lc.off + fi.foff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((lc.off + fi.foff + 16): i64); + emitline("(BP)\n"); + return; + }; // f64/f32 direct struct local store: route via X0. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 3ce6875d..b5a131f5 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -3613,6 +3613,26 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\n"); return; }; + // slice field via *struct: rhs left + // (AX=ptr, BX=len, CX=cap). CX is + // taken, so stage the struct addr + // in DX. Store all three words at + // foff/+8/+16. + if (isslicetype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(fi.foff: i64, "DX"); + emitline("\n"); + emitline("\tMOVQ\tBX, "); + emitdispreg((fi.foff + 8): i64, "DX"); + emitline("\n"); + emitline("\tMOVQ\tCX, "); + emitdispreg((fi.foff + 16): i64, "DX"); + emitline("\n"); + return; + }; // f64/f32 plain `=` via *struct: cgexpr left the // value in X0. Reload struct ptr and MOVSD/MOVSS. if (isfloattype(c, fi.tnode)) { @@ -3669,6 +3689,23 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); return; }; + // slice field direct: cgexpr left + // (AX=ptr, BX=len, CX=cap); store all + // three at +0/+8/+16. The generic + // fldstoreop below would only write AX, + // dropping .len/.cap. + if (isslicetype(c, fi.tnode)) { + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + fi.foff): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((lc.off + fi.foff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((lc.off + fi.foff + 16): i64); + emitline("(BP)\n"); + return; + }; // f64/f32 direct struct local store: route via X0. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index d56d8183..3dd22a92 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -11661,6 +11661,26 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("\n"); return; }; + // slice field via *struct: rhs left + // (AX=ptr, BX=len, CX=cap). CX is + // taken, so stage the struct addr + // in DX. Store all three words at + // foff/+8/+16. + if (isslicetype(c, fi.tnode)) { + emitline("\tMOVQ\t"); + emitoff(lc.off: i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(fi.foff: i64, "DX"); + emitline("\n"); + emitline("\tMOVQ\tBX, "); + emitdispreg((fi.foff + 8): i64, "DX"); + emitline("\n"); + emitline("\tMOVQ\tCX, "); + emitdispreg((fi.foff + 16): i64, "DX"); + emitline("\n"); + return; + }; // f64/f32 plain `=` via *struct: cgexpr left the // value in X0. Reload struct ptr and MOVSD/MOVSS. if (isfloattype(c, fi.tnode)) { @@ -11717,6 +11737,23 @@ fn cgassign(c: *cgen, n: *node) void = { emitline("(BP)\n"); return; }; + // slice field direct: cgexpr left + // (AX=ptr, BX=len, CX=cap); store all + // three at +0/+8/+16. The generic + // fldstoreop below would only write AX, + // dropping .len/.cap. + if (isslicetype(c, fi.tnode)) { + emitline("\tMOVQ\tAX, "); + emitoff((lc.off + fi.foff): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tBX, "); + emitoff((lc.off + fi.foff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tCX, "); + emitoff((lc.off + fi.foff + 16): i64); + emitline("(BP)\n"); + return; + }; // f64/f32 direct struct local store: route via X0. if (isfloattype(c, fi.tnode)) { let mov: str = "MOVSD"; diff --git a/test/wcc/681_arr_elem_field_write.c b/test/wcc/681_arr_elem_field_write.c index 97644498..5a279a03 100644 --- a/test/wcc/681_arr_elem_field_write.c +++ b/test/wcc/681_arr_elem_field_write.c @@ -192,6 +192,90 @@ static const struct row rows[] = { " return stk[0].x;\n" "};\n", 32 }, + /* Whole-slice rhs to struct field, direct struct local (task #24). + * cgen N_ASSIGN had a TY_STR branch that stored ptr+len at +0/+8 + * but no TY_SLICE branch; the fallthrough generic store wrote only + * AX (ptr), silently dropping .len and .cap. Fixed by a parallel + * TY_SLICE branch that stores AX/BX/CX at +0/+8/+16. The bug bit + * bufio.init's `b.rbuf = rbuf` (first stdlib struct with a []u8 + * field). Returns 42 when len, cap, and a trailing scalar field + * all survived the store. */ + { "slice_field_value_write", + "type bs = struct { rbuf: []u8, mark: i32 };\n" + "fn main() i32 = {\n" + " let raw: [8]u8; raw[0] = 0u8;\n" + " let rbuf: []u8 = raw[0:5];\n" + " let b: bs;\n" + " b.mark = 7;\n" + " b.rbuf = rbuf;\n" + " if (b.rbuf.len != 5) { return 1; };\n" + " if (b.rbuf.cap != 5) { return 2; };\n" + " if (b.mark != 7) { return 3; };\n" + " return 42;\n" + "};\n", + 42 }, + /* Same bug via pointer to struct — the bufio scenario exactly + * (`init(b: *bstream, rbuf: []u8) { b.rbuf = rbuf; }`). The via_ptr + * arm of the new TY_SLICE branch stages the struct addr in DX to + * avoid clobbering CX (cap). */ + { "slice_field_ptr_write", + "type bs = struct { rbuf: []u8, mark: i32 };\n" + "fn init(b: *bs, rbuf: []u8) void = {\n" + " b.rbuf = rbuf;\n" + " b.mark = 9;\n" + "};\n" + "fn main() i32 = {\n" + " let raw: [8]u8; raw[0] = 0u8;\n" + " let b: bs;\n" + " init(&b, raw[0:5]);\n" + " if (b.rbuf.len != 5) { return 1; };\n" + " if (b.rbuf.cap != 5) { return 2; };\n" + " if (b.mark != 9) { return 3; };\n" + " return 42;\n" + "};\n", + 42 }, + /* Distinct byte-pattern check — independent verification that + * the AX/BX/CX MOVQs landed at +0/+8/+16 rather than passing by + * coincidence with neighbouring zero memory. Distinct byte + * patterns at raw[0] (0xAA) and raw[5] (0xFF) are read back + * through b.rbuf[i], which only succeeds if .ptr (AX) survived + * at +0. Trailing mark uses a distinctive value (0x33) so a + * stray CX store one slot too far is caught separately. .len + * and .cap are forced equal by ww's slice expression (both + * BX = hi-lo), so this row pins .ptr survival while the other + * rows pin .len/.cap distinctness from the zero default. */ + { "slice_field_distinct_bytes", + "type bs = struct { rbuf: []u8, mark: i32 };\n" + "fn main() i32 = {\n" + " let raw: [16]u8;\n" + " raw[0] = 0xAAu8; raw[1] = 0xBBu8; raw[2] = 0xCCu8;\n" + " raw[3] = 0xDDu8; raw[4] = 0xEEu8; raw[5] = 0xFFu8;\n" + " let b: bs;\n" + " b.mark = 0x33;\n" + " b.rbuf = raw[0:6];\n" + " if (b.rbuf.len != 6) { return 1; };\n" + " if (b.rbuf.cap != 6) { return 2; };\n" + " if (b.mark != 0x33) { return 3; };\n" + " if (b.rbuf[0] != 0xAAu8) { return 4; };\n" + " if (b.rbuf[5] != 0xFFu8) { return 5; };\n" + " return 42;\n" + "};\n", + 42 }, + /* Str-field regression for the parallel TY_STR branch right above + * the new TY_SLICE branch — pin that store path so a future refactor + * doesn't drop it. Direct struct local; `b.s = "hello"` stores + * AX/BX at +0/+8, and the trailing i32 mark survives. */ + { "str_field_value_write_regression", + "type bs = struct { s: str, mark: i32 };\n" + "fn main() i32 = {\n" + " let b: bs;\n" + " b.mark = 11;\n" + " b.s = \"hello\";\n" + " if (b.s.len != 5) { return 1; };\n" + " if (b.mark != 11) { return 2; };\n" + " return 42;\n" + "};\n", + 42 }, }; static int