perf(javascript): index member-effect paths for projected reads (#8108)

* perf(javascript): index member-effect paths for projected reads

Each projected read scanned every member-write record of its binding and
tested the record path against the selection. On the #7774 reproducer
only 3.7% of scanned records pass that test. Index each binding's
append-only records by path in a lazily built trie that extends itself
with newly appended records, and visit only records whose exact keys a
selected choice names or whose keys are wildcards. Candidates come back
in record order and still pass the exact predicate, now owned next to
the index as semanticEffectPathMatches.

The semantic IR and the complete analyze-javascript-application Evidence
are byte-identical. On the full reproducer each worker phase drops from
about 80 s to 29 s, and to 8 s together with #8103.

Refs #7774

Co-Authored-By: Claude Opus 5.5 <[email protected]>

* perf(javascript): stop escape checks at the first indexed match

The escape check only asks whether any matching escape is unsuperseded,
so it does not need ordered candidates. Share one trie traversal between
the ordered write candidates and an early-exit visitor for this check.

Co-Authored-By: Claude Opus 5.5 <[email protected]>

---------

Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-30 17:52:46 +08:00
1 parent 6bb831df60
commit 767f6afc18
3 files changed
+268 -20

No files matched your search

@@ -0,0 +1,114 @@
import { expect, it } from "vitest";
import type { SemanticAliasSelection } from "./javascriptSemanticAliasSelections.js";
import {
semanticEffectPathCandidates,
semanticEffectPathMatches,
semanticSomeEffectPathCandidate,
} from "./javascriptSemanticEffectPathIndex.js";
import type { JavaScriptSemanticPropertyPath } from "./javascriptSemanticPropertyPaths.js";
import type { JavaScriptSemanticMemberEffect } from "./javascriptSemanticState.js";
const effectKeys: JavaScriptSemanticPropertyPath = [
"a",
"b",
"c",
0,
"0",
1,
null,
{ excludedKeys: ["a"] },
{ excludedKeys: [], startIndex: 1 },
];
const selections: readonly SemanticAliasSelection[] = [
"a",
"b",
"d",
0,
"1",
null,
{ excludedKeys: ["b"] },
{ alternatives: ["a", "c"] },
{ alternatives: [0, "0", "b"] },
{ alternatives: ["a", null] },
];
it("selects exactly the linearly matching effects, in record order", () => {
let seed = 7774;
const next = (bound: number): number => {
seed = (seed * 1_103_515_245 + 12_345) % 2_147_483_648;
return Math.floor((seed / 2_147_483_648) * bound);
};
const effects: JavaScriptSemanticMemberEffect[] = [];
let compared = 0;
for (let round = 0; round < 400; round++) {
// Records are append-only; the index must extend across queries.
for (let added = next(4); added > 0; added--)
effects.push({
path: Array.from({ length: next(5) }, () => effectKeys[next(9)]!),
node: undefined,
});
const path = Array.from({ length: next(5) }, () => selections[next(10)]!);
const expected = effects.filter((effect) =>
semanticEffectPathMatches(effect.path, path),
);
const candidates = semanticEffectPathCandidates(effects, path);
expect(
candidates.filter((effect) =>
semanticEffectPathMatches(effect.path, path),
),
).toEqual(expected);
expect(
candidates.every(
(effect, index) =>
index === 0 ||
effects.indexOf(candidates[index - 1]!) < effects.indexOf(effect),
),
).toBe(true);
const evenDepth = (effect: JavaScriptSemanticMemberEffect) =>
semanticEffectPathMatches(effect.path, path) &&
effect.path.length % 2 === 0;
expect(semanticSomeEffectPathCandidate(effects, path, evenDepth)).toBe(
effects.some(evenDepth),
);
compared += expected.length;
}
expect(effects.length).toBeGreaterThan(400);
expect(compared).toBeGreaterThan(0);
});
it("skips effects whose exact keys no selected choice names", () => {
const effects: JavaScriptSemanticMemberEffect[] = [
{ path: ["a", "x"], node: undefined },
{ path: ["b"], node: undefined },
{ path: [null, "x"], node: undefined },
{ path: ["a", "x", "y"], node: undefined },
{ path: [], node: undefined },
];
expect(
semanticEffectPathCandidates(effects, ["a", "x"]).map(({ path }) => path),
).toEqual([["a", "x"], [null, "x"], []]);
expect(
semanticEffectPathCandidates(effects, [{ alternatives: ["b", "c"] }]).map(
({ path }) => path,
),
).toEqual([["b"], []]);
});
it("stops visiting candidates at the first satisfied predicate", () => {
const effects: JavaScriptSemanticMemberEffect[] = Array.from(
{ length: 100 },
() => ({ path: ["a"], node: undefined }),
);
let visited = 0;
expect(
semanticSomeEffectPathCandidate(effects, ["a"], () => {
visited += 1;
return true;
}),
).toBe(true);
expect(visited).toBe(1);
expect(semanticSomeEffectPathCandidate(effects, ["b"], () => true)).toBe(
false,
);
});
@@ -0,0 +1,140 @@
import type { JavaScriptSemanticMemberEffect } from "./javascriptSemanticState.js";
import {
semanticAliasSelectionChoices,
type SemanticAliasSelectionPath,
} from "./javascriptSemanticAliasSelections.js";
import {
semanticPropertyPathKeyMatches,
type JavaScriptSemanticPropertyPath,
} from "./javascriptSemanticPropertyPaths.js";
/** Whether an effect recorded at one member path can reach a projected read. */
export const semanticEffectPathMatches = (
effectPath: JavaScriptSemanticPropertyPath,
path: SemanticAliasSelectionPath,
): boolean =>
path.length > 0 &&
effectPath.length <= path.length &&
effectPath.every((key, index) => {
const selected = path[index];
return (
selected !== undefined &&
semanticAliasSelectionChoices(selected).some(
(choice) =>
choice === null ||
typeof choice === "object" ||
semanticPropertyPathKeyMatches(key, String(choice)),
)
);
});
// Most nodes are leaves of one path. Materialize each collection only when it
// holds a record or child.
interface EffectPathNode {
/** Record positions whose recorded path ends at this depth. */
ends?: number[];
/** Children for exact keys, by their string property name. */
exact?: Map<string, EffectPathNode>;
/** Child for a wildcard or copied-slot key, which can match any name. */
open?: EffectPathNode;
}
interface EffectPathIndex {
readonly root: EffectPathNode;
indexed: number;
}
// Binding member effects are append-only, so each index extends itself with
// records appended since its previous query and lives as long as its records.
const effectPathIndexes = new WeakMap<
readonly JavaScriptSemanticMemberEffect[],
EffectPathIndex
>();
const currentEffectPathIndex = (
effects: readonly JavaScriptSemanticMemberEffect[],
): EffectPathIndex => {
let index = effectPathIndexes.get(effects);
if (index === undefined) {
index = { root: {}, indexed: 0 };
effectPathIndexes.set(effects, index);
}
for (; index.indexed < effects.length; index.indexed++) {
let node = index.root;
for (const key of effects[index.indexed]!.path) {
if (key === null || typeof key === "object") node = node.open ??= {};
else {
const exact = (node.exact ??= new Map());
const name = String(key);
let child = exact.get(name);
if (child === undefined) {
child = {};
exact.set(name, child);
}
node = child;
}
}
(node.ends ??= []).push(index.indexed);
}
return index;
};
// Visit record positions whose recorded path may match a projected read, in
// index order. Only records longer than the read, or with an exact key that no
// selected choice names, are skipped; wildcard and copied-slot keys remain.
// Returns true as soon as `visit` does.
const visitEffectPathCandidates = (
effects: readonly JavaScriptSemanticMemberEffect[],
path: SemanticAliasSelectionPath,
visit: (position: number) => boolean,
): boolean => {
const pending = [{ node: currentEffectPathIndex(effects).root, depth: 0 }];
while (pending.length > 0) {
const { node, depth } = pending.pop()!;
for (const position of node.ends ?? []) if (visit(position)) return true;
if (depth >= path.length) continue;
if (node.open !== undefined)
pending.push({ node: node.open, depth: depth + 1 });
const exact = node.exact;
if (exact === undefined) continue;
const choices = semanticAliasSelectionChoices(path[depth]!);
if (choices.some((choice) => choice === null || typeof choice === "object"))
for (const child of exact.values())
pending.push({ node: child, depth: depth + 1 });
else
for (const name of new Set(choices.map((choice) => String(choice)))) {
const child = exact.get(name);
if (child !== undefined)
pending.push({ node: child, depth: depth + 1 });
}
}
return false;
};
/**
* Effects whose recorded path may match a projected read, in record order.
* Candidates are a superset, so callers still apply `semanticEffectPathMatches`.
*/
export const semanticEffectPathCandidates = (
effects: readonly JavaScriptSemanticMemberEffect[],
path: SemanticAliasSelectionPath,
): readonly JavaScriptSemanticMemberEffect[] => {
const positions: number[] = [];
visitEffectPathCandidates(effects, path, (position) => {
positions.push(position);
return false;
});
return positions
.sort((left, right) => left - right)
.map((position) => effects[position]!);
};
/** Whether any candidate effect for a projected read satisfies `predicate`. */
export const semanticSomeEffectPathCandidate = (
effects: readonly JavaScriptSemanticMemberEffect[],
path: SemanticAliasSelectionPath,
predicate: (effect: JavaScriptSemanticMemberEffect) => boolean,
): boolean =>
visitEffectPathCandidates(effects, path, (position) =>
predicate(effects[position]!),
);
@@ -3,10 +3,7 @@ import type {
JavaScriptSemanticAnalysisState,
JavaScriptSemanticBindingState,
} from "./javascriptSemanticState.js";
import {
semanticPropertyPathKeyMatches,
type JavaScriptSemanticPropertyPath,
} from "./javascriptSemanticPropertyPaths.js";
import type { JavaScriptSemanticPropertyPath } from "./javascriptSemanticPropertyPaths.js";
import { unwrapJavaScriptExpression } from "./javascriptAstValues.js";
import {
semanticObjectPropertyReferences,
@@ -26,6 +23,11 @@ import {
semanticReferenceSourcesInactive,
} from "./javascriptSemanticReferenceSources.js";
import { semanticStoredSourceSelection } from "./javascriptSemanticIterationSources.js";
import {
semanticEffectPathCandidates,
semanticEffectPathMatches,
semanticSomeEffectPathCandidate,
} from "./javascriptSemanticEffectPathIndex.js";
import {
semanticAliasSelectionChoices,
type SemanticAliasSelectionPath,
@@ -301,20 +303,7 @@ const projectedSourceFactsAtRead = (
const matches = (
effect: JavaScriptSemanticBindingState["memberWrites"][number],
) =>
path.length > 0 &&
effect.path.length <= path.length &&
effect.path.every((key, index) => {
const selected = path[index];
return (
selected !== undefined &&
semanticAliasSelectionChoices(selected).some(
(choice) =>
choice === null ||
typeof choice === "object" ||
semanticPropertyPathKeyMatches(key, String(choice)),
)
);
}) &&
semanticEffectPathMatches(effect.path, path) &&
(effect.receiverInitializers === undefined ||
effect.receiverInitializers.length === 0 ||
captured.length === 0 ||
@@ -329,7 +318,10 @@ const projectedSourceFactsAtRead = (
semanticEffectTiming(selectedAt(effect.path.length), effect.node, state) !==
"after" &&
!targetsReplacedPrefix(effect);
const writes = binding.memberWrites.filter(matches);
const writes = semanticEffectPathCandidates(
binding.memberWrites,
path,
).filter(matches);
let replacement:
| JavaScriptSemanticBindingState["memberWrites"][number]
| undefined;
@@ -374,7 +366,9 @@ const projectedSourceFactsAtRead = (
(replacement.assignedSources?.[0]?.target?.start ?? -1)));
if (
includeUnknown &&
binding.escapedPaths.some(
semanticSomeEffectPathCandidate(
binding.escapedPaths,
path,
(effect) => matches(effect) && !superseded(effect),
)
)