Files
ww/lib/ww/typ.ww
Hojun-Cho 82c1948239 selfhost/cmd/wcc + lib/ww/typ: nullable fold + slot-pad fast-path (Phase A.3)
A.2's slotsize fast-path covered PTR/SLICE/CHAN/FN/STR but bailed on
TAGGED (no nullable fold) and on primitives (cstage let_emit_size pads
to 8B for slot storage; tinfo.size is natural width). Fallback hit
count under wwdump build was 2187. A.3 closes both gaps.

tinfo gains a `nullable: i32` field (fits the existing 4B pad, struct
stays 96B). tinfofornode's N_TTAGGED arm detects `(*T | void)` (exactly
2 variants, one N_TPTR, one bare N_TNAME "void" — aliased or !void-
wrapped void don't match) and folds to size=8, align=8, nullable=1.
Mirrors cmd/wcc/check.c:412-426.

slotsize fast-path re-adds TY_TAGGED (safe now) and gains a primitive-
pad branch: BOOL/RUNE/I8-I64/U8-U64/INT/UINT/UINTPTR/ENUM/F32/F64 →
return 8. Padding lives at the read site; tinfo.size remains a faithful
natural-width SSoT. TUPLE/TSTRUCT/TARRAY deliberately stay on the
fallback because per-field stride is registerstruct.totsize, not
tinfo.size.

