diff --git a/Makefile b/Makefile index 77a87e74..0fd48345 100644 --- a/Makefile +++ b/Makefile @@ -419,6 +419,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_def_float_lit_run \ $(BIN)/test_struct_composite_init_run \ $(BIN)/test_array_static_init_run \ + $(BIN)/test_strarray_static_run \ $(BIN)/test_array_init_acceptiffits_run \ $(BIN)/test_arrlit_elem_narrow_run \ $(BIN)/test_amp_def_global_run \ @@ -1702,6 +1703,11 @@ $(BIN)/test_array_static_init_run: test/wcc/919_array_static_init_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_strarray_static_run: test/wcc/919_strarray_static_run.c \ + $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_array_init_acceptiffits_run: test/wcc/920_array_init_acceptiffits_run.c \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6c_ww $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e00cc589..e2ed720c 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -11485,6 +11485,100 @@ emit_array_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, int emit_phase) return 1; } +/* emit_strarray_data — module-level `let xs: [N]str = ["a","b",…];` + * static init (#18). The str-element case can't ride emit_array_lit_bytes: + * a str element carries a ptr→rodata relocation, not just bytes. So the + * scalar-str-global pattern (emit_lets str arm: DATAW header with a zero + * ptr placeholder + inline LE len, then a DATAR patching the ptr half) + * is applied per element at offset idx*esz. Each strlit was pre-interned + * by let_pre_intern so its rodata _S_ row exists before this row's DATAR + * references it. + * + * Scoped to DATAW (writable `let`): A_DATAR requires its holder be a + * DATAW slot (w6a asm.c:362), so a read-only `def [N]str` can't carry + * the relocs — that generalisation is a #18 follow-up. Returns 0 if the + * element type isn't str, leaving the generic array path / zero-init to + * the caller. */ +static int +emit_strarray_data(FILE *out, Cg *c, const char *directive, + const char *name, const char *module, Type *t, Node *rhs) +{ + Type *u = type_unwrap(t); + if (u == NULL || u->kind != TY_ARRAY) return 0; + Type *etype = u->sub; + Type *eu = (etype && etype->kind == TY_NAMED) ? etype->under : etype; + if (eu == NULL || eu->kind != TY_STR) return 0; + if (strcmp(directive, "DATAW") != 0) return 0; + int esz = (int)etype->size; + int alen = (int)u->alen; + + /* Validate: each cast-peeled element is an N_STRLIT, up to an + * optional trailing `...` repeat marker. Bail (return 0) on any + * non-strlit so a non-reducible rhs still falls through to the + * generic path rather than emitting a partial row. */ + Node *last_ev = NULL; + int repeat = 0; + int cnt = 0; + for (Node *e = rhs->list; e && cnt < 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_STRLIT) return 0; + last_ev = ev; + cnt++; + } + + const char *sym = mod_mangle_value(c, name, module); + fprintf(out, "DATAW %s(SB),\"", sym); + 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; + for (int i = 0; i < 8; i++) emit_data_byte(out, 0); + u64 v = ev->strlen; + for (int i = 0; i < 8; i++) + emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff)); + for (int i = 16; i < esz; i++) emit_data_byte(out, 0); + idx++; + } + while (idx < alen) { + u64 v = (repeat && last_ev != NULL) ? last_ev->strlen : 0; + for (int i = 0; i < 8; i++) emit_data_byte(out, 0); + for (int i = 0; i < 8; i++) + emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff)); + for (int i = 16; i < esz; i++) emit_data_byte(out, 0); + idx++; + } + fputs("\"\n", out); + + 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->strlen > 0) { + const char *lab = intern_strlit(c, ev->str, ev->strlen); + fprintf(out, "DATAR %s+%d(SB),%s(SB)\n", + sym, idx * esz, lab); + } + idx++; + } + while (idx < alen) { + if (repeat && last_ev != NULL && last_ev->strlen > 0) { + const char *lab = intern_strlit(c, last_ev->str, + last_ev->strlen); + fprintf(out, "DATAR %s+%d(SB),%s(SB)\n", + sym, idx * esz, lab); + } + idx++; + } + 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). */ @@ -11494,6 +11588,10 @@ emit_array_data(FILE *out, Cg *c, const char *directive, { Type *u = type_unwrap(t); if (u == NULL || u->kind != TY_ARRAY) return 0; + /* str-element arrays carry per-element ptr relocations — handled + * by the dedicated DATAW+DATAR helper (#18). */ + if (emit_strarray_data(out, c, directive, name, module, t, rhs)) + return 1; if (!emit_array_lit_bytes(out, c, t, rhs, 0)) return 0; fprintf(out, "%s %s(SB),\"", directive, mod_mangle_value(c, name, module)); @@ -11704,9 +11802,53 @@ let_pre_intern(Cg *c, Node *file) if (file == NULL) return; for (Node *d = file->list; d; d = d->next) { if (d->kind != N_LET) continue; - if (let_emit_size(d->type) != (int)ty_str->size) continue; Node *r = d->rhs; while (r != NULL && r->kind == N_CAST) r = r->lhs; + /* #18: `let xs: [N]str = […];` — pre-intern each element's + * strlit in element order (then repeat-fill) so emit_strarray_ + * data's DATAR rows find an _S_ rodata row. Must match that + * helper's interning order exactly to keep labels stable. */ + Type *u = type_unwrap(d->type); + if (u != NULL && u->kind == TY_ARRAY + && r != NULL && r->kind == N_ARRLIT) { + Type *eu = (u->sub && u->sub->kind == TY_NAMED) + ? u->sub->under : u->sub; + if (eu != NULL && eu->kind == TY_STR) { + int alen = (int)u->alen; + int cnt = 0; + Node *last_ev = NULL; + int repeat = 0; + for (Node *e = r->list; e && cnt < 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_STRLIT) + break; + if (ev->strlen > 0) + (void)intern_strlit(c, ev->str, + ev->strlen); + last_ev = ev; + cnt++; + } + if (repeat && last_ev != NULL + && last_ev->strlen > 0) { + while (cnt < alen) { + (void)intern_strlit(c, + last_ev->str, + last_ev->strlen); + cnt++; + } + } + continue; + } + } + if (let_emit_size(d->type) != (int)ty_str->size) continue; if (r == NULL || r->kind != N_STRLIT) continue; if (r->strlen == 0) continue; (void)intern_strlit(c, r->str, r->strlen); diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index e7d492a2..517ec133 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -32885,20 +32885,82 @@ export fn letpreintern(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_LET) { - let sz: i32 = letemitsize(c, d); - // #43: route the str-let gate through primtypesize so - // #1 doesn't desync this with emitletdataw's matching - // `sz == primtypesize("str"): i32` strlit-init branch. - if (sz == primtypesize("str"): i32) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + // #18: `let xs: [N]str = […];` — pre-intern each + // element's strlit in element order (then repeat-fill) + // so emitstrarraydata's DATAR rows find an _S_ rodata + // row. Must match that helper's interning order exactly + // to keep labels stable. + let handled: bool = false; + if (d.lhs != nil && r != nil) { + if (d.lhs.kind == nkind.N_TARRAY + && r.kind == nkind.N_ARRLIT) { + let au: *tinfo = d.lhs.type_: *tinfo; + for (au != nil && au.kind == tykind.TY_NAMED) { + au = au.under; + }; + let eu: *tinfo = nil; + if (au != nil) { + if (au.kind == tykind.TY_ARRAY) { + eu = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { + eu = eu.under; + }; + }; + }; + if (eu != nil && eu.kind == tykind.TY_STR) { + handled = true; + let alen: i32 = au.alen: i32; + let cnt: i32 = 0; + let last_ev: *node = nil; + let repeat: bool = false; + let e: *node = r.list; + for (e != nil && cnt < 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) { break; }; + if (ev.kind != nkind.N_STRLIT) { break; }; + if (ev.str.len > 0) { + internstrlit(c, ev.str); + }; + last_ev = ev; + cnt += 1; + e = e.next; + }; + if (repeat && last_ev != nil) { + if (last_ev.str.len > 0) { + for (cnt < alen) { + internstrlit(c, last_ev.str); + cnt += 1; + }; + }; + }; + }; }; - if (r != nil) { - if (r.kind == nkind.N_STRLIT) { - if (r.str.len > 0) { - internstrlit(c, r.str); + }; + if (!handled) { + let sz: i32 = letemitsize(c, d); + // #43: route the str-let gate through primtypesize so + // #1 doesn't desync this with emitletdataw's matching + // `sz == primtypesize("str"): i32` strlit-init branch. + if (sz == primtypesize("str"): i32) { + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { + internstrlit(c, r.str); + }; }; }; }; @@ -33440,6 +33502,126 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, return true; }; +// emitstrarraydata — module-level `let xs: [N]str = […];` static init +// (#18). Mirror of cstage emit_strarray_data. A str element carries a +// ptr→rodata relocation, not just bytes, so it can't ride +// emitarraylitbytes (bytes-only); instead apply the scalar-str-global +// pattern (DATAW header with a zero ptr placeholder + inline LE len, +// then a per-element DATAR) at offset idx*esz. Each strlit was pre- +// interned by letpreintern so its _S_ rodata row exists before this +// row's DATAR references it. Scoped to DATAW (writable `let`): A_DATAR +// requires a DATAW holder, so a read-only `def [N]str` can't carry the +// relocs (#18 follow-up). Returns false when the element type isn't str. +fn emitstrarraydata(c: *cgen, directive: str, name: str, module: 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; }; + let eu: *tinfo = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + if (eu == nil) { return false; }; + if (eu.kind != tykind.TY_STR) { return false; }; + if (!streq(directive, "DATAW")) { return false; }; + let esz: i32 = au.sub.size: i32; + let alen: i32 = au.alen: i32; + + let last_ev: *node = nil; + let repeat: bool = false; + let cnt: i32 = 0; + let e: *node = rhs.list; + for (e != nil && cnt < 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 (ev.kind != nkind.N_STRLIT) { return false; }; + last_ev = ev; + cnt += 1; + e = e.next; + }; + + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline("(SB),\""); + let idx: i32 = 0; + e = 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; }; + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + let v: u64 = ev.str.len: u64; + i = 0; + for (i < 8) { + emitdatawbyte((v & 255u64): u8); + v = v >> 8u64; + i += 1; + }; + i = 16; + for (i < esz) { emitdatawbyte(0u8); i += 1; }; + idx += 1; + e = e.next; + }; + for (idx < alen) { + let v: u64 = 0u64; + if (repeat && last_ev != nil) { v = last_ev.str.len: u64; }; + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + i = 0; + for (i < 8) { + emitdatawbyte((v & 255u64): u8); + v = v >> 8u64; + i += 1; + }; + i = 16; + for (i < esz) { emitdatawbyte(0u8); i += 1; }; + idx += 1; + }; + emitline("\"\n"); + + idx = 0; + e = 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.str.len > 0) { + let lab: str = internstrlit(c, ev.str); + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+"); + emitint((idx * esz): i64); + emitline("(SB),"); + emitbytes( lab.ptr, lab.len: u64); + emitline("(SB)\n"); + }; + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat && last_ev != nil && last_ev.str.len > 0) { + let lab: str = internstrlit(c, last_ev.str); + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+"); + emitint((idx * esz): i64); + emitline("(SB),"); + emitbytes( lab.ptr, lab.len: u64); + emitline("(SB)\n"); + }; + 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;` @@ -33465,6 +33647,11 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, emitline("\"\n"); return true; }; + // str-element arrays carry per-element ptr relocations — handled + // by the dedicated DATAW+DATAR helper (#18). + if (emitstrarraydata(c, directive, name, module, arrt, rhs)) { + return true; + }; if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; }; emitline(directive); emitline(" "); diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 0e0725c2..da9b466b 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -1220,20 +1220,82 @@ export fn letpreintern(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_LET) { - let sz: i32 = letemitsize(c, d); - // #43: route the str-let gate through primtypesize so - // #1 doesn't desync this with emitletdataw's matching - // `sz == primtypesize("str"): i32` strlit-init branch. - if (sz == primtypesize("str"): i32) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + // #18: `let xs: [N]str = […];` — pre-intern each + // element's strlit in element order (then repeat-fill) + // so emitstrarraydata's DATAR rows find an _S_ rodata + // row. Must match that helper's interning order exactly + // to keep labels stable. + let handled: bool = false; + if (d.lhs != nil && r != nil) { + if (d.lhs.kind == nkind.N_TARRAY + && r.kind == nkind.N_ARRLIT) { + let au: *tinfo = d.lhs.type_: *tinfo; + for (au != nil && au.kind == tykind.TY_NAMED) { + au = au.under; + }; + let eu: *tinfo = nil; + if (au != nil) { + if (au.kind == tykind.TY_ARRAY) { + eu = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { + eu = eu.under; + }; + }; + }; + if (eu != nil && eu.kind == tykind.TY_STR) { + handled = true; + let alen: i32 = au.alen: i32; + let cnt: i32 = 0; + let last_ev: *node = nil; + let repeat: bool = false; + let e: *node = r.list; + for (e != nil && cnt < 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) { break; }; + if (ev.kind != nkind.N_STRLIT) { break; }; + if (ev.str.len > 0) { + internstrlit(c, ev.str); + }; + last_ev = ev; + cnt += 1; + e = e.next; + }; + if (repeat && last_ev != nil) { + if (last_ev.str.len > 0) { + for (cnt < alen) { + internstrlit(c, last_ev.str); + cnt += 1; + }; + }; + }; + }; }; - if (r != nil) { - if (r.kind == nkind.N_STRLIT) { - if (r.str.len > 0) { - internstrlit(c, r.str); + }; + if (!handled) { + let sz: i32 = letemitsize(c, d); + // #43: route the str-let gate through primtypesize so + // #1 doesn't desync this with emitletdataw's matching + // `sz == primtypesize("str"): i32` strlit-init branch. + if (sz == primtypesize("str"): i32) { + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { + internstrlit(c, r.str); + }; }; }; }; @@ -1775,6 +1837,126 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, return true; }; +// emitstrarraydata — module-level `let xs: [N]str = […];` static init +// (#18). Mirror of cstage emit_strarray_data. A str element carries a +// ptr→rodata relocation, not just bytes, so it can't ride +// emitarraylitbytes (bytes-only); instead apply the scalar-str-global +// pattern (DATAW header with a zero ptr placeholder + inline LE len, +// then a per-element DATAR) at offset idx*esz. Each strlit was pre- +// interned by letpreintern so its _S_ rodata row exists before this +// row's DATAR references it. Scoped to DATAW (writable `let`): A_DATAR +// requires a DATAW holder, so a read-only `def [N]str` can't carry the +// relocs (#18 follow-up). Returns false when the element type isn't str. +fn emitstrarraydata(c: *cgen, directive: str, name: str, module: 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; }; + let eu: *tinfo = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + if (eu == nil) { return false; }; + if (eu.kind != tykind.TY_STR) { return false; }; + if (!streq(directive, "DATAW")) { return false; }; + let esz: i32 = au.sub.size: i32; + let alen: i32 = au.alen: i32; + + let last_ev: *node = nil; + let repeat: bool = false; + let cnt: i32 = 0; + let e: *node = rhs.list; + for (e != nil && cnt < 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 (ev.kind != nkind.N_STRLIT) { return false; }; + last_ev = ev; + cnt += 1; + e = e.next; + }; + + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline("(SB),\""); + let idx: i32 = 0; + e = 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; }; + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + let v: u64 = ev.str.len: u64; + i = 0; + for (i < 8) { + emitdatawbyte((v & 255u64): u8); + v = v >> 8u64; + i += 1; + }; + i = 16; + for (i < esz) { emitdatawbyte(0u8); i += 1; }; + idx += 1; + e = e.next; + }; + for (idx < alen) { + let v: u64 = 0u64; + if (repeat && last_ev != nil) { v = last_ev.str.len: u64; }; + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + i = 0; + for (i < 8) { + emitdatawbyte((v & 255u64): u8); + v = v >> 8u64; + i += 1; + }; + i = 16; + for (i < esz) { emitdatawbyte(0u8); i += 1; }; + idx += 1; + }; + emitline("\"\n"); + + idx = 0; + e = 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.str.len > 0) { + let lab: str = internstrlit(c, ev.str); + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+"); + emitint((idx * esz): i64); + emitline("(SB),"); + emitbytes( lab.ptr, lab.len: u64); + emitline("(SB)\n"); + }; + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat && last_ev != nil && last_ev.str.len > 0) { + let lab: str = internstrlit(c, last_ev.str); + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+"); + emitint((idx * esz): i64); + emitline("(SB),"); + emitbytes( lab.ptr, lab.len: u64); + emitline("(SB)\n"); + }; + 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;` @@ -1800,6 +1982,11 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, emitline("\"\n"); return true; }; + // str-element arrays carry per-element ptr relocations — handled + // by the dedicated DATAW+DATAR helper (#18). + if (emitstrarraydata(c, directive, name, module, arrt, rhs)) { + return true; + }; if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; }; emitline(directive); emitline(" "); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 27c88ada..b1143b88 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -32885,20 +32885,82 @@ export fn letpreintern(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_LET) { - let sz: i32 = letemitsize(c, d); - // #43: route the str-let gate through primtypesize so - // #1 doesn't desync this with emitletdataw's matching - // `sz == primtypesize("str"): i32` strlit-init branch. - if (sz == primtypesize("str"): i32) { - let r: *node = d.rhs; - for (r != nil) { - if (r.kind != nkind.N_CAST) { break; }; - r = r.lhs; + let r: *node = d.rhs; + for (r != nil) { + if (r.kind != nkind.N_CAST) { break; }; + r = r.lhs; + }; + // #18: `let xs: [N]str = […];` — pre-intern each + // element's strlit in element order (then repeat-fill) + // so emitstrarraydata's DATAR rows find an _S_ rodata + // row. Must match that helper's interning order exactly + // to keep labels stable. + let handled: bool = false; + if (d.lhs != nil && r != nil) { + if (d.lhs.kind == nkind.N_TARRAY + && r.kind == nkind.N_ARRLIT) { + let au: *tinfo = d.lhs.type_: *tinfo; + for (au != nil && au.kind == tykind.TY_NAMED) { + au = au.under; + }; + let eu: *tinfo = nil; + if (au != nil) { + if (au.kind == tykind.TY_ARRAY) { + eu = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { + eu = eu.under; + }; + }; + }; + if (eu != nil && eu.kind == tykind.TY_STR) { + handled = true; + let alen: i32 = au.alen: i32; + let cnt: i32 = 0; + let last_ev: *node = nil; + let repeat: bool = false; + let e: *node = r.list; + for (e != nil && cnt < 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) { break; }; + if (ev.kind != nkind.N_STRLIT) { break; }; + if (ev.str.len > 0) { + internstrlit(c, ev.str); + }; + last_ev = ev; + cnt += 1; + e = e.next; + }; + if (repeat && last_ev != nil) { + if (last_ev.str.len > 0) { + for (cnt < alen) { + internstrlit(c, last_ev.str); + cnt += 1; + }; + }; + }; + }; }; - if (r != nil) { - if (r.kind == nkind.N_STRLIT) { - if (r.str.len > 0) { - internstrlit(c, r.str); + }; + if (!handled) { + let sz: i32 = letemitsize(c, d); + // #43: route the str-let gate through primtypesize so + // #1 doesn't desync this with emitletdataw's matching + // `sz == primtypesize("str"): i32` strlit-init branch. + if (sz == primtypesize("str"): i32) { + if (r != nil) { + if (r.kind == nkind.N_STRLIT) { + if (r.str.len > 0) { + internstrlit(c, r.str); + }; }; }; }; @@ -33440,6 +33502,126 @@ fn emitarraylitbytes(c: *cgen, arrt: *tinfo, rhs: *node, return true; }; +// emitstrarraydata — module-level `let xs: [N]str = […];` static init +// (#18). Mirror of cstage emit_strarray_data. A str element carries a +// ptr→rodata relocation, not just bytes, so it can't ride +// emitarraylitbytes (bytes-only); instead apply the scalar-str-global +// pattern (DATAW header with a zero ptr placeholder + inline LE len, +// then a per-element DATAR) at offset idx*esz. Each strlit was pre- +// interned by letpreintern so its _S_ rodata row exists before this +// row's DATAR references it. Scoped to DATAW (writable `let`): A_DATAR +// requires a DATAW holder, so a read-only `def [N]str` can't carry the +// relocs (#18 follow-up). Returns false when the element type isn't str. +fn emitstrarraydata(c: *cgen, directive: str, name: str, module: 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; }; + let eu: *tinfo = au.sub; + for (eu != nil && eu.kind == tykind.TY_NAMED) { eu = eu.under; }; + if (eu == nil) { return false; }; + if (eu.kind != tykind.TY_STR) { return false; }; + if (!streq(directive, "DATAW")) { return false; }; + let esz: i32 = au.sub.size: i32; + let alen: i32 = au.alen: i32; + + let last_ev: *node = nil; + let repeat: bool = false; + let cnt: i32 = 0; + let e: *node = rhs.list; + for (e != nil && cnt < 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 (ev.kind != nkind.N_STRLIT) { return false; }; + last_ev = ev; + cnt += 1; + e = e.next; + }; + + emitline("DATAW "); + emitsymnamehint(c, name, module); + emitline("(SB),\""); + let idx: i32 = 0; + e = 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; }; + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + let v: u64 = ev.str.len: u64; + i = 0; + for (i < 8) { + emitdatawbyte((v & 255u64): u8); + v = v >> 8u64; + i += 1; + }; + i = 16; + for (i < esz) { emitdatawbyte(0u8); i += 1; }; + idx += 1; + e = e.next; + }; + for (idx < alen) { + let v: u64 = 0u64; + if (repeat && last_ev != nil) { v = last_ev.str.len: u64; }; + let i: i32 = 0; + for (i < 8) { emitdatawbyte(0u8); i += 1; }; + i = 0; + for (i < 8) { + emitdatawbyte((v & 255u64): u8); + v = v >> 8u64; + i += 1; + }; + i = 16; + for (i < esz) { emitdatawbyte(0u8); i += 1; }; + idx += 1; + }; + emitline("\"\n"); + + idx = 0; + e = 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.str.len > 0) { + let lab: str = internstrlit(c, ev.str); + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+"); + emitint((idx * esz): i64); + emitline("(SB),"); + emitbytes( lab.ptr, lab.len: u64); + emitline("(SB)\n"); + }; + idx += 1; + e = e.next; + }; + for (idx < alen) { + if (repeat && last_ev != nil && last_ev.str.len > 0) { + let lab: str = internstrlit(c, last_ev.str); + emitline("DATAR "); + emitsymnamehint(c, name, module); + emitline("+"); + emitint((idx * esz): i64); + emitline("(SB),"); + emitbytes( lab.ptr, lab.len: u64); + emitline("(SB)\n"); + }; + 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;` @@ -33465,6 +33647,11 @@ fn emitarraydata(c: *cgen, directive: str, name: str, module: str, emitline("\"\n"); return true; }; + // str-element arrays carry per-element ptr relocations — handled + // by the dedicated DATAW+DATAR helper (#18). + if (emitstrarraydata(c, directive, name, module, arrt, rhs)) { + return true; + }; if (!emitarraylitbytes(c, arrt, rhs, 0)) { return false; }; emitline(directive); emitline(" "); diff --git a/test/wcc/919_strarray_static_run.c b/test/wcc/919_strarray_static_run.c new file mode 100644 index 00000000..1254948c --- /dev/null +++ b/test/wcc/919_strarray_static_run.c @@ -0,0 +1,211 @@ +/* + * 919_strarray_static_run — BUG #18. Runtime + cs==ww byte-id net for a + * module-level `let xs: [N]str = ["a","b",…];` static table. + * + * THE BUG (BOTH stages dropped it — shared-logic gap, not rule-10): + * A module-level [N]str static init emitted NO .data at all. The + * per-element str header carries a ptr→rodata relocation (not just + * bytes), so it can't ride emit_array_lit_bytes (byte-only): the str- + * element case fell through to fold_int_literal, returned 0, and + * emit_lets zero-init'd / skipped — leaving `main.` undefined. + * `w6l: undefined reference` at link. Function-LOCAL [N]str worked + * (runtime element stores); only the module-level STATIC DATA form was + * broken. + * + * THE FIX (#18): emit_strarray_data / emitstrarraydata apply the scalar- + * str-global pattern (DATAW header with a zero ptr placeholder + inline + * LE len, then a per-element `DATAR sym+idx*esz(SB),_S_n(SB)`) at each + * element offset. let_pre_intern / letpreintern pre-intern each element + * strlit so the _S_ rodata rows precede the DATAR references. Scoped to + * the DATAW (`let`) directive — A_DATAR requires a DATAW holder. + * + * EACH ROW CARRIES BOTH DIMENSIONS (801 model): + * (a) cstage `ww build` + run, asserting the exit — proves the table's + * .len bytes are correct AND each element's .ptr relocation + * resolves to the right rodata label (the rows deref a NON-zero + * element's first byte, so a dropped/wrong reloc gives a wrong + * char — built-in negative control). + * (b) w6c vs w6c_ww `.s` cmp — FAILS if the stages diverge. + * + * GATE POLARITY: must stay GREEN. A wrong exit means the static table + * relocations regressed; a byte-id FAIL means the stages diverged. + * + * NB: the rows read elements via the `.ptr`/`.len` pseudo-fields, NOT + * the `len()` builtin — `len(xs[i])` on an indexed str element is a + * separate pre-existing read-side miscompile (returns the ptr; filed + * alongside #18), orthogonal to the emission fix under test here. + */ +#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[] = { + /* .len bytes: 2+3+1 == 6. A dropped table would fail to link. */ + { "strtab_len", + "package main;\n" + "let t: [3]str = [\"ab\",\"cde\",\"f\"];\n" + "export fn main() i32 = { return (t[0].len + t[1].len + t[2].len): i32; };\n", + 6 }, + /* element-2 .ptr reloc: *(t[2].ptr) == 'C' (67), NOT 'A' — proves + * the +2*esz relocation resolves to "C", not element 0. */ + { "strtab_ptr_elem2", + "package main;\n" + "let t: [3]str = [\"A\",\"B\",\"C\"];\n" + "export fn main() i32 = { let p: *u8 = t[2].ptr; return (*p): i32; };\n", + 67 }, + /* runtime-indexed .ptr: i=1, *(t[i].ptr) == 'y' (121) — the + * LEAQ tab(SB)+i*esz addressing reaches the right element's reloc. */ + { "strtab_ptr_varindex", + "package main;\n" + "let t: [3]str = [\"xx\",\"yyy\",\"z\"];\n" + "export fn main() i32 = { let i: i32 = 1; let p: *u8 = t[i].ptr; " + "return (*p): i32; };\n", + 121 }, + /* empty-element (no reloc, len 0) followed by a real element: t[0] + * len==0, *(t[1].ptr) == 'Z' (90) — the empty slot must not shift + * the following element's reloc offset. */ + { "strtab_empty_then_ptr", + "package main;\n" + "let t: [2]str = [\"\",\"Z\"];\n" + "export fn main() i32 = { if (t[0].len != 0) { return 88; }; " + "let p: *u8 = t[1].ptr; return (*p): i32; };\n", + 90 }, + /* repeat suffix `[X, Y...]` — the trailing `...` fills the rest of + * [3]str with the last explicit element ("Y"). t[2] is a repeat- + * filled slot: *(t[2].ptr) == 'Y' (89), NOT 'X' — proves the +48 + * fill row's DATAR resolves to last_ev ("Y"), exercising the + * repeat branch (its own len-fill + DATAR loop) the other rows skip. */ + { "strtab_repeat_suffix", + "package main;\n" + "let t: [3]str = [\"X\",\"Y\"...];\n" + "export fn main() i32 = { let p: *u8 = t[2].ptr; return (*p): i32; };\n", + 89 }, + { 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, "strarray_static: w6c_ww missing — cannot run " + "the cs==ww byte-id gate\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/wwsas_%d_%d.ww", getpid(), i); + FILE *f = fopen(src, "wb"); + if (f == NULL) { fail++; continue; } + fputs(rows[i].src, f); + fclose(f); + + /* (a) cstage build + run in a scratch dir. */ + char tmpdir[64]; + snprintf(tmpdir, sizeof tmpdir, "/tmp/wwsas_%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); + + /* (b) cs==ww byte-id gate. */ + char cs_s[64], ws_s[64]; + snprintf(cs_s, sizeof cs_s, "/tmp/wwsas_%d_%d_cs.s", getpid(), i); + snprintf(ws_s, sizeof ws_s, "/tmp/wwsas_%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 strarray static-init tests failed\n", + fail, n); + return 1; + } + printf("strarray_static: %d/%d ok (cstage run + cs==ww byte-id)\n", + n, n); + return 0; +}