ww: cgen trap batch (def-str field, chained-ptr write, scalar+str tuple ABI)

This commit is contained in:
2026-05-11 20:49:21 +09:00
parent 2ac15e4e99
commit c5f30f2fce
14 changed files with 1629 additions and 67 deletions

View File

@@ -4,22 +4,28 @@ Identifiers: Plan 9 style. lowercase, words run together (`newbuf`, `tcpsock`, `
Syntax and idioms: Hare-shaped. Trailing `;`, `=` after fn/type signatures, `export` for visibility, `match`/`?`/`!` for tagged-union errors. Consult `ref/hare/` for canonical signatures and error-handling patterns before inventing your own.
The C bootstrap's cgen ("wwstage") has four known silent-miscompilation traps. They produce wrong runtime behavior, not compile errors. When porting C → ww here, default to the workarounds:
The C bootstrap's cgen has known silent-miscompilation traps. They produce wrong runtime behavior, not compile errors. When porting C → ww here, default to the workarounds:
1. **Two-level field write through pointer field doesn't stick.**
`r.sym.isdyn = 1` where `r.sym: *T` drops the write. Bind the inner pointer to a local first:
```
let sym: *lsym = r.sym;
sym.isdyn = 1;
```
1. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
2. **Tuple return `(scalar, str)` corrupts the str half.** Both ptr and len come back garbage. Split into two functions — one returns the scalar, another returns the str. Plain `str` returns are fine.
Fixed (no workaround needed):
3. **`def NAME: str = "...";` is broken.** The `.len` picks up an unrelated accumulator. Wrap the literal in a nullary fn instead:
```
fn namestr() str = { return "..."; };
```
4. **`amalloc(n)` with n < struct size silently corrupts neighbours.** No error — the bump arena hands out n bytes and field writes overflow into the next record. When introducing or growing a struct, audit every `amalloc(_, n)` call site and over-size (we routinely pass 48 for a 40-byte struct). Symptom: linked-list prepends lose all but the most recent entry.
- `def NAME: str = "..."` field access. `.len`/`.ptr` on an Sdef ident
now inline the literal length / strlit address rather than reading
BP+8. See cmd/w6c/cgen.c N_DOT.
- Two-level field write through pointer field: `r.sym.isdyn = 1` where
`r.sym: *T` now stores. The chained-N_DOT N_ASSIGN branch evaluates
the inner pointer and stores at *(ptr + field.offset). See
cmd/w6c/cgen.c N_ASSIGN and selfhost/cmd/wcc/cgenexpr.ww cgassign.
- Tuple return `(scalar, str)` (24B). Returns now follow an AX:DX:CX
convention: AX = scalar elem, DX = str.ptr, CX = str.len. Receive
sites all destructure off the same regs regardless of positional
order:
let n, s = call(); // ww comma form
let (n, s) = call(); // Hare-style paren form
let t: (i64, str) = call(); // positional t.0 / t.1.len
Wwstage parser + cgen are byte-identical to C cgen on these shapes.
See cmd/w6c/cgen.c N_RETURN/N_LET/N_DOT/N_MLET, lib/ww/parse/{stmt,
expr}.ww and selfhost/cmd/wcc/{cgenstmt,cgenexpr,cgenutil,cgendecl}.ww.
If a port "should work" but the binary is wrong, suspect these first.