w6c+selfhost: cgen N_ASSIGN TY_STRUCT field branch (closes #25)

Parallel to TY_STR/TY_SLICE branches at cgen.c:1939/1962. Word-copy
from src slot to field+k*8 via AX, MOVL/MOVB ragged tail. N_IDENT
rhs only — struct-call-result and struct-literal rhs are different
code paths, filed as task #27. Symmetric in N_ASSIGN field case AND
spine-walker terminal; mirrored in wwstage cgassign four sub-shapes
(*struct base, direct local, global, spine terminal).
This commit is contained in:
2026-05-14 18:16:51 +09:00
parent eb0602d070
commit 1726bcef18
5 changed files with 843 additions and 0 deletions

View File

@@ -276,6 +276,153 @@ static const struct row rows[] = {
" return 42;\n"
"};\n",
42 },
/* Whole-struct rhs to struct field (task #25). cgen N_ASSIGN had
* special-cases for TY_STR (2 words) and TY_SLICE (3 words) but no
* TY_STRUCT branch — the fallthrough fldstoreop wrote only AX,
* dropping every trailing word. Fixed by a word-copy branch that
* runs MOVQ from rhs slot to dest field, plus a 4/1-byte ragged
* tail. The 16B row is the BORDER case: same total size as TY_STR
* but must NOT take the str branch (str field has 16B header; this
* is a 2-word struct value, structurally identical but type-
* dispatched). Distinct byte values per word so a stray store at
* the wrong offset surfaces as a misvalue, not coincidental zero. */
{ "struct_field_value_write_16B",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x1122334455667788i64;\n"
" o.b = 0x99aabbccddeeff00i64;\n"
" let x: outer;\n"
" x.mark = 0x55;\n"
" x.i = o;\n"
" if (x.i.a != 0x1122334455667788i64) { return 1; };\n"
" if (x.i.b != 0x99aabbccddeeff00i64) { return 2; };\n"
" if (x.mark != 0x55) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* 24B struct rhs (the original #25 probe case). Three words at +0/+8/+16
* exercise the word-copy loop with no ragged tail. Distinct byte
* patterns pin each word independently. */
{ "struct_field_value_write_24B",
"type inner = struct { a: i64, b: i64, c: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" let x: outer;\n"
" x.mark = 7;\n"
" x.i = o;\n"
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (x.mark != 7) { return 4; };\n"
" return 42;\n"
"};\n",
42 },
/* 32B struct rhs — four-word copy, no ragged tail. Trailing mark in
* the outer struct catches a stray fifth-word store. */
{ "struct_field_value_write_32B",
"type inner = struct { a: i64, b: i64, c: i64, d: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" o.d = 0x3d3d3d3d3d3d3d3di64;\n"
" let x: outer;\n"
" x.mark = 0x33;\n"
" x.i = o;\n"
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (x.i.d != 0x3d3d3d3d3d3d3d3di64) { return 4; };\n"
" if (x.mark != 0x33) { return 5; };\n"
" return 42;\n"
"};\n",
42 },
/* Nested struct field write through chained N_DOT spine
* (`b.inner.deep = other_deep`). Confirms #22's spine walker
* handles a TY_STRUCT terminal, not just scalar/str/slice. The
* 24B inner struct payload must survive both the spine walk and
* the word-copy. */
{ "struct_field_value_write_nested_chain",
"type deep = struct { a: i64, b: i64, c: i64 };\n"
"type mid = struct { d: deep, mark: i32 };\n"
"type outer = struct { m: mid, tag: i32 };\n"
"fn main() i32 = {\n"
" let other: deep;\n"
" other.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" other.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" other.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" let v: outer;\n"
" v.m.mark = 5;\n"
" v.tag = 9;\n"
" v.m.d = other;\n"
" if (v.m.d.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (v.m.d.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (v.m.d.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (v.m.mark != 5) { return 4; };\n"
" if (v.tag != 9) { return 5; };\n"
" return 42;\n"
"};\n",
42 },
/* Whole-struct rhs to a struct field via a *struct base — exercises
* the via_ptr arm of the new TY_STRUCT branch. Stages the dest addr
* in BX (MOVQ off(BP),BX) before iterating, then writes at fi.foff+k(BX). */
{ "struct_field_value_write_via_ptr",
"type inner = struct { a: i64, b: i64, c: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn fill(x: *outer) void = {\n"
" let o: inner;\n"
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" x.mark = 7;\n"
" x.i = o;\n"
"};\n"
"fn main() i32 = {\n"
" let x: outer;\n"
" fill(&x);\n"
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (x.mark != 7) { return 4; };\n"
" return 42;\n"
"};\n",
42 },
/* Ragged tail — 12B inner struct (3 i32 fields, maxalign=4, no 8B
* round-up) lands the word-copy loop at k=8 with 4 bytes left and
* pins the MOVL tail branch. Tail-only structs are reachable for
* any TY_STRUCT whose maxalign < 8: this is the smallest such
* shape. The trailing i32 mark in the outer struct catches a stray
* MOVQ tail (which would overwrite +4 of the mark slot). The 5/3/2
* byte tails (struct of just i8 fields) fall back to MOVQ in cgen
* and would overwrite past the field boundary; the language permits
* align==1 structs but they're not exercised here — filed as a
* sub-followup if a real callsite surfaces. */
{ "struct_field_value_write_ragged_tail_12B",
"type inner = struct { a: i32, b: i32, c: i32 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x11223344;\n"
" o.b = 0x55667788;\n"
" o.c = 0x29aabbcc;\n"
" let x: outer;\n"
" x.mark = 0x33;\n"
" x.i = o;\n"
" if (x.i.a != 0x11223344) { return 1; };\n"
" if (x.i.b != 0x55667788) { return 2; };\n"
" if (x.i.c != 0x29aabbcc) { return 3; };\n"
" if (x.mark != 0x33) { return 4; };\n"
" return 42;\n"
"};\n",
42 },
};
static int