-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Published under GPLv2 per JVMCI license requirements
- Loading branch information
0 parents
commit 045ba37
Showing
22 changed files
with
1,461 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
/out/ | ||
/.idea/ | ||
*.iml |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Premain-Class: one.nalim.Agent |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
# nalim | ||
|
||
Nalim is a library for linking Java methods to native functions using | ||
[JVMCI](https://openjdk.org/jeps/243) (JVM compiler interface). | ||
|
||
Unlike other Java frameworks for native library access, nalim does not | ||
use JNI and therefore does not incur [JNI related overhead](https://stackoverflow.com/a/24747484/3448419). | ||
|
||
When calling a native function with nalim | ||
- a thread does not switch from `in_Java` to `in_native` state and back; | ||
- no memory barrier is involved; | ||
- no JNI handles are created; | ||
- exception checks and safepoint checks are omitted; | ||
- native function can access primitive arrays directly in the heap. | ||
|
||
As a result, native calls become faster comparing to JNI, especially when | ||
a target function is short. In this sense, nalim is similar to | ||
[JNI Critical Natives](https://stackoverflow.com/a/36309652/3448419), | ||
but relies on a standard supported interface. JNI Critical Natives | ||
have been [deprecated](https://bugs.openjdk.org/browse/JDK-8233343) in JDK 16 | ||
and [obsoleted](https://bugs.openjdk.org/browse/JDK-8258192) since JDK 18, | ||
so nalim can serve as a replacement. | ||
|
||
### Examples | ||
|
||
#### 1. Basic usage | ||
|
||
```java | ||
public class Libc { | ||
|
||
@Link | ||
public static native int getuid(); | ||
|
||
@Link | ||
public static native int getgid(); | ||
|
||
static { | ||
Linker.linkClass(Libc.class); | ||
} | ||
} | ||
``` | ||
``` | ||
System.out.println("My user id = " + Libc.getuid()); | ||
``` | ||
|
||
#### 2. Linking by a different name | ||
|
||
```java | ||
public class Mem { | ||
|
||
@Link(name = "malloc") | ||
public static native long allocate(long size); | ||
|
||
@Link(name = "free") | ||
public static native void release(long ptr); | ||
|
||
static { | ||
Linker.linkClass(Mem.class); | ||
} | ||
} | ||
``` | ||
|
||
#### 3. Working with arrays | ||
|
||
```java | ||
@Library("ssl") | ||
public class LibSSL { | ||
|
||
public static byte[] sha256(byte[] data) { | ||
byte[] digest = new byte[32]; | ||
SHA256(data, data.length, digest); | ||
return digest; | ||
} | ||
|
||
@Link | ||
private static native void SHA256(byte[] data, int len, byte[] digest); | ||
} | ||
``` | ||
|
||
#### 4. Inlining raw machine code | ||
|
||
```java | ||
public class Cpu { | ||
|
||
// rdtsc | ||
// shl $0x20,%rdx | ||
// or %rdx,%rax | ||
// ret | ||
@Code({15, 49, 72, -63, -30, 32, 72, 9, -48, -61}) | ||
public static native long rdtsc(); | ||
|
||
static { | ||
Linker.linkClass(Cpu.class); | ||
} | ||
} | ||
``` | ||
|
||
### Running | ||
|
||
#### 1. As an agent | ||
|
||
``` | ||
java -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI \ | ||
-javaagent:nalim.jar -cp <classpath> MainClass | ||
``` | ||
|
||
This is the simplest way to add nalim to your application, | ||
as the agent exports all required JDK internal packages for you. | ||
|
||
The agent optionally accepts a list of classes whose native methods | ||
will be automatically linked at startup: | ||
``` | ||
-javaagent:nalim.jar=com.example.MyLib,com.example.OtherLib | ||
``` | ||
|
||
#### 2. On the classpath | ||
|
||
If not adding nalim as an agent, you'll have to add all required | ||
`--add-exports` manually. | ||
|
||
``` | ||
java -XX:+UnlockExperimentalVMOptions -XX:+EnableJVMCI \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.code=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.code.site=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.hotspot=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.runtime=ALL-UNNAMED \ | ||
-cp nalim.jar:app.jar MainClass | ||
``` | ||
|
||
### Performance | ||
|
||
JMH benchmark for comparing regular JNI calls with nalim calls is available | ||
[here](https://github.com/apangin/nalim/blob/master/example/one/nalim/bench). | ||
|
||
The following results were obtained on Intel Core i7-1280P CPU with JDK 17.0.4.1. | ||
|
||
#### Simple native method | ||
|
||
``` | ||
static native int add(int a, int b); | ||
``` | ||
|
||
``` | ||
Benchmark Mode Cnt Score Error Units | ||
JniBench.add_jni avgt 10 6,535 ± 0,225 ns/op | ||
JniBench.add_nalim avgt 10 0,862 ± 0,035 ns/op | ||
``` | ||
|
||
#### Array processing | ||
|
||
``` | ||
static native long max(long[] array, int length); | ||
``` | ||
|
||
``` | ||
Benchmark (length) Mode Cnt Score Error Units | ||
JniBench.max_jni 10 avgt 10 25,103 ± 0,994 ns/op | ||
JniBench.max_jni 100 avgt 10 55,981 ± 2,930 ns/op | ||
JniBench.max_jni 1000 avgt 10 433,106 ± 1,661 ns/op | ||
JniBench.max_nalim 10 avgt 10 3,477 ± 0,215 ns/op | ||
JniBench.max_nalim 100 avgt 10 38,368 ± 2,348 ns/op | ||
JniBench.max_nalim 1000 avgt 10 420,540 ± 4,049 ns/op | ||
``` | ||
|
||
### Supported platforms | ||
|
||
- **Linux:** amd64 aarch64 | ||
- **macOS:** amd64 aarch64 | ||
- **Windows:** amd64 | ||
|
||
### Limitations | ||
|
||
A native function called with nalim has certain limitations comparing to a regular | ||
JNI function. | ||
|
||
1. It must be `static`. | ||
2. It does not have access to `JNIEnv` and therefore cannot call JNI functions, | ||
in particular, it cannot throw exceptions. | ||
3. Only primitive types and primitive arrays can be passed as arguments. | ||
4. A function must return as soon as possible, since it blocks JVM from reaching | ||
a safepoint. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
#!/bin/sh | ||
javac --add-modules jdk.internal.vm.ci \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.code=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.code.site=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.hotspot=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.meta=ALL-UNNAMED \ | ||
--add-exports jdk.internal.vm.ci/jdk.vm.ci.runtime=ALL-UNNAMED \ | ||
--source 11 --target 11 \ | ||
-d . src/one/nalim/*.java | ||
|
||
jar cfm nalim.jar MANIFEST.MF one |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package one.nalim.bench; | ||
|
||
import one.nalim.Link; | ||
import one.nalim.Linker; | ||
import org.openjdk.jmh.annotations.*; | ||
|
||
import java.util.concurrent.ThreadLocalRandom; | ||
|
||
@State(Scope.Benchmark) | ||
public class JniBench { | ||
|
||
int a = 123; | ||
int b = 45678; | ||
|
||
@Param({"10", "100", "1000"}) | ||
int length; | ||
|
||
long[] array; | ||
|
||
@Setup | ||
public void setup() { | ||
array = ThreadLocalRandom.current().longs(length).toArray(); | ||
} | ||
|
||
@Benchmark | ||
public long add_jni() { | ||
return add(a, b); | ||
} | ||
|
||
@Benchmark | ||
public long add_nalim() { | ||
return raw_add(a, b); | ||
} | ||
|
||
@Benchmark | ||
public long max_jni() { | ||
return max(array, array.length); | ||
} | ||
|
||
@Benchmark | ||
public long max_nalim() { | ||
return raw_max(array, array.length); | ||
} | ||
|
||
static native int add(int a, int b); | ||
|
||
static native long max(long[] array, int length); | ||
|
||
@Link | ||
static native int raw_add(int a, int b); | ||
|
||
@Link | ||
static native long raw_max(long[] array, int length); | ||
|
||
static { | ||
System.loadLibrary("jnibench"); | ||
Linker.linkClass(JniBench.class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
#include <jni.h> | ||
|
||
JNIEXPORT jint JNICALL | ||
Java_bench_JniBench_add(JNIEnv* env, jclass unused, jint a, jint b) { | ||
return a + b; | ||
} | ||
|
||
JNIEXPORT jint JNICALL | ||
raw_add(jint a, jint b) { | ||
return a + b; | ||
} | ||
|
||
JNIEXPORT jlong JNICALL | ||
Java_bench_JniBench_max(JNIEnv* env, jclass unused, jlongArray array, jint length) { | ||
jboolean isCopy; | ||
jlong* data = (jlong*) (*env)->GetPrimitiveArrayCritical(env, array, &isCopy); | ||
|
||
jlong max = 1LL << 63; | ||
jint i; | ||
for (i = 0; i < length; i++) { | ||
if (data[i] > max) max = data[i]; | ||
} | ||
|
||
(*env)->ReleasePrimitiveArrayCritical(env, array, data, JNI_ABORT); | ||
return max; | ||
} | ||
|
||
JNIEXPORT jlong JNICALL | ||
raw_max(jlong* data, jint length) { | ||
jlong max = 1LL << 63; | ||
jint i; | ||
for (i = 0; i < length; i++) { | ||
if (data[i] > max) max = data[i]; | ||
} | ||
return max; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package one.nalim.example; | ||
|
||
import one.nalim.Code; | ||
import one.nalim.Linker; | ||
|
||
public class Cpu { | ||
|
||
// rdtsc | ||
// shl $0x20,%rdx | ||
// or %rdx,%rax | ||
// ret | ||
@Code({15, 49, 72, -63, -30, 32, 72, 9, -48, -61}) | ||
public static native long rdtsc(); | ||
|
||
static { | ||
Linker.linkClass(Cpu.class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package one.nalim.example; | ||
|
||
import one.nalim.Library; | ||
import one.nalim.Link; | ||
|
||
@Library("ssl") | ||
public class LibSSL { | ||
|
||
public static byte[] sha256(byte[] data) { | ||
byte[] digest = new byte[32]; | ||
SHA256(data, data.length, digest); | ||
return digest; | ||
} | ||
|
||
@Link | ||
private static native void SHA256(byte[] data, int len, byte[] digest); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package one.nalim.example; | ||
|
||
import one.nalim.Link; | ||
import one.nalim.Linker; | ||
|
||
public class Libc { | ||
|
||
@Link | ||
public static native int getuid(); | ||
|
||
@Link | ||
public static native int getgid(); | ||
|
||
static { | ||
Linker.linkClass(Libc.class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package one.nalim.example; | ||
|
||
import one.nalim.Link; | ||
import one.nalim.Linker; | ||
|
||
public class Mem { | ||
|
||
@Link(name = "malloc") | ||
public static native long allocate(long size); | ||
|
||
@Link(name = "free") | ||
public static native void release(long ptr); | ||
|
||
static { | ||
Linker.linkClass(Mem.class); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package one.nalim.example; | ||
|
||
import one.nalim.Link; | ||
import one.nalim.Linker; | ||
|
||
public class Time { | ||
|
||
public static long[] current() { | ||
long[] result = new long[2]; | ||
clock_gettime(0, result); | ||
return result; | ||
} | ||
|
||
@Link | ||
private static native void clock_gettime(int clk_id, long[] tp); | ||
|
||
static { | ||
Linker.linkClass(Time.class); | ||
} | ||
} |
Oops, something went wrong.