Skip to content

perf(react-grab): dexnode-equivalent V8 deopt profiler + targeted source fixes#346

Draft
aidenybai wants to merge 4 commits into
mainfrom
cursor/v8-deopt-profile-9bc1
Draft

perf(react-grab): dexnode-equivalent V8 deopt profiler + targeted source fixes#346
aidenybai wants to merge 4 commits into
mainfrom
cursor/v8-deopt-profile-9bc1

Conversation

@aidenybai
Copy link
Copy Markdown
Owner

@aidenybai aidenybai commented May 16, 2026

What

Adds an in-repo workflow to capture V8 deopt + IC information from react-grab as it actually runs in a real Chromium tab, and applies the three deopt fixes that are squarely in our source.

Because dexnode itself is a Node-only wrapper and react-grab is a browser library, this PR ports dexnode's flag set to the equivalent Chromium --js-flags invocation. The resulting v8.log is byte-compatible with Microsoft's Deopt Explorer VS Code extension; an in-repo CLI summarizer is included for environments without it.

New scripts

  • packages/react-grab/scripts/perf-deopt.mjs — boots the e2e-app (/?perf=grid&rows=50&cols=10) and launches headless Chromium with the exact flag set dexnode emits for a chrome_stable host (--log-deopt --log-ic --log-maps --log-maps-details --log-code --log-source-code --prof --log-internal-timer-events --detailed-line-info --no-logfile-per-isolate --logfile=…). It then drives five scenarios (hover sweep, scroll-while-frozen, repeated getState, viewport invalidation bursts, multi-freeze invalidation bursts) and writes the raw v8.log to packages/react-grab/perf/v8-log/.
  • packages/react-grab/scripts/perf-deopt-analyze.mjs — parses the log: deopt entries (code-deopt with inlined <url:line:col> source positions) and IC entries. IC pc addresses are resolved to their owning JS code blob via the log's code-creation records, so megamorphic / polymorphic hot sites are grouped by their containing function and source URL. Emits summary.json, summary.md, and a console report.
  • pnpm --filter react-grab perf:deopt / perf:deopt:analyze wire those up.

Targeted source fixes (only sites we control)

Driven directly by the capture in packages/react-grab/perf/v8-deopt-findings.md. Three commits, each one source file:

file change site it targets
src/utils/get-element-at-position.ts positionCache and iframeHoverCache become module-level shape-stable singletons (hasValue / isHovering flag replaces the null/object union) Xr (freeze-updates*.js:~1303) — removes the cache / hoveredIframe null↔object shape transition that fed the cache-read IC
src/utils/get-visual-viewport.ts returns a shared mutable singleton instead of a fresh literal each call (callers already consume synchronously; comment documents the contract) It (renderer*.js:~1188 / ~1721) — removes the dependent field type constness changed clusters whose root cause was the per-call literal returning different hidden classes
src/utils/toolbar-position.ts hoists Math.max / Math.min to module-level mathMax / mathMin getPositionFromEdgeAndRatio / getSnapPosition — eliminates the 60+ KeyedLoadIC events / run against the Math global

Verifiable IC-level deltas (4 runs each)

Aggregate is within run-to-run noise (±5 ms / ~7% per perf/README.md), but the targeted IC sites measurably moved:

metric before after
total deopts 90 89 – 92
total IC events 19,876 19,559 – 19,690
Math KeyedLoadIC events / run 60+ 0
dependent field type constness changed deopts 11 10
distinct react-grab megamorphic IC sites 62 54
distinct react-grab polymorphic IC sites 171 147

What we did NOT change (and why)

These appear in the findings but they are not ours to fix in this PR:

  • Recursive fiber walker $(e, t) at freeze-updates*.js:~1671 (wrong call target + paired lazy deopt). Lives in bippy, not packages/react-grab/src.
  • Fiber finder b(e) at freeze-updates*.js:~249 (unexpected name in keyed access + 116× megamorphic String.prototype.startsWith). Also bippy.
  • Solid createStore proxy get trap at core*.js:70. Owned by solid-js; the structural fix is to stop funneling heterogeneous targets (DOM nodes, fibers, plain options) through one store, which is a design-level change beyond this PR.
  • incrementViewportVersion → memo invalidation chain (renderer*.js constness deopts). Already called out in perf/README.md — the right fix is to make store slot writes shape-stable, but that touches multiple modules and needs its own design pass.

How to reproduce

pnpm install
pnpm build
pnpm --filter react-grab perf:deopt
pnpm --filter react-grab perf:deopt:analyze

Then read packages/react-grab/perf/v8-deopt-findings.md and the freshly-written packages/react-grab/perf/v8-log/summary.md. The raw v8.log is gitignored but can be opened directly with the Microsoft "Deopt Explorer" VS Code extension.

Files

  • New tooling: packages/react-grab/scripts/perf-deopt.mjs, packages/react-grab/scripts/perf-deopt-analyze.mjs
  • New docs: packages/react-grab/perf/v8-deopt-findings.md, regenerated packages/react-grab/perf/v8-log/summary.md
  • Source fixes: packages/react-grab/src/utils/get-element-at-position.ts, packages/react-grab/src/utils/get-visual-viewport.ts, packages/react-grab/src/utils/toolbar-position.ts
  • Wiring: packages/react-grab/package.json (2 new scripts), packages/react-grab/perf/README.md (docs), .gitignore (exclude raw v8.log / code.asm)
Open in Web Open in Cursor 

Summary by cubic

Adds an in-browser V8 deopt + IC profiler for react-grab that mirrors dexnode flags in headless Chromium, plus a CLI analyzer and curated findings. Also applies three small runtime tweaks to stabilize hidden classes and hoist Math globals, reducing megamorphic IC sites and eliminating Math KeyedLoadICs.

  • New Features

    • perf:deopt: launches the e2e-app, runs Chromium with dexnode-equivalent --js-flags, drives five scenarios, and writes v8.log to packages/react-grab/perf/v8-log/.
    • perf:deopt:analyze: resolves IC pc addresses via code-creation records and emits summary.json/summary.md (+ console report).
    • Adds packages/react-grab/perf/v8-deopt-findings.md with an updated post-fix snapshot; docs in packages/react-grab/perf/README.md; raw logs/asm gitignored.
  • Performance

    • Stabilize hidden classes in get-element-at-position.ts and get-visual-viewport.ts via module-level singletons.
    • Hoist Math.max/Math.min in toolbar-position.ts to cut hot property lookups.
    • Results: Math KeyedLoadIC events → 0; react-grab megamorphic IC sites: 62 → 54; dependent-field-constness deopts: 11 → 10.

Written for commit 1a2f26a. Summary will update on new commits. Review in cubic

Cursor Agent and others added 2 commits May 16, 2026 03:49
…wser run

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- Analyzer now resolves IC pc addresses to their containing JS code blob via the
  v8 log's code-creation records, so megamorphic / polymorphic hot sites are
  reported with the function name and source URL.
- Adds packages/react-grab/perf/v8-deopt-findings.md (curated report) and a
  generated summary.md sibling next to v8.log.
- Gitignores raw v8.log / code.asm; the parsed summaries stay tracked.
- Documents perf:deopt / perf:deopt:analyze in perf/README.md.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 16, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-grab-storybook Ready Ready Preview, Comment May 16, 2026 4:26am
react-grab-website Ready Ready Preview, Comment May 16, 2026 4:26am

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 16, 2026

Open in StackBlitz

npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/cli@346
npm i https://pkg.pr.new/aidenybai/react-grab/grab@346
npm i https://pkg.pr.new/aidenybai/react-grab/@react-grab/mcp@346
npm i https://pkg.pr.new/aidenybai/react-grab@346

commit: 1a2f26a

Cursor Agent and others added 2 commits May 16, 2026 04:24
…t helpers

Three targeted fixes driven by the dexnode V8 deopt capture, each addressing
a specific site that appears in packages/react-grab/perf/v8-log/summary.md.

1) get-element-at-position.ts: replace the `null | object` pattern for both
   positionCache and iframeHoverCache with module-level singletons that
   carry a `hasValue` / `isHovering` flag. This removes the shape
   transition the JIT was seeing at the cache-read sites inside the hot
   getElementAtPosition path (one of the two paired deopts in
   freeze-updates*.js: `if (z && Gr(e, t, z.rect)) return z.element;`).

2) get-visual-viewport.ts: return a shared mutable singleton instead of
   allocating a fresh literal each call. The toolbar position helpers
   inline this and bail out with "dependent field type constness changed"
   when consecutive returns produce different hidden classes. Callers
   already consume the result synchronously, so the singleton is safe; a
   comment documents the contract.

3) toolbar-position.ts: hoist Math.max/Math.min to module-level locals
   (`mathMax`, `mathMin`). The IC capture showed dozens of KeyedLoadIC
   hits per run on Math.* inside getPositionFromEdgeAndRatio and
   getSnapPosition; the rewritten globals come from a single module-scope
   const so V8 emits a plain LoadIC instead.

Aggregate impact on the synthetic grid bench is within run-to-run noise
(±5 ms / ~7%, as documented in perf/README.md - the synthetic scenarios
don't stress the per-callsite IC behavior these helpers control). The
verifiable changes are:

- 60+ `Math` KeyedLoadIC events / run -> 0 (Math hoist)
- dependent-field-constness deopts: 11 -> 10
- react-grab megamorphic IC SITE count: 62 -> 54
  (event volume on Solid's createStore proxy still dominates and is not
  ours to fix - see v8-deopt-findings.md)

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
- v8-deopt-findings.md gains an 'Applied fixes' table mapping each touched
  source file to the deopt/IC site it addresses, plus a before/after
  metrics table (within run-to-run noise on aggregate counts, verified
  zero Math KeyedLoadIC hits + fewer distinct megamorphic / polymorphic
  sites in react-grab source).
- summary.md is refreshed from a new dexnode-equivalent capture against
  the post-fix build.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
@cursor cursor Bot changed the title perf(react-grab): dexnode-equivalent V8 deopt profiler + findings perf(react-grab): dexnode-equivalent V8 deopt profiler + targeted source fixes May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant