Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8264781

Implementation of Foreign Function and Memory API (Incubator)

    XMLWordPrintable

    Details

    • Type: CSR
    • Status: Closed
    • Priority: P4
    • Resolution: Approved
    • Fix Version/s: 17
    • Component/s: core-libs
    • Labels:
      None
    • Compatibility Kind:
      source, binary, behavioral
    • Compatibility Risk:
      medium
    • Compatibility Risk Description:
      Hide
      The introduction of the `ResourceScope` abstraction, which is now a _required_ parameter when creating resources managed by the API (e.g. memory segments) is source incompatible. On top of that, some of the abstractions (segments, valists) no longer implement the `AutoCloseable` interface, leading to more source compatibility issues. Finally, some of the renaming around static factory methods has also potential for breaking existing source code.
      Show
      The introduction of the `ResourceScope` abstraction, which is now a _required_ parameter when creating resources managed by the API (e.g. memory segments) is source incompatible. On top of that, some of the abstractions (segments, valists) no longer implement the `AutoCloseable` interface, leading to more source compatibility issues. Finally, some of the renaming around static factory methods has also potential for breaking existing source code.
    • Interface Kind:
      Java API, add/remove/modify command line option
    • Scope:
      JDK

      Description

      Summary

      This CSR refers to the latest iteration of the Foreign Memory Access and Foreign Linker API originally targeted for Java 14 and Java 16, respectively, with the goal of refining some of the API rough edges, as well as addressing the feedback received so far from developers.

      Problem

      Real-world use of the Foreign Memory Access and Foreign Linker APIs revealed some remaining usability issues, listed below:

      • Managing the life-cycle of memory segments is not intuitive, and can lead developers to write code that performs less efficiently than expected. For instance, the MemorySegment API heavily relies on dynamic ownership changes (see MemorySegment::handoff), where the ownership of a segment is updated in place, by killing the old segment and return a new one with new ownership characteristics. While for confined segment such an operation can be supported at a relatively low cost, the same cannot be said for shared segment (introduced in Java 16); since shared segment feature a relatively expensive close operation, it is generally not feasible to dynamically change ownership of a shared segment in critical code paths.

      • Shared segments makes it hard to support asynchronous IO operations (e.g. SocketChannel::read(ByteBuffer)) on buffer views obtained by such segments; this is due to the fact that a shared segment can be closed by any thread - possibly in the middle of an async IO operation. The API did not provide any workaround for this use case; for this reason, asynchronous operations on byte buffer views obtained from shared segments are not supported (as stated in the javadoc for MemorySegment::asByteBuffer), and, as a result, clients are faced with a choice between shared segment and full byte buffer interoperability.

      • When linking a foreign function, the address of said function has to be specified ahead of time (e.g. when the native method handle is obtained); while this is good in most cases when the user wants to call a foreign functions, some use cases require more flexibility. Certain libraries in fact, provide extension points by means of function pointers, with a well-defined signature. In such cases, it would be preferable to obtain a native method handle without having to specify an address, and late-bind the address when the foreign call is made.

      • Allocating native memory can often be the source of performance bottlenecks in real world applications; while the API allows a more optimized allocation, using the NativeScope abstraction, not all the API points which perform allocation can be provided the desired allocation strategy. For instance, it is not possible to pass a NativeScope to a native function which returns a struct by value. In addition, NativeScope only represent a possible choice in the allocator design space: the allocation scheme it provides is, essentially, a thread-confined arena-based allocation. It is not possible to make NativeScope more general and/or to support different allocation schemes.

      • Some of the static factory methods in the API are hostile to the use of static imports - for instance, consider MemoryLayout.ofSequence.

      • Both the Foreign Memory Access API and the Foreign Linker API contained unsafe API points, called restricted methods; access to restricted methods is controlled by a read-only JDK property (namely, foreign.restricted), whose value must be explicitly set to permit for access to occur. While this solution is good enough to make restricted methods disabled by default, the use of a JDK property as a means to do so is rather ad-hoc and brittle.

      Solution

      Here we describe the main ideas behind the API changes brought forward in this CSR:

      • The new API models segment life-cycle with an explicit abstraction, named ResourceScope. A ResourceScope is a stateful abstraction which can be either alive, or closed. Resources managed by a resource scope can only be accessed if the owning resource scope is alive. Resource scopes can be confined (e.g. only thread which created the scope has access to the managed resources) or shared, and can be (optionally) associated with a Cleaner object, which is responsible for closing the scope in case the scope becomes unreachable. By making the ResourceScope abstraction a first-class citizen in the API, the semantics of other abstractions such as MemorySegment, MemoryAddress, VaList becomes clearer: these abstractions cannot, in fact, be closed in isolation - they can instead be closed, by closing the resource scope they belong to. This allowed us to greatly simplify the MemorySegment abstraction, and remove all API points related to dynamic ownership changes. Also, MemorySegment is no longer AutoCloseable (but ResourceScope is), which further clarifies the semantics of the API when multiple segments (e.g. slices) are owned by the same scope.

      • As we have seen, there are cases where APIs might want to temporarily inhibit deterministic deallocation. The API now allows clients to acquire a ResourceScope; this returns a ResourceScope.Handle instance. This idiom can be used to perform operations on a segment (or any other resource managed by the scope) while the scope handle is being held by the client. Any attempt to close the resource scope when one or more handles have been acquired will result in an exception. When a client no longer need to work on a resource associated with the acquire scope, the acquire handle can be released. This allows to address interoperability issues between memory segments and byte buffer views in the context of asynchronous IO operations.

      • The CLinker interface now offers an overload of downcallHandle which accept no Addressable parameter; the native method handle returned by this overload instead accepts an additional prefix argument (of type Addressable) which can be used to specify the foreign function entry point at call-time, rather than at link-time.

      • A new abstraction, namely SegmentAllocator, replaces NativeScope. SegmentAllocator is a functional interface which provides several default methods which help when allocating off-heap memory from Java objects (e.g. on heap arrays). The SegmentAllocator interface provides some ready-made allocators, such as an arena allocator, which provides the same optimized allocation scheme previously provided by NativeScope. Since now SegmentAllocator and ResourceScope are independent entities, clients can combine them at will, meaning that it is now possible. for instance, to create an arena allocator out of a shared resource scope. Several API points in CLinker have been enhanced to take an extra SegmentAllocator parameter, instead of the NativeScope parameter accepted previously. Most notably, all native method handles generated by CLinker::downcallHandle will now accept an additional SegmentAllocator parameter, in case the foreign function returns a MemorySegment. (An additional overload of CLinker::downcallHandle allows the user to select an allocator at link-time, if desired). To facilitate conversion from ResourceScope to SegmentAllocator, some API points in CLinker (e.g. CLinker::toCString) define overloads accepting a ResourceScope parameter; this scope is then internally converted into a so called scoped allocator (an allocator which allocates segment tied to the provided scope). This makes the API simpler to use in case clients do not require an alternate, more efficient allocation strategy.

      • Some of the static factory methods in the API were renamed to be more friendly with respect to static imports. All factories in the layout API have been renamed - e.g. from ofXYZ to XYZlayout. In the newly added APIs (ResourceScope and SegmentAllocator) we used the new prefix whenever allocating a new instance was an important part of the API contract (this is especially true for resource scope creation). We also used the of prefix to denote a loose conversion - e.g. SegmentAllocator.ofScope is used to create an allocator from a resource scope.

      • To allow access to restricted methods in the API, a new experimental command line option in the Java launcher is added, namely --enable-native-access=<module list>. This options accepts a list of modules (separated by commas), where a module name can also be ALL-UNNAMED (for the unnamed module). Adding this command line flag to the launcher has the effect of allowing access to restricted methods to a given set of modules (the list of modules specified in the command line option). Access to restricted methods from any other module not in the list is disallowed and will result in an IllegalCallerException.

      Specification

      A specdiff of the changes as of April 29th, 2021 has been attached to this CSR (v2).

      A link of the latest javadoc (as of April 29th, 2021) is included below:

      http://cr.openjdk.java.net/~mcimadamore/JEP-412/v2/javadoc/jdk/incubator/foreign/package-summary.html

      A link of the latest specdiff (as of April 29th, 2021) is included below:

      http://cr.openjdk.java.net/~mcimadamore/JEP-412/v2/specdiff/overview-summary.html

        Attachments

        1. specdiff_v0.zip
          488 kB
        2. specdiff_v1.zip
          382 kB
        3. specdiff_v2.zip
          424 kB
        4. specdiff-v3.zip
          444 kB

          Issue Links

            Activity

              People

              Assignee:
              mcimadamore Maurizio Cimadamore
              Reporter:
              mcimadamore Maurizio Cimadamore
              Reviewed By:
              Chris Hegarty, Jorn Vernee
              Votes:
              0 Vote for this issue
              Watchers:
              3 Start watching this issue

                Dates

                Created:
                Updated:
                Resolved: