JDK-8318645 : Add Panama feature to pass heap segments to native code
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang.foreign
  • Priority: P2
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 22
  • Submitted: 2023-10-23
  • Updated: 2023-10-30
  • Resolved: 2023-10-30
Related Reports
CSR :  
Description
Summary
-------

Add a foreign linker option to allow passing heap segments to critical native functions.

Problem
-------

Currently the foreign linker (`java.lang.foreign.Linker`) allows linking downcall handles that only accepts off-heap memory segments where addresses are expected by the native function.

This is fine when code calling the downcall handle fully controls the allocation of these memory segments. However, in some cases there are existing APIs that accept Java arrays whose data needs to be passed to the native function. This can currently be done by allocating a new off-heap memory segment, then copying the contents of the array into it, calling the native function, and afterwards copying the potentially changed contents of the off-heap segment back into the array.

As one might imagine, this extra allocation + 2 copies have an impact on performance that becomes larger with the size of the Java array. It is not always possible to eliminate the Java array by replacing it with an off-heap memory segment, since the Java array might appear in an API signature, so replacing it would break compatibility.

Passing a Java array to native code without an extra allocation + copies is something that is possible with JNI as well, through the `GetPrimitiveArrayCritical` and `ReleasePrimitiveArrayCritical` functions. Meaning that if existing code that uses JNI together with these functions, wants to migrate to using the FFM API, it would have to accept a performance regression.

Solution
--------

Add a linker option that can be used to specify that a downcall handle can accept heap memory segments. This will allow code to wrap a Java array into a memory segment, using `MemorySegment::ofArray`, and pass that memory segment to the donwcall handle. Heap segments created through `MemorySegment::ofBuffer` will also be supported.

Using this option imposes several limitation on the downcall handle:

* The native function that it calls must complete quickly, since the JVM is blocked from reaching a safepoint during this call.
* The native function must not call back into Java (this is also enforced by the FFM API at runtime).

This solution is limited to heap memory segments that are passed *by reference* to downcall method handles (e.g. whose corresponding layout in the function descriptor used to link the function is an `AddressLayout`). So, for example, it is not possible to store a reference to a heap segment into an existing native segment modelling an off-heap struct, and then pass that native segment to a downcall method handle (an exception would occur when trying to write the heap segment to the struct e.g. using `MemorySegment::set(AddressLayout, long, MemorySegment)`).

The existing `Linker.Option.critical` is enhanced to take an additional `boolean` argument, which indicates if heap segments should be allowed or not.

We want to retain the ability to create a critical downcall handle that does not accept heap segments, as there is some extra overhead involved when passing purely off-heap segments if heap segments are also allowed.

Specification
-------------
Note that we also add a note about passing heap segments to downcallHandle, to specify in which cases an exception is thrown by the returned MethodHandle. This was missing prior.

```
diff --git a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java
index 738ff19a5170..3ca9ca93793d 100644
--- a/src/java.base/share/classes/java/lang/foreign/Linker.java
+++ b/src/java.base/share/classes/java/lang/foreign/Linker.java
@@ -565,10 +565,13 @@ static Linker nativeLinker() {
      * <p>
      * The returned method handle will throw an {@link IllegalArgumentException} if the {@link MemorySegment}
      * representing the target address of the foreign function is the {@link MemorySegment#NULL} address. If an argument
-     * is a {@link MemorySegment}, whose corresponding layout is a {@linkplain GroupLayout group layout}, the linker
+     * is a {@link MemorySegment} whose corresponding layout is a {@linkplain GroupLayout group layout}, the linker
      * might attempt to access the contents of the segment. As such, one of the exceptions specified by the
      * {@link MemorySegment#get(ValueLayout.OfByte, long)} or the
-     * {@link MemorySegment#copy(MemorySegment, long, MemorySegment, long, long)} methods may be thrown.
+     * {@link MemorySegment#copy(MemorySegment, long, MemorySegment, long, long)} methods may be thrown. If an argument
+     * is a {@link MemorySegment} whose corresponding layout is an {@linkplain AddressLayout address layout}, the linker
+     * will throw an {@link IllegalArgumentException} if the segment is a heap memory segment, unless heap memory segments
+     * are explicitly allowed through the {@link Linker.Option#critical(boolean)} linker option.
      * The returned method handle will additionally throw {@link NullPointerException} if any argument
      * passed to it is {@code null}.
      *
@@ -776,9 +779,18 @@ static StructLayout captureStateLayout() {
          * <p>
          * Using this linker option when linking non-critical functions is likely to have adverse effects,
          * such as loss of performance, or JVM crashes.
+         * <p>
+         * Critical functions can optionally allow access to the Java heap. This allows clients to pass heap
+         * memory segments as addresses, where normally only off-heap memory segments would be allowed. The memory region
+         * inside the Java heap is exposed through a temporary native address that is valid for the duration of the
+         * function call. Use of this mechanism is therefore only recommend when a function needs to do
+         * short-lived access to Java heap memory, and copying the relevant data to an off-heap memory segment would be
+         * prohibitive in terms of performance.
+         *
+         * @param allowHeapAccess whether the linked function should allow access to the Java heap.
          */
-        static Option critical() {
-            return LinkerOptions.Critical.INSTANCE;
+        static Option critical(boolean allowHeapAccess) {
+            return allowHeapAccess ? LinkerOptions.Critical.ALLOW_HEAP : LinkerOptions.Critical.DONT_ALLOW_HEAP;
         }
     }
 }
```


Comments
Moving to Approved.
30-10-2023