cmd+rt+selfhost+test: graduate alloc to (*T | nomem) / ([]T | nomem)

Per Hare convention, alloc is a typed builtin that returns a tagged
union carrying nomem as the OOM variant. Callers spell their policy:
`alloc(T)!` aborts on OOM (the old behavior), `alloc(T)?` propagates
when the enclosing fn already returns nomem.

cstage: check builds TY_TAGGED{*T | nomem} (or {[]T | nomem}); cgen
emits AX=tag, DX=ptr per the general tagged-return ABI (the (*T|!void)
nullable-ptr fold gated in ea76ee4 keeps this clean). wwstage cgalloc
mirrors. rt/alloc.s zeroes AX on syscall error so the builtin's null
check sees a clean 0 instead of mmap's -errno leaking through as a
poisoned pointer.

Migration: 3 `!` sites in test/wcc/700_e2e.c, 1 `!` site in
rt/ensure.ww (preserves the pre-existing sizeof bug tracked by #27),
1 `?` site in selfhost/test/tagged_ptr_ret.ww (allocbox exercises
real `?` propagation against a (*T | nomem) return).

130/130 tests green, 994_w6c_ww + 995_self_rebuild stage byte-identity
preserved. Follow-ups #31 (wwstage checkletassign leniency), #32
(wwstage slice-form gap), #33 (tagged_ptr_ret.ww make-test wiring).
This commit is contained in:
2026-05-19 20:25:14 +09:00
parent d27411d833
commit 61705fb39e
9 changed files with 204 additions and 41 deletions

View File

@@ -5,6 +5,12 @@
//
// We pin to PROT_READ|PROT_WRITE and MAP_PRIVATE|MAP_ANONYMOUS so
// callers never have to plumb file descriptors through.
//
// On mmap failure the raw syscall returns -errno (negative). Task #30
// graduated the `alloc` builtin to a fallible `(*T | nomem)` /
// `([]T | nomem)` signature whose cgen branches on a null return, so
// the failure path here returns 0 instead of a poisoned pointer. The
// builtin's caller is expected to `!`/`?` the result.
TEXT rt_alloc,$0
MOVQ DI, SI // arg 1: length = caller's n
@@ -15,6 +21,10 @@ TEXT rt_alloc,$0
MOVQ $0, R9 // arg 5: offset = 0
MOVQ $9, AX // syscall: mmap
SYSCALL
CMPQ $0, AX
JGE rt_alloc_ok
XORQ AX, AX
rt_alloc_ok:
RET
TEXT rt_free,$0

View File

@@ -37,7 +37,12 @@ export fn rt_ensure(s: *slice, membsz: u64) void = {
let nc: i64 = s.cap * 2i64;
if (nc < 8i64) { nc = 8i64; };
for (nc < s.len) { nc *= 2i64; };
let np: *u8 = alloc((nc: u64) * membsz): *u8;
// Task #30: the alloc builtin returns `(*T | nomem)`; `!` aborts
// on OOM. The longstanding sizeof-only allocation bug (task #27)
// stays unfixed here — rt_ensure presumes a same-module `fn
// alloc(n)` resolution that the bare cur_mod=NULL gate at
// cmd/wcc/check.c:984 doesn't honour.
let np: *u8 = alloc((nc: u64) * membsz)!: *u8;
let n: u64 = (s.cap: u64) * membsz;
let i: u64 = 0u64;
for (i < n) {