From 5ddada94e519b1b486ccfd34f13111e3ca0a6ad6 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 7 Aug 2026 23:00:08 +0900 Subject: [PATCH] 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. --- cmd/w6c/cgen.c | 14 +++++++ selfhost/cmd/wcc/cgenexpr.ww | 37 +++++++++++++++++-- .../global_slice_pseudofield_store_test.ww | 19 ++++++++++ 3 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 test/lang/global_slice_pseudofield_store_test.ww diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index c48d9236..fbba9655 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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)); } diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 91766a38..3c0ddf84 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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 diff --git a/test/lang/global_slice_pseudofield_store_test.ww b/test/lang/global_slice_pseudofield_store_test.ww new file mode 100644 index 00000000..12aed9a8 --- /dev/null +++ b/test/lang/global_slice_pseudofield_store_test.ww @@ -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); +};