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:
6
Makefile
6
Makefile
@@ -340,6 +340,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_arr_module_index_run \
|
||||
$(BIN)/test_arr_float_call_index_run \
|
||||
$(BIN)/test_def_float_lit_run \
|
||||
$(BIN)/test_struct_composite_init_run \
|
||||
$(BIN)/test_f64cgen_run \
|
||||
$(BIN)/test_f64crossmod_run \
|
||||
$(BIN)/test_tuprecv_run \
|
||||
@@ -1153,6 +1154,11 @@ $(BIN)/test_def_float_lit_run: test/wcc/917_def_float_lit_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_struct_composite_init_run: test/wcc/918_struct_composite_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 $@ $<
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -16583,9 +16583,15 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||
// branch above, swapping the BP frame slot for the global VA.
|
||||
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
|
||||
// #129 A.2: also handles struct-typed `def`s via defvarstructinfo;
|
||||
// emitstructdata gives them DATA storage at name(SB), and this
|
||||
// LEAQ-and-offset shape mirrors the let path. Pre-A.2 the def
|
||||
// fell through to the integer-let MOVQ catch-all (reading garbage
|
||||
// from the wrong offset).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
let si: *structinfo = letvarstructinfo(c, lhs.str);
|
||||
if (si == nil) { si = defvarstructinfo(c, lhs.str); };
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
@@ -24903,6 +24909,36 @@ fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// defvarstructinfo — sister of letvarstructinfo for top-level struct
|
||||
// `def`s. #129 A.2 adds DATA storage for struct-typed defs; the
|
||||
// LOAD-side cgdot direct-struct-global branch needs to resolve the
|
||||
// def's structinfo the same way it resolves a let's, so the field-
|
||||
// offset arithmetic + LEAQ name(SB) routing fires. Walks c.defs and
|
||||
// the type-spec node (defent.dtnode), aliaslookup-chasing TY_NAMED
|
||||
// through to the underlying struct name. Returns nil for non-struct
|
||||
// defs (int/float/str — those use the existing emitsymname-based
|
||||
// paths).
|
||||
fn defvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||
let e: *defent = c.defs;
|
||||
for (e != nil) {
|
||||
if (streq(e.dname, name)) {
|
||||
let t: *node = e.dtnode;
|
||||
for (t != nil) {
|
||||
if (t.kind != nkind.N_TNAME) { return nil; };
|
||||
let nm: str = t.str;
|
||||
let si: *structinfo = structlookup(c, nm);
|
||||
if (si != nil) { return si; };
|
||||
let nx: *node = aliaslookup(c, nm);
|
||||
if (nx == nil) { return nil; };
|
||||
t = nx;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
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 = {
|
||||
@@ -25061,6 +25097,160 @@ fn emitfloatlitdata(c: *cgen, directive: str, name: str,
|
||||
return true;
|
||||
};
|
||||
|
||||
// emitstructlitbytes — payload of a struct-typed top-level let/def
|
||||
// with N_STRUCTLIT rhs. Walks structt.fields, zero-fills padding via
|
||||
// the per-field offset (rule 13), dispatches per field type:
|
||||
// foldintliteral for int/bool/nil, inline bitcast+sign-XOR for float,
|
||||
// recursive call for nested struct. Other field kinds (str / slice /
|
||||
// ptr-with-address / array) are out of #129 A.2 scope — rule-7 aborts
|
||||
// loud rather than silently emitting wrong bytes. Mirror of cstage
|
||||
// emit_struct_lit_bytes. `base` offsets the field-start computation
|
||||
// so the recursive call walks an inner struct's fields within its
|
||||
// outer parent's byte stream.
|
||||
fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
|
||||
base: u64) bool = {
|
||||
let su: *tinfo = structt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su == nil) { return false; };
|
||||
if (su.kind != tykind.TY_STRUCT) { return false; };
|
||||
let pos: u64 = base;
|
||||
let f: *tfield = su.fields;
|
||||
for (f != nil) {
|
||||
let fstart: u64 = base + f.offset;
|
||||
for (pos < fstart) {
|
||||
emitdatawbyte(0u8);
|
||||
pos = pos + 1u64;
|
||||
};
|
||||
let v: *node = nil;
|
||||
if (rhs != nil) {
|
||||
let fnod: *node = rhs.list;
|
||||
for (fnod != nil) {
|
||||
if (streq(fnod.str, f.name)) {
|
||||
v = fnod.lhs;
|
||||
break;
|
||||
};
|
||||
fnod = fnod.next;
|
||||
};
|
||||
};
|
||||
let fsz: i32 = f.type_.size: i32;
|
||||
if (v == nil) {
|
||||
let i: i32 = 0;
|
||||
for (i < fsz) {
|
||||
emitdatawbyte(0u8);
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
let vr: *node = v;
|
||||
for (vr != nil && vr.kind == nkind.N_CAST) { vr = vr.lhs; };
|
||||
let fu: *tinfo = f.type_;
|
||||
for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; };
|
||||
if (fu != nil && fu.kind == tykind.TY_STRUCT) {
|
||||
if (vr == nil) {
|
||||
let m: str = "emitstructlitbytes: nested struct field rhs nil (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (vr.kind != nkind.N_STRUCTLIT) {
|
||||
let m: str = "emitstructlitbytes: nested struct rhs not N_STRUCTLIT (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitstructlitbytes(c, f.type_, vr, fstart);
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
if (typeisfloat(f.type_)) {
|
||||
let isf32: bool = (fsz == 4);
|
||||
let neg: bool = false;
|
||||
let fr: *node = vr;
|
||||
if (fr != nil) { if (fr.kind == nkind.N_UN) {
|
||||
if (fr.op == tkind.TK_MINUS) {
|
||||
neg = true;
|
||||
fr = fr.lhs;
|
||||
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
|
||||
} else { if (fr.op == tkind.TK_PLUS) {
|
||||
fr = fr.lhs;
|
||||
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
|
||||
};};
|
||||
};};
|
||||
if (fr == nil) {
|
||||
let m: str = "emitstructlitbytes: float field rhs nil (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fr.kind != nkind.N_FLOATLIT) {
|
||||
let m: str = "emitstructlitbytes: float field rhs not FLOATLIT (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let bits: u64 = fr.uval;
|
||||
if (isf32) {
|
||||
let dv: f64 = *((&bits): *f64);
|
||||
let fv: f32 = (dv: f32);
|
||||
let uv: u32 = *((&fv): *u32);
|
||||
bits = uv: u64;
|
||||
};
|
||||
let i: i32 = 0;
|
||||
let nb: u64 = bits;
|
||||
for (i < fsz) {
|
||||
let b: u8 = (nb & 255u64): u8;
|
||||
if (neg) {
|
||||
if (i == fsz - 1) { b = b ^ 128u8; };
|
||||
};
|
||||
emitdatawbyte(b);
|
||||
nb = nb >> 8u64;
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
let iv: u64 = 0u64;
|
||||
if (!foldintliteral(vr, &iv)) {
|
||||
let m: str = "emitstructlitbytes: field rhs not foldable (str/slice/ptr/array out of #129 A.2 scope)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let i: i32 = 0;
|
||||
let nb: u64 = iv;
|
||||
for (i < fsz) {
|
||||
emitdatawbyte((nb & 255u64): u8);
|
||||
nb = nb >> 8u64;
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
};
|
||||
let endpos: u64 = base + structt.size;
|
||||
for (pos < endpos) {
|
||||
emitdatawbyte(0u8);
|
||||
pos = pos + 1u64;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// emitstructdata — top-level wrapper. Opens the DATA/DATAW directive
|
||||
// then delegates to emitstructlitbytes. Shared between emitletdataw
|
||||
// struct arm and emitdefconstants struct arm (#129 A.2).
|
||||
fn emitstructdata(c: *cgen, directive: str, name: str,
|
||||
structt: *tinfo, rhs: *node) bool = {
|
||||
let su: *tinfo = structt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su == nil) { return false; };
|
||||
if (su.kind != tykind.TY_STRUCT) { return false; };
|
||||
emitline(directive);
|
||||
emitline(" ");
|
||||
emitsymname(c, name);
|
||||
emitline("(SB),\"");
|
||||
emitstructlitbytes(c, structt, rhs, 0u64);
|
||||
emitline("\"\n");
|
||||
return true;
|
||||
};
|
||||
|
||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
@@ -25080,6 +25270,21 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
emitfloatlitdata(c, "DATAW", nm, fsz,
|
||||
d.rhs);
|
||||
};
|
||||
// #129 A.2: struct-typed let with N_STRUCTLIT rhs
|
||||
// routes through the emitstructdata SSoT helper.
|
||||
// Pre-A.2 emitletdataw had no struct arm, so the
|
||||
// declaration fell out of the .data section and
|
||||
// the link surfaced an undefined-symbol error.
|
||||
if (issg) {
|
||||
let r: *node = d.rhs;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_STRUCTLIT) {
|
||||
let st: *tinfo = d.lhs.type_: *tinfo;
|
||||
emitstructdata(c, "DATAW", nm,
|
||||
st, r);
|
||||
};
|
||||
};
|
||||
};
|
||||
// Skip the scalar 8B path when the global is a
|
||||
// fixed-size array that just happens to sum to 8
|
||||
// bytes (e.g. [4]u16, [8]u8) — the array path
|
||||
@@ -25341,6 +25546,25 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||
if (dfsz > 0) {
|
||||
emitfloatlitdata(c, "DATA", d.str,
|
||||
dfsz, d.rhs);
|
||||
} else {
|
||||
// #129 A.2: struct-typed def with N_STRUCTLIT
|
||||
// rhs. The checker stamps d.lhs.type_ with the
|
||||
// struct's tinfo; helper peels TY_NAMED. Parallel
|
||||
// to emitletdataw struct arm; uses DATA (read-
|
||||
// only) directive.
|
||||
if (r != nil) { if (r.kind == nkind.N_STRUCTLIT) {
|
||||
let st: *tinfo = d.lhs.type_: *tinfo;
|
||||
let su: *tinfo = st;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) {
|
||||
su = su.under;
|
||||
};
|
||||
if (su != nil) {
|
||||
if (su.kind == tykind.TY_STRUCT) {
|
||||
emitstructdata(c, "DATA",
|
||||
d.str, st, r);
|
||||
};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
@@ -25606,6 +25830,9 @@ type defent = struct {
|
||||
dname: str,
|
||||
dmod: str, // originating module (`// MODULE: foo`), or empty
|
||||
drhs: *node,
|
||||
dtnode: *node, // #129 A.2: type-spec node (d.lhs); needed for
|
||||
// struct-def structinfo lookup at the cgdot
|
||||
// LOAD-side widening site.
|
||||
dnext: *defent,
|
||||
};
|
||||
|
||||
@@ -25614,7 +25841,7 @@ fn collectdefs(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_DEF) {
|
||||
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dnext=c.defs})!;
|
||||
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dtnode=d.lhs, dnext=c.defs})!;
|
||||
c.defs = e;
|
||||
};
|
||||
d = d.next;
|
||||
|
||||
@@ -1114,6 +1114,36 @@ fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// defvarstructinfo — sister of letvarstructinfo for top-level struct
|
||||
// `def`s. #129 A.2 adds DATA storage for struct-typed defs; the
|
||||
// LOAD-side cgdot direct-struct-global branch needs to resolve the
|
||||
// def's structinfo the same way it resolves a let's, so the field-
|
||||
// offset arithmetic + LEAQ name(SB) routing fires. Walks c.defs and
|
||||
// the type-spec node (defent.dtnode), aliaslookup-chasing TY_NAMED
|
||||
// through to the underlying struct name. Returns nil for non-struct
|
||||
// defs (int/float/str — those use the existing emitsymname-based
|
||||
// paths).
|
||||
fn defvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||
let e: *defent = c.defs;
|
||||
for (e != nil) {
|
||||
if (streq(e.dname, name)) {
|
||||
let t: *node = e.dtnode;
|
||||
for (t != nil) {
|
||||
if (t.kind != nkind.N_TNAME) { return nil; };
|
||||
let nm: str = t.str;
|
||||
let si: *structinfo = structlookup(c, nm);
|
||||
if (si != nil) { return si; };
|
||||
let nx: *node = aliaslookup(c, nm);
|
||||
if (nx == nil) { return nil; };
|
||||
t = nx;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
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 = {
|
||||
@@ -1272,6 +1302,160 @@ fn emitfloatlitdata(c: *cgen, directive: str, name: str,
|
||||
return true;
|
||||
};
|
||||
|
||||
// emitstructlitbytes — payload of a struct-typed top-level let/def
|
||||
// with N_STRUCTLIT rhs. Walks structt.fields, zero-fills padding via
|
||||
// the per-field offset (rule 13), dispatches per field type:
|
||||
// foldintliteral for int/bool/nil, inline bitcast+sign-XOR for float,
|
||||
// recursive call for nested struct. Other field kinds (str / slice /
|
||||
// ptr-with-address / array) are out of #129 A.2 scope — rule-7 aborts
|
||||
// loud rather than silently emitting wrong bytes. Mirror of cstage
|
||||
// emit_struct_lit_bytes. `base` offsets the field-start computation
|
||||
// so the recursive call walks an inner struct's fields within its
|
||||
// outer parent's byte stream.
|
||||
fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
|
||||
base: u64) bool = {
|
||||
let su: *tinfo = structt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su == nil) { return false; };
|
||||
if (su.kind != tykind.TY_STRUCT) { return false; };
|
||||
let pos: u64 = base;
|
||||
let f: *tfield = su.fields;
|
||||
for (f != nil) {
|
||||
let fstart: u64 = base + f.offset;
|
||||
for (pos < fstart) {
|
||||
emitdatawbyte(0u8);
|
||||
pos = pos + 1u64;
|
||||
};
|
||||
let v: *node = nil;
|
||||
if (rhs != nil) {
|
||||
let fnod: *node = rhs.list;
|
||||
for (fnod != nil) {
|
||||
if (streq(fnod.str, f.name)) {
|
||||
v = fnod.lhs;
|
||||
break;
|
||||
};
|
||||
fnod = fnod.next;
|
||||
};
|
||||
};
|
||||
let fsz: i32 = f.type_.size: i32;
|
||||
if (v == nil) {
|
||||
let i: i32 = 0;
|
||||
for (i < fsz) {
|
||||
emitdatawbyte(0u8);
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
let vr: *node = v;
|
||||
for (vr != nil && vr.kind == nkind.N_CAST) { vr = vr.lhs; };
|
||||
let fu: *tinfo = f.type_;
|
||||
for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; };
|
||||
if (fu != nil && fu.kind == tykind.TY_STRUCT) {
|
||||
if (vr == nil) {
|
||||
let m: str = "emitstructlitbytes: nested struct field rhs nil (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (vr.kind != nkind.N_STRUCTLIT) {
|
||||
let m: str = "emitstructlitbytes: nested struct rhs not N_STRUCTLIT (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitstructlitbytes(c, f.type_, vr, fstart);
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
if (typeisfloat(f.type_)) {
|
||||
let isf32: bool = (fsz == 4);
|
||||
let neg: bool = false;
|
||||
let fr: *node = vr;
|
||||
if (fr != nil) { if (fr.kind == nkind.N_UN) {
|
||||
if (fr.op == tkind.TK_MINUS) {
|
||||
neg = true;
|
||||
fr = fr.lhs;
|
||||
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
|
||||
} else { if (fr.op == tkind.TK_PLUS) {
|
||||
fr = fr.lhs;
|
||||
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
|
||||
};};
|
||||
};};
|
||||
if (fr == nil) {
|
||||
let m: str = "emitstructlitbytes: float field rhs nil (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fr.kind != nkind.N_FLOATLIT) {
|
||||
let m: str = "emitstructlitbytes: float field rhs not FLOATLIT (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let bits: u64 = fr.uval;
|
||||
if (isf32) {
|
||||
let dv: f64 = *((&bits): *f64);
|
||||
let fv: f32 = (dv: f32);
|
||||
let uv: u32 = *((&fv): *u32);
|
||||
bits = uv: u64;
|
||||
};
|
||||
let i: i32 = 0;
|
||||
let nb: u64 = bits;
|
||||
for (i < fsz) {
|
||||
let b: u8 = (nb & 255u64): u8;
|
||||
if (neg) {
|
||||
if (i == fsz - 1) { b = b ^ 128u8; };
|
||||
};
|
||||
emitdatawbyte(b);
|
||||
nb = nb >> 8u64;
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
let iv: u64 = 0u64;
|
||||
if (!foldintliteral(vr, &iv)) {
|
||||
let m: str = "emitstructlitbytes: field rhs not foldable (str/slice/ptr/array out of #129 A.2 scope)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let i: i32 = 0;
|
||||
let nb: u64 = iv;
|
||||
for (i < fsz) {
|
||||
emitdatawbyte((nb & 255u64): u8);
|
||||
nb = nb >> 8u64;
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
};
|
||||
let endpos: u64 = base + structt.size;
|
||||
for (pos < endpos) {
|
||||
emitdatawbyte(0u8);
|
||||
pos = pos + 1u64;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// emitstructdata — top-level wrapper. Opens the DATA/DATAW directive
|
||||
// then delegates to emitstructlitbytes. Shared between emitletdataw
|
||||
// struct arm and emitdefconstants struct arm (#129 A.2).
|
||||
fn emitstructdata(c: *cgen, directive: str, name: str,
|
||||
structt: *tinfo, rhs: *node) bool = {
|
||||
let su: *tinfo = structt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su == nil) { return false; };
|
||||
if (su.kind != tykind.TY_STRUCT) { return false; };
|
||||
emitline(directive);
|
||||
emitline(" ");
|
||||
emitsymname(c, name);
|
||||
emitline("(SB),\"");
|
||||
emitstructlitbytes(c, structt, rhs, 0u64);
|
||||
emitline("\"\n");
|
||||
return true;
|
||||
};
|
||||
|
||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
@@ -1291,6 +1475,21 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
emitfloatlitdata(c, "DATAW", nm, fsz,
|
||||
d.rhs);
|
||||
};
|
||||
// #129 A.2: struct-typed let with N_STRUCTLIT rhs
|
||||
// routes through the emitstructdata SSoT helper.
|
||||
// Pre-A.2 emitletdataw had no struct arm, so the
|
||||
// declaration fell out of the .data section and
|
||||
// the link surfaced an undefined-symbol error.
|
||||
if (issg) {
|
||||
let r: *node = d.rhs;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_STRUCTLIT) {
|
||||
let st: *tinfo = d.lhs.type_: *tinfo;
|
||||
emitstructdata(c, "DATAW", nm,
|
||||
st, r);
|
||||
};
|
||||
};
|
||||
};
|
||||
// Skip the scalar 8B path when the global is a
|
||||
// fixed-size array that just happens to sum to 8
|
||||
// bytes (e.g. [4]u16, [8]u8) — the array path
|
||||
@@ -1552,6 +1751,25 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||
if (dfsz > 0) {
|
||||
emitfloatlitdata(c, "DATA", d.str,
|
||||
dfsz, d.rhs);
|
||||
} else {
|
||||
// #129 A.2: struct-typed def with N_STRUCTLIT
|
||||
// rhs. The checker stamps d.lhs.type_ with the
|
||||
// struct's tinfo; helper peels TY_NAMED. Parallel
|
||||
// to emitletdataw struct arm; uses DATA (read-
|
||||
// only) directive.
|
||||
if (r != nil) { if (r.kind == nkind.N_STRUCTLIT) {
|
||||
let st: *tinfo = d.lhs.type_: *tinfo;
|
||||
let su: *tinfo = st;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) {
|
||||
su = su.under;
|
||||
};
|
||||
if (su != nil) {
|
||||
if (su.kind == tykind.TY_STRUCT) {
|
||||
emitstructdata(c, "DATA",
|
||||
d.str, st, r);
|
||||
};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
@@ -1817,6 +2035,9 @@ type defent = struct {
|
||||
dname: str,
|
||||
dmod: str, // originating module (`// MODULE: foo`), or empty
|
||||
drhs: *node,
|
||||
dtnode: *node, // #129 A.2: type-spec node (d.lhs); needed for
|
||||
// struct-def structinfo lookup at the cgdot
|
||||
// LOAD-side widening site.
|
||||
dnext: *defent,
|
||||
};
|
||||
|
||||
@@ -1825,7 +2046,7 @@ fn collectdefs(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_DEF) {
|
||||
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dnext=c.defs})!;
|
||||
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dtnode=d.lhs, dnext=c.defs})!;
|
||||
c.defs = e;
|
||||
};
|
||||
d = d.next;
|
||||
|
||||
@@ -1964,9 +1964,15 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||
// branch above, swapping the BP frame slot for the global VA.
|
||||
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
|
||||
// #129 A.2: also handles struct-typed `def`s via defvarstructinfo;
|
||||
// emitstructdata gives them DATA storage at name(SB), and this
|
||||
// LEAQ-and-offset shape mirrors the let path. Pre-A.2 the def
|
||||
// fell through to the integer-let MOVQ catch-all (reading garbage
|
||||
// from the wrong offset).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
let si: *structinfo = letvarstructinfo(c, lhs.str);
|
||||
if (si == nil) { si = defvarstructinfo(c, lhs.str); };
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
|
||||
@@ -16583,9 +16583,15 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||
// branch above, swapping the BP frame slot for the global VA.
|
||||
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
|
||||
// #129 A.2: also handles struct-typed `def`s via defvarstructinfo;
|
||||
// emitstructdata gives them DATA storage at name(SB), and this
|
||||
// LEAQ-and-offset shape mirrors the let path. Pre-A.2 the def
|
||||
// fell through to the integer-let MOVQ catch-all (reading garbage
|
||||
// from the wrong offset).
|
||||
if (lhs != nil) {
|
||||
if (lhs.kind == nkind.N_IDENT) {
|
||||
let si: *structinfo = letvarstructinfo(c, lhs.str);
|
||||
if (si == nil) { si = defvarstructinfo(c, lhs.str); };
|
||||
if (si != nil) {
|
||||
let fi: *fieldinfo = si.fields;
|
||||
for (fi != nil) {
|
||||
@@ -24903,6 +24909,36 @@ fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||
return nil;
|
||||
};
|
||||
|
||||
// defvarstructinfo — sister of letvarstructinfo for top-level struct
|
||||
// `def`s. #129 A.2 adds DATA storage for struct-typed defs; the
|
||||
// LOAD-side cgdot direct-struct-global branch needs to resolve the
|
||||
// def's structinfo the same way it resolves a let's, so the field-
|
||||
// offset arithmetic + LEAQ name(SB) routing fires. Walks c.defs and
|
||||
// the type-spec node (defent.dtnode), aliaslookup-chasing TY_NAMED
|
||||
// through to the underlying struct name. Returns nil for non-struct
|
||||
// defs (int/float/str — those use the existing emitsymname-based
|
||||
// paths).
|
||||
fn defvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||
let e: *defent = c.defs;
|
||||
for (e != nil) {
|
||||
if (streq(e.dname, name)) {
|
||||
let t: *node = e.dtnode;
|
||||
for (t != nil) {
|
||||
if (t.kind != nkind.N_TNAME) { return nil; };
|
||||
let nm: str = t.str;
|
||||
let si: *structinfo = structlookup(c, nm);
|
||||
if (si != nil) { return si; };
|
||||
let nx: *node = aliaslookup(c, nm);
|
||||
if (nx == nil) { return nil; };
|
||||
t = nx;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
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 = {
|
||||
@@ -25061,6 +25097,160 @@ fn emitfloatlitdata(c: *cgen, directive: str, name: str,
|
||||
return true;
|
||||
};
|
||||
|
||||
// emitstructlitbytes — payload of a struct-typed top-level let/def
|
||||
// with N_STRUCTLIT rhs. Walks structt.fields, zero-fills padding via
|
||||
// the per-field offset (rule 13), dispatches per field type:
|
||||
// foldintliteral for int/bool/nil, inline bitcast+sign-XOR for float,
|
||||
// recursive call for nested struct. Other field kinds (str / slice /
|
||||
// ptr-with-address / array) are out of #129 A.2 scope — rule-7 aborts
|
||||
// loud rather than silently emitting wrong bytes. Mirror of cstage
|
||||
// emit_struct_lit_bytes. `base` offsets the field-start computation
|
||||
// so the recursive call walks an inner struct's fields within its
|
||||
// outer parent's byte stream.
|
||||
fn emitstructlitbytes(c: *cgen, structt: *tinfo, rhs: *node,
|
||||
base: u64) bool = {
|
||||
let su: *tinfo = structt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su == nil) { return false; };
|
||||
if (su.kind != tykind.TY_STRUCT) { return false; };
|
||||
let pos: u64 = base;
|
||||
let f: *tfield = su.fields;
|
||||
for (f != nil) {
|
||||
let fstart: u64 = base + f.offset;
|
||||
for (pos < fstart) {
|
||||
emitdatawbyte(0u8);
|
||||
pos = pos + 1u64;
|
||||
};
|
||||
let v: *node = nil;
|
||||
if (rhs != nil) {
|
||||
let fnod: *node = rhs.list;
|
||||
for (fnod != nil) {
|
||||
if (streq(fnod.str, f.name)) {
|
||||
v = fnod.lhs;
|
||||
break;
|
||||
};
|
||||
fnod = fnod.next;
|
||||
};
|
||||
};
|
||||
let fsz: i32 = f.type_.size: i32;
|
||||
if (v == nil) {
|
||||
let i: i32 = 0;
|
||||
for (i < fsz) {
|
||||
emitdatawbyte(0u8);
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
let vr: *node = v;
|
||||
for (vr != nil && vr.kind == nkind.N_CAST) { vr = vr.lhs; };
|
||||
let fu: *tinfo = f.type_;
|
||||
for (fu != nil && fu.kind == tykind.TY_NAMED) { fu = fu.under; };
|
||||
if (fu != nil && fu.kind == tykind.TY_STRUCT) {
|
||||
if (vr == nil) {
|
||||
let m: str = "emitstructlitbytes: nested struct field rhs nil (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (vr.kind != nkind.N_STRUCTLIT) {
|
||||
let m: str = "emitstructlitbytes: nested struct rhs not N_STRUCTLIT (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
emitstructlitbytes(c, f.type_, vr, fstart);
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
if (typeisfloat(f.type_)) {
|
||||
let isf32: bool = (fsz == 4);
|
||||
let neg: bool = false;
|
||||
let fr: *node = vr;
|
||||
if (fr != nil) { if (fr.kind == nkind.N_UN) {
|
||||
if (fr.op == tkind.TK_MINUS) {
|
||||
neg = true;
|
||||
fr = fr.lhs;
|
||||
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
|
||||
} else { if (fr.op == tkind.TK_PLUS) {
|
||||
fr = fr.lhs;
|
||||
for (fr != nil && fr.kind == nkind.N_CAST) { fr = fr.lhs; };
|
||||
};};
|
||||
};};
|
||||
if (fr == nil) {
|
||||
let m: str = "emitstructlitbytes: float field rhs nil (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
if (fr.kind != nkind.N_FLOATLIT) {
|
||||
let m: str = "emitstructlitbytes: float field rhs not FLOATLIT (#129 A.2)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let bits: u64 = fr.uval;
|
||||
if (isf32) {
|
||||
let dv: f64 = *((&bits): *f64);
|
||||
let fv: f32 = (dv: f32);
|
||||
let uv: u32 = *((&fv): *u32);
|
||||
bits = uv: u64;
|
||||
};
|
||||
let i: i32 = 0;
|
||||
let nb: u64 = bits;
|
||||
for (i < fsz) {
|
||||
let b: u8 = (nb & 255u64): u8;
|
||||
if (neg) {
|
||||
if (i == fsz - 1) { b = b ^ 128u8; };
|
||||
};
|
||||
emitdatawbyte(b);
|
||||
nb = nb >> 8u64;
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
continue;
|
||||
};
|
||||
let iv: u64 = 0u64;
|
||||
if (!foldintliteral(vr, &iv)) {
|
||||
let m: str = "emitstructlitbytes: field rhs not foldable (str/slice/ptr/array out of #129 A.2 scope)\n";
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let i: i32 = 0;
|
||||
let nb: u64 = iv;
|
||||
for (i < fsz) {
|
||||
emitdatawbyte((nb & 255u64): u8);
|
||||
nb = nb >> 8u64;
|
||||
i = i + 1;
|
||||
};
|
||||
pos = fstart + fsz: u64;
|
||||
f = f.tnext;
|
||||
};
|
||||
let endpos: u64 = base + structt.size;
|
||||
for (pos < endpos) {
|
||||
emitdatawbyte(0u8);
|
||||
pos = pos + 1u64;
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
// emitstructdata — top-level wrapper. Opens the DATA/DATAW directive
|
||||
// then delegates to emitstructlitbytes. Shared between emitletdataw
|
||||
// struct arm and emitdefconstants struct arm (#129 A.2).
|
||||
fn emitstructdata(c: *cgen, directive: str, name: str,
|
||||
structt: *tinfo, rhs: *node) bool = {
|
||||
let su: *tinfo = structt;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) { su = su.under; };
|
||||
if (su == nil) { return false; };
|
||||
if (su.kind != tykind.TY_STRUCT) { return false; };
|
||||
emitline(directive);
|
||||
emitline(" ");
|
||||
emitsymname(c, name);
|
||||
emitline("(SB),\"");
|
||||
emitstructlitbytes(c, structt, rhs, 0u64);
|
||||
emitline("\"\n");
|
||||
return true;
|
||||
};
|
||||
|
||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
@@ -25080,6 +25270,21 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
||||
emitfloatlitdata(c, "DATAW", nm, fsz,
|
||||
d.rhs);
|
||||
};
|
||||
// #129 A.2: struct-typed let with N_STRUCTLIT rhs
|
||||
// routes through the emitstructdata SSoT helper.
|
||||
// Pre-A.2 emitletdataw had no struct arm, so the
|
||||
// declaration fell out of the .data section and
|
||||
// the link surfaced an undefined-symbol error.
|
||||
if (issg) {
|
||||
let r: *node = d.rhs;
|
||||
if (r != nil) {
|
||||
if (r.kind == nkind.N_STRUCTLIT) {
|
||||
let st: *tinfo = d.lhs.type_: *tinfo;
|
||||
emitstructdata(c, "DATAW", nm,
|
||||
st, r);
|
||||
};
|
||||
};
|
||||
};
|
||||
// Skip the scalar 8B path when the global is a
|
||||
// fixed-size array that just happens to sum to 8
|
||||
// bytes (e.g. [4]u16, [8]u8) — the array path
|
||||
@@ -25341,6 +25546,25 @@ fn emitdefconstants(c: *cgen, file: *node) void = {
|
||||
if (dfsz > 0) {
|
||||
emitfloatlitdata(c, "DATA", d.str,
|
||||
dfsz, d.rhs);
|
||||
} else {
|
||||
// #129 A.2: struct-typed def with N_STRUCTLIT
|
||||
// rhs. The checker stamps d.lhs.type_ with the
|
||||
// struct's tinfo; helper peels TY_NAMED. Parallel
|
||||
// to emitletdataw struct arm; uses DATA (read-
|
||||
// only) directive.
|
||||
if (r != nil) { if (r.kind == nkind.N_STRUCTLIT) {
|
||||
let st: *tinfo = d.lhs.type_: *tinfo;
|
||||
let su: *tinfo = st;
|
||||
for (su != nil && su.kind == tykind.TY_NAMED) {
|
||||
su = su.under;
|
||||
};
|
||||
if (su != nil) {
|
||||
if (su.kind == tykind.TY_STRUCT) {
|
||||
emitstructdata(c, "DATA",
|
||||
d.str, st, r);
|
||||
};
|
||||
};
|
||||
};};
|
||||
};
|
||||
};
|
||||
if (ok) {
|
||||
@@ -25606,6 +25830,9 @@ type defent = struct {
|
||||
dname: str,
|
||||
dmod: str, // originating module (`// MODULE: foo`), or empty
|
||||
drhs: *node,
|
||||
dtnode: *node, // #129 A.2: type-spec node (d.lhs); needed for
|
||||
// struct-def structinfo lookup at the cgdot
|
||||
// LOAD-side widening site.
|
||||
dnext: *defent,
|
||||
};
|
||||
|
||||
@@ -25614,7 +25841,7 @@ fn collectdefs(c: *cgen, file: *node) void = {
|
||||
let d: *node = file.list;
|
||||
for (d != nil) {
|
||||
if (d.kind == nkind.N_DEF) {
|
||||
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dnext=c.defs})!;
|
||||
let e: *defent = alloc(defent{dname=d.str, dmod=d.nmod, drhs=d.rhs, dtnode=d.lhs, dnext=c.defs})!;
|
||||
c.defs = e;
|
||||
};
|
||||
d = d.next;
|
||||
|
||||
247
test/wcc/918_struct_composite_init_run.c
Normal file
247
test/wcc/918_struct_composite_init_run.c
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* 918_struct_composite_init_run — runtime + byte-id net for #129
|
||||
* Phase A.2: module-level `let`/`def` with composite-struct
|
||||
* initializer silently emitted undefined ref OR wrong bytes pre-fix.
|
||||
*
|
||||
* Pre-A.2 failure modes:
|
||||
* - cstage emit_lets / wwstage emitletdataw skipped struct-typed lets
|
||||
* entirely (`if (is_struct) continue;` / parallel) — link surfaced
|
||||
* `undefined reference to 'main.NAME'`.
|
||||
* - cstage emit_defs / wwstage emitdefconstants had no struct arm —
|
||||
* same undef ref for def, plus a SEPARATE LOAD-side cgen bug
|
||||
* emitting `MOVSXD (BP), AX` (reading stack frame byte 0) when the
|
||||
* LOAD did get past the link.
|
||||
*
|
||||
* Phase A.2 fix (per A.1 SSoT-helper precedent):
|
||||
* - cstage: emit_struct_data + emit_struct_lit_bytes helpers walk
|
||||
* Tfield list in declaration order, zero-fill padding via per-
|
||||
* field offsets (rule 13), dispatch per field type. Float-field
|
||||
* bytes inlined (mirroring A.1's emit_floatlit_data shape but
|
||||
* localised so the byte loop covers padding too). Nested struct
|
||||
* recurses. Out-of-scope field kinds (array / str / slice / ptr)
|
||||
* fatal loud per rule-7.
|
||||
* - cstage: emit_lets + emit_defs gain struct arms routing through
|
||||
* the helper.
|
||||
* - cstage: LOAD-side widening at `if (u && u->kind == TY_STRUCT
|
||||
* && lhs->kind == N_IDENT)` arm — the `let_islet`-gated LEAQ
|
||||
* name(SB) shape now also fires for struct defs via the new
|
||||
* `def_isstructdef` registry (mirror of letvars).
|
||||
* - wwstage: parallel emitstructdata + emitstructlitbytes helpers,
|
||||
* defent.dtnode field, defvarstructinfo (cgdot LOAD widening).
|
||||
*
|
||||
* Bootstrap NEUTRAL: zero `let/def: T = T{...}` consumers in lib/ or
|
||||
* selfhost/. γ-cleanup is the first consumer (lib/math:floatinfo).
|
||||
*
|
||||
* Rows cover all A.2-scope shapes:
|
||||
* - (a) `let CFG: cfg_t = cfg_t{a=1, b=2u64};` — plain mixed-int let
|
||||
* - (b) `def CFG: cfg_t = cfg_t{a=1, b=2u64};` — plain mixed-int def
|
||||
* (exercises LOAD-widening; both stages)
|
||||
* - (c) `let F: ft = ft{a=1.5, b=99u32};` — let with float field
|
||||
* - (d) `def F: ft = ft{a=1.5, b=99u32};` — def with float field
|
||||
* (storage + LOAD + float-narrow path together)
|
||||
* - (e) `let Z: zt = zt{};` — empty struct-lit zero-fill
|
||||
* - (f) `let CFG: cfg_t;` — regression: no-rhs (pre-existing path
|
||||
* unchanged)
|
||||
*
|
||||
* Each row carries (a) cstage `ww build` + run asserting exit code
|
||||
* and (b) w6c vs w6c_ww `.s` cmp (rule-10 byte-id).
|
||||
*
|
||||
* Nested-struct shape (#3 in design report) is OMITTED here — the
|
||||
* helper implements the recursion but #145 (parser/checker inner-
|
||||
* literal field-name leak) blocks end-to-end correctness; nested
|
||||
* row deferred until #145 lands.
|
||||
*
|
||||
* Array-in-struct shape parked to Phase A.3 boundary.
|
||||
*/
|
||||
#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_int_struct",
|
||||
"package main;\n"
|
||||
"type cfg_t = struct { a: i32, b: u64 };\n"
|
||||
"let CFG: cfg_t = cfg_t{a=1, b=2u64};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return CFG.a;\n"
|
||||
"};\n", 1 },
|
||||
{ "def_int_struct",
|
||||
"package main;\n"
|
||||
"type cfg_t = struct { a: i32, b: u64 };\n"
|
||||
"def CFG: cfg_t = cfg_t{a=1, b=2u64};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return CFG.a;\n"
|
||||
"};\n", 1 },
|
||||
{ "let_float_field",
|
||||
"package main;\n"
|
||||
"type ft = struct { a: f64, b: u32 };\n"
|
||||
"let F: ft = ft{a=1.5, b=99u32};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return (F.a: i32);\n"
|
||||
"};\n", 1 },
|
||||
{ "def_float_field",
|
||||
"package main;\n"
|
||||
"type ft = struct { a: f64, b: u32 };\n"
|
||||
"def F: ft = ft{a=1.5, b=99u32};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return (F.a: i32);\n"
|
||||
"};\n", 1 },
|
||||
{ "let_empty_struct",
|
||||
"package main;\n"
|
||||
"type zt = struct { a: i32, b: u64 };\n"
|
||||
"let Z: zt = zt{};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return Z.a;\n"
|
||||
"};\n", 0 },
|
||||
/* 8B struct hits the cstage emit_lets scalar-8B short-circuit
|
||||
* (sz==8 fold_int_literal arm) — without the `!let_isstruct`
|
||||
* gate the struct lit fold-fails and the let drops entirely,
|
||||
* emitting no DATA. Both stages now route via the struct arm. */
|
||||
{ "let_int_struct_8b",
|
||||
"package main;\n"
|
||||
"type s8 = struct { a: i32, b: i32 };\n"
|
||||
"let X: s8 = s8{a=7, b=42};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return X.a;\n"
|
||||
"};\n", 7 },
|
||||
/* Regression: no-rhs path unchanged (emit_data_row_zero / wwstage
|
||||
* parallel). */
|
||||
{ "let_norhs_struct",
|
||||
"package main;\n"
|
||||
"type cfg_t = struct { a: i32, b: u64 };\n"
|
||||
"let CFG: cfg_t;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" return CFG.a;\n"
|
||||
"};\n", 0 },
|
||||
{ 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, "strcomp: 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/wwstrc_%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/wwstrc_%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/wwstrc_%d_%d_cs.s",
|
||||
getpid(), i);
|
||||
snprintf(ws_s, sizeof ws_s, "/tmp/wwstrc_%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 struct-composite tests failed\n", fail, n);
|
||||
return 1;
|
||||
}
|
||||
printf("strcomp: %d/%d ok (cstage run + cs==ww byte-id)\n",
|
||||
n, n);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user