The last four raw `->under` reads outside the whitelist were the
static-DATA emitters' ELEMENT-type single peels (the outer type already
chased): emit_array_lit_bytes:14356, emit_strarray_data:14574,
emit_slice_data:14788, let_pre_intern:15088 -> type_chase_named.
:15088 is the :14574 row's label-order leg and must flip in the same
commit or _S_ labels intern in emit order, not decl order (the in-tree
comment at the site); the strarr row's byte-id is the coupling proof.
Behavior moves (ken B7 first-position oracle + impl pre-state, all
pre-observed at 05f7af7):
- [N]alias-struct + [N]alias-str globals graduate cs link-ERR
("undefined reference") -> 0/0 BYTE-ID (cs emits ww's DATAW).
- zero-consumer latent silence closed: a never-referenced
2-level-elem-alias global silently lacked DATA (no reference, no
link error); now emits, pinned by the byte-id cell.
- []alias-str diagnostic routing: the alias escaped the 3-way
slice-of-{str,slice,tagged} fatal onto the downstream "not a
foldable constant" text — now the intended 3-way text (== control).
- []alias-tagged DESIGNED NARROWING: the alias dodged the 3-way fatal
ENTIRELY — cs silently accepted + RAN WRONG for reachable consumer
shapes (review-verified at base: a len+payload-read probe exits 1;
the len-only row was luck-correct). Now loud with the 3-way text;
widen what the gate SEES, never what it ACCEPTS (B6-c2 precedent).
- kb7_slc/slc0 scalar legs byte-NEUTRAL (the synthesized-array
choke-point already handled them); full kb corpus sweep: movers are
exactly the two graduation shapes, nothing else.
tools/peellint (sizelint clone, dep of test/test-unit): character-scan
strips comments and string/char literals, then matches the under-token
accessor-spelling-wide — `->under`/`.under` in C (deref-dot is the
same peel), `.under` in ww, optional whitespace after the operator,
and the line-split continuation (operator at EOL, `under` next line).
Scope cmd/wcc + cmd/w6c + selfhost/cmd/wcc + lib/ww (lib/ww/typ.ww
ruled IN — it is type.c's ww mirror, the accessor layer itself);
`peel-ok`/`peellint-ok` annotations exempt a 10-line window. Green at
this tip = zero unwhitelisted raw peels survive; the gate lands in the
commit that deletes the last raw read (the-funnel-completing-commit-
carries-the-gate; sizelint precedent). Whitelist, 27 entries:
cmd/wcc/type.c :78 :141 construction, :162 chase body,
:180 :193 :214 recursive chase
cmd/wcc/check.c :102 :2572 resolve-state probes, :2586 construction
cmd/w6c/cgen.c :731 probe-cleared scan peel (B5-c1),
:813/:814 :834/:835 peel-ok #218 variant-match
lib/ww/typ.ww :316 construction, :374 :385 :410 :437 :447 :463
:475 :488 :514 recursive chase
selfhost/cmd/wcc/cgenutil.ww :1302 chase body (tichase),
:2759 probe-cleared peel
selfhost/cmd/wcc/check.ww :1815 construction (peellint-ok)
Negative validation wired into 944_peellint_gate (B4 precedent):
re-introduced raw peel (C and ww spellings) REDS the lint; corrupted
annotation (peel-okk-…, token-bounded matcher) REDS the lint; the
check.ww:3683 "io.underread" prose, a code read of a longer field, and
comment-quoted tokens are pinned green regression rows; real tree must
lint clean. 944_alias_emit_b7_run pins all four emit paths
table-driven (14 rows / 36 checks) incl. ken's ww observation cells
(ww checker rejects slice-literal globals, "let: not assignable" —
unmoved; plain []str louds at ww's own emitslicedata 3-way, pinned by
the shared needle).
REVIEW AMENDMENT (reviewer-B7, fix-what-you-find): the frozen tip's
regex matcher passed five compiling evasion spellings green — `t ->
under` spacing, `t->`/EOL + `under` next-line (both stages; ww parses
`t.`/EOL too), C deref-dot `(*t).under`, ww `t. under`, and a string
literal containing a block-comment opener that blinded the regex
comment-strip for the rest of the file. The matcher is now a
character scan (comments + string/char literals stripped before
matching) with the widened token rule above; all six spellings are
pinned RED rows in 944_peellint_gate (checks 10 -> 16). The 10-line
annotation window stays as designed (a peel within an annotation's
window is exempt by construction — the window IS the exemption
mechanism). Lint + test bytes only; zero compiler-source bytes moved
in review.
What this does NOT close, said out loud (f2-ruling): a consumer that
never spells `under` at all — a switch on t->kind that simply never
peels — has no token for the lint to see. The accessor+lint closes the
WRONG-PEEL class (single-peel where chase was needed) by construction;
the NO-PEEL class is closed only at sites where classification routes
through the internalized chasing helpers, and contained elsewhere by
the acceptance-commit-carries-tripwires doctrine, which stays standing
for every future acceptance widening. The gate does not make alias
bugs impossible; it makes the four-times-burned shape unwritable.
Rule-11 note: forced fuse — the four conversions ARE the last raw-read
deletions; peellint cannot be green one commit earlier (consumer-graph
-forces-the-fuse precedent, #61).
Invariants: cs asm byte-NEUTRAL on the whole bootstrap corpus (five
mains + smoke, base-input pre==post); five mains cs==ww byte-id at
tip; _ww binary quartet bit-identical to the W2 baseline (ww changes
are comment-only annotation bytes — codegen-inert, proven by the md5
hold); w6c_ww+wwdump main.combined.ww regen'd via make, idempotent;
989 lib ratchet zero flips (31 byte-id / 9 pinned-divergent / 3
pinned-wwreject across 43 units); sizelint 0; peellint 0;
make test-unit "all 294 tests passed" (292 + the two new suites).
616 lines
22 KiB
Plaintext
616 lines
22 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 program.
|
|
|
|
package ww;
|
|
|
|
import os;
|
|
|
|
// ---- 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,
|
|
TY_SIZE = 36, // #85 fold-1; mirrors TY_UINTPTR (8/8 amd64)
|
|
TY_OPAQUE = 37, // #108(a); abstract + unsized, behind indirection only
|
|
};
|
|
|
|
// #108(a): unsized sentinel for abstract types (tinfo.size / .align).
|
|
// Mirrors harec SIZE_UNDEFINED = (size_t)-1 (ref/harec/include/types.h
|
|
// :58) and cstage cmd/wcc/ww.h; not 0, so a bare opaque local can't
|
|
// fabricate a 0-byte slot. Value == U64_MAX.
|
|
def SIZE_UNDEFINED: u64 = 18446744073709551615;
|
|
|
|
// ---- tinfo / tfield / tparam -----------------------------------------
|
|
|
|
type tfield = struct {
|
|
name: str,
|
|
type_: *tinfo,
|
|
offset: u64,
|
|
tnext: *tfield,
|
|
};
|
|
|
|
type tparam = struct {
|
|
name: str,
|
|
type_: *tinfo,
|
|
// #61a: per-variant `!T` error mark for TY_TAGGED variants.
|
|
// cstage-MIRROR divergence: harec carries no per-variant flag —
|
|
// it models `!T` as a distinct STORAGE_ERROR type node
|
|
// (ref/harec/include/types.h:144, src/types.c:151-159
|
|
// type_is_error). wwstage tinfo has no iserror field
|
|
// (check.ww TTAGGED arm), so the bit rides the shared param
|
|
// struct instead, matching cstage Type.iserror semantics.
|
|
// Faithful STORAGE_ERROR-node port filed as #62.
|
|
iserror: bool,
|
|
tnext: *tparam,
|
|
};
|
|
|
|
// #57 A.6.3i-phase-1: tuple positional element. Distinct from tfield
|
|
// (named, struct member) per harec's split at ref/harec/include/types.h
|
|
// :109-115 (struct_field) vs :122-126 (type_tuple) — tuple positionals
|
|
// carry no name (positional only) and a separate next-link. Rule-12
|
|
// sea-of-stars mirrors Hare's structural choice; the empty-name idiom
|
|
// from #50's TTAGGED-on-tparam would conflate two semantic axes
|
|
// (variants can be named; positionals never can).
|
|
type ttupleelem = struct {
|
|
type_: *tinfo,
|
|
offset: u64,
|
|
tnext: *ttupleelem,
|
|
};
|
|
|
|
type tinfo = struct {
|
|
kind: tykind,
|
|
size: u64,
|
|
align: u64,
|
|
sub: *tinfo, // ptr/slice/array/chan element
|
|
alen: u64,
|
|
fields: *tfield,
|
|
params: *tparam,
|
|
tupleelems: *ttupleelem, // #57 A.6.3i-phase-1: TY_TUPLE
|
|
// positional chain (harec types.h:122-126
|
|
// `struct type_tuple`). Distinct slot from
|
|
// .fields so struct-member vs tuple-
|
|
// positional stay axis-separated.
|
|
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).
|
|
name: str,
|
|
under: *tinfo,
|
|
resolving: i32, // #62/#69: TY_NAMED demand-resolution cycle guard.
|
|
// Mirrors cstage Type.resolving (cmd/wcc/ww.h)
|
|
// and harec idecl->in_progress (ref/harec/src/
|
|
// check.c:4767): set while the alias body
|
|
// resolves; a VALUE-position read of an
|
|
// in-progress named is a true type cycle and
|
|
// loud-rejects. Pointer positions never read
|
|
// size, so legal self-refs stay accepted.
|
|
slotsize: u64, // #61 A.5: stack-slot SSoT split from `size`.
|
|
// `size` stays natural (Hare-faithful);
|
|
// `slotsize` carries the slot-padded width
|
|
// cgen's let/struct-field layout demands.
|
|
// For primitives/ptr/slice/chan/fn/str/tagged
|
|
// `slotsize == size`; struct + tuple + array
|
|
// of struct diverge — see check.ww tinfo-
|
|
// fornode + cgenutil.ww registerstruct.
|
|
// Pad-to-8 of narrow primitives in let slots
|
|
// still lives at slotsize()'s read site;
|
|
// graduating it here would break `[N]i32`
|
|
// stride (4*N stays natural).
|
|
};
|
|
|
|
// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes
|
|
// tinfofornode lookups keyed by AST pointer. perf #18: the original
|
|
// flat prepend-only list made the cache-MISS scan O(N) per call ->
|
|
// O(N2) over a compile (91% of all wwstage instructions on a 5k-line
|
|
// input). Now a node-ptr hash index, mirroring sym.ww scope.buckets
|
|
// (rule-12): cnext chains WITHIN a bucket; lookup/bind hash then touch
|
|
// only one bucket -> O(1) amortized. Identical lookup results (same
|
|
// *tinfo for the same node), so emitted asm is byte-identical.
|
|
type tinfocacheent = struct {
|
|
key: *node,
|
|
val: *tinfo,
|
|
cnext: *tinfocacheent,
|
|
};
|
|
|
|
// Tuning knob, NOT a type size (rule-13 N/A): power-of-two so the
|
|
// bucket index is a MASK, not a mod. ~5400 nodes on a big input ->
|
|
// well under one entry/bucket. Mirror of sym.ww:38 NBUCKETS (16),
|
|
// scaled up — sym's 16 would give ~340-deep chains here.
|
|
def NBUCKETS_TINFO: u64 = 8192u64;
|
|
|
|
// ---- tctx — the box of primitive types -------------------------------
|
|
|
|
type tctx = struct {
|
|
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,
|
|
tysize: *tinfo,
|
|
tyopaque: *tinfo,
|
|
tyf32: *tinfo,
|
|
tyf64: *tinfo,
|
|
tystr: *tinfo,
|
|
tyerr: *tinfo,
|
|
tynever: *tinfo,
|
|
tyuntypedint: *tinfo,
|
|
tyuntypedfloat: *tinfo,
|
|
tyuntypedstr: *tinfo,
|
|
tyuntypedrune: *tinfo,
|
|
tyuntypedbool: *tinfo,
|
|
tyuntypednil: *tinfo,
|
|
tinfobuckets: **tinfocacheent, // length NBUCKETS_TINFO; node-ptr hash index
|
|
};
|
|
|
|
// ---- constructors -----------------------------------------------------
|
|
|
|
export fn newtype(k: tykind) *tinfo = {
|
|
let t: *tinfo = alloc(tinfo{kind=k, size=0u64, align=0u64, sub=nil, alen=0u64, fields=nil, params=nil, tupleelems=nil, ret=nil, variadic=0, nullable=0, name="", under=nil, slotsize=0u64})!;
|
|
return t;
|
|
};
|
|
|
|
fn prim(k: tykind, nm: str, sz: u64, al: u64) *tinfo = {
|
|
let t: *tinfo = newtype(k);
|
|
t.name = nm;
|
|
t.size = sz;
|
|
if (al > 0u64) { t.align = al; } else { t.align = sz; };
|
|
t.slotsize = sz;
|
|
return t;
|
|
};
|
|
|
|
export fn typesinit(c: *tctx) void = {
|
|
c.tyvoid = prim(tykind.TY_VOID, "void", 0u64, 1u64);
|
|
c.tybool = prim(tykind.TY_BOOL, "bool", 1u64, 1u64);
|
|
c.tyrune = prim(tykind.TY_RUNE, "rune", 4u64, 4u64);
|
|
c.tyi8 = prim(tykind.TY_I8, "i8", 1u64, 1u64);
|
|
c.tyi16 = prim(tykind.TY_I16, "i16", 2u64, 2u64);
|
|
c.tyi32 = prim(tykind.TY_I32, "i32", 4u64, 4u64);
|
|
c.tyi64 = prim(tykind.TY_I64, "i64", 8u64, 8u64);
|
|
c.tyu8 = prim(tykind.TY_U8, "u8", 1u64, 1u64);
|
|
c.tyu16 = prim(tykind.TY_U16, "u16", 2u64, 2u64);
|
|
c.tyu32 = prim(tykind.TY_U32, "u32", 4u64, 4u64);
|
|
c.tyu64 = prim(tykind.TY_U64, "u64", 8u64, 8u64);
|
|
c.tyint = prim(tykind.TY_INT, "int", 8u64, 8u64);
|
|
c.tyuint = prim(tykind.TY_UINT, "uint", 8u64, 8u64);
|
|
c.tyuintptr= prim(tykind.TY_UINTPTR, "uintptr", 8u64, 8u64);
|
|
c.tysize = prim(tykind.TY_SIZE, "size", 8u64, 8u64); // #85
|
|
c.tyf32 = prim(tykind.TY_F32, "f32", 4u64, 4u64);
|
|
c.tyf64 = prim(tykind.TY_F64, "f64", 8u64, 8u64);
|
|
// str IS []u8: { *u8, len, cap } — 24B, 3-reg ABI (#1/Phase 3).
|
|
// Size sourced from a u8-slice's size (typeslice SSoT) so str and
|
|
// []u8 can never drift; no second hardcoded 24. Mirrors cstage
|
|
// type.c `type_slice(a, ty_u8)->size`.
|
|
//
|
|
// The slice tinfo MUST land in a local first: the inline form
|
|
// `typeslice(c.tyu8).size` triggers a cgen bug — `call().field`
|
|
// where the call returns a *pointer* emits no deref (it uses the
|
|
// returned pointer AS the field value), so tystr.size would become
|
|
// a heap address → runaway slot-size loops. Filed as task #6
|
|
// (cstage cgen.c N_DOT base=N_CALL-returning-pointer + wwstage
|
|
// cgdot mirror); retained here as a local until that lands.
|
|
let u8slice: *tinfo = typeslice(c.tyu8);
|
|
c.tystr = prim(tykind.TY_STR, "str", u8slice.size, 8u64);
|
|
c.tystr.sub = c.tyu8; // str IS []u8: element is u8 (Phase 2 F1)
|
|
c.tyerr = prim(tykind.TY_ERR, "<err>", 0u64, 1u64);
|
|
c.tynever = prim(tykind.TY_NEVER, "never", 0u64, 1u64);
|
|
// #108(a): abstract + unsized. SIZE_UNDEFINED (not 0) blocks a bare
|
|
// `let x: opaque` 0-byte slot; legal only behind indirection.
|
|
// Mirrors cstage type.c ty_opaque (harec types.c:1446).
|
|
c.tyopaque = prim(tykind.TY_OPAQUE, "opaque", SIZE_UNDEFINED, SIZE_UNDEFINED);
|
|
|
|
c.tyuntypedint = prim(tykind.TY_UNTYPED_INT, "untyped_int", 0u64, 1u64);
|
|
c.tyuntypedfloat = prim(tykind.TY_UNTYPED_FLOAT, "untyped_float", 0u64, 1u64);
|
|
c.tyuntypedstr = prim(tykind.TY_UNTYPED_STR, "untyped_str", 0u64, 1u64);
|
|
c.tyuntypedrune = prim(tykind.TY_UNTYPED_RUNE, "untyped_rune", 0u64, 1u64);
|
|
c.tyuntypedbool = prim(tykind.TY_UNTYPED_BOOL, "untyped_bool", 0u64, 1u64);
|
|
c.tyuntypednil = prim(tykind.TY_UNTYPED_NIL, "untyped_nil", 0u64, 1u64);
|
|
let tib: []*tinfocacheent = alloc([], NBUCKETS_TINFO)!; // mirror sym.ww:63
|
|
c.tinfobuckets = tib.ptr;
|
|
};
|
|
|
|
export fn typeptr(sub: *tinfo) *tinfo = {
|
|
let t: *tinfo = newtype(tykind.TY_PTR);
|
|
t.sub = sub;
|
|
t.size = 8u64;
|
|
t.align = 8u64;
|
|
t.slotsize = 8u64;
|
|
return t;
|
|
};
|
|
|
|
export fn typeslice(sub: *tinfo) *tinfo = {
|
|
let t: *tinfo = newtype(tykind.TY_SLICE);
|
|
t.sub = sub;
|
|
t.size = 24u64; // sizelint-ok: SSoT for slice header (#64)
|
|
t.align = 8u64;
|
|
t.slotsize = 24u64; // sizelint-ok: SSoT for slice slotsize (#64)
|
|
return t;
|
|
};
|
|
|
|
export fn typearray(sub: *tinfo, n: u64) *tinfo = {
|
|
let t: *tinfo = newtype(tykind.TY_ARRAY);
|
|
t.sub = sub;
|
|
t.alen = n;
|
|
if (sub != nil) {
|
|
t.size = sub.size * n;
|
|
t.align = sub.align;
|
|
// #61 A.5: ti.slotsize = stride * elen using the element's
|
|
// slot-padded width. Primitives have slotsize == size so
|
|
// `[N]i32` stride stays 4 (natural); structs have padded
|
|
// slotsize so `[N]Triplet` stride lifts to 16.
|
|
t.slotsize = sub.slotsize * n;
|
|
} else {
|
|
t.align = 1u64;
|
|
};
|
|
return t;
|
|
};
|
|
|
|
export fn typechan(sub: *tinfo) *tinfo = {
|
|
let t: *tinfo = newtype(tykind.TY_CHAN);
|
|
t.sub = sub;
|
|
t.size = 8u64;
|
|
t.align = 8u64;
|
|
t.slotsize = 8u64;
|
|
return t;
|
|
};
|
|
|
|
export fn typenamed(name: str, under: *tinfo) *tinfo = {
|
|
let t: *tinfo = newtype(tykind.TY_NAMED);
|
|
t.name = name;
|
|
t.under = under; // peel-ok: construction
|
|
if (under != nil) {
|
|
t.size = under.size;
|
|
t.align = under.align;
|
|
t.slotsize = under.slotsize;
|
|
};
|
|
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.
|
|
// Node ptrs are 8+-aligned, so the low 3-4 bits are always zero —
|
|
// shift right 4 before masking or every 16th bucket would cluster.
|
|
fn tinfobucket(key: *node) u64 = {
|
|
return ((key: u64) >> 4u64) & (NBUCKETS_TINFO - 1u64);
|
|
};
|
|
|
|
export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
|
|
let bi: u64 = tinfobucket(key);
|
|
let e: *tinfocacheent = c.tinfobuckets[bi];
|
|
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 bi: u64 = tinfobucket(key);
|
|
let e: *tinfocacheent = alloc(tinfocacheent{key=key, val=val, cnext=c.tinfobuckets[bi]})!;
|
|
c.tinfobuckets[bi] = 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_SIZE) { 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); };
|
|
// peel-ok: recursive chase
|
|
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; };
|
|
// peel-ok: recursive chase
|
|
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);
|
|
};
|
|
|
|
// TY_RUNE is unsigned: Unicode codepoint (0..0x10FFFF) zero-extends on
|
|
// sub-word load (MOVL, not MOVSXD). TY_ENUM recurses on .sub so a
|
|
// `type k = enum u32 {…}` reads as unsigned. Cite cstage type.c:178
|
|
// `type_isunsigned`; rule 10 keeps wwstage aligned down to cstage.
|
|
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_SIZE) { return true; };
|
|
if (k == tykind.TY_RUNE){ return true; };
|
|
// peel-ok: recursive chase
|
|
if (k == tykind.TY_NAMED) { return typeisunsigned(t.under); };
|
|
if (k == tykind.TY_ENUM) { return typeisunsigned(t.sub); };
|
|
return false;
|
|
};
|
|
|
|
// typeissigned — does this type need sign-extension on a sub-word
|
|
// (1/2/4B) load? Mirrors cstage cgen.c:240 `fld_issigned`. Cgen-facing
|
|
// predicate (TY_BOOL is unsigned for storage purposes — 0/1 → MOVZBQ),
|
|
// so it doesn't simply mirror `!typeisunsigned`. Pair-of-`is*`
|
|
// convention follows ref/hare/types/ helpers.
|
|
export fn typeissigned(t: *tinfo) bool = {
|
|
if (t == nil) { return false; };
|
|
if (t.kind == tykind.TY_BOOL) { return false; };
|
|
if (typeisunsigned(t)) { return false; };
|
|
return typeisint(t);
|
|
};
|
|
|
|
// typeisstr — TY_STR (and TY_UNTYPED_STR for literals pre-default).
|
|
// Cite cstage cgen.c:159 `type_isstr` — single TY_NAMED peel, accepts
|
|
// the same untyped form. ww walks the .under chain so alias-of-alias
|
|
// (`type s2 = s1; type s1 = str;`) lands the same way.
|
|
export fn typeisstr(t: *tinfo) bool = {
|
|
if (t == nil) { return false; };
|
|
let k: tykind = t.kind;
|
|
if (k == tykind.TY_STR) { return true; };
|
|
if (k == tykind.TY_UNTYPED_STR) { return true; };
|
|
// peel-ok: recursive chase
|
|
if (k == tykind.TY_NAMED) { return typeisstr(t.under); };
|
|
return false;
|
|
};
|
|
|
|
// typeisslice — TY_SLICE. Cite cstage cgen.c:174 `type_isslice`.
|
|
export fn typeisslice(t: *tinfo) bool = {
|
|
if (t == nil) { return false; };
|
|
let k: tykind = t.kind;
|
|
if (k == tykind.TY_SLICE) { return true; };
|
|
// peel-ok: recursive chase
|
|
if (k == tykind.TY_NAMED) { return typeisslice(t.under); };
|
|
return false;
|
|
};
|
|
|
|
// typeistagged — TY_TAGGED (alias-aware). Cite cstage cgen.c:516
|
|
// `type_istagged` — same single-peel shape. The node-keyed wwstage
|
|
// helper this replaces also unwrapped a leading N_TBANG so
|
|
// `type error = !(invalid | overflow);` registered as tagged. Post-
|
|
// A.6.2 the TBANG unwrap is handled by tinfofornode (check.ww:1145-
|
|
// 1152 returns the inner tinfo unchanged) so we recover the cstage
|
|
// semantics with the bare kind check + NAMED chase.
|
|
export fn typeistagged(t: *tinfo) bool = {
|
|
if (t == nil) { return false; };
|
|
let k: tykind = t.kind;
|
|
if (k == tykind.TY_TAGGED) { return true; };
|
|
// peel-ok: recursive chase
|
|
if (k == tykind.TY_NAMED) { return typeistagged(t.under); };
|
|
return false;
|
|
};
|
|
|
|
// typeisf32 — narrower-than-typeisfloat: only TY_F32 (after alias
|
|
// chase). Cite cstage cgen.c:188 `type_isf32`. Used to pick MOVSS vs
|
|
// MOVSD and the SS-variant arithmetic / cast opcodes.
|
|
export fn typeisf32(t: *tinfo) bool = {
|
|
if (t == nil) { return false; };
|
|
let k: tykind = t.kind;
|
|
if (k == tykind.TY_F32) { return true; };
|
|
// peel-ok: recursive chase
|
|
if (k == tykind.TY_NAMED) { return typeisf32(t.under); };
|
|
return false;
|
|
};
|
|
|
|
// typeisnullable — TY_TAGGED with the `(*T | void)` one-word fold.
|
|
// Cite cstage cgen.c:396 `type_isnullable`. The .nullable flag is
|
|
// stamped by tinfofornode (check.ww:1309-1318) when the two-variant
|
|
// shape matches.
|
|
export fn typeisnullable(t: *tinfo) bool = {
|
|
if (t == nil) { return false; };
|
|
let k: tykind = t.kind;
|
|
if (k == tykind.TY_TAGGED) { return t.nullable != 0; };
|
|
// peel-ok: recursive chase
|
|
if (k == tykind.TY_NAMED) { return typeisnullable(t.under); };
|
|
return false;
|
|
};
|
|
|
|
// typeis8byteprim — does this type take exactly one 8-byte stack
|
|
// slot (ptr / fn / chan / 64-bit int / scalar primitive padded up to
|
|
// 8 / `[N]T` whose natural width is 8) rather than a wider aggregate?
|
|
// Mirrors the ladder cstage's cgen.c N_LET zero-init takes on `sz==8`
|
|
// (cmd/wcc/check.c sizing + cgen.c N_LET). The node-keyed wwstage
|
|
// helper this replaces predates tinfo and AST-walked TBANG / TNAME
|
|
// alias chains; tinfofornode now collapses TBANG (check.ww:1145) and
|
|
// TY_NAMED.under carries the chain, so the tinfo walk handles every
|
|
// shape the AST walker did.
|
|
export fn typeis8byteprim(t: *tinfo) bool = {
|
|
if (t == nil) { return false; };
|
|
let k: tykind = t.kind;
|
|
if (k == tykind.TY_PTR) { return true; };
|
|
if (k == tykind.TY_FN) { return true; };
|
|
if (k == tykind.TY_CHAN) { return true; };
|
|
if (k == tykind.TY_SLICE) { return false; };
|
|
if (k == tykind.TY_TUPLE) { return false; };
|
|
if (k == tykind.TY_TAGGED) { return false; };
|
|
if (k == tykind.TY_STR) { return false; };
|
|
if (k == tykind.TY_STRUCT) { return false; };
|
|
if (k == tykind.TY_ARRAY) { return t.size == 8u64; };
|
|
// peel-ok: recursive chase
|
|
if (k == tykind.TY_NAMED) { return typeis8byteprim(t.under); };
|
|
// Remaining: primitives (i8/u8/.../i64/u64/bool/rune/f32/f64/
|
|
// int/uint/uintptr) and TY_VOID. All slot-pad to 8 and zero-init
|
|
// in cstage's `sz==8` branch.
|
|
return true;
|
|
};
|
|
|
|
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_TAGGED) {
|
|
// Structural: variant lists match position-by-position, and
|
|
// the nullable `(*T|void)` fold is part of identity. Mirrors
|
|
// cstage type_eq's TY_TAGGED arm (cmd/wcc/type.c:271-284);
|
|
// the missing branch let any two tagged unions compare equal
|
|
// (fell through to the primitive `return true`), which
|
|
// #218's cgvariantmatch structural fallback was the first
|
|
// caller to exercise.
|
|
if (a.nullable != b.nullable) { 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_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
|
|
};
|