jvm-rs/docs/native-ffi.md

3.8 KiB

Native Methods and FFI System

Location: crates/core/src/thread.rs, crates/core/src/native/

Library Loading

Native libraries are loaded dynamically using libloading:

  • Location: crates/core/src/main.rs
  • Supported platforms: Windows (.dll), Linux (.so)
  • Libraries loaded:
    • roast_vm - VM-specific native methods
    • jvm - Java virtual machine standard library
    • java - Java standard library

Libraries are registered with the VM via Vm::load_native_library() and stored in native_libraries: Arc<RwLock<Vec<(String, Library)>>>.

Native Method Registration

Native methods are registered through JNI's RegisterNatives function:

  • Location: crates/core/src/native/jni.rs
  • Process:
    1. Java code calls RegisterNatives() with method names, signatures, and function pointers
    2. VM generates JNI-formatted symbol names using generate_jni_method_name()
    3. Function pointers are stored in Vm::native_methods: DashMap<String, *const c_void>
    4. Filters prevent registration of certain methods (e.g., Thread operations)

Native Method Dispatch

When a native method is invoked:

  • Detection: MethodData::ACC_NATIVE flag identifies native methods
  • Location: crates/core/src/thread.rs
  • Flow:
    1. execute_method() checks if method has ACC_NATIVE flag
    2. If static, adds class reference to args; otherwise adds instance reference
    3. Calls invoke_native() with method reference and arguments

FFI System - libffi Integration

libffi is used to call native functions with correct calling conventions:

Call Interface Building

fn build_cif(&self) -> Cif {
    let mut args = vec![
        Type::pointer(), // JNIEnv*
        Type::pointer(), // jclass/jobject
    ];
    for v in self.desc.parameters {
        args.push(v.into())
    }
    let return_type = ...;
    Builder::new().args(args).res(return_type).into_cif()
}
  • Constructs a Call Interface (Cif) from method signature
  • Maps Java types to FFI types
  • Always adds JNIEnv* and jclass/jobject as first two parameters

Argument Marshalling

fn build_args(params, storage, jnienv) -> Vec<Arg>
  • Marshals Java values to native format
  • Converts references to object IDs (u32)
  • Boxes primitives for FFI passing
  • Stores all values in a temporary vector

Function Invocation

let cp = CodePtr::from_ptr(p);
cif.call::<ReturnType>(cp, built_args.as_ref());
  • Converts function pointer to CodePtr
  • Calls through libffi with correct return type handling

Type Mapping

Java Type FFI Type
byte i8
char u16
short i16
int i32
long i64
float f32
double f64
boolean i8
Object/Array pointer

JNI Function Table

A complete JNI function table is created and passed to native code:

  • Location: crates/core/src/native/jni.rs
  • Implementation:
    • 250+ JNI functions defined as unsafe extern "system" functions
    • Covers class operations, method invocation, field access, array operations, string handling
    • Many functions are stubs returning todo!() for unimplemented features

Unsafe Support (sun.misc.Unsafe)

Low-level unsafe operations are tracked:

  • Location: crates/core/src/native/unsafe.rs
  • Features:
    • Field offset registry mapping to class/field pairs
    • Off-heap memory allocation tracking
    • Base offset constant: 0x1_0000_0000

Key Implementation Characteristics

  • Thread-safe: All structures use DashMap, RwLock, or Mutex
  • JNI environment: Created per-thread, stored in VmThread::jni_env
  • Symbol lookup: Two-pass search (without params, then with params)
  • Error handling: Returns VmError::NativeError if symbols not found
  • Tracking: Maintains statistics on library resolution counts