JDK-8296952 : Foreign linker implementation update following JEP 434
  • Type: CSR
  • Component: core-libs
  • Sub-Component: java.lang.foreign
  • Priority: P3
  • Status: Finalized
  • Resolution: Unresolved
  • Fix Versions: 20
  • Submitted: 2022-11-14
  • Updated: 2022-11-15
Related Reports
CSR :  
Description
Summary
-------

Add a `Linker.Option` that can be used to capture native state that the VM might overwrite

Problem
-------

After a native call occurs which sets native state, such as the thread-local `errno` value, the state can be overwritten by a JVM implementation before it can be read through a subsequent native call, for instance because the implementation needs to allocate some native memory using an OS API in between calls.

See: https://bugs.openjdk.org/browse/JDK-8292302, https://bugs.openjdk.org/browse/JDK-8293829

Solution
--------

Add a `Linker.Option` which can be used to capture native state such as the `errno` value as part of another native call. This allows saving the value before it can be overwritten by a JVM implementation.

The native state is written to a native memory segment that is provided as an additional leading argument when calling a method handle that is linked with this linker option.

Since there could be multiple values that need saving (`errno`, `GetLastError`, `WSAGetLastError`), and the values that can be overwritten are dependent on the JVM implementation, the API accepts the values that should be saved in the form of a varargs array of strings. The layout of the native memory segment into which the values are saved is also determined by the implementation, since that is again implementation dependent. This layout also indicates at which locations in the segment values are saved, by having nested "field" layouts with the corresponding name of the saved value.

The values that can be saved by a particular implementation can be retrieved through an informative API that returns a `Set<String>` of supported names.

Specification
-------------

The following method is added  to the `Linker.Option` interface:

    /**
     * {@return A linker option used to save portions of the execution state immediately after
     *          calling a foreign function associated with a downcall method handle,
     *          before it can be overwritten by the Java runtime, or read through conventional means}
     * <p>
     * A downcall method handle linked with this option will feature an additional {@link MemorySegment}
     * parameter directly following the target address parameter. This memory segment must be a
     * native segment into which the captured state is written.
     *
     * @param capturedState the names of the values to save.
     * @see CaptureCallState#supported()
     */
    static CaptureCallState captureCallState(String... capturedState) {

The nested interface `Linker.Option.CaptureCallState` is added as well:

	/**
	 * A linker option for saving portions of the execution state immediately
	 * after calling a foreign function associated with a downcall method handle,
	 * before it can be overwritten by the runtime, or read through conventional means.
	 * <p>
	 * Execution state is captured by a downcall method handle on invocation, by writing it
	 * to a native segment provided by the user to the downcall method handle.
	 * <p>
	 * The native segment should have the layout {@linkplain CaptureCallState#layout associated}
	 * with the particular {@code CaptureCallState} instance used to link the downcall handle.
	 * <p>
	 * Captured state can be retrieved from this native segment by constructing var handles
	 * from the {@linkplain #layout layout} associated with the {@code CaptureCallState} instance.
	 * <p>
	 * The following example demonstrates the use of this linker option:
	 * {@snippet lang = "java":
	 * MemorySegment targetAddress = ...
	 * CaptureCallState ccs = Linker.Option.captureCallState("errno");
	 * MethodHandle handle = Linker.nativeLinker().downcallHandle(targetAddress, FunctionDescriptor.ofVoid(), ccs);
	 *
	 * VarHandle errnoHandle = ccs.layout().varHandle(PathElement.groupElement("errno"));
	 * try (MemorySession session = MemorySession.openConfined()) {
	 *     MemorySegment capturedState = session.allocate(ccs.layout());
	 *     handle.invoke(capturedState);
	 *     int errno = errnoHandle.get(capturedState);
	 *     // use errno
	 * }
	 * }
	 */
	sealed interface CaptureCallState extends Option
									  permits LinkerOptions.CaptureCallStateImpl {
		/**
		 * {@return A struct layout that represents the layout of the native segment passed
		 *          to a downcall handle linked with this {@code CapturedCallState} instance}
		 */
		StructLayout layout();

		/**
		 * {@return the names of the state that can be capture by this implementation}
		 */
		static Set<String> supported() {
			...
		}
	}