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:
369
cmd/w6c/cgen.c
369
cmd/w6c/cgen.c
@@ -2984,6 +2984,175 @@ cg_tagged_tuple_payload_shift(Cg *c, Type *tup)
|
||||
ins2(c, A_MOVQ, areg(seq[i + 1]), areg(seq[i]));
|
||||
}
|
||||
|
||||
/* cg_arrlit_fill_bp — #31: fill the [count]T destination at BP-relative
|
||||
* `off` from an N_ARRLIT, extracted verbatim from the N_LET array-init
|
||||
* path so the slice-borrow base materialisation (the N_SLICE-over-
|
||||
* N_ARRLIT arm) reuses the identical element-store sequence. `lu` is the
|
||||
* [count]T array type the checker re-stamped (#25); `arrlit` the literal. */
|
||||
static void
|
||||
cg_arrlit_fill_bp(Cg *c, Local **locals, Type *lu, Node *arrlit, int off)
|
||||
{
|
||||
Type *esub = lu->sub;
|
||||
int esz = esub ? (int)esub->size : 1;
|
||||
/* #270-1c: an AGGREGATE (struct/array/tuple) element
|
||||
* of an array literal — the scalar per-element MOVQ
|
||||
* below stores only the first 8 bytes (unpopulated
|
||||
* tail). Fill each element slot from its literal
|
||||
* (cg_structlit_fill_bp) or source ident (word-copy). */
|
||||
Type *esubu = type_chase_named(esub);
|
||||
int is_agg = esubu && (esubu->kind == TY_STRUCT
|
||||
|| esubu->kind == TY_ARRAY
|
||||
|| esubu->kind == TY_TUPLE);
|
||||
/* #12: a tagged-union element. NOT folded into is_agg —
|
||||
* is_agg's body does N_STRUCTLIT/N_IDENT word-copy and
|
||||
* FATALs on the literal/scalar case, never boxing the
|
||||
* tag+payload. Route each element through the same
|
||||
* cg_widen_tagged_store choke-point every other tagged
|
||||
* store uses (let-init, vararg gather, struct-field). */
|
||||
int is_tagged_el = esubu && esubu->kind == TY_TAGGED;
|
||||
int is_str_el = type_isstr(esub);
|
||||
/* #20/#270 str-slice arm: a slice element is a 24B
|
||||
* {ptr,len,cap} header just like str; cgexpr lowers it
|
||||
* into AX/BX/CX. Both must store all three words — the
|
||||
* scalar 1-word MOVQ below drops .len and .cap. */
|
||||
int is_slice_el = type_isslice(esub);
|
||||
/* float element → store FROM X0; the AX path stores
|
||||
* raw double low-bits, garbage for f32 (#122, twin of
|
||||
* the arr[i]= store fix and the cgen.c:6423 read). */
|
||||
int is_float_el = type_isfloat(esub);
|
||||
int fmov = type_isf32(esub) ? A_MOVSS : A_MOVSD;
|
||||
int op = A_MOVQ;
|
||||
if (!is_str_el) {
|
||||
if (esz == 1) op = A_MOVB;
|
||||
else if (esz == 2) op = A_MOVW;
|
||||
else if (esz == 4) op = A_MOVL;
|
||||
/* #128a: esz==2 routes to MOVW (A_MOVW landed in
|
||||
* both stages' w6a). Pre-fix the 2-byte case fell
|
||||
* through to MOVQ, over-writing 6B into the next
|
||||
* element's slot; sequential adjacent writes
|
||||
* accident-corrected fully-init arrays but
|
||||
* partial inits clobbered neighbours. */
|
||||
}
|
||||
int idx = 0;
|
||||
Node *last = NULL;
|
||||
int repeat = 0;
|
||||
for (Node *e = arrlit->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str &&
|
||||
strcmp(e->str, "...") == 0) {
|
||||
repeat = 1;
|
||||
break;
|
||||
}
|
||||
int base = off + idx * esz;
|
||||
if (is_agg) {
|
||||
if (e->kind == N_STRUCTLIT) {
|
||||
cg_structlit_fill_bp(c, locals,
|
||||
esubu, e, base);
|
||||
} else if (e->kind == N_IDENT) {
|
||||
int soff = localfind(*locals,
|
||||
e->str);
|
||||
int k = 0;
|
||||
for (; k + 8 <= esz; k += 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
}
|
||||
if (k + 4 <= esz) {
|
||||
ins2(c, A_MOVL,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVL,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= esz) {
|
||||
ins2(c, A_MOVW,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVW,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= esz) {
|
||||
ins2(c, A_MOVB,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVB,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
k += 1;
|
||||
}
|
||||
} else {
|
||||
fatal("#270-1c: array-literal "
|
||||
"aggregate element shape "
|
||||
"unsupported (rule-7)");
|
||||
}
|
||||
last = e;
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
if (is_tagged_el) {
|
||||
cg_widen_tagged_store(c, locals, esub,
|
||||
e, D_BP, base, esz);
|
||||
last = e;
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, e, *locals);
|
||||
if (is_str_el || is_slice_el) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, base + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, base + 16));
|
||||
} else if (is_float_el) {
|
||||
ins2(c, fmov, areg(D_X0),
|
||||
amem(D_BP, base));
|
||||
} else {
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
}
|
||||
last = e;
|
||||
idx++;
|
||||
}
|
||||
if (repeat && is_agg)
|
||||
fatal("#270-1c: `...` repeat of an aggregate "
|
||||
"array-literal element not wired (rule-7)");
|
||||
/* #12: `...` re-stores from AX, but cg_widen_tagged_store
|
||||
* consumed the node and trashed AX — a repeat-fill would
|
||||
* write garbage. No consumer needs `[N]tagged=[x,...]`. */
|
||||
if (repeat && is_tagged_el)
|
||||
fatal("#12: `...` repeat of a tagged-union "
|
||||
"array-literal element not wired (rule-7)");
|
||||
if (repeat && last) {
|
||||
/* fill remaining slots with the value still in
|
||||
* AX (and BX for str). */
|
||||
while (idx < (int)lu->alen) {
|
||||
int base = off + idx * esz;
|
||||
if (is_str_el || is_slice_el) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, base + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, base + 16));
|
||||
} else if (is_float_el) {
|
||||
ins2(c, fmov, areg(D_X0),
|
||||
amem(D_BP, base));
|
||||
} else {
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
cgexpr(Cg *c, Node *n, Local *locals)
|
||||
{
|
||||
@@ -6428,7 +6597,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* silently wrong for non-u8). Other non-ident
|
||||
* bases stay esz=1 (unscaled). */
|
||||
int esz = (base && (base->kind == N_IDENT
|
||||
|| base->kind == N_DOT)
|
||||
|| base->kind == N_DOT
|
||||
|| base->kind == N_ARRLIT)
|
||||
&& bu && bu->sub)
|
||||
? (int)bu->sub->size : 1;
|
||||
/* base addr → push */
|
||||
@@ -8685,7 +8855,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* non-ident bases stay esz=1 (unscaled) -- #76 residual,
|
||||
* non-ident cluster #74. */
|
||||
int esz = (base && (base->kind == N_IDENT
|
||||
|| base->kind == N_DOT) && bu && bu->sub)
|
||||
|| base->kind == N_DOT || base->kind == N_ARRLIT)
|
||||
&& bu && bu->sub)
|
||||
? (int)bu->sub->size : 1;
|
||||
if (base && base->kind == N_IDENT) {
|
||||
int boff = localfind(locals, base->str);
|
||||
@@ -8701,6 +8872,40 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
} else {
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
|
||||
}
|
||||
} else if (base && base->kind == N_ARRLIT && bu
|
||||
&& bu->kind == TY_ARRAY) {
|
||||
/* #31: an array LITERAL base — the desugared one-step
|
||||
* `let xs: []T = [..]` borrow (the ONLY context that
|
||||
* reaches here; call-arg/return/assign loud-reject at the
|
||||
* checker, reject_arrlit_borrow, deferred to #33). The
|
||||
* literal has no storage address — cgexpr would leave
|
||||
* AX=garbage and the borrow's .ptr would dangle.
|
||||
* Materialise it into a FRESH per-borrow @slicescr stack
|
||||
* slot (distinct slot per borrow: a borrow's backing must
|
||||
* stay live for the slice's lifetime, so it can't share a
|
||||
* cached SSoT slot the way @aggargscr/@tagscr — drained/
|
||||
* consumed in place — do; two live borrows would otherwise
|
||||
* alias one backing). Reuses local_alloc + the shared
|
||||
* array-init fill; the checker re-stamped base->type to
|
||||
* [count]T (#25) so the fill stores at the declared
|
||||
* element width.
|
||||
*
|
||||
* Escape (WHY, rob): a `let xs: []T = [..]; return xs;`
|
||||
* returns a slice pointing at this frame slot, freed on
|
||||
* return = dangling. This is IDENTICAL to the pre-existing
|
||||
* named-array borrow (`let a: [N]T = [..]; return a;`) and
|
||||
* is Hare-consistent: ww has no escape analysis, no GC, no
|
||||
* heap promotion — borrowing a local past its frame is a
|
||||
* programmer footgun, not promoted. Don't "fix" this
|
||||
* expecting heap promotion; ww deliberately doesn't, same
|
||||
* as Hare. */
|
||||
int cnt = (int)bu->alen;
|
||||
int bsz = (bu->sub ? (int)bu->sub->size : 1) * cnt;
|
||||
if (bsz < 1) bsz = 1;
|
||||
int scr = local_alloc(c, &locals, "@slicescr", bsz,
|
||||
cg_frame);
|
||||
cg_arrlit_fill_bp(c, &locals, bu, base, scr);
|
||||
ins2(c, A_LEAQ, amem(D_BP, scr), areg(D_AX));
|
||||
} else if (base) {
|
||||
/* #252: N_DOT `[N]T`-field base → field ADDRESS via
|
||||
* cg_dotbase_addr (LEAQ), not the auto-deref VALUE load
|
||||
@@ -9071,165 +9276,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
* (is_agg excludes TY_TAGGED) — tracked as task #12. */
|
||||
if (n->rhs && n->rhs->kind == N_ARRLIT && lu
|
||||
&& lu->kind == TY_ARRAY) {
|
||||
Type *esub = lu->sub;
|
||||
int esz = esub ? (int)esub->size : 1;
|
||||
/* #270-1c: an AGGREGATE (struct/array/tuple) element
|
||||
* of an array literal — the scalar per-element MOVQ
|
||||
* below stores only the first 8 bytes (unpopulated
|
||||
* tail). Fill each element slot from its literal
|
||||
* (cg_structlit_fill_bp) or source ident (word-copy). */
|
||||
Type *esubu = type_chase_named(esub);
|
||||
int is_agg = esubu && (esubu->kind == TY_STRUCT
|
||||
|| esubu->kind == TY_ARRAY
|
||||
|| esubu->kind == TY_TUPLE);
|
||||
/* #12: a tagged-union element. NOT folded into is_agg —
|
||||
* is_agg's body does N_STRUCTLIT/N_IDENT word-copy and
|
||||
* FATALs on the literal/scalar case, never boxing the
|
||||
* tag+payload. Route each element through the same
|
||||
* cg_widen_tagged_store choke-point every other tagged
|
||||
* store uses (let-init, vararg gather, struct-field). */
|
||||
int is_tagged_el = esubu && esubu->kind == TY_TAGGED;
|
||||
int is_str_el = type_isstr(esub);
|
||||
/* #20/#270 str-slice arm: a slice element is a 24B
|
||||
* {ptr,len,cap} header just like str; cgexpr lowers it
|
||||
* into AX/BX/CX. Both must store all three words — the
|
||||
* scalar 1-word MOVQ below drops .len and .cap. */
|
||||
int is_slice_el = type_isslice(esub);
|
||||
/* float element → store FROM X0; the AX path stores
|
||||
* raw double low-bits, garbage for f32 (#122, twin of
|
||||
* the arr[i]= store fix and the cgen.c:6423 read). */
|
||||
int is_float_el = type_isfloat(esub);
|
||||
int fmov = type_isf32(esub) ? A_MOVSS : A_MOVSD;
|
||||
int op = A_MOVQ;
|
||||
if (!is_str_el) {
|
||||
if (esz == 1) op = A_MOVB;
|
||||
else if (esz == 2) op = A_MOVW;
|
||||
else if (esz == 4) op = A_MOVL;
|
||||
/* #128a: esz==2 routes to MOVW (A_MOVW landed in
|
||||
* both stages' w6a). Pre-fix the 2-byte case fell
|
||||
* through to MOVQ, over-writing 6B into the next
|
||||
* element's slot; sequential adjacent writes
|
||||
* accident-corrected fully-init arrays but
|
||||
* partial inits clobbered neighbours. */
|
||||
}
|
||||
int idx = 0;
|
||||
Node *last = NULL;
|
||||
int repeat = 0;
|
||||
for (Node *e = n->rhs->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str &&
|
||||
strcmp(e->str, "...") == 0) {
|
||||
repeat = 1;
|
||||
break;
|
||||
}
|
||||
int base = off + idx * esz;
|
||||
if (is_agg) {
|
||||
if (e->kind == N_STRUCTLIT) {
|
||||
cg_structlit_fill_bp(c, locals,
|
||||
esubu, e, base);
|
||||
} else if (e->kind == N_IDENT) {
|
||||
int soff = localfind(*locals,
|
||||
e->str);
|
||||
int k = 0;
|
||||
for (; k + 8 <= esz; k += 8) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
}
|
||||
if (k + 4 <= esz) {
|
||||
ins2(c, A_MOVL,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVL,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= esz) {
|
||||
ins2(c, A_MOVW,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVW,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= esz) {
|
||||
ins2(c, A_MOVB,
|
||||
amem(D_BP, soff + k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVB,
|
||||
areg(D_AX),
|
||||
amem(D_BP, base + k));
|
||||
k += 1;
|
||||
}
|
||||
} else {
|
||||
fatal("#270-1c: array-literal "
|
||||
"aggregate element shape "
|
||||
"unsupported (rule-7)");
|
||||
}
|
||||
last = e;
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
if (is_tagged_el) {
|
||||
cg_widen_tagged_store(c, locals, esub,
|
||||
e, D_BP, base, esz);
|
||||
last = e;
|
||||
idx++;
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, e, *locals);
|
||||
if (is_str_el || is_slice_el) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, base + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, base + 16));
|
||||
} else if (is_float_el) {
|
||||
ins2(c, fmov, areg(D_X0),
|
||||
amem(D_BP, base));
|
||||
} else {
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
}
|
||||
last = e;
|
||||
idx++;
|
||||
}
|
||||
if (repeat && is_agg)
|
||||
fatal("#270-1c: `...` repeat of an aggregate "
|
||||
"array-literal element not wired (rule-7)");
|
||||
/* #12: `...` re-stores from AX, but cg_widen_tagged_store
|
||||
* consumed the node and trashed AX — a repeat-fill would
|
||||
* write garbage. No consumer needs `[N]tagged=[x,...]`. */
|
||||
if (repeat && is_tagged_el)
|
||||
fatal("#12: `...` repeat of a tagged-union "
|
||||
"array-literal element not wired (rule-7)");
|
||||
if (repeat && last) {
|
||||
/* fill remaining slots with the value still in
|
||||
* AX (and BX for str). */
|
||||
while (idx < (int)lu->alen) {
|
||||
int base = off + idx * esz;
|
||||
if (is_str_el || is_slice_el) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, base + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, base + 16));
|
||||
} else if (is_float_el) {
|
||||
ins2(c, fmov, areg(D_X0),
|
||||
amem(D_BP, base));
|
||||
} else {
|
||||
ins2(c, op, areg(D_AX),
|
||||
amem(D_BP, base));
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
cg_arrlit_fill_bp(c, locals, lu, n->rhs, off);
|
||||
break;
|
||||
}
|
||||
/* Struct ident copy: `let p2: T = p1;` where T is a struct
|
||||
|
||||
@@ -396,13 +396,21 @@ arrlit_init_fits(Checker *c, Type *dt, Node *rhs)
|
||||
{
|
||||
if (rhs == NULL || rhs->kind != N_ARRLIT) return 0;
|
||||
Type *u = (dt && dt->kind == TY_NAMED) ? dt->under : dt;
|
||||
if (u == NULL || u->kind != TY_ARRAY) return 0;
|
||||
/* #25: a SLICE target is admitted via the same per-element coercion
|
||||
* as the array path — the #258 borrow demands an exact element
|
||||
* type_eq, which an arrlit's self-stamped [N]<default> can't meet for
|
||||
* untyped_str / bare-int-width elements. Peel to the slice element T
|
||||
* and run the array-element coercion against it. */
|
||||
if (u == NULL || (u->kind != TY_ARRAY && u->kind != TY_SLICE))
|
||||
return 0;
|
||||
Type *et = u->sub;
|
||||
Type *eu = (et && et->kind == TY_NAMED) ? et->under : et;
|
||||
u64 count = 0;
|
||||
for (Node *e = rhs->list; e; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
continue;
|
||||
count++;
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
u64 v;
|
||||
@@ -417,6 +425,12 @@ arrlit_init_fits(Checker *c, Type *dt, Node *rhs)
|
||||
if (!type_assignable(et, e->type) && !assignable_addrfn(c, et, e))
|
||||
return 0;
|
||||
}
|
||||
/* #25/#31: re-stamp the literal as [count]T so desugar_arrayslice keys
|
||||
* on an exact-element-eq array and the cgen N_SLICE-over-N_ARRLIT arm
|
||||
* (#31) materialises the borrow backing at the DECLARED element width.
|
||||
* The array path keeps the declared array type, so this is slice-only. */
|
||||
if (u->kind == TY_SLICE)
|
||||
rhs->type = type_array(c->a, et, count);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -939,6 +953,26 @@ coerce_floatlit(Node *n, Type *target)
|
||||
* the four acceptance sites can call it unconditionally; it no-ops unless
|
||||
* the dst is a slice and the src an array with an exactly-matching
|
||||
* element. */
|
||||
/* reject_arrlit_borrow — #31/#33: the array-literal → slice borrow is
|
||||
* supported only at a `let` init, where clet spills the literal to a
|
||||
* per-borrow backing slot (#31). In call-arg / return / assign position
|
||||
* there is no addressable backing — the borrow's .ptr would dangle (the
|
||||
* original #31 silent segfault). Reject loudly here so the gap is a
|
||||
* compile error, not a miscompile. rule-10: wwstage rejects the same
|
||||
* source (its untyped-arrlit element fails the borrow's typeeq); aligning
|
||||
* cstage DOWN keeps both stages loud-identical. Full non-let support is
|
||||
* #33. Returns 1 (and emits the error) when it refuses the borrow. */
|
||||
static int
|
||||
reject_arrlit_borrow(Checker *c, Type *dst, Node *expr)
|
||||
{
|
||||
if (expr == NULL || expr->kind != N_ARRLIT) return 0;
|
||||
Type *du = (dst && dst->kind == TY_NAMED) ? dst->under : dst;
|
||||
if (du == NULL || du->kind != TY_SLICE) return 0;
|
||||
err(c, expr->pos, "array literal cannot borrow as a slice here; "
|
||||
"bind it to a `let` first");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
desugar_arrayslice(Checker *c, Type *dst, Node *expr)
|
||||
{
|
||||
@@ -1539,8 +1573,11 @@ cexpr(Checker *c, Node *n)
|
||||
&& !assignable_addrfn(c, p->type, a))
|
||||
err(c, a->pos, "argument type %s not assignable to %s",
|
||||
type_name(c->a, at), type_name(c->a, p->type));
|
||||
/* #258: `f(arr)` borrows the array as a full slice. */
|
||||
desugar_arrayslice(c, p->type, a);
|
||||
/* #258: `f(arr)` borrows the array as a full slice.
|
||||
* #31/#33: a bare array LITERAL arg has no backing —
|
||||
* loud-reject (supported only at a `let`). */
|
||||
if (!reject_arrlit_borrow(c, p->type, a))
|
||||
desugar_arrayslice(c, p->type, a);
|
||||
p = p->next;
|
||||
}
|
||||
if (p != NULL && !p->variadic)
|
||||
@@ -1568,8 +1605,11 @@ cexpr(Checker *c, Node *n)
|
||||
!assignable_addrfn(c, l, n->rhs))
|
||||
err(c, n->pos, "cannot assign %s to %s",
|
||||
type_name(c->a, r), type_name(c->a, l));
|
||||
/* #258: `s = arr` borrows the array as a full slice. */
|
||||
desugar_arrayslice(c, l, n->rhs);
|
||||
/* #258: `s = arr` borrows the array as a full slice.
|
||||
* #31/#33: a bare array LITERAL rhs has no backing —
|
||||
* loud-reject (supported only at a `let`). */
|
||||
if (!reject_arrlit_borrow(c, l, n->rhs))
|
||||
desugar_arrayslice(c, l, n->rhs);
|
||||
return n->type = l;
|
||||
}
|
||||
case N_STRUCTLIT: {
|
||||
@@ -2032,8 +2072,11 @@ cstmt(Checker *c, Node *n)
|
||||
type_name(c->a, rt), type_name(c->a, c->ret));
|
||||
/* #104 fold-2: `fn g() f32 = { return 1.0; }` — narrow to f32. */
|
||||
coerce_floatlit(n->lhs, c->ret);
|
||||
/* #258: `return arr` borrows the array as a full slice. */
|
||||
desugar_arrayslice(c, c->ret, n->lhs);
|
||||
/* #258: `return arr` borrows the array as a full slice.
|
||||
* #31/#33: a bare array LITERAL has no backing — loud-reject
|
||||
* (supported only at a `let`). */
|
||||
if (!reject_arrlit_borrow(c, c->ret, n->lhs))
|
||||
desugar_arrayslice(c, c->ret, n->lhs);
|
||||
break;
|
||||
}
|
||||
case N_IF: {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 ------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
280
test/wcc/953_arrlit_slice_run.c
Normal file
280
test/wcc/953_arrlit_slice_run.c
Normal file
@@ -0,0 +1,280 @@
|
||||
/*
|
||||
* 953_arrlit_slice_run — runtime + byte-id + reject net for #25/#31: a
|
||||
* one-step array-LITERAL initialiser for a SLICE local (`let xs:[]T=[..]`).
|
||||
*
|
||||
* #31 (silent miscompile, cs!=ww): the #258 array→slice borrow wrapped the
|
||||
* un-addressable N_ARRLIT directly as the N_SLICE base; cgen never spilled
|
||||
* the literal to a stack slot, so .ptr pointed at garbage (`let xs:[]i32=
|
||||
* [10,20,30]; xs[1]` returned 1; []u8/[]str segfaulted). #25 (over-strict
|
||||
* reject): a slice target fell through to the exact-element type_eq borrow
|
||||
* gate, so bare-int-width ([]u8=[1,2,3]) and str elements rejected.
|
||||
*
|
||||
* Fix (re-stamp + per-borrow scratch, both stages symmetric, NO new codegen
|
||||
* shape): the checker re-stamps the slice arrlit as [count]T (per-element
|
||||
* coercion + range-check, #25); cgen materialises the borrow base into a
|
||||
* FRESH per-borrow @slicescr stack slot (distinct slot per borrow — a
|
||||
* borrow's backing outlives the lowering, so it can't share a cached slot;
|
||||
* two live borrows would otherwise alias one backing), filled via the shared
|
||||
* array-init element store (#31). 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 per rule-10; full non-let support is deferred (#33).
|
||||
*
|
||||
* Accept rows (cstage `ww build` + run for exit code, then w6c vs w6c_ww .s
|
||||
* cmp for the rule-10 byte-id gate):
|
||||
* - i32_sum let xs:[]i32=[10,20,30]; xs[0]+xs[1]+xs[2] → 60
|
||||
* - i32_idx xs[1] (the #31 pin: was 1, want 20) → 20
|
||||
* - u8_coerce let zs:[]u8=[1,2,3]; bare-int→u8 width (#25) → 6
|
||||
* - u8_typed let xs:[]u8=[10u8,20u8,30u8]; xs[2] → 30
|
||||
* - i64_stride let xs:[]i64=[7i64,42i64]; xs[1] (8B stride) → 42
|
||||
* - str_read let ys:[]str=["ab","c"]; ys[0].len*10+ys[1].len → 21
|
||||
* - len_read let xs:[]i32=[10,20,30]; xs.len → 3
|
||||
* - multi_live let xs=[1,2,3]; let ys=[4,5]; xs[0]+ys[0] → 5
|
||||
* (SOUNDNESS PIN: fresh-per-borrow; a shared slot → 4+4=8)
|
||||
* - borrow_mut xs[1]=99 through the borrow, read back → 99
|
||||
*
|
||||
* Reject rows (cs==ww symmetric loud-reject, both w6c and w6c_ww non-zero):
|
||||
* - reject_oob let q:[]u8=[256,1] → out-of-range element (#25)
|
||||
* - reject_call sum([1,2,3]) → non-let borrow (#31/#33)
|
||||
* - reject_ret return [1,2,3] → non-let borrow
|
||||
* - reject_assign s = [1,2,3] → non-let borrow
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
slurp_eq(const char *a, const char *b)
|
||||
{
|
||||
FILE *fa = fopen(a, "rb"), *fb = fopen(b, "rb");
|
||||
if (fa == NULL || fb == NULL) {
|
||||
if (fa) fclose(fa);
|
||||
if (fb) fclose(fb);
|
||||
return -1;
|
||||
}
|
||||
int ca, cb, eq = 0;
|
||||
do {
|
||||
ca = fgetc(fa);
|
||||
cb = fgetc(fb);
|
||||
if (ca != cb) { eq = -1; break; }
|
||||
} while (ca != EOF);
|
||||
fclose(fa);
|
||||
fclose(fb);
|
||||
return eq;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want_exit; int reject; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "i32_sum",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []i32 = [10, 20, 30];\n"
|
||||
" return xs[0] + xs[1] + xs[2];\n"
|
||||
"};\n", 60, 0 },
|
||||
{ "i32_idx",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []i32 = [10, 20, 30];\n"
|
||||
" return xs[1];\n"
|
||||
"};\n", 20, 0 },
|
||||
{ "u8_coerce",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let zs: []u8 = [1, 2, 3];\n"
|
||||
" return zs[0]: i32 + zs[1]: i32 + zs[2]: i32;\n"
|
||||
"};\n", 6, 0 },
|
||||
{ "u8_typed",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []u8 = [10u8, 20u8, 30u8];\n"
|
||||
" return xs[2]: i32;\n"
|
||||
"};\n", 30, 0 },
|
||||
{ "i64_stride",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []i64 = [7i64, 42i64];\n"
|
||||
" return xs[1]: i32;\n"
|
||||
"};\n", 42, 0 },
|
||||
{ "str_read",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let ys: []str = [\"ab\", \"c\"];\n"
|
||||
" return ys[0].len: i32 * 10 + ys[1].len: i32;\n"
|
||||
"};\n", 21, 0 },
|
||||
{ "len_read",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []i32 = [10, 20, 30];\n"
|
||||
" return xs.len: i32;\n"
|
||||
"};\n", 3, 0 },
|
||||
{ "multi_live",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []i32 = [1, 2, 3];\n"
|
||||
" let ys: []i32 = [4, 5];\n"
|
||||
" return xs[0] + ys[0];\n"
|
||||
"};\n", 5, 0 },
|
||||
{ "borrow_mut",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let xs: []i32 = [1, 2, 3];\n"
|
||||
" xs[1] = 99;\n"
|
||||
" return xs[1];\n"
|
||||
"};\n", 99, 0 },
|
||||
/* Reject rows: out-of-range element + the three non-let contexts. */
|
||||
{ "reject_oob",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let q: []u8 = [256, 1];\n"
|
||||
" return q[0]: i32;\n"
|
||||
"};\n", 0, 1 },
|
||||
{ "reject_call",
|
||||
"package main;\n"
|
||||
"fn sum(s: []i32) i32 = { return s[0]; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return sum([1, 2, 3]);\n"
|
||||
"};\n", 0, 1 },
|
||||
{ "reject_ret",
|
||||
"package main;\n"
|
||||
"fn mk() []i32 = { return [1, 2, 3]; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return mk()[0];\n"
|
||||
"};\n", 0, 1 },
|
||||
{ "reject_assign",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: []i32 = [0, 0];\n"
|
||||
" s = [1, 2, 3];\n"
|
||||
" return s[0];\n"
|
||||
"};\n", 0, 1 },
|
||||
{ NULL, NULL, 0, 0 },
|
||||
};
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
const char *bin = getenv("BIN");
|
||||
if (!bin) bin = "out/bin";
|
||||
char absbin[1024];
|
||||
if (bin[0] != '/') {
|
||||
char cwd[1024];
|
||||
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||
bin = absbin;
|
||||
}
|
||||
|
||||
char w6c[1100], w6c_ww[1100];
|
||||
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
|
||||
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
|
||||
if (access(w6c_ww, X_OK) != 0) {
|
||||
fprintf(stderr, "arrlit_slice: w6c_ww missing — cannot run the "
|
||||
"cs==ww byte-id gate (the whole point of this test)\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
int n = 0, fail = 0;
|
||||
for (int i = 0; rows[i].src; i++, n++) {
|
||||
char src[64];
|
||||
snprintf(src, sizeof src, "/tmp/wwas_%d_%d.ww", getpid(), i);
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (f == NULL) { fail++; continue; }
|
||||
fputs(rows[i].src, f);
|
||||
fclose(f);
|
||||
|
||||
char cs_s[64], ws_s[64];
|
||||
snprintf(cs_s, sizeof cs_s, "/tmp/wwas_%d_%d_cs.s", getpid(), i);
|
||||
snprintf(ws_s, sizeof ws_s, "/tmp/wwas_%d_%d_ww.s", getpid(), i);
|
||||
char cmd[2048];
|
||||
|
||||
if (rows[i].reject) {
|
||||
/* Both stages must refuse (non-zero). */
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||
w6c, cs_s, src);
|
||||
if (runwait(cmd) == 0) {
|
||||
fprintf(stderr, "row[%s]: w6c ACCEPTED a row that "
|
||||
"must reject\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||
w6c_ww, ws_s, src);
|
||||
if (runwait(cmd) == 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww ACCEPTED a row "
|
||||
"that must reject\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ws_s);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Positive row: cstage build + run for exit code. */
|
||||
char tmpdir[64];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/wwas_%d_d_%d",
|
||||
getpid(), i);
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s",
|
||||
tmpdir, bin, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: cstage build failed\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
unlink(src); rmdir(tmpdir);
|
||||
continue;
|
||||
}
|
||||
char outbin[128];
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
if (got != rows[i].want_exit) {
|
||||
fprintf(stderr, "row[%s]: cstage exit %d, want %d\n",
|
||||
rows[i].label, got, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
unlink(outbin); rmdir(tmpdir);
|
||||
|
||||
/* rule-10 byte-id: w6c vs w6c_ww .s. */
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||
w6c, cs_s, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label);
|
||||
fail++; unlink(src); continue;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null",
|
||||
w6c_ww, ws_s, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww failed\n",
|
||||
rows[i].label);
|
||||
fail++; unlink(src); unlink(cs_s); continue;
|
||||
}
|
||||
if (slurp_eq(cs_s, ws_s) != 0) {
|
||||
fprintf(stderr,
|
||||
"row[%s]: cstage/wwstage .s DIFFER (rule-10 "
|
||||
"byte-id violation)\n", rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
unlink(src); unlink(cs_s); unlink(ws_s);
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "%d/%d arrlit-slice tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("arrlit_slice: %d/%d ok (cstage run + cs==ww byte-id + "
|
||||
"reject)\n", n, n);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user