Upcalls: Passing Java Code as a Function Pointer to a Foreign Function

An upcall is a call from native code back to Java code. An upcall stub enables you to pass Java code as a function pointer to a foreign function.

Consider the standard C library function qsort, which sorts the elements of an array:

void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void *, const void *));

It takes four arguments:

  • base: Pointer to the first element of the array to be sorted
  • nbemb: Number of elements in the array
  • size: Size, in bytes, of each element in the array
  • compar: Pointer to the function that compares two elements

The following example calls the qsort function to sort an int array. However, this method requires a pointer to a function that compares two array elements. The example defines a comparison method named Qsort::qsortCompare, creates a method handle to represent this comparison method, and then creates a function pointer from this method handle.

import java.lang.foreign.*;
import java.lang.invoke.*;
import java.lang.foreign.ValueLayout.*;

public class InvokeQsort {
        
    class Qsort {
        static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
            return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0));
        }
    }
    
    // Obtain instance of native linker
    final static Linker linker = Linker.nativeLinker();
    
    static int[] qsortTest(int[] unsortedArray) throws Throwable {
        
        int[] sorted = null;
        
        // Create downcall handle for qsort
        MethodHandle qsort = linker.downcallHandle(
            linker.defaultLookup().find("qsort").get(),
            FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.ADDRESS));
        
        // Create method handle for qsortCompare
        MethodHandle comparHandle = MethodHandles.lookup()
            .findStatic(Qsort.class,
                        "qsortCompare",
                        MethodType.methodType(int.class,
                                              MemorySegment.class,
                                              MemorySegment.class));
                                              
        // Create a Java description of a C function implemented by a Java method
        FunctionDescriptor qsortCompareDesc = FunctionDescriptor.of(
            ValueLayout.JAVA_INT,
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT));


        // Create function pointer for qsortCompare
        MemorySegment compareFunc = linker.upcallStub(comparHandle,
                                                      qsortCompareDesc,
                                                      Arena.ofAuto());        
        
        try (Arena arena = Arena.ofConfined()) {                    
        
            // Allocate off-heap memory and store unsortedArray in it                
            MemorySegment array = arena.allocateArray(ValueLayout.JAVA_INT,
                                                      unsortedArray);        
                    
            // Call qsort        
            qsort.invoke(array,
                        (long)unsortedArray.length,
                        ValueLayout.JAVA_INT.byteSize(),
                        compareFunc);
            
            // Access off-heap memory
            sorted = array.toArray(ValueLayout.JAVA_INT);              
        }
        return sorted;
    }        
    
    public static void main(String[] args) {
        try { 
            int[] sortedArray = InvokeQsort.qsortTest(new int[] { 0, 9, 3, 4, 6, 5, 1, 8, 2, 7 });
            for (int num : sortedArray) {
                System.out.print(num + " ");
            }
            System.out.println();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }
}

The following sections describe this example in detail:

Defining the Java Method That Compares Two Elements

The following class defines the Java method that compares two elements, in this case two int values:

    class Qsort {
        static int qsortCompare(MemorySegment elem1, MemorySegment elem2) {
            return Integer.compare(elem1.get(ValueLayout.JAVA_INT, 0), elem2.get(ValueLayout.JAVA_INT, 0));
        }
    }

In this method, the int values are represented by MemorySegment objects. A memory segment provides access to a contiguous region of memory. To obtain a value from a memory segment, call one of its get methods. This example calls the get(ValueLayout.OfInt, long), where the second argument is the offset in bytes relative to the memory address's location. The second argument is 0 because the memory segments in this example store only one value.

Creating a Downcall Method Handle for the qsort Function

The following statements create a downcall method handle for the qsort function:

    // Obtain instance of native linker
    final static Linker linker = Linker.nativeLinker();
    
    static int[] qsortTest(int[] unsortedArray) throws Throwable {
        
        int[] sorted = null;
        
        // Create downcall handle for qsort
        MethodHandle qsort = linker.downcallHandle(
            linker.defaultLookup().find("qsort").get(),
            FunctionDescriptor.ofVoid(ValueLayout.ADDRESS,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.JAVA_LONG,
                                      ValueLayout.ADDRESS));

Creating a Method Handle to Represent the Comparison Method qsortCompare

The following statement creates a method handle to represent the comparison method Qsort::qsortCompare:

        // Create method handle for qsortCompare
        MethodHandle comparHandle = MethodHandles.lookup()
            .findStatic(Qsort.class,
                        "qsortCompare",
                        MethodType.methodType(int.class,
                                              MemorySegment.class,
                                              MemorySegment.class));

The MethodHandles.Lookup.findStatic(Class, String, MethodType) method creates a method handle for a static method. It takes three arguments:

  • The method's class
  • The method's name
  • The method's type: The first argument of MethodType::methodType is the method's return value's type. The rest are the types of the method's arguments.

Creating a Function Pointer from the Method Handle compareHandle

The following statement creates a function pointer from the method handle compareHandle:

        // Create a Java description of a C function implemented by a Java method
        FunctionDescriptor qsortCompareDesc = FunctionDescriptor.of(
            ValueLayout.JAVA_INT,
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT),
            ValueLayout.ADDRESS.withTargetLayout(ValueLayout.JAVA_INT));

        // Create function pointer for qsortCompare
        MemorySegment compareFunc = linker.upcallStub(comparHandle,
                                                      qsortCompareDesc,
                                                      Arena.ofAuto());  

The Linker::upcallStub method takes three arguments:

  • The method handle from which to create a function pointer
  • The function pointer's function descriptor; in this example, the arguments for FunctionDescriptor.of correspond to the return value type and arguments of Qsort::qsortCompare
  • The arena to associate with the function pointer. The static method Arena.ofAuto() creates a new arena that is managed, automatically, by the garbage collector.

Allocating Off-Heap Memory to Store the int Array

The following statements allocate off-heap memory, then store the int array to be sorted in it:

        try (Arena arena = Arena.ofConfined()) {                    
        
            // Allocate off-heap memory and store unsortedArray in it                
            MemorySegment array = arena.allocateArray(ValueLayout.JAVA_INT,
                                                      unsortedArray);

Calling the qsort Function

The following statement calls the qsort function:

            // Call qsort        
            qsort.invoke(array,
                        (long)unsortedArray.length,
                        ValueLayout.JAVA_INT.byteSize(),
                        compareFunc);

In this example, the arguments of MethodHandle::invoke correspond to those of the standard C library qsort function.

Copying the Sorted Array Values from Off-Heap to On-Heap Memory

Finally, the following statement copies the sorted array values from off-heap to on-heap memory:

            // Access off-heap memory
            sorted = array.toArray(ValueLayout.JAVA_INT);