wcc/ww: emit correct (tag,payload) for a tagged value in static-init (#19)
A tagged-union value nested in module-level array/struct static-init mis-emitted in both stages: the lit-bytes emitters had no TY_TAGGED arm, so a tagged element/field fell to the int path and the payload landed in the TAG word -- match then read the wrong variant. The zero-placeholder idiom (today the only way to declare a tagged global: zero-init in static, write at runtime) was correct only by accident (int-variant zero folds to (0,0), which equals the right (tag0,0)). Extract a raw-byte core emittaggedbytes/emit_tagged_bytes -- variant tag@+0, int payload@+8, zero-pad to the slot size; no directive, no offset, no reloc -- and refactor the scalar tagged emitter to delegate to it (byte-id-neutral). Add a TY_TAGGED member branch to the array and struct lit-bytes emitters (both stages) that calls the core at the existing full-slot stride, before the int fallthrough. Zero stays (0,0) byte-identical; a non-zero element/field now emits (tag,payload) correctly. A wide (str/slice) or struct/>8B payload nested in an aggregate needs reloc-at-member-offset machinery the aggregate byte-emitters don't have, so it is loud-rejected (rule 7), deferred to #30; the existing slice-of-tagged static-init reject is unchanged. Regenerates the w6c and wwdump combined.ww. Table-driven 843 test: non-zero array/struct (pre-fix returned the wrong variant), the non-tag-0 bool-variant edge, byte-id-neutral zero-placeholder rows, and wide-payload reject rows; each run row also pins cs-vs-ww asm.
This commit is contained in:
116
cmd/w6c/cgen.c
116
cmd/w6c/cgen.c
@@ -15315,6 +15315,11 @@ emit_floatlit_data(FILE *out, Cg *c, const char *directive,
|
||||
* 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);
|
||||
/* Forward declaration: the struct/array member emitters call the shared
|
||||
* tagged-payload core (#19 option A), defined alongside emit_tagged_data
|
||||
* further down. */
|
||||
static int emit_tagged_bytes(FILE *out, Type *u, Node *rhs, int sz,
|
||||
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
|
||||
@@ -15368,6 +15373,22 @@ emit_struct_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, u64 base)
|
||||
Node *vr = v;
|
||||
while (vr != NULL && vr->kind == N_CAST) vr = vr->lhs;
|
||||
Type *fu = type_chase_named(f->type);
|
||||
/* #19 option A: a non-nullable tagged-union field rides the
|
||||
* shared (tag,payload) core at the field slot size, mirroring the
|
||||
* scalar tagged global — NOT the int emitter, which mis-folds the
|
||||
* payload into the tag word. Wide/struct/non-foldable payload
|
||||
* loud-rejects (task #30 sub-item, rule 7). v is non-NULL here
|
||||
* (the absent-field zero-fill is handled above). */
|
||||
if (fu && fu->kind == TY_TAGGED && !fu->nullable) {
|
||||
if (!emit_tagged_bytes(out, fu, v, fsz, 1))
|
||||
fatal("emit_struct_lit_bytes: tagged-union "
|
||||
"struct-field '%s' static-init needs a "
|
||||
"zero/int payload; wide (str/slice) or struct "
|
||||
"payload is deferred (task #30, rule 7)",
|
||||
f->name ? f->name : "?");
|
||||
pos = fstart + (u64)fsz;
|
||||
continue;
|
||||
}
|
||||
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
|
||||
@@ -15504,6 +15525,46 @@ emit_array_lit_bytes(FILE *out, Cg *c, Type *t, Node *rhs, int emit_phase)
|
||||
int esz = etype ? (int)etype->size : 1;
|
||||
int alen = (int)u->alen;
|
||||
|
||||
/* #19 option A: a non-nullable tagged-union element rides the shared
|
||||
* (tag,payload) core at the full slot stride esz, mirroring the scalar
|
||||
* tagged global — NOT the int emitter. A wide/struct/non-foldable
|
||||
* payload element loud-rejects (task #30 sub-item, rule 7). A nullable
|
||||
* `(*T|void)` element is a 1-word fold, not a tag box — left to the
|
||||
* existing int path (task #15). */
|
||||
if (eu && eu->kind == TY_TAGGED && !eu->nullable) {
|
||||
int idx = 0;
|
||||
Node *last_ev = NULL;
|
||||
for (Node *e = rhs->list; e && idx < alen; e = e->next) {
|
||||
if (e->kind == N_FIELD && e->str
|
||||
&& strcmp(e->str, "...") == 0)
|
||||
break;
|
||||
if (!emit_tagged_bytes(out, eu, e, esz, 0))
|
||||
fatal("emit_array_lit_bytes: tagged-union array "
|
||||
"element static-init needs a zero/int "
|
||||
"payload; wide (str/slice) or struct payload "
|
||||
"is deferred (task #30, rule 7)");
|
||||
last_ev = e;
|
||||
idx++;
|
||||
}
|
||||
if (emit_phase == 0) return 1;
|
||||
idx = 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; }
|
||||
emit_tagged_bytes(out, eu, e, esz, 1);
|
||||
idx++;
|
||||
}
|
||||
for (; idx < alen; idx++) {
|
||||
if (repeat && last_ev)
|
||||
emit_tagged_bytes(out, eu, last_ev, esz, 1);
|
||||
else
|
||||
for (int b = 0; b < esz; b++)
|
||||
emit_data_byte(out, 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (eu && eu->kind == TY_STRUCT) {
|
||||
/* Validate: every element must be N_STRUCTLIT (after N_CAST
|
||||
* peel). */
|
||||
@@ -15803,6 +15864,47 @@ emit_strarray_data(FILE *out, Cg *c, const char *directive,
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* emit_tagged_bytes — raw sz-byte static-init payload for a tagged-union
|
||||
* value: variant tag@+0 (8B), int payload@+8 (8B), zero-pad to sz. NO
|
||||
* directive open/close, NO reloc — emits exactly sz bytes via
|
||||
* emit_data_byte at the current cursor. Handles zero (NULL rhs) + a
|
||||
* foldable int payload only; a wide (str/slice) payload needs a DATAR the
|
||||
* raw core cannot place inside an already-open aggregate directive, and a
|
||||
* struct/non-foldable payload has no scalar form — both return 0 WITHOUT
|
||||
* emitting, and the caller loud-rejects (task #30 wide/struct sub-item).
|
||||
* `u` is the type-chased TY_TAGGED union type. emit_phase 0 = validate
|
||||
* only; 1 = emit. rob's EXTRACT ruling (#19 option A): the scalar wrapper
|
||||
* emit_tagged_data and the aggregate member branches (emit_array_lit_bytes
|
||||
* / emit_struct_lit_bytes) share this raw core so a nested tagged member
|
||||
* rides the SAME (tag,payload) SSoT as a scalar tagged global. Mirror of
|
||||
* wwstage emittaggedbytes. */
|
||||
static int
|
||||
emit_tagged_bytes(FILE *out, Type *u, Node *rhs, int sz, int emit_phase)
|
||||
{
|
||||
if (u == NULL || u->kind != TY_TAGGED) return 0;
|
||||
if (rhs == NULL) {
|
||||
if (emit_phase)
|
||||
for (int i = 0; i < sz; i++) emit_data_byte(out, 0);
|
||||
return 1;
|
||||
}
|
||||
Node *r = rhs;
|
||||
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
||||
if (r == NULL) return 0;
|
||||
int tag = cg_tag_for_variant(u, r->type);
|
||||
if (tag < 0) return 0;
|
||||
if (type_isstr(r->type) || type_isslice(r->type)) return 0;
|
||||
u64 v;
|
||||
if (!fold_int_literal(r, &v)) return 0;
|
||||
if (emit_phase) {
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)(((u64)tag >> (i * 8)) & 0xff));
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff));
|
||||
for (int i = 16; i < sz; i++) emit_data_byte(out, 0);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* emit_tagged_data — module-level `let g: (T0 | T1 | ...) = v;` static
|
||||
* init (#87). The static DATA must byte-MIRROR a runtime LOCAL tagged box
|
||||
* of the same type (the SSoT pin, rob §3): tag word at +0 (the const-
|
||||
@@ -15823,7 +15925,7 @@ emit_tagged_data(FILE *out, Cg *c, const char *name, const char *module,
|
||||
const char *sym = mod_mangle_value(c, name, module);
|
||||
if (rhs == NULL) {
|
||||
fprintf(out, "DATAW %s(SB),\"", sym);
|
||||
for (int i = 0; i < sz; i++) emit_data_byte(out, 0);
|
||||
emit_tagged_bytes(out, u, rhs, sz, 1);
|
||||
fputs("\"\n", out);
|
||||
return 1;
|
||||
}
|
||||
@@ -15855,14 +15957,12 @@ emit_tagged_data(FILE *out, Cg *c, const char *name, const char *module,
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
u64 v;
|
||||
if (!fold_int_literal(r, &v)) return 0;
|
||||
/* int/zero payload via the shared raw core; validate (phase 0)
|
||||
* BEFORE opening the directive so a non-foldable rhs returns 0
|
||||
* without leaving a half-written DATAW. */
|
||||
if (!emit_tagged_bytes(out, u, rhs, sz, 0)) return 0;
|
||||
fprintf(out, "DATAW %s(SB),\"", sym);
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)(((u64)tag >> (i * 8)) & 0xff));
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff));
|
||||
for (int i = 16; i < sz; i++) emit_data_byte(out, 0);
|
||||
emit_tagged_bytes(out, u, rhs, sz, 1);
|
||||
fputs("\"\n", out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user