wcc/ww: registerstruct field offset is the checker's natural tfield.offset
wwstage carried TWO struct-layout sources. registerstruct (cgenutil.ww)
recomputed each field's `fi.foff` via fieldsize — slot-padded, round-8 —
for the WRITE (construction / field store) path, while the READ path
(cgplaceaddr / dotbaseaddr) used the checker's natural `tfield.offset`.
They diverged iff a struct had a nested sub-8 composite field
(slotsize != size) plus a successor: ww wrote the successor at the
slot-padded offset and read it at the natural offset, mis-addressing its
own field. cstage has no structinfo and reads tfield directly, self-
consistently natural (cmd/w6c/cgen.c).
Fix: make `fi.foff` a VIEW of the checker's already-built natural layout.
Lock-step walk tstruct.list (AST N_TFIELD) and ti.fields (tfield) — both
head-first declared order, both skip non-TFIELD identically — and copy
foff = tf.offset, fsz = tf.type_.size. si.totsize keeps the slot-padded
stack-slot number (ti.slotsize, already 8-rounded at check.ww:2259).
fieldsize is no longer called here (its `*p OP=` scalar-width caller is
untouched). LOUD nil-guards on tstruct.type_ / tichase / a tfield walk
desync — all unreachable post-check, never silent. fi.tnode stays the
AST node (its node-keyed readers need it); repointing the ~60 fi.foff
readers to tfield is the out-of-scope (ii-b) follow-up.
This unifies ww's second source onto the value cstage already emits, so
cs==ww is preserved, not newly created (wwstage-cgen only; no cstage
edit). The shape is corpus-absent — ww uses both sources on its own
structs, so a divergent struct would have broken the bootstrap — hence
gate-blind; 989_nestfield_run is the proof (nested inner{x:u8,y:u8} in
outer{a:u8,p:inner[,z:i64]}, every field read back == written, dual-stage
cs==ww). It also makes 681 ragged_tail_12B genuinely correct: the
predecessor #71 already shrank the whole-struct copy to the source's
natural length, so packing mark at natural offset 12 no longer clobbers.
This commit is contained in:
@@ -20402,44 +20402,60 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
return 8;
|
||||
};
|
||||
|
||||
// #44/#55: fi.foff is a VIEW of the checker's already-built NATURAL
|
||||
// `tfield.offset` (check.ww N_TSTRUCT L2234), NOT a second slot-padded
|
||||
// layout recomputed via fieldsize. The two sources diverged iff a struct
|
||||
// had a nested sub-8 composite field (slotsize != size) plus a successor:
|
||||
// the WRITE path used this slot-padded foff, the READ path
|
||||
// (cgplaceaddr/dotbaseaddr) read tfield.offset natural — ww mis-addressed
|
||||
// its own fields. cstage has no structinfo and reads tfield directly
|
||||
// (self-consistently natural); this unifies ww's second source onto it,
|
||||
// preserving cs==ww. Lock-step walk: tstruct.list N_TFIELD AST nodes and
|
||||
// ti.fields tfields share one head-first declared order (both skip
|
||||
// non-TFIELD identically), so they advance in exact step. si.totsize keeps
|
||||
// the slot-padded total (stack-slot allocator's number) from ti.slotsize,
|
||||
// already 8-rounded at check.ww:2259-2261.
|
||||
fn registerstruct(c: *cgen, name: str, srcmod: str, tstruct: *node) void = {
|
||||
let si: *structinfo = alloc(structinfo{
|
||||
sname = name,
|
||||
smod = srcmod,
|
||||
})!;
|
||||
if (tstruct.type_ == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: struct node has no stamped tinfo\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let ti: *tinfo = tichase(tstruct.type_: *tinfo);
|
||||
if (ti == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: tichase yielded nil tinfo\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let head: *fieldinfo = nil;
|
||||
let tail: *fieldinfo = nil;
|
||||
let off: i32 = 0;
|
||||
let f: *node = tstruct.list;
|
||||
let tf: *tfield = ti.fields;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let sz: i32 = fieldsize(c, f.lhs);
|
||||
// Align to 8 for any field >= 4 bytes (matches our other
|
||||
// cgen choices). i8/u8/bool may sit on odd byte offsets;
|
||||
// the C cgen does similar best-effort packing.
|
||||
let aln: i32 = 1;
|
||||
if (sz >= 8) { aln = 8; }
|
||||
else { if (sz >= 4) { aln = 4; }
|
||||
else { if (sz >= 2) { aln = 2; }; }; };
|
||||
if ((off & (aln - 1)) != 0) {
|
||||
off = (off + aln - 1) & ~(aln - 1);
|
||||
if (tf == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: AST/tfield walk desync (more TFIELDs than tinfo.fields)\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let fi: *fieldinfo = alloc(fieldinfo{
|
||||
fname = f.str,
|
||||
foff = off,
|
||||
fsz = sz,
|
||||
foff = tf.offset: i32,
|
||||
fsz = tf.type_.size: i32,
|
||||
tnode = f.lhs,
|
||||
})!;
|
||||
if (head == nil) { head = fi; tail = fi; }
|
||||
else { tail.finext = fi; tail = fi; };
|
||||
off += sz;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
// Round total to 8 for stack-slot use.
|
||||
if ((off & 7) != 0) { off = (off + 7) & ~7; };
|
||||
si.fields = head;
|
||||
si.totsize = off;
|
||||
si.totsize = ti.slotsize: i32;
|
||||
si.sinext = c.structs;
|
||||
c.structs = si;
|
||||
};
|
||||
|
||||
@@ -2608,44 +2608,60 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
return 8;
|
||||
};
|
||||
|
||||
// #44/#55: fi.foff is a VIEW of the checker's already-built NATURAL
|
||||
// `tfield.offset` (check.ww N_TSTRUCT L2234), NOT a second slot-padded
|
||||
// layout recomputed via fieldsize. The two sources diverged iff a struct
|
||||
// had a nested sub-8 composite field (slotsize != size) plus a successor:
|
||||
// the WRITE path used this slot-padded foff, the READ path
|
||||
// (cgplaceaddr/dotbaseaddr) read tfield.offset natural — ww mis-addressed
|
||||
// its own fields. cstage has no structinfo and reads tfield directly
|
||||
// (self-consistently natural); this unifies ww's second source onto it,
|
||||
// preserving cs==ww. Lock-step walk: tstruct.list N_TFIELD AST nodes and
|
||||
// ti.fields tfields share one head-first declared order (both skip
|
||||
// non-TFIELD identically), so they advance in exact step. si.totsize keeps
|
||||
// the slot-padded total (stack-slot allocator's number) from ti.slotsize,
|
||||
// already 8-rounded at check.ww:2259-2261.
|
||||
fn registerstruct(c: *cgen, name: str, srcmod: str, tstruct: *node) void = {
|
||||
let si: *structinfo = alloc(structinfo{
|
||||
sname = name,
|
||||
smod = srcmod,
|
||||
})!;
|
||||
if (tstruct.type_ == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: struct node has no stamped tinfo\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let ti: *tinfo = tichase(tstruct.type_: *tinfo);
|
||||
if (ti == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: tichase yielded nil tinfo\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let head: *fieldinfo = nil;
|
||||
let tail: *fieldinfo = nil;
|
||||
let off: i32 = 0;
|
||||
let f: *node = tstruct.list;
|
||||
let tf: *tfield = ti.fields;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let sz: i32 = fieldsize(c, f.lhs);
|
||||
// Align to 8 for any field >= 4 bytes (matches our other
|
||||
// cgen choices). i8/u8/bool may sit on odd byte offsets;
|
||||
// the C cgen does similar best-effort packing.
|
||||
let aln: i32 = 1;
|
||||
if (sz >= 8) { aln = 8; }
|
||||
else { if (sz >= 4) { aln = 4; }
|
||||
else { if (sz >= 2) { aln = 2; }; }; };
|
||||
if ((off & (aln - 1)) != 0) {
|
||||
off = (off + aln - 1) & ~(aln - 1);
|
||||
if (tf == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: AST/tfield walk desync (more TFIELDs than tinfo.fields)\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let fi: *fieldinfo = alloc(fieldinfo{
|
||||
fname = f.str,
|
||||
foff = off,
|
||||
fsz = sz,
|
||||
foff = tf.offset: i32,
|
||||
fsz = tf.type_.size: i32,
|
||||
tnode = f.lhs,
|
||||
})!;
|
||||
if (head == nil) { head = fi; tail = fi; }
|
||||
else { tail.finext = fi; tail = fi; };
|
||||
off += sz;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
// Round total to 8 for stack-slot use.
|
||||
if ((off & 7) != 0) { off = (off + 7) & ~7; };
|
||||
si.fields = head;
|
||||
si.totsize = off;
|
||||
si.totsize = ti.slotsize: i32;
|
||||
si.sinext = c.structs;
|
||||
c.structs = si;
|
||||
};
|
||||
|
||||
@@ -20402,44 +20402,60 @@ fn fieldsize(c: *cgen, tnode: *node) i32 = {
|
||||
return 8;
|
||||
};
|
||||
|
||||
// #44/#55: fi.foff is a VIEW of the checker's already-built NATURAL
|
||||
// `tfield.offset` (check.ww N_TSTRUCT L2234), NOT a second slot-padded
|
||||
// layout recomputed via fieldsize. The two sources diverged iff a struct
|
||||
// had a nested sub-8 composite field (slotsize != size) plus a successor:
|
||||
// the WRITE path used this slot-padded foff, the READ path
|
||||
// (cgplaceaddr/dotbaseaddr) read tfield.offset natural — ww mis-addressed
|
||||
// its own fields. cstage has no structinfo and reads tfield directly
|
||||
// (self-consistently natural); this unifies ww's second source onto it,
|
||||
// preserving cs==ww. Lock-step walk: tstruct.list N_TFIELD AST nodes and
|
||||
// ti.fields tfields share one head-first declared order (both skip
|
||||
// non-TFIELD identically), so they advance in exact step. si.totsize keeps
|
||||
// the slot-padded total (stack-slot allocator's number) from ti.slotsize,
|
||||
// already 8-rounded at check.ww:2259-2261.
|
||||
fn registerstruct(c: *cgen, name: str, srcmod: str, tstruct: *node) void = {
|
||||
let si: *structinfo = alloc(structinfo{
|
||||
sname = name,
|
||||
smod = srcmod,
|
||||
})!;
|
||||
if (tstruct.type_ == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: struct node has no stamped tinfo\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let ti: *tinfo = tichase(tstruct.type_: *tinfo);
|
||||
if (ti == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: tichase yielded nil tinfo\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let head: *fieldinfo = nil;
|
||||
let tail: *fieldinfo = nil;
|
||||
let off: i32 = 0;
|
||||
let f: *node = tstruct.list;
|
||||
let tf: *tfield = ti.fields;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let sz: i32 = fieldsize(c, f.lhs);
|
||||
// Align to 8 for any field >= 4 bytes (matches our other
|
||||
// cgen choices). i8/u8/bool may sit on odd byte offsets;
|
||||
// the C cgen does similar best-effort packing.
|
||||
let aln: i32 = 1;
|
||||
if (sz >= 8) { aln = 8; }
|
||||
else { if (sz >= 4) { aln = 4; }
|
||||
else { if (sz >= 2) { aln = 2; }; }; };
|
||||
if ((off & (aln - 1)) != 0) {
|
||||
off = (off + aln - 1) & ~(aln - 1);
|
||||
if (tf == nil) {
|
||||
let msg: str = "#44/#55: registerstruct: AST/tfield walk desync (more TFIELDs than tinfo.fields)\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let fi: *fieldinfo = alloc(fieldinfo{
|
||||
fname = f.str,
|
||||
foff = off,
|
||||
fsz = sz,
|
||||
foff = tf.offset: i32,
|
||||
fsz = tf.type_.size: i32,
|
||||
tnode = f.lhs,
|
||||
})!;
|
||||
if (head == nil) { head = fi; tail = fi; }
|
||||
else { tail.finext = fi; tail = fi; };
|
||||
off += sz;
|
||||
tf = tf.tnext;
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
// Round total to 8 for stack-slot use.
|
||||
if ((off & 7) != 0) { off = (off + 7) & ~7; };
|
||||
si.fields = head;
|
||||
si.totsize = off;
|
||||
si.totsize = ti.slotsize: i32;
|
||||
si.sinext = c.structs;
|
||||
c.structs = si;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user