-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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
JNA: Support GraalVM #1608
base: master
Are you sure you want to change the base?
JNA: Support GraalVM #1608
Conversation
This comment was marked as outdated.
This comment was marked as outdated.
Hey @sgammon thanks for this! Understand this is still a work in progress, but it's exciting (for me, at least) to see expansion to other operating systems (and VMs). |
This comment was marked as outdated.
This comment was marked as outdated.
8b323da
to
8978b9b
Compare
Okay, I've cleaned up the stack of commits. It should be much more ready for review now. The full stack of commits is still available and can be restored if we need to unwind certain pieces. |
Well, developing a JNA-based cross-platform library necessitates a lot of upstream contributions. ;) I did an initial review of the code but before digging into details, I think this is more of a philosophical discussion on whether another artifact vs. the usual JNA-packaged binary is the right fit for this repo. You've acknowledged that this is different and explained why, but I'm still trying to understand the usage from a downstream dependency's standpoint. For example, in OSHI, I simply import the JNA dependency and expect it to work on any (supported) operating system. Using a different JAR doesn't seem to fit into this model; I'm either packaging both JARs and picking one of them at runtime, or "building" at runtime. Are there other ways we can solve this problem? |
This comment was marked as outdated.
This comment was marked as outdated.
Thanks for the more full explanation. I'm still not sure about all the technical details. I'm sure @matthiasblaesing will have some comments here and I'll wait for him to chime in before commenting more. I do think it's great to support this capability, I just don't quite understand (yet) enough about any possible alternative approaches to conclude (as you have) this is the best one, but I'm sure we'll get that sorted out. |
This comment was marked as outdated.
This comment was marked as outdated.
Adds a JAR publication at `jna-graalvm.jar`, with accompanying build infrastructure, which provides support for JNA within the context of the Substrate Virtual Machine (SVM). GraalVM Native Image targets use SVM instead of JVM at runtime. JNA's current strategy of unpacking libraries at runtime works under SVM, but is suboptimal; the binary is native, so it can simply include JNA object code for the current platform directly. To accomplish this, several GraalVM "feature" implementations are provided in this new publication. By default, regular JNA access is enabled through the `JavaNativeAccess` feature; this class enables reflection and runtime JNI configurations for downstream projects which use JNA. Another feature, `SubstrateStaticJNA`, is experimental because it relies on unstable GraalVM APIs, but instead of loading JNA at runtime from a dynamic library, it builds JNA into the final native image with a static object. These features are enabled through a resource within `META-INF`, called `native-image.properties`, which is picked up by the native image compiler at build time. The new artifact only needs to be present for GraalVM native targets at build time; otherwise, the classes and libraries in `jna-graalvm.jar` are inert. Includes tested support for: - macOS aarch64 - Linux amd64 - feat: add `jna-graalvm.jar` publication - feat: add base `JavaNativeAccess` feature for auto-config of JNA - feat: add initial implementation of `SubstrateStaticJNA` feature - test: sample/test gradle build for native image - chore: ci config to run native sample - chore: add readme to `lib/gvm` Signed-off-by: Sam Gammon <[email protected]> Signed-off-by: Dario Valdespino <[email protected]> Co-authored-by: Sam Gammon <[email protected]> Co-authored-by: Dario Valdespino <[email protected]>
6d07b23
to
874c272
Compare
This comment was marked as outdated.
This comment was marked as outdated.
874c272
to
62b1878
Compare
JNIEXPORT jint JNICALL | ||
JNI_OnLoad_jnidispatch(JavaVM *jvm, void *UNUSED(reserved)) { | ||
setupJna(jvm); | ||
return JNI_VERSION_1_8; // upgrade to JNI_VERSION_1_8; required for static JNI |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is required by spec for static JNI support. JNI_VERSION_1_8
was introduced in JDK8, so this is safe to apply now that JNA has a minimum that matches.
62b1878
to
f7b3d3d
Compare
f7b3d3d
to
4fb7944
Compare
When operating under static linkage in SVM (Native Image), JNA's `JNI_OnLoad` hooks are not run. We need to sanity-check at the first JNI border and run static initialization manually. Additionally, `JNI_OnLoad` should be provided in static contexts as `JNI_OnLoad_jnidispatch`. This changeset fixes both issues. Signed-off-by: Sam Gammon <[email protected]> Signed-off-by: Dario Valdespino <[email protected]>
When linked statically on GraalVM, JNI symbols declared in the overloaded form cannot be resolved. Luckily, all of `Native`'s callsites are in `Pointer` or itself, and all `native` methods of `Native` are non-public. This PR adjusts the JNA C API to avoid using overloaded `read`, `write`, or `getDirectByteBuffer`. Callsites are amended in `Pointer` accordingly. Signed-off-by: Sam Gammon <[email protected]> Signed-off-by: Dario Valdespino <[email protected]>
Implements a new optional linkage feature, called Static JNI, under GraalVM Native Image. With `com.sun.jna.SubstrateStaticJNA` enabled (opt-in), JNA is loaded eagerly at image build time, and then linked against a static copy of `libjnidispatch` at image link-time. The result is that `libjnidispatch.a` is embedded within the final image. No precursor library unpacking step is necessary before using JNA in this circumstance, because JNA's native layer is built directly into the image itself. - feat: implement static jni feature - chore: full gvm ci build - chore: add static jni sample Signed-off-by: Sam Gammon <[email protected]> Signed-off-by: Dario Valdespino <[email protected]> Co-authored-by: Sam Gammon <[email protected]> Co-authored-by: Dario Valdespino <[email protected]>
4fb7944
to
548ebec
Compare
This comment was marked as outdated.
This comment was marked as outdated.
- chore: switch to jna snapshot - chore: add new jna graalvm artifact - chore: opt-in to static jna feature - chore: cleanup superfluous configs Adopts java-native-access/jna#1608 Signed-off-by: Sam Gammon <[email protected]>
- chore: switch to jna snapshot - chore: add new jna graalvm artifact - chore: opt-in to static jna feature - chore: cleanup superfluous configs Adopts java-native-access/jna#1608 Signed-off-by: Sam Gammon <[email protected]>
- chore: switch to jna snapshot - chore: add new jna graalvm artifact - chore: opt-in to static jna feature - chore: cleanup superfluous configs Adopts java-native-access/jna#1608 Signed-off-by: Sam Gammon <[email protected]>
- chore: switch to jna snapshot - chore: add new jna graalvm artifact - chore: opt-in to static jna feature - chore: cleanup superfluous configs Adopts java-native-access/jna#1608 Signed-off-by: Sam Gammon <[email protected]>
My current take on GraalVM: GraalVM still feels massively experimental and I fail to see a road for GraalVM project. At some point it was part of the JDK, then it got split of, then realignment with the JDK was planned (project galahad), but questions regarding the state basicly state "we will see, when or even if graalvm technology will be reintegrated into the JDK" (my takeaway from this short conversation: https://mail.openjdk.org/pipermail/galahad-dev/2024-June/thread.html#6 (Thread "What's the progress of Project Galahad")). So from my POV GraalVM has a good chance to die the same death that nashorn died. At this point I only skimmed this PR, but the biggest part the jumped out was, that the whole native interface is changed. This alone introduces about 3 days of work to rebuild all libraries. So I have to ask why? I also notice that the example project is placed in a new |
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
This comment was marked as outdated.
- chore: switch to jna snapshot - chore: add new jna graalvm artifact - chore: opt-in to static jna feature - chore: cleanup superfluous configs Adopts java-native-access/jna#1608 Signed-off-by: Sam Gammon <[email protected]>
When is this version likely to be released? |
@workcheng At the moment I think there are no plans, but it would not be hard to complete. @matthiasblaesing What if I cleaned this up, and only included the very stable parts, and left the "static JNI" work until later? At least then JNA would "just work" for GraalVM native builds, without needing to register symbols. I am happy to rebase, clean it up, and offer it for review in a timely manner if there is interest in shipping this feature. |
(Removing the Static JNI portion removes any experimental parts, and would obviate the need to issue an update to native libraries entirely, as no native code needs to change.) |
Before this release, are there any alternative methods if I want to operate JNI in GraalVM? |
My current requirement is to use Java to operate on the Windows registry to control some behaviors of the Windows system, such as setting proxy addresses and so on. |
This comment was marked as off-topic.
This comment was marked as off-topic.
Oh, okay. I use this tool to configure the Windows proxy. It works fine directly on the JVM, but it doesn't work when packaged into GraalVM, and there are no error messages either. I thought this feature was required for it to take effect. Thanks. I'll see if there are any alternative methods or look into what's causing this. |
I plan to take a second look at this PR soon, with the following amendments in mind:
I also haven't yet benchmarked JNA on the static JNI configuration, but I bet it is significantly faster. JNA today is much slower than JNI or FFM and I think that gap could potentially be closed for Native Image users. |
Summary
Adds a JAR publication at
jna-graalvm.jar
, with accompanying build infrastructure, that provides support for JNA within the context of the Substrate Virtual Machine (SVM).JNA is already possible on SVM today, but requires extensive (and app-specific) configuration, which can end up being brittle. If methods aren't caught for configuration at build-time, dispatch at runtime can throw. This PR ships automatic configuration support for GVM to JNA itself, as an optional add-on.
Features
Usage
jna-graalvm.jar
to theirnative-image
classpath--features=com.sun.jna.SubstrateStaticJNA
In addition to baseline configurations required for any use at all of JNA, the base feature leverages GraalVM's analysis to detect when a developer is using JNA features, and then registers configurations appropriately for their classes, too.
For example, when the developer writes:
... then
CLibrary
is registered as a dynamic proxy with GraalVM automatically.Trying it out
Tip
See this comment to try this out in an existing codebase without building from source.
You should see:
Rationale
GraalVM Native Image targets use SVM instead of JVM at runtime. JNA's current strategy of unpacking libraries at runtime works under SVM, but is suboptimal; the binary is native, so it can simply include JNA object code directly. A configuration-only approach also leaves JNA code brittle, because configuration must be specified for all JNA touchpoints used by the app, and must stay in sync over time.
To accomplish automatic configuration, several GraalVM "feature" implementations are provided in this new publication. By default, regular JNA access is enabled through the
JavaNativeAccess
feature; this class enables reflection and runtime JNI configurations for downstream projects which use JNA.Another feature,
SubstrateStaticJNA
, is experimental because it relies on unstable GraalVM APIs, but instead of loading JNA at runtime from a dynamic library, it builds JNA into the final native image with a static object.These features are enabled through a resource within
META-INF
, callednative-image.properties
, which is picked up by the native image compiler at build time. The new artifact only needs to be present for GraalVM native targets at build time; otherwise, the classes and libraries injna-graalvm.jar
are inert.Approach
Abstract Base
This new class is package-private and provides protected utilities for use exclusively by GraalVM
Feature
implementations; for unfamiliar readers,Feature
s are build-time classes that contribute to compiler configuration.Common logic provided:
jna-graalvm.jar
resourceJavaNativeAccess
featureThis feature is designed to be registered unconditionally in a downstream
native-image
build; it is found automatically via thenative-image.properties
resource, which declares it via--features=...JavaNativeAccess
. Thus, (1) having thejna-graalvm.jar
on your build-time classpath and (2) using JNA is enough to activate the feature.Configurations are contributed by this feature which always apply to JNA in the context of Substrate, native image's equivalent to JVM: these include runtime JNI access and proxy access, both of which must be declared ahead of time in GraalVM's AOT mode.
What it does:
How to use it:
jna-graalvm.jar
to your build-time classpath fornative-image
Note
Many projects register these same configurations within
[proxy,jni,reflect]-config.json
files in their project; these configuration files can be cleaned up downstream once this feature becomes available. Users no longer have to generate this configuration themselves. Extra configurations are inert.SubstrateStaticJNA
featureThis feature is experimental, because it relies on unstable APIs within GraalVM's native image SDK1. Through a technique known as Static JNI2, the precursor library unpacking step normally needed for JNA's operation can be eliminated entirely. Instead of steps taken at runtime, a static library is unpacked at build time, and built directly into the user's native image.
This has many advantages: the library unpack step is no longer needed and so startup time in JNA-consuming apps is reduced; artifact size is reduced, since native libraries are no longer bundles as resources, and potentially compressed without benefit. Since the binary targets native code, unused variants of
libjnidispatch.[so,dylib,dll]
can be omitted, resulting in smaller image sizes.The
StaticJNAFeature
is designed to interoperate with theJavaNativeAccess
feature. The two can be used in a build together, orStaticJNAFeature
can be omitted to preserve the current library behavior. This feature is opt-in and is not injected by thenative-image.properties
file.What it does:
[lib]jnidispatch.[a,lib]
, at build time, according to current JNA behaviorjnidispatch
as a static JNI "built-in"How to use it:
jna-graalvm.jar
to your build-time classpath fornative-image
native-image ... --features=com.sun.jna.SubstrateStaticJNA
Caution
Obligatory warning that this is an experimental and unstable technique. Please don't rely on it for production use. Once oracle/graal#3359 is fixed, this feature can ship as default.
Footnotes
https://github.com/oracle/graal/issues/3359 ↩
https://www.blog.akhil.cc/static-jni ↩