wcc/check+w6c+w6c_ww: materialize array-literal slice-borrow base into per-fn scratch (fix #25 + #31)
A one-step `let xs: []T = [e0,e1,..]` had two faults. #31 (silent, cs!=ww): the #258 array→slice borrow wrapped the un-addressable N_ARRLIT directly as the N_SLICE base and cgen never spilled it to a stack slot, so .ptr dangled (`let xs:[]i32=[10,20,30]; xs[1]` returned the un-stored header 1; []u8/[]str segfaulted). #25 (over-strict): a slice target fell through to the exact- element type_eq borrow gate, rejecting bare-int-width ([]u8=[1,2,3]) and str elements the array-init path coerces. Fix (re-stamp + per-borrow scratch; both stages byte-identical asm): - Checker re-stamps the slice arrlit as [count]T, reusing the array-init per-element coercion + range-check (#25): in-range accepts, out-of-range loud-rejects. cstage arrlit_init_fits gains a TY_SLICE arm; wwstage checkletassign mirrors it and stashes the synthesized [count]T tnode on arrlit.lhs (free for N_ARRLIT) so cgen can size the backing NODE-wise (elemsizeofc) and count from the tnode's .rhs intlit — the arrlit's own value tinfo carries the literal's untyped element (unsized), so node-first sizing is required (a cstage/wwstage representation divergence; cstage's Type IS sized and reads base->type). - cgen materialises the N_ARRLIT borrow base into a FRESH per-borrow @slicescr stack slot (distinct slot per borrow: a borrow's backing must outlive the lowering, so it can't share a cached @aggargscr/@tagscr-style slot — two live borrows would alias one backing; localalloc/local_alloc is always-fresh), filled by REUSING the array-init element fill extracted from the N_LET path (cstage cg_arrlit_fill_bp, wwstage cgarrlitfillbp — same store sequence the byte-id-green `let a:[N]T=[..]` uses, the frame-order + store-op guarantee), then LEAQ'd as the base. Supported ONLY at a `let` init. In call-arg / return / assign position there is no addressable backing, so both stages LOUD-REJECT ("bind it to a `let` first") — aligning cstage DOWN to wwstage (which already refused the untyped arrlit element) per rule-10; this closes #31's silent call-arg segfault as a compile error. Full non-let support is deferred (#33). Escape (rule-8 WHY): a `let xs:[]T=[..]; return xs;` returns a slice into a freed frame slot = dangling, IDENTICAL to the pre-existing named-array borrow and Hare-consistent (no escape analysis / GC / heap promotion). Test 953_arrlit_slice_run: 8 accept rows (cstage runtime readback + cs==ww byte-id, frame-size canary incl.) covering the #31 i32 pin, bare-int→u8 coercion, str readback, the multi-live soundness pin (xs[0]+ys[0]=5, not 8 — proves fresh-per-borrow), and a mutate-through-borrow proof; 4 reject rows (out-of-range element + the three non-let contexts, loud in both stages). Tuple-element slices stay blocked by the pre-existing #30 array-init FATAL.
This commit is contained in:
@@ -1660,6 +1660,17 @@ fn cgslice(c: *cgen, n: *node) void = {
|
||||
dotbu = dotbu.under;
|
||||
};
|
||||
};};
|
||||
// #31: an N_ARRLIT base (the desugared one-step `let xs:[]T=[..]`
|
||||
// borrow — the ONLY context that reaches here; call-arg/return/assign
|
||||
// loud-reject at the checker, #33) has no storage. Its [count]T type
|
||||
// NODE is stashed on base.lhs by checkletassign's #25 re-stamp; size /
|
||||
// count come NODE-wise (elemsizeofc / .rhs intlit), because wwstage
|
||||
// narrow-primitive tinfos are unsized (#8). Cstage twin reads base->type
|
||||
// (its Type IS sized).
|
||||
let arrlittn: *node = nil;
|
||||
if (base != nil) { if (base.kind == nkind.N_ARRLIT) {
|
||||
arrlittn = base.lhs;
|
||||
};};
|
||||
// esz from the type table for an N_IDENT base (#76; mirrors the
|
||||
// cgindex idiom) or an N_DOT array/slice-field base (#252: scale by
|
||||
// the field's element width, not esz=1 — silently wrong for non-u8).
|
||||
@@ -1671,7 +1682,9 @@ fn cgslice(c: *cgen, n: *node) void = {
|
||||
esz = elemsizeofc(c, globaltn);
|
||||
} else { if (dotbu != nil && dotbu.sub != nil) {
|
||||
esz = dotbu.sub.size: i32;
|
||||
};};};
|
||||
} else { if (arrlittn != nil) {
|
||||
esz = elemsizeofc(c, arrlittn);
|
||||
};};};};
|
||||
// base address
|
||||
if (baselocal != nil) {
|
||||
let tn: *node = baselocal.tnode;
|
||||
@@ -1701,6 +1714,32 @@ fn cgslice(c: *cgen, n: *node) void = {
|
||||
emitsymname(c, globalname);
|
||||
emitline("(SB), AX\n");
|
||||
};
|
||||
} else { if (base != nil && base.kind == nkind.N_ARRLIT
|
||||
&& arrlittn != nil) {
|
||||
// #31: materialise the array literal into a FRESH per-borrow
|
||||
// @slicescr stack slot (distinct slot per borrow — a borrow's
|
||||
// backing must outlive the lowering, so it can't share a cached
|
||||
// slot; localalloc is always-fresh, mirror of cstage local_alloc),
|
||||
// fill it via the shared element-fill, then LEAQ the slot as base.
|
||||
// Size/count NODE-wise off the stashed [count]T tnode (#8: tinfo
|
||||
// primitive sizes are 0). Escape (WHY, rob): a `let xs:[]T=[..];
|
||||
// return xs;` returns a slice into this frame slot, freed on
|
||||
// return = dangling — IDENTICAL to the named-array borrow and
|
||||
// Hare-consistent (no escape analysis / GC / heap promotion; a
|
||||
// local borrowed past its frame is a footgun, not promoted).
|
||||
let cnt: i32 = 0;
|
||||
if (arrlittn.rhs != nil) {
|
||||
if (arrlittn.rhs.kind == nkind.N_INTLIT) {
|
||||
cnt = arrlittn.rhs.uval: i32;
|
||||
};
|
||||
};
|
||||
let bsz: i32 = elemsizeofc(c, arrlittn) * cnt;
|
||||
if (bsz < 1) { bsz = 1; };
|
||||
let scr: i32 = localalloc(c, "@slicescr", bsz, nil);
|
||||
cgarrlitfillbp(c, arrlittn, base, scr);
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(scr: i64);
|
||||
emitline("(BP), AX\n");
|
||||
} else { if (base != nil) {
|
||||
// #252: N_DOT `[N]T`-field base → field ADDRESS via
|
||||
// dotbaseaddr (LEAQ), not the auto-deref VALUE load cgexpr
|
||||
@@ -1708,7 +1747,7 @@ fn cgslice(c: *cgen, n: *node) void = {
|
||||
if (!dotbaseaddr(c, base, "AX")) {
|
||||
cgexpr(c, base);
|
||||
};
|
||||
};};};
|
||||
};};};};
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
// lo (default 0)
|
||||
if (lo != nil) { cgexpr(c, lo); }
|
||||
@@ -1773,9 +1812,21 @@ fn cgslice(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(dotbu.alen: i64);
|
||||
emitline(", AX\n");
|
||||
} else { if (arrlittn != nil) {
|
||||
// #31: default-hi for the arrlit base = its element count (the
|
||||
// stashed [count]T tnode's .rhs intlit).
|
||||
let hc: i64 = 0i64;
|
||||
if (arrlittn.rhs != nil) {
|
||||
if (arrlittn.rhs.kind == nkind.N_INTLIT) {
|
||||
hc = arrlittn.rhs.uval: i64;
|
||||
};
|
||||
};
|
||||
emitline("\tMOVQ\t$");
|
||||
emitint(hc);
|
||||
emitline(", AX\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
};};};};
|
||||
};};};};};
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tCX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
|
||||
Reference in New Issue
Block a user