selfhost: graduate wwstage &N_DOT[N_INDEX] to cstage canonical lean form (#21)
Latent #21 has two surface shapes — register polarity in cgun TK_AMP N_INDEX's complex-base arm, and indexbaseesz's over-broad .ptr pseudo-field gate — that share a single semantic path: &N_DOT[N_INDEX] where the inner N_DOT cannot be peeled into a plain ident base. Polarity-A (cgenexpr.ww) lifted to cstage's three-line shape; stride-B (cgenutil.ww) narrowed so the .ptr arm only fires on actual str/slice inners and falls through to the generic struct-field arm for struct N_TNAME bases. The fixes compose at the same call site (esz from indexbaseesz, then the IMULQ-or-elide gate, then complex-base emit), so splitting them into two commits would leave a half-fixed intermediate — neither half stands alone as a bisect-clean closure. Sentinel 755_amp_dot_idx exercises both shapes across 4 stride classes (slice-elem 24, struct-elem 16, u8 stride-1 elide, i64 stride-8); pre-fix 5/12 fail, post-fix 12/12 ok. Latent silent miscompile in lib/memio + lib/bufio's .ptr[i] shape also unmasked.
This commit is contained in:
@@ -1145,18 +1145,31 @@ fn indexbaseesz(c: *cgen, base: *node) i32 = {
|
||||
if (tn == nil) { return 8; };
|
||||
|
||||
// `.ptr` pseudo-field on str/slice → element of the str/slice.
|
||||
// Gated on inner kind, NOT on the field name alone: a struct with
|
||||
// a literal `ptr: *T` field (lib/memio.state, lib/bufio.state) must
|
||||
// route through the generic struct-field arm below so the stride
|
||||
// comes from primsize/structlookup, not the str/slice default. The
|
||||
// over-broad pre-#21 shortcut hard-coded esz=8 and silently
|
||||
// miscompiled `m.ptr[i]` for `*u8` callers (also widened the load
|
||||
// op MOVZBQ → MOVQ in cgindex). Mirrors cstage which routes every
|
||||
// base through `base->type->sub->size` (cmd/w6c/cgen.c idx_eff).
|
||||
if (streq(fld, "ptr")) {
|
||||
let innert: *node = tn;
|
||||
if (tn.kind == nkind.N_TPTR) { innert = tn.lhs; };
|
||||
if (innert == nil) { return 8; };
|
||||
if (innert.kind == nkind.N_TNAME) {
|
||||
if (streq(innert.str, "str")) { return 1; };
|
||||
if (innert != nil) {
|
||||
if (innert.kind == nkind.N_TNAME) {
|
||||
if (streq(innert.str, "str")) { return 1; };
|
||||
};
|
||||
// Slice element: resolve through elemsizeofc so a
|
||||
// slice of a named struct (e.g. *[]option) returns
|
||||
// the struct stride instead of falling through to
|
||||
// elemsizeof's default 8.
|
||||
if (innert.kind == nkind.N_TSLICE) {
|
||||
return elemsizeofc(c, innert);
|
||||
};
|
||||
};
|
||||
// Slice element: resolve through elemsizeofc so a slice of a
|
||||
// named struct (e.g. *[]option) returns the struct stride
|
||||
// instead of falling through to elemsizeof's default 8.
|
||||
if (innert.kind == nkind.N_TSLICE) { return elemsizeofc(c, innert); };
|
||||
return 8;
|
||||
// Inner is a struct N_TNAME (or unresolved) — fall through
|
||||
// to the generic struct-field arm below.
|
||||
};
|
||||
|
||||
// Generic struct field: if it's *T, element size is T's size.
|
||||
|
||||
Reference in New Issue
Block a user