cgen: address module-global str/slice pseudo-field stores via the symbol

A module-global base has no frame slot; treating its local-lookup miss
as offset zero wrote .ptr/.len/.cap at the caller return address. LEAQ
the symbol like the struct-field global arms do. Both stages.
This commit is contained in:
2026-08-07 23:00:08 +09:00
parent 7dc3150b65
commit 5ddada94e5
3 changed files with 67 additions and 3 deletions

View File

@@ -5378,10 +5378,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
else if (strcmp(fld, "cap") == 0) delta = 16;
if (delta < 0) goto after_dot_assign;
int boff = localfind(locals, base->str);
/* A global has no frame slot; treating its lookup miss as
* offset zero writes .len at the caller return address. */
int is_global = boff == 0 && !via_ptr
&& let_islet(base->str);
if (n->op != TK_ASSIGN) {
if (via_ptr) {
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
ins2(c, A_MOVQ, amem(D_BX, delta), areg(D_BX));
} else if (is_global) {
ins2(c, A_LEAQ, masym(c, base->str),
areg(D_BX));
ins2(c, A_MOVQ, amem(D_BX, delta),
areg(D_BX));
} else {
ins2(c, A_MOVQ, amem(D_BP, boff + delta), areg(D_BX));
}
@@ -5395,6 +5404,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (via_ptr) {
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_BX));
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, delta));
} else if (is_global) {
ins2(c, A_LEAQ, masym(c, base->str),
areg(D_BX));
ins2(c, A_MOVQ, areg(D_AX),
amem(D_BX, delta));
} else {
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + delta));
}

View File

@@ -11170,9 +11170,9 @@ fn cgassign(c: *cgen, n: *syntax.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`.
// Top-level global field assignment: `g.f = expr;` and `g.f += expr;`
// for a struct field or str/slice pseudo-field. Reached when the local
// lookup misses but the IDENT base is a registered module `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
@@ -11185,6 +11185,37 @@ fn cgassign(c: *cgen, n: *syntax.node) void = {
if (base.kind == syntax.nkind.N_IDENT) {
let bn: str = base.str;
if (localfindnode(c, bn) == nil) {
let btype: *syntax.tinfo = base.type_: *syntax.tinfo;
if (isletvar(c, bn) &&
(syntax.typeisstr(btype) || syntax.typeisslice(btype))) {
let delta: i32 = -1;
if (syntax.streq(fld, "ptr")) { delta = 0; };
if (syntax.streq(fld, "len")) { delta = 8; };
if (syntax.streq(fld, "cap")) { delta = 16; };
if (delta >= 0) {
if (n.op != syntax.tkind.TK_ASSIGN) {
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\tMOVQ\t");
emitdispreg(delta: i64, "BX");
emitline(", BX\n");
emitline("\tPUSHQ\tBX\n");
};
cgexpr(c, n.rhs);
if (n.op != syntax.tkind.TK_ASSIGN) {
emitline("\tPOPQ\tBX\n");
cgdotfieldcombine(c, n.op, false);
};
emitline("\tLEAQ\t");
emitsymname(c, bn);
emitline("(SB), BX\n");
emitline("\tMOVQ\tAX, ");
emitdispreg(delta: i64, "BX");
emitline("\n");
return;
};
};
// #31: resolve the global value-struct field OFFSET + type
// off the checker-STAMPED receiver tinfo (tichase(base.type_)),
// NOT the name-keyed global-struct leaf lookup. A global decl

View File

@@ -0,0 +1,19 @@
// A module-global slice header is not a BP-relative local. Writing its
// pseudo-fields through offset zero overwrites the caller's saved control
// state instead of the global header.
package global_slice_pseudofield_store_test;
let values: []i64 = [10, 20, 30, 40];
let replacement: [2]i64 = [70, 80];
@test fn global_header_store() void = {
values.ptr = &replacement[0];
values.len = 1;
values.cap = 2;
values.len += 1;
assert(values.len == 2);
assert(values.cap == 2);
assert(values[0] == 70);
assert(values[1] == 80);
};