wcc/cgen: #87 plain tagged-union module-global DATA + match SB-resolution (both-stage)
A PLAIN (non-alias) module-level tagged-union global SEGV'd on BOTH
stages: no static DATA was emitted (let_emit_size/letemitsize returned 0
for TY_TAGGED) so the global was never registered, and the match
scrutinee resolved it as a frame-local at offset 0 — reading saved BP as
the tag. Two sub-sites, one route (neither half ships alone — DATA
without SB-resolution still SEGVs; SB-resolution without DATA reads
nothing):
(a) DATA-emitter — a non-nullable TY_TAGGED arm emits the box that
byte-MIRRORS a runtime LOCAL of the same type: tag word at +0 (the
const-selected variant index via cg_tag_for_variant / taggedvariant-
index), payload at +8, zero-padded to the union box size. int and
str/slice literal variants are wired (str carries a DATAR ptr patch
at +8); any other variant payload loud-stops (rule 7). emit_tagged_
data + emittaggeddata are the per-stage twins; let_pre_intern/
letpreintern gain the matching str-variant intern. Nullable stays 0
so the (*T|void) one-word fold keeps the 8B scalar arm.
(b) match-scrutinee global resolution — the PLAIN-tagged twin of #78:
a global tagged ident scrutinee LEAQs name(SB) and copies the box
into an @match_spill slot the dispatch indexes off BP.
DATA target (mirror of the local box, verified byte-for-byte): for
(i32|str)=42 the 32B box is tag0 | 42@8 | zero-pad; for ="x" it is
tag1 | ptr0@8(DATAR _S_n) | len@16 | cap@24. cs and ww emit byte-
identical asm.
Pins (rob §3, dual-stage 910 cstage + 997 wwstage, attest_pass.ww): the
tagged global-vs-local byte-identity pin (match over the GLOBAL gives the
same arm/value as over a LOCAL — was SEGV both stages) and the str-
variant tag-1 pin, plus the #86 tuple global-vs-local lock-pin guarding
the already-correct emitter path.
929 fail_global_src graduates: a >48B tagged GLOBAL by-value arg now
resolves through the cgplaceaddr MEMORY-class arm (LEAQ g(SB) + blit)
instead of the #38b loud-stop, and runs correctly (uninit zero box ->
first variant); the row becomes a positive run pin. The struct-variant
>48B init still loud-stops via the data emitter.
The first-class-VALUE copy of a tagged ident (`let q = g`) stays a
pre-existing silent #49/#46 sibling (local and global identically),
filed separately — out of this fold's two sub-sites.
This commit is contained in:
120
cmd/w6c/cgen.c
120
cmd/w6c/cgen.c
@@ -1246,6 +1246,16 @@ let_emit_size(Type *t)
|
||||
* the 0 here SILENTLY skipped the
|
||||
* definition and every read saw
|
||||
* BP-frame garbage. */
|
||||
case TY_TAGGED:
|
||||
/* #87: non-nullable tagged-union global — box size (tag word
|
||||
* + payload, mirror of the runtime local box). int/str-literal
|
||||
* variant init via emit_tagged_data; match-scrutinee reads
|
||||
* resolve the box at name(SB). Pre-#87 the missing arm sized 0
|
||||
* → no DATA, no letvar registration, and match read saved BP
|
||||
* as the tag (SEGV). The nullable `(*T | void)` one-word fold
|
||||
* stays 0 (handled, when const, by the 8B scalar arm in
|
||||
* emit_lets — adding it here would re-route that path). */
|
||||
return u->nullable ? 0 : (int)u->size;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -9460,7 +9470,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int is_nullable = type_isnullable(st);
|
||||
int slot_size = (su && su->kind == TY_TAGGED) ? (int)su->size : 16;
|
||||
int sl_off;
|
||||
if (s->kind == N_IDENT) {
|
||||
if (s->kind == N_IDENT && let_islet(s->str)
|
||||
&& !is_nullable && su && su->kind == TY_TAGGED) {
|
||||
/* #87: a tagged-union GLOBAL scrutinee — the box lives
|
||||
* in static DATA at name(SB), not the BP frame.
|
||||
* localfind would return 0 and the dispatch would read
|
||||
* saved BP as the tag (SEGV). The PLAIN-tagged twin of
|
||||
* #78's SB-resolution: LEAQ the address and copy the
|
||||
* box into a scratch slot the dispatch indexes off BP.
|
||||
* `is`/case-let binds read the same scratch. */
|
||||
sl_off = localoff(c, &locals, "@match_spill",
|
||||
slot_size, cg_frame);
|
||||
ins2(c, A_LEAQ, mahint(c, s->str, c->cur_mod),
|
||||
areg(D_AX));
|
||||
for (int k = 0; k < slot_size; k += 8) {
|
||||
ins2(c, A_MOVQ, amem(D_AX, k), areg(D_DX));
|
||||
ins2(c, A_MOVQ, areg(D_DX),
|
||||
amem(D_BP, sl_off + k));
|
||||
}
|
||||
} else if (s->kind == N_IDENT) {
|
||||
sl_off = localfind(locals, s->str);
|
||||
} else if (s->kind == N_DOT && s->lhs && s->lhs->kind == N_IDENT
|
||||
&& s->lhs->type) {
|
||||
@@ -14702,6 +14730,70 @@ emit_strarray_data(FILE *out, Cg *c, const char *directive,
|
||||
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-
|
||||
* selected variant's index, cg_tag_for_variant — the same routine the
|
||||
* runtime widen + match dispatch key on), the payload at +8, zero-padded
|
||||
* to the union's box size. So `let g: (i32|str) = 42` and a local
|
||||
* `let l: (i32|str) = 42` read byte-identically. int and str-literal
|
||||
* variants are wired (the shapes Hare stdlib uses, ref/hare/time/chrono/
|
||||
* utc.ha:44); any other variant payload returns 0 and the caller loud-
|
||||
* stops (rule 7) — never the pre-#87 silent no-DATA + saved-BP read. */
|
||||
static int
|
||||
emit_tagged_data(FILE *out, Cg *c, const char *name, const char *module,
|
||||
Type *t, Node *rhs)
|
||||
{
|
||||
Type *u = type_chase_named(t);
|
||||
if (u == NULL || u->kind != TY_TAGGED || u->nullable) return 0;
|
||||
int sz = (int)u->size;
|
||||
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);
|
||||
fputs("\"\n", out);
|
||||
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;
|
||||
int wide = type_isstr(r->type) || type_isslice(r->type);
|
||||
if (wide) {
|
||||
/* str/slice variant: {ptr placeholder, len, cap} at +8 with a
|
||||
* DATAR patching the ptr word, mirroring the runtime box
|
||||
* (LEAQ _S_n into ptr@+8, len@+16, cap@+24). */
|
||||
if (r->kind != N_STRLIT) return 0;
|
||||
u64 len = r->strlen;
|
||||
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, 0);
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((len >> (i * 8)) & 0xff));
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((len >> (i * 8)) & 0xff));
|
||||
for (int i = 32; i < sz; i++) emit_data_byte(out, 0);
|
||||
fputs("\"\n", out);
|
||||
if (len > 0) {
|
||||
const char *lab = intern_strlit(c, r->str, r->strlen);
|
||||
fprintf(out, "DATAR %s+8(SB),%s(SB)\n", sym, lab);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
u64 v;
|
||||
if (!fold_int_literal(r, &v)) 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);
|
||||
fputs("\"\n", out);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* emit_tuple_data — module-level `let g: (T0, T1, ...) = (v0, v1, ...);`
|
||||
* static init (C-t3, #48). Slot layout (C-t0): a scalar element is one
|
||||
* 8B LE word, a str element its 24B header slot (8 zero ptr placeholder
|
||||
@@ -14921,6 +15013,23 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* #87: non-nullable tagged-union global — emit the box that
|
||||
* mirrors the runtime local (tag + payload). let_emit_size
|
||||
* keeps nullable at 0 so the (*T|void) one-word fold stays on
|
||||
* the 8B scalar arm below. */
|
||||
{
|
||||
Type *gu = type_chase_named(d->type);
|
||||
if (gu != NULL && gu->kind == TY_TAGGED
|
||||
&& !gu->nullable) {
|
||||
if (!emit_tagged_data(out, c, d->str,
|
||||
d->module, d->type, d->rhs))
|
||||
fatal("global tagged let `%s`: "
|
||||
"unsupported variant init "
|
||||
"(int/str literal only; rule 7)",
|
||||
d->str);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* #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`
|
||||
@@ -15200,6 +15309,15 @@ let_pre_intern(Cg *c, Node *file)
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* #87: tagged global with a str/slice-variant literal init —
|
||||
* pre-intern so emit_tagged_data's DATAR (ptr@+8) finds its
|
||||
* _S_ rodata row (the #48 tuple-arm pattern). */
|
||||
if (u != NULL && u->kind == TY_TAGGED && !u->nullable
|
||||
&& r != NULL && r->kind == N_STRLIT && r->strlen > 0
|
||||
&& (type_isstr(r->type) || type_isslice(r->type))) {
|
||||
(void)intern_strlit(c, r->str, r->strlen);
|
||||
continue;
|
||||
}
|
||||
if (let_emit_size(d->type) != (int)ty_str->size) continue;
|
||||
if (r == NULL || r->kind != N_STRLIT) continue;
|
||||
if (r->strlen == 0) continue;
|
||||
|
||||
Reference in New Issue
Block a user