Add jextract module: SQLite bindings generated with jextract, FFM arenas, C strings, out-parameters, errors, upcalls, and a JMH comparison with JNI
jextract-25 early-access build (25-jextract+2-4) on JDK 25.0.5+8 against SQLite 3.45.1: 25 functions and 13 constants generated into 4 committed files (the whole header would be 83 files), a small Db wrapper, the four arena kinds used wrongly on purpose, ADDRESS vs C_POINTER zero-length vs unbounded segments and the SIGSEGV from getString on a NULL pointer, sqlite3_open out-parameter, return codes to exceptions, a sqlite3_exec upcall and the VM exit when it throws, the JDK 25 native-access warning and --illegal-native-access=deny, library loading failures, and a JMH benchmark of FFM vs hand-written JNI vs plain Java (indicative only: shared 2-CPU machine). Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b6Kg9RWSTJzyz6KP9X
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
$ jextract --version
|
||||
jextract 25
|
||||
JDK version 25+37-3491
|
||||
LibClang version clang version 13.0.0
|
||||
$ java -version
|
||||
openjdk version "25.0.5" 2026-10-20 LTS
|
||||
OpenJDK Runtime Environment Temurin-25.0.5+8 (build 25.0.5+8-LTS)
|
||||
OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (build 25.0.5+8-LTS, mixed mode, sharing)
|
||||
$ gcc --version | head -1
|
||||
gcc (Ubuntu 13.3.0-6ubuntu2~24.04.1) 13.3.0
|
||||
$ dpkg-query -W libsqlite3-dev libsqlite3-0
|
||||
libsqlite3-0:amd64 3.45.1-1ubuntu2.8
|
||||
libsqlite3-dev:amd64 3.45.1-1ubuntu2.8
|
||||
$ grep -E '#define SQLITE_(VERSION|VERSION_NUMBER) ' /usr/include/sqlite3.h
|
||||
#define SQLITE_VERSION "3.45.1"
|
||||
#define SQLITE_VERSION_NUMBER 3045001
|
||||
@@ -0,0 +1,26 @@
|
||||
$ scripts/gen.sh
|
||||
$ find generated -name '*.java' | wc -l
|
||||
4
|
||||
$ wc -l $(find generated -name '*.java') | sort -n
|
||||
63 generated/demo/sqlite/sqlite3_h$shared.java
|
||||
69 generated/demo/sqlite/sqlite3_bind_text$x0.java
|
||||
73 generated/demo/sqlite/sqlite3_exec$callback.java
|
||||
1662 generated/demo/sqlite/sqlite3_h.java
|
||||
1867 total
|
||||
$ jextract --output <tmp> -t demo.sqlite.full -l sqlite3 /usr/include/sqlite3.h # no filters: the whole header
|
||||
warnings: 14
|
||||
sqlite3.h:272:16: warning: Skipping sqlite3 (type Declared(sqlite3) is not supported)
|
||||
sqlite3.h:4045:16: warning: Skipping sqlite3_stmt (type Declared(sqlite3_stmt) is not supported)
|
||||
files: 83
|
||||
lines: 44295
|
||||
bytes: 1339454
|
||||
lines in the one big header class: 22335
|
||||
$ grep -o 'public static [A-Za-z.]* sqlite3_open[a-z_0-9$]*(' generated/demo/sqlite/sqlite3_h.java | sort -u
|
||||
public static FunctionDescriptor sqlite3_open$descriptor(
|
||||
public static FunctionDescriptor sqlite3_open_v2$descriptor(
|
||||
public static MemorySegment sqlite3_open$address(
|
||||
public static MemorySegment sqlite3_open_v2$address(
|
||||
public static MethodHandle sqlite3_open$handle(
|
||||
public static MethodHandle sqlite3_open_v2$handle(
|
||||
public static int sqlite3_open(
|
||||
public static int sqlite3_open_v2(
|
||||
@@ -0,0 +1,14 @@
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Hello
|
||||
sqlite3_libversion() = 3.45.1
|
||||
returned segment byteSize = 9223372036854775807
|
||||
sqlite3_libversion_number() = 3045001
|
||||
sqlite3_sourceid() = 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257ccalt1
|
||||
|
||||
$ java --enable-native-access=ALL-UNNAMED -Djextract.trace.downcalls=true -cp build/classes demo.Hello
|
||||
sqlite3_libversion()
|
||||
sqlite3_libversion() = 3.45.1
|
||||
returned segment byteSize = 9223372036854775807
|
||||
sqlite3_libversion_number()
|
||||
sqlite3_libversion_number() = 3045001
|
||||
sqlite3_sourceid()
|
||||
sqlite3_sourceid() = 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257ccalt1
|
||||
@@ -0,0 +1,18 @@
|
||||
$ java -cp build/classes demo.Hello # no flag at all, JDK 25 default
|
||||
WARNING: A restricted method in java.lang.foreign.AddressLayout has been called
|
||||
WARNING: java.lang.foreign.AddressLayout::withTargetLayout has been called by demo.sqlite.sqlite3_h$shared in an unnamed module (file:/tmp/work/jextract/repo/jextract/build/classes/)
|
||||
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
|
||||
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
|
||||
|
||||
sqlite3_libversion() = 3.45.1
|
||||
returned segment byteSize = 9223372036854775807
|
||||
sqlite3_libversion_number() = 3045001
|
||||
sqlite3_sourceid() = 2024-01-30 16:01:20 e876e51a0ed5c5b3126f52e532044363a014bc594cfefa87ffb5b82257ccalt1
|
||||
|
||||
$ java --illegal-native-access=deny -cp build/classes demo.Hello
|
||||
Exception in thread "main" java.lang.ExceptionInInitializerError
|
||||
at demo.Hello.main(Hello.java:10)
|
||||
Caused by: java.lang.IllegalCallerException: Illegal native access from an unnamed module (file:/tmp/work/jextract/repo/jextract/build/classes/)
|
||||
at java.base/java.lang.Module.ensureNativeAccess(Module.java:307)
|
||||
at java.base/java.lang.System$1.ensureNativeAccess(System.java:2107)
|
||||
at java.base/jdk.internal.reflect.Reflection.ensureNativeAccess(Reflection.java:123)
|
||||
@@ -0,0 +1,6 @@
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Tour
|
||||
rows changed by last statement: 1, last rowid: 3
|
||||
[1, Ankur, 41]
|
||||
[2, Asha, 36]
|
||||
[3, Ravi, 29]
|
||||
[[NoAge, null]]
|
||||
@@ -0,0 +1,21 @@
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Arenas
|
||||
== confined: one owner thread
|
||||
owner reads -> ok
|
||||
other thread reads -> WrongThreadException: Attempted access outside owning thread
|
||||
other thread closes -> WrongThreadException: Attempted access outside owning thread
|
||||
read after close -> IllegalStateException: Already closed
|
||||
close twice -> IllegalStateException: Already closed
|
||||
== shared: any thread, one close
|
||||
other thread reads -> ok
|
||||
other thread closes -> ok
|
||||
read after close -> IllegalStateException: Already closed
|
||||
== auto: the garbage collector frees it; close() is not allowed
|
||||
allocate 8 bytes -> ok
|
||||
close() -> UnsupportedOperationException: Attempted to close a non-closeable session
|
||||
== global: never freed; close() is not allowed
|
||||
allocate 8 bytes -> ok
|
||||
close() -> UnsupportedOperationException: Attempted to close a non-closeable session
|
||||
== bounds: a segment knows its size
|
||||
read int at offset 0 -> ok
|
||||
read int at offset 4 -> IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x7fba74666600, byteSize: 4 }; new offset = 4; new length = 4
|
||||
read long at offset 0 -> IndexOutOfBoundsException: Out of bound access on segment MemorySegment{ kind: native, address: 0x7fba74666600, byteSize: 4 }; new offset = 0; new length = 8
|
||||
@@ -0,0 +1,18 @@
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Trap
|
||||
ValueLayout.ADDRESS : byteSize=0
|
||||
getString(0) : IndexOutOfBoundsException, "No null terminator found..."
|
||||
reinterpret(100) : hello from C land
|
||||
C_POINTER : byteSize=9223372036854775807
|
||||
getString(0) : hello from C land
|
||||
|
||||
$ java --enable-native-access=ALL-UNNAMED -XX:ErrorFile=/tmp/jextract-hs_err.log -cp build/classes demo.NullText # first lines of the crash banner
|
||||
address=0 byteSize=9223372036854775807 equals(NULL)=true
|
||||
about to call getString(0) on it
|
||||
#
|
||||
# A fatal error has been detected by the Java Runtime Environment:
|
||||
#
|
||||
# SIGSEGV (0xb) at pc=0x00007f002fe4645d, pid=20843, tid=20844
|
||||
#
|
||||
# JRE version: OpenJDK Runtime Environment Temurin-25.0.5+8 (25.0.5+8) (build 25.0.5+8-LTS)
|
||||
# Java VM: OpenJDK 64-Bit Server VM Temurin-25.0.5+8 (25.0.5+8-LTS, mixed mode, sharing, tiered, compressed oops, compressed class ptrs, g1 gc, linux-amd64)
|
||||
exit code: 134
|
||||
@@ -0,0 +1,8 @@
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Errors
|
||||
open in a directory that does not exist -> unable to open database file (rc=14)
|
||||
open a missing file read-only -> unable to open database file (rc=14)
|
||||
syntax error -> near "SELEC": syntax error (rc=1)
|
||||
missing table -> no such table: ghost (rc=1)
|
||||
NOT NULL violation -> NOT NULL constraint failed: t.name (rc=19)
|
||||
PRIMARY KEY violation -> UNIQUE constraint failed: t.id (rc=19)
|
||||
SQLITE_CONSTRAINT = 19, sqlite3_errstr(19) = constraint failed
|
||||
@@ -0,0 +1,13 @@
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Callbacks
|
||||
row: id=1 name=one
|
||||
row: id=2 name=two
|
||||
row: id=3 name=NULL
|
||||
sqlite3_exec returned 0
|
||||
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp build/classes demo.Callbacks explode
|
||||
java.lang.IllegalStateException: thrown inside the callback
|
||||
at demo.Callbacks.lambda$main$0(Callbacks.java:23)
|
||||
at demo.sqlite.sqlite3_h.sqlite3_exec(sqlite3_h.java:417)
|
||||
at demo.Callbacks.main(Callbacks.java:39)
|
||||
Unrecoverable uncaught exception encountered. The VM will now exit
|
||||
exit code: 1
|
||||
@@ -0,0 +1,14 @@
|
||||
$ java --enable-native-access=ALL-UNNAMED -Djava.library.path=build/lib org.openjdk.jmh.Main -f 5 -wi 3 -w 1 -i 5 -r 1 (25.0.5+8-LTS, JMH 1.37)
|
||||
machine: 2 CPUs, Intel(R) Xeon(R) Processor @ 2.10GHz
|
||||
load average before the run: 4.00 4.80 2.92 (1, 5, 15 minutes; this box has 2 CPUs and was shared with other jobs)
|
||||
sqlite: libsqlite3-dev 3.45.1-1ubuntu2.8
|
||||
Benchmark Mode Cnt Score Error Units
|
||||
SqliteBench.insert1000_ffm avgt 25 653368.858 ± 161095.825 ns/op
|
||||
SqliteBench.insert1000_jni avgt 25 544963.964 ± 97090.203 ns/op
|
||||
SqliteBench.strlike_ffm_arenaPerCall avgt 25 192.233 ± 62.055 ns/op
|
||||
SqliteBench.strlike_ffm_preallocated avgt 25 82.660 ± 16.253 ns/op
|
||||
SqliteBench.strlike_jni avgt 25 244.929 ± 82.330 ns/op
|
||||
SqliteBench.versionNumber_ffm avgt 25 16.819 ± 4.063 ns/op
|
||||
SqliteBench.versionNumber_jni avgt 25 14.054 ± 4.949 ns/op
|
||||
SqliteBench.versionNumber_plainJava avgt 25 0.651 ± 0.162 ns/op
|
||||
load average after the run: 5.97 6.05 4.10
|
||||
@@ -0,0 +1,14 @@
|
||||
$ jextract -t demo.sqlite -l sqlitez --include-function sqlite3_libversion --header-class-name sqlite3_h /usr/include/sqlite3.h
|
||||
static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup(System.mapLibraryName("sqlitez"), LIBRARY_ARENA)
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp <classes> V
|
||||
Exception in thread "main" java.lang.ExceptionInInitializerError
|
||||
at V.main(V.java:6)
|
||||
Caused by: java.lang.IllegalArgumentException: Cannot open library: libsqlitez.so
|
||||
at java.base/java.lang.foreign.SymbolLookup.libraryLookup(SymbolLookup.java:350)
|
||||
at java.base/java.lang.foreign.SymbolLookup.libraryLookup(SymbolLookup.java:301)
|
||||
|
||||
$ jextract -t demo.sqlite -l :libsqlite3.so.0 --include-function sqlite3_libversion --header-class-name sqlite3_h /usr/include/sqlite3.h
|
||||
static final SymbolLookup SYMBOL_LOOKUP = SymbolLookup.libraryLookup("libsqlite3.so.0", LIBRARY_ARENA)
|
||||
$ java --enable-native-access=ALL-UNNAMED -cp <classes> V
|
||||
sqlite3_libversion() = 3.45.1
|
||||
|
||||
Reference in New Issue
Block a user