diff --git a/Makefile b/Makefile index 1858677a..95facd41 100644 --- a/Makefile +++ b/Makefile @@ -341,6 +341,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_arr_float_call_index_run \ $(BIN)/test_def_float_lit_run \ $(BIN)/test_struct_composite_init_run \ + $(BIN)/test_array_static_init_run \ $(BIN)/test_f64cgen_run \ $(BIN)/test_f64crossmod_run \ $(BIN)/test_tuprecv_run \ @@ -1159,6 +1160,11 @@ $(BIN)/test_struct_composite_init_run: test/wcc/918_struct_composite_init_run.c $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_array_static_init_run: test/wcc/919_array_static_init_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_f64cgen_run: test/wcc/951_f64cgen_run.c $(BIN)/ww $(BIN)/w6c \ $(BIN)/w6a $(BIN)/w6l $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 1a1fef8f..0f5407ab 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -711,6 +711,17 @@ struct DefStruct { }; static DefStruct *defstructs; +/* #129 A.3: array-typed defs now have DATA storage and need the same + * LEAQ name(SB) + indexed-load shape as array-typed lets at cgindex + * and N_DOT base-resolution sites. Mirrors DefStruct (A.2). */ +typedef struct DefArray DefArray; +struct DefArray { + const char *name; + Type *type; + DefArray *next; +}; +static DefArray *defarrays; + /* Slot size for a top-level `let` of type t, or 0 if the type isn't * supported as a writable global yet. Tagged unions are deferred. * enums route through their storage type. @@ -914,6 +925,7 @@ let_collect(Cg *c, Node *file) { letvars = NULL; defstructs = NULL; + defarrays = NULL; if (file == NULL) return; for (Node *d = file->list; d; d = d->next) { if (d->kind == N_LET) { @@ -927,18 +939,32 @@ let_collect(Cg *c, Node *file) continue; } if (d->kind == N_DEF) { + if (d->str == NULL || d->str[0] == '\0') continue; /* #129 A.2: struct-typed defs now have DATA storage * (emit_defs struct arm); register them so the N_DOT * struct-let LEAQ-and-offset shape widens to cover * them too. Other def kinds (int / float / str) * stay on their existing load paths. */ - if (d->str == NULL || d->str[0] == '\0') continue; - if (!let_isstruct(d->type)) continue; - DefStruct *ds = amalloc(c->a, sizeof *ds); - ds->name = d->str; - ds->type = d->type; - ds->next = defstructs; - defstructs = ds; + if (let_isstruct(d->type)) { + DefStruct *ds = amalloc(c->a, sizeof *ds); + ds->name = d->str; + ds->type = d->type; + ds->next = defstructs; + defstructs = ds; + continue; + } + /* #129 A.3: array-typed defs now have DATA storage + * (emit_defs array arm); register them so cgindex's + * `let_islet`-gated LEAQ name(SB) base-load widens + * to defs too (LOAD-side twin of the struct-def + * registry). */ + if (let_isarray(d->type)) { + DefArray *da = amalloc(c->a, sizeof *da); + da->name = d->str; + da->type = d->type; + da->next = defarrays; + defarrays = da; + } } } } @@ -952,6 +978,15 @@ def_isstructdef(const char *name) return 0; } +static int +def_isarraydef(const char *name) +{ + if (name == NULL) return 0; + for (DefArray *da = defarrays; da; da = da->next) + if (strcmp(da->name, name) == 0) return 1; + return 0; +} + static int let_islet(const char *name) { @@ -6797,7 +6832,12 @@ cgexpr(Cg *c, Node *n, Local *locals) if (n->lhs->kind == N_IDENT && u) { int off = localfind(locals, n->lhs->str); - int isglobal = (off == 0) && let_islet(n->lhs->str); + /* #129 A.3: array-typed defs now have DATA storage; the + * LEAQ name(SB) base-load must fire for them too, not + * just let_islet. Parallel to A.2's def_isstructdef + * gate at the N_DOT direct-struct-ident arm. */ + int isglobal = (off == 0) && (let_islet(n->lhs->str) + || def_isarraydef(n->lhs->str)); cgexpr(c, n->rhs, locals); /* idx → AX */ if (esz > 1) { ins2(c, A_MOVQ, aimm(esz), areg(D_CX)); @@ -8523,6 +8563,12 @@ emit_floatlit_data(FILE *out, Cg *c, const char *directive, return 1; } +/* Forward declaration: emit_struct_lit_bytes recurses into + * emit_array_lit_bytes for nested array fields (#129 A.3 closes the + * A.2 shape-15 park). Defined further down. */ +static int emit_array_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, + int emit_phase); + /* emit_struct_lit_bytes — emit the byte sequence for a struct-typed * top-level let/def whose rhs is an N_STRUCTLIT (or NULL for bare * no-rhs). Walks Tfield list in declaration order, zero-fills padding @@ -8585,6 +8631,24 @@ emit_struct_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, u64 base) pos = fstart + (u64)fsz; continue; } + /* #129 A.3: array-typed field with N_ARRLIT rhs (the shape + * parked in A.2). Calls emit_array_lit_bytes which dispatches + * by element kind (int/float/struct). Returns 0 if the rhs + * shape can't reduce — fatal here per rule-7 since the field + * is declared array-typed and a non-reducible inner rhs is + * a real bug surface, not a fall-through. */ + if (fu && fu->kind == TY_ARRAY) { + if (vr == NULL || vr->kind != N_ARRLIT) + fatal("emit_struct_lit_bytes: array field " + "'%s' rhs is not N_ARRLIT (#129 A.3)", + f->name ? f->name : "?"); + if (!emit_array_lit_bytes(out, c, f->type, vr, 1)) + fatal("emit_struct_lit_bytes: array field " + "'%s' rhs has non-reducible elements " + "(#129 A.3)", f->name ? f->name : "?"); + pos = fstart + (u64)fsz; + continue; + } if (type_isfloat(f->type)) { int isf32 = type_isf32(f->type); u64 fv = 0; @@ -8654,6 +8718,210 @@ emit_struct_data(FILE *out, Cg *c, const char *directive, return 1; } +/* emit_array_lit_bytes — emit alen * esz bytes for an [N]T top-level + * let/def with N_ARRLIT rhs. Per-element dispatch: + * - int element (covers bool/rune/typed-int/N_UN-int): fold_int_literal + * per element, emit LE bytes. Existing pre-#129-A.3 emit_lets array + * arm logic preserved byte-for-byte so the bootstrap consumers in + * lib/os, lib/bufio, lib/strings, lib/encoding/utf8, lib/strconv/ + * stof_data don't shift. + * - float element (f32/f64): peel N_CAST/N_UN(±), bitcast magnitude + * via union (mirrors emit_floatlit_data), sign-XOR top byte of each + * element inline. NO 2^63 immediate. + * - struct element: per element call emit_struct_lit_bytes (#129 A.2 + * helper). + * - other element kinds (str/slice/ptr-with-address/nested-array): + * return 0 — caller falls through to zero-init (str/slice accepts + * no-rhs already). + * + * Trailing `...` repeat marker fills remaining slots with the last + * value (mirrors the scalar repeat path). Returns 1 on emit, 0 if the + * rhs shape can't reduce to a foldable literal — caller MUST then + * fall back to zero-init / skip path; the caller opens the DATA/DATAW + * directive AFTER a successful validate-only call. Two-call pattern + * keeps emit-on-failure from emitting partial bytes. + * + * `emit_phase = 0` runs validate-only (returns 1 if ok); `emit_phase + * = 1` actually emits. */ +static int +emit_array_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, int emit_phase) +{ + Type *u = type_unwrap(t); + if (u == NULL || u->kind != TY_ARRAY) return 0; + Type *etype = u->sub; + Type *eu = (etype && etype->kind == TY_NAMED) ? etype->under : etype; + int esz = etype ? (int)etype->size : 1; + int alen = (int)u->alen; + + if (eu && eu->kind == TY_STRUCT) { + /* Validate: every element must be N_STRUCTLIT (after N_CAST + * peel). */ + int idx = 0; + Node *last_ev = NULL; + int repeat = 0; + for (Node *e = rhs->list; e && idx < alen; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) { + repeat = 1; + break; + } + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + if (ev == NULL || ev->kind != N_STRUCTLIT) return 0; + last_ev = ev; + idx++; + } + if (!emit_phase) return 1; + idx = 0; + repeat = 0; + for (Node *e = rhs->list; e && idx < alen; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) { + repeat = 1; + break; + } + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + emit_struct_lit_bytes(out, c, etype, ev, 0); + idx++; + } + while (idx < alen) { + if (repeat && last_ev != NULL) + emit_struct_lit_bytes(out, c, etype, last_ev, 0); + else + for (int b = 0; b < esz; b++) + emit_data_byte(out, 0); + idx++; + } + return 1; + } + + if (type_isfloat(etype)) { + int isf32 = type_isf32(etype); + /* Validate: every element must be N_FLOATLIT (after N_CAST + * + optional N_UN(±) peel). */ + int idx = 0; + for (Node *e = rhs->list; e && idx < alen; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) break; + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + if (ev != NULL && ev->kind == N_UN + && (ev->op == TK_MINUS || ev->op == TK_PLUS)) { + ev = ev->lhs; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + } + if (ev == NULL || ev->kind != N_FLOATLIT) return 0; + idx++; + } + if (!emit_phase) return 1; + idx = 0; + u64 last_bits = 0; + int last_neg = 0; + int repeat = 0; + for (Node *e = rhs->list; e && idx < alen; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) { + repeat = 1; + break; + } + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + int neg = 0; + if (ev != NULL && ev->kind == N_UN + && (ev->op == TK_MINUS || ev->op == TK_PLUS)) { + if (ev->op == TK_MINUS) neg = 1; + ev = ev->lhs; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + } + u64 bits = 0; + if (isf32) { + union { float f; u32 u; } x; + x.f = (float)ev->fval; + bits = (u64)x.u; + } else { + union { double d; u64 u; } x; + x.d = ev->fval; + bits = x.u; + } + for (int b = 0; b < esz; b++) { + u8 byt = (u8)((bits >> (b * 8)) & 0xff); + if (neg && b == esz - 1) byt = (u8)(byt ^ 0x80); + emit_data_byte(out, byt); + } + last_bits = bits; + last_neg = neg; + idx++; + } + while (idx < alen) { + if (repeat) { + for (int b = 0; b < esz; b++) { + u8 byt = (u8)((last_bits >> (b * 8)) & 0xff); + if (last_neg && b == esz - 1) + byt = (u8)(byt ^ 0x80); + emit_data_byte(out, byt); + } + } else { + for (int b = 0; b < esz; b++) + emit_data_byte(out, 0); + } + idx++; + } + return 1; + } + + /* Int-element path — preserved BYTE-FOR-BYTE from the pre-A.3 + * emit_lets in-place array arm so the bootstrap consumers (u8 / + * i8 / u16 arrays in lib/os, lib/bufio, lib/strings, lib/ + * encoding/utf8, lib/strconv/stof_data) don't shift. */ + u64 *vals = amalloc(c->a, sizeof(u64) * (size_t)alen); + int idx = 0; + int ok = 1; + u64 last = 0; + int repeat = 0; + for (Node *e = rhs->list; e && idx < alen; e = e->next) { + if (e->kind == N_FIELD && e->str + && strcmp(e->str, "...") == 0) { + repeat = 1; + break; + } + Node *ev = e; + while (ev && ev->kind == N_CAST) ev = ev->lhs; + if (ev == NULL) { ok = 0; break; } + if (!fold_int_literal(ev, &last)) { ok = 0; break; } + vals[idx++] = last; + } + if (!ok) return 0; + if (!emit_phase) return 1; + if (repeat) { + while (idx < alen) vals[idx++] = last; + } else { + while (idx < alen) vals[idx++] = 0; + } + for (int i = 0; i < alen; i++) { + u64 v = vals[i]; + for (int b = 0; b < esz; b++) + emit_data_byte(out, (u8)((v >> (b * 8)) & 0xff)); + } + return 1; +} + +/* emit_array_data — opens DATA/DATAW prefix on validate success, then + * emits payload. Two-pass keeps emit-on-failure from emitting partial + * bytes (would corrupt the asm if rhs reduces partway through). */ +static int +emit_array_data(FILE *out, Cg *c, const char *directive, + const char *name, Type *t, Node *rhs) +{ + Type *u = type_unwrap(t); + if (u == NULL || u->kind != TY_ARRAY) return 0; + if (!emit_array_lit_bytes(out, c, t, rhs, 0)) return 0; + fprintf(out, "%s %s(SB),\"", directive, mod_mangle(c, name)); + emit_array_lit_bytes(out, c, t, rhs, 1); + fputs("\"\n", out); + return 1; +} + static void emit_lets(Cg *c, FILE *out, Node *file) { @@ -8718,59 +8986,17 @@ emit_lets(Cg *c, FILE *out, Node *file) fprintf(out, "DATAR %s+0(SB),%s(SB)\n", sym, lab); continue; } - /* Array literal init: `let xs: [N]T = [v0, v1, ...];`. Walk - * elements in declaration order; each must reduce to an - * integer literal (casts are stripped). The trailing `...` - * repeat marker fills remaining slots with the last value. - * Falls through to zero-init if any element isn't a - * constant we can evaluate at emit time. */ + /* Array literal init: `let xs: [N]T = [v0, v1, ...];`. The + * helper dispatches per element kind (int/float/struct). + * Int-element path preserved BYTE-FOR-BYTE from pre-A.3 so + * bootstrap consumers (lib/os, lib/bufio, lib/strings, lib/ + * encoding/utf8, lib/strconv/stof_data) don't shift. Float + * + struct elements gain emit; ptr / nested-array fall + * through to zero-init (existing path below). */ if (r != NULL && r->kind == N_ARRLIT && let_isarray(d->type)) { - Type *u = type_unwrap(d->type); - int esz = (u && u->sub) ? (int)u->sub->size : 1; - int alen = (u) ? (int)u->alen : 0; - u64 *vals = amalloc(c->a, sizeof(u64) * (size_t)alen); - int idx = 0; - int ok = 1; - u64 last = 0; - int repeat = 0; - for (Node *e = r->list; e && idx < alen; e = e->next) { - if (e->kind == N_FIELD && e->str && - strcmp(e->str, "...") == 0) { - repeat = 1; - break; - } - Node *ev = e; - while (ev && ev->kind == N_CAST) ev = ev->lhs; - if (ev == NULL) { ok = 0; break; } - /* Same helper as the scalar arm above — - * fold_int_literal covers N_UN over a leaf - * so signed-negative array elements (`-1i8`) - * encode as their two's-complement bytes - * once truncated to esz below. */ - if (!fold_int_literal(ev, &last)) { - ok = 0; - break; - } - vals[idx++] = last; - } - if (ok) { - if (repeat) { - while (idx < alen) vals[idx++] = last; - } else { - while (idx < alen) vals[idx++] = 0; - } - fprintf(out, "DATAW %s(SB),\"", - mod_mangle(c, d->str)); - for (int i = 0; i < alen; i++) { - u64 v = vals[i]; - for (int b = 0; b < esz; b++) { - emit_data_byte(out, - (u8)((v >> (b * 8)) & 0xff)); - } - } - fputs("\"\n", out); + if (emit_array_data(out, c, "DATAW", d->str, + d->type, r)) continue; - } /* fall through to zero-init */ } /* Otherwise: zero-init. str accepts nil / ""; struct @@ -8846,6 +9072,15 @@ emit_defs(Cg *c, FILE *out, Node *file) d->str, d->type, d->rhs); continue; } + /* #129 A.3: array-typed def with N_ARRLIT rhs. Parallel to + * emit_lets's array arm; uses DATA (read-only). LOAD-side + * widening at cgindex/cgdot resolves the def's address via + * LEAQ name(SB). */ + if (let_isarray(d->type) && d->rhs->kind == N_ARRLIT) { + (void)emit_array_data(out, c, "DATA", + d->str, d->type, d->rhs); + continue; + } } (void)c; } diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 7e26ee37..e81d7a77 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -15515,6 +15515,12 @@ fn cgindex(c: *cgen, n: *node) void = { f32_elem = elemisf32c(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); + // #129 A.3: array-typed defs now have DATA storage; + // resolve their base via the same N_TARRAY path as + // lets. defvartnode is the def-side sister of + // letvartnode (parallel to defvarstructinfo at the + // A.2 cgdot widening site). + if (tn == nil) { tn = defvartnode(c, bn); }; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; @@ -24939,6 +24945,21 @@ fn defvarstructinfo(c: *cgen, name: str) *structinfo = { return nil; }; +// defvartnode — sister of letvartnode for top-level `def`s. Returns +// the type-spec node (defent.dtnode) for the named def, or nil. #129 +// A.3 uses it in cgindex's array-base resolution so a `def: [N]T` +// resolves through the same N_TARRAY-detect → LEAQ name(SB) shape as +// a let array. Parallel to defvarstructinfo (#129 A.2) at the LOAD +// side widening. +fn defvartnode(c: *cgen, name: str) *node = { + let e: *defent = c.defs; + for (e != nil) { + if (streq(e.dname, name)) { return e.dtnode; }; + e = e.dnext; + }; + return nil; +}; + // emitdatawbyte — write one byte of an asm string literal using // the same escape rules as emitdefconstants / emitdatasection. fn emitdatawbyte(b: u8) void = { @@ -25163,6 +25184,30 @@ fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node, f = f.tnext; continue; }; + // #129 A.3: array-typed field with N_ARRLIT rhs (the shape + // parked in A.2). Recurses through emitarraylitbytes for + // element-kind dispatch. Rule-7 stops loudly if rhs shape + // doesn't match. + if (fu != nil && fu.kind == tykind.TY_ARRAY) { + if (vr == nil) { + let m: str = "emitstructlitbytes: array field rhs nil (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + if (vr.kind != nkind.N_ARRLIT) { + let m: str = "emitstructlitbytes: array field rhs not N_ARRLIT (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + if (!emitarraylitbytes(c, f.type_, vr, 1)) { + let m: str = "emitstructlitbytes: array field rhs has non-reducible elements (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + pos = fstart + fsz: u64; + f = f.tnext; + continue; + }; if (typeisfloat(f.type_)) { let isf32: bool = (fsz == 4); let neg: bool = false; @@ -25251,6 +25296,260 @@ fn emitstructdata(c: *cgen, directive: str, name: str, return true; }; +// emitarraylitbytes — emit alen * esz bytes for an [N]T top-level let/ +// def with N_ARRLIT rhs. Mirrors cstage emit_array_lit_bytes. Per- +// element dispatch: +// - int (covers bool/rune/typed-int/N_UN-int): foldintliteral per +// element. Existing pre-#129-A.3 emitletdataw array arm logic +// preserved byte-for-byte so bootstrap consumers (lib/os, lib/ +// bufio, lib/strings, lib/encoding/utf8, lib/strconv/stof_data) +// don't shift. +// - float (f32/f64): peel N_CAST/N_UN(±), bitcast magnitude via +// pointer-cast round-trip (mirror emitfloatlitdata), sign-XOR +// top byte of each element inline. No 2^63 immediate. +// - struct: per element call emitstructlitbytes (#129 A.2 helper). +// - other element kinds (ptr/nested-array): returns false — caller +// falls through to zero-init. +// +// Two-pass validate-then-emit (`emit_phase=0` validate-only, `=1` +// actually emit) keeps emit-on-failure from emitting partial bytes +// into an open DATA literal. +fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, + emit_phase: i32) bool = { + let au: *tinfo = arrt; + for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; }; + if (au == nil) { return false; }; + if (au.kind != tykind.TY_ARRAY) { return false; }; + let esz: i32 = au.sub.size: i32; + let alen: i32 = au.alen: i32; + let eu: *tinfo = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + + if (eu != nil && eu.kind == tykind.TY_STRUCT) { + // Validate: every element must be N_STRUCTLIT (after N_CAST). + let idx: i32 = 0; + let last_ev: *node = nil; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_STRUCTLIT) { return false; }; + last_ev = ev; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + let repeat: bool = false; + e = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + emitstructlitbytes(c, au.sub, ev, 0u64); + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat && last_ev != nil) { + emitstructlitbytes(c, au.sub, last_ev, 0u64); + } else { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + }; + idx += 1; + }; + return true; + }; + + if (typeisfloat(au.sub)) { + let isf32: bool = typeisf32(au.sub); + // Validate. + let idx: i32 = 0; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev != nil) { if (ev.kind == nkind.N_UN) { + if (ev.op == tkind.TK_MINUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + } else { if (ev.op == tkind.TK_PLUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + };}; + };}; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_FLOATLIT) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + let last_bits: u64 = 0u64; + let last_neg: bool = false; + let repeat: bool = false; + e = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + let neg: bool = false; + if (ev != nil) { if (ev.kind == nkind.N_UN) { + if (ev.op == tkind.TK_MINUS) { + neg = true; + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + } else { if (ev.op == tkind.TK_PLUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + };}; + };}; + let bits: u64 = ev.uval; + if (isf32) { + let dv: f64 = *((&bits): *f64); + let fv: f32 = (dv: f32); + let uv: u32 = *((&fv): *u32); + bits = uv: u64; + }; + let bb: i32 = 0; + let nb: u64 = bits; + for (bb < esz) { + let byt: u8 = (nb & 255u64): u8; + if (neg) { + if (bb == esz - 1) { byt = byt ^ 128u8; }; + }; + emitdatawbyte(byt); + nb = nb >> 8u64; + bb += 1; + }; + last_bits = bits; + last_neg = neg; + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat) { + let bb: i32 = 0; + let nb: u64 = last_bits; + for (bb < esz) { + let byt: u8 = (nb & 255u64): u8; + if (last_neg) { + if (bb == esz - 1) { byt = byt ^ 128u8; }; + }; + emitdatawbyte(byt); + nb = nb >> 8u64; + bb += 1; + }; + } else { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + }; + idx += 1; + }; + return true; + }; + + // Int-element path — preserved byte-for-byte from the pre-A.3 + // emitletdataw in-place arm so bootstrap consumers (u8/i8/u16 + // arrays) don't shift. + let idx: i32 = 0; + let e: *node = rhs.list; + let last: u64 = 0u64; + let repeat: bool = false; + // Validate first. + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (!foldintliteral(ev, &last)) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + last = 0u64; + repeat = false; + e = rhs.list; + let inrepeat: bool = false; + for (idx < alen) { + let v: u64 = last; + if (!inrepeat && e != nil) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + inrepeat = true; + } else { + e = e.next; + }; + } else { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (!foldintliteral(ev, &v)) { v = 0u64; }; + last = v; + e = e.next; + }; + }; + let nb: u64 = v; + let bb: i32 = 0; + for (bb < esz) { + emitdatawbyte((nb & 255u64): u8); + nb = nb >> 8u64; + bb += 1; + }; + idx += 1; + }; + return true; +}; + +// emitarraydata — top-level wrapper. Two-pass validate-then-emit +// avoids partial-byte corruption if the rhs shape can't reduce. +// nil rhs is the "no-rhs zero-init" shape (e.g. `let buf: [N]u8;` +// in lib/strconv/strconv.ww:287, lib/os/os.ww:92, etc.) — emit +// alen*esz zero bytes. This was the implicit pre-A.3 emitletdataw +// behavior (the old loop emitted zeros when `elems` was nil); the +// refactor would have skipped emit entirely without this branch, +// causing `undefined reference to strconv.f64tos_buf` at link. +fn emitarraydata(c: *cgen, directive: str, name: str, + arrt: *tinfo, rhs: *node) bool = { + let au: *tinfo = arrt; + for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; }; + if (au == nil) { return false; }; + if (au.kind != tykind.TY_ARRAY) { return false; }; + if (rhs == nil) { + let total: u64 = arrt.size; + emitline(directive); + emitline(" "); + emitsymname(c, name); + emitline("(SB),\""); + let i: u64 = 0u64; + for (i < total) { emitdatawbyte(0u8); i = i + 1u64; }; + emitline("\"\n"); + return true; + }; + if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; }; + emitline(directive); + emitline(" "); + emitsymname(c, name); + emitline("(SB),\""); + emitarraylitbytes(c, arrt, rhs, 1); + emitline("\"\n"); + return true; +}; + fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -25437,72 +25736,31 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - // Top-level `[N]T = [a, b, ...]` array global. - // Emits N*esz bytes with each element's bytes - // little-endian for the declared primitive width. - // Element fold goes through foldintliteral (same - // helper as emitdefconstants / scalar arm above) - // so `-1i8` and friends emit their two's-complement - // bytes after the leading N_CAST peel — pre-#19 - // this arm only matched bare N_INTLIT/N_RUNELIT and - // silently emitted zero for unfoldable elements. - // `...` (N_FIELD with str="...") repeats the last - // folded value across the remaining slots. + // #129 A.3: array global routes through the + // emitarraydata SSoT helper. Int-elem path is + // byte-for-byte preserved (bootstrap consumers in + // lib/os, lib/bufio, lib/strings, lib/encoding/ + // utf8, lib/strconv/stof_data don't shift). Float/ + // struct elements gain emit via element-kind + // dispatch. Helper validates pre-emit so partial + // fold-failures don't corrupt the DATA literal. + // No-rhs arrays (e.g. `let buf: [N]u8;`) go through + // the same helper with rhs=nil → zero-fill branch. if (d.lhs != nil) { if (d.lhs.kind == nkind.N_TARRAY) { - let elemn: *node = d.lhs.lhs; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let ps: i32 = primsize(elemn.str); - if (ps > 0) { esz = ps; }; + let rh: *node = d.rhs; + let route: bool = false; + if (rh == nil) { route = true; }; + if (rh != nil) { + if (rh.kind == nkind.N_ARRLIT) { + route = true; }; }; - let total: i32 = sz; - let alen: i32 = total / esz; - let elems: *node = nil; - if (d.rhs != nil) { - if (d.rhs.kind == nkind.N_ARRLIT) { - elems = d.rhs.list; - }; + if (route) { + let at: *tinfo = d.lhs.type_: *tinfo; + emitarraydata(c, "DATAW", nm, + at, rh); }; - emitline("DATAW "); - emitsymname(c, nm); - emitline("(SB),\""); - let i: i32 = 0; - let e: *node = elems; - let last: u64 = 0u64; - let inrepeat: bool = false; - for (i < alen) { - let v: u64 = last; - if (!inrepeat && e != nil) { - if (e.kind == nkind.N_FIELD) { - if (streq(e.str, "...")) { - inrepeat = true; - } else { - e = e.next; - }; - } else { - let ev: *node = e; - for (ev != nil) { - if (ev.kind != nkind.N_CAST) { break; }; - ev = ev.lhs; - }; - if (!foldintliteral(ev, &v)) { v = 0u64; }; - last = v; - e = e.next; - }; - }; - let nb: u64 = v; - let b: i32 = 0; - for (b < esz) { - emitdatawbyte((nb & 255u64): u8); - nb = nb >> 8u64; - b += 1; - }; - i += 1; - }; - emitline("\"\n"); }; }; }; @@ -25565,6 +25823,21 @@ fn emitdefconstants(c: *cgen, file: *node) void = { }; }; };}; + // #129 A.3: array-typed def with N_ARRLIT rhs. + // Parallel to emitletdataw array arm; uses DATA. + if (r != nil) { if (r.kind == nkind.N_ARRLIT) { + let at: *tinfo = d.lhs.type_: *tinfo; + let au: *tinfo = at; + for (au != nil && au.kind == tykind.TY_NAMED) { + au = au.under; + }; + if (au != nil) { + if (au.kind == tykind.TY_ARRAY) { + emitarraydata(c, "DATA", + d.str, at, r); + }; + }; + };}; }; }; if (ok) { diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index a544978e..823ee059 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -1144,6 +1144,21 @@ fn defvarstructinfo(c: *cgen, name: str) *structinfo = { return nil; }; +// defvartnode — sister of letvartnode for top-level `def`s. Returns +// the type-spec node (defent.dtnode) for the named def, or nil. #129 +// A.3 uses it in cgindex's array-base resolution so a `def: [N]T` +// resolves through the same N_TARRAY-detect → LEAQ name(SB) shape as +// a let array. Parallel to defvarstructinfo (#129 A.2) at the LOAD +// side widening. +fn defvartnode(c: *cgen, name: str) *node = { + let e: *defent = c.defs; + for (e != nil) { + if (streq(e.dname, name)) { return e.dtnode; }; + e = e.dnext; + }; + return nil; +}; + // emitdatawbyte — write one byte of an asm string literal using // the same escape rules as emitdefconstants / emitdatasection. fn emitdatawbyte(b: u8) void = { @@ -1368,6 +1383,30 @@ fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node, f = f.tnext; continue; }; + // #129 A.3: array-typed field with N_ARRLIT rhs (the shape + // parked in A.2). Recurses through emitarraylitbytes for + // element-kind dispatch. Rule-7 stops loudly if rhs shape + // doesn't match. + if (fu != nil && fu.kind == tykind.TY_ARRAY) { + if (vr == nil) { + let m: str = "emitstructlitbytes: array field rhs nil (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + if (vr.kind != nkind.N_ARRLIT) { + let m: str = "emitstructlitbytes: array field rhs not N_ARRLIT (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + if (!emitarraylitbytes(c, f.type_, vr, 1)) { + let m: str = "emitstructlitbytes: array field rhs has non-reducible elements (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + pos = fstart + fsz: u64; + f = f.tnext; + continue; + }; if (typeisfloat(f.type_)) { let isf32: bool = (fsz == 4); let neg: bool = false; @@ -1456,6 +1495,260 @@ fn emitstructdata(c: *cgen, directive: str, name: str, return true; }; +// emitarraylitbytes — emit alen * esz bytes for an [N]T top-level let/ +// def with N_ARRLIT rhs. Mirrors cstage emit_array_lit_bytes. Per- +// element dispatch: +// - int (covers bool/rune/typed-int/N_UN-int): foldintliteral per +// element. Existing pre-#129-A.3 emitletdataw array arm logic +// preserved byte-for-byte so bootstrap consumers (lib/os, lib/ +// bufio, lib/strings, lib/encoding/utf8, lib/strconv/stof_data) +// don't shift. +// - float (f32/f64): peel N_CAST/N_UN(±), bitcast magnitude via +// pointer-cast round-trip (mirror emitfloatlitdata), sign-XOR +// top byte of each element inline. No 2^63 immediate. +// - struct: per element call emitstructlitbytes (#129 A.2 helper). +// - other element kinds (ptr/nested-array): returns false — caller +// falls through to zero-init. +// +// Two-pass validate-then-emit (`emit_phase=0` validate-only, `=1` +// actually emit) keeps emit-on-failure from emitting partial bytes +// into an open DATA literal. +fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, + emit_phase: i32) bool = { + let au: *tinfo = arrt; + for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; }; + if (au == nil) { return false; }; + if (au.kind != tykind.TY_ARRAY) { return false; }; + let esz: i32 = au.sub.size: i32; + let alen: i32 = au.alen: i32; + let eu: *tinfo = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + + if (eu != nil && eu.kind == tykind.TY_STRUCT) { + // Validate: every element must be N_STRUCTLIT (after N_CAST). + let idx: i32 = 0; + let last_ev: *node = nil; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_STRUCTLIT) { return false; }; + last_ev = ev; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + let repeat: bool = false; + e = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + emitstructlitbytes(c, au.sub, ev, 0u64); + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat && last_ev != nil) { + emitstructlitbytes(c, au.sub, last_ev, 0u64); + } else { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + }; + idx += 1; + }; + return true; + }; + + if (typeisfloat(au.sub)) { + let isf32: bool = typeisf32(au.sub); + // Validate. + let idx: i32 = 0; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev != nil) { if (ev.kind == nkind.N_UN) { + if (ev.op == tkind.TK_MINUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + } else { if (ev.op == tkind.TK_PLUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + };}; + };}; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_FLOATLIT) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + let last_bits: u64 = 0u64; + let last_neg: bool = false; + let repeat: bool = false; + e = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + let neg: bool = false; + if (ev != nil) { if (ev.kind == nkind.N_UN) { + if (ev.op == tkind.TK_MINUS) { + neg = true; + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + } else { if (ev.op == tkind.TK_PLUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + };}; + };}; + let bits: u64 = ev.uval; + if (isf32) { + let dv: f64 = *((&bits): *f64); + let fv: f32 = (dv: f32); + let uv: u32 = *((&fv): *u32); + bits = uv: u64; + }; + let bb: i32 = 0; + let nb: u64 = bits; + for (bb < esz) { + let byt: u8 = (nb & 255u64): u8; + if (neg) { + if (bb == esz - 1) { byt = byt ^ 128u8; }; + }; + emitdatawbyte(byt); + nb = nb >> 8u64; + bb += 1; + }; + last_bits = bits; + last_neg = neg; + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat) { + let bb: i32 = 0; + let nb: u64 = last_bits; + for (bb < esz) { + let byt: u8 = (nb & 255u64): u8; + if (last_neg) { + if (bb == esz - 1) { byt = byt ^ 128u8; }; + }; + emitdatawbyte(byt); + nb = nb >> 8u64; + bb += 1; + }; + } else { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + }; + idx += 1; + }; + return true; + }; + + // Int-element path — preserved byte-for-byte from the pre-A.3 + // emitletdataw in-place arm so bootstrap consumers (u8/i8/u16 + // arrays) don't shift. + let idx: i32 = 0; + let e: *node = rhs.list; + let last: u64 = 0u64; + let repeat: bool = false; + // Validate first. + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (!foldintliteral(ev, &last)) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + last = 0u64; + repeat = false; + e = rhs.list; + let inrepeat: bool = false; + for (idx < alen) { + let v: u64 = last; + if (!inrepeat && e != nil) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + inrepeat = true; + } else { + e = e.next; + }; + } else { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (!foldintliteral(ev, &v)) { v = 0u64; }; + last = v; + e = e.next; + }; + }; + let nb: u64 = v; + let bb: i32 = 0; + for (bb < esz) { + emitdatawbyte((nb & 255u64): u8); + nb = nb >> 8u64; + bb += 1; + }; + idx += 1; + }; + return true; +}; + +// emitarraydata — top-level wrapper. Two-pass validate-then-emit +// avoids partial-byte corruption if the rhs shape can't reduce. +// nil rhs is the "no-rhs zero-init" shape (e.g. `let buf: [N]u8;` +// in lib/strconv/strconv.ww:287, lib/os/os.ww:92, etc.) — emit +// alen*esz zero bytes. This was the implicit pre-A.3 emitletdataw +// behavior (the old loop emitted zeros when `elems` was nil); the +// refactor would have skipped emit entirely without this branch, +// causing `undefined reference to strconv.f64tos_buf` at link. +fn emitarraydata(c: *cgen, directive: str, name: str, + arrt: *tinfo, rhs: *node) bool = { + let au: *tinfo = arrt; + for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; }; + if (au == nil) { return false; }; + if (au.kind != tykind.TY_ARRAY) { return false; }; + if (rhs == nil) { + let total: u64 = arrt.size; + emitline(directive); + emitline(" "); + emitsymname(c, name); + emitline("(SB),\""); + let i: u64 = 0u64; + for (i < total) { emitdatawbyte(0u8); i = i + 1u64; }; + emitline("\"\n"); + return true; + }; + if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; }; + emitline(directive); + emitline(" "); + emitsymname(c, name); + emitline("(SB),\""); + emitarraylitbytes(c, arrt, rhs, 1); + emitline("\"\n"); + return true; +}; + fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -1642,72 +1935,31 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - // Top-level `[N]T = [a, b, ...]` array global. - // Emits N*esz bytes with each element's bytes - // little-endian for the declared primitive width. - // Element fold goes through foldintliteral (same - // helper as emitdefconstants / scalar arm above) - // so `-1i8` and friends emit their two's-complement - // bytes after the leading N_CAST peel — pre-#19 - // this arm only matched bare N_INTLIT/N_RUNELIT and - // silently emitted zero for unfoldable elements. - // `...` (N_FIELD with str="...") repeats the last - // folded value across the remaining slots. + // #129 A.3: array global routes through the + // emitarraydata SSoT helper. Int-elem path is + // byte-for-byte preserved (bootstrap consumers in + // lib/os, lib/bufio, lib/strings, lib/encoding/ + // utf8, lib/strconv/stof_data don't shift). Float/ + // struct elements gain emit via element-kind + // dispatch. Helper validates pre-emit so partial + // fold-failures don't corrupt the DATA literal. + // No-rhs arrays (e.g. `let buf: [N]u8;`) go through + // the same helper with rhs=nil → zero-fill branch. if (d.lhs != nil) { if (d.lhs.kind == nkind.N_TARRAY) { - let elemn: *node = d.lhs.lhs; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let ps: i32 = primsize(elemn.str); - if (ps > 0) { esz = ps; }; + let rh: *node = d.rhs; + let route: bool = false; + if (rh == nil) { route = true; }; + if (rh != nil) { + if (rh.kind == nkind.N_ARRLIT) { + route = true; }; }; - let total: i32 = sz; - let alen: i32 = total / esz; - let elems: *node = nil; - if (d.rhs != nil) { - if (d.rhs.kind == nkind.N_ARRLIT) { - elems = d.rhs.list; - }; + if (route) { + let at: *tinfo = d.lhs.type_: *tinfo; + emitarraydata(c, "DATAW", nm, + at, rh); }; - emitline("DATAW "); - emitsymname(c, nm); - emitline("(SB),\""); - let i: i32 = 0; - let e: *node = elems; - let last: u64 = 0u64; - let inrepeat: bool = false; - for (i < alen) { - let v: u64 = last; - if (!inrepeat && e != nil) { - if (e.kind == nkind.N_FIELD) { - if (streq(e.str, "...")) { - inrepeat = true; - } else { - e = e.next; - }; - } else { - let ev: *node = e; - for (ev != nil) { - if (ev.kind != nkind.N_CAST) { break; }; - ev = ev.lhs; - }; - if (!foldintliteral(ev, &v)) { v = 0u64; }; - last = v; - e = e.next; - }; - }; - let nb: u64 = v; - let b: i32 = 0; - for (b < esz) { - emitdatawbyte((nb & 255u64): u8); - nb = nb >> 8u64; - b += 1; - }; - i += 1; - }; - emitline("\"\n"); }; }; }; @@ -1770,6 +2022,21 @@ fn emitdefconstants(c: *cgen, file: *node) void = { }; }; };}; + // #129 A.3: array-typed def with N_ARRLIT rhs. + // Parallel to emitletdataw array arm; uses DATA. + if (r != nil) { if (r.kind == nkind.N_ARRLIT) { + let at: *tinfo = d.lhs.type_: *tinfo; + let au: *tinfo = at; + for (au != nil && au.kind == tykind.TY_NAMED) { + au = au.under; + }; + if (au != nil) { + if (au.kind == tykind.TY_ARRAY) { + emitarraydata(c, "DATA", + d.str, at, r); + }; + }; + };}; }; }; if (ok) { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 107455a0..dd659df6 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -896,6 +896,12 @@ fn cgindex(c: *cgen, n: *node) void = { f32_elem = elemisf32c(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); + // #129 A.3: array-typed defs now have DATA storage; + // resolve their base via the same N_TARRAY path as + // lets. defvartnode is the def-side sister of + // letvartnode (parallel to defvarstructinfo at the + // A.2 cgdot widening site). + if (tn == nil) { tn = defvartnode(c, bn); }; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 32b19ebc..996ecb0a 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -15515,6 +15515,12 @@ fn cgindex(c: *cgen, n: *node) void = { f32_elem = elemisf32c(c, baselocal.tnode); } else { let tn: *node = letvartnode(c, bn); + // #129 A.3: array-typed defs now have DATA storage; + // resolve their base via the same N_TARRAY path as + // lets. defvartnode is the def-side sister of + // letvartnode (parallel to defvarstructinfo at the + // A.2 cgdot widening site). + if (tn == nil) { tn = defvartnode(c, bn); }; if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { isglobalarr = true; @@ -24939,6 +24945,21 @@ fn defvarstructinfo(c: *cgen, name: str) *structinfo = { return nil; }; +// defvartnode — sister of letvartnode for top-level `def`s. Returns +// the type-spec node (defent.dtnode) for the named def, or nil. #129 +// A.3 uses it in cgindex's array-base resolution so a `def: [N]T` +// resolves through the same N_TARRAY-detect → LEAQ name(SB) shape as +// a let array. Parallel to defvarstructinfo (#129 A.2) at the LOAD +// side widening. +fn defvartnode(c: *cgen, name: str) *node = { + let e: *defent = c.defs; + for (e != nil) { + if (streq(e.dname, name)) { return e.dtnode; }; + e = e.dnext; + }; + return nil; +}; + // emitdatawbyte — write one byte of an asm string literal using // the same escape rules as emitdefconstants / emitdatasection. fn emitdatawbyte(b: u8) void = { @@ -25163,6 +25184,30 @@ fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node, f = f.tnext; continue; }; + // #129 A.3: array-typed field with N_ARRLIT rhs (the shape + // parked in A.2). Recurses through emitarraylitbytes for + // element-kind dispatch. Rule-7 stops loudly if rhs shape + // doesn't match. + if (fu != nil && fu.kind == tykind.TY_ARRAY) { + if (vr == nil) { + let m: str = "emitstructlitbytes: array field rhs nil (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + if (vr.kind != nkind.N_ARRLIT) { + let m: str = "emitstructlitbytes: array field rhs not N_ARRLIT (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + if (!emitarraylitbytes(c, f.type_, vr, 1)) { + let m: str = "emitstructlitbytes: array field rhs has non-reducible elements (#129 A.3)\n"; + os.write(2, m.ptr, m.len: u64); + os.exit(1); + }; + pos = fstart + fsz: u64; + f = f.tnext; + continue; + }; if (typeisfloat(f.type_)) { let isf32: bool = (fsz == 4); let neg: bool = false; @@ -25251,6 +25296,260 @@ fn emitstructdata(c: *cgen, directive: str, name: str, return true; }; +// emitarraylitbytes — emit alen * esz bytes for an [N]T top-level let/ +// def with N_ARRLIT rhs. Mirrors cstage emit_array_lit_bytes. Per- +// element dispatch: +// - int (covers bool/rune/typed-int/N_UN-int): foldintliteral per +// element. Existing pre-#129-A.3 emitletdataw array arm logic +// preserved byte-for-byte so bootstrap consumers (lib/os, lib/ +// bufio, lib/strings, lib/encoding/utf8, lib/strconv/stof_data) +// don't shift. +// - float (f32/f64): peel N_CAST/N_UN(±), bitcast magnitude via +// pointer-cast round-trip (mirror emitfloatlitdata), sign-XOR +// top byte of each element inline. No 2^63 immediate. +// - struct: per element call emitstructlitbytes (#129 A.2 helper). +// - other element kinds (ptr/nested-array): returns false — caller +// falls through to zero-init. +// +// Two-pass validate-then-emit (`emit_phase=0` validate-only, `=1` +// actually emit) keeps emit-on-failure from emitting partial bytes +// into an open DATA literal. +fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, + emit_phase: i32) bool = { + let au: *tinfo = arrt; + for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; }; + if (au == nil) { return false; }; + if (au.kind != tykind.TY_ARRAY) { return false; }; + let esz: i32 = au.sub.size: i32; + let alen: i32 = au.alen: i32; + let eu: *tinfo = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + + if (eu != nil && eu.kind == tykind.TY_STRUCT) { + // Validate: every element must be N_STRUCTLIT (after N_CAST). + let idx: i32 = 0; + let last_ev: *node = nil; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_STRUCTLIT) { return false; }; + last_ev = ev; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + let repeat: bool = false; + e = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + emitstructlitbytes(c, au.sub, ev, 0u64); + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat && last_ev != nil) { + emitstructlitbytes(c, au.sub, last_ev, 0u64); + } else { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + }; + idx += 1; + }; + return true; + }; + + if (typeisfloat(au.sub)) { + let isf32: bool = typeisf32(au.sub); + // Validate. + let idx: i32 = 0; + let e: *node = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev != nil) { if (ev.kind == nkind.N_UN) { + if (ev.op == tkind.TK_MINUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + } else { if (ev.op == tkind.TK_PLUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + };}; + };}; + if (ev == nil) { return false; }; + if (ev.kind != nkind.N_FLOATLIT) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + let last_bits: u64 = 0u64; + let last_neg: bool = false; + let repeat: bool = false; + e = rhs.list; + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + let neg: bool = false; + if (ev != nil) { if (ev.kind == nkind.N_UN) { + if (ev.op == tkind.TK_MINUS) { + neg = true; + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + } else { if (ev.op == tkind.TK_PLUS) { + ev = ev.lhs; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + };}; + };}; + let bits: u64 = ev.uval; + if (isf32) { + let dv: f64 = *((&bits): *f64); + let fv: f32 = (dv: f32); + let uv: u32 = *((&fv): *u32); + bits = uv: u64; + }; + let bb: i32 = 0; + let nb: u64 = bits; + for (bb < esz) { + let byt: u8 = (nb & 255u64): u8; + if (neg) { + if (bb == esz - 1) { byt = byt ^ 128u8; }; + }; + emitdatawbyte(byt); + nb = nb >> 8u64; + bb += 1; + }; + last_bits = bits; + last_neg = neg; + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat) { + let bb: i32 = 0; + let nb: u64 = last_bits; + for (bb < esz) { + let byt: u8 = (nb & 255u64): u8; + if (last_neg) { + if (bb == esz - 1) { byt = byt ^ 128u8; }; + }; + emitdatawbyte(byt); + nb = nb >> 8u64; + bb += 1; + }; + } else { + let bb: i32 = 0; + for (bb < esz) { emitdatawbyte(0u8); bb += 1; }; + }; + idx += 1; + }; + return true; + }; + + // Int-element path — preserved byte-for-byte from the pre-A.3 + // emitletdataw in-place arm so bootstrap consumers (u8/i8/u16 + // arrays) don't shift. + let idx: i32 = 0; + let e: *node = rhs.list; + let last: u64 = 0u64; + let repeat: bool = false; + // Validate first. + for (e != nil && idx < alen) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { repeat = true; break; }; + }; + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (ev == nil) { return false; }; + if (!foldintliteral(ev, &last)) { return false; }; + idx += 1; + e = e.next; + }; + if (emit_phase == 0) { return true; }; + idx = 0; + last = 0u64; + repeat = false; + e = rhs.list; + let inrepeat: bool = false; + for (idx < alen) { + let v: u64 = last; + if (!inrepeat && e != nil) { + if (e.kind == nkind.N_FIELD) { + if (streq(e.str, "...")) { + inrepeat = true; + } else { + e = e.next; + }; + } else { + let ev: *node = e; + for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; }; + if (!foldintliteral(ev, &v)) { v = 0u64; }; + last = v; + e = e.next; + }; + }; + let nb: u64 = v; + let bb: i32 = 0; + for (bb < esz) { + emitdatawbyte((nb & 255u64): u8); + nb = nb >> 8u64; + bb += 1; + }; + idx += 1; + }; + return true; +}; + +// emitarraydata — top-level wrapper. Two-pass validate-then-emit +// avoids partial-byte corruption if the rhs shape can't reduce. +// nil rhs is the "no-rhs zero-init" shape (e.g. `let buf: [N]u8;` +// in lib/strconv/strconv.ww:287, lib/os/os.ww:92, etc.) — emit +// alen*esz zero bytes. This was the implicit pre-A.3 emitletdataw +// behavior (the old loop emitted zeros when `elems` was nil); the +// refactor would have skipped emit entirely without this branch, +// causing `undefined reference to strconv.f64tos_buf` at link. +fn emitarraydata(c: *cgen, directive: str, name: str, + arrt: *tinfo, rhs: *node) bool = { + let au: *tinfo = arrt; + for (au != nil && au.kind == tykind.TY_NAMED) { au = au.under; }; + if (au == nil) { return false; }; + if (au.kind != tykind.TY_ARRAY) { return false; }; + if (rhs == nil) { + let total: u64 = arrt.size; + emitline(directive); + emitline(" "); + emitsymname(c, name); + emitline("(SB),\""); + let i: u64 = 0u64; + for (i < total) { emitdatawbyte(0u8); i = i + 1u64; }; + emitline("\"\n"); + return true; + }; + if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; }; + emitline(directive); + emitline(" "); + emitsymname(c, name); + emitline("(SB),\""); + emitarraylitbytes(c, arrt, rhs, 1); + emitline("\"\n"); + return true; +}; + fn emitletdataw(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { @@ -25437,72 +25736,31 @@ fn emitletdataw(c: *cgen, file: *node) void = { emitline("\"\n"); }; }; - // Top-level `[N]T = [a, b, ...]` array global. - // Emits N*esz bytes with each element's bytes - // little-endian for the declared primitive width. - // Element fold goes through foldintliteral (same - // helper as emitdefconstants / scalar arm above) - // so `-1i8` and friends emit their two's-complement - // bytes after the leading N_CAST peel — pre-#19 - // this arm only matched bare N_INTLIT/N_RUNELIT and - // silently emitted zero for unfoldable elements. - // `...` (N_FIELD with str="...") repeats the last - // folded value across the remaining slots. + // #129 A.3: array global routes through the + // emitarraydata SSoT helper. Int-elem path is + // byte-for-byte preserved (bootstrap consumers in + // lib/os, lib/bufio, lib/strings, lib/encoding/ + // utf8, lib/strconv/stof_data don't shift). Float/ + // struct elements gain emit via element-kind + // dispatch. Helper validates pre-emit so partial + // fold-failures don't corrupt the DATA literal. + // No-rhs arrays (e.g. `let buf: [N]u8;`) go through + // the same helper with rhs=nil → zero-fill branch. if (d.lhs != nil) { if (d.lhs.kind == nkind.N_TARRAY) { - let elemn: *node = d.lhs.lhs; - let esz: i32 = 8; - if (elemn != nil) { - if (elemn.kind == nkind.N_TNAME) { - let ps: i32 = primsize(elemn.str); - if (ps > 0) { esz = ps; }; + let rh: *node = d.rhs; + let route: bool = false; + if (rh == nil) { route = true; }; + if (rh != nil) { + if (rh.kind == nkind.N_ARRLIT) { + route = true; }; }; - let total: i32 = sz; - let alen: i32 = total / esz; - let elems: *node = nil; - if (d.rhs != nil) { - if (d.rhs.kind == nkind.N_ARRLIT) { - elems = d.rhs.list; - }; + if (route) { + let at: *tinfo = d.lhs.type_: *tinfo; + emitarraydata(c, "DATAW", nm, + at, rh); }; - emitline("DATAW "); - emitsymname(c, nm); - emitline("(SB),\""); - let i: i32 = 0; - let e: *node = elems; - let last: u64 = 0u64; - let inrepeat: bool = false; - for (i < alen) { - let v: u64 = last; - if (!inrepeat && e != nil) { - if (e.kind == nkind.N_FIELD) { - if (streq(e.str, "...")) { - inrepeat = true; - } else { - e = e.next; - }; - } else { - let ev: *node = e; - for (ev != nil) { - if (ev.kind != nkind.N_CAST) { break; }; - ev = ev.lhs; - }; - if (!foldintliteral(ev, &v)) { v = 0u64; }; - last = v; - e = e.next; - }; - }; - let nb: u64 = v; - let b: i32 = 0; - for (b < esz) { - emitdatawbyte((nb & 255u64): u8); - nb = nb >> 8u64; - b += 1; - }; - i += 1; - }; - emitline("\"\n"); }; }; }; @@ -25565,6 +25823,21 @@ fn emitdefconstants(c: *cgen, file: *node) void = { }; }; };}; + // #129 A.3: array-typed def with N_ARRLIT rhs. + // Parallel to emitletdataw array arm; uses DATA. + if (r != nil) { if (r.kind == nkind.N_ARRLIT) { + let at: *tinfo = d.lhs.type_: *tinfo; + let au: *tinfo = at; + for (au != nil && au.kind == tykind.TY_NAMED) { + au = au.under; + }; + if (au != nil) { + if (au.kind == tykind.TY_ARRAY) { + emitarraydata(c, "DATA", + d.str, at, r); + }; + }; + };}; }; }; if (ok) { diff --git a/test/wcc/919_array_static_init_run.c b/test/wcc/919_array_static_init_run.c new file mode 100644 index 00000000..15f4f6ba --- /dev/null +++ b/test/wcc/919_array_static_init_run.c @@ -0,0 +1,260 @@ +/* + * 919_array_static_init_run — runtime + byte-id net for #129 Phase + * A.3: module-level let/def with array initializer. + * + * Pre-A.3 state: + * - Int-element arrays (u8/i8/u16/u32/u64/i32 etc) already worked + * in both stages via fold_int_literal. + * - Float-element arrays ([N]f64, [N]f32) → undef-ref at link + * (emit_lets array arm fold_int_literal fails on FLOATLIT). + * - Array DEFs (def A: [N]T = [...]) → emit_defs no array arm + * (storage missing) AND cgindex broken (reads LEAQ (BP), BX — + * stack frame, not data section). + * - Array-in-struct field (`def D: dt = dt{tag=42, buf=[...]}`) → + * #129 A.2 rule-7 fatal "array field rhs not foldable" — the + * shape parked in A.2 awaiting A.3. + * + * Phase A.3 fix (mirror A.1/A.2 SSoT-helper precedent): + * - cstage: emit_array_data + emit_array_lit_bytes helpers with + * element-kind dispatch (int via fold_int_literal preserving + * bootstrap byte-id, float via inline bitcast + sign-XOR byte- + * loop mirror of A.1, struct via emit_struct_lit_bytes recursion + * mirror of A.2). Two-pass validate-then-emit avoids partial-byte + * corruption on rhs-fold-failure. + * - cstage: emit_lets array arm routes through helper; emit_defs + * gains array arm. + * - cstage: DefArray registry + def_isarraydef populated in + * let_collect; cgindex N_INDEX direct-ident `isglobal` gate + * widened to (let_islet || def_isarraydef). + * - cstage: emit_struct_lit_bytes (A.2 helper) gains TY_ARRAY field + * arm calling emit_array_lit_bytes recursively (closes A.2 parked + * shape 15). + * - wwstage: parallel emitarraydata + emitarraylitbytes; emitstruct + * litbytes TY_ARRAY arm; defvartnode helper; cgindex falls through + * to defvartnode after letvartnode nil. + * + * Bootstrap RISK: live consumers in lib/os, lib/bufio, lib/strings, + * lib/encoding/utf8 (dfa + masks), lib/strconv/stof_data + * (left_shift_table). All use typed-int-literal elements; the int-elem + * helper path is byte-for-byte preserved → bootstrap NEUTRAL. + * + * Rows (size strata 1B/2B/4B/8B × count strata 1/2/4 × int/float/struct/ + * empty/no-rhs/def-variant, avoiding the 16B-evade pattern from A.2): + * + * - (a) `let A: [4]u8 = [1u8, 2u8, 3u8, 4u8]` — 1B regression + * - (b) `let A: [4]u32 = [1u32, 2u32, 3u32, 4u32]` — 4B regression + * - (c) `let A: [2]u64 = [1u64, 2u64]` — 8B regression + * - (d) `let A: [4]i32 = [-1, -2, -3, -4]` — N_UN peel regression + * - (e) `let A: [4]f64 = [1.5, -2.5, 3.5, -4.5]` — NEW float-elem + * - (f) `let A: [4]f32 = [1.5f32, -2.5f32, 3.5f32, -4.5f32]` — NEW + * f32 narrow + sign-XOR per element + * - (g) `def A: [4]u32 = [11u32, 22u32, 33u32, 44u32]` — NEW + * def-storage + LOAD-widening + * - (h) `def A: [4]f64 = [1.5, 2.5, 3.5, 4.5]` — NEW def-variant of + * float + * - (i) `def D: dt = dt{tag=42, buf=[1u8,2u8,3u8,4u8]}` — NEW + * closes A.2 shape 15 (struct-with-array-field) + * - (j) `let A: [4]u8 = [0u8, 0u8, 0u8, 0u8]` — explicit-zero + * regression + * - (k) `let A: [1]u8 = [0u8]` — single-elem (matches lib/os/ + * emptypath pattern) + * + * Each row: cstage `ww build` + run asserting exit code + w6c vs + * w6c_ww `.s` cmp (rule-10 byte-id). + * + * Deferred: + * - Pointer-element arrays `[N]*T = [&G, &H]` — needs DATAR per + * element (own task/fold). + * - Nested arrays `[N][M]T` — no current consumer. + * - Bare-int `[N]u8 = [1, 2, 3, 4]` — #130 (checker issue). + * - Partial init `[4]u8 = [1u8]` — checker rejects (parser/checker + * decision). + */ +#include +#include +#include +#include +#include +#include + +static int +runwait(const char *cmd) +{ + int rc = system(cmd); + if (rc == -1) return -1; + if (WIFEXITED(rc)) return WEXITSTATUS(rc); + return -1; +} + +struct row { const char *label; const char *src; int want_exit; }; + +static const struct row rows[] = { + { "let_u8_arr", + "package main;\n" + "let A: [4]u8 = [1u8, 2u8, 3u8, 4u8];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 1 }, + { "let_u32_arr", + "package main;\n" + "let A: [4]u32 = [1u32, 2u32, 3u32, 4u32];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 1 }, + { "let_u64_arr", + "package main;\n" + "let A: [2]u64 = [1u64, 2u64];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 1 }, + { "let_i32_neg_arr", + "package main;\n" + "let A: [4]i32 = [-1, -2, -3, -4];\n" + "export fn main() i32 = { return A[0]; };\n", 255 /* -1 */ }, + /* Float-element array — NEW in A.3. Includes both signs to exercise + * the sign-XOR byte-loop per element. */ + { "let_f64_arr", + "package main;\n" + "let A: [4]f64 = [1.5, -2.5, 3.5, -4.5];\n" + "export fn main() i32 = { return (A[0]: i32); };\n", 1 }, + { "let_f32_arr", + "package main;\n" + "let A: [4]f32 = [1.5f32, -2.5f32, 3.5f32, -4.5f32];\n" + "export fn main() i32 = { return (A[0]: i32); };\n", 1 }, + /* def-variant exercises the LOAD-widening (def_isarraydef) at + * cgindex/cgident. Storage emit also new. */ + { "def_u32_arr", + "package main;\n" + "def A: [4]u32 = [11u32, 22u32, 33u32, 44u32];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 11 }, + { "def_f64_arr", + "package main;\n" + "def A: [4]f64 = [1.5, 2.5, 3.5, 4.5];\n" + "export fn main() i32 = { return (A[0]: i32); };\n", 1 }, + /* The A.2-parked shape-15 — closes via emit_struct_lit_bytes + * TY_ARRAY field arm. */ + { "let_struct_with_arr_field", + "package main;\n" + "type dt = struct { tag: i32, buf: [4]u8 };\n" + "let D: dt = dt{tag=42, buf=[1u8, 2u8, 3u8, 4u8]};\n" + "export fn main() i32 = { return D.tag; };\n", 42 }, + { "let_u8_zero_arr", + "package main;\n" + "let A: [4]u8 = [0u8, 0u8, 0u8, 0u8];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 0 }, + { "let_u8_single", + "package main;\n" + "let A: [1]u8 = [7u8];\n" + "export fn main() i32 = { return A[0]: i32; };\n", 7 }, + { NULL, NULL, 0 } +}; + +static int +slurp_eq(const char *a, const char *b) +{ + FILE *fa = fopen(a, "rb"); + FILE *fb = fopen(b, "rb"); + if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; } + int rc = 0; + for (;;) { + int ca = fgetc(fa); + int cb = fgetc(fb); + if (ca != cb) { rc = -1; break; } + if (ca == EOF) break; + } + fclose(fa); fclose(fb); + return rc; +} + +int +main(void) +{ + const char *bin = getenv("BIN"); + if (!bin) bin = "out/bin"; + char absbin[1024]; + if (bin[0] != '/') { + char cwd[1024]; + if (getcwd(cwd, sizeof cwd) == NULL) return 1; + snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); + bin = absbin; + } + + char w6c[1100], w6c_ww[1100]; + snprintf(w6c, sizeof w6c, "%s/w6c", bin); + snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin); + if (access(w6c_ww, X_OK) != 0) { + fprintf(stderr, "arrinit: w6c_ww missing — cannot run " + "the cs==ww byte-id gate (the whole point of this test)\n"); + return 1; + } + + int n = 0, fail = 0; + for (int i = 0; rows[i].src; i++, n++) { + char src[64]; + snprintf(src, sizeof src, "/tmp/wwari_%d_%d.ww", getpid(), i); + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(rows[i].src, f); + fclose(f); + + char tmpdir[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwari_%d_d_%d", + getpid(), i); + mkdir(tmpdir, 0755); + + char cmd[2048]; + snprintf(cmd, sizeof cmd, "cd %s && %s/ww build %s", + tmpdir, bin, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: cstage build failed\n", + rows[i].label); + fail++; + unlink(src); rmdir(tmpdir); + continue; + } + + char outbin[128]; + const char *base = strrchr(src, '/'); + base = base ? base + 1 : src; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + + int got = runwait(outbin); + if (got != rows[i].want_exit) { + fprintf(stderr, "row[%s]: cstage exit %d, want %d\n", + rows[i].label, got, rows[i].want_exit); + fail++; + } + unlink(outbin); rmdir(tmpdir); + + char cs_s[64], ws_s[64]; + snprintf(cs_s, sizeof cs_s, "/tmp/wwari_%d_%d_cs.s", + getpid(), i); + snprintf(ws_s, sizeof ws_s, "/tmp/wwari_%d_%d_ww.s", + getpid(), i); + + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", + w6c, cs_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c failed\n", rows[i].label); + fail++; unlink(src); continue; + } + snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", + w6c_ww, ws_s, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww failed\n", + rows[i].label); + fail++; unlink(src); unlink(cs_s); continue; + } + if (slurp_eq(cs_s, ws_s) != 0) { + fprintf(stderr, + "row[%s]: cstage/wwstage .s DIFFER (rule-10 " + "byte-id violation)\n", rows[i].label); + fail++; + } + unlink(src); unlink(cs_s); unlink(ws_s); + } + + if (fail) { + fprintf(stderr, "%d/%d array-static-init tests failed\n", fail, n); + return 1; + } + printf("arrinit: %d/%d ok (cstage run + cs==ww byte-id)\n", + n, n); + return 0; +}