gitnexus 1.6.8-rc.38 → 1.6.8-rc.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,66 +13,6 @@ export declare const CB_RESET_TIMEOUT_MS = 60000;
13
13
  export declare const HF_MAX_TIMEOUT_MS: number;
14
14
  /** Upper bound clamped on the env-override attempt count. */
15
15
  export declare const HF_MAX_ATTEMPTS_CAP = 10;
16
- /**
17
- * @internal Exported only for unit tests and the two embedder entry points
18
- * (`core/embeddings/embedder.ts` + `mcp/core/embedder.ts`). Not part of the
19
- * public package API.
20
- *
21
- * Minimal subset of `@huggingface/transformers`' `env` object that gitnexus
22
- * mutates. Defining a local structural type keeps this helper free of a
23
- * transitive dependency on transformers' generated `.d.ts` while still
24
- * giving full type-checking on the two fields we actually touch.
25
- */
26
- export interface HfEnvSubset {
27
- cacheDir: string;
28
- remoteHost: string;
29
- }
30
- /**
31
- * @internal Exported only for unit tests and the two embedder entry points
32
- * (`core/embeddings/embedder.ts` + `mcp/core/embedder.ts`). Not part of the
33
- * public package API.
34
- *
35
- * Apply user-controlled HuggingFace environment overrides to the
36
- * `@huggingface/transformers` `env` object. Centralises the two env-var
37
- * bridges so every gitnexus embedder entry point (the analyze pipeline
38
- * and the MCP server) behaves identically.
39
- *
40
- * - **`HF_HOME`** → `env.cacheDir` (default: `~/.cache/huggingface`).
41
- * transformers.js otherwise defaults to `./node_modules/.cache` inside
42
- * its own install dir, which is unwritable when gitnexus is installed
43
- * globally (e.g. `/usr/lib/node_modules/`).
44
- *
45
- * - **`HF_ENDPOINT`** → `env.remoteHost` (#1205). transformers.js does
46
- * not read `HF_ENDPOINT` on its own — it reads `env.remoteHost` —
47
- * even though `HF_ENDPOINT` is the standard env var the upstream
48
- * `huggingface_hub` Python client and the official HF mirror docs
49
- * tell users to set. Bridging the two unblocks `--embeddings` for
50
- * users behind networks where `huggingface.co` is unreachable
51
- * (corporate proxies, the GFW, air-gapped mirrors). The trailing
52
- * slash is normalised because transformers.js builds URLs by string
53
- * concatenation and a missing slash silently falls through to its
54
- * default `huggingface.co/...` host.
55
- *
56
- * Mutation rather than return-and-apply because callers already hold a
57
- * reference to the live `env` object imported from
58
- * `@huggingface/transformers` — passing the same reference in keeps the
59
- * call site a single line at each entry point.
60
- */
61
- export declare function applyHfEnvOverrides(env: HfEnvSubset): void;
62
- /**
63
- * @internal Exported for unit tests and the two embedder entry points.
64
- *
65
- * Returns true when an error message indicates a network-level fetch failure
66
- * during HuggingFace model download (e.g. `TypeError: fetch failed`,
67
- * `ECONNREFUSED`, `ENOTFOUND`, `ETIMEDOUT`, `ECONNRESET`).
68
- *
69
- * These errors are not device-specific and cannot be fixed by falling back to
70
- * a different ONNX device — the caller should rethrow immediately with
71
- * guidance about `HF_ENDPOINT`.
72
- */
73
- export declare function isNetworkFetchError(message: string): boolean;
74
- /** @internal Used by `withHfDownloadRetry` to mark a circuit-open rejection. */
75
- export declare const CIRCUIT_OPEN_TAG = "hf-circuit-open";
76
16
  /**
77
17
  * Module-level singleton shared by both embedder entry points
78
18
  * (`core/embeddings/embedder.ts` + `mcp/core/embedder.ts`). Per-process
@@ -82,18 +22,12 @@ export declare const CIRCUIT_OPEN_TAG = "hf-circuit-open";
82
22
  * recovery-time stampedes).
83
23
  */
84
24
  export declare const hfDownloadCircuit: CircuitBreaker;
85
- /** @internal Returns true for errors that should abort without retry (circuit-open). */
86
- export declare function isHfCircuitOpenError(message: string): boolean;
87
25
  /**
88
26
  * Returns true for any HuggingFace download failure that warrants showing the
89
27
  * `HF_ENDPOINT` remediation hint: either a raw network error or a
90
28
  * circuit-open rejection (which itself was caused by repeated network errors).
91
29
  */
