perf(javascript): prune typed-array element reference escapes (#5139)

* perf(javascript): prune typed-array element reference escapes

* fix(javascript): retain references from typed-array element properties

---------

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Ge Li
2026-10-22 17:41:50 -04:00
committed by GitHub
parent 0344d578b2
commit 6d73515bf1
3 changed files with 410 additions and 10 deletions
@@ -1,5 +1,9 @@
import * as t from "@babel/types";
import { isIntrinsicMapLookup } from "./javascriptSemanticMapOperations.js";
import {
isIntrinsicTypedArrayConstruction,
isTypedArrayNumericIndex,
} from "./javascriptSemanticTypedArrays.js";
import {
semanticGlobalProcessOrigin,
@@ -1218,6 +1222,8 @@ type MutationObservableOrigin =
| "ambient"
| "array"
| "array-prototype"
| "typed-array"
| "typed-array-element"
| "function"
| "opaque";
@@ -1229,6 +1235,7 @@ interface MutationObserverNode {
readonly read?: {
readonly source: MutationObserverNode;
readonly key: PropertyPath[number];
readonly numericIndex: boolean;
};
}
@@ -1283,15 +1290,16 @@ const observableMutationPaths = (
const project = (
source: MutationObserverNode,
path: PropertyPath,
numericIndex = false,
): MutationObserverNode => {
let observer = source;
for (const key of path) {
const identity = JSON.stringify(
const identity = `${numericIndex ? "numeric:" : ""}${JSON.stringify(
key === null || typeof key === "object" ? key : String(key),
);
)}`;
let selected = observer.projections?.get(identity);
if (selected === undefined) {
selected = create({ source: observer, key });
selected = create({ source: observer, key, numericIndex });
(observer.projections ??= new Map()).set(identity, selected);
(observer.dependents ??= new Set()).add(selected);
schedule(selected);
@@ -1352,6 +1360,11 @@ const observableMutationPaths = (
// Keep class/getter projections distinct so cycle pruning does not
// discard observable escapes through a shared getter result.
if (
t.isNewExpression(expression) &&
isIntrinsicTypedArrayConstruction(expression, state)
)
(observer.origins ??= new Set()).add("typed-array");
else if (
t.isNewExpression(expression) &&
semanticClassCandidates(expression.callee, state).length === 0
)
@@ -1383,12 +1396,19 @@ const observableMutationPaths = (
t.isOptionalMemberExpression(expression)
)
link(
project(expressionNode(expression.object), [
semanticStaticPropertyKey(
project(
expressionNode(expression.object),
[
semanticStaticPropertyKey(
expression.property,
expression.computed,
),
],
isTypedArrayNumericIndex(
expression.property,
expression.computed,
),
]),
),
observer,
);
else
@@ -1416,16 +1436,29 @@ const observableMutationPaths = (
for (const source of observer.sources ?? [])
for (const origin of source.origins ?? []) add(origin);
if (observer.read !== undefined) {
const { source, key } = observer.read;
const { source, key, numericIndex } = observer.read;
if (key === null || typeof key === "object") {
// Dynamic keys and copied-slot masks retain their original paths.
// Enumerating their exclusion combinations would recreate the growth
// that the mutation collector's selection union already prevents.
if ((source.origins?.size ?? 0) > 0) add("opaque");
for (const origin of source.origins ?? [])
add(
origin === "typed-array" && numericIndex
? "typed-array-element"
: "opaque",
);
} else
for (const origin of source.origins ?? []) {
if (typeof origin === "string") {
if (origin === "array-prototype" || origin === "opaque")
if (origin === "typed-array") {
// Custom properties can hold references; only canonical numeric
// indices have the native Number/BigInt/undefined result contract.
add(numericIndex ? "typed-array-element" : "opaque");
} else if (origin === "typed-array-element") {
// Number/BigInt property reads can expose constructors, methods
// or objects returned by prototype getters after boxing.
add("opaque");
} else if (origin === "array-prototype" || origin === "opaque")
add(origin);
else if (
origin === "ambient" &&
@@ -1477,7 +1510,12 @@ const observableMutationPaths = (
if (effect === "escape") {
observer = project(observer, path);
settle();
return (observer.origins?.size ?? 0) === 0 ? null : path;
const origins = observer.origins;
return origins === undefined ||
origins.size === 0 ||
(origins.size === 1 && origins.has("typed-array-element"))
? null
: path;
}
if ((observer.origins?.size ?? 0) === 0)
return path.length === 0 ? path : [null];