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

Using picocli with GraalVM's native-image tool #410

Closed
stengerh opened this issue Jul 12, 2018 · 22 comments
Closed

Using picocli with GraalVM's native-image tool #410

stengerh opened this issue Jul 12, 2018 · 22 comments
Labels
theme: codegen An issue or change related to the picocli-codegen module type: enhancement ✨
Milestone

Comments

@stengerh
Copy link
Contributor

I have tried to use the AOT compilation with the GraalVM' native-image tool. I tried something similar to the example from the picocli documentation first, i.e.

    public static void main(String[] args) {
        CommandLine.run(new Example(), System.out, args);
    }

This was unsuccessful as the compiled native executable did not seem to parse and bind the arguments to the commands fields. What did work though was to let picocli do the annotation processing in the JVM which generated the image, i.e. in a static field initializer:

    private static final CommandLine CMD = new CommandLine(new Example());

    public static void main(String[] args) {
        CMD.parseWithHandlers(new CommandLine.RunLast(),
                new CommandLine.DefaultExceptionHandler<List<Object>>(),
                args);
    }

I am posting this here so that others can benefit from it. It would be nice if you included instructions for using native-image with picocli into the documentation. :)

@remkop
Copy link
Owner

remkop commented Jul 12, 2018

I haven’t tried GraalVM yet, unsure when I’ll be able to get to it.

Would you be able to provide a patch or pull request for the picocli user manual? That would be great!

@stengerh
Copy link
Contributor Author

I am working on it. I have encountered another issue while doing so, see #417.

My plan is to add an example and a section in the user manual. Do you sync the user manual with the wiki automatically or is that done manually?

@remkop
Copy link
Owner

remkop commented Jul 18, 2018

Thanks for #417!

The manual on the wiki is out of date. Please modify the index.adoc file instead.

If instructions are non-trivial or longer than a few paragraphs we can create a separate documentation page (and link from and back to the index page).

@kravemir
Copy link

I'm highly interested!

As I develop a (hobby) command line application in Java with picocli, and actual time spent by execution of command is sometimes a very tiny fraction of whole execution time.

AOT should help a lot.

@remkop
Copy link
Owner

remkop commented Jul 31, 2018

@stengerh I can update the wiki to the latest version of the user manual if that helps.

@stengerh
Copy link
Contributor Author

@remkop I don't think this will be necessary.

I am working on a chapter for the user manual. I took a closer at picocli and AOT compilation, so there will be more information than in my post above. However I got side-tracked by summer-time activities. ;)

I can push a preliminary version later this week. I still want to clean up some rough edges before I create a pull request. Here's a summary of my findings so far:

  • Configuration by annotations requires tricks (see first post). Reflection configuration might work but I haven't tested this yet.
  • Collection and map typed arguments are better than array type arguments. Array typed arguments require configuration by code. Configuration through files does not work. This seems to be a limitation or bug with GraalVM reflection configuration parser for array types.
  • Explicit initialization for collection and map typed arguments is better, especially if using implementation types (e.g. LinkedList or TreeMap), since this avoids the need for reflection and thus reflection configuration.
  • Conditionally-enabled picocli type converters don't work as Class.forName is not available. This also affects arguments of type java.lang.Class and java.sql.Driver and everything else which relies on the ServiceLoader mechanism.

It would be nice to AOT compile and run the picocli unit tests to get a more complete picture but this would require a testing framework which does not rely on runtime reflection (particularly Class.forName). I haven't looked into this yet.

@remkop
Copy link
Owner

remkop commented Jul 31, 2018

That all sounds awesome. Take your time and enjoy the summer!

(Sounds like you have a lot of material there. Maybe interesting to also write it up in a blog post or an article for InfoQ, DZone or JavaCodeGeeks.)

@remkop
Copy link
Owner

remkop commented Aug 25, 2018

@stengerh I’m still very interested in this.

@bootstraponline
Copy link

I tried picocli on the latest GraalVM and it's not working out of the box. Hopefully as graal matures it'll support this use case.

@remkop
Copy link
Owner

remkop commented Sep 4, 2018

@bootstraponline Thanks for reporting this! I haven't had a chance to look at GraalVM yet.