Post-A.3 fallback hit count: 134 (94% reduction from A.2's 2187).
Reviewer's per-kind breakdown: N_TNAME 101 (alias-to-struct chains)
+ N_TARRAY 33 (struct-element rounding) account for all remaining
hits. Both A.4 work.

Probes: `(*i32 | void)` byte-identical between stages with the
8B nullable encoding. `(*i32 | nomem)` correctly does NOT fold
(nomem ≠ bare void). `(*i32 | !void)` correctly does NOT fold
(N_TBANG isn't N_TNAME).

131/131 + 994 + 995 + bootstrap byte-identical (ww2==ww3==ww4).
2026-05-20 12:51:22 +09:00

383 lines
11 KiB
Plaintext

// lib/ww/typ.ww — port of cmd/wcc/type.c.
//
// Status: full structural port. The C version uses module-globals for
// the primitive types (tyvoid, tyi32, …); ww doesn't have writable
// global storage yet, so we bundle the primitives into a `tctx` that
// the checker passes around explicitly. typesinit fills the tctx
// once per arena.
package ww;
import os;
import mem;
// ---- TypeKind ---------------------------------------------------------
// Numeric values must stay aligned with cmd/wcc/ww.h TypeKind so the
// next diff signal (typed-AST printer / cgen) can compare across the
// two implementations.
// Mirror of the C `TypeKind` enum in cmd/wcc/ww.h. Numeric values
// are explicit and must stay in sync — the selfhost selfcheck and
// typed-AST printers depend on matching numeric layout.
type tykind = enum i32 {
TY_NONE = 0,
TY_VOID = 1,
TY_BOOL = 2,
TY_RUNE = 3,
TY_I8 = 4,
TY_I16 = 5,
TY_I32 = 6,
TY_I64 = 7,
TY_U8 = 8,
TY_U16 = 9,
TY_U32 = 10,
TY_U64 = 11,
TY_UINT = 12,
TY_INT = 13,
TY_UINTPTR = 14,
TY_F32 = 15,
TY_F64 = 16,
TY_STR = 17,
TY_PTR = 18,
TY_SLICE = 19,
TY_ARRAY = 20,
TY_STRUCT = 21,
TY_FN = 22,
TY_CHAN = 23,
TY_NAMED = 24,
TY_TUPLE = 25,
TY_TAGGED = 26,
TY_ERR = 27,
TY_NEVER = 28,
TY_UNTYPED_INT = 29,
TY_UNTYPED_FLOAT = 30,
TY_UNTYPED_STR = 31,
TY_UNTYPED_RUNE = 32,
TY_UNTYPED_BOOL = 33,
TY_UNTYPED_NIL = 34,
// Tail-appended values keep prior TY_* stable for the byte-diff
// against cmd/wcc/ww.h.
TY_ENUM = 35,
};
// ---- tinfo / tfield / tparam -----------------------------------------
type tfield = struct {
name: str,
type_: *tinfo,
offset: u64,
tnext: *tfield,
};
type tparam = struct {
name: str,
type_: *tinfo,
tnext: *tparam,
};
type tinfo = struct {
kind: tykind,
size: u64,
align: u64,
sub: *tinfo, // ptr/slice/array/chan element
alen: u64,
fields: *tfield,
params: *tparam,
ret: *tinfo,
variadic: i32,
nullable: i32, // #61 A.3: TY_TAGGED `(*T | void)` fold collapses to
// 8B ptr slot (null is the void variant). Mirrors
// cstage Type.nullable (cmd/wcc/ww.h:430-433);
// slot sits in variadic's natural pad so amalloc(96)
// is unchanged.
name: str,
under: *tinfo,
};
// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes
// tinfofornode lookups keyed by AST pointer. Linked-list shape mirrors
// other wwstage-side caches (cgen.aliases, cgen.structs) — sea-of-stars
// over hash-table cleverness, and Sym/Scope already pay the FNV cost
// for the resolver pass.
type tinfocacheent = struct {
key: *node,
val: *tinfo,
cnext: *tinfocacheent,
};
// ---- tctx — the box of primitive types -------------------------------
type tctx = struct {
a: *arena,
tyvoid: *tinfo,
tybool: *tinfo,
tyrune: *tinfo,
tyi8: *tinfo,
tyi16: *tinfo,
tyi32: *tinfo,
tyi64: *tinfo,
tyu8: *tinfo,
tyu16: *tinfo,
tyu32: *tinfo,
tyu64: *tinfo,
tyint: *tinfo,
tyuint: *tinfo,
tyuintptr: *tinfo,
tyf32: *tinfo,
tyf64: *tinfo,
tystr: *tinfo,
tyerr: *tinfo,
tynever: *tinfo,
tyuntypedint: *tinfo,
tyuntypedfloat: *tinfo,
tyuntypedstr: *tinfo,
tyuntypedrune: *tinfo,
tyuntypedbool: *tinfo,
tyuntypednil: *tinfo,
tinfocache: *tinfocacheent,
};
// ---- constructors -----------------------------------------------------
export fn newtype(a: *arena, k: tykind) *tinfo = {
let t: *tinfo = amalloc(a, 96u64): *tinfo;
t.kind = k;
return t;
};
fn prim(a: *arena, k: tykind, nm: str, sz: u64, al: u64) *tinfo = {
let t: *tinfo = newtype(a, k);
t.name = nm;
t.size = sz;
if (al > 0u64) { t.align = al; } else { t.align = sz; };
return t;
};
export fn typesinit(c: *tctx, a: *arena) void = {
c.a = a;
c.tyvoid = prim(a, tykind.TY_VOID, "void", 0u64, 1u64);
c.tybool = prim(a, tykind.TY_BOOL, "bool", 1u64, 1u64);
c.tyrune = prim(a, tykind.TY_RUNE, "rune", 4u64, 4u64);
c.tyi8 = prim(a, tykind.TY_I8, "i8", 1u64, 1u64);
c.tyi16 = prim(a, tykind.TY_I16, "i16", 2u64, 2u64);
c.tyi32 = prim(a, tykind.TY_I32, "i32", 4u64, 4u64);
c.tyi64 = prim(a, tykind.TY_I64, "i64", 8u64, 8u64);
c.tyu8 = prim(a, tykind.TY_U8, "u8", 1u64, 1u64);
c.tyu16 = prim(a, tykind.TY_U16, "u16", 2u64, 2u64);
c.tyu32 = prim(a, tykind.TY_U32, "u32", 4u64, 4u64);
c.tyu64 = prim(a, tykind.TY_U64, "u64", 8u64, 8u64);
c.tyint = prim(a, tykind.TY_INT, "int", 8u64, 8u64);
c.tyuint = prim(a, tykind.TY_UINT, "uint", 8u64, 8u64);
c.tyuintptr= prim(a, tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
c.tyf32 = prim(a, tykind.TY_F32, "f32", 4u64, 4u64);
c.tyf64 = prim(a, tykind.TY_F64, "f64", 8u64, 8u64);
c.tystr = prim(a, tykind.TY_STR, "str", 16u64, 8u64);
c.tyerr = prim(a, tykind.TY_ERR, "<err>", 0u64, 1u64);
c.tynever = prim(a, tykind.TY_NEVER, "never", 0u64, 1u64);
c.tyuntypedint = prim(a, tykind.TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
c.tyuntypedfloat = prim(a, tykind.TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
c.tyuntypedstr = prim(a, tykind.TY_UNTYPED_STR, "untyped_str", 0u64, 1u64);
c.tyuntypedrune = prim(a, tykind.TY_UNTYPED_RUNE, "untyped_rune", 0u64, 1u64);
c.tyuntypedbool = prim(a, tykind.TY_UNTYPED_BOOL, "untyped_bool", 0u64, 1u64);
c.tyuntypednil = prim(a, tykind.TY_UNTYPED_NIL, "untyped_nil", 0u64, 1u64);
};
export fn typeptr(a: *arena, sub: *tinfo) *tinfo = {
let t: *tinfo = newtype(a, tykind.TY_PTR);
t.sub = sub;
t.size = 8u64;
t.align = 8u64;
return t;
};
export fn typeslice(a: *arena, sub: *tinfo) *tinfo = {
let t: *tinfo = newtype(a, tykind.TY_SLICE);
t.sub = sub;
t.size = 24u64;
t.align = 8u64;
return t;
};
export fn typearray(a: *arena, sub: *tinfo, n: u64) *tinfo = {
let t: *tinfo = newtype(a, tykind.TY_ARRAY);
t.sub = sub;
t.alen = n;
if (sub != nil) {
t.size = sub.size * n;
t.align = sub.align;
} else {
t.align = 1u64;
};
return t;
};
export fn typechan(a: *arena, sub: *tinfo) *tinfo = {
let t: *tinfo = newtype(a, tykind.TY_CHAN);
t.sub = sub;
t.size = 8u64;
t.align = 8u64;
return t;
};
export fn typenamed(a: *arena, name: str, under: *tinfo) *tinfo = {
let t: *tinfo = newtype(a, tykind.TY_NAMED);
t.name = name;
t.under = under;
if (under != nil) {
t.size = under.size;
t.align = under.align;
};
return t;
};
// #61 audit §1.8 — A.1 infrastructure: tinfocache lookup/bind. Keyed
// by AST node-pointer so two different N_TNAME("i32") nodes get
// independent entries that both resolve to c.tyi32. Used by
// tinfofornode in check.ww; cgen still reads sizes via primtypesize
// until A.2+ graduates each walker family.
export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
let e: *tinfocacheent = c.tinfocache;
for (e != nil) {
if (e.key == key) { return e.val; };
e = e.cnext;
};
return nil;
};
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
let e: *tinfocacheent = amalloc(c.a, 32u64): *tinfocacheent;
e.key = key;
e.val = val;
e.cnext = c.tinfocache;
c.tinfocache = e;
};
// ---- predicates -------------------------------------------------------
export fn typeisint(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_I8) { return true; };
if (k == tykind.TY_I16) { return true; };
if (k == tykind.TY_I32) { return true; };
if (k == tykind.TY_I64) { return true; };
if (k == tykind.TY_U8) { return true; };
if (k == tykind.TY_U16) { return true; };
if (k == tykind.TY_U32) { return true; };
if (k == tykind.TY_U64) { return true; };
if (k == tykind.TY_INT) { return true; };
if (k == tykind.TY_UINT){ return true; };
if (k == tykind.TY_UINTPTR) { return true; };
if (k == tykind.TY_RUNE){ return true; };
if (k == tykind.TY_UNTYPED_INT) { return true; };
if (k == tykind.TY_UNTYPED_RUNE) { return true; };
if (k == tykind.TY_ENUM) { return typeisint(t.sub); };
if (k == tykind.TY_NAMED) { return typeisint(t.under); };
return false;
};
export fn typeisfloat(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_F32) { return true; };
if (k == tykind.TY_F64) { return true; };
if (k == tykind.TY_UNTYPED_FLOAT) { return true; };
if (k == tykind.TY_NAMED) { return typeisfloat(t.under); };
return false;
};
export fn typeisnum(t: *tinfo) bool = {
if (typeisint(t)) { return true; };
return typeisfloat(t);
};
export fn typeisunsigned(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_U8) { return true; };
if (k == tykind.TY_U16) { return true; };
if (k == tykind.TY_U32) { return true; };
if (k == tykind.TY_U64) { return true; };
if (k == tykind.TY_UINT){ return true; };
if (k == tykind.TY_UINTPTR) { return true; };
if (k == tykind.TY_NAMED) { return typeisunsigned(t.under); };
return false;
};
export fn typeisuntyped(t: *tinfo) bool = {
if (t == nil) { return false; };
let k: tykind = t.kind;
if (k == tykind.TY_UNTYPED_INT) { return true; };
if (k == tykind.TY_UNTYPED_FLOAT) { return true; };
if (k == tykind.TY_UNTYPED_STR) { return true; };
if (k == tykind.TY_UNTYPED_RUNE) { return true; };
if (k == tykind.TY_UNTYPED_BOOL) { return true; };
if (k == tykind.TY_UNTYPED_NIL) { return true; };
return false;
};
// typeeq — structural equality. Named types compare nominally.
export fn typeeq(a: *tinfo, b: *tinfo) bool = {
if (a == b) { return true; };
if (a == nil) { return false; };
if (b == nil) { return false; };
if (a.kind != b.kind) { return false; };
let k: tykind = a.kind;
if (k == tykind.TY_PTR) { return typeeq(a.sub, b.sub); };
if (k == tykind.TY_SLICE) { return typeeq(a.sub, b.sub); };
if (k == tykind.TY_CHAN) { return typeeq(a.sub, b.sub); };
if (k == tykind.TY_ARRAY) {
if (a.alen != b.alen) { return false; };
return typeeq(a.sub, b.sub);
};
if (k == tykind.TY_FN) {
if (a.variadic != b.variadic) { return false; };
if (!typeeq(a.ret, b.ret)) { return false; };
let pa: *tparam = a.params;
let pb: *tparam = b.params;
for (true) {
if (pa == nil) { if (pb == nil) { return true; }; return false; };
if (pb == nil) { return false; };
if (!typeeq(pa.type_, pb.type_)) { return false; };
pa = pa.tnext;
pb = pb.tnext;
};
return true;
};
if (k == tykind.TY_STRUCT) {
let fa: *tfield = a.fields;
let fb: *tfield = b.fields;
for (true) {
if (fa == nil) { if (fb == nil) { return true; }; return false; };
if (fb == nil) { return false; };
let na: str = fa.name;
let nb: str = fb.name;
if (na.len != nb.len) { return false; };
let i: i32 = 0;
for (i < na.len) {
if (na[i] != nb[i]) { return false; };
i += 1;
};
if (!typeeq(fa.type_, fb.type_)) { return false; };
fa = fa.tnext;
fb = fb.tnext;
};
return true;
};
if (k == tykind.TY_NAMED) { return false; }; // nominal: only same ptr
if (k == tykind.TY_TUPLE) {
let pa: *tparam = a.params;
let pb: *tparam = b.params;
for (true) {
if (pa == nil) { if (pb == nil) { return true; }; return false; };
if (pb == nil) { return false; };
if (!typeeq(pa.type_, pb.type_)) { return false; };
pa = pa.tnext;
pb = pb.tnext;
};
return true;
};
return true; // primitives match by kind alone
};