Doubling down on arenas in the FFM API

Maurizio Cimadamore maurizio.cimadamore at oracle.com
Thu Feb 2 13:44:32 UTC 2023


On 02/02/2023 13:15, radek at smogura.eu wrote:

    Hi Maurizio,

    Thank you for sharing this. I agree that there’s a tension between
    Scope and Arena, and for i.e. passing Arena to FileChannel::map look
    bit like we pass too big object there.

    I just thought (sorry if it was proposed somewhere else), to
    introduce supporting object Scopable (can’t imagine better name on
    short notice). So something which can have a scope (own - freshly
    generated or shared in some way).

    The Scopable would have single method scope(), and for simplicity
    Scope could be Scopable returning “this”.

    If MemorySegment should be Scopable - I don’t know - as there’s next
    issue of allocating segments which are dependent in some way and
    deallocation should be executed in order.

Hi Rado,
Thanks for the comments. Having a common interface for all things with a 
scope accessor is possible, and something we have considered to bring 
Arena and SegmentScope under the same umbrella.

The main problem though, is where do you put the “allocate” method. It 
is unfortunate that, in the Java 20 API, there are /three/ ways to 
allocate a native segment:

|MemorySegment.allocateNative(100, arena.scope()) // 1 
arena.allocate(100) // 2 
SegmentAllocator.nativeAllocator(arena.scope()).allocate(100); // 3 |

This seems overkill, and adding an interface on top of Arena and 
SegmentScope doesn’t help much. Well, Scopeable might also extend 
SegmentAllocator, so you can do:

|SegmentScope.auto().allocate(100) |

But now this creates an issue: if a scope is an allocator, then an Arena 
provides /two/ allocators: the allocator in its “allocate” method, and 
the “fallback” allocator, available accessing the arena’s scope.

This seemed overly confusing.

There’s also another aspect in this: what we call SegmentScope.auto() 
really does act as an arena (as the document explains) - just one that 
cannot be closed explicitly. So having too much splitting in the API 
seems to create unnecessary asymmetries and non-orthogonality.

Once you embrace the fact that Arena is your unit of allocation for 
native segments, everything becomes easier. In the API proposed in the 
document there is now only /one/ way to allocate a native segment, 
namely Arena::allocate.

P.S.

Related to this, we also considered adding a Scopeable/Scoped interface 
(implemented by MemorySegment) instead of a separate “Scope” interface. 
While initially appealing, as:

|segment.isAlive() |

Seems better than:

|segment.scope().isAlive() |

That approach starts running out of gas when you consider things like 
“how do you compare the lifetime of two Scoped/Scopeable” ? You can’t 
use “equals” (as equals on MemorySegment means something else) - so you 
end up with something like this:

|segment.isLifetimeEquals(....) |

or, if we also consider lifetime containment:

|segment.isLifetimeContainedBy(....) |

Both of which seems less direct than:

|segment.scope().equals(...) |

or

|segment.scope().containedBy(...) |

On top of that, since now Arena does not have a scope, but /is/ a scope, 
we need to make arena an abstract class, which somewhat limits extension 
options for clients.

So we have concluded that keeping a small scope interface off to the 
side (MemorySegment.Scope) represented the most pragmatic compromise.

Maurizio

    Kind regards, Radosław Smogura

        On 31 Jan 2023, at 19:46, Maurizio Cimadamore

        maurizio.cimadamore at oracle.com
        <http://mailto:maurizio.cimadamore@oracle.com> wrote:

        Hi, as discussed here [1], it is not clear as to whether Java 20
        iteration of the Foreign Function & Memory API (FFM API) has yet
        reached bottom, especially when it comes to managing the lifetime
        of the regions of memory backing memory segments. After collecting
        some rounds of internal and external feedback, it was clear that
        while the Java 20 API has all the functionalities we require for
        writing efficient and robust native interop code, some of the
        concepts in the API were made a bit harder to grok, as users had to
        choose between two toplevel abstractions, namely |SegmentScope| and
        |Arena|. This choice is made even more difficult, as some of the
        functionalities (e.g. allocation) is duplicated in both API points.
        As a result, we have been busy exploring different ways to restack
        the FFM API in search of something more approachable.

        The results of our findings are described in this document:

        http://cr.openjdk.java.net/~mcimadamore/panama/scoped_arenas.html

        Here, we propose a possible simplification of the FFM API, where we
        make |Arena| the true star of the show, which results in the
        following changes:

          * factories such as |SegmentScope::auto| are now moved to
            |Arena|;
          * all segment-producing methods (such as |FileChannel::map|) now
            accept an |Arena| parameter; /static factories such as
            |MemorySegment::allocateNative| have been dropped; / scopes are
            made less prominent, and moved to a nested class
            (|MemorySegment.Scope|).

        This gives us a remarkably simple API, which brings together the
        best aspects of the Java 19 and Java 20 FFM API iterations. On the
        one hand, |Arena| is now the most important abstraction that users
        of the FFM API have to deal with (in a way, |Arena| is the new
        |MemorySession|); at the same time, we still have a way to model
        the lifetime of an |Arena| (and all the segments allocated by it)
        using a |MemorySegment.Scope| - which is desirable both in terms of
        debugging (e.g. inspecting whether two segments/arenas have the
        same lifetime) and, more importantly, in terms of allowing the
        definition of custom arenas via simple delegation (as in Java 20).

        As always, feedback is welcome. While this proposal does not
        significantly alter the expressiveness of the FFM API, the proposed
        API comes with some limitations. For instance, since all allocation
        routines are now |Arena|-centric (see above), it is no longer
        possible to allocate a new segment if a corresponding arena is not
        available (we call this co-allocation). As explained in the
        document, while it would be possible to add back the missing
        co-allocation functionality, extensive analysis of the code using
        the FFM API has shown co-allocation to be /extremely/ rare (**) -
        and of dubious value. For these reasons, we would like to aim for a
        more principled approach which avoids co-allocation altogether, and
        allows for more encapsulation of the capabilities associated with
        an |Arena| object.

        Maurizio

        (**) We have only found /one/ usage [2] in over 10K Java files and
        more than 11M LoC analyzed. Moreover, this usage is only present in
        the Java 19 branch of the project, and removed in the “main” branch
        (which tracks the Java 20 FFM API). We suspect that this use of
        co-allocation has been made irrelevant after the unification of
        |MemoryAddress| and |MemorySegment|.

        [1] -
        https://mail.openjdk.org/pipermail/panama-dev/2022-December/018182.html

        [2] -
        https://urldefense.com/v3/__https://github.com/boulder-on/JPassport/blob/Java_19/jpassport/src/main/java/jpassport/Utils.java*L418__;Iw!!ACWV5N9M2RV99hQ!N83lUkxsLt0-52DJ28iFtyghkVYTBrkIqpba_S_rHp-LgkOjS11XHE2aNR0-4t77U_S3UqP_HU-K1tufeLRhfQs$


​
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/panama-dev/attachments/20230202/59ce8abf/attachment-0001.htm>


More information about the panama-dev mailing list