Skip to content

[WIP] Java jni - #72

Draft
monofuel wants to merge 10 commits into
treeform:masterfrom
monofuel:java-jni
Draft

monofuel wants to merge 10 commits into
treeform:masterfrom
monofuel:java-jni

Conversation

@monofuel

Copy link
Copy Markdown
Contributor

I'm building a Minecraft plugin and wanted to use Nim

monofuel and others added 10 commits October 10, 2026 18:47
-d:gennyJava generates a C file against jni.h with a JNI_OnLoad that
calls NimMain and RegisterNatives, plus a Java class with static native
methods and constants. Objects, ref objects, seqs and enums are skipped
with a compile-time warning for now.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The JVM raises SIGSEGV on purpose for null checks and safepoints. Nim's
signal handler catches those and exits, killing the host JVM.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Natives are registered on the library class and wrapped by a public API:
enums become Java enums, value objects become classes converted field by
field in C (cached class, field and constructor IDs), ref objects and
seqs become AutoCloseable handle classes released by a Cleaner, procs on
objects become instance methods, and raising procs throw <Lib>Exception.
TestJavaStandard runs the standard test library through Java.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pointer parameter maps to java.nio.ByteBuffer and the wrapper passes
GetDirectBufferAddress, so Java and Nim can share memory without copies.
Pointer returns stay unsupported.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Only call NimMain from JNI_OnLoad when Nim does not initialize itself
  (--app:lib without --noMain already runs it from a load constructor),
  so top-level code runs once.
- Every JNI conversion can fail and jumps to cleanup before calling Nim:
  heap ByteBuffers throw IllegalArgumentException, null value objects and
  enums throw NullPointerException, and Call*/NewObject results are
  exception-checked. -Xcheck:jni now reports no warnings.
- Strings are transcoded between UTF-16 and standard UTF-8 instead of
  passing JNI modified UTF-8; invalid UTF-8 from Nim becomes U+FFFD and
  exception messages go through the same path.
- Pointer fields and seq entries are skipped; emitNative refuses pointer
  returns.
- Cleaner releases are queued and run on the next call into the library
  (or releasePending()), so Nim refcounts are only touched by calling
  threads. Handle-bearing calls keep their Java objects reachable with
  reachabilityFence, and closed objects throw IllegalStateException.
- Enums use the real C size and explicit ordinal values; Java enums carry
  a value field instead of relying on ordinal().
- Value objects that Nim would not pass as plain C structs (over 24 bytes
  without {.bycopy.}, inherited, packed, union, variant) are skipped.
- Constants are rendered as Java literals from their values.
- Overloads with the same Java signature get the overload prefix appended;
  other method clashes and types named like the generated classes fail at
  compile time. Java keywords get a trailing underscore.
- Seq get/set/delete check bounds and throw IndexOutOfBoundsException.
- Value object equals/hashCode use Float/Double.compare semantics.
- Platform JNI include directory, quoted include paths, JNICALL wrappers,
  pointer-sized C type for Nim int, java.lang names fully qualified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Building the generated JNI C with -DGENNY_JAVA_HOT_RELOAD turns it into a
small loader: Nim exports become function pointers that reload(path)
binds to a dlopened Nim library, so the Nim code can be swapped while the
JVM keeps running. A failed reload keeps the current library. Handles
record the library generation and refuse to cross a reload; their
releases are dropped instead of reaching the new library. Normal builds
keep plain externs and get reload() throwing
UnsupportedOperationException.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Validate generated ABI fingerprints before activation and retain the previous library for commit or rollback. Release native references before switching and permanently invalidate stale handles, including after rollback.

Keep Nim string results and error messages owned until JNI finishes copying them. Cover preparation, cancellation, rollback and handle generations in the Java reload suite; normal Java and reload tests pass.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant