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 <[email protected]> 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.
>
>