On Wed, 5 Apr 2023 04:50:04 GMT, Chen Liang <li...@openjdk.org> wrote:

>> As John Rose has pointed out in this issue, the current j.l.r.Proxy based 
>> implementation of MethodHandleProxies.asInterface has a few issues:
>> 1. Exposes too much information via Proxy supertype (and WrapperInstance 
>> interface)
>> 2. Does not allow future expansion to support SAM[^1] abstract classes
>> 3. Slow (in fact, very slow)
>> 
>> This patch addresses all 3 problems:
>> 1. It implements proxies with one hidden class for each requested interface 
>> and replaced WrapperInstance inheritance with an annotation. This can avoid 
>> unexpected passing of `instanceof`, and avoids the nasty problem of 
>> exporting a JDK interface to a dynamic module to ensure access.
>> 2. This patch obtains already generated classes from a ClassValue by the 
>> requested interface type; the ClassValue can later be updated to compute 
>> implementation generation for abstract classes as well.
>> 3. This patch's generated hidden classes has call performance on par with 
>> those of lambda expressions; the creation performance is slightly less than 
>> that of LambdaMetafactory: 
>> https://jmh.morethan.io/?gist=fcb946d83ee4ac7386901795ca49b224
>> 
>> Additionally, an obsolete `ProxyForMethodHandle` test was removed, for it's 
>> no longer applicable. Tests in `jdk/java/lang/invoke` and 
>> `jdk/java/lang/reflect` pass.
>> 
>> In addition, I have a question: in 
>> [8161245](https://bugs.openjdk.org/browse/JDK-8161245) it seems some fields 
>> can be optimized as seen in 
>> [ciField.cpp](https://github.com/openjdk/jdk/blob/6aec6f3a842ead30b26cd31dc57a2ab268f67875/src/hotspot/share/ci/ciField.cpp#L219).
>>  Does it affect the execution performance of MethodHandle in hidden classes' 
>> Condy vs. MethodHandle in regular final field in hidden classes?
>> 
>> [^1]: single abstract method
>
> Chen Liang has updated the pull request with a new target base due to a merge 
> or a rebase. The incremental webrev excludes the unrelated changes brought in 
> by the merge/rebase. The pull request contains 13 additional commits since 
> the last revision:
> 
>  - Use a dumper for interface instances, other cleanup
>  - Merge branch 'master' into explore/mhp-iface
>  - Merge branch 'master' into explore/mhp-iface
>  - Remove unused JavaLangReflectReflectAccess.invokeDefault
>  - rethrow error
>  - Benchmark compareing to lamda metafactory, various minor updates
>  - Cache the spinned class, loading is still slow
>  - Wrapper instance type is already available
>  - mark records as private
>  - Merge branch 'master' into explore/mhp-iface
>  - ... and 3 more: https://git.openjdk.org/jdk/compare/389698f9...0eec507d

I think the approach taken by this patch is good.

I agree with your analysis about access checking. The access checks are 
preformed when creating the target method handle, and if the target is a 
`@CallerSensitive` method, the caller will be the lookup class. So, it seems 
like it's not possible to e.g. turn a MethodHandle pointing at 
MethodHandles::lookup into an interface instance to get access to a Lookup 
created in the target interface's module.

I also think the approach of creating a new class per method handle is good. 
This is currently the only way to guarantee peak performance as far as I'm 
aware. If only a single class is desired, this can be achieved by using a 
lambda expression that captures the target method handle. Though, that also 
requires knowing the target type statically.

I think this change needs a CSR as well to document the change in behavior. If 
an application currently calls asInterfaceInstance many times for the same 
interface, the amount of classes generated will increase (potentially a lot).

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 187:

> 185:             throw newIllegalArgumentException("a sealed interface", 
> intfc.getName());
> 186:         if (intfc.isHidden())
> 187:             throw newIllegalArgumentException("a hidden interface", 
> intfc.getName());

This extra check should be reflected in the javadoc as well. (there's already a 
note about the interface needing to be public and not sealed).

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 200:

> 198: 
> 199:         // Interface-specific setup
> 200:         var info = INTERFACE_INFOS.get(intfc); // throws 
> IllegalArgumentException

I find the use of `var` confusing here, since the type doesn't appear locally 
and is not well known either.
Suggestion:

        InterfaceInfo info = INTERFACE_INFOS.get(intfc); // throws 
IllegalArgumentException

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 213:

> 211:                     .defineClassAsLookup(true, List.of(mhs));
> 212:             proxy = lookup.findConstructor(lookup.lookupClass(), 
> methodType(void.class))
> 213:                     .asType(methodType(Object.class)).invokeExact();

This can use `invoke` instead of an explicit `asType` and `invokeExact`. It is 
more or less the same.
Suggestion:

            proxy = lookup.findConstructor(lookup.lookupClass(), 
methodType(void.class)).invoke();

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 225:

> 223:     private record LocalMethodInfo(MethodTypeDesc desc, List<ClassDesc> 
> thrown) {}
> 224: 
> 225:     private record InterfaceInfo(@Stable MethodType[] types, Lookup 
> lookup, @Stable byte[] template) {}

Use of `@Stable` here is not needed. We don't constant fold through 
`InterfaceInfo` instances.
Suggestion:

    private record InterfaceInfo(MethodType[] types, Lookup lookup, byte[] 
template) {}

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 256:

> 254:             }
> 255: 
> 256:             var template = createTemplate(desc(intfc), 
> methods.get(0).getName(), infos);

I think using an explicit type would be preferable here as well
Suggestion:

            byte[] template = createTemplate(desc(intfc), 
methods.get(0).getName(), infos);

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 293:

> 291: 
> 292:     // Spin an implementation class for an interface. A new class should 
> be defined for each handle.
> 293:     // constructor parameter: Array[target, mh1, mh2, ...]

This comment seems outdated. The constructor doesn't have parameters atm

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 294:

> 292:     // Spin an implementation class for an interface. A new class should 
> be defined for each handle.
> 293:     // constructor parameter: Array[target, mh1, mh2, ...]
> 294:     private static byte[] createTemplate(ClassDesc ifaceDesc, String 
> name, List<LocalMethodInfo> methods) {

'name' is quite ambiguous here, I suggest `name` -> `methodName`

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 309:

> 307: 
> 308:             // actual implementations
> 309:             int i = 1;

Can you give `i` a more descriptive name here? Also, can you add a comment that 
says why it starts at `1` (e.g. `// +1 skip original target mh`).

src/java.base/share/classes/java/lang/invoke/MethodHandleProxies.java line 321:

> 319:                                         bcb.loadInstruction(kind, slot);
> 320:                                         slot += kind.slotSize();
> 321:                                     }

You could also use `CodeBuilder::parameterSlot` instead of computing the slot 
manually.

test/jdk/java/lang/invoke/MethodHandleProxies/BasicTest.java line 123:

> 121:         assertEquals(Objects.toIdentityString(p1), p1.toString());
> 122:         assertEquals(Objects.toIdentityString(p2), p2.toString());
> 123:     }

