From 50d70e74e45f384e62cd65dafd0ebe303898926b Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 8 Aug 2026 14:10:55 +0900 Subject: [PATCH] w6c: append() accepts struct CALL rvalue elements (#34) Evaluate the call pre-grow into a per-site scratch, receiving by the N_LET matrix (sret / float / odd-tail / GP), then grow, slot, sized ladder. Both stages, byte-identical. The two reject pins graduate to 16B GP accept rows; three new fixtures cover 24B GP, 3B/4B tails, 16B float, and 40B sret. --- cmd/w6c/cgen.c | 77 ++++++++++++++- internal/wwfixture/types.ww | 10 +- selfhost/cmd/wcc/cgenexpr.ww | 98 ++++++++++++++++++- selfhost/cmd/wcc/cgenutil.ww | 54 ++++++++++ test/wcc/data/r34_append_struct_call/case.ww | 25 +++++ .../data/r34_append_struct_call_float/case.ww | 18 ++++ .../data/r34_append_struct_call_sret/case.ww | 20 ++++ .../r800append_struct_call_loudstop/case.ww | 11 --- test/wcc/data/r806ap_reject_call_src/case.ww | 9 -- 9 files changed, 295 insertions(+), 27 deletions(-) create mode 100644 test/wcc/data/r34_append_struct_call/case.ww create mode 100644 test/wcc/data/r34_append_struct_call_float/case.ww create mode 100644 test/wcc/data/r34_append_struct_call_sret/case.ww delete mode 100644 test/wcc/data/r800append_struct_call_loudstop/case.ww delete mode 100644 test/wcc/data/r806ap_reject_call_src/case.ww diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e2105327..fab13eba 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -9467,7 +9467,8 @@ cgexpr(Cg *c, Node *n, Local *locals) int abase_slice = 0, aroot_off = 0; Node *aroot = NULL, *aidx = NULL; if (el_struct && vn->kind != N_STRUCTLIT - && vn->kind != N_IDENT) { + && vn->kind != N_IDENT + && vn->kind != N_CALL) { Node *ch = vn; int aok = 1; while (aok && ch->kind == N_DOT) { @@ -9567,6 +9568,80 @@ cgexpr(Cg *c, Node *n, Local *locals) } continue; } + if (vn->kind == N_CALL) { + /* #34 close: a struct CALL rvalue + * element. Evaluate the call FIRST + * into a fresh per-site scratch + * (the STRUCTLIT arm's pre-grow + * discipline: arg exprs must see + * the pre-append len, and the + * value must survive rt_ensure), + * receiving by the N_LET matrix + * (#23 sret / #171a float / #107 + * odd-tail / #169 GP), then grow, + * slot, sized-ladder scratch-> + * slot. */ + int st_scr = local_alloc(c, + &locals, "@appendstructscr", + esz, cg_frame); + int sclass[2], snb; + if (cg_sret_retsize(esub) > 0) { + cg_sret_dest_off = st_scr; + cgexpr(c, vn, locals); + cg_sret_dest_off = 0; + } else if ((snb = struct_float_class(esubu, sclass)) > 0) { + cgexpr(c, vn, locals); + int gpcur = 0, ssecur = 0; + for (int e = 0; e < snb; e++) { + if (sclass[e]) { + ins2(c, A_MOVSD, + areg(tuple_sse_seq[ssecur]), + amem(D_BP, st_scr + e * 8)); + ssecur++; + } else { + ins2(c, A_MOVQ, + areg(tuple_rseq[gpcur]), + amem(D_BP, st_scr + e * 8)); + gpcur++; + } + } + } else if (esz < 8 && (esz % 8 == 3 + || esz % 8 == 5 || esz % 8 == 6 + || esz % 8 == 7)) { + cgexpr(c, vn, locals); + ins2(c, A_MOVQ, areg(D_AX), + amem(D_BP, st_scr)); + } else { + cgexpr(c, vn, locals); + cg_agg_reg_store(c, &locals, + D_BP, st_scr, esz, 1); + } + cg_append_grow(c, sn_direct, sn_off, + sn_scr, esz); + cg_append_slot(c, sn_direct, sn_off, + sn_scr, esz, D_BX); + int ck = 0; + for (; ck + 8 <= esz; ck += 8) { + ins2(c, A_MOVQ, amem(D_BP, st_scr + ck), areg(D_AX)); + ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, ck)); + } + if (ck + 4 <= esz) { + ins2(c, A_MOVL, amem(D_BP, st_scr + ck), areg(D_AX)); + ins2(c, A_MOVL, areg(D_AX), amem(D_BX, ck)); + ck += 4; + } + if (ck + 2 <= esz) { + ins2(c, A_MOVW, amem(D_BP, st_scr + ck), areg(D_AX)); + ins2(c, A_MOVW, areg(D_AX), amem(D_BX, ck)); + ck += 2; + } + if (ck + 1 <= esz) { + ins2(c, A_MOVB, amem(D_BP, st_scr + ck), areg(D_AX)); + ins2(c, A_MOVB, areg(D_AX), amem(D_BX, ck)); + ck += 1; + } + continue; + } if (vn->kind == N_STRUCTLIT) { /* #59 (#50's eval-order kin): * fill the literal into a fresh diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index 440f3e30..adc26ec8 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -1,13 +1,13 @@ package wwfixture; def protocolversion: i32 = 1; -def corpuscount: i32 = 1631; -def errorcount: i32 = 338; +def corpuscount: i32 = 1634; +def errorcount: i32 = 336; def compilecount: i32 = 17; def runcount: i32 = 198; -def runexitcount: i32 = 1078; -def nativecount: i32 = 3262; -def corpushash: str = "f1186d55d4f4a447de61b1246f8296c9f5996b406de36da0e5eb277fd2352e52"; +def runexitcount: i32 = 1083; +def nativecount: i32 = 3268; +def corpushash: str = "cb7cf09decc2f64b40c1f06b836c5b2aec0a4f1b9b2a78665f228606a0245a1f"; type directive = enum i32 { ERROR = 0, diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 04719f6b..5cd65ec4 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -6599,7 +6599,8 @@ fn cgappend(c: *cgen, n: *syntax.node) void = { let asoff: i32 = 0; let aroot: *syntax.node = nil; let aidx: *syntax.node = nil; - if (elstruct && vn.kind != syntax.nkind.N_STRUCTLIT && vn.kind != syntax.nkind.N_IDENT) { + if (elstruct && vn.kind != syntax.nkind.N_STRUCTLIT && vn.kind != syntax.nkind.N_IDENT + && vn.kind != syntax.nkind.N_CALL) { let ch: *syntax.node = vn; let aok: bool = true; for (aok && ch.kind == syntax.nkind.N_DOT) { @@ -6722,6 +6723,101 @@ fn cgappend(c: *cgen, n: *syntax.node) void = { vn = vn.next; continue; }; + if (vn.kind == syntax.nkind.N_CALL) { + // #34 close: a struct CALL rvalue element. + // Evaluate the call FIRST into a fresh per-site + // scratch (the STRUCTLIT arm's pre-grow + // discipline: arg exprs must see the pre-append + // len, and the value must survive rt_ensure), + // receiving by the N_LET matrix (#23 sret / + // #171a float / #107 odd-tail / #169 GP), then + // grow, slot, sized-ladder scratch->slot. + let stscr: i32 = localalloc(c, "@appendstructscr", esz, nil); + let scs: i32 = callsretsize(c, vn); + if (scs > 0) { + c.sretdestoff = stscr; + cgexpr(c, vn); + c.sretdestoff = 0; + } else { + let sfc: i32 = structfloatclassti(esubti); + if (sfc != 0) { + cgexpr(c, vn); + let nb: i32 = sfc & 15; + let gpcur: i32 = 0; + let ssecur: i32 = 0; + let e: i32 = 0; + for (e < nb) { + let issse: bool = (sfc & (16 << e)) != 0; + if (issse) { + emitline("\tMOVSD\t"); + emitline(tupsse(ssecur)); + emitline(", "); + emitoff((stscr + e * 8): i64); + emitline("(BP)\n"); + ssecur += 1; + } else { + emitline("\tMOVQ\t"); + emitline(tupreg(gpcur)); + emitline(", "); + emitoff((stscr + e * 8): i64); + emitline("(BP)\n"); + gpcur += 1; + }; + e += 1; + }; + } else { if (esz == 3 || esz == 5 + || esz == 6 || esz == 7) { + cgexpr(c, vn); + emitline("\tMOVQ\tAX, "); + emitoff(stscr: i64); + emitline("(BP)\n"); + } else { + cgexpr(c, vn); + cgaggregstore(c, "BP", stscr, esz, true); + }; }; + }; + cgappendgrow(c, sndirect, sn_off, snscr, esz); + cgappendslot(c, sndirect, sn_off, snscr, esz, "BX"); + let ck: i32 = 0; + for (ck + 8 <= esz) { + emitline("\tMOVQ\t"); + emitoff((stscr + ck): i64); + emitline("(BP), AX\n"); + emitline("\tMOVQ\tAX, "); + emitdispreg(ck: i64, "BX"); + emitline("\n"); + ck += 8; + }; + if (ck + 4 <= esz) { + emitline("\tMOVL\t"); + emitoff((stscr + ck): i64); + emitline("(BP), AX\n"); + emitline("\tMOVL\tAX, "); + emitdispreg(ck: i64, "BX"); + emitline("\n"); + ck += 4; + }; + if (ck + 2 <= esz) { + emitline("\tMOVW\t"); + emitoff((stscr + ck): i64); + emitline("(BP), AX\n"); + emitline("\tMOVW\tAX, "); + emitdispreg(ck: i64, "BX"); + emitline("\n"); + ck += 2; + }; + if (ck + 1 <= esz) { + emitline("\tMOVB\t"); + emitoff((stscr + ck): i64); + emitline("(BP), AX\n"); + emitline("\tMOVB\tAX, "); + emitdispreg(ck: i64, "BX"); + emitline("\n"); + ck += 1; + }; + vn = vn.next; + continue; + }; if (vn.kind == syntax.nkind.N_STRUCTLIT) { // #59 (#50's eval-order kin): resolve the struct // info, then fill the literal into a fresh diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index acd9921e..c82e41cd 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -3454,6 +3454,60 @@ fn structfloatclass(c: *cgen, t: *syntax.node) i32 = { return enc; }; +// structfloatclassti — tinfo twin of structfloatclass for sites with +// no declared tnode (the stamped element type behind an append CALL +// source, #34). Same gates and encoding as the tnode form: 0, or +// nb + 16/32 per SSE eightbyte. Cite cstage cgen.c struct_float_class +// (Type-keyed there, so the tinfo walk IS the aligned shape). +fn structfloatclassti(t: *syntax.tinfo) i32 = { + t = tichase(t); + if (t == nil || t.kind != syntax.tykind.TY_STRUCT) { return 0; }; + let sz: i32 = t.size: i32; + if (sz <= 0 || sz > 16) { return 0; }; + // SysV classifies aggregates in 8-byte eightbytes; 8 is the + // eightbyte stride, not a type footprint. + let nb: i32 = 1; + if (sz > 8) { nb = 2; }; + let nflt0: i32 = 0; let nflt1: i32 = 0; + let nint0: i32 = 0; let nint1: i32 = 0; + let fi: *syntax.tfield = t.fields; + for (fi != nil) { + let fu: *syntax.tinfo = tichase(fi.type_); + if (fu == nil) { return 0; }; + let foff: i32 = fi.offset: i32; + let fsz: i32 = fu.size: i32; + let e: i32 = foff / 8; + if (e < 0 || e >= nb) { return 0; }; + if (fu.kind == syntax.tykind.TY_F32) { return 0; }; + if (fu.kind == syntax.tykind.TY_F64) { + if (foff - (foff / 8) * 8 != 0) { return 0; }; + if (fsz != 8) { return 0; }; + if (e == 0) { nflt0 += 1; } else { nflt1 += 1; }; + } else { + if (fu.kind == syntax.tykind.TY_STRUCT) { return 0; }; + if (fu.kind == syntax.tykind.TY_ARRAY) { return 0; }; + if (fu.kind == syntax.tykind.TY_SLICE) { return 0; }; + if (fu.kind == syntax.tykind.TY_STR) { return 0; }; + if (fu.kind == syntax.tykind.TY_TAGGED) { return 0; }; + if (fu.kind == syntax.tykind.TY_TUPLE) { return 0; }; + if (fsz > 8) { return 0; }; + if ((foff + fsz - 1) / 8 != e) { return 0; }; + if (e == 0) { nint0 += 1; } else { nint1 += 1; }; + }; + fi = fi.tnext; + }; + let enc: i32 = nb; + let hasfloat: bool = false; + if (nflt0 == 1 && nint0 == 0) { enc += 16; hasfloat = true; } + else { if (nflt0 != 0) { return 0; }; }; + if (nb == 2) { + if (nflt1 == 1 && nint1 == 0) { enc += 32; hasfloat = true; } + else { if (nflt1 != 0) { return 0; }; }; + }; + if (!hasfloat) { return 0; }; + return enc; +}; + // istaggedtype — alias-aware. Reads stamped tinfo so `T`, // `type alias = (A|B)`, `type error = !(invalid|overflow)` all // resolve to TY_TAGGED — tinfofornode handles the N_TBANG unwrap diff --git a/test/wcc/data/r34_append_struct_call/case.ww b/test/wcc/data/r34_append_struct_call/case.ww new file mode 100644 index 00000000..6f492251 --- /dev/null +++ b/test/wcc/data/r34_append_struct_call/case.ww @@ -0,0 +1,25 @@ +//ww:run-exit 0 +// #34 close: append() accepts a struct CALL rvalue element — GP +// register-class receive (24B str+i32), odd sub-8 (3B), and sized +// tail (4B) shapes, evaluated pre-grow into a per-site scratch. +package main; +type gp24 = struct { name: str, id: i32 }; +type odd3 = struct { a: u8, b: u8, c: u8 }; +type tail4 = struct { v: i32 }; +fn mkgp(id: i32) gp24 = { let e: gp24; e.name = "x"; e.id = id; return e; }; +fn mkodd() odd3 = { let e: odd3; e.a = 1u8; e.b = 2u8; e.c = 3u8; return e; }; +fn mktail() tail4 = { let e: tail4; e.v = 5; return e; }; +fn main() i32 = { + let gs: []gp24 = []; + append(gs, mkgp(7)); + append(gs, mkgp(9)); + if (gs.len != 2 || gs[0].id != 7 || gs[1].id != 9) { return 1; }; + if (gs[0].name.len != 1) { return 2; }; + let os_: []odd3 = []; + append(os_, mkodd()); + if (os_[0].a != 1u8 || os_[0].c != 3u8) { return 3; }; + let ts: []tail4 = []; + append(ts, mktail()); + if (ts[0].v != 5) { return 4; }; + return 0; +}; diff --git a/test/wcc/data/r34_append_struct_call_float/case.ww b/test/wcc/data/r34_append_struct_call_float/case.ww new file mode 100644 index 00000000..dc82fe31 --- /dev/null +++ b/test/wcc/data/r34_append_struct_call_float/case.ww @@ -0,0 +1,18 @@ +//ww:run-exit 0 +// #34 close, float leg: a float-bearing <=16B struct CALL element is +// received on independent SSE/GP return cursors (the #171a matrix). +package main; +type flt16 = struct { x: f64, n: i64 }; +fn mkflt(v: f64, n: i64) flt16 = { + let e: flt16; e.x = v; e.n = n; return e; +}; +fn main() i32 = { + let fs: []flt16 = []; + append(fs, mkflt(2.5, 11i64)); + append(fs, mkflt(0.25, 13i64)); + if (fs.len != 2) { return 1; }; + if (fs[0].n != 11i64 || fs[1].n != 13i64) { return 2; }; + if (fs[0].x != 2.5) { return 3; }; + if (fs[1].x != 0.25) { return 4; }; + return 0; +}; diff --git a/test/wcc/data/r34_append_struct_call_sret/case.ww b/test/wcc/data/r34_append_struct_call_sret/case.ww new file mode 100644 index 00000000..fb71b262 --- /dev/null +++ b/test/wcc/data/r34_append_struct_call_sret/case.ww @@ -0,0 +1,20 @@ +//ww:run-exit 0 +// #34 close, sret leg: a >24B struct CALL element writes through the +// hidden RDI into the append scratch before the grow. +package main; +type big40 = struct { a: i64, b: i64, c: i64, d: i64, e: i64 }; +fn mkbig(base: i64) big40 = { + let e: big40; + e.a = base; e.b = base + 1i64; e.c = base + 2i64; + e.d = base + 3i64; e.e = base + 4i64; + return e; +}; +fn main() i32 = { + let bs: []big40 = []; + append(bs, mkbig(10i64)); + append(bs, mkbig(20i64)); + if (bs.len != 2) { return 1; }; + if (bs[0].a != 10i64 || bs[0].e != 14i64) { return 2; }; + if (bs[1].a != 20i64 || bs[1].e != 24i64) { return 3; }; + return 0; +}; diff --git a/test/wcc/data/r800append_struct_call_loudstop/case.ww b/test/wcc/data/r800append_struct_call_loudstop/case.ww deleted file mode 100644 index bb56e77c..00000000 --- a/test/wcc/data/r800append_struct_call_loudstop/case.ww +++ /dev/null @@ -1,11 +0,0 @@ -//ww:error "#34: append() struct element source shape unsupported (rule-7)" -package main; -type pt = struct { x: i32, y: i32, z: i64 }; -fn mk() pt = { - return pt { x = 1, y = 2, z = 3 }; -}; -export fn main() i32 = { - let xs: []pt = []; - append(xs, mk()); - return 0; -}; diff --git a/test/wcc/data/r806ap_reject_call_src/case.ww b/test/wcc/data/r806ap_reject_call_src/case.ww deleted file mode 100644 index 5220b6c8..00000000 --- a/test/wcc/data/r806ap_reject_call_src/case.ww +++ /dev/null @@ -1,9 +0,0 @@ -//ww:error "#34: append() struct element source shape unsupported (rule-7)" -package main; -type box = struct { pc: size, a: i64 }; -fn mk() box = { return box { pc = 1, a = 2 }; }; -export fn main() i32 = { - let bs: []box = []; - append(bs, mk()); - return 0; -};