[PR #3836] [MERGED] perf(javascript): index object literal members for exact-key reference lookup #5937

Closed
opened 2026-10-31 01:10:35 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/3836
Author: @kaoru0822-kitauji
Created: 10/15/2026
Status: ✅ Merged
Merged: 10/15/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: perf/object-property-reference-index


📝 Commits (1)

  • 6686608 perf(javascript): index object literal members for exact-key reference lookup

📊 Changes

2 files changed (+143 additions, -1 deletions)

View changed files

➕ src/domain/javascript/javascriptSemanticObjectReferences.test.ts (+73 -0)
📝 src/domain/javascript/javascriptSemanticObjectReferences.ts (+70 -1)

📄 Description

Measured hotspot. No existing issue; found while reviewing the cost of #3831.

Problem

semanticObjectPropertyReferences (javascriptSemanticObjectReferences.ts) answers "which members can supply key k of this object literal" by copying and reverse-scanning every property on each call. Member-mutation tracking calls it once per write (referencedValues), so K writes to an N-property literal cost O(N·K). Generated configuration and state objects with many assignments are common in bundles.

CPU profile of one 4,000-property literal with 4,000 writes on main: semanticObjectPropertyReferences takes 5.4 s of 10.6 s self time, and its unwrapJavaScriptExpression / semanticStaticPropertyKey callees take another 2.2 s.

Change

For an exact string or number key, a property with a different static name only updates override bookkeeping (overwritten, dataOverwritten, accessor sets) under its own name, which the queried key never reads. Only these members can affect key k:

  • spreads,
  • computed keys,
  • __proto__ data entries (non-computed, non-shorthand),
  • properties whose static name is String(k).

Each literal is indexed once (WeakMap<ObjectExpression, …>), and the existing loop walks only those members, merged back into reverse source order. Wildcard (null) and copied-slot selection keys keep the full scan unchanged.

Validation

Scaling (analyzeJavaScriptSemantics, one N-property literal with N writes, third run):

N main this PR
1,000 243 ms 45 ms
2,000 884 ms 82 ms
4,000 3,372 ms 171 ms
8,000 — 313 ms

main grows about 4× per doubling (quadratic); this PR grows linearly.

Equivalence on real applications (per-file JSON.stringify of analyzeParsedJavaScriptSemantics IR, freshly built main vs this PR):

corpus files identical semantic ms main → PR
Discord 1 1 33 → 30
Notion 96 96 10,535 → 9,210
Signal 572 572 18,138 → 17,845
Claude 421 421* 37,121 → 37,181
Tabby 2,183 2,183 10,568 → 10,219
VS Code out/ 27 27 16,767 → 16,541

* One Claude file (index.chunk-e5zmsebk.js) throws RangeError: Maximum call stack size exceeded in getter-result recursion on both builds. That is a pre-existing bug I'm looking at separately; the outcome is identical here.

Output size is unchanged, since the IR is identical.

Tests:

  • New javascriptSemanticObjectReferences.test.ts uses an order-sensitive literal (duplicate keys, an early spread, a computed key, a __proto__ data entry, a later object spread, a getter overridden by a data property) and covers exact keys, an absent key, the wildcard full scan, and a numeric key. Its expected values were generated from main's full-scan implementation, so the index must reproduce them exactly.
  • 161 JavaScript/domain test files pass (2,973 tests), as does npm run check:fast.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/3836 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/15/2026 **Status:** ✅ Merged **Merged:** 10/15/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `perf/object-property-reference-index` --- ### 📝 Commits (1) - [`6686608`](https://github.com/morluto/rea/commit/6686608145690995c614d564251c3c4a15aaf3fb) perf(javascript): index object literal members for exact-key reference lookup ### 📊 Changes **2 files changed** (+143 additions, -1 deletions) <details> <summary>View changed files</summary> ➕ `src/domain/javascript/javascriptSemanticObjectReferences.test.ts` (+73 -0) 📝 `src/domain/javascript/javascriptSemanticObjectReferences.ts` (+70 -1) </details> ### 📄 Description Measured hotspot. No existing issue; found while reviewing the cost of #3831. ## Problem `semanticObjectPropertyReferences` (`javascriptSemanticObjectReferences.ts`) answers "which members can supply key *k* of this object literal" by copying and reverse-scanning **every** property on each call. Member-mutation tracking calls it once per write (`referencedValues`), so *K* writes to an *N*-property literal cost O(N·K). Generated configuration and state objects with many assignments are common in bundles. CPU profile of one 4,000-property literal with 4,000 writes on `main`: `semanticObjectPropertyReferences` takes 5.4 s of 10.6 s self time, and its `unwrapJavaScriptExpression` / `semanticStaticPropertyKey` callees take another 2.2 s. ## Change For an **exact** string or number key, a property with a *different* static name only updates override bookkeeping (`overwritten`, `dataOverwritten`, accessor sets) under its own name, which the queried key never reads. Only these members can affect key *k*: - spreads, - computed keys, - `__proto__` data entries (non-computed, non-shorthand), - properties whose static name is `String(k)`. Each literal is indexed once (`WeakMap<ObjectExpression, …>`), and the existing loop walks only those members, merged back into reverse source order. Wildcard (`null`) and copied-slot selection keys keep the full scan unchanged. ## Validation **Scaling** (`analyzeJavaScriptSemantics`, one N-property literal with N writes, third run): | N | `main` | this PR | |---:|---:|---:| | 1,000 | 243 ms | 45 ms | | 2,000 | 884 ms | 82 ms | | 4,000 | 3,372 ms | 171 ms | | 8,000 | — | 313 ms | `main` grows about 4× per doubling (quadratic); this PR grows linearly. **Equivalence on real applications** (per-file `JSON.stringify` of `analyzeParsedJavaScriptSemantics` IR, freshly built `main` vs this PR): | corpus | files | identical | semantic ms `main` → PR | |---|---:|---:|---:| | Discord | 1 | 1 | 33 → 30 | | Notion | 96 | 96 | 10,535 → 9,210 | | Signal | 572 | 572 | 18,138 → 17,845 | | Claude | 421 | 421* | 37,121 → 37,181 | | Tabby | 2,183 | 2,183 | 10,568 → 10,219 | | VS Code `out/` | 27 | 27 | 16,767 → 16,541 | \* One Claude file (`index.chunk-e5zmsebk.js`) throws `RangeError: Maximum call stack size exceeded` in getter-result recursion on **both** builds. That is a pre-existing bug I'm looking at separately; the outcome is identical here. Output size is unchanged, since the IR is identical. **Tests:** - New `javascriptSemanticObjectReferences.test.ts` uses an order-sensitive literal (duplicate keys, an early spread, a computed key, a `__proto__` data entry, a later object spread, a getter overridden by a data property) and covers exact keys, an absent key, the wildcard full scan, and a numeric key. Its expected values were generated from `main`'s full-scan implementation, so the index must reproduce them exactly. - 161 JavaScript/domain test files pass (2,973 tests), as does `npm run check:fast`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-31 01:10:35 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#5937
No description provided.