92
30
  export declare function isHfDownloadFailure(message: string): boolean;
93
- /** @internal Wraps `fn` in a hard time-limit. The timeout error contains
94
- * `ETIMEDOUT` so that `isNetworkFetchError` classifies it correctly.
95
- */
96
- export declare function withDownloadTimeout<T>(fn: () => Promise<T>, timeoutMs: number): Promise<T>;
97
31
  export interface HfRetryOptions {
98
32
  /** Maximum total attempts including the initial one (default: `HF_MAX_ATTEMPTS`). */
99
33
  maxAttempts?: number;
@@ -89,10 +89,16 @@ export declare const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION: number;
89
89
  * never fires). Truncation degrades to a sound empty REACHING_DEF for that one
90
90
  * function (status `truncated`), never wrong facts.
91
91
  *
92
- * This ceiling is the SOUND backstop, not a perf fix: WTO / loop-aware iteration
93
- * ordering was benchmarked and rejected (0% faster the cost is dense-set
94
- * propagation, not visitation order; see the no-go note in reaching-defs.ts at
95
- * the RPO-order site). SSA-sparse reaching-defs is the deferred real fix.
92
+ * As of #2201 this ceiling is an adversarial-only backstop that effectively
93
+ * never fires on real code: the production solver auto-selects the SSA-sparse
94
+ * path for the looping functions that would breach it, and the SSA path has no
95
+ * fixpoint iteration (it answers reaching queries from the def-use graph in one
96
+ * pass) so it computes the full facts where the dense worklist would have
97
+ * truncated. The budget is still consulted on the dense fallback path (small /
98
+ * loop-free functions, and throw-edge / unreachable-block functions the SSA path
99
+ * does not model). WTO / loop-aware iteration ordering was benchmarked and
100
+ * rejected (0% faster — the cost was dense-set propagation, not visitation
101
+ * order); SSA-sparse was the real fix. See reaching-defs.ts.
96
102
  */
97
103
  export declare const DEFAULT_PDG_MAX_REACHING_DEF_BLOCK_REVISITS = 64;
98
104
  export interface CfgEmitResult {
@@ -72,10 +72,16 @@ export const DEFAULT_PDG_MAX_REACHING_DEF_FACTS_PER_FUNCTION = REACHING_DEF_FACT
72
72
  * never fires). Truncation degrades to a sound empty REACHING_DEF for that one
73
73
  * function (status `truncated`), never wrong facts.
74
74
  *
75
- * This ceiling is the SOUND backstop, not a perf fix: WTO / loop-aware iteration
76
- * ordering was benchmarked and rejected (0% faster the cost is dense-set
77
- * propagation, not visitation order; see the no-go note in reaching-defs.ts at
78
- * the RPO-order site). SSA-sparse reaching-defs is the deferred real fix.
75
+ * As of #2201 this ceiling is an adversarial-only backstop that effectively
76
+ * never fires on real code: the production solver auto-selects the SSA-sparse
77
+ * path for the looping functions that would breach it, and the SSA path has no
78
+ * fixpoint iteration (it answers reaching queries from the def-use graph in one
79
+ * pass) so it computes the full facts where the dense worklist would have
80
+ * truncated. The budget is still consulted on the dense fallback path (small /
81
+ * loop-free functions, and throw-edge / unreachable-block functions the SSA path
82
+ * does not model). WTO / loop-aware iteration ordering was benchmarked and
83
+ * rejected (0% faster — the cost was dense-set propagation, not visitation
84
+ * order); SSA-sparse was the real fix. See reaching-defs.ts.
79
85
  */
80
86
  export const DEFAULT_PDG_MAX_REACHING_DEF_BLOCK_REVISITS = 64;
