hg: mlvm/mlvm/jdk: meth: add proxy maker for closures
John Rose
john.r.rose at oracle.com
Thu May 27 18:39:51 PDT 2010
On May 25, 2010, at 2:12 PM, Charles Oliver Nutter wrote:
> On Mon, May 24, 2010 at 8:22 PM, John Rose <john.r.rose at oracle.com> wrote:
>> Right. Here are the degrees of freedom I see at this point:
>> - whether to accept SAM types which are not interfaces (default: yes)
>> - whether to allow a query API for recovering a method handle from a proxy (default: yes)
>> - whether to allow multiple methods to be associated with multiple MHs (default: no)
>
> So you'd wire up a single handle that is dispatched through for all
> incoming cases? Similar to how we (and presumably Groovy, when there
> are no type hints) have a single JRuby "DynamicMethod" object for all
> overloads of a given method name, and it makes a selection based on
> actual types at runtime?
No, it would be some sort of marked tuple of method handles. The most concrete representation would be a pair of arrays: String[] and MethodHandle[].
Or it could be an interface CallResolver which, when queried with Class, String, and MethodType, gives a MethodHandle to call. You get best optimization when the CallResolver logic can be folded away for constant inputs.
Forcing all calls to go through a single MH brings unrelated flows of control together, hurting optimization.
>> Also, the query API in point 2 is harder to get right if you accept the other points.
>
> I don't have a particular need for the query API, though I haven't
> thought through the possibilities yet. For the most part, all I ever
> need is the implementation of some interface or abstract class, and
> after that it can "just" be a class to me.
The query API would allow the wrapper to be unwrapped directly. This is useful if you are ping-ponging the same value between modules that expect a MH or expect a wrapper. You can unwrap by binding one of the wrapper's methods to the wrapper, but it is likely that implementations would produce an ever-lengthening chain of MH indirections, one link for each wrap or unwrap step. That seems like a performance hazard to me.
It is likely people will want to decorate method handles with many additional APIs. (That's what JMH was for.) If we have a standard interface AsMethodHandle or MethodHandleBox, then these decorated method handles can act much like subclassed objects of MH, since any MH-consuming API will work with a decorated MH, simply by calling MethodHandleBox.asMethodHandle. The explicit conversion call is clunkier than an implicit conversion to a supertype, but the effect is the same.
>> Here's chained multi-phase version:
>> class InstanceBuilder<T,R> {
>> InstanceBuilder(List<Class<? extends T>> supers, List<String> names, List<MethodType> types);
>> InstanceBuilder bindReceiver(R receiver);
>> InstanceBuilder bindMethod(String name, MethodHandle method);
>> T newInstance();
>> }
>>
>> And so on...
>
> Yeah, these seem like the right general direction. I'd probably my
> money behind the multi-phase, multi-call version.
Probably the most credible alternative to an InstanceBuilder API is a CallResolver API (see above). I don't know how to choose between them...
> To add another use case to the Scala Function interfaces (which have
> multiple abstract methods): JRuby's own DynamicMethod has a number of
> call paths of different arities; if it were possible to implement all
> of them with handles we'd never have to generate another "invoker"
> again.
Well, you can do an adapter class right now. Is the lack of inlining stopping you?
>> Wrapping method handles adds layers of indirection and boxing hazards as Remi points out.
>
> To be sure...I'd love to move back into the realm of having no users
> and JRuby being in its early research days, so I could commit to being
> Java 7-only. It would make meeting performance challenges a vastly
> simpler proposition. Unfortunately that's pretty hard to justify right
> now, when we're busy trying to get paying customers (who stubbornly
> refuse to use trunk builds of MLVM...imagine the nerve!). :)
Let's keep thinking about commoning up the 6 and 7 source lines. It seems to me that we ought to be able to do better.
>> Another possibility is to ask for an implicit conversion from selected application types X to MethodHandle. That in effect invents a new type relation: delegation. So it's no small matter. It's hard to keep that genie confined to a small bottle.
>
> Being able to generate subclasses would definitely be the more
> straightforward option here; we don't really care about the actual
> type of *any* of our generated DynamicMethod subtypes...we just need a
> way to stitch calls from A to B through a known interface without
> defeating inlining.
If you had more control over inlining, would you be able to make more architectural adjustments to co-adapt the source lines? Maybe it's time for the long-awaited @Inline directive.
>
>>> Perhaps this also helps Java 7 closures work nicer by making it
>>> possible to have a generic Closure or Function superclass completely
>>> independent of java.dyn? Or by making it possible to implement
>>> (faster) Java reflection with fewer generic frames under the covers
>>> via a single abstract supertype populated by indy?
>>
>> The best way I know to accelerate Java reflective invocation is this:
>> java.lang.reflect.Method foo = ...;
>> try { z = MethodHandles.unreflect(foo).invokeGeneric(a, b, c, ...); }
>> catch { ... }
>>
>> For this to be faster, it assumes there is method handle caching on unreflect, which I haven't done. But could be done.
>>
>> For example, you could preserve source compatibility by making a wrapper method to hide MethodHandles.
>
> This would be similar to us lazily generating a direct handle object
> for each reflected method, which we currently support but don't turn
> on by default. In our case, there's an explosion of user-loaded
> classes to deal with, as well as the stickiness of loading custom
> classes that reference classes you might want to unload later...back
> to the ClassLoader hard reference issue again.
>
> But your trick is cleaner; we could potentially have a separate branch
> that goes out to indy code if it's around, and that indy code would
> use this logic. The ideal case would be that both indy and non-indy
> dispatch through the same interface...and then we're back to needing a
> way to implement an interface entirely with method handles again :)
If MHs.unreflect(foo) in the example above is your own API, then you can change the return value (in different builds) to get source compatibility:
class RubyInvocables { static java.dyn.MethodHandle getInvocable(RubyMethod m) { ... } }
// use: RubyInvocables.getInvocable(foo).invokeGeneric(a, b, c, ...);
class RubyInvocables { static RubyInvocableInterface getInvocable(RubyMethod m) { ... } }
interface RubyInvocableInterface {
Object invokeGeneric(); Object invokeGeneric(Object a); Object invokeGeneric(Object a, Object b);
Object invokeGeneric(Object a, Object b, Object c);
Object invokeGeneric(Object a, Object b, Object c, Object... ds); }
// use: RubyInvocables.getInvocable(foo).invokeGeneric(a, b, c, ...);
-- John
More information about the mlvm-dev
mailing list