lib/ww: hash-index the tinfo cache, kills O(n2) compile (perf)
tinfocachelookup walked a flat prepend-only association list on every cache miss -> O(N) scan x O(N) calls = O(N2) (91% of all wwstage instructions on a 5k-line input; w6c_ww ~265x slower than its C twin). Replace the single list head with a node-ptr hash index, mirroring sym.ww scope.buckets (rule-12): NBUCKETS_TINFO=8192 power-of-two buckets, ptr hashed via (key>>4)&(N-1) (>>4 drops the always-zero aligned low bits so buckets don't cluster), cnext now chains within a bucket. First-match-in-bucket preserves the old most-recent-bind-wins order -> identical *tinfo per node -> byte-identical asm. cstage (cmd/wcc C) has no such cache, so this is wwstage-internal: no emitted-asm change, no cstage-symmetry obligation. Verified byte-identical output (baseline vs new binary, same 32k-line input) and 52.6s -> 0.54s (~97x). combined.ww regenerated for w6c + wwdump (only tools embedding typ.ww). test-unit (235) + smoke green.
This commit is contained in:
@@ -139,16 +139,25 @@ type tinfo = struct {
|
||||
};
|
||||
|
||||
// #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.
|
||||
// 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 {
|
||||
@@ -179,7 +188,7 @@ type tctx = struct {
|
||||
tyuntypedrune: *tinfo,
|
||||
tyuntypedbool: *tinfo,
|
||||
tyuntypednil: *tinfo,
|
||||
tinfocache: *tinfocacheent,
|
||||
tinfobuckets: **tinfocacheent, // length NBUCKETS_TINFO; node-ptr hash index
|
||||
};
|
||||
|
||||
// ---- constructors -----------------------------------------------------
|
||||
@@ -244,6 +253,8 @@ export fn typesinit(c: *tctx) void = {
|
||||
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 = {
|
||||
@@ -308,8 +319,15 @@ export fn typenamed(name: str, under: *tinfo) *tinfo = {
|
||||
// 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 e: *tinfocacheent = c.tinfocache;
|
||||
let bi: u64 = tinfobucket(key);
|
||||
let e: *tinfocacheent = c.tinfobuckets[bi];
|
||||
for (e != nil) {
|
||||
if (e.key == key) { return e.val; };
|
||||
e = e.cnext;
|
||||
@@ -318,8 +336,9 @@ export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
|
||||
};
|
||||
|
||||
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
|
||||
let e: *tinfocacheent = alloc(tinfocacheent{key=key, val=val, cnext=c.tinfocache})!;
|
||||
c.tinfocache = e;
|
||||
let bi: u64 = tinfobucket(key);
|
||||
let e: *tinfocacheent = alloc(tinfocacheent{key=key, val=val, cnext=c.tinfobuckets[bi]})!;
|
||||
c.tinfobuckets[bi] = e;
|
||||
};
|
||||
|
||||
// ---- predicates -------------------------------------------------------
|
||||
|
||||
@@ -9667,16 +9667,25 @@ type tinfo = struct {
|
||||
};
|
||||
|
||||
// #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.
|
||||
// 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 {
|
||||
@@ -9707,7 +9716,7 @@ type tctx = struct {
|
||||
tyuntypedrune: *tinfo,
|
||||
tyuntypedbool: *tinfo,
|
||||
tyuntypednil: *tinfo,
|
||||
tinfocache: *tinfocacheent,
|
||||
tinfobuckets: **tinfocacheent, // length NBUCKETS_TINFO; node-ptr hash index
|
||||
};
|
||||
|
||||
// ---- constructors -----------------------------------------------------
|
||||
@@ -9772,6 +9781,8 @@ export fn typesinit(c: *tctx) void = {
|
||||
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 = {
|
||||
@@ -9836,8 +9847,15 @@ export fn typenamed(name: str, under: *tinfo) *tinfo = {
|
||||
// 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 e: *tinfocacheent = c.tinfocache;
|
||||
let bi: u64 = tinfobucket(key);
|
||||
let e: *tinfocacheent = c.tinfobuckets[bi];
|
||||
for (e != nil) {
|
||||
if (e.key == key) { return e.val; };
|
||||
e = e.cnext;
|
||||
@@ -9846,8 +9864,9 @@ export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
|
||||
};
|
||||
|
||||
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
|
||||
let e: *tinfocacheent = alloc(tinfocacheent{key=key, val=val, cnext=c.tinfocache})!;
|
||||
c.tinfocache = e;
|
||||
let bi: u64 = tinfobucket(key);
|
||||
let e: *tinfocacheent = alloc(tinfocacheent{key=key, val=val, cnext=c.tinfobuckets[bi]})!;
|
||||
c.tinfobuckets[bi] = e;
|
||||
};
|
||||
|
||||
// ---- predicates -------------------------------------------------------
|
||||
|
||||
@@ -9667,16 +9667,25 @@ type tinfo = struct {
|
||||
};
|
||||
|
||||
// #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.
|
||||
// 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 {
|
||||
@@ -9707,7 +9716,7 @@ type tctx = struct {
|
||||
tyuntypedrune: *tinfo,
|
||||
tyuntypedbool: *tinfo,
|
||||
tyuntypednil: *tinfo,
|
||||
tinfocache: *tinfocacheent,
|
||||
tinfobuckets: **tinfocacheent, // length NBUCKETS_TINFO; node-ptr hash index
|
||||
};
|
||||
|
||||
// ---- constructors -----------------------------------------------------
|
||||
@@ -9772,6 +9781,8 @@ export fn typesinit(c: *tctx) void = {
|
||||
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 = {
|
||||
@@ -9836,8 +9847,15 @@ export fn typenamed(name: str, under: *tinfo) *tinfo = {
|
||||
// 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 e: *tinfocacheent = c.tinfocache;
|
||||
let bi: u64 = tinfobucket(key);
|
||||
let e: *tinfocacheent = c.tinfobuckets[bi];
|
||||
for (e != nil) {
|
||||
if (e.key == key) { return e.val; };
|
||||
e = e.cnext;
|
||||
@@ -9846,8 +9864,9 @@ export fn tinfocachelookup(c: *tctx, key: *node) *tinfo = {
|
||||
};
|
||||
|
||||
export fn tinfocachebind(c: *tctx, key: *node, val: *tinfo) void = {
|
||||
let e: *tinfocacheent = alloc(tinfocacheent{key=key, val=val, cnext=c.tinfocache})!;
|
||||
c.tinfocache = e;
|
||||
let bi: u64 = tinfobucket(key);
|
||||
let e: *tinfocacheent = alloc(tinfocacheent{key=key, val=val, cnext=c.tinfobuckets[bi]})!;
|
||||
c.tinfobuckets[bi] = e;
|
||||
};
|
||||
|
||||
// ---- predicates -------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user