mirror of
https://github.com/openjdk/jdk.git
synced 2025-08-26 22:34:27 +02:00
8304265: Implementation of Foreign Function and Memory API (Third Preview)
Co-authored-by: Maurizio Cimadamore <mcimadamore@openjdk.org> Co-authored-by: Jorn Vernee <jvernee@openjdk.org> Co-authored-by: Paul Sandoz <psandoz@openjdk.org> Co-authored-by: Feilong Jiang <fjiang@openjdk.org> Co-authored-by: Per Minborg <pminborg@openjdk.org> Reviewed-by: erikj, jvernee, vlivanov, psandoz
This commit is contained in:
parent
41d58533ac
commit
cbccc4c817
267 changed files with 6947 additions and 8029 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2022, 2023, Oracle and/or its affiliates. All rights reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
|
@ -27,40 +27,105 @@ package java.lang.foreign;
|
|||
|
||||
import jdk.internal.foreign.MemorySessionImpl;
|
||||
import jdk.internal.javac.PreviewFeature;
|
||||
import jdk.internal.ref.CleanerFactory;
|
||||
|
||||
import java.lang.foreign.MemorySegment.Scope;
|
||||
|
||||
/**
|
||||
* An arena controls the lifecycle of memory segments, providing both flexible allocation and timely deallocation.
|
||||
* An arena controls the lifecycle of native memory segments, providing both flexible allocation and timely deallocation.
|
||||
* <p>
|
||||
* An arena has a {@linkplain #scope() scope}, called the arena scope. When the arena is {@linkplain #close() closed},
|
||||
* the arena scope is no longer {@linkplain SegmentScope#isAlive() alive}. As a result, all the
|
||||
* segments associated with the arena scope are invalidated, safely and atomically, their backing memory regions are
|
||||
* deallocated (where applicable) and can no longer be accessed after the arena is closed:
|
||||
* An arena has a {@linkplain MemorySegment.Scope scope} - the <em>arena scope</em>. All the segments allocated
|
||||
* by the arena are associated with the arena scope. As such, the arena determines the temporal bounds
|
||||
* of all the memory segments allocated by it.
|
||||
* <p>
|
||||
* Moreover, an arena also determines whether access to memory segments allocated by it should be
|
||||
* {@linkplain MemorySegment#isAccessibleBy(Thread) restricted} to specific threads.
|
||||
* An arena is a {@link SegmentAllocator} and features several allocation methods that can be used by clients
|
||||
* to obtain native segments.
|
||||
* <p>
|
||||
* The simplest arena is the {@linkplain Arena#global() global arena}. The global arena
|
||||
* features an <em>unbounded lifetime</em>. As such, native segments allocated with the global arena are always
|
||||
* accessible and their backing regions of memory are never deallocated. Moreover, memory segments allocated with the
|
||||
* global arena can be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} from any thread.
|
||||
* {@snippet lang = java:
|
||||
* MemorySegment segment = Arena.global().allocate(100, 1);
|
||||
* ...
|
||||
* // segment is never deallocated!
|
||||
*}
|
||||
* <p>
|
||||
* Alternatively, clients can obtain an {@linkplain Arena#ofAuto() automatic arena}, that is an arena
|
||||
* which features a <em>bounded lifetime</em> that is managed, automatically, by the garbage collector. As such, the regions
|
||||
* of memory backing memory segments allocated with the automatic arena are deallocated at some unspecified time
|
||||
* <em>after</em> the automatic arena (and all the segments allocated by it) become
|
||||
* <a href="../../../java/lang/ref/package.html#reachability">unreachable</a>, as shown below:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* try (Arena arena = Arena.openConfined()) {
|
||||
* MemorySegment segment = MemorySegment.allocateNative(100, arena.scope());
|
||||
* ...
|
||||
* } // memory released here
|
||||
* MemorySegment segment = Arena.ofAuto().allocate(100, 1);
|
||||
* ...
|
||||
* segment = null; // the segment region becomes available for deallocation after this point
|
||||
*}
|
||||
*
|
||||
* Furthermore, an arena is a {@link SegmentAllocator}. All the segments {@linkplain #allocate(long, long) allocated} by the
|
||||
* arena are associated with the arena scope. This makes arenas extremely useful when interacting with foreign code, as shown below:
|
||||
* Memory segments allocated with an automatic arena can also be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} from any thread.
|
||||
* <p>
|
||||
* Rather than leaving deallocation in the hands of the Java runtime, clients will often wish to exercise control over
|
||||
* the timing of deallocation for regions of memory that back memory segments. Two kinds of arenas support this,
|
||||
* namely {@linkplain #ofConfined() confined} and {@linkplain #ofShared() shared} arenas. They both feature
|
||||
* bounded lifetimes that are managed manually. For instance, the lifetime of a confined arena starts when the confined
|
||||
* arena is created, and ends when the confined arena is {@linkplain #close() closed}. As a result, the regions of memory
|
||||
* backing memory segments allocated with a confined arena are deallocated when the confined arena is closed.
|
||||
* When this happens, all the segments allocated with the confined arena are invalidated, and subsequent access
|
||||
* operations on these segments will fail {@link IllegalStateException}:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* try (Arena arena = Arena.openConfined()) {
|
||||
* MemorySegment nativeArray = arena.allocateArray(ValueLayout.JAVA_INT, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9);
|
||||
* MemorySegment nativeString = arena.allocateUtf8String("Hello!");
|
||||
* MemorySegment upcallStub = linker.upcallStub(handle, desc, arena.scope());
|
||||
* MemorySegment segment = null;
|
||||
* try (Arena arena = Arena.ofConfined()) {
|
||||
* segment = arena.allocate(100);
|
||||
* ...
|
||||
* } // memory released here
|
||||
* } // segment region deallocated here
|
||||
* segment.get(ValueLayout.JAVA_BYTE, 0); // throws IllegalStateException
|
||||
*}
|
||||
*
|
||||
* Memory segments allocated with a {@linkplain #ofConfined() confined arena} can only be accessed (and closed) by the
|
||||
* thread that created the arena. If access to a memory segment from multiple threads is required, clients can allocate
|
||||
* segments in a {@linkplain #ofShared() shared arena} instead.
|
||||
* <p>
|
||||
* The characteristics of the various arenas are summarized in the following table:
|
||||
*
|
||||
* <blockquote><table class="plain">
|
||||
* <caption style="display:none">Arenas characteristics</caption>
|
||||
* <thead>
|
||||
* <tr>
|
||||
* <th scope="col">Kind</th>
|
||||
* <th scope="col">Bounded lifetime</th>
|
||||
* <th scope="col">Explicitly closeable</th>
|
||||
* <th scope="col">Accessible from multiple threads</th>
|
||||
* </tr>
|
||||
* </thead>
|
||||
* <tbody>
|
||||
* <tr><th scope="row" style="font-weight:normal">Global</th>
|
||||
* <td style="text-align:center;">No</td>
|
||||
* <td style="text-align:center;">No</td>
|
||||
* <td style="text-align:center;">Yes</td></tr>
|
||||
* <tr><th scope="row" style="font-weight:normal">Automatic</th>
|
||||
* <td style="text-align:center;">Yes</td>
|
||||
* <td style="text-align:center;">No</td>
|
||||
* <td style="text-align:center;">Yes</td></tr>
|
||||
* <tr><th scope="row" style="font-weight:normal">Confined</th>
|
||||
* <td style="text-align:center;">Yes</td>
|
||||
* <td style="text-align:center;">Yes</td>
|
||||
* <td style="text-align:center;">No</td></tr>
|
||||
* <tr><th scope="row" style="font-weight:normal">Shared</th>
|
||||
* <td style="text-align:center;">Yes</td>
|
||||
* <td style="text-align:center;">Yes</td>
|
||||
* <td style="text-align:center;">Yes</td></tr>
|
||||
* </tbody>
|
||||
* </table></blockquote>
|
||||
*
|
||||
* <h2 id = "thread-confinement">Safety and thread-confinement</h2>
|
||||
*
|
||||
* Arenas provide strong temporal safety guarantees: a memory segment allocated by an arena cannot be accessed
|
||||
* <em>after</em> the arena has been closed. The cost of providing this guarantee varies based on the
|
||||
* number of threads that have access to the memory segments allocated by the arena. For instance, if an arena
|
||||
* is always created and closed by one thread, and the memory segments associated with the arena's scope are always
|
||||
* is always created and closed by one thread, and the memory segments allocated by the arena are always
|
||||
* accessed by that same thread, then ensuring correctness is trivial.
|
||||
* <p>
|
||||
* Conversely, if an arena allocates segments that can be accessed by multiple threads, or if the arena can be closed
|
||||
|
@ -70,34 +135,120 @@ import jdk.internal.javac.PreviewFeature;
|
|||
* impact, arenas are divided into <em>thread-confined</em> arenas, and <em>shared</em> arenas.
|
||||
* <p>
|
||||
* Confined arenas, support strong thread-confinement guarantees. Upon creation, they are assigned an
|
||||
* {@linkplain #isCloseableBy(Thread) owner thread}, typically the thread which initiated the creation operation.
|
||||
* The segments created by a confined arena can only be {@linkplain SegmentScope#isAccessibleBy(Thread) accessed}
|
||||
* <em>owner thread</em>, typically the thread which initiated the creation operation.
|
||||
* The segments created by a confined arena can only be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed}
|
||||
* by the owner thread. Moreover, any attempt to close the confined arena from a thread other than the owner thread will
|
||||
* fail with {@link WrongThreadException}.
|
||||
* <p>
|
||||
* Shared arenas, on the other hand, have no owner thread. The segments created by a shared arena
|
||||
* can be {@linkplain SegmentScope#isAccessibleBy(Thread) accessed} by any thread. This might be useful when
|
||||
* can be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by any thread. This might be useful when
|
||||
* multiple threads need to access the same memory segment concurrently (e.g. in the case of parallel processing).
|
||||
* Moreover, a shared arena {@linkplain #isCloseableBy(Thread) can be closed} by any thread.
|
||||
* Moreover, a shared arena can be closed by any thread.
|
||||
*
|
||||
* <h2 id = "custom-arenas">Custom arenas</h2>
|
||||
*
|
||||
* Clients can define custom arenas to implement more efficient allocation strategies, or to have better control over
|
||||
* when (and by whom) an arena can be closed. As an example, the following code defines a <em>slicing arena</em> that behaves
|
||||
* like a confined arena (i.e., single-threaded access), but internally uses a
|
||||
* {@linkplain SegmentAllocator#slicingAllocator(MemorySegment) slicing allocator} to respond to allocation requests.
|
||||
* When the slicing arena is closed, the underlying confined arena is also closed; this will invalidate all segments
|
||||
* allocated with the slicing arena (since the scope of the slicing arena is the same as that of the underlying
|
||||
* confined arena):
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* class SlicingArena implements Arena {
|
||||
* final Arena arena = Arena.ofConfined();
|
||||
* final SegmentAllocator slicingAllocator;
|
||||
*
|
||||
* SlicingArena(long size) {
|
||||
* slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size));
|
||||
* }
|
||||
*
|
||||
* public void allocate(long byteSize, long byteAlignment) {
|
||||
* return slicingAllocator.allocate(byteSize, byteAlignment);
|
||||
* }
|
||||
*
|
||||
* public MemorySegment.Scope scope() {
|
||||
* return arena.scope();
|
||||
* }
|
||||
*
|
||||
* public void close() {
|
||||
* return arena.close();
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* In other words, a slicing arena provides a vastly more efficient and scalable allocation strategy, while still retaining
|
||||
* the timely deallocation guarantee provided by the underlying confined arena:
|
||||
*
|
||||
* {@snippet lang = java:
|
||||
* try (Arena slicingArena = new SlicingArena(1000)) {
|
||||
* for (int i = 0 ; i < 10 ; i++) {
|
||||
* MemorySegment s = slicingArena.allocateArray(JAVA_INT, 1, 2, 3, 4, 5);
|
||||
* ...
|
||||
* }
|
||||
* } // all memory allocated is released here
|
||||
* }
|
||||
*
|
||||
* @implSpec
|
||||
* Implementations of this interface are thread-safe.
|
||||
*
|
||||
* @see MemorySegment
|
||||
*
|
||||
* @since 20
|
||||
*/
|
||||
@PreviewFeature(feature=PreviewFeature.Feature.FOREIGN)
|
||||
public interface Arena extends SegmentAllocator, AutoCloseable {
|
||||
|
||||
/**
|
||||
* Creates a new arena that is managed, automatically, by the garbage collector.
|
||||
* Segments obtained with the returned arena can be
|
||||
* {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by any thread.
|
||||
* Calling {@link #close()} on the returned arena will result in an {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @return a new arena that is managed, automatically, by the garbage collector.
|
||||
*/
|
||||
static Arena ofAuto() {
|
||||
return MemorySessionImpl.createImplicit(CleanerFactory.cleaner()).asArena();
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the global arena. Segments obtained with the global arena can be
|
||||
* {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} by any thread.
|
||||
* Calling {@link #close()} on the returned arena will result in an {@link UnsupportedOperationException}.
|
||||
*
|
||||
* @return the global arena.
|
||||
*/
|
||||
static Arena global() {
|
||||
class Holder {
|
||||
static final Arena GLOBAL = MemorySessionImpl.GLOBAL.asArena();
|
||||
}
|
||||
return Holder.GLOBAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new confined arena, owned by the current thread}
|
||||
*/
|
||||
static Arena ofConfined() {
|
||||
return MemorySessionImpl.createConfined(Thread.currentThread()).asArena();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new shared arena}
|
||||
*/
|
||||
static Arena ofShared() {
|
||||
return MemorySessionImpl.createShared().asArena();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a native memory segment with the given size (in bytes) and alignment constraint (in bytes).
|
||||
* The returned segment is associated with the arena scope.
|
||||
* The returned segment is associated with this {@linkplain #scope() arena scope}.
|
||||
* The segment's {@link MemorySegment#address() address} is the starting address of the
|
||||
* allocated off-heap memory region backing the segment, and the address is
|
||||
* aligned according the provided alignment constraint.
|
||||
*
|
||||
* @implSpec
|
||||
* The default implementation of this method is equivalent to the following code:
|
||||
* {@snippet lang = java:
|
||||
* MemorySegment.allocateNative(bytesSize, byteAlignment, scope());
|
||||
*}
|
||||
* More generally implementations of this method must return a native segment featuring the requested size,
|
||||
* Implementations of this method must return a native segment featuring the requested size,
|
||||
* and that is compatible with the provided alignment constraint. Furthermore, for any two segments
|
||||
* {@code S1, S2} returned by this method, the following invariant must hold:
|
||||
*
|
||||
|
@ -110,57 +261,43 @@ public interface Arena extends SegmentAllocator, AutoCloseable {
|
|||
* @return a new native memory segment.
|
||||
* @throws IllegalArgumentException if {@code bytesSize < 0}, {@code alignmentBytes <= 0}, or if {@code alignmentBytes}
|
||||
* is not a power of 2.
|
||||
* @throws IllegalStateException if the arena has already been {@linkplain #close() closed}.
|
||||
* @throws WrongThreadException if this method is called from a thread {@code T},
|
||||
* such that {@code scope().isAccessibleBy(T) == false}.
|
||||
* @see MemorySegment#allocateNative(long, long, SegmentScope)
|
||||
* @throws IllegalStateException if this arena has already been {@linkplain #close() closed}.
|
||||
* @throws WrongThreadException if this arena is confined, and this method is called from a thread {@code T}
|
||||
* other than the arena owner thread.
|
||||
*/
|
||||
@Override
|
||||
default MemorySegment allocate(long byteSize, long byteAlignment) {
|
||||
return MemorySegment.allocateNative(byteSize, byteAlignment, scope());
|
||||
return ((MemorySessionImpl)scope()).allocate(byteSize, byteAlignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return the arena scope}
|
||||
*/
|
||||
SegmentScope scope();
|
||||
Scope scope();
|
||||
|
||||
/**
|
||||
* Closes this arena. If this method completes normally, the arena scope is no longer {@linkplain SegmentScope#isAlive() alive},
|
||||
* Closes this arena. If this method completes normally, the arena scope is no longer {@linkplain Scope#isAlive() alive},
|
||||
* and all the memory segments associated with it can no longer be accessed. Furthermore, any off-heap region of memory backing the
|
||||
* segments associated with that scope are also released.
|
||||
* segments obtained from this arena are also released.
|
||||
*
|
||||
* @apiNote This operation is not idempotent; that is, closing an already closed arena <em>always</em> results in an
|
||||
* exception being thrown. This reflects a deliberate design choice: failure to close an arena might reveal a bug
|
||||
* in the underlying application logic.
|
||||
*
|
||||
* @see SegmentScope#isAlive()
|
||||
* @implSpec If this method completes normally, then {@code this.scope().isAlive() == false}.
|
||||
* Implementations are allowed to throw {@link UnsupportedOperationException} if an explicit close operation is
|
||||
* not supported.
|
||||
*
|
||||
* @see Scope#isAlive()
|
||||
*
|
||||
* @throws IllegalStateException if the arena has already been closed.
|
||||
* @throws IllegalStateException if the arena scope is {@linkplain SegmentScope#whileAlive(Runnable) kept alive}.
|
||||
* @throws WrongThreadException if this method is called from a thread {@code T},
|
||||
* such that {@code isCloseableBy(T) == false}.
|
||||
* @throws IllegalStateException if a segment associated with this arena is being accessed concurrently, e.g.
|
||||
* by a {@linkplain Linker#downcallHandle(FunctionDescriptor, Linker.Option...) downcall method handle}.
|
||||
* @throws WrongThreadException if this arena is confined, and this method is called from a thread {@code T}
|
||||
* other than the arena owner thread.
|
||||
* @throws UnsupportedOperationException if this arena does not support explicit closure.
|
||||
*/
|
||||
@Override
|
||||
void close();
|
||||
|
||||
/**
|
||||
* {@return {@code true} if the provided thread can close this arena}
|
||||
* @param thread the thread to be tested.
|
||||
*/
|
||||
boolean isCloseableBy(Thread thread);
|
||||
|
||||
/**
|
||||
* {@return a new confined arena, owned by the current thread}
|
||||
*/
|
||||
static Arena openConfined() {
|
||||
return MemorySessionImpl.createConfined(Thread.currentThread()).asArena();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@return a new shared arena}
|
||||
*/
|
||||
static Arena openShared() {
|
||||
return MemorySessionImpl.createShared().asArena();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue