selfhost/cmd/wcc/check+test: fold size(T)/align(T)/offset(e.f) at check time
Mirror cstage cmd/wcc/check.c:907-960. Three typed-builtin intercepts that cstage already had: - size(T) — folds to a literal integer at check time from a newly-introduced astsize walker over the type AST. Mirrors the size computation in cstage resolve_type at check.c:286-528. - align(T) — same, via astalign. - offset(e.f) — folds the byte offset of field f in e's struct type via astoffset. Peels exactly one N_TPTR for `p.field`. seedprimitives registers the three names as SK_FN nil; exprtype's N_CALL arm gates on a same-module shadow check (per #23 alloc precedent) and consumes the parser-planted type-expression arg. The fold is in-place — foldtointlit mutates N_CALL into N_INTLIT so cgen sees a plain integer. resolvewalk's N_CALL trigger invokes exprtype so the fold fires from non-let contexts too (e.g. inside `if (size(T) != …)`). selfhost/test/smoke.ww gains a probe-8 block: size/align/offset assertions across str, primitive widths, ptrs, slices, and two structs (`point`, `mixalign`) covering both no-padding and i8+i64 natural-align padding cases. Known divergences NOT in #42 scope: - size((*T|void)) ≠ 8 on the cstage nullable-ptr fold (#13 family, unreachable through current grammar). - 8B-struct bare-let zero-init wwstage skip vs cstage emit (#59). - Same-module shadow gate added here, cstage has none — sibling shape to #26 (free/append/len gates). Closes the original chain that started with the user's call to fix the structural debt — six precondition fixes (#51, #52, #53, #55, #56, #50) landed before this fold could safely live in the check pass. Unblocks #43 (sweep literal 16s → size(str)) and #1 (str → 24B becomes one line).
This commit is contained in:
@@ -7082,6 +7082,15 @@ fn seedprimitives(c: *checker) void = {
|
||||
scopedefine(c.top, "alloc", skind.SK_FN, nil, nil);
|
||||
scopedefine(c.top, "free", skind.SK_FN, nil, nil);
|
||||
scopedefine(c.top, "append", skind.SK_FN, nil, nil);
|
||||
// #42: typed builtins folded to integer literals at check time —
|
||||
// `size(T)` / `align(T)` (arg is a type-expression planted by the
|
||||
// parser at lib/ww/parse/expr.ww:254-267) and `offset(e.f)` (arg is
|
||||
// an N_DOT). exprtype intercepts these and rewrites the N_CALL to
|
||||
// N_INTLIT so cgen never sees an unresolved size/align/offset symbol.
|
||||
// Mirrors cmd/wcc/check.c:907-955.
|
||||
scopedefine(c.top, "size", skind.SK_FN, nil, nil);
|
||||
scopedefine(c.top, "align", skind.SK_FN, nil, nil);
|
||||
scopedefine(c.top, "offset", skind.SK_FN, nil, nil);
|
||||
};
|
||||
|
||||
// declmod — module-tag stamp for a top-level decl.
|
||||
@@ -7399,6 +7408,24 @@ fn resolvewalk(c: *checker, n: *node) void = {
|
||||
};
|
||||
};
|
||||
|
||||
// #42: trigger the size/align/offset fold here so the mutation
|
||||
// fires regardless of context (if-conditions, expression statements,
|
||||
// etc.) — wwstage's exprtype is otherwise called only from
|
||||
// checkletassign / checkretassign / TRYPROP, and an unwrapped
|
||||
// `if (size(str) != 16)` would otherwise leave the N_CALL alone
|
||||
// and cgen would emit a stray `CALL size(SB)`. Mirrors cstage
|
||||
// cstmt's recursive cexpr discipline.
|
||||
if (k == nkind.N_CALL) {
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == nkind.N_IDENT) {
|
||||
let nm: str = n.lhs.str;
|
||||
if (streq(nm, "size") || streq(nm, "align") || streq(nm, "offset")) {
|
||||
let _t: *node = exprtype(c, n);
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
// After walking children: a local `let X: T = init;` registers
|
||||
// `X` so subsequent statements can resolve it. Top-level lets
|
||||
// are installed in installdecl, so this duplicate install at
|
||||
@@ -7585,6 +7612,201 @@ fn mktname(c: *checker, nm: str) *node = {
|
||||
return n;
|
||||
};
|
||||
|
||||
// #42: AST-level layout helpers for the size(T)/align(T)/offset(e.f)
|
||||
// fold. Mirror cstage resolve_type's size/align computation
|
||||
// (cmd/wcc/check.c:286-528) on AST nodes — wwstage check.ww never
|
||||
// materialises tinfo for user types so the fold has to walk the AST
|
||||
// directly. Struct layout follows cstage check.c:471-526 (align each
|
||||
// field, max align for the whole record, round size up to alignment).
|
||||
fn astalign(c: *checker, t: *node) i64 = {
|
||||
if (t == nil) { return 1i64; };
|
||||
let k: nkind = t.kind;
|
||||
if (k == nkind.N_TBANG) { return astalign(c, t.lhs); };
|
||||
if (k == nkind.N_TPTR) { return 8i64; };
|
||||
if (k == nkind.N_TSLICE) { return 8i64; };
|
||||
if (k == nkind.N_TCHAN) { return 8i64; };
|
||||
if (k == nkind.N_TFN) { return 8i64; };
|
||||
if (k == nkind.N_TARRAY) { return astalign(c, t.lhs); };
|
||||
if (k == nkind.N_TTAGGED) { return 8i64; };
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let m: i64 = 1i64;
|
||||
let p: *node = t.list;
|
||||
for (p != nil) {
|
||||
let pa: i64 = astalign(c, p);
|
||||
if (pa > m) { m = pa; };
|
||||
p = p.next;
|
||||
};
|
||||
return m;
|
||||
};
|
||||
if (k == nkind.N_TSTRUCT) {
|
||||
let m: i64 = 1i64;
|
||||
let f: *node = t.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let fa: i64 = astalign(c, f.lhs);
|
||||
if (fa > m) { m = fa; };
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return m;
|
||||
};
|
||||
if (k == nkind.N_TENUM) {
|
||||
if (t.lhs != nil) { return astalign(c, t.lhs); };
|
||||
return 4i64;
|
||||
};
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "void") || streq(nm, "bool") || streq(nm, "i8") || streq(nm, "u8")) { return 1i64; };
|
||||
if (streq(nm, "i16") || streq(nm, "u16")) { return 2i64; };
|
||||
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
||||
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64") || streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr") || streq(nm, "str")) { return 8i64; };
|
||||
let resolved: *node = resolvealias(c, t);
|
||||
if (resolved != nil && resolved != t) {
|
||||
return astalign(c, resolved);
|
||||
};
|
||||
};
|
||||
return 1i64;
|
||||
};
|
||||
|
||||
fn astsize(c: *checker, t: *node) i64 = {
|
||||
if (t == nil) { return 0i64; };
|
||||
let k: nkind = t.kind;
|
||||
if (k == nkind.N_TBANG) { return astsize(c, t.lhs); };
|
||||
if (k == nkind.N_TPTR) { return 8i64; };
|
||||
if (k == nkind.N_TSLICE) { return 24i64; };
|
||||
if (k == nkind.N_TCHAN) { return 8i64; };
|
||||
if (k == nkind.N_TFN) { return 8i64; };
|
||||
if (k == nkind.N_TARRAY) {
|
||||
let elen: i64 = 0i64;
|
||||
if (t.rhs != nil) {
|
||||
if (t.rhs.kind == nkind.N_INTLIT) { elen = t.rhs.uval: i64; };
|
||||
};
|
||||
return astsize(c, t.lhs) * elen;
|
||||
};
|
||||
if (k == nkind.N_TTUPLE) {
|
||||
let total: i64 = 0i64;
|
||||
let p: *node = t.list;
|
||||
for (p != nil) {
|
||||
total += astsize(c, p);
|
||||
p = p.next;
|
||||
};
|
||||
return total;
|
||||
};
|
||||
if (k == nkind.N_TSTRUCT) {
|
||||
let off: i64 = 0i64;
|
||||
let maxal: i64 = 1i64;
|
||||
let f: *node = t.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let fa: i64 = astalign(c, f.lhs);
|
||||
if (fa > maxal) { maxal = fa; };
|
||||
off = (off + fa - 1i64) & ~(fa - 1i64);
|
||||
off += astsize(c, f.lhs);
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return (off + maxal - 1i64) & ~(maxal - 1i64);
|
||||
};
|
||||
if (k == nkind.N_TTAGGED) {
|
||||
// 8 (tag) + max variant payload, rounded up to 8.
|
||||
let maxsz: i64 = 0i64;
|
||||
let v: *node = t.list;
|
||||
for (v != nil) {
|
||||
let sz: i64 = astsize(c, v);
|
||||
if (sz > maxsz) { maxsz = sz; };
|
||||
v = v.next;
|
||||
};
|
||||
let pad: i64 = (maxsz + 7i64) & ~7i64;
|
||||
return 8i64 + pad;
|
||||
};
|
||||
if (k == nkind.N_TENUM) {
|
||||
if (t.lhs != nil) { return astsize(c, t.lhs); };
|
||||
return 4i64;
|
||||
};
|
||||
if (k == nkind.N_TNAME) {
|
||||
let nm: str = t.str;
|
||||
if (streq(nm, "void")) { return 0i64; };
|
||||
if (streq(nm, "bool")) { return 1i64; };
|
||||
if (streq(nm, "i8") || streq(nm, "u8")) { return 1i64; };
|
||||
if (streq(nm, "i16") || streq(nm, "u16")) { return 2i64; };
|
||||
if (streq(nm, "i32") || streq(nm, "u32") || streq(nm, "f32") || streq(nm, "rune")) { return 4i64; };
|
||||
if (streq(nm, "i64") || streq(nm, "u64") || streq(nm, "f64") || streq(nm, "int") || streq(nm, "uint") || streq(nm, "uintptr")) { return 8i64; };
|
||||
if (streq(nm, "str")) { return 16i64; };
|
||||
let resolved: *node = resolvealias(c, t);
|
||||
if (resolved != nil && resolved != t) {
|
||||
return astsize(c, resolved);
|
||||
};
|
||||
};
|
||||
return 0i64;
|
||||
};
|
||||
|
||||
// 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
|
||||
// honouring per-field alignment, return -1 if the field name is
|
||||
// absent so the caller can flag the error and fold to 0.
|
||||
fn astoffset(c: *checker, dot: *node) i64 = {
|
||||
if (dot == nil) { return -1i64; };
|
||||
if (dot.kind != nkind.N_DOT) { return -1i64; };
|
||||
let recv: *node = scruttype(c, dot.lhs);
|
||||
if (recv == nil) { return -1i64; };
|
||||
let rt: *node = resolvealias(c, unwrapbang(recv));
|
||||
if (rt == nil) { return -1i64; };
|
||||
if (rt.kind == nkind.N_TPTR) {
|
||||
rt = resolvealias(c, unwrapbang(rt.lhs));
|
||||
};
|
||||
if (rt == nil) { return -1i64; };
|
||||
if (rt.kind != nkind.N_TSTRUCT) { return -1i64; };
|
||||
let off: i64 = 0i64;
|
||||
let f: *node = rt.list;
|
||||
for (f != nil) {
|
||||
if (f.kind == nkind.N_TFIELD) {
|
||||
let fa: i64 = astalign(c, f.lhs);
|
||||
off = (off + fa - 1i64) & ~(fa - 1i64);
|
||||
if (streq(f.str, dot.str)) { return off; };
|
||||
off += astsize(c, f.lhs);
|
||||
};
|
||||
f = f.next;
|
||||
};
|
||||
return -1i64;
|
||||
};
|
||||
|
||||
// arenau64tos — decimal string for the folded INTLIT's `str` field.
|
||||
// Cstage uses aprintf("%llu") at the same site (cmd/wcc/check.c:921);
|
||||
// wwstage cgen only reads `uval` for N_INTLIT codegen so `str` is
|
||||
// just for the AST printer, but set it for parity with the parser's
|
||||
// own literal-emit shape.
|
||||
fn arenau64tos(a: *arena, v: u64) str = {
|
||||
let buf: *u8 = amalloc(a, 24u64): *u8;
|
||||
let i: i32 = 23;
|
||||
buf[i] = 0u8;
|
||||
if (v == 0u64) { i -= 1; buf[i] = 48u8; };
|
||||
let n: u64 = v;
|
||||
for (n > 0u64) {
|
||||
i -= 1;
|
||||
buf[i] = (48u64 + (n % 10u64)): u8;
|
||||
n /= 10u64;
|
||||
};
|
||||
let r: str;
|
||||
r.ptr = buf + (i: u64);
|
||||
r.len = 23 - i;
|
||||
return r;
|
||||
};
|
||||
|
||||
// foldtointlit — mutate `n` in place to an N_INTLIT with value `v`.
|
||||
// Used by the #42 size/align/offset intercepts so cgen sees the
|
||||
// folded literal rather than an unresolved call. Mirrors cstage
|
||||
// cmd/wcc/check.c:919-927 / :951-958.
|
||||
fn foldtointlit(c: *checker, n: *node, v: i64) void = {
|
||||
n.kind = nkind.N_INTLIT;
|
||||
n.uval = v: u64;
|
||||
n.str = arenau64tos(c.a, v: u64);
|
||||
n.lhs = nil;
|
||||
n.list = nil;
|
||||
let empty: str;
|
||||
n.tsuffix = empty;
|
||||
};
|
||||
|
||||
// exprtype — best-effort type-AST inference for an expression
|
||||
// node. Handles literals, identifiers, calls, and casts; returns
|
||||
// nil for shapes we don't statically know (binary ops, struct
|
||||
@@ -7661,6 +7883,57 @@ fn exprtype(c: *checker, e: *node) *node = {
|
||||
};
|
||||
};
|
||||
};
|
||||
// #42: size(T) / align(T) / offset(e.f) typed-builtin intercepts.
|
||||
// Fold the N_CALL in place to an N_INTLIT so cgen never sees an
|
||||
// unresolved size/align/offset symbol. Same-module shadow gate
|
||||
// mirrors the alloc precedent (#23) so a user `fn size(...)`
|
||||
// inside this module suppresses the builtin. Mirrors cstage
|
||||
// cmd/wcc/check.c:907-960.
|
||||
if (callee.kind == nkind.N_IDENT) {
|
||||
let bname: str = callee.str;
|
||||
let issize: bool = streq(bname, "size");
|
||||
let isalign: bool = streq(bname, "align");
|
||||
let isoffset: bool = streq(bname, "offset");
|
||||
if (issize || isalign || isoffset) {
|
||||
let shadowed: bool = false;
|
||||
if (c.curmod.len > 0) {
|
||||
if (scopelookupinmodule(c.cur, c.curmod, bname) != nil) {
|
||||
shadowed = true;
|
||||
};
|
||||
};
|
||||
if (!shadowed) {
|
||||
if (e.list != nil) {
|
||||
if (issize) {
|
||||
let v: i64 = astsize(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
return mktname(c, "i32");
|
||||
};
|
||||
if (isalign) {
|
||||
let v: i64 = astalign(c, e.list);
|
||||
foldtointlit(c, e, v);
|
||||
return mktname(c, "i32");
|
||||
};
|
||||
// offset(e.f): the arg is a value expression
|
||||
// (N_DOT), parsed via parsearglist — not a
|
||||
// type expression.
|
||||
if (isoffset) {
|
||||
if (e.list.next == nil && e.list.kind == nkind.N_DOT) {
|
||||
let off: i64 = astoffset(c, e.list);
|
||||
if (off < 0i64) {
|
||||
os.write(2, "offset: no field '".ptr, 18u64);
|
||||
os.write(2, e.list.str.ptr, e.list.str.len: u64);
|
||||
os.write(2, "'\n".ptr, 2u64);
|
||||
c.errs += 1;
|
||||
off = 0i64;
|
||||
};
|
||||
foldtointlit(c, e, off);
|
||||
return mktname(c, "i32");
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
let nm: str;
|
||||
nm.ptr = nil; nm.len = 0;
|
||||
if (callee.kind == nkind.N_IDENT) { nm = callee.str; };
|
||||
|
||||
Reference in New Issue
Block a user