[ 
https://issues.apache.org/jira/browse/GROOVY-12191?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Daniel Sun updated GROOVY-12191:
--------------------------------
    Description: 
h3. Problem

With invokedynamic enabled (default since Groovy 4), linked MOP call sites were 
guarded by a *single process-wide* {{SwitchPoint}} 
({{{}IndyInterface.switchPoint{}}}).

Any MetaClass registry change or category enter/leave invalidated that switch 
point, so *every* linked site fell back and re-linked — including sites whose 
receiver type was unrelated.

That global invalidation is expensive when MetaClass churn is common (e.g. 
ExpandoMetaClass / mixins on startup or per-request paths, Grails-like 
patterns). Unrelated hot monomorphic sites pay re-link and JIT deopt cost they 
should not.
h3. Goal

Keep linked call sites optimized unless the *relevant* MetaClass state for that 
site actually changed.
h3. Approach

One SwitchPoint domain {*}per class{*}, stored on {{{}ClassInfo{}}}:
 * MetaClass change for type {{T}} retires {{{}T{}}}'s SwitchPoint *and* those 
of loaded subtypes / implementors (hierarchy fan-out).
 * Unrelated types keep their SwitchPoints; their call sites stay optimized.
 * Category enter/leave (and {{{}VMPlugin.invalidateCallSites(){}}}) 
bulk-retire *all* loaded class SwitchPoints so sites re-link under the new 
category state. There is *no* second category SwitchPoint on the hot path.
 * Linked handles always install a *single* class-domain guard via 
{{IndyInvalidation.guardWithMopSwitchPoints(...)}} — same monomorphic guard 
shape as before, without global deopt on unrelated MetaClass churn.

Final classes short-circuit hierarchy fan-out (no full {{ClassInfo}} scan). 
Non-final types batch retirements with {{{}SwitchPoint.invalidateAll{}}}.
h3. Invalidation map
||Event||What is retired||
|MetaClass change for type {{T}} (registry / 
{{{}ClassInfo.incVersion{}}})|{{T}} + loaded subtypes / implementors|
|Category enter/leave, {{invalidateCallSites()}}|All loaded class SwitchPoints 
(bulk)|
|Unattributed MetaClass registry event|All loaded class SwitchPoints (bulk)|
|First MetaClass *install* on a class|Version bump only (no linked sites yet); 
replacement / clear retires that class's SwitchPoint|

 

  was:
h3. Problem

With invokedynamic enabled (default since Groovy 4), linked MOP call sites were 
guarded by a *single process-wide* {{SwitchPoint}} 
({{IndyInterface.switchPoint}}).

Any MetaClass registry change or category enter/leave invalidated that switch 
point, so *every* linked site fell back and re-linked — including sites whose 
receiver type was unrelated.

That global invalidation is expensive when MetaClass churn is common (e.g. 
ExpandoMetaClass / mixins on startup or per-request paths, Grails-like 
patterns). Unrelated hot monomorphic sites pay re-link and JIT deopt cost they 
should not.

h3. Goal

Keep linked call sites optimized unless the *relevant* MetaClass state for that 
site actually changed.

h3. Approach (minimal model)

One SwitchPoint domain *per class*, stored on {{ClassInfo}}:

 * MetaClass change for type {{T}} retires {{T}}'s SwitchPoint *and* those of 
loaded subtypes / implementors (hierarchy fan-out).
 * Unrelated types keep their SwitchPoints; their call sites stay optimized.
 * Category enter/leave (and {{VMPlugin.invalidateCallSites()}}) bulk-retire 
*all* loaded class SwitchPoints so sites re-link under the new category state. 
There is *no* second category SwitchPoint on the hot path.
 * Linked handles always install a *single* class-domain guard via 
{{IndyInvalidation.guardWithMopSwitchPoints(...)}} — same monomorphic guard 
shape as before, without global deopt on unrelated MetaClass churn.

Final classes short-circuit hierarchy fan-out (no full {{ClassInfo}} scan). 
Non-final types batch retirements with {{SwitchPoint.invalidateAll}}.

h3. Invalidation map

||Event||What is retired||
|MetaClass change for type {{T}} (registry / {{ClassInfo.incVersion}})|{{T}} + 
loaded subtypes / implementors|
|Category enter/leave, {{invalidateCallSites()}}|All loaded class SwitchPoints 
(bulk)|
|Unattributed MetaClass registry event|All loaded class SwitchPoints (bulk)|
|First MetaClass *install* on a class|Version bump only (no linked sites yet); 
replacement / clear retires that class's SwitchPoint|

h3. Binary compatibility

{{IndyInterface.switchPoint}} is retained and rotated on category / 
{{invalidateCallSites}} for external observers, but is *no longer* the MOP 
guard on linked call sites (documented as deprecated for that use). New code 
uses {{IndyInvalidation}}.



> Scope indy SwitchPoint invalidation
> -----------------------------------
>
>                 Key: GROOVY-12191
>                 URL: https://issues.apache.org/jira/browse/GROOVY-12191
>             Project: Groovy
>          Issue Type: Improvement
>            Reporter: Daniel Sun
>            Priority: Major
>
> h3. Problem
> With invokedynamic enabled (default since Groovy 4), linked MOP call sites 
> were guarded by a *single process-wide* {{SwitchPoint}} 
> ({{{}IndyInterface.switchPoint{}}}).
> Any MetaClass registry change or category enter/leave invalidated that switch 
> point, so *every* linked site fell back and re-linked — including sites whose 
> receiver type was unrelated.
> That global invalidation is expensive when MetaClass churn is common (e.g. 
> ExpandoMetaClass / mixins on startup or per-request paths, Grails-like 
> patterns). Unrelated hot monomorphic sites pay re-link and JIT deopt cost 
> they should not.
> h3. Goal
> Keep linked call sites optimized unless the *relevant* MetaClass state for 
> that site actually changed.
> h3. Approach
> One SwitchPoint domain {*}per class{*}, stored on {{{}ClassInfo{}}}:
>  * MetaClass change for type {{T}} retires {{{}T{}}}'s SwitchPoint *and* 
> those of loaded subtypes / implementors (hierarchy fan-out).
>  * Unrelated types keep their SwitchPoints; their call sites stay optimized.
>  * Category enter/leave (and {{{}VMPlugin.invalidateCallSites(){}}}) 
> bulk-retire *all* loaded class SwitchPoints so sites re-link under the new 
> category state. There is *no* second category SwitchPoint on the hot path.
>  * Linked handles always install a *single* class-domain guard via 
> {{IndyInvalidation.guardWithMopSwitchPoints(...)}} — same monomorphic guard 
> shape as before, without global deopt on unrelated MetaClass churn.
> Final classes short-circuit hierarchy fan-out (no full {{ClassInfo}} scan). 
> Non-final types batch retirements with {{{}SwitchPoint.invalidateAll{}}}.
> h3. Invalidation map
> ||Event||What is retired||
> |MetaClass change for type {{T}} (registry / 
> {{{}ClassInfo.incVersion{}}})|{{T}} + loaded subtypes / implementors|
> |Category enter/leave, {{invalidateCallSites()}}|All loaded class 
> SwitchPoints (bulk)|
> |Unattributed MetaClass registry event|All loaded class SwitchPoints (bulk)|
> |First MetaClass *install* on a class|Version bump only (no linked sites 
> yet); replacement / clear retires that class's SwitchPoint|
>  



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to