RFR 8206955 MethodHandleProxies.asInterfaceInstance does not support default methods

forax at univ-mlv.fr forax at univ-mlv.fr
Wed Jul 11 20:48:57 UTC 2018



----- Mail original -----
> De: "John Rose" <john.r.rose at oracle.com>
> À: "Peter Levart" <peter.levart at gmail.com>
> Cc: "Remi Forax" <forax at univ-mlv.fr>, "core-libs-dev" <core-libs-dev at openjdk.java.net>
> Envoyé: Mercredi 11 Juillet 2018 21:07:37
> Objet: Re: RFR 8206955 MethodHandleProxies.asInterfaceInstance does not support default methods

> On Jul 11, 2018, at 9:32 AM, Peter Levart <peter.levart at gmail.com> wrote:
>> 
>> Sorry Paul for hijacking the thread, just answering to Remi ...
>> 
>> On 07/11/2018 05:31 PM, Remi Forax wrote:
>>> ----- Mail original -----
>>>> De: "Peter Levart" <peter.levart at gmail.com>
>>>> À: "Paul Sandoz" <paul.sandoz at oracle.com>, "core-libs-dev"
>>>> <core-libs-dev at openjdk.java.net>
>>>> Envoyé: Mercredi 11 Juillet 2018 17:15:09
>>>> Objet: Re: RFR 8206955 MethodHandleProxies.asInterfaceInstance does not support
>>>> default methods
>>>> Hi Paul,
>>>> 
>>>> The patch looks ok. I hope IMPL_LOOKUP has access to all methods (even
>>>> if located in package-private interfaces and/or in concealed packages of
>>>> modules)?
>>>> 
>>>> Just a thought... Would it be possible to implement this API in terms of
>>>> LambdaMetafactory ?
>>>> 
>>>> Regards, Peter
>>> Hi Peter,
>>> not with the current LambdaMetaFactory, the LambdaMetaFactory only accept some
>>> kind of method handles (constant method calls) not all kind of method handles.
>>> 
>>> That said the current implementation of MethodHandleProxies is very raw and not
>>> very efficient, we should use the same idea as the lambda meta factory i.e spin
>>> an anonymous class and use the mechanism of constant patching offer by
>>> unsafe.defineAnonymousClass to inject the method handle into proxy so it will
>>> work with any method handle.
>>> 
>>> For each interface, you should cache the bytecode of the anonymous class you
>>> want to load and use defineAnonymousClass with the method handle each time
>>> asInterfaceInstance is called.
>> 
>> If the generated class used invokeExact on the method handle, bytecode should be
>> generated specifically for each tuple (interface type, method handle type), as
>> the needed conversions of arguments/return values would be specific for each
>> distinct combination of the two types.
>> 
>> ...which would still mean that you would define new anonymous class for each
>> method handle instance, just the bytecodes would be generated once per
>> (interface type, method handle type) combination.
>> 
>> The method handle could then be constant-folded in the generated class, but
>> selection of the underlying proxy class would still be governed by the proxy
>> instance which would be invoked via the interface method on the functional
>> interface. If the proxy instance could not be constant-folded (i.e. was not
>> assigned to static final field and used from it), the combined invocation
>> performance would still not be the same as using invokeExact on the constant
>> method handle, would it?
>> 
>> So perhaps for this API it is more suitable to:
>> 
>> - define the specific proxy class once per (interface type, method handle type)
>> combination (and cache the class itself, not just bytecode)
>> - have that proxy class implement a constructor taking the method handle and
>> assign it to a @Stable instance field
>> - implement the single interface method as parameter/return value conversions
>> around invokeExact on the method handle taken from @Stable instance field
>> 
>> If such proxy instance was constant-folded, so would be the @Stable method
>> handle field, right?
>> 
>> What do you think of this strategy?
> 
> 
> Here's my $0.02:
> 
> - Performance does not seem to be important yet for MHP; Paul is mainly
> concerned with functionality.
> - But such implementation strategies are desirable for performance.
> - The ClassSpecializer mechanism is a good candidate for managing the tuple
> species (it was factored from BMH).
> - I think a reasonable balance would be for a MHP instance to factor into (a) a
> @Stable MH (or List::of), and (b) a @Stable tuple.
> 
> Downsides of factoring MHP into MH and tuple sub-objects:
> 
> - Three heap nodes instead of the minimum single node.
> - Indirection costs.  (Note that constants fold through @Stable links including
> List::of or stable-array.)
> 
> Upsides:
> 
> - Fewer generated classes:  One per interface, one per payload tuple, one per
> MH.
> - Possible shared code with publicly-visible tuple carrier API (probably needed
> for pattern "extractors").

yes, but without value types, tuple carrier still do boxing, but only one  boxing instead of one by arguments. 

> 
> More ideas:
> 
> An alternative is to do a full-custom species for every distinct MHP
> combination.  This might be OK,
> until people start to use MHP in earnest; then it will cause startup problems
> IMO.  Factoring the
> MHP nodes makes the thing scale better in terms of static code, at a modest cost
> in heap flatness.
> 
> If (long-term if) we want to flatten the MPH nodes, but only for hot-path MHPs
> (assuming in the long
> term a wide variety of usage), then we might try Vladimir's MH customization
> hack, which puts
> invocation counters on MH transform chains, and upgrades them in-place to use
> customized LFs
> if they get hot.  Something like this could be done with MHP combos that turn
> hot.  Perhaps existing
> MHPs that get hot could even be internally converted to delegate by one patched
> hop to a flattened
> version of the object, and new MHPs of the same syndrome would just generate the
> flattened
> version from the start, sans secret indirection.

What about my beloved fork-and-patch operator ?
It takes an instance and a bytecode to bytecode function, when calls this operator calls the function with the bytecode of the class of the instance, get the new bytecode, create a new class from it and then patch the class of the instance to new class.
Like with the redefinition, the transformation from old class and the new class should avoid structural transformations like inheritance change, and only allows change of the bytecode of the method.

so by default all MHP on the same interface uses the same code, with a counter inside the MPH, when the MPH becomes hot, the fork-and-patch operator is used to provide another class specialized for underlying mh (using constant patching).

> 
> — John

Rémi


More information about the core-libs-dev mailing list