JDK-8282192 : Implementation of Foreign Function & Memory API (Preview)
  • Type: CSR
  • Component: core-libs
  • Priority: P3
  • Status: Closed
  • Resolution: Approved
  • Fix Versions: 19
  • Submitted: 2022-02-21
  • Updated: 2023-08-29
  • Resolved: 2022-05-04
Related Reports
CSR :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Description
Summary
-------

This CSR refers to the latest iteration of the Foreign Function & Memory API originally targeted for Java 17, with the goal of further refining and consolidating the API into a _preview_ API.

Problem
-------

Make the Foreign Function & Memory API a _preview_ API. In addition to some minor, necessary API differences and renamings (listed in the section below), feedback on the incubating API revealed the following issues:

 * Making a full scope abstraction available when operating on e.g. `MemorySegment` is too powerful: this allows clients of a `MemorySegment` to close a scope associated with a segment (which also brings down other resources associated with the same scope).
 * The mechanism for keeping a scope alive (`ResourceScope::keepAlive`) is not constrained enough to be truly useful. For instance, the ad-hoc nature of temporal dependencies makes it impossible to capture temporal dependencies between scopes in a static graph. This problem is discussed [here](https://inside.java/2021/10/12/panama-scope-dependencies/).
 * Memory layouts support can have unbounded size; this was introduced with the idea to support C's variable length arrays in structs, but was never used in full. In all cases where an unbounded sequence layout was used, a zero-length sequence layout could be used instead.
 * Closing a shared scope could issue exceptions at _both_ the closing and the accessing sites, or make the resource temporarily inaccessible for a certain period of time.
 * Alignment of constants in `ValueLayout` does not reflect real alignment (e.g. all constants are 1-byte aligned)

Solution
-------

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

 * First, as the API is no longer an incubating API, all classes have been moved into a package under the `java.base` module, namely `java/lang/foreign`;

 * Some abstractions in the previous iterations of the API have been removed, to achieve a tighter coupling with other JDK APIs. More specifically:
  - `jdk.incubator.foreign.MemoryHandles` has been dropped and all the var handle combinators moved where they belong, namely `java.lang.invoke.MethodHandles`
  - `jdk.incubator.foreign.SymbolLookup` has been dropped. Instead a new lookup (final) instance method has been added to `java.lang.ClassLoader`

 * The `ResourceScope` abstraction, present in previous iterations of the API has been renamed to `MemorySession`. This is to reflect the fact that a `MemorySession` is not always associated with a lexical scope (e.g. a a try-with-resources block).

 * `MemorySession` now supports an `isCloseable` predicate; namely, not all sessions can be closed. For instance, implicit and global sessions cannot be closed, and this is now reflected in the API. This also allows us to associate non-closeable session *views* with resources such as memory segments, which allows clients to access most of the details of a segment scope, without allowing the client to close the scope.

 * The API for keeping sessions alive has been simplified a lot; there's now only a method called `MemorySession::whileAlive` which takes a `Runnable`, and executes its action while the session is kept alive.

 * Support for unbounded sequence layouts has been dropped, with resulting simplifications in the layout API. To reduce some of the boilerplate associated with creating strided array element var handles (which were frequently created using unbounded sequence layouts), the API now offers a dedicated factory method: `MemoryLayout::arrayElementVarHandle` which allows clients to create the same strided var handles as before, without the need to manually wrap the element layout in a fictional container/sequence layout.

 * The logic for closing shared sessions has been improved; as a result closing a shared scope while another thread is accessing a resource associated with that scope will only result in a failure on the accessing thread.

 * All constants in `ValueLayout` now feature the correct alignment.


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

A specdiff and a javadoc of the changes as of February 21th, 2022 has been attached to this CSR.
Comments
Moving to Approved for JDK 19. Feedback for future iterations: If it has not already been added, I recommend an apiNote explicitly stating the Java Memory Model does not hold for off-heap access, etc. To use a @Restricted annotation, it may be helpful to switch some types from being interface-based to be classed-based since annotation inheritance only works on classes. Given that the interfaces in question are now sealed, switching to sealed abstract classes instead may not be much of an imposition for users of the API.
04-05-2022

I've added another attachment (v3) containing the final specdiff, which addresses most of the comments raised above. A browsable version of the same is available here: http://cr.openjdk.java.net/~mcimadamore/8282191/v3/specdiff_out/overview-summary.html While a link to the final javadoc is available here: http://cr.openjdk.java.net/~mcimadamore/8282191/v3/javadoc/java.base/module-summary.html
03-05-2022

Moving to Provisional. The javadoc and specdiff did not both reflect all the mentioned changes, including CLinker -> Linker renaming; I reviewed the javadoc that did have the listed changes present. FileChannel.map(java.nio.channels.FileChannel.MapMode,long,long,java.lang.foreign.MemorySession) IllegalArgumentException - If offset < 0 , size < 0 or offset + size < 0 . Might be clearer to see "if offset + size overflows the range of long" In the package-level comments for java.lang.foreign -- there are bad/missing JLS section numbers (or javadoc command config) " segment will be released at the end of the block, according to the semantics described in Section of The Java Language Specification." " in the same way as array access is, as described in Section of The Java Language Specification. " This might just be a generation artifact rather than an issue with the sources. FunctionDescriptor.argumentLayouts() returning List<MemoryLayout> Should a package-level statement to make along the lines of "Returned lists are immutable, etc." be included avoid per-method statements of the assumed properties of returned collections? MemoryAddress -- for the index method, I suggest explicitly stating whether or not negative indexes are considered valid. In MemoryLayout, in "..for a sequence layout S whose element layout is E and size is L, the size of S is that of E, multiplied by L" Missing italics for the first "L". "MemoryAddress Offset parameters -- can they be zero or negative?. If not, wording like "@param offset ..., must be non-negative" "@param offset ..., must be positive" depending on that the constraints are could be a succinct way to indicate this information. MemorySegment.asOverlappingSlice -- was returning an Optional<MemorySegment> considered instead of returning null for no overlap? MemorySession: " In this scenario, a client might consider using a closeable memory session. Closeable memory sessions are memory sessions that can be closed explicitly, as demonstrated in the following example:..." The try-with-resources example doesn't contain an explicit call to close; I suggest changing the wording to avoid implying it will. Likewise, the wording of isCloseable() Returns true, if this session can be closed explicitly. could be improved to avoid implying a try-with-resources close wouldn't work as well as a source-visible close. MemorySession.equals -- please explicitly state whether or not the can-be-closed status is considered in the equality calculation. Interface VaList "Per the C specification (see C standard 6.5.2.2 Function calls - item 6), " -- please state the version of the C standard being referenced.
19-04-2022

Attached specdiff_v2.zip. Re-proposed. * Link to javadoc: http://cr.openjdk.java.net/~mcimadamore/8282191/v2/javadoc/java.base/module-summary.html * Link to specdiff: http://cr.openjdk.java.net/~mcimadamore/8282191/v2/specdiff_out/overview-summary.html Let me know if I should reflect the new changes in the summary of this CSR.
06-04-2022

Some changes were made since this CSR was marked "provisional": * Support for the Constable interface in MemoryLayout and FunctionDescriptor dropped (for details see [1]) * Following internal feedback, CLinker has been renamed to Linker, and CLinker::systemCLinker renamed to Linker::nativeLinker * Due to poor "discoverability" of ClassLoader.findNative, we have brought back the SymbolLookup abstraction, already available in Java 18 The consistency of the javadoc has been improved significantly, and many issues were fixed. The term "native" is now used in two places: (i) to speak about "native" segments, that is, segments backed by off-heap memory (which has real, physical addresses). And (ii) native linker - the linker for the current architecture/OS. The use of the word "native" here seems consistent with what other APIs have done in this space (e.g. ByteOrder::nativeOrder, or Connection::nativeSQL). The `Linker` javadoc now clearly define what "native" means in the context of this API. New specdiff here: http://cr.openjdk.java.net/~mcimadamore/8282191/v2/specdiff_out/overview-summary.html [1] - https://mail.openjdk.java.net/pipermail/panama-dev/2022-March/016631.html
06-04-2022

[~mcimadamore], I suggest re-Proposing the API for another round of review and attaching new specdiffs showing the changes.
05-04-2022

I understand the concern with having some memory sessions being not closeable, despite `MemorySession` implementing `AutoCloseable`. In a way, this is not too dissimilar from what happens with `Collections::unmodifiableList`, which returns a plain `List` whose methods throw `UnsupportedOperationException`. While I realize that might not be a great precedent, I think there is one factor in this API that differentiates `MemorySession` from `List`: the client who owns a memory session, typically knows very well as to whether the session it's working with is closeable or not. For instance: ``` // in this case the session is obviously closeable try (MemorySession session = MemorySession.openConfined()) { ... } // in this case the session is obviously non-closeable MemorySession session = MemorySession.openImplicit(); ... ``` Since the code that creates a memory session is typically responsible for closing it down, that code knows whether a session is closeable or not. The only cases where a client cannot be sure are cases where the client does not own the session: ``` void doSomething(MemorySegment segment) { MemorySession session = segment.session() ... } ``` In this case, the client did not create a memory session - it just retrieved a session from an existing segment. Sure, a client might attempt to close the segment's session - but such a client should have no guarantee that the operation will actually succeed. Whether `MemorySession::close` will succeed or not depends on factors that are outside the control of the client (e.g. how the segment was created in the first place by the API the client is interacting with). In other words, non-closeable sessions are a way for API creators to _prevent_ clients of segments created within the API to perform sensitive operations such as `MemorySegment::close`. An alternate design was also considered - one which did not have the concept of non-closeable session, but instead removed the `MemorySegment::session` accessor from the API. While this allows API to protect their segments, such a design quickly proved to be too limiting, especially in cases where segments were created with a one-off memory session (not too uncommon a case in the native interop use case): ``` SegmentAllocator confinedMalloc = (size, align) -> MemorySegment.allocateNative(size, align, MemorySession.openConfined()); ... MemorySegment segment = malloc.allocate(100); ... segment ... // how do I close? ``` For this reason, we opted for a design where the session is always available from a memory segment, as that ensures that memory segments are usable either when managed by a shared session, or when managed by a standalone session. The creator of the session has the option, through non-closeable views, to restrict close operation on segments it returns, as follows: ``` private MemorySession apiSession = ... MemorySegment makeSegment(long size) { return MemorySegment.allocateNative(size, apiSession.asNonCloseable()); } ``` Since the API returns segments associated with a non-closeable session view, clients will not be able to close the session through the segment. And we felt that adding a separate type for this case felt like too much (as in this case the client getting an exception on close is essentially trying to violate an API invariant).
22-03-2022

My concern wrt Closeable is that are are *a lot* of Closeable classes so having one that may not really be Closeable in practice is going against a very strong precedent.
21-03-2022

I can add something re. marker interface, your suggestion seems good. I guess in my mental model, a marker interface is something like `Serializable` which you can use in your own classes to bring in some trait. This is not what `PathElement` is about. Also, now that I think more on this topic, I wonder if PathElement should be made Constable (since the whole point of having this API shape, as pointed out above, is to allow these things to be represented as loadable pool constants), in which case the interface will acquire one more method (and so will not be marker anymore, I suppose). I have to think more about whether we want `Constable`. As for MemorySession, the concern is in part naming, but there is also something more; a session has other dynamic properties associated with it; an optional owner thread. While you can represent one property in a special subtype, things get messy if you have multiple properties (e.g. NonCloseableConfinedSession) and you get an explosion of types. Note that "closing" is just _one_ of the thing that a client might want to have restricted; perhaps some clients would want to inhibit the ability of a session to "register" further cleanup action (so as to disallow 3rd party clients from allocating new memory on that session). So, I view this more as a permission/access control kind of thing. Right now there's one (the most common), but it's is not excluded that we might have others too. Also, the `asNonCloseable` method is very similar to `MemorySegment::asReadOnly`. It is a method that returns a view with certain properties. Finally, since this method will only really be used in very specific cases (e.g. if the user wants to create segments associated with a non-closeable view of a session, to prevent closing), adding a full blown type just for this seemed (and still seems) a bit of an overkill. Another option we have explored in this space (but which requires more thought and bake time) is the ability for the user to define a custom session - that is, add a non-sealed extension point for `MemorySession`, some sort of `ForwardingMemorySession` that the user can use to add custom behavior on top of an existing session, using delegation. This idiom is very powerful and would indeed also subsume `asNonCloseable`.
21-03-2022

For MemoryLayout.PathElement, I recommend an explicit statement in interface's javadoc that it is a marker interface. E.g. replace "Instances of this class are used to form layout paths. ..." with something like. "A marker interface used to form layout paths. ..." If the main concern is naming, I recommend "MemorySession" be the good, closeable version and another name used for the non-closeable superinterface. Thanks for the additional context or ValueLayout. I've thought about it a bit more and don't see a better alternative as of yet.
21-03-2022

Thanks for the comments. Some replies: * there are indeed some default methods, but the implementation is sealed, so using implSpec felt a bit too much * I've fixed the equals/hashCode, discovered that after I filed the CSR * Restricted annotation is where we want to go - we might or not include it in final release * PathElement has no methods but it has factories; and it is used in the signatures of many layout methods. Originally this was a builder-like interface, but we abandoned the builder-style API in favor of this one which is more amenable to constant-ization (e.g. layout path can be loaded with condy + ldc) * I've done another pass to deal with statements around sealing * isCloseable vs. CloseableMemorySession. It's 60/40 really. The main issue is that the try-with-resource use would get more verbose (unless `var` is used), as the "good" name would be taken by the uncommon, non-closeable case. Given we avoid adding too many abstractions, as this is ultimately a low-level API (note that we also don't have SharedSession vs. ConfinedSession) having a predicate felt an adequate compromise. * The separate ValueLayout subclasses are needed to define the sharp, non-boxing accessors in MemorySement and MemoryAddress (e.g. to allow for overload resolution). That's the main use. * CLinker could provide more details, yes, that said, there is nothing preventing CLinker to work on platforms with 36-bit integers - assuming the layouts to deal with these are defined somewhere (and that the linker code is ported for those platforms). Feels like an implNote. Also, for both this and the VaList - it's not dependent on the version of "C" as much as it's dependent on the version of the ABI. So, IMHO, we should just list the set of supported ABIs, either in the toplevel javadoc, or in the `systemCLinker` factory.
21-03-2022

