wcc: struct-composite let/def DATA emit via SSoT helper (#129 A.2)
Extract emit_struct_data + emit_struct_lit_bytes helpers (both stages, mirrored) for module-level let/def with N_STRUCTLIT initializer. Walks Tfield linked-list in declaration order, zero-fills padding via per- field offset (rule 13, no hardcoded sizes), dispatches per field kind: integer via fold_int_literal, float via inline bitcast + sign-XOR byte- loop (A.1 shape, no INT64_MIN materialised — sibling #144), nested struct via recursion (#145 inner-field-name-leak gates the test row). Out-of-scope field kinds (str/slice/ptr/array) fatal loud per rule 7. LOAD-side widened symmetric to A.1 precedent: cstage cgexpr N_DOT direct-struct-ident + chained-N_DOT widened via new DefStruct registry (def_isstructdef populated in let_collect); wwstage cgdot direct-struct- global falls through to defvarstructinfo on letvarstructinfo nil (defent.dtnode field added, populated in collectdefs). Both stages materialise struct-def via LEAQ name(SB) same as struct-let. Pre-existing cstage scalar 8B short-circuit at emit_lets caused silent fold-fail-continue on 8B struct lits (`struct{i32,i32}`); gate now excludes let_isstruct so 8B struct lits route through emit_struct_data. Wwstage's `!issg` gate was already correct; symmetric ordering restored. Closes (all bootstrap-NEUTRAL pre-impl; γ-cleanup #40 first consumer): - emit_lets `is_struct continue` skip → struct lets emitted no DATA - emit_defs no struct arm → struct defs emitted no DATA - cstage cgexpr N_DOT for struct-def emitted MOVSXD (BP), AX (broken stack-frame read) - cstage emit_lets sz==8 short-circuit silently skipped 8B struct lits Test 918 (7 rows: let_int_struct / def_int_struct / let_float_field / def_float_field / let_empty_struct / let_int_struct_8b / let_norhs_ struct_regression) registered. Make test: 181/181 incl. 990-997 byte-id + combined_ww_fresh. Followups filed: - #145 (task #41) — nested struct-lit inner field-name leaks as extern - #42 — wwstage dotchainresolve missing defvarstructinfo lookup (A.2- scope-clean today; surfaces post-#145 nested-struct shapes) - A.3 (task #39) — array static-init audit (parks shape-4 array-in-struct) - γ-cleanup (task #40) — lib/math const-floatinfo re-fold, blocked-by A.2
This commit is contained in:
229
cmd/w6c/cgen.c
229
cmd/w6c/cgen.c
@@ -698,6 +698,19 @@ struct LetVar {
|
||||
};
|
||||
static LetVar *letvars;
|
||||
|
||||
/* #129 A.2: struct-typed defs that now have DATA storage need the
|
||||
* same LEAQ-and-field-offset N_DOT-load shape as struct-typed lets.
|
||||
* Tracked separately so let_islet's existing callers (which gate
|
||||
* scalar/float/str arms) don't pick up struct defs and re-route their
|
||||
* narrow-load logic. */
|
||||
typedef struct DefStruct DefStruct;
|
||||
struct DefStruct {
|
||||
const char *name;
|
||||
Type *type;
|
||||
DefStruct *next;
|
||||
};
|
||||
static DefStruct *defstructs;
|
||||
|
||||
/* 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.
|
||||
@@ -900,19 +913,45 @@ static void
|
||||
let_collect(Cg *c, Node *file)
|
||||
{
|
||||
letvars = NULL;
|
||||
defstructs = NULL;
|
||||
if (file == NULL) return;
|
||||
for (Node *d = file->list; d; d = d->next) {
|
||||
if (d->kind != N_LET) continue;
|
||||
if (d->str == NULL || d->str[0] == '\0') continue;
|
||||
if (let_emit_size(d->type) == 0) continue;
|
||||
LetVar *lv = amalloc(c->a, sizeof *lv);
|
||||
lv->name = d->str;
|
||||
lv->type = d->type; /* #128b */
|
||||
lv->next = letvars;
|
||||
letvars = lv;
|
||||
if (d->kind == N_LET) {
|
||||
if (d->str == NULL || d->str[0] == '\0') continue;
|
||||
if (let_emit_size(d->type) == 0) continue;
|
||||
LetVar *lv = amalloc(c->a, sizeof *lv);
|
||||
lv->name = d->str;
|
||||
lv->type = d->type;
|
||||
lv->next = letvars;
|
||||
letvars = lv;
|
||||
continue;
|
||||
}
|
||||
if (d->kind == N_DEF) {
|
||||
/* #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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
def_isstructdef(const char *name)
|
||||
{
|
||||
if (name == NULL) return 0;
|
||||
for (DefStruct *ds = defstructs; ds; ds = ds->next)
|
||||
if (strcmp(ds->name, name) == 0) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int
|
||||
let_islet(const char *name)
|
||||
{
|
||||
@@ -6117,7 +6156,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int base_reg = D_BP;
|
||||
int base_disp = root_off;
|
||||
int root_resolved = (root_off != 0);
|
||||
if (!root_resolved && let_islet(cur->str)) {
|
||||
/* #129 A.2: struct-typed defs (def_isstructdef)
|
||||
* now have DATA storage and need the same
|
||||
* LEAQ-and-offset shape as struct lets. */
|
||||
if (!root_resolved && (let_islet(cur->str)
|
||||
|| def_isstructdef(cur->str))) {
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, cur->str), areg(D_CX));
|
||||
base_reg = D_CX;
|
||||
@@ -6357,7 +6400,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int is_global = 0;
|
||||
int base_reg = D_BP;
|
||||
int base_disp = off;
|
||||
if (off == 0 && let_islet(n->lhs->str)) {
|
||||
/* #129 A.2: struct-typed defs now also resolve via
|
||||
* LEAQ name(SB) (paralleling lets). Pre-A.2 the
|
||||
* `def_isstructdef` arm fell through to the default
|
||||
* BP-relative path with off=0, emitting `MOV (BP),`
|
||||
* which reads the stack frame's first slot instead
|
||||
* of the def's data section. */
|
||||
if (off == 0 && (let_islet(n->lhs->str)
|
||||
|| def_isstructdef(n->lhs->str))) {
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str), areg(D_CX));
|
||||
is_global = 1;
|
||||
base_reg = D_CX;
|
||||
@@ -8473,6 +8523,137 @@ emit_floatlit_data(FILE *out, Cg *c, const char *directive,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* 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
|
||||
* gaps via the offset table (rule 13), and dispatches per field type:
|
||||
* integer/bool/nil via fold_int_literal, float via emit_floatlit_data's
|
||||
* peel+bitcast core inlined, nested struct via recursion (the per-field
|
||||
* inner literal lookup; nested-struct field-name-leak is a separate
|
||||
* #145 bug filed against the parser/checker — the recursion is
|
||||
* unblocked because emit-time field resolution goes through the type
|
||||
* table, not the parser's symbol table). Array / str / slice / ptr-
|
||||
* with-address fields are out of #129 A.2 scope — fatals loudly per
|
||||
* rule-7 so a future consumer gets a precise stop rather than a
|
||||
* silent zero-emit.
|
||||
*
|
||||
* Shared by emit_struct_data (#129 Phase A.2) below; broken out so the
|
||||
* recursive call can recurse on the inner field bytes without re-
|
||||
* opening the "DIR name(SB),\"" prefix. */
|
||||
static int
|
||||
emit_struct_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, u64 base)
|
||||
{
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
if (u == NULL || u->kind != TY_STRUCT) return 0;
|
||||
u64 pos = base;
|
||||
for (Tfield *f = u->fields; f != NULL; f = f->next) {
|
||||
u64 fstart = base + f->offset;
|
||||
while (pos < fstart) {
|
||||
emit_data_byte(out, 0);
|
||||
pos++;
|
||||
}
|
||||
Node *v = NULL;
|
||||
if (rhs != NULL) {
|
||||
for (Node *fn = rhs->list; fn != NULL; fn = fn->next) {
|
||||
if (fn->str && f->name
|
||||
&& strcmp(fn->str, f->name) == 0) {
|
||||
v = fn->lhs;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
int fsz = (int)f->type->size;
|
||||
if (v == NULL) {
|
||||
for (int i = 0; i < fsz; i++) emit_data_byte(out, 0);
|
||||
pos += (u64)fsz;
|
||||
continue;
|
||||
}
|
||||
Node *vr = v;
|
||||
while (vr != NULL && vr->kind == N_CAST) vr = vr->lhs;
|
||||
Type *fu = (f->type && f->type->kind == TY_NAMED)
|
||||
? f->type->under : f->type;
|
||||
if (fu && fu->kind == TY_STRUCT) {
|
||||
/* Recurse into nested struct lit. Pre-#145 the parser/
|
||||
* checker has its own gap on inner-N_STRUCTLIT field
|
||||
* name resolution; this emit recursion goes through
|
||||
* the type table so it's correct in isolation. */
|
||||
if (vr == NULL || vr->kind != N_STRUCTLIT)
|
||||
fatal("emit_struct_lit_bytes: nested struct "
|
||||
"field '%s' rhs is not N_STRUCTLIT "
|
||||
"(#129 A.2)", f->name ? f->name : "?");
|
||||
(void)emit_struct_lit_bytes(out, c, f->type, vr, fstart);
|
||||
pos = fstart + (u64)fsz;
|
||||
continue;
|
||||
}
|
||||
if (type_isfloat(f->type)) {
|
||||
int isf32 = type_isf32(f->type);
|
||||
u64 fv = 0;
|
||||
int neg = 0;
|
||||
Node *fr = vr;
|
||||
if (fr != NULL && fr->kind == N_UN
|
||||
&& (fr->op == TK_MINUS || fr->op == TK_PLUS)) {
|
||||
if (fr->op == TK_MINUS) neg = 1;
|
||||
fr = fr->lhs;
|
||||
while (fr != NULL && fr->kind == N_CAST)
|
||||
fr = fr->lhs;
|
||||
}
|
||||
if (fr == NULL || fr->kind != N_FLOATLIT)
|
||||
fatal("emit_struct_lit_bytes: float field "
|
||||
"'%s' rhs not foldable FLOATLIT (#129 A.2)",
|
||||
f->name ? f->name : "?");
|
||||
if (isf32) {
|
||||
union { float f; u32 u; } x;
|
||||
x.f = (float)fr->fval;
|
||||
fv = (u64)x.u;
|
||||
} else {
|
||||
union { double d; u64 u; } x;
|
||||
x.d = fr->fval;
|
||||
fv = x.u;
|
||||
}
|
||||
for (int i = 0; i < fsz; i++) {
|
||||
u8 b = (u8)((fv >> (i * 8)) & 0xff);
|
||||
if (neg && i == fsz - 1) b = (u8)(b ^ 0x80);
|
||||
emit_data_byte(out, b);
|
||||
}
|
||||
pos = fstart + (u64)fsz;
|
||||
continue;
|
||||
}
|
||||
u64 iv = 0;
|
||||
if (!fold_int_literal(vr, &iv))
|
||||
fatal("emit_struct_lit_bytes: field '%s' rhs not a "
|
||||
"foldable literal (str/slice/ptr/array fields "
|
||||
"are out of #129 A.2 scope)",
|
||||
f->name ? f->name : "?");
|
||||
for (int i = 0; i < fsz; i++)
|
||||
emit_data_byte(out, (u8)((iv >> (i * 8)) & 0xff));
|
||||
pos = fstart + (u64)fsz;
|
||||
}
|
||||
/* Tail padding to t->size. */
|
||||
u64 end = base + t->size;
|
||||
while (pos < end) {
|
||||
emit_data_byte(out, 0);
|
||||
pos++;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* emit_struct_data — top-level wrapper that opens the DATA/DATAW
|
||||
* directive and delegates the byte payload to emit_struct_lit_bytes.
|
||||
* Shared SSoT between emit_lets's struct arm and emit_defs's struct
|
||||
* arm (#129 Phase A.2, rule-12 sea-of-stars). Returns 1 on emit, 0 if
|
||||
* the type isn't a struct. */
|
||||
static int
|
||||
emit_struct_data(FILE *out, Cg *c, const char *directive,
|
||||
const char *name, Type *t, Node *rhs)
|
||||
{
|
||||
Type *u = (t && t->kind == TY_NAMED) ? t->under : t;
|
||||
if (u == NULL || u->kind != TY_STRUCT) return 0;
|
||||
fprintf(out, "%s %s(SB),\"", directive, mod_mangle(c, name));
|
||||
emit_struct_lit_bytes(out, c, t, rhs, 0);
|
||||
fputs("\"\n", out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
emit_lets(Cg *c, FILE *out, Node *file)
|
||||
{
|
||||
@@ -8486,7 +8667,14 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
d->str, d->type, d->rhs);
|
||||
continue;
|
||||
}
|
||||
if (sz == 8 && !let_isarray(d->type)) {
|
||||
/* #129 A.2: gate `!let_isstruct` so an 8B struct lit
|
||||
* (`struct { i32, i32 }`, `struct { f32, f32 }`, …) does
|
||||
* NOT short-circuit through the scalar 8B `fold_int_literal`
|
||||
* arm — fold-fail-`continue` would otherwise drop the let
|
||||
* entirely, emitting no DATA and diverging from wwstage's
|
||||
* emitletdataw (which gates its 8B scalar with `!issg`).
|
||||
* Symmetric ordering with the wwstage struct arm. */
|
||||
if (sz == 8 && !let_isarray(d->type) && !let_isstruct(d->type)) {
|
||||
u64 v = 0;
|
||||
if (d->rhs != NULL) {
|
||||
Node *r = d->rhs;
|
||||
@@ -8592,6 +8780,15 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
int is_struct = let_isstruct(d->type);
|
||||
int is_array = let_isarray(d->type);
|
||||
int empty_str = (r->kind == N_STRLIT && r->strlen == 0);
|
||||
/* #129 A.2: struct-typed let with N_STRUCTLIT rhs
|
||||
* routes through the emit_struct_data SSoT. Pre-#129
|
||||
* this fell through to `continue` and emit-NOTHING,
|
||||
* so the link surfaced an undefined ref. */
|
||||
if (is_struct && r->kind == N_STRUCTLIT) {
|
||||
if (emit_struct_data(out, c, "DATAW", d->str,
|
||||
d->type, r))
|
||||
continue;
|
||||
}
|
||||
if (is_struct) continue;
|
||||
if (is_array) continue;
|
||||
if (r->kind != N_NIL && !empty_str) continue;
|
||||
@@ -8639,6 +8836,16 @@ emit_defs(Cg *c, FILE *out, Node *file)
|
||||
d->str, d->type, d->rhs);
|
||||
continue;
|
||||
}
|
||||
/* #129 A.2: struct-typed def with N_STRUCTLIT rhs. Parallel
|
||||
* to emit_lets's struct arm; uses DATA (read-only) directive.
|
||||
* Without the LOAD-side widening below the def's address
|
||||
* still wouldn't be reachable, but storage is the precondition
|
||||
* for the LOAD path to find something. */
|
||||
if (let_isstruct(d->type) && d->rhs->kind == N_STRUCTLIT) {
|
||||
(void)emit_struct_data(out, c, "DATA",
|
||||
d->str, d->type, d->rhs);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
(void)c;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user