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

@@ -4130,16 +4130,30 @@ cgexpr(Cg *c, Node *n, Local *locals)
* bytes. Size comes from the value's static type.
* `n->lhs->type == ty_err` gate (mirrors assert above)
* — check.c only stamps ty_err when no user-scoped
* `alloc` shadows the builtin (task #23). */
* `alloc` shadows the builtin (task #23).
*
* Result is the graduated `(*T | nomem)` tagged-pointer
* ABI (AX=tag, DX=ptr) — task #30. rt_alloc returns 0
* on OOM (rt/alloc.s); we branch on AX, building tag=1
* (nomem, DX=0) on null and tag=0 (success, DX=ptr)
* after the value-init stores complete. Callers wrap
* with `!` / `?` to consume the union. */
Node *v = n->list;
Type *t = v->type;
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
Type *def = type_default(t);
int sz = def ? (int)def->size : 8;
if (sz == 0) sz = 8;
/* call os.alloc(sz) */
char *alloc_ok = mklabel(c, "alloc_ok");
char *alloc_done = mklabel(c, "alloc_done");
ins2(c, A_MOVQ, aimm(sz), areg(D_DI));
ins1(c, A_CALL, asym(ffi_resolve("alloc")));
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
ins1(c, A_JNE, abranch(alloc_ok));
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
ins2(c, A_MOVQ, aimm(0), areg(D_DX));
ins1(c, A_JMP, abranch(alloc_done));
label(c, alloc_ok);
ins1(c, A_PUSHQ, areg(D_AX)); /* save ptr */
if (v->kind == N_STRUCTLIT && u && u->kind == TY_STRUCT) {
for (Node *f = v->list; f; f = f->next) {
@@ -4190,7 +4204,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
else if (sz == 4) op = A_MOVL;
ins2(c, op, areg(D_AX), amem(D_BX, 0));
}
ins1(c, A_POPQ, areg(D_AX)); /* return the ptr */
ins1(c, A_POPQ, areg(D_DX)); /* DX = success ptr */
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
label(c, alloc_done);
break;
}
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str &&
@@ -6346,29 +6362,52 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
int isf32 = type_isf32(lt);
/* alloc([], n) initialiser for a slice local: allocate
* n*esize bytes, build the {ptr, 0, n} header in the slot.
* Element size comes from the declared slice type. */
if (n->rhs && lu && lu->kind == TY_SLICE && sz == 24
&& n->rhs->kind == N_CALL && n->rhs->lhs
&& n->rhs->lhs->kind == N_IDENT
&& strcmp(n->rhs->lhs->str, "alloc") == 0
&& n->rhs->list && n->rhs->list->kind == N_ARRLIT
&& n->rhs->list->list == NULL
&& n->rhs->list->next && n->rhs->list->next->next == NULL) {
Node *count = n->rhs->list->next;
int esz = (lu->sub) ? (int)lu->sub->size : 1;
cgexpr(c, count, *locals); /* AX = n */
ins1(c, A_PUSHQ, areg(D_AX)); /* save count */
if (esz > 1) {
ins2(c, A_MOVQ, aimm(esz), areg(D_BX));
ins2(c, A_IMULQ, areg(D_BX), areg(D_AX));
* Element size comes from the declared slice type.
*
* Task #30 graduated the builtin to `([]T | nomem)`. The let
* declares a bare `[]T`, so the canonical idiom wraps in `!`
* to abort on OOM; walk into the N_TRYUNW to keep the
* direct-store fast path. */
{
Node *call = NULL;
int via_tryunw = 0;
if (n->rhs && n->rhs->kind == N_TRYUNW && n->rhs->lhs
&& n->rhs->lhs->kind == N_CALL) {
call = n->rhs->lhs;
via_tryunw = 1;
}
if (call && lu && lu->kind == TY_SLICE && sz == 24
&& call->lhs && call->lhs->kind == N_IDENT
&& strcmp(call->lhs->str, "alloc") == 0
&& call->list && call->list->kind == N_ARRLIT
&& call->list->list == NULL
&& call->list->next
&& call->list->next->next == NULL) {
Node *count = call->list->next;
int esz = (lu->sub) ? (int)lu->sub->size : 1;
cgexpr(c, count, *locals); /* AX = n */
ins1(c, A_PUSHQ, areg(D_AX)); /* save count */
if (esz > 1) {
ins2(c, A_MOVQ, aimm(esz), areg(D_BX));
ins2(c, A_IMULQ, areg(D_BX), areg(D_AX));
}
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
ins1(c, A_CALL, asym(ffi_resolve("alloc")));
if (via_tryunw) {
char *ok = mklabel(c, "tryunw_ok");
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
ins1(c, A_JNE, abranch(ok));
ins2(c, A_MOVQ, aimm(1), areg(D_DI));
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
ins0(c, A_SYSCALL);
label(c, ok);
}
ins1(c, A_POPQ, areg(D_BX)); /* count */
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
ins2(c, A_MOVQ, aimm(0), amem(D_BP, off + 8));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 16));
break;
}
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
ins1(c, A_CALL, asym(ffi_resolve("alloc")));
ins1(c, A_POPQ, areg(D_BX)); /* count */
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
ins2(c, A_MOVQ, aimm(0), amem(D_BP, off + 8));
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 16));
break;
}
/* str initialiser: cgexpr produces (AX=ptr, BX=len). */
if (n->rhs && type_isstr(lt) && sz == 16) {