w6c+wwstage: struct-array-field slice as call-arg via dotbaseaddr (#257)
An inline slice of a struct `[N]T`-field passed DIRECTLY as a call argument (`rd(x.o[lo:hi])`) materialized the slice .ptr from the field VALUE, not its ADDRESS: the pushargs/pushargsrev N_SLICE inline builder's non-ident else-arm did plain cgexpr(base), so the N_DOT field auto-derefs (MOVL field,AX used as .ptr) -> callee derefs garbage -> SEGFAULT. The let-init / assign-rhs / return / hoist-to-local contexts already routed through the cgslice #252 choke-point; only this call-arg builder kept a private duplicate. cs==ww both segfaulted identically pre-fix (gate-blind). Fix (symmetric both stages): - route the else-arm through cg_dotbase_addr / dotbaseaddr (the cgslice #252 choke-point: array-field-gated, so `[]T`/str/`*T` fields fall through to cgexpr; chained inner `o.p.m` handled via its #253 arm); - extend the N_IDENT-only esz gate to N_DOT bases, taking the element width from the checker-stamped base->type (rule-13 type table), so non-u8 call-arg slices scale stride. Before: `MOVL -8(BP),AX; PUSHQ AX` (field value as .ptr). After: `LEAQ -8(BP),AX; PUSHQ AX` (field address). cs==ww byte-identical. Helper note: used dotbaseaddr (not dotchainaddr as first scoped) — it is the established cgslice choke-point and is array-field-gated, so a slice/ str-typed field base keeps the correct cgexpr header-ptr load; bare dotchainaddr lacks that gate and would mis-emit the field address for those. dotbaseaddr already handles the chained `o.p.m` inner via #253. Tests: test/wcc/949 gains 6 call-arg rows (u8, i32-esz-stride, via-*struct, chained, + hoist-to-local and bare-local-array controls), each run- correctness AND cs==ww byte-id. PROOF-GREP residual: the tagged-union-element indexed-STORE arm (cgen.c:~4972 / cgenexpr.ww:~5024) is the same N_DOT-base auto-deref shape, still unrouted in BOTH stages (symmetric, segfaults) — a distinct consumption axis filed separately; NOT fixed here.
This commit is contained in:
@@ -6167,11 +6167,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
Type *bt = base ? base->type : NULL;
|
Type *bt = base ? base->type : NULL;
|
||||||
Type *bu = (bt && bt->kind == TY_NAMED) ?
|
Type *bu = (bt && bt->kind == TY_NAMED) ?
|
||||||
bt->under : bt;
|
bt->under : bt;
|
||||||
/* N_IDENT-gated: non-ident bases stay esz=1
|
/* esz from the type table for an N_IDENT base
|
||||||
* (unscaled), byte-id with wwstage which has no
|
* (#76) or an N_DOT array/slice-field base
|
||||||
* tnode there (rule 10) -- #76 residual, non-
|
* (#257: scale by the field's element width via
|
||||||
* ident cluster #74. */
|
* the checker-stamped base->type, not esz=1 --
|
||||||
int esz = (base && base->kind == N_IDENT
|
* silently wrong for non-u8). Other non-ident
|
||||||
|
* bases stay esz=1 (unscaled). */
|
||||||
|
int esz = (base && (base->kind == N_IDENT
|
||||||
|
|| base->kind == N_DOT)
|
||||||
&& bu && bu->sub)
|
&& bu && bu->sub)
|
||||||
? (int)bu->sub->size : 1;
|
? (int)bu->sub->size : 1;
|
||||||
/* base addr → push */
|
/* base addr → push */
|
||||||
@@ -6192,6 +6195,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
} else {
|
} else {
|
||||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
|
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
|
||||||
}
|
}
|
||||||
|
} else if (cg_dotbase_addr(c, base, D_AX, locals)) {
|
||||||
|
/* #257: N_DOT `[N]T`-field base as a call
|
||||||
|
* arg → field ADDRESS (LEAQ), not the
|
||||||
|
* auto-deref VALUE load cgexpr emits. Same
|
||||||
|
* choke-point as the cgslice #252 site;
|
||||||
|
* `[]T`/str/`*T` fields fall through to
|
||||||
|
* cgexpr (correct header/ptr load). */
|
||||||
} else {
|
} else {
|
||||||
cgexpr(c, base, locals);
|
cgexpr(c, base, locals);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15624,15 +15624,30 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// #257: an N_DOT `[N]T`-field base (`x.o[lo:hi]` as a call
|
||||||
|
// arg) carries no tnode — resolve esz / base-address from the
|
||||||
|
// checker-stamped element tinfo on base.type_ instead. Cstage
|
||||||
|
// twin reads base->type (cgen.c pushargs N_SLICE esz). Mirror
|
||||||
|
// of the cgslice #252 site.
|
||||||
|
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
|
// esz from the type table for an N_IDENT base (#76; mirrors
|
||||||
// the cgindex idiom). Non-ident base stays esz=1 -> ptr
|
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
|
||||||
// unscaled, matching cstage's base->kind==N_IDENT gate.
|
// scale by the field's element width, not esz=1 -> silently
|
||||||
|
// wrong for non-u8). Other non-ident bases stay esz=1.
|
||||||
let esz: i32 = 1;
|
let esz: i32 = 1;
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
esz = elemsizeofc(c, baselocal.tnode);
|
esz = elemsizeofc(c, baselocal.tnode);
|
||||||
} else { if (globaltn != nil) {
|
} else { if (globaltn != nil) {
|
||||||
esz = elemsizeofc(c, globaltn);
|
esz = elemsizeofc(c, globaltn);
|
||||||
};};
|
} else { if (dotbu != nil && dotbu.sub != nil) {
|
||||||
|
esz = dotbu.sub.size: i32;
|
||||||
|
};};};
|
||||||
// base address → push
|
// base address → push
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
let tn: *node = baselocal.tnode;
|
let tn: *node = baselocal.tnode;
|
||||||
@@ -15661,9 +15676,14 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
emitsymname(c, globalname);
|
emitsymname(c, globalname);
|
||||||
emitline("(SB), AX\n");
|
emitline("(SB), AX\n");
|
||||||
};
|
};
|
||||||
|
} else { if (dotbaseaddr(c, base, "AX")) {
|
||||||
|
// #257: N_DOT `[N]T`-field base as a call arg → field
|
||||||
|
// ADDRESS (LEAQ), not the auto-deref VALUE load cgexpr
|
||||||
|
// emits. Same choke-point as the cgslice #252 site;
|
||||||
|
// `[]T`/str/`*T` fields fall through to cgexpr.
|
||||||
} else {
|
} else {
|
||||||
cgexpr(c, base);
|
cgexpr(c, base);
|
||||||
};};
|
};};};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
// hi (default base length) → push
|
// hi (default base length) → push
|
||||||
if (hi != nil) {
|
if (hi != nil) {
|
||||||
|
|||||||
@@ -274,15 +274,30 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// #257: an N_DOT `[N]T`-field base (`x.o[lo:hi]` as a call
|
||||||
|
// arg) carries no tnode — resolve esz / base-address from the
|
||||||
|
// checker-stamped element tinfo on base.type_ instead. Cstage
|
||||||
|
// twin reads base->type (cgen.c pushargs N_SLICE esz). Mirror
|
||||||
|
// of the cgslice #252 site.
|
||||||
|
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
|
// esz from the type table for an N_IDENT base (#76; mirrors
|
||||||
// the cgindex idiom). Non-ident base stays esz=1 -> ptr
|
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
|
||||||
// unscaled, matching cstage's base->kind==N_IDENT gate.
|
// scale by the field's element width, not esz=1 -> silently
|
||||||
|
// wrong for non-u8). Other non-ident bases stay esz=1.
|
||||||
let esz: i32 = 1;
|
let esz: i32 = 1;
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
esz = elemsizeofc(c, baselocal.tnode);
|
esz = elemsizeofc(c, baselocal.tnode);
|
||||||
} else { if (globaltn != nil) {
|
} else { if (globaltn != nil) {
|
||||||
esz = elemsizeofc(c, globaltn);
|
esz = elemsizeofc(c, globaltn);
|
||||||
};};
|
} else { if (dotbu != nil && dotbu.sub != nil) {
|
||||||
|
esz = dotbu.sub.size: i32;
|
||||||
|
};};};
|
||||||
// base address → push
|
// base address → push
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
let tn: *node = baselocal.tnode;
|
let tn: *node = baselocal.tnode;
|
||||||
@@ -311,9 +326,14 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
emitsymname(c, globalname);
|
emitsymname(c, globalname);
|
||||||
emitline("(SB), AX\n");
|
emitline("(SB), AX\n");
|
||||||
};
|
};
|
||||||
|
} else { if (dotbaseaddr(c, base, "AX")) {
|
||||||
|
// #257: N_DOT `[N]T`-field base as a call arg → field
|
||||||
|
// ADDRESS (LEAQ), not the auto-deref VALUE load cgexpr
|
||||||
|
// emits. Same choke-point as the cgslice #252 site;
|
||||||
|
// `[]T`/str/`*T` fields fall through to cgexpr.
|
||||||
} else {
|
} else {
|
||||||
cgexpr(c, base);
|
cgexpr(c, base);
|
||||||
};};
|
};};};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
// hi (default base length) → push
|
// hi (default base length) → push
|
||||||
if (hi != nil) {
|
if (hi != nil) {
|
||||||
|
|||||||
@@ -15624,15 +15624,30 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
// #257: an N_DOT `[N]T`-field base (`x.o[lo:hi]` as a call
|
||||||
|
// arg) carries no tnode — resolve esz / base-address from the
|
||||||
|
// checker-stamped element tinfo on base.type_ instead. Cstage
|
||||||
|
// twin reads base->type (cgen.c pushargs N_SLICE esz). Mirror
|
||||||
|
// of the cgslice #252 site.
|
||||||
|
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
|
// esz from the type table for an N_IDENT base (#76; mirrors
|
||||||
// the cgindex idiom). Non-ident base stays esz=1 -> ptr
|
// the cgindex idiom) or an N_DOT array/slice-field base (#257:
|
||||||
// unscaled, matching cstage's base->kind==N_IDENT gate.
|
// scale by the field's element width, not esz=1 -> silently
|
||||||
|
// wrong for non-u8). Other non-ident bases stay esz=1.
|
||||||
let esz: i32 = 1;
|
let esz: i32 = 1;
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
esz = elemsizeofc(c, baselocal.tnode);
|
esz = elemsizeofc(c, baselocal.tnode);
|
||||||
} else { if (globaltn != nil) {
|
} else { if (globaltn != nil) {
|
||||||
esz = elemsizeofc(c, globaltn);
|
esz = elemsizeofc(c, globaltn);
|
||||||
};};
|
} else { if (dotbu != nil && dotbu.sub != nil) {
|
||||||
|
esz = dotbu.sub.size: i32;
|
||||||
|
};};};
|
||||||
// base address → push
|
// base address → push
|
||||||
if (baselocal != nil) {
|
if (baselocal != nil) {
|
||||||
let tn: *node = baselocal.tnode;
|
let tn: *node = baselocal.tnode;
|
||||||
@@ -15661,9 +15676,14 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
emitsymname(c, globalname);
|
emitsymname(c, globalname);
|
||||||
emitline("(SB), AX\n");
|
emitline("(SB), AX\n");
|
||||||
};
|
};
|
||||||
|
} else { if (dotbaseaddr(c, base, "AX")) {
|
||||||
|
// #257: N_DOT `[N]T`-field base as a call arg → field
|
||||||
|
// ADDRESS (LEAQ), not the auto-deref VALUE load cgexpr
|
||||||
|
// emits. Same choke-point as the cgslice #252 site;
|
||||||
|
// `[]T`/str/`*T` fields fall through to cgexpr.
|
||||||
} else {
|
} else {
|
||||||
cgexpr(c, base);
|
cgexpr(c, base);
|
||||||
};};
|
};};};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
// hi (default base length) → push
|
// hi (default base length) → push
|
||||||
if (hi != nil) {
|
if (hi != nil) {
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
/*
|
/*
|
||||||
* 949_dotbase_addr_slice_run — runtime + byte-id net for the array-
|
* 949_dotbase_addr_slice_run — runtime + byte-id net for the array-
|
||||||
* field-base-address family: #252 (the addr-of + slice SIBLING of #135)
|
* 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
|
* #253 (the CHAINED-base close-out), and #257 (the CALL-ARG consumption
|
||||||
* single-level read/write index path.
|
* axis — an inline struct-array-field slice passed straight as a call
|
||||||
|
* argument). 949_dotbase_arr_run covers the single-level read/write
|
||||||
|
* index path.
|
||||||
*
|
*
|
||||||
* #252: taking `&x.o[i]` (address-of an element) or slicing
|
* #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
|
* `x.o[lo:hi]` / `x.o[lo:]` of a struct's `[N]T`-typed FIELD computed
|
||||||
@@ -377,6 +379,78 @@ static const struct row rows[] = {
|
|||||||
" let o: top; o.a.b.m[1] = 66u8;\n"
|
" let o: top; o.a.b.m[1] = 66u8;\n"
|
||||||
" return o.a.b.m[1]: i32;\n"
|
" return o.a.b.m[1]: i32;\n"
|
||||||
"};\n", 66, 0 },
|
"};\n", 66, 0 },
|
||||||
|
/* #257 call-arg consumption axis. An INLINE slice of a struct
|
||||||
|
* `[N]T`-field passed DIRECTLY as a call argument materialized the
|
||||||
|
* slice .ptr from the field VALUE, not its ADDRESS: the pushargs
|
||||||
|
* N_SLICE inline builder's non-ident else-arm did plain cgexpr(base)
|
||||||
|
* -> the N_DOT field auto-derefs (MOVL field,AX used as .ptr) ->
|
||||||
|
* callee derefs garbage -> SEGFAULT. The let-init / assign-rhs /
|
||||||
|
* return / hoist-to-local contexts already routed through the cgslice
|
||||||
|
* #252 choke-point; only this call-arg builder kept a private
|
||||||
|
* duplicate. Fix routes the else-arm through cg_dotbase_addr /
|
||||||
|
* dotbaseaddr (array-field-gated; chained inner via #253) + extends
|
||||||
|
* the N_IDENT-only esz gate to N_DOT bases (element width from the
|
||||||
|
* checker-stamped base->type). cs==ww both segfaulted identically
|
||||||
|
* pre-fix (gate-blind). */
|
||||||
|
{ "callarg_u8",
|
||||||
|
"package main;\n"
|
||||||
|
"type e = struct { o: [4]u8 };\n"
|
||||||
|
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: e;\n"
|
||||||
|
" x.o[1] = 66u8;\n"
|
||||||
|
" return rd(x.o[1:4]);\n"
|
||||||
|
"};\n", 66, 1 },
|
||||||
|
{ "callarg_i32",
|
||||||
|
"package main;\n"
|
||||||
|
"type e = struct { o: [4]i32 };\n"
|
||||||
|
"fn rd(b: []i32) i32 = { return b[0]; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: e;\n"
|
||||||
|
" x.o[1] = 88;\n"
|
||||||
|
" return rd(x.o[1:3]);\n"
|
||||||
|
"};\n", 88, 1 },
|
||||||
|
{ "callarg_ptr_u8",
|
||||||
|
"package main;\n"
|
||||||
|
"type e = struct { o: [4]u8 };\n"
|
||||||
|
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
|
||||||
|
"fn f(p: *e) i32 = { return rd(p.o[1:4]); };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: e;\n"
|
||||||
|
" x.o[1] = 66u8;\n"
|
||||||
|
" return f(&x);\n"
|
||||||
|
"};\n", 66, 1 },
|
||||||
|
{ "callarg_chain",
|
||||||
|
"package main;\n"
|
||||||
|
"type inner = struct { m: [4]u8 };\n"
|
||||||
|
"type outer = struct { p: *inner };\n"
|
||||||
|
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let a: inner; a.m[1] = 66u8;\n"
|
||||||
|
" let o: outer; o.p = &a;\n"
|
||||||
|
" return rd(o.p.m[1:4]);\n"
|
||||||
|
"};\n", 66, 1 },
|
||||||
|
/* Controls: an N_IDENT slice arg (hoist-to-local) and a bare-local-
|
||||||
|
* array slice arg take the N_IDENT fast-paths, NOT the N_DOT else-arm
|
||||||
|
* — assert they still emit correct code. */
|
||||||
|
{ "callarg_ctrl_local",
|
||||||
|
"package main;\n"
|
||||||
|
"type e = struct { o: [4]u8 };\n"
|
||||||
|
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: e;\n"
|
||||||
|
" x.o[1] = 66u8;\n"
|
||||||
|
" let sl: []u8 = x.o[1:4];\n"
|
||||||
|
" return rd(sl);\n"
|
||||||
|
"};\n", 66, 1 },
|
||||||
|
{ "callarg_ctrl_arr",
|
||||||
|
"package main;\n"
|
||||||
|
"fn rd(b: []u8) i32 = { return b[0]: i32; };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let a: [4]u8;\n"
|
||||||
|
" a[1] = 66u8;\n"
|
||||||
|
" return rd(a[1:4]);\n"
|
||||||
|
"};\n", 66, 1 },
|
||||||
{ NULL, NULL, 0, 0 }
|
{ NULL, NULL, 0, 0 }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user