perf(javascript): keep one member-effect record per stored fact (#8103)

Member-mutation indexing appended a binding write or escape record for
every traversal state that reached it. States that differ only in
coverage fields the record never stores (origin, owner initializer,
getter widening) produced identical records, and each later receiver
query rescanned all of them. Record each stored fact once, alongside the
existing required-module effect deduplication. Propagation is unchanged.

Effect ordering also re-walked the same top-level statement ancestry for
every candidate record. Cache it with the other initializer positions,
and test the selected path before the costlier receiver comparison.

On the #7774 reproducer the semantic IR and the complete
analyze-javascript-application Evidence are byte-identical, while the
CLI run drops from 146 s to 40 s.

Refs #7774

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kaoru0822andClaude Opus 5.5 authored and GitHub committed 2026-10-30 02:26:01 -07:00
1 parent 6b68fb642a
commit a47dcca010
4 files changed
+184 -38

No files matched your search

@@ -271,3 +271,77 @@ it("preserves exact member writes through an externally supplied caught value",
}),
]);
});
const indexedMemberEffects = (source: string) => {
const parsed = parseJavaScriptSource(source);
if (parsed === null) throw new Error("Expected parsed source");
const index = completeSemanticSteps(
indexJavaScriptBindingsWithReceiversSteps(parsed.program),
);
completeSemanticSteps(index.indexMemberMutationFactsSteps());
return [...index.bindings.bindingsById.values()];
};
it("records a write reached through two alias routes once", () => {
// `c.y = 1` reaches `a.x` through `b` and through the later `c = a.x`.
const a = indexedMemberEffects(`
function f(a) {
var b = a.x;
var c = b;
c.y = 1;
c = a.x;
c.y = 2;
}
`).find(({ name }) => name === "a");
expect(
a?.memberWrites.map(({ path, node, receiverIdentity }) => [
path,
t.isAssignmentExpression(node) && t.isNumericLiteral(node.right)
? node.right.value
: undefined,
receiverIdentity,
]),
).toEqual([
[["x", "y"], 1, "possible"],
[["x", "y"], 2, "definite"],
]);
});
it("keeps one record per stored fact for loop-carried register aliases", () => {
const identities = new WeakMap<object, number>();
let identityCount = 0;
const identity = (value: object | undefined) => {
if (value === undefined) return undefined;
if (!identities.has(value)) identities.set(value, identityCount++);
return identities.get(value);
};
const bindings = indexedMemberEffects(`
function dispatch(a) {
var b, c;
for (b = a.target; b; ) {
c = b;
c.seen = 1;
if ((c = b.owner)) { b = c; continue; }
b = b.parentNode;
}
}
`);
const records = bindings.flatMap(({ memberWrites, escapedPaths }) => [
memberWrites,
escapedPaths,
]);
expect(records.some((effects) => effects.length > 1)).toBe(true);
for (const effects of records) {
const facts = effects.map((effect) =>
JSON.stringify([
effect.path,
identity(effect.node),
identity(effect.receiverAt),
effect.receiverIdentity,
effect.receiverInitializers?.map((source) => identity(source)),
effect.assignedSources?.map((source) => identity(source)),
]),
);
expect(new Set(facts).size).toBe(facts.length);
}
});