diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/CLinker.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/CLinker.java
index a3794980be3..6ff6f1cb5ea 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/CLinker.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/CLinker.java
@@ -25,6 +25,7 @@
*/
package jdk.incubator.foreign;
+import jdk.internal.foreign.MemoryScope;
import jdk.internal.foreign.NativeMemorySegmentImpl;
import jdk.internal.foreign.PlatformLayouts;
import jdk.internal.foreign.Utils;
@@ -37,7 +38,6 @@
import java.nio.charset.Charset;
import java.util.Objects;
import java.util.function.Consumer;
-import java.util.stream.Stream;
import static jdk.internal.foreign.PlatformLayouts.*;
@@ -66,7 +66,8 @@
* carrier type can be used to match the native {@code va_list} type.
*
* For the linking process to be successful, some requirements must be satisfied; if {@code M} and {@code F} are
- * the method type and the function descriptor, respectively, used during the linking process, then it must be that:
+ * the method type (obtained after dropping any prefix arguments) and the function descriptor, respectively,
+ * used during the linking process, then it must be that:
*
*
The arity of {@code M} is the same as that of {@code F};
*
If the return type of {@code M} is {@code void}, then {@code F} should have no return layout
@@ -126,8 +127,12 @@ static CLinker getInstance() {
}
/**
- * Obtain a foreign method handle, with the given type and featuring the given function descriptor,
+ * Obtains a foreign method handle, with the given type and featuring the given function descriptor,
* which can be used to call a target foreign function at the given address.
+ *
+ * If the provided method type's return type is {@code MemorySegment}, then the resulting method handle features
+ * an additional prefix parameter, of type {@link SegmentAllocator}), which will be used by the linker runtime
+ * to allocate structs returned by-value.
*
* @see LibraryLookup#lookup(String)
*
@@ -144,11 +149,40 @@ default MethodHandle downcallHandle(Addressable symbol, MethodType type, Functio
/**
* Obtain a foreign method handle, with the given type and featuring the given function descriptor,
- * which can be used to call a target foreign function at an address passed in as a leading argument.
+ * which can be used to call a target foreign function at the given address.
+ *
+ * If the provided method type's return type is {@code MemorySegment}, then the provided allocator will be used by
+ * the linker runtime to allocate structs returned by-value.
+ *
+ * @see LibraryLookup#lookup(String)
+ *
+ * @param symbol downcall symbol.
+ * @param allocator the segment allocator.
+ * @param type the method type.
+ * @param function the function descriptor.
+ * @return the downcall method handle.
+ * @throws IllegalArgumentException in the case of a method type and function descriptor mismatch.
+ */
+ default MethodHandle downcallHandle(Addressable symbol, SegmentAllocator allocator, MethodType type, FunctionDescriptor function) {
+ Objects.requireNonNull(symbol);
+ Objects.requireNonNull(allocator);
+ MethodHandle downcall = MethodHandles.insertArguments(downcallHandle(type, function), 0, symbol);
+ if (type.returnType().equals(MemorySegment.class)) {
+ downcall = MethodHandles.insertArguments(downcall, 0, allocator);
+ }
+ return downcall;
+ }
+
+
+ /**
+ * Obtains a foreign method handle, with the given type and featuring the given function descriptor, which can be
+ * used to call a target foreign function at an address.
+ * The resulting method handle features a prefix parameter (as the first parameter) corresponding to the address, of
+ * type {@link Addressable}.
*
- * For a given method type {@code (As...) -> R}, the returned method handle shall have the method type
- * {@code (Addressable, As...) -> R}, where {@code As...} are zero or more parameter types, and {@code R}
- * is the return type (which can be {@code void}).
+ * If the provided method type's return type is {@code MemorySegment}, then the resulting method handle features an
+ * additional prefix parameter (inserted immediately after the address parameter), of type {@link SegmentAllocator}),
+ * which will be used by the linker runtime to allocate structs returned by-value.
*
* @see LibraryLookup#lookup(String)
*
@@ -160,20 +194,36 @@ default MethodHandle downcallHandle(Addressable symbol, MethodType type, Functio
MethodHandle downcallHandle(MethodType type, FunctionDescriptor function);
/**
- * Allocates a native segment whose base address (see {@link MemorySegment#address}) can be
- * passed to other foreign functions (as a function pointer); calling such a function pointer
- * from native code will result in the execution of the provided method handle.
+ * Allocates a native segment with given scope which can be passed to other foreign functions (as a function pointer);
+ * calling such a function pointer from native code will result in the execution of the provided method handle.
*
- *
The returned segment is shared, and it only features
- * the {@link MemorySegment#CLOSE} access mode. When the returned segment is closed,
+ *
The returned segment is associated with the provided scope. When such scope is closed,
* the corresponding native stub will be deallocated.
*
* @param target the target method handle.
* @param function the function descriptor.
+ * @param scope the upcall stub scope.
* @return the native stub segment.
* @throws IllegalArgumentException if the target's method type and the function descriptor mismatch.
*/
- MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function);
+ MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, ResourceScope scope);
+
+ /**
+ * Allocates a native segment which can be passed to other foreign functions (as a function pointer);
+ * calling such a function pointer from native code will result in the execution of the provided method handle.
+ *
+ * The returned segment is associated with a fresh, shared, resource scope,
+ * which will be automatically closed when the segment (or views derived from it) is no longer in use.
+ * The scope associated with the returned segment cannot be closed directly e.g. by calling {@link ResourceScope#close()}.
+ *
+ * @param target the target method handle.
+ * @param function the function descriptor.
+ * @return the native stub segment.
+ * @throws IllegalArgumentException if the target's method type and the function descriptor mismatch.
+ */
+ default MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function) {
+ return upcallStub(target, function, MemoryScope.createDefault());
+ }
/**
* The layout for the {@code char} C type
@@ -227,7 +277,8 @@ static T asVarArg(T layout) {
/**
* Converts a Java string into a null-terminated C string, using the
- * platform's default charset, storing the result into a new native memory segment.
+ * platform's default charset, storing the result into a new native memory segment, associated with
+ * a fresh {@link ResourceScope#ofDefault() default scope}.
*
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
@@ -244,7 +295,7 @@ static MemorySegment toCString(String str) {
/**
* Converts a Java string into a null-terminated C string, using the given {@link java.nio.charset.Charset charset},
- * storing the result into a new native memory segment.
+ * storing the result into a new native memory segment, associated with a fresh {@link ResourceScope#ofDefault() default scope}.
*
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
@@ -263,7 +314,7 @@ static MemorySegment toCString(String str, Charset charset) {
/**
* Converts a Java string into a null-terminated C string, using the platform's default charset,
- * storing the result into a native memory segment allocated using the provided scope.
+ * storing the result into a native memory segment allocated using the provided allocator.
*
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
@@ -271,18 +322,35 @@ static MemorySegment toCString(String str, Charset charset) {
* control over the encoding process is required.
*
* @param str the Java string to be converted into a C string.
- * @param scope the scope to be used for the native segment allocation.
+ * @param allocator the allocator to be used for the native segment allocation.
* @return a new native memory segment containing the converted C string.
*/
- static MemorySegment toCString(String str, NativeScope scope) {
+ static MemorySegment toCString(String str, SegmentAllocator allocator) {
Objects.requireNonNull(str);
- Objects.requireNonNull(scope);
- return toCString(str.getBytes(), scope);
+ Objects.requireNonNull(allocator);
+ return toCString(str.getBytes(), allocator);
+ }
+
+ /**
+ * Converts a Java string into a null-terminated C string, using the platform's default charset,
+ * storing the result into a native memory segment associated with the provided resource scope.
+ *
+ * This method always replaces malformed-input and unmappable-character
+ * sequences with this charset's default replacement byte array. The
+ * {@link java.nio.charset.CharsetEncoder} class should be used when more
+ * control over the encoding process is required.
+ *
+ * @param str the Java string to be converted into a C string.
+ * @param scope the resource scope to be associated with the returned segment.
+ * @return a new native memory segment containing the converted C string.
+ */
+ static MemorySegment toCString(String str, ResourceScope scope) {
+ return toCString(str, SegmentAllocator.scoped(scope));
}
/**
* Converts a Java string into a null-terminated C string, using the given {@link java.nio.charset.Charset charset},
- * storing the result into a new native memory segment native memory segment allocated using the provided scope.
+ * storing the result into a new native memory segment native memory segment allocated using the provided allocator.
*
* This method always replaces malformed-input and unmappable-character
* sequences with this charset's default replacement byte array. The
@@ -291,14 +359,32 @@ static MemorySegment toCString(String str, NativeScope scope) {
*
* @param str the Java string to be converted into a C string.
* @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the C string.
- * @param scope the scope to be used for the native segment allocation.
+ * @param allocator the allocator to be used for the native segment allocation.
* @return a new native memory segment containing the converted C string.
*/
- static MemorySegment toCString(String str, Charset charset, NativeScope scope) {
+ static MemorySegment toCString(String str, Charset charset, SegmentAllocator allocator) {
Objects.requireNonNull(str);
Objects.requireNonNull(charset);
- Objects.requireNonNull(scope);
- return toCString(str.getBytes(charset), scope);
+ Objects.requireNonNull(allocator);
+ return toCString(str.getBytes(charset), allocator);
+ }
+
+ /**
+ * Converts a Java string into a null-terminated C string, using the given {@link java.nio.charset.Charset charset},
+ * storing the result into a native memory segment associated with the provided resource scope.
+ *
+ * This method always replaces malformed-input and unmappable-character
+ * sequences with this charset's default replacement byte array. The
+ * {@link java.nio.charset.CharsetEncoder} class should be used when more
+ * control over the encoding process is required.
+ *
+ * @param str the Java string to be converted into a C string.
+ * @param charset The {@link java.nio.charset.Charset} to be used to compute the contents of the C string.
+ * @param scope the resource scope to be associated with the returned segment.
+ * @return a new native memory segment containing the converted C string.
+ */
+ static MemorySegment toCString(String str, Charset charset, ResourceScope scope) {
+ return toCString(str, charset, SegmentAllocator.scoped(scope));
}
/**
@@ -395,8 +481,8 @@ private static MemorySegment toCString(byte[] bytes) {
return segment;
}
- private static MemorySegment toCString(byte[] bytes, NativeScope scope) {
- MemorySegment addr = scope.allocate(bytes.length + 1, 1L);
+ private static MemorySegment toCString(byte[] bytes, SegmentAllocator allocator) {
+ MemorySegment addr = allocator.allocate(bytes.length + 1, 1L);
copy(addr, bytes);
return addr;
}
@@ -458,15 +544,15 @@ static void freeMemoryRestricted(MemoryAddress addr) {
* explicitly permitted types.
*
*/
- interface VaList extends Addressable, AutoCloseable {
+ interface VaList extends Addressable {
/**
* Reads the next value as an {@code int} and advances this va list's position.
*
* @param layout the layout of the value
* @return the value read as an {@code int}
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code int}
*/
int vargAsInt(MemoryLayout layout);
@@ -476,8 +562,8 @@ interface VaList extends Addressable, AutoCloseable {
*
* @param layout the layout of the value
* @return the value read as an {@code long}
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code long}
*/
long vargAsLong(MemoryLayout layout);
@@ -487,8 +573,8 @@ interface VaList extends Addressable, AutoCloseable {
*
* @param layout the layout of the value
* @return the value read as an {@code double}
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code double}
*/
double vargAsDouble(MemoryLayout layout);
@@ -498,8 +584,8 @@ interface VaList extends Addressable, AutoCloseable {
*
* @param layout the layout of the value
* @return the value read as an {@code MemoryAddress}
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemoryAddress}
*/
MemoryAddress vargAsAddress(MemoryLayout layout);
@@ -507,13 +593,12 @@ interface VaList extends Addressable, AutoCloseable {
/**
* Reads the next value as a {@code MemorySegment}, and advances this va list's position.
*
- * The memory segment returned by this method will be allocated using
- * {@link MemorySegment#allocateNative(long, long)}, and will have to be closed separately.
+ * The memory segment returned by this method is associated with a fresh {@link ResourceScope#ofDefault() default scope}.
*
* @param layout the layout of the value
* @return the value read as an {@code MemorySegment}
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemorySegment}
*/
MemorySegment vargAsSegment(MemoryLayout layout);
@@ -521,54 +606,53 @@ interface VaList extends Addressable, AutoCloseable {
/**
* Reads the next value as a {@code MemorySegment}, and advances this va list's position.
*
- * The memory segment returned by this method will be allocated using the given {@code NativeScope}.
+ * The memory segment returned by this method will be allocated using the given {@link SegmentAllocator}.
*
* @param layout the layout of the value
- * @param scope the scope to allocate the segment in
+ * @param allocator the allocator to be used for the native segment allocation
* @return the value read as an {@code MemorySegment}
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
* @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemorySegment}
*/
- MemorySegment vargAsSegment(MemoryLayout layout, NativeScope scope);
+ MemorySegment vargAsSegment(MemoryLayout layout, SegmentAllocator allocator);
/**
- * Skips a number of elements with the given memory layouts, and advances this va list's position.
+ * Reads the next value as a {@code MemorySegment}, and advances this va list's position.
+ *
+ * The memory segment returned by this method will be associated with the given {@link ResourceScope}.
*
- * @param layouts the layout of the value
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @param layout the layout of the value
+ * @param scope the resource scope to be associated with the returned segment
+ * @return the value read as an {@code MemorySegment}
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
+ * @throws IllegalArgumentException if the given memory layout is not compatible with {@code MemorySegment}
*/
- void skip(MemoryLayout... layouts);
+ MemorySegment vargAsSegment(MemoryLayout layout, ResourceScope scope);
/**
- * A predicate used to check if the memory associated with the C {@code va_list} modelled
- * by this instance is still valid to use.
+ * Skips a number of elements with the given memory layouts, and advances this va list's position.
*
- * @return true, if the memory associated with the C {@code va_list} modelled by this instance is still valid
- * @see #close()
+ * @param layouts the layout of the value
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
*/
- boolean isAlive();
+ void skip(MemoryLayout... layouts);
/**
- * Releases the underlying C {@code va_list} modelled by this instance, and any native memory that is attached
- * to this va list that holds its elements (see {@link VaList#make(Consumer)}).
- *
- * After calling this method, {@link #isAlive()} will return {@code false} and further attempts to read values
- * from this va list will result in an exception.
- *
- * @see #isAlive()
+ * Returns the resource scope associated with this instance.
+ * @return the resource scope associated with this instance.
*/
- void close();
+ ResourceScope scope();
/**
* Copies this C {@code va_list} at its current position. Copying is useful to traverse the va list's elements
* starting from the current position, without affecting the state of the original va list, essentially
* allowing the elements to be traversed multiple times.
*
- * If this method needs to allocate native memory for the copy, it will use
- * {@link MemorySegment#allocateNative(long, long)} to do so. {@link #close()} will have to be called on the
- * returned va list instance to release the allocated memory.
+ * Any native resource required by the execution of this method will be allocated in the resource scope
+ * associated with this instance (see {@link #scope()}).
*
* This method only copies the va list cursor itself and not the memory that may be attached to the
* va list which holds its elements. That means that if this va list was created with the
@@ -576,31 +660,11 @@ interface VaList extends Addressable, AutoCloseable {
* elements, making the copy unusable.
*
* @return a copy of this C {@code va_list}.
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
+ * @throws IllegalStateException if the resource scope associated with this instance has been closed
+ * (see {@link #scope()}).
*/
VaList copy();
- /**
- * Copies this C {@code va_list} at its current position. Copying is useful to traverse the va list's elements
- * starting from the current position, without affecting the state of the original va list, essentially
- * allowing the elements to be traversed multiple times.
- *
- * If this method needs to allocate native memory for the copy, it will use
- * the given {@code NativeScope} to do so.
- *
- * This method only copies the va list cursor itself and not the memory that may be attached to the
- * va list which holds its elements. That means that if this va list was created with the
- * {@link #make(Consumer)} method, closing this va list will also release the native memory that holds its
- * elements, making the copy unusable.
- *
- * @param scope the scope to allocate the copy in
- * @return a copy of this C {@code va_list}.
- * @throws IllegalStateException if the C {@code va_list} associated with this instance is no longer valid
- * (see {@link #close()}).
- */
- VaList copy(NativeScope scope);
-
/**
* Returns the memory address of the C {@code va_list} associated with this instance.
*
@@ -610,7 +674,8 @@ interface VaList extends Addressable, AutoCloseable {
MemoryAddress address();
/**
- * Constructs a new {@code VaList} instance out of a memory address pointing to an existing C {@code va_list}.
+ * Constructs a new {@code VaList} instance out of a memory address pointing to an existing C {@code va_list},
+ * backed by the {@link ResourceScope#globalScope() global} resource scope.
*
* This method is restricted. Restricted method are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
@@ -620,20 +685,35 @@ interface VaList extends Addressable, AutoCloseable {
* @return a new {@code VaList} instance backed by the C {@code va_list} at {@code address}.
*/
static VaList ofAddressRestricted(MemoryAddress address) {
+ return SharedUtils.newVaListOfAddress(address, ResourceScope.globalScope());
+ }
+
+ /**
+ * Constructs a new {@code VaList} instance out of a memory address pointing to an existing C {@code va_list},
+ * with given resource scope.
+ *
+ * This method is restricted. Restricted method are unsafe, and, if used incorrectly, their use might crash
+ * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
+ * restricted methods, and use safe and supported functionalities, where possible.
+ *
+ * @param address a memory address pointing to an existing C {@code va_list}.
+ * @param scope the resource scope to be associated with the returned {@code VaList} instance.
+ * @return a new {@code VaList} instance backed by the C {@code va_list} at {@code address}.
+ */
+ static VaList ofAddressRestricted(MemoryAddress address, ResourceScope scope) {
Utils.checkRestrictedAccess("VaList.ofAddressRestricted");
Objects.requireNonNull(address);
- return SharedUtils.newVaListOfAddress(address);
+ Objects.requireNonNull(scope);
+ return SharedUtils.newVaListOfAddress(address, scope);
}
/**
- * Constructs a new {@code VaList} using a builder (see {@link Builder}).
+ * Constructs a new {@code VaList} using a builder (see {@link Builder}), associated with a fresh
+ * a fresh {@link ResourceScope#ofDefault() default scope}.
*
- * If this method needs to allocate native memory for the va list, it will use
- * {@link MemorySegment#allocateNative(long, long)} to do so.
- *
- * This method will allocate native memory to hold the elements in the va list. This memory
- * will be 'attached' to the returned va list instance, and will be released when {@link VaList#close()}
- * is called.
+ * If this method needs to allocate native memory, such memory will be managed by the same scope which also
+ * manages the returned valist instance; as such, this memory will be released only when the returned
+ * valist instance becomes unreachable.
*
* Note that when there are no elements added to the created va list,
* this method will return the same as {@link #empty()}.
@@ -644,17 +724,15 @@ static VaList ofAddressRestricted(MemoryAddress address) {
*/
static VaList make(Consumer actions) {
Objects.requireNonNull(actions);
- return SharedUtils.newVaList(actions, MemorySegment::allocateNative);
+ return SharedUtils.newVaList(actions, MemoryScope.createDefault());
}
/**
- * Constructs a new {@code VaList} using a builder (see {@link Builder}).
- *
- * If this method needs to allocate native memory for the va list, it will use
- * the given {@code NativeScope} to do so.
+ * Constructs a new {@code VaList} using a builder (see {@link Builder}), associated with a given
+ * {@link ResourceScope resource scope}.
*
- * This method will allocate native memory to hold the elements in the va list. This memory
- * will be managed by the given {@code NativeScope}, and will be released when the scope is closed.
+ * If this method needs to allocate native memory, such memory will be managed by the given
+ * {@link ResourceScope resource scope}, and will be released when the resource scope is {@link ResourceScope#close closed}.
*
* Note that when there are no elements added to the created va list,
* this method will return the same as {@link #empty()}.
@@ -664,10 +742,10 @@ static VaList make(Consumer actions) {
* @param scope the scope to be used for the valist allocation.
* @return a new {@code VaList} instance backed by a fresh C {@code va_list}.
*/
- static VaList make(Consumer actions, NativeScope scope) {
+ static VaList make(Consumer actions, ResourceScope scope) {
Objects.requireNonNull(actions);
Objects.requireNonNull(scope);
- return SharedUtils.newVaList(actions, SharedUtils.Allocator.ofScope(scope));
+ return SharedUtils.newVaList(actions, scope);
}
/**
@@ -750,7 +828,7 @@ interface Builder {
* A C type kind. Each kind corresponds to a particular C language builtin type, and can be attached to
* {@link ValueLayout} instances using the {@link MemoryLayout#withAttribute(String, Constable)} in order
* to obtain a layout which can be classified accordingly by {@link CLinker#downcallHandle(Addressable, MethodType, FunctionDescriptor)}
- * and {@link CLinker#upcallStub(MethodHandle, FunctionDescriptor)}.
+ * and {@link CLinker#upcallStub(MethodHandle, FunctionDescriptor, ResourceScope)}.
*/
enum TypeKind {
/**
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/LibraryLookup.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/LibraryLookup.java
index b92a3addc6f..3cca0144b4f 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/LibraryLookup.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/LibraryLookup.java
@@ -46,12 +46,12 @@
*
* In cases where a client wants to create a memory segment out of a lookup symbol, the client might want to attach the
* lookup symbol to the newly created segment, so that the symbol will be kept reachable as long as the memory segment
- * is reachable; this can be achieved by creating the segment using the {@link MemoryAddress#asSegmentRestricted(long, Runnable, Object)}
+ * is reachable; this can be achieved by creating the segment using the {@link MemoryAddress#asSegmentRestricted(long, ResourceScope)}.
* restricted segment factory, as follows:
*
* To allow for a library to be unloaded, a client will have to discard any strong references it
@@ -82,7 +82,7 @@ interface Symbol extends Addressable {
/**
* The memory address of this lookup symbol. If the memory associated with this symbol needs to be dereferenced,
- * clients can obtain a segment from this symbol's address using the {@link MemoryAddress#asSegmentRestricted(long, Runnable, Object)},
+ * clients can obtain a segment from this symbol's address using the {@link MemoryAddress#asSegmentRestricted(long, Runnable, ResourceScope)},
* and making sure that the created segment maintains a strong reference to this symbol, to prevent library unloading.
* @return the memory address of this lookup symbol.
*/
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MappedMemorySegments.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MappedMemorySegments.java
index 4b919a7fb6b..8c451207a1e 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MappedMemorySegments.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MappedMemorySegments.java
@@ -41,7 +41,7 @@
* Clients requiring sophisticated, low-level control over mapped memory segments, should consider writing
* custom mapped memory segment factories; using JNI, e.g. on Linux, it is possible to call {@code mmap}
* with the desired parameters; the returned address can be easily wrapped into a memory segment, using
- * {@link MemoryAddress#ofLong(long)} and {@link MemoryAddress#asSegmentRestricted(long, Runnable, Object)}.
+ * {@link MemoryAddress#ofLong(long)} and {@link MemoryAddress#asSegmentRestricted(long, Runnable, ResourceScope)}.
*
*
Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
* elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown.
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java
index 3348506f8ce..59fedf11a78 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemoryAddress.java
@@ -26,10 +26,8 @@
package jdk.incubator.foreign;
-import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.MemoryAddressImpl;
-import jdk.internal.foreign.NativeMemorySegmentImpl;
-import jdk.internal.foreign.Utils;
+import jdk.internal.ref.CleanerFactory;
import java.lang.ref.Cleaner;
@@ -91,57 +89,77 @@ default MemoryAddress address() {
long segmentOffset(MemorySegment segment);
/**
- * Returns a new confined native memory segment with given size, and whose base address is this address; the returned segment has its own temporal
- * bounds, and can therefore be closed. This method can be useful when interacting with custom native memory sources (e.g. custom allocators),
- * where an address to some underlying memory region is typically obtained from native code (often as a plain {@code long} value).
- *
- * The returned segment will feature all access modes
- * (see {@link MemorySegment#ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * Returns a shared native memory segment with given size, and whose base address is this address. This method
+ * can be useful when interacting with custom native memory sources (e.g. custom allocators), where an address to some
+ * underlying memory region is typically obtained from native code (often as a plain {@code long} value).
+ * The returned segment is associated with the {@link ResourceScope#globalScope() global} resource scope.
*
* Clients should ensure that the address and bounds refers to a valid region of memory that is accessible for reading and,
* if appropriate, writing; an attempt to access an invalid memory location from Java code will either return an arbitrary value,
* have no visible effect, or cause an unspecified exception to be thrown.
*
- * Calling {@link MemorySegment#close()} on the returned segment will not result in releasing any
- * memory resources which might implicitly be associated with the segment. This method is equivalent to the following code:
+ * This method is equivalent to the following code:
*
* This method is restricted. Restricted methods are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
* restricted methods, and use safe and supported functionalities, where possible.
*
* @param bytesSize the desired size.
- * @return a new confined native memory segment with given base address and size.
+ * @return a new native memory segment with given base address and size.
* @throws IllegalArgumentException if {@code bytesSize <= 0}.
* @throws UnsupportedOperationException if this address is an heap address.
* @throws IllegalAccessError if the runtime property {@code foreign.restricted} is not set to either
* {@code permit}, {@code warn} or {@code debug} (the default value is set to {@code deny}).
*/
default MemorySegment asSegmentRestricted(long bytesSize) {
- return asSegmentRestricted(bytesSize, null, null);
+ return asSegmentRestricted(bytesSize, null, ResourceScope.globalScope());
}
/**
- * Returns a new confined native memory segment with given size, and whose base address is this address; the returned segment has its own temporal
- * bounds, and can therefore be closed. This method can be useful when interacting with custom native memory sources (e.g. custom allocators),
- * where an address to some underlying memory region is typically obtained from native code (often as a plain {@code long} value).
- *
- * The returned segment will feature all access modes
- * (see {@link MemorySegment#ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
- * Moreover, the returned segment will keep a strong reference to the supplied attachment object (if any), which can
- * be useful in cases where the lifecycle of the segment is dependent on that of some other external resource.
+ * Returns a native memory segment with given size and resource scope, and whose base address is this address. This method
+ * can be useful when interacting with custom native memory sources (e.g. custom allocators), where an address to some
+ * underlying memory region is typically obtained from native code (often as a plain {@code long} value).
+ * The returned segment is not read-only (see {@link MemorySegment#isReadOnly()}), and is associated with the
+ * provided resource scope.
*
* Clients should ensure that the address and bounds refers to a valid region of memory that is accessible for reading and,
* if appropriate, writing; an attempt to access an invalid memory location from Java code will either return an arbitrary value,
* have no visible effect, or cause an unspecified exception to be thrown.
*
- * Calling {@link MemorySegment#close()} on the returned segment will not result in releasing any
- * memory resources which might implicitly be associated with the segment, but will result in calling the
- * provided cleanup action (if any).
+ * This method is equivalent to the following code:
+ *
+ * This method is restricted. Restricted methods are unsafe, and, if used incorrectly, their use might crash
+ * the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
+ * restricted methods, and use safe and supported functionalities, where possible.
+ *
+ * @param bytesSize the desired size.
+ * @param scope the native segment scope.
+ * @return a new native memory segment with given base address, size and scope.
+ * @throws IllegalArgumentException if {@code bytesSize <= 0}.
+ * @throws UnsupportedOperationException if this address is an heap address.
+ * @throws IllegalAccessError if the runtime property {@code foreign.restricted} is not set to either
+ * {@code permit}, {@code warn} or {@code debug} (the default value is set to {@code deny}).
+ */
+ default MemorySegment asSegmentRestricted(long bytesSize, ResourceScope scope) {
+ return asSegmentRestricted(bytesSize, null, scope);
+ }
+
+ /**
+ * Returns a new native memory segment with given size and resource scope, and whose base address is this address. This method
+ * can be useful when interacting with custom native memory sources (e.g. custom allocators), where an address to some
+ * underlying memory region is typically obtained from native code (often as a plain {@code long} value).
+ * The returned segment is associated with the provided resource scope.
+ *
+ * Clients should ensure that the address and bounds refers to a valid region of memory that is accessible for reading and,
+ * if appropriate, writing; an attempt to access an invalid memory location from Java code will either return an arbitrary value,
+ * have no visible effect, or cause an unspecified exception to be thrown.
*
- * Both the cleanup action and the attachment object (if any) will be preserved under terminal operations such as
- * {@link MemorySegment#handoff(Thread)}, {@link MemorySegment#share()} and {@link MemorySegment#registerCleaner(Cleaner)}.
+ * Calling {@link ResourceScope#close()} on the scope associated with the returned segment will result in calling
+ * the provided cleanup action (if any).
*
* This method is restricted. Restricted methods are unsafe, and, if used incorrectly, their use might crash
* the JVM or, worse, silently result in memory corruption. Thus, clients should refrain from depending on
@@ -149,14 +167,14 @@ default MemorySegment asSegmentRestricted(long bytesSize) {
*
* @param bytesSize the desired size.
* @param cleanupAction the cleanup action; can be {@code null}.
- * @param attachment an attachment object that will be kept strongly reachable by the returned segment; can be {@code null}.
- * @return a new confined native memory segment with given base address and size.
+ * @param scope the native segment scope.
+ * @return a new native memory segment with given base address, size and scope.
* @throws IllegalArgumentException if {@code bytesSize <= 0}.
* @throws UnsupportedOperationException if this address is an heap address.
* @throws IllegalAccessError if the runtime property {@code foreign.restricted} is not set to either
* {@code permit}, {@code warn} or {@code debug} (the default value is set to {@code deny}).
*/
- MemorySegment asSegmentRestricted(long bytesSize, Runnable cleanupAction, Object attachment);
+ MemorySegment asSegmentRestricted(long bytesSize, Runnable cleanupAction, ResourceScope scope);
/**
* Returns the raw long value associated with this memory address.
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java
index a8bd56e4b61..43400dffb87 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/MemorySegment.java
@@ -26,13 +26,12 @@
package jdk.incubator.foreign;
-import java.io.FileDescriptor;
-import java.lang.ref.Cleaner;
import java.nio.ByteBuffer;
import jdk.internal.foreign.AbstractMemorySegmentImpl;
import jdk.internal.foreign.HeapMemorySegmentImpl;
import jdk.internal.foreign.MappedMemorySegmentImpl;
+import jdk.internal.foreign.MemoryScope;
import jdk.internal.foreign.NativeMemorySegmentImpl;
import jdk.internal.foreign.Utils;
@@ -40,14 +39,13 @@
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.util.Objects;
-import java.util.Optional;
import java.util.Spliterator;
/**
* A memory segment models a contiguous region of memory. A memory segment is associated with both spatial
- * and temporal bounds. Spatial bounds ensure that memory access operations on a memory segment cannot affect a memory location
+ * and temporal bounds (e.g. a {@link ResourceScope}). Spatial bounds ensure that memory access operations on a memory segment cannot affect a memory location
* which falls outside the boundaries of the memory segment being accessed. Temporal bounds ensure that memory access
- * operations on a segment cannot occur after a memory segment has been closed (see {@link MemorySegment#close()}).
+ * operations on a segment cannot occur after the resource scope associated with a memory segment has been closed (see {@link ResourceScope#close()}).
*
* All implementations of this interface must be value-based;
* programmers should treat instances that are {@linkplain Object#equals(Object) equal} as interchangeable and should not
@@ -82,108 +80,55 @@
* {@link MemorySegment#mapFile(Path, long, long, FileChannel.MapMode)}. Such memory segments are called mapped memory segments;
* mapped memory segments are associated with an underlying file descriptor. For more operations on mapped memory segments, please refer to the
* {@link MappedMemorySegments} class.
- *
- * Array and buffer segments are effectively views over existing memory regions which might outlive the
- * lifecycle of the segments derived from them, and can even be manipulated directly (e.g. via array access, or direct use
- * of the {@link ByteBuffer} API) by other clients. As a result, while sharing array or buffer segments is possible,
- * it is strongly advised that clients wishing to do so take extra precautions to make sure that the underlying memory sources
- * associated with such segments remain inaccessible, and that said memory sources are never aliased by more than one segment
- * at a time - e.g. so as to prevent concurrent modifications of the contents of an array, or buffer segment.
- *
- *
Explicit deallocation
- *
- * Memory segments are closed explicitly (see {@link MemorySegment#close()}). When a segment is closed, it is no longer
- * alive (see {@link #isAlive()}, and subsequent operation on the segment (or on any {@link MemoryAddress} instance
- * derived from it) will fail with {@link IllegalStateException}.
- *
- * Closing a segment might trigger the releasing of the underlying memory resources associated with said segment, depending on
- * the kind of memory segment being considered:
- *
- *
closing a native memory segment results in freeing the native memory associated with it
- *
closing a mapped memory segment results in the backing memory-mapped file to be unmapped
- *
closing a buffer, or a heap segment does not have any side-effect, other than marking the segment
- * as not alive (see {@link MemorySegment#isAlive()}). Also, since the buffer and heap segments might keep
- * strong references to the original buffer or array instance, it is the responsibility of clients to ensure that
- * these segments are discarded in a timely manner, so as not to prevent garbage collection to reclaim the underlying
- * objects.
*
- * Memory segments supports zero or more access modes. Supported access modes are {@link #READ},
- * {@link #WRITE}, {@link #CLOSE}, {@link #SHARE} and {@link #HANDOFF}. The set of access modes supported by a segment alters the
- * set of operations that are supported by that segment. For instance, attempting to call {@link #close()} on
- * a segment which does not support the {@link #CLOSE} access mode will result in an exception.
+ * Memory segments are associated to a resource scope (see {@link ResourceScope}), which can be accessed using
+ * the {@link #scope()} method. As for all resources associated with a resource scope, a segment cannot be
+ * accessed after its corresponding scope has been closed. For instance, the following code will result in an
+ * exception:
+ *
+ * Additionally, access to a memory segment in subject to the thread-confinement checks enforced by the owning scope; that is,
+ * if the segment is associated with a shared scope, it can be accessed by multiple threads; if it is associated with a confined
+ * scope, it can only be accessed by the thread which own the scope.
*
- * The set of supported access modes can only be made stricter (by supporting fewer access modes). This means
- * that restricting the set of access modes supported by a segment before sharing it with other clients
- * is generally a good practice if the creator of the segment wants to retain some control over how the segment
- * is going to be accessed.
+ * Heap and buffer segments are always associated with a global, shared scope. This scope cannot be closed,
+ * and can be considered as always alive.
*
*
Memory segment views
*
- * Memory segments support views. For instance, it is possible to alter the set of supported access modes,
- * by creating an immutable view of a memory segment, as follows:
+ * Memory segments support views. For instance, it is possible to create an immutable view of a memory segment, as follows:
*
* It is also possible to create views whose spatial bounds are stricter than the ones of the original segment
* (see {@link MemorySegment#asSlice(long, long)}).
*
- * Temporal bounds of the original segment are inherited by the view; that is, closing a segment view, such as a sliced
- * view, will cause the original segment to be closed; as such special care must be taken when sharing views
- * between multiple clients. If a client want to protect itself against early closure of a segment by
- * another actor, it is the responsibility of that client to take protective measures, such as removing {@link #CLOSE}
- * from the set of supported access modes, before sharing the view with another client.
+ * Temporal bounds of the original segment are inherited by the views; that is, when the scope associated with a segment
+ * is closed, all the views associated with that segment will also be rendered inaccessible.
*
* To allow for interoperability with existing code, a byte buffer view can be obtained from a memory segment
* (see {@link #asByteBuffer()}). This can be useful, for instance, for those clients that want to keep using the
* {@link ByteBuffer} API, but need to operate on large memory segments. Byte buffers obtained in such a way support
* the same spatial and temporal access restrictions associated to the memory segment from which they originated.
*
- *
*
- * Memory segments support strong thread-confinement guarantees. Upon creation, they are assigned an owner thread,
- * typically the thread which initiated the creation operation. After creation, only the owner thread will be allowed
- * to directly manipulate the memory segment (e.g. close the memory segment) or access the underlying memory associated with
- * the segment using a memory access var handle. Any attempt to perform such operations from a thread other than the
- * owner thread will result in a runtime failure.
- *
- * The {@link #handoff(Thread)} method can be used to change the thread-confinement properties of a memory segment.
- * This method is, like {@link #close()}, a terminal operation which marks the original segment as not alive
- * (see {@link #isAlive()}) and creates a new segment with the desired thread-confinement properties. Calling
- * {@link #handoff(Thread)} is only possible if the segment features the corresponding {@link #HANDOFF} access mode.
- *
- * For instance, if a client wants to transfer ownership of a segment to another (known) thread, it can do so as follows:
+ * A client might obtain a {@link Spliterator} from a segment, which can then be used to slice the segment and allow multiple
+ * threads to work in parallel on disjoint segment slices (to do this, the segment has to be associated with a shared scope).
+ * The following code can be used to sum all int values in a memory segment in parallel:
*
*
- *
- * By doing so, the original segment is marked as not alive, and a new segment is returned whose owner thread
- * is {@code threadA}; this allows, for instance, for two threads {@code A} and {@code B} to share
- * a segment in a controlled, cooperative and race-free fashion (also known as serial thread confinement).
- *
- * Alternatively, the {@link #share()} method can be used to remove thread ownership altogether; this is only possible
- * if the segment features the corresponding {@link #SHARE} access mode. The following code shows how clients can
- * obtain a shared segment:
- *
- *
- *
- * Again here, the original segment is marked as not alive, and a new shared segment is returned which features no owner
- * thread (e.g. {@link #ownerThread()} returns {@code null}). This might be useful when multiple threads need to process
- * the contents of the same memory segment concurrently (e.g. in the case of parallel processing). For instance, a client
- * might obtain a {@link Spliterator} from a shared segment, which can then be used to slice the segment and allow multiple
- * threads to work in parallel on disjoint segment slices. The following code can be used to sum all int values in a memory segment in parallel:
- *
- *
*
- * Once shared, a segment can be claimed back by a given thread (again using {@link #handoff(Thread)}); in fact, many threads
- * can attempt to gain ownership of the same segment, concurrently, and only one of them is guaranteed to succeed.
- *
- * When using shared segments, clients should make sure that no other thread is accessing the segment while
- * the segment is being closed. If one or more threads attempts to access a segment concurrently while the
- * segment is being closed, an exception might occur on both the accessing and the closing threads. Clients should
- * refrain from attempting to close a segment repeatedly (e.g. keep calling {@link #close()} until no exception is thrown);
- * such exceptions should instead be seen as an indication that the client code is lacking appropriate synchronization between the threads
- * accessing/closing the segment.
- *
- *
Implicit deallocation
- *
- * Clients can register a memory segment against a {@link Cleaner}, to make sure that underlying resources associated with
- * that segment will be released when the segment becomes unreachable, which can be useful to prevent native memory
- * leaks. This can be achieved using the {@link #registerCleaner(Cleaner)} method, as follows:
- *
- *
- *
- * Here, the original segment is marked as not alive, and a new segment is returned (the owner thread of the returned
- * segment set is set to that of the current thread, see {@link #ownerThread()}); the new segment
- * will also be registered with the the {@link Cleaner} instance provided to the {@link #registerCleaner(Cleaner)} method;
- * as such, if not closed explicitly (see {@link #close()}), the new segment will be automatically closed by the cleaner.
- *
* @apiNote In the future, if the Java language permits, {@link MemorySegment}
* may become a {@code sealed} interface, which would prohibit subclassing except by other explicitly permitted subtypes.
*
* @implSpec
* Implementations of this interface are immutable, thread-safe and value-based.
*/
-public interface MemorySegment extends Addressable, AutoCloseable {
+public interface MemorySegment extends Addressable {
/**
* The base memory address associated with this memory segment. The returned address is
@@ -245,10 +164,10 @@ public interface MemorySegment extends Addressable, AutoCloseable {
* if the supplied layout is a sequence layout whose element count is {@code N}, then calling {@link Spliterator#trySplit()}
* will result in a spliterator serving approximatively {@code N/2} elements (depending on whether N is even or not).
* As such, splitting is possible as long as {@code N >= 2}. The spliterator returns segments that feature the same
- * access modes as the given segment less the {@link #CLOSE} access mode.
+ * scope as the given segment.
*
* The returned spliterator effectively allows to slice this segment into disjoint sub-segments, which can then
- * be processed in parallel by multiple threads (if the segment is shared).
+ * be processed in parallel by multiple threads.
*
* @param layout the layout to be used for splitting.
* @return the element spliterator for this segment
@@ -258,10 +177,10 @@ public interface MemorySegment extends Addressable, AutoCloseable {
Spliterator spliterator(SequenceLayout layout);
/**
- * The thread owning this segment.
- * @return the thread owning this segment.
+ * Returns the resource scope associated with this memory segment.
+ * @return the resource scope associated with this memory segment.
*/
- Thread ownerThread();
+ ResourceScope scope();
/**
* The size (in bytes) of this memory segment.
@@ -269,33 +188,6 @@ public interface MemorySegment extends Addressable, AutoCloseable {
*/
long byteSize();
- /**
- * Obtains a segment view with specific access modes. Supported access modes are {@link #READ}, {@link #WRITE},
- * {@link #CLOSE}, {@link #SHARE} and {@link #HANDOFF}. It is generally not possible to go from a segment with stricter access modes
- * to one with less strict access modes. For instance, attempting to add {@link #WRITE} access mode to a read-only segment
- * will be met with an exception.
- * @param accessModes an ORed mask of zero or more access modes.
- * @return a segment view with specific access modes.
- * @throws IllegalArgumentException when {@code mask} is an access mask which is less strict than the one supported by this
- * segment, or when {@code mask} contains bits not associated with any of the supported access modes.
- */
- MemorySegment withAccessModes(int accessModes);
-
- /**
- * Does this segment support a given set of access modes?
- * @param accessModes an ORed mask of zero or more access modes.
- * @return true, if the access modes in {@code accessModes} are stricter than the ones supported by this segment.
- * @throws IllegalArgumentException when {@code mask} contains bits not associated with any of the supported access modes.
- */
- boolean hasAccessModes(int accessModes);
-
- /**
- * Returns the access modes associated with this segment; the result is represented as ORed values from
- * {@link #READ}, {@link #WRITE}, {@link #CLOSE}, {@link #SHARE} and {@link #HANDOFF}.
- * @return the access modes associated with this segment.
- */
- int accessModes();
-
/**
* Obtains a new memory segment view whose base address is the same as the base address of this segment plus a given offset,
* and whose new size is specified by the given argument.
@@ -377,133 +269,27 @@ default MemorySegment asSlice(MemoryAddress newBase) {
}
/**
- * Is this a mapped segment? Returns true if this segment is a mapped memory segment,
- * created using the {@link #mapFile(Path, long, long, FileChannel.MapMode)} factory, or a buffer segment
- * derived from a {@link java.nio.MappedByteBuffer} using the {@link #ofByteBuffer(ByteBuffer)} factory.
- * @return {@code true} if this segment is a mapped segment.
- */
- boolean isMapped();
-
- /**
- * Is this segment alive?
- * @return true, if the segment is alive.
- * @see MemorySegment#close()
+ * Is this segment read-only?
+ * @return {@code true}, if this segment is read-only.
+ * @see #asReadOnly()
*/
- boolean isAlive();
+ boolean isReadOnly();
/**
- * Closes this memory segment. This is a terminal operation; as a side-effect, if this operation completes
- * without exceptions, this segment will be marked as not alive, and subsequent operations on this segment
- * will fail with {@link IllegalStateException}.
- *
- * Depending on the kind of memory segment being closed, calling this method further triggers deallocation of all the resources
- * associated with the memory segment.
- *
- * @apiNote This operation is not idempotent; that is, closing an already closed segment always results in an
- * exception being thrown. This reflects a deliberate design choice: segment state transitions should be
- * manifest in the client code; a failure in any of these transitions reveals a bug in the underlying application
- * logic. This is especially useful when reasoning about the lifecycle of dependent segment views (see {@link #asSlice(MemoryAddress)},
- * where closing one segment might side-effect multiple segments. In such cases it might in fact not be obvious, looking
- * at the code, as to whether a given segment is alive or not.
- *
- * @throws IllegalStateException if this segment is not alive, or if access occurs from a thread other than the
- * thread owning this segment, or if this segment is shared and the segment is concurrently accessed while this method is
- * called.
- * @throws UnsupportedOperationException if this segment does not support the {@link #CLOSE} access mode.
+ * Obtains a read-only view of this segment. The resulting segment will be identical to this one, but
+ * attempts to overwrite the contents of the returned segment will cause runtime exceptions.
+ * @return a read-only view of this segment
+ * @see #isReadOnly()
*/
- void close();
+ MemorySegment asReadOnly();
/**
- * Obtains a new confined memory segment backed by the same underlying memory region as this segment. The returned segment will
- * be confined on the specified thread, and will feature the same spatial bounds and access modes (see {@link #accessModes()})
- * as this segment.
- *
- * This is a terminal operation; as a side-effect, if this operation completes
- * without exceptions, this segment will be marked as not alive, and subsequent operations on this segment
- * will fail with {@link IllegalStateException}.
- *
- * In case where the owner thread of the returned segment differs from that of this segment, write accesses to this
- * segment's content happens-before
- * hand-over from the current owner thread to the new owner thread, which in turn happens before read accesses
- * to the returned segment's contents on the new owner thread.
- *
- * @param thread the new owner thread
- * @return a new confined memory segment whose owner thread is set to {@code thread}.
- * @throws IllegalStateException if this segment is not alive, or if access occurs from a thread other than the
- * thread owning this segment.
- * @throws UnsupportedOperationException if this segment does not support the {@link #HANDOFF} access mode.
- */
- MemorySegment handoff(Thread thread);
-
- /**
- * Obtains a new confined memory segment backed by the same underlying memory region as this segment, but whose
- * temporal bounds are controlled by the provided {@link NativeScope} instance.
- *
- * This is a terminal operation;
- * as a side-effect, this segment will be marked as not alive, and subsequent operations on this segment
- * will fail with {@link IllegalStateException}.
- *
- * The returned segment will feature only {@link MemorySegment#READ} and {@link MemorySegment#WRITE} access modes
- * (assuming these were available in the original segment). As such the returned segment cannot be closed directly
- * using {@link MemorySegment#close()} - but it will be closed indirectly when this native scope is closed. The
- * returned segment will also be confined by the same thread as the provided native scope (see {@link NativeScope#ownerThread()}).
- *
- * In case where the owner thread of the returned segment differs from that of this segment, write accesses to this
- * segment's content happens-before
- * hand-over from the current owner thread to the new owner thread, which in turn happens before read accesses
- * to the returned segment's contents on the new owner thread.
- *
- * @param nativeScope the native scope.
- * @return a new confined memory segment backed by the same underlying memory region as this segment, but whose life-cycle
- * is tied to that of {@code nativeScope}.
- * @throws IllegalStateException if this segment is not alive, or if access occurs from a thread other than the
- * thread owning this segment.
- * @throws UnsupportedOperationException if this segment does not support the {@link #HANDOFF} access mode.
- */
- MemorySegment handoff(NativeScope nativeScope);
-
- /**
- * Obtains a new shared memory segment backed by the same underlying memory region as this segment. The returned segment will
- * not be confined on any thread and can therefore be accessed concurrently from multiple threads; moreover, the
- * returned segment will feature the same spatial bounds and access modes (see {@link #accessModes()})
- * as this segment.
- *
- * This is a terminal operation; as a side-effect, if this operation completes
- * without exceptions, this segment will be marked as not alive, and subsequent operations on this segment
- * will fail with {@link IllegalStateException}.
- *
- * Write accesses to this segment's content happens-before
- * hand-over from the current owner thread to the new owner thread, which in turn happens before read accesses
- * to the returned segment's contents on a new thread.
- *
- * @return a new memory shared segment backed by the same underlying memory region as this segment.
- * @throws IllegalStateException if this segment is not alive, or if access occurs from a thread other than the
- * thread owning this segment.
- */
- MemorySegment share();
-
- /**
- * Register this memory segment instance against a {@link Cleaner} object, by returning a new memory segment backed
- * by the same underlying memory region as this segment. The returned segment will feature the same confinement,
- * spatial bounds and access modes (see {@link #accessModes()}) as this segment. Moreover, the returned segment
- * will be associated with the specified {@link Cleaner} object; this allows for the segment to be closed
- * as soon as it becomes unreachable, which might be helpful in preventing native memory leaks.
- *
- * This is a terminal operation; as a side-effect, if this operation completes
- * without exceptions, this segment will be marked as not alive, and subsequent operations on this segment
- * will fail with {@link IllegalStateException}.
- *
- * The implicit deallocation behavior associated with the returned segment will be preserved under terminal
- * operations such as {@link #handoff(Thread)} and {@link #share()}.
- *
- * @param cleaner the cleaner object, responsible for implicit deallocation of the returned segment.
- * @return a new memory segment backed by the same underlying memory region as this segment, which features
- * implicit deallocation.
- * @throws IllegalStateException if this segment is not alive, or if access occurs from a thread other than the
- * thread owning this segment, or if this segment is already associated with a cleaner.
- * @throws UnsupportedOperationException if this segment does not support the {@link #CLOSE} access mode.
+ * Is this a mapped segment? Returns true if this segment is a mapped memory segment,
+ * created using the {@link #mapFile(Path, long, long, FileChannel.MapMode)} factory, or a buffer segment
+ * derived from a {@link java.nio.MappedByteBuffer} using the {@link #ofByteBuffer(ByteBuffer)} factory.
+ * @return {@code true} if this segment is a mapped segment.
*/
- MemorySegment registerCleaner(Cleaner cleaner);
+ boolean isMapped();
/**
* Fills a value into this memory segment.
@@ -528,7 +314,7 @@ default MemorySegment asSlice(MemoryAddress newBase) {
* @return this memory segment
* @throws IllegalStateException if this segment is not alive, or if access occurs from a thread other than the
* thread owning this segment
- * @throws UnsupportedOperationException if this segment does not support the {@link #WRITE} access mode
+ * @throws UnsupportedOperationException if this segment is read-only (see {@link #isReadOnly()}).
*/
MemorySegment fill(byte value);
@@ -549,9 +335,7 @@ default MemorySegment asSlice(MemoryAddress newBase) {
* @throws IndexOutOfBoundsException if {@code src.byteSize() > this.byteSize()}.
* @throws IllegalStateException if either the source segment or this segment have been already closed,
* or if access occurs from a thread other than the thread owning either segment.
- * @throws UnsupportedOperationException if either the source segment or this segment do not feature required access modes;
- * more specifically, {@code src} should feature at least the {@link MemorySegment#READ} access mode,
- * while this segment should feature at least the {@link MemorySegment#WRITE} access mode.
+ * @throws UnsupportedOperationException if this segment is read-only (see {@link #isReadOnly()}).
*/
void copyFrom(MemorySegment src);
@@ -576,15 +360,13 @@ default MemorySegment asSlice(MemoryAddress newBase) {
* @throws IllegalStateException if either this segment of the other segment
* have been already closed, or if access occurs from a thread other than the
* thread owning either segment
- * @throws UnsupportedOperationException if either this segment or the other
- * segment does not feature at least the {@link MemorySegment#READ} access mode
*/
long mismatch(MemorySegment other);
/**
* Wraps this segment in a {@link ByteBuffer}. Some of the properties of the returned buffer are linked to
* the properties of this segment. For instance, if this segment is immutable
- * (e.g. the segment has access mode {@link #READ} but not {@link #WRITE}), then the resulting buffer is read-only
+ * (e.g. the segment is a read-only segment, see {@link #isReadOnly()}), then the resulting buffer is read-only
* (see {@link ByteBuffer#isReadOnly()}. Additionally, if this is a native memory segment, the resulting buffer is
* direct (see {@link ByteBuffer#isDirect()}).
*
@@ -593,11 +375,10 @@ default MemorySegment asSlice(MemoryAddress newBase) {
* are set to this segment' size (see {@link MemorySegment#byteSize()}). For this reason, a byte buffer cannot be
* returned if this segment' size is greater than {@link Integer#MAX_VALUE}.
*
- * The life-cycle of the returned buffer will be tied to that of this segment. That means that if the this segment
- * is closed (see {@link MemorySegment#close()}, accessing the returned
- * buffer will throw an {@link IllegalStateException}.
+ * The life-cycle of the returned buffer will be tied to that of this segment. That is, accessing the returned buffer
+ * after the scope associated with this segment has been closed (see {@link ResourceScope#close()}, will throw an {@link IllegalStateException}.
*
- * If this segment is shared, calling certain I/O operations on the resulting buffer might result in
+ * If this segment is associated with a shared scope, calling certain I/O operations on the resulting buffer might result in
* an unspecified exception being thrown. Examples of such problematic operations are {@link FileChannel#read(ByteBuffer)},
* {@link FileChannel#write(ByteBuffer)}, {@link java.nio.channels.SocketChannel#read(ByteBuffer)} and
* {@link java.nio.channels.SocketChannel#write(ByteBuffer)}.
@@ -608,83 +389,70 @@ default MemorySegment asSlice(MemoryAddress newBase) {
* @return a {@link ByteBuffer} view of this memory segment.
* @throws UnsupportedOperationException if this segment cannot be mapped onto a {@link ByteBuffer} instance,
* e.g. because it models an heap-based segment that is not based on a {@code byte[]}), or if its size is greater
- * than {@link Integer#MAX_VALUE}, or if the segment does not support the {@link #READ} access mode.
+ * than {@link Integer#MAX_VALUE}.
*/
ByteBuffer asByteBuffer();
/**
* Copy the contents of this memory segment into a fresh byte array.
* @return a fresh byte array copy of this memory segment.
- * @throws UnsupportedOperationException if this segment does not feature the {@link #READ} access mode, or if this
- * segment's contents cannot be copied into a {@link byte[]} instance, e.g. its size is greater than {@link Integer#MAX_VALUE},
* @throws IllegalStateException if this segment has been closed, or if access occurs from a thread other than the
- * thread owning this segment.
+ * thread owning this segment, or if this segment's contents cannot be copied into a {@link byte[]} instance,
+ * e.g. its size is greater than {@link Integer#MAX_VALUE}.
*/
byte[] toByteArray();
/**
* Copy the contents of this memory segment into a fresh short array.
* @return a fresh short array copy of this memory segment.
- * @throws UnsupportedOperationException if this segment does not feature the {@link #READ} access mode, or if this
- * segment's contents cannot be copied into a {@link short[]} instance, e.g. because {@code byteSize() % 2 != 0},
- * or {@code byteSize() / 2 > Integer#MAX_VALUE}.
* @throws IllegalStateException if this segment has been closed, or if access occurs from a thread other than the
- * thread owning this segment.
+ * thread owning this segment, or if this segment's contents cannot be copied into a {@link short[]} instance,
+ * e.g. because {@code byteSize() % 2 != 0}, or {@code byteSize() / 2 > Integer#MAX_VALUE}
*/
short[] toShortArray();
/**
* Copy the contents of this memory segment into a fresh char array.
* @return a fresh char array copy of this memory segment.
- * @throws UnsupportedOperationException if this segment does not feature the {@link #READ} access mode, or if this
- * segment's contents cannot be copied into a {@link char[]} instance, e.g. because {@code byteSize() % 2 != 0},
- * or {@code byteSize() / 2 > Integer#MAX_VALUE}.
* @throws IllegalStateException if this segment has been closed, or if access occurs from a thread other than the
- * thread owning this segment.
+ * thread owning this segment, or if this segment's contents cannot be copied into a {@link char[]} instance,
+ * e.g. because {@code byteSize() % 2 != 0}, or {@code byteSize() / 2 > Integer#MAX_VALUE}.
*/
char[] toCharArray();
/**
* Copy the contents of this memory segment into a fresh int array.
* @return a fresh int array copy of this memory segment.
- * @throws UnsupportedOperationException if this segment does not feature the {@link #READ} access mode, or if this
- * segment's contents cannot be copied into a {@link int[]} instance, e.g. because {@code byteSize() % 4 != 0},
- * or {@code byteSize() / 4 > Integer#MAX_VALUE}.
* @throws IllegalStateException if this segment has been closed, or if access occurs from a thread other than the
- * thread owning this segment.
+ * thread owning this segment, or if this segment's contents cannot be copied into a {@link int[]} instance,
+ * e.g. because {@code byteSize() % 4 != 0}, or {@code byteSize() / 4 > Integer#MAX_VALUE}.
*/
int[] toIntArray();
/**
* Copy the contents of this memory segment into a fresh float array.
* @return a fresh float array copy of this memory segment.
- * @throws UnsupportedOperationException if this segment does not feature the {@link #READ} access mode, or if this
- * segment's contents cannot be copied into a {@link float[]} instance, e.g. because {@code byteSize() % 4 != 0},
- * or {@code byteSize() / 4 > Integer#MAX_VALUE}.
* @throws IllegalStateException if this segment has been closed, or if access occurs from a thread other than the
- * thread owning this segment.
+ * thread owning this segment, or if this segment's contents cannot be copied into a {@link float[]} instance,
+ * e.g. because {@code byteSize() % 4 != 0}, or {@code byteSize() / 4 > Integer#MAX_VALUE}.
*/
float[] toFloatArray();
/**
* Copy the contents of this memory segment into a fresh long array.
* @return a fresh long array copy of this memory segment.
- * @throws UnsupportedOperationException if this segment does not feature the {@link #READ} access mode, or if this
- * segment's contents cannot be copied into a {@link long[]} instance, e.g. because {@code byteSize() % 8 != 0},
- * or {@code byteSize() / 8 > Integer#MAX_VALUE}.
* @throws IllegalStateException if this segment has been closed, or if access occurs from a thread other than the
- * thread owning this segment.
+ * thread owning this segment, or if this segment's contents cannot be copied into a {@link long[]} instance,
+ * e.g. because {@code byteSize() % 8 != 0}, or {@code byteSize() / 8 > Integer#MAX_VALUE}.
*/
long[] toLongArray();
/**
* Copy the contents of this memory segment into a fresh double array.
* @return a fresh double array copy of this memory segment.
- * @throws UnsupportedOperationException if this segment does not feature the {@link #READ} access mode, or if this
- * segment's contents cannot be copied into a {@link double[]} instance, e.g. because {@code byteSize() % 8 != 0},
- * or {@code byteSize() / 8 > Integer#MAX_VALUE}.
* @throws IllegalStateException if this segment has been closed, or if access occurs from a thread other than the
- * thread owning this segment.
+ * thread owning this segment, or if this segment's contents cannot be copied into a {@link double[]} instance,
+ * e.g. because {@code byteSize() % 8 != 0}, or {@code byteSize() / 8 > Integer#MAX_VALUE}.
*/
double[] toDoubleArray();
@@ -693,15 +461,15 @@ default MemorySegment asSlice(MemoryAddress newBase) {
* buffer. The segment starts relative to the buffer's position (inclusive)
* and ends relative to the buffer's limit (exclusive).
*
- * The segment will feature all access modes (see {@link #ALL_ACCESS}),
- * unless the given buffer is {@linkplain ByteBuffer#isReadOnly() read-only} in which case the segment will
- * not feature the {@link #WRITE} access mode, and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * If the buffer is {@link ByteBuffer#isReadOnly() read-only}, the resulting segment will also be
+ * {@link ByteBuffer#isReadOnly() read-only}. The scope associated with this segment can either be the
+ * {@link ResourceScope#globalScope() global} resource scope, in case the buffer has been created independently,
+ * or to some other (possibly closeable) resource scope, in case the buffer has been obtained using {@link #asByteBuffer()}.
*
- * The resulting memory segment keeps a reference to the backing buffer, to ensure it remains reachable
- * for the life-time of the segment.
+ * The resulting memory segment keeps a reference to the backing buffer, keeping it reachable.
*
* @param bb the byte buffer backing the buffer memory segment.
- * @return a new confined buffer memory segment.
+ * @return a new buffer memory segment.
*/
static MemorySegment ofByteBuffer(ByteBuffer bb) {
return AbstractMemorySegmentImpl.ofBuffer(bb);
@@ -709,13 +477,10 @@ static MemorySegment ofByteBuffer(ByteBuffer bb) {
/**
* Creates a new confined array memory segment that models the memory associated with a given heap-allocated byte array.
- *
- * The resulting memory segment keeps a reference to the backing array, to ensure it remains reachable
- * for the life-time of the segment. The segment will feature all access modes
- * (see {@link #ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * The returned segment's resource scope is set to the {@link ResourceScope#globalScope() global} resource scope.
*
* @param arr the primitive array backing the array memory segment.
- * @return a new confined array memory segment.
+ * @return a new array memory segment.
*/
static MemorySegment ofArray(byte[] arr) {
return HeapMemorySegmentImpl.OfByte.fromArray(arr);
@@ -723,13 +488,10 @@ static MemorySegment ofArray(byte[] arr) {
/**
* Creates a new confined array memory segment that models the memory associated with a given heap-allocated char array.
- *
- * The resulting memory segment keeps a reference to the backing array, to ensure it remains reachable
- * for the life-time of the segment. The segment will feature all access modes
- * (see {@link #ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * The returned segment's resource scope is set to the {@link ResourceScope#globalScope() global} resource scope.
*
* @param arr the primitive array backing the array memory segment.
- * @return a new confined array memory segment.
+ * @return a new array memory segment.
*/
static MemorySegment ofArray(char[] arr) {
return HeapMemorySegmentImpl.OfChar.fromArray(arr);
@@ -737,13 +499,10 @@ static MemorySegment ofArray(char[] arr) {
/**
* Creates a new confined array memory segment that models the memory associated with a given heap-allocated short array.
- *
- * The resulting memory segment keeps a reference to the backing array, to ensure it remains reachable
- * for the life-time of the segment. The segment will feature all access modes
- * (see {@link #ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * The returned segment's resource scope is set to the {@link ResourceScope#globalScope() global} resource scope.
*
* @param arr the primitive array backing the array memory segment.
- * @return a new confined array memory segment.
+ * @return a new array memory segment.
*/
static MemorySegment ofArray(short[] arr) {
return HeapMemorySegmentImpl.OfShort.fromArray(arr);
@@ -751,13 +510,10 @@ static MemorySegment ofArray(short[] arr) {
/**
* Creates a new confined array memory segment that models the memory associated with a given heap-allocated int array.
- *
- * The resulting memory segment keeps a reference to the backing array, to ensure it remains reachable
- * for the life-time of the segment. The segment will feature all access modes
- * (see {@link #ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * The returned segment's resource scope is set to the {@link ResourceScope#globalScope() global} resource scope.
*
* @param arr the primitive array backing the array memory segment.
- * @return a new confined array memory segment.
+ * @return a new array memory segment.
*/
static MemorySegment ofArray(int[] arr) {
return HeapMemorySegmentImpl.OfInt.fromArray(arr);
@@ -765,13 +521,10 @@ static MemorySegment ofArray(int[] arr) {
/**
* Creates a new confined array memory segment that models the memory associated with a given heap-allocated float array.
- *
- * The resulting memory segment keeps a reference to the backing array, to ensure it remains reachable
- * for the life-time of the segment. The segment will feature all access modes
- * (see {@link #ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * The returned segment's resource scope is set to the {@link ResourceScope#globalScope() global} resource scope.
*
* @param arr the primitive array backing the array memory segment.
- * @return a new confined array memory segment.
+ * @return a new array memory segment.
*/
static MemorySegment ofArray(float[] arr) {
return HeapMemorySegmentImpl.OfFloat.fromArray(arr);
@@ -779,13 +532,10 @@ static MemorySegment ofArray(float[] arr) {
/**
* Creates a new confined array memory segment that models the memory associated with a given heap-allocated long array.
- *
- * The resulting memory segment keeps a reference to the backing array, to ensure it remains reachable
- * for the life-time of the segment. The segment will feature all access modes
- * (see {@link #ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * The returned segment's resource scope is set to the {@link ResourceScope#globalScope() global} resource scope.
*
* @param arr the primitive array backing the array memory segment.
- * @return a new confined array memory segment.
+ * @return a new array memory segment.
*/
static MemorySegment ofArray(long[] arr) {
return HeapMemorySegmentImpl.OfLong.fromArray(arr);
@@ -793,13 +543,10 @@ static MemorySegment ofArray(long[] arr) {
/**
* Creates a new confined array memory segment that models the memory associated with a given heap-allocated double array.
- *
- * The resulting memory segment keeps a reference to the backing array, to ensure it remains reachable
- * for the life-time of the segment. The segment will feature all access modes
- * (see {@link #ALL_ACCESS}).
+ * The returned segment's resource scope is set to the {@link ResourceScope#globalScope() global} resource scope.
*
* @param arr the primitive array backing the array memory segment.
- * @return a new confined array memory segment.
+ * @return a new array memory segment.
*/
static MemorySegment ofArray(double[] arr) {
return HeapMemorySegmentImpl.OfDouble.fromArray(arr);
@@ -807,51 +554,173 @@ static MemorySegment ofArray(double[] arr) {
/**
* Creates a new confined native memory segment that models a newly allocated block of off-heap memory with given layout.
+ * The returned segment is associated with a fresh {@link ResourceScope#ofDefault() default scope}. Resources associated with this
+ * segment will be automatically released when the returned segment (or any slices and views derived from it) is no longer in use.
*
- *
- * @implNote The block of off-heap memory associated with the returned native memory segment is initialized to zero.
- * Moreover, a client is responsible to call the {@link MemorySegment#close()} on a native memory segment,
- * to make sure the backing off-heap memory block is deallocated accordingly. Failure to do so will result in off-heap memory leaks.
+ *
+ * The block of off-heap memory associated with the returned native memory segment is initialized to zero.
*
* @param layout the layout of the off-heap memory block backing the native memory segment.
* @return a new native memory segment.
* @throws IllegalArgumentException if the specified layout has illegal size or alignment constraint.
*/
static MemorySegment allocateNative(MemoryLayout layout) {
+ return allocateNative(layout, MemoryScope.createDefault());
+ }
+
+ /**
+ * Creates a new confined native memory segment that models a newly allocated block of off-heap memory with given layout
+ * and resource scope. A client is responsible make sure that the resource scope associated to the returned segment is closed
+ * when the segment is no longer in use. Failure to do so will result in off-heap memory leaks.
+ *
+ * The block of off-heap memory associated with the returned native memory segment is initialized to zero.
+ *
+ * @param layout the layout of the off-heap memory block backing the native memory segment.
+ * @param scope the segment scope.
+ * @return a new native memory segment.
+ * @throws IllegalArgumentException if the specified layout has illegal size or alignment constraint.
+ */
+ static MemorySegment allocateNative(MemoryLayout layout, ResourceScope scope) {
+ Objects.requireNonNull(scope);
Objects.requireNonNull(layout);
- return allocateNative(layout.byteSize(), layout.byteAlignment());
+ return allocateNative(layout.byteSize(), layout.byteAlignment(), scope);
}
/**
* Creates a new confined native memory segment that models a newly allocated block of off-heap memory with given size (in bytes).
+ * The returned segment is associated with a fresh {@link ResourceScope#ofDefault() default scope}. Resources associated with this
+ * segment will be automatically released when the returned segment (or any slices and views derived from it) is no longer in use.
*
- *
- * @implNote The block of off-heap memory associated with the returned native memory segment is initialized to zero.
- * Moreover, a client is responsible to call the {@link MemorySegment#close()} on a native memory segment,
- * to make sure the backing off-heap memory block is deallocated accordingly. Failure to do so will result in off-heap memory leaks.
+ *
+ * The block of off-heap memory associated with the returned native memory segment is initialized to zero.
*
* @param bytesSize the size (in bytes) of the off-heap memory block backing the native memory segment.
- * @return a new confined native memory segment.
- * @throws IllegalArgumentException if {@code bytesSize < 0}.
+ * @return a new native memory segment.
+ * @throws IllegalArgumentException if {@code bytesSize <= 0}.
*/
static MemorySegment allocateNative(long bytesSize) {
return allocateNative(bytesSize, 1);
}
/**
- * Creates a new confined mapped memory segment that models a memory-mapped region of a file from a given path.
+ * Creates a new confined native memory segment that models a newly allocated block of off-heap memory with given size (in bytes)
+ * and resource scope. A client is responsible make sure that the resource scope associated to the returned segment is closed
+ * when the segment is no longer in use. Failure to do so will result in off-heap memory leaks.
+ *
+ * The block of off-heap memory associated with the returned native memory segment is initialized to zero.
+ *
+ * @param bytesSize the size (in bytes) of the off-heap memory block backing the native memory segment.
+ * @param scope the segment scope.
+ * @return a new native memory segment.
+ * @throws IllegalArgumentException if {@code bytesSize <= 0}.
+ */
+ static MemorySegment allocateNative(long bytesSize, ResourceScope scope) {
+ return allocateNative(bytesSize, 1, scope);
+ }
+
+ /**
+ * Creates a new confined native memory segment that models a newly allocated block of off-heap memory with given size
+ * and alignment constraints (in bytes). The returned segment is associated with a fresh {@link ResourceScope#ofDefault() default scope}.
+ * Resources associated with this segment will be automatically released when the returned segment
+ * (or any slices and views derived from it) is no longer in use.
+ *
+ * The block of off-heap memory associated with the returned native memory segment is initialized to zero.
+ *
+ * @param bytesSize the size (in bytes) of the off-heap memory block backing the native memory segment.
+ * @param alignmentBytes the alignment constraint (in bytes) of the off-heap memory block backing the native memory segment.
+ * @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.
+ */
+ static MemorySegment allocateNative(long bytesSize, long alignmentBytes) {
+ return allocateNative(bytesSize, alignmentBytes, MemoryScope.createDefault());
+ }
+
+ /**
+ * Creates a new confined native memory segment that models a newly allocated block of off-heap memory with given size
+ * (in bytes), alignment constraint (in bytes) and resource scope. A client is responsible make sure that the resource
+ * scope associated to the returned segment is closed when the segment is no longer in use.
+ * Failure to do so will result in off-heap memory leaks.
+ *
+ * The block of off-heap memory associated with the returned native memory segment is initialized to zero.
+ *
+ * @param bytesSize the size (in bytes) of the off-heap memory block backing the native memory segment.
+ * @param alignmentBytes the alignment constraint (in bytes) of the off-heap memory block backing the native memory segment.
+ * @param scope the segment scope.
+ * @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.
+ */
+ static MemorySegment allocateNative(long bytesSize, long alignmentBytes, ResourceScope scope) {
+ Objects.requireNonNull(scope);
+ if (bytesSize <= 0) {
+ throw new IllegalArgumentException("Invalid allocation size : " + bytesSize);
+ }
+
+ if (alignmentBytes <= 0 ||
+ ((alignmentBytes & (alignmentBytes - 1)) != 0L)) {
+ throw new IllegalArgumentException("Invalid alignment constraint : " + alignmentBytes);
+ }
+
+ return NativeMemorySegmentImpl.makeNativeSegment(bytesSize, alignmentBytes, (MemoryScope) scope);
+ }
+
+ /**
+ * Creates a new mapped memory segment that models a memory-mapped region of a file from a given path.
+ * The returned segment is associated with a fresh {@link ResourceScope#ofDefault() default scope}. Resources associated with this
+ * segment will be automatically released when the returned segment (or any slices and views derived from it) is no longer in use.
+ *
+ *
+ * @implNote When obtaining a mapped segment from a newly created file, the initialization state of the contents of the block
+ * of mapped memory associated with the returned mapped memory segment is unspecified and should not be relied upon.
+ *
+ * @param path the path to the file to memory map.
+ * @param bytesOffset the offset (expressed in bytes) within the file at which the mapped segment is to start.
+ * @param bytesSize the size (in bytes) of the mapped memory backing the memory segment.
+ * @param mapMode a file mapping mode, see {@link FileChannel#map(FileChannel.MapMode, long, long)}; the chosen mapping mode
+ * might affect the behavior of the returned memory mapped segment (see {@link MappedMemorySegments#force(MemorySegment)}).
+ * @return a new mapped memory segment.
+ * @throws IllegalArgumentException if {@code bytesOffset < 0}.
+ * @throws IllegalArgumentException if {@code bytesSize < 0}.
+ * @throws UnsupportedOperationException if an unsupported map mode is specified, or if the {@code path} is associated
+ * with a provider that does not support creating file channels.
+ * @throws IOException if the specified path does not point to an existing file, or if some other I/O error occurs.
+ * @throws SecurityException If a security manager is installed and it denies an unspecified permission required by the implementation.
+ * In the case of the default provider, the {@link SecurityManager#checkRead(String)} method is invoked to check
+ * read access if the file is opened for reading. The {@link SecurityManager#checkWrite(String)} method is invoked to check
+ * write access if the file is opened for writing.
+ */
+ static MemorySegment mapFile(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode) throws IOException {
+ return mapFile(path, bytesOffset, bytesSize, mapMode, MemoryScope.createDefault());
+ }
+
+ /**
+ * Creates a new mapped memory segment that models a memory-mapped region of a file from a given path.
*
- * The segment will feature all access modes (see {@link #ALL_ACCESS}),
- * unless the given mapping mode is {@linkplain FileChannel.MapMode#READ_ONLY READ_ONLY}, in which case
- * the segment will not feature the {@link #WRITE} access mode, and its confinement thread is the current thread (see {@link Thread#currentThread()}).
+ * If the specified mapping mode is {@linkplain FileChannel.MapMode#READ_ONLY READ_ONLY}, the resulting segment
+ * will be read-only (see {@link #isReadOnly()}).
*
* The content of a mapped memory segment can change at any time, for example
* if the content of the corresponding region of the mapped file is changed by
@@ -875,6 +744,7 @@ static MemorySegment allocateNative(long bytesSize) {
* @param bytesSize the size (in bytes) of the mapped memory backing the memory segment.
* @param mapMode a file mapping mode, see {@link FileChannel#map(FileChannel.MapMode, long, long)}; the chosen mapping mode
* might affect the behavior of the returned memory mapped segment (see {@link MappedMemorySegments#force(MemorySegment)}).
+ * @param scope the segment scope.
* @return a new confined mapped memory segment.
* @throws IllegalArgumentException if {@code bytesOffset < 0}, {@code bytesSize < 0}, or if {@code path} is not associated
* with the default file system.
@@ -885,47 +755,18 @@ static MemorySegment allocateNative(long bytesSize) {
* read access if the file is opened for reading. The {@link SecurityManager#checkWrite(String)} method is invoked to check
* write access if the file is opened for writing.
*/
- static MemorySegment mapFile(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode) throws IOException {
- return MappedMemorySegmentImpl.makeMappedSegment(path, bytesOffset, bytesSize, mapMode);
- }
-
- /**
- * Creates a new confined native memory segment that models a newly allocated block of off-heap memory with given size and
- * alignment constraint (in bytes). The segment will feature all access modes
- * (see {@link #ALL_ACCESS}), and its confinement thread is the current thread (see {@link Thread#currentThread()}).
- *
- * @implNote The block of off-heap memory associated with the returned native memory segment is initialized to zero.
- * Moreover, a client is responsible to call the {@link MemorySegment#close()} on a native memory segment,
- * to make sure the backing off-heap memory block is deallocated accordingly. Failure to do so will result in off-heap memory leaks.
- *
- * @param bytesSize the size (in bytes) of the off-heap memory block backing the native memory segment.
- * @param alignmentBytes the alignment constraint (in bytes) of the off-heap memory block backing the native memory segment.
- * @return a new confined native memory segment.
- * @throws IllegalArgumentException if {@code bytesSize < 0}, {@code alignmentBytes < 0}, or if {@code alignmentBytes}
- * is not a power of 2.
- */
- static MemorySegment allocateNative(long bytesSize, long alignmentBytes) {
- if (bytesSize <= 0) {
- throw new IllegalArgumentException("Invalid allocation size : " + bytesSize);
- }
-
- if (alignmentBytes < 0 ||
- ((alignmentBytes & (alignmentBytes - 1)) != 0L)) {
- throw new IllegalArgumentException("Invalid alignment constraint : " + alignmentBytes);
- }
-
- return NativeMemorySegmentImpl.makeNativeSegment(bytesSize, alignmentBytes);
+ static MemorySegment mapFile(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode, ResourceScope scope) throws IOException {
+ Objects.requireNonNull(scope);
+ return MappedMemorySegmentImpl.makeMappedSegment(path, bytesOffset, bytesSize, mapMode, (MemoryScope) scope);
}
/**
- * Returns a shared native memory segment whose base address is {@link MemoryAddress#NULL} and whose size is {@link Long#MAX_VALUE}.
+ * Returns a native memory segment whose base address is {@link MemoryAddress#NULL} and whose size is {@link Long#MAX_VALUE}.
* This method can be very useful when dereferencing memory addresses obtained when interacting with native libraries.
- * The segment will feature the {@link #READ} and {@link #WRITE} access modes.
+ * The returned segment is associated with the global resource scope (see {@link ResourceScope#globalScope()}).
* Equivalent to (but likely more efficient than) the following code:
*
* This method is restricted. Restricted methods are unsafe, and, if used incorrectly, their use might crash
@@ -940,49 +781,4 @@ static MemorySegment ofNativeRestricted() {
Utils.checkRestrictedAccess("MemorySegment.ofNativeRestricted");
return NativeMemorySegmentImpl.EVERYTHING;
}
-
- // access mode masks
-
- /**
- * Read access mode; read operations are supported by a segment which supports this access mode.
- * @see MemorySegment#accessModes()
- * @see MemorySegment#withAccessModes(int)
- */
- int READ = 1;
-
- /**
- * Write access mode; write operations are supported by a segment which supports this access mode.
- * @see MemorySegment#accessModes()
- * @see MemorySegment#withAccessModes(int)
- */
- int WRITE = READ << 1;
-
- /**
- * Close access mode; calling {@link #close()} is supported by a segment which supports this access mode.
- * @see MemorySegment#accessModes()
- * @see MemorySegment#withAccessModes(int)
- */
- int CLOSE = WRITE << 1;
-
- /**
- * Share access mode; this segment support sharing with threads other than the owner thread (see {@link #share()}).
- * @see MemorySegment#accessModes()
- * @see MemorySegment#withAccessModes(int)
- */
- int SHARE = CLOSE << 1;
-
- /**
- * Handoff access mode; this segment support serial thread-confinement via thread ownership changes
- * (see {@link #handoff(NativeScope)} and {@link #handoff(Thread)}).
- * @see MemorySegment#accessModes()
- * @see MemorySegment#withAccessModes(int)
- */
- int HANDOFF = SHARE << 1;
-
- /**
- * Default access mode; this is a union of all the access modes supported by memory segments.
- * @see MemorySegment#accessModes()
- * @see MemorySegment#withAccessModes(int)
- */
- int ALL_ACCESS = READ | WRITE | CLOSE | SHARE | HANDOFF;
}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeScope.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeScope.java
deleted file mode 100644
index fb521345a15..00000000000
--- a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/NativeScope.java
+++ /dev/null
@@ -1,472 +0,0 @@
-/*
- * Copyright (c) 2020, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- *
- */
-
-package jdk.incubator.foreign;
-
-import jdk.internal.foreign.AbstractMemorySegmentImpl;
-import jdk.internal.foreign.AbstractNativeScope;
-import jdk.internal.foreign.Utils;
-
-import java.lang.invoke.VarHandle;
-import java.lang.reflect.Array;
-import java.nio.ByteOrder;
-import java.util.Objects;
-import java.util.OptionalLong;
-import java.util.function.Function;
-import java.util.stream.Stream;
-
-/**
- * A native scope is an abstraction which provides shared temporal bounds for one or more allocations, backed
- * by off-heap memory. Native scopes can be either bounded or unbounded, depending on whether the size
- * of the native scope is known statically. If an application knows before-hand how much memory it needs to allocate,
- * then using a bounded native scope will typically provide better performance than independently allocating the memory
- * for each value (e.g. using {@link MemorySegment#allocateNative(long)}), or using an unbounded native scope.
- * For this reason, using a bounded native scope is recommended in cases where programs might need to emulate native stack allocation.
- *
- * Allocation scopes are thread-confined (see {@link #ownerThread()}; as such, the resulting {@link MemorySegment} instances
- * returned by the native scope will be backed by memory segments confined by the same owner thread as the native scope's
- * owner thread.
- *
- * To allow for more usability, it is possible for a native scope to reclaim ownership of an existing memory segment
- * (see {@link MemorySegment#handoff(NativeScope)}). This might be useful to allow one or more segments which were independently
- * created to share the same life-cycle as a given native scope - which in turns enables a client to group all memory
- * allocation and usage under a single try-with-resources block.
- *
- *
Unless otherwise specified, passing a {@code null} argument, or an array argument containing one or more {@code null}
- * elements to a method in this class causes a {@link NullPointerException NullPointerException} to be thrown.
- *
- * @apiNote In the future, if the Java language permits, {@link NativeScope}
- * may become a {@code sealed} interface, which would prohibit subclassing except by
- * explicitly permitted types.
- */
-public interface NativeScope extends AutoCloseable {
-
- /**
- * If this native scope is bounded, returns the size, in bytes, of this native scope.
- * @return the size, in bytes, of this native scope (if available).
- */
- OptionalLong byteSize();
-
- /**
- * The thread owning this native scope.
- * @return the thread owning this native scope.
- */
- Thread ownerThread();
-
- /**
- * Returns the number of allocated bytes in this native scope.
- * @return the number of allocated bytes in this native scope.
- */
- long allocatedBytes();
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given byte value.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a byte value.
- */
- default MemorySegment allocate(ValueLayout layout, byte value) {
- Objects.requireNonNull(layout);
- VarHandle handle = layout.varHandle(byte.class);
- MemorySegment addr = allocate(layout);
- handle.set(addr, value);
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given char value.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a char value.
- */
- default MemorySegment allocate(ValueLayout layout, char value) {
- Objects.requireNonNull(layout);
- VarHandle handle = layout.varHandle(char.class);
- MemorySegment addr = allocate(layout);
- handle.set(addr, value);
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given short value.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a short value.
- */
- default MemorySegment allocate(ValueLayout layout, short value) {
- Objects.requireNonNull(layout);
- VarHandle handle = layout.varHandle(short.class);
- MemorySegment addr = allocate(layout);
- handle.set(addr, value);
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given int value.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a int value.
- */
- default MemorySegment allocate(ValueLayout layout, int value) {
- Objects.requireNonNull(layout);
- VarHandle handle = layout.varHandle(int.class);
- MemorySegment addr = allocate(layout);
- handle.set(addr, value);
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given float value.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a float value.
- */
- default MemorySegment allocate(ValueLayout layout, float value) {
- Objects.requireNonNull(layout);
- VarHandle handle = layout.varHandle(float.class);
- MemorySegment addr = allocate(layout);
- handle.set(addr, value);
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given long value.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a long value.
- */
- default MemorySegment allocate(ValueLayout layout, long value) {
- Objects.requireNonNull(layout);
- VarHandle handle = layout.varHandle(long.class);
- MemorySegment addr = allocate(layout);
- handle.set(addr, value);
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given double value.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a double value.
- */
- default MemorySegment allocate(ValueLayout layout, double value) {
- Objects.requireNonNull(layout);
- VarHandle handle = layout.varHandle(double.class);
- MemorySegment addr = allocate(layout);
- handle.set(addr, value);
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given address value
- * (expressed as an {@link Addressable} instance).
- * The address value might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
- * The segment returned by this method cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @param value the value to be set on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- * @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
- */
- default MemorySegment allocate(ValueLayout layout, Addressable value) {
- Objects.requireNonNull(value);
- Objects.requireNonNull(layout);
- if (MemoryLayouts.ADDRESS.byteSize() != layout.byteSize()) {
- throw new IllegalArgumentException("Layout size mismatch - " + layout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
- }
- switch ((int)layout.byteSize()) {
- case 4: return allocate(layout, (int)value.address().toRawLongValue());
- case 8: return allocate(layout, value.address().toRawLongValue());
- default: throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
- }
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given byte array.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a byte value.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, byte[] array) {
- return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given short array.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a short value.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, short[] array) {
- return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given char array.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a char value.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, char[] array) {
- return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given int array.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a int value.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, int[] array) {
- return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given float array.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a float value.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, float[] array) {
- return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given long array.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a long value.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, long[] array) {
- return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given double array.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a double value.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, double[] array) {
- return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout and initialize it with given address array.
- * The address value of each array element might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover, the returned
- * segment must conform to the layout alignment constraints.
- * @param elementLayout the element layout of the array to be allocated.
- * @param array the array to be copied on the newly allocated memory block.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * array.length)}.
- * @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
- */
- default MemorySegment allocateArray(ValueLayout elementLayout, Addressable[] array) {
- Objects.requireNonNull(elementLayout);
- Objects.requireNonNull(array);
- Stream.of(array).forEach(Objects::requireNonNull);
- if (MemoryLayouts.ADDRESS.byteSize() != elementLayout.byteSize()) {
- throw new IllegalArgumentException("Layout size mismatch - " + elementLayout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
- }
- switch ((int)elementLayout.byteSize()) {
- case 4: return copyArrayWithSwapIfNeeded(Stream.of(array)
- .mapToInt(a -> (int)a.address().toRawLongValue()).toArray(),
- elementLayout, MemorySegment::ofArray);
- case 8: return copyArrayWithSwapIfNeeded(Stream.of(array)
- .mapToLong(a -> a.address().toRawLongValue()).toArray(),
- elementLayout, MemorySegment::ofArray);
- default: throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
- }
- }
-
- private MemorySegment copyArrayWithSwapIfNeeded(Z array, ValueLayout elementLayout,
- Function heapSegmentFactory) {
- Objects.requireNonNull(array);
- Objects.requireNonNull(elementLayout);
- Utils.checkPrimitiveCarrierCompat(array.getClass().componentType(), elementLayout);
- MemorySegment addr = allocate(MemoryLayout.ofSequence(Array.getLength(array), elementLayout));
- if (elementLayout.byteSize() == 1 || (elementLayout.order() == ByteOrder.nativeOrder())) {
- addr.copyFrom(heapSegmentFactory.apply(array));
- } else {
- ((AbstractMemorySegmentImpl)addr).copyFromSwap(heapSegmentFactory.apply(array), elementLayout.byteSize());
- }
- return addr;
- }
-
- /**
- * Allocate a block of memory in this native scope with given layout. The segment returned by this method is
- * associated with a segment which cannot be closed. Moreover, the returned segment must conform to the layout alignment constraints.
- * @param layout the layout of the block of memory to be allocated.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < layout.byteSize()}.
- */
- default MemorySegment allocate(MemoryLayout layout) {
- Objects.requireNonNull(layout);
- return allocate(layout.byteSize(), layout.byteAlignment());
- }
-
- /**
- * Allocate a block of memory corresponding to an array with given element layout and size.
- * The segment returned by this method is associated with a segment which cannot be closed.
- * Moreover, the returned segment must conform to the layout alignment constraints. This is equivalent to the
- * following code:
- *
- * @param elementLayout the array element layout.
- * @param count the array element count.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if this is a
- * bounded allocation scope, and {@code byteSize().getAsLong() - allocatedBytes() < (elementLayout.byteSize() * count)}.
- */
- default MemorySegment allocateArray(MemoryLayout elementLayout, long count) {
- Objects.requireNonNull(elementLayout);
- return allocate(MemoryLayout.ofSequence(count, elementLayout));
- }
-
- /**
- * Allocate a block of memory in this native scope with given size. The segment returned by this method is
- * associated with a segment which cannot be closed. Moreover, the returned segment must be aligned to {@code size}.
- * @param bytesSize the size (in bytes) of the block of memory to be allocated.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
- * {@code limit() - size() < bytesSize}.
- */
- default MemorySegment allocate(long bytesSize) {
- return allocate(bytesSize, bytesSize);
- }
-
- /**
- * Allocate a block of memory in this native scope with given size and alignment constraint.
- * The segment returned by this method is associated with a segment which cannot be closed. Moreover,
- * the returned segment must be aligned to {@code alignment}.
- * @param bytesSize the size (in bytes) of the block of memory to be allocated.
- * @param bytesAlignment the alignment (in bytes) of the block of memory to be allocated.
- * @return a segment for the newly allocated memory block.
- * @throws OutOfMemoryError if there is not enough space left in this native scope, that is, if
- * {@code limit() - size() < bytesSize}.
- */
- MemorySegment allocate(long bytesSize, long bytesAlignment);
-
- /**
- * Close this native scope; calling this method will render any segment obtained through this native scope
- * unusable and might release any backing memory resources associated with this native scope.
- */
- @Override
- void close();
-
- /**
- * Creates a new bounded native scope, backed by off-heap memory.
- * @param size the size of the native scope.
- * @return a new bounded native scope, with given size (in bytes).
- */
- static NativeScope boundedScope(long size) {
- return new AbstractNativeScope.BoundedNativeScope(size);
- }
-
- /**
- * Creates a new unbounded native scope, backed by off-heap memory.
- * @return a new unbounded native scope.
- */
- static NativeScope unboundedScope() {
- return new AbstractNativeScope.UnboundedNativeScope();
- }
-}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/ResourceScope.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/ResourceScope.java
new file mode 100644
index 00000000000..0a3399c3de9
--- /dev/null
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/ResourceScope.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (c) 2021, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.foreign;
+
+import jdk.internal.foreign.MemoryScope;
+
+import java.lang.invoke.MethodHandle;
+import java.lang.ref.Cleaner;
+import java.nio.channels.FileChannel;
+import java.nio.file.Path;
+import java.util.Objects;
+import java.util.Spliterator;
+
+/**
+ * A resource scope manages the lifecycle of one or more resources. Resources (e.g. {@link MemorySegment}) associated
+ * with a resource scope can only be accessed while the resource scope is alive (see {@link #isAlive()}),
+ * and by the thread associated with the resource scope (if any).
+ *
+ *
Explicit closure
+ *
+ * Resource scopes created using one of the factories in this class can be closed explicitly (see {@link ResourceScope#close()}).
+ * When a resource scope is closed, it is no longer alive (see {@link #isAlive()}, and subsequent operation on
+ * resources derived from that scope (e.g. attempting to access a {@link MemorySegment} instance) will fail with {@link IllegalStateException}.
+ *
+ * Closing a resource scope will cause all the cleanup actions associated with that scope (see {@link #addOnClose(Runnable)}) to be called.
+ * Moreover, closing a resource scope might trigger the releasing of the underlying memory resources associated with said scope; for instance:
+ *
+ *
closing the scope associated with a native memory segment results in freeing the native memory associated with it
+ * (see {@link MemorySegment#allocateNative(long, ResourceScope)}, or {@link SegmentAllocator#arenaUnbounded(ResourceScope)})
+ *
closing the scope associated with a mapped memory segment results in the backing memory-mapped file to be unmapped
+ * (see {@link MemorySegment#mapFile(Path, long, long, FileChannel.MapMode, ResourceScope)})
+ *
closing the scope associated with an upcall stub results in releasing the stub
+ * (see {@link CLinker#upcallStub(MethodHandle, FunctionDescriptor, ResourceScope)}
+ *
+ *
+ *
Implicit closure
+ *
+ * Resource scopes can be associated with a {@link Cleaner} instance (see {@link #ofConfined(Cleaner)}) - we call these
+ * resource scopes managed resource scopes. A managed resource scope is closed automatically once the scope instance
+ * becomes unreachable.
+ *
+ * Managed resource scopes can still be closed explicitly (see {@link #close()}); this can be useful to allow for predictable,
+ * deterministic resource deallocation, while still prevent accidental native memory leaks. In case a managed resource
+ * scope is closed explicitly, no further action will be taken when the scope becomes unreachable; that is, cleanup actions
+ * (see {@link #addOnClose(Runnable)}) associated with a resource scope, whether managed or not, are called exactly once.
+ *
+ *
+ *
+ * Resource scopes can be further divided into two categories: thread-confined resource scopes, and shared
+ * resource scopes.
+ *
+ * Confined resource scopes (see {@link #ofConfined()}), support strong thread-confinement guarantees. Upon creation,
+ * they are assigned an owner thread, typically the thread which initiated the creation operation (see {@link #ownerThread()}).
+ * After creating a confined resource scope, only the owner thread will be allowed to directly manipulate the resources
+ * associated with this resource scope. Any attempt to perform resource access from a thread other than the
+ * owner thread will result in a runtime failure.
+ *
+ * Shared resource scopes (see {@link #ofShared()}), on the other hand, have no owner thread; as such resources associated
+ * with this shared resource scopes can be accessed by multiple threads. This might be useful when multiple threads need
+ * to access the same resource concurrently (e.g. in the case of parallel processing). For instance, a client
+ * might obtain a {@link Spliterator} from a shared segment, which can then be used to slice the segment and allow multiple
+ * threads to work in parallel on disjoint segment slices. The following code can be used to sum all int values in a memory segment in parallel:
+ *
+ *
+ * When using shared resource scopes, clients should make sure that no other thread is accessing the segment while
+ * the segment is being closed. If one or more threads attempts to access a segment concurrently while the
+ * segment is being closed, an exception might occur on both the accessing and the closing threads. Clients should
+ * refrain from attempting to close a shared resource scope repeatedly (e.g. keep calling {@link #close()} until no exception is thrown);
+ * such exceptions should instead be seen as an indication that the client code is lacking appropriate synchronization between the threads
+ * accessing/closing the resources associated with the shared resource scope.
+ *
+ *
Scope handles
+ *
+ * Resource scopes can be made non-closeable by acquiring one or more resource scope handles (see
+ * {@link #acquire()}. A resource scope handle can be used to make sure that its corresponding scope cannot be closed
+ * (either explicitly, or implicitly) for a certain period of time - e.g. when one or more resources associated with
+ * the parent scope need to be accessed. A resource scope can be acquired multiple times; the resource scope can only be
+ * closed after all the handles acquired against that scope have been closed (see {@link Handle#close()}).
+ * This can be useful when clients need to perform a critical operation on a memory segment, during which they have
+ * to ensure that the segment will not be released; this can be done as follows:
+ *
+ *
+ *
+ * @apiNote In the future, if the Java language permits, {@link ResourceScope}
+ * may become a {@code sealed} interface, which would prohibit subclassing except by other explicitly permitted subtypes.
+ *
+ * @implSpec
+ * Implementations of this interface are immutable, thread-safe and value-based.
+ */
+public interface ResourceScope extends AutoCloseable {
+ /**
+ * Is this resource scope alive?
+ * @return true, if this resource scope is alive.
+ * @see ResourceScope#close()
+ */
+ boolean isAlive();
+
+ /**
+ * The thread owning this resource scope.
+ * @return the thread owning this resource scope, or {@code null} if this resource scope is shared.
+ */
+ Thread ownerThread();
+
+ /**
+ * Closes this resource scope. As a side-effect, if this operation completes without exceptions, this scope will be marked
+ * as not alive, and subsequent operations on resources associated with this scope will fail with {@link IllegalStateException}.
+ * Additionally, upon successful closure, all native resources associated with this resource scope will be released.
+ *
+ * @apiNote This operation is not idempotent; that is, closing an already closed resource scope always results in an
+ * exception being thrown. This reflects a deliberate design choice: resource scope state transitions should be
+ * manifest in the client code; a failure in any of these transitions reveals a bug in the underlying application
+ * logic.
+ *
+ * @throws IllegalStateException if one of the following condition is met:
+ *
+ *
this resource scope is not alive
+ *
this resource scope is confined, and this method is called from a thread other than the thread owning this resource scope
+ *
this resource scope is shared and a resource associated with this scope is accessed while this method is called
+ *
one or more handles (see {@link #acquire()}) associated with this resource scope have not been closed
+ *
+ * @throws UnsupportedOperationException if the {@code close} operation is not supported by this resource scope. This
+ * is the case for the resource scope returned by {@link #globalScope()}, or the resource scopes associated to
+ * memory segments created using certain factories (such as {@link MemorySegment#allocateNative(long)}).
+ */
+ void close();
+
+ /**
+ * Add a custom cleanup action which will be executed when the resource scope is closed.
+ * @param runnable the custom cleanup action to be associated with this scope.
+ * @throws IllegalStateException if this scope has already been closed.
+ */
+ void addOnClose(Runnable runnable);
+
+ /**
+ * Make this resource scope non-closeable by acquiring a new resource scope handle. This scope cannot be closed unless all its
+ * acquired handles have been closed first.
+ * @return a resource scope handle.
+ */
+ Handle acquire();
+
+ /**
+ * An abstraction modelling resource scope handle. A resource scope handle is typically acquired by clients (see
+ * {@link #acquire()} in order to prevent the resource scope from being closed while executing a certain operation.
+ * A resource scope handle features a method (see {@link #close()}) which can be used by clients to release the handle.
+ */
+ interface Handle extends AutoCloseable {
+
+ /**
+ * Release this handle on the resource scope associated with this instance. This method is idempotent,
+ * that is, closing an already closed handle has no effect.
+ */
+ @Override
+ void close();
+ }
+
+ /**
+ * Create a new confined scope. The resulting scope is closeable, and is not managed by a {@link Cleaner}.
+ * @return a new confined scope.
+ */
+ static ResourceScope ofConfined() {
+ return ofConfined(null, null);
+ }
+
+ /**
+ * Create a new confined scope managed by a {@link Cleaner}.
+ * @param cleaner the cleaner to be associated with the returned scope.
+ * @return a new confined scope, managed by {@code cleaner}.
+ * @throws NullPointerException if {@code cleaner == null}.
+ */
+ static ResourceScope ofConfined(Cleaner cleaner) {
+ Objects.requireNonNull(cleaner);
+ return ofConfined(null, cleaner);
+ }
+
+ /**
+ * Create a new confined scope. The resulting scope might be managed by a {@link Cleaner} (where provided).
+ * An optional attachment can be associated with the resulting scope.
+ * @param attachment an attachment object which is kept alive by the returned resource scope (can be {@code null}).
+ * @param cleaner the cleaner to be associated with the returned scope. Can be {@code null}.
+ * @return a new confined scope, managed by {@code cleaner} (where provided).
+ */
+ static ResourceScope ofConfined(Object attachment, Cleaner cleaner) {
+ return MemoryScope.createConfined(attachment, cleaner);
+ }
+
+ /**
+ * Create a new shared scope. The resulting scope is closeable, and is not managed by a {@link Cleaner}.
+ * @return a new shared scope.
+ */
+ static ResourceScope ofShared() {
+ return ofShared(null, null);
+ }
+
+ /**
+ * Create a new shared scope managed by a {@link Cleaner}.
+ * @param cleaner the cleaner to be associated with the returned scope.
+ * @return a new shared scope, managed by {@code cleaner}.
+ * @throws NullPointerException if {@code cleaner == null}.
+ */
+ static ResourceScope ofShared(Cleaner cleaner) {
+ Objects.requireNonNull(cleaner);
+ return ofShared(null, cleaner);
+ }
+
+ /**
+ * Create a new shared scope. The resulting scope might be managed by a {@link Cleaner} (where provided).
+ * An optional attachment can be associated with the resulting scope.
+ * @param attachment an attachment object which is kept alive by the returned resource scope (can be {@code null}).
+ * @param cleaner the cleaner to be associated with the returned scope. Can be {@code null}.
+ * @return a new shared scope, managed by {@code cleaner} (where provided).
+ */
+ static ResourceScope ofShared(Object attachment, Cleaner cleaner) {
+ return MemoryScope.createShared(attachment, cleaner);
+ }
+
+ /**
+ * Create a new default scope, a shared, non-closeable scope which only features implicit closure.
+ * This resource scope is used as a valid default where no resource scope is provided by the user. For instance, this code:
+ *
+ *
+ * @return a new default scope.
+ */
+ static ResourceScope ofDefault() {
+ return MemoryScope.createDefault();
+ }
+
+ /**
+ * A non-closeable, shared, global scope which is assumed to be always alive.
+ * @return the global scope.
+ */
+ static ResourceScope globalScope() {
+ return MemoryScope.GLOBAL;
+ }
+}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/SegmentAllocator.java b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/SegmentAllocator.java
new file mode 100644
index 00000000000..36a54797604
--- /dev/null
+++ b/src/jdk.incubator.foreign/share/classes/jdk/incubator/foreign/SegmentAllocator.java
@@ -0,0 +1,472 @@
+/*
+ * Copyright (c) 2021, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.incubator.foreign;
+
+import jdk.internal.foreign.ArenaAllocator;
+import jdk.internal.foreign.AbstractMemorySegmentImpl;
+import jdk.internal.foreign.MemoryScope;
+import jdk.internal.foreign.NativeMemorySegmentImpl;
+import jdk.internal.foreign.Utils;
+
+import java.lang.invoke.VarHandle;
+import java.lang.reflect.Array;
+import java.nio.ByteOrder;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+/**
+ * This interface models a memory allocator. Clients implementing this interface
+ * must implement the {@link #allocate(long, long)} method. This interface defines several default methods
+ * which can be useful to create segments from several kinds of Java values such as primitives and arrays.
+ * This interface can be seen as a thin wrapper around the basic capabilities for creating native segments
+ * (e.g. {@link MemorySegment#allocateNative(long, long)}); since {@link SegmentAllocator} is a functional interface,
+ * clients can easily obtain a native allocator by using either a lambda expression or a method reference.
+ *
+ * This interface provides a factory, namely {@link SegmentAllocator#scoped(ResourceScope)} which can be used to obtain
+ * a scoped allocator, that is, an allocator which creates segment bound by a given scope. This can be useful
+ * when working inside a try-with-resources construct:
+ *
+ *
+ *
+ * In addition, this interface also defines factories for commonly used allocators; for instance, {@link #malloc(Supplier)} returns a
+ * native allocator which returns segments backed by separate resources scopes, while {@link #arenaUnbounded(ResourceScope)}
+ * and {@link #arenaBounded(long, ResourceScope)} are arena-style native allocators. Finally {@link #prefix(MemorySegment)}
+ * returns an allocator which wraps a segment (either on-heap or off-heap) and recycles its content upon each new allocation request.
+ *
+ * Unless otherwise specified, whenever an allocator is associated with a shared resource scope, then allocation is
+ * thread-safe and may be performed concurrently; conversely, if the allocator is associated with a confined resource scope
+ * then allocation is confined to the owning thread of the allocator's resource scope.
+ */
+@FunctionalInterface
+public interface SegmentAllocator {
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given byte value.
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a byte value.
+ */
+ default MemorySegment allocate(ValueLayout layout, byte value) {
+ Objects.requireNonNull(layout);
+ VarHandle handle = layout.varHandle(byte.class);
+ MemorySegment addr = allocate(layout);
+ handle.set(addr, value);
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given char value.
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a char value.
+ */
+ default MemorySegment allocate(ValueLayout layout, char value) {
+ Objects.requireNonNull(layout);
+ VarHandle handle = layout.varHandle(char.class);
+ MemorySegment addr = allocate(layout);
+ handle.set(addr, value);
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given short value.
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a short value.
+ */
+ default MemorySegment allocate(ValueLayout layout, short value) {
+ Objects.requireNonNull(layout);
+ VarHandle handle = layout.varHandle(short.class);
+ MemorySegment addr = allocate(layout);
+ handle.set(addr, value);
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given int value.
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a int value.
+ */
+ default MemorySegment allocate(ValueLayout layout, int value) {
+ Objects.requireNonNull(layout);
+ VarHandle handle = layout.varHandle(int.class);
+ MemorySegment addr = allocate(layout);
+ handle.set(addr, value);
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given float value.
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a float value.
+ */
+ default MemorySegment allocate(ValueLayout layout, float value) {
+ Objects.requireNonNull(layout);
+ VarHandle handle = layout.varHandle(float.class);
+ MemorySegment addr = allocate(layout);
+ handle.set(addr, value);
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given long value.
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a long value.
+ */
+ default MemorySegment allocate(ValueLayout layout, long value) {
+ Objects.requireNonNull(layout);
+ VarHandle handle = layout.varHandle(long.class);
+ MemorySegment addr = allocate(layout);
+ handle.set(addr, value);
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given double value.
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize()} does not conform to the size of a double value.
+ */
+ default MemorySegment allocate(ValueLayout layout, double value) {
+ Objects.requireNonNull(layout);
+ VarHandle handle = layout.varHandle(double.class);
+ MemorySegment addr = allocate(layout);
+ handle.set(addr, value);
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given address value
+ * (expressed as an {@link Addressable} instance).
+ * The address value might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
+ * @param layout the layout of the block of memory to be allocated.
+ * @param value the value to be set on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
+ */
+ default MemorySegment allocate(ValueLayout layout, Addressable value) {
+ Objects.requireNonNull(value);
+ Objects.requireNonNull(layout);
+ if (MemoryLayouts.ADDRESS.byteSize() != layout.byteSize()) {
+ throw new IllegalArgumentException("Layout size mismatch - " + layout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
+ }
+ return switch ((int)layout.byteSize()) {
+ case 4 -> allocate(layout, (int)value.address().toRawLongValue());
+ case 8 -> allocate(layout, value.address().toRawLongValue());
+ default -> throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
+ };
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given byte array.
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a byte value.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, byte[] array) {
+ return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given short array.
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a short value.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, short[] array) {
+ return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given char array.
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a char value.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, char[] array) {
+ return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given int array.
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a int value.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, int[] array) {
+ return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given float array.
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a float value.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, float[] array) {
+ return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given long array.
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a long value.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, long[] array) {
+ return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given double array.
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code elementLayout.byteSize()} does not conform to the size of a double value.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, double[] array) {
+ return copyArrayWithSwapIfNeeded(array, elementLayout, MemorySegment::ofArray);
+ }
+
+ /**
+ * Allocate a block of memory with given layout and initialize it with given address array.
+ * The address value of each array element might be narrowed according to the platform address size (see {@link MemoryLayouts#ADDRESS}).
+ * @param elementLayout the element layout of the array to be allocated.
+ * @param array the array to be copied on the newly allocated memory block.
+ * @return a segment for the newly allocated memory block.
+ * @throws IllegalArgumentException if {@code layout.byteSize() != MemoryLayouts.ADDRESS.byteSize()}.
+ */
+ default MemorySegment allocateArray(ValueLayout elementLayout, Addressable[] array) {
+ Objects.requireNonNull(elementLayout);
+ Objects.requireNonNull(array);
+ Stream.of(array).forEach(Objects::requireNonNull);
+ if (MemoryLayouts.ADDRESS.byteSize() != elementLayout.byteSize()) {
+ throw new IllegalArgumentException("Layout size mismatch - " + elementLayout.byteSize() + " != " + MemoryLayouts.ADDRESS.byteSize());
+ }
+ return switch ((int)elementLayout.byteSize()) {
+ case 4 -> copyArrayWithSwapIfNeeded(Stream.of(array)
+ .mapToInt(a -> (int)a.address().toRawLongValue()).toArray(),
+ elementLayout, MemorySegment::ofArray);
+ case 8 -> copyArrayWithSwapIfNeeded(Stream.of(array)
+ .mapToLong(a -> a.address().toRawLongValue()).toArray(),
+ elementLayout, MemorySegment::ofArray);
+ default -> throw new UnsupportedOperationException("Unsupported pointer size"); // should not get here
+ };
+ }
+
+ private MemorySegment copyArrayWithSwapIfNeeded(Z array, ValueLayout elementLayout,
+ Function heapSegmentFactory) {
+ Objects.requireNonNull(array);
+ Objects.requireNonNull(elementLayout);
+ Utils.checkPrimitiveCarrierCompat(array.getClass().componentType(), elementLayout);
+ MemorySegment addr = allocate(MemoryLayout.ofSequence(Array.getLength(array), elementLayout));
+ if (elementLayout.byteSize() == 1 || (elementLayout.order() == ByteOrder.nativeOrder())) {
+ addr.copyFrom(heapSegmentFactory.apply(array));
+ } else {
+ ((AbstractMemorySegmentImpl)addr).copyFromSwap(heapSegmentFactory.apply(array), elementLayout.byteSize());
+ }
+ return addr;
+ }
+
+ /**
+ * Allocate a block of memory with given layout.
+ * @param layout the layout of the block of memory to be allocated.
+ * @return a segment for the newly allocated memory block.
+ */
+ default MemorySegment allocate(MemoryLayout layout) {
+ Objects.requireNonNull(layout);
+ return allocate(layout.byteSize(), layout.byteAlignment());
+ }
+
+ /**
+ * Allocate a block of memory corresponding to an array with given element layout and size.
+ * @param elementLayout the array element layout.
+ * @param count the array element count.
+ * @return a segment for the newly allocated memory block.
+ */
+ default MemorySegment allocateArray(MemoryLayout elementLayout, long count) {
+ Objects.requireNonNull(elementLayout);
+ return allocate(MemoryLayout.ofSequence(count, elementLayout));
+ }
+
+ /**
+ * Allocate a block of memory with given size, with default alignment (1-byte aligned).
+ * @param bytesSize the size (in bytes) of the block of memory to be allocated.
+ * @return a segment for the newly allocated memory block.
+ */
+ default MemorySegment allocate(long bytesSize) {
+ return allocate(bytesSize, 1);
+ }
+
+ /**
+ * Allocate a block of memory with given size and alignment constraint.
+ * @param bytesSize the size (in bytes) of the block of memory to be allocated.
+ * @param bytesAlignment the alignment (in bytes) of the block of memory to be allocated.
+ * @return a segment for the newly allocated memory block.
+ */
+ MemorySegment allocate(long bytesSize, long bytesAlignment);
+
+ /**
+ * Returns a native allocator which allocates memory segments using the {@code malloc} allocation primitive,
+ * each backed by a resource scope that is obtained using the provided supplier. For instance, to create an allocator which
+ * returns independent, confined segments, clients can use the following code:
+ *
+ *
+ *
+ * @param scopeFactory the factory used to generate the resource scope attached to each newly allocated segment.
+ * @return a native allocator using the {@code malloc} allocation primitive.
+ */
+ static SegmentAllocator malloc(Supplier scopeFactory) {
+ Objects.requireNonNull(scopeFactory);
+ return (size, align) -> MemorySegment.allocateNative(size, align, scopeFactory.get());
+ }
+
+ /**
+ * Returns a native arena-based allocator which allocates a single memory segment, of given size (using malloc),
+ * and then responds to allocation request by returning different slices of that same segment
+ * (until no further allocation is possible).
+ * This can be useful when clients want to perform multiple allocation requests while avoiding the cost associated
+ * with allocating a new off-heap memory region upon each allocation request.
+ *
+ * The returned allocator might throw an {@link OutOfMemoryError} if an incoming allocation request exceeds
+ * the allocator capacity.
+ *
+ * @param size the size (in bytes) of the allocation arena.
+ * @param scope the scope associated with the segments returned by this allocator.
+ * @return a new bounded arena-based allocator
+ */
+ static SegmentAllocator arenaBounded(long size, ResourceScope scope) {
+ Objects.requireNonNull(scope);
+ return scope.ownerThread() == null ?
+ new ArenaAllocator.BoundedSharedArenaAllocator(scope, size) :
+ new ArenaAllocator.BoundedArenaAllocator(scope, size);
+ }
+
+ /**
+ * Returns a native unbounded arena-based allocator.
+ *
+ * The returned allocator allocates a memory segment {@code S} of a certain fixed size (using malloc) and then
+ * responds to allocation requests in one of the following ways:
+ *
+ *
if the size of the allocation requests is smaller than the size of {@code S}, and {@code S} has a free
+ * slice {@code S'} which fits that allocation request, return that {@code S'}.
+ *
if the size of the allocation requests is smaller than the size of {@code S}, and {@code S} has no free
+ * slices which fits that allocation request, allocate a new segment {@code S'} (using malloc), which has same size as {@code S}
+ * and set {@code S = S'}; the allocator then tries to respond to the same allocation request again.
+ *
if the size of the allocation requests is bigger than the size of {@code S}, allocate a new segment {@code S'}
+ * (using malloc), which has a sufficient size to satisfy the allocation request, and return {@code S'}.
+ *
+ *
+ * This segment allocator can be useful when clients want to perform multiple allocation requests while avoiding the
+ * cost associated with allocating a new off-heap memory region upon each allocation request.
+ *
+ * The returned allocator might throw an {@link OutOfMemoryError} if an incoming allocation request exceeds
+ * the system capacity.
+ *
+ * @param scope the scope associated with the segments returned by this allocator.
+ * @return a new unbounded arena-based allocator
+ */
+ static SegmentAllocator arenaUnbounded(ResourceScope scope) {
+ Objects.requireNonNull(scope);
+ return scope.ownerThread() == null ?
+ new ArenaAllocator.UnboundedSharedArenaAllocator(scope) :
+ new ArenaAllocator(scope);
+ }
+
+ /**
+ * Returns a segment allocator which responds to allocation requests by recycling a single segment; that is,
+ * each new allocation request will return a new slice starting at the segment offset {@code 0} (alignment
+ * constraints are ignored by this allocator). This can be useful to limit allocation requests in case a client
+ * knows that they have fully processed the contents of the allocated segment before the subsequent allocation request
+ * takes place.
+ *
+ * While the allocator returned by this method is thread-safe, concurrent access on the same recycling
+ * allocator might cause a thread to overwrite contents written to the underlying segment by a different thread.
+ *
+ * @param segment the memory segment to be recycled by the returned allocator.
+ * @return an allocator which recycles an existing segment upon each new allocation request.
+ */
+ static SegmentAllocator prefix(MemorySegment segment) {
+ Objects.requireNonNull(segment);
+ return (size, align) -> segment.asSlice(0, size);
+ }
+
+ /**
+ * Returns a native allocator which responds to allocation requests by allocating new segments
+ * bound by the given resource scope, using the {@link MemorySegment#allocateNative(long, long, ResourceScope)}
+ * factory. This code is equivalent (but likely more efficient) to the following:
+ *
+ *
+ * @param scope the resource scope associated to the segments created by the returned allocator.
+ * @return an allocator which allocates new memory segment bound by the provided resource scope.
+ */
+ static SegmentAllocator scoped(ResourceScope scope) {
+ Objects.requireNonNull(scope);
+ return (MemoryScope)scope;
+ }
+
+ /**
+ * Returns the default native allocator which creates segments using the default
+ * {@link MemorySegment#allocateNative(long, long) native segment factory}. This code is equivalent
+ * (but likely more efficient) to the following:
+ *
*
* Here create a native memory segment, that is, a memory segment backed by
* off-heap memory; the size of the segment is 40 bytes, enough to store 10 values of the primitive type {@code int}.
- * The segment is created inside a try-with-resources construct: this idiom ensures that all the memory resources
- * associated with the segment will be released at the end of the block, according to the semantics described in
- * Section {@jls 14.20.3} of The Java Language Specification. Inside the try-with-resources block, we initialize
- * the contents of the memory segment using the
+ * Inside a loop, we then initialize the contents of the memory segment using the
* {@link jdk.incubator.foreign.MemoryAccess#setIntAtIndex(jdk.incubator.foreign.MemorySegment, long, int)} helper method;
* more specifically, if we view the memory segment as a set of 10 adjacent slots,
* {@code s[i]}, where {@code 0 <= i < 10}, where the size of each slot is exactly 4 bytes, the initialization logic above will set each slot
@@ -66,16 +62,25 @@
*
Deterministic deallocation
*
* When writing code that manipulates memory segments, especially if backed by memory which resides outside the Java heap, it is
- * crucial that the resources associated with a memory segment are released when the segment is no longer in use, by calling the {@link jdk.incubator.foreign.MemorySegment#close()}
- * method either explicitly, or implicitly, by relying on try-with-resources construct (as demonstrated in the example above).
- * Closing a given memory segment is an atomic operation which can either succeed - and result in the underlying
- * memory associated with the segment to be released, or fail with an exception.
- *
- * The deterministic deallocation model differs significantly from the implicit strategies adopted within other APIs, most
- * notably the {@link java.nio.ByteBuffer} API: in that case, when a native byte buffer is created (see {@link java.nio.ByteBuffer#allocateDirect(int)}),
- * the underlying memory is not released until the byte buffer reference becomes unreachable. While implicit deallocation
- * models such as this can be very convenient - clients do not have to remember to close a direct buffer - such models can also make it
- * hard for clients to ensure that the memory associated with a direct buffer has indeed been released.
+ * often crucial that the resources associated with a memory segment are released when the segment is no longer in use,
+ * and in a timely fashion. For this reason, there might be cases where waiting for the garbage collector to determine that a segment
+ * is unreachable is not optimal.
+ * Clients that operate under these assumptions might want to programmatically release the memory associated
+ * with a memory segment. This can be done, using the {@link jdk.incubator.foreign.ResourceScope} abstraction, as shown below:
+ *
+ *
+ *
+ * This example is almost identical to the prior one; this time we first create a so called resource scope,
+ * which is used to bind the life-cycle of the segment created immediately afterwards. Note the use of the
+ * try-with-resources construct: this idiom ensures that all the memory resources associated with the segment will be released
+ * at the end of the block, according to the semantics described in Section {@jls 14.20.3} of The Java Language Specification.
*
*
Safety
*
@@ -86,14 +91,9 @@
* Section {@jls 15.10.4} of The Java Language Specification.
*
* Since memory segments can be closed (see above), segments are also validated (upon access) to make sure that
- * the segment being accessed has not been closed prematurely. We call this guarantee temporal safety. Note that,
- * in the general case, guaranteeing temporal safety can be hard, as multiple threads could attempt to access and/or close
- * the same memory segment concurrently. The memory access API addresses this problem by imposing strong
- * thread-confinement guarantees on memory segments: upon creation, a memory segment is associated with an owner thread,
- * which is the only thread that can either access or close the segment.
- *
- * Together, spatial and temporal safety ensure that each memory access operation either succeeds - and accesses a valid
- * memory location - or fails.
+ * the resource scope associated with the segment being accessed has not been closed prematurely.
+ * We call this guarantee temporal safety. Together, spatial and temporal safety ensure that each memory access
+ * operation either succeeds - and accesses a valid memory location - or fails.
*
*
Foreign function access
* The key abstractions introduced to support foreign function access are {@link jdk.incubator.foreign.LibraryLookup} and {@link jdk.incubator.foreign.CLinker}.
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
index 971533569cf..ce8f524db26 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java
@@ -35,9 +35,6 @@
import jdk.internal.vm.annotation.ForceInline;
import sun.security.action.GetPropertyAction;
-import java.io.FileDescriptor;
-import java.lang.invoke.VarHandle;
-import java.lang.ref.Cleaner;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.function.Consumer;
@@ -60,8 +57,8 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
private static final boolean enableSmallSegments =
Boolean.parseBoolean(GetPropertyAction.privilegedGetProperty("jdk.incubator.foreign.SmallSegments", "true"));
- final static int FIRST_RESERVED_FLAG = 1 << 16; // upper 16 bits are reserved
- final static int SMALL = FIRST_RESERVED_FLAG;
+ final static int READ_ONLY = 1;
+ final static int SMALL = READ_ONLY << 1;
final static long NONCE = new Random().nextLong();
final static JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess();
@@ -87,8 +84,17 @@ public abstract class AbstractMemorySegmentImpl extends MemorySegmentProxy imple
static int defaultAccessModes(long size) {
return (enableSmallSegments && size < Integer.MAX_VALUE) ?
- ALL_ACCESS | SMALL :
- ALL_ACCESS;
+ SMALL : 0;
+ }
+
+ @Override
+ public AbstractMemorySegmentImpl asReadOnly() {
+ return dup(0, length, mask | READ_ONLY, scope);
+ }
+
+ @Override
+ public boolean isReadOnly() {
+ return isSet(READ_ONLY);
}
@Override
@@ -115,7 +121,7 @@ public Spliterator spliterator(SequenceLayout sequenceLayout) {
throw new IllegalArgumentException();
}
return new SegmentSplitter(sequenceLayout.elementLayout().byteSize(), sequenceLayout.elementCount().getAsLong(),
- withAccessModes(accessModes() & ~CLOSE));
+ this);
}
@Override
@@ -223,121 +229,28 @@ public final MemoryAddress address() {
@Override
public final ByteBuffer asByteBuffer() {
- if (!isSet(READ)) {
- throw unsupportedAccessMode(READ);
- }
checkArraySize("ByteBuffer", 1);
ByteBuffer _bb = makeByteBuffer();
- if (!isSet(WRITE)) {
+ if (isSet(READ_ONLY)) {
//scope is IMMUTABLE - obtain a RO byte buffer
_bb = _bb.asReadOnlyBuffer();
}
return _bb;
}
- @Override
- public final int accessModes() {
- return mask & ALL_ACCESS;
- }
-
@Override
public final long byteSize() {
return length;
}
- @Override
public final boolean isAlive() {
return scope.isAlive();
}
- @Override
public Thread ownerThread() {
return scope.ownerThread();
}
- @Override
- public AbstractMemorySegmentImpl withAccessModes(int accessModes) {
- checkAccessModes(accessModes);
- if ((~accessModes() & accessModes) != 0) {
- throw new IllegalArgumentException("Cannot acquire more access modes");
- }
- return dup(0, length, (mask & ~ALL_ACCESS) | accessModes, scope);
- }
-
- @Override
- public boolean hasAccessModes(int accessModes) {
- checkAccessModes(accessModes);
- return (accessModes() & accessModes) == accessModes;
- }
-
- private void checkAccessModes(int accessModes) {
- if ((accessModes & ~ALL_ACCESS) != 0) {
- throw new IllegalArgumentException("Invalid access modes");
- }
- }
-
- public MemorySegment handoff(Thread thread) {
- Objects.requireNonNull(thread);
- checkValidState();
- if (!isSet(HANDOFF)) {
- throw unsupportedAccessMode(HANDOFF);
- }
- try {
- return dup(0L, length, mask, scope.confineTo(thread));
- } finally {
- //flush read/writes to segment memory before returning the new segment
- VarHandle.fullFence();
- }
- }
-
- @Override
- public MemorySegment share() {
- checkValidState();
- if (!isSet(SHARE)) {
- throw unsupportedAccessMode(SHARE);
- }
- try {
- return dup(0L, length, mask, scope.share());
- } finally {
- //flush read/writes to segment memory before returning the new segment
- VarHandle.fullFence();
- }
- }
-
- @Override
- public MemorySegment handoff(NativeScope scope) {
- Objects.requireNonNull(scope);
- checkValidState();
- if (!isSet(HANDOFF)) {
- throw unsupportedAccessMode(HANDOFF);
- }
- if (!isSet(CLOSE)) {
- throw unsupportedAccessMode(CLOSE);
- }
- MemorySegment dup = handoff(scope.ownerThread());
- ((AbstractNativeScope)scope).register(dup);
- return dup.withAccessModes(accessModes() & (READ | WRITE));
- }
-
- @Override
- public MemorySegment registerCleaner(Cleaner cleaner) {
- Objects.requireNonNull(cleaner);
- checkValidState();
- if (!isSet(CLOSE)) {
- throw unsupportedAccessMode(CLOSE);
- }
- return dup(0L, length, mask, scope.cleanable(cleaner));
- }
-
- @Override
- public final void close() {
- checkValidState();
- if (!isSet(CLOSE)) {
- throw unsupportedAccessMode(CLOSE);
- }
- scope.close();
- }
-
@Override
public boolean isMapped() {
return false;
@@ -393,19 +306,12 @@ public boolean isSmall() {
@Override
public void checkAccess(long offset, long length, boolean readOnly) {
- if (!readOnly && !isSet(WRITE)) {
- throw unsupportedAccessMode(WRITE);
- } else if (readOnly && !isSet(READ)) {
- throw unsupportedAccessMode(READ);
+ if (!readOnly && isSet(READ_ONLY)) {
+ throw new UnsupportedOperationException("Attempt to write a read-only segment");
}
checkBounds(offset, length);
}
- private void checkAccessAndScope(long offset, long length, boolean readOnly) {
- checkValidState();
- checkAccess(offset, length, readOnly);
- }
-
private void checkValidState() {
try {
scope.checkValidState();
@@ -432,11 +338,11 @@ private boolean isSet(int mask) {
private int checkArraySize(String typeName, int elemSize) {
if (length % elemSize != 0) {
- throw new UnsupportedOperationException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, length));
+ throw new IllegalStateException(String.format("Segment size is not a multiple of %d. Size: %d", elemSize, length));
}
long arraySize = length / elemSize;
if (arraySize > (Integer.MAX_VALUE - 8)) { //conservative check
- throw new UnsupportedOperationException(String.format("Segment is too large to wrap as %s. Size: %d", typeName, length));
+ throw new IllegalStateException(String.format("Segment is too large to wrap as %s. Size: %d", typeName, length));
}
return (int)arraySize;
}
@@ -466,31 +372,6 @@ private void checkBoundsSmall(int offset, int length) {
}
}
- UnsupportedOperationException unsupportedAccessMode(int expected) {
- return new UnsupportedOperationException((String.format("Required access mode %s ; current access modes: %s",
- modeStrings(expected).get(0), modeStrings(mask))));
- }
-
- private List modeStrings(int mode) {
- List modes = new ArrayList<>();
- if ((mode & READ) != 0) {
- modes.add("READ");
- }
- if ((mode & WRITE) != 0) {
- modes.add("WRITE");
- }
- if ((mode & CLOSE) != 0) {
- modes.add("CLOSE");
- }
- if ((mode & SHARE) != 0) {
- modes.add("SHARE");
- }
- if ((mode & HANDOFF) != 0) {
- modes.add("HANDOFF");
- }
- return modes;
- }
-
private IndexOutOfBoundsException outOfBoundException(long offset, long length) {
return new IndexOutOfBoundsException(String.format("Out of bound access on segment %s; new offset = %d; new length = %d",
this, offset, length));
@@ -608,14 +489,14 @@ public static AbstractMemorySegmentImpl ofBuffer(ByteBuffer bb) {
bufferScope = bufferSegment.scope;
modes = bufferSegment.mask;
} else {
- bufferScope = MemoryScope.createConfined(bb, MemoryScope.DUMMY_CLEANUP_ACTION, null);
+ bufferScope = MemoryScope.GLOBAL;
modes = defaultAccessModes(size);
}
if (bb.isReadOnly()) {
- modes &= ~WRITE;
+ modes |= READ_ONLY;
}
if (base != null) {
- return new HeapMemorySegmentImpl.OfByte(bbAddress + pos, (byte[])base, size, modes, bufferScope);
+ return new HeapMemorySegmentImpl.OfByte(bbAddress + pos, (byte[])base, size, modes);
} else if (unmapper == null) {
return new NativeMemorySegmentImpl(bbAddress + pos, size, modes, bufferScope);
} else {
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractNativeScope.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractNativeScope.java
deleted file mode 100644
index 33b8079719a..00000000000
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/AbstractNativeScope.java
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (c) 2020, 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
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation. Oracle designates this
- * particular file as subject to the "Classpath" exception as provided
- * by Oracle in the LICENSE file that accompanied this code.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-package jdk.internal.foreign;
-
-import jdk.incubator.foreign.MemorySegment;
-import jdk.incubator.foreign.NativeScope;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.OptionalLong;
-
-public abstract class AbstractNativeScope implements NativeScope {
-
- private final List segments = new ArrayList<>();
- private final Thread ownerThread;
-
- private static final int SCOPE_MASK = MemorySegment.READ | MemorySegment.WRITE; // no terminal operations allowed
-
- AbstractNativeScope() {
- this.ownerThread = Thread.currentThread();
- }
-
- public static NativeScope emptyScope() {
- return new EmptyScope();
- }
-
- @Override
- public Thread ownerThread() {
- return ownerThread;
- }
-
- @Override
- public void close() {
- segments.forEach(MemorySegment::close);
- }
-
- void checkOwnerThread() {
- if (Thread.currentThread() != ownerThread()) {
- throw new IllegalStateException("Attempt to access scope from different thread");
- }
- }
-
- MemorySegment newSegment(long size, long align) {
- MemorySegment segment = MemorySegment.allocateNative(size, align);
- segments.add(segment);
- return segment;
- }
-
- MemorySegment newSegment(long size) {
- return newSegment(size, size);
- }
-
- public void register(MemorySegment segment) {
- segments.add(segment);
- }
-
- public static class UnboundedNativeScope extends AbstractNativeScope {
-
- private static final long BLOCK_SIZE = 4 * 1024;
- private static final long MAX_ALLOC_SIZE = BLOCK_SIZE / 2;
-
- private MemorySegment segment;
- private long sp = 0L;
- private long size = 0L;
-
- @Override
- public OptionalLong byteSize() {
- return OptionalLong.empty();
- }
-
- @Override
- public long allocatedBytes() {
- return size;
- }
-
- public UnboundedNativeScope() {
- super();
- this.segment = newSegment(BLOCK_SIZE);
- }
-
- @Override
- public MemorySegment allocate(long bytesSize, long bytesAlignment) {
- checkOwnerThread();
- if (Utils.alignUp(bytesSize, bytesAlignment) > MAX_ALLOC_SIZE) {
- MemorySegment segment = newSegment(bytesSize, bytesAlignment);
- return segment.withAccessModes(SCOPE_MASK);
- }
- // try to slice from current segment first...
- MemorySegment slice = trySlice(bytesSize, bytesAlignment);
- if (slice == null) {
- // ... if that fails, allocate a new segment and slice from there
- sp = 0L;
- segment = newSegment(BLOCK_SIZE, 1L);
- slice = trySlice(bytesSize, bytesAlignment);
- if (slice == null) {
- // this should not be possible - allocations that do not fit in BLOCK_SIZE should get their own
- // standalone segment (see above).
- throw new AssertionError("Cannot get here!");
- }
- }
- return slice;
- }
-
- private MemorySegment trySlice(long bytesSize, long bytesAlignment) {
- long min = segment.address().toRawLongValue();
- long start = Utils.alignUp(min + sp, bytesAlignment) - min;
- if (segment.byteSize() - start < bytesSize) {
- return null;
- } else {
- MemorySegment slice = segment.asSlice(start, bytesSize)
- .withAccessModes(SCOPE_MASK);
- sp = start + bytesSize;
- size += Utils.alignUp(bytesSize, bytesAlignment);
- return slice;
- }
- }
- }
-
- public static class BoundedNativeScope extends AbstractNativeScope {
- private final MemorySegment segment;
- private long sp = 0L;
-
- @Override
- public OptionalLong byteSize() {
- return OptionalLong.of(segment.byteSize());
- }
-
- @Override
- public long allocatedBytes() {
- return sp;
- }
-
- public BoundedNativeScope(long size) {
- super();
- this.segment = newSegment(size, 1);
- }
-
- @Override
- public MemorySegment allocate(long bytesSize, long bytesAlignment) {
- checkOwnerThread();
- long min = segment.address().toRawLongValue();
- long start = Utils.alignUp(min + sp, bytesAlignment) - min;
- try {
- MemorySegment slice = segment.asSlice(start, bytesSize)
- .withAccessModes(SCOPE_MASK);
- sp = start + bytesSize;
- return slice;
- } catch (IndexOutOfBoundsException ex) {
- throw new OutOfMemoryError("Not enough space left to allocate");
- }
- }
- }
-
- // only for registering
- private static class EmptyScope extends AbstractNativeScope {
- @Override
- public OptionalLong byteSize() {
- return OptionalLong.of(0);
- }
-
- @Override
- public long allocatedBytes() {
- return 0;
- }
-
- @Override
- public MemorySegment allocate(long bytesSize, long bytesAlignment) {
- throw new OutOfMemoryError("Not enough space left to allocate");
- }
- }
-}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/ArenaAllocator.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/ArenaAllocator.java
new file mode 100644
index 00000000000..98c3cc9147f
--- /dev/null
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/ArenaAllocator.java
@@ -0,0 +1,116 @@
+package jdk.internal.foreign;
+
+import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.SegmentAllocator;
+import jdk.incubator.foreign.ResourceScope;
+
+import java.util.OptionalLong;
+
+public class ArenaAllocator implements SegmentAllocator {
+
+ private final SegmentAllocator allocator;
+ private MemorySegment segment;
+
+ private static final long BLOCK_SIZE = 4 * 1024;
+ private static final long MAX_ALLOC_SIZE = BLOCK_SIZE / 2;
+
+ private long sp = 0L;
+
+ public ArenaAllocator(ResourceScope scope) {
+ this(BLOCK_SIZE, scope);
+ }
+
+ ArenaAllocator(long initialSize, ResourceScope scope) {
+ this.allocator = SegmentAllocator.malloc(() -> scope);
+ this.segment = allocator.allocate(initialSize, 1);
+ }
+
+ MemorySegment newSegment(long size, long align) {
+ return allocator.allocate(size, align);
+ }
+
+ @Override
+ public MemorySegment allocate(long bytesSize, long bytesAlignment) {
+ checkConfinementIfNeeded();
+ if (Utils.alignUp(bytesSize, bytesAlignment) > MAX_ALLOC_SIZE) {
+ return newSegment(bytesSize, bytesAlignment);
+ }
+ // try to slice from current segment first...
+ MemorySegment slice = trySlice(bytesSize, bytesAlignment);
+ if (slice == null) {
+ // ... if that fails, allocate a new segment and slice from there
+ sp = 0L;
+ segment = newSegment(BLOCK_SIZE, 1L);
+ slice = trySlice(bytesSize, bytesAlignment);
+ if (slice == null) {
+ // this should not be possible - allocations that do not fit in BLOCK_SIZE should get their own
+ // standalone segment (see above).
+ throw new AssertionError("Cannot get here!");
+ }
+ }
+ return slice;
+ }
+
+ private MemorySegment trySlice(long bytesSize, long bytesAlignment) {
+ long min = segment.address().toRawLongValue();
+ long start = Utils.alignUp(min + sp, bytesAlignment) - min;
+ if (segment.byteSize() - start < bytesSize) {
+ return null;
+ } else {
+ MemorySegment slice = segment.asSlice(start, bytesSize);
+ sp = start + bytesSize;
+ return slice;
+ }
+ }
+
+ private void checkConfinementIfNeeded() {
+ Thread segmentThread = segment.scope().ownerThread();
+ if (segmentThread != null && segmentThread != Thread.currentThread()) {
+ throw new IllegalStateException("Attempt to allocate outside confinement thread");
+ }
+ }
+
+ public static class BoundedArenaAllocator extends ArenaAllocator {
+
+ public BoundedArenaAllocator(ResourceScope scope, long size) {
+ super(size, scope);
+ }
+
+ @Override
+ MemorySegment newSegment(long size, long align) {
+ throw new OutOfMemoryError("Not enough space left to allocate");
+ }
+ }
+
+ public static class BoundedSharedArenaAllocator extends BoundedArenaAllocator {
+ public BoundedSharedArenaAllocator(ResourceScope scope, long size) {
+ super(scope, size);
+ }
+
+ @Override
+ public synchronized MemorySegment allocate(long bytesSize, long bytesAlignment) {
+ return super.allocate(bytesSize, bytesAlignment);
+ }
+ }
+
+ public static class UnboundedSharedArenaAllocator implements SegmentAllocator {
+
+ final ResourceScope scope;
+
+ final ThreadLocal allocators = new ThreadLocal<>() {
+ @Override
+ protected ArenaAllocator initialValue() {
+ return new ArenaAllocator(scope);
+ }
+ };
+
+ public UnboundedSharedArenaAllocator(ResourceScope scope) {
+ this.scope = scope;
+ }
+
+ @Override
+ public MemorySegment allocate(long bytesSize, long bytesAlignment) {
+ return allocators.get().allocate(bytesSize, bytesAlignment);
+ }
+ }
+}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/ConfinedScope.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/ConfinedScope.java
new file mode 100644
index 00000000000..ab001fee19f
--- /dev/null
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/ConfinedScope.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2021, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.foreign;
+
+import jdk.internal.vm.annotation.ForceInline;
+
+import java.lang.ref.Cleaner;
+
+/**
+ * A confined scope, which features an owner thread. The liveness check features an additional
+ * confinement check - that is, calling any operation on this scope from a thread other than the
+ * owner thread will result in an exception. Because of this restriction, checking the liveness bit
+ * can be performed in plain mode.
+ */
+final class ConfinedScope extends MemoryScope {
+
+ private boolean closed; // = false
+ private int lockCount = 0;
+ private final Thread owner;
+
+ public ConfinedScope(Thread owner, Object ref, Cleaner cleaner, boolean closeable) {
+ super(ref, cleaner, closeable, new ConfinedResourceList());
+ this.owner = owner;
+ }
+
+ @ForceInline
+ public final void checkValidState() {
+ if (owner != Thread.currentThread()) {
+ throw new IllegalStateException("Attempted access outside owning thread");
+ }
+ if (closed) {
+ throw new IllegalStateException("Already closed");
+ }
+ }
+
+ @Override
+ public boolean isAlive() {
+ return !closed;
+ }
+
+ @Override
+ public Handle acquire() {
+ checkValidState();
+ if (!closeable) return DUMMY_LOCK;
+ lockCount++;
+ return new ConfinedHandle();
+ }
+
+ void justClose() {
+ this.checkValidState();
+ if (lockCount == 0) {
+ closed = true;
+ } else {
+ throw new IllegalStateException("Scope is acquired by " + lockCount + " locks");
+ }
+ }
+
+ @Override
+ public Thread ownerThread() {
+ return owner;
+ }
+
+ /**
+ * A confined resource list; no races are possible here.
+ */
+ static final class ConfinedResourceList extends ResourceList {
+ @Override
+ void add(ResourceCleanup cleanup) {
+ if (fst != ResourceCleanup.CLOSED_LIST) {
+ cleanup.next = fst;
+ fst = cleanup;
+ } else {
+ throw new IllegalStateException("Already closed!");
+ }
+ }
+
+ @Override
+ void cleanup() {
+ if (fst != ResourceCleanup.CLOSED_LIST) {
+ ResourceCleanup prev = fst;
+ fst = ResourceCleanup.CLOSED_LIST;
+ cleanup(prev);
+ } else {
+ throw new IllegalStateException("Attempt to cleanup an already closed resource list");
+ }
+ }
+ }
+
+ /**
+ * A confined resource scope handle; no races are possible here.
+ */
+ final class ConfinedHandle implements Handle {
+ boolean released = false;
+
+ @Override
+ public void close() {
+ checkValidState(); // thread check
+ if (!released) {
+ released = true;
+ lockCount--;
+ }
+ }
+ }
+}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java
index 30e2fc0608b..41d58f762e5 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/HeapMemorySegmentImpl.java
@@ -34,7 +34,6 @@
import java.nio.ByteBuffer;
import java.util.Objects;
-import java.util.function.Supplier;
/**
* Implementation for heap memory segments. An heap memory segment is composed by an offset and
@@ -52,8 +51,8 @@ public abstract class HeapMemorySegmentImpl extends AbstractMemorySegmentImpl
final H base;
@ForceInline
- HeapMemorySegmentImpl(long offset, H base, long length, int mask, MemoryScope scope) {
- super(length, mask, scope);
+ HeapMemorySegmentImpl(long offset, H base, long length, int mask) {
+ super(length, mask, MemoryScope.GLOBAL);
this.offset = offset;
this.base = base;
}
@@ -75,20 +74,20 @@ ByteBuffer makeByteBuffer() {
throw new UnsupportedOperationException("Not an address to an heap-allocated byte array");
}
JavaNioAccess nioAccess = SharedSecrets.getJavaNioAccess();
- return nioAccess.newHeapByteBuffer((byte[]) base(), (int)min() - BYTE_ARR_BASE, (int) byteSize(), this);
+ return nioAccess.newHeapByteBuffer((byte[]) base(), (int)min() - BYTE_ARR_BASE, (int) byteSize(), null);
}
// factories
public static class OfByte extends HeapMemorySegmentImpl {
- OfByte(long offset, byte[] base, long length, int mask, MemoryScope scope) {
- super(offset, base, length, mask, scope);
+ OfByte(long offset, byte[] base, long length, int mask) {
+ super(offset, base, length, mask);
}
@Override
OfByte dup(long offset, long size, int mask, MemoryScope scope) {
- return new OfByte(this.offset + offset, base, size, mask, scope);
+ return new OfByte(this.offset + offset, base, size, mask);
}
@Override
@@ -99,20 +98,19 @@ byte[] base() {
public static MemorySegment fromArray(byte[] arr) {
Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_BYTE_INDEX_SCALE;
- MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null);
- return new OfByte(Unsafe.ARRAY_BYTE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
+ return new OfByte(Unsafe.ARRAY_BYTE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
}
}
public static class OfChar extends HeapMemorySegmentImpl {
- OfChar(long offset, char[] base, long length, int mask, MemoryScope scope) {
- super(offset, base, length, mask, scope);
+ OfChar(long offset, char[] base, long length, int mask) {
+ super(offset, base, length, mask);
}
@Override
OfChar dup(long offset, long size, int mask, MemoryScope scope) {
- return new OfChar(this.offset + offset, base, size, mask, scope);
+ return new OfChar(this.offset + offset, base, size, mask);
}
@Override
@@ -123,20 +121,19 @@ char[] base() {
public static MemorySegment fromArray(char[] arr) {
Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_CHAR_INDEX_SCALE;
- MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null);
- return new OfChar(Unsafe.ARRAY_CHAR_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
+ return new OfChar(Unsafe.ARRAY_CHAR_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
}
}
public static class OfShort extends HeapMemorySegmentImpl {
- OfShort(long offset, short[] base, long length, int mask, MemoryScope scope) {
- super(offset, base, length, mask, scope);
+ OfShort(long offset, short[] base, long length, int mask) {
+ super(offset, base, length, mask);
}
@Override
OfShort dup(long offset, long size, int mask, MemoryScope scope) {
- return new OfShort(this.offset + offset, base, size, mask, scope);
+ return new OfShort(this.offset + offset, base, size, mask);
}
@Override
@@ -147,20 +144,19 @@ short[] base() {
public static MemorySegment fromArray(short[] arr) {
Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_SHORT_INDEX_SCALE;
- MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null);
- return new OfShort(Unsafe.ARRAY_SHORT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
+ return new OfShort(Unsafe.ARRAY_SHORT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
}
}
public static class OfInt extends HeapMemorySegmentImpl {
- OfInt(long offset, int[] base, long length, int mask, MemoryScope scope) {
- super(offset, base, length, mask, scope);
+ OfInt(long offset, int[] base, long length, int mask) {
+ super(offset, base, length, mask);
}
@Override
OfInt dup(long offset, long size, int mask, MemoryScope scope) {
- return new OfInt(this.offset + offset, base, size, mask, scope);
+ return new OfInt(this.offset + offset, base, size, mask);
}
@Override
@@ -171,20 +167,19 @@ int[] base() {
public static MemorySegment fromArray(int[] arr) {
Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_INT_INDEX_SCALE;
- MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null);
- return new OfInt(Unsafe.ARRAY_INT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
+ return new OfInt(Unsafe.ARRAY_INT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
}
}
public static class OfLong extends HeapMemorySegmentImpl {
- OfLong(long offset, long[] base, long length, int mask, MemoryScope scope) {
- super(offset, base, length, mask, scope);
+ OfLong(long offset, long[] base, long length, int mask) {
+ super(offset, base, length, mask);
}
@Override
OfLong dup(long offset, long size, int mask, MemoryScope scope) {
- return new OfLong(this.offset + offset, base, size, mask, scope);
+ return new OfLong(this.offset + offset, base, size, mask);
}
@Override
@@ -195,20 +190,19 @@ long[] base() {
public static MemorySegment fromArray(long[] arr) {
Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_LONG_INDEX_SCALE;
- MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null);
- return new OfLong(Unsafe.ARRAY_LONG_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
+ return new OfLong(Unsafe.ARRAY_LONG_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
}
}
public static class OfFloat extends HeapMemorySegmentImpl {
- OfFloat(long offset, float[] base, long length, int mask, MemoryScope scope) {
- super(offset, base, length, mask, scope);
+ OfFloat(long offset, float[] base, long length, int mask) {
+ super(offset, base, length, mask);
}
@Override
OfFloat dup(long offset, long size, int mask, MemoryScope scope) {
- return new OfFloat(this.offset + offset, base, size, mask, scope);
+ return new OfFloat(this.offset + offset, base, size, mask);
}
@Override
@@ -219,20 +213,19 @@ float[] base() {
public static MemorySegment fromArray(float[] arr) {
Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_FLOAT_INDEX_SCALE;
- MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null);
- return new OfFloat(Unsafe.ARRAY_FLOAT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
+ return new OfFloat(Unsafe.ARRAY_FLOAT_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
}
}
public static class OfDouble extends HeapMemorySegmentImpl {
- OfDouble(long offset, double[] base, long length, int mask, MemoryScope scope) {
- super(offset, base, length, mask, scope);
+ OfDouble(long offset, double[] base, long length, int mask) {
+ super(offset, base, length, mask);
}
@Override
OfDouble dup(long offset, long size, int mask, MemoryScope scope) {
- return new OfDouble(this.offset + offset, base, size, mask, scope);
+ return new OfDouble(this.offset + offset, base, size, mask);
}
@Override
@@ -243,8 +236,8 @@ OfDouble dup(long offset, long size, int mask, MemoryScope scope) {
public static MemorySegment fromArray(double[] arr) {
Objects.requireNonNull(arr);
long byteSize = (long)arr.length * Unsafe.ARRAY_DOUBLE_INDEX_SCALE;
- MemoryScope scope = MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null);
- return new OfDouble(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize), scope);
+ return new OfDouble(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, arr, byteSize, defaultAccessModes(byteSize));
}
}
+
}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/LibrariesHelper.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/LibrariesHelper.java
index ea2c42576c2..a8bbee2b284 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/LibrariesHelper.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/LibrariesHelper.java
@@ -29,18 +29,16 @@
import java.io.File;
import jdk.incubator.foreign.LibraryLookup;
+import jdk.incubator.foreign.ResourceScope;
import jdk.internal.loader.NativeLibraries;
import jdk.internal.loader.NativeLibrary;
-import jdk.internal.ref.CleanerFactory;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.Arrays;
-import java.util.IdentityHashMap;
+import java.lang.ref.Reference;
+import java.lang.ref.WeakReference;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
public final class LibrariesHelper {
@@ -49,7 +47,7 @@ private LibrariesHelper() {}
private final static NativeLibraries nativeLibraries =
NativeLibraries.rawNativeLibraries(LibrariesHelper.class, true);
- private final static Map loadedLibraries = new IdentityHashMap<>();
+ private final static Map> loadedLibraries = new ConcurrentHashMap<>();
/**
* Load the specified shared library.
@@ -76,43 +74,40 @@ public static LibraryLookup load(String path) {
"Library not found: " + path);
}
- // return the absolute path of the library of given name by searching
- // in the given array of paths.
- private static Optional findLibraryPath(Path[] paths, String libName) {
- return Arrays.stream(paths).
- map(p -> p.resolve(System.mapLibraryName(libName))).
- filter(Files::isRegularFile).map(Path::toAbsolutePath).findFirst();
- }
-
public static LibraryLookup getDefaultLibrary() {
return LibraryLookupImpl.DEFAULT_LOOKUP;
}
- synchronized static LibraryLookupImpl lookup(Supplier librarySupplier, String notFoundMsg) {
+ static LibraryLookupImpl lookup(Supplier librarySupplier, String notFoundMsg) {
NativeLibrary library = librarySupplier.get();
if (library == null) {
throw new IllegalArgumentException(notFoundMsg);
}
- AtomicInteger refCount = loadedLibraries.computeIfAbsent(library, lib -> new AtomicInteger());
- refCount.incrementAndGet();
- LibraryLookupImpl lookup = new LibraryLookupImpl(library);
- CleanerFactory.cleaner().register(lookup, () -> tryUnload(library));
- return lookup;
- }
-
- synchronized static void tryUnload(NativeLibrary library) {
- AtomicInteger refCount = loadedLibraries.get(library);
- if (refCount.decrementAndGet() == 0) {
- loadedLibraries.remove(library);
- nativeLibraries.unload(library);
+ ResourceScope[] holder = new ResourceScope[1];
+ try {
+ WeakReference scopeRef = loadedLibraries.computeIfAbsent(library, lib -> {
+ MemoryScope s = MemoryScope.createDefault();
+ holder[0] = s; // keep the scope alive at least until the outer method returns
+ s.addOrCleanupIfFail(MemoryScope.ResourceList.ResourceCleanup.ofRunnable(() -> {
+ nativeLibraries.unload(library);
+ loadedLibraries.remove(library);
+ }));
+ return new WeakReference<>(s);
+ });
+ return new LibraryLookupImpl(library, scopeRef.get());
+ } finally {
+ Reference.reachabilityFence(holder);
}
}
+ //Todo: in principle we could expose a scope accessor, so that users could unload libraries at will
static class LibraryLookupImpl implements LibraryLookup {
final NativeLibrary library;
+ final ResourceScope scope;
- LibraryLookupImpl(NativeLibrary library) {
+ LibraryLookupImpl(NativeLibrary library, ResourceScope scope) {
this.library = library;
+ this.scope = scope;
}
@Override
@@ -120,7 +115,7 @@ public Optional lookup(String name) {
try {
Objects.requireNonNull(name);
MemoryAddress addr = MemoryAddress.ofLong(library.lookup(name));
- return Optional.of(new Symbol() { // inner class - retains a link to enclosing lookup
+ return Optional.of(new Symbol() { // inner class - retains a link to the scope
@Override
public String name() {
return name;
@@ -136,7 +131,7 @@ public MemoryAddress address() {
}
}
- static LibraryLookup DEFAULT_LOOKUP = new LibraryLookupImpl(NativeLibraries.defaultLibrary);
+ static LibraryLookup DEFAULT_LOOKUP = new LibraryLookupImpl(NativeLibraries.defaultLibrary, MemoryScope.GLOBAL);
}
/* used for testing */
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java
index 36a0e83323e..6eb5cc8532f 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java
@@ -30,7 +30,6 @@
import jdk.internal.misc.ScopedMemoryAccess;
import sun.nio.ch.FileChannelImpl;
-import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@@ -40,7 +39,6 @@
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.Objects;
-import java.util.Optional;
/**
* Implementation for a mapped memory segments. A mapped memory segment is a native memory segment, which
@@ -61,7 +59,8 @@ public class MappedMemorySegmentImpl extends NativeMemorySegmentImpl {
@Override
ByteBuffer makeByteBuffer() {
- return nioAccess.newMappedByteBuffer(unmapper, min, (int)length, null, this);
+ return nioAccess.newMappedByteBuffer(unmapper, min, (int)length, null,
+ scope == MemoryScope.GLOBAL ? null : this);
}
@Override
@@ -77,11 +76,6 @@ public MappedMemorySegmentImpl asSlice(long offset, long newSize) {
return (MappedMemorySegmentImpl)super.asSlice(offset, newSize);
}
- @Override
- public MappedMemorySegmentImpl withAccessModes(int accessModes) {
- return (MappedMemorySegmentImpl)super.withAccessModes(accessModes);
- }
-
@Override
public boolean isMapped() {
return true;
@@ -111,9 +105,10 @@ public void force() {
// factories
- public static MemorySegment makeMappedSegment(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode) throws IOException {
+ public static MemorySegment makeMappedSegment(Path path, long bytesOffset, long bytesSize, FileChannel.MapMode mapMode, MemoryScope scope) throws IOException {
Objects.requireNonNull(path);
Objects.requireNonNull(mapMode);
+ scope.checkValidStateSlow();
if (bytesSize < 0) throw new IllegalArgumentException("Requested bytes size must be >= 0.");
if (bytesOffset < 0) throw new IllegalArgumentException("Requested bytes offset must be >= 0.");
FileSystem fs = path.getFileSystem();
@@ -125,14 +120,20 @@ public static MemorySegment makeMappedSegment(Path path, long bytesOffset, long
UnmapperProxy unmapperProxy = ((FileChannelImpl)channelImpl).mapInternal(mapMode, bytesOffset, bytesSize);
int modes = defaultAccessModes(bytesSize);
if (mapMode == FileChannel.MapMode.READ_ONLY) {
- modes &= ~WRITE;
+ modes |= READ_ONLY;
}
if (unmapperProxy != null) {
- MemoryScope scope = MemoryScope.createConfined(null, unmapperProxy::unmap, null);
- return new MappedMemorySegmentImpl(unmapperProxy.address(), unmapperProxy, bytesSize,
+ AbstractMemorySegmentImpl segment = new MappedMemorySegmentImpl(unmapperProxy.address(), unmapperProxy, bytesSize,
modes, scope);
+ scope.addOrCleanupIfFail(new MemoryScope.ResourceList.ResourceCleanup() {
+ @Override
+ public void cleanup() {
+ unmapperProxy.unmap();
+ }
+ });
+ return segment;
} else {
- return new EmptyMappedMemorySegmentImpl(modes);
+ return new EmptyMappedMemorySegmentImpl(modes, scope);
}
}
}
@@ -149,9 +150,8 @@ private static OpenOption[] openOptions(FileChannel.MapMode mapMode) {
static class EmptyMappedMemorySegmentImpl extends MappedMemorySegmentImpl {
- public EmptyMappedMemorySegmentImpl(int modes) {
- super(0, null, 0, modes,
- MemoryScope.createConfined(null, MemoryScope.DUMMY_CLEANUP_ACTION, null));
+ public EmptyMappedMemorySegmentImpl(int modes, MemoryScope scope) {
+ super(0, null, 0, modes, scope);
}
@Override
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java
index f7d39bd7944..cf122a0877e 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryAddressImpl.java
@@ -27,6 +27,7 @@
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.ResourceScope;
import java.util.Objects;
@@ -93,19 +94,26 @@ public String toString() {
}
@Override
- public MemorySegment asSegmentRestricted(long bytesSize, Runnable cleanupAction, Object attachment) {
+ public MemorySegment asSegmentRestricted(long bytesSize, Runnable cleanupAction, ResourceScope scope) {
+ Objects.requireNonNull(scope);
Utils.checkRestrictedAccess("MemoryAddress.asSegmentRestricted");
if (bytesSize <= 0) {
throw new IllegalArgumentException("Invalid size : " + bytesSize);
}
- return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(this, bytesSize, cleanupAction, attachment);
+ return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(this, bytesSize,
+ cleanupAction,
+ (MemoryScope) scope);
}
public static MemorySegment ofLongUnchecked(long value) {
return ofLongUnchecked(value, Long.MAX_VALUE);
}
+ public static MemorySegment ofLongUnchecked(long value, long byteSize, MemoryScope memoryScope) {
+ return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(MemoryAddress.ofLong(value), byteSize, null, memoryScope);
+ }
+
public static MemorySegment ofLongUnchecked(long value, long byteSize) {
- return MemoryAddress.ofLong(value).asSegmentRestricted(byteSize).share();
+ return NativeMemorySegmentImpl.makeNativeSegmentUnchecked(MemoryAddress.ofLong(value), byteSize, null, MemoryScope.GLOBAL);
}
}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java
index 624e5e5ba7c..abfa5bf7279 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/MemoryScope.java
@@ -26,12 +26,13 @@
package jdk.internal.foreign;
+import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.ResourceScope;
+import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.misc.ScopedMemoryAccess;
-import jdk.internal.ref.PhantomCleanable;
-import jdk.internal.vm.annotation.ForceInline;
+import jdk.internal.ref.CleanerFactory;
+import jdk.internal.vm.annotation.Stable;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.VarHandle;
import java.lang.ref.Cleaner;
import java.lang.ref.Reference;
import java.util.Objects;
@@ -39,7 +40,7 @@
/**
* This class manages the temporal bounds associated with a memory segment as well
* as thread confinement. A scope has a liveness bit, which is updated when the scope is closed
- * (this operation is triggered by {@link AbstractMemorySegmentImpl#close()}). This bit is consulted prior
+ * (this operation is triggered by {@link ResourceScope#close()}). This bit is consulted prior
* to memory access (see {@link #checkValidState()}).
* There are two kinds of memory scope: confined memory scope and shared memory scope.
* A confined memory scope has an associated owner thread that confines some operations to
@@ -49,116 +50,99 @@
* shared scopes use a more sophisticated synchronization mechanism, which guarantees that no concurrent
* access is possible when a scope is being closed (see {@link jdk.internal.misc.ScopedMemoryAccess}).
*/
-abstract class MemoryScope implements ScopedMemoryAccess.Scope {
+public abstract class MemoryScope implements ResourceScope, ScopedMemoryAccess.Scope, SegmentAllocator {
- static final Runnable DUMMY_CLEANUP_ACTION = () -> { };
+ final ResourceList resourceList;
+ final boolean closeable;
+ protected final Object ref;
+
+ @Override
+ public void addOnClose(Runnable runnable) {
+ Objects.requireNonNull(runnable);
+ addInternal(ResourceList.ResourceCleanup.ofRunnable(runnable));
+ }
+
+ /**
+ * Add a cleanup action. If a failure occurred (because of a add vs. close race), call the cleanup action.
+ * This semantics is useful when allocating new memory segments, since we first do a malloc/mmap and _then_
+ * we register the cleanup (free/munmap) against the scope; so, if registration fails, we still have to
+ * cleanup memory. From the perspective of the client, such a failure would manifest as a factory
+ * returning a segment that is already "closed" - which is always possible anyway (e.g. if the scope
+ * is closed _after_ the cleanup for the segment is registered but _before_ the factory returns the
+ * new segment to the client). For this reason, it's not worth adding extra complexity to the segment
+ * initialization logic here - and using an optimistic logic works well in practice.
+ */
+ public void addOrCleanupIfFail(ResourceList.ResourceCleanup resource) {
+ try {
+ addInternal(resource);
+ } catch (Throwable ex) {
+ resource.cleanup();
+ }
+ }
+
+ void addInternal(ResourceList.ResourceCleanup resource) {
+ try {
+ checkValidStateSlow();
+ resourceList.add(resource);
+ } catch (ScopedMemoryAccess.Scope.ScopedAccessError err) {
+ throw new IllegalStateException("Already closed");
+ }
+ }
- private MemoryScope(Object ref, Runnable cleanupAction, Cleaner cleaner) {
- Objects.requireNonNull(cleanupAction);
+ protected MemoryScope(Object ref, Cleaner cleaner, boolean closeable, ResourceList resourceList) {
this.ref = ref;
- this.cleanupAction = cleanupAction;
- this.scopeCleanable = cleaner != null ?
- new ScopeCleanable(this, cleaner, cleanupAction) :
- null;
+ this.resourceList = resourceList;
+ this.closeable = closeable;
+ if (cleaner != null) {
+ cleaner.register(this, resourceList);
+ }
+ }
+
+ public static MemoryScope createDefault() {
+ return new SharedScope(null, CleanerFactory.cleaner(), false);
+ }
+
+ public static MemoryScope createConfined(Thread thread, Object ref, Cleaner cleaner) {
+ return new ConfinedScope(thread, ref, cleaner, true);
}
/**
* Creates a confined memory scope with given attachment and cleanup action. The returned scope
* is assumed to be confined on the current thread.
* @param ref an optional reference to an instance that needs to be kept reachable
- * @param cleanupAction a cleanup action to be executed when returned scope is closed
* @return a confined memory scope
*/
- static MemoryScope createConfined(Object ref, Runnable cleanupAction, Cleaner cleaner) {
- return new ConfinedScope(Thread.currentThread(), ref, cleanupAction, cleaner);
+ public static MemoryScope createConfined(Object ref, Cleaner cleaner) {
+ return new ConfinedScope(Thread.currentThread(), ref, cleaner, true);
}
/**
* Creates a shared memory scope with given attachment and cleanup action.
* @param ref an optional reference to an instance that needs to be kept reachable
- * @param cleanupAction a cleanup action to be executed when returned scope is closed
* @return a shared memory scope
*/
- static MemoryScope createShared(Object ref, Runnable cleanupAction, Cleaner cleaner) {
- return new SharedScope(ref, cleanupAction, cleaner);
+ public static MemoryScope createShared(Object ref, Cleaner cleaner) {
+ return new SharedScope(ref, cleaner, true);
}
- protected final Object ref;
- protected final ScopeCleanable scopeCleanable;
- protected final Runnable cleanupAction;
-
/**
* Closes this scope, executing any cleanup action (where provided).
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread.
*/
- final void close() {
- try {
- justClose();
- cleanupAction.run();
- if (scopeCleanable != null) {
- scopeCleanable.clear();
- }
- } finally {
- Reference.reachabilityFence(this);
+ public final void close() {
+ if (!closeable) {
+ throw new UnsupportedOperationException("Scope cannot be closed");
}
- }
-
- abstract void justClose();
-
- /**
- * Duplicates this scope with given new "owner" thread and {@link #close() closes} it.
- * @param newOwner new owner thread of the returned memory scope
- * @return a new confined scope, which is a duplicate of this scope, but with a new owner thread.
- * @throws IllegalStateException if this scope is already closed or if this is
- * a confined scope and this method is called outside of the owner thread.
- */
- final MemoryScope confineTo(Thread newOwner) {
try {
justClose();
- if (scopeCleanable != null) {
- scopeCleanable.clear();
- }
- return new ConfinedScope(newOwner, ref, cleanupAction, scopeCleanable != null ?
- scopeCleanable.cleaner : null);
+ resourceList.cleanup();
} finally {
Reference.reachabilityFence(this);
}
}
- /**
- * Duplicates this scope with given new "owner" thread and {@link #close() closes} it.
- * @return a new shared scope, which is a duplicate of this scope.
- * @throws IllegalStateException if this scope is already closed or if this is
- * a confined scope and this method is called outside of the owner thread,
- * or if this is already a shared scope.
- */
- final MemoryScope share() {
- try {
- justClose();
- if (scopeCleanable != null) {
- scopeCleanable.clear();
- }
- return new SharedScope(ref, cleanupAction, scopeCleanable != null ?
- scopeCleanable.cleaner : null);
- } finally {
- Reference.reachabilityFence(this);
- }
- }
-
- final MemoryScope cleanable(Cleaner cleaner) {
- if (scopeCleanable != null) {
- throw new IllegalStateException("Already registered with a cleaner");
- }
- try {
- justClose();
- return ownerThread() == null ?
- new SharedScope(ref, cleanupAction, cleaner) :
- new ConfinedScope(ownerThread(), ref, cleanupAction, cleaner);
- } finally {
- Reference.reachabilityFence(this);
- }
- }
+ abstract void justClose();
/**
* Returns "owner" thread of this scope.
@@ -172,12 +156,27 @@ final MemoryScope cleanable(Cleaner cleaner) {
*/
public abstract boolean isAlive();
+
+ /**
+ * This is a faster version of {@link #checkValidStateSlow()}, which is called upon memory access, and which
+ * relies on invariants associated with the memory scope implementations (typically, volatile access
+ * to the closed state bit is replaced with plain access, and ownership check is removed where not needed.
+ * Should be used with care.
+ */
+ public abstract void checkValidState();
+
/**
* Checks that this scope is still alive (see {@link #isAlive()}).
* @throws IllegalStateException if this scope is already closed or if this is
* a confined scope and this method is called outside of the owner thread.
*/
- public abstract void checkValidState();
+ public final void checkValidStateSlow() {
+ if (ownerThread() != null && Thread.currentThread() != ownerThread()) {
+ throw new IllegalStateException("Attempted access outside owning thread");
+ } else if (!isAlive()) {
+ throw new IllegalStateException("Already closed");
+ }
+ }
@Override
protected Object clone() throws CloneNotSupportedException {
@@ -185,123 +184,68 @@ protected Object clone() throws CloneNotSupportedException {
}
/**
- * A confined scope, which features an owner thread. The liveness check features an additional
- * confinement check - that is, calling any operation on this scope from a thread other than the
- * owner thread will result in an exception. Because of this restriction, checking the liveness bit
- * can be performed in plain mode (see {@link #checkAliveRaw(MemoryScope)}).
+ * Allocates a segment using this scope. Used by {@link SegmentAllocator#scoped(ResourceScope)}.
*/
- static class ConfinedScope extends MemoryScope {
-
- private boolean closed; // = false
- final Thread owner;
-
- public ConfinedScope(Thread owner, Object ref, Runnable cleanupAction, Cleaner cleaner) {
- super(ref, cleanupAction, cleaner);
- this.owner = owner;
- }
-
- @ForceInline
- public final void checkValidState() {
- if (owner != Thread.currentThread()) {
- throw new IllegalStateException("Attempted access outside owning thread");
- }
- if (closed) {
- throw ScopedAccessError.INSTANCE;
- }
- }
+ @Override
+ public MemorySegment allocate(long bytesSize, long bytesAlignment) {
+ return MemorySegment.allocateNative(bytesSize, bytesAlignment, this);
+ }
+ public static MemoryScope GLOBAL = new SharedScope( null, null, false) {
@Override
- public boolean isAlive() {
- return !closed;
+ void addInternal(ResourceList.ResourceCleanup resource) {
+ // do nothing
}
+ };
- void justClose() {
- checkValidState();
- closed = true;
- }
-
- @Override
- public Thread ownerThread() {
- return owner;
- }
- }
+ public final Handle DUMMY_LOCK = () -> { };
/**
- * A shared scope, which can be shared across multiple threads. Closing a shared scope has to ensure that
- * (i) only one thread can successfully close a scope (e.g. in a close vs. close race) and that
- * (ii) no other thread is accessing the memory associated with this scope while the segment is being
- * closed. To ensure the former condition, a CAS is performed on the liveness bit. Ensuring the latter
- * is trickier, and require a complex synchronization protocol (see {@link jdk.internal.misc.ScopedMemoryAccess}).
- * Since it is the responsibility of the closing thread to make sure that no concurrent access is possible,
- * checking the liveness bit upon access can be performed in plain mode (see {@link #checkAliveRaw(MemoryScope)}),
- * as in the confined case.
+ * A list of all cleanup actions associated with a resource scope. Cleanup actions are modelled as instances
+ * of the {@link ResourceCleanup} class, and, together, form a linked list. Depending on whether a scope
+ * is shared or confined, different implementations of this class will be used, see {@link ConfinedScope.ConfinedResourceList}
+ * and {@link SharedScope.SharedResourceList}.
*/
- static class SharedScope extends MemoryScope {
-
- static ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
+ public abstract static class ResourceList implements Runnable {
+ ResourceCleanup fst;
- final static int ALIVE = 0;
- final static int CLOSING = 1;
- final static int CLOSED = 2;
+ abstract void add(ResourceCleanup cleanup);
- int state = ALIVE;
+ abstract void cleanup();
- private static final VarHandle STATE;
-
- static {
- try {
- STATE = MethodHandles.lookup().findVarHandle(SharedScope.class, "state", int.class);
- } catch (Throwable ex) {
- throw new ExceptionInInitializerError(ex);
- }
- }
-
- SharedScope(Object ref, Runnable cleanupAction, Cleaner cleaner) {
- super(ref, cleanupAction, cleaner);
+ public final void run() {
+ cleanup(); // cleaner interop
}
- @Override
- public Thread ownerThread() {
- return null;
- }
-
- @Override
- public void checkValidState() {
- if (state != ALIVE) {
- throw ScopedAccessError.INSTANCE;
+ static void cleanup(ResourceCleanup first) {
+ ResourceCleanup current = first;
+ while (current != null) {
+ current.cleanup();
+ current = current.next;
}
}
- void justClose() {
- if (!STATE.compareAndSet(this, ALIVE, CLOSING)) {
- throw new IllegalStateException("Already closed");
- }
- boolean success = SCOPED_MEMORY_ACCESS.closeScope(this);
- STATE.setVolatile(this, success ? CLOSED : ALIVE);
- if (!success) {
- throw new IllegalStateException("Cannot close while another thread is accessing the segment");
- }
- }
+ public static abstract class ResourceCleanup {
+ ResourceCleanup next;
- @Override
- public boolean isAlive() {
- return (int)STATE.getVolatile(this) != CLOSED;
- }
- }
+ public abstract void cleanup();
- static class ScopeCleanable extends PhantomCleanable {
- final Cleaner cleaner;
- final Runnable cleanupAction;
+ final static ResourceCleanup CLOSED_LIST = new ResourceCleanup() {
+ @Override
+ public void cleanup() {
+ throw new IllegalStateException("This resource list has already been closed!");
+ }
+ };
- public ScopeCleanable(MemoryScope referent, Cleaner cleaner, Runnable cleanupAction) {
- super(referent, cleaner);
- this.cleaner = cleaner;
- this.cleanupAction = cleanupAction;
+ static ResourceCleanup ofRunnable(Runnable cleanupAction) {
+ return new ResourceCleanup() {
+ @Override
+ public void cleanup() {
+ cleanupAction.run();
+ }
+ };
+ }
}
- @Override
- protected void performCleanup() {
- cleanupAction.run();
- }
}
}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java
index 395f21f4d51..344131c7c8b 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java
@@ -28,6 +28,7 @@
import jdk.incubator.foreign.MemoryAddress;
import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM;
import jdk.internal.vm.annotation.ForceInline;
@@ -41,12 +42,12 @@
*/
public class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl {
- public static final MemorySegment EVERYTHING = makeNativeSegmentUnchecked(MemoryAddress.NULL, Long.MAX_VALUE, MemoryScope.DUMMY_CLEANUP_ACTION, null)
- .share()
- .withAccessModes(READ | WRITE);
+ public static final MemorySegment EVERYTHING = makeNativeSegmentUnchecked(MemoryAddress.NULL, Long.MAX_VALUE, null, MemoryScope.GLOBAL);
private static final Unsafe unsafe = Unsafe.getUnsafe();
+ public static final SegmentAllocator DEFAULT_ALLOCATOR = MemorySegment::allocateNative;
+
// The maximum alignment supported by malloc - typically 16 on
// 64-bit platforms and 8 on 32-bit platforms.
private final static long MAX_MALLOC_ALIGN = Unsafe.ADDRESS_SIZE == 4 ? 8 : 16;
@@ -68,7 +69,8 @@ NativeMemorySegmentImpl dup(long offset, long size, int mask, MemoryScope scope)
@Override
ByteBuffer makeByteBuffer() {
- return nioAccess.newDirectByteBuffer(min(), (int) this.length, null, this);
+ return nioAccess.newDirectByteBuffer(min(), (int) this.length, null,
+ scope == MemoryScope.GLOBAL ? null : this);
}
@Override
@@ -83,7 +85,8 @@ Object base() {
// factories
- public static MemorySegment makeNativeSegment(long bytesSize, long alignmentBytes) {
+ public static MemorySegment makeNativeSegment(long bytesSize, long alignmentBytes, MemoryScope scope) {
+ scope.checkValidStateSlow();
if (VM.isDirectMemoryPageAligned()) {
alignmentBytes = Math.max(alignmentBytes, nioAccess.pageSize());
}
@@ -98,12 +101,15 @@ public static MemorySegment makeNativeSegment(long bytesSize, long alignmentByte
unsafe.setMemory(buf, alignedSize, (byte)0);
}
long alignedBuf = Utils.alignUp(buf, alignmentBytes);
- MemoryScope scope = MemoryScope.createConfined(null, () -> {
+ AbstractMemorySegmentImpl segment = new NativeMemorySegmentImpl(buf, alignedSize,
+ defaultAccessModes(alignedSize), scope);
+ scope.addOrCleanupIfFail(new MemoryScope.ResourceList.ResourceCleanup() {
+ @Override
+ public void cleanup() {
unsafe.freeMemory(buf);
nioAccess.unreserveMemory(alignedSize, bytesSize);
- }, null);
- MemorySegment segment = new NativeMemorySegmentImpl(buf, alignedSize,
- defaultAccessModes(alignedSize), scope);
+ }
+ });
if (alignedSize != bytesSize) {
long delta = alignedBuf - buf;
segment = segment.asSlice(delta, bytesSize);
@@ -111,8 +117,12 @@ public static MemorySegment makeNativeSegment(long bytesSize, long alignmentByte
return segment;
}
- public static MemorySegment makeNativeSegmentUnchecked(MemoryAddress min, long bytesSize, Runnable cleanupAction, Object ref) {
- return new NativeMemorySegmentImpl(min.toRawLongValue(), bytesSize, defaultAccessModes(bytesSize),
- MemoryScope.createConfined(ref, cleanupAction == null ? MemoryScope.DUMMY_CLEANUP_ACTION : cleanupAction, null));
+ public static MemorySegment makeNativeSegmentUnchecked(MemoryAddress min, long bytesSize, Runnable cleanupAction, MemoryScope scope) {
+ scope.checkValidStateSlow();
+ AbstractMemorySegmentImpl segment = new NativeMemorySegmentImpl(min.toRawLongValue(), bytesSize, defaultAccessModes(bytesSize), scope);
+ if (cleanupAction != null) {
+ scope.addOnClose(cleanupAction);
+ }
+ return segment;
}
}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/SharedScope.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/SharedScope.java
new file mode 100644
index 00000000000..9f218a93738
--- /dev/null
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/SharedScope.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2021, 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
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package jdk.internal.foreign;
+
+import jdk.internal.misc.ScopedMemoryAccess;
+
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
+import java.lang.ref.Cleaner;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * A shared scope, which can be shared across multiple threads. Closing a shared scope has to ensure that
+ * (i) only one thread can successfully close a scope (e.g. in a close vs. close race) and that
+ * (ii) no other thread is accessing the memory associated with this scope while the segment is being
+ * closed. To ensure the former condition, a CAS is performed on the liveness bit. Ensuring the latter
+ * is trickier, and require a complex synchronization protocol (see {@link jdk.internal.misc.ScopedMemoryAccess}).
+ * Since it is the responsibility of the closing thread to make sure that no concurrent access is possible,
+ * checking the liveness bit upon access can be performed in plain mode, as in the confined case.
+ */
+class SharedScope extends MemoryScope {
+
+ private static final ScopedMemoryAccess SCOPED_MEMORY_ACCESS = ScopedMemoryAccess.getScopedMemoryAccess();
+
+ private final static int ALIVE = 0;
+ private final static int CLOSING = -1;
+ private final static int CLOSED = -2;
+ private final static int MAX_FORKS = Integer.MAX_VALUE;
+
+ private int state = ALIVE;
+
+ private static final VarHandle STATE;
+
+ static {
+ try {
+ STATE = MethodHandles.lookup().findVarHandle(jdk.internal.foreign.SharedScope.class, "state", int.class);
+ } catch (Throwable ex) {
+ throw new ExceptionInInitializerError(ex);
+ }
+ }
+
+ SharedScope(Object ref, Cleaner cleaner, boolean closeable) {
+ super(ref, cleaner, closeable, new SharedResourceList());
+ }
+
+ @Override
+ public Thread ownerThread() {
+ return null;
+ }
+
+ @Override
+ public void checkValidState() {
+ if (state < ALIVE) {
+ throw ScopedAccessError.INSTANCE;
+ }
+ }
+
+ @Override
+ public Handle acquire() {
+ if (!closeable) return DUMMY_LOCK;
+ int value;
+ do {
+ value = (int) STATE.getVolatile(this);
+ if (value < ALIVE) {
+ //segment is not alive!
+ throw new IllegalStateException("Already closed");
+ } else if (value == MAX_FORKS) {
+ //overflow
+ throw new IllegalStateException("Segment acquire limit exceeded");
+ }
+ } while (!STATE.compareAndSet(this, value, value + 1));
+ return new SharedHandle();
+ }
+
+ void justClose() {
+ int prevState = (int) STATE.compareAndExchange(this, ALIVE, CLOSING);
+ if (prevState < 0) {
+ throw new IllegalStateException("Already closed");
+ } else if (prevState != ALIVE) {
+ throw new IllegalStateException("Scope is acquired by " + prevState + " locks");
+ }
+ boolean success = SCOPED_MEMORY_ACCESS.closeScope(this);
+ STATE.setVolatile(this, success ? CLOSED : ALIVE);
+ if (!success) {
+ throw new IllegalStateException("Cannot close while another thread is accessing the segment");
+ }
+ }
+
+ @Override
+ public boolean isAlive() {
+ return (int) STATE.getVolatile(this) != CLOSED;
+ }
+
+ /**
+ * A shared resource list; this implementation has to handle add vs. add races, as well as add vs. cleanup races.
+ */
+ static class SharedResourceList extends ResourceList {
+
+ static final VarHandle FST;
+
+ static {
+ try {
+ FST = MethodHandles.lookup().findVarHandle(ResourceList.class, "fst", ResourceCleanup.class);
+ } catch (Throwable ex) {
+ throw new ExceptionInInitializerError();
+ }
+ }
+
+ @Override
+ void add(ResourceCleanup cleanup) {
+ while (true) {
+ ResourceCleanup prev = (ResourceCleanup) FST.getAcquire(this);
+ cleanup.next = prev;
+ ResourceCleanup newSegment = (ResourceCleanup) FST.compareAndExchangeRelease(this, prev, cleanup);
+ if (newSegment == ResourceCleanup.CLOSED_LIST) {
+ // too late
+ throw new IllegalStateException("Already closed");
+ } else if (newSegment == prev) {
+ return; //victory
+ }
+ // keep trying
+ }
+ }
+
+ void cleanup() {
+ // At this point we are only interested about add vs. close races - not close vs. close
+ // (because MemoryScope::justClose ensured that this thread won the race to close the scope).
+ // So, the only "bad" thing that could happen is that some other thread adds to this list
+ // while we're closing it.
+ if (FST.getAcquire(this) != ResourceCleanup.CLOSED_LIST) {
+ //ok now we're really closing down
+ ResourceCleanup prev = null;
+ while (true) {
+ prev = (ResourceCleanup) FST.getAcquire(this);
+ // no need to check for DUMMY, since only one thread can get here!
+ if (FST.weakCompareAndSetRelease(this, prev, ResourceCleanup.CLOSED_LIST)) {
+ break;
+ }
+ }
+ cleanup(prev);
+ } else {
+ throw new IllegalStateException("Attempt to cleanup an already closed resource list");
+ }
+ }
+ }
+
+ /**
+ * A shared resource scope handle; this implementation has to handle close vs. close races.
+ */
+ class SharedHandle implements Handle {
+ final AtomicBoolean released = new AtomicBoolean(false);
+
+ @Override
+ public void close() {
+ if (released.compareAndSet(false, true)) {
+ int value;
+ do {
+ value = (int) STATE.getVolatile(jdk.internal.foreign.SharedScope.this);
+ if (value <= ALIVE) {
+ //cannot get here - we can't close segment twice
+ throw new IllegalStateException("Already closed");
+ }
+ } while (!STATE.compareAndSet(jdk.internal.foreign.SharedScope.this, value, value - 1));
+ }
+ }
+ }
+}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/Utils.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/Utils.java
index 4aea34250e7..a6dd9924aa7 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/Utils.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/Utils.java
@@ -150,5 +150,4 @@ public static void checkLayoutType(MemoryLayout layout, Class extends MemoryLa
if (!layoutType.isInstance(layout))
throw new IllegalArgumentException("Expected a " + layoutType.getSimpleName() + ": " + layout);
}
-
}
diff --git a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java
index 9f8ca6b83bf..e13ab3e7ed4 100644
--- a/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java
+++ b/src/jdk.incubator.foreign/share/classes/jdk/internal/foreign/abi/Binding.java
@@ -28,7 +28,10 @@
import jdk.incubator.foreign.MemoryHandles;
import jdk.incubator.foreign.MemoryLayout;
import jdk.incubator.foreign.MemorySegment;
+import jdk.incubator.foreign.ResourceScope;
+import jdk.incubator.foreign.SegmentAllocator;
import jdk.internal.foreign.MemoryAddressImpl;
+import jdk.internal.foreign.MemoryScope;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
@@ -216,16 +219,105 @@ public abstract class Binding {
MH_BASE_ADDRESS = lookup.findVirtual(MemorySegment.class, "address",
methodType(MemoryAddress.class));
MH_COPY_BUFFER = lookup.findStatic(Binding.Copy.class, "copyBuffer",
- methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, SharedUtils.Allocator.class));
+ methodType(MemorySegment.class, MemorySegment.class, long.class, long.class, Context.class));
MH_ALLOCATE_BUFFER = lookup.findStatic(Binding.Allocate.class, "allocateBuffer",
- methodType(MemorySegment.class, long.class, long.class, SharedUtils.Allocator.class));
+ methodType(MemorySegment.class, long.class, long.class, Context.class));
MH_TO_SEGMENT = lookup.findStatic(Binding.ToSegment.class, "toSegment",
- methodType(MemorySegment.class, MemoryAddress.class, long.class, SharedUtils.Allocator.class));
+ methodType(MemorySegment.class, MemoryAddress.class, long.class, Context.class));
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
+ /**
+ * A binding context is used as an helper to carry out evaluation of certain bindings; for instance,
+ * it helps {@link Allocate} bindings, by providing the {@link SegmentAllocator} that should be used for
+ * the allocation operation, or {@link ToSegment} bindings, by providing the {@link ResourceScope} that
+ * should be used to create an unsafe struct from a memory address.
+ */
+ public static class Context implements AutoCloseable {
+ private final SegmentAllocator allocator;
+ private final ResourceScope scope;
+
+ private Context(SegmentAllocator allocator, ResourceScope scope) {
+ this.allocator = allocator;
+ this.scope = scope;
+ }
+
+ public SegmentAllocator allocator() {
+ return allocator;
+ }
+
+ public ResourceScope scope() {
+ return scope;
+ }
+
+ @Override
+ public void close() {
+ scope().close();
+ }
+
+ /**
+ * Create a binding context from given native scope.
+ */
+ public static Context ofBoundedAllocator(long size) {
+ ResourceScope scope = ResourceScope.ofConfined();
+ return new Context(SegmentAllocator.arenaBounded(size, scope), scope);
+ }
+
+ /**
+ * Create a binding context from given segment allocator. The resulting context will throw when
+ * the context's scope is accessed.
+ */
+ public static Context ofAllocator(SegmentAllocator allocator) {
+ return new Context(allocator, null) {
+ @Override
+ public ResourceScope scope() {
+ throw new UnsupportedOperationException();
+ }
+ };
+ }
+
+ /**
+ * Create a binding context from given scope. The resulting context will throw when
+ * the context's allocator is accessed.
+ */
+ public static Context ofScope() {
+ ResourceScope scope = ResourceScope.ofConfined();
+ return new Context(null, scope) {
+ @Override
+ public SegmentAllocator allocator() { throw new UnsupportedOperationException(); }
+ };
+ }
+
+ /**
+ * Dummy binding context. Throws exceptions when attempting to access allocator/scope, and its
+ * {@link #close()} is idempotent.
+ */
+ public static Context DUMMY = new Context(null, null) {
+ @Override
+ public SegmentAllocator allocator() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ResourceScope scope() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void close() {
+ // do nothing
+ }
+ };
+
+ /**
+ * Default binding context. Does not provide a resource scope, but provides a default {@link SegmentAllocator}
+ * which uses {@link MemorySegment#allocateNative(long, long)}.
+ */
+ public static Context DEFAULT = ofAllocator(MemorySegment::allocateNative);
+ }
+
enum Tag {
VM_STORE,
VM_LOAD,
@@ -253,7 +345,7 @@ public Tag tag() {
public abstract void verify(Deque> stack);
public abstract void interpret(Deque