perf(javascript): reduce retained semantic analysis allocations (#1601)

Reduce retained semantic allocations and memoize lexical scope ancestry while preserving semantic identities. Refs #1065 and #1563; the complete-directory memory failure remains open.
This commit is contained in:
Ysopwnable
2026-10-11 03:20:05 +08:00
committed by GitHub
parent f4e437e544
commit b28c0337fc
7 changed files with 125 additions and 44 deletions
@@ -60,6 +60,7 @@ export interface JavaScriptSemanticAnalysisState {
readonly parentsByNode: WeakMap<t.Node, t.Node>;
readonly scopes: JavaScriptSemanticScopeState[];
readonly scopesById: Map<string, JavaScriptSemanticScopeState>;
/** Scope boundaries and descendant scopes resolved on demand. */
readonly scopeByNode: WeakMap<t.Node, JavaScriptSemanticScopeState>;
readonly bindingsById: Map<string, JavaScriptSemanticBindingState>;
readonly callables: JavaScriptSemanticCallable[];
@@ -94,7 +95,7 @@ export const resolveSemanticBindingState = (
node: t.Node,
name: string,
): JavaScriptSemanticBindingState | undefined => {
let scope = state.scopeByNode.get(node);
let scope = semanticScopeForNode(state, node);
while (scope !== undefined) {
const binding = scope.bindings.get(name);
if (binding !== undefined) return binding;
@@ -113,7 +114,7 @@ export const semanticResolutionBlocked = (
node: t.Node,
name: string,
): boolean => {
let scope = state.scopeByNode.get(node);
let scope = semanticScopeForNode(state, node);
while (scope !== undefined) {
if (scope.bindings.has(name)) return false;
if (!scope.bindingsComplete) return true;
@@ -125,6 +126,28 @@ export const semanticResolutionBlocked = (
return false;
};
const semanticScopeForNode = (
state: JavaScriptSemanticAnalysisState,
node: t.Node,
): JavaScriptSemanticScopeState | undefined => {
let candidate: t.Node | undefined = node;
while (candidate !== undefined) {
const scope = state.scopeByNode.get(candidate);
if (scope !== undefined) {
// Repeated references in a deep expression share this ancestry. Cache
// the traversed path without eagerly indexing nodes never resolved.
let descendant: t.Node | undefined = node;
while (descendant !== undefined && descendant !== candidate) {
state.scopeByNode.set(descendant, scope);
descendant = state.parentsByNode.get(descendant);
}
return scope;
}
candidate = state.parentsByNode.get(candidate);
}
return undefined;
};
/** Resolve one name starting from an explicitly selected lexical scope. */
export const resolveSemanticBindingFromScope = (
scope: JavaScriptSemanticScopeState,