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

@@ -985,7 +985,22 @@ cexpr(Checker *c, Node *n)
scope_lookup_in_module(c->cur, c->cur_mod, "alloc"))) {
Type *t = cexpr(c, n->list);
Type *def = type_default(t);
n->type = type_ptr(c->a, def ? def : ty_void);
Type *pt = type_ptr(c->a, def ? def : ty_void);
/* Task #30 — graduate to Hare's `(*T | nomem)` shape;
* the cgen branches on rt_alloc's null return to emit
* the nomem variant. Two-variant union with TY_PTR +
* TY_NAMED(nomem) does not trip the nullable-pointer
* fold (#25), so the result rides the general AX=tag,
* DX=ptr ABI both stages already share. */
Type *tt = newtype(c->a, TY_TAGGED);
Tparam *vp = amalloc(c->a, sizeof *vp);
Tparam *ve = amalloc(c->a, sizeof *ve);
vp->type = pt; vp->next = ve;
ve->type = ty_nomem; ve->next = NULL;
tt->params = vp;
tt->size = 16;
tt->align = 8;
n->type = tt;
n->lhs->type = ty_err;
return n->type;
}
@@ -1046,7 +1061,22 @@ cexpr(Checker *c, Node *n)
!(c->cur_mod &&
scope_lookup_in_module(c->cur, c->cur_mod, "alloc"))) {
(void)cexpr(c, n->list->next);
n->type = type_slice(c->a, ty_u8);
Type *st = type_slice(c->a, ty_u8);
/* Task #30 — slice form graduates the same way:
* `alloc([], n)` now returns `([]T | nomem)`. Slot is
* 8 (tag) + 24 (slice payload) = 32B. The element type
* defaults to u8 here; the let-init shortcut in
* cmd/w6c/cgen.c N_LET drives the real element size
* from the declared slice type. */
Type *tt = newtype(c->a, TY_TAGGED);
Tparam *vs = amalloc(c->a, sizeof *vs);
Tparam *ve = amalloc(c->a, sizeof *ve);
vs->type = st; vs->next = ve;
ve->type = ty_nomem; ve->next = NULL;
tt->params = vs;
tt->size = 32;
tt->align = 8;
n->type = tt;
n->lhs->type = ty_err;
return n->type;
}