cgen: #49 aggregate-ASSIGN word0-only family — one mem-to-mem funnel (cg_aggcopy), both stages
Whole-aggregate reassignment `b = a` fell to the N_ASSIGN scalar tail
and copied ONE MOVQ — word 0 of any struct/array/tuple — in BOTH
stages, byte-identical, gate-blind (ken f49_min asm proof; latent
because lib style is let-init, whose #265/#268 copy is full-width).
Same class at three more positions: struct-lit FIELD init from an
ident source (`outer{.., r = r}`, the #38 non-tagged half), the deref
place `*p = s` (#31-A), and the module-let global `g = a` / `g = pt{..}`.
Fix: extract the C1.25 assign-resolver word-copy tail verbatim into
cg_aggcopy/aggcopy — the ONE place-resolved (SI)->(BX) aggregate copy
— and wire it at the N_ASSIGN ident-aggregate arm (local + global),
the deref-place divert into the existing resolver aggregate arm, and
the structlit-fill aggregate-field arm, all fed by aggarg_srcaddr
(the closed #265/#268 dispatch). The new arms key on the FULL alias
chase (type_chase_named / chased stamped tinfo, the #22 precedent) in
BOTH stages — the region's single-peel `lu`/`fu` would miss
`type b = a; type a = struct` on cstage while the wwstage twin fired
(ken R1, gA3b: master cs ran the word0 corruption, exit 2; now 0).
Non-addressable aggregate rhs (tuple-lit, unhandled call shapes) dies
LOUD (rule 7) instead of silently truncating: #31-E `*p = (3,4)` and
#31-G's deref flavor `*p = mk()` are now loud both stages (the INDEX
flavor `a[i] = mk()` stays in the legacy INDEX arm — receive
machinery, not this funnel; still filed under #31). #31-B rides: the
cstage-only <=24B gate before cg_structlit_fill_bp is lifted (the
wwstage twin never gated — a >24B literal reassign was
cs-zero/ww-filled, rule-10 break). Global structlit reassign rides
the existing DST_GLOBAL fill machinery.
Unsplit (rule 11): the assign arm, fill arm and deref divert all
route through the one new funnel (cg_aggcopy + aggarg_srcaddr) in
both stages; splitting by site or by stage would ship a transient
cs!=ww (gate-red) or a funnel with no consumer.
941 t2_reject_chain_arg: the row's tuple-LITERAL field fill now louds
at the #49 fill arm before reaching the pinned ARG-site reject; the
fill switches to an ident source (newly working via the fill arm) so
the original arg-site pin still fires.
test/wcc/812_agg_assign_width.c: 17 runtime-readback rows (the only
oracle for a gate-blind class) + per-row asm byte-id; every row fails
at 7545bf7 (ken matrix f49_min/f49b/f49c/fA_16b/f38b + gA3b/gA6 +
impl-A probes; reviewer-A re-probed 5 rows + the gA2 12B shape at a
master git-archive scratch). Alias rows use FIELD-WISE init: the
struct-LIT spelling louds earlier at the pre-existing task-#7
aggregate-let bound on wwstage (the #5 alias-arc's hole, not this
funnel's). Reviewer-A amendment (test-only, K5 self-certify): add
the ken-gA2 odd-size row (12B {u32,u32,u32}, maxalign 4 — pins the
MOVL tail; master both stages exit 3) and gA4's neighbor guards on
the deref row, completing ken's validated matrix in the committed
suite.
This commit is contained in:
15
Makefile
15
Makefile
@@ -399,6 +399,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_arrlit_overlong \
|
||||
$(BIN)/test_idx_structlit_store \
|
||||
$(BIN)/test_inferred_let_struct \
|
||||
$(BIN)/test_agg_assign_width \
|
||||
$(BIN)/test_placeaddr_store \
|
||||
$(BIN)/test_tryprop_multisuccess \
|
||||
$(BIN)/test_append_place \
|
||||
@@ -1101,6 +1102,20 @@ $(BIN)/test_inferred_let_struct: test/wcc/811_inferred_let_struct.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# Task #49 (Family A, ken silent-set triage): whole-aggregate ASSIGN
|
||||
# `b = a` copied word0 only — one MOVQ for any struct/array/tuple — in
|
||||
# BOTH stages, byte-identical, gate-blind. Same class at the structlit
|
||||
# fill field-from-ident, the deref place (#31-A) and the module-let
|
||||
# global. Fixed via the ONE mem-to-mem funnel (cg_aggcopy/aggcopy fed
|
||||
# by aggarg_srcaddr); non-addressable aggregate rhs dies loud. Runtime
|
||||
# readback rows (the only oracle for a gate-blind class) + per-row
|
||||
# asm byte-id.
|
||||
$(BIN)/test_agg_assign_width: test/wcc/812_agg_assign_width.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
# F8+F9 (tasks #5/#12, regex fold-2b): `?` interim single-success gate
|
||||
# (|success| > 1 loud-rejected on BOTH stages until task #14's
|
||||
# subset-union typing) + direct `f()? is T` / `match (f()?)` reject
|
||||
|
||||
248
cmd/w6c/cgen.c
248
cmd/w6c/cgen.c
@@ -2073,6 +2073,37 @@ aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* cg_aggcopy — the ONE place-resolved mem-to-mem aggregate copy:
|
||||
* sz bytes (SI) → (BX) via AX, a MOVQ run plus a 4/2/1 sized tail.
|
||||
* Extracted verbatim from the C1.25 assign-resolver tail so every
|
||||
* aggregate copy position (resolver field store, #49 ident reassign,
|
||||
* #49 structlit fill-field) funnels through one loop — close-by-
|
||||
* construction, no per-site width logic to skew. */
|
||||
static void
|
||||
cg_aggcopy(Cg *c, int sz)
|
||||
{
|
||||
int k = 0;
|
||||
for (; k + 8 <= sz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, k));
|
||||
}
|
||||
if (k + 4 <= sz) {
|
||||
ins2(c, A_MOVL, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX), amem(D_BX, k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= sz) {
|
||||
ins2(c, A_MOVW, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX), amem(D_BX, k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= sz) {
|
||||
ins2(c, A_MOVB, amem(D_SI, k), areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX), amem(D_BX, k));
|
||||
k += 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
* expression into dst_reg; returns 1 when the shape is wired, 0
|
||||
* otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
@@ -3111,6 +3142,43 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* #49 (f38b/x5f-h): an aggregate field from an ADDRESSABLE
|
||||
* source expr — `outer{.., r = r}` — fell to the scalar
|
||||
* tail below and stored word0 only. Funnel: source address
|
||||
* via aggarg_srcaddr (SI), field address via LEAQ (BX —
|
||||
* loaded AFTER the source walk, which clobbers BX/AX), then
|
||||
* cg_aggcopy. Non-addressable aggregate sources (tuple-lit,
|
||||
* >24B/odd-tail call) die loud — pre-#49 they were the same
|
||||
* silent word0 (rule 7). FULL alias chase (#22 precedent),
|
||||
* not the region's single-peel `fu` — the wwstage twin
|
||||
* full-chases the stamped tinfo; a single peel here would
|
||||
* miss `type b = a; type a = struct` and silently diverge. */
|
||||
Type *fagg = type_chase_named(ft);
|
||||
if (fagg && (fagg->kind == TY_STRUCT || fagg->kind == TY_ARRAY
|
||||
|| fagg->kind == TY_TUPLE)) {
|
||||
if (!aggarg_srcaddr(c, f->lhs, D_SI, *locals_p))
|
||||
fatal("structlit fill: aggregate field '%s' "
|
||||
"from a non-addressable source unwired "
|
||||
"(task #49/rule-7)",
|
||||
f->str ? f->str : "?");
|
||||
if (mode == DST_BP)
|
||||
ins2(c, A_LEAQ, amem(D_BP, disp + (int)foff),
|
||||
areg(D_BX));
|
||||
else {
|
||||
if (mode == DST_PTR_LOCAL)
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ, masym(c, name),
|
||||
areg(D_BX));
|
||||
if (disp + (int)foff != 0)
|
||||
ins2(c, A_ADDQ,
|
||||
aimm(disp + (int)foff),
|
||||
areg(D_BX));
|
||||
}
|
||||
cg_aggcopy(c, (int)fagg->size);
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
/* For non-BP modes, cgexpr just clobbered BX; reload it
|
||||
* before the store. */
|
||||
@@ -6073,14 +6141,26 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
"tagged GLOBAL lvalue unwired");
|
||||
}
|
||||
}
|
||||
/* #49 (#31-A fold): an aggregate pointee diverts the whole
|
||||
* deref-assign to the resolver aggregate arm below — the
|
||||
* scalar tail here stored ONE word of `*p = s` (#31-A);
|
||||
* tuple-lit (#31-E) and call (#31-G) rhs now die loud there
|
||||
* instead of silently truncating. str/slice pointees keep
|
||||
* their 3-word arm here (byte-id-pinned). */
|
||||
int deref_agg = 0;
|
||||
if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR
|
||||
&& n->op == TK_ASSIGN) {
|
||||
Type *du = type_chase_named(n->lhs->type);
|
||||
if (du && (du->kind == TY_STRUCT
|
||||
|| du->kind == TY_ARRAY
|
||||
|| du->kind == TY_TUPLE))
|
||||
deref_agg = 1;
|
||||
}
|
||||
/* Deref-target assignment `*p = v;`. The size of the store is
|
||||
* determined by the type *p points at; the pointer expression
|
||||
* is evaluated after the value so we don't need to spill BX.
|
||||
* Retained gap: an aggregate >8B rhs (ident, tuple-lit, call)
|
||||
* truncates to one word here — task #31 A/E/G; struct-lit
|
||||
* diverts at the place_slit gate, array-lit dies loud (#32). */
|
||||
* is evaluated after the value so we don't need to spill BX. */
|
||||
if (n->lhs && n->lhs->kind == N_UN && n->lhs->op == TK_STAR
|
||||
&& n->op == TK_ASSIGN && !place_slit) {
|
||||
&& n->op == TK_ASSIGN && !place_slit && !deref_agg) {
|
||||
Type *pt = n->lhs->lhs ? n->lhs->lhs->type : NULL;
|
||||
Type *pu = (pt && pt->kind == TY_NAMED) ? pt->under : pt;
|
||||
Type *vt = (pu && pu->kind == TY_PTR) ? pu->sub : NULL;
|
||||
@@ -6259,37 +6339,61 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Struct local reassignment: `s = expr;` where s is
|
||||
* a TY_STRUCT local of size <=24B. Two rhs shapes,
|
||||
* mirroring cglet's N_STRUCTLIT and the call-result
|
||||
* branch above:
|
||||
* - N_STRUCTLIT: walk fields, store at off+foff
|
||||
* directly (same shape as the let-init branch).
|
||||
* - N_CALL: cgexpr → AX/DX/CX, sized stores per the
|
||||
* same ASYMMETRY rules documented at the N_LET
|
||||
* receive site (MOVQ for full 8B chunks plus
|
||||
* MOVL/MOVW/MOVB tail). The struct-IDENT word-copy
|
||||
* rhs shape (s = p) is left unwired; #5 is scoped to
|
||||
* the receive side of #4's cgreturn (calls + literals).
|
||||
* Sizes >24B and non-{0,1,2,4}-byte tails fall through
|
||||
* to the existing scalar path. */
|
||||
if (lu && (lu->kind == TY_STRUCT || lu->kind == TY_ARRAY)
|
||||
&& (int)lu->size <= 24) {
|
||||
/* #49: aggregate (struct/array/tuple) IDENT
|
||||
* reassignment — `s = expr;`. Literal and call rhs
|
||||
* keep their dedicated receive arms; every OTHER rhs
|
||||
* is an addressable source and funnels through the
|
||||
* ONE mem-to-mem copy (aggarg_srcaddr → SI, dst
|
||||
* address → BX, cg_aggcopy — the let-init copy's
|
||||
* assign-position twin). Pre-#49 any shape that
|
||||
* missed an arm fell to the scalar tail below and
|
||||
* word0-copied: `b = a` lost every byte past 8 (ken
|
||||
* f49_min; latent because lib style is let-init).
|
||||
* The block never falls through to the scalar tail
|
||||
* (rule 7). Keyed on the FULL alias chase (the #22
|
||||
* type_chase_named precedent), NOT the region's
|
||||
* single-peel `lu` — `type b = a; type a = struct`
|
||||
* left a TY_NAMED after one peel, missing the arm
|
||||
* (the wwstage twin full-chases the stamped tinfo;
|
||||
* a single peel here would silently diverge). */
|
||||
Type *au = type_chase_named(lt);
|
||||
if (au && (au->kind == TY_STRUCT || au->kind == TY_ARRAY
|
||||
|| au->kind == TY_TUPLE)) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
if (off != 0) {
|
||||
int sz = (int)lu->size;
|
||||
if (n->rhs && n->rhs->kind == N_STRUCTLIT) {
|
||||
int sz = (int)au->size;
|
||||
int isglob = off == 0 && let_islet(n->lhs->str);
|
||||
if (off == 0 && !isglob)
|
||||
fatal("unsupported assign target: "
|
||||
"unresolved identifier '%s'",
|
||||
n->lhs->str);
|
||||
if (n->rhs && n->rhs->kind == N_STRUCTLIT) {
|
||||
if (off != 0) {
|
||||
/* Delegate to the shared BP-relative
|
||||
* structlit fill helper. Handles
|
||||
* TK_ELLIPSIS autofill, tagged fields,
|
||||
* float/scalar stores, AND nested
|
||||
* struct-typed structlit values via
|
||||
* recursion (#17 silent-zero fix). */
|
||||
cg_structlit_fill_bp(c, &locals, lu,
|
||||
* recursion (#17 silent-zero fix).
|
||||
* #31-B: the pre-#49 ≤24B gate is
|
||||
* lifted — the fill walks fields at
|
||||
* any size; the wwstage twin never
|
||||
* gated, so a >24B literal reassign
|
||||
* was cs-zero/ww-filled (rule-10). */
|
||||
cg_structlit_fill_bp(c, &locals, au,
|
||||
n->rhs, off);
|
||||
break;
|
||||
}
|
||||
if (n->rhs && n->rhs->kind == N_CALL
|
||||
/* Global structlit reassign rides the
|
||||
* DST_GLOBAL fill (the N_DOT global arms'
|
||||
* machinery); pre-#49 it fell to the
|
||||
* scalar tail and zeroed word0 only. */
|
||||
cg_structlit_fill(c, &locals, au, n->rhs,
|
||||
DST_GLOBAL, 0, n->lhs->str, 0);
|
||||
break;
|
||||
}
|
||||
if (n->rhs && n->rhs->kind == N_CALL) {
|
||||
if (off != 0 && au->kind != TY_TUPLE
|
||||
&& sz <= 24
|
||||
&& (sz % 8 == 0 || sz % 8 == 1
|
||||
|| sz % 8 == 2
|
||||
|| sz % 8 == 4)) {
|
||||
@@ -6309,10 +6413,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (lu->kind == TY_ARRAY
|
||||
&& let_islet(n->lhs->str)
|
||||
&& n->rhs && n->rhs->kind == N_CALL
|
||||
&& n->op == TK_ASSIGN) {
|
||||
if (isglob && au->kind == TY_ARRAY
|
||||
&& sz <= 24) {
|
||||
/* #272: `g = f();` where g is a GLOBAL
|
||||
* aggregate ≤24B. The callee leaves the result
|
||||
* in AX/DX/CX (#272 reg-return); the scalar IDENT
|
||||
@@ -6323,28 +6425,45 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* global arm above and the #220 sret-to-symbol path.
|
||||
* #276: this arm is TY_ARRAY-only — a ≤24B STRUCT
|
||||
* global receive can be float-class (X0/X1, not
|
||||
* AX/DX/CX) so it stays at its pre-existing symmetric
|
||||
* fall-through; closing it needs struct_float_class
|
||||
* here. No consumer. Arrays are never float-class, so
|
||||
* AX/DX/CX is always correct for this arm. */
|
||||
int sz = (int)lu->size;
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_DI));
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = sz / 8;
|
||||
int tail = sz % 8;
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(D_DI, i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW
|
||||
: A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(D_DI, full * 8));
|
||||
* AX/DX/CX) so it has no receive here; pre-#49
|
||||
* it fell through symmetric-silent, now it dies
|
||||
* loud below. No consumer. Arrays are never
|
||||
* float-class, so AX/DX/CX is always correct
|
||||
* for this arm. */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_DI));
|
||||
int regs[3] = { D_AX, D_DX, D_CX };
|
||||
int full = sz / 8;
|
||||
int tail = sz % 8;
|
||||
for (int i = 0; i < full; i++)
|
||||
ins2(c, A_MOVQ, areg(regs[i]),
|
||||
amem(D_DI, i * 8));
|
||||
if (tail > 0) {
|
||||
int op = (tail == 4) ? A_MOVL
|
||||
: (tail == 2) ? A_MOVW
|
||||
: A_MOVB;
|
||||
ins2(c, op, areg(regs[full]),
|
||||
amem(D_DI, full * 8));
|
||||
}
|
||||
break;
|
||||
}
|
||||
fatal("assign: aggregate call receive "
|
||||
"shape unwired (task #49/#276/"
|
||||
"rule-7)");
|
||||
}
|
||||
if (aggarg_srcaddr(c, n->rhs, D_SI, locals)) {
|
||||
if (off != 0)
|
||||
ins2(c, A_LEAQ, amem(D_BP, off),
|
||||
areg(D_BX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, n->lhs->str),
|
||||
areg(D_BX));
|
||||
cg_aggcopy(c, sz);
|
||||
break;
|
||||
}
|
||||
fatal("assign: aggregate rhs shape unwired "
|
||||
"(task #49/rule-7)");
|
||||
}
|
||||
}
|
||||
/* F6 (cgplaceaddr, commit C1): an N_DOT lvalue none of the
|
||||
@@ -6449,34 +6568,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (!placed)
|
||||
fatal("unsupported assign target "
|
||||
"shape");
|
||||
int k = 0;
|
||||
for (; k + 8 <= fsz; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
}
|
||||
if (k + 4 <= fsz) {
|
||||
ins2(c, A_MOVL, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVL, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 4;
|
||||
}
|
||||
if (k + 2 <= fsz) {
|
||||
ins2(c, A_MOVW, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVW, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 2;
|
||||
}
|
||||
if (k + 1 <= fsz) {
|
||||
ins2(c, A_MOVB, amem(D_SI, k),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVB, areg(D_AX),
|
||||
amem(D_BX, k));
|
||||
k += 1;
|
||||
}
|
||||
cg_aggcopy(c, fsz);
|
||||
break;
|
||||
}
|
||||
if (fu && (fu->kind == TY_STR
|
||||
|
||||
@@ -18578,6 +18578,20 @@ fn istaggedtype(c: *cgen, t: *node) bool = {
|
||||
return typeistagged(t.type_: *tinfo);
|
||||
};
|
||||
|
||||
// tnodeisagg — struct/array/tuple kind off the checker-STAMPED tinfo
|
||||
// (the #49 funnel predicate; #209/#211 discipline — never tnode
|
||||
// names). Mirror of cstage's chase-then-kind test at the fill /
|
||||
// assign aggregate arms.
|
||||
fn tnodeisagg(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let u: *tinfo = t.type_: *tinfo;
|
||||
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
|
||||
if (u == nil) { return false; };
|
||||
if (u.kind == tykind.TY_STRUCT) { return true; };
|
||||
if (u.kind == tykind.TY_ARRAY) { return true; };
|
||||
return u.kind == tykind.TY_TUPLE;
|
||||
};
|
||||
|
||||
// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage
|
||||
// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across
|
||||
// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-
|
||||
@@ -20506,6 +20520,59 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
|
||||
};
|
||||
};
|
||||
fi = nil;
|
||||
} else if (tnodeisagg(fi.tnode)) {
|
||||
// #49 (f38b/x5f-h): an aggregate (struct/array/
|
||||
// tuple) field from an ADDRESSABLE source expr —
|
||||
// `outer{.., r = r}` — fell to the scalar tail
|
||||
// below and stored word0 only. Funnel: source
|
||||
// address via aggargsrcaddr (SI), field address
|
||||
// via LEAQ/ADDQ (BX — loaded AFTER the source
|
||||
// walk, which clobbers BX/AX), then aggcopy.
|
||||
// Width = the checker-STAMPED tinfo size (the
|
||||
// cstage fl->type->size SSoT; fi.fsz is the
|
||||
// slot-padded extent and skews on maxalign<8).
|
||||
// Non-addressable aggregate sources (tuple-lit,
|
||||
// >24B/odd-tail call) die loud — pre-#49 the
|
||||
// same silent word0 (rule 7). Mirror of cstage
|
||||
// cg_structlit_fill #49 arm.
|
||||
if (!aggargsrcaddr(c, fieldnode.lhs, "SI")) {
|
||||
let m49g: str = "structlit fill: aggregate field '";
|
||||
os.write(2, m49g.ptr, m49g.len: u64);
|
||||
os.write(2, fname.ptr, fname.len: u64);
|
||||
let m49h: str = "' from a non-addressable source unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49h.ptr, m49h.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (mode == 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff((disp + fi.foff): i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (disp + fi.foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint((disp + fi.foff): i64);
|
||||
emitline(", BX\n");
|
||||
};
|
||||
};
|
||||
let agsz49: i32 = 0;
|
||||
if (fi.tnode != nil) {
|
||||
let agti49: *tinfo = fi.tnode.type_: *tinfo;
|
||||
for (agti49 != nil && agti49.kind == tykind.TY_NAMED) {
|
||||
agti49 = agti49.under;
|
||||
};
|
||||
if (agti49 != nil) { agsz49 = agti49.size: i32; };
|
||||
};
|
||||
aggcopy(c, agsz49);
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered
|
||||
@@ -21952,6 +22019,53 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// aggcopy — the ONE place-resolved mem-to-mem aggregate copy: sz
|
||||
// bytes (SI) → (BX) via AX, a MOVQ run plus a 4/2/1 sized tail.
|
||||
// Extracted verbatim from the C1.25 assign-resolver tail so every
|
||||
// aggregate copy position (resolver field store, #49 ident reassign,
|
||||
// #49 structlit fill-field) funnels through one loop — close-by-
|
||||
// construction, no per-site width logic to skew. Mirror of cstage
|
||||
// cg_aggcopy.
|
||||
fn aggcopy(c: *cgen, sz: i32) void = {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 8;
|
||||
};
|
||||
if (k + 4 <= sz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 4;
|
||||
};
|
||||
if (k + 2 <= sz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 2;
|
||||
};
|
||||
if (k + 1 <= sz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
// expression into dstreg; returns true when the shape is wired, false
|
||||
// otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
@@ -27588,6 +27702,29 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #49 (#31-A fold): an aggregate pointee diverts the whole
|
||||
// deref-assign to the resolver aggregate arm below — the scalar
|
||||
// tail there stored ONE word of `*p = s` (#31-A); tuple-lit
|
||||
// (#31-E) and call (#31-G) rhs now die loud there instead of
|
||||
// silently truncating. str/slice pointees keep their 3-word arm
|
||||
// (byte-id-pinned). Mirror of cstage deref_agg.
|
||||
let derefagg: bool = false;
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR
|
||||
&& n.op == tkind.TK_ASSIGN) {
|
||||
let du: *tinfo = lhs.type_: *tinfo;
|
||||
for (du != nil && du.kind == tykind.TY_NAMED) {
|
||||
du = du.under;
|
||||
};
|
||||
if (du != nil) {
|
||||
if (du.kind == tykind.TY_STRUCT
|
||||
|| du.kind == tykind.TY_ARRAY
|
||||
|| du.kind == tykind.TY_TUPLE) {
|
||||
derefagg = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
||||
// write nothing. Detected by lhs being an nkind.N_IDENT with empty str
|
||||
// (planted by parseprimary on the tkind.TK_UNDER token).
|
||||
@@ -27707,7 +27844,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_UN) {
|
||||
if (lhs.op == tkind.TK_STAR) {
|
||||
if (n.op == tkind.TK_ASSIGN && !placeslit) {
|
||||
if (n.op == tkind.TK_ASSIGN && !placeslit
|
||||
&& !derefagg) {
|
||||
let inner: *node = lhs.lhs;
|
||||
let elemstr: bool = false;
|
||||
let elemfloat: bool = false;
|
||||
@@ -30439,6 +30577,56 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// #49 (global twin): aggregate module-let reassign —
|
||||
// `g = a` / `g = pt{...}`. Same funnel as the local
|
||||
// arm: structlit → mode-2 (DST_GLOBAL) fill; call →
|
||||
// loud (#276: ≤24B struct global receive was a
|
||||
// documented symmetric fall-through, now loud);
|
||||
// addressable rhs → aggargsrcaddr + LEAQ g(SB), BX
|
||||
// + aggcopy. Pre-#49 every shape fell to the scalar
|
||||
// tail below: one MOVQ AX, g(SB).
|
||||
if (n.op == tkind.TK_ASSIGN && lvftn != nil) {
|
||||
let gau: *tinfo = lvftn.type_: *tinfo;
|
||||
for (gau != nil && gau.kind == tykind.TY_NAMED) {
|
||||
gau = gau.under;
|
||||
};
|
||||
if (gau != nil) {
|
||||
if (gau.kind == tykind.TY_STRUCT
|
||||
|| gau.kind == tykind.TY_ARRAY
|
||||
|| gau.kind == tykind.TY_TUPLE) {
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
let gsi49: *structinfo = nil;
|
||||
if (lvftn.kind == nkind.N_TNAME) {
|
||||
gsi49 = structlookup(c, lvftn.str);
|
||||
};
|
||||
if (gsi49 == nil) {
|
||||
// wwstage-only bail (anonymous
|
||||
// type; the @placescr precedent).
|
||||
let m49d: str = "assign: structlit layout unresolved (rule-7)\n";
|
||||
os.write(2, m49d.ptr, m49d.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgstructlitfill(c, gsi49, n.rhs, 2, 0, nm, 0);
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_CALL) {
|
||||
let m49e: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n";
|
||||
os.write(2, m49e.ptr, m49e.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (aggargsrcaddr(c, n.rhs, "SI")) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
aggcopy(c, gau.size: i32);
|
||||
return;
|
||||
};
|
||||
let m49f: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49f.ptr, m49f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
// str/slice top-level let: str IS []u8, so both store the
|
||||
@@ -30817,6 +31005,60 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
// #49: aggregate (struct/array/tuple) IDENT
|
||||
// reassignment — any rhs shape that missed the
|
||||
// dedicated arms above (struct-lit fill, call
|
||||
// receive, sret) funnels through the ONE mem-to-mem
|
||||
// copy (aggargsrcaddr → SI, dst → BX, aggcopy), or
|
||||
// dies loud. Pre-#49 it fell to the scalar tail
|
||||
// below and word0-copied `b = a` (cstage cgen.c
|
||||
// N_ASSIGN aggregate-ident twin). Kind keys off the
|
||||
// checker-STAMPED tinfo (#209/#211 discipline).
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
let agu: *tinfo = nil;
|
||||
if (lcn != nil) {
|
||||
if (lcn.tnode != nil) {
|
||||
agu = lcn.tnode.type_: *tinfo;
|
||||
};
|
||||
};
|
||||
for (agu != nil && agu.kind == tykind.TY_NAMED) {
|
||||
agu = agu.under;
|
||||
};
|
||||
if (agu != nil) {
|
||||
if (agu.kind == tykind.TY_STRUCT
|
||||
|| agu.kind == tykind.TY_ARRAY
|
||||
|| agu.kind == tykind.TY_TUPLE) {
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
// wwstage-only bail: the fill is
|
||||
// structinfo-keyed; a literal
|
||||
// reaching past the name-keyed arm
|
||||
// above has no registry entry
|
||||
// (anonymous struct type). Loud,
|
||||
// rule 7 (the resolver @placescr
|
||||
// precedent); cstage fills from
|
||||
// Type directly.
|
||||
let m49a: str = "assign: structlit layout unresolved (rule-7)\n";
|
||||
os.write(2, m49a.ptr, m49a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_CALL) {
|
||||
let m49b: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n";
|
||||
os.write(2, m49b.ptr, m49b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (aggargsrcaddr(c, n.rhs, "SI")) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
aggcopy(c, agu.size: i32);
|
||||
return;
|
||||
};
|
||||
let m49c: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49c.ptr, m49c.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -31067,43 +31309,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
os.write(2, mau.ptr, mau.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let kc: i32 = 0;
|
||||
for (kc + 8 <= fsz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 8;
|
||||
};
|
||||
if (kc + 4 <= fsz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 4;
|
||||
};
|
||||
if (kc + 2 <= fsz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 2;
|
||||
};
|
||||
if (kc + 1 <= fsz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 1;
|
||||
};
|
||||
aggcopy(c, fsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1374,6 +1374,53 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// aggcopy — the ONE place-resolved mem-to-mem aggregate copy: sz
|
||||
// bytes (SI) → (BX) via AX, a MOVQ run plus a 4/2/1 sized tail.
|
||||
// Extracted verbatim from the C1.25 assign-resolver tail so every
|
||||
// aggregate copy position (resolver field store, #49 ident reassign,
|
||||
// #49 structlit fill-field) funnels through one loop — close-by-
|
||||
// construction, no per-site width logic to skew. Mirror of cstage
|
||||
// cg_aggcopy.
|
||||
fn aggcopy(c: *cgen, sz: i32) void = {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 8;
|
||||
};
|
||||
if (k + 4 <= sz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 4;
|
||||
};
|
||||
if (k + 2 <= sz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 2;
|
||||
};
|
||||
if (k + 1 <= sz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
// expression into dstreg; returns true when the shape is wired, false
|
||||
// otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
@@ -7010,6 +7057,29 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #49 (#31-A fold): an aggregate pointee diverts the whole
|
||||
// deref-assign to the resolver aggregate arm below — the scalar
|
||||
// tail there stored ONE word of `*p = s` (#31-A); tuple-lit
|
||||
// (#31-E) and call (#31-G) rhs now die loud there instead of
|
||||
// silently truncating. str/slice pointees keep their 3-word arm
|
||||
// (byte-id-pinned). Mirror of cstage deref_agg.
|
||||
let derefagg: bool = false;
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR
|
||||
&& n.op == tkind.TK_ASSIGN) {
|
||||
let du: *tinfo = lhs.type_: *tinfo;
|
||||
for (du != nil && du.kind == tykind.TY_NAMED) {
|
||||
du = du.under;
|
||||
};
|
||||
if (du != nil) {
|
||||
if (du.kind == tykind.TY_STRUCT
|
||||
|| du.kind == tykind.TY_ARRAY
|
||||
|| du.kind == tykind.TY_TUPLE) {
|
||||
derefagg = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
||||
// write nothing. Detected by lhs being an nkind.N_IDENT with empty str
|
||||
// (planted by parseprimary on the tkind.TK_UNDER token).
|
||||
@@ -7129,7 +7199,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_UN) {
|
||||
if (lhs.op == tkind.TK_STAR) {
|
||||
if (n.op == tkind.TK_ASSIGN && !placeslit) {
|
||||
if (n.op == tkind.TK_ASSIGN && !placeslit
|
||||
&& !derefagg) {
|
||||
let inner: *node = lhs.lhs;
|
||||
let elemstr: bool = false;
|
||||
let elemfloat: bool = false;
|
||||
@@ -9861,6 +9932,56 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// #49 (global twin): aggregate module-let reassign —
|
||||
// `g = a` / `g = pt{...}`. Same funnel as the local
|
||||
// arm: structlit → mode-2 (DST_GLOBAL) fill; call →
|
||||
// loud (#276: ≤24B struct global receive was a
|
||||
// documented symmetric fall-through, now loud);
|
||||
// addressable rhs → aggargsrcaddr + LEAQ g(SB), BX
|
||||
// + aggcopy. Pre-#49 every shape fell to the scalar
|
||||
// tail below: one MOVQ AX, g(SB).
|
||||
if (n.op == tkind.TK_ASSIGN && lvftn != nil) {
|
||||
let gau: *tinfo = lvftn.type_: *tinfo;
|
||||
for (gau != nil && gau.kind == tykind.TY_NAMED) {
|
||||
gau = gau.under;
|
||||
};
|
||||
if (gau != nil) {
|
||||
if (gau.kind == tykind.TY_STRUCT
|
||||
|| gau.kind == tykind.TY_ARRAY
|
||||
|| gau.kind == tykind.TY_TUPLE) {
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
let gsi49: *structinfo = nil;
|
||||
if (lvftn.kind == nkind.N_TNAME) {
|
||||
gsi49 = structlookup(c, lvftn.str);
|
||||
};
|
||||
if (gsi49 == nil) {
|
||||
// wwstage-only bail (anonymous
|
||||
// type; the @placescr precedent).
|
||||
let m49d: str = "assign: structlit layout unresolved (rule-7)\n";
|
||||
os.write(2, m49d.ptr, m49d.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgstructlitfill(c, gsi49, n.rhs, 2, 0, nm, 0);
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_CALL) {
|
||||
let m49e: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n";
|
||||
os.write(2, m49e.ptr, m49e.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (aggargsrcaddr(c, n.rhs, "SI")) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
aggcopy(c, gau.size: i32);
|
||||
return;
|
||||
};
|
||||
let m49f: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49f.ptr, m49f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
// str/slice top-level let: str IS []u8, so both store the
|
||||
@@ -10239,6 +10360,60 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
// #49: aggregate (struct/array/tuple) IDENT
|
||||
// reassignment — any rhs shape that missed the
|
||||
// dedicated arms above (struct-lit fill, call
|
||||
// receive, sret) funnels through the ONE mem-to-mem
|
||||
// copy (aggargsrcaddr → SI, dst → BX, aggcopy), or
|
||||
// dies loud. Pre-#49 it fell to the scalar tail
|
||||
// below and word0-copied `b = a` (cstage cgen.c
|
||||
// N_ASSIGN aggregate-ident twin). Kind keys off the
|
||||
// checker-STAMPED tinfo (#209/#211 discipline).
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
let agu: *tinfo = nil;
|
||||
if (lcn != nil) {
|
||||
if (lcn.tnode != nil) {
|
||||
agu = lcn.tnode.type_: *tinfo;
|
||||
};
|
||||
};
|
||||
for (agu != nil && agu.kind == tykind.TY_NAMED) {
|
||||
agu = agu.under;
|
||||
};
|
||||
if (agu != nil) {
|
||||
if (agu.kind == tykind.TY_STRUCT
|
||||
|| agu.kind == tykind.TY_ARRAY
|
||||
|| agu.kind == tykind.TY_TUPLE) {
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
// wwstage-only bail: the fill is
|
||||
// structinfo-keyed; a literal
|
||||
// reaching past the name-keyed arm
|
||||
// above has no registry entry
|
||||
// (anonymous struct type). Loud,
|
||||
// rule 7 (the resolver @placescr
|
||||
// precedent); cstage fills from
|
||||
// Type directly.
|
||||
let m49a: str = "assign: structlit layout unresolved (rule-7)\n";
|
||||
os.write(2, m49a.ptr, m49a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_CALL) {
|
||||
let m49b: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n";
|
||||
os.write(2, m49b.ptr, m49b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (aggargsrcaddr(c, n.rhs, "SI")) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
aggcopy(c, agu.size: i32);
|
||||
return;
|
||||
};
|
||||
let m49c: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49c.ptr, m49c.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -10489,43 +10664,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
os.write(2, mau.ptr, mau.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let kc: i32 = 0;
|
||||
for (kc + 8 <= fsz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 8;
|
||||
};
|
||||
if (kc + 4 <= fsz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 4;
|
||||
};
|
||||
if (kc + 2 <= fsz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 2;
|
||||
};
|
||||
if (kc + 1 <= fsz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 1;
|
||||
};
|
||||
aggcopy(c, fsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -2568,6 +2568,20 @@ fn istaggedtype(c: *cgen, t: *node) bool = {
|
||||
return typeistagged(t.type_: *tinfo);
|
||||
};
|
||||
|
||||
// tnodeisagg — struct/array/tuple kind off the checker-STAMPED tinfo
|
||||
// (the #49 funnel predicate; #209/#211 discipline — never tnode
|
||||
// names). Mirror of cstage's chase-then-kind test at the fill /
|
||||
// assign aggregate arms.
|
||||
fn tnodeisagg(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let u: *tinfo = t.type_: *tinfo;
|
||||
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
|
||||
if (u == nil) { return false; };
|
||||
if (u.kind == tykind.TY_STRUCT) { return true; };
|
||||
if (u.kind == tykind.TY_ARRAY) { return true; };
|
||||
return u.kind == tykind.TY_TUPLE;
|
||||
};
|
||||
|
||||
// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage
|
||||
// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across
|
||||
// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-
|
||||
@@ -4496,6 +4510,59 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
|
||||
};
|
||||
};
|
||||
fi = nil;
|
||||
} else if (tnodeisagg(fi.tnode)) {
|
||||
// #49 (f38b/x5f-h): an aggregate (struct/array/
|
||||
// tuple) field from an ADDRESSABLE source expr —
|
||||
// `outer{.., r = r}` — fell to the scalar tail
|
||||
// below and stored word0 only. Funnel: source
|
||||
// address via aggargsrcaddr (SI), field address
|
||||
// via LEAQ/ADDQ (BX — loaded AFTER the source
|
||||
// walk, which clobbers BX/AX), then aggcopy.
|
||||
// Width = the checker-STAMPED tinfo size (the
|
||||
// cstage fl->type->size SSoT; fi.fsz is the
|
||||
// slot-padded extent and skews on maxalign<8).
|
||||
// Non-addressable aggregate sources (tuple-lit,
|
||||
// >24B/odd-tail call) die loud — pre-#49 the
|
||||
// same silent word0 (rule 7). Mirror of cstage
|
||||
// cg_structlit_fill #49 arm.
|
||||
if (!aggargsrcaddr(c, fieldnode.lhs, "SI")) {
|
||||
let m49g: str = "structlit fill: aggregate field '";
|
||||
os.write(2, m49g.ptr, m49g.len: u64);
|
||||
os.write(2, fname.ptr, fname.len: u64);
|
||||
let m49h: str = "' from a non-addressable source unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49h.ptr, m49h.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (mode == 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff((disp + fi.foff): i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (disp + fi.foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint((disp + fi.foff): i64);
|
||||
emitline(", BX\n");
|
||||
};
|
||||
};
|
||||
let agsz49: i32 = 0;
|
||||
if (fi.tnode != nil) {
|
||||
let agti49: *tinfo = fi.tnode.type_: *tinfo;
|
||||
for (agti49 != nil && agti49.kind == tykind.TY_NAMED) {
|
||||
agti49 = agti49.under;
|
||||
};
|
||||
if (agti49 != nil) { agsz49 = agti49.size: i32; };
|
||||
};
|
||||
aggcopy(c, agsz49);
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered
|
||||
|
||||
@@ -18578,6 +18578,20 @@ fn istaggedtype(c: *cgen, t: *node) bool = {
|
||||
return typeistagged(t.type_: *tinfo);
|
||||
};
|
||||
|
||||
// tnodeisagg — struct/array/tuple kind off the checker-STAMPED tinfo
|
||||
// (the #49 funnel predicate; #209/#211 discipline — never tnode
|
||||
// names). Mirror of cstage's chase-then-kind test at the fill /
|
||||
// assign aggregate arms.
|
||||
fn tnodeisagg(t: *node) bool = {
|
||||
if (t == nil) { return false; };
|
||||
let u: *tinfo = t.type_: *tinfo;
|
||||
for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; };
|
||||
if (u == nil) { return false; };
|
||||
if (u.kind == tykind.TY_STRUCT) { return true; };
|
||||
if (u.kind == tykind.TY_ARRAY) { return true; };
|
||||
return u.kind == tykind.TY_TUPLE;
|
||||
};
|
||||
|
||||
// isfloattype — f32 / f64 / untyped_float (alias-aware). Cite cstage
|
||||
// cgen.c:117 `cg_isfloat`. Dispatches MOVSS/MOVSD-shaped paths across
|
||||
// cglet, cgident, cgassign, cgbin, cgcast, cgcall, cgreturn, fn-
|
||||
@@ -20506,6 +20520,59 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
|
||||
};
|
||||
};
|
||||
fi = nil;
|
||||
} else if (tnodeisagg(fi.tnode)) {
|
||||
// #49 (f38b/x5f-h): an aggregate (struct/array/
|
||||
// tuple) field from an ADDRESSABLE source expr —
|
||||
// `outer{.., r = r}` — fell to the scalar tail
|
||||
// below and stored word0 only. Funnel: source
|
||||
// address via aggargsrcaddr (SI), field address
|
||||
// via LEAQ/ADDQ (BX — loaded AFTER the source
|
||||
// walk, which clobbers BX/AX), then aggcopy.
|
||||
// Width = the checker-STAMPED tinfo size (the
|
||||
// cstage fl->type->size SSoT; fi.fsz is the
|
||||
// slot-padded extent and skews on maxalign<8).
|
||||
// Non-addressable aggregate sources (tuple-lit,
|
||||
// >24B/odd-tail call) die loud — pre-#49 the
|
||||
// same silent word0 (rule 7). Mirror of cstage
|
||||
// cg_structlit_fill #49 arm.
|
||||
if (!aggargsrcaddr(c, fieldnode.lhs, "SI")) {
|
||||
let m49g: str = "structlit fill: aggregate field '";
|
||||
os.write(2, m49g.ptr, m49g.len: u64);
|
||||
os.write(2, fname.ptr, fname.len: u64);
|
||||
let m49h: str = "' from a non-addressable source unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49h.ptr, m49h.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (mode == 0) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff((disp + fi.foff): i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), BX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), BX\n");
|
||||
};
|
||||
if (disp + fi.foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint((disp + fi.foff): i64);
|
||||
emitline(", BX\n");
|
||||
};
|
||||
};
|
||||
let agsz49: i32 = 0;
|
||||
if (fi.tnode != nil) {
|
||||
let agti49: *tinfo = fi.tnode.type_: *tinfo;
|
||||
for (agti49 != nil && agti49.kind == tykind.TY_NAMED) {
|
||||
agti49 = agti49.under;
|
||||
};
|
||||
if (agti49 != nil) { agsz49 = agti49.size: i32; };
|
||||
};
|
||||
aggcopy(c, agsz49);
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered
|
||||
@@ -21952,6 +22019,53 @@ fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = {
|
||||
return false;
|
||||
};
|
||||
|
||||
// aggcopy — the ONE place-resolved mem-to-mem aggregate copy: sz
|
||||
// bytes (SI) → (BX) via AX, a MOVQ run plus a 4/2/1 sized tail.
|
||||
// Extracted verbatim from the C1.25 assign-resolver tail so every
|
||||
// aggregate copy position (resolver field store, #49 ident reassign,
|
||||
// #49 structlit fill-field) funnels through one loop — close-by-
|
||||
// construction, no per-site width logic to skew. Mirror of cstage
|
||||
// cg_aggcopy.
|
||||
fn aggcopy(c: *cgen, sz: i32) void = {
|
||||
let k: i32 = 0;
|
||||
for (k + 8 <= sz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 8;
|
||||
};
|
||||
if (k + 4 <= sz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 4;
|
||||
};
|
||||
if (k + 2 <= sz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 2;
|
||||
};
|
||||
if (k + 1 <= sz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(k: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff(k: i64);
|
||||
emitline("(BX)\n");
|
||||
k += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// cgplaceaddr — compute the ADDRESS of an arbitrary place (lvalue)
|
||||
// expression into dstreg; returns true when the shape is wired, false
|
||||
// otherwise (the caller loud-stops — rule 7, never a silent drop).
|
||||
@@ -27588,6 +27702,29 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #49 (#31-A fold): an aggregate pointee diverts the whole
|
||||
// deref-assign to the resolver aggregate arm below — the scalar
|
||||
// tail there stored ONE word of `*p = s` (#31-A); tuple-lit
|
||||
// (#31-E) and call (#31-G) rhs now die loud there instead of
|
||||
// silently truncating. str/slice pointees keep their 3-word arm
|
||||
// (byte-id-pinned). Mirror of cstage deref_agg.
|
||||
let derefagg: bool = false;
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_UN && lhs.op == tkind.TK_STAR
|
||||
&& n.op == tkind.TK_ASSIGN) {
|
||||
let du: *tinfo = lhs.type_: *tinfo;
|
||||
for (du != nil && du.kind == tykind.TY_NAMED) {
|
||||
du = du.under;
|
||||
};
|
||||
if (du != nil) {
|
||||
if (du.kind == tykind.TY_STRUCT
|
||||
|| du.kind == tykind.TY_ARRAY
|
||||
|| du.kind == tykind.TY_TUPLE) {
|
||||
derefagg = true;
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
// Discard lvalue `_ = expr;` — evaluate rhs for side effects,
|
||||
// write nothing. Detected by lhs being an nkind.N_IDENT with empty str
|
||||
// (planted by parseprimary on the tkind.TK_UNDER token).
|
||||
@@ -27707,7 +27844,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_UN) {
|
||||
if (lhs.op == tkind.TK_STAR) {
|
||||
if (n.op == tkind.TK_ASSIGN && !placeslit) {
|
||||
if (n.op == tkind.TK_ASSIGN && !placeslit
|
||||
&& !derefagg) {
|
||||
let inner: *node = lhs.lhs;
|
||||
let elemstr: bool = false;
|
||||
let elemfloat: bool = false;
|
||||
@@ -30439,6 +30577,56 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
};
|
||||
// #49 (global twin): aggregate module-let reassign —
|
||||
// `g = a` / `g = pt{...}`. Same funnel as the local
|
||||
// arm: structlit → mode-2 (DST_GLOBAL) fill; call →
|
||||
// loud (#276: ≤24B struct global receive was a
|
||||
// documented symmetric fall-through, now loud);
|
||||
// addressable rhs → aggargsrcaddr + LEAQ g(SB), BX
|
||||
// + aggcopy. Pre-#49 every shape fell to the scalar
|
||||
// tail below: one MOVQ AX, g(SB).
|
||||
if (n.op == tkind.TK_ASSIGN && lvftn != nil) {
|
||||
let gau: *tinfo = lvftn.type_: *tinfo;
|
||||
for (gau != nil && gau.kind == tykind.TY_NAMED) {
|
||||
gau = gau.under;
|
||||
};
|
||||
if (gau != nil) {
|
||||
if (gau.kind == tykind.TY_STRUCT
|
||||
|| gau.kind == tykind.TY_ARRAY
|
||||
|| gau.kind == tykind.TY_TUPLE) {
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
let gsi49: *structinfo = nil;
|
||||
if (lvftn.kind == nkind.N_TNAME) {
|
||||
gsi49 = structlookup(c, lvftn.str);
|
||||
};
|
||||
if (gsi49 == nil) {
|
||||
// wwstage-only bail (anonymous
|
||||
// type; the @placescr precedent).
|
||||
let m49d: str = "assign: structlit layout unresolved (rule-7)\n";
|
||||
os.write(2, m49d.ptr, m49d.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
cgstructlitfill(c, gsi49, n.rhs, 2, 0, nm, 0);
|
||||
return;
|
||||
};
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_CALL) {
|
||||
let m49e: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n";
|
||||
os.write(2, m49e.ptr, m49e.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (aggargsrcaddr(c, n.rhs, "SI")) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, nm);
|
||||
emitline("(SB), BX\n");
|
||||
aggcopy(c, gau.size: i32);
|
||||
return;
|
||||
};
|
||||
let m49f: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49f.ptr, m49f.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
// str/slice top-level let: str IS []u8, so both store the
|
||||
@@ -30817,6 +31005,60 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitline("(BP)\n");
|
||||
return;
|
||||
};
|
||||
// #49: aggregate (struct/array/tuple) IDENT
|
||||
// reassignment — any rhs shape that missed the
|
||||
// dedicated arms above (struct-lit fill, call
|
||||
// receive, sret) funnels through the ONE mem-to-mem
|
||||
// copy (aggargsrcaddr → SI, dst → BX, aggcopy), or
|
||||
// dies loud. Pre-#49 it fell to the scalar tail
|
||||
// below and word0-copied `b = a` (cstage cgen.c
|
||||
// N_ASSIGN aggregate-ident twin). Kind keys off the
|
||||
// checker-STAMPED tinfo (#209/#211 discipline).
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
let agu: *tinfo = nil;
|
||||
if (lcn != nil) {
|
||||
if (lcn.tnode != nil) {
|
||||
agu = lcn.tnode.type_: *tinfo;
|
||||
};
|
||||
};
|
||||
for (agu != nil && agu.kind == tykind.TY_NAMED) {
|
||||
agu = agu.under;
|
||||
};
|
||||
if (agu != nil) {
|
||||
if (agu.kind == tykind.TY_STRUCT
|
||||
|| agu.kind == tykind.TY_ARRAY
|
||||
|| agu.kind == tykind.TY_TUPLE) {
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_STRUCTLIT) {
|
||||
// wwstage-only bail: the fill is
|
||||
// structinfo-keyed; a literal
|
||||
// reaching past the name-keyed arm
|
||||
// above has no registry entry
|
||||
// (anonymous struct type). Loud,
|
||||
// rule 7 (the resolver @placescr
|
||||
// precedent); cstage fills from
|
||||
// Type directly.
|
||||
let m49a: str = "assign: structlit layout unresolved (rule-7)\n";
|
||||
os.write(2, m49a.ptr, m49a.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (n.rhs != nil && n.rhs.kind == nkind.N_CALL) {
|
||||
let m49b: str = "assign: aggregate call receive shape unwired (task #49/#276/rule-7)\n";
|
||||
os.write(2, m49b.ptr, m49b.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (aggargsrcaddr(c, n.rhs, "SI")) {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
aggcopy(c, agu.size: i32);
|
||||
return;
|
||||
};
|
||||
let m49c: str = "assign: aggregate rhs shape unwired (task #49/rule-7)\n";
|
||||
os.write(2, m49c.ptr, m49c.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
};
|
||||
};
|
||||
cgexpr(c, n.rhs);
|
||||
if (n.op == tkind.TK_ASSIGN) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -31067,43 +31309,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
os.write(2, mau.ptr, mau.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let kc: i32 = 0;
|
||||
for (kc + 8 <= fsz) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 8;
|
||||
};
|
||||
if (kc + 4 <= fsz) {
|
||||
emitline("\tMOVL\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVL\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 4;
|
||||
};
|
||||
if (kc + 2 <= fsz) {
|
||||
emitline("\tMOVW\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVW\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 2;
|
||||
};
|
||||
if (kc + 1 <= fsz) {
|
||||
emitline("\tMOVB\t");
|
||||
emitoff(kc: i64);
|
||||
emitline("(SI), AX\n");
|
||||
emitline("\tMOVB\tAX, ");
|
||||
emitoff(kc: i64);
|
||||
emitline("(BX)\n");
|
||||
kc += 1;
|
||||
};
|
||||
aggcopy(c, fsz);
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
||||
498
test/wcc/812_agg_assign_width.c
Normal file
498
test/wcc/812_agg_assign_width.c
Normal file
@@ -0,0 +1,498 @@
|
||||
/*
|
||||
* 812_agg_assign_width — cstage and wwstage agree, byte-for-byte and
|
||||
* at runtime, that an aggregate ASSIGN copies the WHOLE value (task
|
||||
* #49, Family A of ken's silent-set triage, /tmp/ken_silent_triage.md).
|
||||
*
|
||||
* The bug: plain whole-aggregate reassignment `b = a` fell to the
|
||||
* N_ASSIGN scalar tail in BOTH stages and copied ONE MOVQ — word 0 of
|
||||
* a 16/24/48B struct (ken f49_min cs.s asm proof; fA_16b shows even a
|
||||
* 16B {size,size} fails). Same class at three more positions: a
|
||||
* struct-lit FIELD initialised from an ident source (`outer{.., r=r}`,
|
||||
* ken f38b / x5f-h) word0-copied inside cg_structlit_fill; a deref
|
||||
* place `*p = s` truncated to 8 bytes (#31-A); a module-let global
|
||||
* `g = a` / `g = pt{...}` stored one word (scalar `MOVQ AX, g(SB)`).
|
||||
* Tagged/match context was INCIDENTAL — the hole is tagged-independent
|
||||
* and gate-blind (byte-identical wrongness), latent only because lib
|
||||
* style is let-init (`let b = a` runs the #265/#268 full-width copy —
|
||||
* the working sibling these fixes mirror).
|
||||
*
|
||||
* The fix (BOTH stages, converged byte-identical): ONE place-resolved
|
||||
* mem-to-mem copy funnel — cg_aggcopy/aggcopy, extracted verbatim from
|
||||
* the C1.25 assign-resolver tail — fed by aggarg_srcaddr (source addr →
|
||||
* SI) and a LEAQ of the destination (BP slot or g(SB) symbol → BX),
|
||||
* wired at: the N_ASSIGN ident-aggregate arm (local + global), the
|
||||
* deref-place divert into the existing resolver aggregate arm, and the
|
||||
* structlit-fill aggregate-field arm. Non-addressable aggregate rhs
|
||||
* shapes die LOUD (rule 7) instead of silently truncating.
|
||||
*
|
||||
* MUTATION: every row below fails at master 7545bf7 (probed 2026-06-05,
|
||||
* ken's matrix f49_min/f49b/f49c/fA_16b/f38b + impl-A probes pA_*: all
|
||||
* non-zero exits, byte-id both stages — the class is gate-blind, so
|
||||
* runtime readback is the only oracle).
|
||||
*
|
||||
* row | shape | want
|
||||
* ---------------------+-------------------------------------+------
|
||||
* assign_16b | {size,size} b = a, x*10+y readback | 39
|
||||
* assign_24b_plain | {size,size,size} b = a, sum | 6
|
||||
* assign_48b | 6-field b = a, sum | 21
|
||||
* assign_odd_12b | {u32,u32,u32} 12B maxalign-4 (gA2) | 0
|
||||
* assign_match_bind | dst = s from a match binding over | 0
|
||||
* | (st|void), st has a tagged member |
|
||||
* | (ken f49_min — the ORIGINAL #49 |
|
||||
* | context, regression pin) |
|
||||
* lit_field_from_ident | outer{tag, r = r} field fill (f38b) | 21
|
||||
* assign_str_field | {id, name:str} b = a, ladder | 0
|
||||
* assign_nested | {tag, inner{x,y}} b = a, sum | 6
|
||||
* arrelem_roundtrip | a[1] = b (kin, pre-wired #270-1b) | 15
|
||||
* | then cc = a[1] (the new read side) |
|
||||
* assign_deref | *p = s, 24B struct (#31-A fold) w/ | 27
|
||||
* | guards either side of the pointee |
|
||||
* assign_global | g = a then b = g (module-let dst | 36
|
||||
* | and src, both new arms) |
|
||||
* assign_dot_source | b = o.i (N_DOT source via | 6
|
||||
* | aggarg_srcaddr) |
|
||||
* assign_tuple | (size,size) u = t slot copy | 13
|
||||
* global_structlit | g = pt{...} (DST_GLOBAL fill wire) | 18
|
||||
* alias_named_assign | type row = st; b = a (ken R1/gA3b — | 0
|
||||
* | full alias chase, field-wise init) |
|
||||
* alias_twolevel_assign| ali -> base -> struct chain (gA6) | 0
|
||||
* neighbor_guard | b = a with live guards either side | 0
|
||||
* | of b — over-copy smashes them |
|
||||
*/
|
||||
#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;
|
||||
}
|
||||
|
||||
struct row { const char *label; const char *src; int want; };
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "assign_16b",
|
||||
"package main;\n"
|
||||
"type p2 = struct { x: size, y: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: p2 = p2 { x = 3: size, y = 9: size };\n"
|
||||
"\tlet b: p2 = p2 { x = 0: size, y = 0: size };\n"
|
||||
"\tb = a;\n"
|
||||
"\treturn (b.x * 10 + b.y): i32;\n"
|
||||
"};\n",
|
||||
39 },
|
||||
|
||||
{ "assign_24b_plain",
|
||||
"package main;\n"
|
||||
"type st = struct { id: size, x: size, y: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: st = st { id = 1: size, x = 2: size, y = 3: size };\n"
|
||||
"\tlet b: st = st { id = 0: size, x = 0: size, y = 0: size };\n"
|
||||
"\tb = a;\n"
|
||||
"\treturn (b.id + b.x + b.y): i32;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
|
||||
{ "assign_48b",
|
||||
"package main;\n"
|
||||
"type big = struct { a: size, b: size, c: size, d: size, e: size, f: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: big = big { a = 1: size, b = 2: size, c = 3: size, d = 4: size, e = 5: size, f = 6: size };\n"
|
||||
"\tlet y: big = big { a = 0: size, b = 0: size, c = 0: size, d = 0: size, e = 0: size, f = 0: size };\n"
|
||||
"\ty = x;\n"
|
||||
"\treturn (y.a + y.b + y.c + y.d + y.e + y.f): i32;\n"
|
||||
"};\n",
|
||||
21 },
|
||||
|
||||
/* ken gA2 — odd size: 12B {u32,u32,u32}, maxalign 4. Pins the
|
||||
* MOVQ-run + MOVL tail of the funnel (the fi.fsz slot-padded
|
||||
* skew worry) — NOT a multiple of 8. Pre-fix at master: word0
|
||||
* copies a+b in one MOVQ, c lost → exit 3 (probed both stages). */
|
||||
{ "assign_odd_12b",
|
||||
"package main;\n"
|
||||
"type tri = struct { a: u32, b: u32, c: u32 };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet x: tri = tri { a = 7: u32, b = 8: u32, c = 9: u32 };\n"
|
||||
"\tlet y: tri = tri { a = 0: u32, b = 0: u32, c = 0: u32 };\n"
|
||||
"\ty = x;\n"
|
||||
"\tif (y.a != 7) { return 1; };\n"
|
||||
"\tif (y.b != 8) { return 2; };\n"
|
||||
"\tif (y.c != 9) { return 3; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* ken f49_min verbatim — the ORIGINAL #49 filing context:
|
||||
* assign from a match binding, struct with a tagged member.
|
||||
* Pre-fix exit 4 (dst.m's tag word never copied). */
|
||||
{ "assign_match_bind",
|
||||
"package main;\n"
|
||||
"type st = struct { id: size, m: (void | size) };\n"
|
||||
"type un = (st | void);\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet src: un = st { id = 6: size, m = 8: size };\n"
|
||||
"\tlet dst: st = st { id = 0: size, m = void };\n"
|
||||
"\tmatch (src) {\n"
|
||||
"\tcase let s: st => {\n"
|
||||
"\t\tif (!(s.m is size)) { return 1; };\n"
|
||||
"\t\tdst = s;\n"
|
||||
"\t};\n"
|
||||
"\tcase void => { return 2; };\n"
|
||||
"\t};\n"
|
||||
"\tif (dst.id != 6) { return 3; };\n"
|
||||
"\tif (!(dst.m is size)) { return 4; };\n"
|
||||
"\tif (dst.m as size != 8) { return 5; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* ken f38b shape — struct-lit FIELD from an ident source. Pre-fix
|
||||
* exit: o.r.id copied (word0), x/y zero → 6. */
|
||||
{ "lit_field_from_ident",
|
||||
"package main;\n"
|
||||
"type rep = struct { id: size, x: size, y: size };\n"
|
||||
"type outer = struct { tag: size, r: rep };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet r: rep = rep { id = 6: size, x = 7: size, y = 8: size };\n"
|
||||
"\tlet o: outer = outer { tag = 4: size, r = r };\n"
|
||||
"\tif (o.tag != 4) { return 99; };\n"
|
||||
"\treturn (o.r.id + o.r.x + o.r.y): i32;\n"
|
||||
"};\n",
|
||||
21 },
|
||||
|
||||
/* 3-word str header inside the copied struct: the header words
|
||||
* past .ptr are exactly what a word0 copy drops. Ladder form —
|
||||
* `.len` in mixed arithmetic trips the PRE-EXISTING task-#26
|
||||
* typing divergence (cstage rejects), which is not this class. */
|
||||
{ "assign_str_field",
|
||||
"package main;\n"
|
||||
"type named = struct { id: size, name: str };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: named = named { id = 7: size, name = \"hello\" };\n"
|
||||
"\tlet b: named = named { id = 0: size, name = \"\" };\n"
|
||||
"\tb = a;\n"
|
||||
"\tif (b.id != 7) { return 1; };\n"
|
||||
"\tif (b.name.len != 5) { return 2; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
{ "assign_nested",
|
||||
"package main;\n"
|
||||
"type inner = struct { x: size, y: size };\n"
|
||||
"type outer = struct { tag: size, i: inner };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size } };\n"
|
||||
"\tlet b: outer = outer { tag = 0: size, i = inner { x = 0: size, y = 0: size } };\n"
|
||||
"\tb = a;\n"
|
||||
"\treturn (b.tag + b.i.x + b.i.y): i32;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
|
||||
/* a[i] = b was already wired (#270-1b INDEX-place word-copy) —
|
||||
* this row pins the KIN pair: the pre-existing store plus the
|
||||
* NEW ident-from-index read (`cc = a[1]` word0-copied pre-fix). */
|
||||
{ "arrelem_roundtrip",
|
||||
"package main;\n"
|
||||
"type pt = struct { x: size, y: size, z: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: [2]pt = [pt { x = 0: size, y = 0: size, z = 0: size }, pt { x = 0: size, y = 0: size, z = 0: size }];\n"
|
||||
"\tlet b: pt = pt { x = 4: size, y = 5: size, z = 6: size };\n"
|
||||
"\ta[1] = b;\n"
|
||||
"\tif (a[0].x != 0) { return 99; };\n"
|
||||
"\tlet cc: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
|
||||
"\tcc = a[1];\n"
|
||||
"\treturn (cc.x + cc.y + cc.z): i32;\n"
|
||||
"};\n",
|
||||
15 },
|
||||
|
||||
/* #31-A fold: deref place from an addressable aggregate ident —
|
||||
* pre-fix stored 8 bytes (probe pA_deref exit 2). Guards either
|
||||
* side of the pointee slot per ken gA4: an over-wide copy through
|
||||
* the resolved place smashes one. */
|
||||
{ "assign_deref",
|
||||
"package main;\n"
|
||||
"type pt = struct { x: size, y: size, z: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet guard1: size = 111: size;\n"
|
||||
"\tlet d: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
|
||||
"\tlet guard2: size = 222: size;\n"
|
||||
"\tlet s: pt = pt { x = 8: size, y = 9: size, z = 10: size };\n"
|
||||
"\tlet p: *pt = &d;\n"
|
||||
"\t*p = s;\n"
|
||||
"\tif (guard1 != 111) { return 98; };\n"
|
||||
"\tif (guard2 != 222) { return 97; };\n"
|
||||
"\treturn (d.x + d.y + d.z): i32;\n"
|
||||
"};\n",
|
||||
27 },
|
||||
|
||||
/* Module-let global as BOTH destination (`g = a`, LEAQ g(SB)
|
||||
* dest) and source (`b = g`, aggarg_srcaddr global arm). */
|
||||
{ "assign_global",
|
||||
"package main;\n"
|
||||
"type pt = struct { x: size, y: size, z: size };\n"
|
||||
"let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: pt = pt { x = 11: size, y = 12: size, z = 13: size };\n"
|
||||
"\tg = a;\n"
|
||||
"\tif (g.z != 13) { return 99; };\n"
|
||||
"\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
|
||||
"\tb = g;\n"
|
||||
"\treturn (b.x + b.y + b.z): i32;\n"
|
||||
"};\n",
|
||||
36 },
|
||||
|
||||
{ "assign_dot_source",
|
||||
"package main;\n"
|
||||
"type inner = struct { x: size, y: size, z: size };\n"
|
||||
"type outer = struct { tag: size, i: inner };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet o: outer = outer { tag = 1: size, i = inner { x = 2: size, y = 3: size, z = 4: size } };\n"
|
||||
"\tlet b: inner = inner { x = 0: size, y = 0: size, z = 0: size };\n"
|
||||
"\tb = o.i;\n"
|
||||
"\treturn (b.x + b.z): i32;\n"
|
||||
"};\n",
|
||||
6 },
|
||||
|
||||
/* Tuple ident reassign rides the same funnel (8B/elem slots). */
|
||||
{ "assign_tuple",
|
||||
"package main;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet t: (size, size) = (4: size, 9: size);\n"
|
||||
"\tlet u: (size, size) = (0: size, 0: size);\n"
|
||||
"\tu = t;\n"
|
||||
"\treturn (u.0 + u.1): i32;\n"
|
||||
"};\n",
|
||||
13 },
|
||||
|
||||
/* Global structlit reassign — newly wired through the DST_GLOBAL
|
||||
* fill (pre-fix: one scalar MOVQ of AX≈0 to g(SB)). */
|
||||
{ "global_structlit",
|
||||
"package main;\n"
|
||||
"type pt = struct { x: size, y: size, z: size };\n"
|
||||
"let g: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tg = pt { x = 5: size, y = 6: size, z = 7: size };\n"
|
||||
"\treturn (g.x + g.y + g.z): i32;\n"
|
||||
"};\n",
|
||||
18 },
|
||||
|
||||
/* ken R1 (gA3b): ALIAS-named aggregate — the funnel dispatch must
|
||||
* FULL-chase the alias (type_chase_named / chased tinfo); a
|
||||
* single peel left cstage at TY_NAMED, fell to the scalar tail
|
||||
* and word0-copied while wwstage copied full-width (live cs≠ww).
|
||||
* FIELD-WISE init: the struct-LIT spelling can't pin the ww side
|
||||
* (it louds earlier at the pre-existing task-#7 aggregate-let
|
||||
* bound — the #5 alias-arc's hole, not this funnel's). */
|
||||
{ "alias_named_assign",
|
||||
"package main;\n"
|
||||
"type st = struct { a: size, b: size };\n"
|
||||
"type row = st;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: row;\n"
|
||||
"\ta.a = 4; a.b = 9;\n"
|
||||
"\tlet b: row;\n"
|
||||
"\tb.a = 0; b.b = 0;\n"
|
||||
"\tb = a;\n"
|
||||
"\tif (b.a != 4) { return 1; };\n"
|
||||
"\tif (b.b != 9) { return 2; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* ken gA6: TWO-level alias chain (`ali -> base -> struct`) — pins
|
||||
* that the dispatch is a full CHASE, not a deeper fixed peel. */
|
||||
{ "alias_twolevel_assign",
|
||||
"package main;\n"
|
||||
"type ali = base;\n"
|
||||
"type base = struct { a: size, b: size, c: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet a: ali;\n"
|
||||
"\ta.a = 4; a.b = 9; a.c = 13;\n"
|
||||
"\tlet b: ali;\n"
|
||||
"\tb.a = 0; b.b = 0; b.c = 0;\n"
|
||||
"\tb = a;\n"
|
||||
"\tif (b.a != 4) { return 1; };\n"
|
||||
"\tif (b.b != 9) { return 2; };\n"
|
||||
"\tif (b.c != 13) { return 3; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* Neighbor guards on both sides of the destination slot: a copy
|
||||
* WIDER than size(T) (the inverse regression) smashes one. */
|
||||
{ "neighbor_guard",
|
||||
"package main;\n"
|
||||
"type pt = struct { x: size, y: size, z: size };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
"\tlet guard1: size = 111: size;\n"
|
||||
"\tlet b: pt = pt { x = 0: size, y = 0: size, z = 0: size };\n"
|
||||
"\tlet guard2: size = 222: size;\n"
|
||||
"\tlet a: pt = pt { x = 1: size, y = 2: size, z = 3: size };\n"
|
||||
"\tb = a;\n"
|
||||
"\tif (guard1 != 111) { return 1; };\n"
|
||||
"\tif (guard2 != 222) { return 2; };\n"
|
||||
"\tif (b.x + b.y + b.z != 6) { return 3; };\n"
|
||||
"\treturn 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
static int
|
||||
run_driver(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/aggas_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/aggas_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||
r->label, driver);
|
||||
unlink(src); rmdir(tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
int got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return got;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — generate .s via cstage's w6c and wwstage's
|
||||
* w6c_ww and diff. The class is gate-blind (both stages were wrong
|
||||
* identically), so byte-id here pins that the CONVERGED fix stays
|
||||
* converged. */
|
||||
static int
|
||||
asm_byte_identical(const char *bin, const struct row *r, int i)
|
||||
{
|
||||
char src[64], cs[64], ws[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/aggas_asm_%d_%d.ww", getpid(), i);
|
||||
snprintf(cs, sizeof cs, "/tmp/aggas_asm_%d_%d_c.s", getpid(), i);
|
||||
snprintf(ws, sizeof ws, "/tmp/aggas_asm_%d_%d_w.s", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c errored\n", r->label);
|
||||
unlink(src);
|
||||
return -1;
|
||||
}
|
||||
snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null",
|
||||
bin, ws, src);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label);
|
||||
unlink(src); unlink(cs);
|
||||
return -1;
|
||||
}
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
int rc = 0;
|
||||
if (!fc || !fw) {
|
||||
rc = -1;
|
||||
} else {
|
||||
for (;;) {
|
||||
int a = fgetc(fc);
|
||||
int b = fgetc(fw);
|
||||
if (a != b) { rc = -1; break; }
|
||||
if (a == EOF) break;
|
||||
}
|
||||
}
|
||||
if (fc) fclose(fc);
|
||||
if (fw) fclose(fw);
|
||||
if (rc != 0)
|
||||
fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n",
|
||||
r->label);
|
||||
unlink(src); unlink(cs); unlink(ws);
|
||||
return rc;
|
||||
}
|
||||
|
||||
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 cdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
char wdrv[1024];
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
struct { const char *name; const char *path; int gated_on_existence; }
|
||||
drivers[] = {
|
||||
{ "cstage", cdrv, 0 },
|
||||
{ "wwstage", wdrv, 1 },
|
||||
{ NULL, NULL, 0 },
|
||||
};
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
|
||||
for (int d = 0; drivers[d].name; d++) {
|
||||
if (drivers[d].gated_on_existence
|
||||
&& access(drivers[d].path, X_OK) != 0) {
|
||||
fprintf(stderr, "agg_assign_width: skip %s (no %s)\n",
|
||||
drivers[d].name, drivers[d].path);
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < n; i++) {
|
||||
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||
total++;
|
||||
if (got != rows[i].want) {
|
||||
fprintf(stderr,
|
||||
"agg_assign_width[%s][%s]: exit=%d want=%d\n",
|
||||
drivers[d].name, rows[i].label,
|
||||
got, rows[i].want);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (access(wdrv, X_OK) == 0) {
|
||||
for (int i = 0; i < n; i++) {
|
||||
total++;
|
||||
if (asm_byte_identical(bin, &rows[i], i) != 0)
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"agg_assign_width: %d/%d fixtures failed\n", fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("agg_assign_width: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
@@ -1389,7 +1389,14 @@ static const struct row rows[] = {
|
||||
K_BUILDERR, "unsupported field-read shape" },
|
||||
/* rule-7: a tuple arg from a source whose cgexpr does NOT fill
|
||||
* the cursor (here a struct-field chain) dies loud — pre-C-t2
|
||||
* it fell to the scalar single-PUSHQ default silently. */
|
||||
* it fell to the scalar single-PUSHQ default silently.
|
||||
* #49 note: the tuple field fills from an IDENT source
|
||||
* (`t = tt`, the #49 fill-field copy) so the build reaches the
|
||||
* pinned ARG reject. The previous tuple-LITERAL fill
|
||||
* (`t = (1, 2)`) now louds EARLIER at the #49 fill arm
|
||||
* ("aggregate field from a non-addressable source unwired") —
|
||||
* pre-#49 it silently word0-filled and only the arg push
|
||||
* loudened. */
|
||||
{ "t2_reject_chain_arg",
|
||||
"package main;\n"
|
||||
"type holder = struct { t: (i64, i64) };\n"
|
||||
@@ -1397,7 +1404,8 @@ static const struct row rows[] = {
|
||||
" return t.0 + t.1;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let h: holder = holder{ t = (1, 2) };\n"
|
||||
" let tt: (i64, i64) = (1, 2);\n"
|
||||
" let h: holder = holder{ t = tt };\n"
|
||||
" if (sum(h.t) != 3) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n", 0,
|
||||
|
||||
Reference in New Issue
Block a user