From c7530f9a816d84354516d3ea137f4a0d91d3b8b3 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:33:35 +0800 Subject: [PATCH] perf(javascript): reduce analysis and source-map work (#955) * perf(javascript): reduce semantic traversal overhead * perf(source-maps): reuse validated regular map rows --- ...avascriptSemanticAnalysis.dataflow.test.ts | 25 +++++++++++++++++++ .../javascript/javascriptSemanticCalls.ts | 4 +-- .../javascriptSemanticTraversal.test.ts | 13 ++++++++++ .../javascript/javascriptSemanticTraversal.ts | 20 ++++++++++----- .../sourceMaps/TraceSourceMap.test.ts | 3 +++ src/javascript/sourceMaps/TraceSourceMap.ts | 4 ++- 6 files changed, 59 insertions(+), 10 deletions(-) diff --git a/src/domain/javascript/javascriptSemanticAnalysis.dataflow.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.dataflow.test.ts index 384b8fe671..ba8351c450 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.dataflow.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.dataflow.test.ts @@ -149,6 +149,31 @@ describe("JavaScript semantic analysis: dataflow 1", () => { }); }); +describe("JavaScript semantic call result flows", () => { + it("links only directly assigned scalar call results", () => { + const ir = analyzeJavaScriptSemantics(` + function run() { + const direct = produce(); + const { field } = unpack(); + const nested = outer(inner()); + return direct; + } + `); + const bindingNames = new Map( + ir.bindings.map(({ bindingId, name }) => [bindingId, name]), + ); + + expect( + ir.callResultFlows.map(({ bindingId }) => bindingNames.get(bindingId)), + ).toEqual(["direct", "nested"]); + expect(ir.callSites).toHaveLength(4); + expect(ir.callResultFlows.map(({ callSiteId }) => callSiteId)).toEqual([ + ir.callSites[0]?.callSiteId, + ir.callSites[2]?.callSiteId, + ]); + }); +}); + describe("JavaScript semantic analysis: read-modify-write", () => { it.each(["+= 1", "++", "--", "||= 1", "&&= 1", "??= 1"])( "retains the property read in a read-modify-write operation: %s", diff --git a/src/domain/javascript/javascriptSemanticCalls.ts b/src/domain/javascript/javascriptSemanticCalls.ts index e3e866286c..ca775e478a 100644 --- a/src/domain/javascript/javascriptSemanticCalls.ts +++ b/src/domain/javascript/javascriptSemanticCalls.ts @@ -183,9 +183,7 @@ const collectCallResultFlow = ( context.ancestors, context.state, ).filter(({ projectionPath }) => projectionPath.length === 0)) { - const binding = [...context.state.bindingsById.values()].find( - ({ bindingId }) => bindingId === assigned.bindingId, - ); + const binding = context.state.bindingsById.get(assigned.bindingId); if (binding === undefined) continue; const identifierRange = range(assigned.identifier); const definition = binding.definitions.find( diff --git a/src/domain/javascript/javascriptSemanticTraversal.test.ts b/src/domain/javascript/javascriptSemanticTraversal.test.ts index 1d012c1df2..482afe2b94 100644 --- a/src/domain/javascript/javascriptSemanticTraversal.test.ts +++ b/src/domain/javascript/javascriptSemanticTraversal.test.ts @@ -51,3 +51,16 @@ it("preserves source-tree enter/exit order and detached ancestor snapshots", () ], ]); }); + +it("skips sparse child slots without disturbing sibling visit order", () => { + const parsed = parseJavaScriptSource("const values = [first, , second];"); + if (parsed === null) + throw new TypeError("Expected a parsed JavaScript fixture"); + const identifiers: string[] = []; + traverseJavaScriptAst(parsed.program, { + enter: (node) => { + if (t.isIdentifier(node)) identifiers.push(node.name); + }, + }); + expect(identifiers).toEqual(["values", "first", "second"]); +}); diff --git a/src/domain/javascript/javascriptSemanticTraversal.ts b/src/domain/javascript/javascriptSemanticTraversal.ts index d3a99f2707..5eb311ac20 100644 --- a/src/domain/javascript/javascriptSemanticTraversal.ts +++ b/src/domain/javascript/javascriptSemanticTraversal.ts @@ -51,11 +51,19 @@ interface TraversalFrame { } const childNodes = (node: t.Node): t.Node[] => { - const keys: readonly string[] = t.VISITOR_KEYS[node.type] ?? []; - return keys.flatMap((key) => { + const keys = t.VISITOR_KEYS[node.type]; + if (keys === undefined) return []; + const children: t.Node[] = []; + for (const key of keys) { const value: unknown = Reflect.get(node, key); - if (t.isNode(value)) return [value]; - if (!Array.isArray(value)) return []; - return value.filter((item): item is t.Node => t.isNode(item)); - }); + if (t.isNode(value)) { + children.push(value); + continue; + } + if (!Array.isArray(value)) continue; + for (const item of value) { + if (t.isNode(item)) children.push(item); + } + } + return children; }; diff --git a/src/javascript/sourceMaps/TraceSourceMap.test.ts b/src/javascript/sourceMaps/TraceSourceMap.test.ts index 8518c87787..81d9ba76a3 100644 --- a/src/javascript/sourceMaps/TraceSourceMap.test.ts +++ b/src/javascript/sourceMaps/TraceSourceMap.test.ts @@ -151,6 +151,9 @@ describe("source-map layout admission", () => { expect(() => trace({ ...leaf, mappings })).toThrow(); }, ); + it("validates regular-map source indexes after reusing its decoded rows", () => { + expect(() => trace({ ...leaf, mappings: "ACAA" })).toThrow(/index/u); + }); it("rejects huge indexed offsets before upstream row allocation", () => { expect(() => trace({ diff --git a/src/javascript/sourceMaps/TraceSourceMap.ts b/src/javascript/sourceMaps/TraceSourceMap.ts index c48b501068..2fb5ef5599 100644 --- a/src/javascript/sourceMaps/TraceSourceMap.ts +++ b/src/javascript/sourceMaps/TraceSourceMap.ts @@ -81,10 +81,12 @@ const decodeValidatedMap = ( inspected: ReturnType, url: string, ) => { + let regularRows: ReturnType | undefined; for (const leaf of inspected.leaves) { const decoded = decodedMappings( new TraceMap(JSON.stringify(leaf.map), url), ); + if (inspected.format === "regular") regularRows = decoded; for (const [line, row] of decoded.entries()) for (const segment of row) { if ( @@ -112,7 +114,7 @@ const decodeValidatedMap = ( } } const map = new AnyMap(inspected.jsonText, url); - const rows = decodedMappings(map); + const rows = regularRows ?? decodedMappings(map); for (const row of rows) for (const segment of row) { if (