w6c+selfhost: cgen N_ASSIGN TY_SLICE field branch (closes #24)

Parallel to the existing TY_STR branch at cgen.c:1939. Stores AX/BX/CX
at field+0/+8/+16 across three sub-shapes (via_ptr, is_global, direct
local) — DX as addr scratch where needed so CX (cap) survives.
Symmetric in wwstage cgassign. Surfaces tasks #25 (whole-struct rhs)
and #26 (whole-tagged rhs) in the same locus class.
This commit is contained in:
2026-05-14 03:18:49 +09:00
parent 79c758c046
commit eb0602d070
5 changed files with 221 additions and 0 deletions

View File

@@ -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) {

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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