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

@@ -2711,7 +2711,14 @@ fn cgbin(c: *cgen, n: *node) void = {
// region. For an N_STRUCTLIT arg, allocate the struct's totsize and
// emit per-field stores at each field's offset. For a scalar/ptr,
// allocate 8 bytes and store one word. Mirrors cmd/w6c/cgen.c's
// alloc-special branch in N_CALL. Returns the heap ptr in AX.
// alloc-special branch in N_CALL.
//
// Task #30: result is the graduated `(*T | nomem)` tagged-pointer
// pair (AX=tag, DX=ptr). rt_alloc now returns 0 on OOM
// (rt/alloc.s); branch on AX to emit the nomem variant (tag=1,
// DX=0) or the success variant (tag=0, DX=ptr) after the
// value-init stores complete. Callers wrap with `!` / `?` to
// consume the union.
fn cgalloc(c: *cgen, n: *node) void = {
let v: *node = n.list;
let sz: i32 = 8;
@@ -2727,10 +2734,18 @@ fn cgalloc(c: *cgen, n: *node) void = {
si = structlookup(c, sname);
if (si != nil) { sz = si.totsize; };
};
let okl: str = mklabel(c, "alloc_ok");
let donel: str = mklabel(c, "alloc_done");
emitline("\tMOVQ\t$");
emitint(sz: i64);
emitline(", DI\n");
emitline("\tCALL\trt_alloc(SB)\n");
emitline("\tCMPQ\t$0, AX\n");
emitline("\tJNE\t"); emitline(okl); emitline("\n");
emitline("\tMOVQ\t$1, AX\n");
emitline("\tMOVQ\t$0, DX\n");
emitline("\tJMP\t"); emitline(donel); emitline("\n");
emitlabel(okl);
emitline("\tPUSHQ\tAX\n");
if (v.kind == nkind.N_STRUCTLIT) {
if (si != nil) {
@@ -2783,7 +2798,9 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitline(sop);
emitline("\tAX, (BX)\n");
};
emitline("\tPOPQ\tAX\n");
emitline("\tPOPQ\tDX\n");
emitline("\tMOVQ\t$0, AX\n");
emitlabel(donel);
};
// cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.