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;
|
||||
|
||||
/* 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/
|
||||
* tagged unions are deferred. enums route through their storage
|
||||
* type. Keep this tight — extending it requires the matching
|
||||
* load/store code below. */
|
||||
* supported as a writable global yet. Floats (MOVSS/SD) and tagged
|
||||
* unions are deferred. enums route through their storage type.
|
||||
* Keep this tight — extending it requires the matching load/store
|
||||
* code below. */
|
||||
static int
|
||||
let_emit_size(Type *t)
|
||||
{
|
||||
@@ -419,6 +419,9 @@ let_emit_size(Type *t)
|
||||
return 16; /* {ptr, len}; literal-strlit init NYI. */
|
||||
case TY_SLICE:
|
||||
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:
|
||||
return 0;
|
||||
}
|
||||
@@ -445,6 +448,26 @@ let_isslice(Type *t)
|
||||
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
|
||||
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; }
|
||||
else if (fsz == 4) { load_op = signed_field ? A_MOVSXD : A_MOVL; store_op = A_MOVL; }
|
||||
int boff = localfind(locals, base->str);
|
||||
int is_global = (boff == 0 && !via_ptr
|
||||
&& let_islet(base->str));
|
||||
int foff = (int)f->offset;
|
||||
/* str-typed field: rhs cgexpr leaves (AX=ptr, BX=len);
|
||||
* 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, areg(D_AX), amem(D_CX, foff + 0));
|
||||
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 {
|
||||
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));
|
||||
@@ -1195,6 +1224,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (via_ptr) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), 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 {
|
||||
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) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
|
||||
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 {
|
||||
ins2(c, store_op, areg(D_AX), amem(D_BP, boff + foff));
|
||||
}
|
||||
@@ -2558,9 +2593,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
if (strcmp(f->name, n->str) != 0) continue;
|
||||
/* 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;
|
||||
if (str_fu && str_fu->kind == TY_STR) {
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off + (int)f->offset + 0),
|
||||
amem(base_reg, base_disp + (int)f->offset + 0),
|
||||
areg(D_AX));
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, off + (int)f->offset + 8),
|
||||
amem(base_reg, base_disp + (int)f->offset + 8),
|
||||
areg(D_BX));
|
||||
break;
|
||||
}
|
||||
@@ -2585,7 +2633,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (fsz == 1) op = A_MOVZBQ;
|
||||
else if (fsz == 4) op = signed_field ? A_MOVSXD : A_MOVL;
|
||||
ins2(c, op,
|
||||
amem(D_BP, off + (int)f->offset),
|
||||
amem(base_reg, base_disp + (int)f->offset),
|
||||
areg(D_AX));
|
||||
break;
|
||||
}
|
||||
@@ -3709,12 +3757,14 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
emit_data_row(out, "DATAW", mod_mangle(c, d->str), v);
|
||||
continue;
|
||||
}
|
||||
/* Multi-word (str=16, slice=24). Only zero-init shapes
|
||||
* are supported: no rhs, or `nil`, or `""` (which interns
|
||||
* to a strlit but we still emit a zero header — the
|
||||
* program has to assign a real strlit/slice at runtime
|
||||
* to use it). */
|
||||
if (d->rhs != NULL) {
|
||||
/* Multi-word (str=16, slice=24, struct=N). Only zero-init
|
||||
* shapes are supported: no rhs, or `nil`, or `""` (which
|
||||
* interns to a strlit but we still emit a zero header —
|
||||
* the program has to assign a real strlit / slice / field
|
||||
* at runtime to use it). Struct literals as init are
|
||||
* 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;
|
||||
while (r != NULL && r->kind == N_CAST) r = r->lhs;
|
||||
if (r == NULL) continue;
|
||||
@@ -3722,6 +3772,8 @@ emit_lets(Cg *c, FILE *out, Node *file)
|
||||
if (r->kind != N_NIL && !empty_str)
|
||||
continue;
|
||||
}
|
||||
if (d->rhs != NULL && let_isstruct(d->type))
|
||||
continue;
|
||||
emit_data_row_zero(out, "DATAW", mod_mangle(c, d->str), sz);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user