From 99c2f9fb54b2abf5f3471058dc072e19be80315a Mon Sep 17 00:00:00 2001 From: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Date: Fri, 16 Oct 2026 01:46:03 +0000 Subject: [PATCH] perf(javascript): select member-mutation origins after coverage markValue selected a binding's capture origins, ordering every initializer against the capture with semanticMutationInitializers, before the coverage antichain rejected the revisit. On alias-heavy wasm2js output almost every visit stops at coverage, so most of that work was discarded. Only the local setter check of a non-delete write needs origins before coverage; select them after coverage for every other effect. The semantic phase on pdf.js's openjpeg_nowasm_fallback.js drops from 249.9 s to 148.6 s with an identical IR digest. Semantic IR and module projections are byte-identical across 125 real files. Refs: #2498 Co-Authored-By: Claude Opus 5.5 --- .../javascriptSemanticMemberMutations.ts | 46 ++++++++++++------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/domain/javascript/javascriptSemanticMemberMutations.ts b/src/domain/javascript/javascriptSemanticMemberMutations.ts index 1400d25258..20ff84590e 100644 --- a/src/domain/javascript/javascriptSemanticMemberMutations.ts +++ b/src/domain/javascript/javascriptSemanticMemberMutations.ts @@ -51,6 +51,10 @@ import { type PropertyPath = JavaScriptSemanticPropertyPath; type ValueEffect = "write" | "escape"; +type SemanticCaptureOrigins = Pick< + JavaScriptSemanticBindingState, + "initializers" | "referenceInitializers" | "directOrigins" +>; /** Preserve uncertainty from property writes and references exposed to calls, yielding during traversal and fixpoint iteration. */ @@ -244,6 +248,26 @@ export function* collectSemanticMemberMutationsSteps( // A capture key already ends the array; a path stays one nested element. const lifetimeKey = (captureKey: string, path: PropertyPath): string => `${captureKey.slice(0, -1)},${JSON.stringify(path)}]`; + // An owner initializer names one copied value; otherwise origins depend on + // where the alias captured the binding. + const captureOrigins = ( + binding: JavaScriptSemanticBindingState, + capture: { + readonly originAt: t.Node | undefined; + readonly ownerInitializer?: t.Node; + }, + ): SemanticCaptureOrigins => + capture.ownerInitializer !== undefined + ? { + initializers: binding.initializers.filter( + ({ node }) => node === capture.ownerInitializer, + ), + referenceInitializers: [], + directOrigins: [], + } + : capture.originAt === undefined + ? binding + : semanticMutationInitializers(binding, capture.originAt, parents); const pendingReferences: { readonly initializer: JavaScriptSemanticBindingState["referenceInitializers"][number]; readonly path: PropertyPath; @@ -441,29 +465,16 @@ export function* collectSemanticMemberMutationsSteps( index === selectorIndex ? { ...selector, excludedKeys } : key, ); } - const origins = - current.ownerInitializer !== undefined - ? { - initializers: binding.initializers.filter( - ({ node }) => node === current.ownerInitializer, - ), - referenceInitializers: [], - directOrigins: [], - } - : current.originAt === undefined - ? binding - : semanticMutationInitializers( - binding, - current.originAt, - parents, - ); // A setter on the binding's own literal runs on assignment and may // replace any member of its receiver, so the written key is unknown. - // Deleting the accessor never calls it. + // Deleting the accessor never calls it. Only that check needs origins + // before coverage; most revisits stop there, so others select later. + let origins: SemanticCaptureOrigins | undefined; if ( effect === "write" && !t.isUnaryExpression(mutation, { operator: "delete" }) ) { + origins = captureOrigins(binding, current); const receiver = localSetterReceiver(origins.initializers, path); if (receiver !== undefined) path = [...receiver, null]; } @@ -488,6 +499,7 @@ export function* collectSemanticMemberMutationsSteps( coveringPaths.set(pathState, covering); } if (!covering.retain(path)) continue; + origins ??= captureOrigins(binding, current); // A recorded write invalidates the binding to a non-primitive value, so // only its first write can target a primitive. Re-evaluating it would // replay every earlier write and make repeated writes superlinear.