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:
2026-05-20 05:22:57 +09:00
parent c58d3511e8
commit 3ec944a67f
6 changed files with 902 additions and 0 deletions

View File

@@ -3287,6 +3287,20 @@ fn count_emit(ctx: *void, b: u8) void = {
c.n += 1;
};
// --- size/align/offset typed-builtin fixtures (#42) --------------------
type point = struct {
x: i32,
y: i32,
};
// Mixed-alignment struct: i8 lays at 0, then i64 needs to skip to
// offset 8 (the i64's natural align). Probe asserts both ends.
type mixalign = struct {
tag: i8,
val: i64,
};
// --- byte scanner like lex.ww's hot path -------------------------------
fn count_digits(s: str) i32 = {
@@ -3380,6 +3394,33 @@ export fn main() i32 = {
os.close(fd);
if (n <= 0i64) { return 22; };
// Probe 8 — size(T) / align(T) / offset(e.f) typed-builtin folds
// (#42). Each call folds to an N_INTLIT at check time; cgen
// materialises the literal as a plain `MOVQ $N, AX`. Mirrors
// cstage cmd/wcc/check.c:907-960 byte-for-byte on this corpus.
if (size(str) != 16) { return 23; };
if (size(i64) != 8) { return 24; };
if (size(i32) != 4) { return 25; };
if (align(i64) != 8) { return 26; };
if (align(i32) != 4) { return 27; };
// Initialize struct locals explicitly so the cgen path doesn't
// drift from cstage on bare `let X: T;` zero-init (pre-existing
// wwstage divergence outside #42).
let pt: point = point { x = 0, y = 0 };
if (offset(pt.x) != 0) { return 28; };
if (offset(pt.y) != 4) { return 29; };
let mx: mixalign = mixalign { tag = 0i8, val = 0i64 };
if (offset(mx.tag) != 0) { return 30; };
if (offset(mx.val) != 8) { return 31; }; // align-padded to 8
// Width breadth: smallest prim, ptr, slice, struct (8B + padded),
// covering astsize's TPTR/TSLICE/TNAME-resolve-to-struct arms.
if (size(i8) != 1) { return 32; };
if (align(i8) != 1) { return 33; };
if (size(*i32) != 8) { return 34; };
if (size([]i32) != 24) { return 35; };
if (size(point) != 8) { return 36; };
if (size(mixalign) != 16) { return 37; };
return 42;
};

View File

@@ -74,6 +74,20 @@ fn count_emit(ctx: *void, b: u8) void = {
c.n += 1;
};
// --- size/align/offset typed-builtin fixtures (#42) --------------------
type point = struct {
x: i32,
y: i32,
};
// Mixed-alignment struct: i8 lays at 0, then i64 needs to skip to
// offset 8 (the i64's natural align). Probe asserts both ends.
type mixalign = struct {
tag: i8,
val: i64,
};
// --- byte scanner like lex.ww's hot path -------------------------------
fn count_digits(s: str) i32 = {
@@ -167,5 +181,32 @@ export fn main() i32 = {
os.close(fd);
if (n <= 0i64) { return 22; };
// Probe 8 — size(T) / align(T) / offset(e.f) typed-builtin folds
// (#42). Each call folds to an N_INTLIT at check time; cgen
// materialises the literal as a plain `MOVQ $N, AX`. Mirrors
// cstage cmd/wcc/check.c:907-960 byte-for-byte on this corpus.
if (size(str) != 16) { return 23; };
if (size(i64) != 8) { return 24; };
if (size(i32) != 4) { return 25; };
if (align(i64) != 8) { return 26; };
if (align(i32) != 4) { return 27; };
// Initialize struct locals explicitly so the cgen path doesn't
// drift from cstage on bare `let X: T;` zero-init (pre-existing
// wwstage divergence outside #42).
let pt: point = point { x = 0, y = 0 };
if (offset(pt.x) != 0) { return 28; };
if (offset(pt.y) != 4) { return 29; };
let mx: mixalign = mixalign { tag = 0i8, val = 0i64 };
if (offset(mx.tag) != 0) { return 30; };
if (offset(mx.val) != 8) { return 31; }; // align-padded to 8
// Width breadth: smallest prim, ptr, slice, struct (8B + padded),
// covering astsize's TPTR/TSLICE/TNAME-resolve-to-struct arms.
if (size(i8) != 1) { return 32; };
if (align(i8) != 1) { return 33; };
if (size(*i32) != 8) { return 34; };
if (size([]i32) != 24) { return 35; };
if (size(point) != 8) { return 36; };
if (size(mixalign) != 16) { return 37; };
return 42;
};