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
|
* loads/stores via cgindex. Mirror
|
||||||
* of selfhost letemitsize's
|
* of selfhost letemitsize's
|
||||||
* N_TARRAY branch. */
|
* 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:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -3505,6 +3513,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
goto ident_done;
|
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)
|
if (let_islet(n->str)
|
||||||
&& (let_isstr(n->type) || let_isslice(n->type))) {
|
&& (let_isstr(n->type) || let_isslice(n->type))) {
|
||||||
/* Top-level str/slice global: load each word
|
/* Top-level str/slice global: load each word
|
||||||
@@ -6681,9 +6703,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
idx--;
|
idx--;
|
||||||
}
|
}
|
||||||
int off = localfind(locals, a->lhs->str);
|
int off = localfind(locals, a->lhs->str);
|
||||||
ins2(c, A_MOVQ,
|
/* C-t3 (#48): GLOBAL tuple base —
|
||||||
amem(D_BP, off + foff + 8),
|
* pre-fix localfind's 0 read the .len
|
||||||
areg(D_AX));
|
* 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;
|
lendone = 1;
|
||||||
}
|
}
|
||||||
/* struct-field N_DOT (`len(s.field)`): the old
|
/* 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;
|
? tp->type->under : tp->type;
|
||||||
int op = fldloadop(tp->type, fsz);
|
int op = fldloadop(tp->type, fsz);
|
||||||
int off = localfind(locals, n->lhs->str);
|
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;
|
/* f64/f32 tuple field must ride X0 via MOVSD/MOVSS;
|
||||||
* the integer fldloadop left it in AX (#103 FACE Z).
|
* the integer fldloadop left it in AX (#103 FACE Z).
|
||||||
* Mirrors the struct-field float load at cgen.c:1462,
|
* 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;
|
int tup_isf32 = 0;
|
||||||
if (fld_isfloat(tp->type, &tup_isf32)) {
|
if (fld_isfloat(tp->type, &tup_isf32)) {
|
||||||
int mov = tup_isf32 ? A_MOVSS : A_MOVSD;
|
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));
|
areg(D_X0));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -9599,15 +9651,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
* slice-rhs convention (#1/Phase 3 collapse).
|
* slice-rhs convention (#1/Phase 3 collapse).
|
||||||
* UNLIKE the field arms there is no slice-element
|
* UNLIKE the field arms there is no slice-element
|
||||||
* sibling here, so the triple is hand-authored;
|
* sibling here, so the triple is hand-authored;
|
||||||
* base is BP (frame, not a target reg) so the
|
* base is BP (frame) or CX (global base — CX is
|
||||||
* canonical ptr/len/cap order has no clobber risk. */
|
* written LAST so it survives the +0/+8 reads). */
|
||||||
if (fu && fu->kind == TY_STR) {
|
if (fu && fu->kind == TY_STR) {
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 0), areg(D_AX));
|
ins2(c, A_MOVQ,
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 8), areg(D_BX));
|
amem(base_reg, base_disp + foff + 0),
|
||||||
ins2(c, A_MOVQ, amem(D_BP, off + foff + 16), areg(D_CX));
|
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;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
@@ -13333,6 +13392,88 @@ emit_strarray_data(FILE *out, Cg *c, const char *directive,
|
|||||||
return 1;
|
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
|
/* emit_array_data — opens DATA/DATAW prefix on validate success, then
|
||||||
* emits payload. Two-pass keeps emit-on-failure from emitting partial
|
* emits payload. Two-pass keeps emit-on-failure from emitting partial
|
||||||
* bytes (would corrupt the asm if rhs reduces partway through). */
|
* 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);
|
d->str, d->module, d->type, d->rhs);
|
||||||
continue;
|
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
|
/* #129 A.2: gate `!let_isstruct` so an 8B struct lit
|
||||||
* (`struct { i32, i32 }`, `struct { f32, f32 }`, …) does
|
* (`struct { i32, i32 }`, `struct { f32, f32 }`, …) does
|
||||||
* NOT short-circuit through the scalar 8B `fold_int_literal`
|
* NOT short-circuit through the scalar 8B `fold_int_literal`
|
||||||
@@ -13696,6 +13857,28 @@ let_pre_intern(Cg *c, Node *file)
|
|||||||
continue;
|
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 (let_emit_size(d->type) != (int)ty_str->size) continue;
|
||||||
if (r == NULL || r->kind != N_STRLIT) continue;
|
if (r == NULL || r->kind != N_STRLIT) continue;
|
||||||
if (r->strlen == 0) continue;
|
if (r->strlen == 0) continue;
|
||||||
|
|||||||
@@ -21180,6 +21180,24 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
||||||
// never-defined) drop through to the silent return.
|
// never-defined) drop through to the silent return.
|
||||||
if (isletvar(c, nm)) {
|
if (isletvar(c, nm)) {
|
||||||
|
// 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 (cgtupleslottocursor 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. Mirrors the cstage cgexpr
|
||||||
|
// non-local ident guard.
|
||||||
|
let gtt: *node = letvartnode(c, nm);
|
||||||
|
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
|
||||||
|
gtt = aliaslookup(c, gtt.str);
|
||||||
|
};
|
||||||
|
if (gtt != nil) {
|
||||||
|
if (gtt.kind == nkind.N_TTUPLE) {
|
||||||
|
let mgt: str = "#48: global tuple as a first-class value unwired (element reads only; rule 7)\n";
|
||||||
|
os.write(2, mgt.ptr, mgt.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
let isstr: bool = letvarisstr(c, nm);
|
let isstr: bool = letvarisstr(c, nm);
|
||||||
let issl: bool = letvarisslice(c, nm);
|
let issl: bool = letvarisslice(c, nm);
|
||||||
if (isstr || issl) {
|
if (isstr || issl) {
|
||||||
@@ -23082,6 +23100,79 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level TUPLE global positional read (C-t3, #48): `g.N` —
|
||||||
|
// LEAQ name(SB) into CX, then load at the element's SLOT offset
|
||||||
|
// (C-t0 layout), the element's natural width. Mirrors the local
|
||||||
|
// N_TTUPLE arm above and cstage's N_DOT TY_TUPLE global base.
|
||||||
|
// Pre-C-t3 the tuple global wasn't in collectlets at all (no
|
||||||
|
// DATA) and the module-leaf fallback mis-emitted the field index
|
||||||
|
// as a symbol (`MOVQ 0(SB), AX`).
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
|
let gtt: *node = letvartnode(c, lhs.str);
|
||||||
|
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
|
||||||
|
gtt = aliaslookup(c, gtt.str);
|
||||||
|
};
|
||||||
|
if (gtt != nil) {
|
||||||
|
if (gtt.kind == nkind.N_TTUPLE) {
|
||||||
|
let gidx: i32 = fldnumidx(fld);
|
||||||
|
if (gidx >= 0) {
|
||||||
|
let gtp: *node = gtt.list;
|
||||||
|
let gfoff: i32 = 0;
|
||||||
|
let gi: i32 = 0;
|
||||||
|
for (gi < gidx) {
|
||||||
|
if (gtp == nil) { gi = gidx; }
|
||||||
|
else {
|
||||||
|
gfoff += slotsize(c, gtp.lhs);
|
||||||
|
gtp = gtp.next;
|
||||||
|
gi += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (gtp != nil) {
|
||||||
|
let gpt: *node = gtp.lhs;
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
if (isstrtype(c, gpt)) {
|
||||||
|
// CX (the base) is written LAST so
|
||||||
|
// it survives the +0/+8 reads.
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 0): i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 8): i64, "CX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 16): i64, "CX");
|
||||||
|
emitline(", CX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (isfloattype(c, gpt)) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, gpt)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(gfoff: i64, "CX");
|
||||||
|
emitline(", X0\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let gnsz: i32 = 8;
|
||||||
|
let gpti: *tinfo = gpt.type_: *tinfo;
|
||||||
|
if (gpti != nil) { gnsz = gpti.size: i32; };
|
||||||
|
let gop: str = tnodeloadop(c, gpt, gnsz);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(gop);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(gfoff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Top-level struct global field read — LEAQ name(SB), CX then
|
// Top-level struct global field read — LEAQ name(SB), CX then
|
||||||
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||||
// branch above, swapping the BP frame slot for the global VA.
|
// branch above, swapping the BP frame slot for the global VA.
|
||||||
@@ -25938,6 +26029,45 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): GLOBAL tuple len(g.N) —
|
||||||
|
// LEAQ name(SB) into CX, .len word at
|
||||||
|
// the SLOT offset + 8. Graduates the
|
||||||
|
// C5 loud-stop this shape previously
|
||||||
|
// hit; twin of the cgdot global-tuple
|
||||||
|
// arm and cstage's #235 global base.
|
||||||
|
if (lc == nil) {
|
||||||
|
let gtn: *node = letvartnode(c, a.lhs.str);
|
||||||
|
for (gtn != nil && gtn.kind == nkind.N_TNAME) {
|
||||||
|
gtn = aliaslookup(c, gtn.str);
|
||||||
|
};
|
||||||
|
if (gtn != nil) {
|
||||||
|
if (gtn.kind == nkind.N_TTUPLE) {
|
||||||
|
let gidx: i32 = fldnumidx(a.str);
|
||||||
|
if (gidx >= 0) {
|
||||||
|
let gtp: *node = gtn.list;
|
||||||
|
let gfoff: i32 = 0;
|
||||||
|
let gi: i32 = 0;
|
||||||
|
for (gi < gidx) {
|
||||||
|
if (gtp == nil) { gi = gidx; }
|
||||||
|
else {
|
||||||
|
gfoff += slotsize(c, gtp.lhs);
|
||||||
|
gtp = gtp.next;
|
||||||
|
gi += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (gtp != nil) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, a.lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 8): i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// struct-field N_DOT (`len(s.field)`): the
|
// struct-field N_DOT (`len(s.field)`): the
|
||||||
// old fallback returned .ptr as the length.
|
// old fallback returned .ptr as the length.
|
||||||
// Falls to the resolver route below.
|
// Falls to the resolver route below.
|
||||||
@@ -35519,6 +35649,27 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
|
|||||||
};
|
};
|
||||||
return alen * esz;
|
return alen * esz;
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): tuple global — per-element slot sum (C-t0
|
||||||
|
// layout: a str/slice its header, everything else one 8B
|
||||||
|
// eightbyte). Mirrors cstage let_emit_size TY_TUPLE (u->size,
|
||||||
|
// the checker slot sum). Pre-C-t3 the 0 here kept tuple
|
||||||
|
// globals out of collectlets entirely — no DATA emitted, and
|
||||||
|
// the module-leaf fallback mis-emitted the field index as a
|
||||||
|
// symbol (`MOVQ 0(SB), AX`).
|
||||||
|
if (t.kind == nkind.N_TTUPLE) {
|
||||||
|
let tsum: i32 = 0;
|
||||||
|
let p: *node = t.list;
|
||||||
|
for (p != nil) {
|
||||||
|
let et: *node = p.lhs;
|
||||||
|
if (isstrtype(c, et) || isslicetype(c, et)) {
|
||||||
|
tsum += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
tsum += 8;
|
||||||
|
};
|
||||||
|
p = p.next;
|
||||||
|
};
|
||||||
|
return tsum;
|
||||||
|
};
|
||||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
@@ -35853,6 +36004,42 @@ export fn letpreintern(c: *cgen, file: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): tuple global — pre-intern str-element
|
||||||
|
// literals in element order so emitletdataw's tuple
|
||||||
|
// arm's DATAR rows find their _S_ rodata rows (the
|
||||||
|
// #18 array-arm pattern; cstage let_pre_intern twin).
|
||||||
|
if (!handled && r != nil && d.lhs != nil) {
|
||||||
|
if (r.kind == nkind.N_TUPLE) {
|
||||||
|
let tlt: *node = d.lhs;
|
||||||
|
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
|
||||||
|
tlt = aliaslookup(c, tlt.str);
|
||||||
|
};
|
||||||
|
if (tlt != nil) {
|
||||||
|
if (tlt.kind == nkind.N_TTUPLE) {
|
||||||
|
handled = true;
|
||||||
|
let tp: *node = tlt.list;
|
||||||
|
let e: *node = r.list;
|
||||||
|
for (e != nil && tp != nil) {
|
||||||
|
let et: *node = tp.lhs;
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) {
|
||||||
|
ev = ev.lhs;
|
||||||
|
};
|
||||||
|
if (ev != nil) {
|
||||||
|
if (ev.kind == nkind.N_STRLIT
|
||||||
|
&& (isstrtype(c, et) || isslicetype(c, et))) {
|
||||||
|
if (ev.str.len > 0) {
|
||||||
|
internstrlit(c, ev.str);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
tp = tp.next;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
// #43: route the str-let gate through primtypesize so
|
// #43: route the str-let gate through primtypesize so
|
||||||
@@ -36661,6 +36848,128 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
|||||||
emitline(".d(SB)\n");
|
emitline(".d(SB)\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// emittupledata — module-level `let g: (T0, T1, ...) = (v0, ...);`
|
||||||
|
// 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 emitstrarraydata's rows. Elements must reduce to int
|
||||||
|
// (foldintliteral) or str literals; anything else returns false and the
|
||||||
|
// caller loud-stops (rule 7 — pre-C-t3 the whole definition was
|
||||||
|
// SILENTLY skipped and reads saw garbage). rhs == nil zero-inits the
|
||||||
|
// slot. Mirror of cstage emit_tuple_data.
|
||||||
|
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
|
||||||
|
if (tt == nil) { return false; };
|
||||||
|
if (rhs == nil) {
|
||||||
|
let zsz: i32 = 0;
|
||||||
|
let p0: *node = tt.list;
|
||||||
|
for (p0 != nil) {
|
||||||
|
let et0: *node = p0.lhs;
|
||||||
|
if (isstrtype(c, et0) || isslicetype(c, et0)) {
|
||||||
|
zsz += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
zsz += 8;
|
||||||
|
};
|
||||||
|
p0 = p0.next;
|
||||||
|
};
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let zi: i32 = 0;
|
||||||
|
for (zi < zsz) { emitdatawbyte(0u8); zi += 1; };
|
||||||
|
emitline("\"\n");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (rhs.kind != nkind.N_TUPLE) { return false; };
|
||||||
|
// Validate first (two-pass, emitarraydata precedent) so a partial
|
||||||
|
// row never reaches the output.
|
||||||
|
let tp: *node = tt.list;
|
||||||
|
let e: *node = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
if (ev == nil) { return false; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
if (ev.kind != nkind.N_STRLIT) { return false; };
|
||||||
|
} else {
|
||||||
|
let v: u64 = 0u64;
|
||||||
|
if (!foldintliteral(ev, &v)) { return false; };
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
tp = tt.list;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < 8) { emitdatawbyte(0u8); i += 1; };
|
||||||
|
let lv: u64 = ev.str.len: u64;
|
||||||
|
i = 0;
|
||||||
|
for (i < 8) {
|
||||||
|
emitdatawbyte((lv & 255u64): u8);
|
||||||
|
lv = lv >> 8u64;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
i = 16;
|
||||||
|
let ssz: i32 = primtypesize("str"): i32;
|
||||||
|
for (i < ssz) { emitdatawbyte(0u8); i += 1; };
|
||||||
|
} else {
|
||||||
|
let v: u64 = 0u64;
|
||||||
|
foldintliteral(ev, &v);
|
||||||
|
let i: i32 = 0;
|
||||||
|
let nv: u64 = v;
|
||||||
|
for (i < 8) {
|
||||||
|
emitdatawbyte((nv & 255u64): u8);
|
||||||
|
nv = nv >> 8u64;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
let foff: i32 = 0;
|
||||||
|
tp = tt.list;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
if (ev.str.len > 0) {
|
||||||
|
let lab: str = internstrlit(c, ev.str);
|
||||||
|
emitline("DATAR ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("+");
|
||||||
|
emitint(foff: i64);
|
||||||
|
emitline("(SB),");
|
||||||
|
emitbytes( lab.ptr, lab.len: u64);
|
||||||
|
emitline("(SB)\n");
|
||||||
|
};
|
||||||
|
foff += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
foff += 8;
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
@@ -36670,6 +36979,36 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
let issg: bool = letvarisstruct(c, nm);
|
let issg: bool = letvarisstruct(c, nm);
|
||||||
let fsz: i32 = letvarisfloat(c, nm);
|
let fsz: i32 = letvarisfloat(c, nm);
|
||||||
|
// C-t3 (#48): tuple global — slot-laid DATAW
|
||||||
|
// row (+ DATAR ptr patches for str elements)
|
||||||
|
// via emittupledata. Unsupported element
|
||||||
|
// inits die LOUD; pre-C-t3 the definition was
|
||||||
|
// silently skipped (no DATA, no diagnostic)
|
||||||
|
// and reads saw garbage. The istup gate also
|
||||||
|
// keeps a tuple out of the sz==8 / str-size
|
||||||
|
// arms below (a 24B tuple == str size).
|
||||||
|
let tlt: *node = d.lhs;
|
||||||
|
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
|
||||||
|
tlt = aliaslookup(c, tlt.str);
|
||||||
|
};
|
||||||
|
let istup: bool = false;
|
||||||
|
if (tlt != nil) {
|
||||||
|
if (tlt.kind == nkind.N_TTUPLE) {
|
||||||
|
istup = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (istup) {
|
||||||
|
let tr: *node = d.rhs;
|
||||||
|
for (tr != nil) {
|
||||||
|
if (tr.kind != nkind.N_CAST) { break; };
|
||||||
|
tr = tr.lhs;
|
||||||
|
};
|
||||||
|
if (!emittupledata(c, nm, d.nmod, tlt, tr)) {
|
||||||
|
let mtg: str = "global tuple let: unsupported element init (int/str literals only; rule 7)\n";
|
||||||
|
os.write(2, mtg.ptr, mtg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
if (fsz > 0) {
|
if (fsz > 0) {
|
||||||
// Float global: routes through the
|
// Float global: routes through the
|
||||||
// emitfloatlitdata SSoT helper, shared
|
// emitfloatlitdata SSoT helper, shared
|
||||||
@@ -36704,7 +37043,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
if (d.lhs != nil) {
|
if (d.lhs != nil) {
|
||||||
if (d.lhs.kind == nkind.N_TARRAY) { isarr8 = true; };
|
if (d.lhs.kind == nkind.N_TARRAY) { isarr8 = true; };
|
||||||
};
|
};
|
||||||
if (sz == 8 && !issg && fsz == 0 && !isarr8) {
|
if (sz == 8 && !issg && fsz == 0 && !isarr8 && !istup) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -36735,7 +37074,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) {
|
if (sz == primtypesize("str"): i32 && !issg && !istup && !letvarisslice(c, nm)) {
|
||||||
let r: *node = d.rhs;
|
let r: *node = d.rhs;
|
||||||
for (r != nil) {
|
for (r != nil) {
|
||||||
if (r.kind != nkind.N_CAST) { break; };
|
if (r.kind != nkind.N_CAST) { break; };
|
||||||
|
|||||||
@@ -996,6 +996,27 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
|
|||||||
};
|
};
|
||||||
return alen * esz;
|
return alen * esz;
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): tuple global — per-element slot sum (C-t0
|
||||||
|
// layout: a str/slice its header, everything else one 8B
|
||||||
|
// eightbyte). Mirrors cstage let_emit_size TY_TUPLE (u->size,
|
||||||
|
// the checker slot sum). Pre-C-t3 the 0 here kept tuple
|
||||||
|
// globals out of collectlets entirely — no DATA emitted, and
|
||||||
|
// the module-leaf fallback mis-emitted the field index as a
|
||||||
|
// symbol (`MOVQ 0(SB), AX`).
|
||||||
|
if (t.kind == nkind.N_TTUPLE) {
|
||||||
|
let tsum: i32 = 0;
|
||||||
|
let p: *node = t.list;
|
||||||
|
for (p != nil) {
|
||||||
|
let et: *node = p.lhs;
|
||||||
|
if (isstrtype(c, et) || isslicetype(c, et)) {
|
||||||
|
tsum += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
tsum += 8;
|
||||||
|
};
|
||||||
|
p = p.next;
|
||||||
|
};
|
||||||
|
return tsum;
|
||||||
|
};
|
||||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
@@ -1330,6 +1351,42 @@ export fn letpreintern(c: *cgen, file: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): tuple global — pre-intern str-element
|
||||||
|
// literals in element order so emitletdataw's tuple
|
||||||
|
// arm's DATAR rows find their _S_ rodata rows (the
|
||||||
|
// #18 array-arm pattern; cstage let_pre_intern twin).
|
||||||
|
if (!handled && r != nil && d.lhs != nil) {
|
||||||
|
if (r.kind == nkind.N_TUPLE) {
|
||||||
|
let tlt: *node = d.lhs;
|
||||||
|
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
|
||||||
|
tlt = aliaslookup(c, tlt.str);
|
||||||
|
};
|
||||||
|
if (tlt != nil) {
|
||||||
|
if (tlt.kind == nkind.N_TTUPLE) {
|
||||||
|
handled = true;
|
||||||
|
let tp: *node = tlt.list;
|
||||||
|
let e: *node = r.list;
|
||||||
|
for (e != nil && tp != nil) {
|
||||||
|
let et: *node = tp.lhs;
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) {
|
||||||
|
ev = ev.lhs;
|
||||||
|
};
|
||||||
|
if (ev != nil) {
|
||||||
|
if (ev.kind == nkind.N_STRLIT
|
||||||
|
&& (isstrtype(c, et) || isslicetype(c, et))) {
|
||||||
|
if (ev.str.len > 0) {
|
||||||
|
internstrlit(c, ev.str);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
tp = tp.next;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
// #43: route the str-let gate through primtypesize so
|
// #43: route the str-let gate through primtypesize so
|
||||||
@@ -2138,6 +2195,128 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
|||||||
emitline(".d(SB)\n");
|
emitline(".d(SB)\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// emittupledata — module-level `let g: (T0, T1, ...) = (v0, ...);`
|
||||||
|
// 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 emitstrarraydata's rows. Elements must reduce to int
|
||||||
|
// (foldintliteral) or str literals; anything else returns false and the
|
||||||
|
// caller loud-stops (rule 7 — pre-C-t3 the whole definition was
|
||||||
|
// SILENTLY skipped and reads saw garbage). rhs == nil zero-inits the
|
||||||
|
// slot. Mirror of cstage emit_tuple_data.
|
||||||
|
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
|
||||||
|
if (tt == nil) { return false; };
|
||||||
|
if (rhs == nil) {
|
||||||
|
let zsz: i32 = 0;
|
||||||
|
let p0: *node = tt.list;
|
||||||
|
for (p0 != nil) {
|
||||||
|
let et0: *node = p0.lhs;
|
||||||
|
if (isstrtype(c, et0) || isslicetype(c, et0)) {
|
||||||
|
zsz += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
zsz += 8;
|
||||||
|
};
|
||||||
|
p0 = p0.next;
|
||||||
|
};
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let zi: i32 = 0;
|
||||||
|
for (zi < zsz) { emitdatawbyte(0u8); zi += 1; };
|
||||||
|
emitline("\"\n");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (rhs.kind != nkind.N_TUPLE) { return false; };
|
||||||
|
// Validate first (two-pass, emitarraydata precedent) so a partial
|
||||||
|
// row never reaches the output.
|
||||||
|
let tp: *node = tt.list;
|
||||||
|
let e: *node = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
if (ev == nil) { return false; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
if (ev.kind != nkind.N_STRLIT) { return false; };
|
||||||
|
} else {
|
||||||
|
let v: u64 = 0u64;
|
||||||
|
if (!foldintliteral(ev, &v)) { return false; };
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
tp = tt.list;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < 8) { emitdatawbyte(0u8); i += 1; };
|
||||||
|
let lv: u64 = ev.str.len: u64;
|
||||||
|
i = 0;
|
||||||
|
for (i < 8) {
|
||||||
|
emitdatawbyte((lv & 255u64): u8);
|
||||||
|
lv = lv >> 8u64;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
i = 16;
|
||||||
|
let ssz: i32 = primtypesize("str"): i32;
|
||||||
|
for (i < ssz) { emitdatawbyte(0u8); i += 1; };
|
||||||
|
} else {
|
||||||
|
let v: u64 = 0u64;
|
||||||
|
foldintliteral(ev, &v);
|
||||||
|
let i: i32 = 0;
|
||||||
|
let nv: u64 = v;
|
||||||
|
for (i < 8) {
|
||||||
|
emitdatawbyte((nv & 255u64): u8);
|
||||||
|
nv = nv >> 8u64;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
let foff: i32 = 0;
|
||||||
|
tp = tt.list;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
if (ev.str.len > 0) {
|
||||||
|
let lab: str = internstrlit(c, ev.str);
|
||||||
|
emitline("DATAR ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("+");
|
||||||
|
emitint(foff: i64);
|
||||||
|
emitline("(SB),");
|
||||||
|
emitbytes( lab.ptr, lab.len: u64);
|
||||||
|
emitline("(SB)\n");
|
||||||
|
};
|
||||||
|
foff += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
foff += 8;
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
@@ -2147,6 +2326,36 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
let issg: bool = letvarisstruct(c, nm);
|
let issg: bool = letvarisstruct(c, nm);
|
||||||
let fsz: i32 = letvarisfloat(c, nm);
|
let fsz: i32 = letvarisfloat(c, nm);
|
||||||
|
// C-t3 (#48): tuple global — slot-laid DATAW
|
||||||
|
// row (+ DATAR ptr patches for str elements)
|
||||||
|
// via emittupledata. Unsupported element
|
||||||
|
// inits die LOUD; pre-C-t3 the definition was
|
||||||
|
// silently skipped (no DATA, no diagnostic)
|
||||||
|
// and reads saw garbage. The istup gate also
|
||||||
|
// keeps a tuple out of the sz==8 / str-size
|
||||||
|
// arms below (a 24B tuple == str size).
|
||||||
|
let tlt: *node = d.lhs;
|
||||||
|
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
|
||||||
|
tlt = aliaslookup(c, tlt.str);
|
||||||
|
};
|
||||||
|
let istup: bool = false;
|
||||||
|
if (tlt != nil) {
|
||||||
|
if (tlt.kind == nkind.N_TTUPLE) {
|
||||||
|
istup = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (istup) {
|
||||||
|
let tr: *node = d.rhs;
|
||||||
|
for (tr != nil) {
|
||||||
|
if (tr.kind != nkind.N_CAST) { break; };
|
||||||
|
tr = tr.lhs;
|
||||||
|
};
|
||||||
|
if (!emittupledata(c, nm, d.nmod, tlt, tr)) {
|
||||||
|
let mtg: str = "global tuple let: unsupported element init (int/str literals only; rule 7)\n";
|
||||||
|
os.write(2, mtg.ptr, mtg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
if (fsz > 0) {
|
if (fsz > 0) {
|
||||||
// Float global: routes through the
|
// Float global: routes through the
|
||||||
// emitfloatlitdata SSoT helper, shared
|
// emitfloatlitdata SSoT helper, shared
|
||||||
@@ -2181,7 +2390,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
if (d.lhs != nil) {
|
if (d.lhs != nil) {
|
||||||
if (d.lhs.kind == nkind.N_TARRAY) { isarr8 = true; };
|
if (d.lhs.kind == nkind.N_TARRAY) { isarr8 = true; };
|
||||||
};
|
};
|
||||||
if (sz == 8 && !issg && fsz == 0 && !isarr8) {
|
if (sz == 8 && !issg && fsz == 0 && !isarr8 && !istup) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -2212,7 +2421,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) {
|
if (sz == primtypesize("str"): i32 && !issg && !istup && !letvarisslice(c, nm)) {
|
||||||
let r: *node = d.rhs;
|
let r: *node = d.rhs;
|
||||||
for (r != nil) {
|
for (r != nil) {
|
||||||
if (r.kind != nkind.N_CAST) { break; };
|
if (r.kind != nkind.N_CAST) { break; };
|
||||||
|
|||||||
@@ -903,6 +903,24 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
||||||
// never-defined) drop through to the silent return.
|
// never-defined) drop through to the silent return.
|
||||||
if (isletvar(c, nm)) {
|
if (isletvar(c, nm)) {
|
||||||
|
// 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 (cgtupleslottocursor 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. Mirrors the cstage cgexpr
|
||||||
|
// non-local ident guard.
|
||||||
|
let gtt: *node = letvartnode(c, nm);
|
||||||
|
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
|
||||||
|
gtt = aliaslookup(c, gtt.str);
|
||||||
|
};
|
||||||
|
if (gtt != nil) {
|
||||||
|
if (gtt.kind == nkind.N_TTUPLE) {
|
||||||
|
let mgt: str = "#48: global tuple as a first-class value unwired (element reads only; rule 7)\n";
|
||||||
|
os.write(2, mgt.ptr, mgt.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
let isstr: bool = letvarisstr(c, nm);
|
let isstr: bool = letvarisstr(c, nm);
|
||||||
let issl: bool = letvarisslice(c, nm);
|
let issl: bool = letvarisslice(c, nm);
|
||||||
if (isstr || issl) {
|
if (isstr || issl) {
|
||||||
@@ -2805,6 +2823,79 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level TUPLE global positional read (C-t3, #48): `g.N` —
|
||||||
|
// LEAQ name(SB) into CX, then load at the element's SLOT offset
|
||||||
|
// (C-t0 layout), the element's natural width. Mirrors the local
|
||||||
|
// N_TTUPLE arm above and cstage's N_DOT TY_TUPLE global base.
|
||||||
|
// Pre-C-t3 the tuple global wasn't in collectlets at all (no
|
||||||
|
// DATA) and the module-leaf fallback mis-emitted the field index
|
||||||
|
// as a symbol (`MOVQ 0(SB), AX`).
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
|
let gtt: *node = letvartnode(c, lhs.str);
|
||||||
|
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
|
||||||
|
gtt = aliaslookup(c, gtt.str);
|
||||||
|
};
|
||||||
|
if (gtt != nil) {
|
||||||
|
if (gtt.kind == nkind.N_TTUPLE) {
|
||||||
|
let gidx: i32 = fldnumidx(fld);
|
||||||
|
if (gidx >= 0) {
|
||||||
|
let gtp: *node = gtt.list;
|
||||||
|
let gfoff: i32 = 0;
|
||||||
|
let gi: i32 = 0;
|
||||||
|
for (gi < gidx) {
|
||||||
|
if (gtp == nil) { gi = gidx; }
|
||||||
|
else {
|
||||||
|
gfoff += slotsize(c, gtp.lhs);
|
||||||
|
gtp = gtp.next;
|
||||||
|
gi += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (gtp != nil) {
|
||||||
|
let gpt: *node = gtp.lhs;
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
if (isstrtype(c, gpt)) {
|
||||||
|
// CX (the base) is written LAST so
|
||||||
|
// it survives the +0/+8 reads.
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 0): i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 8): i64, "CX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 16): i64, "CX");
|
||||||
|
emitline(", CX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (isfloattype(c, gpt)) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, gpt)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(gfoff: i64, "CX");
|
||||||
|
emitline(", X0\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let gnsz: i32 = 8;
|
||||||
|
let gpti: *tinfo = gpt.type_: *tinfo;
|
||||||
|
if (gpti != nil) { gnsz = gpti.size: i32; };
|
||||||
|
let gop: str = tnodeloadop(c, gpt, gnsz);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(gop);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(gfoff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Top-level struct global field read — LEAQ name(SB), CX then
|
// Top-level struct global field read — LEAQ name(SB), CX then
|
||||||
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||||
// branch above, swapping the BP frame slot for the global VA.
|
// branch above, swapping the BP frame slot for the global VA.
|
||||||
@@ -5661,6 +5752,45 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): GLOBAL tuple len(g.N) —
|
||||||
|
// LEAQ name(SB) into CX, .len word at
|
||||||
|
// the SLOT offset + 8. Graduates the
|
||||||
|
// C5 loud-stop this shape previously
|
||||||
|
// hit; twin of the cgdot global-tuple
|
||||||
|
// arm and cstage's #235 global base.
|
||||||
|
if (lc == nil) {
|
||||||
|
let gtn: *node = letvartnode(c, a.lhs.str);
|
||||||
|
for (gtn != nil && gtn.kind == nkind.N_TNAME) {
|
||||||
|
gtn = aliaslookup(c, gtn.str);
|
||||||
|
};
|
||||||
|
if (gtn != nil) {
|
||||||
|
if (gtn.kind == nkind.N_TTUPLE) {
|
||||||
|
let gidx: i32 = fldnumidx(a.str);
|
||||||
|
if (gidx >= 0) {
|
||||||
|
let gtp: *node = gtn.list;
|
||||||
|
let gfoff: i32 = 0;
|
||||||
|
let gi: i32 = 0;
|
||||||
|
for (gi < gidx) {
|
||||||
|
if (gtp == nil) { gi = gidx; }
|
||||||
|
else {
|
||||||
|
gfoff += slotsize(c, gtp.lhs);
|
||||||
|
gtp = gtp.next;
|
||||||
|
gi += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (gtp != nil) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, a.lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 8): i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// struct-field N_DOT (`len(s.field)`): the
|
// struct-field N_DOT (`len(s.field)`): the
|
||||||
// old fallback returned .ptr as the length.
|
// old fallback returned .ptr as the length.
|
||||||
// Falls to the resolver route below.
|
// Falls to the resolver route below.
|
||||||
|
|||||||
@@ -21180,6 +21180,24 @@ fn cgident(c: *cgen, n: *node) void = {
|
|||||||
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
// (AX, BX[, CX]). Names that aren't lets either (typos,
|
||||||
// never-defined) drop through to the silent return.
|
// never-defined) drop through to the silent return.
|
||||||
if (isletvar(c, nm)) {
|
if (isletvar(c, nm)) {
|
||||||
|
// 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 (cgtupleslottocursor 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. Mirrors the cstage cgexpr
|
||||||
|
// non-local ident guard.
|
||||||
|
let gtt: *node = letvartnode(c, nm);
|
||||||
|
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
|
||||||
|
gtt = aliaslookup(c, gtt.str);
|
||||||
|
};
|
||||||
|
if (gtt != nil) {
|
||||||
|
if (gtt.kind == nkind.N_TTUPLE) {
|
||||||
|
let mgt: str = "#48: global tuple as a first-class value unwired (element reads only; rule 7)\n";
|
||||||
|
os.write(2, mgt.ptr, mgt.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
let isstr: bool = letvarisstr(c, nm);
|
let isstr: bool = letvarisstr(c, nm);
|
||||||
let issl: bool = letvarisslice(c, nm);
|
let issl: bool = letvarisslice(c, nm);
|
||||||
if (isstr || issl) {
|
if (isstr || issl) {
|
||||||
@@ -23082,6 +23100,79 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level TUPLE global positional read (C-t3, #48): `g.N` —
|
||||||
|
// LEAQ name(SB) into CX, then load at the element's SLOT offset
|
||||||
|
// (C-t0 layout), the element's natural width. Mirrors the local
|
||||||
|
// N_TTUPLE arm above and cstage's N_DOT TY_TUPLE global base.
|
||||||
|
// Pre-C-t3 the tuple global wasn't in collectlets at all (no
|
||||||
|
// DATA) and the module-leaf fallback mis-emitted the field index
|
||||||
|
// as a symbol (`MOVQ 0(SB), AX`).
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
|
let gtt: *node = letvartnode(c, lhs.str);
|
||||||
|
for (gtt != nil && gtt.kind == nkind.N_TNAME) {
|
||||||
|
gtt = aliaslookup(c, gtt.str);
|
||||||
|
};
|
||||||
|
if (gtt != nil) {
|
||||||
|
if (gtt.kind == nkind.N_TTUPLE) {
|
||||||
|
let gidx: i32 = fldnumidx(fld);
|
||||||
|
if (gidx >= 0) {
|
||||||
|
let gtp: *node = gtt.list;
|
||||||
|
let gfoff: i32 = 0;
|
||||||
|
let gi: i32 = 0;
|
||||||
|
for (gi < gidx) {
|
||||||
|
if (gtp == nil) { gi = gidx; }
|
||||||
|
else {
|
||||||
|
gfoff += slotsize(c, gtp.lhs);
|
||||||
|
gtp = gtp.next;
|
||||||
|
gi += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (gtp != nil) {
|
||||||
|
let gpt: *node = gtp.lhs;
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
if (isstrtype(c, gpt)) {
|
||||||
|
// CX (the base) is written LAST so
|
||||||
|
// it survives the +0/+8 reads.
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 0): i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 8): i64, "CX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 16): i64, "CX");
|
||||||
|
emitline(", CX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (isfloattype(c, gpt)) {
|
||||||
|
let mov: str = "MOVSD";
|
||||||
|
if (isf32type(c, gpt)) { mov = "MOVSS"; };
|
||||||
|
emitline("\t");
|
||||||
|
emitline(mov);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(gfoff: i64, "CX");
|
||||||
|
emitline(", X0\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let gnsz: i32 = 8;
|
||||||
|
let gpti: *tinfo = gpt.type_: *tinfo;
|
||||||
|
if (gpti != nil) { gnsz = gpti.size: i32; };
|
||||||
|
let gop: str = tnodeloadop(c, gpt, gnsz);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(gop);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(gfoff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Top-level struct global field read — LEAQ name(SB), CX then
|
// Top-level struct global field read — LEAQ name(SB), CX then
|
||||||
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||||
// branch above, swapping the BP frame slot for the global VA.
|
// branch above, swapping the BP frame slot for the global VA.
|
||||||
@@ -25938,6 +26029,45 @@ fn cgcall(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): GLOBAL tuple len(g.N) —
|
||||||
|
// LEAQ name(SB) into CX, .len word at
|
||||||
|
// the SLOT offset + 8. Graduates the
|
||||||
|
// C5 loud-stop this shape previously
|
||||||
|
// hit; twin of the cgdot global-tuple
|
||||||
|
// arm and cstage's #235 global base.
|
||||||
|
if (lc == nil) {
|
||||||
|
let gtn: *node = letvartnode(c, a.lhs.str);
|
||||||
|
for (gtn != nil && gtn.kind == nkind.N_TNAME) {
|
||||||
|
gtn = aliaslookup(c, gtn.str);
|
||||||
|
};
|
||||||
|
if (gtn != nil) {
|
||||||
|
if (gtn.kind == nkind.N_TTUPLE) {
|
||||||
|
let gidx: i32 = fldnumidx(a.str);
|
||||||
|
if (gidx >= 0) {
|
||||||
|
let gtp: *node = gtn.list;
|
||||||
|
let gfoff: i32 = 0;
|
||||||
|
let gi: i32 = 0;
|
||||||
|
for (gi < gidx) {
|
||||||
|
if (gtp == nil) { gi = gidx; }
|
||||||
|
else {
|
||||||
|
gfoff += slotsize(c, gtp.lhs);
|
||||||
|
gtp = gtp.next;
|
||||||
|
gi += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (gtp != nil) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, a.lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((gfoff + 8): i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// struct-field N_DOT (`len(s.field)`): the
|
// struct-field N_DOT (`len(s.field)`): the
|
||||||
// old fallback returned .ptr as the length.
|
// old fallback returned .ptr as the length.
|
||||||
// Falls to the resolver route below.
|
// Falls to the resolver route below.
|
||||||
@@ -35519,6 +35649,27 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
|
|||||||
};
|
};
|
||||||
return alen * esz;
|
return alen * esz;
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): tuple global — per-element slot sum (C-t0
|
||||||
|
// layout: a str/slice its header, everything else one 8B
|
||||||
|
// eightbyte). Mirrors cstage let_emit_size TY_TUPLE (u->size,
|
||||||
|
// the checker slot sum). Pre-C-t3 the 0 here kept tuple
|
||||||
|
// globals out of collectlets entirely — no DATA emitted, and
|
||||||
|
// the module-leaf fallback mis-emitted the field index as a
|
||||||
|
// symbol (`MOVQ 0(SB), AX`).
|
||||||
|
if (t.kind == nkind.N_TTUPLE) {
|
||||||
|
let tsum: i32 = 0;
|
||||||
|
let p: *node = t.list;
|
||||||
|
for (p != nil) {
|
||||||
|
let et: *node = p.lhs;
|
||||||
|
if (isstrtype(c, et) || isslicetype(c, et)) {
|
||||||
|
tsum += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
tsum += 8;
|
||||||
|
};
|
||||||
|
p = p.next;
|
||||||
|
};
|
||||||
|
return tsum;
|
||||||
|
};
|
||||||
if (t.kind != nkind.N_TNAME) { return 0; };
|
if (t.kind != nkind.N_TNAME) { return 0; };
|
||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
@@ -35853,6 +36004,42 @@ export fn letpreintern(c: *cgen, file: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// C-t3 (#48): tuple global — pre-intern str-element
|
||||||
|
// literals in element order so emitletdataw's tuple
|
||||||
|
// arm's DATAR rows find their _S_ rodata rows (the
|
||||||
|
// #18 array-arm pattern; cstage let_pre_intern twin).
|
||||||
|
if (!handled && r != nil && d.lhs != nil) {
|
||||||
|
if (r.kind == nkind.N_TUPLE) {
|
||||||
|
let tlt: *node = d.lhs;
|
||||||
|
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
|
||||||
|
tlt = aliaslookup(c, tlt.str);
|
||||||
|
};
|
||||||
|
if (tlt != nil) {
|
||||||
|
if (tlt.kind == nkind.N_TTUPLE) {
|
||||||
|
handled = true;
|
||||||
|
let tp: *node = tlt.list;
|
||||||
|
let e: *node = r.list;
|
||||||
|
for (e != nil && tp != nil) {
|
||||||
|
let et: *node = tp.lhs;
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) {
|
||||||
|
ev = ev.lhs;
|
||||||
|
};
|
||||||
|
if (ev != nil) {
|
||||||
|
if (ev.kind == nkind.N_STRLIT
|
||||||
|
&& (isstrtype(c, et) || isslicetype(c, et))) {
|
||||||
|
if (ev.str.len > 0) {
|
||||||
|
internstrlit(c, ev.str);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
tp = tp.next;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
if (!handled) {
|
if (!handled) {
|
||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
// #43: route the str-let gate through primtypesize so
|
// #43: route the str-let gate through primtypesize so
|
||||||
@@ -36661,6 +36848,128 @@ fn emitslicedata(c: *cgen, name: str, module: str, slt: *tinfo,
|
|||||||
emitline(".d(SB)\n");
|
emitline(".d(SB)\n");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// emittupledata — module-level `let g: (T0, T1, ...) = (v0, ...);`
|
||||||
|
// 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 emitstrarraydata's rows. Elements must reduce to int
|
||||||
|
// (foldintliteral) or str literals; anything else returns false and the
|
||||||
|
// caller loud-stops (rule 7 — pre-C-t3 the whole definition was
|
||||||
|
// SILENTLY skipped and reads saw garbage). rhs == nil zero-inits the
|
||||||
|
// slot. Mirror of cstage emit_tuple_data.
|
||||||
|
fn emittupledata(c: *cgen, name: str, module: str, tt: *node, rhs: *node) bool = {
|
||||||
|
if (tt == nil) { return false; };
|
||||||
|
if (rhs == nil) {
|
||||||
|
let zsz: i32 = 0;
|
||||||
|
let p0: *node = tt.list;
|
||||||
|
for (p0 != nil) {
|
||||||
|
let et0: *node = p0.lhs;
|
||||||
|
if (isstrtype(c, et0) || isslicetype(c, et0)) {
|
||||||
|
zsz += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
zsz += 8;
|
||||||
|
};
|
||||||
|
p0 = p0.next;
|
||||||
|
};
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let zi: i32 = 0;
|
||||||
|
for (zi < zsz) { emitdatawbyte(0u8); zi += 1; };
|
||||||
|
emitline("\"\n");
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
if (rhs.kind != nkind.N_TUPLE) { return false; };
|
||||||
|
// Validate first (two-pass, emitarraydata precedent) so a partial
|
||||||
|
// row never reaches the output.
|
||||||
|
let tp: *node = tt.list;
|
||||||
|
let e: *node = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
if (ev == nil) { return false; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
if (ev.kind != nkind.N_STRLIT) { return false; };
|
||||||
|
} else {
|
||||||
|
let v: u64 = 0u64;
|
||||||
|
if (!foldintliteral(ev, &v)) { return false; };
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
tp = tt.list;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < 8) { emitdatawbyte(0u8); i += 1; };
|
||||||
|
let lv: u64 = ev.str.len: u64;
|
||||||
|
i = 0;
|
||||||
|
for (i < 8) {
|
||||||
|
emitdatawbyte((lv & 255u64): u8);
|
||||||
|
lv = lv >> 8u64;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
i = 16;
|
||||||
|
let ssz: i32 = primtypesize("str"): i32;
|
||||||
|
for (i < ssz) { emitdatawbyte(0u8); i += 1; };
|
||||||
|
} else {
|
||||||
|
let v: u64 = 0u64;
|
||||||
|
foldintliteral(ev, &v);
|
||||||
|
let i: i32 = 0;
|
||||||
|
let nv: u64 = v;
|
||||||
|
for (i < 8) {
|
||||||
|
emitdatawbyte((nv & 255u64): u8);
|
||||||
|
nv = nv >> 8u64;
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
let foff: i32 = 0;
|
||||||
|
tp = tt.list;
|
||||||
|
e = rhs.list;
|
||||||
|
for (e != nil) {
|
||||||
|
let et: *node = nil;
|
||||||
|
if (tp != nil) { et = tp.lhs; };
|
||||||
|
let ev: *node = e;
|
||||||
|
for (ev != nil && ev.kind == nkind.N_CAST) { ev = ev.lhs; };
|
||||||
|
let wide: bool = isstrtype(c, et) || isslicetype(c, et);
|
||||||
|
if (wide) {
|
||||||
|
if (ev.str.len > 0) {
|
||||||
|
let lab: str = internstrlit(c, ev.str);
|
||||||
|
emitline("DATAR ");
|
||||||
|
emitsymnamehint(c, name, module);
|
||||||
|
emitline("+");
|
||||||
|
emitint(foff: i64);
|
||||||
|
emitline("(SB),");
|
||||||
|
emitbytes( lab.ptr, lab.len: u64);
|
||||||
|
emitline("(SB)\n");
|
||||||
|
};
|
||||||
|
foff += (tyslicesize(): i32);
|
||||||
|
} else {
|
||||||
|
foff += 8;
|
||||||
|
};
|
||||||
|
e = e.next;
|
||||||
|
if (tp != nil) { tp = tp.next; };
|
||||||
|
};
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
fn emitletdataw(c: *cgen, file: *node) void = {
|
fn emitletdataw(c: *cgen, file: *node) void = {
|
||||||
let d: *node = file.list;
|
let d: *node = file.list;
|
||||||
for (d != nil) {
|
for (d != nil) {
|
||||||
@@ -36670,6 +36979,36 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
let issg: bool = letvarisstruct(c, nm);
|
let issg: bool = letvarisstruct(c, nm);
|
||||||
let fsz: i32 = letvarisfloat(c, nm);
|
let fsz: i32 = letvarisfloat(c, nm);
|
||||||
|
// C-t3 (#48): tuple global — slot-laid DATAW
|
||||||
|
// row (+ DATAR ptr patches for str elements)
|
||||||
|
// via emittupledata. Unsupported element
|
||||||
|
// inits die LOUD; pre-C-t3 the definition was
|
||||||
|
// silently skipped (no DATA, no diagnostic)
|
||||||
|
// and reads saw garbage. The istup gate also
|
||||||
|
// keeps a tuple out of the sz==8 / str-size
|
||||||
|
// arms below (a 24B tuple == str size).
|
||||||
|
let tlt: *node = d.lhs;
|
||||||
|
for (tlt != nil && tlt.kind == nkind.N_TNAME) {
|
||||||
|
tlt = aliaslookup(c, tlt.str);
|
||||||
|
};
|
||||||
|
let istup: bool = false;
|
||||||
|
if (tlt != nil) {
|
||||||
|
if (tlt.kind == nkind.N_TTUPLE) {
|
||||||
|
istup = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
if (istup) {
|
||||||
|
let tr: *node = d.rhs;
|
||||||
|
for (tr != nil) {
|
||||||
|
if (tr.kind != nkind.N_CAST) { break; };
|
||||||
|
tr = tr.lhs;
|
||||||
|
};
|
||||||
|
if (!emittupledata(c, nm, d.nmod, tlt, tr)) {
|
||||||
|
let mtg: str = "global tuple let: unsupported element init (int/str literals only; rule 7)\n";
|
||||||
|
os.write(2, mtg.ptr, mtg.len: u64);
|
||||||
|
os.exit(1);
|
||||||
|
};
|
||||||
|
};
|
||||||
if (fsz > 0) {
|
if (fsz > 0) {
|
||||||
// Float global: routes through the
|
// Float global: routes through the
|
||||||
// emitfloatlitdata SSoT helper, shared
|
// emitfloatlitdata SSoT helper, shared
|
||||||
@@ -36704,7 +37043,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
if (d.lhs != nil) {
|
if (d.lhs != nil) {
|
||||||
if (d.lhs.kind == nkind.N_TARRAY) { isarr8 = true; };
|
if (d.lhs.kind == nkind.N_TARRAY) { isarr8 = true; };
|
||||||
};
|
};
|
||||||
if (sz == 8 && !issg && fsz == 0 && !isarr8) {
|
if (sz == 8 && !issg && fsz == 0 && !isarr8 && !istup) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -36735,7 +37074,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == primtypesize("str"): i32 && !issg && !letvarisslice(c, nm)) {
|
if (sz == primtypesize("str"): i32 && !issg && !istup && !letvarisslice(c, nm)) {
|
||||||
let r: *node = d.rhs;
|
let r: *node = d.rhs;
|
||||||
for (r != nil) {
|
for (r != nil) {
|
||||||
if (r.kind != nkind.N_CAST) { break; };
|
if (r.kind != nkind.N_CAST) { break; };
|
||||||
|
|||||||
@@ -436,6 +436,52 @@ static const struct row rows[] = {
|
|||||||
" return 0;\n"
|
" return 0;\n"
|
||||||
"};\n", 0,
|
"};\n", 0,
|
||||||
K_BUILDERR, "element kind unsupported" },
|
K_BUILDERR, "element kind unsupported" },
|
||||||
|
|
||||||
|
/* ---- C-t3 (#48): GLOBAL tuples. Pre-C-t3 the definition was
|
||||||
|
* SILENTLY skipped (let_emit_size 0 → no DATA, no diagnostic);
|
||||||
|
* cstage element reads then read BP-frame garbage (localfind→0)
|
||||||
|
* and wwstage mis-emitted the field index as a symbol
|
||||||
|
* (`MOVQ 0(SB), AX`). Now: slot-laid DATAW (+ DATAR str-element
|
||||||
|
* ptr patches), element reads + len(g.N) via LEAQ sym(SB);
|
||||||
|
* unsupported element inits and the whole-tuple-as-value shape
|
||||||
|
* die LOUD. ---- */
|
||||||
|
{ "t3_global_elem",
|
||||||
|
"package main;\n"
|
||||||
|
"let g: (str, i64) = (\"hello\", 9);\n"
|
||||||
|
"let h: (u32, u32) = (5, 6);\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (len(g.0) != 5) { return 1; };\n"
|
||||||
|
" if (g.1 != 9) { return 2; };\n"
|
||||||
|
" if (h.0 != 5) { return 3; };\n"
|
||||||
|
" if (h.1 != 6) { return 4; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0, K_RUN, NULL },
|
||||||
|
{ "t3_reject_float_global",
|
||||||
|
"package main;\n"
|
||||||
|
"let g: (f64, i64) = (2.5, 4);\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (g.1 != 4) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "unsupported element init (int/str literals only; rule 7)" },
|
||||||
|
{ "t3_reject_whole_value",
|
||||||
|
"package main;\n"
|
||||||
|
"let g: (i64, i64) = (3, 4);\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let q: (i64, i64) = g;\n"
|
||||||
|
" if (q.0 != 3) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "global tuple as a first-class value unwired" },
|
||||||
|
/* pre-existing loud (no-regress pin): global tuple element WRITE. */
|
||||||
|
{ "t3_reject_global_write",
|
||||||
|
"package main;\n"
|
||||||
|
"let g: (i64, i64) = (3, 4);\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" g.0 = 7;\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n", 0,
|
||||||
|
K_BUILDERR, "unsupported assign target shape" },
|
||||||
};
|
};
|
||||||
|
|
||||||
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
/* build+run via a driver (ww / ww_ww); returns 0 pass, nonzero fail. */
|
||||||
|
|||||||
Reference in New Issue
Block a user