wcc: array static-init let/def DATA emit via SSoT helper (#129 A.3)

Extract emit_array_data + emit_array_lit_bytes helpers (both stages,
mirrored) for module-level let/def with N_ARRLIT initializer or no-rhs
zero-init. Two-pass validate-then-emit: validate pass walks elements
and fails atomically on any non-foldable element (no partial-byte
emit on failure); emit pass writes element bytes after success.
Element-kind dispatch: integer via fold_int_literal byte-for-byte
preserved from pre-A.3 inline arm (bootstrap NEUTRAL — 6 live consumers
in lib/os/bufio/strings/encoding-utf8/strconv-stof_data), float via
inline bitcast + sign-XOR byte-loop (A.1 shape, no INT64_MIN — sibling
#144), struct via recursion into emit_struct_lit_bytes (A.2 helper).
Out-of-scope element kinds (ptr-elem, nested-array) rule-7 fatal.

emit_struct_lit_bytes gains TY_ARRAY field arm calling emit_array_lit_
bytes recursively — closes A.2 parked shape-15 (array-in-struct
`def D: dt = dt{tag=42, buf=[1u8,2u8,3u8,4u8]};`).

LOAD-side widened symmetric to A.2 precedent: cstage cgindex N_INDEX
direct-ident isglobal gate widened via new DefArray registry
(def_isarraydef populated in let_collect parallel to DefStruct);
wwstage cgindex N_INDEX falls through to defvartnode on letvartnode nil
(reads defent.dtnode field added in A.2). Both stages materialise
array-def via LEAQ name(SB) same as array-let.

Mid-impl rule-7 stop: refactor initially routed only rhs==N_ARRLIT
through emitarraydata, leaving nil-rhs zero-init arrays (e.g.
`let f64tos_buf: [64]u8;` in lib/strconv) silently SKIPPED → undef-ref
at link of wwstage-rebuilt selfhost binaries. Caught on first gate run
via bootstrap 994/995 RED. Fixed by adding nil-rhs branch to
emitarraydata (zero-fills arrt.size bytes) + widening wwstage caller
to route both N_ARRLIT and nil through helper. Same-class-lower-stratum
pattern (recurring across A.1 N_UN-peel, A.2 sz==8-short-circuit, A.3
nil-rhs-drop); banked as feedback memory.

Test 919 (11 rows: int-elem 1B/4B/8B + signed-N_UN-peel + float-elem
f64/f32 + def-int / def-float / struct-with-array-field shape-15 +
explicit-zero + single-elem-regression) registered. Make test:
182/182 incl. 990-997 byte-id + combined_ww_fresh.

Followups filed:
- #43 — wwstage emitletdataw str/slice-size arms lack !isarr guards;
  hypothetical no-rhs [16/24]u8 triple-emits (NOT A.3-introduced;
  no live consumer; 2-line parity fix)
This commit is contained in:
2026-05-27 05:56:24 +09:00
parent 0ed0b3933c
commit 9e3bc4ea37
7 changed files with 1562 additions and 242 deletions

View File

@@ -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 $@ $<

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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;
}