w6c+selfhost: str globals — 16B DATAW + (LEAQ, MOVQ, MOVQ) sequences

Extend top-level mutable `let` to cover str. The cgen now:

  - emits a 16-byte zero DATAW for `let s: str;` (and the trivial
    `nil` / `""` inits); a non-empty strlit init is skipped because
    a compile-time .data → .text reloc isn't supported yet, so the
    user gets a clean undefined-symbol error at link;
  - loads `s` as `(LEAQ s(SB), CX; MOVQ (CX), AX; MOVQ 8(CX), BX)`
    so the (AX=ptr, BX=len) pair convention is preserved;
  - stores via the same `&s` indirection for `s = expr;` and routes
    the `.ptr` / `.len` pseudo-field N_DOT branch through it; and
  - tracks the declared type on each LetVar so cgident / cgdot /
    cgassign pick the right load/store shape.

Selfhost cgen mirrors all four paths byte-for-byte; test 990
(cgen-match on err.ww) and tests 994/995 (self-rebuild) stay
green. 630_let_global gains two new fixtures (`let msg: str;` +
runtime assign, plus reassign from a helper).

Slice and struct globals still NYI — same scope deferred.
This commit is contained in:
2026-05-12 12:10:23 +09:00
parent 4bf1b56872
commit 208bdd25df
6 changed files with 648 additions and 222 deletions

View File

@@ -91,6 +91,28 @@ static const struct fixture fixtures[] = {
"fn main() i32 = { return val: i32; };\n",
123,
},
{
"str-zeroinit",
/* `let msg: str;` zero-inits the {ptr,len} slot. Assigning
* a strlit at runtime updates both halves; .len then reads
* the stored length. */
"let msg: str;\n"
"fn main() i32 = {\n"
"\tmsg = \"hello\";\n"
"\treturn msg.len: i32;\n"
"};\n",
5,
},
{
"str-reassign",
"let msg: str;\n"
"fn set(s: str) void = { msg = s; };\n"
"fn main() i32 = {\n"
"\tset(\"abcdefg\");\n"
"\treturn msg.len: i32;\n"
"};\n",
7,
},
{ NULL, NULL, 0 }
};