I appreciate Substrate VM currently lists Method Handles as not supported and I suspect the following exception, which could be found in various issues is due to this reason:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Invoke with MethodHandle argument could not be reduced to at most a single call
Is this a limitation by design that cannot be overcome or would it be possible to fix this problem with at least a workaround, if not a complete solution?
For example, I'm seeing this error when trying to build a native image of a simple application that uses Lucene , due to some core classes of Lucene,
This means Lucene and ElasticSearch won't be candidates for native images. I can see what a large (and great!) piece of work Graal is, it would be good to know if some limitations are bound to stay due to design.
I'm experiencing the same issue and am willing to write code to specifically avoid it, if it is possible.
Coding in Java, what I'd like to be able to do (for two classes A and B) is:
Map in class A (in a static initializer)My order of preference for how to implement this is:
Which of these three approaches are possible? Are there examples/samples of any of them?
I've simplified this to a single class:
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Simple case of dynamic invocation using MethodHandle
* This class is intended to be a Singleton (which may simplify things)
*/
public class MethodHandleTest {
private static final MethodHandleTest instance = new MethodHandleTest();
private Map<String, MethodHandle> methods;
static {
instance.methods = MethodHandleTest.reflect(instance);
}
public static void main(String[] args) {
MethodHandleTest service = MethodHandleTest.instance;
List<Integer> params = Arrays.asList(2, 2);
Integer sum = service.callMethod("add", params);
Integer diff = service.callMethod("sub", params);
System.out.println("Sum is: " + sum);
System.out.println("Difference is: " + diff);
}
public Integer callMethod(String methodName, List<Integer> params) {
Integer result;
final MethodHandle mh = methods.get(methodName);
if (mh != null) {
try {
result = (Integer) mh.invokeWithArguments(params);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
} else {
throw new IllegalArgumentException("Method not found");
}
return result;
}
public Integer add(Integer a, Integer b) {
return a + b;
}
public Integer sub(Integer a, Integer b) {
return a - b;
}
static Map<String, MethodHandle> reflect(Object apiObject) {
Class<?> apiClass = apiObject.getClass();
java.lang.reflect.Method[] methods = apiClass.getDeclaredMethods();
MethodHandles.Lookup lookup = MethodHandles.lookup();
Map<String, MethodHandle> methodMap = new HashMap<>();
for (Method method : methods) {
final int modifiers = method.getModifiers();
if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers)) {
String name = method.getName();
System.out.println(name);
MethodHandle handle;
try {
handle = lookup.unreflect(method).bindTo(apiObject);
} catch (IllegalAccessException e) {
continue; // no access, Skip this one
}
methodMap.put(name, handle);
}
}
return methodMap;
}
}
So to rephrase my questions: Can I change this code to use a different type of MethodHandle or a CallSite or some other mechanism to be able to use dynamic dispatch over a Map (or equiv) data structure that is built by a static initializer? If I can't do it with standard Java, what other options are there?
if you change your example to use java.lang.reflect.Method instead of MethodHandle, i.e., have a Map<String, Method>, it will work out-of-the-box. You also don't need a reflection configuration file because you do the lookup of the Method instances already during image generation.
So the old-school Method way is better to use than MethodHandle -- I'll try it.
Yes, we have full support for Method.invoke. That is much easier to support than MethodHandle because Method.invoke can "just" invoke a method, while MethodHandle allows nested composition, i.e., complex arbitrary expressions that can be changed at run time and therefore not compiled ahead of time.
It's working, thanks!
So, if I want Graal support I need to use Method.invoke, but if I want better performance in the JVM, I should use MethodHandle.invoke*. I suppose I can make it configurable at compile time.
I don't think MethodHandle will get you better performance on the HotSpot VM. Since neither the MethodHandle you are invoking is a compile-time constant nor the types of the arguments you are passing in are constant, both Method and MethodHandle invokes need to do the same argument checks (type checks that the arguments match the signature, unboxing of primitive values, ...).
That's good to hear. Thanks for all your help. I should be publishing this work as Open Source when it's ready. It should be an interesting application for Graal. Thanks again!
@christianwimmer any comments regarding the feasibility of a solution for third party code? Even some cumbersome external hint/config mechanism would be OK. I recently saw mention of a runtime tracing tool (of sorts) for Graal that discovers runtime invocation of types to create configurations for Graal (rough description) A solution like that would also be great for this case. Just speculating here, since this is not my field of expertise.
So IIUC the difficulty in supporting method handles is not the reflective access, as that can be done with the same mechanisms which facilitate core reflection support. The difficulty is in implementing combinators using run-time code generation.
Sure, we can't compile method handle combinators ahead of time ... but do we actually need to compile them at all? Why not just do it the inefficient way?
As a starting point, the basic user-facing functionality of the method handle API mostly appears as if it could be implemented on top of core reflection. Method handle combinators are basically a declarative API for building an AST, and we could implement them as an interpreter over that AST in plain old Java. In turn this could be AOT compiled like any other Java code, it would just be very slow compared to using method handles on the JVM. The graal native image compiler then translates any occurrences of invokevirtual back to a normal method invocation of invokeExact or invoke on our custom method handle implementation.
Are there any critical elements that I'm missing in that analysis? I don't presume I'm offering any new insight here, I only ask to gain a better understanding of the problem and the team's position on it.
I do feel this is an area where there is bit of mismatch where OpenJDK is heading and where Graal is heading. For each release of Java, MethodHandlers and Lookup objects are pushed more and more as the solution and the future by the various OpenJDK architects. But what good does it do if using MethodHandlers and Lookup object disqualifies you from running your application in Graal.
There are lots of different use cases for method handles. Some of them are supported, some of them can be supported, and for some of them support is not really possible (unless you build an image that contains Graal as the JIT compiler or are willing to run really slowly).
Here is a rough picture of the spectrum:
I strongly dispute that slow support is useless. I bet there are plenty of examples of third party libraries / frameworks / etc. out there which use them in places that aren't necessarily going to be performance-critical for the end user. You've received a few bug reports which look like that already, no?
As for including Graal in the image, I'm sure lots of people would appreciate that as an option too. Wouldn't this also be necessary for those who want to include dynamic language runtimes like graal.js in native images and have decent performance? For those people, to then also support fast method handles would presumably cost close to nothing.
Regardless, seems like a reasonable tradeoff to me. Slow method handles, or fast ones and a larger image. Works either way, get the performance you're willing to pay for. Also out of curiosity, could native images link against libgraal as a shared library?
Anyway thanks for the explanation of the state of affairs.
I'm seeing a related issue with this line of code:
https://github.com/scala/scala/pull/8779/files#diff-75c547cedb525c06d7bab20ee470e8eeR25
error output:
Error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: Invoke with MethodHandle argument could not be reduced to at most a single call or single field access. The method handle must be a compile time constant, e.g., be loaded from a `static final` field. Method that contains the method handle invocation: java.lang.invoke.LambdaForm$MH/634445912.invoke_MT(Object, Object)
[bosatsu:3147] analysis: 57,890.61 ms, 2.52 GB
To diagnose the issue, you can add the option --report-unsupported-elements-at-runtime. The error is then reported at run time when the invoke is executed.
Detailed message:
Trace:
at parsing scala.collection.immutable.VM.releaseFence(VM.java:25)
Call path from entry point to scala.collection.immutable.VM.releaseFence():
at scala.collection.immutable.VM.releaseFence(VM.java:25)
at scala.collection.immutable.HashSet$HashSetBuilder.result(HashSet.scala:1283)
at scala.collection.immutable.Set$SetBuilderImpl.result(Set.scala:344)
at scala.collection.immutable.Set$SetBuilderImpl.result(Set.scala:329)
at scala.collection.generic.GenericCompanion.apply(GenericCompanion.scala:57)
at fastparse.parsers.Combinators$Repeat.toString(Combinators.scala:496)
at java.lang.String.valueOf(String.java:2994)
at java.nio.charset.IllegalCharsetNameException.<init>(IllegalCharsetNameException.java:55)
at java.nio.charset.Charset.checkName(Charset.java:315)
at com.oracle.svm.core.jdk.Target_java_nio_charset_Charset.lookup(CharsetSubstitutions.java:78)
at java.nio.charset.Charset.isSupported(Charset.java:505)
at com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_ARRAY:Ljava_nio_charset_Charset_2_0002eisSupported_00028Ljava_lang_String_2_00029Z(generated:0)
Error: Use -H:+ReportExceptionStackTraces to print stacktrace of underlying exception
Error: Image build request failed with exit status 1
The confusing thing to me is that it seems to say that if the MethodHandle is loaded from a static final, it should be okay. Am I misreading that? The field in question is static final.
It seems that you can not use native-image with scala 2.12.12 (or likely 2.13 since this was backported from there).
I opened this issue: https://github.com/scala/bug/issues/12129
MethodHandle support is under development and is being tracked by https://github.com/oracle/graal/issues/2761.
Most helpful comment
There are lots of different use cases for method handles. Some of them are supported, some of them can be supported, and for some of them support is not really possible (unless you build an image that contains Graal as the JIT compiler or are willing to run really slowly).
Here is a rough picture of the spectrum: