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);
|
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
|
||||||
|
// 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
|
// Tagged-union init: delegate to cgwidentaggedstore, which
|
||||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||||
// ABI call), struct payload (literal/ident), str payload,
|
// 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);
|
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
|
||||||
|
// 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
|
// Tagged-union init: delegate to cgwidentaggedstore, which
|
||||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||||
// ABI call), struct payload (literal/ident), str payload,
|
// 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);
|
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
|
||||||
|
// 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
|
// Tagged-union init: delegate to cgwidentaggedstore, which
|
||||||
// handles nullable fold, tagged source (ident or AX/DX/CX
|
// handles nullable fold, tagged source (ident or AX/DX/CX
|
||||||
// ABI call), struct payload (literal/ident), str payload,
|
// ABI call), struct payload (literal/ident), str payload,
|
||||||
|
|||||||
@@ -43,6 +43,25 @@ fn allocbox() (*point | nomem) = {
|
|||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Task #32: slice-form `let s: []T = alloc([], n)!;` shortcut. Both
|
||||||
|
// stages must lower to `n*esz` bytes via rt_alloc, abort on null, and
|
||||||
|
// build a {ptr, 0, n} header in the let slot. Pre-#32 wwstage fell
|
||||||
|
// through to cgalloc, allocating 8B and dropping the slice header
|
||||||
|
// entirely — silent miscompile. Cap-only would pass on a junk header
|
||||||
|
// pointing to dead memory; write-then-read on s[0]/s[cap-1] proves
|
||||||
|
// the ptr field is a real rt_alloc'd region (would SIGSEGV otherwise).
|
||||||
|
// IMULQ esz path is currently unreachable from user code — check.c
|
||||||
|
// pins the alloc shape to []u8 (cstage check.c:1052-1082) — so this
|
||||||
|
// row only exercises esz=1; the cgen elemsizeofc resolution stays
|
||||||
|
// defensive against a future check.c relaxation.
|
||||||
|
fn sliceshort() i32 = {
|
||||||
|
let s: []u8 = alloc([], 16)!;
|
||||||
|
if (s.cap != 16) { return -1i32; };
|
||||||
|
s[0] = 42u8;
|
||||||
|
s[15] = 99u8;
|
||||||
|
return (s[0]: i32) + (s[15]: i32);
|
||||||
|
};
|
||||||
|
|
||||||
export fn main() i32 = {
|
export fn main() i32 = {
|
||||||
let rc: i32 = 0;
|
let rc: i32 = 0;
|
||||||
match (caller(0i64)) {
|
match (caller(0i64)) {
|
||||||
@@ -74,5 +93,7 @@ export fn main() i32 = {
|
|||||||
rc = 5;
|
rc = 5;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
if (sliceshort() != 141i32) { rc = 6; }
|
||||||
|
else { fmt.println("sliceshort ok"); };
|
||||||
return rc;
|
return rc;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user