gitnexus 1.6.8-rc.37 → 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.
- package/dist/core/embeddings/hf-env.d.ts +0 -66
- package/dist/core/ingestion/cfg/control-flow-context.d.ts +9 -0
- package/dist/core/ingestion/cfg/control-flow-context.js +11 -0
- package/dist/core/ingestion/cfg/emit.d.ts +10 -4
- package/dist/core/ingestion/cfg/emit.js +10 -4
- package/dist/core/ingestion/cfg/reaching-defs-graph.d.ts +18 -0
- package/dist/core/ingestion/cfg/reaching-defs-graph.js +312 -0
- package/dist/core/ingestion/cfg/reaching-defs.d.ts +70 -25
- package/dist/core/ingestion/cfg/reaching-defs.js +519 -150
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.d.ts +15 -1
- package/dist/core/ingestion/cfg/visitors/csharp-harvest.js +29 -1
- package/dist/core/ingestion/cfg/visitors/csharp.d.ts +6 -0
- package/dist/core/ingestion/cfg/visitors/csharp.js +161 -1
- package/dist/core/ingestion/cfg/visitors/dart-harvest.d.ts +8 -0
- package/dist/core/ingestion/cfg/visitors/dart-harvest.js +26 -0
- package/dist/core/ingestion/cfg/visitors/dart.d.ts +7 -4
- package/dist/core/ingestion/cfg/visitors/dart.js +148 -1
- package/dist/core/ingestion/cfg/visitors/java-harvest.d.ts +8 -0
- package/dist/core/ingestion/cfg/visitors/java-harvest.js +19 -0
- package/dist/core/ingestion/cfg/visitors/java.d.ts +6 -5
- package/dist/core/ingestion/cfg/visitors/java.js +106 -10
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.d.ts +9 -0
- package/dist/core/ingestion/cfg/visitors/kotlin-harvest.js +20 -0
- package/dist/core/ingestion/cfg/visitors/kotlin.d.ts +8 -6
- package/dist/core/ingestion/cfg/visitors/kotlin.js +58 -9
- package/dist/core/ingestion/cfg/visitors/php-harvest.d.ts +8 -0
- package/dist/core/ingestion/cfg/visitors/php-harvest.js +20 -0
- package/dist/core/ingestion/cfg/visitors/php.d.ts +8 -6
- package/dist/core/ingestion/cfg/visitors/php.js +110 -1
- package/dist/core/ingestion/cfg/visitors/swift-harvest.d.ts +8 -0
- package/dist/core/ingestion/cfg/visitors/swift-harvest.js +18 -0
- package/dist/core/ingestion/cfg/visitors/swift.d.ts +6 -0
- package/dist/core/ingestion/cfg/visitors/swift.js +66 -0
- package/dist/core/ingestion/pipeline-phases/parse-impl.d.ts +0 -21
- package/dist/core/ingestion/pipeline.d.ts +0 -10
- package/dist/core/ingestion/workers/worker-pool.d.ts +0 -40
- package/dist/core/logger.d.ts +0 -5
- package/dist/core/run-analyze.js +8 -0
- package/dist/core/tree-sitter/safe-parse.d.ts +0 -5
- package/dist/mcp/local/local-backend.d.ts +0 -24
- package/dist/storage/repo-manager.d.ts +13 -0
- package/package.json +1 -1
|
@@ -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;
|
|
@@ -62,6 +62,15 @@ export declare class ControlFlowContext {
|
|
|
62
62
|
resolveBreak(label?: string): JumpResolution | undefined;
|
|
63
63
|
/** Resolve a `continue`: like {@link resolveBreak} but only loop frames match. */
|
|
64
64
|
resolveContinue(label?: string): JumpResolution | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* Resolve a Java `yield e` (switch-EXPRESSION arm exit): the nearest enclosing
|
|
67
|
+
* SWITCH frame's exit, threading the finalizers stacked above it. Unlike a
|
|
68
|
+
* `break`, a `yield` ALWAYS targets the switch — never an intervening loop — so
|
|
69
|
+
* it cannot match a loop frame (a `yield` inside a loop inside a switch arm
|
|
70
|
+
* still exits the whole switch). Returns `undefined` when there is no enclosing
|
|
71
|
+
* switch (malformed input); the caller falls back to its conservative routing.
|
|
72
|
+
*/
|
|
73
|
+
resolveYield(): JumpResolution | undefined;
|
|
65
74
|
/** Every active finalizer, innermost first — what a `return` must cross. */
|
|
66
75
|
finalizersForReturn(): readonly FinalizerFrame[];
|
|
67
76
|
/**
|
|
@@ -39,6 +39,17 @@ export class ControlFlowContext {
|
|
|
39
39
|
resolveContinue(label) {
|
|
40
40
|
return this.resolve((f) => f.kind === 'loop' && (label === undefined || f.labels.includes(label)), (f) => f.continueTo);
|
|
41
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a Java `yield e` (switch-EXPRESSION arm exit): the nearest enclosing
|
|
44
|
+
* SWITCH frame's exit, threading the finalizers stacked above it. Unlike a
|
|
45
|
+
* `break`, a `yield` ALWAYS targets the switch — never an intervening loop — so
|
|
46
|
+
* it cannot match a loop frame (a `yield` inside a loop inside a switch arm
|
|
47
|
+
* still exits the whole switch). Returns `undefined` when there is no enclosing
|
|
48
|
+
* switch (malformed input); the caller falls back to its conservative routing.
|
|
49
|
+
*/
|
|
50
|
+
resolveYield() {
|
|
51
|
+
return this.resolve((f) => f.kind === 'switch');
|
|
52
|
+
}
|
|
42
53
|
/** Every active finalizer, innermost first — what a `return` must cross. */
|
|
43
54
|
finalizersForReturn() {
|
|
44
55
|
const fins = [];
|
|
@@ -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
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
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
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
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
|
|
3
|
-
*
|
|
4
|
-
* recovers statement-granular def→use facts from M1's
|
|
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
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* seconds + GB of heap (`maxFacts` does not help:
|
|
71
|
-
*
|
|
72
|
-
* any facts would be unsound — the solver bails to a sound
|
|
73
|
-
* `status: 'truncated'` (like the `overflow` guard).
|
|
74
|
-
*
|
|
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;
|