cmd+selfhost+test: relax alloc-slice element-type pin via LHS retype
`alloc([], n)` synthesizes ([]u8 | nomem) at expression level — that's fine, since the slice form only legitimately appears in let-init position where the LHS carries the real element type. In clet, after type-checking the rhs, peel any N_TRYPROP/N_TRYUNW wrapper, match the alloc-slice AST shape with the same-module shadow gate (from #23), and retype the call's tagged return to ([]T | nomem) where T is the declared LHS element. Then assignability sees []T vs []T and accepts. Cgen N_LET shortcut gains a viatryprop arm next to the existing viatryunw — on rt_alloc returning null, emits the tagged-return nomem propagation (MOVQ $nidx, AX; epilogue) instead of exit(1). nidx comes from cg_tag_for_variant on the enclosing fn's return type, matching the existing TRYPROP propret path. Wwstage mirrors all four hunks (check.ww + cgenstmt.ww). Promotes the previously-silent conf=false skip into a confident accept. Unblocks #6 (dupall) and lays the path for #4/#7. Byte-identity holds modulo the pre-existing #44 alloc/rt_alloc symbol divergence.
This commit is contained in:
@@ -6366,15 +6366,22 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
*
|
*
|
||||||
* Task #30 graduated the builtin to `([]T | nomem)`. The let
|
* Task #30 graduated the builtin to `([]T | nomem)`. The let
|
||||||
* declares a bare `[]T`, so the canonical idiom wraps in `!`
|
* declares a bare `[]T`, so the canonical idiom wraps in `!`
|
||||||
* to abort on OOM; walk into the N_TRYUNW to keep the
|
* (abort on OOM) or `?` (propagate nomem to the enclosing
|
||||||
* direct-store fast path. */
|
* fn's tagged return). Task #45 extends the shortcut to also
|
||||||
|
* match N_TRYPROP and emit the propret pattern. */
|
||||||
{
|
{
|
||||||
Node *call = NULL;
|
Node *call = NULL;
|
||||||
int via_tryunw = 0;
|
int via_tryunw = 0;
|
||||||
|
int via_tryprop = 0;
|
||||||
if (n->rhs && n->rhs->kind == N_TRYUNW && n->rhs->lhs
|
if (n->rhs && n->rhs->kind == N_TRYUNW && n->rhs->lhs
|
||||||
&& n->rhs->lhs->kind == N_CALL) {
|
&& n->rhs->lhs->kind == N_CALL) {
|
||||||
call = n->rhs->lhs;
|
call = n->rhs->lhs;
|
||||||
via_tryunw = 1;
|
via_tryunw = 1;
|
||||||
|
} else if (n->rhs && n->rhs->kind == N_TRYPROP
|
||||||
|
&& n->rhs->lhs
|
||||||
|
&& n->rhs->lhs->kind == N_CALL) {
|
||||||
|
call = n->rhs->lhs;
|
||||||
|
via_tryprop = 1;
|
||||||
}
|
}
|
||||||
if (call && lu && lu->kind == TY_SLICE && sz == 24
|
if (call && lu && lu->kind == TY_SLICE && sz == 24
|
||||||
&& call->lhs && call->lhs->kind == N_IDENT
|
&& call->lhs && call->lhs->kind == N_IDENT
|
||||||
@@ -6401,6 +6408,23 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||||
ins0(c, A_SYSCALL);
|
ins0(c, A_SYSCALL);
|
||||||
label(c, ok);
|
label(c, ok);
|
||||||
|
} else if (via_tryprop) {
|
||||||
|
/* #45: null = nomem; propagate to the
|
||||||
|
* enclosing fn's tagged return. AX = tag
|
||||||
|
* of nomem variant in cg_ret_type; epilogue
|
||||||
|
* RETs to caller. */
|
||||||
|
char *ok = mklabel(c, "tryprop_ok");
|
||||||
|
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||||
|
ins1(c, A_JNE, abranch(ok));
|
||||||
|
Type *r = cg_ret_type;
|
||||||
|
if (r && r->kind == TY_NAMED) r = r->under;
|
||||||
|
int nidx = cg_tag_for_variant(r, ty_nomem);
|
||||||
|
if (nidx < 0) nidx = 1;
|
||||||
|
ins2(c, A_MOVQ, aimm(nidx), areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||||
|
ins1(c, A_POPQ, areg(D_BP));
|
||||||
|
ins0(c, A_RET);
|
||||||
|
label(c, ok);
|
||||||
}
|
}
|
||||||
ins1(c, A_POPQ, areg(D_BX)); /* count */
|
ins1(c, A_POPQ, areg(D_BX)); /* count */
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||||
|
|||||||
@@ -1494,6 +1494,48 @@ clet(Checker *c, Node *n)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/* #45: alloc([], n) defers element type to the let-init context
|
||||||
|
* (Hare-style). cexpr's alloc-slice branch synthesizes
|
||||||
|
* ([]u8 | nomem) with no LHS context; when the let declares []T,
|
||||||
|
* retype the inner call (and any ?/! wrapper) to ([]T | nomem) /
|
||||||
|
* []T so the assignability check below succeeds for any T. cgen
|
||||||
|
* already drives element size from declared->sub at the N_LET
|
||||||
|
* shortcut (cmd/w6c/cgen.c). */
|
||||||
|
if (declared && declared->kind == TY_SLICE && declared->sub
|
||||||
|
&& declared->sub != ty_u8 && n->rhs) {
|
||||||
|
Node *wrap = NULL;
|
||||||
|
Node *call = n->rhs;
|
||||||
|
if (call->kind == N_TRYPROP || call->kind == N_TRYUNW) {
|
||||||
|
wrap = call;
|
||||||
|
call = call->lhs;
|
||||||
|
}
|
||||||
|
if (call && call->kind == N_CALL && call->lhs
|
||||||
|
&& call->lhs->kind == N_IDENT
|
||||||
|
&& call->lhs->type == ty_err
|
||||||
|
&& call->lhs->str
|
||||||
|
&& 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) {
|
||||||
|
Type *st = type_slice(c->a, declared->sub);
|
||||||
|
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;
|
||||||
|
call->type = tt;
|
||||||
|
if (wrap) {
|
||||||
|
wrap->type = st;
|
||||||
|
initt = st;
|
||||||
|
} else {
|
||||||
|
initt = tt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (declared && initt && initt != ty_err && !has_arr_repeat &&
|
if (declared && initt && initt != ty_err && !has_arr_repeat &&
|
||||||
!type_assignable(declared, initt))
|
!type_assignable(declared, initt))
|
||||||
err(c, n->pos, "init %s not assignable to declared %s",
|
err(c, n->pos, "init %s not assignable to declared %s",
|
||||||
|
|||||||
@@ -7837,6 +7837,58 @@ fn checkletassign(c: *checker, n: *node) void = {
|
|||||||
if (n.rhs == nil) { return; }; // no init
|
if (n.rhs == nil) { return; }; // no init
|
||||||
let src: *node = exprtype(c, n.rhs);
|
let src: *node = exprtype(c, n.rhs);
|
||||||
if (src == nil) { return; }; // can't infer
|
if (src == nil) { return; }; // can't infer
|
||||||
|
// #45: alloc([], n) defers element type to the let-init context
|
||||||
|
// (Hare-style). exprtype's alloc-slice branch synthesizes
|
||||||
|
// ([]u8 | nomem) / []u8 (for the ?/! wrap) with no LHS context;
|
||||||
|
// when the let declares []T, retype src to []T / ([]T | nomem)
|
||||||
|
// so isassignable sees exact equality. cgenstmt cglet drives the
|
||||||
|
// element size from n.lhs already (cmd/wcc/cgenstmt.ww), so this
|
||||||
|
// stays symmetric with cstage check.c clet's parallel retype.
|
||||||
|
if (n.lhs.kind == nkind.N_TSLICE) {
|
||||||
|
let wrapped: bool = false;
|
||||||
|
let inner: *node = n.rhs;
|
||||||
|
if (inner.kind == nkind.N_TRYPROP) {
|
||||||
|
wrapped = true;
|
||||||
|
inner = inner.lhs;
|
||||||
|
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||||
|
wrapped = true;
|
||||||
|
inner = inner.lhs;
|
||||||
|
}; };
|
||||||
|
if (inner != nil && inner.kind == nkind.N_CALL) {
|
||||||
|
let callee: *node = inner.lhs;
|
||||||
|
let a0: *node = inner.list;
|
||||||
|
let a1: *node = nil;
|
||||||
|
let a2: *node = nil;
|
||||||
|
if (a0 != nil) { a1 = a0.next; };
|
||||||
|
if (a1 != nil) { a2 = a1.next; };
|
||||||
|
if (callee != nil
|
||||||
|
&& callee.kind == nkind.N_IDENT
|
||||||
|
&& streq(callee.str, "alloc")
|
||||||
|
&& a0 != nil && a0.kind == nkind.N_ARRLIT
|
||||||
|
&& a0.list == nil
|
||||||
|
&& a1 != nil && a2 == nil) {
|
||||||
|
let shadowed: bool = false;
|
||||||
|
if (c.curmod.len > 0) {
|
||||||
|
if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) {
|
||||||
|
shadowed = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (!shadowed) {
|
||||||
|
let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
|
||||||
|
sl.lhs = n.lhs.lhs;
|
||||||
|
if (wrapped) {
|
||||||
|
src = sl;
|
||||||
|
} else {
|
||||||
|
let nome: *node = mktname(c, "nomem");
|
||||||
|
sl.next = nome;
|
||||||
|
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||||
|
tt.list = sl;
|
||||||
|
src = tt;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
let conf: bool = false;
|
let conf: bool = false;
|
||||||
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||||
if (!conf) { return; };
|
if (!conf) { return; };
|
||||||
@@ -18353,17 +18405,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let off: i32 = localadd(c, nm, sz, tn);
|
let off: i32 = localadd(c, nm, sz, tn);
|
||||||
if (n.rhs != nil) {
|
if (n.rhs != nil) {
|
||||||
let rhs: *node = n.rhs;
|
let rhs: *node = n.rhs;
|
||||||
// `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of
|
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||||
// cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate
|
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
|
||||||
// n*esz bytes via rt_alloc, exit(1) on null, then build the
|
// n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice
|
||||||
// {ptr, 0, n} slice header in the let slot. The `!` wraps the
|
// header in the let slot. The `!`/`?` wraps the builtin's
|
||||||
// builtin's `([]T | nomem)` return; walk into the N_TRYUNW to
|
// `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP
|
||||||
// keep the direct-store fast path rather than falling through
|
// to keep the direct-store fast path rather than falling
|
||||||
// to cgalloc (which models scalar alloc and would land an 8B
|
// through to cgalloc (which models scalar alloc and would
|
||||||
// region and a junk slice header).
|
// land an 8B region and a junk slice header). `?` propagates
|
||||||
|
// nomem via AX = tag of nomem in c.fnret, then epilogue RET.
|
||||||
{
|
{
|
||||||
let scall: *node = nil;
|
let scall: *node = nil;
|
||||||
let viatryunw: bool = false;
|
let viatryunw: bool = false;
|
||||||
|
let viatryprop: bool = false;
|
||||||
if (rhs.kind == nkind.N_TRYUNW) {
|
if (rhs.kind == nkind.N_TRYUNW) {
|
||||||
if (rhs.lhs != nil) {
|
if (rhs.lhs != nil) {
|
||||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||||
@@ -18371,7 +18425,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
viatryunw = true;
|
viatryunw = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
} else { if (rhs.kind == nkind.N_TRYPROP) {
|
||||||
|
if (rhs.lhs != nil) {
|
||||||
|
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||||
|
scall = rhs.lhs;
|
||||||
|
viatryprop = true;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
}; };
|
||||||
let shapeok: bool = false;
|
let shapeok: bool = false;
|
||||||
if (scall != nil && tn != nil
|
if (scall != nil && tn != nil
|
||||||
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
||||||
@@ -18397,10 +18458,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// names too — not just primitives. elemsizeofc follows
|
// names too — not just primitives. elemsizeofc follows
|
||||||
// TNAME through structlookup/aliaslookup, matching the
|
// TNAME through structlookup/aliaslookup, matching the
|
||||||
// cstage path byte-identically. A bare primsize/slotsize
|
// cstage path byte-identically. A bare primsize/slotsize
|
||||||
// fork would silently land esz=1 on `[]point` (today
|
// fork would silently land esz=1 on `[]point`.
|
||||||
// blocked at check.c, but the defensive cgen path must
|
|
||||||
// stay byte-identical with cstage for the moment check
|
|
||||||
// relaxes).
|
|
||||||
let esz: i32 = elemsizeofc(c, tn);
|
let esz: i32 = elemsizeofc(c, tn);
|
||||||
let count: *node = scall.list.next;
|
let count: *node = scall.list.next;
|
||||||
cgexpr(c, count);
|
cgexpr(c, count);
|
||||||
@@ -18424,6 +18482,24 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tSYSCALL\n");
|
emitline("\tSYSCALL\n");
|
||||||
emitlabel(okl);
|
emitlabel(okl);
|
||||||
};
|
};
|
||||||
|
if (viatryprop) {
|
||||||
|
// #45: null = nomem; propagate to the
|
||||||
|
// enclosing fn's tagged return. AX = tag
|
||||||
|
// of nomem variant in c.fnret, epilogue
|
||||||
|
// RETs to caller.
|
||||||
|
let okl: str = mklabel(c, "tryprop_ok");
|
||||||
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n");
|
||||||
|
let nidx: i32 = flatvariantidx(c, c.fnret, "nomem");
|
||||||
|
if (nidx < 0) { nidx = 1; };
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(nidx: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
};
|
||||||
emitline("\tPOPQ\tBX\n");
|
emitline("\tPOPQ\tBX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
|
|||||||
@@ -574,17 +574,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let off: i32 = localadd(c, nm, sz, tn);
|
let off: i32 = localadd(c, nm, sz, tn);
|
||||||
if (n.rhs != nil) {
|
if (n.rhs != nil) {
|
||||||
let rhs: *node = n.rhs;
|
let rhs: *node = n.rhs;
|
||||||
// `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of
|
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||||
// cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate
|
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
|
||||||
// n*esz bytes via rt_alloc, exit(1) on null, then build the
|
// n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice
|
||||||
// {ptr, 0, n} slice header in the let slot. The `!` wraps the
|
// header in the let slot. The `!`/`?` wraps the builtin's
|
||||||
// builtin's `([]T | nomem)` return; walk into the N_TRYUNW to
|
// `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP
|
||||||
// keep the direct-store fast path rather than falling through
|
// to keep the direct-store fast path rather than falling
|
||||||
// to cgalloc (which models scalar alloc and would land an 8B
|
// through to cgalloc (which models scalar alloc and would
|
||||||
// region and a junk slice header).
|
// land an 8B region and a junk slice header). `?` propagates
|
||||||
|
// nomem via AX = tag of nomem in c.fnret, then epilogue RET.
|
||||||
{
|
{
|
||||||
let scall: *node = nil;
|
let scall: *node = nil;
|
||||||
let viatryunw: bool = false;
|
let viatryunw: bool = false;
|
||||||
|
let viatryprop: bool = false;
|
||||||
if (rhs.kind == nkind.N_TRYUNW) {
|
if (rhs.kind == nkind.N_TRYUNW) {
|
||||||
if (rhs.lhs != nil) {
|
if (rhs.lhs != nil) {
|
||||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||||
@@ -592,7 +594,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
viatryunw = true;
|
viatryunw = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
} else { if (rhs.kind == nkind.N_TRYPROP) {
|
||||||
|
if (rhs.lhs != nil) {
|
||||||
|
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||||
|
scall = rhs.lhs;
|
||||||
|
viatryprop = true;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
}; };
|
||||||
let shapeok: bool = false;
|
let shapeok: bool = false;
|
||||||
if (scall != nil && tn != nil
|
if (scall != nil && tn != nil
|
||||||
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
||||||
@@ -618,10 +627,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// names too — not just primitives. elemsizeofc follows
|
// names too — not just primitives. elemsizeofc follows
|
||||||
// TNAME through structlookup/aliaslookup, matching the
|
// TNAME through structlookup/aliaslookup, matching the
|
||||||
// cstage path byte-identically. A bare primsize/slotsize
|
// cstage path byte-identically. A bare primsize/slotsize
|
||||||
// fork would silently land esz=1 on `[]point` (today
|
// fork would silently land esz=1 on `[]point`.
|
||||||
// blocked at check.c, but the defensive cgen path must
|
|
||||||
// stay byte-identical with cstage for the moment check
|
|
||||||
// relaxes).
|
|
||||||
let esz: i32 = elemsizeofc(c, tn);
|
let esz: i32 = elemsizeofc(c, tn);
|
||||||
let count: *node = scall.list.next;
|
let count: *node = scall.list.next;
|
||||||
cgexpr(c, count);
|
cgexpr(c, count);
|
||||||
@@ -645,6 +651,24 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tSYSCALL\n");
|
emitline("\tSYSCALL\n");
|
||||||
emitlabel(okl);
|
emitlabel(okl);
|
||||||
};
|
};
|
||||||
|
if (viatryprop) {
|
||||||
|
// #45: null = nomem; propagate to the
|
||||||
|
// enclosing fn's tagged return. AX = tag
|
||||||
|
// of nomem variant in c.fnret, epilogue
|
||||||
|
// RETs to caller.
|
||||||
|
let okl: str = mklabel(c, "tryprop_ok");
|
||||||
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n");
|
||||||
|
let nidx: i32 = flatvariantidx(c, c.fnret, "nomem");
|
||||||
|
if (nidx < 0) { nidx = 1; };
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(nidx: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
};
|
||||||
emitline("\tPOPQ\tBX\n");
|
emitline("\tPOPQ\tBX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
|
|||||||
@@ -975,6 +975,58 @@ fn checkletassign(c: *checker, n: *node) void = {
|
|||||||
if (n.rhs == nil) { return; }; // no init
|
if (n.rhs == nil) { return; }; // no init
|
||||||
let src: *node = exprtype(c, n.rhs);
|
let src: *node = exprtype(c, n.rhs);
|
||||||
if (src == nil) { return; }; // can't infer
|
if (src == nil) { return; }; // can't infer
|
||||||
|
// #45: alloc([], n) defers element type to the let-init context
|
||||||
|
// (Hare-style). exprtype's alloc-slice branch synthesizes
|
||||||
|
// ([]u8 | nomem) / []u8 (for the ?/! wrap) with no LHS context;
|
||||||
|
// when the let declares []T, retype src to []T / ([]T | nomem)
|
||||||
|
// so isassignable sees exact equality. cgenstmt cglet drives the
|
||||||
|
// element size from n.lhs already (cmd/wcc/cgenstmt.ww), so this
|
||||||
|
// stays symmetric with cstage check.c clet's parallel retype.
|
||||||
|
if (n.lhs.kind == nkind.N_TSLICE) {
|
||||||
|
let wrapped: bool = false;
|
||||||
|
let inner: *node = n.rhs;
|
||||||
|
if (inner.kind == nkind.N_TRYPROP) {
|
||||||
|
wrapped = true;
|
||||||
|
inner = inner.lhs;
|
||||||
|
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||||
|
wrapped = true;
|
||||||
|
inner = inner.lhs;
|
||||||
|
}; };
|
||||||
|
if (inner != nil && inner.kind == nkind.N_CALL) {
|
||||||
|
let callee: *node = inner.lhs;
|
||||||
|
let a0: *node = inner.list;
|
||||||
|
let a1: *node = nil;
|
||||||
|
let a2: *node = nil;
|
||||||
|
if (a0 != nil) { a1 = a0.next; };
|
||||||
|
if (a1 != nil) { a2 = a1.next; };
|
||||||
|
if (callee != nil
|
||||||
|
&& callee.kind == nkind.N_IDENT
|
||||||
|
&& streq(callee.str, "alloc")
|
||||||
|
&& a0 != nil && a0.kind == nkind.N_ARRLIT
|
||||||
|
&& a0.list == nil
|
||||||
|
&& a1 != nil && a2 == nil) {
|
||||||
|
let shadowed: bool = false;
|
||||||
|
if (c.curmod.len > 0) {
|
||||||
|
if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) {
|
||||||
|
shadowed = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (!shadowed) {
|
||||||
|
let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
|
||||||
|
sl.lhs = n.lhs.lhs;
|
||||||
|
if (wrapped) {
|
||||||
|
src = sl;
|
||||||
|
} else {
|
||||||
|
let nome: *node = mktname(c, "nomem");
|
||||||
|
sl.next = nome;
|
||||||
|
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||||
|
tt.list = sl;
|
||||||
|
src = tt;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
let conf: bool = false;
|
let conf: bool = false;
|
||||||
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||||
if (!conf) { return; };
|
if (!conf) { return; };
|
||||||
|
|||||||
@@ -7837,6 +7837,58 @@ fn checkletassign(c: *checker, n: *node) void = {
|
|||||||
if (n.rhs == nil) { return; }; // no init
|
if (n.rhs == nil) { return; }; // no init
|
||||||
let src: *node = exprtype(c, n.rhs);
|
let src: *node = exprtype(c, n.rhs);
|
||||||
if (src == nil) { return; }; // can't infer
|
if (src == nil) { return; }; // can't infer
|
||||||
|
// #45: alloc([], n) defers element type to the let-init context
|
||||||
|
// (Hare-style). exprtype's alloc-slice branch synthesizes
|
||||||
|
// ([]u8 | nomem) / []u8 (for the ?/! wrap) with no LHS context;
|
||||||
|
// when the let declares []T, retype src to []T / ([]T | nomem)
|
||||||
|
// so isassignable sees exact equality. cgenstmt cglet drives the
|
||||||
|
// element size from n.lhs already (cmd/wcc/cgenstmt.ww), so this
|
||||||
|
// stays symmetric with cstage check.c clet's parallel retype.
|
||||||
|
if (n.lhs.kind == nkind.N_TSLICE) {
|
||||||
|
let wrapped: bool = false;
|
||||||
|
let inner: *node = n.rhs;
|
||||||
|
if (inner.kind == nkind.N_TRYPROP) {
|
||||||
|
wrapped = true;
|
||||||
|
inner = inner.lhs;
|
||||||
|
} else { if (inner.kind == nkind.N_TRYUNW) {
|
||||||
|
wrapped = true;
|
||||||
|
inner = inner.lhs;
|
||||||
|
}; };
|
||||||
|
if (inner != nil && inner.kind == nkind.N_CALL) {
|
||||||
|
let callee: *node = inner.lhs;
|
||||||
|
let a0: *node = inner.list;
|
||||||
|
let a1: *node = nil;
|
||||||
|
let a2: *node = nil;
|
||||||
|
if (a0 != nil) { a1 = a0.next; };
|
||||||
|
if (a1 != nil) { a2 = a1.next; };
|
||||||
|
if (callee != nil
|
||||||
|
&& callee.kind == nkind.N_IDENT
|
||||||
|
&& streq(callee.str, "alloc")
|
||||||
|
&& a0 != nil && a0.kind == nkind.N_ARRLIT
|
||||||
|
&& a0.list == nil
|
||||||
|
&& a1 != nil && a2 == nil) {
|
||||||
|
let shadowed: bool = false;
|
||||||
|
if (c.curmod.len > 0) {
|
||||||
|
if (scopelookupinmodule(c.cur, c.curmod, "alloc") != nil) {
|
||||||
|
shadowed = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (!shadowed) {
|
||||||
|
let sl: *node = newnode(c.a, nkind.N_TSLICE, "", 0, 0);
|
||||||
|
sl.lhs = n.lhs.lhs;
|
||||||
|
if (wrapped) {
|
||||||
|
src = sl;
|
||||||
|
} else {
|
||||||
|
let nome: *node = mktname(c, "nomem");
|
||||||
|
sl.next = nome;
|
||||||
|
let tt: *node = newnode(c.a, nkind.N_TTAGGED, "", 0, 0);
|
||||||
|
tt.list = sl;
|
||||||
|
src = tt;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
let conf: bool = false;
|
let conf: bool = false;
|
||||||
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||||
if (!conf) { return; };
|
if (!conf) { return; };
|
||||||
@@ -18353,17 +18405,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let off: i32 = localadd(c, nm, sz, tn);
|
let off: i32 = localadd(c, nm, sz, tn);
|
||||||
if (n.rhs != nil) {
|
if (n.rhs != nil) {
|
||||||
let rhs: *node = n.rhs;
|
let rhs: *node = n.rhs;
|
||||||
// `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of
|
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||||
// cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate
|
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
|
||||||
// n*esz bytes via rt_alloc, exit(1) on null, then build the
|
// n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice
|
||||||
// {ptr, 0, n} slice header in the let slot. The `!` wraps the
|
// header in the let slot. The `!`/`?` wraps the builtin's
|
||||||
// builtin's `([]T | nomem)` return; walk into the N_TRYUNW to
|
// `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP
|
||||||
// keep the direct-store fast path rather than falling through
|
// to keep the direct-store fast path rather than falling
|
||||||
// to cgalloc (which models scalar alloc and would land an 8B
|
// through to cgalloc (which models scalar alloc and would
|
||||||
// region and a junk slice header).
|
// land an 8B region and a junk slice header). `?` propagates
|
||||||
|
// nomem via AX = tag of nomem in c.fnret, then epilogue RET.
|
||||||
{
|
{
|
||||||
let scall: *node = nil;
|
let scall: *node = nil;
|
||||||
let viatryunw: bool = false;
|
let viatryunw: bool = false;
|
||||||
|
let viatryprop: bool = false;
|
||||||
if (rhs.kind == nkind.N_TRYUNW) {
|
if (rhs.kind == nkind.N_TRYUNW) {
|
||||||
if (rhs.lhs != nil) {
|
if (rhs.lhs != nil) {
|
||||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||||
@@ -18371,7 +18425,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
viatryunw = true;
|
viatryunw = true;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
} else { if (rhs.kind == nkind.N_TRYPROP) {
|
||||||
|
if (rhs.lhs != nil) {
|
||||||
|
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||||
|
scall = rhs.lhs;
|
||||||
|
viatryprop = true;
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
}; };
|
||||||
let shapeok: bool = false;
|
let shapeok: bool = false;
|
||||||
if (scall != nil && tn != nil
|
if (scall != nil && tn != nil
|
||||||
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
||||||
@@ -18397,10 +18458,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
// names too — not just primitives. elemsizeofc follows
|
// names too — not just primitives. elemsizeofc follows
|
||||||
// TNAME through structlookup/aliaslookup, matching the
|
// TNAME through structlookup/aliaslookup, matching the
|
||||||
// cstage path byte-identically. A bare primsize/slotsize
|
// cstage path byte-identically. A bare primsize/slotsize
|
||||||
// fork would silently land esz=1 on `[]point` (today
|
// fork would silently land esz=1 on `[]point`.
|
||||||
// blocked at check.c, but the defensive cgen path must
|
|
||||||
// stay byte-identical with cstage for the moment check
|
|
||||||
// relaxes).
|
|
||||||
let esz: i32 = elemsizeofc(c, tn);
|
let esz: i32 = elemsizeofc(c, tn);
|
||||||
let count: *node = scall.list.next;
|
let count: *node = scall.list.next;
|
||||||
cgexpr(c, count);
|
cgexpr(c, count);
|
||||||
@@ -18424,6 +18482,24 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tSYSCALL\n");
|
emitline("\tSYSCALL\n");
|
||||||
emitlabel(okl);
|
emitlabel(okl);
|
||||||
};
|
};
|
||||||
|
if (viatryprop) {
|
||||||
|
// #45: null = nomem; propagate to the
|
||||||
|
// enclosing fn's tagged return. AX = tag
|
||||||
|
// of nomem variant in c.fnret, epilogue
|
||||||
|
// RETs to caller.
|
||||||
|
let okl: str = mklabel(c, "tryprop_ok");
|
||||||
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n");
|
||||||
|
let nidx: i32 = flatvariantidx(c, c.fnret, "nomem");
|
||||||
|
if (nidx < 0) { nidx = 1; };
|
||||||
|
emitline("\tMOVQ\t$");
|
||||||
|
emitint(nidx: i64);
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\tBP, SP\n\tPOPQ\tBP\n\tRET\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
};
|
||||||
emitline("\tPOPQ\tBX\n");
|
emitline("\tPOPQ\tBX\n");
|
||||||
emitline("\tMOVQ\tAX, ");
|
emitline("\tMOVQ\tAX, ");
|
||||||
emitoff(off: i64);
|
emitoff(off: i64);
|
||||||
|
|||||||
@@ -307,6 +307,34 @@ static const struct row rows[] = {
|
|||||||
" append(s, 72u8, 105u8);\n"
|
" append(s, 72u8, 105u8);\n"
|
||||||
" return s.cap;\n"
|
" return s.cap;\n"
|
||||||
"};", 16 },
|
"};", 16 },
|
||||||
|
/* #45: alloc([], n) now defers element type to the let-init LHS.
|
||||||
|
* `[]rune` is 4B-per-element; the cgen shortcut scales count by
|
||||||
|
* size(T). Returns s.cap = 8. */
|
||||||
|
{ "package main;\n"
|
||||||
|
"import os;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let s: []rune = alloc([], 8)!;\n"
|
||||||
|
" return s.cap;\n"
|
||||||
|
"};", 8 },
|
||||||
|
/* #45: same as above for `[]str` (16B-per-element). */
|
||||||
|
{ "package main;\n"
|
||||||
|
"import os;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let s: []str = alloc([], 4)!;\n"
|
||||||
|
" return s.cap;\n"
|
||||||
|
"};", 4 },
|
||||||
|
/* #45: `?` form. doit propagates nomem to its (i32 | nomem)
|
||||||
|
* return; the alloc-slice shortcut emits MOVQ $nomem_tag, AX +
|
||||||
|
* propret on null. doit returns 12 on success; main unwraps. */
|
||||||
|
{ "package main;\n"
|
||||||
|
"import os;\n"
|
||||||
|
"fn doit() (i32 | nomem) = {\n"
|
||||||
|
" let s: []str = alloc([], 12)?;\n"
|
||||||
|
" return s.cap: i32;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" return doit()!;\n"
|
||||||
|
"};", 12 },
|
||||||
/* alloc-builtin shadow (task #23): a non-main package declares
|
/* alloc-builtin shadow (task #23): a non-main package declares
|
||||||
* `fn alloc(n: i64) i64` and calls it bare. The same-module gate
|
* `fn alloc(n: i64) i64` and calls it bare. The same-module gate
|
||||||
* must suppress the builtin and dispatch to the user fn so the
|
* must suppress the builtin and dispatch to the user fn so the
|
||||||
|
|||||||
@@ -144,6 +144,20 @@ static const struct row rows[] = {
|
|||||||
" let s: []u8 = alloc([], 16)!;\n"
|
" let s: []u8 = alloc([], 16)!;\n"
|
||||||
"};\n",
|
"};\n",
|
||||||
NULL },
|
NULL },
|
||||||
|
/* #45: alloc([], n) defers element type to LHS context — `[]str`
|
||||||
|
* with `!` must be accepted (cstage clet retypes, wwstage
|
||||||
|
* checkletassign mirror). Pre-#45 this errored with
|
||||||
|
* "init []u8 not assignable to declared []str" on cstage. */
|
||||||
|
{ "fn caller() void = {\n"
|
||||||
|
" let s: []str = alloc([], 4)!;\n"
|
||||||
|
"};\n",
|
||||||
|
NULL },
|
||||||
|
/* #45: same idiom in `?` form inside a (T | nomem) fn. */
|
||||||
|
{ "fn caller() (i32 | nomem) = {\n"
|
||||||
|
" let s: []str = alloc([], 4)?;\n"
|
||||||
|
" return s.cap: i32;\n"
|
||||||
|
"};\n",
|
||||||
|
NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|||||||
@@ -199,6 +199,24 @@ main(void)
|
|||||||
" for (let (k, v) .. s) { total += k + v; };\n"
|
" for (let (k, v) .. s) { total += k + v; };\n"
|
||||||
" return total: i32;\n"
|
" return total: i32;\n"
|
||||||
"};" },
|
"};" },
|
||||||
|
/* #45: alloc-slice `!` form, []T element ≠ u8. */
|
||||||
|
{ "alloc_slice_rune_unw",
|
||||||
|
"package main;\n"
|
||||||
|
"import os;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
" let s: []rune = alloc([], 8)!;\n"
|
||||||
|
" return s.cap;\n"
|
||||||
|
"};" },
|
||||||
|
/* #45: alloc-slice `?` form propagates nomem via the
|
||||||
|
* enclosing fn's tagged return. */
|
||||||
|
{ "alloc_slice_str_prop",
|
||||||
|
"package main;\n"
|
||||||
|
"import os;\n"
|
||||||
|
"fn doit() (i32 | nomem) = {\n"
|
||||||
|
" let s: []str = alloc([], 4)?;\n"
|
||||||
|
" return s.cap: i32;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn main() i32 = { return doit()!; };" },
|
||||||
{ NULL, NULL },
|
{ NULL, NULL },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user