Species-static members vs singletons

Brian Goetz brian.goetz at oracle.com
Thu Jun 2 22:24:21 UTC 2016


> Brian,**I see that you mentioned that generic instance methods are 
> messier.
> > The translation for generic instance methods is still somewhat 
> messier (will post separately), but still less messy than if we also 
> had to manage / cache a receiver.
> Is this issue part of that mess? Do you have a solution, or is this an 
> open issue? I tried making m species statics with a receiver argument, 
> but that makes the invocation non-virtual.

Here’s some more notes on how we might translate generic methods using 
species-static methods and nested classes.


        General strategy

  *

    A {static,species,instance} generic method m() in |Foo| is desugared
    into a species method |m()| in a {static,species,instance} nested
    class |Foo$m|.

  *

    The accessibility of the method |m()| is lifted onto the class |Foo$m|.

  *

    Foo also gets an erased bridge, that redirects to the erased
    invocation of the generic method (binary compatibility only.)

  *

    A generic method is invoked with indy. The indy call site statically
    captures the type parameters for the invocation, some representation
    of the owning class Foo, and the method m(). The dynamic argument
    list captures the Foo-valued receiver (for instance methods) and the
    arguments to the generic method.


        Goals

  *

    Dispatch should be fast :)

  *

    It would be nice if the name-mangling strategy (|Foo$m|) were
    private to |Foo| — that it does not appear in bytecode of Foo’s
    clients or subclasses.

  *

    It would be ideal if we could express what we want with bytecode
    alone, not indy, but that does not seem possible in all cases at
    this time.


        Static methods

Given source code:

|class Foo<any T> { ACC static<any U> void m(U u) { } } |

We translate this as:

|@Generic class Foo { // for binary compatibility only <where ref T> 
@Bridge ACC static void m(Object u) { Foo.Foo$m<erased>m(u); } @Generic 
ACC static class Foo$m<any U> { ACC species void m(U u) { } } } |

An invocation |Foo.<U>m(u)| (where U is known statically) can be 
translated in bytecode as

|invokespecies Foo.Foo$m<U>.m(U) |

However, since it was a goal to not let the mangled name |Foo$m| leak 
into client code, we can easily wrap this with an indy:

|invokedynamic GenericStaticMethod[Foo, "m", descriptor, U](u) ^bootstrap 
^static args ^dyn args |

and let the bootstrap put together the class name |Foo$m| from 
constituent parts (the bootstrap and the compiler share a conspiratorial 
connection, but the client bytecode doesn’t participate). Since 
everything is static at the call site, we can link to a 
|ConstantCallSite| that always dispatches to an |MH[invokespecies 
Foo$m<U>.m(U)]|.


        Species methods

Species-static generic methods are translated almost the same way; given 
class

|class Foo<any T> { ACC species<any U> void m(T t, U u) { } } |

we translate as

|@Generic class Foo { // for binary compatibility only <where ref T> 
@Bridge ACC species void m(Object t, Object u) { 
Foo<erased>.Foo$m<erased>m(t, u); } @Generic ACC species class Foo$m<any 
U> { ACC species void m(T t, U u) { } } } |

and an invocation |Foo<T>.<U>m(T,U)| is translated as

|invokespecies Foo<T>.Foo$m<U>.m(T, U) |


        Instance methods

Our translation strategy — desugaring to a helper class — introduces 
some challenges in instance method dispatch.

|class Foo<any T> { ACC <any U> void m(T t, U u) { } } class Bar<any T> 
extends Foo<T> { @Override ACC <any U> void m(T t, U u) { } } |

I am proposing we translate this as:

|@Generic class Foo { // for binary compatibility only <where ref T> 
@Bridge ACC void m(Object t, Object u) { this.Foo$m<erased>m(t, u); } 
@Generic ACC species class Foo$m<any U> { ACC species void m(Foo<T> 
outer, T t, U u) { } } } @Generic class Bar { // for binary 
compatibility only <where ref T> @Bridge ACC void m(Object t, Object u) 
{ this.Bar$m<erased>m(t, u); } @Generic ACC species class Bar$m<any U> { 
ACC species void m(Bar<T> outer, T t, U u) { } } } |

In this proposal, |Bar$m| does not extend |Foo$m|; this is to avoid 
leaking dependence on desugaring in subclass bytecode. The 
implementation methods in |Xxx$m| take an extra “outer” argument, which 
is the receiver for the instance generic method invocation. The use of 
species-static methods for the implementation methods mean that we need 
not maintain instances of Foo$m, but instead can pass the actual 
Fooreceiver directly to the implementation.

For an invocation:

|Foo<T> f = ... f.<U>m(t,u) |

We translate with indy as:

|invokedynamic InstanceStaticMethod[ParamType[Foo,T], "m", descriptor, 
U](r,t,u) |

