[ 
https://issues.apache.org/jira/browse/GROOVY-12191?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18100498#comment-18100498
 ] 

ASF GitHub Bot commented on GROOVY-12191:
-----------------------------------------

daniellansun commented on PR #2736:
URL: https://github.com/apache/groovy/pull/2736#issuecomment-5133665005

   @blackdrag Thank you — this is exactly the right question, and your A/B/C, 
D/E, F/G scripts make the boundary sharp.
   
   ### Short answer
   
   Hierarchy SwitchPoint fan-out is needed in **one** live-selection situation 
only:
   
   > The **receiver’s MetaClass is unchanged**, selection previously linked a 
result that depended on an **ancestor** modified `MutableMetaClass` (almost 
always EMC) via the **missing-method** walk 
(`MetaClassImpl.findMethodInClassHierarchy`), and that ancestor later **gains 
or loses** methods that change the walk’s result.
   
   Concrete dual:
   
   | Already-linked site on child | Parent MetaClass event | Without hierarchy 
SP | With hierarchy SP |
   |---|---|---|---|
   | Miss for name `m` | Parent EMC adds `m` | Stale miss | Re-link → hit |
   | Hit found only via parent EMC | Parent EMC removed / replaced away | Stale 
hit | Re-link → miss |
   
   That is **all** hierarchy SwitchPoint is for. It is not a general “parent 
MetaClass version” for child tables, and it does **not** rebuild a child’s 
method index.
   
   ### Mapping your examples
   
   They are consistent with that rule; most of them **do not** require 
hierarchy SP:
   
   1. **Present method on the child** (your `B.m2` / declared `foo` cases)  
      The hierarchy walk does not open for that applicable name/signature. 
Parent mutation is **not** required for correctness of the *present* method. We 
may still over-invalidate subtype domains when parent EMC mutates (monomorphic 
domain trade-off), but that is over-invalidation, not a semantic necessity for 
those names.
   
   2. **Construction-time / EMC snapshot** (D/E after `removeMetaClass(E)`, F/G 
after `removeMetaClass(F)`)  
      The more specific method is already **copied into the child’s MetaClass 
instance**. Parent replace/remove does not change that instance’s tables. 
Hierarchy SP cannot and should not “undo” a snapshot. Replacing the **child** 
MetaClass is what rebuilds the view — exactly as in your D/E ladder.
   
   3. **User-surprising B vs C blocking** (`B.m2` blocks more-specific `A.m2` 
for `B` receivers, not for `C`)  
      Agreed this is awkward from a user POV. It is a property of the 
missing-method walk / “present method wins” rule, not of SwitchPoint scoping. 
We are **not** redefining that MOP rule in this PR.
   
   4. **The case that *does* need hierarchy SP** (what the machinery is for):
   
   ```groovy
   class Parent {}
   class Child extends Parent {}
   def call(x) {
     try { x.onlyOnParent(); 'hit' }
     catch (MissingMethodException e) { 'miss' }
   }
   def c = new Child()
   assert call(c) == 'miss'          // monomorphic miss linked on Child
   Parent.metaClass.onlyOnParent = { -> 'now-visible' }
   assert call(c) == 'hit'           // must re-select; Child MetaClass 
unchanged
   ```
   
   and the inverse (linked hierarchy hit → parent EMC removed → miss again).  
   Same pattern for `Object.metaClass.foo` / interface MetaClass / `Object[]` 
lattice.
   
   Without fan-out, those already-linked subtype sites stay wrong until 
something else retires the child’s domain.
   
   ### “A lot of machinery for a small feature”
   
   Fair point. The *feature* is small in description (missing-method hierarchy 
+ EMC), but it is the same feature that makes `Object.metaClass.foo = …` / 
parent EMC methods visible on subtypes without giving every subtype its own 
EMC. For 6.0 we **keep** that MOP observation path (so hierarchy SP stays as 
its invalidation counterpart). Pure unmodified `MetaClassImpl` ↔ 
`MetaClassImpl` (or null) replace stays **exact-class only** — no hierarchy.
   
   What we *did* simplify on the domain side (current tip of this PR):
   
   - SwitchPoints are per **MetaClass instance** (weak identity map in 
`IndyInvalidation`), not a process-wide SP and not a permanent ClassInfo 
“generation” dual to MetaClass.
   - `ClassInfo` only has a **pending** domain for pre-MetaClass link 
(defineClass safety); first MetaClass install retires it.
   - Hierarchy fan-out remains indexed (`ClassHierarchyIndex`) and 
MetaClass-aware (EMC / modified mutable / interface / array / global EMC → 
fan-out; hierarchy-local `MetaClassImpl` pairs → exact).
   
   So hierarchy logic is not “version every parent change for every child 
site”; it is “when parent can change what a **linked miss/hit on the 
missing-method path** means, re-select subtype sites.”
   
   ### Bottom line
   
   | Question | Answer |
   |---|---|
   | Must parent MC change invalidate a site if the receiver MC is unchanged? | 
**Only** if selection can still change via the live missing-method hierarchy 
walk (linked miss↔hit against an ancestor modified `MutableMetaClass` / EMC, 
including interface/array lattice). |
   | What is hierarchy SP trying to solve? | Keep already-linked **subtype** 
indy sites honest for that walk — nothing else. |
   | Snapshots / present methods / EMC copies? | Not hierarchy SP’s job; they 
stay until the **receiver** MetaClass is replaced or rebuilt. |
   
   Happy to tighten docs or tests further if any row of this matrix still 
disagrees with the behaviour you want for 6.0.
   




> 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
>             Fix For: 6.0.0-beta-1
>
>
> 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