Skip to content

JNI and Java API

Augists edited this page Apr 24, 2026 · 4 revisions

JNI and Java API

The Java classes and the ABI of libmtpnddjni.so are identical on both branches: the same Java code runs unchanged against either the feature/c .so (C + Sylvan + Lace) or the feature/go .so (pure Go, goroutine-based). Drop in whichever libmtpnddjni.so you want benchmarked.

The Java API lives under jni/. It consists of:

  • A JNI shim compiled into libmtpnddjni.so
    • feature/c: jni/src/main/native/mtpndd_jni.c → Sylvan/Lace C core
    • feature/go: jni/main.go → pure-Go core, via cgo -buildmode=c-shared
  • Pure-Java classes in jni/src/main/java/org/ants/mtpndd/:
    • MTPNDDEngine — lifecycle + operation entry points
    • MTPNDD — thin handle over a native node pointer
    • MTPNDDConfig — builder-pattern configuration

Build

The top-level CMake build produces build/jni/libmtpnddjni.so when MTPNDD_BUILD_JNI=ON (default).

cmake -B build
cmake --build build -j                 # builds native C + JNI .so

Then build the Java JAR:

cd jni
mvn -DskipTests package                # produces target/mtpndd-java-0.1.0-SNAPSHOT.jar
mvn test                               # runs the JUnit 5 suite

Library Path

The Java side resolves the native library in this order:

  1. org.ants.mtpndd.library.path system property (explicit path to libmtpnddjni.so)
  2. Auto-discovery under the project's build/jni/ (new layout) or jni/build/ (legacy standalone build)
  3. java.library.path via System.loadLibrary("mtpnddjni")

Explicit override:

java -Dorg.ants.mtpndd.library.path="$PWD/build/jni/libmtpnddjni.so" \
     -cp jni/target/mtpndd-java-0.1.0-SNAPSHOT.jar:jni/target/test-classes \
     org.ants.mtpndd.NQueensMTPNDD 8 onehot

Quick Example

import org.ants.mtpndd.*;

MTPNDDEngine.init(new MTPNDDConfig.Builder()
    .workers(6)
    .mtpnddNodeTableSize(1L << 20)
    .operationCacheSize(1L << 20)
    .build());

int fieldA = MTPNDDEngine.declareField(8);   // 8-bit field
int fieldB = MTPNDDEngine.declareField(4);
MTPNDDEngine.generateFields();

MTPNDD x = MTPNDD.getVar(fieldA, 0);         // literal: fieldA bit 0 = 1
MTPNDD y = MTPNDD.getNotVar(fieldA, 1);      // literal: fieldA bit 1 = 0
MTPNDD f = MTPNDDEngine.and(x, y);
MTPNDDEngine.ref(f);

double sat = MTPNDDEngine.satCount(f);
System.out.println("sat count = " + sat);

MTPNDDEngine.deref(f);
MTPNDDEngine.shutdown();

MTPNDDEngine (static facade)

Lifecycle

Method Notes
init(MTPNDDConfig) Initialize Lace + Sylvan + MTPNDD with the given config
shutdown() Shut down in the correct order (Sylvan before Lace)
isInitialized() true between init and shutdown

Field management

Method Notes
int declareField(int bitWidth) Register a field, returns its id. Must precede generateFields()
generateFields() Finalize; afterwards declareField is no longer permitted
MTPNDDFieldInfo getFieldInfo(int fieldId) Field metadata (bit width, var offset, …)

Boolean ops

and, or, not, diff, exist(value, fieldId). Batched forms: andBatch(lefts[], rights[]), orReduce(values[]), andReduce(values[]).

Counting / introspection

satCount, minZeros, leafCount, abstractPlus(node, fieldId), abstractPlusValidate(node).

Reference management

ref(node), deref(node). Every node returned by an op is already referenced; call deref when you're done with it.

MT (multi-terminal) operations

makeFraction(numer, denom), makeDouble(value), plus, minus, times, divide, leafGc().

Stats and timing

stats() returns native counters (node table occupancy, op cache hits, GC events). resetTiming() / printTimingReport() control Java-side timing around JNI calls.

MTPNDD (node handle)

Thin wrapper around a native pointer with ergonomic instance methods:

  • Boolean: and(other), or(other), diff(other), not(), exist(fieldId)
  • Counting: satCount(), minZeros()
  • Predicates: isTrue(), isFalse(), isTerminal()
  • Introspection: getFieldId(), getEdges() (returns MTPNDDEdges)
  • Lifecycle: ref(), deref()
  • Static builders: MTPNDD.getVar(field, idx), getNotVar(field, idx), getTrue(), getFalse()

MTPNDDConfig.Builder

Fluent builder mirroring every field in the C mtpndd_pal_config_t. See Configuration Reference for defaults and semantics.

MTPNDDConfig cfg = new MTPNDDConfig.Builder()
    .workers(6)                            // 0 = auto-detect
    .laceDequeSize(1L << 20)
    .bddNodeTableSize(1L << 22)
    .mtpnddNodeTableSize(1L << 20)
    .operationCacheSize(1L << 20)
    .quickGrowthThreshold(0.75)            // -1 to disable
    .edgeBucketCount(8)                    // power of 2, ≤ 64
    .nodetableBucketCount(65537)
    .nodeSlabCapacity(1024)
    .edgeEntrySlabCapacity(4096)
    .nodetableEntrySlabCapacity(2048)
    .edgeMapSlabCapacity(2048)
    .build();

Testing the JNI Build

The JUnit 5 suite lives at jni/src/test/java/org/ants/mtpndd/. @BeforeAll helpers discover the .so from build/jni/ (current layout) or jni/build/ (legacy). Running mvn test exercises both the boolean path (nqueens-style) and the MT/fraction path.

feature/go JNI Bridge

The Go-side bridge at jni/main.go + jni/exports.go presents the same JNI surface but auto-tunes the Go engine for long-running JNI hosts. Callers never set these — they're hardcoded in mapInitArgs when the init path detects the JNI entry:

Setting Go-native default JNI override Why
SpawnPairThreshold 4 1024 Disable in-Go goroutine spawning. SRE's N Java worker threads already saturate cores making independent JNI calls; in-Go spawn became 25 %+ of CPU in runtime.futex / runtime.schedule. Cut fattree08 MF=3 from 28.9 s → 15.8 s (v1.9)
NDD.CacheClearInterval 2²¹ (~2 M puts) 2⁶² (effectively off) Go's strong-ref design means periodic clears drop otherwise-live hits; cachePutCount atomic contention also showed up in profiles (v1.10 / v1.14)
BDD.CacheClearInterval 2²¹ 2⁶² Same
SlabChunkSize 2¹⁸ = 256 K nodes same (ignores nodeSlabCapacity) sre-ndd passes 2048 as a C batch-size constant; applying it as a Go chunk size shrinks chunks 128× and overflowed the chunk directory on ft12+. Fixed in 2cf68e8 by ignoring that field

MTPNDD_BDD_SPAWN_CUTOFF env var is not honoured by the Go backend (it's a Sylvan concept). Optional -tags pprof build exposes an HTTP pprof endpoint — useful for profiling under the sre-ndd harness.

Handle representation: the Go bridge casts *mtpndd.Node to uintptr and hands that to Java as jlong. The same cast goes in the reverse direction on method entry. No handle table, no weak wrapper — relies on feature/go's strong-ref lifetime model keeping nodes alive until MTPNDDEngine.shutdown() (which triggers mtpndd.Reset()).