81
87
  /**
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Pure graph sub-stages for the reaching-definitions solvers (#2201 review R4).
3
+ *
4
+ * Extracted from reaching-defs.ts to keep that module focused on the
5
+ * orchestrator, the dense oracle, the statement sweep, and the dispatcher.
6
+ * Everything here is a pure function of plain arrays — no CFG, no harvest, no
7
+ * solver state — so this module has NO dependency on reaching-defs.ts (a strict
8
+ * one-way import) and each stage is independently testable. The SSA pipeline
9
+ * (dominators → dominance frontiers → Tarjan SCC → reach-set condensation)
10
+ * implements Cooper-Harvey-Kennedy + Cytron + Tarjan; reverse-post-order, the
11
+ * loop-reachability check, and the def-set/lattice primitives are shared with
12
+ * the dense GEN/KILL solver and the dispatcher.
13
+ *
14
+ * These are held byte-identical to their former inline form by the differential
15
+ * equivalence fuzz (test/unit/cfg/reaching-defs-equivalence.test.ts) — any diff
16
+ * after extraction is an extraction bug, never the oracle.
17
+ */
18
+ export {};
@@ -0,0 +1,312 @@
1
+ /**
2
+ * Pure graph sub-stages for the reaching-definitions solvers (#2201 review R4).
3
+ *
4
+ * Extracted from reaching-defs.ts to keep that module focused on the
5
+ * orchestrator, the dense oracle, the statement sweep, and the dispatcher.
6
+ * Everything here is a pure function of plain arrays — no CFG, no harvest, no
7
+ * solver state — so this module has NO dependency on reaching-defs.ts (a strict
8
+ * one-way import) and each stage is independently testable. The SSA pipeline
9
+ * (dominators → dominance frontiers → Tarjan SCC → reach-set condensation)
10
+ * implements Cooper-Harvey-Kennedy + Cytron + Tarjan; reverse-post-order, the
11
+ * loop-reachability check, and the def-set/lattice primitives are shared with
12
+ * the dense GEN/KILL solver and the dispatcher.
13
+ *
14
+ * These are held byte-identical to their former inline form by the differential
15
+ * equivalence fuzz (test/unit/cfg/reaching-defs-equivalence.test.ts) — any diff
16
+ * after extraction is an extraction bug, never the oracle.
17
+ */
18
+ /**
19
+ * RPO over blocks reachable from `entry`; unreachable blocks appended by index.
20
+ * Returns the order AND the reachability bitmap the DFS already computed, so a
21
+ * caller needing "is every block reachable?" reuses this pass instead of a
22
+ * separate BFS (#2201 review R8 — the SSA path's reachability gate).
23
+ *
24
+ * @internal
25
+ */
26
+ export function reversePostOrder(entry, succs, n) {
27
+ const visited = new Array(n).fill(false);
28
+ const post = [];
29
+ // Iterative DFS with an explicit phase stack (children pushed in reverse so
30
+ // they pop in sorted order — determinism).
31
+ const stack = [{ node: entry, childIdx: 0 }];
32
+ visited[entry] = true;
33
+ while (stack.length) {
34
+ const top = stack[stack.length - 1];
35
+ const children = succs[top.node];
36
+ if (top.childIdx < children.length) {
37
+ const next = children[top.childIdx];
38
+ top.childIdx += 1;
39
+ if (!visited[next]) {
40
+ visited[next] = true;
41
+ stack.push({ node: next, childIdx: 0 });
42
+ }
43
+ }
44
+ else {
45
+ post.push(top.node);
46
+ stack.pop();
47
+ }
48
+ }
49
+ const order = post.reverse();
50
+ for (let b = 0; b < n; b++)
51
+ if (!visited[b])
52
+ order.push(b);
53
+ return { order, visited };
54
+ }
55
+ /**
56
+ * Immediate dominators (Cooper-Harvey-Kennedy; correct on irreducible CFGs).
57
+ * `rpo` is the reverse-post-order rooted at the synthetic start `S`, `dPredsX`
58
+ * the dominator-graph predecessors (incl. S→entry). Returns idom[b] for every
59
+ * node in [0, nx); idom[S] === S.
60
+ *
61
+ * @internal
62
+ */
63
+ export function buildDominators(rpo, dPredsX, S, nx) {
64
+ const rpoIdx = new Array(nx);
65
+ rpo.forEach((b, i) => (rpoIdx[b] = i));
66
+ const idom = new Array(nx).fill(-1);
67
+ idom[S] = S;
68
+ const intersect = (a, b) => {
69
+ while (a !== b) {
70
+ while (rpoIdx[a] > rpoIdx[b])
71
+ a = idom[a];
72
+ while (rpoIdx[b] > rpoIdx[a])
73
+ b = idom[b];
74
+ }
75
+ return a;
76
+ };
77
+ for (let changed = true; changed;) {
78
+ changed = false;
79
+ for (const b of rpo) {
80
+ if (b === S)
81
+ continue;
82
+ let nd = -1;
83
+ for (const p of dPredsX[b])
84
+ if (idom[p] !== -1)
85
+ nd = nd === -1 ? p : intersect(nd, p);
86
+ if (nd !== -1 && idom[b] !== nd) {
87
+ idom[b] = nd;
88
+ changed = true;
89
+ }
90
+ }
91
+ }
92
+ return idom;
93
+ }
94
+ /**
95
+ * Dominance frontiers (Cytron). df[b] is the set of nodes where b's dominance
96
+ * ends — the φ-placement targets for any binding defined in b.
97
+ *
98
+ * @internal
99
+ */
100
+ export function buildDominanceFrontiers(dPredsX, idom, nx) {
101
+ const df = Array.from({ length: nx }, () => new Set());
102
+ for (let b = 0; b < nx; b++) {
103
+ const dp = dPredsX[b];
104
+ if (dp.length < 2)
105
+ continue;
106
+ for (const p of dp) {
107
+ let runner = p;
108
+ while (runner !== idom[b] && runner !== -1) {
109
+ df[runner].add(b);
110
+ runner = idom[runner];
111
+ }
112
+ }
113
+ }
114
+ return df;
115
+ }
116
+ /**
117
+ * Tarjan strongly-connected components over the value-graph operand edges
118
+ * (`nodeOps[node]` = operand node ids). Iterative (explicit work stack — the
119
+ * graph can be deep). SCCs are emitted in REVERSE topological order, so an
120
+ * SCC's operand SCCs are numbered before it — the property
121
+ * {@link condenseReachingSets} relies on for its single forward pass.
122
+ *
123
+ * @internal
124
+ */
125
+ export function tarjanScc(nodeOps) {
126
+ const N = nodeOps.length;
127
+ const sccOf = new Array(N).fill(-1);
128
+ const sccMembers = [];
129
+ const index = new Array(N).fill(-1);
130
+ const low = new Array(N).fill(0);
131
+ const onStk = new Array(N).fill(false);
132
+ const tarjanStk = [];
133
+ let counter = 0;
134
+ for (let start = 0; start < N; start++) {
135
+ if (index[start] !== -1)
136
+ continue;
137
+ const work = [{ node: start, oi: 0 }];
138
+ index[start] = low[start] = counter++;
139
+ tarjanStk.push(start);
140
+ onStk[start] = true;
141
+ while (work.length) {
142
+ const top = work[work.length - 1];
143
+ const ops = nodeOps[top.node];
144
+ if (top.oi < ops.length) {
145
+ const w = ops[top.oi++];
146
+ if (index[w] === -1) {
147
+ index[w] = low[w] = counter++;
148
+ tarjanStk.push(w);
149
+ onStk[w] = true;
150
+ work.push({ node: w, oi: 0 });
151
+ }
152
+ else if (onStk[w] && index[w] < low[top.node]) {
153
+ low[top.node] = index[w];
154
+ }
155
+ }
156
+ else {
157
+ if (low[top.node] === index[top.node]) {
158
+ const members = [];
159
+ let w;
160
+ do {
161
+ w = tarjanStk.pop();
162
+ onStk[w] = false;
163
+ sccOf[w] = sccMembers.length;
164
+ members.push(w);
165
+ } while (w !== top.node);
166
+ sccMembers.push(members);
167
+ }
168
+ work.pop();
169
+ if (work.length) {
170
+ const par = work[work.length - 1].node;
171
+ if (low[top.node] < low[par])
172
+ low[par] = low[top.node];
173
+ }
174
+ }
175
+ }
176
+ }
177
+ return { sccOf, sccMembers };
178
+ }
179
+ /**
180
+ * Reaching def-key set per SCC via condensation (cycle-safe union). Tarjan emits
181
+ * SCCs in reverse topological order, so a single forward pass over SCCs resolves
182
+ * every union: an SCC's reaching set is its members' own leaf keys plus the
183
+ * already-computed reaching sets of its cross-SCC operands.
184
+ *
185
+ * Alias fast path (#2201 review R2): an SCC with NO own leaf keys whose cross-SCC
186
+ * operands all resolve to a SINGLE source SCC has exactly that source's reaching
187
+ * set — share it BY REFERENCE instead of copying element-by-element (the O(defs²)
188
+ * cost at wide-fan-in φ merges). Safe: the returned sets are read-only after this
189
+ * pass, and contents are identical (set iteration order is irrelevant — the
190
+ * sweep sorts each use's keys before emission, KTD6).
191
+ *
192
+ * @internal
193
+ */
194
+ export function condenseReachingSets(sccMembers, sccOf, nodeKeys, nodeOps) {
195
+ const reachByScc = new Array(sccMembers.length);
196
+ for (let s = 0; s < sccMembers.length; s++) {
197
+ const members = sccMembers[s];
198
+ let aliasTarget = -1; // the unique cross-SCC source SCC, or -1 if none/many
199
+ let hasOwnKeys = false;
200
+ let multiSource = false;
201
+ for (const node of members) {
202
+ if (nodeKeys[node]) {
203
+ hasOwnKeys = true;
204
+ break;
205
+ }
206
+ for (const w of nodeOps[node]) {
207
+ const ws = sccOf[w];
208
+ if (ws === s)
209
+ continue; // intra-SCC operand: same set being built, adds nothing
210
+ if (aliasTarget === -1)
211
+ aliasTarget = ws;
212
+ else if (aliasTarget !== ws) {
213
+ multiSource = true;
214
+ break;
215
+ }
216
+ }
217
+ if (multiSource)
218
+ break;
219
+ }
220
+ if (!hasOwnKeys && !multiSource && aliasTarget !== -1) {
221
+ reachByScc[s] = reachByScc[aliasTarget]; // zero-copy share
222
+ continue;
223
+ }
224
+ // General case: union own leaf keys + every distinct cross-SCC operand set.
225
+ const set = new Set();
226
+ for (const node of members) {
227
+ const keys = nodeKeys[node];
228
+ if (keys)
229
+ for (const k of keys)
230
+ set.add(k);
231
+ for (const w of nodeOps[node]) {
232
+ const ws = sccOf[w];
233
+ if (ws !== s)
234
+ for (const k of reachByScc[ws])
235
+ set.add(k);
236
+ }
237
+ }
238
+ reachByScc[s] = set;
239
+ }
240
+ return reachByScc;
241
+ }
242
+ /**
243
+ * True iff a cycle is reachable from `entry` (the CFG has a loop). Iterative DFS
244
+ * with a gray/black coloring; a gray successor is a back-edge. O(V+E). Used by
245
+ * the production dispatcher to decide SSA-vs-dense.
246
+ *
247
+ * @internal
248
+ */
249
+ export function hasReachableLoop(entry, succs, n) {
250
+ const color = new Uint8Array(n); // 0 white, 1 gray, 2 black
251
+ const stack = [{ node: entry, i: 0 }];
252
+ color[entry] = 1;
253
+ while (stack.length) {
254
+ const top = stack[stack.length - 1];
255
+ const ss = succs[top.node];
256
+ if (top.i < ss.length) {
257
+ const next = ss[top.i++];
258
+ if (color[next] === 1)
259
+ return true;
260
+ if (color[next] === 0) {
261
+ color[next] = 1;
262
+ stack.push({ node: next, i: 0 });
263
+ }
264
+ }
265
+ else {
266
+ color[top.node] = 2;
267
+ stack.pop();
268
+ }
269
+ }
270
+ return false;
271
+ }
272
+ /**
273
+ * Order-stable union of two def-sets (shares `a` when `b` adds nothing).
274
+ *
275
+ * @internal
276
+ */
277
+ export function unionSets(a, b) {
278
+ let target = a;
279
+ let copied = false;
280
+ for (const key of b) {
281
+ if (!target.has(key)) {
282
+ if (!copied) {
283
+ target = new Set(a);
284
+ copied = true;
285
+ }
286
+ target.add(key);
287
+ }
288
+ }
289
+ return target;
290
+ }
291
+ /**
292
+ * Per-binding lattice equality with a reference fast path (sets only ever grow).
293
+ *
294
+ * @internal
295
+ */
296
+ export function latticeEquals(a, b) {
297
+ if (a === b)
298
+ return true;
299
+ if (a.size !== b.size)
300
+ return false;
301
+ for (const [k, bSet] of b) {
302
+ const aSet = a.get(k);
303
+ if (aSet === bSet)
304
+ continue;
305
+ if (!aSet || aSet.size !== bSet.size)
306
+ return false;
307
+ for (const v of bSet)
308
+ if (!aSet.has(v))
309
+ return false;
310
+ }
311
+ return true;
312
+ }
@@ -1,8 +1,27 @@
1
1
  /**
2
- * Reaching definitions (#2082 M2 U3) classic GEN/KILL monotone fixpoint over
3
- * one function's CFG, plus the canonical intra-block statement sweep that
4
- * recovers statement-granular def→use facts from M1's coalesced blocks
5
- * WITHOUT re-splitting the CFG.
2
+ * Reaching definitions (#2082 M2 U3, SSA-sparse rewrite #2201) per-function
3
+ * intraprocedural may-reaching-definitions, plus the canonical intra-block
4
+ * statement sweep that recovers statement-granular def→use facts from M1's
5
+ * coalesced blocks WITHOUT re-splitting the CFG.
6
+ *
7
+ * ARCHITECTURE (#2201): the analysis is split into solver-INDEPENDENT stages
8
+ * (shared by every path, so the byte-identical surface is maximal) and a
9
+ * swappable IN-set computation:
10
+ * - {@link harvestStatementFacts} — per-block GEN/allDefs + def/use telemetry.
11
+ * - {@link buildAdjacency} — throw-aware predecessor/successor adjacency.
12
+ * - the IN-set computer — answers block-entry reaching-set queries. Two
13
+ * implementations: {@link computeInSetsSparse} (SSA — CHK dominators →
14
+ * Cytron dominance frontiers + φ-placement → stack renaming over a
15
+ * synthetic entry, walked SCC-condensed) and {@link computeInSetsDense}
16
+ * (the original GEN/KILL worklist). Production runs {@link
17
+ * computeInSetsAuto}, which picks the SSA solver for looping functions large
18
+ * enough to amortize construction (where it is asymptotically faster and
19
+ * never hits the dense ceiling) and the dense worklist everywhere else; the
20
+ * dense path also serves the throw-edge / unreachable-block cases the SSA
21
+ * path does not model. The two are held byte-identical by the equivalence
22
+ * fuzz — only set CONTENTS must match (the sweep sorts each use's keys
23
+ * before the maxFacts cutoff, so iteration order is irrelevant).
24
+ * - {@link sweepFacts} — statement sweep + sort + maxFacts truncation.
6
25
  *
7
26
  * PURE AND DETERMINISTIC (load-bearing contract):
8
27
  * - Pure function of its inputs — no graph, no logger (warnings are the
@@ -14,17 +33,10 @@
14
33
  * insertion-ordered Maps/Sets throughout, and the output fact array is
15
34
  * explicitly sorted. Snapshot tests and content-derived edge ids rely on it.
16
35
  *
17
- * COMPLEXITY DISCIPLINE (the four-times-repeated repo bug shape is per-item
18
- * re-derivation inside the loop): def-sets are SHARED BY REFERENCE, never
19
- * deep-copied a MUST def's kill is total per binding, so a transfer either
20
- * aliases the incoming set or replaces it; a MAY def (conditional context —
21
- * see StatementFacts.mayDefs) unions WITHOUT killing via a copy-on-extend.
22
- * Single-predecessor blocks alias the predecessor's OUT map outright;
23
- * multi-pred merges union only bindings whose incoming sets differ by
24
- * reference. Iteration is reverse post-order, seeded with every block
25
- * (unreachable blocks keep ⊥ IN — correct, their defs reach nothing).
26
- * Convergence: sets grow monotonically within the finite def-site universe ⇒
27
- * ≤ loop-depth+1 passes in practice.
36
+ * COMPLEXITY DISCIPLINE: def-sets are SHARED BY REFERENCE, never deep-copied —
37
+ * a MUST def's kill is total per binding, so a transfer either aliases the
38
+ * incoming set or replaces it; a MAY def (conditional context see
39
+ * StatementFacts.mayDefs) unions WITHOUT killing via a copy-on-extend.
28
40
  *
29
41
  * `limits.maxFacts` bounds materialization: facts are O(defs×uses) BY SPEC in
30
42
  * merge-heavy code (N branch-arm defs × N later uses = N² facts), and a
@@ -62,18 +74,42 @@ export interface ReachingDefsLimits {
62
74
  */
63
75
  readonly maxFacts?: number;
64
76
  /**
65
- * Maximum total block dequeues in the dataflow fixpoint. Iterative
66
- * reaching-defs on a reducible CFG converges in O(loop-nesting-depth) passes,
67
- * so a worklist visits each block a small multiple of times for real code; a
68
- * pathologically deep loop nest (machine-generated / obfuscated) drives the
69
- * pass count and thus the visit total — to O(blocks²) and the solver to
70
- * seconds + GB of heap (`maxFacts` does not help: fact count stays linear).
71
- * When the visit total exceeds this budget the fixpoint has NOT converged, so
72
- * any facts would be unsound — the solver bails to a sound empty
73
- * `status: 'truncated'` (like the `overflow` guard). `undefined`/0 ⇒ unlimited
74
- * (the default for direct callers; the emit path sets a per-function budget).
77
+ * Adversarial-only safety bound on the DENSE worklist's iteration.
78
+ *
79
+ * The dense GEN/KILL solver reads this as a ceiling on total block dequeues:
80
+ * iterative reaching-defs on a reducible CFG converges in O(loop-nesting-depth)
81
+ * passes, but a pathologically deep loop nest drives the visit total — and thus
82
+ * the solver — to O(blocks²), seconds + GB of heap (`maxFacts` does not help:
83
+ * fact count stays linear). Exceeding the budget means the fixpoint has NOT
84
+ * converged, so any facts would be unsound — the dense solver bails to a sound
85
+ * empty `status: 'truncated'` (like the `overflow` guard).
86
+ *
87
+ * The SSA solver (#2201) has NO fixpoint iteration — it answers reaching
88
+ * queries from the def-use graph in one pass — so it always converges and this
89
+ * budget never trips it. The production dispatcher ({@link computeInSetsAuto})
90
+ * routes the deep nests that would breach the dense ceiling to the SSA solver,
91
+ * which computes their full facts: the ceiling that fired on the dense worklist
92
+ * effectively never fires on real code (#2201 acceptance). The budget is still
93
+ * honored on the dense fallback path (small / loop-free functions, and the
94
+ * throw-edge / unreachable-block cases the SSA path does not model).
95
+ *
96
+ * `undefined`/0 ⇒ unlimited (the default for direct callers; the emit path sets
97
+ * a per-function budget).
75
98
  */
76
99
  readonly maxBlockVisits?: number;
100
+ /**
101
+ * Memory bound on the SSA-sparse solver's value-graph construction (#2201
102
+ * review R1). `maxFacts` bounds fact MATERIALIZATION (sweepFacts) but nothing
103
+ * bounds the φ/value-graph the sparse path builds first; a high-binding-density
104
+ * deep loop routed to SSA (≥ SSA_MIN_BLOCKS blocks + a reachable loop) builds an
105
+ * O(blocks×bindings) graph the dense path would have truncated at the
106
+ * `maxBlockVisits` ceiling (~1.5 GB measured on a 3000-block × 300-binding
107
+ * function). When the projected node count would exceed this, the sparse solver
108
+ * falls back to the dense oracle (byte-identical, and bounded — dense honors
109
+ * `maxBlockVisits`). Honored ONLY by the sparse path; the dense solver ignores
110
+ * it. `undefined`/0 ⇒ {@link DEFAULT_MAX_SSA_VALUE_GRAPH_NODES}.
111
+ */
112
+ readonly maxSsaValueGraphNodes?: number;
77
113
  }
78
114
  export interface FunctionDefUse {
79
115
  /**
@@ -99,5 +135,14 @@ export interface FunctionDefUse {
99
135
  /**
100
136
  * Compute reaching definitions for one function. See the module doc for the
101
137
  * purity/determinism/sharing contract.
138
+ *
139
+ * This is the production entry point. As of #2201 it auto-dispatches via
140
+ * {@link computeInSetsAuto} — the SSA-sparse solver ({@link computeInSetsSparse})
141
+ * for looping functions large enough to amortize construction, the dense
142
+ * GEN/KILL worklist ({@link computeInSetsDense}) everywhere else (and for the
143
+ * throw-edge / unreachable-block functions the SSA path does not model). The two
144
+ * solvers are held byte-identical by the equivalence fuzz (status, bindings,
145
+ * sorted facts, def/use telemetry), so the dispatch is a pure performance
146
+ * heuristic; the dense solver doubles as that differential oracle.
102
147
  */
103
148
  export declare function computeReachingDefs(cfg: FunctionCfg, limits?: ReachingDefsLimits): FunctionDefUse;