diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index ac37b4a3..41b950b3 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -57,6 +57,11 @@ static int cg_tupfscr; * arg); 0 means "not yet allocated", cg_tupargscr_sz the cached width. */ static int cg_tupargscr; static int cg_tupargscr_sz; +/* #271: per-fn @aggargscr scratch for a >24B (sret-class) aggregate + * arg sourced from a CALL — the result is sret'd here, then pushed + * word-by-word into the arg convention. 0 = not yet allocated. */ +static int cg_aggargscr; +static int cg_aggargscr_sz; /* Per-fn @-prefix scratch SSoT (task #26, follow-up to #15-cstage's * @retscr). Pre-#26 each site allocated a labelseq-stamped fresh slot * per call (mklabel "tagbase" / "tagscr" / "argscr" / "idxscr"); the @@ -598,6 +603,28 @@ node_isstructarg(Node *n) return sz > 0 && sz <= 16; } +/* aggarg_size — byte size of a by-value aggregate (struct OR array) + * call arg, else 0. The size axis the ≤16B-struct node_isstructarg + * carve-out doesn't cover: arrays of any size and structs > 16B (#271). + * Pure-int transport only; a float-bearing struct keeps the #165 SSE + * eightbyte path (gated separately at the push/drain sites). */ +static int +aggarg_size(Type *t) +{ + if (t == NULL) return 0; + if (t->kind == TY_NAMED) t = t->under; + if (t == NULL) return 0; + if (t->kind == TY_STRUCT || t->kind == TY_ARRAY) + return (int)t->size; + return 0; +} + +static int +node_isaggarg(Node *n) +{ + return n && aggarg_size(n->type) > 0; +} + /* Pick the appropriate scalar SSE opcode (SS vs SD) for a node's * float type. Untyped float defaults to SD. */ static int @@ -1852,6 +1879,70 @@ cg_dotbase_addr(Cg *c, Node *base, int dst_reg, Local *locals) } return 1; } +/* aggarg_srcaddr — land the ADDRESS of an addressable aggregate arg + * source in `dst`, reusing the closed #265/#268 let-init-copy dispatch: + * ident/global slot (LEAQ), deref operand (cgexpr of the pointer), + * N_DOT field (cg_dotchain_addr, #253), N_INDEX element (the &base[i] + * spine, #252/#270). Returns 0 for a source kind not covered (caller + * loud-stops, rule 7). The CALL source is handled separately at the + * push site (receive-to-regs / sret-to-scratch). */ +static int +aggarg_srcaddr(Cg *c, Node *src, int dst, Local *locals) +{ + if (src->kind == N_UN && src->op == TK_STAR) { + cgexpr(c, src->lhs, locals); + if (dst != D_AX) + ins2(c, A_MOVQ, areg(D_AX), areg(dst)); + return 1; + } + if (src->kind == N_IDENT) { + int soff = localfind(locals, src->str); + if (soff != 0) { + ins2(c, A_LEAQ, amem(D_BP, soff), areg(dst)); + return 1; + } + /* global value source. Gated to a module-`let` (let_islet, + * the wwstage letvartnode twin); a const array/struct `def` + * aggregate ARG is untested and out of scope (#274) — both + * stages fall through to the caller's loud-stop, aligned DOWN + * to the leaner wwstage per rule-10. */ + if (let_islet(src->str)) { + ins2(c, A_LEAQ, masym(c, src->str), areg(dst)); + return 1; + } + return 0; + } + if (src->kind == N_DOT) + return cg_dotchain_addr(c, src, dst, locals); + if (src->kind == N_INDEX) { + Node *base = src->lhs; + Node *idx = src->rhs; + Type *bt = base ? base->type : NULL; + Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + if (base && base->kind == N_IDENT && bu + && bu->kind == TY_ARRAY) { + int esz = (bu->sub) ? (int)bu->sub->size : 1; + cgexpr(c, idx, locals); + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), areg(D_CX)); + ins2(c, A_IMULQ, areg(D_CX), areg(D_AX)); + } + int boff = localfind(locals, base->str); + if (boff != 0) + ins2(c, A_LEAQ, amem(D_BP, boff), areg(D_BX)); + else + ins2(c, A_LEAQ, masym(c, base->str), + areg(D_BX)); + ins2(c, A_ADDQ, areg(D_BX), areg(D_AX)); + if (dst != D_AX) + ins2(c, A_MOVQ, areg(D_AX), areg(dst)); + return 1; + } + return 0; + } + return 0; +} + /* cg_structlit_fill modes — see helper docstring. */ enum { DST_BP = 0, @@ -6397,6 +6488,103 @@ cgexpr(Cg *c, Node *n, Local *locals) } continue; } + /* #271: aggregate (struct/array) arg from any source the + * ≤16B-struct-IDENT fast path above doesn't cover — a + * 16B struct from a non-ident source, OR any array, OR a + * struct > 16B. The arg twin of the #265/#268 let-init + * copy: materialise the source's ADDRESS in SI and push + * its ceil(sz/8) words high→low (the pop drains word0 + * into the first arg reg). A CALL source receives first — + * ≤24B in AX/DX/CX pushed straight, >24B sret'd into + * @aggargscr then pushed from there. Pre-fix every such + * source fell to the scalar default (one PUSHQ for a + * multi-word aggregate) and stack-imbalanced against the + * type-based multi-word drain. */ + if (!widen[i] && node_isaggarg(args[i]) + && !(node_isstructarg(args[i]) + && args[i]->kind == N_IDENT)) { + int aggsz = aggarg_size(args[i]->type); + int nwords = (aggsz + 7) / 8; + /* A float-bearing ≤16B struct from a non-ident + * source would need the #165 SSE eightbyte + * transport the GP push/drain here can't model — + * loud-stop rather than silently GP-pass it (a + * ≤16B struct with any float field; the wwstage + * tinfo mirror uses the same predicate). */ + { + Type *st = args[i]->type; + if (st && st->kind == TY_NAMED) + st = st->under; + if (st && st->kind == TY_STRUCT + && st->size <= 16) { + int f32; + for (Tfield *f = st->fields; f; + f = f->next) + if (fld_isfloat(f->type, + &f32)) + fatal("#271/#165: " + "float-bearing " + "struct arg from a " + "non-ident source " + "needs SSE eightbyte " + "transport (out of " + "scope)"); + } + } + if (args[i]->kind == N_CALL) { + if (cg_sret_retsize(args[i]->type) > 0) { + if (cg_aggargscr == 0) { + cg_aggargscr = + local_alloc(c, &locals, + "@aggargscr", aggsz, + cg_frame); + cg_aggargscr_sz = aggsz; + } else if (aggsz > + cg_aggargscr_sz) { + fatal("cgcall: @aggargscr " + "cached sz %d, need %d " + "(#271 pinned offset " + "can't grow)", + cg_aggargscr_sz, + aggsz); + } + cg_sret_dest_off = cg_aggargscr; + cgexpr(c, args[i], locals); + cg_sret_dest_off = 0; + for (int k = nwords - 1; k >= 0; + k--) { + ins2(c, A_MOVQ, + amem(D_BP, + cg_aggargscr + k*8), + areg(D_AX)); + ins1(c, A_PUSHQ, + areg(D_AX)); + } + } else { + /* ≤24B: producer left AX=word0, + * DX=word1, CX=word2. Push + * high→low so the pop drains + * word0 first. */ + int rr[3] = { D_AX, D_DX, D_CX }; + cgexpr(c, args[i], locals); + for (int k = nwords - 1; k >= 0; + k--) + ins1(c, A_PUSHQ, + areg(rr[k])); + } + continue; + } + if (!aggarg_srcaddr(c, args[i], D_SI, locals)) + fatal("#271: aggregate arg from " + "unsupported source kind %d", + args[i]->kind); + for (int k = nwords - 1; k >= 0; k--) { + ins2(c, A_MOVQ, amem(D_SI, k*8), + areg(D_AX)); + ins1(c, A_PUSHQ, areg(D_AX)); + } + continue; + } if (widen[i]) { /* Concrete → tagged-union widening at the call * site. Mirrors the let/assign/return widening: @@ -6687,6 +6875,24 @@ cgexpr(Cg *c, Node *n, Local *locals) stackslots++; } } + } else if (node_isaggarg(args[i]) + && !node_isstructarg(args[i])) { + /* #271: array / >16B-struct aggregate arg — + * drain its ceil(sz/8) staged words into the + * INTEGER arg cursor (overflow spills to the + * stack, reached by the callee via positive BP + * offsets). The ≤16B struct case stays in + * node_isstructarg above (SSE class path + * intact). */ + int aggsz = aggarg_size(args[i]->type); + int nw = (aggsz + 7) / 8; + for (int k = 0; k < nw; k++) { + if (ii < 6) + ins1(c, A_POPQ, + areg(sysv_argregs[ii++])); + else + stackslots++; + } } else if (node_istaggedarg(args[i])) { int sz = tagged_arg_size(args[i]->type); int eb = sz / 8; @@ -10284,6 +10490,8 @@ cgfn(Cg *c, FILE *out, Node *fn) cg_tupfscr = 0; cg_tupargscr = 0; cg_tupargscr_sz = 0; + cg_aggargscr = 0; + cg_aggargscr_sz = 0; cg_tagbase = 0; cg_tagbase_sz = 0; cg_tagscr = 0; @@ -10343,6 +10551,13 @@ cgfn(Cg *c, FILE *out, Node *fn) int slice = (pu && pu->kind == TY_SLICE); int is_str = type_isstr(pt); int is_struct = pu && pu->kind == TY_STRUCT && pu->size <= 16; + /* #271: a by-value array param, or a struct param > 16B — + * received as ceil(sz/8) GP eightbytes, the callee twin of the + * generalised aggregate-arg push. The ≤16B struct keeps its own + * (possibly SSE-classified) path above. */ + int is_bigagg = pu && ((pu->kind == TY_ARRAY) + || (pu->kind == TY_STRUCT && pu->size > 16)); + int agg_eb = is_bigagg ? (int)((pu->size + 7) / 8) : 0; int tagged_sz = tagged_arg_size(pt); int is_tagged = tagged_sz > 0; int isf = cg_isfloat(pt); @@ -10448,7 +10663,8 @@ cgfn(Cg *c, FILE *out, Node *fn) * — the caller pushes the triple (#1/Phase 3). */ int eightbytes = (slice || is_str) ? 3 : (is_struct ? struct_eb : - (is_tagged ? tagged_eb : 1)); + (is_bigagg ? agg_eb : + (is_tagged ? tagged_eb : 1))); int regs_left = isf ? (8 - fargi) : (6 - argi); if (regs_left >= eightbytes) { /* #60: route slice/str slot widths through Type.size SSoT @@ -10456,9 +10672,10 @@ cgfn(Cg *c, FILE *out, Node *fn) * this site (or its stack-stitch mirror below). */ int sz = (slice || is_str) ? (int)pu->size : (is_struct ? (int)pu->size : - (is_tagged ? tagged_sz : 8)); + (is_bigagg ? (int)pu->size : + (is_tagged ? tagged_sz : 8))); int off = localoff(c, &locals, p->str, sz, &frame); - if (slice || is_str || is_struct || is_tagged) { + if (slice || is_str || is_struct || is_bigagg || is_tagged) { for (int k = 0; k < eightbytes; k++, argi++) ins2(c, A_MOVQ, areg(sysv_argregs[argi]), @@ -10476,7 +10693,7 @@ cgfn(Cg *c, FILE *out, Node *fn) argi++; } } else if (eightbytes > 1 && regs_left > 0 && - (slice || is_str || is_struct || is_tagged)) { + (slice || is_str || is_struct || is_bigagg || is_tagged)) { /* Multi-word arg that partially fits in regs: caller * filled (regs_left) registers greedily, the rest spilled * to stack at positive BP offsets. Stitch a single local @@ -10486,7 +10703,8 @@ cgfn(Cg *c, FILE *out, Node *fn) /* #60: same SSoT routing as the regs-fit arm above. */ int sz = (slice || is_str) ? (int)pu->size : (is_struct ? (int)pu->size : - (is_tagged ? tagged_sz : 8)); + (is_bigagg ? (int)pu->size : + (is_tagged ? tagged_sz : 8))); int off = localoff(c, &locals, p->str, sz, &frame); extern int cg_stack_arg_cursor; int k = 0; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 8c196277..5156fc57 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -15976,6 +15976,93 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { }; }; }; + // #271: aggregate (struct/array) arg from any source the ≤16B + // struct-IDENT fast path above doesn't cover — a 16B struct from a + // non-ident source, OR any array, OR a struct > 16B. The arg twin + // of the #265/#268 let-init copy (mirror of cstage cgen.c #271 push + // arm): materialise the source ADDRESS in SI and push its ceil(sz/8) + // words high→low (the pop drains word0 into the first arg reg). A + // CALL source receives first — ≤24B in AX/DX/CX pushed straight, + // >24B sret'd into @aggargscr then pushed from there. Pre-fix every + // such source fell to the scalar default (one PUSHQ for a multi-word + // aggregate) and stack-imbalanced against the type-based drain. + let aggsz: i32 = aggargsizetn(arg.type_: *tinfo); + if (aggsz > 0) { + // Exclude a ≤16B-struct IDENT — it owns the structparamsize + // fast path above (or, when a cross-module same-leaf collision + // makes the name-keyed structparamsize miss it, the scalar + // default below, byte-id with cstage's 1-word struct push; + // #784/#223). The exclusion is TYPE-keyed via the stamped + // tinfo, mirroring cstage node_isstructarg (struct_arg_size on + // args[i]->type) — a name-keyed gate here re-opens the #211/#13 + // name-keyed divergence the cstage type gate doesn't have. + let structident: bool = false; + if (arg.kind == nkind.N_IDENT) { + let st: *tinfo = arg.type_: *tinfo; + for (st != nil && st.kind == tykind.TY_NAMED) { + st = st.under; + }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + if (st.size: i32 <= 16) { structident = true; }; + }; }; + }; + if (!structident) { + if (aggargfloatstop(arg)) { + let msg: str = "#271/#165: float-bearing struct arg from a non-ident source needs SSE eightbyte transport (out of scope)\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + let nwords: i32 = (aggsz + 7) / 8; + if (arg.kind == nkind.N_CALL) { + if (callsretsize(c, arg) > 0) { + let scr: i32 = localadd(c, "@aggargscr", + aggsz, nil); + c.sretdestoff = scr; + cgexpr(c, arg); + c.sretdestoff = 0; + let k: i32 = nwords - 1; + for (k >= 0) { + emitline("\tMOVQ\t"); + emitoff((scr + k*8): i64); + emitline("(BP), AX\n"); + emitline("\tPUSHQ\tAX\n"); + k -= 1; + }; + } else { + // ≤24B: producer left AX=word0, + // DX=word1, CX=word2. Push high→low so + // the pop drains word0 first. + cgexpr(c, arg); + let k: i32 = nwords - 1; + for (k >= 0) { + if (k == 2) { + emitline("\tPUSHQ\tCX\n"); + } else { if (k == 1) { + emitline("\tPUSHQ\tDX\n"); + } else { + emitline("\tPUSHQ\tAX\n"); + }; }; + k -= 1; + }; + }; + return rest + nwords; + }; + if (!aggargsrcaddr(c, arg, "SI")) { + let msg: str = "#271: aggregate arg from unsupported source kind\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + let k: i32 = nwords - 1; + for (k >= 0) { + emitline("\tMOVQ\t"); + emitoff((k*8): i64); + emitline("(SI), AX\n"); + emitline("\tPUSHQ\tAX\n"); + k -= 1; + }; + return rest + nwords; + }; + }; // Float arg: cgexpr leaves the value in X0. Push 8 bytes from // X0 via SUBQ+MOVSD so cgcall's pop side can drain into the // XMM stream (X0..X7). f32 still occupies 8B on the stack — @@ -17562,6 +17649,47 @@ fn structparamsize(c: *cgen, t: *node) i32 = { return si.totsize; }; +// aggargsize — byte size of a by-value aggregate (struct OR array) call +// arg, else 0 (#271, mirror of cstage aggarg_size). The size axis the +// ≤16B-struct structparamsize carve-out doesn't cover: arrays of any +// size and structs > 16B. Reads the stamped tinfo size (the type table, +// byte-id with cstage Type.size). +fn aggargsizetn(t: *tinfo) i32 = { + if (t == nil) { return 0; }; + let u: *tinfo = t; + for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (u == nil) { return 0; }; + if (u.kind == tykind.TY_STRUCT || u.kind == tykind.TY_ARRAY) { + return u.size: i32; + }; + return 0; +}; + +fn nodeisaggarg(n: *node) bool = { + if (n == nil) { return false; }; + return aggargsizetn(n.type_: *tinfo) > 0; +}; + +// aggargfloatstop — true iff the arg is a ≤16B struct with any float +// field (#271/#165). Such a struct from a non-ident source would need +// the SSE eightbyte transport the GP aggregate push/drain can't model; +// both stages loud-stop on it. Same predicate as the cstage Tfield +// fld_isfloat walk (cmd/w6c/cgen.c #271 push arm). +fn aggargfloatstop(n: *node) bool = { + if (n == nil) { return false; }; + let st: *tinfo = n.type_: *tinfo; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st == nil) { return false; }; + if (st.kind != tykind.TY_STRUCT) { return false; }; + if (st.size: i32 > 16) { return false; }; + let f: *tfield = st.fields; + for (f != nil) { + if (typeisfloat(f.type_)) { return true; }; + f = f.tnext; + }; + return false; +}; + // structfloatclass — SysV per-eightbyte classification for the #165 // float-bearing-struct param case (param twin of #171's struct return; // classifies per-eightbyte, not #163's per-element). Returns 0 when the @@ -20626,6 +20754,92 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { return true; }; +// aggargsrcaddr — land the ADDRESS of an addressable aggregate (struct/ +// array) call-arg source in dstreg (#271, mirror of cstage +// aggarg_srcaddr). Reuses the closed #265/#268 let-init-copy dispatch: +// local ident slot (LEAQ off(BP)), module-let global (LEAQ name(SB)), +// deref operand (cgexpr of the pointer), N_DOT field (dotchainaddr, +// #253), N_INDEX element of an N_IDENT array base (the &base[i] spine, +// #252/#270). Returns false for an uncovered source kind (caller loud- +// stops, rule 7). The CALL source is handled at the push site. +fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = { + if (src.kind == nkind.N_UN) { + if (src.op == tkind.TK_STAR) { + cgexpr(c, src.lhs); + if (!streq(dst, "AX")) { + emitline("\tMOVQ\tAX, "); + emitline(dst); + emitline("\n"); + }; + return true; + }; + }; + if (src.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, src.str); + if (lc != nil) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + // global value source. Gated to a module-`let` (letvartnode, + // the cstage let_islet twin); a const array/struct `def` + // aggregate ARG is untested + out of scope (#274; both stages + // loud-stop, rule-10 aligned). + if (letvartnode(c, src.str) != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, src.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + return true; + }; + return false; + }; + if (src.kind == nkind.N_DOT) { + return dotchainaddr(c, src, dst); + }; + if (src.kind == nkind.N_INDEX) { + let base: *node = src.lhs; + let idx: *node = src.rhs; + if (base == nil) { return false; }; + if (base.kind != nkind.N_IDENT) { return false; }; + let bu: *tinfo = base.type_: *tinfo; + for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu == nil) { return false; }; + if (bu.kind != tykind.TY_ARRAY) { return false; }; + let esz: i32 = 1; + if (bu.sub != nil) { esz = bu.sub.size: i32; }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + let boff: *local = localfindnode(c, base.str); + if (boff != nil) { + emitline("\tLEAQ\t"); + emitoff(boff.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), BX\n"); + }; + emitline("\tADDQ\tBX, AX\n"); + if (!streq(dst, "AX")) { + emitline("\tMOVQ\tAX, "); + emitline(dst); + emitline("\n"); + }; + return true; + }; + return false; +}; + fn cgindex(c: *cgen, n: *node) void = { // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast @@ -24004,6 +24218,13 @@ fn cgcall(c: *cgen, n: *node) void = { // words and shift intidx out of sync. let tcs: i32 = taggedcallslot(c, a); if (tcs > 0) { extra = tcs / 8 - 1; }; + // #271: array / >16B-struct / non-ident 16B-struct + // aggregate arg — pushargsrev staged ceil(sz/8) words; + // drain exactly that many so intidx tracks per-arg + // (the ≤16B struct IDENT case is the stfc branch + // above). Mirror of cstage node_isaggarg drain arm. + let aggsz: i32 = aggargsizetn(a.type_: *tinfo); + if (aggsz > 0) { extra = (aggsz + 7) / 8 - 1; }; let words: i32 = 1 + extra; let w: i32 = 0; for (w < words) { @@ -30870,18 +31091,66 @@ fn cgfnparams(c: *cgen, params: *node) void = { localaddstack(c, nm, p.lhs, 16 + stkcursor*8); stkcursor += nw; };}; - } else { - if (idx < 6) { - let off: i32 = localadd(c, nm, 8, p.lhs); - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - idx += 1; + } else { let aggsz2: i32 = aggargsizetn(p.lhs.type_: *tinfo); + if (aggsz2 > 0) { + // #271: array / >16B-struct by-value param — + // received as ceil(sz/8) GP eightbytes, the + // callee twin of the generalised aggregate-arg + // push. Mirror of the cstage is_bigagg arm + // (regs-fit / partial-stitch / stack-spill). + let nw2: i32 = (aggsz2 + 7) / 8; + if (idx + nw2 <= 6) { + let off: i32 = localadd(c, nm, aggsz2, p.lhs); + let w: i32 = 0; + for (w < nw2) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + } else { if (idx < 6) { + let off: i32 = localadd(c, nm, aggsz2, p.lhs); + let regs_left: i32 = 6 - idx; + let w: i32 = 0; + for (w < regs_left) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + for (w < nw2) { + emitline("\tMOVQ\t"); + emitoff((16 + stkcursor*8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + stkcursor += 1; + w += 1; + }; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += nw2; + };}; } else { - localaddstack(c, nm, p.lhs, 16 + stkcursor*8); - stkcursor += 1; + if (idx < 6) { + let off: i32 = localadd(c, nm, 8, p.lhs); + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + idx += 1; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += 1; + }; }; }; };};}; diff --git a/selfhost/cmd/wcc/cgendecl.ww b/selfhost/cmd/wcc/cgendecl.ww index bcf7f7c9..341ff4b9 100644 --- a/selfhost/cmd/wcc/cgendecl.ww +++ b/selfhost/cmd/wcc/cgendecl.ww @@ -424,18 +424,66 @@ fn cgfnparams(c: *cgen, params: *node) void = { localaddstack(c, nm, p.lhs, 16 + stkcursor*8); stkcursor += nw; };}; - } else { - if (idx < 6) { - let off: i32 = localadd(c, nm, 8, p.lhs); - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - idx += 1; + } else { let aggsz2: i32 = aggargsizetn(p.lhs.type_: *tinfo); + if (aggsz2 > 0) { + // #271: array / >16B-struct by-value param — + // received as ceil(sz/8) GP eightbytes, the + // callee twin of the generalised aggregate-arg + // push. Mirror of the cstage is_bigagg arm + // (regs-fit / partial-stitch / stack-spill). + let nw2: i32 = (aggsz2 + 7) / 8; + if (idx + nw2 <= 6) { + let off: i32 = localadd(c, nm, aggsz2, p.lhs); + let w: i32 = 0; + for (w < nw2) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + } else { if (idx < 6) { + let off: i32 = localadd(c, nm, aggsz2, p.lhs); + let regs_left: i32 = 6 - idx; + let w: i32 = 0; + for (w < regs_left) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + for (w < nw2) { + emitline("\tMOVQ\t"); + emitoff((16 + stkcursor*8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + stkcursor += 1; + w += 1; + }; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += nw2; + };}; } else { - localaddstack(c, nm, p.lhs, 16 + stkcursor*8); - stkcursor += 1; + if (idx < 6) { + let off: i32 = localadd(c, nm, 8, p.lhs); + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + idx += 1; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += 1; + }; }; }; };};}; diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 3b8fa24e..9a9c4b70 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1128,6 +1128,92 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { return true; }; +// aggargsrcaddr — land the ADDRESS of an addressable aggregate (struct/ +// array) call-arg source in dstreg (#271, mirror of cstage +// aggarg_srcaddr). Reuses the closed #265/#268 let-init-copy dispatch: +// local ident slot (LEAQ off(BP)), module-let global (LEAQ name(SB)), +// deref operand (cgexpr of the pointer), N_DOT field (dotchainaddr, +// #253), N_INDEX element of an N_IDENT array base (the &base[i] spine, +// #252/#270). Returns false for an uncovered source kind (caller loud- +// stops, rule 7). The CALL source is handled at the push site. +fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = { + if (src.kind == nkind.N_UN) { + if (src.op == tkind.TK_STAR) { + cgexpr(c, src.lhs); + if (!streq(dst, "AX")) { + emitline("\tMOVQ\tAX, "); + emitline(dst); + emitline("\n"); + }; + return true; + }; + }; + if (src.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, src.str); + if (lc != nil) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + // global value source. Gated to a module-`let` (letvartnode, + // the cstage let_islet twin); a const array/struct `def` + // aggregate ARG is untested + out of scope (#274; both stages + // loud-stop, rule-10 aligned). + if (letvartnode(c, src.str) != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, src.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + return true; + }; + return false; + }; + if (src.kind == nkind.N_DOT) { + return dotchainaddr(c, src, dst); + }; + if (src.kind == nkind.N_INDEX) { + let base: *node = src.lhs; + let idx: *node = src.rhs; + if (base == nil) { return false; }; + if (base.kind != nkind.N_IDENT) { return false; }; + let bu: *tinfo = base.type_: *tinfo; + for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu == nil) { return false; }; + if (bu.kind != tykind.TY_ARRAY) { return false; }; + let esz: i32 = 1; + if (bu.sub != nil) { esz = bu.sub.size: i32; }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + let boff: *local = localfindnode(c, base.str); + if (boff != nil) { + emitline("\tLEAQ\t"); + emitoff(boff.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), BX\n"); + }; + emitline("\tADDQ\tBX, AX\n"); + if (!streq(dst, "AX")) { + emitline("\tMOVQ\tAX, "); + emitline(dst); + emitline("\n"); + }; + return true; + }; + return false; +}; + fn cgindex(c: *cgen, n: *node) void = { // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast @@ -4506,6 +4592,13 @@ fn cgcall(c: *cgen, n: *node) void = { // words and shift intidx out of sync. let tcs: i32 = taggedcallslot(c, a); if (tcs > 0) { extra = tcs / 8 - 1; }; + // #271: array / >16B-struct / non-ident 16B-struct + // aggregate arg — pushargsrev staged ceil(sz/8) words; + // drain exactly that many so intidx tracks per-arg + // (the ≤16B struct IDENT case is the stfc branch + // above). Mirror of cstage node_isaggarg drain arm. + let aggsz: i32 = aggargsizetn(a.type_: *tinfo); + if (aggsz > 0) { extra = (aggsz + 7) / 8 - 1; }; let words: i32 = 1 + extra; let w: i32 = 0; for (w < words) { diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 65074cfc..21619605 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -466,6 +466,93 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { }; }; }; + // #271: aggregate (struct/array) arg from any source the ≤16B + // struct-IDENT fast path above doesn't cover — a 16B struct from a + // non-ident source, OR any array, OR a struct > 16B. The arg twin + // of the #265/#268 let-init copy (mirror of cstage cgen.c #271 push + // arm): materialise the source ADDRESS in SI and push its ceil(sz/8) + // words high→low (the pop drains word0 into the first arg reg). A + // CALL source receives first — ≤24B in AX/DX/CX pushed straight, + // >24B sret'd into @aggargscr then pushed from there. Pre-fix every + // such source fell to the scalar default (one PUSHQ for a multi-word + // aggregate) and stack-imbalanced against the type-based drain. + let aggsz: i32 = aggargsizetn(arg.type_: *tinfo); + if (aggsz > 0) { + // Exclude a ≤16B-struct IDENT — it owns the structparamsize + // fast path above (or, when a cross-module same-leaf collision + // makes the name-keyed structparamsize miss it, the scalar + // default below, byte-id with cstage's 1-word struct push; + // #784/#223). The exclusion is TYPE-keyed via the stamped + // tinfo, mirroring cstage node_isstructarg (struct_arg_size on + // args[i]->type) — a name-keyed gate here re-opens the #211/#13 + // name-keyed divergence the cstage type gate doesn't have. + let structident: bool = false; + if (arg.kind == nkind.N_IDENT) { + let st: *tinfo = arg.type_: *tinfo; + for (st != nil && st.kind == tykind.TY_NAMED) { + st = st.under; + }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + if (st.size: i32 <= 16) { structident = true; }; + }; }; + }; + if (!structident) { + if (aggargfloatstop(arg)) { + let msg: str = "#271/#165: float-bearing struct arg from a non-ident source needs SSE eightbyte transport (out of scope)\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + let nwords: i32 = (aggsz + 7) / 8; + if (arg.kind == nkind.N_CALL) { + if (callsretsize(c, arg) > 0) { + let scr: i32 = localadd(c, "@aggargscr", + aggsz, nil); + c.sretdestoff = scr; + cgexpr(c, arg); + c.sretdestoff = 0; + let k: i32 = nwords - 1; + for (k >= 0) { + emitline("\tMOVQ\t"); + emitoff((scr + k*8): i64); + emitline("(BP), AX\n"); + emitline("\tPUSHQ\tAX\n"); + k -= 1; + }; + } else { + // ≤24B: producer left AX=word0, + // DX=word1, CX=word2. Push high→low so + // the pop drains word0 first. + cgexpr(c, arg); + let k: i32 = nwords - 1; + for (k >= 0) { + if (k == 2) { + emitline("\tPUSHQ\tCX\n"); + } else { if (k == 1) { + emitline("\tPUSHQ\tDX\n"); + } else { + emitline("\tPUSHQ\tAX\n"); + }; }; + k -= 1; + }; + }; + return rest + nwords; + }; + if (!aggargsrcaddr(c, arg, "SI")) { + let msg: str = "#271: aggregate arg from unsupported source kind\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + let k: i32 = nwords - 1; + for (k >= 0) { + emitline("\tMOVQ\t"); + emitoff((k*8): i64); + emitline("(SI), AX\n"); + emitline("\tPUSHQ\tAX\n"); + k -= 1; + }; + return rest + nwords; + }; + }; // Float arg: cgexpr leaves the value in X0. Push 8 bytes from // X0 via SUBQ+MOVSD so cgcall's pop side can drain into the // XMM stream (X0..X7). f32 still occupies 8B on the stack — @@ -2052,6 +2139,47 @@ fn structparamsize(c: *cgen, t: *node) i32 = { return si.totsize; }; +// aggargsize — byte size of a by-value aggregate (struct OR array) call +// arg, else 0 (#271, mirror of cstage aggarg_size). The size axis the +// ≤16B-struct structparamsize carve-out doesn't cover: arrays of any +// size and structs > 16B. Reads the stamped tinfo size (the type table, +// byte-id with cstage Type.size). +fn aggargsizetn(t: *tinfo) i32 = { + if (t == nil) { return 0; }; + let u: *tinfo = t; + for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (u == nil) { return 0; }; + if (u.kind == tykind.TY_STRUCT || u.kind == tykind.TY_ARRAY) { + return u.size: i32; + }; + return 0; +}; + +fn nodeisaggarg(n: *node) bool = { + if (n == nil) { return false; }; + return aggargsizetn(n.type_: *tinfo) > 0; +}; + +// aggargfloatstop — true iff the arg is a ≤16B struct with any float +// field (#271/#165). Such a struct from a non-ident source would need +// the SSE eightbyte transport the GP aggregate push/drain can't model; +// both stages loud-stop on it. Same predicate as the cstage Tfield +// fld_isfloat walk (cmd/w6c/cgen.c #271 push arm). +fn aggargfloatstop(n: *node) bool = { + if (n == nil) { return false; }; + let st: *tinfo = n.type_: *tinfo; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st == nil) { return false; }; + if (st.kind != tykind.TY_STRUCT) { return false; }; + if (st.size: i32 > 16) { return false; }; + let f: *tfield = st.fields; + for (f != nil) { + if (typeisfloat(f.type_)) { return true; }; + f = f.tnext; + }; + return false; +}; + // structfloatclass — SysV per-eightbyte classification for the #165 // float-bearing-struct param case (param twin of #171's struct return; // classifies per-eightbyte, not #163's per-element). Returns 0 when the diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 5808e10d..986b6db8 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -15976,6 +15976,93 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { }; }; }; + // #271: aggregate (struct/array) arg from any source the ≤16B + // struct-IDENT fast path above doesn't cover — a 16B struct from a + // non-ident source, OR any array, OR a struct > 16B. The arg twin + // of the #265/#268 let-init copy (mirror of cstage cgen.c #271 push + // arm): materialise the source ADDRESS in SI and push its ceil(sz/8) + // words high→low (the pop drains word0 into the first arg reg). A + // CALL source receives first — ≤24B in AX/DX/CX pushed straight, + // >24B sret'd into @aggargscr then pushed from there. Pre-fix every + // such source fell to the scalar default (one PUSHQ for a multi-word + // aggregate) and stack-imbalanced against the type-based drain. + let aggsz: i32 = aggargsizetn(arg.type_: *tinfo); + if (aggsz > 0) { + // Exclude a ≤16B-struct IDENT — it owns the structparamsize + // fast path above (or, when a cross-module same-leaf collision + // makes the name-keyed structparamsize miss it, the scalar + // default below, byte-id with cstage's 1-word struct push; + // #784/#223). The exclusion is TYPE-keyed via the stamped + // tinfo, mirroring cstage node_isstructarg (struct_arg_size on + // args[i]->type) — a name-keyed gate here re-opens the #211/#13 + // name-keyed divergence the cstage type gate doesn't have. + let structident: bool = false; + if (arg.kind == nkind.N_IDENT) { + let st: *tinfo = arg.type_: *tinfo; + for (st != nil && st.kind == tykind.TY_NAMED) { + st = st.under; + }; + if (st != nil) { if (st.kind == tykind.TY_STRUCT) { + if (st.size: i32 <= 16) { structident = true; }; + }; }; + }; + if (!structident) { + if (aggargfloatstop(arg)) { + let msg: str = "#271/#165: float-bearing struct arg from a non-ident source needs SSE eightbyte transport (out of scope)\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + let nwords: i32 = (aggsz + 7) / 8; + if (arg.kind == nkind.N_CALL) { + if (callsretsize(c, arg) > 0) { + let scr: i32 = localadd(c, "@aggargscr", + aggsz, nil); + c.sretdestoff = scr; + cgexpr(c, arg); + c.sretdestoff = 0; + let k: i32 = nwords - 1; + for (k >= 0) { + emitline("\tMOVQ\t"); + emitoff((scr + k*8): i64); + emitline("(BP), AX\n"); + emitline("\tPUSHQ\tAX\n"); + k -= 1; + }; + } else { + // ≤24B: producer left AX=word0, + // DX=word1, CX=word2. Push high→low so + // the pop drains word0 first. + cgexpr(c, arg); + let k: i32 = nwords - 1; + for (k >= 0) { + if (k == 2) { + emitline("\tPUSHQ\tCX\n"); + } else { if (k == 1) { + emitline("\tPUSHQ\tDX\n"); + } else { + emitline("\tPUSHQ\tAX\n"); + }; }; + k -= 1; + }; + }; + return rest + nwords; + }; + if (!aggargsrcaddr(c, arg, "SI")) { + let msg: str = "#271: aggregate arg from unsupported source kind\n"; + os.write(2, msg.ptr, msg.len: u64); + os.exit(1); + }; + let k: i32 = nwords - 1; + for (k >= 0) { + emitline("\tMOVQ\t"); + emitoff((k*8): i64); + emitline("(SI), AX\n"); + emitline("\tPUSHQ\tAX\n"); + k -= 1; + }; + return rest + nwords; + }; + }; // Float arg: cgexpr leaves the value in X0. Push 8 bytes from // X0 via SUBQ+MOVSD so cgcall's pop side can drain into the // XMM stream (X0..X7). f32 still occupies 8B on the stack — @@ -17562,6 +17649,47 @@ fn structparamsize(c: *cgen, t: *node) i32 = { return si.totsize; }; +// aggargsize — byte size of a by-value aggregate (struct OR array) call +// arg, else 0 (#271, mirror of cstage aggarg_size). The size axis the +// ≤16B-struct structparamsize carve-out doesn't cover: arrays of any +// size and structs > 16B. Reads the stamped tinfo size (the type table, +// byte-id with cstage Type.size). +fn aggargsizetn(t: *tinfo) i32 = { + if (t == nil) { return 0; }; + let u: *tinfo = t; + for (u != nil && u.kind == tykind.TY_NAMED) { u = u.under; }; + if (u == nil) { return 0; }; + if (u.kind == tykind.TY_STRUCT || u.kind == tykind.TY_ARRAY) { + return u.size: i32; + }; + return 0; +}; + +fn nodeisaggarg(n: *node) bool = { + if (n == nil) { return false; }; + return aggargsizetn(n.type_: *tinfo) > 0; +}; + +// aggargfloatstop — true iff the arg is a ≤16B struct with any float +// field (#271/#165). Such a struct from a non-ident source would need +// the SSE eightbyte transport the GP aggregate push/drain can't model; +// both stages loud-stop on it. Same predicate as the cstage Tfield +// fld_isfloat walk (cmd/w6c/cgen.c #271 push arm). +fn aggargfloatstop(n: *node) bool = { + if (n == nil) { return false; }; + let st: *tinfo = n.type_: *tinfo; + for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; }; + if (st == nil) { return false; }; + if (st.kind != tykind.TY_STRUCT) { return false; }; + if (st.size: i32 > 16) { return false; }; + let f: *tfield = st.fields; + for (f != nil) { + if (typeisfloat(f.type_)) { return true; }; + f = f.tnext; + }; + return false; +}; + // structfloatclass — SysV per-eightbyte classification for the #165 // float-bearing-struct param case (param twin of #171's struct return; // classifies per-eightbyte, not #163's per-element). Returns 0 when the @@ -20626,6 +20754,92 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { return true; }; +// aggargsrcaddr — land the ADDRESS of an addressable aggregate (struct/ +// array) call-arg source in dstreg (#271, mirror of cstage +// aggarg_srcaddr). Reuses the closed #265/#268 let-init-copy dispatch: +// local ident slot (LEAQ off(BP)), module-let global (LEAQ name(SB)), +// deref operand (cgexpr of the pointer), N_DOT field (dotchainaddr, +// #253), N_INDEX element of an N_IDENT array base (the &base[i] spine, +// #252/#270). Returns false for an uncovered source kind (caller loud- +// stops, rule 7). The CALL source is handled at the push site. +fn aggargsrcaddr(c: *cgen, src: *node, dst: str) bool = { + if (src.kind == nkind.N_UN) { + if (src.op == tkind.TK_STAR) { + cgexpr(c, src.lhs); + if (!streq(dst, "AX")) { + emitline("\tMOVQ\tAX, "); + emitline(dst); + emitline("\n"); + }; + return true; + }; + }; + if (src.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, src.str); + if (lc != nil) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), "); + emitline(dst); + emitline("\n"); + return true; + }; + // global value source. Gated to a module-`let` (letvartnode, + // the cstage let_islet twin); a const array/struct `def` + // aggregate ARG is untested + out of scope (#274; both stages + // loud-stop, rule-10 aligned). + if (letvartnode(c, src.str) != nil) { + emitline("\tLEAQ\t"); + emitsymname(c, src.str); + emitline("(SB), "); + emitline(dst); + emitline("\n"); + return true; + }; + return false; + }; + if (src.kind == nkind.N_DOT) { + return dotchainaddr(c, src, dst); + }; + if (src.kind == nkind.N_INDEX) { + let base: *node = src.lhs; + let idx: *node = src.rhs; + if (base == nil) { return false; }; + if (base.kind != nkind.N_IDENT) { return false; }; + let bu: *tinfo = base.type_: *tinfo; + for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; + if (bu == nil) { return false; }; + if (bu.kind != tykind.TY_ARRAY) { return false; }; + let esz: i32 = 1; + if (bu.sub != nil) { esz = bu.sub.size: i32; }; + cgexpr(c, idx); + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", CX\n"); + emitline("\tIMULQ\tCX, AX\n"); + }; + let boff: *local = localfindnode(c, base.str); + if (boff != nil) { + emitline("\tLEAQ\t"); + emitoff(boff.off: i64); + emitline("(BP), BX\n"); + } else { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), BX\n"); + }; + emitline("\tADDQ\tBX, AX\n"); + if (!streq(dst, "AX")) { + emitline("\tMOVQ\tAX, "); + emitline(dst); + emitline("\n"); + }; + return true; + }; + return false; +}; + fn cgindex(c: *cgen, n: *node) void = { // Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL, // str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast @@ -24004,6 +24218,13 @@ fn cgcall(c: *cgen, n: *node) void = { // words and shift intidx out of sync. let tcs: i32 = taggedcallslot(c, a); if (tcs > 0) { extra = tcs / 8 - 1; }; + // #271: array / >16B-struct / non-ident 16B-struct + // aggregate arg — pushargsrev staged ceil(sz/8) words; + // drain exactly that many so intidx tracks per-arg + // (the ≤16B struct IDENT case is the stfc branch + // above). Mirror of cstage node_isaggarg drain arm. + let aggsz: i32 = aggargsizetn(a.type_: *tinfo); + if (aggsz > 0) { extra = (aggsz + 7) / 8 - 1; }; let words: i32 = 1 + extra; let w: i32 = 0; for (w < words) { @@ -30870,18 +31091,66 @@ fn cgfnparams(c: *cgen, params: *node) void = { localaddstack(c, nm, p.lhs, 16 + stkcursor*8); stkcursor += nw; };}; - } else { - if (idx < 6) { - let off: i32 = localadd(c, nm, 8, p.lhs); - emitline("\tMOVQ\t"); - emitline(argregname(idx)); - emitline(", "); - emitoff(off: i64); - emitline("(BP)\n"); - idx += 1; + } else { let aggsz2: i32 = aggargsizetn(p.lhs.type_: *tinfo); + if (aggsz2 > 0) { + // #271: array / >16B-struct by-value param — + // received as ceil(sz/8) GP eightbytes, the + // callee twin of the generalised aggregate-arg + // push. Mirror of the cstage is_bigagg arm + // (regs-fit / partial-stitch / stack-spill). + let nw2: i32 = (aggsz2 + 7) / 8; + if (idx + nw2 <= 6) { + let off: i32 = localadd(c, nm, aggsz2, p.lhs); + let w: i32 = 0; + for (w < nw2) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + } else { if (idx < 6) { + let off: i32 = localadd(c, nm, aggsz2, p.lhs); + let regs_left: i32 = 6 - idx; + let w: i32 = 0; + for (w < regs_left) { + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + idx += 1; + w += 1; + }; + for (w < nw2) { + emitline("\tMOVQ\t"); + emitoff((16 + stkcursor*8): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitoff((off + w*8): i64); + emitline("(BP)\n"); + stkcursor += 1; + w += 1; + }; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += nw2; + };}; } else { - localaddstack(c, nm, p.lhs, 16 + stkcursor*8); - stkcursor += 1; + if (idx < 6) { + let off: i32 = localadd(c, nm, 8, p.lhs); + emitline("\tMOVQ\t"); + emitline(argregname(idx)); + emitline(", "); + emitoff(off: i64); + emitline("(BP)\n"); + idx += 1; + } else { + localaddstack(c, nm, p.lhs, 16 + stkcursor*8); + stkcursor += 1; + }; }; }; };};}; diff --git a/test/wcc/949_dotbase_addr_slice_run.c b/test/wcc/949_dotbase_addr_slice_run.c index 35ef564c..ae424893 100644 --- a/test/wcc/949_dotbase_addr_slice_run.c +++ b/test/wcc/949_dotbase_addr_slice_run.c @@ -1180,6 +1180,147 @@ static const struct row rows[] = { " let x: [2]inner = [p, q];\n" " return (x[0].a + x[0].b + x[1].a + x[1].b): i32;\n" "};\n", 18, 1 }, + /* #271 aggregate ARG from any NON-IDENT source — the arg-pass twin + * of the #265/#268 let-init copy. Passing an aggregate BY VALUE as a + * call argument worked ONLY for an IDENT source (≤16B struct); every + * non-ident source (CALL mk(), N_DOT o.f, N_INDEX a[i], DEREF *p) and + * every array / >16B-struct (even as an IDENT) fell to the scalar + * default — one PUSHQ for a multi-word aggregate — stack-imbalancing + * against the multi-word drain (cs!=ww, both garbage). The fix + * materialises the source into the arg convention: the source ADDRESS + * in SI (ident LEAQ / deref / dotchainaddr #253 / &base[i] #252-270) + * then ceil(sz/8) words pushed; a CALL receives first (≤24B in + * AX/DX/CX, >24B sret'd into @aggargscr). The callee prologue gained a + * matching array / >16B-struct receive. Each callee reads back ALL + * members (full sum) so a dropped word fails. Covered: struct 16B + * (reg-class) AND struct 32B (sret-class) AND array [4]u32, from each + * non-ident source + an ident control. byteid=1 throughout: both + * stages converge on the correct full-aggregate push (master both- + * broken-and-divergent → fix correct, #263 lesson). */ + { "arg_struct16_call", + "package main;\n" + "type t = struct { x: i64, y: i64 };\n" + "fn mk() t = { let a: t; a.x=3i64; a.y=7i64; return a; };\n" + "fn sum(b: t) i64 = { return b.x + b.y; };\n" + "export fn main() i32 = { return sum(mk()): i32; };\n", 10, 1 }, + { "arg_struct16_dot", + "package main;\n" + "type t = struct { x: i64, y: i64 };\n" + "type o = struct { f: t };\n" + "fn sum(b: t) i64 = { return b.x + b.y; };\n" + "export fn main() i32 = {\n" + " let q: o; q.f.x=3i64; q.f.y=7i64;\n" + " return sum(q.f): i32;\n" + "};\n", 10, 1 }, + { "arg_struct16_idx", + "package main;\n" + "type t = struct { x: i64, y: i64 };\n" + "fn sum(b: t) i64 = { return b.x + b.y; };\n" + "export fn main() i32 = {\n" + " let a: [2]t; a[1].x=3i64; a[1].y=7i64;\n" + " return sum(a[1]): i32;\n" + "};\n", 10, 1 }, + { "arg_struct16_deref", + "package main;\n" + "type t = struct { x: i64, y: i64 };\n" + "fn sum(b: t) i64 = { return b.x + b.y; };\n" + "export fn main() i32 = {\n" + " let v: t; v.x=3i64; v.y=7i64; let p: *t = &v;\n" + " return sum(*p): i32;\n" + "};\n", 10, 1 }, + { "arg_struct16_ident", + "package main;\n" + "type t = struct { x: i64, y: i64 };\n" + "fn sum(b: t) i64 = { return b.x + b.y; };\n" + "export fn main() i32 = {\n" + " let v: t; v.x=3i64; v.y=7i64;\n" + " return sum(v): i32;\n" + "};\n", 10, 1 }, + { "arg_arr16_call", + "package main;\n" + "fn mk() [4]u32 = { let a: [4]u32; a[0]=1u32;a[1]=2u32;a[2]=3u32;a[3]=4u32; return a; };\n" + "fn sum(b: [4]u32) i32 = { return (b[0]+b[1]+b[2]+b[3]): i32; };\n" + "export fn main() i32 = { return sum(mk()); };\n", 10, 1 }, + { "arg_arr16_dot", + "package main;\n" + "type o = struct { f: [4]u32 };\n" + "fn sum(b: [4]u32) i32 = { return (b[0]+b[1]+b[2]+b[3]): i32; };\n" + "export fn main() i32 = {\n" + " let q: o; q.f[0]=1u32; q.f[1]=2u32; q.f[2]=3u32; q.f[3]=4u32;\n" + " return sum(q.f);\n" + "};\n", 10, 1 }, + { "arg_arr16_idx", + "package main;\n" + "fn sum(b: [4]u32) i32 = { return (b[0]+b[1]+b[2]+b[3]): i32; };\n" + "export fn main() i32 = {\n" + " let a: [2][4]u32;\n" + " a[1][0]=1u32; a[1][1]=2u32; a[1][2]=3u32; a[1][3]=4u32;\n" + " return sum(a[1]);\n" + "};\n", 10, 1 }, + { "arg_arr16_deref", + "package main;\n" + "fn sum(b: [4]u32) i32 = { return (b[0]+b[1]+b[2]+b[3]): i32; };\n" + "export fn main() i32 = {\n" + " let v: [4]u32; v[0]=1u32; v[1]=2u32; v[2]=3u32; v[3]=4u32;\n" + " let p: *[4]u32 = &v;\n" + " return sum(*p);\n" + "};\n", 10, 1 }, + { "arg_arr16_ident", + "package main;\n" + "fn sum(b: [4]u32) i32 = { return (b[0]+b[1]+b[2]+b[3]): i32; };\n" + "export fn main() i32 = {\n" + " let v: [4]u32; v[0]=1u32; v[1]=2u32; v[2]=3u32; v[3]=4u32;\n" + " return sum(v);\n" + "};\n", 10, 1 }, + { "arg_struct32_call", + "package main;\n" + "type t = struct { h: [8]u32 };\n" + "fn mk() t = { let a: t; a.h[0]=1u32;a.h[1]=2u32;a.h[2]=3u32;a.h[3]=4u32;a.h[4]=5u32;a.h[5]=6u32;a.h[6]=7u32;a.h[7]=8u32; return a; };\n" + "fn sum(b: t) i32 = { return (b.h[0]+b.h[1]+b.h[2]+b.h[3]+b.h[4]+b.h[5]+b.h[6]+b.h[7]): i32; };\n" + "export fn main() i32 = { return sum(mk()); };\n", 36, 1 }, + { "arg_struct32_dot", + "package main;\n" + "type t = struct { h: [8]u32 };\n" + "type o = struct { f: t };\n" + "fn sum(b: t) i32 = { return (b.h[0]+b.h[1]+b.h[2]+b.h[3]+b.h[4]+b.h[5]+b.h[6]+b.h[7]): i32; };\n" + "export fn main() i32 = {\n" + " let q: o;\n" + " q.f.h[0]=1u32;q.f.h[1]=2u32;q.f.h[2]=3u32;q.f.h[3]=4u32;\n" + " q.f.h[4]=5u32;q.f.h[5]=6u32;q.f.h[6]=7u32;q.f.h[7]=8u32;\n" + " return sum(q.f);\n" + "};\n", 36, 1 }, + { "arg_struct32_idx", + "package main;\n" + "type t = struct { h: [8]u32 };\n" + "fn sum(b: t) i32 = { return (b.h[0]+b.h[1]+b.h[2]+b.h[3]+b.h[4]+b.h[5]+b.h[6]+b.h[7]): i32; };\n" + "export fn main() i32 = {\n" + " let a: [2]t;\n" + " let p: *t = &a[1];\n" + " p.h[0]=1u32;p.h[1]=2u32;p.h[2]=3u32;p.h[3]=4u32;\n" + " p.h[4]=5u32;p.h[5]=6u32;p.h[6]=7u32;p.h[7]=8u32;\n" + " return sum(a[1]);\n" + "};\n", 36, 1 }, + { "arg_struct32_deref", + "package main;\n" + "type t = struct { h: [8]u32 };\n" + "fn sum(b: t) i32 = { return (b.h[0]+b.h[1]+b.h[2]+b.h[3]+b.h[4]+b.h[5]+b.h[6]+b.h[7]): i32; };\n" + "export fn main() i32 = {\n" + " let v: t;\n" + " v.h[0]=1u32;v.h[1]=2u32;v.h[2]=3u32;v.h[3]=4u32;\n" + " v.h[4]=5u32;v.h[5]=6u32;v.h[6]=7u32;v.h[7]=8u32;\n" + " let p: *t = &v;\n" + " return sum(*p);\n" + "};\n", 36, 1 }, + { "arg_struct32_ident", + "package main;\n" + "type t = struct { h: [8]u32 };\n" + "fn sum(b: t) i32 = { return (b.h[0]+b.h[1]+b.h[2]+b.h[3]+b.h[4]+b.h[5]+b.h[6]+b.h[7]): i32; };\n" + "export fn main() i32 = {\n" + " let v: t;\n" + " v.h[0]=1u32;v.h[1]=2u32;v.h[2]=3u32;v.h[3]=4u32;\n" + " v.h[4]=5u32;v.h[5]=6u32;v.h[6]=7u32;v.h[7]=8u32;\n" + " return sum(v);\n" + "};\n", 36, 1 }, { NULL, NULL, 0, 0 } };