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

@@ -85,11 +85,14 @@ static const struct row rows[] = {
"export fn main() i32 = { return 0; };\n",
"\tMOVZBQ\t(AX), AX\n",
"" },
/* Write: `obj.arr[i] = v` where arr: [N](i64|str) — 24B tagged.
/* Write: `obj.arr[i] = v` where arr: [N](i64|str) — tagged element.
* Post-fix wwstage: cgassign N_DOT arm sets elemtn → tagged-store
* path → IMULQ $24 + byte-copy from scratch. Scanlocals N_DOT arm
* pre-reserves @tagscr in the frame. Pre-fix: scalar `MOVQ AX,
* (BX)` over the 24B slot. */
* path → IMULQ (stride) + byte-copy from scratch. Scanlocals N_DOT
* arm pre-reserves @tagscr in the frame. Pre-fix: scalar `MOVQ AX,
* (BX)` over the slot.
* #1/Phase 3: str IS []u8 (24B), so the (i64|str) slot is
* 8(tag)+24(str payload)=32B — stride is $32, not the 16B-world
* $24. Byte-identical across stages. */
{ "dotbase_array_tagged_write",
"type T = (i64 | str);\n"
"type S = struct{ pad: i64, arr: [4]T };\n"
@@ -98,7 +101,7 @@ static const struct row rows[] = {
" s.arr[i] = 42i64;\n"
"};\n"
"export fn main() i32 = { return 0; };\n",
"\tMOVQ\t$24, CX\n",
"\tMOVQ\t$32, CX\n",
"" },
};