Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Can Graal handle Method Handlers in the future? #955

Closed
grumpyrodriguez opened this issue Feb 4, 2019 · 16 comments
Closed

Can Graal handle Method Handlers in the future? #955

grumpyrodriguez opened this issue Feb 4, 2019 · 16 comments

Comments

@grumpyrodriguez
Copy link

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.

@msgilligan
Copy link
Contributor

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:

  1. Enumerate (and possibly filter) the methods in a class B and store in a Map in class A (in a static initializer)
  2. Dynamically call one or more of those methods at runtime from class A.

My order of preference for how to implement this is:

  1. Find a way to code it in standard Java so it's compatible with Graal
  2. Use annotations or some other Graal-specific tools to make it work
  3. Use some kind of code generation tool to do it

Which of these three approaches are possible? Are there examples/samples of any of them?

@msgilligan
Copy link
Contributor

msgilligan commented Mar 18, 2019

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;
    }
}

@msgilligan
Copy link
Contributor

msgilligan commented Mar 18, 2019

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?

@christianwimmer
Copy link
Member

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.

@msgilligan
Copy link
Contributor

So the old-school Method way is better to use than MethodHandle -- I'll try it.

@christianwimmer
Copy link
Member

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.

@msgilligan
Copy link
Contributor

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.

@christianwimmer
Copy link
Member

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, ...).

@msgilligan
Copy link
Contributor

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!

@grumpyrodriguez
Copy link
Author

@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.

@eliasvasylenko
Copy link

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.

@kaspernielsen
Copy link

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.

@christianwimmer
Copy link
Member

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:

  • Method handles used for lambda expression and VarHandle are fully supported. Future planned usages of such method handles for "gluing things together" in future Java versions can probably be supported the same way. For example, all method handles planned for project Panama's native interface look pretty easy to support.
  • Method handles initialized in a class initializer and then used just to invoke a method or access a field are fully supported. Use cases for that are method handles initialized once to access private memembers.
  • Method handles initialized in a class initializer that do complicated operations (filtering, dispatching to more than one method, ...) can be supported as long as the method handle chain is not changed at run time.
  • Method handles change dynamically at run time (for example as an implementation mechanism for dynamic language implementations) can not be supported efficiently without a JIT compiler available at run time. Slow support (interpretation of the method handle chain) is possible, but the whole point of method handles is fast execution so slow support is more or less useless. We can do high performance support using JIT compilation and the same mechanisms used for our Truffle language implementation framework.

@eliasvasylenko
Copy link

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.

@johnynek
Copy link

johnynek commented Aug 27, 2020

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: scala/bug#12129

sergey-latacora added a commit to latacora/native-cogaws that referenced this issue Sep 21, 2020
relevant issues:
oracle/graal#2214
oracle/graal#955

full exception (fixed):

{:cognitect.anomalies/category :cognitect.anomalies/fault, :cognitect.aws.client/throwable #error {
 :cause 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.Invokers$Holder.invoke_MT(Object, Object, Object, Object)
 :via
 [{:type com.oracle.svm.core.jdk.UnsupportedFeatureError
   :message 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.Invokers$Holder.invoke_MT(Object, Object, Object, Object)
   :at [com.oracle.svm.core.util.VMError unsupportedFeature VMError.java 86]}]
 :trace
 [[com.oracle.svm.core.util.VMError unsupportedFeature VMError.java 86]
  [clojure.lang.Reflector canAccess Reflector.java 49]
  [clojure.lang.Reflector toAccessibleSuperMethod Reflector.java 84]
  [clojure.lang.Reflector lambda$invokeInstanceMethod$0 Reflector.java 99]
  [java.util.stream.ReferencePipeline$3$1 accept ReferencePipeline.java 195]
  [java.util.ArrayList$ArrayListSpliterator forEachRemaining ArrayList.java 1655]
  [java.util.stream.AbstractPipeline copyInto AbstractPipeline.java 484]
  [java.util.stream.AbstractPipeline wrapAndCopyInto AbstractPipeline.java 474]
  [java.util.stream.ReduceOps$ReduceOp evaluateSequential ReduceOps.java 913]
  [java.util.stream.AbstractPipeline evaluate AbstractPipeline.java 234]
  [java.util.stream.ReferencePipeline collect ReferencePipeline.java 578]
  [clojure.lang.Reflector invokeInstanceMethod Reflector.java 101]
  [cognitect.aws.protocols.rest$serialize_uri$fn__12861 invoke rest.clj 29]
  [clojure.string$replace_by invokeStatic string.clj 69]
  [clojure.string$replace invokeStatic string.clj 106]
  [cognitect.aws.protocols.rest$serialize_uri invokeStatic rest.clj 22]
  [cognitect.aws.protocols.rest$serialize_uri invoke rest.clj 22]
  [clojure.core$update invokeStatic core.clj 6202]
  [cognitect.aws.protocols.rest$build_http_request invokeStatic rest.clj 174]
  [cognitect.aws.protocols.rest_xml$fn__12989 invokeStatic rest_xml.clj 11]
  [cognitect.aws.protocols.rest_xml$fn__12989 invoke rest_xml.clj 11]
  [clojure.lang.MultiFn invoke MultiFn.java 234]
  [cognitect.aws.client$send_request$fn__12660$state_machine__7879__auto____12687$fn__12689$fn__12703 invoke client.clj 99]
  [cognitect.aws.client$send_request$fn__12660$state_machine__7879__auto____12687$fn__12689 invoke client.clj 96]
  [cognitect.aws.client$send_request$fn__12660$state_machine__7879__auto____12687 invoke client.clj 84]
  [clojure.core.async.impl.ioc_macros$run_state_machine invokeStatic ioc_macros.clj 978]
  [clojure.core.async.impl.ioc_macros$run_state_machine_wrapped invokeStatic ioc_macros.clj 980]
  [cognitect.aws.client$send_request$fn__12660 invoke client.clj 84]
  [clojure.lang.AFn run AFn.java 22]
  [java.util.concurrent.ThreadPoolExecutor runWorker ThreadPoolExecutor.java 1128]
  [java.util.concurrent.ThreadPoolExecutor$Worker run ThreadPoolExecutor.java 628]
  [clojure.core.async.impl.concurrent$counted_thread_factory$reify__3362$fn__3363 invoke concurrent.clj 29]
  [clojure.lang.AFn run AFn.java 22]
  [java.lang.Thread run Thread.java 834]
  [com.oracle.svm.core.thread.JavaThreads threadStartRoutine JavaThreads.java 517]
  [com.oracle.svm.core.posix.thread.PosixJavaThreads pthreadStartRoutine PosixJavaThreads.java 192]]}}
@cstancu
Copy link
Member

cstancu commented Sep 22, 2020

MethodHandle support is under development and is being tracked by #2761.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

7 participants