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:
@@ -3845,6 +3845,25 @@ fn checkarrlitfits(c: *checker, arrtn: *node, rhs: *node) void = {
|
||||
// unchanged when the shape doesn't match, else a fresh N_SLICE whose base
|
||||
// is `val` (which keeps its stamped array type_). The original sibling
|
||||
// link transfers to the N_SLICE so a desugared call-arg keeps its place.
|
||||
// rejectarrlitborrow — #31/#33 twin of cstage reject_arrlit_borrow. The
|
||||
// array-literal → slice borrow is supported only at a `let` init (where
|
||||
// checkletassign re-stamps + the cgslice N_ARRLIT-base arm spills the
|
||||
// literal to a per-borrow backing slot). In call-arg / return / assign
|
||||
// position there is no addressable backing — loud-reject so the gap is a
|
||||
// compile error, not a dangling-ptr miscompile. Both stages reject here
|
||||
// (rule-10, byte-id-trivial: no asm). Full non-let support is #33.
|
||||
fn rejectarrlitborrow(c: *checker, dsttn: *node, val: *node) bool = {
|
||||
if (val == nil) { return false; };
|
||||
if (val.kind != nkind.N_ARRLIT) { return false; };
|
||||
let du: *node = resolvealias(c, unwrapbang(dsttn));
|
||||
if (du == nil) { return false; };
|
||||
if (du.kind != nkind.N_TSLICE) { return false; };
|
||||
let m: str = "array literal cannot borrow as a slice here; bind it to a `let` first\n";
|
||||
cerr(m);
|
||||
c.errs += 1;
|
||||
return true;
|
||||
};
|
||||
|
||||
fn desugararrayslice(c: *checker, dsttn: *node, srctn: *node, val: *node) *node = {
|
||||
if (dsttn == nil) { return val; };
|
||||
if (srctn == nil) { return val; };
|
||||
@@ -3936,10 +3955,14 @@ fn desugarcallargs(c: *checker, n: *node) void = {
|
||||
};
|
||||
}; };
|
||||
}; };
|
||||
let rep: *node = desugararrayslice(c, param.lhs, atype, a);
|
||||
if (rep != a) {
|
||||
if (prev == nil) { n.list = rep; } else { prev.next = rep; };
|
||||
a = rep;
|
||||
// #31/#33: bare array-literal arg has no backing
|
||||
// — loud-reject (supported only at a `let`).
|
||||
if (!rejectarrlitborrow(c, param.lhs, a)) {
|
||||
let rep: *node = desugararrayslice(c, param.lhs, atype, a);
|
||||
if (rep != a) {
|
||||
if (prev == nil) { n.list = rep; } else { prev.next = rep; };
|
||||
a = rep;
|
||||
};
|
||||
};
|
||||
};
|
||||
if (param.op != tkind.TK_ELLIPSIS) { param = param.next; };
|
||||
@@ -3961,7 +3984,11 @@ fn checkassign(c: *checker, n: *node) void = {
|
||||
if (n.rhs == nil) { return; };
|
||||
let ltn: *node = exprtype(c, n.lhs, nil);
|
||||
let rtn: *node = exprtype(c, n.rhs, nil);
|
||||
n.rhs = desugararrayslice(c, ltn, rtn, n.rhs);
|
||||
// #31/#33: bare array-literal rhs has no backing — loud-reject
|
||||
// (supported only at a `let`).
|
||||
if (!rejectarrlitborrow(c, ltn, n.rhs)) {
|
||||
n.rhs = desugararrayslice(c, ltn, rtn, n.rhs);
|
||||
};
|
||||
};
|
||||
|
||||
// inferarraylen — `let xs: [_]T = arrlit;` length inference (#7). The
|
||||
@@ -4122,6 +4149,41 @@ fn checkletassign(c: *checker, n: *node) void = {
|
||||
checkarrlitfits(c, n.lhs, n.rhs);
|
||||
return;
|
||||
};
|
||||
// #25/#31: an array literal initialising a SLICE local. Re-stamp the
|
||||
// literal as [count]T (the slice element) so the #258 borrow's exact-
|
||||
// element typeeq holds and the cgen N_SLICE-over-N_ARRLIT arm reads the
|
||||
// declared element width. Run the same per-element coercion + range-
|
||||
// check the array path runs (checkarrlitfits against a synthesized
|
||||
// [count]T), then drive isassignable + the borrow off [count]T. Twin of
|
||||
// cstage arrlit_init_fits' slice arm. Local-only (c.cur != c.top): the
|
||||
// borrow runs at runtime; module-level slice-from-arrlit stays #32.
|
||||
if (c.cur != c.top && n.lhs.kind == nkind.N_TSLICE
|
||||
&& n.rhs.kind == nkind.N_ARRLIT) {
|
||||
let cnt: u64 = 0u64;
|
||||
let e0: *node = n.rhs.list;
|
||||
for (e0 != nil) {
|
||||
let skip: bool = false;
|
||||
if (e0.kind == nkind.N_FIELD) {
|
||||
if (streq(e0.str, "...")) { skip = true; };
|
||||
};
|
||||
if (!skip) { cnt += 1u64; };
|
||||
e0 = e0.next;
|
||||
};
|
||||
let cn: *node = newnode(nkind.N_INTLIT, "", 0, 0);
|
||||
cn.uval = cnt;
|
||||
let arr: *node = newnode(nkind.N_TARRAY, "", 0, 0);
|
||||
arr.lhs = n.lhs.lhs; // declared slice element type
|
||||
arr.rhs = cn;
|
||||
checkarrlitfits(c, arr, n.rhs);
|
||||
n.rhs.type_ = tinfofornode(c, arr): *void;
|
||||
// #31: stash the [count]T tnode on the arrlit (arrlit.lhs is free
|
||||
// — the parser sets only .list) so the cgslice N_ARRLIT-base arm
|
||||
// can size the backing NODE-wise via elemsizeofc(base.lhs). wwstage
|
||||
// narrow-primitive tinfos are unsized (i32/u8 .size==0, #8), so the
|
||||
// element width must come from the type NODE, not the tinfo.
|
||||
n.rhs.lhs = arr;
|
||||
src = arr;
|
||||
};
|
||||
let conf: bool = false;
|
||||
let ok: bool = isassignable(c, n.lhs, src, &conf);
|
||||
// #206: direct `&fn` → `*alias` / `(*alias | void)` slot.
|
||||
@@ -4164,7 +4226,11 @@ fn checkretassign(c: *checker, n: *node) void = {
|
||||
if (!conf) { return; };
|
||||
if (!ok) { errnotassign(c, c.fnret, src, "return"); };
|
||||
// #258: `return arr` borrows the array as a full slice.
|
||||
n.lhs = desugararrayslice(c, c.fnret, src, n.lhs);
|
||||
// #31/#33: bare array-literal has no backing — loud-reject
|
||||
// (supported only at a `let`).
|
||||
if (!rejectarrlitborrow(c, c.fnret, n.lhs)) {
|
||||
n.lhs = desugararrayslice(c, c.fnret, src, n.lhs);
|
||||
};
|
||||
};
|
||||
|
||||
// ---- is / as validity ------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user