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:
2026-10-30 16:36:09 +00:00
co-authored by Claude Sonnet 5.5
parent 41101bea40
commit ecceaa8b32
37 changed files with 2867 additions and 0 deletions
+16
View File
@@ -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
+26
View File
@@ -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(
+14
View File
@@ -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
+18
View File
@@ -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)
+6
View File
@@ -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]]
+21
View File
@@ -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
+18
View File
@@ -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
+8
View File
@@ -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
+13
View File
@@ -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
+14
View File
@@ -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
+14
View File
@@ -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