From 942abf0482162bd00c3212a672c8cabfcd3e3c1d Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Sat, 6 Jun 2026 20:27:43 +0900 Subject: [PATCH] wcc/cgen: #117 const slice-of-(str,*fn) DATA + &fn->DATAR reloc (both-stage) --- Makefile | 7 + cmd/w6c/cgen.c | 162 +++++++++++--- selfhost/cmd/w6c/main.combined.ww | 185 +++++++++++++--- selfhost/cmd/wcc/cgen.ww | 185 +++++++++++++--- selfhost/cmd/wwdump/main.combined.ww | 185 +++++++++++++--- test/wcc/946_const_slice_aggregate_run.c | 271 +++++++++++++++++++++++ 6 files changed, 889 insertions(+), 106 deletions(-) create mode 100644 test/wcc/946_const_slice_aggregate_run.c diff --git a/Makefile b/Makefile index 8be5ec39..6666b8d6 100644 --- a/Makefile +++ b/Makefile @@ -325,6 +325,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_mixed_scalar_tuple_sret_run \ $(BIN)/test_tuple_in_union_run \ $(BIN)/test_nonlit_tuple_widen_run \ + $(BIN)/test_const_slice_aggregate_run \ $(BIN)/test_tuple_slot_layout_run \ $(BIN)/test_tagged_tuple_widen_run \ $(BIN)/test_tagged_structlit_payload_run \ @@ -1629,6 +1630,12 @@ $(BIN)/test_tuple_slot_layout_run: test/wcc/941_tuple_slot_layout_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_const_slice_aggregate_run: test/wcc/946_const_slice_aggregate_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_tagged_tuple_widen_run: test/wcc/936_tagged_tuple_widen_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e37dd1c4..fa6ec624 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -15010,6 +15010,22 @@ emit_tagged_data(FILE *out, Cg *c, const char *name, const char *module, return 1; } +/* node_fnptr_sym — if `ev` (casts already peeled by the caller) is the + * address-of a top-level fn (`&f`), return its mangled TEXT symbol; NULL + * otherwise. The detect-half of the FIRST &fn→DATAR reloc machinery + * (#117 slice-row + #119 scalar-global); mirrors the address-of-fn codegen + * arm (TY_FN at the N_UN TK_AMP ident, cgen.c N_UN TK_AMP). */ +static const char * +node_fnptr_sym(Cg *c, Node *ev) +{ + if (ev == NULL || ev->kind != N_UN || ev->op != TK_AMP) return NULL; + Node *opnd = ev->lhs; + if (opnd == NULL || opnd->kind != N_IDENT) return NULL; + Type *ou = type_chase_named(opnd->type); + if (ou == NULL || ou->kind != TY_FN) return NULL; + return mod_mangle_fn(c, opnd->str, c->cur_mod); +} + /* tuple_row_foldable — validate that every cast-peeled element of `rhs` * (an N_TUPLE) reduces to a static row: an int literal (fold_int_literal) * or a str literal in a str/slice slot. A tagged element slot has no @@ -15038,6 +15054,8 @@ tuple_row_foldable(Cg *c, Type *u, Node *rhs) if (ev->kind != N_STRLIT) return 0; continue; } + /* #117: a `&fn` element folds to an 8B reloc slot. */ + if (node_fnptr_sym(c, ev) != NULL) continue; u64 v; if (!fold_int_literal(ev, &v)) return 0; } @@ -15068,6 +15086,12 @@ emit_tuple_row_bytes(Cg *c, FILE *out, Type *u, Node *rhs) emit_data_byte(out, 0); continue; } + /* #117: a `&fn` element is an 8B zero ptr placeholder; the + * reloc is patched in emit_tuple_row_relocs. */ + if (node_fnptr_sym(c, ev) != NULL) { + for (int i = 0; i < 8; i++) emit_data_byte(out, 0); + continue; + } u64 v = 0; (void)fold_int_literal(ev, &v); for (int i = 0; i < 8; i++) @@ -15091,11 +15115,21 @@ emit_tuple_row_relocs(Cg *c, FILE *out, const char *holder, int row_off, while (ev && ev->kind == N_CAST) ev = ev->lhs; int wide = tp && (type_isstr(tp->type) || type_isslice(tp->type)); - if (wide && ev->strlen > 0) { - const char *lab = intern_strlit(c, ev->str, - ev->strlen); - fprintf(out, "DATAR %s+%d(SB),%s(SB)\n", - holder, foff, lab); + if (wide) { + if (ev->strlen > 0) { + const char *lab = intern_strlit(c, ev->str, + ev->strlen); + fprintf(out, "DATAR %s+%d(SB),%s(SB)\n", + holder, foff, lab); + } + } else { + /* #117: the `&fn` element's reloc — the FIRST + * &fn→DATAR in the emitter; patches the 8B slot at + * holder+foff with the fn's TEXT VA. */ + const char *fsym = node_fnptr_sym(c, ev); + if (fsym != NULL) + fprintf(out, "DATAR %s+%d(SB),%s(SB)\n", + holder, foff, fsym); } foff += tuple_eslot(tp ? tp->type : NULL); } @@ -15163,9 +15197,15 @@ emit_array_data(FILE *out, Cg *c, const char *directive, * * Scoped to a writable `let` — A_DATAR's holder must be a DATAW slot * (w6a asm.c:362), so a read-only `def []T = [...]` can't carry the ptr - * reloc. That, a `...` repeat (a slice literal has no target length), - * and slice-of-{str,slice,tagged} elements (per-element relocs / #17) - * all loud-stop (rule 7) — #10 follow-ups, never silent fall-through. + * reloc. That, a `...` repeat (a slice literal has no target length), and + * slice-of-{str,slice,tagged} elements (per-element relocs / #17) all + * loud-stop (rule 7) — #10 follow-ups, never silent fall-through. + * + * #117: a slice of (str,*fn)-style TUPLE rows builds the backing as k + * tuple rows (emit_tuple_row_*) — str element = 24B header + ptr→char + * DATAR, &fn element = 8B slot + fn→DATAR reloc, at tuple-slot offsets. + * Bounded to TY_TUPLE rows (fold-6's charclass_map need); other aggregate + * element kinds stay loud below (the honest construction boundary). * Returns 0 only on the early shape guards (not a slice / rhs not * N_ARRLIT) so the caller's gate stays the sole entry contract. */ static int @@ -15181,10 +15221,6 @@ emit_slice_data(FILE *out, Cg *c, const char *directive, const char *name, "asm.c:362); read-only `def` unsupported (#10, rule 7)"); Type *etype = u->sub; Type *eu = type_chase_named(etype); - if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE - || eu->kind == TY_TAGGED)) - fatal("emit_slice_data: slice-of-{str,slice,tagged} literal " - "static-init unsupported (#10 follow-up, rule 7)"); int k = 0; for (Node *e = rhs->list; e; e = e->next) { if (e->kind == N_FIELD && e->str && strcmp(e->str, "...") == 0) @@ -15192,24 +15228,59 @@ emit_slice_data(FILE *out, Cg *c, const char *directive, const char *name, "length in a slice literal (#10, rule 7)"); k++; } - int esz = etype ? (int)etype->size : 1; - /* Synthesize [k]T to ride the emit_array_lit_bytes choke-point. */ - Type arr; - memset(&arr, 0, sizeof arr); - arr.kind = TY_ARRAY; - arr.sub = etype; - arr.alen = (u64)k; - arr.size = (u64)k * (u64)esz; - if (!emit_array_lit_bytes(out, c, &arr, rhs, 0)) - fatal("emit_slice_data: slice-literal element not a foldable " - "constant (#10, rule 7)"); - const char *sym = mod_mangle_value(c, name, module); const char *bk = aprintf(c->a, "%s.d", sym); - /* Writable backing data. */ - fprintf(out, "DATAW %s(SB),\"", bk); - emit_array_lit_bytes(out, c, &arr, rhs, 1); - fputs("\"\n", out); + if (eu && eu->kind == TY_TUPLE) { + /* #117 aggregate-element arm: k tuple rows. Validate every row + * first (two-pass, partial-row safe). The backing is ONE DATAW + * (w6a ignores +off on DATAW), then per-row relocs at the + * row's backing-relative offset. */ + int stride = etype ? (int)etype->size : 0; + for (Node *e = rhs->list; e; e = e->next) { + Node *row = e; + while (row && row->kind == N_CAST) row = row->lhs; + if (row == NULL || row->kind != N_TUPLE + || !tuple_row_foldable(c, eu, row)) + fatal("emit_slice_data: tuple-row element not a " + "foldable constant ((str,*fn) rows only; " + "#117, rule 7)"); + } + fprintf(out, "DATAW %s(SB),\"", bk); + for (Node *e = rhs->list; e; e = e->next) { + Node *row = e; + while (row && row->kind == N_CAST) row = row->lhs; + emit_tuple_row_bytes(c, out, eu, row); + } + fputs("\"\n", out); + int row_off = 0; + for (Node *e = rhs->list; e; e = e->next) { + Node *row = e; + while (row && row->kind == N_CAST) row = row->lhs; + emit_tuple_row_relocs(c, out, bk, row_off, eu, row); + row_off += stride; + } + } else { + if (eu && (eu->kind == TY_STR || eu->kind == TY_SLICE + || eu->kind == TY_TAGGED)) + fatal("emit_slice_data: slice-of-{str,slice,tagged} " + "literal static-init unsupported (#10 follow-up, " + "rule 7)"); + int esz = etype ? (int)etype->size : 1; + /* Synthesize [k]T to ride the emit_array_lit_bytes choke. */ + Type arr; + memset(&arr, 0, sizeof arr); + arr.kind = TY_ARRAY; + arr.sub = etype; + arr.alen = (u64)k; + arr.size = (u64)k * (u64)esz; + if (!emit_array_lit_bytes(out, c, &arr, rhs, 0)) + fatal("emit_slice_data: slice-literal element not a " + "foldable constant (#10, rule 7)"); + /* Writable backing data. */ + fprintf(out, "DATAW %s(SB),\"", bk); + emit_array_lit_bytes(out, c, &arr, rhs, 1); + fputs("\"\n", out); + } /* 24B header: ptr placeholder + LE len + LE cap (both = k). Word * sizes from the type table (rule 13). */ fprintf(out, "DATAW %s(SB),\"", sym); @@ -15554,6 +15625,41 @@ let_pre_intern(Cg *c, Node *file) } continue; } + /* #117: slice-of-tuple global — pre-intern each row's str- + * element literals in row-then-element order so emit_slice_data's + * per-row DATAR patches find their _S_ rodata rows (the #48 + * tuple-arm pattern, walked across k rows). */ + if (u != NULL && u->kind == TY_SLICE && r != NULL + && r->kind == N_ARRLIT) { + Type *seu = type_chase_named(u->sub); + if (seu != NULL && seu->kind == TY_TUPLE) { + for (Node *row = r->list; row; row = row->next) { + Node *rt = row; + while (rt && rt->kind == N_CAST) + rt = rt->lhs; + if (rt == NULL || rt->kind != N_TUPLE) + continue; + Tparam *tp = seu->params; + for (Node *e = rt->list; e; + e = e->next, + tp = tp ? tp->next : NULL) { + Node *ev = e; + while (ev && ev->kind == N_CAST) + ev = ev->lhs; + if (ev == NULL + || ev->kind != N_STRLIT) + continue; + if (!(tp && (type_isstr(tp->type) + || type_isslice(tp->type)))) + continue; + if (ev->strlen > 0) + (void)intern_strlit(c, + ev->str, ev->strlen); + } + } + continue; + } + } /* #87: tagged global with a str/slice-variant literal init — * pre-intern so emit_tagged_data's DATAR (ptr@+8) finds its * _S_ rodata row (the #48 tuple-arm pattern). */ diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 9e2d1596..a282759c 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -38944,6 +38944,49 @@ export fn letpreintern(c: *cgen, file: *node) void = { // #87: tagged global with a str/slice-variant literal init — // pre-intern so emittaggeddata's DATAR (ptr@+8) finds its _S_ // rodata row (the #48 tuple-arm pattern; cstage letpreintern twin). + // #117: slice-of-tuple global — pre-intern each row's + // str-element literals in row-then-element order so + // emitslicedata's per-row DATAR patches find their _S_ + // rodata rows (cstage letpreintern twin). Bounded to + // inline N_TTUPLE element types. + if (!handled && r != nil && d.lhs != nil) { + if (r.kind == nkind.N_ARRLIT + && d.lhs.kind == nkind.N_TSLICE) { + let tupnode: *node = d.lhs.lhs; + if (tupnode != nil && tupnode.kind == nkind.N_TTUPLE) { + handled = true; + let row: *node = r.list; + for (row != nil) { + let rw: *node = row; + for (rw != nil && rw.kind == nkind.N_CAST) { + rw = rw.lhs; + }; + if (rw != nil && rw.kind == nkind.N_TUPLE) { + let tp: *node = tupnode.list; + let e: *node = rw.list; + for (e != nil && tp != nil) { + let et: *node = tp.lhs; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { + ev = ev.lhs; + }; + if (ev != nil) { + if (ev.kind == nkind.N_STRLIT + && (isstrtype(c, et) || isslicetype(c, et))) { + if (ev.str.len > 0) { + internstrlit(c, ev.str); + }; + }; + }; + e = e.next; + tp = tp.next; + }; + }; + row = row.next; + }; + }; + }; + }; if (!handled && r != nil && d.lhs != nil) { let tlt: *node = d.lhs; for (tlt != nil && tlt.kind == nkind.N_TNAME) { @@ -39684,7 +39727,7 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, // and slice-of-{str,slice,tagged} elements (per-element relocs / #17) // all loud-stop (rule 7, #10 follow-ups). fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, - rhs: *node) void = { + sltnode: *node, rhs: *node) void = { let su: *tinfo = slt; su = tichase(su); // Defensive, mirrors cstage emit_slice_data's @@ -39696,14 +39739,6 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, let etype: *tinfo = su.sub; let eu: *tinfo = etype; eu = tichase(eu); - if (eu != nil) { - if (eu.kind == tykind.TY_STR || eu.kind == tykind.TY_SLICE - || eu.kind == tykind.TY_TAGGED) { - let m: str = "emitslicedata: slice-of-{str,slice,tagged} literal static-init unsupported (#10 follow-up, rule 7)\n"; - os.write(2, m.ptr, m.len: u64); - os.exit(1); - }; - }; // Count elements; reject `...` (a slice literal has no target N). let k: i32 = 0; let e: *node = rhs.list; @@ -39718,23 +39753,84 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, k += 1; e = e.next; }; - let esz: i32 = etype.size: i32; - // Synthesize [k]T to ride the emitarraylitbytes choke-point. - let arrt: *tinfo = newtype(tykind.TY_ARRAY); - arrt.sub = etype; - arrt.alen = k: u64; - arrt.size = (k * esz): u64; - if (!emitarraylitbytes(c, arrt, rhs, 0)) { - let m: str = "emitslicedata: slice-literal element not a foldable constant (#10, rule 7)\n"; - os.write(2, m.ptr, m.len: u64); - os.exit(1); + // #117 aggregate-element arm: a slice of inline (str,*fn)-style + // TUPLE rows. The element type-AST node (sltnode.lhs = N_TTUPLE) + // drives the per-element slot classification the node-based + // emittuplerow helpers expect; cstage drives the same off the tuple + // tinfo's params. Bounded to inline N_TTUPLE element types. + let tupnode: *node = nil; + if (sltnode != nil) { tupnode = sltnode.lhs; }; + let istuprow: bool = false; + if (eu != nil && eu.kind == tykind.TY_TUPLE && tupnode != nil) { + if (tupnode.kind == nkind.N_TTUPLE) { istuprow = true; }; + }; + if (istuprow) { + let stride: i32 = etype.size: i32; + // Validate every row before any bytes (two-pass, partial-row + // safe). + let e2: *node = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + let bad: bool = false; + if (row == nil) { bad = true; } + else if (row.kind != nkind.N_TUPLE) { bad = true; } + else if (!tuplerowfoldable(c, tupnode, row)) { bad = true; }; + if (bad) { + let m: str = "emitslicedata: tuple-row element not a foldable constant ((str,*fn) rows only; #117, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + e2 = e2.next; + }; + // Backing: k rows, bytes (one DATAW) then per-row relocs. + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline(".d(SB),\""); + e2 = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + emittuplerowbytes(c, tupnode, row); + e2 = e2.next; + }; + emitline("\"\n"); + let rowoff: i32 = 0; + e2 = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + emittuplerowrelocs(c, name, module, true, rowoff, tupnode, row); + rowoff += stride; + e2 = e2.next; + }; + } else { + if (eu != nil) { + if (eu.kind == tykind.TY_STR || eu.kind == tykind.TY_SLICE + || eu.kind == tykind.TY_TAGGED) { + let m: str = "emitslicedata: slice-of-{str,slice,tagged} literal static-init unsupported (#10 follow-up, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + let esz: i32 = etype.size: i32; + // Synthesize [k]T to ride the emitarraylitbytes choke-point. + let arrt: *tinfo = newtype(tykind.TY_ARRAY); + arrt.sub = etype; + arrt.alen = k: u64; + arrt.size = (k * esz): u64; + if (!emitarraylitbytes(c, arrt, rhs, 0)) { + let m: str = "emitslicedata: slice-literal element not a foldable constant (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + // Writable backing data. + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline(".d(SB),\""); + emitarraylitbytes(c, arrt, rhs, 1); + emitline("\"\n"); }; - // Writable backing data. - emitline("DATAW "); - emitsymnamehint(c, name, module); - emitline(".d(SB),\""); - emitarraylitbytes(c, arrt, rhs, 1); - emitline("\"\n"); // 24B header: ptr placeholder + LE len + LE cap (both = k). Word // sizes from the type table (rule 13). emitline("DATAW "); @@ -39850,6 +39946,23 @@ fn emittaggeddata(c: *cgen, name: str, module: str, tt: *node, rhs: *node, sz: i return true; }; +// nodefnptr — true if `ev` (casts already peeled by the caller) is the +// address-of a top-level fn (`&f`). The detect-half of the FIRST &fn→DATAR +// reloc machinery (#117 slice-row + #119 scalar-global); mirrors the +// address-of-fn codegen arm (fnretlookup at the N_UN TK_AMP ident, +// cgenexpr.ww). The reloc target symbol is emitted via emitfnname at the +// call site (cstage node_fnptr_sym returns the mangled string directly). +fn nodefnptr(c: *cgen, ev: *node) bool = { + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_UN) { return false; }; + if (ev.op != tkind.TK_AMP) { return false; }; + let opnd: *node = ev.lhs; + if (opnd == nil) { return false; }; + if (opnd.kind != nkind.N_IDENT) { return false; }; + if (fnretlookup(c, opnd.str) == nil) { return false; }; + return true; +}; + // tuplerowfoldable — validate every cast-peeled element of `rhs` (an // N_TUPLE) reduces to a static row: an int literal (foldintliteral) or a // str literal in a str/slice slot. A tagged element slot has no @@ -39880,6 +39993,8 @@ fn tuplerowfoldable(c: *cgen, tt: *node, rhs: *node) bool = { let wide: bool = isstrtype(c, et) || isslicetype(c, et); if (wide) { if (ev.kind != nkind.N_STRLIT) { return false; }; + } else if (nodefnptr(c, ev)) { + // #117: a `&fn` element folds to an 8B reloc slot. } else { let v: u64 = 0u64; if (!foldintliteral(ev, &v)) { return false; }; @@ -39918,6 +40033,11 @@ fn emittuplerowbytes(c: *cgen, tt: *node, rhs: *node) void = { i = 16; let ssz: i32 = primtypesize("str"): i32; for (i < ssz) { emitdatawbyte(0u8); i += 1; }; + } else if (nodefnptr(c, ev)) { + // #117: a `&fn` element is an 8B zero ptr placeholder; + // the reloc is patched in emittuplerowrelocs. + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; } else { let v: u64 = 0u64; foldintliteral(ev, &v); @@ -39965,6 +40085,19 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i }; foff += (tyslicesize(): i32); } else { + // #117: the `&fn` element's reloc — the FIRST &fn→DATAR + // in the emitter; patches the 8B slot at holder+foff + // with the fn's TEXT VA via emitfnname. + if (nodefnptr(c, ev)) { + emitline("DATAR "); + emitsymnamehint(c, name, module); + if (backing) { emitline(".d"); }; + emitline("+"); + emitint(foff: i64); + emitline("(SB),"); + emitfnname(c, ev.lhs.str, c.curmod); + emitline("(SB)\n"); + }; // #22: slot stride via the accessor (tagged is // rejected upstream; non-wide is 8 today — keeps the // stride on the accessor scale). @@ -40220,7 +40353,7 @@ fn emitletdataw(c: *cgen, file: *node) void = { // only/`...` shapes (rule 7). if (r != nil && r.kind == nkind.N_ARRLIT) { emitslicedata(c, nm, d.nmod, - d.lhs.type_: *tinfo, r); + d.lhs.type_: *tinfo, d.lhs, r); } else { // zero-init: accept no rhs or nil. // Any other rhs is skipped → diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 59918ff3..621c3f74 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -1390,6 +1390,49 @@ export fn letpreintern(c: *cgen, file: *node) void = { // #87: tagged global with a str/slice-variant literal init — // pre-intern so emittaggeddata's DATAR (ptr@+8) finds its _S_ // rodata row (the #48 tuple-arm pattern; cstage letpreintern twin). + // #117: slice-of-tuple global — pre-intern each row's + // str-element literals in row-then-element order so + // emitslicedata's per-row DATAR patches find their _S_ + // rodata rows (cstage letpreintern twin). Bounded to + // inline N_TTUPLE element types. + if (!handled && r != nil && d.lhs != nil) { + if (r.kind == nkind.N_ARRLIT + && d.lhs.kind == nkind.N_TSLICE) { + let tupnode: *node = d.lhs.lhs; + if (tupnode != nil && tupnode.kind == nkind.N_TTUPLE) { + handled = true; + let row: *node = r.list; + for (row != nil) { + let rw: *node = row; + for (rw != nil && rw.kind == nkind.N_CAST) { + rw = rw.lhs; + }; + if (rw != nil && rw.kind == nkind.N_TUPLE) { + let tp: *node = tupnode.list; + let e: *node = rw.list; + for (e != nil && tp != nil) { + let et: *node = tp.lhs; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { + ev = ev.lhs; + }; + if (ev != nil) { + if (ev.kind == nkind.N_STRLIT + && (isstrtype(c, et) || isslicetype(c, et))) { + if (ev.str.len > 0) { + internstrlit(c, ev.str); + }; + }; + }; + e = e.next; + tp = tp.next; + }; + }; + row = row.next; + }; + }; + }; + }; if (!handled && r != nil && d.lhs != nil) { let tlt: *node = d.lhs; for (tlt != nil && tlt.kind == nkind.N_TNAME) { @@ -2130,7 +2173,7 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, // and slice-of-{str,slice,tagged} elements (per-element relocs / #17) // all loud-stop (rule 7, #10 follow-ups). fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, - rhs: *node) void = { + sltnode: *node, rhs: *node) void = { let su: *tinfo = slt; su = tichase(su); // Defensive, mirrors cstage emit_slice_data's @@ -2142,14 +2185,6 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, let etype: *tinfo = su.sub; let eu: *tinfo = etype; eu = tichase(eu); - if (eu != nil) { - if (eu.kind == tykind.TY_STR || eu.kind == tykind.TY_SLICE - || eu.kind == tykind.TY_TAGGED) { - let m: str = "emitslicedata: slice-of-{str,slice,tagged} literal static-init unsupported (#10 follow-up, rule 7)\n"; - os.write(2, m.ptr, m.len: u64); - os.exit(1); - }; - }; // Count elements; reject `...` (a slice literal has no target N). let k: i32 = 0; let e: *node = rhs.list; @@ -2164,23 +2199,84 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, k += 1; e = e.next; }; - let esz: i32 = etype.size: i32; - // Synthesize [k]T to ride the emitarraylitbytes choke-point. - let arrt: *tinfo = newtype(tykind.TY_ARRAY); - arrt.sub = etype; - arrt.alen = k: u64; - arrt.size = (k * esz): u64; - if (!emitarraylitbytes(c, arrt, rhs, 0)) { - let m: str = "emitslicedata: slice-literal element not a foldable constant (#10, rule 7)\n"; - os.write(2, m.ptr, m.len: u64); - os.exit(1); + // #117 aggregate-element arm: a slice of inline (str,*fn)-style + // TUPLE rows. The element type-AST node (sltnode.lhs = N_TTUPLE) + // drives the per-element slot classification the node-based + // emittuplerow helpers expect; cstage drives the same off the tuple + // tinfo's params. Bounded to inline N_TTUPLE element types. + let tupnode: *node = nil; + if (sltnode != nil) { tupnode = sltnode.lhs; }; + let istuprow: bool = false; + if (eu != nil && eu.kind == tykind.TY_TUPLE && tupnode != nil) { + if (tupnode.kind == nkind.N_TTUPLE) { istuprow = true; }; + }; + if (istuprow) { + let stride: i32 = etype.size: i32; + // Validate every row before any bytes (two-pass, partial-row + // safe). + let e2: *node = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + let bad: bool = false; + if (row == nil) { bad = true; } + else if (row.kind != nkind.N_TUPLE) { bad = true; } + else if (!tuplerowfoldable(c, tupnode, row)) { bad = true; }; + if (bad) { + let m: str = "emitslicedata: tuple-row element not a foldable constant ((str,*fn) rows only; #117, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + e2 = e2.next; + }; + // Backing: k rows, bytes (one DATAW) then per-row relocs. + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline(".d(SB),\""); + e2 = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + emittuplerowbytes(c, tupnode, row); + e2 = e2.next; + }; + emitline("\"\n"); + let rowoff: i32 = 0; + e2 = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + emittuplerowrelocs(c, name, module, true, rowoff, tupnode, row); + rowoff += stride; + e2 = e2.next; + }; + } else { + if (eu != nil) { + if (eu.kind == tykind.TY_STR || eu.kind == tykind.TY_SLICE + || eu.kind == tykind.TY_TAGGED) { + let m: str = "emitslicedata: slice-of-{str,slice,tagged} literal static-init unsupported (#10 follow-up, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + let esz: i32 = etype.size: i32; + // Synthesize [k]T to ride the emitarraylitbytes choke-point. + let arrt: *tinfo = newtype(tykind.TY_ARRAY); + arrt.sub = etype; + arrt.alen = k: u64; + arrt.size = (k * esz): u64; + if (!emitarraylitbytes(c, arrt, rhs, 0)) { + let m: str = "emitslicedata: slice-literal element not a foldable constant (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + // Writable backing data. + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline(".d(SB),\""); + emitarraylitbytes(c, arrt, rhs, 1); + emitline("\"\n"); }; - // Writable backing data. - emitline("DATAW "); - emitsymnamehint(c, name, module); - emitline(".d(SB),\""); - emitarraylitbytes(c, arrt, rhs, 1); - emitline("\"\n"); // 24B header: ptr placeholder + LE len + LE cap (both = k). Word // sizes from the type table (rule 13). emitline("DATAW "); @@ -2296,6 +2392,23 @@ fn emittaggeddata(c: *cgen, name: str, module: str, tt: *node, rhs: *node, sz: i return true; }; +// nodefnptr — true if `ev` (casts already peeled by the caller) is the +// address-of a top-level fn (`&f`). The detect-half of the FIRST &fn→DATAR +// reloc machinery (#117 slice-row + #119 scalar-global); mirrors the +// address-of-fn codegen arm (fnretlookup at the N_UN TK_AMP ident, +// cgenexpr.ww). The reloc target symbol is emitted via emitfnname at the +// call site (cstage node_fnptr_sym returns the mangled string directly). +fn nodefnptr(c: *cgen, ev: *node) bool = { + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_UN) { return false; }; + if (ev.op != tkind.TK_AMP) { return false; }; + let opnd: *node = ev.lhs; + if (opnd == nil) { return false; }; + if (opnd.kind != nkind.N_IDENT) { return false; }; + if (fnretlookup(c, opnd.str) == nil) { return false; }; + return true; +}; + // tuplerowfoldable — validate every cast-peeled element of `rhs` (an // N_TUPLE) reduces to a static row: an int literal (foldintliteral) or a // str literal in a str/slice slot. A tagged element slot has no @@ -2326,6 +2439,8 @@ fn tuplerowfoldable(c: *cgen, tt: *node, rhs: *node) bool = { let wide: bool = isstrtype(c, et) || isslicetype(c, et); if (wide) { if (ev.kind != nkind.N_STRLIT) { return false; }; + } else if (nodefnptr(c, ev)) { + // #117: a `&fn` element folds to an 8B reloc slot. } else { let v: u64 = 0u64; if (!foldintliteral(ev, &v)) { return false; }; @@ -2364,6 +2479,11 @@ fn emittuplerowbytes(c: *cgen, tt: *node, rhs: *node) void = { i = 16; let ssz: i32 = primtypesize("str"): i32; for (i < ssz) { emitdatawbyte(0u8); i += 1; }; + } else if (nodefnptr(c, ev)) { + // #117: a `&fn` element is an 8B zero ptr placeholder; + // the reloc is patched in emittuplerowrelocs. + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; } else { let v: u64 = 0u64; foldintliteral(ev, &v); @@ -2411,6 +2531,19 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i }; foff += (tyslicesize(): i32); } else { + // #117: the `&fn` element's reloc — the FIRST &fn→DATAR + // in the emitter; patches the 8B slot at holder+foff + // with the fn's TEXT VA via emitfnname. + if (nodefnptr(c, ev)) { + emitline("DATAR "); + emitsymnamehint(c, name, module); + if (backing) { emitline(".d"); }; + emitline("+"); + emitint(foff: i64); + emitline("(SB),"); + emitfnname(c, ev.lhs.str, c.curmod); + emitline("(SB)\n"); + }; // #22: slot stride via the accessor (tagged is // rejected upstream; non-wide is 8 today — keeps the // stride on the accessor scale). @@ -2666,7 +2799,7 @@ fn emitletdataw(c: *cgen, file: *node) void = { // only/`...` shapes (rule 7). if (r != nil && r.kind == nkind.N_ARRLIT) { emitslicedata(c, nm, d.nmod, - d.lhs.type_: *tinfo, r); + d.lhs.type_: *tinfo, d.lhs, r); } else { // zero-init: accept no rhs or nil. // Any other rhs is skipped → diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 4ff6954d..36e17eb2 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -38944,6 +38944,49 @@ export fn letpreintern(c: *cgen, file: *node) void = { // #87: tagged global with a str/slice-variant literal init — // pre-intern so emittaggeddata's DATAR (ptr@+8) finds its _S_ // rodata row (the #48 tuple-arm pattern; cstage letpreintern twin). + // #117: slice-of-tuple global — pre-intern each row's + // str-element literals in row-then-element order so + // emitslicedata's per-row DATAR patches find their _S_ + // rodata rows (cstage letpreintern twin). Bounded to + // inline N_TTUPLE element types. + if (!handled && r != nil && d.lhs != nil) { + if (r.kind == nkind.N_ARRLIT + && d.lhs.kind == nkind.N_TSLICE) { + let tupnode: *node = d.lhs.lhs; + if (tupnode != nil && tupnode.kind == nkind.N_TTUPLE) { + handled = true; + let row: *node = r.list; + for (row != nil) { + let rw: *node = row; + for (rw != nil && rw.kind == nkind.N_CAST) { + rw = rw.lhs; + }; + if (rw != nil && rw.kind == nkind.N_TUPLE) { + let tp: *node = tupnode.list; + let e: *node = rw.list; + for (e != nil && tp != nil) { + let et: *node = tp.lhs; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { + ev = ev.lhs; + }; + if (ev != nil) { + if (ev.kind == nkind.N_STRLIT + && (isstrtype(c, et) || isslicetype(c, et))) { + if (ev.str.len > 0) { + internstrlit(c, ev.str); + }; + }; + }; + e = e.next; + tp = tp.next; + }; + }; + row = row.next; + }; + }; + }; + }; if (!handled && r != nil && d.lhs != nil) { let tlt: *node = d.lhs; for (tlt != nil && tlt.kind == nkind.N_TNAME) { @@ -39684,7 +39727,7 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, // and slice-of-{str,slice,tagged} elements (per-element relocs / #17) // all loud-stop (rule 7, #10 follow-ups). fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, - rhs: *node) void = { + sltnode: *node, rhs: *node) void = { let su: *tinfo = slt; su = tichase(su); // Defensive, mirrors cstage emit_slice_data's @@ -39696,14 +39739,6 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, let etype: *tinfo = su.sub; let eu: *tinfo = etype; eu = tichase(eu); - if (eu != nil) { - if (eu.kind == tykind.TY_STR || eu.kind == tykind.TY_SLICE - || eu.kind == tykind.TY_TAGGED) { - let m: str = "emitslicedata: slice-of-{str,slice,tagged} literal static-init unsupported (#10 follow-up, rule 7)\n"; - os.write(2, m.ptr, m.len: u64); - os.exit(1); - }; - }; // Count elements; reject `...` (a slice literal has no target N). let k: i32 = 0; let e: *node = rhs.list; @@ -39718,23 +39753,84 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo, k += 1; e = e.next; }; - let esz: i32 = etype.size: i32; - // Synthesize [k]T to ride the emitarraylitbytes choke-point. - let arrt: *tinfo = newtype(tykind.TY_ARRAY); - arrt.sub = etype; - arrt.alen = k: u64; - arrt.size = (k * esz): u64; - if (!emitarraylitbytes(c, arrt, rhs, 0)) { - let m: str = "emitslicedata: slice-literal element not a foldable constant (#10, rule 7)\n"; - os.write(2, m.ptr, m.len: u64); - os.exit(1); + // #117 aggregate-element arm: a slice of inline (str,*fn)-style + // TUPLE rows. The element type-AST node (sltnode.lhs = N_TTUPLE) + // drives the per-element slot classification the node-based + // emittuplerow helpers expect; cstage drives the same off the tuple + // tinfo's params. Bounded to inline N_TTUPLE element types. + let tupnode: *node = nil; + if (sltnode != nil) { tupnode = sltnode.lhs; }; + let istuprow: bool = false; + if (eu != nil && eu.kind == tykind.TY_TUPLE && tupnode != nil) { + if (tupnode.kind == nkind.N_TTUPLE) { istuprow = true; }; + }; + if (istuprow) { + let stride: i32 = etype.size: i32; + // Validate every row before any bytes (two-pass, partial-row + // safe). + let e2: *node = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + let bad: bool = false; + if (row == nil) { bad = true; } + else if (row.kind != nkind.N_TUPLE) { bad = true; } + else if (!tuplerowfoldable(c, tupnode, row)) { bad = true; }; + if (bad) { + let m: str = "emitslicedata: tuple-row element not a foldable constant ((str,*fn) rows only; #117, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + e2 = e2.next; + }; + // Backing: k rows, bytes (one DATAW) then per-row relocs. + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline(".d(SB),\""); + e2 = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + emittuplerowbytes(c, tupnode, row); + e2 = e2.next; + }; + emitline("\"\n"); + let rowoff: i32 = 0; + e2 = rhs.list; + for (e2 != nil) { + let row: *node = e2; + for (row != nil && row.kind == nkind.N_CAST) { row = row.lhs; }; + emittuplerowrelocs(c, name, module, true, rowoff, tupnode, row); + rowoff += stride; + e2 = e2.next; + }; + } else { + if (eu != nil) { + if (eu.kind == tykind.TY_STR || eu.kind == tykind.TY_SLICE + || eu.kind == tykind.TY_TAGGED) { + let m: str = "emitslicedata: slice-of-{str,slice,tagged} literal static-init unsupported (#10 follow-up, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + let esz: i32 = etype.size: i32; + // Synthesize [k]T to ride the emitarraylitbytes choke-point. + let arrt: *tinfo = newtype(tykind.TY_ARRAY); + arrt.sub = etype; + arrt.alen = k: u64; + arrt.size = (k * esz): u64; + if (!emitarraylitbytes(c, arrt, rhs, 0)) { + let m: str = "emitslicedata: slice-literal element not a foldable constant (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + // Writable backing data. + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline(".d(SB),\""); + emitarraylitbytes(c, arrt, rhs, 1); + emitline("\"\n"); }; - // Writable backing data. - emitline("DATAW "); - emitsymnamehint(c, name, module); - emitline(".d(SB),\""); - emitarraylitbytes(c, arrt, rhs, 1); - emitline("\"\n"); // 24B header: ptr placeholder + LE len + LE cap (both = k). Word // sizes from the type table (rule 13). emitline("DATAW "); @@ -39850,6 +39946,23 @@ fn emittaggeddata(c: *cgen, name: str, module: str, tt: *node, rhs: *node, sz: i return true; }; +// nodefnptr — true if `ev` (casts already peeled by the caller) is the +// address-of a top-level fn (`&f`). The detect-half of the FIRST &fn→DATAR +// reloc machinery (#117 slice-row + #119 scalar-global); mirrors the +// address-of-fn codegen arm (fnretlookup at the N_UN TK_AMP ident, +// cgenexpr.ww). The reloc target symbol is emitted via emitfnname at the +// call site (cstage node_fnptr_sym returns the mangled string directly). +fn nodefnptr(c: *cgen, ev: *node) bool = { + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_UN) { return false; }; + if (ev.op != tkind.TK_AMP) { return false; }; + let opnd: *node = ev.lhs; + if (opnd == nil) { return false; }; + if (opnd.kind != nkind.N_IDENT) { return false; }; + if (fnretlookup(c, opnd.str) == nil) { return false; }; + return true; +}; + // tuplerowfoldable — validate every cast-peeled element of `rhs` (an // N_TUPLE) reduces to a static row: an int literal (foldintliteral) or a // str literal in a str/slice slot. A tagged element slot has no @@ -39880,6 +39993,8 @@ fn tuplerowfoldable(c: *cgen, tt: *node, rhs: *node) bool = { let wide: bool = isstrtype(c, et) || isslicetype(c, et); if (wide) { if (ev.kind != nkind.N_STRLIT) { return false; }; + } else if (nodefnptr(c, ev)) { + // #117: a `&fn` element folds to an 8B reloc slot. } else { let v: u64 = 0u64; if (!foldintliteral(ev, &v)) { return false; }; @@ -39918,6 +40033,11 @@ fn emittuplerowbytes(c: *cgen, tt: *node, rhs: *node) void = { i = 16; let ssz: i32 = primtypesize("str"): i32; for (i < ssz) { emitdatawbyte(0u8); i += 1; }; + } else if (nodefnptr(c, ev)) { + // #117: a `&fn` element is an 8B zero ptr placeholder; + // the reloc is patched in emittuplerowrelocs. + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; } else { let v: u64 = 0u64; foldintliteral(ev, &v); @@ -39965,6 +40085,19 @@ fn emittuplerowrelocs(c: *cgen, name: str, module: str, backing: bool, rowoff: i }; foff += (tyslicesize(): i32); } else { + // #117: the `&fn` element's reloc — the FIRST &fn→DATAR + // in the emitter; patches the 8B slot at holder+foff + // with the fn's TEXT VA via emitfnname. + if (nodefnptr(c, ev)) { + emitline("DATAR "); + emitsymnamehint(c, name, module); + if (backing) { emitline(".d"); }; + emitline("+"); + emitint(foff: i64); + emitline("(SB),"); + emitfnname(c, ev.lhs.str, c.curmod); + emitline("(SB)\n"); + }; // #22: slot stride via the accessor (tagged is // rejected upstream; non-wide is 8 today — keeps the // stride on the accessor scale). @@ -40220,7 +40353,7 @@ fn emitletdataw(c: *cgen, file: *node) void = { // only/`...` shapes (rule 7). if (r != nil && r.kind == nkind.N_ARRLIT) { emitslicedata(c, nm, d.nmod, - d.lhs.type_: *tinfo, r); + d.lhs.type_: *tinfo, d.lhs, r); } else { // zero-init: accept no rhs or nil. // Any other rhs is skipped → diff --git a/test/wcc/946_const_slice_aggregate_run.c b/test/wcc/946_const_slice_aggregate_run.c new file mode 100644 index 00000000..c6eb52bd --- /dev/null +++ b/test/wcc/946_const_slice_aggregate_run.c @@ -0,0 +1,271 @@ +/* + * 946_const_slice_aggregate_run — #117/#119: const slice of (str,*fn) + * tuple rows + scalar &fn globals emit DATA (was both-stage LOUD at + * emit_slice_data / silent-no-DATA for the scalar &fn). + * + * #117: a `const [](str,*fn)` (fold-6's charclass_map shape) emitted NO + * DATA — emit_slice_data rode the scalar emit_array_lit_bytes backing and + * loud-stopped on the str/tuple/fn elements. Now the backing is k tuple + * rows (emit_tuple_row_*): each str element a 24B header + ptr→char DATAR, + * each &fn element an 8B slot + the BRAND-NEW &fn→DATAR reloc, at the 8B + * tuple-slot offsets (str@row+0/24B, *fn@row+24/8B = 32B/row, ken-confirmed + * layout). #119: a scalar `let p: *fn = &f` global wires the same reloc + * helper at the emit_lets 8B-scalar site (pre-#119: no DATA → undefined + * ref / garbage deref). + * + * POLARITY: align-BOTH — both stages were LOUD at base, no runtime + * reference. byte-id is BLIND (#263): the runtime READ-BACK is the net. + * The fn-reloc (the genuinely new machinery) is pinned at runtime with + * DISTINCT fns per row, so a wrong reloc is caught. + * + * READ-PATH NOTE (honest boundary): the indexed/ptr tuple-element FIELD + * reads off a const slice (`tbl[i].0`, `(&tbl[i]).0`) and the whole-element + * register-cursor read are pre-existing-broken (the #37/#58 index-cursor + * truncation + "unsupported field-read shape" — separate mechanisms, filed + * siblings; fold-6's compile arm also needs them). The one runtime + * observation available of the BACKING is the whole-element bind's word0 + * (loaded correctly even by the truncating cursor): so the fn-reloc rows + * put the *fn FIRST (word0) to read it back, and the consumer-ordering + * (str,*fn) rows pin `len(tbl)` (header) + cs==ww byte-id on the DATA. The + * str-element header+reloc reuses the proven emit_tuple_data machinery + * (941 t3_global_elem runtime-pinned) — verified here by byte-id. + * + * NNN<950, self-contained (/tmp, no imports) — rule-14's selfhost-sibling + * race does not apply (941/944/945 precedent). Every K_RUN row also pins + * cstage/wwstage asm byte-id. + */ +#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; +} + +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), cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +#define K_RUN 0 /* build+run both drivers, exit==want, + cs==ww byte-id */ +#define K_BUILDERR 1 /* build must FAIL with experr on BOTH drivers (rule 7) */ + +struct row { const char *label; const char *src; int want; + int kind; const char *experr; }; + +static int +errlog_has(const char *path, const char *needle) +{ + FILE *f = fopen(path, "rb"); + if (!f) return 0; + char buf[8192]; + size_t got = fread(buf, 1, sizeof buf - 1, f); + fclose(f); + buf[got] = '\0'; + return strstr(buf, needle) != NULL; +} + +static const struct row rows[] = { + /* #117 fn-reloc readback: *fn FIRST so the whole-element bind's + * word0 (the only backing word the truncating index-cursor loads + * correctly) IS the fn ptr. DISTINCT fns per row — a wrong reloc + * (or a swapped per-row offset) is caught by calling each back. */ + { "fnfirst_reloc", + "package main;\n" + "fn fa(c: rune) bool = { return c == 'a'; };\n" + "fn fz(c: rune) bool = { return c == 'z'; };\n" + "const tbl: [](*fn(c: rune) bool, str) = [(&fa, \"aa\"), (&fz, \"zzz\")];\n" + "export fn main() i32 = {\n" + " let e0 = tbl[0];\n" + " let e1 = tbl[1];\n" + " let f0 = e0.0;\n" + " let f1 = e1.0;\n" + " if (!(*f0)('a')) { return 1; };\n" + " if ((*f0)('z')) { return 2; };\n" + " if (!(*f1)('z')) { return 3; };\n" + " if ((*f1)('a')) { return 4; };\n" + " if (len(tbl) != 2) { return 5; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* fold-6's exact consumer ordering: (str,*fn). The str + fn element + * relocs at the consumer-ordering slot offsets are pinned by cs==ww + * byte-id on the DATA; len(tbl) confirms the slice header resolves. */ + { "consumer_str_fn", + "package main;\n" + "fn fa(c: rune) bool = { return c == 'a'; };\n" + "fn fz(c: rune) bool = { return c == 'z'; };\n" + "const tbl: [](str, *fn(c: rune) bool) = [(\"aa\", &fa), (\"zzz\", &fz)];\n" + "export fn main() i32 = {\n" + " if (len(tbl) != 2) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* multi-row str backing: 3 rows of distinct str lengths — pins the + * per-row 32B stride of the str-header backing (byte-id) + header. */ + { "consumer_3row", + "package main;\n" + "fn fa(c: rune) bool = { return c == 'a'; };\n" + "const tbl: [](str, *fn(c: rune) bool) = " + "[(\"a\", &fa), (\"bb\", &fa), (\"ccc\", &fa)];\n" + "export fn main() i32 = {\n" + " if (len(tbl) != 3) { return 1; };\n" + " return 0;\n" + "};\n", 0, K_RUN, NULL }, + /* honest boundary (rule 7): a non-tuple aggregate element (here a + * bare []str) stays LOUD, symmetric both stages — #117 is NARROW to + * the (str,*fn) tuple form, not the full slice-of-aggregate family. */ + { "loud_slice_of_str", + "package main;\n" + "const xs: []str = [\"a\", \"b\"];\n" + "export fn main() i32 = { return len(xs): i32; };\n", 0, + K_BUILDERR, "slice-of-{str,slice,tagged}" }, +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[96], tmpdir[96], errf[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/csa_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/csa_%d_d_%d", getpid(), i); + snprintf(errf, sizeof errf, "/tmp/csa_%d_e_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s >/dev/null 2>%s", + tmpdir, driver, src, errf); + int brc = runwait(cmd); + if (r->kind == K_BUILDERR) { + int ok = (brc != 0) + && (r->experr == NULL || errlog_has(errf, r->experr)); + if (!ok) + fprintf(stderr, "row[%s]: %s expected loud builderr " + "\"%s\" (brc=%d)\n", r->label, driver, + r->experr ? r->experr : "", brc); + unlink(src); unlink(errf); rmdir(tmpdir); + return ok ? 0 : 1; + } + if (brc != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); unlink(errf); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[256]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + + unlink(src); unlink(outbin); unlink(errf); rmdir(tmpdir); + if (got != r->want) { + fprintf(stderr, "row[%s]: %s exit %d, want %d\n", + r->label, driver, got, r->want); + return 1; + } + return 0; +} + +static int +asm_byte_identical(const char *bin, const struct row *r, int i) +{ + char src[96], cs[96], ws[96], cmd[1024]; + snprintf(src, sizeof src, "/tmp/csa_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/csa_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/csa_asm_%d_%d_w.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c errored\n", r->label); + unlink(src); + return -1; + } + snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", + bin, ws, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); + unlink(src); unlink(cs); + return -1; + } + int rc = slurp_eq(cs, ws); + if (rc != 0) + fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", + r->label); + unlink(src); unlink(cs); unlink(ws); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[2080]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char cdrv[2120], wdrv[2120]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + + for (int i = 0; i < n; i++) { + total++; + if (run_driver(cdrv, &rows[i], i) != 0) fail++; + } + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + total++; + if (run_driver(wdrv, &rows[i], i) != 0) fail++; + } + for (int i = 0; i < n; i++) { + if (rows[i].kind == K_BUILDERR) + continue; + total++; + if (asm_byte_identical(bin, &rows[i], i) != 0) fail++; + } + } + + if (fail) { + fprintf(stderr, "const_slice_aggregate: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("const_slice_aggregate: %d/%d ok\n", total, total); + return 0; +}