w6c+wwstage: addr-of/slice struct array-field via dotbaseaddr (#252)

Taking &x.o[i] (address-of) 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 (MOVL off(BP),AX) instead of its ADDRESS (LEAQ off(BP),AX) ->
garbage pointer -> segfault. The index read/write path was fixed in
#135; this is the unwired addr-of + slice sibling — both base-address
paths fell to the generic cgexpr(base) auto-deref.

Wire the #135 cg_dotbase_addr / dotbaseaddr helper into the addr-of
N_INDEX complex-base arm and the N_SLICE base arm, symmetric on both
stages (guarded if(!dotbase) cgexpr(base)). Extend the slice element
stride (esz) and default-hi length 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.

cstage already derived default-hi via base->type (alen); only wwstage
needed the N_DOT default-hi arm. cs==ww byte-identical on every shape.

test/949_dotbase_addr_slice_run: 7 dual-stage rows (addr-of local +
*struct param, explicit + default-hi u8 slice, non-u8 [4]i32 stride,
bare-local control), run + cs==ww byte-id. Regen w6c/wwdump combined.ww.
This commit is contained in:
2026-06-02 02:52:59 +09:00
parent 8f85878bb2
commit 5ebd9eb6db
6 changed files with 379 additions and 25 deletions

View File

@@ -20727,15 +20727,29 @@ fn cgslice(c: *cgen, n: *node) void = {
};
};
};
// #252: an N_DOT `[N]T`-field base (`s.obuf[lo:hi]`) carries no
// tnode — resolve esz / default-hi / base-address from the checker-
// stamped element tinfo on base.type_ instead. Cstage twin reads
// base->type (cgen.c N_SLICE esz + bu->kind==TY_ARRAY default-hi).
let dotbu: *tinfo = nil;
if (base != nil) { if (base.kind == nkind.N_DOT) {
dotbu = base.type_: *tinfo;
for (dotbu != nil && dotbu.kind == tykind.TY_NAMED) {
dotbu = dotbu.under;
};
};};
// esz from the type table for an N_IDENT base (#76; mirrors the
// cgindex idiom). Non-ident base stays esz=1 -> ptr unscaled,
// matching cstage's base->kind==N_IDENT gate.
// cgindex idiom) or an N_DOT array/slice-field base (#252: scale by
// the field's element width, not esz=1 — silently wrong for non-u8).
// Other non-ident bases stay esz=1 -> ptr unscaled.
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
} else { if (globaltn != nil) {
esz = elemsizeofc(c, globaltn);
};};
} else { if (dotbu != nil && dotbu.sub != nil) {
esz = dotbu.sub.size: i32;
};};};
// base address
if (baselocal != nil) {
let tn: *node = baselocal.tnode;
@@ -20766,7 +20780,12 @@ fn cgslice(c: *cgen, n: *node) void = {
emitline("(SB), AX\n");
};
} else { if (base != nil) {
cgexpr(c, base);
// #252: N_DOT `[N]T`-field base → field ADDRESS via
// dotbaseaddr (LEAQ), not the auto-deref VALUE load cgexpr
// emits. Sibling of the #135 read-side.
if (!dotbaseaddr(c, base, "AX")) {
cgexpr(c, base);
};
};};};
emitline("\tPUSHQ\tAX\n");
// lo (default 0)
@@ -20825,9 +20844,16 @@ fn cgslice(c: *cgen, n: *node) void = {
handled = true;
};};
if (!handled) { emitline("\tMOVQ\t$0, AX\n"); };
} else { if (dotbu != nil && dotbu.kind == tykind.TY_ARRAY) {
// #252: default-hi `s.obuf[lo:]` on a struct array-field →
// element count from the field's array tinfo. Cstage twin
// cgexpr_int(bu->alen) (cgen.c N_SLICE default-hi).
emitline("\tMOVQ\t$");
emitint(dotbu.alen: i64);
emitline(", AX\n");
} else {
emitline("\tMOVQ\t$0, AX\n");
};};};
};};};};
emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tAX\n");
@@ -22538,8 +22564,14 @@ fn cgun(c: *cgen, n: *node) void = {
// + POPQ AX scratch shuffle was rule-10
// verbose-defensive on the wwstage side
// with no semantic asymmetry (task #21).
// #252: an N_DOT `[N]T`-field base needs the
// field ADDRESS (dotbaseaddr LEAQ), not the
// auto-deref VALUE load cgexpr emits. Sibling
// of the #135 read-side wiring.
emitline("\tPUSHQ\tAX\n");
cgexpr(c, base);
if (!dotbaseaddr(c, base, "AX")) {
cgexpr(c, base);
};
emitline("\tPOPQ\tBX\n");
};};};
emitline("\tADDQ\tBX, AX\n");

