w6c+selfhost: struct globals — zero-init DATAW + LEAQ-based field access
Extend top-level mutable `let` to cover structs. Same approach as
str / slice: take the field-access base through &name(SB) instead
of off(BP).
- emit_lets / emitletdataw: emit `sizeof(T)` zero bytes for any
struct global without a baked-in initialiser. Struct-literal
init is skipped → undefined symbol at link if used;
- cgdot read path: when the IDENT base's local lookup misses and
the name is a struct let, LEAQ name(SB), CX and load the field
at fi.foff(CX) with the width-aware op (MOVQ / MOVL /
MOVZBQ / MOVSXD; MOVQ pair for str fields);
- cgassign write path: parallel handling for plain `=` (incl. str
fields) and the compound ops (+=, -=) via load → push → eval
rhs → combine → store with a re-LEAQ between cgexpr clobbers.
Tagged-union fields on struct globals are unsupported in v1 — the
local path's tagged branch isn't generalised yet. Whole-struct
by-value flow through expressions remains NYI (matches the local
status). 630_let_global gains 3 fixtures (read/write, compound +=,
narrow u8 field); selfhost mirror keeps test 990 / 994 / 995 byte
identical; bootstrap fixed point holds.
This commit is contained in:
@@ -398,10 +398,10 @@ struct LetVar {
|
|||||||
static LetVar *letvars;
|
static LetVar *letvars;
|
||||||
|
|
||||||
/* Slot size for a top-level `let` of type t, or 0 if the type isn't
|
/* Slot size for a top-level `let` of type t, or 0 if the type isn't
|
||||||
* supported as a writable global yet. Floats (MOVSS/SD) and struct/
|
* supported as a writable global yet. Floats (MOVSS/SD) and tagged
|
||||||
* tagged unions are deferred. enums route through their storage
|
* unions are deferred. enums route through their storage type.
|
||||||
* type. Keep this tight — extending it requires the matching
|
* Keep this tight — extending it requires the matching load/store
|
||||||
* load/store code below. */
|
* code below. */
|
||||||
static int
|
static int
|
||||||
let_emit_size(Type *t)
|
let_emit_size(Type *t)
|
||||||
{
|
{
|
||||||
@@ -419,6 +419,9 @@ let_emit_size(Type *t)
|
|||||||
return 16; /* {ptr, len}; literal-strlit init NYI. */
|
return 16; /* {ptr, len}; literal-strlit init NYI. */
|
||||||
case TY_SLICE:
|
case TY_SLICE:
|
||||||
return 24; /* {ptr, len, cap}; no init only. */
|
return 24; /* {ptr, len, cap}; no init only. */
|
||||||
|
case TY_STRUCT:
|
||||||
|
return (int)u->size; /* zero-init only; field reads/
|
||||||
|
* scalar-field writes only. */
|
||||||
default:
|
default:
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -445,6 +448,26 @@ let_isslice(Type *t)
|
|||||||
return u && u->kind == TY_SLICE;
|
return u && u->kind == TY_SLICE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Is the unwrapped type a struct? Struct globals only support field
|
||||||
|
* access (read + plain `=` write for scalar fields). Whole-struct
|
||||||
|
* by-value flow through expressions isn't wired. */
|
||||||
|
static int
|
||||||
|
let_isstruct(Type *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) return 0;
|
||||||
|
Type *u = (t->kind == TY_NAMED) ? t->under : t;
|
||||||
|
return u && u->kind == TY_STRUCT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Returns the unwrapped Type — handy when we need to walk struct
|
||||||
|
* fields. NULL if t is NULL or unresolved. */
|
||||||
|
static Type *
|
||||||
|
type_unwrap(Type *t)
|
||||||
|
{
|
||||||
|
if (t == NULL) return NULL;
|
||||||
|
return (t->kind == TY_NAMED) ? t->under : t;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
decl_has_ffisym(Node *d)
|
decl_has_ffisym(Node *d)
|
||||||
{
|
{
|
||||||
@@ -1169,6 +1192,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (fsz == 1) { load_op = A_MOVZBQ; store_op = A_MOVB; }
|
if (fsz == 1) { load_op = A_MOVZBQ; store_op = A_MOVB; }
|
||||||
else if (fsz == 4) { load_op = signed_field ? A_MOVSXD : A_MOVL; store_op = A_MOVL; }
|
else if (fsz == 4) { load_op = signed_field ? A_MOVSXD : A_MOVL; store_op = A_MOVL; }
|
||||||
int boff = localfind(locals, base->str);
|
int boff = localfind(locals, base->str);
|
||||||
|
int is_global = (boff == 0 && !via_ptr
|
||||||
|
&& let_islet(base->str));
|
||||||
int foff = (int)f->offset;
|
int foff = (int)f->offset;
|
||||||
/* str-typed field: rhs cgexpr leaves (AX=ptr, BX=len);
|
/* str-typed field: rhs cgexpr leaves (AX=ptr, BX=len);
|
||||||
* store both halves at field+0 and field+8. The 8/16
|
* store both halves at field+0 and field+8. The 8/16
|
||||||
@@ -1184,6 +1209,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_CX));
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_CX));
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, foff + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, foff + 0));
|
||||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, foff + 8));
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, foff + 8));
|
||||||
|
} else if (is_global) {
|
||||||
|
ins2(c, A_LEAQ, masym(c, base->str), areg(D_CX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, foff + 0));
|
||||||
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, foff + 8));
|
||||||
} else {
|
} else {
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 0));
|
||||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, boff + foff + 8));
|
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, boff + foff + 8));
|
||||||
@@ -1195,6 +1224,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (via_ptr) {
|
if (via_ptr) {
|
||||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
||||||
ins2(c, load_op, amem(D_BX, foff), areg(D_BX));
|
ins2(c, load_op, amem(D_BX, foff), areg(D_BX));
|
||||||
|
} else if (is_global) {
|
||||||
|
ins2(c, A_LEAQ, masym(c, base->str), areg(D_BX));
|
||||||
|
ins2(c, load_op, amem(D_BX, foff), areg(D_BX));
|
||||||
} else {
|
} else {
|
||||||
ins2(c, load_op, amem(D_BP, boff + foff), areg(D_BX));
|
ins2(c, load_op, amem(D_BP, boff + foff), areg(D_BX));
|
||||||
}
|
}
|
||||||
@@ -1217,6 +1249,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (via_ptr) {
|
if (via_ptr) {
|
||||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
||||||
ins2(c, store_op, areg(D_AX), amem(D_BX, foff));
|
ins2(c, store_op, areg(D_AX), amem(D_BX, foff));
|
||||||
|
} else if (is_global) {
|
||||||
|
ins2(c, A_LEAQ, masym(c, base->str), areg(D_BX));
|
||||||
|
ins2(c, store_op, areg(D_AX), amem(D_BX, foff));
|
||||||
} else {
|
} else {
|
||||||
ins2(c, store_op, areg(D_AX), amem(D_BP, boff + foff));
|
ins2(c, store_op, areg(D_AX), amem(D_BP, boff + foff));
|
||||||
}
|
}
|
||||||
@@ -2558,9 +2593,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* real struct field: load at struct_off + field_off */
|
/* real struct field: load at struct_base + field_off.
|
||||||
|
* Base is either a local frame slot (off(BP)) or a top-
|
||||||
|
* level let global (&name(SB) into CX); we resolve which
|
||||||
|
* once and then share the field-walk code. */
|
||||||
if (u && u->kind == TY_STRUCT && n->lhs->kind == N_IDENT) {
|
if (u && u->kind == TY_STRUCT && n->lhs->kind == N_IDENT) {
|
||||||
int off = localfind(locals, n->lhs->str);
|
int off = localfind(locals, n->lhs->str);
|
||||||
|
int is_global = 0;
|
||||||
|
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));
|
||||||
|
is_global = 1;
|
||||||
|
base_reg = D_CX;
|
||||||
|
base_disp = 0;
|
||||||
|
}
|
||||||
|
(void)is_global;
|
||||||
for (Tfield *f = u->fields; f; f = f->next) {
|
for (Tfield *f = u->fields; f; f = f->next) {
|
||||||
if (strcmp(f->name, n->str) != 0) continue;
|
if (strcmp(f->name, n->str) != 0) continue;
|
||||||
/* str field: load (ptr, len) into (AX, BX) so the
|
/* str field: load (ptr, len) into (AX, BX) so the
|
||||||
@@ -2569,10 +2617,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
? f->type->under : f->type;
|
? f->type->under : f->type;
|
||||||
if (str_fu && str_fu->kind == TY_STR) {
|
if (str_fu && str_fu->kind == TY_STR) {
|
||||||
ins2(c, A_MOVQ,
|
ins2(c, A_MOVQ,
|
||||||
amem(D_BP, off + (int)f->offset + 0),
|
amem(base_reg, base_disp + (int)f->offset + 0),
|
||||||
areg(D_AX));
|
areg(D_AX));
|
||||||
ins2(c, A_MOVQ,
|
ins2(c, A_MOVQ,
|
||||||
amem(D_BP, off + (int)f->offset + 8),
|
amem(base_reg, base_disp + (int)f->offset + 8),
|
||||||
areg(D_BX));
|
areg(D_BX));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -2585,7 +2633,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
if (fsz == 1) op = A_MOVZBQ;
|
if (fsz == 1) op = A_MOVZBQ;
|
||||||
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
||||||
ins2(c, op,
|
ins2(c, op,
|
||||||
amem(D_BP, off + (int)f->offset),
|
amem(base_reg, base_disp + (int)f->offset),
|
||||||
areg(D_AX));
|
areg(D_AX));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -3709,12 +3757,14 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
|||||||
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
/* Multi-word (str=16, slice=24). Only zero-init shapes
|
/* Multi-word (str=16, slice=24, struct=N). Only zero-init
|
||||||
* are supported: no rhs, or `nil`, or `""` (which interns
|
* shapes are supported: no rhs, or `nil`, or `""` (which
|
||||||
* to a strlit but we still emit a zero header — the
|
* interns to a strlit but we still emit a zero header —
|
||||||
* program has to assign a real strlit/slice at runtime
|
* the program has to assign a real strlit / slice / field
|
||||||
* to use it). */
|
* at runtime to use it). Struct literals as init are
|
||||||
if (d->rhs != NULL) {
|
* skipped (no compile-time eval), so a non-default init
|
||||||
|
* surfaces as an undefined-symbol link error. */
|
||||||
|
if (d->rhs != NULL && !let_isstruct(d->type)) {
|
||||||
Node *r = d->rhs;
|
Node *r = d->rhs;
|
||||||
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
||||||
if (r == NULL) continue;
|
if (r == NULL) continue;
|
||||||
@@ -3722,6 +3772,8 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
|||||||
if (r->kind != N_NIL && !empty_str)
|
if (r->kind != N_NIL && !empty_str)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (d->rhs != NULL && let_isstruct(d->type))
|
||||||
|
continue;
|
||||||
emit_data_row_zero(out, "DATAW", mod_mangle(c, d->str), sz);
|
emit_data_row_zero(out, "DATAW", mod_mangle(c, d->str), sz);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6645,6 +6645,42 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level struct global field read — LEAQ name(SB), CX then
|
||||||
|
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||||
|
// branch above, swapping the BP frame slot for the global VA.
|
||||||
|
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
|
let si: *structinfo = letvarstructinfo(c, lhs.str);
|
||||||
|
if (si != nil) {
|
||||||
|
let fi: *fieldinfo = si.fields;
|
||||||
|
for (fi != nil) {
|
||||||
|
if (streq(fi.fname, fld)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
if (isstrtype(c, fi.tnode)) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((fi.foff + 8): i64, "CX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
} else {
|
||||||
|
let op: str = fieldloadop(fi);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(op);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fi = fi.finext;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Module-qualified value reference: `mod.name` where `mod`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
||||||
@@ -7245,6 +7281,91 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level struct global field assignment: `g.f = expr;` and
|
||||||
|
// `g.f += expr;` for a scalar/str field. Reached when the local
|
||||||
|
// lookup miss but the IDENT base is a registered struct `let`.
|
||||||
|
// LEAQ name(SB) into BX/CX takes the place of the frame slot
|
||||||
|
// addressing the local branches use. Compound (PLUSEQ/MINUSEQ)
|
||||||
|
// follows the same load → push → eval → combine → store shape
|
||||||
|
// as the via-ptr local path.
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_DOT) {
|
||||||
|
let base: *node = lhs.lhs;
|
||||||
|
let fld: str = lhs.str;
|
||||||
|
if (base != nil) {
|
||||||
|
if (base.kind == nkind.N_IDENT) {
|
||||||
|
let bn: str = base.str;
|
||||||
|
if (localfindnode(c, bn) == nil) {
|
||||||
|
let si: *structinfo = letvarstructinfo(c, bn);
|
||||||
|
if (si != nil) {
|
||||||
|
let fi: *fieldinfo = si.fields;
|
||||||
|
for (fi != nil) {
|
||||||
|
if (streq(fi.fname, fld)) {
|
||||||
|
if (n.op == tkind.TK_ASSIGN) {
|
||||||
|
cgexpr(c, n.rhs);
|
||||||
|
if (isstrtype(c, fi.tnode)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline("\n");
|
||||||
|
emitline("\tMOVQ\tBX, ");
|
||||||
|
emitdispreg((fi.foff + 8): i64, "CX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let sop: str = fieldstoreop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(sop);
|
||||||
|
emitline("\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Compound on scalar field: load
|
||||||
|
// → push → eval rhs → combine →
|
||||||
|
// store. cgexpr clobbers BX, so
|
||||||
|
// re-LEAQ for the store.
|
||||||
|
let lop: str = fieldloadop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(lop);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
emitline("\tPUSHQ\tBX\n");
|
||||||
|
cgexpr(c, n.rhs);
|
||||||
|
emitline("\tPOPQ\tBX\n");
|
||||||
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); };
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) {
|
||||||
|
emitline("\tSUBQ\tAX, BX\n");
|
||||||
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
|
};
|
||||||
|
let sop: str = fieldstoreop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(sop);
|
||||||
|
emitline("\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fi = fi.finext;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
||||||
// of dots resolving to a *struct. Mirrors the C cgen branch
|
// of dots resolving to a *struct. Mirrors the C cgen branch
|
||||||
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
||||||
@@ -9047,9 +9168,10 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||||
// the type isn't yet supported as a writable global. Walks type
|
// the type isn't yet supported as a writable global. Walks type
|
||||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
// 8 → scalar (literal init supported)
|
// 8 → scalar (literal init supported)
|
||||||
// 16 → str (only zero-init / nil / "" supported)
|
// 16 → str (only zero-init / nil / "" supported)
|
||||||
// 24 → slice (only zero-init supported)
|
// 24 → slice (only zero-init supported)
|
||||||
|
// varies → struct (zero-init only; field reads/scalar-field writes)
|
||||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||||
if (d == nil) { return 0; };
|
if (d == nil) { return 0; };
|
||||||
let t: *node = d.lhs;
|
let t: *node = d.lhs;
|
||||||
@@ -9060,6 +9182,8 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
|
|||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
if (streq(nm, "str")) { return 16; };
|
if (streq(nm, "str")) { return 16; };
|
||||||
|
let si: *structinfo = structlookup(c, nm);
|
||||||
|
if (si != nil) { return si.totsize; };
|
||||||
let next: *node = aliaslookup(c, nm);
|
let next: *node = aliaslookup(c, nm);
|
||||||
if (next == nil) { return 0; };
|
if (next == nil) { return 0; };
|
||||||
t = next;
|
t = next;
|
||||||
@@ -9139,6 +9263,54 @@ fn letvarisslice(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// letvarisstruct — is the named top-level let a struct global?
|
||||||
|
// Struct globals use LEAQ name(SB), CX as the field-access base; the
|
||||||
|
// cgdot read and cgassign write paths branch on this to skip the
|
||||||
|
// frame-relative addressing they use for locals.
|
||||||
|
fn letvarisstruct(c: *cgen, name: str) bool = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
if (t.kind != nkind.N_TNAME) { return false; };
|
||||||
|
let nm: str = t.str;
|
||||||
|
if (structlookup(c, nm) != nil) { return true; };
|
||||||
|
let nx: *node = aliaslookup(c, nm);
|
||||||
|
if (nx == nil) { return false; };
|
||||||
|
t = nx;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// letvarstructinfo — for a struct global, return its structinfo
|
||||||
|
// so the cgdot/cgassign paths can look up fields. nil if the let
|
||||||
|
// isn't a struct (or wasn't found).
|
||||||
|
fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
if (t.kind != nkind.N_TNAME) { return nil; };
|
||||||
|
let nm: str = t.str;
|
||||||
|
let si: *structinfo = structlookup(c, nm);
|
||||||
|
if (si != nil) { return si; };
|
||||||
|
let nx: *node = aliaslookup(c, nm);
|
||||||
|
if (nx == nil) { return nil; };
|
||||||
|
t = nx;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
// emitdatawbyte — write one byte of an asm string literal using
|
// emitdatawbyte — write one byte of an asm string literal using
|
||||||
// the same escape rules as emitdefconstants / emitdatasection.
|
// the same escape rules as emitdefconstants / emitdatasection.
|
||||||
fn emitdatawbyte(b: u8) void = {
|
fn emitdatawbyte(b: u8) void = {
|
||||||
@@ -9187,7 +9359,8 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
if (sz == 8) {
|
let issg: bool = letvarisstruct(c, nm);
|
||||||
|
if (sz == 8 && !issg) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -9220,7 +9393,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == 16) {
|
if (sz == 16 && !issg) {
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
let r: *node = d.rhs;
|
let r: *node = d.rhs;
|
||||||
@@ -9248,7 +9421,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == 24) {
|
if (sz == 24 && !issg) {
|
||||||
// Slice: zero-init only (no slice-literal
|
// Slice: zero-init only (no slice-literal
|
||||||
// syntax to honour). Any rhs other than
|
// syntax to honour). Any rhs other than
|
||||||
// `nil` is skipped → undefined symbol at
|
// `nil` is skipped → undefined symbol at
|
||||||
@@ -9277,6 +9450,23 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Struct globals — any size, zero-init only.
|
||||||
|
// A struct literal init isn't compile-time
|
||||||
|
// evaluated yet; skip and the link will surface
|
||||||
|
// an undefined-symbol error if referenced.
|
||||||
|
if (issg) {
|
||||||
|
if (d.rhs == nil) {
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < sz) {
|
||||||
|
emitdatawbyte(0u8);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
d = d.next;
|
d = d.next;
|
||||||
|
|||||||
@@ -584,9 +584,10 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||||
// the type isn't yet supported as a writable global. Walks type
|
// the type isn't yet supported as a writable global. Walks type
|
||||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
// 8 → scalar (literal init supported)
|
// 8 → scalar (literal init supported)
|
||||||
// 16 → str (only zero-init / nil / "" supported)
|
// 16 → str (only zero-init / nil / "" supported)
|
||||||
// 24 → slice (only zero-init supported)
|
// 24 → slice (only zero-init supported)
|
||||||
|
// varies → struct (zero-init only; field reads/scalar-field writes)
|
||||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||||
if (d == nil) { return 0; };
|
if (d == nil) { return 0; };
|
||||||
let t: *node = d.lhs;
|
let t: *node = d.lhs;
|
||||||
@@ -597,6 +598,8 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
|
|||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
if (streq(nm, "str")) { return 16; };
|
if (streq(nm, "str")) { return 16; };
|
||||||
|
let si: *structinfo = structlookup(c, nm);
|
||||||
|
if (si != nil) { return si.totsize; };
|
||||||
let next: *node = aliaslookup(c, nm);
|
let next: *node = aliaslookup(c, nm);
|
||||||
if (next == nil) { return 0; };
|
if (next == nil) { return 0; };
|
||||||
t = next;
|
t = next;
|
||||||
@@ -676,6 +679,54 @@ fn letvarisslice(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// letvarisstruct — is the named top-level let a struct global?
|
||||||
|
// Struct globals use LEAQ name(SB), CX as the field-access base; the
|
||||||
|
// cgdot read and cgassign write paths branch on this to skip the
|
||||||
|
// frame-relative addressing they use for locals.
|
||||||
|
fn letvarisstruct(c: *cgen, name: str) bool = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
if (t.kind != nkind.N_TNAME) { return false; };
|
||||||
|
let nm: str = t.str;
|
||||||
|
if (structlookup(c, nm) != nil) { return true; };
|
||||||
|
let nx: *node = aliaslookup(c, nm);
|
||||||
|
if (nx == nil) { return false; };
|
||||||
|
t = nx;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// letvarstructinfo — for a struct global, return its structinfo
|
||||||
|
// so the cgdot/cgassign paths can look up fields. nil if the let
|
||||||
|
// isn't a struct (or wasn't found).
|
||||||
|
fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
if (t.kind != nkind.N_TNAME) { return nil; };
|
||||||
|
let nm: str = t.str;
|
||||||
|
let si: *structinfo = structlookup(c, nm);
|
||||||
|
if (si != nil) { return si; };
|
||||||
|
let nx: *node = aliaslookup(c, nm);
|
||||||
|
if (nx == nil) { return nil; };
|
||||||
|
t = nx;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
// emitdatawbyte — write one byte of an asm string literal using
|
// emitdatawbyte — write one byte of an asm string literal using
|
||||||
// the same escape rules as emitdefconstants / emitdatasection.
|
// the same escape rules as emitdefconstants / emitdatasection.
|
||||||
fn emitdatawbyte(b: u8) void = {
|
fn emitdatawbyte(b: u8) void = {
|
||||||
@@ -724,7 +775,8 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
if (sz == 8) {
|
let issg: bool = letvarisstruct(c, nm);
|
||||||
|
if (sz == 8 && !issg) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -757,7 +809,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == 16) {
|
if (sz == 16 && !issg) {
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
let r: *node = d.rhs;
|
let r: *node = d.rhs;
|
||||||
@@ -785,7 +837,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == 24) {
|
if (sz == 24 && !issg) {
|
||||||
// Slice: zero-init only (no slice-literal
|
// Slice: zero-init only (no slice-literal
|
||||||
// syntax to honour). Any rhs other than
|
// syntax to honour). Any rhs other than
|
||||||
// `nil` is skipped → undefined symbol at
|
// `nil` is skipped → undefined symbol at
|
||||||
@@ -814,6 +866,23 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Struct globals — any size, zero-init only.
|
||||||
|
// A struct literal init isn't compile-time
|
||||||
|
// evaluated yet; skip and the link will surface
|
||||||
|
// an undefined-symbol error if referenced.
|
||||||
|
if (issg) {
|
||||||
|
if (d.rhs == nil) {
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < sz) {
|
||||||
|
emitdatawbyte(0u8);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
d = d.next;
|
d = d.next;
|
||||||
|
|||||||
@@ -917,6 +917,42 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level struct global field read — LEAQ name(SB), CX then
|
||||||
|
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||||
|
// branch above, swapping the BP frame slot for the global VA.
|
||||||
|
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
|
let si: *structinfo = letvarstructinfo(c, lhs.str);
|
||||||
|
if (si != nil) {
|
||||||
|
let fi: *fieldinfo = si.fields;
|
||||||
|
for (fi != nil) {
|
||||||
|
if (streq(fi.fname, fld)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
if (isstrtype(c, fi.tnode)) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((fi.foff + 8): i64, "CX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
} else {
|
||||||
|
let op: str = fieldloadop(fi);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(op);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fi = fi.finext;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Module-qualified value reference: `mod.name` where `mod`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
||||||
@@ -1517,6 +1553,91 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level struct global field assignment: `g.f = expr;` and
|
||||||
|
// `g.f += expr;` for a scalar/str field. Reached when the local
|
||||||
|
// lookup miss but the IDENT base is a registered struct `let`.
|
||||||
|
// LEAQ name(SB) into BX/CX takes the place of the frame slot
|
||||||
|
// addressing the local branches use. Compound (PLUSEQ/MINUSEQ)
|
||||||
|
// follows the same load → push → eval → combine → store shape
|
||||||
|
// as the via-ptr local path.
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_DOT) {
|
||||||
|
let base: *node = lhs.lhs;
|
||||||
|
let fld: str = lhs.str;
|
||||||
|
if (base != nil) {
|
||||||
|
if (base.kind == nkind.N_IDENT) {
|
||||||
|
let bn: str = base.str;
|
||||||
|
if (localfindnode(c, bn) == nil) {
|
||||||
|
let si: *structinfo = letvarstructinfo(c, bn);
|
||||||
|
if (si != nil) {
|
||||||
|
let fi: *fieldinfo = si.fields;
|
||||||
|
for (fi != nil) {
|
||||||
|
if (streq(fi.fname, fld)) {
|
||||||
|
if (n.op == tkind.TK_ASSIGN) {
|
||||||
|
cgexpr(c, n.rhs);
|
||||||
|
if (isstrtype(c, fi.tnode)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline("\n");
|
||||||
|
emitline("\tMOVQ\tBX, ");
|
||||||
|
emitdispreg((fi.foff + 8): i64, "CX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let sop: str = fieldstoreop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(sop);
|
||||||
|
emitline("\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Compound on scalar field: load
|
||||||
|
// → push → eval rhs → combine →
|
||||||
|
// store. cgexpr clobbers BX, so
|
||||||
|
// re-LEAQ for the store.
|
||||||
|
let lop: str = fieldloadop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(lop);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
emitline("\tPUSHQ\tBX\n");
|
||||||
|
cgexpr(c, n.rhs);
|
||||||
|
emitline("\tPOPQ\tBX\n");
|
||||||
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); };
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) {
|
||||||
|
emitline("\tSUBQ\tAX, BX\n");
|
||||||
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
|
};
|
||||||
|
let sop: str = fieldstoreop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(sop);
|
||||||
|
emitline("\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fi = fi.finext;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
||||||
// of dots resolving to a *struct. Mirrors the C cgen branch
|
// of dots resolving to a *struct. Mirrors the C cgen branch
|
||||||
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
||||||
|
|||||||
@@ -6645,6 +6645,42 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level struct global field read — LEAQ name(SB), CX then
|
||||||
|
// load at fi.foff(CX). Mirrors the local "Direct struct local"
|
||||||
|
// branch above, swapping the BP frame slot for the global VA.
|
||||||
|
// Field-width-aware op handles MOVQ / MOVL / MOVZBQ / MOVSXD.
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_IDENT) {
|
||||||
|
let si: *structinfo = letvarstructinfo(c, lhs.str);
|
||||||
|
if (si != nil) {
|
||||||
|
let fi: *fieldinfo = si.fields;
|
||||||
|
for (fi != nil) {
|
||||||
|
if (streq(fi.fname, fld)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, lhs.str);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
if (isstrtype(c, fi.tnode)) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitdispreg((fi.foff + 8): i64, "CX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
} else {
|
||||||
|
let op: str = fieldloadop(fi);
|
||||||
|
emitline("\t");
|
||||||
|
emitline(op);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline(", AX\n");
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fi = fi.finext;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Module-qualified value reference: `mod.name` where `mod`
|
// Module-qualified value reference: `mod.name` where `mod`
|
||||||
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
// is nkind.N_IDENT bound as skind.SK_USE and the leaf isn't a local.
|
||||||
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
// Treat as a SB symbol — `MOVQ leaf(SB), AX`. Same fallback
|
||||||
@@ -7245,6 +7281,91 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Top-level struct global field assignment: `g.f = expr;` and
|
||||||
|
// `g.f += expr;` for a scalar/str field. Reached when the local
|
||||||
|
// lookup miss but the IDENT base is a registered struct `let`.
|
||||||
|
// LEAQ name(SB) into BX/CX takes the place of the frame slot
|
||||||
|
// addressing the local branches use. Compound (PLUSEQ/MINUSEQ)
|
||||||
|
// follows the same load → push → eval → combine → store shape
|
||||||
|
// as the via-ptr local path.
|
||||||
|
if (lhs != nil) {
|
||||||
|
if (lhs.kind == nkind.N_DOT) {
|
||||||
|
let base: *node = lhs.lhs;
|
||||||
|
let fld: str = lhs.str;
|
||||||
|
if (base != nil) {
|
||||||
|
if (base.kind == nkind.N_IDENT) {
|
||||||
|
let bn: str = base.str;
|
||||||
|
if (localfindnode(c, bn) == nil) {
|
||||||
|
let si: *structinfo = letvarstructinfo(c, bn);
|
||||||
|
if (si != nil) {
|
||||||
|
let fi: *fieldinfo = si.fields;
|
||||||
|
for (fi != nil) {
|
||||||
|
if (streq(fi.fname, fld)) {
|
||||||
|
if (n.op == tkind.TK_ASSIGN) {
|
||||||
|
cgexpr(c, n.rhs);
|
||||||
|
if (isstrtype(c, fi.tnode)) {
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), CX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "CX");
|
||||||
|
emitline("\n");
|
||||||
|
emitline("\tMOVQ\tBX, ");
|
||||||
|
emitdispreg((fi.foff + 8): i64, "CX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let sop: str = fieldstoreop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(sop);
|
||||||
|
emitline("\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
// Compound on scalar field: load
|
||||||
|
// → push → eval rhs → combine →
|
||||||
|
// store. cgexpr clobbers BX, so
|
||||||
|
// re-LEAQ for the store.
|
||||||
|
let lop: str = fieldloadop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(lop);
|
||||||
|
emitline("\t");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline(", BX\n");
|
||||||
|
emitline("\tPUSHQ\tBX\n");
|
||||||
|
cgexpr(c, n.rhs);
|
||||||
|
emitline("\tPOPQ\tBX\n");
|
||||||
|
if (n.op == tkind.TK_PLUSEQ) { emitline("\tADDQ\tBX, AX\n"); };
|
||||||
|
if (n.op == tkind.TK_MINUSEQ) {
|
||||||
|
emitline("\tSUBQ\tAX, BX\n");
|
||||||
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
|
};
|
||||||
|
let sop: str = fieldstoreop(fi);
|
||||||
|
emitline("\tLEAQ\t");
|
||||||
|
emitsymname(c, bn);
|
||||||
|
emitline("(SB), BX\n");
|
||||||
|
emitline("\t");
|
||||||
|
emitline(sop);
|
||||||
|
emitline("\tAX, ");
|
||||||
|
emitdispreg(fi.foff: i64, "BX");
|
||||||
|
emitline("\n");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
fi = fi.finext;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
// Chained `<expr>.field = v` where `<expr>` itself is a chain
|
||||||
// of dots resolving to a *struct. Mirrors the C cgen branch
|
// of dots resolving to a *struct. Mirrors the C cgen branch
|
||||||
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
// added to close trap 1 (cmd/w6c/cgen.c). Without this, only
|
||||||
@@ -9047,9 +9168,10 @@ fn letscalarprim(nm: str) bool = {
|
|||||||
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
// letemitsize — slot size in bytes for a top-level `let`, or 0 if
|
||||||
// the type isn't yet supported as a writable global. Walks type
|
// the type isn't yet supported as a writable global. Walks type
|
||||||
// aliases so byte output matches C cgen, which resolves Type kinds.
|
// aliases so byte output matches C cgen, which resolves Type kinds.
|
||||||
// 8 → scalar (literal init supported)
|
// 8 → scalar (literal init supported)
|
||||||
// 16 → str (only zero-init / nil / "" supported)
|
// 16 → str (only zero-init / nil / "" supported)
|
||||||
// 24 → slice (only zero-init supported)
|
// 24 → slice (only zero-init supported)
|
||||||
|
// varies → struct (zero-init only; field reads/scalar-field writes)
|
||||||
fn letemitsize(c: *cgen, d: *node) i32 = {
|
fn letemitsize(c: *cgen, d: *node) i32 = {
|
||||||
if (d == nil) { return 0; };
|
if (d == nil) { return 0; };
|
||||||
let t: *node = d.lhs;
|
let t: *node = d.lhs;
|
||||||
@@ -9060,6 +9182,8 @@ fn letemitsize(c: *cgen, d: *node) i32 = {
|
|||||||
let nm: str = t.str;
|
let nm: str = t.str;
|
||||||
if (letscalarprim(nm)) { return 8; };
|
if (letscalarprim(nm)) { return 8; };
|
||||||
if (streq(nm, "str")) { return 16; };
|
if (streq(nm, "str")) { return 16; };
|
||||||
|
let si: *structinfo = structlookup(c, nm);
|
||||||
|
if (si != nil) { return si.totsize; };
|
||||||
let next: *node = aliaslookup(c, nm);
|
let next: *node = aliaslookup(c, nm);
|
||||||
if (next == nil) { return 0; };
|
if (next == nil) { return 0; };
|
||||||
t = next;
|
t = next;
|
||||||
@@ -9139,6 +9263,54 @@ fn letvarisslice(c: *cgen, name: str) bool = {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// letvarisstruct — is the named top-level let a struct global?
|
||||||
|
// Struct globals use LEAQ name(SB), CX as the field-access base; the
|
||||||
|
// cgdot read and cgassign write paths branch on this to skip the
|
||||||
|
// frame-relative addressing they use for locals.
|
||||||
|
fn letvarisstruct(c: *cgen, name: str) bool = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
if (t.kind != nkind.N_TNAME) { return false; };
|
||||||
|
let nm: str = t.str;
|
||||||
|
if (structlookup(c, nm) != nil) { return true; };
|
||||||
|
let nx: *node = aliaslookup(c, nm);
|
||||||
|
if (nx == nil) { return false; };
|
||||||
|
t = nx;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// letvarstructinfo — for a struct global, return its structinfo
|
||||||
|
// so the cgdot/cgassign paths can look up fields. nil if the let
|
||||||
|
// isn't a struct (or wasn't found).
|
||||||
|
fn letvarstructinfo(c: *cgen, name: str) *structinfo = {
|
||||||
|
let lv: *letvar = c.lets;
|
||||||
|
for (lv != nil) {
|
||||||
|
if (streq(lv.name, name)) {
|
||||||
|
let t: *node = lv.tnode;
|
||||||
|
for (t != nil) {
|
||||||
|
if (t.kind != nkind.N_TNAME) { return nil; };
|
||||||
|
let nm: str = t.str;
|
||||||
|
let si: *structinfo = structlookup(c, nm);
|
||||||
|
if (si != nil) { return si; };
|
||||||
|
let nx: *node = aliaslookup(c, nm);
|
||||||
|
if (nx == nil) { return nil; };
|
||||||
|
t = nx;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
lv = lv.lvnext;
|
||||||
|
};
|
||||||
|
return nil;
|
||||||
|
};
|
||||||
|
|
||||||
// emitdatawbyte — write one byte of an asm string literal using
|
// emitdatawbyte — write one byte of an asm string literal using
|
||||||
// the same escape rules as emitdefconstants / emitdatasection.
|
// the same escape rules as emitdefconstants / emitdatasection.
|
||||||
fn emitdatawbyte(b: u8) void = {
|
fn emitdatawbyte(b: u8) void = {
|
||||||
@@ -9187,7 +9359,8 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
let nm: str = d.str;
|
let nm: str = d.str;
|
||||||
if (nm.len > 0) {
|
if (nm.len > 0) {
|
||||||
let sz: i32 = letemitsize(c, d);
|
let sz: i32 = letemitsize(c, d);
|
||||||
if (sz == 8) {
|
let issg: bool = letvarisstruct(c, nm);
|
||||||
|
if (sz == 8 && !issg) {
|
||||||
let v: u64 = 0u64;
|
let v: u64 = 0u64;
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
@@ -9220,7 +9393,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == 16) {
|
if (sz == 16 && !issg) {
|
||||||
let ok: bool = true;
|
let ok: bool = true;
|
||||||
if (d.rhs != nil) {
|
if (d.rhs != nil) {
|
||||||
let r: *node = d.rhs;
|
let r: *node = d.rhs;
|
||||||
@@ -9248,7 +9421,7 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
if (sz == 24) {
|
if (sz == 24 && !issg) {
|
||||||
// Slice: zero-init only (no slice-literal
|
// Slice: zero-init only (no slice-literal
|
||||||
// syntax to honour). Any rhs other than
|
// syntax to honour). Any rhs other than
|
||||||
// `nil` is skipped → undefined symbol at
|
// `nil` is skipped → undefined symbol at
|
||||||
@@ -9277,6 +9450,23 @@ fn emitletdataw(c: *cgen, file: *node) void = {
|
|||||||
emitline("\"\n");
|
emitline("\"\n");
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// Struct globals — any size, zero-init only.
|
||||||
|
// A struct literal init isn't compile-time
|
||||||
|
// evaluated yet; skip and the link will surface
|
||||||
|
// an undefined-symbol error if referenced.
|
||||||
|
if (issg) {
|
||||||
|
if (d.rhs == nil) {
|
||||||
|
emitline("DATAW ");
|
||||||
|
emitsymname(c, nm);
|
||||||
|
emitline("(SB),\"");
|
||||||
|
let i: i32 = 0;
|
||||||
|
for (i < sz) {
|
||||||
|
emitdatawbyte(0u8);
|
||||||
|
i += 1;
|
||||||
|
};
|
||||||
|
emitline("\"\n");
|
||||||
|
};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
d = d.next;
|
d = d.next;
|
||||||
|
|||||||
@@ -138,6 +138,49 @@ static const struct fixture fixtures[] = {
|
|||||||
"};\n",
|
"};\n",
|
||||||
99,
|
99,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"struct-field-readwrite",
|
||||||
|
/* Top-level struct global. Field writes hit the global
|
||||||
|
* via LEAQ name(SB) + offset; reads pull each field
|
||||||
|
* with the right width. */
|
||||||
|
"type point = struct { x: i32, y: i32 };\n"
|
||||||
|
"let p: point;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
"\tp.x = 7;\n"
|
||||||
|
"\tp.y = 35;\n"
|
||||||
|
"\treturn p.x + p.y;\n"
|
||||||
|
"};\n",
|
||||||
|
42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"struct-field-compound",
|
||||||
|
/* Compound += on a struct global field — load via
|
||||||
|
* LEAQ+disp, push, eval rhs, combine, store. */
|
||||||
|
"type counter = struct { n: i64 };\n"
|
||||||
|
"let c: counter;\n"
|
||||||
|
"fn bump(d: i64) void = { c.n += d; };\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
"\tbump(10i64);\n"
|
||||||
|
"\tbump(15i64);\n"
|
||||||
|
"\tbump(17i64);\n"
|
||||||
|
"\treturn c.n: i32;\n"
|
||||||
|
"};\n",
|
||||||
|
42,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"struct-narrow-field",
|
||||||
|
/* u8 field on a struct global. Read uses MOVZBQ, store
|
||||||
|
* uses MOVB. Pre-fix this would have stomped neighbouring
|
||||||
|
* bytes by emitting MOVQ. */
|
||||||
|
"type packet = struct { hdr: u8, body: u32 };\n"
|
||||||
|
"let pkt: packet;\n"
|
||||||
|
"fn main() i32 = {\n"
|
||||||
|
"\tpkt.hdr = 42u8;\n"
|
||||||
|
"\tpkt.body = 999u32;\n"
|
||||||
|
"\treturn pkt.hdr: i32;\n"
|
||||||
|
"};\n",
|
||||||
|
42,
|
||||||
|
},
|
||||||
{ NULL, NULL, 0 }
|
{ NULL, NULL, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user