diff --git a/Makefile b/Makefile index d92ab015..b7919a86 100644 --- a/Makefile +++ b/Makefile @@ -244,6 +244,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_arr_tagged_elem \ $(BIN)/test_arr_infer_len \ $(BIN)/test_slice_str_global_zero \ + $(BIN)/test_slice_literal_global \ $(BIN)/test_dot_str_chained_arg \ $(BIN)/test_dot_slice_arg \ $(BIN)/test_dot_tagged_source \ @@ -582,6 +583,12 @@ $(BIN)/test_slice_str_global_zero: test/wcc/686_slice_str_global_zero.c $(BIN)/w $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_slice_literal_global: test/wcc/687_slice_literal_global.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_dot_str_chained_arg: test/wcc/692_dot_str_chained_arg.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 57749c60..bf5e8b15 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -11647,6 +11647,80 @@ emit_array_data(FILE *out, Cg *c, const char *directive, return 1; } +/* emit_slice_data — module-level `let g: []T = [v0, v1, …];` static + * init (#10 part a). A slice literal needs three things: a writable + * backing holding the k elements, a 24B header { ptr, len, cap }, and a + * DATAR patching the ptr word with the backing's VA. The backing rides + * the emit_array_lit_bytes choke-point via a synthesized [k]T so int / + * float / struct / nested-array elements reduce exactly as a [N]T + * global's do. The backing symbol is ".d": a second '.' can + * never collide with a user global, since source identifiers carry no + * '.' (one is inserted only by the module mangle). + * + * 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. + * 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 +emit_slice_data(FILE *out, Cg *c, const char *directive, const char *name, + const char *module, Type *t, Node *rhs) +{ + Type *u = type_unwrap(t); + if (u == NULL || u->kind != TY_SLICE) return 0; + if (rhs == NULL || rhs->kind != N_ARRLIT) return 0; + if (strcmp(directive, "DATAW") != 0) + fatal("emit_slice_data: slice-literal static-init needs a " + "writable `let` (DATAR holder must be DATAW, w6a " + "asm.c:362); read-only `def` unsupported (#10, rule 7)"); + Type *etype = u->sub; + Type *eu = (etype && etype->kind == TY_NAMED) ? etype->under : 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) + fatal("emit_slice_data: '...' repeat has no target " + "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); + /* 24B header: ptr placeholder + LE len + LE cap (both = k). Word + * sizes from the type table (rule 13). */ + fprintf(out, "DATAW %s(SB),\"", sym); + for (int i = 0; i < (int)ty_uintptr->size; i++) emit_data_byte(out, 0); + u64 kv = (u64)k; + for (int i = 0; i < (int)ty_size->size; i++) + emit_data_byte(out, (u8)((kv >> (i * 8)) & 0xff)); + for (int i = 0; i < (int)ty_size->size; i++) + emit_data_byte(out, (u8)((kv >> (i * 8)) & 0xff)); + fputs("\"\n", out); + /* Patch the ptr word with the backing VA. */ + fprintf(out, "DATAR %s+0(SB),%s(SB)\n", sym, bk); + return 1; +} + static void emit_lets(Cg *c, FILE *out, Node *file) { @@ -11725,6 +11799,17 @@ emit_lets(Cg *c, FILE *out, Node *file) continue; /* fall through to zero-init */ } + /* #10: slice-literal static init `let g: []T = [v0, …];`. + * Header { ptr, len, cap } + a writable backing + a DATAR + * patching ptr → backing. emit_slice_data loud-stops on the + * deferred element kinds and on the read-only / `...` shapes + * (rule 7); when the gate matches it always emits or fatals, + * never silently falls through. */ + if (r != NULL && r->kind == N_ARRLIT && let_isslice(d->type)) { + if (emit_slice_data(out, c, "DATAW", d->str, + d->module, d->type, r)) + continue; + } /* Otherwise: zero-init. str accepts nil / ""; struct * accepts no rhs at all; slice accepts nil; array with no * literal init (or a non-constant one) zero-fills. */ @@ -11809,6 +11894,15 @@ emit_defs(Cg *c, FILE *out, Node *file) d->str, d->module, d->type, d->rhs); continue; } + /* #10: a read-only `def g: []T = [...]` slice literal can't + * carry the ptr reloc emit_slice_data needs (DATAR holder must + * be DATAW, w6a asm.c:362). Loud-stop rather than silently + * emit nothing and surface an undefined-ref at link. */ + if (let_isslice(d->type) && d->rhs->kind == N_ARRLIT) + fatal("emit_defs: module-level slice-literal init needs " + "a writable `let` (DATAR holder must be DATAW, w6a " + "asm.c:362); read-only `def` unsupported (#10, " + "rule 7)"); } (void)c; } diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e9b5e468..3a4505cf 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -32922,14 +32922,25 @@ fn letvarisstr(c: *cgen, name: str) bool = { // letvarisslice — is the named top-level let a slice global? // Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the // load sequence ends with `MOVQ 16(CX), CX` (overwrites the -// address holder with the cap). Mirrors C cgen's `let_isslice`. +// address holder with the cap). Mirrors C cgen's `let_isslice`, +// which resolves the declared type via type_unwrap — so an alias of +// a slice IS a slice. Walks the N_TNAME alias chain exactly as the +// sibling letvarisstr does (the structural N_TSLICE node is the +// terminator, in place of letvarisstr's "str" name): without this, +// a `type S = []T; let g: S = [...]` global misroutes to the str arm +// and never reaches emitslicedata, diverging from cstage (#10). fn letvarisslice(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { let t: *node = lv.tnode; - if (t == nil) { return false; }; - if (t.kind == nkind.N_TSLICE) { return true; }; + for (t != nil) { + if (t.kind == nkind.N_TSLICE) { return true; }; + if (t.kind != nkind.N_TNAME) { return false; }; + let nx: *node = aliaslookup(c, t.str); + if (nx == nil) { return false; }; + t = nx; + }; return false; }; lv = lv.lvnext; @@ -32937,24 +32948,6 @@ fn letvarisslice(c: *cgen, name: str) bool = { return false; }; -// letdeclkind — unwrapped (TY_NAMED-peeled) kind of the let decl's -// declared type, read off the checker-stamped type-expression node -// (d.lhs.type_; resolvewalk stamps N_TNAME/N_TSLICE/… at check.ww:566). -// Mirrors cstage type_unwrap(d->type)->kind, the basis of let_isstr/ -// let_isslice (cgen.c:1026/1036). Post-#1 str and slice share a 24B -// header, so emitletdataw's size-only str and slice arms BOTH fired for -// a bare 24B global and double-emitted its DATAW (#10 part b); these two -// arms now branch on kind, not size. nil-safe: returns TY_VOID when -// unstamped so a 24B global still falls to the str arm (zero-init bytes -// are identical either way, so byte-id holds for the unstamped case). -fn letdeclkind(d: *node) tykind = { - if (d.lhs == nil) { return tykind.TY_VOID; }; - let ti: *tinfo = d.lhs.type_: *tinfo; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; - if (ti == nil) { return tykind.TY_VOID; }; - return ti.kind; -}; - // letvarisfloat — slot size for a named float global, or 0 if not // a float-typed let. Walks aliases so the byte-identity contract // matches C cgen's `let_isfloat` (which resolves Type kinds). @@ -33893,6 +33886,102 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, return true; }; +// emitslicedata — module-level `let g: []T = [v0,…];` static init (#10 +// part a). Mirror of cstage emit_slice_data. A slice literal needs a +// writable backing holding the k elements, a 24B header { ptr, len, cap +// }, and a DATAR patching the ptr word with the backing's VA. The +// backing rides the emitarraylitbytes choke-point via a synthesized +// [k]T so int/float/struct/nested-array elements reduce exactly as a +// [N]T global's do. Backing symbol = ".d": a second '.' can +// never collide with a user global (source identifiers carry no '.'). +// Scoped to a writable `let` — A_DATAR's holder must be a DATAW slot +// (w6a asm.c:362); read-only `def`, `...` repeat (no target length), +// 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 = { + let su: *tinfo = slt; + for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; }; + // Defensive, mirrors cstage emit_slice_data's + // `if (u == NULL || u->kind != TY_SLICE) return 0` (rule-10): the + // letvarisslice gate already guarantees a slice, so this is + // unreachable — it guards the su.sub deref below if the contract + // is ever violated rather than nil-derefing. + if (su == nil || su.kind != tykind.TY_SLICE) { return; }; + let etype: *tinfo = su.sub; + let eu: *tinfo = etype; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + 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; + for (e != nil) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + let m: str = "emitslicedata: '...' repeat has no target length in a slice literal (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + 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); + }; + // 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 "); + emitsymnamehint(c, name, module); + emitline("(SB),\""); + let i: i32 = 0; + let ptrsz: i32 = primtypesize("uintptr"): i32; + for (i < ptrsz) { emitdatawbyte(0u8); i += 1; }; + let lensz: i32 = primtypesize("size"): i32; + i = 0; + let kv: u64 = k: u64; + for (i < lensz) { + emitdatawbyte((kv & 255u64): u8); + kv = kv >> 8u64; + i += 1; + }; + i = 0; + kv = k: u64; + for (i < lensz) { + emitdatawbyte((kv & 255u64): u8); + kv = kv >> 8u64; + i += 1; + }; + emitline("\"\n"); + // Patch the ptr word with the backing VA. + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+0(SB),"); + emitsymnamehint(c, name, module); + emitline(".d(SB)\n"); +}; + fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -33967,7 +34056,7 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - if (sz == primtypesize("str"): i32 && !issg && letdeclkind(d) != tykind.TY_SLICE) { + if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; @@ -34032,34 +34121,43 @@ fn emitletdataw(c: *cgen, file: *node) void = { }; }; }; - if (sz == tyslicesize(): i32 && !issg && letdeclkind(d) == tykind.TY_SLICE) { - // Slice: zero-init only (no slice-literal - // syntax to honour). Any rhs other than - // `nil` is skipped → undefined symbol at - // link. - let ok: bool = true; - if (d.rhs != nil) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; - }; - ok = false; - if (r != nil) { - if (r.kind == nkind.N_NIL) { ok = true; }; - }; + if (sz == tyslicesize(): i32 && !issg && letvarisslice(c, nm)) { + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; }; - if (ok) { - emitline("DATAW "); - emitsymnamehint(c, nm, d.nmod); - emitline("(SB),\""); - let i: i32 = 0; - let szsl: i32 = tyslicesize(): i32; - for (i < szsl) { - emitdatawbyte(0u8); - i += 1; + // #10 part a: slice-literal static init + // routes through emitslicedata (header + + // writable backing + DATAR). Loud-stops on + // the deferred element kinds and the read- + // only/`...` shapes (rule 7). + if (r != nil && r.kind == nkind.N_ARRLIT) { + emitslicedata(c, nm, d.nmod, + d.lhs.type_: *tinfo, r); + } else { + // zero-init: accept no rhs or nil. + // Any other rhs is skipped → + // undefined symbol at link. + let ok: bool = true; + if (d.rhs != nil) { + ok = false; + if (r != nil) { + if (r.kind == nkind.N_NIL) { ok = true; }; + }; + }; + if (ok) { + emitline("DATAW "); + emitsymnamehint(c, nm, d.nmod); + emitline("(SB),\""); + let i: i32 = 0; + let szsl: i32 = tyslicesize(): i32; + for (i < szsl) { + emitdatawbyte(0u8); + i += 1; + }; + emitline("\"\n"); }; - emitline("\"\n"); }; }; // Struct globals — any size, zero-init only. @@ -34195,6 +34293,16 @@ fn emitdefconstants(c: *cgen, file: *node) void = { emitarraydata(c, "DATA", d.str, d.nmod, at, r); }; + // #10: a read-only `def g: []T = [...]` + // slice literal can't carry the ptr reloc + // emitslicedata needs (DATAR holder must be + // DATAW, w6a asm.c:362). Loud-stop, never + // silent no-emit. + if (au.kind == tykind.TY_SLICE) { + let m: str = "emitdefconstants: module-level slice-literal init needs a writable `let` (DATAR holder must be DATAW, w6a asm.c:362); read-only `def` unsupported (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; }; };}; }; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index cc1d79a9..ec00aac5 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -1044,14 +1044,25 @@ fn letvarisstr(c: *cgen, name: str) bool = { // letvarisslice — is the named top-level let a slice global? // Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the // load sequence ends with `MOVQ 16(CX), CX` (overwrites the -// address holder with the cap). Mirrors C cgen's `let_isslice`. +// address holder with the cap). Mirrors C cgen's `let_isslice`, +// which resolves the declared type via type_unwrap — so an alias of +// a slice IS a slice. Walks the N_TNAME alias chain exactly as the +// sibling letvarisstr does (the structural N_TSLICE node is the +// terminator, in place of letvarisstr's "str" name): without this, +// a `type S = []T; let g: S = [...]` global misroutes to the str arm +// and never reaches emitslicedata, diverging from cstage (#10). fn letvarisslice(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { let t: *node = lv.tnode; - if (t == nil) { return false; }; - if (t.kind == nkind.N_TSLICE) { return true; }; + for (t != nil) { + if (t.kind == nkind.N_TSLICE) { return true; }; + if (t.kind != nkind.N_TNAME) { return false; }; + let nx: *node = aliaslookup(c, t.str); + if (nx == nil) { return false; }; + t = nx; + }; return false; }; lv = lv.lvnext; @@ -1059,24 +1070,6 @@ fn letvarisslice(c: *cgen, name: str) bool = { return false; }; -// letdeclkind — unwrapped (TY_NAMED-peeled) kind of the let decl's -// declared type, read off the checker-stamped type-expression node -// (d.lhs.type_; resolvewalk stamps N_TNAME/N_TSLICE/… at check.ww:566). -// Mirrors cstage type_unwrap(d->type)->kind, the basis of let_isstr/ -// let_isslice (cgen.c:1026/1036). Post-#1 str and slice share a 24B -// header, so emitletdataw's size-only str and slice arms BOTH fired for -// a bare 24B global and double-emitted its DATAW (#10 part b); these two -// arms now branch on kind, not size. nil-safe: returns TY_VOID when -// unstamped so a 24B global still falls to the str arm (zero-init bytes -// are identical either way, so byte-id holds for the unstamped case). -fn letdeclkind(d: *node) tykind = { - if (d.lhs == nil) { return tykind.TY_VOID; }; - let ti: *tinfo = d.lhs.type_: *tinfo; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; - if (ti == nil) { return tykind.TY_VOID; }; - return ti.kind; -}; - // letvarisfloat — slot size for a named float global, or 0 if not // a float-typed let. Walks aliases so the byte-identity contract // matches C cgen's `let_isfloat` (which resolves Type kinds). @@ -2015,6 +2008,102 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, return true; }; +// emitslicedata — module-level `let g: []T = [v0,…];` static init (#10 +// part a). Mirror of cstage emit_slice_data. A slice literal needs a +// writable backing holding the k elements, a 24B header { ptr, len, cap +// }, and a DATAR patching the ptr word with the backing's VA. The +// backing rides the emitarraylitbytes choke-point via a synthesized +// [k]T so int/float/struct/nested-array elements reduce exactly as a +// [N]T global's do. Backing symbol = ".d": a second '.' can +// never collide with a user global (source identifiers carry no '.'). +// Scoped to a writable `let` — A_DATAR's holder must be a DATAW slot +// (w6a asm.c:362); read-only `def`, `...` repeat (no target length), +// 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 = { + let su: *tinfo = slt; + for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; }; + // Defensive, mirrors cstage emit_slice_data's + // `if (u == NULL || u->kind != TY_SLICE) return 0` (rule-10): the + // letvarisslice gate already guarantees a slice, so this is + // unreachable — it guards the su.sub deref below if the contract + // is ever violated rather than nil-derefing. + if (su == nil || su.kind != tykind.TY_SLICE) { return; }; + let etype: *tinfo = su.sub; + let eu: *tinfo = etype; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + 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; + for (e != nil) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + let m: str = "emitslicedata: '...' repeat has no target length in a slice literal (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + 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); + }; + // 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 "); + emitsymnamehint(c, name, module); + emitline("(SB),\""); + let i: i32 = 0; + let ptrsz: i32 = primtypesize("uintptr"): i32; + for (i < ptrsz) { emitdatawbyte(0u8); i += 1; }; + let lensz: i32 = primtypesize("size"): i32; + i = 0; + let kv: u64 = k: u64; + for (i < lensz) { + emitdatawbyte((kv & 255u64): u8); + kv = kv >> 8u64; + i += 1; + }; + i = 0; + kv = k: u64; + for (i < lensz) { + emitdatawbyte((kv & 255u64): u8); + kv = kv >> 8u64; + i += 1; + }; + emitline("\"\n"); + // Patch the ptr word with the backing VA. + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+0(SB),"); + emitsymnamehint(c, name, module); + emitline(".d(SB)\n"); +}; + fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -2089,7 +2178,7 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - if (sz == primtypesize("str"): i32 && !issg && letdeclkind(d) != tykind.TY_SLICE) { + if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; @@ -2154,34 +2243,43 @@ fn emitletdataw(c: *cgen, file: *node) void = { }; }; }; - if (sz == tyslicesize(): i32 && !issg && letdeclkind(d) == tykind.TY_SLICE) { - // Slice: zero-init only (no slice-literal - // syntax to honour). Any rhs other than - // `nil` is skipped → undefined symbol at - // link. - let ok: bool = true; - if (d.rhs != nil) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; - }; - ok = false; - if (r != nil) { - if (r.kind == nkind.N_NIL) { ok = true; }; - }; + if (sz == tyslicesize(): i32 && !issg && letvarisslice(c, nm)) { + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; }; - if (ok) { - emitline("DATAW "); - emitsymnamehint(c, nm, d.nmod); - emitline("(SB),\""); - let i: i32 = 0; - let szsl: i32 = tyslicesize(): i32; - for (i < szsl) { - emitdatawbyte(0u8); - i += 1; + // #10 part a: slice-literal static init + // routes through emitslicedata (header + + // writable backing + DATAR). Loud-stops on + // the deferred element kinds and the read- + // only/`...` shapes (rule 7). + if (r != nil && r.kind == nkind.N_ARRLIT) { + emitslicedata(c, nm, d.nmod, + d.lhs.type_: *tinfo, r); + } else { + // zero-init: accept no rhs or nil. + // Any other rhs is skipped → + // undefined symbol at link. + let ok: bool = true; + if (d.rhs != nil) { + ok = false; + if (r != nil) { + if (r.kind == nkind.N_NIL) { ok = true; }; + }; + }; + if (ok) { + emitline("DATAW "); + emitsymnamehint(c, nm, d.nmod); + emitline("(SB),\""); + let i: i32 = 0; + let szsl: i32 = tyslicesize(): i32; + for (i < szsl) { + emitdatawbyte(0u8); + i += 1; + }; + emitline("\"\n"); }; - emitline("\"\n"); }; }; // Struct globals — any size, zero-init only. @@ -2317,6 +2415,16 @@ fn emitdefconstants(c: *cgen, file: *node) void = { emitarraydata(c, "DATA", d.str, d.nmod, at, r); }; + // #10: a read-only `def g: []T = [...]` + // slice literal can't carry the ptr reloc + // emitslicedata needs (DATAR holder must be + // DATAW, w6a asm.c:362). Loud-stop, never + // silent no-emit. + if (au.kind == tykind.TY_SLICE) { + let m: str = "emitdefconstants: module-level slice-literal init needs a writable `let` (DATAR holder must be DATAW, w6a asm.c:362); read-only `def` unsupported (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; }; };}; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 8a02399c..75f68e3f 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -32922,14 +32922,25 @@ fn letvarisstr(c: *cgen, name: str) bool = { // letvarisslice — is the named top-level let a slice global? // Slice headers are 24 bytes; the ABI flows as (AX, BX, CX) so the // load sequence ends with `MOVQ 16(CX), CX` (overwrites the -// address holder with the cap). Mirrors C cgen's `let_isslice`. +// address holder with the cap). Mirrors C cgen's `let_isslice`, +// which resolves the declared type via type_unwrap — so an alias of +// a slice IS a slice. Walks the N_TNAME alias chain exactly as the +// sibling letvarisstr does (the structural N_TSLICE node is the +// terminator, in place of letvarisstr's "str" name): without this, +// a `type S = []T; let g: S = [...]` global misroutes to the str arm +// and never reaches emitslicedata, diverging from cstage (#10). fn letvarisslice(c: *cgen, name: str) bool = { let lv: *letvar = c.lets; for (lv != nil) { if (streq(lv.name, name)) { let t: *node = lv.tnode; - if (t == nil) { return false; }; - if (t.kind == nkind.N_TSLICE) { return true; }; + for (t != nil) { + if (t.kind == nkind.N_TSLICE) { return true; }; + if (t.kind != nkind.N_TNAME) { return false; }; + let nx: *node = aliaslookup(c, t.str); + if (nx == nil) { return false; }; + t = nx; + }; return false; }; lv = lv.lvnext; @@ -32937,24 +32948,6 @@ fn letvarisslice(c: *cgen, name: str) bool = { return false; }; -// letdeclkind — unwrapped (TY_NAMED-peeled) kind of the let decl's -// declared type, read off the checker-stamped type-expression node -// (d.lhs.type_; resolvewalk stamps N_TNAME/N_TSLICE/… at check.ww:566). -// Mirrors cstage type_unwrap(d->type)->kind, the basis of let_isstr/ -// let_isslice (cgen.c:1026/1036). Post-#1 str and slice share a 24B -// header, so emitletdataw's size-only str and slice arms BOTH fired for -// a bare 24B global and double-emitted its DATAW (#10 part b); these two -// arms now branch on kind, not size. nil-safe: returns TY_VOID when -// unstamped so a 24B global still falls to the str arm (zero-init bytes -// are identical either way, so byte-id holds for the unstamped case). -fn letdeclkind(d: *node) tykind = { - if (d.lhs == nil) { return tykind.TY_VOID; }; - let ti: *tinfo = d.lhs.type_: *tinfo; - for (ti != nil && ti.kind == tykind.TY_NAMED) { ti = ti.under; }; - if (ti == nil) { return tykind.TY_VOID; }; - return ti.kind; -}; - // letvarisfloat — slot size for a named float global, or 0 if not // a float-typed let. Walks aliases so the byte-identity contract // matches C cgen's `let_isfloat` (which resolves Type kinds). @@ -33893,6 +33886,102 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, return true; }; +// emitslicedata — module-level `let g: []T = [v0,…];` static init (#10 +// part a). Mirror of cstage emit_slice_data. A slice literal needs a +// writable backing holding the k elements, a 24B header { ptr, len, cap +// }, and a DATAR patching the ptr word with the backing's VA. The +// backing rides the emitarraylitbytes choke-point via a synthesized +// [k]T so int/float/struct/nested-array elements reduce exactly as a +// [N]T global's do. Backing symbol = ".d": a second '.' can +// never collide with a user global (source identifiers carry no '.'). +// Scoped to a writable `let` — A_DATAR's holder must be a DATAW slot +// (w6a asm.c:362); read-only `def`, `...` repeat (no target length), +// 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 = { + let su: *tinfo = slt; + for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; }; + // Defensive, mirrors cstage emit_slice_data's + // `if (u == NULL || u->kind != TY_SLICE) return 0` (rule-10): the + // letvarisslice gate already guarantees a slice, so this is + // unreachable — it guards the su.sub deref below if the contract + // is ever violated rather than nil-derefing. + if (su == nil || su.kind != tykind.TY_SLICE) { return; }; + let etype: *tinfo = su.sub; + let eu: *tinfo = etype; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + 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; + for (e != nil) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + let m: str = "emitslicedata: '...' repeat has no target length in a slice literal (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + }; + 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); + }; + // 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 "); + emitsymnamehint(c, name, module); + emitline("(SB),\""); + let i: i32 = 0; + let ptrsz: i32 = primtypesize("uintptr"): i32; + for (i < ptrsz) { emitdatawbyte(0u8); i += 1; }; + let lensz: i32 = primtypesize("size"): i32; + i = 0; + let kv: u64 = k: u64; + for (i < lensz) { + emitdatawbyte((kv & 255u64): u8); + kv = kv >> 8u64; + i += 1; + }; + i = 0; + kv = k: u64; + for (i < lensz) { + emitdatawbyte((kv & 255u64): u8); + kv = kv >> 8u64; + i += 1; + }; + emitline("\"\n"); + // Patch the ptr word with the backing VA. + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+0(SB),"); + emitsymnamehint(c, name, module); + emitline(".d(SB)\n"); +}; + fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -33967,7 +34056,7 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - if (sz == primtypesize("str"): i32 && !issg && letdeclkind(d) != tykind.TY_SLICE) { + if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) { let r: *node = d.rhs; for (r != nil) { if (r.kind != nkind.N_CAST) { break; }; @@ -34032,34 +34121,43 @@ fn emitletdataw(c: *cgen, file: *node) void = { }; }; }; - if (sz == tyslicesize(): i32 && !issg && letdeclkind(d) == tykind.TY_SLICE) { - // Slice: zero-init only (no slice-literal - // syntax to honour). Any rhs other than - // `nil` is skipped → undefined symbol at - // link. - let ok: bool = true; - if (d.rhs != nil) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; - }; - ok = false; - if (r != nil) { - if (r.kind == nkind.N_NIL) { ok = true; }; - }; + if (sz == tyslicesize(): i32 && !issg && letvarisslice(c, nm)) { + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; }; - if (ok) { - emitline("DATAW "); - emitsymnamehint(c, nm, d.nmod); - emitline("(SB),\""); - let i: i32 = 0; - let szsl: i32 = tyslicesize(): i32; - for (i < szsl) { - emitdatawbyte(0u8); - i += 1; + // #10 part a: slice-literal static init + // routes through emitslicedata (header + + // writable backing + DATAR). Loud-stops on + // the deferred element kinds and the read- + // only/`...` shapes (rule 7). + if (r != nil && r.kind == nkind.N_ARRLIT) { + emitslicedata(c, nm, d.nmod, + d.lhs.type_: *tinfo, r); + } else { + // zero-init: accept no rhs or nil. + // Any other rhs is skipped → + // undefined symbol at link. + let ok: bool = true; + if (d.rhs != nil) { + ok = false; + if (r != nil) { + if (r.kind == nkind.N_NIL) { ok = true; }; + }; + }; + if (ok) { + emitline("DATAW "); + emitsymnamehint(c, nm, d.nmod); + emitline("(SB),\""); + let i: i32 = 0; + let szsl: i32 = tyslicesize(): i32; + for (i < szsl) { + emitdatawbyte(0u8); + i += 1; + }; + emitline("\"\n"); }; - emitline("\"\n"); }; }; // Struct globals — any size, zero-init only. @@ -34195,6 +34293,16 @@ fn emitdefconstants(c: *cgen, file: *node) void = { emitarraydata(c, "DATA", d.str, d.nmod, at, r); }; + // #10: a read-only `def g: []T = [...]` + // slice literal can't carry the ptr reloc + // emitslicedata needs (DATAR holder must be + // DATAW, w6a asm.c:362). Loud-stop, never + // silent no-emit. + if (au.kind == tykind.TY_SLICE) { + let m: str = "emitdefconstants: module-level slice-literal init needs a writable `let` (DATAR holder must be DATAW, w6a asm.c:362); read-only `def` unsupported (#10, rule 7)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; }; };}; }; diff --git a/test/wcc/687_slice_literal_global.c b/test/wcc/687_slice_literal_global.c new file mode 100644 index 00000000..8acd9396 --- /dev/null +++ b/test/wcc/687_slice_literal_global.c @@ -0,0 +1,325 @@ +/* + * 687_slice_literal_global — module-level slice-literal static init + * `let g: []T = [v0, v1, …];` materializes a writable backing + a 24B + * { ptr, len, cap } header + a DATAR patching ptr -> backing, and cstage + * / wwstage agree byte-for-byte and at runtime (task #10 part a). + * + * The bug: emit_lets / emitletdataw had str-lit + array-lit arms but no + * slice-lit arm, so a `let g: []u8 = [1u8,2u8,3u8]` emitted NO `DATAW + * main.g` — BOTH stages failed to link ("undefined reference to main.g"). + * byte-id-blind; only the link step exposed it. (wwstage further needed + * #18: its checker desugared the module-level initializer to an N_SLICE + * runtime borrow, so the rhs reached cgen as N_SLICE not N_ARRLIT — fixed + * first so this arm sees the raw array literal, mirroring cstage.) + * + * The fix (BOTH stages, byte-identical): emit_slice_data / emitslicedata + * route the k element bytes through the emit_array_lit_bytes choke-point + * (synthesized [k]T), then emit the header (ptr placeholder + LE len + LE + * cap, all = k) and the ptr-patch DATAR. Backing symbol ".d". + * + * Coverage: []u8 (element read-back + len + cap + sum), []i64 (wider + * element), []i32, and a 1-element edge. Plus dual-stage asm byte-id. + * Mutation-sanity: the old no-emit fails every row at LINK. Plus negative + * rows where the slice-literal static init must FAIL the build loudly + * (rule 7): read-only `def`, `...` repeat, and slice-of-str (per-element + * relocs / #17 deferred). + * + * row | shape | want + * ----------------+------------------------------------------+------ + * u8_e0 | let g:[]u8=[1,2,3]; g[0] | 1 + * u8_e2 | g[2] | 3 + * u8_len | g.len | 3 + * u8_cap | g.cap | 3 + * u8_sum | g[0]+g[1]+g[2]+g.len+g.cap | 12 + * i64_e1 | let g:[]i64=[10,20,30]; g[1] | 20 + * i64_lencap | g.len+g.cap | 6 + * i32_sum | let g:[]i32=[7,8,9]; g[0]+g[1]+g[2] | 24 + * one_edge | let g:[]u8=[42]; g[0]+g.len | 43 + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct row { const char *label; const char *src; int want; }; + +static const struct row rows[] = { + { "u8_e0", + "package main;\n" + "let g: []u8 = [1u8, 2u8, 3u8];\n" + "export fn main() i32 = { return g[0]: i32; };\n", + 1 }, + { "u8_e2", + "package main;\n" + "let g: []u8 = [1u8, 2u8, 3u8];\n" + "export fn main() i32 = { return g[2]: i32; };\n", + 3 }, + { "u8_len", + "package main;\n" + "let g: []u8 = [1u8, 2u8, 3u8];\n" + "export fn main() i32 = { return g.len: i32; };\n", + 3 }, + { "u8_cap", + "package main;\n" + "let g: []u8 = [1u8, 2u8, 3u8];\n" + "export fn main() i32 = { return g.cap: i32; };\n", + 3 }, + { "u8_sum", + "package main;\n" + "let g: []u8 = [1u8, 2u8, 3u8];\n" + "export fn main() i32 = {\n" + "\treturn (g[0]:i32)+(g[1]:i32)+(g[2]:i32)+(g.len:i32)+(g.cap:i32);\n" + "};\n", + 12 }, + { "i64_e1", + "package main;\n" + "let g: []i64 = [10i64, 20i64, 30i64];\n" + "export fn main() i32 = { return g[1]: i32; };\n", + 20 }, + { "i64_lencap", + "package main;\n" + "let g: []i64 = [10i64, 20i64, 30i64];\n" + "export fn main() i32 = { return (g.len:i32)+(g.cap:i32); };\n", + 6 }, + { "i32_sum", + "package main;\n" + "let g: []i32 = [7i32, 8i32, 9i32];\n" + "export fn main() i32 = { return (g[0]:i32)+(g[1]:i32)+(g[2]:i32); };\n", + 24 }, + { "one_edge", + "package main;\n" + "let g: []u8 = [42u8];\n" + "export fn main() i32 = { return (g[0]:i32)+(g.len:i32); };\n", + 43 }, + /* aliased slice type: `type S = []u8` IS a slice — both stages must + * route through emit_slice_data. cstage resolves it via type_unwrap; + * wwstage letvarisslice resolves the alias chain (#10). */ + { "alias_slice", + "package main;\n" + "type S = []u8;\n" + "let g: S = [1u8, 2u8, 3u8];\n" + "export fn main() i32 = { return (g[0]:i32)+(g.len:i32)+(g.cap:i32); };\n", + 7 }, +}; + +/* slice-literal static init that must FAIL the build loudly (rule 7). */ +static const char *neg[] = { + /* read-only `def` can't carry the ptr reloc (DATAR holder must be + * DATAW, w6a asm.c:362). */ + "package main;\n" + "def g: []u8 = [1u8, 2u8, 3u8];\n" + "export fn main() i32 = { return g.len: i32; };\n", + /* `...` repeat has no target length in a slice literal. Uses the + * reachable repeat spelling `[v...]` (no comma) so the cgen loud-stop + * is exercised — the comma form `[v, ...]` parse-errors first and + * would test the parser, not the emit_slice_data guard. */ + "package main;\n" + "let g: []u8 = [1u8, 2u8...];\n" + "export fn main() i32 = { return g.len: i32; };\n", + /* slice-of-str needs per-element relocs (#17) — deferred loud-stop. */ + "package main;\n" + "let g: []str = [\"a\", \"b\"];\n" + "export fn main() i32 = { return g.len: i32; };\n", +}; + +static int +run_driver(const char *driver, const struct row *r, int i) +{ + char src[64], tmpdir[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/slg_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/slg_%d_d_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", + tmpdir, driver, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: build via %s failed\n", + r->label, driver); + unlink(src); rmdir(tmpdir); + return -1; + } + + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + char outbin[128]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + int got = runwait(outbin); + + unlink(src); unlink(outbin); rmdir(tmpdir); + return got; +} + +/* build_should_fail — returns 0 when the build correctly FAILS. */ +static int +build_should_fail(const char *driver, const char *src, int i) +{ + char s[64], tmpdir[64], cmd[1024]; + snprintf(s, sizeof s, "/tmp/slgn_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/slgn_%d_d_%d", getpid(), i); + + FILE *f = fopen(s, "wb"); + if (!f) return -1; + fputs(src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", + tmpdir, driver, s); + int rc = runwait(cmd); + unlink(s); + const char *base = strrchr(s, '/'); + base = base ? base + 1 : s; + char outbin[128]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + unlink(outbin); + rmdir(tmpdir); + return rc == 0 ? -1 : 0; /* build must NOT succeed */ +} + +/* asm_byte_identical — w6c vs w6c_ww .s for the same source must match. */ +static int +asm_byte_identical(const char *bin, const struct row *r, int i) +{ + char src[64], cs[64], ws[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/slg_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/slg_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/slg_asm_%d_%d_w.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c errored\n", r->label); + unlink(src); + return -1; + } + snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", + bin, ws, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); + unlink(src); unlink(cs); + return -1; + } + + FILE *fc = fopen(cs, "rb"); + FILE *fw = fopen(ws, "rb"); + int rc = 0; + if (!fc || !fw) { + rc = -1; + } else { + for (;;) { + int a = fgetc(fc); + int b = fgetc(fw); + if (a != b) { rc = -1; break; } + if (a == EOF) break; + } + } + if (fc) fclose(fc); + if (fw) fclose(fw); + if (rc != 0) + fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", + r->label); + unlink(src); unlink(cs); unlink(ws); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char cdrv[1024]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + char wdrv[1024]; + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + + struct { const char *name; const char *path; int gated_on_existence; } + drivers[] = { + { "cstage", cdrv, 0 }, + { "wwstage", wdrv, 1 }, + { NULL, NULL, 0 }, + }; + + int n = (int)(sizeof rows / sizeof rows[0]); + int nn = (int)(sizeof neg / sizeof neg[0]); + int total = 0, fail = 0; + + for (int d = 0; drivers[d].name; d++) { + if (drivers[d].gated_on_existence + && access(drivers[d].path, X_OK) != 0) { + fprintf(stderr, "slice_literal_global: skip %s (no %s)\n", + drivers[d].name, drivers[d].path); + continue; + } + for (int i = 0; i < n; i++) { + int got = run_driver(drivers[d].path, &rows[i], i); + total++; + if (got != rows[i].want) { + fprintf(stderr, + "slice_literal_global[%s][%s]: exit=%d want=%d\n", + drivers[d].name, rows[i].label, + got, rows[i].want); + fail++; + } + } + for (int i = 0; i < nn; i++) { + total++; + if (build_should_fail(drivers[d].path, neg[i], + 100 + i) != 0) { + fprintf(stderr, + "slice_literal_global[%s][neg%d]: built ok, " + "expected a loud error\n", + drivers[d].name, i); + fail++; + } + } + } + + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + total++; + if (asm_byte_identical(bin, &rows[i], i) != 0) + fail++; + } + } + + if (fail) { + fprintf(stderr, + "slice_literal_global: %d/%d fixtures failed\n", + fail, total); + return 1; + } + printf("slice_literal_global: %d/%d ok\n", total, total); + return 0; +}