selfhost/cmd/wcc/cgenstmt+test: emit slice-form alloc let-init shortcut
Cstage's cmd/w6c/cgen.c:6363-6411 special-cases `let s: []T = alloc([], n)!;` to inline rt_alloc + null-check + exit(1) + slice header build, avoiding a generic call-then-store path. Wwstage's cglet had no mirror — pre-#31 the path was rejected at check, but once #31 made the check side accept it, the cgen side would have silently miscompiled. Mirror added at cgenstmt.ww cglet rhs head, emitting byte-identical asm. Element size goes through elemsizeofc so str (16), structs, and tagged aliases all match cstage's lu->sub->size uniformly — the defensive path matters because check today only allows []u8, but relaxing that is its own task. Test exercises the path: writes to s[0] and s[15], reads back. Would SIGSEGV on a junk header. 994 + 995 byte-identity green.
This commit is contained in:
@@ -18353,6 +18353,91 @@ 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 scall: *node = nil;
|
||||
let viatryunw: bool = false;
|
||||
if (rhs.kind == nkind.N_TRYUNW) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
scall = rhs.lhs;
|
||||
viatryunw = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
let shapeok: bool = false;
|
||||
if (scall != nil && tn != nil
|
||||
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
||||
let callee: *node = scall.lhs;
|
||||
let a0: *node = scall.list;
|
||||
let a1: *node = nil;
|
||||
let a2: *node = nil;
|
||||
if (a0 != nil) { a1 = a0.next; };
|
||||
if (a1 != nil) { a2 = a1.next; };
|
||||
if (callee != nil && a0 != nil && a1 != nil
|
||||
&& a2 == nil) {
|
||||
if (callee.kind == nkind.N_IDENT
|
||||
&& streq(callee.str, "alloc")
|
||||
&& a0.kind == nkind.N_ARRLIT
|
||||
&& a0.list == nil) {
|
||||
shapeok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (shapeok) {
|
||||
// #32: cstage uses `lu->sub->size` (cgen.c:6387), so
|
||||
// the element width must resolve struct/tagged/alias
|
||||
// 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).
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
let count: *node = scall.list.next;
|
||||
cgexpr(c, count);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", BX\n");
|
||||
emitline("\tIMULQ\tBX, AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tCALL\trt_alloc(SB)\n");
|
||||
if (viatryunw) {
|
||||
let okl: str = mklabel(c, "tryunw_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(okl);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$1, DI\n");
|
||||
emitline("\tMOVQ\t$60, AX\n");
|
||||
emitline("\tSYSCALL\n");
|
||||
emitlabel(okl);
|
||||
};
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t$0, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Tagged-union init: delegate to cgwidentaggedstore, which
|
||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
|
||||
@@ -574,6 +574,91 @@ 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 scall: *node = nil;
|
||||
let viatryunw: bool = false;
|
||||
if (rhs.kind == nkind.N_TRYUNW) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
scall = rhs.lhs;
|
||||
viatryunw = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
let shapeok: bool = false;
|
||||
if (scall != nil && tn != nil
|
||||
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
||||
let callee: *node = scall.lhs;
|
||||
let a0: *node = scall.list;
|
||||
let a1: *node = nil;
|
||||
let a2: *node = nil;
|
||||
if (a0 != nil) { a1 = a0.next; };
|
||||
if (a1 != nil) { a2 = a1.next; };
|
||||
if (callee != nil && a0 != nil && a1 != nil
|
||||
&& a2 == nil) {
|
||||
if (callee.kind == nkind.N_IDENT
|
||||
&& streq(callee.str, "alloc")
|
||||
&& a0.kind == nkind.N_ARRLIT
|
||||
&& a0.list == nil) {
|
||||
shapeok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (shapeok) {
|
||||
// #32: cstage uses `lu->sub->size` (cgen.c:6387), so
|
||||
// the element width must resolve struct/tagged/alias
|
||||
// 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).
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
let count: *node = scall.list.next;
|
||||
cgexpr(c, count);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", BX\n");
|
||||
emitline("\tIMULQ\tBX, AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tCALL\trt_alloc(SB)\n");
|
||||
if (viatryunw) {
|
||||
let okl: str = mklabel(c, "tryunw_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(okl);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$1, DI\n");
|
||||
emitline("\tMOVQ\t$60, AX\n");
|
||||
emitline("\tSYSCALL\n");
|
||||
emitlabel(okl);
|
||||
};
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t$0, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Tagged-union init: delegate to cgwidentaggedstore, which
|
||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
|
||||
@@ -18353,6 +18353,91 @@ 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 scall: *node = nil;
|
||||
let viatryunw: bool = false;
|
||||
if (rhs.kind == nkind.N_TRYUNW) {
|
||||
if (rhs.lhs != nil) {
|
||||
if (rhs.lhs.kind == nkind.N_CALL) {
|
||||
scall = rhs.lhs;
|
||||
viatryunw = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
let shapeok: bool = false;
|
||||
if (scall != nil && tn != nil
|
||||
&& tn.kind == nkind.N_TSLICE && sz == 24) {
|
||||
let callee: *node = scall.lhs;
|
||||
let a0: *node = scall.list;
|
||||
let a1: *node = nil;
|
||||
let a2: *node = nil;
|
||||
if (a0 != nil) { a1 = a0.next; };
|
||||
if (a1 != nil) { a2 = a1.next; };
|
||||
if (callee != nil && a0 != nil && a1 != nil
|
||||
&& a2 == nil) {
|
||||
if (callee.kind == nkind.N_IDENT
|
||||
&& streq(callee.str, "alloc")
|
||||
&& a0.kind == nkind.N_ARRLIT
|
||||
&& a0.list == nil) {
|
||||
shapeok = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (shapeok) {
|
||||
// #32: cstage uses `lu->sub->size` (cgen.c:6387), so
|
||||
// the element width must resolve struct/tagged/alias
|
||||
// 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).
|
||||
let esz: i32 = elemsizeofc(c, tn);
|
||||
let count: *node = scall.list.next;
|
||||
cgexpr(c, count);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
if (esz > 1) {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(esz: i64);
|
||||
emitline(", BX\n");
|
||||
emitline("\tIMULQ\tBX, AX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tAX, DI\n");
|
||||
emitline("\tCALL\trt_alloc(SB)\n");
|
||||
if (viatryunw) {
|
||||
let okl: str = mklabel(c, "tryunw_ok");
|
||||
emitline("\tCMPQ\t$0, AX\n");
|
||||
emitline("\tJNE\t");
|
||||
emitline(okl);
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\t$1, DI\n");
|
||||
emitline("\tMOVQ\t$60, AX\n");
|
||||
emitline("\tSYSCALL\n");
|
||||
emitlabel(okl);
|
||||
};
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\t$0, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Tagged-union init: delegate to cgwidentaggedstore, which
|
||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||
// ABI call), struct payload (literal/ident), str payload,
|
||||
|
||||
Reference in New Issue
Block a user