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.go...@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. > >