wwstage: port struct embedding; graduate #59.13
The last frontend-gap pin: wwstage had no Hare struct embedding
(struct { hash.hash, ... }), rejecting lib/crypto/sha256 at parse.
- parse.ww: the three member forms (named / anonymous struct / bare
dotted-ident embed), consume-then-branch since this parser has no
peek; embeds carry f.str == "" and the type in f.lhs.
- check.ww N_TSTRUCT flatten: promote the inner struct's flattened
fields at base+src.offset (check.c:961-990); the embed is one
nested-struct unit in the slot ladder; the resolved inner AST is
planted on the TFIELD rhs for cgen.
- check.ww walkers: astoffset / exprtype N_DOT / #251 struct-lit
field lookups descend embeds through shared helpers; the collision
and non-struct-embed rejects live in validatestructfields (the
once-per-decl diagnostic site).
- cgenutil.ww registerstruct: regfieldrun walks the AST against the
flattened tfield cursor, descending embeds via the planted inner
AST so promoted fieldinfo entries keep the inner field's own name
and type node.
- wwi printers unchanged (both stages already emit nameless fields).
sha256_test compiles byte-identically end to end and its 6 tests
pass; 989_lib_byteid is now 44 id / 0 divergent / 0 wwreject.
Fixtures r5913_* (promoted rw, offset shift, anonymous embed,
two-level embed + promoted fn-ptr callee, three rejects); corpus pin
1485/2970.
This commit is contained in:
@@ -2990,6 +2990,53 @@ fn fieldsize(c: *cgen, tnode: *syntax.node) i32 = {
|
||||
// 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.
|
||||
// regfieldwalk — cursor state threaded through regfieldrun's embed-
|
||||
// descending AST/tfield lock-step walk.
|
||||
type regfieldwalk = struct {
|
||||
tf: *syntax.tfield,
|
||||
head: *fieldinfo,
|
||||
tail: *fieldinfo,
|
||||
};
|
||||
|
||||
// regfieldrun — walk an AST TFIELD list against the flattened
|
||||
// tinfo.fields cursor. A named field consumes one tfield; an embed
|
||||
// (#59.13, f.str == "") descends into the resolved inner struct AST
|
||||
// the checker planted on f.rhs (check.ww N_TSTRUCT flatten) — its
|
||||
// fields were promoted into THIS struct's tinfo run in the same
|
||||
// order, so the one cursor stays in exact step and each promoted
|
||||
// fieldinfo carries the inner field's own name and type node.
|
||||
fn regfieldrun(c: *cgen, flist: *syntax.node, w: *regfieldwalk) void = {
|
||||
let f: *syntax.node = flist;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
if (f.str.len == 0) {
|
||||
if (f.rhs == nil) {
|
||||
let msg: str = "#59.13: registerstruct: embed field has no checker-planted inner struct AST\n";
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
regfieldrun(c, f.rhs.list, w);
|
||||
} else {
|
||||
if (w.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 = w.tf.offset: i32,
|
||||
fsz = w.tf.type_.size: i32,
|
||||
tnode = f.lhs,
|
||||
})!;
|
||||
if (w.head == nil) { w.head = fi; w.tail = fi; }
|
||||
else { w.tail.finext = fi; w.tail = fi; };
|
||||
w.tf = w.tf.tnext;
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
};
|
||||
|
||||
fn registerstruct(c: *cgen, name: str, srcmod: str, tstruct: *syntax.node) void = {
|
||||
let si: *structinfo = alloc(structinfo{
|
||||
sname = name,
|
||||
@@ -3006,30 +3053,12 @@ fn registerstruct(c: *cgen, name: str, srcmod: str, tstruct: *syntax.node) void
|
||||
os.write(2, msg.ptr, msg.len: u64);
|
||||
os.exit(1);
|
||||
};
|
||||
let head: *fieldinfo = nil;
|
||||
let tail: *fieldinfo = nil;
|
||||
let f: *syntax.node = tstruct.list;
|
||||
let tf: *syntax.tfield = ti.fields;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
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 = tf.offset: i32,
|
||||
fsz = tf.type_.size: i32,
|
||||
tnode = f.lhs,
|
||||
})!;
|
||||
if (head == nil) { head = fi; tail = fi; }
|
||||
else { tail.finext = fi; tail = fi; };
|
||||
tf = tf.tnext;
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
si.fields = head;
|
||||
let w: regfieldwalk;
|
||||
w.tf = ti.fields;
|
||||
w.head = nil;
|
||||
w.tail = nil;
|
||||
regfieldrun(c, tstruct.list, &w);
|
||||
si.fields = w.head;
|
||||
si.totsize = ti.slotsize: i32;
|
||||
si.sinext = c.structs;
|
||||
c.structs = si;
|
||||
|
||||
@@ -1563,6 +1563,64 @@ fn yieldclass(t: *syntax.tinfo) i32 = {
|
||||
return 0i32;
|
||||
};
|
||||
|
||||
// aststructoffset — byte offset of `name` inside struct AST `stn`,
|
||||
// walking fields with per-field alignment and descending embeds
|
||||
// (#59.13: promoted names live at base + inner offset; cstage folds
|
||||
// offset() over the flattened tinfo, check.c:1691-1698, so it is
|
||||
// embed-transparent there). -1 on a miss.
|
||||
fn aststructoffset(c: *checker, stn: *syntax.node, name: str, depth: i32) i64 = {
|
||||
if (depth > EMBEDDEPTHMAX) { return -1i64; };
|
||||
let off: i64 = 0i64;
|
||||
let f: *syntax.node = stn.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
let fa: i64 = astalign(c, f.lhs);
|
||||
// packed: no inter-field padding (harec type_store.c:206-213).
|
||||
if (stn.packed == 0) {
|
||||
off = (off + fa - 1i64) & ~(fa - 1i64);
|
||||
};
|
||||
if (f.str.len != 0) {
|
||||
if (syntax.streq(f.str, name)) { return off; };
|
||||
} else {
|
||||
let inner: *syntax.node = structembedbody(c, f.lhs);
|
||||
if (inner != nil) {
|
||||
let r: i64 = aststructoffset(c, inner, name, depth + 1);
|
||||
if (r >= 0i64) { return off + r; };
|
||||
};
|
||||
};
|
||||
off += astsize(c, f.lhs);
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return -1i64;
|
||||
};
|
||||
|
||||
// aststructfieldtype — the declared type expr of `name` inside struct
|
||||
// AST `stn`, descending embeds (#59.13: a promoted name resolves to
|
||||
// the inner field's own type node — the exact node the non-embed walk
|
||||
// would have returned had the field been declared inline). nil on a
|
||||
// miss. Shared by the exprtype N_DOT struct arm and the #251
|
||||
// struct-lit field walk.
|
||||
fn aststructfieldtype(c: *checker, stn: *syntax.node, name: str, depth: i32) *syntax.node = {
|
||||
if (depth > EMBEDDEPTHMAX) { return nil; };
|
||||
let f: *syntax.node = stn.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
if (f.str.len != 0) {
|
||||
if (syntax.streq(f.str, name)) { return f.lhs; };
|
||||
} else {
|
||||
let inner: *syntax.node = structembedbody(c, f.lhs);
|
||||
if (inner != nil) {
|
||||
let r: *syntax.node = aststructfieldtype(c, inner, name, depth + 1);
|
||||
if (r != nil) { return r; };
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return nil;
|
||||
};
|
||||
|
||||
// astoffset — byte offset of `dot.str` inside the struct type of
|
||||
// `dot.lhs`. Mirrors cstage cmd/wcc/check.c:932-961: peel one N_TPTR
|
||||
// (for `p.field` where p is *Struct), require N_TSTRUCT, walk fields
|
||||
@@ -1580,21 +1638,7 @@ fn astoffset(c: *checker, dot: *syntax.node) i64 = {
|
||||
};
|
||||
if (rtyp == nil) { return -1i64; };
|
||||
if (rtyp.kind != syntax.nkind.N_TSTRUCT) { return -1i64; };
|
||||
let off: i64 = 0i64;
|
||||
let f: *syntax.node = rtyp.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
let fa: i64 = astalign(c, f.lhs);
|
||||
// packed: no inter-field padding (harec type_store.c:206-213).
|
||||
if (rtyp.packed == 0) {
|
||||
off = (off + fa - 1i64) & ~(fa - 1i64);
|
||||
};
|
||||
if (syntax.streq(f.str, dot.str)) { return off; };
|
||||
off += astsize(c, f.lhs);
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return -1i64;
|
||||
return aststructoffset(c, rtyp, dot.str, 0);
|
||||
};
|
||||
|
||||
// arenau64tos — decimal string for the folded INTLIT's `str` field.
|
||||
@@ -1910,34 +1954,122 @@ fn stampenumvals(c: *checker, n: *syntax.node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// structembedbody — resolve an embed member's type expr to its struct
|
||||
// body AST, or nil (non-struct embed; the caller owns the diagnostic).
|
||||
fn structembedbody(c: *checker, t: *syntax.node) *syntax.node = {
|
||||
let u: *syntax.node = resolvealias(c, unwrapbang(t));
|
||||
if (u == nil) { return nil; };
|
||||
if (u.kind != syntax.nkind.N_TSTRUCT) { return nil; };
|
||||
return u;
|
||||
};
|
||||
|
||||
// Embed-descend depth cap (#59.13). Embed cycles are loud-rejected by
|
||||
// circularnamed at the tinfo flatten; the AST diagnostic/lookup walks
|
||||
// only have to TERMINATE on them, not report them twice.
|
||||
def EMBEDDEPTHMAX: i32 = 32;
|
||||
|
||||
// structhasfield — does struct AST `stn` declare `name`, directly or
|
||||
// promoted through an embed?
|
||||
fn structhasfield(c: *checker, stn: *syntax.node, name: str, depth: i32) bool = {
|
||||
if (depth > EMBEDDEPTHMAX) { return false; };
|
||||
let f: *syntax.node = stn.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
if (f.str.len != 0) {
|
||||
if (syntax.streq(f.str, name)) { return true; };
|
||||
} else {
|
||||
let inner: *syntax.node = structembedbody(c, f.lhs);
|
||||
if (inner != nil) {
|
||||
if (structhasfield(c, inner, name, depth + 1)) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// earlierhasfield — does any field of `outer` declared BEFORE `upto`
|
||||
// carry `name`, directly or promoted through an embed? The "existing
|
||||
// field" set of cstage check.c:945-950 / :972-980.
|
||||
fn earlierhasfield(c: *checker, outer: *syntax.node, upto: *syntax.node, name: str) bool = {
|
||||
let e: *syntax.node = outer.list;
|
||||
for (e != upto) {
|
||||
if (e.kind == syntax.nkind.N_TFIELD) {
|
||||
if (e.str.len != 0) {
|
||||
if (syntax.streq(e.str, name)) { return true; };
|
||||
} else {
|
||||
let inner: *syntax.node = structembedbody(c, e.lhs);
|
||||
if (inner != nil) {
|
||||
if (structhasfield(c, inner, name, 0)) { return true; };
|
||||
};
|
||||
};
|
||||
};
|
||||
e = e.next;
|
||||
};
|
||||
return false;
|
||||
};
|
||||
|
||||
// embedcollides — err for every name `inner` promotes into `outer`
|
||||
// that a field before `upto` already declares. Mirrors cstage
|
||||
// check.c:971-980.
|
||||
fn embedcollides(c: *checker, outer: *syntax.node, upto: *syntax.node, inner: *syntax.node, depth: i32) void = {
|
||||
if (depth > EMBEDDEPTHMAX) { return; };
|
||||
let f: *syntax.node = inner.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
if (f.str.len != 0) {
|
||||
if (earlierhasfield(c, outer, upto, f.str)) {
|
||||
cerr(upto.file);
|
||||
cerr(": error: embedded field '");
|
||||
cerr(f.str);
|
||||
cerr("' collides with existing field\n");
|
||||
c.errs += 1;
|
||||
};
|
||||
} else {
|
||||
let deeper: *syntax.node = structembedbody(c, f.lhs);
|
||||
if (deeper != nil) {
|
||||
embedcollides(c, outer, upto, deeper, depth + 1);
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
};
|
||||
|
||||
// validatestructfields — reject a struct decl carrying two fields with
|
||||
// the same name. Pure diagnostic: a read-only O(n^2) walk over the named
|
||||
// field list, no n.type_ / offset / checker-state mutation (the byte-id
|
||||
// safety condition — valid programs have no dup, so codegen is untouched).
|
||||
// Cstage twin: cmd/wcc/check.c:943-950 (the regular-named-field arm of
|
||||
// resolve_type's N_TSTRUCT). The embed-collision arm there (check.c:961-
|
||||
// 985) is NOT ported — ww has no struct embedding (parser gap catB-89),
|
||||
// so every N_TFIELD is a regular named field. Fires once per struct decl
|
||||
// from resolvewalk's eager type-decl dispatch (rule-10 symmetric with
|
||||
// cstage's once-per-resolve_type), not from the per-query size/align/
|
||||
// offset recompute arms (use-site = double-fire, drew-dv ruling).
|
||||
// the same name, and the two invalid embed shapes (#59.13: a non-struct
|
||||
// embed; an embed whose promoted name collides with an existing field).
|
||||
// Pure diagnostic: a read-only walk, no n.type_ / offset / checker-state
|
||||
// mutation (the byte-id safety condition — valid programs have no dup,
|
||||
// so codegen is untouched). Cstage twin: cmd/wcc/check.c:943-950 (named
|
||||
// arm) + :961-985 (embed arm) of resolve_type's N_TSTRUCT; cstage errs
|
||||
// inside the flatten, ww keeps the flatten silent because it re-runs
|
||||
// per size/align query. Fires once per struct decl from resolvewalk's
|
||||
// eager type-decl dispatch (rule-10 symmetric with cstage's
|
||||
// once-per-resolve_type), not from the per-query size/align/offset
|
||||
// recompute arms (use-site = double-fire, drew-dv ruling).
|
||||
fn validatestructfields(c: *checker, n: *syntax.node) void = {
|
||||
let f: *syntax.node = n.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD && f.str.len != 0) {
|
||||
let e: *syntax.node = n.list;
|
||||
for (e != f) {
|
||||
if (e.kind == syntax.nkind.N_TFIELD
|
||||
&& e.str.len != 0
|
||||
&& syntax.streq(e.str, f.str)) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) {
|
||||
if (f.str.len != 0) {
|
||||
if (earlierhasfield(c, n, f, f.str)) {
|
||||
cerr(f.file);
|
||||
cerr(": error: duplicate field '");
|
||||
cerr(f.str);
|
||||
cerr("'\n");
|
||||
c.errs += 1;
|
||||
break;
|
||||
};
|
||||
e = e.next;
|
||||
} else {
|
||||
let inner: *syntax.node = structembedbody(c, f.lhs);
|
||||
if (inner == nil) {
|
||||
cerr(f.file);
|
||||
cerr(": error: embedded type must be a struct\n");
|
||||
c.errs += 1;
|
||||
} else {
|
||||
embedcollides(c, n, f, inner, 0);
|
||||
};
|
||||
};
|
||||
};
|
||||
f = f.next;
|
||||
@@ -2417,7 +2549,7 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
|
||||
case syntax.nkind.N_TSTRUCT:
|
||||
// Cstage cmd/wcc/check.c:468-527: per-field alignment, max
|
||||
// align for the whole record, total rounded up to alignment.
|
||||
// Anonymous-embed promotion is deferred (#13).
|
||||
// Embeds promote the inner struct's fields (#59.13, below).
|
||||
//
|
||||
// Pre-bind into the cache BEFORE walking fields so a
|
||||
// self-referential pointer field (e.g., `next: *node` inside
|
||||
@@ -2444,8 +2576,6 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
|
||||
// A.6.3f-a) for the head/tail append pattern. Harec cite:
|
||||
// ref/harec/include/types.h:109-115 struct_field and
|
||||
// ref/harec/src/type_store.c:314-347 struct_init_from_atype.
|
||||
// Anonymous-embed promotion not populated here (#13 per
|
||||
// the cstage cite at check.ww:1263).
|
||||
let fh: *syntax.tfield = nil;
|
||||
let ft_: *syntax.tfield = nil;
|
||||
let off: u64 = 0u64;
|
||||
@@ -2468,11 +2598,46 @@ fn tinfofornode(c: *checker, n: *syntax.node) *syntax.tinfo = {
|
||||
if (n.packed == 0 && ft.align > 0u64) {
|
||||
off = (off + ft.align - 1u64) & ~(ft.align - 1u64);
|
||||
};
|
||||
let fldoff: u64 = off;
|
||||
let tf: *syntax.tfield = alloc(syntax.tfield{name=f.str, type_=ft, offset=fldoff, tnext=nil})!;
|
||||
if (fh == nil) { fh = tf; } else { ft_.tnext = tf; };
|
||||
ft_ = tf;
|
||||
off += ft.size;
|
||||
if (f.str.len == 0) {
|
||||
// Embed (#59.13): promote the inner struct's
|
||||
// already-flattened fields at base+src.offset,
|
||||
// mirroring cstage check.c:961-990. Diagnostics
|
||||
// (non-struct embed, name collisions) live in
|
||||
// validatestructfields — this arm can re-run
|
||||
// per size/align query and must stay silent
|
||||
// (the drew-dv once-per-decl ruling).
|
||||
let inner: *syntax.tinfo = ft;
|
||||
for (inner != nil && inner.kind == syntax.tykind.TY_NAMED) {
|
||||
inner = inner.under;
|
||||
};
|
||||
if (inner != nil && inner.kind == syntax.tykind.TY_STRUCT) {
|
||||
let base: u64 = off;
|
||||
let src: *syntax.tfield = inner.fields;
|
||||
for (src != nil) {
|
||||
let tf: *syntax.tfield = alloc(syntax.tfield{name=src.name, type_=src.type_, offset=base + src.offset, tnext=nil})!;
|
||||
if (fh == nil) { fh = tf; } else { ft_.tnext = tf; };
|
||||
ft_ = tf;
|
||||
src = src.tnext;
|
||||
};
|
||||
off = base + inner.size;
|
||||
// cgenutil registerstruct descends embeds
|
||||
// through the resolved inner struct AST;
|
||||
// plant it on the (otherwise unused) TFIELD
|
||||
// rhs — cgen has no alias resolver.
|
||||
f.rhs = resolvealias(c, unwrapbang(f.lhs));
|
||||
} else {
|
||||
// error-path layout kept defined (cstage
|
||||
// `off += ft ? ft->size : 0`); the build
|
||||
// already fails via validatestructfields.
|
||||
off += ft.size;
|
||||
};
|
||||
} else {
|
||||
let fldoff: u64 = off;
|
||||
let tf: *syntax.tfield = alloc(syntax.tfield{name=f.str, type_=ft, offset=fldoff, tnext=nil})!;
|
||||
if (fh == nil) { fh = tf; } else { ft_.tnext = tf; };
|
||||
ft_ = tf;
|
||||
off += ft.size;
|
||||
};
|
||||
// Slot-padded layout (mirror of cgenutil
|
||||
// fieldsize + registerstruct align rules).
|
||||
let fsz: u64 = fieldslotsize(ft);
|
||||
@@ -4019,15 +4184,14 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// Struct field walk. Cstage L843-849 errors on missing field.
|
||||
// Struct field walk. Cstage L843-849 errors on missing
|
||||
// field. Embed-descending (#59.13): promoted names stamp
|
||||
// the inner field's own type node.
|
||||
if (bu != nil) { if (bu.kind == syntax.nkind.N_TSTRUCT) {
|
||||
let f: *syntax.node = bu.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == syntax.nkind.N_TFIELD) { if (syntax.streq(f.str, e.str)) {
|
||||
e.type_ = tinfofornode(c, f.lhs): *void;
|
||||
return f.lhs;
|
||||
}; };
|
||||
f = f.next;
|
||||
let ftn: *syntax.node = aststructfieldtype(c, bu, e.str, 0);
|
||||
if (ftn != nil) {
|
||||
e.type_ = tinfofornode(c, ftn): *void;
|
||||
return ftn;
|
||||
};
|
||||
}; };
|
||||
// Tuple positional access `t.0`, `t.1`, …. Cstage L850-866
|
||||
@@ -4103,14 +4267,7 @@ fn exprtype(c: *checker, e: *syntax.node, hint: *syntax.node) *syntax.node = {
|
||||
for (fi != nil) {
|
||||
if (fi.kind == syntax.nkind.N_FIELD
|
||||
&& fi.lhs != nil) {
|
||||
let ftn: *syntax.node = nil;
|
||||
let tf: *syntax.node = stn.list;
|
||||
for (tf != nil) {
|
||||
if (tf.kind == syntax.nkind.N_TFIELD) {
|
||||
if (syntax.streq(tf.str, fi.str)) { ftn = tf.lhs; };
|
||||
};
|
||||
tf = tf.next;
|
||||
};
|
||||
let ftn: *syntax.node = aststructfieldtype(c, stn, fi.str, 0);
|
||||
if (ftn != nil) {
|
||||
// #120: `S{ f: 1.0 }` narrows the
|
||||
// field init to the field's f32.
|
||||
|
||||
Reference in New Issue
Block a user