The static receiver type — here |Foo<T>| — is a static parameter to the 
bootstrap. Let’s call this class SR (the actual target will be |Xxx$m| 
where |Xxx| may be SR or a subclass of SR.) The dynamic receiver (a 
|Foo<T>|) is passed in the dynamic argument list as |r|.

The linking of the callsite is somewhat complex, but should optimize 
reasonably well. It proceeds as follows:

  * For each (static receiver class, method, specialization args) — all
    static properties of the callsite — there is a dispatch table, which
    is found statically at linkage time and stored as part of the
    callsite state;
  * The dispatch table is a |ClassValue<MethodHandle>|, which maps the
    dynamic receiver type (a subtype of SR) to a |MethodHandle| for the
    |invokespecies| implementation method;
  * Invocation performs
    |ClassValue.get(receiver.getClass()).invokeExact(receiver, args)|
  * This dispatch can be optionally wrapped with a PIC against
    |receiver.getClass()|

The first thing the bootstrap must do (at linkage time) is compute SR. 
This is done by taking the owner class |Foo<T>|, along with the method 
name and descriptor, computing the name-mangled class |Foo<T>.Foo$m|, 
call it SR’. We then take this class and compute 
|SR=Crass.forSpecialization(SR', U)|. (Both of these computations are 
done with the classloader for |Foo<T>|, and we should check that both 
SR’ and SR share the classloader with |Foo<T>|.) SR corresponds to the 
fully specialized class |Foo<T>.Foo$m<U>|.

Once we’ve computed SR, we have to find the dispatch table for SR, which 
is a multi-step lookup first by |ClassLoader| (to allow for classloader 
unloading) and then by SR, which results in a |DispatchTable| mapping a 
dynamic receiver type |R| to a fully specialized desugared 
species-static MH.

|class DispatchTable extends ClassValue<MethodHandle> { ... } class 
MetaDispatchTable extends ClassValue<DispatchTable> { ... } private 
static final WeakHashMap<ClassLoader, MetaDispatchTable> mdt = ... |

We compute |mdt.computeIfAbsent(SR.getClassLoader(), ...).get(SR)| and 
store that as |DT| in the |CallSite|. The |ClassValue| implementation 
for |MetaDispatchTable| simply creates a new |ClassValue<MH>| entry.

The callsite target is linked to the following logic:

|Class<?> R = r.getClass(); DT.get(R).invokeExact(r, args) |

The |computeValue()| method of |DispatchTable| does the meat of the 
work. For the receiver type R, we have to find the corresponding |Xxx$m| 
class, which might be declared in a superclass of R, specialize it to 
the desired method type parameters, and look up (findSpecies) the 
appropriate specialized MH. (Finding the corresponding |Xxx$m| class 
could be done by walking the hierarchy directly, or by doing a 
|MethodHandle.resolveOrFail| on the erased bridge.)

The cost of an invocation is one |ClassValue| lookup, plus the overhead 
of folding the arguments together appropriately and doing a MH invoke. 
The above logic seems representable as a single method handle expression 
using |fold| and |filter| combinators, but if not, might also introduce 
some varargs spreading/collecting overhead. (It could be further 
optimized by wrapping the the result of DT.get(R) with a PIC on R.) This 
doesn’t seem so bad.

The first time a given target is resolved (a given combination of 
enclosing |Foo<T>|, |m(...)|, type arguments U, and receiver class), a 
relatively expensive linkage step is performed — but is then cached in a 
table specific to the members involved, not the call site — so this 
should stabilize quickly.


        Interface methods

Interface methods add an additional layer, but do not change the story 
fundamentally. If I have:

|interface I<any T> { ACC <any U> void m(T t, U u) { } } class Foo<any T> 
implements I<T> { @Override ACC <any U> void m(T t, U u) { } } |

then we need to generate an artifact |I$m| artifact as we do with 
classes. When linking an invocation which static receiver is a 
specialization of an interface rather than a class, we compute |I$m| as 
our SR, and proceed as before. (In this case, our linkage strategy 
should probably use |resolveOrFail| rather than manual hierarchy 
walking, so we should probably do this in both cases.) Additionally, we 
may need to do a check that the |Xxx| class corresponding to the 
resolved |Xxx$m| holder class actually implements |I<T>| — again this 
can be done at linkage time, not dispatch time.


        Default methods

If we do our dispatch using |resolveOrFail| against the erased bridges, 
and the method is not implemented in the receiver’s superclass 
hierarchy, then I believe that resolution will hand us the MH for the 
default? If, so, we’re good; we resolve to the default, just like any 
other implementation.

One integrity risk here is that the |Xxx$m| hierarchy is properly 
aligned to the |Foo| hierarchy. I think we can validate that (at the 
time we lazily populate the dispatch table) simply by checking that the 
resolved erased target |Xxx.m()| and the corresponding specialized class 
|Xxx$m.m()| share a nest (and hence derive from the same source class.)


More information about the valhalla-spec-observers mailing list