Moving to Provisional. General comments: As a rule of thumb, default methods in interfaces should have implSpec tags describing what they do. Affected interfaces include MemoryLayout, MemorySegment, and SegmentAllocator. (If an interface is sealed, one could argue the implSpec tags aren't strictly needed since only other classes in the platform can implement the interface.) A number of classes implicitly or explicitly inheritDoc the full javadoc of Object.equals/hashCode, which is unnecessary. Affects SequenceLayout, GroupLayout, etc. How about a Restricted annotation type as part of the preview so the restricted methods could be @Restricted? Ideally, @Restricted would have special javadoc support where the restricted warning was automatically presented. MemoryLayout.PathElement seems to be a marker interface since it defines no instance methods of its own. MemorySegment includes the statement: "Non-platform classes should not implement MemorySegment directly. " This and any other similar occurrences can be deleted now since the interfaces are sealed. What is the rationale for having "closeabiliyt" be an isCloseable method calls rather than defining a CloseableMemorySession subinterface? For ValueLayout.OfBoolean, with the current API, there is a lot of API overhead defining separate named types versus just relying on constants in VALUE_LAYOUT. Overrides of withName and withOrder look to be the concrete benefit. In VaList, which version of the C spec is being referenced? For the C Liinker, there seems more could be said concerning, say, minimum versions of C are required, assumptions about the C platform (e.g. sizes of basic types are powers of 2 -- C has run on platforms with 36-bit integers), anything more about the ABI, etc. Perhaps an implNote could provide more information on symbol lookup details on common platforms? Likewise, for ClassLoader.findNative, could an implNote give guidance should be provided on platform mappings on common platforms?
18-03-2022

Adding command line option to interface kind to account for --enable-native-access.
18-03-2022