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:
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
10
rt/alloc.s
10
rt/alloc.s
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user