View File

@@ -1429,15 +1429,29 @@ fn cgslice(c: *cgen, n: *node) void = {
};
};
};
// #252: an N_DOT `[N]T`-field base (`s.obuf[lo:hi]`) carries no
// tnode — resolve esz / default-hi / base-address from the checker-
// stamped element tinfo on base.type_ instead. Cstage twin reads
// base->type (cgen.c N_SLICE esz + bu->kind==TY_ARRAY default-hi).
let dotbu: *tinfo = nil;
if (base != nil) { if (base.kind == nkind.N_DOT) {
dotbu = base.type_: *tinfo;
for (dotbu != nil && dotbu.kind == tykind.TY_NAMED) {
dotbu = dotbu.under;
};
};};
// esz from the type table for an N_IDENT base (#76; mirrors the
// cgindex idiom). Non-ident base stays esz=1 -> ptr unscaled,
// matching cstage's base->kind==N_IDENT gate.
// cgindex idiom) or an N_DOT array/slice-field base (#252: scale by
// the field's element width, not esz=1 — silently wrong for non-u8).
// Other non-ident bases stay esz=1 -> ptr unscaled.
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
} else { if (globaltn != nil) {
esz = elemsizeofc(c, globaltn);
};};
} else { if (dotbu != nil && dotbu.sub != nil) {
esz = dotbu.sub.size: i32;
};};};
// base address
if (baselocal != nil) {
let tn: *node = baselocal.tnode;
@@ -1468,7 +1482,12 @@ fn cgslice(c: *cgen, n: *node) void = {
emitline("(SB), AX\n");
};
} else { if (base != nil) {
cgexpr(c, base);
// #252: N_DOT `[N]T`-field base → field ADDRESS via
// dotbaseaddr (LEAQ), not the auto-deref VALUE load cgexpr
// emits. Sibling of the #135 read-side.
if (!dotbaseaddr(c, base, "AX")) {
cgexpr(c, base);
};
};};};
emitline("\tPUSHQ\tAX\n");
// lo (default 0)
@@ -1527,9 +1546,16 @@ fn cgslice(c: *cgen, n: *node) void = {
handled = true;
};};
if (!handled) { emitline("\tMOVQ\t$0, AX\n"); };
} else { if (dotbu != nil && dotbu.kind == tykind.TY_ARRAY) {
// #252: default-hi `s.obuf[lo:]` on a struct array-field →
// element count from the field's array tinfo. Cstage twin
// cgexpr_int(bu->alen) (cgen.c N_SLICE default-hi).
emitline("\tMOVQ\t$");
emitint(dotbu.alen: i64);
emitline(", AX\n");
} else {
emitline("\tMOVQ\t$0, AX\n");
};};};
};};};};
emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tAX\n");
@@ -3240,8 +3266,14 @@ fn cgun(c: *cgen, n: *node) void = {
// + POPQ AX scratch shuffle was rule-10
// verbose-defensive on the wwstage side
// with no semantic asymmetry (task #21).
// #252: an N_DOT `[N]T`-field base needs the
// field ADDRESS (dotbaseaddr LEAQ), not the
// auto-deref VALUE load cgexpr emits. Sibling
// of the #135 read-side wiring.
emitline("\tPUSHQ\tAX\n");
cgexpr(c, base);
if (!dotbaseaddr(c, base, "AX")) {
cgexpr(c, base);
};
emitline("\tPOPQ\tBX\n");
};};};
emitline("\tADDQ\tBX, AX\n");

