Skip to content

Answer invalidation checks from a per-holder index of contained node keys - #6152

Closed
ondrejmirtes wants to merge 2 commits into
2.2.xfrom
scopeops-invalidation-index
Closed

Answer invalidation checks from a per-holder index of contained node keys#6152
ondrejmirtes wants to merge 2 commits into
2.2.xfrom
scopeops-invalidation-index

Conversation

@ondrejmirtes

Copy link
Copy Markdown
Member

Ported from the resolve-type-rewrite-2 branch. ScopeOps::shouldInvalidateExpression() re-scanned the stored holder's whole subtree (after the substring/compositional-key pre-filters) on every invalidation check. The scan establishes a property of the holder's expression alone, so ExpressionTypeHolder now lazily indexes the node keys of its sub-expressions once — holders are shared across scope copies, so the single scan amortizes — and the check becomes one array lookup, exact by construction. $this invalidations keep the finder (they also match self/static/parent and the current class name, whose resolution depends on the asking scope). 2.2.x's newer pre-filter gates (the requireMoreCharacters variable refinement, keyMayHideSubExpressions) are kept.

The native twins mirror the same split; the ExpressionTypeHolder twin exposes the same lazily-cached getContainedNodeKeys(), key-order identical.

Validation: strict build, smoke (incl. new differential coverage for the index + memoization), side-by-side.php, signature-parity.php, full test suite green with and without the active extension, byte-identical analysis output over src/Analyser src/Rules with the extension on vs off.

Honest benchmark verdict: neutral on 2.2.x — 12 interleaved ABBA pairs over src/Analyser src/Rules (no turbo): A=51.54s vs B=51.54s user CPU (±0.03% in both independent rounds). 2.2.x's pre-filters already absorb almost all checks before the subtree scan, so the index has nothing left to win here. The value is convergence: the resolve-type-rewrite-2 branch measured −2.5% user CPU from this change (its engine performs ~2.3M invalidation checks per self-analysis run), carries it as a twin-divergence, and every rebase pays for the conflict surface. If neutral-CPU-for-convergence isn't worth it, holding this until the branch merge is a fine alternative.

🤖 Generated with Claude Code

https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7

ondrejmirtes and others added 2 commits July 30, 2026 17:40
…keys

ScopeOps::shouldInvalidateExpression() re-scanned the stored holder's
whole subtree on every invalidation check (after the substring and
compositional-key pre-filters). The scan establishes a property of the
holder's expression alone, so ExpressionTypeHolder now lazily indexes
the node keys of its sub-expressions once - holders are shared across
scope copies, so the single subtree scan amortizes over every later
check - and the check becomes one array lookup, exact by construction.

'$this' invalidations keep the finder: they also match self/static/
parent and the current class name, and that name resolution depends on
the asking scope.

The native twins mirror the same split: the ScopeOps core reads the
holder's cached index (built with the native node-key builder) and the
ExpressionTypeHolder twin exposes the same lazily-cached
getContainedNodeKeys(), key-order identical to the PHP implementation.

Ported from the resolve-type-rewrite-2 branch, keeping 2.2.x's newer
pre-filter gates (the requireMoreCharacters variable refinement and
keyMayHideSubExpressions).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
Comment thread turbo-ext/src/support.dep
Comment on lines +2 to +4
/Users/ondrej/Development/phpstan/.claude/worktrees/scopeops-invalidation/turbo-ext/src/support.cpp \
/Users/ondrej/Development/phpstan/.claude/worktrees/scopeops-invalidation/turbo-ext/src/support.h \
/opt/homebrew/Cellar/php/8.5.5_1/include/php/main/php.h \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are these developer machine local path expected ?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably shouldn't be commited at all

ondrejmirtes added a commit that referenced this pull request Jul 30, 2026
With the compositional-key shortcut clearing virtual-node keys, only
308k invalidation checks reach the containment step on a
src/Analyser+src/Rules run (down from 1.16M) - the population where the
index was measured worthless on 2.2.x (phpstan-src PR #6152, 12
interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps
containment (containsExpressionToInvalidate) and their native twins
return to the 2.2.x shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
@ondrejmirtes

Copy link
Copy Markdown
Member Author

For the record: the branch no longer needs this either. The root cause of its 3.2× invalidation-check volume was virtual-node keys defeating the compositional-substring shortcut (plus a missing caller-level prefilter in its invalidateExpressionEntries), not intrinsic load — same events, same holder population as 2.2.x. With the compositional-gate fixes landed there (f65dfce), calls dropped 2.41M → 420k and containment checks 1.16M → 308k — below 2.2.x's 629k, where this PR measured dead neutral — so the index was dropped from the branch too (5ee84cd) and ExpressionTypeHolder/ScopeOps converged back to the 2.2.x shapes.

ondrejmirtes added a commit that referenced this pull request Jul 30, 2026
With the compositional-key shortcut clearing virtual-node keys, only
308k invalidation checks reach the containment step on a
src/Analyser+src/Rules run (down from 1.16M) - the population where the
index was measured worthless on 2.2.x (phpstan-src PR #6152, 12
interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps
containment (containsExpressionToInvalidate) and their native twins
return to the 2.2.x shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
ondrejmirtes added a commit that referenced this pull request Jul 30, 2026
With the compositional-key shortcut clearing virtual-node keys, only
308k invalidation checks reach the containment step on a
src/Analyser+src/Rules run (down from 1.16M) - the population where the
index was measured worthless on 2.2.x (phpstan-src PR #6152, 12
interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps
containment (containsExpressionToInvalidate) and their native twins
return to the 2.2.x shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
ondrejmirtes added a commit that referenced this pull request Jul 30, 2026
With the compositional-key shortcut clearing virtual-node keys, only
308k invalidation checks reach the containment step on a
src/Analyser+src/Rules run (down from 1.16M) - the population where the
index was measured worthless on 2.2.x (phpstan-src PR #6152, 12
interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps
containment (containsExpressionToInvalidate) and their native twins
return to the 2.2.x shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
ondrejmirtes added a commit that referenced this pull request Jul 30, 2026
With the compositional-key shortcut clearing virtual-node keys, only
308k invalidation checks reach the containment step on a
src/Analyser+src/Rules run (down from 1.16M) - the population where the
index was measured worthless on 2.2.x (phpstan-src PR #6152, 12
interleaved pairs, dead neutral). ExpressionTypeHolder, the ScopeOps
containment (containsExpressionToInvalidate) and their native twins
return to the 2.2.x shapes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DaBZjgksga4c5s6Q9FniY7
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.

2 participants