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. * bytes. Size comes from the value's static type.
* `n->lhs->type == ty_err` gate (mirrors assert above) * `n->lhs->type == ty_err` gate (mirrors assert above)
* — check.c only stamps ty_err when no user-scoped * — 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; Node *v = n->list;
Type *t = v->type; Type *t = v->type;
Type *u = (t && t->kind == TY_NAMED) ? t->under : t; Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
Type *def = type_default(t); Type *def = type_default(t);
int sz = def ? (int)def->size : 8; int sz = def ? (int)def->size : 8;
if (sz == 0) sz = 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)); ins2(c, A_MOVQ, aimm(sz), areg(D_DI));
ins1(c, A_CALL, asym(ffi_resolve("alloc"))); 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 */ ins1(c, A_PUSHQ, areg(D_AX)); /* save ptr */
if (v->kind == N_STRUCTLIT && u && u->kind == TY_STRUCT) { if (v->kind == N_STRUCTLIT && u && u->kind == TY_STRUCT) {
for (Node *f = v->list; f; f = f->next) { 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; else if (sz == 4) op = A_MOVL;
ins2(c, op, areg(D_AX), amem(D_BX, 0)); 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; break;
} }
if (n->lhs && n->lhs->kind == N_IDENT && n->lhs->str && 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); int isf32 = type_isf32(lt);
/* alloc([], n) initialiser for a slice local: allocate /* alloc([], n) initialiser for a slice local: allocate
* n*esize bytes, build the {ptr, 0, n} header in the slot. * n*esize bytes, build the {ptr, 0, n} header in the slot.
* Element size comes from the declared slice type. */ * 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 * Task #30 graduated the builtin to `([]T | nomem)`. The let
&& n->rhs->lhs->kind == N_IDENT * declares a bare `[]T`, so the canonical idiom wraps in `!`
&& strcmp(n->rhs->lhs->str, "alloc") == 0 * to abort on OOM; walk into the N_TRYUNW to keep the
&& n->rhs->list && n->rhs->list->kind == N_ARRLIT * direct-store fast path. */
&& n->rhs->list->list == NULL {
&& n->rhs->list->next && n->rhs->list->next->next == NULL) { Node *call = NULL;
Node *count = n->rhs->list->next; int via_tryunw = 0;
int esz = (lu->sub) ? (int)lu->sub->size : 1; if (n->rhs && n->rhs->kind == N_TRYUNW && n->rhs->lhs
cgexpr(c, count, *locals); /* AX = n */ && n->rhs->lhs->kind == N_CALL) {
ins1(c, A_PUSHQ, areg(D_AX)); /* save count */ call = n->rhs->lhs;
if (esz > 1) { via_tryunw = 1;
ins2(c, A_MOVQ, aimm(esz), areg(D_BX)); }
ins2(c, A_IMULQ, areg(D_BX), areg(D_AX)); 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). */ /* str initialiser: cgexpr produces (AX=ptr, BX=len). */
if (n->rhs && type_isstr(lt) && sz == 16) { if (n->rhs && type_isstr(lt) && sz == 16) {

View File

@@ -985,7 +985,22 @@ cexpr(Checker *c, Node *n)
scope_lookup_in_module(c->cur, c->cur_mod, "alloc"))) { scope_lookup_in_module(c->cur, c->cur_mod, "alloc"))) {
Type *t = cexpr(c, n->list); Type *t = cexpr(c, n->list);
Type *def = type_default(t); 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; n->lhs->type = ty_err;
return n->type; return n->type;
} }
@@ -1046,7 +1061,22 @@ cexpr(Checker *c, Node *n)
!(c->cur_mod && !(c->cur_mod &&
scope_lookup_in_module(c->cur, c->cur_mod, "alloc"))) { scope_lookup_in_module(c->cur, c->cur_mod, "alloc"))) {
(void)cexpr(c, n->list->next); (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; n->lhs->type = ty_err;
return n->type; return n->type;
} }

View File

@@ -5,6 +5,12 @@
// //
// We pin to PROT_READ|PROT_WRITE and MAP_PRIVATE|MAP_ANONYMOUS so // We pin to PROT_READ|PROT_WRITE and MAP_PRIVATE|MAP_ANONYMOUS so
// callers never have to plumb file descriptors through. // 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 TEXT rt_alloc,$0
MOVQ DI, SI // arg 1: length = caller's n 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 $0, R9 // arg 5: offset = 0
MOVQ $9, AX // syscall: mmap MOVQ $9, AX // syscall: mmap
SYSCALL SYSCALL
CMPQ $0, AX
JGE rt_alloc_ok
XORQ AX, AX
rt_alloc_ok:
RET RET
TEXT rt_free,$0 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; let nc: i64 = s.cap * 2i64;
if (nc < 8i64) { nc = 8i64; }; if (nc < 8i64) { nc = 8i64; };
for (nc < s.len) { nc *= 2i64; }; 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 n: u64 = (s.cap: u64) * membsz;
let i: u64 = 0u64; let i: u64 = 0u64;
for (i < n) { for (i < n) {

View File

@@ -14581,7 +14581,14 @@ fn cgbin(c: *cgen, n: *node) void = {
// region. For an N_STRUCTLIT arg, allocate the struct's totsize and // 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, // 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 // 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 = { fn cgalloc(c: *cgen, n: *node) void = {
let v: *node = n.list; let v: *node = n.list;
let sz: i32 = 8; let sz: i32 = 8;
@@ -14597,10 +14604,18 @@ fn cgalloc(c: *cgen, n: *node) void = {
si = structlookup(c, sname); si = structlookup(c, sname);
if (si != nil) { sz = si.totsize; }; if (si != nil) { sz = si.totsize; };
}; };
let okl: str = mklabel(c, "alloc_ok");
let donel: str = mklabel(c, "alloc_done");
emitline("\tMOVQ\t$"); emitline("\tMOVQ\t$");
emitint(sz: i64); emitint(sz: i64);
emitline(", DI\n"); emitline(", DI\n");
emitline("\tCALL\trt_alloc(SB)\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"); emitline("\tPUSHQ\tAX\n");
if (v.kind == nkind.N_STRUCTLIT) { if (v.kind == nkind.N_STRUCTLIT) {
if (si != nil) { if (si != nil) {
@@ -14653,7 +14668,9 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitline(sop); emitline(sop);
emitline("\tAX, (BX)\n"); 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. // cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.

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 // 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, // 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 // 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 = { fn cgalloc(c: *cgen, n: *node) void = {
let v: *node = n.list; let v: *node = n.list;
let sz: i32 = 8; let sz: i32 = 8;
@@ -2727,10 +2734,18 @@ fn cgalloc(c: *cgen, n: *node) void = {
si = structlookup(c, sname); si = structlookup(c, sname);
if (si != nil) { sz = si.totsize; }; if (si != nil) { sz = si.totsize; };
}; };
let okl: str = mklabel(c, "alloc_ok");
let donel: str = mklabel(c, "alloc_done");
emitline("\tMOVQ\t$"); emitline("\tMOVQ\t$");
emitint(sz: i64); emitint(sz: i64);
emitline(", DI\n"); emitline(", DI\n");
emitline("\tCALL\trt_alloc(SB)\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"); emitline("\tPUSHQ\tAX\n");
if (v.kind == nkind.N_STRUCTLIT) { if (v.kind == nkind.N_STRUCTLIT) {
if (si != nil) { if (si != nil) {
@@ -2783,7 +2798,9 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitline(sop); emitline(sop);
emitline("\tAX, (BX)\n"); 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. // cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.

View File

@@ -14581,7 +14581,14 @@ fn cgbin(c: *cgen, n: *node) void = {
// region. For an N_STRUCTLIT arg, allocate the struct's totsize and // 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, // 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 // 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 = { fn cgalloc(c: *cgen, n: *node) void = {
let v: *node = n.list; let v: *node = n.list;
let sz: i32 = 8; let sz: i32 = 8;
@@ -14597,10 +14604,18 @@ fn cgalloc(c: *cgen, n: *node) void = {
si = structlookup(c, sname); si = structlookup(c, sname);
if (si != nil) { sz = si.totsize; }; if (si != nil) { sz = si.totsize; };
}; };
let okl: str = mklabel(c, "alloc_ok");
let donel: str = mklabel(c, "alloc_done");
emitline("\tMOVQ\t$"); emitline("\tMOVQ\t$");
emitint(sz: i64); emitint(sz: i64);
emitline(", DI\n"); emitline(", DI\n");
emitline("\tCALL\trt_alloc(SB)\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"); emitline("\tPUSHQ\tAX\n");
if (v.kind == nkind.N_STRUCTLIT) { if (v.kind == nkind.N_STRUCTLIT) {
if (si != nil) { if (si != nil) {
@@ -14653,7 +14668,9 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitline(sop); emitline(sop);
emitline("\tAX, (BX)\n"); 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. // cgappend — Hare-style `append(s, v)` / `append(s, items...)` lowering.

View File

@@ -13,10 +13,19 @@
// Two match arms cover both runtime outcomes — success unwrap (tag=0, // Two match arms cover both runtime outcomes — success unwrap (tag=0,
// ptr payload in DX) and error propagation (tag=1) — exercising the // ptr payload in DX) and error propagation (tag=1) — exercising the
// same AX/DX ABI both stages must agree on. // same AX/DX ABI both stages must agree on.
//
// Task #30: the `alloc` builtin itself now returns `(*T | nomem)`. The
// allocbox arm below propagates the builtin's tagged return through
// the enclosing fn via `?` against a same-shape `(*point | nomem)`
// — matching the team-lead spec's "exercise `alloc(T)?` against a
// real function returning `(T | nomem)`".
package test; package main;
import fmt; import fmt;
import os;
type point = struct { x: i32, y: i32 };
fn alloc1(fail: i64) (*u8 | nomem) = { fn alloc1(fail: i64) (*u8 | nomem) = {
if (fail != 0i64) { let e: nomem; return e; }; if (fail != 0i64) { let e: nomem; return e; };
@@ -29,6 +38,11 @@ fn caller(fail: i64) (*u8 | nomem) = {
return p; return p;
}; };
fn allocbox() (*point | nomem) = {
let p: *point = alloc(point { x = 3, y = 4 })?;
return p;
};
export fn main() i32 = { export fn main() i32 = {
let rc: i32 = 0; let rc: i32 = 0;
match (caller(0i64)) { match (caller(0i64)) {
@@ -50,5 +64,15 @@ export fn main() i32 = {
fmt.println("nomem as expected"); fmt.println("nomem as expected");
}; };
}; };
match (allocbox()) {
case let p: *point => {
fmt.println("allocbox ok");
if (p.x * p.x + p.y * p.y != 25) { rc = 4; };
};
case nomem => {
fmt.println("allocbox unexpected nomem");
rc = 5;
};
};
return rc; return rc;
}; };

View File

@@ -277,12 +277,13 @@ static const struct row rows[] = {
/* alloc() builtin: heap-allocate a struct, init from struct-lit. /* alloc() builtin: heap-allocate a struct, init from struct-lit.
* `package main;` is required so the bare-alloc-builtin gate * `package main;` is required so the bare-alloc-builtin gate
* (task #23) sees c->cur_mod=="main" and doesn't suppress the * (task #23) sees c->cur_mod=="main" and doesn't suppress the
* builtin via the inherited os.alloc decl. */ * builtin via the inherited os.alloc decl. Task #30 graduated
* the builtin to `(*T | nomem)`; the `!` aborts on OOM. */
{ "package main;\n" { "package main;\n"
"import os;\n" "import os;\n"
"type point = struct { x: i32, y: i32 };\n" "type point = struct { x: i32, y: i32 };\n"
"fn main() i32 = {\n" "fn main() i32 = {\n"
" let p: *point = alloc(point { x = 3, y = 4 });\n" " let p: *point = alloc(point { x = 3, y = 4 })!;\n"
" return p.x * p.x + p.y * p.y;\n" " return p.x * p.x + p.y * p.y;\n"
"};", 25 }, "};", 25 },
/* Hare-style range loop: for (let x .. slice) iterates elements */ /* Hare-style range loop: for (let x .. slice) iterates elements */
@@ -296,11 +297,13 @@ static const struct row rows[] = {
" return total;\n" " return total;\n"
"};", 100 }, "};", 100 },
/* alloc([], n): fresh empty slice with cap n. `package main;` for /* alloc([], n): fresh empty slice with cap n. `package main;` for
* the same reason as the value-form test above (task #23 gate). */ * the same reason as the value-form test above (task #23 gate).
* Task #30 graduated the slice form to `([]T | nomem)`; the `!`
* aborts on OOM. */
{ "package main;\n" { "package main;\n"
"import os;\n" "import os;\n"
"fn main() i32 = {\n" "fn main() i32 = {\n"
" let s: []u8 = alloc([], 16);\n" " let s: []u8 = alloc([], 16)!;\n"
" append(s, 72u8, 105u8);\n" " append(s, 72u8, 105u8);\n"
" return s.cap;\n" " return s.cap;\n"
"};", 16 }, "};", 16 },
@@ -358,12 +361,13 @@ static const struct row rows[] = {
" return (t.0 + t.1): i32;\n" " return (t.0 + t.1): i32;\n"
"};", 42 }, "};", 42 },
/* Hare-style abort/assert + free() builtin. `package main;` for /* Hare-style abort/assert + free() builtin. `package main;` for
* the alloc-builtin gate (task #23). */ * the alloc-builtin gate (task #23). Task #30 graduated the
* builtin to a fallible signature; the `!` aborts on OOM. */
{ "package main;\n" { "package main;\n"
"import os;\n" "import os;\n"
"type point = struct { x: i64, y: i64 };\n" "type point = struct { x: i64, y: i64 };\n"
"fn main() i32 = {\n" "fn main() i32 = {\n"
" let p: *point = alloc(point { x = 7, y = 35 });\n" " let p: *point = alloc(point { x = 7, y = 35 })!;\n"
" let r: i64 = p.x + p.y;\n" " let r: i64 = p.x + p.y;\n"
" free(p);\n" " free(p);\n"
" os.assert(r == 42, \"sum mismatch\\n\");\n" " os.assert(r == 42, \"sum mismatch\\n\");\n"