View File

@@ -20727,15 +20727,29 @@ fn cgslice(c: *cgen, n: *node) void = {
};
};
};
// #252: an N_DOT `[N]T`-field base (`s.obuf[lo:hi]`) carries no
// tnode — resolve esz / default-hi / base-address from the checker-
// stamped element tinfo on base.type_ instead. Cstage twin reads
// base->type (cgen.c N_SLICE esz + bu->kind==TY_ARRAY default-hi).
let dotbu: *tinfo = nil;
if (base != nil) { if (base.kind == nkind.N_DOT) {
dotbu = base.type_: *tinfo;
for (dotbu != nil && dotbu.kind == tykind.TY_NAMED) {
dotbu = dotbu.under;
};
};};
// esz from the type table for an N_IDENT base (#76; mirrors the
// cgindex idiom). Non-ident base stays esz=1 -> ptr unscaled,
// matching cstage's base->kind==N_IDENT gate.
// cgindex idiom) or an N_DOT array/slice-field base (#252: scale by
// the field's element width, not esz=1 — silently wrong for non-u8).
// Other non-ident bases stay esz=1 -> ptr unscaled.
let esz: i32 = 1;
if (baselocal != nil) {
esz = elemsizeofc(c, baselocal.tnode);
} else { if (globaltn != nil) {
esz = elemsizeofc(c, globaltn);
};};
} else { if (dotbu != nil && dotbu.sub != nil) {
esz = dotbu.sub.size: i32;
};};};
// base address
if (baselocal != nil) {
let tn: *node = baselocal.tnode;
@@ -20766,7 +20780,12 @@ fn cgslice(c: *cgen, n: *node) void = {
emitline("(SB), AX\n");
};
} else { if (base != nil) {
cgexpr(c, base);
// #252: N_DOT `[N]T`-field base → field ADDRESS via
// dotbaseaddr (LEAQ), not the auto-deref VALUE load cgexpr
// emits. Sibling of the #135 read-side.
if (!dotbaseaddr(c, base, "AX")) {
cgexpr(c, base);
};
};};};
emitline("\tPUSHQ\tAX\n");
// lo (default 0)
@@ -20825,9 +20844,16 @@ fn cgslice(c: *cgen, n: *node) void = {
handled = true;
};};
if (!handled) { emitline("\tMOVQ\t$0, AX\n"); };
} else { if (dotbu != nil && dotbu.kind == tykind.TY_ARRAY) {
// #252: default-hi `s.obuf[lo:]` on a struct array-field →
// element count from the field's array tinfo. Cstage twin
// cgexpr_int(bu->alen) (cgen.c N_SLICE default-hi).
emitline("\tMOVQ\t$");
emitint(dotbu.alen: i64);
emitline(", AX\n");
} else {
emitline("\tMOVQ\t$0, AX\n");
};};};
};};};};
emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tAX\n");
@@ -22538,8 +22564,14 @@ fn cgun(c: *cgen, n: *node) void = {
// + POPQ AX scratch shuffle was rule-10
// verbose-defensive on the wwstage side
// with no semantic asymmetry (task #21).
// #252: an N_DOT `[N]T`-field base needs the
// field ADDRESS (dotbaseaddr LEAQ), not the
// auto-deref VALUE load cgexpr emits. Sibling
// of the #135 read-side wiring.
emitline("\tPUSHQ\tAX\n");
cgexpr(c, base);
if (!dotbaseaddr(c, base, "AX")) {
cgexpr(c, base);
};
emitline("\tPOPQ\tBX\n");
};};};
emitline("\tADDQ\tBX, AX\n");