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:
353
cmd/w6c/cgen.c
353
cmd/w6c/cgen.c
@@ -711,6 +711,17 @@ struct DefStruct {
|
||||
};
|
||||
static DefStruct *defstructs;
|
||||
|
||||
/* #129 A.3: array-typed defs now have DATA storage and need the same
|
||||
* LEAQ name(SB) + indexed-load shape as array-typed lets at cgindex
|
||||
* and N_DOT base-resolution sites. Mirrors DefStruct (A.2). */
|
||||
typedef struct DefArray DefArray;
|
||||
struct DefArray {
|
||||
const char *name;
|
||||
Type *type;
|
||||
DefArray *next;
|
||||
};
|
||||
static DefArray *defarrays;
|
||||
|
||||
/* Slot size for a top-level `let` of type t, or 0 if the type isn't
|
||||
* supported as a writable global yet. Tagged unions are deferred.
|
||||
* enums route through their storage type.
|
||||
@@ -914,6 +925,7 @@ let_collect(Cg *c, Node *file)
|
||||
{
|
||||
letvars = NULL;
|
||||
defstructs = NULL;
|
||||
defarrays = NULL;
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind == N_LET) {
|
||||
@@ -927,18 +939,32 @@ let_collect(Cg *c, Node *file)
|
||||
continue;
|
||||
}
|
||||
if (d->kind == N_DEF) {
|
||||
if (d->str == NULL || d->str[0] == '\0') continue;
|
||||
/* #129 A.2: struct-typed defs now have DATA storage
|
||||
* (emit_defs struct arm); register them so the N_DOT
|
||||
* struct-let LEAQ-and-offset shape widens to cover
|
||||
* them too. Other def kinds (int / float / str)
|
||||
* stay on their existing load paths. */
|
||||
if (d->str == NULL || d->str[0] == '\0') continue;
|
||||
if (!let_isstruct(d->type)) continue;
|
||||
DefStruct *ds = amalloc(c->a, sizeof *ds);
|
||||
ds->name = d->str;
|
||||
ds->type = d->type;
|
||||
ds->next = defstructs;
|
||||
defstructs = ds;
|
||||
if (let_isstruct(d->type)) {
|
||||
DefStruct *ds = amalloc(c->a, sizeof *ds);
|
||||
ds->name = d->str;
|
||||
ds->type = d->type;
|
||||
ds->next = defstructs;
|
||||
defstructs = ds;
|
||||
continue;
|
||||
}
|
||||
/* #129 A.3: array-typed defs now have DATA storage
|
||||
* (emit_defs array arm); register them so cgindex's
|
||||
* `let_islet`-gated LEAQ name(SB) base-load widens
|
||||
* to defs too (LOAD-side twin of the struct-def
|
||||
* registry). */
|
||||
if (let_isarray(d->type)) {
|
||||
DefArray *da = amalloc(c->a, sizeof *da);
|
||||
da->name = d->str;
|
||||
da->type = d->type;
|
||||
da->next = defarrays;
|
||||
defarrays = da;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -952,6 +978,15 @@ def_isstructdef(const char *name)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
def_isarraydef(const char *name)
|
||||
{
|
||||
if (name == NULL) return 0;
|
||||
for (DefArray *da = defarrays; da; da = da->next)
|
||||
if (strcmp(da->name, name) == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
let_islet(const char *name)
|
||||
{
|
||||
@@ -6797,7 +6832,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
|
||||
if (n->lhs->kind == N_IDENT && u) {
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
int isglobal = (off == 0) && let_islet(n->lhs->str);
|
||||
/* #129 A.3: array-typed defs now have DATA storage; the
|
||||
* LEAQ name(SB) base-load must fire for them too, not
|
||||
* just let_islet. Parallel to A.2's def_isstructdef
|
||||
* gate at the N_DOT direct-struct-ident arm. */
|
||||
int isglobal = (off == 0) && (let_islet(n->lhs->str)
|
||||
|| def_isarraydef(n->lhs->str));
|
||||
cgexpr(c, n->rhs, locals); /* idx → AX */
|
||||
if (esz > 1) {
|
||||
ins2(c, A_MOVQ, aimm(esz), areg(D_CX));
|
||||
@@ -8523,6 +8563,12 @@ emit_floatlit_data(FILE *out, Cg *c, const char *directive,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Forward declaration: emit_struct_lit_bytes recurses into
|
||||
* emit_array_lit_bytes for nested array fields (#129 A.3 closes the
|
||||
* A.2 shape-15 park). Defined further down. */
|
||||
static int emit_array_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs,
|
||||
int emit_phase);
|
||||
|
||||
/* emit_struct_lit_bytes — emit the byte sequence for a struct-typed
|
||||
* top-level let/def whose rhs is an N_STRUCTLIT (or NULL for bare
|
||||
* no-rhs). Walks Tfield list in declaration order, zero-fills padding
|
||||
@@ -8585,6 +8631,24 @@ emit_struct_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, u64 base)
|
||||
pos = fstart + (u64)fsz;
|
||||
continue;
|
||||
}
|
||||
/* #129 A.3: array-typed field with N_ARRLIT rhs (the shape
|
||||
* parked in A.2). Calls emit_array_lit_bytes which dispatches
|
||||
* by element kind (int/float/struct). Returns 0 if the rhs
|
||||
* shape can't reduce — fatal here per rule-7 since the field
|
||||
* is declared array-typed and a non-reducible inner rhs is
|
||||
* a real bug surface, not a fall-through. */
|
||||
if (fu && fu->kind == TY_ARRAY) {
|
||||
if (vr == NULL || vr->kind != N_ARRLIT)
|
||||
fatal("emit_struct_lit_bytes: array field "
|
||||
"'%s' rhs is not N_ARRLIT (#129 A.3)",
|
||||
f->name ? f->name : "?");
|
||||
if (!emit_array_lit_bytes(out, c, f->type, vr, 1))
|
||||
fatal("emit_struct_lit_bytes: array field "
|
||||
"'%s' rhs has non-reducible elements "
|
||||
"(#129 A.3)", f->name ? f->name : "?");
|
||||
pos = fstart + (u64)fsz;
|
||||
continue;
|
||||
}
|
||||
if (type_isfloat(f->type)) {
|
||||
int isf32 = type_isf32(f->type);
|
||||
u64 fv = 0;
|
||||
@@ -8654,6 +8718,210 @@ emit_struct_data(FILE *out, Cg *c, const char *directive,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* emit_array_lit_bytes — emit alen * esz bytes for an [N]T top-level
|
||||
* let/def with N_ARRLIT rhs. Per-element dispatch:
|
||||
* - int element (covers bool/rune/typed-int/N_UN-int): fold_int_literal
|
||||
* per element, emit LE bytes. Existing pre-#129-A.3 emit_lets array
|
||||
* arm logic preserved byte-for-byte so the bootstrap consumers in
|
||||
* lib/os, lib/bufio, lib/strings, lib/encoding/utf8, lib/strconv/
|
||||
* stof_data don't shift.
|
||||
* - float element (f32/f64): peel N_CAST/N_UN(±), bitcast magnitude
|
||||
* via union (mirrors emit_floatlit_data), sign-XOR top byte of each
|
||||
* element inline. NO 2^63 immediate.
|
||||
* - struct element: per element call emit_struct_lit_bytes (#129 A.2
|
||||
* helper).
|
||||
* - other element kinds (str/slice/ptr-with-address/nested-array):
|
||||
* return 0 — caller falls through to zero-init (str/slice accepts
|
||||
* no-rhs already).
|
||||
*
|
||||
* Trailing `...` repeat marker fills remaining slots with the last
|
||||
* value (mirrors the scalar repeat path). Returns 1 on emit, 0 if the
|
||||
* rhs shape can't reduce to a foldable literal — caller MUST then
|
||||
* fall back to zero-init / skip path; the caller opens the DATA/DATAW
|
||||
* directive AFTER a successful validate-only call. Two-call pattern
|
||||
* keeps emit-on-failure from emitting partial bytes.
|
||||
*
|
||||
* `emit_phase = 0` runs validate-only (returns 1 if ok); `emit_phase
|
||||
* = 1` actually emits. */
|
||||
static int
|
||||
emit_array_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, int emit_phase)
|
||||
{
|
||||
Type *u = type_unwrap(t);
|
||||
if (u == NULL || u->kind != TY_ARRAY) return 0;
|
||||
Type *etype = u->sub;
|
||||
Type *eu = (etype && etype->kind == TY_NAMED) ? etype->under : etype;
|
||||
int esz = etype ? (int)etype->size : 1;
|
||||
int alen = (int)u->alen;
|
||||
|
||||
if (eu && eu->kind == TY_STRUCT) {
|
||||
/* Validate: every element must be N_STRUCTLIT (after N_CAST
|
||||
* peel). */
|
||||
int idx = 0;
|
||||
Node *last_ev = NULL;
|
||||
int repeat = 0;
|
||||
for (Node *e = rhs->list; e && idx < alen; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0) {
|
||||
repeat = 1;
|
||||
break;
|
||||
}
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
if (ev == NULL || ev->kind != N_STRUCTLIT) return 0;
|
||||
last_ev = ev;
|
||||
idx++;
|
||||
}
|
||||
if (!emit_phase) return 1;
|
||||
idx = 0;
|
||||
repeat = 0;
|
||||
for (Node *e = rhs->list; e && idx < alen; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0) {
|
||||
repeat = 1;
|
||||
break;
|
||||
}
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
emit_struct_lit_bytes(out, c, etype, ev, 0);
|
||||
idx++;
|
||||
}
|
||||
while (idx < alen) {
|
||||
if (repeat && last_ev != NULL)
|
||||
emit_struct_lit_bytes(out, c, etype, last_ev, 0);
|
||||
else
|
||||
for (int b = 0; b < esz; b++)
|
||||
emit_data_byte(out, 0);
|
||||
idx++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (type_isfloat(etype)) {
|
||||
int isf32 = type_isf32(etype);
|
||||
/* Validate: every element must be N_FLOATLIT (after N_CAST
|
||||
* + optional N_UN(±) peel). */
|
||||
int idx = 0;
|
||||
for (Node *e = rhs->list; e && idx < alen; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0) break;
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
if (ev != NULL && ev->kind == N_UN
|
||||
&& (ev->op == TK_MINUS || ev->op == TK_PLUS)) {
|
||||
ev = ev->lhs;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
}
|
||||
if (ev == NULL || ev->kind != N_FLOATLIT) return 0;
|
||||
idx++;
|
||||
}
|
||||
if (!emit_phase) return 1;
|
||||
idx = 0;
|
||||
u64 last_bits = 0;
|
||||
int last_neg = 0;
|
||||
int repeat = 0;
|
||||
for (Node *e = rhs->list; e && idx < alen; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0) {
|
||||
repeat = 1;
|
||||
break;
|
||||
}
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
int neg = 0;
|
||||
if (ev != NULL && ev->kind == N_UN
|
||||
&& (ev->op == TK_MINUS || ev->op == TK_PLUS)) {
|
||||
if (ev->op == TK_MINUS) neg = 1;
|
||||
ev = ev->lhs;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
}
|
||||
u64 bits = 0;
|
||||
if (isf32) {
|
||||
union { float f; u32 u; } x;
|
||||
x.f = (float)ev->fval;
|
||||
bits = (u64)x.u;
|
||||
} else {
|
||||
union { double d; u64 u; } x;
|
||||
x.d = ev->fval;
|
||||
bits = x.u;
|
||||
}
|
||||
for (int b = 0; b < esz; b++) {
|
||||
u8 byt = (u8)((bits >> (b * 8)) & 0xff);
|
||||
if (neg && b == esz - 1) byt = (u8)(byt ^ 0x80);
|
||||
emit_data_byte(out, byt);
|
||||
}
|
||||
last_bits = bits;
|
||||
last_neg = neg;
|
||||
idx++;
|
||||
}
|
||||
while (idx < alen) {
|
||||
if (repeat) {
|
||||
for (int b = 0; b < esz; b++) {
|
||||
u8 byt = (u8)((last_bits >> (b * 8)) & 0xff);
|
||||
if (last_neg && b == esz - 1)
|
||||
byt = (u8)(byt ^ 0x80);
|
||||
emit_data_byte(out, byt);
|
||||
}
|
||||
} else {
|
||||
for (int b = 0; b < esz; b++)
|
||||
emit_data_byte(out, 0);
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Int-element path — preserved BYTE-FOR-BYTE from the pre-A.3
|
||||
* emit_lets in-place array arm so the bootstrap consumers (u8 /
|
||||
* i8 / u16 arrays in lib/os, lib/bufio, lib/strings, lib/
|
||||
* encoding/utf8, lib/strconv/stof_data) don't shift. */
|
||||
u64 *vals = amalloc(c->a, sizeof(u64) * (size_t)alen);
|
||||
int idx = 0;
|
||||
int ok = 1;
|
||||
u64 last = 0;
|
||||
int repeat = 0;
|
||||
for (Node *e = rhs->list; e && idx < alen; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0) {
|
||||
repeat = 1;
|
||||
break;
|
||||
}
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
if (ev == NULL) { ok = 0; break; }
|
||||
if (!fold_int_literal(ev, &last)) { ok = 0; break; }
|
||||
vals[idx++] = last;
|
||||
}
|
||||
if (!ok) return 0;
|
||||
if (!emit_phase) return 1;
|
||||
if (repeat) {
|
||||
while (idx < alen) vals[idx++] = last;
|
||||
} else {
|
||||
while (idx < alen) vals[idx++] = 0;
|
||||
}
|
||||
for (int i = 0; i < alen; i++) {
|
||||
u64 v = vals[i];
|
||||
for (int b = 0; b < esz; b++)
|
||||
emit_data_byte(out, (u8)((v >> (b * 8)) & 0xff));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* emit_array_data — opens DATA/DATAW prefix on validate success, then
|
||||
* emits payload. Two-pass keeps emit-on-failure from emitting partial
|
||||
* bytes (would corrupt the asm if rhs reduces partway through). */
|
||||
static int
|
||||
emit_array_data(FILE *out, Cg *c, const char *directive,
|
||||
const char *name, Type *t, Node *rhs)
|
||||
{
|
||||
Type *u = type_unwrap(t);
|
||||
if (u == NULL || u->kind != TY_ARRAY) return 0;
|
||||
if (!emit_array_lit_bytes(out, c, t, rhs, 0)) return 0;
|
||||
fprintf(out, "%s %s(SB),\"", directive, mod_mangle(c, name));
|
||||
emit_array_lit_bytes(out, c, t, rhs, 1);
|
||||
fputs("\"\n", out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
emit_lets(Cg *c, FILE *out, Node *file)
|
||||
{
|
||||
@@ -8718,59 +8986,17 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
fprintf(out, "DATAR %s+0(SB),%s(SB)\n", sym, lab);
|
||||
continue;
|
||||
}
|
||||
/* Array literal init: `let xs: [N]T = [v0, v1, ...];`. Walk
|
||||
* elements in declaration order; each must reduce to an
|
||||
* integer literal (casts are stripped). The trailing `...`
|
||||
* repeat marker fills remaining slots with the last value.
|
||||
* Falls through to zero-init if any element isn't a
|
||||
* constant we can evaluate at emit time. */
|
||||
/* Array literal init: `let xs: [N]T = [v0, v1, ...];`. The
|
||||
* helper dispatches per element kind (int/float/struct).
|
||||
* Int-element path preserved BYTE-FOR-BYTE from pre-A.3 so
|
||||
* bootstrap consumers (lib/os, lib/bufio, lib/strings, lib/
|
||||
* encoding/utf8, lib/strconv/stof_data) don't shift. Float
|
||||
* + struct elements gain emit; ptr / nested-array fall
|
||||
* through to zero-init (existing path below). */
|
||||
if (r != NULL && r->kind == N_ARRLIT && let_isarray(d->type)) {
|
||||
Type *u = type_unwrap(d->type);
|
||||
int esz = (u && u->sub) ? (int)u->sub->size : 1;
|
||||
int alen = (u) ? (int)u->alen : 0;
|
||||
u64 *vals = amalloc(c->a, sizeof(u64) * (size_t)alen);
|
||||
int idx = 0;
|
||||
int ok = 1;
|
||||
u64 last = 0;
|
||||
int repeat = 0;
|
||||
for (Node *e = r->list; e && idx < alen; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str &&
|
||||
strcmp(e->str, "...") == 0) {
|
||||
repeat = 1;
|
||||
break;
|
||||
}
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
if (ev == NULL) { ok = 0; break; }
|
||||
/* Same helper as the scalar arm above —
|
||||
* fold_int_literal covers N_UN over a leaf
|
||||
* so signed-negative array elements (`-1i8`)
|
||||
* encode as their two's-complement bytes
|
||||
* once truncated to esz below. */
|
||||
if (!fold_int_literal(ev, &last)) {
|
||||
ok = 0;
|
||||
break;
|
||||
}
|
||||
vals[idx++] = last;
|
||||
}
|
||||
if (ok) {
|
||||
if (repeat) {
|
||||
while (idx < alen) vals[idx++] = last;
|
||||
} else {
|
||||
while (idx < alen) vals[idx++] = 0;
|
||||
}
|
||||
fprintf(out, "DATAW %s(SB),\"",
|
||||
mod_mangle(c, d->str));
|
||||
for (int i = 0; i < alen; i++) {
|
||||
u64 v = vals[i];
|
||||
for (int b = 0; b < esz; b++) {
|
||||
emit_data_byte(out,
|
||||
(u8)((v >> (b * 8)) & 0xff));
|
||||
}
|
||||
}
|
||||
fputs("\"\n", out);
|
||||
if (emit_array_data(out, c, "DATAW", d->str,
|
||||
d->type, r))
|
||||
continue;
|
||||
}
|
||||
/* fall through to zero-init */
|
||||
}
|
||||
/* Otherwise: zero-init. str accepts nil / ""; struct
|
||||
@@ -8846,6 +9072,15 @@ emit_defs(Cg *c, FILE *out, Node *file)
|
||||
d->str, d->type, d->rhs);
|
||||
continue;
|
||||
}
|
||||
/* #129 A.3: array-typed def with N_ARRLIT rhs. Parallel to
|
||||
* emit_lets's array arm; uses DATA (read-only). LOAD-side
|
||||
* widening at cgindex/cgdot resolves the def's address via
|
||||
* LEAQ name(SB). */
|
||||
if (let_isarray(d->type) && d->rhs->kind == N_ARRLIT) {
|
||||
(void)emit_array_data(out, c, "DATA",
|
||||
d->str, d->type, d->rhs);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(void)c;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user