Updated VM-bridges document

Brian Goetz brian.goetz at oracle.com
Fri Apr 12 15:44:41 UTC 2019



> 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 declaredin 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