diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index e85b30f0..e1226ca8 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -1685,9 +1685,63 @@ static void cg_widen_tagged_push(Cg*, Local**, Type*, Node*, int); static void cg_widen_tagged_store(Cg*, Local**, Type*, Node*, int, int, int); static void cg_widen_tag_remap(Cg*, Type*, Type*, int); -/* cg_dotbase_addr — compute &(inner.field) into `dst_reg` for a bare +/* cg_dotchain_addr — compute the ADDRESS of a dot/ident lvalue chain + * into `dst_reg`, dereferencing pointer links mid-chain. Returns 1 on + * success, 0 if a link isn't a struct / ptr-to-struct it can resolve. + * Recursion mirrors the read spine (cgen.c:3722 value-struct field / + * :4033 ptr-field): for `x.f`, recurse to &x, deref if x is a *struct + * (so dst holds the pointee base), then add f's offset. Touches ONLY + * dst_reg — no AX, no stack — so it honours cg_dotbase_addr's caller- + * spill contract. The chained-base arm of cg_dotbase_addr (#253) is its + * sole caller. */ +static int +cg_dotchain_addr(Cg *c, Node *node, int dst_reg, Local *locals) +{ + if (node == NULL) return 0; + if (node->kind == N_IDENT) { + int off = localfind(locals, node->str); + if (off != 0) { + ins2(c, A_LEAQ, amem(D_BP, off), areg(dst_reg)); + return 1; + } + if (let_islet(node->str) || def_isstructdef(node->str)) { + ins2(c, A_LEAQ, masym(c, node->str), areg(dst_reg)); + return 1; + } + return 0; + } + if (node->kind != N_DOT) return 0; + Node *x = node->lhs; + if (x == NULL) return 0; + Type *xt = x->type; + if (xt == NULL || xt == ty_err) return 0; + Type *xu = type_chase_named(xt); + if (xu == NULL) return 0; + int xviaptr = 0; + Type *st = NULL; + if (xu->kind == TY_PTR) { + Type *p = type_chase_named(xu->sub); + if (p && p->kind == TY_STRUCT) { st = p; xviaptr = 1; } + } else if (xu->kind == TY_STRUCT) { + st = xu; + } + if (st == NULL) return 0; + Tfield *f = NULL; + for (Tfield *fl = st->fields; fl; fl = fl->next) + if (strcmp(fl->name, node->str) == 0) { f = fl; break; } + if (f == NULL) return 0; + if (!cg_dotchain_addr(c, x, dst_reg, locals)) return 0; + if (xviaptr) + ins2(c, A_MOVQ, amem(dst_reg, 0), areg(dst_reg)); + if ((int)f->offset != 0) + ins2(c, A_ADDQ, aimm((int)f->offset), areg(dst_reg)); + return 1; +} + +/* cg_dotbase_addr — compute &(inner.field) into `dst_reg` for an * N_DOT base where `inner` is an N_IDENT local (struct value OR *struct - * pointer). Returns 1 if emitted, 0 if base shape isn't supported (the + * pointer) OR a chained N_DOT (#253: `o.p.m` / `o.i.m` / `o.a.b.m`). + * Returns 1 if emitted, 0 if base shape isn't supported (the * caller falls back to its prior `cgexpr(base); MOVQ AX, dst_reg`). * * #135: cgexpr on an N_DOT whose .field is a `[N]T`-typed field auto- @@ -1696,18 +1750,26 @@ static void cg_widen_tag_remap(Cg*, Type*, Type*, int); * `d.fld[i] OP= v`), the caller wants the field's ADDRESS — this helper * supplies it inline, avoiding the value-load. Mirror primitive of the * inverse template at cgen.c arr[i].field (the cgdot N_INDEX-lhs - * branch). Chained N_DOT (`a.b.c.field[i]`) deferred — not in #135 - * scope. + * branch). * - * Caller-spill contract: the helper emits at most one MOVQ + one ADDQ - * (or one LEAQ); it does NOT touch AX unless dst_reg == D_AX. Safe to - * call where AX holds an unrelated live value (BX dst). */ + * #253: a chained inner (`inner` is itself an N_DOT) routes through + * cg_dotchain_addr to recover the container's base — the pointer VALUE + * of inner when inner is a *struct (viaptr), else the ADDRESS of inner + * — then adds the array field's offset. Closes the whole array-field- + * base-address family across every op (index r/w, addr-of, slice, + * compound) since all of them route through this helper. + * + * Caller-spill contract: the helper does NOT touch AX unless + * dst_reg == D_AX. Safe to call where AX holds an unrelated live value + * (BX dst); cg_dotchain_addr keeps the same contract. */ static int cg_dotbase_addr(Cg *c, Node *base, int dst_reg, Local *locals) { if (base == NULL || base->kind != N_DOT) return 0; Node *inner = base->lhs; - if (inner == NULL || inner->kind != N_IDENT) return 0; + if (inner == NULL) return 0; + int chained = (inner->kind == N_DOT); + if (inner->kind != N_IDENT && !chained) return 0; Type *bt = inner->type; /* #128b: module-qualified `mod.arr` where arr is an imported * top-level `let X: [N]T`. The checker leaves SK_USE module-idents @@ -1752,8 +1814,19 @@ cg_dotbase_addr(Cg *c, Node *base, int dst_reg, Local *locals) * pointer/slice/str field as an inline array. */ Type *ft = type_chase_named(f->type); if (ft == NULL || ft->kind != TY_ARRAY) return 0; - int inner_off = localfind(locals, inner->str); int foff = (int)f->offset; + /* #253: chained inner — compute the container base via the dot-chain + * spine (pointer VALUE of inner when viaptr, else its ADDRESS), then + * add the field offset. cg_dotchain_addr keeps the spill contract. */ + if (chained) { + if (!cg_dotchain_addr(c, inner, dst_reg, locals)) return 0; + if (viaptr) + ins2(c, A_MOVQ, amem(dst_reg, 0), areg(dst_reg)); + if (foff != 0) + ins2(c, A_ADDQ, aimm(foff), areg(dst_reg)); + return 1; + } + int inner_off = localfind(locals, inner->str); /* #249 (sibling of #135): a module-GLOBAL struct value base. localfind * returns 0 for a global, so the BP-rel form below would emit `LEAQ * (BP)` (read the stack frame, not the global). Resolve the same way diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 55e57569..ec5841fe 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -20190,8 +20190,84 @@ fn cgslicehdr(c: *cgen, base: str) void = { if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); }; }; +// dotchainaddr — emit the ADDRESS of a dot/ident lvalue chain into +// `dstreg`, dereferencing pointer links mid-chain. Returns true on +// success, false if a link isn't a struct / ptr-to-struct it can +// resolve. Recursion mirrors the cstage read spine: for `x.f`, recurse +// to &x, deref if x is a *struct (so dstreg holds the pointee base), +// then add f's offset. Touches ONLY dstreg (no AX, no stack) — same +// spill contract as dotbaseaddr. The chained-base arm of dotbaseaddr +// (#253) is its sole caller. Cstage twin: cmd/w6c/cgen.c +// `cg_dotchain_addr`. +fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = { + if (n == nil) { return false; }; + if (n.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, n.str); + if (lc != nil) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + emitline("\tLEAQ\t"); + emitsymname(c, n.str); + emitline("(SB), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + if (n.kind != nkind.N_DOT) { return false; }; + let x: *node = n.lhs; + if (x == nil) { return false; }; + let xu: *tinfo = x.type_: *tinfo; + for (xu != nil && xu.kind == tykind.TY_NAMED) { xu = xu.under; }; + if (xu == nil) { return false; }; + let xviaptr: bool = false; + let st: *tinfo = nil; + if (xu.kind == tykind.TY_PTR) { + let p: *tinfo = xu.sub; + for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; }; + if (p != nil) { if (p.kind == tykind.TY_STRUCT) { + st = p; + xviaptr = true; + }; }; + } else { if (xu.kind == tykind.TY_STRUCT) { + st = xu; + }; }; + if (st == nil) { return false; }; + let f: *tfield = st.fields; + let foff: i64 = -1; + for (f != nil) { + if (streq(f.name, n.str)) { + foff = f.offset: i64; + break; + }; + f = f.tnext; + }; + if (foff < 0) { return false; }; + if (!dotchainaddr(c, x, dstreg)) { return false; }; + if (xviaptr) { + emitline("\tMOVQ\t("); + emitline(dstreg); + emitline("), "); + emitline(dstreg); + emitline("\n"); + }; + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", "); + emitline(dstreg); + emitline("\n"); + }; + return true; +}; + // dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an -// N_DOT with N_IDENT inner. Returns true if emitted; callers fall back +// N_DOT with N_IDENT inner OR a chained N_DOT inner (#253: `o.p.m` / +// `o.i.m` / `o.a.b.m`). Returns true if emitted; callers fall back // to `cgexpr(c, base); MOVQ AX, dstreg` on false. Cstage twin: // cmd/w6c/cgen.c `cg_dotbase_addr`. // @@ -20200,39 +20276,49 @@ fn cgslicehdr(c: *cgen, base: str) void = { // an LHS or index-base shape (`d.fld[i] = v` / `d.fld[i]` read / `d.fld // [i] OP= v`), the caller wants the field's ADDRESS — this helper // supplies it inline. Reusable primitive of the inverse template -// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). Chained -// N_DOT (`a.b.c.field[i]`) deferred — not in #135 scope. +// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). +// +// #253: a chained inner (`inner` is itself an N_DOT) routes through +// dotchainaddr to recover the container base — the pointer VALUE of +// inner when inner is a *struct (viaptr), else the ADDRESS of inner — +// then adds the field offset. Closes the array-field-base-address +// family across every op (index r/w, addr-of, slice, compound). fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { if (base == nil) { return false; }; if (base.kind != nkind.N_DOT) { return false; }; let inner: *node = base.lhs; if (inner == nil) { return false; }; - if (inner.kind != nkind.N_IDENT) { return false; }; + let chained: bool = (inner.kind == nkind.N_DOT); + if (inner.kind != nkind.N_IDENT && !chained) { return false; }; // #128b: module-qualified `mod.arr` where arr is an imported // top-level `let X: [N]T`. The checker leaves SK_USE module- // idents without a localfindnode entry; detect via letvartnode // resolving to N_TARRAY and emit LEAQ X(SB). Without this, the // cgindex fallback's cgexpr(base) auto-MOVQs the symbol's first // 8 bytes as if it were a pointer-var — wrong shape (cstage - // sister fix in cg_dotbase_addr). - let lc: *local = localfindnode(c, inner.str); + // sister fix in cg_dotbase_addr). N_IDENT-inner only — a chained + // inner has a valid stamped type_ and routes through dotchainaddr. + let lc: *local = nil; let isglobal: bool = false; - if (lc == nil) { - let gt: *node = letvartnode(c, base.str); - if (gt != nil && gt.kind == nkind.N_TARRAY) { - emitline("\tLEAQ\t"); - emitsymname(c, base.str); - emitline("(SB), "); - emitline(dstreg); - emitline("\n"); - return true; + if (!chained) { + lc = localfindnode(c, inner.str); + if (lc == nil) { + let gt: *node = letvartnode(c, base.str); + if (gt != nil && gt.kind == nkind.N_TARRAY) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + // #249 (sibling of #135): inner is a module-GLOBAL struct value + // (let/def), not a local — lc is nil but inner.type_ is a valid + // struct. Resolve the field below and emit a global base (LEAQ + // name(SB)). A non-struct inner (e.g. an SK_USE module qualifier, + // type ty_err) falls through the struct gate to `return false`. + isglobal = true; }; - // #249 (sibling of #135): inner is a module-GLOBAL struct value - // (let/def), not a local — lc is nil but inner.type_ is a valid - // struct. Resolve the field below and emit a global base (LEAQ - // name(SB)). A non-struct inner (e.g. an SK_USE module qualifier, - // type ty_err) falls through the struct gate to `return false`. - isglobal = true; }; let bu: *tinfo = inner.type_: *tinfo; for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; @@ -20269,6 +20355,27 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; }; if (ft == nil) { return false; }; if (ft.kind != tykind.TY_ARRAY) { return false; }; + // #253: chained inner — compute the container base via the dot-chain + // spine (pointer VALUE of inner when viaptr, else its ADDRESS), then + // add the field offset. dotchainaddr keeps the spill contract. + if (chained) { + if (!dotchainaddr(c, inner, dstreg)) { return false; }; + if (viaptr) { + emitline("\tMOVQ\t("); + emitline(dstreg); + emitline("), "); + emitline(dstreg); + emitline("\n"); + }; + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", "); + emitline(dstreg); + emitline("\n"); + }; + return true; + }; let innoff: i64 = 0; if (lc != nil) { innoff = lc.off: i64; }; if (viaptr) { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 19bc2511..bbc6655b 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -892,8 +892,84 @@ fn cgslicehdr(c: *cgen, base: str) void = { if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); }; }; +// dotchainaddr — emit the ADDRESS of a dot/ident lvalue chain into +// `dstreg`, dereferencing pointer links mid-chain. Returns true on +// success, false if a link isn't a struct / ptr-to-struct it can +// resolve. Recursion mirrors the cstage read spine: for `x.f`, recurse +// to &x, deref if x is a *struct (so dstreg holds the pointee base), +// then add f's offset. Touches ONLY dstreg (no AX, no stack) — same +// spill contract as dotbaseaddr. The chained-base arm of dotbaseaddr +// (#253) is its sole caller. Cstage twin: cmd/w6c/cgen.c +// `cg_dotchain_addr`. +fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = { + if (n == nil) { return false; }; + if (n.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, n.str); + if (lc != nil) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + emitline("\tLEAQ\t"); + emitsymname(c, n.str); + emitline("(SB), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + if (n.kind != nkind.N_DOT) { return false; }; + let x: *node = n.lhs; + if (x == nil) { return false; }; + let xu: *tinfo = x.type_: *tinfo; + for (xu != nil && xu.kind == tykind.TY_NAMED) { xu = xu.under; }; + if (xu == nil) { return false; }; + let xviaptr: bool = false; + let st: *tinfo = nil; + if (xu.kind == tykind.TY_PTR) { + let p: *tinfo = xu.sub; + for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; }; + if (p != nil) { if (p.kind == tykind.TY_STRUCT) { + st = p; + xviaptr = true; + }; }; + } else { if (xu.kind == tykind.TY_STRUCT) { + st = xu; + }; }; + if (st == nil) { return false; }; + let f: *tfield = st.fields; + let foff: i64 = -1; + for (f != nil) { + if (streq(f.name, n.str)) { + foff = f.offset: i64; + break; + }; + f = f.tnext; + }; + if (foff < 0) { return false; }; + if (!dotchainaddr(c, x, dstreg)) { return false; }; + if (xviaptr) { + emitline("\tMOVQ\t("); + emitline(dstreg); + emitline("), "); + emitline(dstreg); + emitline("\n"); + }; + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", "); + emitline(dstreg); + emitline("\n"); + }; + return true; +}; + // dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an -// N_DOT with N_IDENT inner. Returns true if emitted; callers fall back +// N_DOT with N_IDENT inner OR a chained N_DOT inner (#253: `o.p.m` / +// `o.i.m` / `o.a.b.m`). Returns true if emitted; callers fall back // to `cgexpr(c, base); MOVQ AX, dstreg` on false. Cstage twin: // cmd/w6c/cgen.c `cg_dotbase_addr`. // @@ -902,39 +978,49 @@ fn cgslicehdr(c: *cgen, base: str) void = { // an LHS or index-base shape (`d.fld[i] = v` / `d.fld[i]` read / `d.fld // [i] OP= v`), the caller wants the field's ADDRESS — this helper // supplies it inline. Reusable primitive of the inverse template -// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). Chained -// N_DOT (`a.b.c.field[i]`) deferred — not in #135 scope. +// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). +// +// #253: a chained inner (`inner` is itself an N_DOT) routes through +// dotchainaddr to recover the container base — the pointer VALUE of +// inner when inner is a *struct (viaptr), else the ADDRESS of inner — +// then adds the field offset. Closes the array-field-base-address +// family across every op (index r/w, addr-of, slice, compound). fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { if (base == nil) { return false; }; if (base.kind != nkind.N_DOT) { return false; }; let inner: *node = base.lhs; if (inner == nil) { return false; }; - if (inner.kind != nkind.N_IDENT) { return false; }; + let chained: bool = (inner.kind == nkind.N_DOT); + if (inner.kind != nkind.N_IDENT && !chained) { return false; }; // #128b: module-qualified `mod.arr` where arr is an imported // top-level `let X: [N]T`. The checker leaves SK_USE module- // idents without a localfindnode entry; detect via letvartnode // resolving to N_TARRAY and emit LEAQ X(SB). Without this, the // cgindex fallback's cgexpr(base) auto-MOVQs the symbol's first // 8 bytes as if it were a pointer-var — wrong shape (cstage - // sister fix in cg_dotbase_addr). - let lc: *local = localfindnode(c, inner.str); + // sister fix in cg_dotbase_addr). N_IDENT-inner only — a chained + // inner has a valid stamped type_ and routes through dotchainaddr. + let lc: *local = nil; let isglobal: bool = false; - if (lc == nil) { - let gt: *node = letvartnode(c, base.str); - if (gt != nil && gt.kind == nkind.N_TARRAY) { - emitline("\tLEAQ\t"); - emitsymname(c, base.str); - emitline("(SB), "); - emitline(dstreg); - emitline("\n"); - return true; + if (!chained) { + lc = localfindnode(c, inner.str); + if (lc == nil) { + let gt: *node = letvartnode(c, base.str); + if (gt != nil && gt.kind == nkind.N_TARRAY) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + // #249 (sibling of #135): inner is a module-GLOBAL struct value + // (let/def), not a local — lc is nil but inner.type_ is a valid + // struct. Resolve the field below and emit a global base (LEAQ + // name(SB)). A non-struct inner (e.g. an SK_USE module qualifier, + // type ty_err) falls through the struct gate to `return false`. + isglobal = true; }; - // #249 (sibling of #135): inner is a module-GLOBAL struct value - // (let/def), not a local — lc is nil but inner.type_ is a valid - // struct. Resolve the field below and emit a global base (LEAQ - // name(SB)). A non-struct inner (e.g. an SK_USE module qualifier, - // type ty_err) falls through the struct gate to `return false`. - isglobal = true; }; let bu: *tinfo = inner.type_: *tinfo; for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; @@ -971,6 +1057,27 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; }; if (ft == nil) { return false; }; if (ft.kind != tykind.TY_ARRAY) { return false; }; + // #253: chained inner — compute the container base via the dot-chain + // spine (pointer VALUE of inner when viaptr, else its ADDRESS), then + // add the field offset. dotchainaddr keeps the spill contract. + if (chained) { + if (!dotchainaddr(c, inner, dstreg)) { return false; }; + if (viaptr) { + emitline("\tMOVQ\t("); + emitline(dstreg); + emitline("), "); + emitline(dstreg); + emitline("\n"); + }; + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", "); + emitline(dstreg); + emitline("\n"); + }; + return true; + }; let innoff: i64 = 0; if (lc != nil) { innoff = lc.off: i64; }; if (viaptr) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 2930dc74..2a60f8d9 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -20190,8 +20190,84 @@ fn cgslicehdr(c: *cgen, base: str) void = { if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); }; }; +// dotchainaddr — emit the ADDRESS of a dot/ident lvalue chain into +// `dstreg`, dereferencing pointer links mid-chain. Returns true on +// success, false if a link isn't a struct / ptr-to-struct it can +// resolve. Recursion mirrors the cstage read spine: for `x.f`, recurse +// to &x, deref if x is a *struct (so dstreg holds the pointee base), +// then add f's offset. Touches ONLY dstreg (no AX, no stack) — same +// spill contract as dotbaseaddr. The chained-base arm of dotbaseaddr +// (#253) is its sole caller. Cstage twin: cmd/w6c/cgen.c +// `cg_dotchain_addr`. +fn dotchainaddr(c: *cgen, n: *node, dstreg: str) bool = { + if (n == nil) { return false; }; + if (n.kind == nkind.N_IDENT) { + let lc: *local = localfindnode(c, n.str); + if (lc != nil) { + emitline("\tLEAQ\t"); + emitoff(lc.off: i64); + emitline("(BP), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + emitline("\tLEAQ\t"); + emitsymname(c, n.str); + emitline("(SB), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + if (n.kind != nkind.N_DOT) { return false; }; + let x: *node = n.lhs; + if (x == nil) { return false; }; + let xu: *tinfo = x.type_: *tinfo; + for (xu != nil && xu.kind == tykind.TY_NAMED) { xu = xu.under; }; + if (xu == nil) { return false; }; + let xviaptr: bool = false; + let st: *tinfo = nil; + if (xu.kind == tykind.TY_PTR) { + let p: *tinfo = xu.sub; + for (p != nil && p.kind == tykind.TY_NAMED) { p = p.under; }; + if (p != nil) { if (p.kind == tykind.TY_STRUCT) { + st = p; + xviaptr = true; + }; }; + } else { if (xu.kind == tykind.TY_STRUCT) { + st = xu; + }; }; + if (st == nil) { return false; }; + let f: *tfield = st.fields; + let foff: i64 = -1; + for (f != nil) { + if (streq(f.name, n.str)) { + foff = f.offset: i64; + break; + }; + f = f.tnext; + }; + if (foff < 0) { return false; }; + if (!dotchainaddr(c, x, dstreg)) { return false; }; + if (xviaptr) { + emitline("\tMOVQ\t("); + emitline(dstreg); + emitline("), "); + emitline(dstreg); + emitline("\n"); + }; + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", "); + emitline(dstreg); + emitline("\n"); + }; + return true; +}; + // dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an -// N_DOT with N_IDENT inner. Returns true if emitted; callers fall back +// N_DOT with N_IDENT inner OR a chained N_DOT inner (#253: `o.p.m` / +// `o.i.m` / `o.a.b.m`). Returns true if emitted; callers fall back // to `cgexpr(c, base); MOVQ AX, dstreg` on false. Cstage twin: // cmd/w6c/cgen.c `cg_dotbase_addr`. // @@ -20200,39 +20276,49 @@ fn cgslicehdr(c: *cgen, base: str) void = { // an LHS or index-base shape (`d.fld[i] = v` / `d.fld[i]` read / `d.fld // [i] OP= v`), the caller wants the field's ADDRESS — this helper // supplies it inline. Reusable primitive of the inverse template -// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). Chained -// N_DOT (`a.b.c.field[i]`) deferred — not in #135 scope. +// `arr[i].field = v` (cstage cgen.c arr[i].field address-eval). +// +// #253: a chained inner (`inner` is itself an N_DOT) routes through +// dotchainaddr to recover the container base — the pointer VALUE of +// inner when inner is a *struct (viaptr), else the ADDRESS of inner — +// then adds the field offset. Closes the array-field-base-address +// family across every op (index r/w, addr-of, slice, compound). fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { if (base == nil) { return false; }; if (base.kind != nkind.N_DOT) { return false; }; let inner: *node = base.lhs; if (inner == nil) { return false; }; - if (inner.kind != nkind.N_IDENT) { return false; }; + let chained: bool = (inner.kind == nkind.N_DOT); + if (inner.kind != nkind.N_IDENT && !chained) { return false; }; // #128b: module-qualified `mod.arr` where arr is an imported // top-level `let X: [N]T`. The checker leaves SK_USE module- // idents without a localfindnode entry; detect via letvartnode // resolving to N_TARRAY and emit LEAQ X(SB). Without this, the // cgindex fallback's cgexpr(base) auto-MOVQs the symbol's first // 8 bytes as if it were a pointer-var — wrong shape (cstage - // sister fix in cg_dotbase_addr). - let lc: *local = localfindnode(c, inner.str); + // sister fix in cg_dotbase_addr). N_IDENT-inner only — a chained + // inner has a valid stamped type_ and routes through dotchainaddr. + let lc: *local = nil; let isglobal: bool = false; - if (lc == nil) { - let gt: *node = letvartnode(c, base.str); - if (gt != nil && gt.kind == nkind.N_TARRAY) { - emitline("\tLEAQ\t"); - emitsymname(c, base.str); - emitline("(SB), "); - emitline(dstreg); - emitline("\n"); - return true; + if (!chained) { + lc = localfindnode(c, inner.str); + if (lc == nil) { + let gt: *node = letvartnode(c, base.str); + if (gt != nil && gt.kind == nkind.N_TARRAY) { + emitline("\tLEAQ\t"); + emitsymname(c, base.str); + emitline("(SB), "); + emitline(dstreg); + emitline("\n"); + return true; + }; + // #249 (sibling of #135): inner is a module-GLOBAL struct value + // (let/def), not a local — lc is nil but inner.type_ is a valid + // struct. Resolve the field below and emit a global base (LEAQ + // name(SB)). A non-struct inner (e.g. an SK_USE module qualifier, + // type ty_err) falls through the struct gate to `return false`. + isglobal = true; }; - // #249 (sibling of #135): inner is a module-GLOBAL struct value - // (let/def), not a local — lc is nil but inner.type_ is a valid - // struct. Resolve the field below and emit a global base (LEAQ - // name(SB)). A non-struct inner (e.g. an SK_USE module qualifier, - // type ty_err) falls through the struct gate to `return false`. - isglobal = true; }; let bu: *tinfo = inner.type_: *tinfo; for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; }; @@ -20269,6 +20355,27 @@ fn dotbaseaddr(c: *cgen, base: *node, dstreg: str) bool = { for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; }; if (ft == nil) { return false; }; if (ft.kind != tykind.TY_ARRAY) { return false; }; + // #253: chained inner — compute the container base via the dot-chain + // spine (pointer VALUE of inner when viaptr, else its ADDRESS), then + // add the field offset. dotchainaddr keeps the spill contract. + if (chained) { + if (!dotchainaddr(c, inner, dstreg)) { return false; }; + if (viaptr) { + emitline("\tMOVQ\t("); + emitline(dstreg); + emitline("), "); + emitline(dstreg); + emitline("\n"); + }; + if (foff != 0) { + emitline("\tADDQ\t$"); + emitint(foff); + emitline(", "); + emitline(dstreg); + emitline("\n"); + }; + return true; + }; let innoff: i64 = 0; if (lc != nil) { innoff = lc.off: i64; }; if (viaptr) { diff --git a/test/wcc/949_dotbase_addr_slice_run.c b/test/wcc/949_dotbase_addr_slice_run.c index 0eb6fff5..a5edb34b 100644 --- a/test/wcc/949_dotbase_addr_slice_run.c +++ b/test/wcc/949_dotbase_addr_slice_run.c @@ -1,27 +1,34 @@ /* - * 949_dotbase_addr_slice_run — runtime + byte-id net for #252, the - * addr-of + slice SIBLING of #135 (949_dotbase_arr_run covers the - * read/write index path). Taking `&x.o[i]` (address-of an element) or - * slicing `x.o[lo:hi]` / `x.o[lo:]` of a struct's `[N]T`-typed FIELD - * computed the field's VALUE as the base address instead of its - * ADDRESS: cgen emitted `MOVL off(BP),AX` (load the field's first - * 8 bytes as a pointer) where it must emit `LEAQ off(BP),AX` (the - * field's address) -> garbage pointer -> SEGFAULT. The index - * read/write path was fixed in #135; the addr-of N_INDEX "complex - * base" arm and the N_SLICE base arm still fell to the generic - * cgexpr(base) auto-deref. cs==ww BOTH stages broken identically + * 949_dotbase_addr_slice_run — runtime + byte-id net for the array- + * field-base-address family: #252 (the addr-of + slice SIBLING of #135) + * and #253 (the CHAINED-base close-out). 949_dotbase_arr_run covers the + * single-level read/write index path. + * + * #252: taking `&x.o[i]` (address-of an element) or slicing + * `x.o[lo:hi]` / `x.o[lo:]` of a struct's `[N]T`-typed FIELD computed + * the field's VALUE as the base address instead of its ADDRESS: cgen + * emitted `MOVL off(BP),AX` (load the field's first 8 bytes as a + * pointer) where it must emit `LEAQ off(BP),AX` (the field's address) + * -> garbage pointer -> SEGFAULT. The index read/write path was fixed + * in #135; the addr-of N_INDEX "complex base" arm and the N_SLICE base + * arm still fell to the generic cgexpr(base) auto-deref. + * + * #253: the same family with a CHAINED base — the inner is itself an + * N_DOT (`o.p.m[i]` / `o.i.m[i]` / `o.a.b.m[i]`), not a bare ident. + * `cg_dotbase_addr` / `dotbaseaddr` rejected a non-ident inner, so the + * caller fell to cgexpr(base) which auto-derefs the array field's first + * 8 bytes AS a pointer -> garbage base -> SEGFAULT (base64 fillobuf + * `s.enc.encmap[...]` blocker). The fix recovers the container base via + * the dot-chain spine (`cg_dotchain_addr` / `dotchainaddr`): the pointer + * VALUE of inner when inner is a *struct, else the ADDRESS of inner, + * then adds the field offset. One helper extension per stage closes the + * whole family — every op (index r/w, addr-of, slice, compound) routes + * through the same helper. cs==ww BOTH stages broken identically * pre-fix (gate-blind, pure correctness — not a byte-id divergence). * - * Fix wires the same `cg_dotbase_addr` (cstage) / `dotbaseaddr` - * (wwstage) helper #135 introduced into those two base-address paths - * (guarded `if(!cg_dotbase_addr(...)) cgexpr(base)`). For the slice - * path the element stride (esz) and default-hi length are extended to - * an N_DOT array-field base too (read from the field's element tinfo / - * array length via the type table — rule-13), so non-u8 element slices - * scale correctly and `s.obuf[lo:]` gets the array's element count. - * - * Rows (cstage `ww build` + run for exit code; w6c vs w6c_ww `.s` cmp - * for rule-10 byte-id): + * Byte-id rows (cstage `ww build` + run for exit code; w6c vs w6c_ww + * `.s` cmp for rule-10 byte-id): + * #252 (bare-ident base): * - addr_local_u8 &x.o[1] on a local value-struct, *p read → 66 * - addr_ptr_u8 &x.o[2] via a *struct param, *p read → 77 * - addr_i32 &x.o[2] on [4]i32 field, *p read (esz=4) → 88 @@ -31,10 +38,40 @@ * - slice_i32_expl [4]i32 field x.o[1:3], s[1] read (esz=4) → 88 * - slice_i32_dflt [4]i32 field x.o[1:], s[2] read (esz=4) → 55 * - control_bare bare-local [4]u8 &a[1] write + a[1:4] read → 44 + * #253 (chained base — *struct-field-pointer / deeper / non-u8): + * - chain_ptr_rd o.p.m[1] read (p:*inner field) → 66 + * - chain_ptr_wr o.p.m[2] write, read back via a.m[2] → 77 + * - chain_ptr_addr &o.p.m[2] then *q read → 55 + * - chain_ptr_sl_e o.p.m[1:4] explicit hi, s[0] → 66 + * - chain_ptr_sl_d o.p.m[1:] default hi, s[0] → 66 + * - chain_ptr_comp o.p.m[1] += v compound → 66 + * - chain_deep_lf o.a.b.m[1] read (a value, b:*inner leaf) → 66 + * - chain_triple o.p.q.m[1] read (two ptr links: dotchain → 66 + * internal deref, pointer-only structs) + * - chain_tri_comp o.p.q.m[1] += v through the triple chain → 66 + * - chain_i32_addr &o.p.m[2] on [4]i32 (esz=4 stride) → 88 + * - chain_i32_slice o.p.m[1:3] on [4]i32, s[1] (esz=4) → 88 + * - ctrl_ptr_rd p.m[1] read via *e param (control) → 66 + * - ctrl_local_rd x.m[1] read, value-local field (control) → 66 * - * The bare-local control asserts the N_IDENT base paths (untouched by - * this fix) still emit correct code; the non-u8 rows assert the esz - * stride extension is wired (not silently esz=1). + * Run-only rows (byteid=0): the chained VALUE-container arm (`o.i.m`, + * inner is a value nested struct). These exercise the same fixed helper + * and run correctly, but a value nested-struct instance trips THREE + * orthogonal pre-existing cs!=ww divergences unrelated to #253 — bare- + * let zero-init policy (wwstage emits an extra `MOVQ $0,off(BP)`), + * global DATAW byte count (wwstage over-emits), and the i32 element- + * LOAD opcode in the index fallback (cstage MOVSXD vs wwstage MOVL) — + * so the rule-10 byte-id gate can't apply here until those are fixed + * (filed: wwstage value-nested-struct emission divergence). Run + * correctness alone proves the #253 segfault is gone for this cell. + * - chain_val_rd o.i.m[1] read (i value nested) → 66 + * - chain_val_addr &o.i.m[2] then *q read → 55 + * - chain_val_slice o.i.m[1:4], s[0] → 66 + * - chain_deep_val o.a.b.m[1] read, full value chain → 66 + * + * The bare-local control asserts the N_IDENT base paths still emit + * correct code; the non-u8 rows assert the esz stride extension is + * wired (not silently esz=1). */ #include #include @@ -52,7 +89,7 @@ runwait(const char *cmd) return -1; } -struct row { const char *label; const char *src; int want_exit; }; +struct row { const char *label; const char *src; int want_exit; int byteid; }; static const struct row rows[] = { { "addr_local_u8", @@ -63,7 +100,7 @@ static const struct row rows[] = { " x.o[1] = 66u8;\n" " let p: *u8 = &x.o[1];\n" " return (*p): i32;\n" - "};\n", 66 }, + "};\n", 66, 1 }, { "addr_ptr_u8", "package main;\n" "type e = struct { o: [4]u8 };\n" @@ -72,7 +109,7 @@ static const struct row rows[] = { " let x: e;\n" " x.o[2] = 77u8;\n" " return rd(&x): i32;\n" - "};\n", 77 }, + "};\n", 77, 1 }, { "addr_i32", "package main;\n" "type e = struct { o: [4]i32 };\n" @@ -81,7 +118,7 @@ static const struct row rows[] = { " x.o[2] = 88;\n" " let p: *i32 = &x.o[2];\n" " return *p;\n" - "};\n", 88 }, + "};\n", 88, 1 }, { "slice_u8_expl", "package main;\n" "type e = struct { o: [4]u8 };\n" @@ -90,7 +127,7 @@ static const struct row rows[] = { " x.o[1] = 66u8;\n" " let s: []u8 = x.o[1:4];\n" " return s[0]: i32;\n" - "};\n", 66 }, + "};\n", 66, 1 }, { "slice_u8_dflthi", "package main;\n" "type e = struct { o: [4]u8 };\n" @@ -99,7 +136,7 @@ static const struct row rows[] = { " x.o[1] = 66u8;\n" " let s: []u8 = x.o[1:];\n" " return s[0]: i32;\n" - "};\n", 66 }, + "};\n", 66, 1 }, { "slice_ptr_u8", "package main;\n" "type e = struct { o: [4]u8 };\n" @@ -108,7 +145,7 @@ static const struct row rows[] = { " let x: e;\n" " x.o[1] = 66u8;\n" " return sl(&x): i32;\n" - "};\n", 66 }, + "};\n", 66, 1 }, { "slice_i32_expl", "package main;\n" "type e = struct { o: [4]i32 };\n" @@ -118,7 +155,7 @@ static const struct row rows[] = { " x.o[2] = 88;\n" " let s: []i32 = x.o[1:3];\n" " return s[1];\n" - "};\n", 88 }, + "};\n", 88, 1 }, { "slice_i32_dflt", "package main;\n" "type e = struct { o: [4]i32 };\n" @@ -127,7 +164,7 @@ static const struct row rows[] = { " x.o[3] = 55;\n" " let s: []i32 = x.o[1:];\n" " return s[2];\n" - "};\n", 55 }, + "};\n", 55, 1 }, { "control_bare", "package main;\n" "export fn main() i32 = {\n" @@ -137,8 +174,176 @@ static const struct row rows[] = { " *p = 33u8;\n" " let s: []u8 = a[1:4];\n" " return s[1]: i32;\n" - "};\n", 44 }, - { NULL, NULL, 0 } + "};\n", 44, 1 }, + /* #253 chained-base rows. inner/outer types declared per-row so + * each source is self-contained. */ + { "chain_ptr_rd", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outer = struct { p: *inner };\n" + "export fn main() i32 = {\n" + " let a: inner; a.m[1] = 66u8;\n" + " let o: outer; o.p = &a;\n" + " return o.p.m[1]: i32;\n" + "};\n", 66, 1 }, + { "chain_ptr_wr", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outer = struct { p: *inner };\n" + "export fn main() i32 = {\n" + " let a: inner;\n" + " let o: outer; o.p = &a;\n" + " o.p.m[2] = 77u8;\n" + " return a.m[2]: i32;\n" + "};\n", 77, 1 }, + { "chain_ptr_addr", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outer = struct { p: *inner };\n" + "export fn main() i32 = {\n" + " let a: inner; a.m[2] = 55u8;\n" + " let o: outer; o.p = &a;\n" + " let q: *u8 = &o.p.m[2];\n" + " return (*q): i32;\n" + "};\n", 55, 1 }, + { "chain_ptr_sl_e", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outer = struct { p: *inner };\n" + "export fn main() i32 = {\n" + " let a: inner; a.m[1] = 66u8;\n" + " let o: outer; o.p = &a;\n" + " let s: []u8 = o.p.m[1:4];\n" + " return s[0]: i32;\n" + "};\n", 66, 1 }, + { "chain_ptr_sl_d", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outer = struct { p: *inner };\n" + "export fn main() i32 = {\n" + " let a: inner; a.m[1] = 66u8;\n" + " let o: outer; o.p = &a;\n" + " let s: []u8 = o.p.m[1:];\n" + " return s[0]: i32;\n" + "};\n", 66, 1 }, + { "chain_ptr_comp", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outer = struct { p: *inner };\n" + "export fn main() i32 = {\n" + " let a: inner; a.m[1] = 60u8;\n" + " let o: outer; o.p = &a;\n" + " o.p.m[1] += 6u8;\n" + " return o.p.m[1]: i32;\n" + "};\n", 66, 1 }, + { "chain_deep_lf", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type mid = struct { b: *inner };\n" + "type top = struct { a: mid };\n" + "export fn main() i32 = {\n" + " let z: inner; z.m[1] = 66u8;\n" + " let o: top; o.a.b = &z;\n" + " return o.a.b.m[1]: i32;\n" + "};\n", 66, 1 }, + { "chain_triple", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type amid = struct { q: *inner };\n" + "type otop = struct { p: *amid };\n" + "export fn main() i32 = {\n" + " let z: inner; z.m[1] = 66u8;\n" + " let aa: amid; aa.q = &z;\n" + " let o: otop; o.p = &aa;\n" + " return o.p.q.m[1]: i32;\n" + "};\n", 66, 1 }, + { "chain_tri_comp", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type amid = struct { q: *inner };\n" + "type otop = struct { p: *amid };\n" + "export fn main() i32 = {\n" + " let z: inner; z.m[1] = 60u8;\n" + " let aa: amid; aa.q = &z;\n" + " let o: otop; o.p = &aa;\n" + " o.p.q.m[1] += 6u8;\n" + " return o.p.q.m[1]: i32;\n" + "};\n", 66, 1 }, + { "chain_i32_addr", + "package main;\n" + "type inneri = struct { m: [4]i32 };\n" + "type outeri = struct { p: *inneri };\n" + "export fn main() i32 = {\n" + " let a: inneri; a.m[2] = 88;\n" + " let o: outeri; o.p = &a;\n" + " let q: *i32 = &o.p.m[2];\n" + " return *q;\n" + "};\n", 88, 1 }, + { "chain_i32_slice", + "package main;\n" + "type inneri = struct { m: [4]i32 };\n" + "type outeri = struct { p: *inneri };\n" + "export fn main() i32 = {\n" + " let a: inneri; a.m[1] = 99; a.m[2] = 88;\n" + " let o: outeri; o.p = &a;\n" + " let s: []i32 = o.p.m[1:3];\n" + " return s[1];\n" + "};\n", 88, 1 }, + { "ctrl_ptr_rd", + "package main;\n" + "type e = struct { m: [4]u8 };\n" + "fn rd(p: *e) u8 = { return p.m[1]; };\n" + "export fn main() i32 = {\n" + " let x: e; x.m[1] = 66u8;\n" + " return rd(&x): i32;\n" + "};\n", 66, 1 }, + { "ctrl_local_rd", + "package main;\n" + "type e = struct { m: [4]u8 };\n" + "export fn main() i32 = {\n" + " let x: e; x.m[1] = 66u8;\n" + " return x.m[1]: i32;\n" + "};\n", 66, 1 }, + /* #253 chained VALUE-container arm (o.i.m). Run-only (byteid=0): + * a value nested-struct instance trips orthogonal pre-existing + * cs!=ww emission divergences (see header). The fixed helper runs + * these correctly — the segfault is gone. */ + { "chain_val_rd", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outv = struct { i: inner };\n" + "export fn main() i32 = {\n" + " let o: outv; o.i.m[1] = 66u8;\n" + " return o.i.m[1]: i32;\n" + "};\n", 66, 0 }, + { "chain_val_addr", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outv = struct { i: inner };\n" + "export fn main() i32 = {\n" + " let o: outv; o.i.m[2] = 55u8;\n" + " let q: *u8 = &o.i.m[2];\n" + " return (*q): i32;\n" + "};\n", 55, 0 }, + { "chain_val_slice", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type outv = struct { i: inner };\n" + "export fn main() i32 = {\n" + " let o: outv; o.i.m[1] = 66u8;\n" + " let s: []u8 = o.i.m[1:4];\n" + " return s[0]: i32;\n" + "};\n", 66, 0 }, + { "chain_deep_val", + "package main;\n" + "type inner = struct { m: [4]u8 };\n" + "type mid = struct { b: inner };\n" + "type top = struct { a: mid };\n" + "export fn main() i32 = {\n" + " let o: top; o.a.b.m[1] = 66u8;\n" + " return o.a.b.m[1]: i32;\n" + "};\n", 66, 0 }, + { NULL, NULL, 0, 0 } }; static int @@ -220,6 +425,13 @@ main(void) } unlink(outbin); rmdir(tmpdir); + if (!rows[i].byteid) { + /* Run-only row — byte-id blocked by an orthogonal + * pre-existing cs!=ww divergence (see header). */ + unlink(src); + continue; + } + char cs_s[64], ws_s[64]; snprintf(cs_s, sizeof cs_s, "/tmp/wwdbs_%d_%d_cs.s", getpid(), i);