wcc: str -> 24B {ptr,len,cap}, 3-reg ABI -- parity with []u8 (both stages)

A ww `str` becomes a 24-byte {ptr,len,cap} value, identical in layout to
[]u8 -- the enabling prerequisite for the Phase 2 `str == []u8` collapse.

Both stages, atomically:
- ty_str 16->24B; str value flows 3-reg AX/BX/CX (was 2-reg); str literals
  emit cap (=len).
- str in a tagged union grows to a 32B slot, using the AX/DX/CX/R8 4th-word
  path already used by 32B slice-variant unions -- str-variant is now
  structurally identical.
- tuple (scalar,str) return: 4-reg AX/DX/CX/R8 + 32B receive, extending the
  existing type-keyed return (no sret).
- str == []u8 for index and .ptr/.len/.cap, kind-gated where size-based
  dispatch collided at 24B; cstage and wwstage mirror exactly.
- table-driven runtime coverage: test/wcc/928_str_abi_run.c.

Cannot be split (rule 10/11): a 24B str and a 16B str cannot coexist across
the two compiler stages without breaking byte-identity, so the size change
and every dependent ABI/codegen site land in one atomic commit, both stages.

Known follow-ups (zero corpus impact, tracked): str-literal global .cap
static-init; >16B struct by-value (pre-existing); tagged-union
match-scrutinee stage divergence (pre-existing).
This commit is contained in:
2026-05-24 06:40:59 +09:00
parent d9345555c0
commit 1140a590bf
16 changed files with 1337 additions and 494 deletions

View File

@@ -239,7 +239,10 @@ static const struct asm_disp_row asm_disp_rows[] = {
"package main;\n"
"type holder = struct { s: str };\n"
"fn dummy() *holder = { return alloc(holder { s = \"x\" })!; };\n",
"\tMOVQ\tAX, (CX)\n" },
/* #1/Phase 3: str IS []u8 (24B), so the alloc-str-field store
* routes the heap base through DX (CX now holds the cap) and
* writes 3 words (ptr/len/cap). Was `(CX)` in the 16B world. */
"\tMOVQ\tAX, (DX)\n" },
/* str at non-zero foff. Pins that the displacement IS emitted
* (`8(CX)`) when foff != 0 — emitdispreg must not suppress
* non-zero offsets too. Pre-fix and post-fix both pass this; it
@@ -250,7 +253,8 @@ static const struct asm_disp_row asm_disp_rows[] = {
"fn dummy() *holder = {\n"
" return alloc(holder { pad = 0, s = \"x\" })!;\n"
"};\n",
"\tMOVQ\tAX, 8(CX)\n" },
/* #1/Phase 3: DX base (str IS []u8, cap in CX); was `8(CX)`. */
"\tMOVQ\tAX, 8(DX)\n" },
/* Generic 8-byte field at foff=0 (non-str, non-float path). Covers
* the `else` branch's `MOVQ AX, (BX)` store via fieldstoreop. */
{ "alloc_int_at_offset0",