Looking at the stack trace in that ticket, the issue may be that GraalVM does not discover the default constructor via reflection. If not explicitly used in the code, this constructor may have been removed from the byte code by Graal.

You may be able to work around this by providing a custom picocli.CommandLine.IFactory implementation that instantiates your commands without using reflection (by maintaining a list of known classes and explicitly calling the constructor for them).

@bootstraponline
Copy link

GraalVM has a bunch of issues right now (~140 on GitHub alone). I think even if this specific problem was resolved, it's not mature enough for real world use.

We aim for compatibility with existing language implementations. Java and JavaScript programs are expected to run fully compatible out-of-the-box.

https://www.graalvm.org/docs/faq/

I'm hoping the bug report helps the Graal team figure out the problem. If picocli starts working out of the box that'll be amazing.

@bootstraponline
Copy link

FYI I solved the reflection issue and ran into another Graal bug. Here's the reflection config I used:
https://github.com/TestArmada/flank/pull/289/files

The Graal team is investigating.

@remkop
Copy link
Owner

remkop commented Sep 6, 2018

Thanks for pushing this issue and thanks for keeping me in the loop!

@remkop
Copy link
Owner

remkop commented Sep 7, 2018

It might be an idea to add a command line tool to picocli to generate the -H:ReflectionConfigurationFiles=<file> file from a CommandSpec or a class with @Command annotations.

https://github.com/oracle/graal/blob/master/substratevm/REFLECTION.md

@bootstraponline
Copy link

Agreed. That was my next step if graal worked.

@remkop
Copy link
Owner

remkop commented Sep 26, 2018

Another idea is to create an annotation processor that generates code (either java source code or byte code) for a class with picocli annotations such that the CommandSpec for the command(s) is created at compile time instead of with reflection at runtime.

@remkop remkop added the theme: codegen An issue or change related to the picocli-codegen module label Oct 3, 2018
@remkop
Copy link
Owner

remkop commented Oct 4, 2018

A first version of ReflectionConfigGenerator is available in the new picocli-codegen module.

This generates JSON that can be specified in the -H:ReflectionConfigurationFiles=<file> option of the native-image tool.

Could you give it a try?

@remkop
Copy link
Owner

remkop commented Oct 4, 2018

An article that describes how to use the tool is here: https://github.com/remkop/picocli/wiki/Picocli-on-GraalVM:-Blazingly-Fast-Command-Line-Apps

Feedback welcome!

@remkop remkop closed this as completed in 6c4858a Oct 4, 2018
@remkop remkop added this to the 3.7 milestone Oct 4, 2018
@remkop
Copy link
Owner

remkop commented Oct 4, 2018

Closing this ticket. The ReflectionConfigGenerator tool will be in the picocli-codegen artefact which will be released with picocli 3.7.

remkop added a commit that referenced this issue Oct 19, 2018
* add documentation to codegen README for generating config during the build
* add --output option to specify a destination file
* add test
* modify exclusions system.property name to `picocli.codegen.excludes` (was `picocli.converters.excludes`)
* update release notes
@bootstraponline
Copy link

I tried the code gen using graalvm-ce-1.0.0-rc9

java -cp picocli-3.8.0.jar:picocli-codegen-3.8.0.jar:flank-SNAPSHOT.jar
picocli.codegen.aot.graalvm.ReflectionConfigGenerator ftl.Main > main.json

I deleted all the system classes in main.json which aren't from my app.

native-image -H:ReflectionConfigurationFiles=main.json -jar flank-SNAPSHOT.jar

I think, unfortunately, graal isn't Kotlin compatible. Hopefully future versions of Graal expand compatibility.

Stacktrace
$ native-image -H:ReflectionConfigurationFiles=main.json -jar flank-SNAPSHOT.jar --report-unsupported-elements-at-runtime
Build on Server(pid: 46750, port: 50862)
[flank-SNAPSHOT:46750]    classlist:   2,799.63 ms
[flank-SNAPSHOT:46750]        (cap):     823.23 ms
[flank-SNAPSHOT:46750]        setup:   1,066.87 ms
warning: unknown locality of class Lkotlin/coroutines/CoroutineContext$plus$1;, assuming class is not local. To remove the warning report an issue to the library or language author. The issue is caused by Lkotlin/coroutines/CoroutineContext$plus$1; which is not following the naming convention.
[flank-SNAPSHOT:46750]     analysis:  15,986.16 ms
error: Non-reducible loop
Detailed message:
Error: Non-reducible loop
Call path from entry point to kotlin.collections.SlidingWindowKt$windowedIterator$1.invokeSuspend(Object):
	at kotlin.collections.SlidingWindowKt$windowedIterator$1.invokeSuspend(SlidingWindow.kt)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:32)
	at kotlinx.coroutines.DispatchedTask.run(Dispatched.kt:234)
	at com.oracle.svm.core.jdk.RuntimeSupport.executeHooks(RuntimeSupport.java:142)
	at com.oracle.svm.core.jdk.RuntimeSupport.executeStartupHooks(RuntimeSupport.java:87)
	at com.oracle.svm.core.JavaMainWrapper.run(JavaMainWrapper.java:156)
	at com.oracle.svm.core.code.CEntryPointCallStubs.com_002eoracle_002esvm_002ecore_002eJavaMainWrapper_002erun_0028int_002corg_002egraalvm_002enativeimage_002ec_002etype_002eCCharPointerPointer_0029(generated:0)
Original exception that caused the problem: org.graalvm.compiler.core.common.PermanentBailoutException: Non-reducible loop
	at org.graalvm.compiler.java.BciBlockMapping.computeBlockOrder(BciBlockMapping.java:876)
	at org.graalvm.compiler.java.BciBlockMapping.build(BciBlockMapping.java:524)
	at org.graalvm.compiler.java.BciBlockMapping.create(BciBlockMapping.java:1103)
	at org.graalvm.compiler.java.BytecodeParser.build(BytecodeParser.java:804)
	at org.graalvm.compiler.java.BytecodeParser.buildRootMethod(BytecodeParser.java:782)
	at org.graalvm.compiler.java.GraphBuilderPhase$Instance.run(GraphBuilderPhase.java:95)
	at org.graalvm.compiler.phases.Phase.run(Phase.java:49)
	at org.graalvm.compiler.phases.BasePhase.apply(BasePhase.java:197)
	at org.graalvm.compiler.phases.Phase.apply(Phase.java:42)
	at org.graalvm.compiler.phases.Phase.apply(Phase.java:38)
	at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.parse(MethodTypeFlowBuilder.java:204)
	at com.oracle.graal.pointsto.flow.MethodTypeFlowBuilder.apply(MethodTypeFlowBuilder.java:323)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.doParse(MethodTypeFlow.java:310)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.ensureParsed(MethodTypeFlow.java:300)
	at com.oracle.graal.pointsto.flow.MethodTypeFlow.addContext(MethodTypeFlow.java:107)
	at com.oracle.graal.pointsto.DefaultAnalysisPolicy$DefaultVirtualInvokeTypeFlow.onObservedUpdate(DefaultAnalysisPolicy.java:186)
	at com.oracle.graal.pointsto.flow.TypeFlow.notifyObservers(TypeFlow.java:347)
	at com.oracle.graal.pointsto.flow.TypeFlow.update(TypeFlow.java:389)
	at com.oracle.graal.pointsto.BigBang$2.run(BigBang.java:508)
	at com.oracle.graal.pointsto.util.CompletionExecutor.lambda$execute$0(CompletionExecutor.java:174)
	at java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(ForkJoinTask.java:1402)
	at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
	at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
	at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
	at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)

Error: Processing image build request failed

@remkop
Copy link
Owner

remkop commented Nov 14, 2018

Thanks for the info! Do you think there is anything picocli can do to help with this?

@bootstraponline
Copy link

Thanks for the info! Do you think there is anything picocli can do to help with this?

Unless you want to redesign picocli to work based on a Kotlin compiler plugin to fully avoid reflection, I don't think so.

Reflection doesn't play nice with the current version of graal (error: Non-reducible loop). I'm hoping they fix that over time.

The picocli library and ReflectionConfigGenerator are easy to use.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
theme: codegen An issue or change related to the picocli-codegen module type: enhancement ✨
Projects
None yet
Development

No branches or pull requests

4 participants