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

JNA: Support GraalVM #1

Closed
wants to merge 1 commit into from
Closed

JNA: Support GraalVM #1

wants to merge 1 commit into from

Conversation

sgammon
Copy link
Member

@sgammon sgammon commented Jun 8, 2024

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

  • Automatic configuration of JNA under GraalVM native image
  • Detection and configuration of user types
  • CI and sample project for GraalVM+JNA
  • Experimental feature for building JNA code directly into the image

Warning

This PR has been filed on this fork for downstream testing. It is not ready to be filed and reviewed yet. Once functionality is ready and tested, it will be rebased and squashed.

Usage

  1. Developer adds new jna-graalvm.jar to their native-image classpath
  2. Developer uses JNA
  3. That's it
  4. Optionally, the developer enables static JNA with --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:

public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
Native.load((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);
void printf(String format, Object... args);
}

... then CLibrary is registered as a dynamic proxy with GraalVM automatically.

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

Approach

---
title: "SVM + JNA"
---
classDiagram
    note for AbstractJNAFeature "Base Feature"
    note for JavaNativeAccess "Baseline Configurations"
    note for StaticJNAFeature "Static JNI Linkage"
    AbstractJNAFeature --|> JavaNativeAccess
    AbstractJNAFeature --|> StaticJNAFeature
    JavaNativeAccess --> UserCode: Detects

    class AbstractJNAFeature {
      Common Logic*
      --
      JNI/Proxy Registration
      Library Resolution + Unpacking
    }
    class JavaNativeAccess {
      Always Active*
      No change to current behavior*
      --
      Runtime JNI Registration
      Runtime Proxy Registration
      Subtype Reachability Handler
    }
    class StaticJNAFeature {
      Active On-Demand*
      --
      Unpacks Static Library at Build Time
      Enables Static JNI Linkage
    }
    class UserCode {
        class X extends Library ...
    }
Loading

Abstract Base

This new class is package-private and provides protected utilities for use exclusively by GraalVM Feature implementations; for unfamiliar readers, Features are build-time classes that contribute to compiler configuration.

Common logic provided:

  • Finding the static native library within the jna-graalvm.jar resource
  • Unpacking the static library to a location on-disk, as is normally done at runtime
  • Common routines for registration of runtime JNI and proxy access

JavaNativeAccess feature

sequenceDiagram
    Native Image->>+Feature: Detects `native-image.properties`, feature self-mounts
    Feature->>+Native Image: Registers reachability handler and common configurations
    Native Image->>User Code: Analysis phase begins
    User Code->>Native Image: class X extends Library { ... }
    Native Image->>-Feature: User extended Library with class X
    Feature->>-Native Image: Register class X as proxy
Loading

This feature is designed to be registered unconditionally in a downstream native-image build; it is found automatically via the native-image.properties resource, which declares it via --features=...JavaNativeAccess. Thus, (1) having the jna-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:

  • Registers classes necessary for runtime JNI access to JNA
  • Registers necessary proxy interface access for proper operation of JNA
  • Registers native library resources which should be persisted within the image

How to use it:

  • Add jna-graalvm.jar to your build-time classpath for native-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 feature

JNIArch

This 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 the JavaNativeAccess feature. The two can be used in a build together, or StaticJNAFeature can be omitted to preserve the current library behavior. This feature is opt-in and is not injected by the native-image.properties file.

What it does:

  • Unpacks the new static library, [lib]jnidispatch.[a,lib], at build time, according to current JNA behavior
  • Configures Native Image to understand JNA's jnidispatch as a static JNI "built-in"

How to use it:

  • Add jna-graalvm.jar to your build-time classpath for native-image
  • Specify the argument 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

  1. https://github.com/oracle/graal/issues/3359

  2. https://www.blog.akhil.cc/static-jni

@sgammon sgammon added enhancement New feature or request ✋ embargoed Waiting for further action labels Jun 8, 2024
@sgammon sgammon self-assigned this Jun 8, 2024
@sgammon sgammon force-pushed the feat/static-graalvm-jni branch 6 times, most recently from f36ddc0 to 37a2c95 Compare June 8, 2024 20:31
.github/workflows/graalvm.workflow.yaml Outdated Show resolved Hide resolved
@@ -69,6 +72,9 @@
<property name="maven-javadoc-jar" value="${dist}/${artifactId}-${jna.version}-javadoc.jar" />
<property name="maven-sources-jar" value="${dist}/${artifactId}-${jna.version}-sources.jar" />

<property name="maven-graalvm-javadoc-jar" value="${dist}/jna-graalvm-${jna.version}-javadoc.jar" />
<property name="maven-graalvm-sources-jar" value="${dist}/jna-graalvm-${jna.version}-sources.jar" />
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sources jar for graalvm doesn't build yet

build.xml Show resolved Hide resolved
Comment on lines -1031 to +1134
<fileset dir="${build.native}" includes="jnidispatch.dll,libjnidispatch.*"/>
<fileset dir="${build.native}" includes="jnidispatch.dll,libjnidispatch.*" excludes="*.a,*.lib"/>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

current JARs (non-static) do not ship the static library, to avoid duplicating lib size contribution to JARs

Comment on lines +1139 to +1141
<jar jarfile="${build}/${native-static.jar}" createUnicodeExtraFields="never" encoding="UTF-8">
<fileset dir="${build.native}" includes="jnidispatch.lib,libjnidispatch.a"/>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

native-static.jar ships only the static libs

@@ -0,0 +1,34 @@
[versions]
jna = "5.15.0-SNAPSHOT"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note for release: should eventually be pinned to minimum support version for gvm

lib/gvm/native-image.properties Show resolved Hide resolved
@sgammon sgammon mentioned this pull request Jun 9, 2024
@sgammon sgammon changed the title Add support for GraalVM JNA: Support GraalVM Jun 9, 2024
This was referenced Jun 10, 2024
sgammon added a commit that referenced this pull request Jun 10, 2024
Signed-off-by: GitHub <noreply@github.com>
sgammon added a commit that referenced this pull request Jun 10, 2024
Signed-off-by: GitHub <noreply@github.com>
@sgammon sgammon added the 🛑 blocked Waiting on other work / PRs label Jun 10, 2024
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

Signed-off-by: Sam Gammon <sam@elide.ventures>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🛑 blocked Waiting on other work / PRs ✋ embargoed Waiting for further action enhancement New feature or request
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

1 participant