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:
@@ -7837,6 +7837,58 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (n.rhs == nil) { return; }; // no init
|
||||
let src: *node = exprtype(c, n.rhs);
|
||||
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 ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||
if (!conf) { return; };
|
||||
@@ -18353,17 +18405,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
let off: i32 = localadd(c, nm, sz, tn);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
// `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of
|
||||
// cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate
|
||||
// n*esz bytes via rt_alloc, exit(1) on null, then build the
|
||||
// {ptr, 0, n} slice header in the let slot. The `!` wraps the
|
||||
// builtin's `([]T | nomem)` return; walk into the N_TRYUNW to
|
||||
// keep the direct-store fast path rather than falling through
|
||||
// to cgalloc (which models scalar alloc and would land an 8B
|
||||
// region and a junk slice header).
|
||||
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
|
||||
// n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice
|
||||
// header in the let slot. The `!`/`?` wraps the builtin's
|
||||
// `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP
|
||||
// to keep the direct-store fast path rather than falling
|
||||
// through to cgalloc (which models scalar alloc and would
|
||||
// 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 viatryunw: bool = false;
|
||||
let viatryprop: bool = false;
|
||||
if (rhs.kind == nkind.N_TRYUNW) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
@@ -18371,7 +18425,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
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;
|
||||
if (scall != nil && tn != nil
|
||||
&& 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
|
||||
// TNAME through structlookup/aliaslookup, matching the
|
||||
// cstage path byte-identically. A bare primsize/slotsize
|
||||
// fork would silently land esz=1 on `[]point` (today
|
||||
// blocked at check.c, but the defensive cgen path must
|
||||
// stay byte-identical with cstage for the moment check
|
||||
// relaxes).
|
||||
// fork would silently land esz=1 on `[]point`.
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
let count: *node = scall.list.next;
|
||||
cgexpr(c, count);
|
||||
@@ -18424,6 +18482,24 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tSYSCALL\n");
|
||||
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("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
|
||||
@@ -574,17 +574,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
let off: i32 = localadd(c, nm, sz, tn);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
// `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of
|
||||
// cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate
|
||||
// n*esz bytes via rt_alloc, exit(1) on null, then build the
|
||||
// {ptr, 0, n} slice header in the let slot. The `!` wraps the
|
||||
// builtin's `([]T | nomem)` return; walk into the N_TRYUNW to
|
||||
// keep the direct-store fast path rather than falling through
|
||||
// to cgalloc (which models scalar alloc and would land an 8B
|
||||
// region and a junk slice header).
|
||||
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
|
||||
// n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice
|
||||
// header in the let slot. The `!`/`?` wraps the builtin's
|
||||
// `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP
|
||||
// to keep the direct-store fast path rather than falling
|
||||
// through to cgalloc (which models scalar alloc and would
|
||||
// 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 viatryunw: bool = false;
|
||||
let viatryprop: bool = false;
|
||||
if (rhs.kind == nkind.N_TRYUNW) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
@@ -592,7 +594,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
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;
|
||||
if (scall != nil && tn != nil
|
||||
&& 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
|
||||
// TNAME through structlookup/aliaslookup, matching the
|
||||
// cstage path byte-identically. A bare primsize/slotsize
|
||||
// fork would silently land esz=1 on `[]point` (today
|
||||
// blocked at check.c, but the defensive cgen path must
|
||||
// stay byte-identical with cstage for the moment check
|
||||
// relaxes).
|
||||
// fork would silently land esz=1 on `[]point`.
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
let count: *node = scall.list.next;
|
||||
cgexpr(c, count);
|
||||
@@ -645,6 +651,24 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tSYSCALL\n");
|
||||
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("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
|
||||
@@ -975,6 +975,58 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (n.rhs == nil) { return; }; // no init
|
||||
let src: *node = exprtype(c, n.rhs);
|
||||
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 ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||
if (!conf) { return; };
|
||||
|
||||
@@ -7837,6 +7837,58 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
if (n.rhs == nil) { return; }; // no init
|
||||
let src: *node = exprtype(c, n.rhs);
|
||||
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 ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||
if (!conf) { return; };
|
||||
@@ -18353,17 +18405,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
let off: i32 = localadd(c, nm, sz, tn);
|
||||
if (n.rhs != nil) {
|
||||
let rhs: *node = n.rhs;
|
||||
// `let s: []T = alloc([], n)!;` shortcut (#32). Mirror of
|
||||
// cstage cgen.c N_LET arrlit-empty + N_TRYUNW branch: allocate
|
||||
// n*esz bytes via rt_alloc, exit(1) on null, then build the
|
||||
// {ptr, 0, n} slice header in the let slot. The `!` wraps the
|
||||
// builtin's `([]T | nomem)` return; walk into the N_TRYUNW to
|
||||
// keep the direct-store fast path rather than falling through
|
||||
// to cgalloc (which models scalar alloc and would land an 8B
|
||||
// region and a junk slice header).
|
||||
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
|
||||
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
|
||||
// n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice
|
||||
// header in the let slot. The `!`/`?` wraps the builtin's
|
||||
// `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP
|
||||
// to keep the direct-store fast path rather than falling
|
||||
// through to cgalloc (which models scalar alloc and would
|
||||
// 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 viatryunw: bool = false;
|
||||
let viatryprop: bool = false;
|
||||
if (rhs.kind == nkind.N_TRYUNW) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
@@ -18371,7 +18425,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
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;
|
||||
if (scall != nil && tn != nil
|
||||
&& 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
|
||||
// TNAME through structlookup/aliaslookup, matching the
|
||||
// cstage path byte-identically. A bare primsize/slotsize
|
||||
// fork would silently land esz=1 on `[]point` (today
|
||||
// blocked at check.c, but the defensive cgen path must
|
||||
// stay byte-identical with cstage for the moment check
|
||||
// relaxes).
|
||||
// fork would silently land esz=1 on `[]point`.
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
let count: *node = scall.list.next;
|
||||
cgexpr(c, count);
|
||||
@@ -18424,6 +18482,24 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tSYSCALL\n");
|
||||
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("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
|
||||
Reference in New Issue
Block a user