wcc+w6c+w6c_ww: global tuple lets — DATA emit + element reads + len(g.N) (C-t3, #48)
Global tuple lets were WHOLLY unwired, silently: let_emit_size returned 0 so emit_lets SKIPPED the definition (no DATA, no diagnostic), then cstage's t.N read and #235 len arm read BP-frame garbage (localfind→0) while wwstage — with the tuple never in collectlets — mis-emitted the field index as a symbol (`MOVQ 0(SB), AX`). ken's #48 was the len() facet of this. Now: let_emit_size/letemitsize admit TY_TUPLE (slot-sum size, rides C-t0); emit_tuple_data/emittupledata lay the slot-format DATAW row — a scalar element one 8B LE word, a str element its 24B header slot with a DATAR ptr patch at the element's slot offset (the #18 [N]str per-element pattern; strlits pre-interned in element order) — and any element that doesn't reduce to an int/str literal dies LOUD instead of skipped. The t.N read and len arms gain the global base (LEAQ sym(SB) into CX, the struct-field-global pattern; wwstage's C5 len loud-stop graduates to the working path). A GLOBAL tuple as a first-class VALUE (`let q = g;`) loud-stops on both stages — pre-fix it byte-identically loaded word0 only and read a stale cursor for words 1+ (element reads are the supported surface). 941 grows the t3 rows: global element reads (str+i64 and packed u32,u32 incl. len(g.0)) + rejects (float-element init, whole-value use, pre-existing element-write anchor). 7/82 checks fail at the C-t2 parent (cs silent-garbage runtime, ww C5 build-fail, both rejects vacuous-or-absent).
This commit is contained in:
203
cmd/w6c/cgen.c
203
cmd/w6c/cgen.c
@@ -1080,6 +1080,14 @@ let_emit_size(Type *t)
|
||||
* loads/stores via cgindex. Mirror
|
||||
* of selfhost letemitsize's
|
||||
* N_TARRAY branch. */
|
||||
case TY_TUPLE:
|
||||
return (int)u->size; /* C-t3 (#48): slot-sum size (C-t0).
|
||||
* int/str-literal element init via
|
||||
* emit_tuple_data; element reads via
|
||||
* the N_DOT t.N global arm. Pre-C-t3
|
||||
* the 0 here SILENTLY skipped the
|
||||
* definition and every read saw
|
||||
* BP-frame garbage. */
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
@@ -3505,6 +3513,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
goto ident_done;
|
||||
}
|
||||
}
|
||||
/* C-t3 (#48, rule 7): a GLOBAL tuple as a first-class
|
||||
* VALUE (`let q = g;` / `return g;` / `f(g)`) has no
|
||||
* slot-to-cursor path (cg_tuple_slot_to_cursor is
|
||||
* BP-relative) — pre-fix it fell to the scalar MOVQ
|
||||
* below, loading word0 only, and the receive read a
|
||||
* STALE cursor for words 1+. Element reads (g.N)
|
||||
* are the supported surface. */
|
||||
if (let_islet(n->str)) {
|
||||
Type *gu = type_chase_named(n->type);
|
||||
if (gu && gu->kind == TY_TUPLE)
|
||||
fatal("#48: global tuple as a "
|
||||
"first-class value unwired "
|
||||
"(element reads only; rule 7)");
|
||||
}
|
||||
if (let_islet(n->str)
|
||||
&& (let_isstr(n->type) || let_isslice(n->type))) {
|
||||
/* Top-level str/slice global: load each word
|
||||
@@ -6681,9 +6703,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
idx--;
|
||||
}
|
||||
int off = localfind(locals, a->lhs->str);
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off + foff + 8),
|
||||
areg(D_AX));
|
||||
/* C-t3 (#48): GLOBAL tuple base —
|
||||
* pre-fix localfind's 0 read the .len
|
||||
* word at foff+8(BP), stack garbage,
|
||||
* SILENT. Twin of the N_DOT TY_TUPLE
|
||||
* global arm. */
|
||||
if (off == 0
|
||||
&& let_islet(a->lhs->str)) {
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, a->lhs->str),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_CX, foff + 8),
|
||||
areg(D_AX));
|
||||
} else {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off + foff + 8),
|
||||
areg(D_AX));
|
||||
}
|
||||
lendone = 1;
|
||||
}
|
||||
/* struct-field N_DOT (`len(s.field)`): the old
|
||||
@@ -9582,6 +9619,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
? tp->type->under : tp->type;
|
||||
int op = fldloadop(tp->type, fsz);
|
||||
int off = localfind(locals, n->lhs->str);
|
||||
/* C-t3 (#48): GLOBAL tuple base — LEAQ the
|
||||
* mangled symbol into CX and read at CX+foff,
|
||||
* the struct-field global pattern below.
|
||||
* Pre-C-t3 localfind's 0 silently read the
|
||||
* stack frame. */
|
||||
int base_reg = D_BP;
|
||||
int base_disp = off;
|
||||
if (off == 0 && let_islet(n->lhs->str)) {
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, n->lhs->str),
|
||||
areg(D_CX));
|
||||
base_reg = D_CX;
|
||||
base_disp = 0;
|
||||
}
|
||||
/* f64/f32 tuple field must ride X0 via MOVSD/MOVSS;
|
||||
* the integer fldloadop left it in AX (#103 FACE Z).
|
||||
* Mirrors the struct-field float load at cgen.c:1462,
|
||||
@@ -9589,7 +9640,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int tup_isf32 = 0;
|
||||
if (fld_isfloat(tp->type, &tup_isf32)) {
|
||||
int mov = tup_isf32 ? A_MOVSS : A_MOVSD;
|
||||
ins2(c, mov, amem(D_BP, off + foff),
|
||||
ins2(c, mov,
|
||||
amem(base_reg, base_disp + foff),
|
||||
areg(D_X0));
|
||||
break;
|
||||
}
|
||||
@@ -9599,15 +9651,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* slice-rhs convention (#1/Phase 3 collapse).
|
||||
* UNLIKE the field arms there is no slice-element
|
||||
* sibling here, so the triple is hand-authored;
|
||||
* base is BP (frame, not a target reg) so the
|
||||
* canonical ptr/len/cap order has no clobber risk. */
|
||||
* base is BP (frame) or CX (global base — CX is
|
||||
* written LAST so it survives the +0/+8 reads). */
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 8), areg(D_BX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 16), areg(D_CX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg, base_disp + foff + 0),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg, base_disp + foff + 8),
|
||||
areg(D_BX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(base_reg, base_disp + foff + 16),
|
||||
areg(D_CX));
|
||||
break;
|
||||
}
|
||||
ins2(c, op, amem(D_BP, off + foff), areg(D_AX));
|
||||
ins2(c, op, amem(base_reg, base_disp + foff),
|
||||
areg(D_AX));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -13333,6 +13392,88 @@ emit_strarray_data(FILE *out, Cg *c, const char *directive,
|
||||
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
|
||||
* + LE len + 8 zero cap) with a DATAR patching the ptr word at the
|
||||
* element's slot offset — the per-element twin of the scalar-str-global
|
||||
* arm, offset like emit_strarray_data's rows. Elements must reduce to
|
||||
* int (fold_int_literal) or str literals; anything else returns 0 and
|
||||
* the caller loud-stops (rule 7 — pre-C-t3 the whole definition was
|
||||
* SILENTLY skipped and reads saw BP-frame garbage). rhs == NULL
|
||||
* zero-inits the slot. */
|
||||
static int
|
||||
emit_tuple_data(FILE *out, Cg *c, const char *name, const char *module,
|
||||
Type *t, Node *rhs)
|
||||
{
|
||||
Type *u = type_unwrap(t);
|
||||
if (u == NULL || u->kind != TY_TUPLE) return 0;
|
||||
const char *sym = mod_mangle_value(c, name, module);
|
||||
if (rhs == NULL) {
|
||||
fprintf(out, "DATAW %s(SB),\"", sym);
|
||||
for (int i = 0; i < (int)u->size; i++)
|
||||
emit_data_byte(out, 0);
|
||||
fputs("\"\n", out);
|
||||
return 1;
|
||||
}
|
||||
if (rhs->kind != N_TUPLE) return 0;
|
||||
/* Validate: every cast-peeled element folds (int) or is a strlit
|
||||
* in a str slot. Two-pass so a partial row never reaches the
|
||||
* output (emit_array_data precedent). */
|
||||
Tparam *tp = u->params;
|
||||
for (Node *e = rhs->list; e; e = e->next, tp = tp ? tp->next : NULL) {
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
if (ev == NULL) return 0;
|
||||
int wide = tp && (type_isstr(tp->type)
|
||||
|| type_isslice(tp->type));
|
||||
if (wide) {
|
||||
if (ev->kind != N_STRLIT) return 0;
|
||||
continue;
|
||||
}
|
||||
u64 v;
|
||||
if (!fold_int_literal(ev, &v)) return 0;
|
||||
}
|
||||
fprintf(out, "DATAW %s(SB),\"", sym);
|
||||
tp = u->params;
|
||||
for (Node *e = rhs->list; e; e = e->next, tp = tp ? tp->next : NULL) {
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
int wide = tp && (type_isstr(tp->type)
|
||||
|| type_isslice(tp->type));
|
||||
if (wide) {
|
||||
u64 v = ev->strlen;
|
||||
for (int i = 0; i < 8; i++) emit_data_byte(out, 0);
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff));
|
||||
for (int i = 16; i < (int)ty_str->size; i++)
|
||||
emit_data_byte(out, 0);
|
||||
continue;
|
||||
}
|
||||
u64 v = 0;
|
||||
(void)fold_int_literal(ev, &v);
|
||||
for (int i = 0; i < 8; i++)
|
||||
emit_data_byte(out, (u8)((v >> (i * 8)) & 0xff));
|
||||
}
|
||||
fputs("\"\n", out);
|
||||
int foff = 0;
|
||||
tp = u->params;
|
||||
for (Node *e = rhs->list; e; e = e->next, tp = tp ? tp->next : NULL) {
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST) ev = ev->lhs;
|
||||
int wide = tp && (type_isstr(tp->type)
|
||||
|| type_isslice(tp->type));
|
||||
if (wide && ev->strlen > 0) {
|
||||
const char *lab = intern_strlit(c, ev->str,
|
||||
ev->strlen);
|
||||
fprintf(out, "DATAR %s+%d(SB),%s(SB)\n",
|
||||
sym, foff, lab);
|
||||
}
|
||||
foff += wide ? (int)ty_str->size : 8;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* emit_array_data — opens DATA/DATAW prefix on validate success, then
|
||||
* emits payload. Two-pass keeps emit-on-failure from emitting partial
|
||||
* bytes (would corrupt the asm if rhs reduces partway through). */
|
||||
@@ -13441,6 +13582,26 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
d->str, d->module, d->type, d->rhs);
|
||||
continue;
|
||||
}
|
||||
/* C-t3 (#48): tuple global — slot-laid DATAW row (+ DATAR
|
||||
* ptr patches for str elements). Unsupported element inits
|
||||
* die LOUD; pre-C-t3 the whole definition was silently
|
||||
* skipped (no DATA, no diagnostic) and reads saw BP-frame
|
||||
* garbage. */
|
||||
{
|
||||
Type *tu = type_unwrap(d->type);
|
||||
if (tu != NULL && tu->kind == TY_TUPLE) {
|
||||
Node *tr = d->rhs;
|
||||
while (tr != NULL && tr->kind == N_CAST)
|
||||
tr = tr->lhs;
|
||||
if (!emit_tuple_data(out, c, d->str,
|
||||
d->module, d->type, tr))
|
||||
fatal("global tuple let `%s`: "
|
||||
"unsupported element init "
|
||||
"(int/str literals 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`
|
||||
@@ -13696,6 +13857,28 @@ let_pre_intern(Cg *c, Node *file)
|
||||
continue;
|
||||
}
|
||||
}
|
||||
/* C-t3 (#48): tuple global — pre-intern str-element literals
|
||||
* in element order so emit_tuple_data's DATAR rows find
|
||||
* their _S_ rodata rows (the #18 array-arm pattern). */
|
||||
if (u != NULL && u->kind == TY_TUPLE
|
||||
&& r != NULL && r->kind == N_TUPLE) {
|
||||
Tparam *tp = u->params;
|
||||
for (Node *e = r->list; e;
|
||||
e = e->next, tp = tp ? tp->next : NULL) {
|
||||
Node *ev = e;
|
||||
while (ev && ev->kind == N_CAST)
|
||||
ev = ev->lhs;
|
||||
if (ev == NULL || ev->kind != N_STRLIT)
|
||||
continue;
|
||||
if (!(tp && (type_isstr(tp->type)
|
||||
|| type_isslice(tp->type))))
|
||||
continue;
|
||||
if (ev->strlen > 0)
|
||||
(void)intern_strlit(c, ev->str,
|
||||
ev->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