Updated VM-bridges document

Karen Kinnear karen.kinnear at oracle.com
Fri Apr 12 22:21:37 UTC 2019


I need to do many more additional examples offline.
I appreciate your trying to make overriding of forwarders simpler for the jvm.
I would like to continue to explore the option having the jvm do the calculation of overriding
both direct and indirect forwarders until we’ve worked more examples.

If we can find a way to do it, it helps with backward compatibility
   - old clients with old receivers don’t go through adaptors - so they miss adaptations
that could either throw an exception or potentially lose data through narrowing.
   - same issue for reflection - Class.getDeclaredMethods() which just returns local methods
     - would be nice if we could not lose existing method names here

I am also exploring invoke local with explicit local name of method - so we can try
to reduce loops - I believe there will be steps at which we will need to identify loops and throw
exceptions or not create a reverser.

That said, it is getting more complex, so glad you are exploring alternatives.

Link below spells out a bit more the rule I am exploring for creating reversers,
with both the example below and another example (Example II)  which has three migration steps,
F <: E <: D
all start with m(Date, Time)
step 1: D m(Date, Time) -> D.m(LDT, Time)
step 2: E.m(Date, Time) -> E.m( Date, LDT)
step 3: D.m(LDT, Time) -> D.m(LDT, LDT)

http://cr.openjdk.java.net/~acorn/Forwarders.pdf

thanks,
Karen

> On Apr 12, 2019, at 11:44 AM, Brian Goetz <brian.goetz at oracle.com> wrote:
> 
> 
>  
>> A VM perspective:
>> 
>> invocation
>> dynamic receiver
>> resolution
>> NOT invoked
>> selection:
>> actual execution
>> invokevirtual D::m(LDT)
>> D
>> D.m(LDT)
>> D.m(LDT)
>> invokevirtual D::m(LDT)
>> E
>> D.m(LDT)
>> E.m(LDT) 
>> reverser: adapt LDT->Date
>>                invoke local E.m(Date)
>>                if return had changed, adapt return back
>> invokevirtual D::m(Date)
>> D
>> D.m(Date)
>> D.m(Date)
>> forwarder: adapt Date->LDT
>>                  invoke local m(LDT)
>>                  if return had changed, adapt
>> invokevirtual D.m(Date)
>> E
>> D.m(Date)
>> E.m(Date)
>> invokevirtual E.m(LDT)
>> E
>> E.m(LDT)
>> reverser)
>> E.m(LDT): 
>> reverser: adapt LDT->Date
>>                invoke local E.m(Date)
>>                if return had changed, adapt return back
>> invokevirtual E.m(Date)
>> E
>> E.m(Date)
>> E.m(Date) // original - unchanged behavior
>> 
> 
> Let me try from the other direction, using the JVMS terminology rather than appealing to as-if.  Where I think we're saying slightly different things is in the interpretation of the lines I colored in blue above (hope the formatting came through.)  You are talking about the E.m(Date) that appears in E.class (good so far).  But I'm talking about the _members_ of E.  And the E.m(Date) that appears in E.class should _not_ be considered a (new-to-E) member of E.      Instead, that E.m(Date) gives rise to a synthetic member E.m(LDT).   I have colored two cases in red because I think this is where our assumptions really parted ways; will come back to this at the bottom.  
> 
> Here's why I'm harping on this distinction; we mark methods as "forwarders" and do something special when we see something override a forwarder.  Taking the same hierarchy:
> 
>     // before
>     class D {
>         void m(Date) { }
>     }
> 
>     class E extends D { 
>         void m(Date) { }
>     }
> 
>     // middle -- D migrates, but E not yet
>     class D {
>         void m(LDT) { }
>         @Forwarding( m(LDT) } void m(Date);
>     }
> 
>     class E extends D { 
>         void m(Date) { }
>     }
> 
>     // after -- E finally gets the memo
>     class D {
>         void m(LDT) { }
>         @Forwarding( m(LDT) } void m(Date);
>     }
> 
>     class E extends D { 
>         void m(LDT) { }
>     }
> 
> Now, let's draw inheritance diagrams (these are not vtables, they are member tables).  I'll use your notation, where I think D.m(X) means "the Code attribute declared in D for m(X)".  
> 
> Before
> m(Date)
> D
> D.m(Date)
> E
> E.m(Date)
> 
> This part is easy; D has m(Date), and E overrides it.  
> 
> Middle
> m(LDT)
> m(Date)
> D
> D.m(LDT)
> forwarder -> m(LDT)
> E
> reverser adapted from E.m(Date)
> inherits forwarder
> 
> Now, both D and E have both m(Date) and m(LDT).  D has a real method for m(LDT), and a forwarder for m(Date).  E has an m(Date), which we see overrides a forwarder.  So we adapt it to be an m(LDT), but we consider E to have inherited the forwarder from D.  I'll come back to this in a minute.
> 
> After	m(LDT)
> m(Date)
> D
> D.m(LDT)
> forwarder -> m(LDT)
> E
> E.m(LDT)
> inherits forwarder
> 
> In this nirvana, there is a forwarder still, but it doesn't affect E, because E has already gotten the memo.  It sits around purely in the case that someone calls m(Date).  
> 
> OK, so why am I saying that membership has to be tilted this way?  Let's go back to the middle case, and add
> 
>     class F extends E {
>         void m(Date) { } // still didn't get the memo
>     }
> 
> 
> Middle
> m(LDT)
> m(Date)
> D
> D.m(LDT)
> forwarder -> m(LDT)
> E
> reverser adapted from E.m(Date)
> inherits forwarder
> F
> reverser adapted from F.m(Date)
> inherits forwarder
> 
> When we go to compute members, I want to see that _F.m(Date) overrides a forwarder too_.  If we merely put E.m(Date) in the (E, m(Date)) box, then it looks like F is overriding an ordinary member, and no reverser is generated.  (Or, we have to keep walking up the chain to see if E.m(Date) in turn overrides a forwarder -- yuck, plus, that makes forwarder-overrides-forwarder even messier.  
> 
> Now, back to your table.  The above interpretation of what is going on comes to the same answer for all of the rows of your table, except these:
> 
>> 
>> invocation
>> dynamic receiver
>> resolution
>> NOT invoked
>> selection:
>> actual execution
>> invokevirtual D.m(Date)
>> E
>> D.m(Date)
>> E.m(Date)
>> invokevirtual E.m(Date)
>> E
>> E.m(Date)
>> E.m(Date) // original - unchanged behavior
>> 
> 
> You are thinking "E has a perfectly good m(Date), let's just select that".  Makes sense, but the cost of that is that it complicates calculation of membership and overriding.  I think I am content to let invocations of m(Date) on receivers of type E go through both rounds of adaptation: forward the call (with adaptation) to m(LDT), which, in the case of E, does the reverse adaptations and ends up at the original Code attribute of E.m(Date).  This sounds ugly (and we'd need to justify some potential failures) but leads us to a simpler interpretation of migration.  
> 
> In your model, we basically have to split the box in two: 
> 
> 
> After	m(LDT)
> m(Date)
> D
> D.m(LDT)
> forwarder -> m(LDT)
> E
> E.m(LDT)
> E.m(Date), but also is viewed as a forwarder by subclasses
> 
> I think its a good goal, but I was trying to eliminate that complexity by accepting the round-trip adaptation -- which goes away when E gets the memo.  
> 
> 



More information about the valhalla-spec-observers mailing list