From 0d3912974139a7e5528a945bcf37f85ab14d4545 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 2 Jun 2026 14:52:44 +0900 Subject: [PATCH] w6c+wwstage: aggregate return from any addressable source (#272 commit-1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The N_RETURN aggregate arms gated the return source on N_IDENT || N_STRUCTLIT; every other aggregate rvalue (array literal, o.field N_DOT, a[i] N_INDEX, *p deref) fell through to the scalar-AX default = a silent 8-byte truncation. Both stages emitted byte-IDENTICAL wrong asm, so the byte-id gate could not catch it (#263 class) — the fix converges on the runtime oracle. Mirror the arg-side closure #271 landed: both arms (≤24B @retscr and >24B sret) now funnel N_ARRLIT through the literal element fill and N_DOT/N_INDEX/deref through aggarg_srcaddr + the #265/#268 whole- aggregate copy. Type-agnostic, so struct AND array returns are closed. A close-by-construction loud-stop (rule 7) guards any future unhandled aggregate source from reaching the scalar default. Closes the callee-half of (b)/(c) and the addressable siblings. The g = mk() global-receive caller-half is commit-2. 949_aggret_source_run pins the class: array-literal / N_DOT / N_INDEX / deref / named-ident control / >24B-sret-deref / struct-field / struct- deref, each summing all members (full readback) with per-row byte-id. --- Makefile | 6 + cmd/w6c/cgen.c | 191 +++++++++++++++- selfhost/cmd/w6c/main.combined.ww | 330 ++++++++++++++++++++++++--- selfhost/cmd/wcc/cgenstmt.ww | 330 ++++++++++++++++++++++++--- selfhost/cmd/wwdump/main.combined.ww | 330 ++++++++++++++++++++++++--- test/wcc/949_aggret_source_run.c | 224 ++++++++++++++++++ 6 files changed, 1299 insertions(+), 112 deletions(-) create mode 100644 test/wcc/949_aggret_source_run.c diff --git a/Makefile b/Makefile index bf26697e..e6344e50 100644 --- a/Makefile +++ b/Makefile @@ -406,6 +406,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_structlit_arrfield_run \ $(BIN)/test_arraytoslice_run \ $(BIN)/test_valstruct_subsize_run \ + $(BIN)/test_aggret_source_run \ $(BIN)/test_continue_run \ $(BIN)/test_callret_unsigned_arith_run \ $(BIN)/test_sar_shr_run \ @@ -1621,6 +1622,11 @@ $(BIN)/test_valstruct_subsize_run: test/wcc/949_valstruct_subsize_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_aggret_source_run: test/wcc/949_aggret_source_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_continue_run: test/wcc/911_continue_run.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 41b950b3..f95a4426 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -9640,7 +9640,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) if (rt && (rt->kind == TY_STRUCT || rt->kind == TY_ARRAY) && (int)rt->size > 24 && (n->lhs->kind == N_IDENT - || n->lhs->kind == N_STRUCTLIT)) { + || n->lhs->kind == N_STRUCTLIT + || n->lhs->kind == N_ARRLIT + || n->lhs->kind == N_DOT + || n->lhs->kind == N_INDEX + || (n->lhs->kind == N_UN + && n->lhs->op == TK_STAR))) { /* Natural size = max(foff + fsz) over declared * fields; mirrors selfhost cgenutil.ww * structnaturalsize / sretretsize. Pre-fix this @@ -9668,7 +9673,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) cg_structlit_fill(c, locals, rt, n->lhs, DST_PTR_LOCAL, cg_sret_arg_off, NULL, 0); - } else { + } else if (n->lhs->kind == N_IDENT) { /* N_IDENT: word-copy from rhs slot to * *(@sretarg). Whole 8B words via MOVQ; * trailing partial words via MOVL/MOVB @@ -9704,6 +9709,50 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) amem(D_BX, k)); k += 1; } + } else if (n->lhs->kind == N_ARRLIT) { + /* #272: a >24B array-literal return has no + * consumer and the ptr-relative element fill + * is untested. Loud-stop (rule 7) rather than + * fall to the scalar default. ≤24B is wired. */ + fatal("#272: >24B array-literal return " + "unsupported (rule 7, no consumer)"); + } else { + /* #272: N_DOT / N_INDEX / deref — land the + * source ADDRESS in SI FIRST (aggarg_srcaddr + * clobbers BX on its N_INDEX spine), THEN + * reload the dest ptr from @sretarg into BX + * and memcpy sz bytes — same #265/#268 copy + * shape as the ≤24B arm. Loud-stop any source + * the helper can't address. */ + if (!aggarg_srcaddr(c, n->lhs, D_SI, *locals)) + fatal("#272: aggregate return from " + "unsupported source kind %d", + n->lhs->kind); + ins2(c, A_MOVQ, + amem(D_BP, cg_sret_arg_off), + areg(D_BX)); + int k = 0; + while (k + 8 <= sz) { + ins2(c, A_MOVQ, amem(D_SI, k), + areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BX, k)); + k += 8; + } + while (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; + } + while (k < 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; + } } /* sret return: RAX = dest pointer. */ ins2(c, A_MOVQ, @@ -9729,10 +9778,23 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * for the same rationale. The ≤24B register-return * ABI uses the same TY_STRUCT gate. */ Type *rt = type_chase_named(cg_ret_type); + /* #272: aggregate-return source-shape closure. Beyond the + * #267 N_IDENT/N_STRUCTLIT pair, every OTHER addressable + * aggregate rvalue (`return [..]` N_ARRLIT, `return o.f` + * N_DOT, `return a[i]` N_INDEX, `return *p` deref) fell to + * the scalar-AX default below = silent truncation. Funnel + * them through the SAME @retscr materialise the arg side + * closed in #271 (aggarg_srcaddr). N_CALL still passes + * through the tail (the callee already left AX/DX/CX). */ if (rt && (rt->kind == TY_STRUCT || rt->kind == TY_ARRAY) && rt->size <= 24 && (n->lhs->kind == N_IDENT - || n->lhs->kind == N_STRUCTLIT)) { + || n->lhs->kind == N_STRUCTLIT + || n->lhs->kind == N_ARRLIT + || n->lhs->kind == N_DOT + || n->lhs->kind == N_INDEX + || (n->lhs->kind == N_UN + && n->lhs->op == TK_STAR))) { int sz = (int)rt->size; /* Single-slot @retscr (#14): see tagged arm * above for rationale. Fixed "@retscr" name @@ -9762,7 +9824,71 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) * trailing bytes. */ cg_structlit_fill_bp(c, locals, rt, n->lhs, scr); - } else { + } else if (n->lhs->kind == N_ARRLIT) { + /* #272: materialise the array literal into + * @retscr per element, mirroring the + * let-init N_ARRLIT scalar/float fill + * (cgen.c N_LET). Non-scalar elements + * (struct/array/str/slice) loud-stop: no + * return-by-value consumer exists (rule 7), + * and the let-init path already covers them + * for the addressable forms. */ + Type *esub = rt->sub; + int esz = esub ? (int)esub->size : 1; + Type *esubu = type_chase_named(esub); + if ((esubu && (esubu->kind == TY_STRUCT + || esubu->kind == TY_ARRAY + || esubu->kind == TY_TUPLE)) + || type_isstr(esub) + || type_isslice(esub)) + fatal("#272: array-literal return " + "with non-scalar element " + "unsupported (rule 7, no " + "consumer)"); + int isfl = type_isfloat(esub); + int fmov = type_isf32(esub) ? A_MOVSS + : A_MOVSD; + int op = A_MOVQ; + if (esz == 1) op = A_MOVB; + else if (esz == 2) op = A_MOVW; + else if (esz == 4) op = A_MOVL; + int idx = 0; + Node *last = NULL; + int repeat = 0; + for (Node *e = n->lhs->list; e; + e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) { + repeat = 1; + break; + } + cgexpr(c, e, *locals); + if (isfl) + ins2(c, fmov, areg(D_X0), + amem(D_BP, + scr + idx * esz)); + else + ins2(c, op, areg(D_AX), + amem(D_BP, + scr + idx * esz)); + last = e; + idx++; + } + if (repeat && last) + while (idx < (int)rt->alen) { + if (isfl) + ins2(c, fmov, + areg(D_X0), + amem(D_BP, + scr + idx * esz)); + else + ins2(c, op, + areg(D_AX), + amem(D_BP, + scr + idx * esz)); + idx++; + } + } else if (n->lhs->kind == N_IDENT) { /* N_IDENT: word-copy rhs slot into * scratch. Whole 8B words via MOVQ; * trailing partial word via MOVL/MOVB @@ -9795,6 +9921,48 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) amem(D_BP, scr + k)); k += 1; } + } else { + /* #272: N_DOT / N_INDEX / deref — land the + * source ADDRESS in SI via the #271 arg-side + * helper, then memcpy sz bytes into @retscr + * (the #265/#268 let-init copy shape). Loud- + * stop any source the helper can't address + * (rule 7); the gate above already excludes + * N_CALL (tail passthrough). */ + if (!aggarg_srcaddr(c, n->lhs, D_SI, + *locals)) + fatal("#272: aggregate return " + "from unsupported source " + "kind %d", n->lhs->kind); + int k = 0; + while (k + 8 <= sz) { + ins2(c, A_MOVQ, amem(D_SI, k), + areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BP, scr + k)); + k += 8; + } + if (k + 4 <= sz) { + ins2(c, A_MOVL, amem(D_SI, k), + areg(D_AX)); + ins2(c, A_MOVL, areg(D_AX), + amem(D_BP, scr + 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_BP, scr + 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_BP, scr + k)); + k += 1; + } } /* #171a: float-bearing struct RETURN (the return * twin of #165's param recv). A qualifying struct's @@ -9840,6 +10008,21 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) break; } } + /* #272 close-by-construction: the addressable aggregate-return + * sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all route + * through the @retscr / *(@sretarg) arms above and break; an + * aggregate N_CALL passes through the cgexpr tail (the callee + * already left AX/DX/CX). Any OTHER aggregate rvalue reaching + * here would truncate to AX silently — loud-stop (rule 7) so a + * future unhandled shape is caught, not miscompiled. */ + if (n->lhs && cg_ret_type) { + Type *rtc = type_chase_named(cg_ret_type); + if (rtc && (rtc->kind == TY_STRUCT || rtc->kind == TY_ARRAY) + && n->lhs->kind != N_CALL) + fatal("#272: aggregate return reaches scalar default " + "(source kind %d) — unclosed shape", + n->lhs->kind); + } if (n->lhs && node_isstr(n->lhs)) { /* str IS []u8: AX=ptr, BX=len, CX=cap from cgexpr — * no AX:DX shuffle, same as a slice (#1/Phase 3). */ diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 5156fc57..38ca399c 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -28454,8 +28454,19 @@ fn cgreturn(c: *cgen, n: *node) void = { return; }; let okrhs: bool = false; + // #272: >24B sret addressable-source closure — + // N_DOT/N_INDEX/deref land their address in SI then + // memcpy through *(@sretarg), mirroring cstage cgen.c + // N_RETURN sret arm. N_ARRLIT >24B has no consumer + // (loud-stops in cstage); not wired here. + let addrsrc: bool = false; if (rhs.kind == nkind.N_IDENT) { okrhs = true; }; if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; }; + if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; }; + }; if (okrhs) { if (rhs.kind == nkind.N_STRUCTLIT) { let trefn: *node = rhs.lhs; @@ -28477,6 +28488,43 @@ fn cgreturn(c: *cgen, n: *node) void = { 1, sretargoff, emptys, 0); }; + } else { if (addrsrc) { + if (!aggargsrcaddr(c, rhs, "SI")) { + let m4: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m4.ptr, m4.len: u64); + os.exit(1); + }; + emitline("\tMOVQ\t"); + emitoff(sretargoff: i64); + emitline("(BP), BX\n"); + let k: i32 = 0; + for (k + 8 <= scs) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 8; + }; + for (k + 4 <= scs) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 4; + }; + for (k < scs) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 1; + }; } else { let rl: *local = localfindnode(c, rhs.str); if (rl != nil) { @@ -28512,7 +28560,7 @@ fn cgreturn(c: *cgen, n: *node) void = { k += 1; }; }; - }; + }; }; // sret return: RAX = dest pointer. emitline("\tMOVQ\t"); emitoff(sretargoff: i64); @@ -28549,12 +28597,21 @@ fn cgreturn(c: *cgen, n: *node) void = { let rsz: i32 = structabisize(rsi); if (rsz <= 24) { let okrhs: bool = false; + // #272: struct ≤24B addressable-source closure — + // N_DOT/N_INDEX/deref memcpy into @retscr before the + // shared structfloatclass tail (mirror cstage cgen.c). + let addrsrc: bool = false; if (rhs.kind == nkind.N_IDENT) { okrhs = true; }; if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; }; + if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; }; + }; if (okrhs) { let scroff: i32 = localadd(c, "@retscr", 24, nil); @@ -28577,6 +28634,51 @@ fn cgreturn(c: *cgen, n: *node) void = { // values recurse instead of dropping // trailing bytes. cgstructlitfillbp(c, rsi, rhs, scroff); + } else { if (addrsrc) { + // N_DOT / N_INDEX / deref: land src addr in SI, + // then memcpy rsz bytes into @retscr (#265/#268 shape). + if (!aggargsrcaddr(c, rhs, "SI")) { + let m5: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m5.ptr, m5.len: u64); + os.exit(1); + }; + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= rsz) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= rsz) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; } else { // N_IDENT: word-copy from rhs slot // to scratch. Whole 8B words via @@ -28614,7 +28716,7 @@ fn cgreturn(c: *cgen, n: *node) void = { k += 1; }; }; - }; + }; }; // #171a: float-bearing struct RETURN (return // twin of #165's param recv). A qualifying // struct's float eightbytes ride the SSE return @@ -28683,30 +28785,45 @@ fn cgreturn(c: *cgen, n: *node) void = { // (tinfo.size = sub.size*len) mirrors cstage rt->size. No // structfloatclass (pure-int arrays); N_CALL forward at reg- // class falls to the default cgexpr passthrough below. - if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY - && rhs.kind == nkind.N_IDENT) { + if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY) { + // #272: array return-by-value source-shape closure. + // Beyond the #267 N_IDENT word-copy, route N_ARRLIT + // (literal fill), N_DOT/N_INDEX/deref (aggargsrcaddr + + // memcpy) into @retscr — the mirror of cstage cgen.c + // N_RETURN ≤24B arm. N_CALL stays on the cgexpr tail (the + // callee already left AX/DX/CX). + let arrok: bool = false; + if (rhs.kind == nkind.N_IDENT) { arrok = true; }; + if (rhs.kind == nkind.N_ARRLIT) { arrok = true; }; + if (rhs.kind == nkind.N_DOT) { arrok = true; }; + if (rhs.kind == nkind.N_INDEX) { arrok = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { arrok = true; }; + }; let ati: *tinfo = c.fnret.type_: *tinfo; for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; - if (ati != nil) { + if (arrok && ati != nil) { let rsz: i32 = ati.size: i32; if (rsz <= 24) { - let rl: *local = localfindnode(c, rhs.str); - if (rl != nil) { - let scroff: i32 = localadd(c, "@retscr", 24, nil); - emitline("\tXORQ\tAX, AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(scroff: i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tAX, "); - emitoff((scroff + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tAX, "); - emitoff((scroff + 16): i64); - emitline("(BP)\n"); + let scroff: i32 = localadd(c, "@retscr", 24, nil); + emitline("\tXORQ\tAX, AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(scroff: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 16): i64); + emitline("(BP)\n"); + if (rhs.kind == nkind.N_IDENT) { + let rl: *local = localfindnode(c, rhs.str); + let roff: i32 = 0; + if (rl != nil) { roff = rl.off; }; let k: i32 = 0; for (k + 8 <= rsz) { emitline("\tMOVQ\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff((scroff + k): i64); @@ -28715,7 +28832,7 @@ fn cgreturn(c: *cgen, n: *node) void = { }; for (k + 4 <= rsz) { emitline("\tMOVL\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitoff((scroff + k): i64); @@ -28724,31 +28841,172 @@ fn cgreturn(c: *cgen, n: *node) void = { }; for (k < rsz) { emitline("\tMOVB\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitoff((scroff + k): i64); emitline("(BP)\n"); k += 1; }; - emitline("\tMOVQ\t"); - emitoff(scroff: i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\t"); - emitoff((scroff + 8): i64); - emitline("(BP), DX\n"); - emitline("\tMOVQ\t"); - emitoff((scroff + 16): i64); - emitline("(BP), CX\n"); - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.lastwasreturn = 1; - return; - }; + } else { if (rhs.kind == nkind.N_ARRLIT) { + // scalar/float element fill; non-scalar + // elements loud-stop (rule 7, no consumer). + let esubti: *tinfo = nil; + if (ati.sub != nil) { esubti = ati.sub; }; + for (esubti != nil && esubti.kind == tykind.TY_NAMED) { esubti = esubti.under; }; + let esz: i32 = 8; + if (esubti != nil) { esz = esubti.size: i32; }; + let badel: bool = false; + if (esubti != nil) { + if (esubti.kind == tykind.TY_STRUCT) { badel = true; }; + if (esubti.kind == tykind.TY_ARRAY) { badel = true; }; + if (esubti.kind == tykind.TY_TUPLE) { badel = true; }; + if (esubti.kind == tykind.TY_SLICE) { badel = true; }; + if (esubti.kind == tykind.TY_STR) { badel = true; }; + }; + if (badel) { + let m2: str = "#272: array-literal return with non-scalar element unsupported (rule 7, no consumer)\n"; + os.write(2, m2.ptr, m2.len: u64); + os.exit(1); + }; + let esub: *node = c.fnret.lhs; + let isfl: bool = isfloattype(c, esub); + let fmov: str = "MOVSD"; + if (isf32type(c, esub)) { fmov = "MOVSS"; }; + let op: str = "MOVQ"; + if (esz == 1) { op = "MOVB"; } else { if (esz == 2) { op = "MOVW"; } else { if (esz == 4) { op = "MOVL"; }; }; }; + 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 { + cgexpr(c, e); + if (isfl) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(op); + emitline("\tAX, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + }; + idx += 1; + e = e.next; + }; + }; + if (repeat) { + let total: i32 = rsz / esz; + for (idx < total) { + if (isfl) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(op); + emitline("\tAX, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + }; + idx += 1; + }; + }; + } else { + // N_DOT / N_INDEX / deref: land src addr in SI, + // then memcpy rsz bytes into @retscr (#265/#268 + // copy shape). Loud-stop unaddressable sources. + if (!aggargsrcaddr(c, rhs, "SI")) { + let m3: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m3.ptr, m3.len: u64); + os.exit(1); + }; + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= rsz) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= rsz) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; + }; }; + emitline("\tMOVQ\t"); + emitoff(scroff: i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 8): i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 16): i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.lastwasreturn = 1; + return; }; }; }; + // #272 close-by-construction: addressable aggregate-return + // sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all break in + // the arms above; an aggregate N_CALL passes through cgexpr + // (callee left AX/DX/CX). Any OTHER aggregate rvalue reaching + // here would truncate to AX silently — loud-stop (rule 7), + // mirroring cstage cgen.c N_RETURN. + { + let aggret: bool = false; + if (c.fnret != nil) { + if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; }; + if (c.fnret.kind == nkind.N_TNAME) { + if (structlookup(c, c.fnret.str) != nil) { aggret = true; }; + }; + }; + if (aggret && rhs.kind != nkind.N_CALL) { + let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n"; + os.write(2, m6.ptr, m6.len: u64); + os.exit(1); + }; + }; cgexpr(c, rhs); } else { // Bare `return;` from a tagged-union-returning fn is diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 30c32a76..209f9535 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -868,8 +868,19 @@ fn cgreturn(c: *cgen, n: *node) void = { return; }; let okrhs: bool = false; + // #272: >24B sret addressable-source closure — + // N_DOT/N_INDEX/deref land their address in SI then + // memcpy through *(@sretarg), mirroring cstage cgen.c + // N_RETURN sret arm. N_ARRLIT >24B has no consumer + // (loud-stops in cstage); not wired here. + let addrsrc: bool = false; if (rhs.kind == nkind.N_IDENT) { okrhs = true; }; if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; }; + if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; }; + }; if (okrhs) { if (rhs.kind == nkind.N_STRUCTLIT) { let trefn: *node = rhs.lhs; @@ -891,6 +902,43 @@ fn cgreturn(c: *cgen, n: *node) void = { 1, sretargoff, emptys, 0); }; + } else { if (addrsrc) { + if (!aggargsrcaddr(c, rhs, "SI")) { + let m4: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m4.ptr, m4.len: u64); + os.exit(1); + }; + emitline("\tMOVQ\t"); + emitoff(sretargoff: i64); + emitline("(BP), BX\n"); + let k: i32 = 0; + for (k + 8 <= scs) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 8; + }; + for (k + 4 <= scs) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 4; + }; + for (k < scs) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 1; + }; } else { let rl: *local = localfindnode(c, rhs.str); if (rl != nil) { @@ -926,7 +974,7 @@ fn cgreturn(c: *cgen, n: *node) void = { k += 1; }; }; - }; + }; }; // sret return: RAX = dest pointer. emitline("\tMOVQ\t"); emitoff(sretargoff: i64); @@ -963,12 +1011,21 @@ fn cgreturn(c: *cgen, n: *node) void = { let rsz: i32 = structabisize(rsi); if (rsz <= 24) { let okrhs: bool = false; + // #272: struct ≤24B addressable-source closure — + // N_DOT/N_INDEX/deref memcpy into @retscr before the + // shared structfloatclass tail (mirror cstage cgen.c). + let addrsrc: bool = false; if (rhs.kind == nkind.N_IDENT) { okrhs = true; }; if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; }; + if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; }; + }; if (okrhs) { let scroff: i32 = localadd(c, "@retscr", 24, nil); @@ -991,6 +1048,51 @@ fn cgreturn(c: *cgen, n: *node) void = { // values recurse instead of dropping // trailing bytes. cgstructlitfillbp(c, rsi, rhs, scroff); + } else { if (addrsrc) { + // N_DOT / N_INDEX / deref: land src addr in SI, + // then memcpy rsz bytes into @retscr (#265/#268 shape). + if (!aggargsrcaddr(c, rhs, "SI")) { + let m5: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m5.ptr, m5.len: u64); + os.exit(1); + }; + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= rsz) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= rsz) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; } else { // N_IDENT: word-copy from rhs slot // to scratch. Whole 8B words via @@ -1028,7 +1130,7 @@ fn cgreturn(c: *cgen, n: *node) void = { k += 1; }; }; - }; + }; }; // #171a: float-bearing struct RETURN (return // twin of #165's param recv). A qualifying // struct's float eightbytes ride the SSE return @@ -1097,30 +1199,45 @@ fn cgreturn(c: *cgen, n: *node) void = { // (tinfo.size = sub.size*len) mirrors cstage rt->size. No // structfloatclass (pure-int arrays); N_CALL forward at reg- // class falls to the default cgexpr passthrough below. - if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY - && rhs.kind == nkind.N_IDENT) { + if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY) { + // #272: array return-by-value source-shape closure. + // Beyond the #267 N_IDENT word-copy, route N_ARRLIT + // (literal fill), N_DOT/N_INDEX/deref (aggargsrcaddr + + // memcpy) into @retscr — the mirror of cstage cgen.c + // N_RETURN ≤24B arm. N_CALL stays on the cgexpr tail (the + // callee already left AX/DX/CX). + let arrok: bool = false; + if (rhs.kind == nkind.N_IDENT) { arrok = true; }; + if (rhs.kind == nkind.N_ARRLIT) { arrok = true; }; + if (rhs.kind == nkind.N_DOT) { arrok = true; }; + if (rhs.kind == nkind.N_INDEX) { arrok = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { arrok = true; }; + }; let ati: *tinfo = c.fnret.type_: *tinfo; for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; - if (ati != nil) { + if (arrok && ati != nil) { let rsz: i32 = ati.size: i32; if (rsz <= 24) { - let rl: *local = localfindnode(c, rhs.str); - if (rl != nil) { - let scroff: i32 = localadd(c, "@retscr", 24, nil); - emitline("\tXORQ\tAX, AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(scroff: i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tAX, "); - emitoff((scroff + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tAX, "); - emitoff((scroff + 16): i64); - emitline("(BP)\n"); + let scroff: i32 = localadd(c, "@retscr", 24, nil); + emitline("\tXORQ\tAX, AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(scroff: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 16): i64); + emitline("(BP)\n"); + if (rhs.kind == nkind.N_IDENT) { + let rl: *local = localfindnode(c, rhs.str); + let roff: i32 = 0; + if (rl != nil) { roff = rl.off; }; let k: i32 = 0; for (k + 8 <= rsz) { emitline("\tMOVQ\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff((scroff + k): i64); @@ -1129,7 +1246,7 @@ fn cgreturn(c: *cgen, n: *node) void = { }; for (k + 4 <= rsz) { emitline("\tMOVL\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitoff((scroff + k): i64); @@ -1138,31 +1255,172 @@ fn cgreturn(c: *cgen, n: *node) void = { }; for (k < rsz) { emitline("\tMOVB\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitoff((scroff + k): i64); emitline("(BP)\n"); k += 1; }; - emitline("\tMOVQ\t"); - emitoff(scroff: i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\t"); - emitoff((scroff + 8): i64); - emitline("(BP), DX\n"); - emitline("\tMOVQ\t"); - emitoff((scroff + 16): i64); - emitline("(BP), CX\n"); - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.lastwasreturn = 1; - return; - }; + } else { if (rhs.kind == nkind.N_ARRLIT) { + // scalar/float element fill; non-scalar + // elements loud-stop (rule 7, no consumer). + let esubti: *tinfo = nil; + if (ati.sub != nil) { esubti = ati.sub; }; + for (esubti != nil && esubti.kind == tykind.TY_NAMED) { esubti = esubti.under; }; + let esz: i32 = 8; + if (esubti != nil) { esz = esubti.size: i32; }; + let badel: bool = false; + if (esubti != nil) { + if (esubti.kind == tykind.TY_STRUCT) { badel = true; }; + if (esubti.kind == tykind.TY_ARRAY) { badel = true; }; + if (esubti.kind == tykind.TY_TUPLE) { badel = true; }; + if (esubti.kind == tykind.TY_SLICE) { badel = true; }; + if (esubti.kind == tykind.TY_STR) { badel = true; }; + }; + if (badel) { + let m2: str = "#272: array-literal return with non-scalar element unsupported (rule 7, no consumer)\n"; + os.write(2, m2.ptr, m2.len: u64); + os.exit(1); + }; + let esub: *node = c.fnret.lhs; + let isfl: bool = isfloattype(c, esub); + let fmov: str = "MOVSD"; + if (isf32type(c, esub)) { fmov = "MOVSS"; }; + let op: str = "MOVQ"; + if (esz == 1) { op = "MOVB"; } else { if (esz == 2) { op = "MOVW"; } else { if (esz == 4) { op = "MOVL"; }; }; }; + 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 { + cgexpr(c, e); + if (isfl) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(op); + emitline("\tAX, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + }; + idx += 1; + e = e.next; + }; + }; + if (repeat) { + let total: i32 = rsz / esz; + for (idx < total) { + if (isfl) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(op); + emitline("\tAX, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + }; + idx += 1; + }; + }; + } else { + // N_DOT / N_INDEX / deref: land src addr in SI, + // then memcpy rsz bytes into @retscr (#265/#268 + // copy shape). Loud-stop unaddressable sources. + if (!aggargsrcaddr(c, rhs, "SI")) { + let m3: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m3.ptr, m3.len: u64); + os.exit(1); + }; + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= rsz) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= rsz) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; + }; }; + emitline("\tMOVQ\t"); + emitoff(scroff: i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 8): i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 16): i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.lastwasreturn = 1; + return; }; }; }; + // #272 close-by-construction: addressable aggregate-return + // sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all break in + // the arms above; an aggregate N_CALL passes through cgexpr + // (callee left AX/DX/CX). Any OTHER aggregate rvalue reaching + // here would truncate to AX silently — loud-stop (rule 7), + // mirroring cstage cgen.c N_RETURN. + { + let aggret: bool = false; + if (c.fnret != nil) { + if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; }; + if (c.fnret.kind == nkind.N_TNAME) { + if (structlookup(c, c.fnret.str) != nil) { aggret = true; }; + }; + }; + if (aggret && rhs.kind != nkind.N_CALL) { + let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n"; + os.write(2, m6.ptr, m6.len: u64); + os.exit(1); + }; + }; cgexpr(c, rhs); } else { // Bare `return;` from a tagged-union-returning fn is diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 986b6db8..5a357665 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -28454,8 +28454,19 @@ fn cgreturn(c: *cgen, n: *node) void = { return; }; let okrhs: bool = false; + // #272: >24B sret addressable-source closure — + // N_DOT/N_INDEX/deref land their address in SI then + // memcpy through *(@sretarg), mirroring cstage cgen.c + // N_RETURN sret arm. N_ARRLIT >24B has no consumer + // (loud-stops in cstage); not wired here. + let addrsrc: bool = false; if (rhs.kind == nkind.N_IDENT) { okrhs = true; }; if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; }; + if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; }; + }; if (okrhs) { if (rhs.kind == nkind.N_STRUCTLIT) { let trefn: *node = rhs.lhs; @@ -28477,6 +28488,43 @@ fn cgreturn(c: *cgen, n: *node) void = { 1, sretargoff, emptys, 0); }; + } else { if (addrsrc) { + if (!aggargsrcaddr(c, rhs, "SI")) { + let m4: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m4.ptr, m4.len: u64); + os.exit(1); + }; + emitline("\tMOVQ\t"); + emitoff(sretargoff: i64); + emitline("(BP), BX\n"); + let k: i32 = 0; + for (k + 8 <= scs) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 8; + }; + for (k + 4 <= scs) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 4; + }; + for (k < scs) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff(k: i64); + emitline("(BX)\n"); + k += 1; + }; } else { let rl: *local = localfindnode(c, rhs.str); if (rl != nil) { @@ -28512,7 +28560,7 @@ fn cgreturn(c: *cgen, n: *node) void = { k += 1; }; }; - }; + }; }; // sret return: RAX = dest pointer. emitline("\tMOVQ\t"); emitoff(sretargoff: i64); @@ -28549,12 +28597,21 @@ fn cgreturn(c: *cgen, n: *node) void = { let rsz: i32 = structabisize(rsi); if (rsz <= 24) { let okrhs: bool = false; + // #272: struct ≤24B addressable-source closure — + // N_DOT/N_INDEX/deref memcpy into @retscr before the + // shared structfloatclass tail (mirror cstage cgen.c). + let addrsrc: bool = false; if (rhs.kind == nkind.N_IDENT) { okrhs = true; }; if (rhs.kind == nkind.N_STRUCTLIT) { okrhs = true; }; + if (rhs.kind == nkind.N_DOT) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_INDEX) { okrhs = true; addrsrc = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { okrhs = true; addrsrc = true; }; + }; if (okrhs) { let scroff: i32 = localadd(c, "@retscr", 24, nil); @@ -28577,6 +28634,51 @@ fn cgreturn(c: *cgen, n: *node) void = { // values recurse instead of dropping // trailing bytes. cgstructlitfillbp(c, rsi, rhs, scroff); + } else { if (addrsrc) { + // N_DOT / N_INDEX / deref: land src addr in SI, + // then memcpy rsz bytes into @retscr (#265/#268 shape). + if (!aggargsrcaddr(c, rhs, "SI")) { + let m5: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m5.ptr, m5.len: u64); + os.exit(1); + }; + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= rsz) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= rsz) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; } else { // N_IDENT: word-copy from rhs slot // to scratch. Whole 8B words via @@ -28614,7 +28716,7 @@ fn cgreturn(c: *cgen, n: *node) void = { k += 1; }; }; - }; + }; }; // #171a: float-bearing struct RETURN (return // twin of #165's param recv). A qualifying // struct's float eightbytes ride the SSE return @@ -28683,30 +28785,45 @@ fn cgreturn(c: *cgen, n: *node) void = { // (tinfo.size = sub.size*len) mirrors cstage rt->size. No // structfloatclass (pure-int arrays); N_CALL forward at reg- // class falls to the default cgexpr passthrough below. - if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY - && rhs.kind == nkind.N_IDENT) { + if (c.fnret != nil && c.fnret.kind == nkind.N_TARRAY) { + // #272: array return-by-value source-shape closure. + // Beyond the #267 N_IDENT word-copy, route N_ARRLIT + // (literal fill), N_DOT/N_INDEX/deref (aggargsrcaddr + + // memcpy) into @retscr — the mirror of cstage cgen.c + // N_RETURN ≤24B arm. N_CALL stays on the cgexpr tail (the + // callee already left AX/DX/CX). + let arrok: bool = false; + if (rhs.kind == nkind.N_IDENT) { arrok = true; }; + if (rhs.kind == nkind.N_ARRLIT) { arrok = true; }; + if (rhs.kind == nkind.N_DOT) { arrok = true; }; + if (rhs.kind == nkind.N_INDEX) { arrok = true; }; + if (rhs.kind == nkind.N_UN) { + if (rhs.op == tkind.TK_STAR) { arrok = true; }; + }; let ati: *tinfo = c.fnret.type_: *tinfo; for (ati != nil && ati.kind == tykind.TY_NAMED) { ati = ati.under; }; - if (ati != nil) { + if (arrok && ati != nil) { let rsz: i32 = ati.size: i32; if (rsz <= 24) { - let rl: *local = localfindnode(c, rhs.str); - if (rl != nil) { - let scroff: i32 = localadd(c, "@retscr", 24, nil); - emitline("\tXORQ\tAX, AX\n"); - emitline("\tMOVQ\tAX, "); - emitoff(scroff: i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tAX, "); - emitoff((scroff + 8): i64); - emitline("(BP)\n"); - emitline("\tMOVQ\tAX, "); - emitoff((scroff + 16): i64); - emitline("(BP)\n"); + let scroff: i32 = localadd(c, "@retscr", 24, nil); + emitline("\tXORQ\tAX, AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff(scroff: i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 8): i64); + emitline("(BP)\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + 16): i64); + emitline("(BP)\n"); + if (rhs.kind == nkind.N_IDENT) { + let rl: *local = localfindnode(c, rhs.str); + let roff: i32 = 0; + if (rl != nil) { roff = rl.off; }; let k: i32 = 0; for (k + 8 <= rsz) { emitline("\tMOVQ\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVQ\tAX, "); emitoff((scroff + k): i64); @@ -28715,7 +28832,7 @@ fn cgreturn(c: *cgen, n: *node) void = { }; for (k + 4 <= rsz) { emitline("\tMOVL\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVL\tAX, "); emitoff((scroff + k): i64); @@ -28724,31 +28841,172 @@ fn cgreturn(c: *cgen, n: *node) void = { }; for (k < rsz) { emitline("\tMOVB\t"); - emitoff((rl.off + k): i64); + emitoff((roff + k): i64); emitline("(BP), AX\n"); emitline("\tMOVB\tAX, "); emitoff((scroff + k): i64); emitline("(BP)\n"); k += 1; }; - emitline("\tMOVQ\t"); - emitoff(scroff: i64); - emitline("(BP), AX\n"); - emitline("\tMOVQ\t"); - emitoff((scroff + 8): i64); - emitline("(BP), DX\n"); - emitline("\tMOVQ\t"); - emitoff((scroff + 16): i64); - emitline("(BP), CX\n"); - emitline("\tMOVQ\tBP, SP\n"); - emitline("\tPOPQ\tBP\n"); - emitline("\tRET\n"); - c.lastwasreturn = 1; - return; - }; + } else { if (rhs.kind == nkind.N_ARRLIT) { + // scalar/float element fill; non-scalar + // elements loud-stop (rule 7, no consumer). + let esubti: *tinfo = nil; + if (ati.sub != nil) { esubti = ati.sub; }; + for (esubti != nil && esubti.kind == tykind.TY_NAMED) { esubti = esubti.under; }; + let esz: i32 = 8; + if (esubti != nil) { esz = esubti.size: i32; }; + let badel: bool = false; + if (esubti != nil) { + if (esubti.kind == tykind.TY_STRUCT) { badel = true; }; + if (esubti.kind == tykind.TY_ARRAY) { badel = true; }; + if (esubti.kind == tykind.TY_TUPLE) { badel = true; }; + if (esubti.kind == tykind.TY_SLICE) { badel = true; }; + if (esubti.kind == tykind.TY_STR) { badel = true; }; + }; + if (badel) { + let m2: str = "#272: array-literal return with non-scalar element unsupported (rule 7, no consumer)\n"; + os.write(2, m2.ptr, m2.len: u64); + os.exit(1); + }; + let esub: *node = c.fnret.lhs; + let isfl: bool = isfloattype(c, esub); + let fmov: str = "MOVSD"; + if (isf32type(c, esub)) { fmov = "MOVSS"; }; + let op: str = "MOVQ"; + if (esz == 1) { op = "MOVB"; } else { if (esz == 2) { op = "MOVW"; } else { if (esz == 4) { op = "MOVL"; }; }; }; + 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 { + cgexpr(c, e); + if (isfl) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(op); + emitline("\tAX, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + }; + idx += 1; + e = e.next; + }; + }; + if (repeat) { + let total: i32 = rsz / esz; + for (idx < total) { + if (isfl) { + emitline("\t"); + emitline(fmov); + emitline("\tX0, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + } else { + emitline("\t"); + emitline(op); + emitline("\tAX, "); + emitoff((scroff + idx * esz): i64); + emitline("(BP)\n"); + }; + idx += 1; + }; + }; + } else { + // N_DOT / N_INDEX / deref: land src addr in SI, + // then memcpy rsz bytes into @retscr (#265/#268 + // copy shape). Loud-stop unaddressable sources. + if (!aggargsrcaddr(c, rhs, "SI")) { + let m3: str = "#272: aggregate return from unsupported source kind\n"; + os.write(2, m3.ptr, m3.len: u64); + os.exit(1); + }; + let k: i32 = 0; + for (k + 8 <= rsz) { + emitline("\tMOVQ\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 8; + }; + if (k + 4 <= rsz) { + emitline("\tMOVL\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVL\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 4; + }; + if (k + 2 <= rsz) { + emitline("\tMOVW\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVW\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 2; + }; + if (k + 1 <= rsz) { + emitline("\tMOVB\t"); + emitoff(k: i64); + emitline("(SI), AX\n"); + emitline("\tMOVB\tAX, "); + emitoff((scroff + k): i64); + emitline("(BP)\n"); + k += 1; + }; + }; }; + emitline("\tMOVQ\t"); + emitoff(scroff: i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 8): i64); + emitline("(BP), DX\n"); + emitline("\tMOVQ\t"); + emitoff((scroff + 16): i64); + emitline("(BP), CX\n"); + emitline("\tMOVQ\tBP, SP\n"); + emitline("\tPOPQ\tBP\n"); + emitline("\tRET\n"); + c.lastwasreturn = 1; + return; }; }; }; + // #272 close-by-construction: addressable aggregate-return + // sources (IDENT/STRUCTLIT/ARRLIT/DOT/INDEX/deref) all break in + // the arms above; an aggregate N_CALL passes through cgexpr + // (callee left AX/DX/CX). Any OTHER aggregate rvalue reaching + // here would truncate to AX silently — loud-stop (rule 7), + // mirroring cstage cgen.c N_RETURN. + { + let aggret: bool = false; + if (c.fnret != nil) { + if (c.fnret.kind == nkind.N_TARRAY) { aggret = true; }; + if (c.fnret.kind == nkind.N_TNAME) { + if (structlookup(c, c.fnret.str) != nil) { aggret = true; }; + }; + }; + if (aggret && rhs.kind != nkind.N_CALL) { + let m6: str = "#272: aggregate return reaches scalar default — unclosed shape\n"; + os.write(2, m6.ptr, m6.len: u64); + os.exit(1); + }; + }; cgexpr(c, rhs); } else { // Bare `return;` from a tagged-union-returning fn is diff --git a/test/wcc/949_aggret_source_run.c b/test/wcc/949_aggret_source_run.c new file mode 100644 index 00000000..95c1cb88 --- /dev/null +++ b/test/wcc/949_aggret_source_run.c @@ -0,0 +1,224 @@ +/* + * 949_aggret_source_run — runtime + byte-id net for #272: aggregate + * return-by-value from every ADDRESSABLE source shape, not just the + * #267 N_IDENT / N_STRUCTLIT pair. + * + * Root: the N_RETURN aggregate arms gated the return source on + * N_IDENT || N_STRUCTLIT. Every OTHER aggregate rvalue — an array + * literal (`return [..]`), a struct/array field (`return o.f`, N_DOT), + * an array element (`return a[i]`, N_INDEX), a deref (`return *p`) — + * fell through to the scalar-AX default below = a silent truncation to + * the first 8 bytes. Both stages emitted byte-IDENTICAL wrong asm + * (the byte-id gate alone could NOT catch it — #263 in its purest + * form), so each row asserts the RUNTIME value (full readback: every + * member summed, so a dropped word fails) AND cs==ww byte-id. + * + * Fix (#272 commit-1): both N_RETURN arms (≤24B @retscr and >24B sret) + * funnel N_ARRLIT through the literal element fill and N_DOT/N_INDEX/ + * deref through aggarg_srcaddr + the #265/#268 whole-aggregate copy — + * the return mirror of the arg-side closure #271 landed. Closes the + * class for struct AND array returns; a close-by-construction loud-stop + * guards any future unhandled shape from reaching the scalar default. + * + * Rows: source-kind {array-literal, struct field (N_DOT), array element + * (N_INDEX), deref (*p), named-ident control, >24B deref (sret arm), + * struct field, struct deref} × return. Each callee returns an + * aggregate whose members the caller sums in full. + */ +#include +#include +#include +#include +#include +#include + +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_exit; }; + +static const struct row rows[] = { + /* array literal — `return [1,2,3]` (N_ARRLIT, ≤24B @retscr). */ + { "arrlit", + "package main;\n" + "fn mk() [3]i64 = { return [1i64, 2i64, 3i64]; };\n" + "export fn main() i32 = { let r: [3]i64 = mk();\n" + " return (r[0]+r[1]+r[2]): i32; };\n", 6 }, + /* struct array-field — `return o.a` (N_DOT, ≤24B). */ + { "dot_arrfield", + "package main;\n" + "type box = struct { a: [3]i64 };\n" + "fn mk() [3]i64 = { let o: box = box { a = [10i64, 20i64, 30i64] };\n" + " return o.a; };\n" + "export fn main() i32 = { let r: [3]i64 = mk();\n" + " return (r[0]+r[1]+r[2]): i32; };\n", 60 }, + /* array element — `return g2[1]` (N_INDEX, ≤24B). Source is a + * fully-init global 2D array (the element-store path is a separate + * #270/#273 item; the global static-init is wired). */ + { "index_elem", + "package main;\n" + "let g2: [2][3]i64 = [[1i64,2i64,3i64],[4i64,5i64,6i64]];\n" + "fn mk() [3]i64 = { return g2[1]; };\n" + "export fn main() i32 = { let r: [3]i64 = mk();\n" + " return (r[0]+r[1]+r[2]): i32; };\n", 15 }, + /* deref — `return *p` (N_UN TK_STAR, ≤24B). */ + { "deref", + "package main;\n" + "fn mk(p: *[3]i64) [3]i64 = { return *p; };\n" + "export fn main() i32 = { let a: [3]i64 = [7i64,8i64,9i64];\n" + " let r: [3]i64 = mk(&a); return (r[0]+r[1]+r[2]): i32; };\n", 24 }, + /* named-ident CONTROL — `return a` (N_IDENT, pre-#272 path). */ + { "ident_ctl", + "package main;\n" + "fn mk() [3]i64 = { let a: [3]i64 = [2i64,4i64,6i64]; return a; };\n" + "export fn main() i32 = { let r: [3]i64 = mk();\n" + " return (r[0]+r[1]+r[2]): i32; };\n", 12 }, + /* >24B deref — [4]i64 (32B) rides the sret arm, not @retscr. */ + { "deref_sret", + "package main;\n" + "fn mk(p: *[4]i64) [4]i64 = { return *p; };\n" + "export fn main() i32 = { let a: [4]i64 = [1i64,2i64,3i64,4i64];\n" + " let r: [4]i64 = mk(&a);\n" + " return (r[0]+r[1]+r[2]+r[3]): i32; };\n", 10 }, + /* struct field (N_DOT) returning a STRUCT (not array) — the + * type-agnostic addressable path covers both. */ + { "struct_dot", + "package main;\n" + "type pair = struct { a: i64, b: i64 };\n" + "type box = struct { p: pair };\n" + "fn mk() pair = { let bx: box = box { p = pair { a = 3i64, b = 4i64 } };\n" + " return bx.p; };\n" + "export fn main() i32 = { let r: pair = mk();\n" + " return (r.a + r.b): i32; };\n", 7 }, + /* struct deref (*p) returning a STRUCT. */ + { "struct_deref", + "package main;\n" + "type pair = struct { a: i64, b: i64 };\n" + "fn mk(p: *pair) pair = { return *p; };\n" + "export fn main() i32 = { let x: pair = pair { a = 5i64, b = 9i64 };\n" + " let r: pair = mk(&x); return (r.a + r.b): i32; };\n", 14 }, + { NULL, NULL, 0 } +}; + +static int +slurp_eq(const char *a, const char *b) +{ + FILE *fa = fopen(a, "rb"); + FILE *fb = fopen(b, "rb"); + if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } + int rc = 0; + for (;;) { + int ca = fgetc(fa); + int cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + 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 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, "aggret_source: 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/wwags_%d_%d.ww", getpid(), i); + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(rows[i].src, f); + fclose(f); + + char tmpdir[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwags_%d_d_%d", + getpid(), i); + mkdir(tmpdir, 0755); + + char cmd[2048]; + 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); + + char cs_s[64], ws_s[64]; + snprintf(cs_s, sizeof cs_s, "/tmp/wwags_%d_%d_cs.s", + getpid(), i); + snprintf(ws_s, sizeof ws_s, "/tmp/wwags_%d_%d_ww.s", + getpid(), i); + + 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 aggret-source tests failed\n", + fail, n); + return 1; + } + printf("aggret_source: %d/%d ok (cstage run + cs==ww byte-id)\n", + n, n); + return 0; +}