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:
2026-06-04 01:32:27 +09:00
parent c490ed3ec1
commit bf1037d8c4
9 changed files with 1621 additions and 861 deletions

View File

@@ -14212,6 +14212,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; };
@@ -14303,10 +14322,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; };
@@ -14328,7 +14351,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
@@ -14489,6 +14516,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.
@@ -14531,7 +14593,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 ------------------------------------------------
@@ -15956,6 +16022,8 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
// scale by the field's element width, not esz=1 -> silently
// wrong for non-u8). Other non-ident bases stay esz=1.
// (#31: a bare N_ARRLIT arg never reaches here — it loud-rejects
// at the checker, supported only at a `let`; #33.)
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
@@ -21425,6 +21493,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).
@@ -21436,7 +21515,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;
@@ -21466,6 +21547,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
@@ -21473,7 +21580,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); }
@@ -21538,9 +21645,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");
@@ -29389,6 +29508,238 @@ fn cgexprstmt(c: *cgen, n: *node) void = {
return;
};
// cgarrlitfillbp — #31: fill the [count]T destination at BP-relative
// `off` from an N_ARRLIT, extracted from the cglet array-init path so
// the slice-borrow base materialisation (cgslice N_ARRLIT-base arm)
// reuses the IDENTICAL element-store sequence — the frame-order /
// store-op guarantee for rule-10 byte-id (ken). `arrtn` is the [count]T
// type NODE (cglet n.lhs; cgslice the re-stamped tnode on arrlit.lhs,
// #25); `rhs` the literal. Twin of cstage cg_arrlit_fill_bp.
fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = {
let elemn: *node = arrtn.lhs;
let esz: i32 = 8;
let isstrel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
if (streq(elemn.str, "str")) {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
};
// #270-1c: an AGGREGATE (struct/array/tuple) element of
// an array literal — the scalar per-element store below
// writes only the first 8 bytes (unpopulated tail). Fill
// each element slot from its literal (cgstructlitfillbp)
// or source ident (word-copy). esz is the element's
// natural size (cstage esub->size).
let esubti: *tinfo = nil;
if (elemn != nil) { esubti = elemn.type_: *tinfo; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
let isagg: bool = esubti != nil
&& (esubti.kind == tykind.TY_STRUCT
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
// branch above, so esz stayed the 8 sentinel (wrong stride,
// the -96-vs-80 cs!=ww frame divergence) and the scalar
// store dropped .len/.cap. Size it from the stamped tinfo
// and route it through the 3-word header store below.
let isslicel: bool = esubti != nil
&& esubti.kind == tykind.TY_SLICE;
if (isslicel) { esz = esubti.size: i32; };
// #12: a tagged-union element. NOT folded into isagg —
// isagg's body word-copies/fatals and never boxes the
// tag+payload; route through the cgwidentaggedstore
// choke-point the N_LET tagged path (cgenstmt.ww:1627)
// uses. esz must come from the stamped slot size (#8-class
// trap, rule-13): the narrow override below only rescues
// 1/2/4, so a tagged 16/24B element keeps the wrong 8
// sentinel stride without this.
let istaggedel: bool = esubti != nil
&& esubti.kind == tykind.TY_TAGGED;
if (istaggedel) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS
// narrowing only touches X0; the AX store (mop) would
// write the raw double low-bits, garbage for f32 (#122,
// mirrors cstage cgen.c:6889 arr-lit float store).
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
if (isagg) {
if (e.kind == nkind.N_STRUCTLIT) {
let esi: *structinfo = structlookupchain(c, elemn);
cgstructlitfillbp(c, esi, e, off + idx * esz);
} else { if (e.kind == nkind.N_IDENT) {
let sl: *local = localfindnode(c, e.str);
let soff: i32 = 0;
if (sl != nil) { soff = sl.off; };
let kc: i32 = 0;
for (kc + 8 <= esz) {
emitline("\tMOVQ\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 8;
};
if (kc + 4 <= esz) {
emitline("\tMOVL\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 4;
};
if (kc + 2 <= esz) {
emitline("\tMOVW\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 2;
};
if (kc + 1 <= esz) {
emitline("\tMOVB\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 1;
};
} else {
let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n";
os.write(2, m1c.ptr, m1c.len: u64);
os.exit(1);
}; };
} else { if (istaggedel) {
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
} else {
cgexpr(c, e);
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
}; };
idx += 1;
e = e.next;
};
};
if (repeat && isagg) {
let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n";
os.write(2, m1cr.ptr, m1cr.len: u64);
os.exit(1);
};
// #12: `...` re-stores from AX, but cgwidentaggedstore consumed
// the node and trashed AX — the repeat-fill would write garbage.
// No consumer needs `[N]tagged=[x,...]`.
if (repeat && istaggedel) {
let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n";
os.write(2, m12r.ptr, m12r.len: u64);
os.exit(1);
};
// AX (and BX for str) still holds the last stored value;
// fill remaining slots up to the declared length with it.
if (repeat) {
let total: i32 = idx;
if (arrtn != nil) {
if (arrtn.kind == nkind.N_TARRAY) {
if (arrtn.rhs != nil) {
if (arrtn.rhs.kind == nkind.N_INTLIT) {
total = arrtn.rhs.uval: i32;
};
};
};
};
for (idx < total) {
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
idx += 1;
};
};
};
fn cglet(c: *cgen, n: *node) void = {
let nm: str = n.str;
let sz: i32 = letslotsize(c, n);
@@ -29662,228 +30013,7 @@ fn cglet(c: *cgen, n: *node) void = {
// composites generally. The str/slice element now stores all 3
// words; [N]tagged element arrays still hit the gap, task #12.)
if (rhs.kind == nkind.N_ARRLIT) {
let elemn: *node = n.lhs.lhs;
let esz: i32 = 8;
let isstrel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
if (streq(elemn.str, "str")) {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
};
// #270-1c: an AGGREGATE (struct/array/tuple) element of
// an array literal — the scalar per-element store below
// writes only the first 8 bytes (unpopulated tail). Fill
// each element slot from its literal (cgstructlitfillbp)
// or source ident (word-copy). esz is the element's
// natural size (cstage esub->size).
let esubti: *tinfo = nil;
if (elemn != nil) { esubti = elemn.type_: *tinfo; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
let isagg: bool = esubti != nil
&& (esubti.kind == tykind.TY_STRUCT
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
// branch above, so esz stayed the 8 sentinel (wrong stride,
// the -96-vs-80 cs!=ww frame divergence) and the scalar
// store dropped .len/.cap. Size it from the stamped tinfo
// and route it through the 3-word header store below.
let isslicel: bool = esubti != nil
&& esubti.kind == tykind.TY_SLICE;
if (isslicel) { esz = esubti.size: i32; };
// #12: a tagged-union element. NOT folded into isagg —
// isagg's body word-copies/fatals and never boxes the
// tag+payload; route through the cgwidentaggedstore
// choke-point the N_LET tagged path (cgenstmt.ww:1627)
// uses. esz must come from the stamped slot size (#8-class
// trap, rule-13): the narrow override below only rescues
// 1/2/4, so a tagged 16/24B element keeps the wrong 8
// sentinel stride without this.
let istaggedel: bool = esubti != nil
&& esubti.kind == tykind.TY_TAGGED;
if (istaggedel) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS
// narrowing only touches X0; the AX store (mop) would
// write the raw double low-bits, garbage for f32 (#122,
// mirrors cstage cgen.c:6889 arr-lit float store).
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
if (isagg) {
if (e.kind == nkind.N_STRUCTLIT) {
let esi: *structinfo = structlookupchain(c, elemn);
cgstructlitfillbp(c, esi, e, off + idx * esz);
} else { if (e.kind == nkind.N_IDENT) {
let sl: *local = localfindnode(c, e.str);
let soff: i32 = 0;
if (sl != nil) { soff = sl.off; };
let kc: i32 = 0;
for (kc + 8 <= esz) {
emitline("\tMOVQ\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 8;
};
if (kc + 4 <= esz) {
emitline("\tMOVL\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 4;
};
if (kc + 2 <= esz) {
emitline("\tMOVW\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 2;
};
if (kc + 1 <= esz) {
emitline("\tMOVB\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 1;
};
} else {
let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n";
os.write(2, m1c.ptr, m1c.len: u64);
os.exit(1);
}; };
} else { if (istaggedel) {
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
} else {
cgexpr(c, e);
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
}; };
idx += 1;
e = e.next;
};
};
if (repeat && isagg) {
let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n";
os.write(2, m1cr.ptr, m1cr.len: u64);
os.exit(1);
};
// #12: `...` re-stores from AX, but cgwidentaggedstore consumed
// the node and trashed AX — the repeat-fill would write garbage.
// No consumer needs `[N]tagged=[x,...]`.
if (repeat && istaggedel) {
let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n";
os.write(2, m12r.ptr, m12r.len: u64);
os.exit(1);
};
// AX (and BX for str) still holds the last stored value;
// fill remaining slots up to the declared length with it.
if (repeat) {
let total: i32 = idx;
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TARRAY) {
if (n.lhs.rhs != nil) {
if (n.lhs.rhs.kind == nkind.N_INTLIT) {
total = n.lhs.rhs.uval: i32;
};
};
};
};
for (idx < total) {
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
idx += 1;
};
};
cgarrlitfillbp(c, n.lhs, rhs, off);
c.lastwasreturn = 0;
return;
};

View File

@@ -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");

View File

@@ -1471,6 +1471,238 @@ fn cgexprstmt(c: *cgen, n: *node) void = {
return;
};
// cgarrlitfillbp — #31: fill the [count]T destination at BP-relative
// `off` from an N_ARRLIT, extracted from the cglet array-init path so
// the slice-borrow base materialisation (cgslice N_ARRLIT-base arm)
// reuses the IDENTICAL element-store sequence — the frame-order /
// store-op guarantee for rule-10 byte-id (ken). `arrtn` is the [count]T
// type NODE (cglet n.lhs; cgslice the re-stamped tnode on arrlit.lhs,
// #25); `rhs` the literal. Twin of cstage cg_arrlit_fill_bp.
fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = {
let elemn: *node = arrtn.lhs;
let esz: i32 = 8;
let isstrel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
if (streq(elemn.str, "str")) {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
};
// #270-1c: an AGGREGATE (struct/array/tuple) element of
// an array literal — the scalar per-element store below
// writes only the first 8 bytes (unpopulated tail). Fill
// each element slot from its literal (cgstructlitfillbp)
// or source ident (word-copy). esz is the element's
// natural size (cstage esub->size).
let esubti: *tinfo = nil;
if (elemn != nil) { esubti = elemn.type_: *tinfo; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
let isagg: bool = esubti != nil
&& (esubti.kind == tykind.TY_STRUCT
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
// branch above, so esz stayed the 8 sentinel (wrong stride,
// the -96-vs-80 cs!=ww frame divergence) and the scalar
// store dropped .len/.cap. Size it from the stamped tinfo
// and route it through the 3-word header store below.
let isslicel: bool = esubti != nil
&& esubti.kind == tykind.TY_SLICE;
if (isslicel) { esz = esubti.size: i32; };
// #12: a tagged-union element. NOT folded into isagg —
// isagg's body word-copies/fatals and never boxes the
// tag+payload; route through the cgwidentaggedstore
// choke-point the N_LET tagged path (cgenstmt.ww:1627)
// uses. esz must come from the stamped slot size (#8-class
// trap, rule-13): the narrow override below only rescues
// 1/2/4, so a tagged 16/24B element keeps the wrong 8
// sentinel stride without this.
let istaggedel: bool = esubti != nil
&& esubti.kind == tykind.TY_TAGGED;
if (istaggedel) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS
// narrowing only touches X0; the AX store (mop) would
// write the raw double low-bits, garbage for f32 (#122,
// mirrors cstage cgen.c:6889 arr-lit float store).
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
if (isagg) {
if (e.kind == nkind.N_STRUCTLIT) {
let esi: *structinfo = structlookupchain(c, elemn);
cgstructlitfillbp(c, esi, e, off + idx * esz);
} else { if (e.kind == nkind.N_IDENT) {
let sl: *local = localfindnode(c, e.str);
let soff: i32 = 0;
if (sl != nil) { soff = sl.off; };
let kc: i32 = 0;
for (kc + 8 <= esz) {
emitline("\tMOVQ\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 8;
};
if (kc + 4 <= esz) {
emitline("\tMOVL\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 4;
};
if (kc + 2 <= esz) {
emitline("\tMOVW\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 2;
};
if (kc + 1 <= esz) {
emitline("\tMOVB\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 1;
};
} else {
let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n";
os.write(2, m1c.ptr, m1c.len: u64);
os.exit(1);
}; };
} else { if (istaggedel) {
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
} else {
cgexpr(c, e);
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
}; };
idx += 1;
e = e.next;
};
};
if (repeat && isagg) {
let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n";
os.write(2, m1cr.ptr, m1cr.len: u64);
os.exit(1);
};
// #12: `...` re-stores from AX, but cgwidentaggedstore consumed
// the node and trashed AX — the repeat-fill would write garbage.
// No consumer needs `[N]tagged=[x,...]`.
if (repeat && istaggedel) {
let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n";
os.write(2, m12r.ptr, m12r.len: u64);
os.exit(1);
};
// AX (and BX for str) still holds the last stored value;
// fill remaining slots up to the declared length with it.
if (repeat) {
let total: i32 = idx;
if (arrtn != nil) {
if (arrtn.kind == nkind.N_TARRAY) {
if (arrtn.rhs != nil) {
if (arrtn.rhs.kind == nkind.N_INTLIT) {
total = arrtn.rhs.uval: i32;
};
};
};
};
for (idx < total) {
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
idx += 1;
};
};
};
fn cglet(c: *cgen, n: *node) void = {
let nm: str = n.str;
let sz: i32 = letslotsize(c, n);
@@ -1744,228 +1976,7 @@ fn cglet(c: *cgen, n: *node) void = {
// composites generally. The str/slice element now stores all 3
// words; [N]tagged element arrays still hit the gap, task #12.)
if (rhs.kind == nkind.N_ARRLIT) {
let elemn: *node = n.lhs.lhs;
let esz: i32 = 8;
let isstrel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
if (streq(elemn.str, "str")) {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
};
// #270-1c: an AGGREGATE (struct/array/tuple) element of
// an array literal — the scalar per-element store below
// writes only the first 8 bytes (unpopulated tail). Fill
// each element slot from its literal (cgstructlitfillbp)
// or source ident (word-copy). esz is the element's
// natural size (cstage esub->size).
let esubti: *tinfo = nil;
if (elemn != nil) { esubti = elemn.type_: *tinfo; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
let isagg: bool = esubti != nil
&& (esubti.kind == tykind.TY_STRUCT
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
// branch above, so esz stayed the 8 sentinel (wrong stride,
// the -96-vs-80 cs!=ww frame divergence) and the scalar
// store dropped .len/.cap. Size it from the stamped tinfo
// and route it through the 3-word header store below.
let isslicel: bool = esubti != nil
&& esubti.kind == tykind.TY_SLICE;
if (isslicel) { esz = esubti.size: i32; };
// #12: a tagged-union element. NOT folded into isagg —
// isagg's body word-copies/fatals and never boxes the
// tag+payload; route through the cgwidentaggedstore
// choke-point the N_LET tagged path (cgenstmt.ww:1627)
// uses. esz must come from the stamped slot size (#8-class
// trap, rule-13): the narrow override below only rescues
// 1/2/4, so a tagged 16/24B element keeps the wrong 8
// sentinel stride without this.
let istaggedel: bool = esubti != nil
&& esubti.kind == tykind.TY_TAGGED;
if (istaggedel) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS
// narrowing only touches X0; the AX store (mop) would
// write the raw double low-bits, garbage for f32 (#122,
// mirrors cstage cgen.c:6889 arr-lit float store).
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
if (isagg) {
if (e.kind == nkind.N_STRUCTLIT) {
let esi: *structinfo = structlookupchain(c, elemn);
cgstructlitfillbp(c, esi, e, off + idx * esz);
} else { if (e.kind == nkind.N_IDENT) {
let sl: *local = localfindnode(c, e.str);
let soff: i32 = 0;
if (sl != nil) { soff = sl.off; };
let kc: i32 = 0;
for (kc + 8 <= esz) {
emitline("\tMOVQ\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 8;
};
if (kc + 4 <= esz) {
emitline("\tMOVL\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 4;
};
if (kc + 2 <= esz) {
emitline("\tMOVW\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 2;
};
if (kc + 1 <= esz) {
emitline("\tMOVB\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 1;
};
} else {
let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n";
os.write(2, m1c.ptr, m1c.len: u64);
os.exit(1);
}; };
} else { if (istaggedel) {
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
} else {
cgexpr(c, e);
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
}; };
idx += 1;
e = e.next;
};
};
if (repeat && isagg) {
let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n";
os.write(2, m1cr.ptr, m1cr.len: u64);
os.exit(1);
};
// #12: `...` re-stores from AX, but cgwidentaggedstore consumed
// the node and trashed AX — the repeat-fill would write garbage.
// No consumer needs `[N]tagged=[x,...]`.
if (repeat && istaggedel) {
let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n";
os.write(2, m12r.ptr, m12r.len: u64);
os.exit(1);
};
// AX (and BX for str) still holds the last stored value;
// fill remaining slots up to the declared length with it.
if (repeat) {
let total: i32 = idx;
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TARRAY) {
if (n.lhs.rhs != nil) {
if (n.lhs.rhs.kind == nkind.N_INTLIT) {
total = n.lhs.rhs.uval: i32;
};
};
};
};
for (idx < total) {
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
idx += 1;
};
};
cgarrlitfillbp(c, n.lhs, rhs, off);
c.lastwasreturn = 0;
return;
};

View File

@@ -290,6 +290,8 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
// scale by the field's element width, not esz=1 -> silently
// wrong for non-u8). Other non-ident bases stay esz=1.
// (#31: a bare N_ARRLIT arg never reaches here — it loud-rejects
// at the checker, supported only at a `let`; #33.)
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);

View File

@@ -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 ------------------------------------------------

View File

@@ -14212,6 +14212,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; };
@@ -14303,10 +14322,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; };
@@ -14328,7 +14351,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
@@ -14489,6 +14516,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.
@@ -14531,7 +14593,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 ------------------------------------------------
@@ -15956,6 +16022,8 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
// scale by the field's element width, not esz=1 -> silently
// wrong for non-u8). Other non-ident bases stay esz=1.
// (#31: a bare N_ARRLIT arg never reaches here — it loud-rejects
// at the checker, supported only at a `let`; #33.)
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
@@ -21425,6 +21493,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).
@@ -21436,7 +21515,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;
@@ -21466,6 +21547,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
@@ -21473,7 +21580,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); }
@@ -21538,9 +21645,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");
@@ -29389,6 +29508,238 @@ fn cgexprstmt(c: *cgen, n: *node) void = {
return;
};
// cgarrlitfillbp — #31: fill the [count]T destination at BP-relative
// `off` from an N_ARRLIT, extracted from the cglet array-init path so
// the slice-borrow base materialisation (cgslice N_ARRLIT-base arm)
// reuses the IDENTICAL element-store sequence — the frame-order /
// store-op guarantee for rule-10 byte-id (ken). `arrtn` is the [count]T
// type NODE (cglet n.lhs; cgslice the re-stamped tnode on arrlit.lhs,
// #25); `rhs` the literal. Twin of cstage cg_arrlit_fill_bp.
fn cgarrlitfillbp(c: *cgen, arrtn: *node, rhs: *node, off: i32) void = {
let elemn: *node = arrtn.lhs;
let esz: i32 = 8;
let isstrel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
if (streq(elemn.str, "str")) {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
};
// #270-1c: an AGGREGATE (struct/array/tuple) element of
// an array literal — the scalar per-element store below
// writes only the first 8 bytes (unpopulated tail). Fill
// each element slot from its literal (cgstructlitfillbp)
// or source ident (word-copy). esz is the element's
// natural size (cstage esub->size).
let esubti: *tinfo = nil;
if (elemn != nil) { esubti = elemn.type_: *tinfo; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
let isagg: bool = esubti != nil
&& (esubti.kind == tykind.TY_STRUCT
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
// branch above, so esz stayed the 8 sentinel (wrong stride,
// the -96-vs-80 cs!=ww frame divergence) and the scalar
// store dropped .len/.cap. Size it from the stamped tinfo
// and route it through the 3-word header store below.
let isslicel: bool = esubti != nil
&& esubti.kind == tykind.TY_SLICE;
if (isslicel) { esz = esubti.size: i32; };
// #12: a tagged-union element. NOT folded into isagg —
// isagg's body word-copies/fatals and never boxes the
// tag+payload; route through the cgwidentaggedstore
// choke-point the N_LET tagged path (cgenstmt.ww:1627)
// uses. esz must come from the stamped slot size (#8-class
// trap, rule-13): the narrow override below only rescues
// 1/2/4, so a tagged 16/24B element keeps the wrong 8
// sentinel stride without this.
let istaggedel: bool = esubti != nil
&& esubti.kind == tykind.TY_TAGGED;
if (istaggedel) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS
// narrowing only touches X0; the AX store (mop) would
// write the raw double low-bits, garbage for f32 (#122,
// mirrors cstage cgen.c:6889 arr-lit float store).
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
if (isagg) {
if (e.kind == nkind.N_STRUCTLIT) {
let esi: *structinfo = structlookupchain(c, elemn);
cgstructlitfillbp(c, esi, e, off + idx * esz);
} else { if (e.kind == nkind.N_IDENT) {
let sl: *local = localfindnode(c, e.str);
let soff: i32 = 0;
if (sl != nil) { soff = sl.off; };
let kc: i32 = 0;
for (kc + 8 <= esz) {
emitline("\tMOVQ\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 8;
};
if (kc + 4 <= esz) {
emitline("\tMOVL\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 4;
};
if (kc + 2 <= esz) {
emitline("\tMOVW\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 2;
};
if (kc + 1 <= esz) {
emitline("\tMOVB\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 1;
};
} else {
let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n";
os.write(2, m1c.ptr, m1c.len: u64);
os.exit(1);
}; };
} else { if (istaggedel) {
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
} else {
cgexpr(c, e);
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
}; };
idx += 1;
e = e.next;
};
};
if (repeat && isagg) {
let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n";
os.write(2, m1cr.ptr, m1cr.len: u64);
os.exit(1);
};
// #12: `...` re-stores from AX, but cgwidentaggedstore consumed
// the node and trashed AX — the repeat-fill would write garbage.
// No consumer needs `[N]tagged=[x,...]`.
if (repeat && istaggedel) {
let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n";
os.write(2, m12r.ptr, m12r.len: u64);
os.exit(1);
};
// AX (and BX for str) still holds the last stored value;
// fill remaining slots up to the declared length with it.
if (repeat) {
let total: i32 = idx;
if (arrtn != nil) {
if (arrtn.kind == nkind.N_TARRAY) {
if (arrtn.rhs != nil) {
if (arrtn.rhs.kind == nkind.N_INTLIT) {
total = arrtn.rhs.uval: i32;
};
};
};
};
for (idx < total) {
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
idx += 1;
};
};
};
fn cglet(c: *cgen, n: *node) void = {
let nm: str = n.str;
let sz: i32 = letslotsize(c, n);
@@ -29662,228 +30013,7 @@ fn cglet(c: *cgen, n: *node) void = {
// composites generally. The str/slice element now stores all 3
// words; [N]tagged element arrays still hit the gap, task #12.)
if (rhs.kind == nkind.N_ARRLIT) {
let elemn: *node = n.lhs.lhs;
let esz: i32 = 8;
let isstrel: bool = false;
if (elemn != nil) {
if (elemn.kind == nkind.N_TNAME) {
if (streq(elemn.str, "str")) {
esz = primtypesize("str"): i32;
isstrel = true;
} else {
let ps: i32 = primsize(elemn.str);
if (ps > 0) { esz = ps; };
};
};
};
// #270-1c: an AGGREGATE (struct/array/tuple) element of
// an array literal — the scalar per-element store below
// writes only the first 8 bytes (unpopulated tail). Fill
// each element slot from its literal (cgstructlitfillbp)
// or source ident (word-copy). esz is the element's
// natural size (cstage esub->size).
let esubti: *tinfo = nil;
if (elemn != nil) { esubti = elemn.type_: *tinfo; };
for (esubti != nil && esubti.kind == tykind.TY_NAMED) {
esubti = esubti.under;
};
let isagg: bool = esubti != nil
&& (esubti.kind == tykind.TY_STRUCT
|| esubti.kind == tykind.TY_ARRAY
|| esubti.kind == tykind.TY_TUPLE);
if (isagg) { esz = esubti.size: i32; };
// #20/#270 str-slice arm: a slice element (N_TSLICE) is
// a 24B {ptr,len,cap} header — it matches no prim/str/agg
// branch above, so esz stayed the 8 sentinel (wrong stride,
// the -96-vs-80 cs!=ww frame divergence) and the scalar
// store dropped .len/.cap. Size it from the stamped tinfo
// and route it through the 3-word header store below.
let isslicel: bool = esubti != nil
&& esubti.kind == tykind.TY_SLICE;
if (isslicel) { esz = esubti.size: i32; };
// #12: a tagged-union element. NOT folded into isagg —
// isagg's body word-copies/fatals and never boxes the
// tag+payload; route through the cgwidentaggedstore
// choke-point the N_LET tagged path (cgenstmt.ww:1627)
// uses. esz must come from the stamped slot size (#8-class
// trap, rule-13): the narrow override below only rescues
// 1/2/4, so a tagged 16/24B element keeps the wrong 8
// sentinel stride without this.
let istaggedel: bool = esubti != nil
&& esubti.kind == tykind.TY_TAGGED;
if (istaggedel) { esz = esubti.size: i32; };
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
// neither a builtin prim (primsize=0 above, so esz stayed
// the 8 sentinel) nor an aggregate, so the scalar store kept
// an 8B stride/MOVQ and overran the stride-4 frame slot —
// smashing the saved BP / return addr (SEGFAULT). Mirror
// cstage's uniform lu->sub->size (cgen.c:6387) and the
// elemsizeofc read-side fix: take the stamped element tinfo's
// size for a narrow scalar (1/2/4). Wider non-prim elements
// (tagged/slice/str two-half) stay the documented follow-up
// at :1742-1744 — the single-MOVx store below is scalar-only.
if (!isstrel && !isagg && esz == 8 && esubti != nil) {
let es: i32 = esubti.size: i32;
if (es == 1 || es == 2 || es == 4) { esz = es; };
};
let mop: str = tnodestoreop(c, elemn, esz);
// float element → store FROM X0 (MOVSS/MOVSD): cgexpr
// leaves a float in X0 and for f32 the #104 CVTSD2SS
// narrowing only touches X0; the AX store (mop) would
// write the raw double low-bits, garbage for f32 (#122,
// mirrors cstage cgen.c:6889 arr-lit float store).
let isfloatel: bool = isfloattype(c, elemn);
let fmov: str = "MOVSD";
if (isf32type(c, elemn)) { fmov = "MOVSS"; };
let idx: i32 = 0;
let repeat: bool = false;
let e: *node = rhs.list;
for (e != nil) {
let isellip: bool = false;
if (e.kind == nkind.N_FIELD) {
if (streq(e.str, "...")) {
repeat = true;
isellip = true;
};
};
if (isellip) {
e = nil;
} else {
if (isagg) {
if (e.kind == nkind.N_STRUCTLIT) {
let esi: *structinfo = structlookupchain(c, elemn);
cgstructlitfillbp(c, esi, e, off + idx * esz);
} else { if (e.kind == nkind.N_IDENT) {
let sl: *local = localfindnode(c, e.str);
let soff: i32 = 0;
if (sl != nil) { soff = sl.off; };
let kc: i32 = 0;
for (kc + 8 <= esz) {
emitline("\tMOVQ\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 8;
};
if (kc + 4 <= esz) {
emitline("\tMOVL\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVL\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 4;
};
if (kc + 2 <= esz) {
emitline("\tMOVW\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVW\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 2;
};
if (kc + 1 <= esz) {
emitline("\tMOVB\t");
emitoff((soff + kc): i64);
emitline("(BP), AX\n");
emitline("\tMOVB\tAX, ");
emitoff((off + idx * esz + kc): i64);
emitline("(BP)\n");
kc += 1;
};
} else {
let m1c: str = "#270-1c: array-literal aggregate element shape unsupported (rule-7)\n";
os.write(2, m1c.ptr, m1c.len: u64);
os.exit(1);
}; };
} else { if (istaggedel) {
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
} else {
cgexpr(c, e);
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
}; };
idx += 1;
e = e.next;
};
};
if (repeat && isagg) {
let m1cr: str = "#270-1c: `...` repeat of an aggregate array-literal element not wired (rule-7)\n";
os.write(2, m1cr.ptr, m1cr.len: u64);
os.exit(1);
};
// #12: `...` re-stores from AX, but cgwidentaggedstore consumed
// the node and trashed AX — the repeat-fill would write garbage.
// No consumer needs `[N]tagged=[x,...]`.
if (repeat && istaggedel) {
let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n";
os.write(2, m12r.ptr, m12r.len: u64);
os.exit(1);
};
// AX (and BX for str) still holds the last stored value;
// fill remaining slots up to the declared length with it.
if (repeat) {
let total: i32 = idx;
if (n.lhs != nil) {
if (n.lhs.kind == nkind.N_TARRAY) {
if (n.lhs.rhs != nil) {
if (n.lhs.rhs.kind == nkind.N_INTLIT) {
total = n.lhs.rhs.uval: i32;
};
};
};
};
for (idx < total) {
if (isstrel || isslicel) {
emitline("\tMOVQ\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tBX, ");
emitoff((off + idx * esz + 8): i64);
emitline("(BP)\n");
emitline("\tMOVQ\tCX, ");
emitoff((off + idx * esz + 16): i64);
emitline("(BP)\n");
} else { if (isfloatel) {
emitline("\t");
emitline(fmov);
emitline("\tX0, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
} else {
emitline("\t");
emitline(mop);
emitline("\tAX, ");
emitoff((off + idx * esz): i64);
emitline("(BP)\n");
}; };
idx += 1;
};
};
cgarrlitfillbp(c, n.lhs, rhs, off);
c.lastwasreturn = 0;
return;
};