From a95b83c2b06ef713664d6abb3d29c7c149a2ce01 Mon Sep 17 00:00:00 2001 From: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com> Date: Tue, 27 Oct 2026 22:53:42 +0800 Subject: [PATCH] perf(javascript): cache callable result syntax by AST identity (#7508) Cache direct return/yield source expressions by immutable AST identity for all existing static and semantic consumers. Weak keys bound retention to AST lifetime; mutable analysis facts and Evidence remain unchanged. Refs #6311; full CastleIO analysis still times out. Validation: 225 focused tests; baseline cache-regression controls; build, serial check:fast and test discovery passed. CastleIO direct-syntax facts equal with 78,272 warm lookups reduced from 2.72-2.81s to 13.0-13.4ms. Complete sourcemap-codec CLI analyses retained byte-identical Evidence; no end-to-end speedup or token reduction claimed. CI: source 20,888 passed / 15 skipped; seven built-dependent shards, static, docs, package builds, Linux native baseline and real JS recovery CLI/MCP passed. Shard 6 Hermes path-preview failures both reproduce on exact main b9d1a61dd; Portable tests fails downstream. Greptile 5/5, no findings; Codex quota exhausted. Authorized admin bypass for unrelated baseline CI and unchecked human readiness; no human review attestation made. --- .../javascriptSemanticCallableResults.test.ts | 69 +++++++++++++++++++ .../javascriptSemanticCallableResults.ts | 18 ++++- 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/domain/javascript/javascriptSemanticCallableResults.test.ts diff --git a/src/domain/javascript/javascriptSemanticCallableResults.test.ts b/src/domain/javascript/javascriptSemanticCallableResults.test.ts new file mode 100644 index 0000000000..f2755e5e4e --- /dev/null +++ b/src/domain/javascript/javascriptSemanticCallableResults.test.ts @@ -0,0 +1,69 @@ +import * as t from "@babel/types"; +import { expect, it } from "vitest"; +import { semanticCallableResultExpressions } from "./javascriptSemanticCallableResults.js"; +import { parseJavaScriptSource } from "./javascriptSourceParser.js"; + +it("reuses direct result syntax while excluding nested callable results", () => { + const parsed = parseJavaScriptSource(`function* outer(){ + function inner(){return hidden;} + class Hidden{method(){return hidden;}} + if(flag)return first; + yield* second; + }`); + const callable = parsed?.program.body[0]; + if (!t.isFunctionDeclaration(callable)) + throw new Error("Expected callable fixture"); + const statements = callable.body.body; + let reads = 0; + Object.defineProperty(callable.body, "body", { + get: () => { + reads += 1; + return statements; + }, + }); + const result = semanticCallableResultExpressions(callable); + expect( + result.map(({ node, kind, delegated }) => [ + t.isIdentifier(node) ? node.name : null, + kind, + delegated, + ]), + ).toEqual([ + ["first", "return", false], + ["second", "yield", true], + ]); + expect(reads).toBeGreaterThan(0); + reads = 0; + for (let i = 0; i < 32; i++) + expect(semanticCallableResultExpressions(callable)).toBe(result); + expect(reads).toBe(0); +}); + +it("keeps identical source text in separate syntax trees distinct", () => { + const first = t.arrowFunctionExpression([], t.identifier("value")); + const second = t.arrowFunctionExpression([], t.identifier("value")); + expect(semanticCallableResultExpressions(first)[0]?.node).toBe(first.body); + expect(semanticCallableResultExpressions(second)[0]?.node).toBe(second.body); +}); + +it("retains empty results and bare return/yield source sites", () => { + const empty = t.functionExpression(null, [], t.blockStatement([])); + const emptyResult = semanticCallableResultExpressions(empty); + expect(emptyResult).toEqual([]); + expect(semanticCallableResultExpressions(empty)).toBe(emptyResult); + + const bareReturn = t.returnStatement(); + const bareYield = t.yieldExpression(); + const generator = t.functionExpression( + null, + [], + t.blockStatement([t.expressionStatement(bareYield), bareReturn]), + true, + ); + const result = semanticCallableResultExpressions(generator); + expect(result).toEqual([ + { node: null, site: bareYield, kind: "yield", delegated: false }, + { node: null, site: bareReturn, kind: "return", delegated: false }, + ]); + expect(semanticCallableResultExpressions(generator)).toBe(result); +}); diff --git a/src/domain/javascript/javascriptSemanticCallableResults.ts b/src/domain/javascript/javascriptSemanticCallableResults.ts index ba66a22790..d8ac7526f5 100644 --- a/src/domain/javascript/javascriptSemanticCallableResults.ts +++ b/src/domain/javascript/javascriptSemanticCallableResults.ts @@ -10,15 +10,25 @@ export interface SemanticCallableResultExpression { readonly delegated: boolean; } +// Syntax is immutable across the static and semantic passes. These are source +// expressions, independent of changing binding, mutation or invocation facts. +// Weak keys keep retained results within the lifetime of their parsed AST. +const resultExpressions = new WeakMap< + t.Node, + readonly SemanticCallableResultExpression[] +>(); + /** Collect direct result syntax without executing or evaluating a callable. */ export const semanticCallableResultExpressions = ( callable: t.Node, ): readonly SemanticCallableResultExpression[] => { + const cached = resultExpressions.get(callable); + if (cached !== undefined) return cached; if ( t.isArrowFunctionExpression(callable) && !t.isBlockStatement(callable.body) - ) - return [ + ) { + const result: readonly SemanticCallableResultExpression[] = [ { node: callable.body, site: callable.body, @@ -26,6 +36,9 @@ export const semanticCallableResultExpressions = ( delegated: false, }, ]; + resultExpressions.set(callable, result); + return result; + } if (!t.isFunction(callable)) return []; const output: SemanticCallableResultExpression[] = []; const pending = childNodes(callable.body).reverse(); @@ -49,6 +62,7 @@ export const semanticCallableResultExpressions = ( }); for (const child of childNodes(node).reverse()) pending.push(child); } + resultExpressions.set(callable, output); return output; };