tbh I don't really see the point of this test since we don't override 
hashCode/equals/toString?

test/micro/org/openjdk/bench/java/lang/invoke/MethodHandleProxiesAsIFInstance.java
 line 82:

> 80: 
> 81:     @Benchmark
> 82:     public Doable lambdaCreate() throws Throwable {

I know this is pre-existing, but I don't think having both call and create 
benchmarks in the same class is that useful. Since the time that is taken by 
each is very different.

I suggest splitting these benchmarks into 2 files instead: one for the call 
benchmarks, and the other for create, and create + call benchmarks (and then 
change the output time units for the latter).

I think the call benchmark could be fleshed out a bit more as well. It would be 
interesting to see these cases:
1. direct call to `doWork` (this would be the baseline)
2. call through non-constant method handle
3. call through non-constant interface instance created with lambda (existing 
`lambdaCall`)
4. call through non-constant interface instance created with 
MHP::asInterfaceInstance (existing `testCall`)
5. call through constant (`static final`) method handle
6. call through constant (`static final`) interface instance created with lambda
7. call through constant (`static final`) interface instance created with 
MHP::asInterfaceInstance

-------------

PR Review: https://git.openjdk.org/jdk/pull/13197#pullrequestreview-1372885252
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158688369
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158515268
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158513973
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158516812
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158529119
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158551214
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158548324
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158557239
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158555913
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158585395
PR Review Comment: https://git.openjdk.org/jdk/pull/13197#discussion_r1158668111

Reply via email to