javajava-17java-ffm

OpenJDK 17 - JEP 412: Foreign Function & Memory API (Incubator)


What is wrong with this Java 17 CLinker.getInstance().downcallHandle(CLinker.systemLookup().lookup("radixsort"), ...); with reference to reverse-engineering JEP 412: Foreign Function & Memory API (Incubator) from JEP 419: Foreign Function & Memory API (Second Incubator) as the documentation is elided?

Please refer to the erratum.

JEP 419: Foreign Function & Memory API (Second Incubator) works fine as in the following code snippet and output:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
// import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;
import jdk.incubator.foreign.ValueLayout;

// JEP 419: Foreign Function & Memory API (Second Incubator)
class ForeignFunctionMemoryAPISecondIncubator {
   private void radixsort() throws Throwable {

        // 1. Find foreign function on the C library path
        CLinker linker = CLinker.systemCLinker();
    // .get() // Or // .orElseThrow()
        MethodHandle radixSort = linker.downcallHandle(
            linker.lookup("radixsort").get(),
            FunctionDescriptor.ofVoid(ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.JAVA_CHAR));

        // 2. Allocate on-heap memory to store four strings
        String[] javaStrings   = { "mouse", "cat", "dog", "car" };
        System.out.println("radixsort input: " + Arrays.toString(javaStrings));

        // 3. Allocate off-heap memory to store four pointers
    // https://openjdk.org/jeps/419 // Erratum // error: cannot find symbol ofSequence
        /*
    MemorySegment offHeap  = MemorySegment.allocateNative(
                             MemoryLayout.ofSequence(javaStrings.length,
                                                     ValueLayout.ADDRESS), ...);        
    */

        SegmentAllocator allocator = SegmentAllocator.implicitAllocator();
        MemorySegment offHeap = allocator.allocateArray(ValueLayout.ADDRESS, javaStrings.length);

        // 4. Copy the strings from on-heap to off-heap
        for (int i = 0; i < javaStrings.length; i++) {
            // Allocate a string off-heap, then store a pointer to it
            MemorySegment cString = SegmentAllocator.implicitAllocator().allocateUtf8String(javaStrings[i]);
            offHeap.setAtIndex(ValueLayout.ADDRESS, i, cString);
        }

        // 5. Sort the off-heap data by calling the foreign function
    // invoke // unreported exception Throwable; must be caught or declared to be thrown
        radixSort.invoke(offHeap, javaStrings.length, MemoryAddress.NULL, '\0');

        // 6. Copy the (reordered) strings from off-heap to on-heap
        for (int i = 0; i < javaStrings.length; i++) {
            MemoryAddress cStringPtr = offHeap.getAtIndex(ValueLayout.ADDRESS, i);
            javaStrings[i] = cStringPtr.getUtf8String(0);
        }

        System.out.println("radixsort output: " + Arrays.toString(javaStrings));
    }

    public static void main(String[] args) throws Throwable {
        System.out.println(String.format("Java Version: %s", System.getProperty("java.version")));

    var incubatorForeignFunctionMemoryAPISecond = new ForeignFunctionMemoryAPISecondIncubator();
        incubatorForeignFunctionMemoryAPISecond.radixsort();
    }
}

// Output
/*
WARNING: Using incubator modules: jdk.incubator.foreign
warning: using incubating module(s): jdk.incubator.foreign
1 warning
Java Version: 18.0.2
radixsort input: [mouse, cat, dog, car]
radixsort output: [car, cat, dog, mouse]
*/

However, JEP 412: Foreign Function & Memory API (Incubator) throws the error as in the following code snippet and exception output:

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodType;
import java.util.Arrays;
import jdk.incubator.foreign.CLinker;
import jdk.incubator.foreign.FunctionDescriptor;
import jdk.incubator.foreign.MemoryAccess;
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.ResourceScope;
import jdk.incubator.foreign.SegmentAllocator;

// JEP 412: Foreign Function & Memory API (Incubator)
class ForeignFunctionMemoryAPIIncubator {

   private void radixsort() throws Throwable {

    // 1. Find foreign function on the C library path
    MethodHandle radixSort = CLinker.getInstance().downcallHandle(
                             CLinker.systemLookup().lookup("radixsort").get(),
            MethodType.methodType(MemoryAddress.class, int.class, MemoryAddress.class, char.class),
            FunctionDescriptor.of(CLinker.C_POINTER, CLinker.C_INT, CLinker.C_POINTER, CLinker.C_CHAR));


        // 2. Allocate on-heap memory to store four strings
        String[] javaStrings   = { "mouse", "cat", "dog", "car" };
        System.out.println("radixsort input: " + Arrays.toString(javaStrings));

        // 3. Allocate off-heap memory to store four pointers
    // https://openjdk.org/jeps/412 // Erratum // error: cannot find symbol ofSequence
    /*
    MemorySegment offHeap  = MemorySegment.allocateNative(
                             MemoryLayout.ofSequence(javaStrings.length,
                                                     CLinker.C_POINTER), ...);
        */

    try (ResourceScope scopeResource = ResourceScope.newConfinedScope()) {
        SegmentAllocator allocatorSegment = SegmentAllocator.arenaAllocator(scopeResource);

            MemorySegment offHeap = allocatorSegment.allocateArray(CLinker.C_POINTER, javaStrings.length);
   
        // 4. Copy the strings from on-heap to off-heap
        for (int i = 0; i < javaStrings.length; i++) {
                // Allocate a string off-heap, then store a pointer to it
                MemorySegment cString = CLinker.toCString(javaStrings[i], ResourceScope.newImplicitScope());
                MemoryAccess.setAddressAtIndex(offHeap, i, cString.address());
        }

            // 5. Sort the off-heap data by calling the foreign function
        // invoke // unreported exception Throwable; must be caught or declared to be thrown
            radixSort.invoke(offHeap.address(), javaStrings.length, MemoryAddress.NULL, '\0');

            // 6. Copy the (reordered) strings from off-heap to on-heap
        for (int i = 0; i < javaStrings.length; i++) {
            MemoryAddress cStringPtr = MemoryAccess.getAddressAtIndex(offHeap, i);
            // https://openjdk.org/jeps/412 // Erratum // error: cannot find symbol toJavaStringRestricted
            // javaStrings[i] = CLinker.toJavaStringRestricted(cStringPtr);
            javaStrings[i] = CLinker.toJavaString(cStringPtr);
        }
        }
        System.out.println("radixsort output: " + Arrays.toString(javaStrings));
    }

    public static void main(String[] args) throws Throwable {
        System.out.println(String.format("Java Version: %s", System.getProperty("java.version")));

    var functionMemoryAPIIncubatorForeign = new ForeignFunctionMemoryAPIIncubator();
        functionMemoryAPIIncubatorForeign.radixsort();
    }
}

Output

WARNING: Using incubator modules: jdk.incubator.foreign
warning: using incubating module(s): jdk.incubator.foreign
1 warning
Java Version: 17.0.2
Exception in thread "main" java.lang.IllegalArgumentException: Carrier size mismatch: char != b8[abi/kind=CHAR]
    at jdk.incubator.foreign/jdk.internal.foreign.Utils.checkPrimitiveCarrierCompat(Utils.java:111)
    at jdk.incubator.foreign/jdk.internal.foreign.abi.SharedUtils.checkCompatibleType(SharedUtils.java:230)
    at jdk.incubator.foreign/jdk.internal.foreign.abi.SharedUtils.checkFunctionTypes(SharedUtils.java:251)
    at jdk.incubator.foreign/jdk.internal.foreign.abi.aarch64.CallArranger.getBindings(CallArranger.java:99)
    at jdk.incubator.foreign/jdk.internal.foreign.abi.aarch64.CallArranger.arrangeDowncall(CallArranger.java:128)
    at jdk.incubator.foreign/jdk.internal.foreign.abi.aarch64.macos.MacOsAArch64Linker.downcallHandle(MacOsAArch64Linker.java:84)
    at jdk.incubator.foreign/jdk.internal.foreign.AbstractCLinker.downcallHandle(AbstractCLinker.java:44)
    at ForeignFunctionMemoryAPIIncubator.radixsort(ForeignFunctionMemoryAPIIncubator.java:19)
    at ForeignFunctionMemoryAPIIncubator.main(ForeignFunctionMemoryAPIIncubator.java:68)

Reverse-engineering JEP 412: Foreign Function & Memory API (Incubator) from JEP 419: Foreign Function & Memory API (Second Incubator) not working as expected.


Solution

  • Here's the answer:

    MethodHandle radixSort = CLinker.getInstance().downcallHandle( CLinker.systemLookup().lookup("radixsort").get(), MethodType.methodType(void.class, MemoryAddress.class, int.class, MemoryAddress.class, int.class), FunctionDescriptor.ofVoid(CLinker.C_POINTER, CLinker.C_INT, CLinker.C_POINTER, CLinker.C_INT));