wcc: N_DOT-base address arithmetic for [N]T-field index (#135)
Strategy (a) use-site fix: new helper cg_dotbase_addr (cstage) / dotbaseaddr (wwstage) detects `base.kind == N_DOT` whose field type is TY_ARRAY and emits the field's address inline — LEAQ inner_off+ field_off(BP) for a value-struct inner, MOVQ inner_off(BP),reg + ADDQ field_off,reg for a *struct inner. The TY_ARRAY-only gate (after TY_NAMED peel) keeps the helper INERT on TY_PTR/TY_SLICE/TY_STR/ TY_TAGGED field kinds where the existing cgexpr(base) path is correct (loads pointer/header value, then adds scaled index). Wired at 6 sites: cstage cgassign N_INDEX-lhs plain ASSIGN + #133 compound arm + cgindex N_INDEX read fallback; wwstage twin × 3. Closes the silent-segfault on `(*struct).array_field[i]` reads and writes — pre-fix cgexpr on the N_DOT base auto-derefed and loaded the field's first 8 bytes as if they were a pointer, faulting on packed [N]u8 arrays (small u64 → unmapped page). Bootstrap-NEUTRAL: zero working callers in either direction pre-fix (symmetric READ + WRITE segfault evidence). All corpus + 990-997 byte-id + combined_ww_fresh stay green post-fix. 949_dotbase_arr_run: 3 rows direct runtime + cs==ww byte-id (READ u8, plain WRITE u8, compound WRITE u8). Wider element widths and value-struct base / pointer-field-control rows deferred — blocked by orthogonal pre-existing wwstage divergences (i32-return ABI MOVSXD vs MOVL, uninit-struct-let zero-init asymmetry) documented in the test body. The TY_ARRAY-gate no-over-fire is implicitly verified by 994/995 (corpus exercises thousands of struct.pointerfield[i] shapes; any over-fire would shift bytes). Chained N_DOT (`outer.inner.array[i]` depth ≥2) deferred to #137 — confirmed not in ref/hare/strconv/decimal.ha or sibling strconv/. Not a fold-3 blocker; helper bails (returns false) on chained shape, caller falls back to existing cgexpr path.
This commit is contained in:
@@ -15038,6 +15038,85 @@ fn cgslicehdr(c: *cgen, base: str) void = {
|
||||
if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
|
||||
};
|
||||
|
||||
// dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an
|
||||
// N_DOT with N_IDENT inner. 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`.
|
||||
//
|
||||
// #135: cgexpr on an N_DOT whose .field is a `[N]T`-typed field auto-
|
||||
// derefs + loads the field's 8-byte VALUE as if it were a pointer. For
|
||||
// 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.
|
||||
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 lc: *local = localfindnode(c, inner.str);
|
||||
if (lc == nil) { return false; };
|
||||
let bu: *tinfo = inner.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
let viaptr: bool = false;
|
||||
let structt: *tinfo = nil;
|
||||
if (bu.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = bu.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
structt = st;
|
||||
viaptr = true;
|
||||
}; };
|
||||
} else { if (bu.kind == tykind.TY_STRUCT) {
|
||||
structt = bu;
|
||||
}; };
|
||||
if (structt == nil) { return false; };
|
||||
let f: *tfield = structt.fields;
|
||||
let foff: i64 = -1;
|
||||
let ft: *tinfo = nil;
|
||||
for (f != nil) {
|
||||
if (streq(f.name, base.str)) {
|
||||
foff = f.offset: i64;
|
||||
ft = f.type_;
|
||||
break;
|
||||
};
|
||||
f = f.tnext;
|
||||
};
|
||||
if (foff < 0) { return false; };
|
||||
// Only fire on `[N]T` fields — for `*T` / `[]T` / `str` fields
|
||||
// the existing cgexpr(base) path correctly loads the pointer/
|
||||
// header value; over-firing here would skip the deref. Cstage
|
||||
// twin gate at cg_dotbase_addr.
|
||||
for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; };
|
||||
if (ft == nil) { return false; };
|
||||
if (ft.kind != tykind.TY_ARRAY) { return false; };
|
||||
let innoff: i64 = lc.off: i64;
|
||||
if (viaptr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(innoff);
|
||||
emitline("(BP), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
if (foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint(foff);
|
||||
emitline(", ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(innoff + foff);
|
||||
emitline("(BP), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
fn cgindex(c: *cgen, n: *node) void = {
|
||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
||||
@@ -15268,8 +15347,13 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// Generic fallback when base isn't a plain ident.
|
||||
// #135: N_DOT base on `[N]T` field needs the field's ADDRESS,
|
||||
// not its value. cgexpr would auto-deref + load the 8-byte value
|
||||
// as if it were a pointer. dotbaseaddr emits the address inline.
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
if (!dotbaseaddr(c, base, "AX")) {
|
||||
cgexpr(c, base);
|
||||
};
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tADDQ\tBX, AX\n");
|
||||
if (elem_tagged) {
|
||||
@@ -18597,10 +18681,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else { if (dotbaseaddr(c, base, "BX")) {
|
||||
// #135: N_DOT base address-of-field inline.
|
||||
} else {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
};};};
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
@@ -18757,10 +18843,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else { if (dotbaseaddr(c, base, "BX")) {
|
||||
// #135: N_DOT base address-of-field inline.
|
||||
} else {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
};};};
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
let lop: str = tnodeloadop(c, elemtn, esz);
|
||||
|
||||
@@ -741,6 +741,85 @@ fn cgslicehdr(c: *cgen, base: str) void = {
|
||||
if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
|
||||
};
|
||||
|
||||
// dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an
|
||||
// N_DOT with N_IDENT inner. 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`.
|
||||
//
|
||||
// #135: cgexpr on an N_DOT whose .field is a `[N]T`-typed field auto-
|
||||
// derefs + loads the field's 8-byte VALUE as if it were a pointer. For
|
||||
// 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.
|
||||
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 lc: *local = localfindnode(c, inner.str);
|
||||
if (lc == nil) { return false; };
|
||||
let bu: *tinfo = inner.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
let viaptr: bool = false;
|
||||
let structt: *tinfo = nil;
|
||||
if (bu.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = bu.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
structt = st;
|
||||
viaptr = true;
|
||||
}; };
|
||||
} else { if (bu.kind == tykind.TY_STRUCT) {
|
||||
structt = bu;
|
||||
}; };
|
||||
if (structt == nil) { return false; };
|
||||
let f: *tfield = structt.fields;
|
||||
let foff: i64 = -1;
|
||||
let ft: *tinfo = nil;
|
||||
for (f != nil) {
|
||||
if (streq(f.name, base.str)) {
|
||||
foff = f.offset: i64;
|
||||
ft = f.type_;
|
||||
break;
|
||||
};
|
||||
f = f.tnext;
|
||||
};
|
||||
if (foff < 0) { return false; };
|
||||
// Only fire on `[N]T` fields — for `*T` / `[]T` / `str` fields
|
||||
// the existing cgexpr(base) path correctly loads the pointer/
|
||||
// header value; over-firing here would skip the deref. Cstage
|
||||
// twin gate at cg_dotbase_addr.
|
||||
for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; };
|
||||
if (ft == nil) { return false; };
|
||||
if (ft.kind != tykind.TY_ARRAY) { return false; };
|
||||
let innoff: i64 = lc.off: i64;
|
||||
if (viaptr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(innoff);
|
||||
emitline("(BP), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
if (foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint(foff);
|
||||
emitline(", ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(innoff + foff);
|
||||
emitline("(BP), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
fn cgindex(c: *cgen, n: *node) void = {
|
||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
||||
@@ -971,8 +1050,13 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// Generic fallback when base isn't a plain ident.
|
||||
// #135: N_DOT base on `[N]T` field needs the field's ADDRESS,
|
||||
// not its value. cgexpr would auto-deref + load the 8-byte value
|
||||
// as if it were a pointer. dotbaseaddr emits the address inline.
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
if (!dotbaseaddr(c, base, "AX")) {
|
||||
cgexpr(c, base);
|
||||
};
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tADDQ\tBX, AX\n");
|
||||
if (elem_tagged) {
|
||||
@@ -4300,10 +4384,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else { if (dotbaseaddr(c, base, "BX")) {
|
||||
// #135: N_DOT base address-of-field inline.
|
||||
} else {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
};};};
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
@@ -4460,10 +4546,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else { if (dotbaseaddr(c, base, "BX")) {
|
||||
// #135: N_DOT base address-of-field inline.
|
||||
} else {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
};};};
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
let lop: str = tnodeloadop(c, elemtn, esz);
|
||||
|
||||
@@ -15038,6 +15038,85 @@ fn cgslicehdr(c: *cgen, base: str) void = {
|
||||
if (streq(base, "AX")) { emitmovqload(0i64, base, "AX"); };
|
||||
};
|
||||
|
||||
// dotbaseaddr — emit `&(inner.field)` into `dstreg` when `base` is an
|
||||
// N_DOT with N_IDENT inner. 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`.
|
||||
//
|
||||
// #135: cgexpr on an N_DOT whose .field is a `[N]T`-typed field auto-
|
||||
// derefs + loads the field's 8-byte VALUE as if it were a pointer. For
|
||||
// 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.
|
||||
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 lc: *local = localfindnode(c, inner.str);
|
||||
if (lc == nil) { return false; };
|
||||
let bu: *tinfo = inner.type_: *tinfo;
|
||||
for (bu != nil && bu.kind == tykind.TY_NAMED) { bu = bu.under; };
|
||||
if (bu == nil) { return false; };
|
||||
let viaptr: bool = false;
|
||||
let structt: *tinfo = nil;
|
||||
if (bu.kind == tykind.TY_PTR) {
|
||||
let st: *tinfo = bu.sub;
|
||||
for (st != nil && st.kind == tykind.TY_NAMED) { st = st.under; };
|
||||
if (st != nil) { if (st.kind == tykind.TY_STRUCT) {
|
||||
structt = st;
|
||||
viaptr = true;
|
||||
}; };
|
||||
} else { if (bu.kind == tykind.TY_STRUCT) {
|
||||
structt = bu;
|
||||
}; };
|
||||
if (structt == nil) { return false; };
|
||||
let f: *tfield = structt.fields;
|
||||
let foff: i64 = -1;
|
||||
let ft: *tinfo = nil;
|
||||
for (f != nil) {
|
||||
if (streq(f.name, base.str)) {
|
||||
foff = f.offset: i64;
|
||||
ft = f.type_;
|
||||
break;
|
||||
};
|
||||
f = f.tnext;
|
||||
};
|
||||
if (foff < 0) { return false; };
|
||||
// Only fire on `[N]T` fields — for `*T` / `[]T` / `str` fields
|
||||
// the existing cgexpr(base) path correctly loads the pointer/
|
||||
// header value; over-firing here would skip the deref. Cstage
|
||||
// twin gate at cg_dotbase_addr.
|
||||
for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; };
|
||||
if (ft == nil) { return false; };
|
||||
if (ft.kind != tykind.TY_ARRAY) { return false; };
|
||||
let innoff: i64 = lc.off: i64;
|
||||
if (viaptr) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(innoff);
|
||||
emitline("(BP), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
if (foff != 0) {
|
||||
emitline("\tADDQ\t$");
|
||||
emitint(foff);
|
||||
emitline(", ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitoff(innoff + foff);
|
||||
emitline("(BP), ");
|
||||
emitline(dstreg);
|
||||
emitline("\n");
|
||||
};
|
||||
return true;
|
||||
};
|
||||
|
||||
fn cgindex(c: *cgen, n: *node) void = {
|
||||
// Element-size-aware load: u8 → MOVZBQ, i32 → MOVSXD, u32 → MOVL,
|
||||
// str → (ptr, len) into (AX, BX), everything else → MOVQ. Fast
|
||||
@@ -15268,8 +15347,13 @@ fn cgindex(c: *cgen, n: *node) void = {
|
||||
return;
|
||||
};
|
||||
// Generic fallback when base isn't a plain ident.
|
||||
// #135: N_DOT base on `[N]T` field needs the field's ADDRESS,
|
||||
// not its value. cgexpr would auto-deref + load the 8-byte value
|
||||
// as if it were a pointer. dotbaseaddr emits the address inline.
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, base);
|
||||
if (!dotbaseaddr(c, base, "AX")) {
|
||||
cgexpr(c, base);
|
||||
};
|
||||
emitline("\tPOPQ\tBX\n");
|
||||
emitline("\tADDQ\tBX, AX\n");
|
||||
if (elem_tagged) {
|
||||
@@ -18597,10 +18681,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else { if (dotbaseaddr(c, base, "BX")) {
|
||||
// #135: N_DOT base address-of-field inline.
|
||||
} else {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
};};};
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
emitline("\tPOPQ\tAX\n"); // value
|
||||
@@ -18757,10 +18843,12 @@ fn cgassign(c: *cgen, n: *node) void = {
|
||||
emitoff(baselocal.off: i64);
|
||||
emitline("(BP), BX\n");
|
||||
};
|
||||
} else { if (dotbaseaddr(c, base, "BX")) {
|
||||
// #135: N_DOT base address-of-field inline.
|
||||
} else {
|
||||
cgexpr(c, base);
|
||||
emitline("\tMOVQ\tAX, BX\n");
|
||||
};};};
|
||||
};};};};
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
emitline("\tADDQ\tAX, BX\n");
|
||||
let lop: str = tnodeloadop(c, elemtn, esz);
|
||||
|
||||
Reference in New Issue
Block a user