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:
2026-06-14 18:11:28 +09:00
parent 34c1051a63
commit 9767ff8fff
6 changed files with 831 additions and 53 deletions

View File

@@ -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;
}