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

Support GraalVM 22.0 #63

Closed
J000Z opened this issue Feb 18, 2022 · 16 comments
Closed

Support GraalVM 22.0 #63

J000Z opened this issue Feb 18, 2022 · 16 comments
Labels

Comments

@J000Z
Copy link

J000Z commented Feb 18, 2022

Hi,

I wan't able to compile opencv-stiching-native with latest GraalVM 22.0. Is there a plan to support that?

Following is part of the errors I got. Do you have any idea what these errors are? Can they be fixed by configuring GraalVM?

48 fatal errors detected:
Fatal error: com.oracle.graal.pointsto.util.AnalysisError$ParsingError: Error encountered while parsing com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_VARARGS_Unsafe_compareAndSwapLong_d44cf3dbd79fe11bdbf37794e85c4e49eea12682(com.oracle.svm.jni.nativeapi.JNIEnvironment, com.oracle.svm.jni.nativeapi.JNIObjectHandle, com.oracle.svm.jni.nativeapi.JNIMethodId, com.oracle.svm.jni.nativeapi.JNIObjectHandle, long, long, long) 
Parsing context: <no parsing context available> 

	at com.oracle.graal.pointsto.util.AnalysisError.parsingError(AnalysisError.java:141)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.createTypeFlow(MethodTypeFlow.java:315)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.ensureTypeFlowCreated(MethodTypeFlow.java:286)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.addContext(MethodTypeFlow.java:107)
	at com.oracle.graal.pointsto.PointsToAnalysis$1.run(PointsToAnalysis.java:461)
	at com.oracle.graal.pointsto.util.CompletionExecutor.executeCommand(CompletionExecutor.java:195)
	at com.oracle.graal.pointsto.util.CompletionExecutor.lambda$executeService$0(CompletionExecutor.java:179)
	at java.base/java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1426)
	at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:290)
	at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1020)
	at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1656)
	at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1594)
	at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:183)
Caused by: org.graalvm.compiler.debug.GraalError: unimplemented
	at jdk.internal.vm.compiler/org.graalvm.compiler.debug.GraalError.unimplemented(GraalError.java:39)
	at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext.pop(GraphBuilderContext.java:106)
	at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeAccessPlugin.createUnsafeAccess(StandardGraphBuilderPlugins.java:1226)
	at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeCompareAndSwapPlugin.apply(StandardGraphBuilderPlugins.java:1362)
	at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.execute(InvocationPlugin.java:176)
	at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.tryInvocationPlugin(PEGraphDecoder.java:1075)
	at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.trySimplifyInvoke(PEGraphDecoder.java:985)
	at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.handleInvoke(PEGraphDecoder.java:946)
	at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.GraphDecoder.processNextNode(GraphDecoder.java:788)
	at com.oracle.graal.pointsto.phases.InlineBeforeAnalysisGraphDecoder.processNextNode(InlineBeforeAnalysis.java:242)
	at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.GraphDecoder.decode(GraphDecoder.java:529)
	at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.decode(PEGraphDecoder.java:822)
	at com.oracle.graal.pointsto.phases.InlineBeforeAnalysis.decodeGraph(InlineBeforeAnalysis.java:99)
	at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.parse(MethodTypeFlowBuilder.java:175)
	at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.apply(MethodTypeFlowBuilder.java:358)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.createTypeFlow(MethodTypeFlow.java:297)
	... 11 more

@saudet
Copy link
Member

saudet commented Feb 18, 2022

You'll need to update the version of the plugin in the pom.xml file as well.

@saudet
Copy link
Member

saudet commented Mar 21, 2022

Actually, it looks like we need to add a couple of allowUnsafeAccess to the config files, see oracle/graal#2694. Please give it a try!

@J000Z
Copy link
Author

J000Z commented May 3, 2022

I added a feature

class RuntimeReflectionRegistrationFeature implements Feature {
    public void beforeAnalysis(BeforeAnalysisAccess access) {
        try {
            RuntimeReflection.register(Unsafe.class);
            JNIRuntimeAccess.register(Unsafe.class);

            for (Method declaredMethod : Unsafe.class.getDeclaredMethods()) {
                JNIRuntimeAccess.register(declaredMethod);
                RuntimeReflection.register(declaredMethod);
            }
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
}

But it doesn't help. Do you know where bytedeo uses these unsafe calls?

@saudet
Copy link
Member

saudet commented May 3, 2022

For JavaCPP itself, it's all in the indexer package: https://github.com/bytedeco/javacpp/search?q=unsafe

That's not something we can set at runtime, it's something that needs to be set at build time.
JavaCPP generates config files for GraalVM Native Image at build time around here:
https://github.com/bytedeco/javacpp/blob/master/src/main/java/org/bytedeco/javacpp/tools/Generator.java#L1995-L2026

@J000Z
Copy link
Author

J000Z commented May 8, 2022

The issue seems to be that GraalVM has problem analyzing Unsafe method calls from JNI c/c++ code, which are triggered by "name" : "sun.misc.Unsafe" items in "jni-config.json"

Does JavaCPP generate c/c++ code that calls Unsafe methods?

@saudet
Copy link
Member

saudet commented May 8, 2022

No, but I don't think this is issue. If that fixes it though, please open a pull request to that effect.

@agibsonccc
Copy link

agibsonccc commented Jun 12, 2022

@saudet I'm running in to this as well. I tried adding the relevant allowUnsafeAccess to all the raw indexers and I still seem to get the same error. Do you know if we might need to add this to any presets that potentially use the RawIndexer?

I looked at a relevant netty fix and they seemed to just disable their unsafe mechanism. I'm wondering if we might need to do something similar in javacpp?

I've also tried attaching a debugger to the native compilation process and my understanding is that they now do substitutions for any unsafe calls then that seems to throw the error.

The unimplemented error itself I'm not sure about. It could either be unimplemented behavior for unsafe in graal itself or maybe it's just noise.

For context, I'm trying this on java 11 and 17 graalvm 22 as well.

saudet added a commit to bytedeco/javacpp that referenced this issue Jun 12, 2022
@saudet
Copy link
Member

saudet commented Jun 12, 2022

Indeed, it looks like it doesn't like it anymore when we have anything related to sun.misc.Unsafe in the config files, so I've tried to strip that out, and that fixes the compile error, plus I can still access sun.misc.Unsafe! Well, whatever, I've pushed that in commit
bytedeco/javacpp@c6c52b6. Please give it a try with the snapshots: http://bytedeco.org/builds/

@agibsonccc
Copy link

agibsonccc commented Jun 12, 2022

@saudet thanks. Looking at this further it appears the same problem happens with graalvm 21.3 as well. I'm going to try to go down the versions at least to see when this all was triggered.

Edit: I get a different error on 20.x so it's from 21.3 at least not just 22.

@agibsonccc
Copy link

agibsonccc commented Jun 12, 2022

Still seeing this even with snapshots:

  cturedGraph:110899{AnalysisMethod<com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_VARARGS_Unsafe_compareAndExchangeBooleanRelease_03d52ce73019ea0811e9c74206a7d2672ba736e2 -> com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod@5a9b96cb>}
    Exception raised in scope ForkJoinPool-2-worker-25.InlineBeforeAnalysis.PEGraphDecode: org.graalvm.compiler.debug.GraalError: unimplemented
        at jdk.internal.vm.compiler/org.graalvm.compiler.debug.GraalError.unimplemented(GraalError.java:39)
        at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext.pop(GraphBuilderContext.java:104)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeAccessPlugin.createUnsafeAccess(StandardGraphBuilderPlugins.java:1276)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeCompareAndSwapPlugin.apply(StandardGraphBuilderPlugins.java:1394)
        at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.execute(InvocationPlugin.java:300)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.tryInvocationPlugin(PEGraphDecoder.java:1065)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.trySimplifyInvoke(PEGraphDecoder.java:975)
        at jdk.inte

I think the source might be the UnsafeIndexers. The fix I saw in netty was to make it optional: netty/netty@ff3858d

Not sure if it's possible to disable unsafe usage in the indexers or not. For completeness, here's how I attempted to allow unsafe access for all the indexers:

  {
    "name": "org.bytedeco.javacpp.indexer.ByteRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.UByteRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.BooleanRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.DoubleRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.CharRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.FloatRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.HalfRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.IntRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.LongRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.ShortRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.Bfloat16RawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccturedGraph:110899{AnalysisMethod<com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_VARARGS_Unsafe_compareAndExchangeBooleanRelease_03d52ce73019ea0811e9c74206a7d2672ba736e2 -> com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod@5a9b96cb>}
    Exception raised in scope ForkJoinPool-2-worker-25.InlineBeforeAnalysis.PEGraphDecode: org.graalvm.compiler.debug.GraalError: unimplemented
        at jdk.internal.vm.compiler/org.graalvm.compiler.debug.GraalError.unimplemented(GraalError.java:39)
        at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext.pop(GraphBuilderContext.java:104)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeAccessPlugin.createUnsafeAccess(StandardGraphBuilderPlugins.java:1276)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeCompareAndSwapPlugin.apply(StandardGraphBuilderPlugins.java:1394)
        at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.execute(InvocationPlugin.java:300)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.tryInvocationPlugin(PEGraphDecoder.java:1065)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.trySimplifyInvoke(PEGraphDecoder.java:975)
        at jdk.intecess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.UShortRawIndecturedGraph:110899{AnalysisMethod<com.oracle.svm.jni.JNIJavaCallWrappers.jniInvoke_VARARGS_Unsafe_compareAndExchangeBooleanRelease_03d52ce73019ea0811e9c74206a7d2672ba736e2 -> com.oracle.svm.jni.hosted.JNIJavaCallWrapperMethod@5a9b96cb>}
    Exception raised in scope ForkJoinPool-2-worker-25.InlineBeforeAnalysis.PEGraphDecode: org.graalvm.compiler.debug.GraalError: unimplemented
        at jdk.internal.vm.compiler/org.graalvm.compiler.debug.GraalError.unimplemented(GraalError.java:39)
        at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext.pop(GraphBuilderContext.java:104)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeAccessPlugin.createUnsafeAccess(StandardGraphBuilderPlugins.java:1276)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.StandardGraphBuilderPlugins$UnsafeCompareAndSwapPlugin.apply(StandardGraphBuilderPlugins.java:1394)
        at jdk.internal.vm.compiler/org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.execute(InvocationPlugin.java:300)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.tryInvocationPlugin(PEGraphDecoder.java:1065)
        at jdk.internal.vm.compiler/org.graalvm.compiler.replacements.PEGraphDecoder.trySimplifyInvoke(PEGraphDecoder.java:975)
        at jdk.intexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.ULongRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  },
  {
    "name": "org.bytedeco.javacpp.indexer.UIntRawIndexer",
    "allDeclaredFields":true,
    "queryAllDeclaredMethods":true,
    "queryAllDeclaredConstructors":true,
    "allDeclaredMethods":true,
    "allDeclaredConstructors":true,
    "fields":[{"name":"RAW", "allowUnsafeAccess" : true},
      {"name":"pointer", "allowUnsafeAccess" : true},
      {"name":"base", "allowUnsafeAccess" : true},
      {"name":"size", "allowUnsafeAccess" : true}
    ]

  }

In the mean time I'll keep digging through the 21.x release notes to see what's going on. I'm now wondering if maybe there's an indirect change that makes this happen.

My hunch is this still might have something to do with their substitutions. That's what it keeps referencing in the errors. I wonder if their unsafe implementation is still incomplete? In their PR where they supposedly fixed this issue they missed a few.

@saudet
Copy link
Member

saudet commented Jun 12, 2022

You need to make sure that all the JSON files you pass to it do not contain "Unsafe" anywhere. How are you making sure of that?

@agibsonccc
Copy link

I'm manually configuring the file paths. Let me get rid of all declarations.

@saudet
Copy link
Member

saudet commented Jun 12, 2022

I'm telling you, it works here. I can't fix what isn't broken! You'll need to give me something that doesn't work if you want me to debug it.

@agibsonccc
Copy link

agibsonccc commented Jun 12, 2022

Yeah of course. Let me see if I can debug this on my end and narrow this down. I was just posting the main part of the project I was specifically focusing on wrt unsafe and javacpp. Without changes everything worked before 21.x and worked on 20.x.

I'm not expecting some magic here :) - I've also cloned javacpp and might try to see if removing unsafe helps. It's a fairly easy theory to test since everything's optional.

If it works I'll submit a PR and you can coach me through how you might want to design a fix around it if the solutions is good enough. Otherwise I'll keep trying to narrow this down.

Edit: Ok that clearly didn't work. Let me try rerunning the generator.

@agibsonccc
Copy link

@saudet feel free to close this issue. In the scope of this project it does work. Of note I also had to update the native plugin to:

   <plugin>
                <groupId>org.graalvm.buildtools</groupId>
                <artifactId>native-maven-plugin</artifactId>
                <version>0.9.11</version>
                <configuration>
                    <skip>false</skip>
                    <imageName>stitching</imageName>
                    <mainClass>${exec.mainClass}</mainClass>
                    <buildArgs>--no-fallback --allow-incomplete-classpath</buildArgs>
                </configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>build</goal>
                        </goals>
                        <phase>package</phase>
                    </execution>
                </executions>
            </plugin>

@J000Z J000Z closed this as completed Jun 13, 2022
@saudet
Copy link
Member

saudet commented Nov 3, 2022

The fix for this has been released with JavaCPP 1.5.8. Thanks for reporting!

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

No branches or pull requests

3 participants