diff --git a/src/java.base/share/classes/java/lang/foreign/Arena.java b/src/java.base/share/classes/java/lang/foreign/Arena.java index 457847d76c6..45cb46d33d6 100644 --- a/src/java.base/share/classes/java/lang/foreign/Arena.java +++ b/src/java.base/share/classes/java/lang/foreign/Arena.java @@ -43,9 +43,10 @@ import java.lang.foreign.MemorySegment.Scope; * to obtain native segments. *
* The simplest arena is the {@linkplain Arena#global() global arena}. The global arena - * features an unbounded lifetime. As such, native segments allocated with the global arena are always - * accessible and their backing regions of memory are never deallocated. Moreover, memory segments allocated with the - * global arena can be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} from any thread. + * features an unbounded lifetime. The scope of the global arena is the global scope. + * As such, native segments allocated with the global arena are always accessible and their backing regions + * of memory are never deallocated. + * Moreover, memory segments allocated with the global arena can be {@linkplain MemorySegment#isAccessibleBy(Thread) accessed} from any thread. * {@snippet lang = java: * MemorySegment segment = Arena.global().allocate(100, 1); // @highlight regex='global()' * ... @@ -53,7 +54,8 @@ import java.lang.foreign.MemorySegment.Scope; *} *
* Alternatively, clients can obtain an {@linkplain Arena#ofAuto() automatic arena}, that is an arena - * which features a bounded lifetime that is managed, automatically, by the garbage collector. As such, the regions + * which features a bounded lifetime that is managed, automatically, by the garbage collector. The scope + * of an automatic arena is an automatic scope. As such, the regions * of memory backing memory segments allocated with the automatic arena are deallocated at some unspecified time * after the automatic arena (and all the segments allocated by it) becomes * unreachable, as shown below: @@ -67,11 +69,9 @@ import java.lang.foreign.MemorySegment.Scope; * Rather than leaving deallocation in the hands of the Java runtime, clients will often wish to exercise control over * the timing of deallocation for regions of memory that back memory segments. Two kinds of arenas support this, * namely {@linkplain #ofConfined() confined} and {@linkplain #ofShared() shared} arenas. They both feature - * bounded lifetimes that are managed manually. For instance, the lifetime of a confined arena starts when the confined - * arena is created, and ends when the confined arena is {@linkplain #close() closed}. As a result, the regions of memory - * backing memory segments allocated with a confined arena are deallocated when the confined arena is closed. - * When this happens, all the segments allocated with the confined arena are invalidated, and subsequent access - * operations on these segments will fail {@link IllegalStateException}: + * bounded lifetimes that are managed manually. For instance, when a confined arena is {@linkplain #close() closed} + * successfully, its scope is {@linkplain Scope#isAlive() invalidated}. As a result, all the memory segments allocated + * by the arena can no longer be accessed, and their regions of memory are deallocated: * * {@snippet lang = java: * MemorySegment segment = null; @@ -219,7 +219,7 @@ public interface Arena extends SegmentAllocator, AutoCloseable { */ static Arena global() { class Holder { - static final Arena GLOBAL = MemorySessionImpl.GLOBAL.asArena(); + static final Arena GLOBAL = MemorySessionImpl.GLOBAL_SESSION.asArena(); } return Holder.GLOBAL; } diff --git a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java index 9adca13b774..747e2b9270d 100644 --- a/src/java.base/share/classes/java/lang/foreign/Linker.java +++ b/src/java.base/share/classes/java/lang/foreign/Linker.java @@ -365,7 +365,7 @@ import java.util.stream.Stream; * * The size of the segment returned by the {@code malloc} downcall method handle is * zero. Moreover, the scope of the - * returned segment is a fresh scope that is always alive. To provide safe access to the segment, we must, + * returned segment is the global scope. To provide safe access to the segment, we must, * unsafely, resize the segment to the desired size (100, in this case). It might also be desirable to * attach the segment to some existing {@linkplain Arena arena}, so that the lifetime of the region of memory * backing the segment can be managed automatically, as for any other native segment created directly from Java code. @@ -563,7 +563,7 @@ public sealed interface Linker permits AbstractLinker { *
* Moreover, if the provided function descriptor's return layout is an {@linkplain AddressLayout address layout}, * invoking the returned method handle will return a native segment associated with - * a fresh scope that is always alive. Under normal conditions, the size of the returned segment is {@code 0}. + * the global scope. Under normal conditions, the size of the returned segment is {@code 0}. * However, if the function descriptor's return layout has a {@linkplain AddressLayout#targetLayout() target layout} * {@code T}, then the size of the returned segment is set to {@code T.byteSize()}. *
@@ -602,7 +602,7 @@ public sealed interface Linker permits AbstractLinker { * upcall stub segment will be deallocated when the provided confined arena is {@linkplain Arena#close() closed}. *
* An upcall stub argument whose corresponding layout is an {@linkplain AddressLayout address layout} - * is a native segment associated with a fresh scope that is always alive. + * is a native segment associated with the global scope. * Under normal conditions, the size of this segment argument is {@code 0}. * However, if the address layout has a {@linkplain AddressLayout#targetLayout() target layout} {@code T}, then the size of the * segment argument is set to {@code T.byteSize()}. diff --git a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java index db656ada136..e527d7f203a 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java @@ -492,8 +492,8 @@ public sealed interface MemoryLayout permits SequenceLayout, GroupLayout, Paddin * *
* If the selected layout is an {@linkplain AddressLayout address layout}, calling {@link VarHandle#get(Object...)} - * on the returned var handle will return a new memory segment. The segment is associated with a fresh scope that is - * always alive. Moreover, the size of the segment depends on whether the address layout has a + * on the returned var handle will return a new memory segment. The segment is associated with the global scope. + * Moreover, the size of the segment depends on whether the address layout has a * {@linkplain AddressLayout#targetLayout() target layout}. More specifically: *
@@ -625,7 +625,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * MemorySegment cleanupSegment = MemorySegment.ofAddress(this.address()) * .reinterpret(byteSize()); * } - * That is, the cleanup action receives a segment that is associated with a fresh scope that is always alive, + * That is, the cleanup action receives a segment that is associated with the global scope, * and is accessible from any thread. The size of the segment accepted by the cleanup action is {@link #byteSize()}. *
* This method is restricted. @@ -664,7 +664,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * MemorySegment cleanupSegment = MemorySegment.ofAddress(this.address()) * .reinterpret(newSize); * } - * That is, the cleanup action receives a segment that is associated with a fresh scope that is always alive, + * That is, the cleanup action receives a segment that is associated with the global scope, * and is accessible from any thread. The size of the segment accepted by the cleanup action is {@code newSize}. *
* This method is restricted. @@ -1155,11 +1155,9 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { *
* If the provided buffer has been obtained by calling {@link #asByteBuffer()} on a memory segment whose * {@linkplain Scope scope} is {@code S}, the returned segment will be associated with the - * same scope {@code S}. Otherwise, the scope of the returned segment is a fresh scope that is always alive. - *
- * The scope associated with the returned segment keeps the provided buffer reachable. As such, if - * the provided buffer is a direct buffer, its backing memory region will not be deallocated as long as the - * returned segment (or any of its slices) are kept reachable. + * same scope {@code S}. Otherwise, the scope of the returned segment is an automatic scope that keeps the provided + * buffer reachable. As such, if the provided buffer is a direct buffer, its backing memory region will not be + * deallocated as long as the returned segment (or any of its slices) are kept reachable. * * @param buffer the buffer instance to be turned into a new memory segment. * @return a memory segment, derived from the given buffer instance. @@ -1174,7 +1172,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a heap segment backed by the on-heap region of memory that holds the given byte array. - * The scope of the returned segment is a fresh scope that is always alive, and keeps the given array reachable. + * The scope of the returned segment is an automatic scope that keeps the given array reachable. * The returned segment is always accessible, from any thread. Its {@link #address()} is set to zero. * * @param byteArray the primitive array backing the heap memory segment. @@ -1186,7 +1184,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a heap segment backed by the on-heap region of memory that holds the given char array. - * The scope of the returned segment is a fresh scope that is always alive, and keeps the given array reachable. + * The scope of the returned segment is an automatic scope that keeps the given array reachable. * The returned segment is always accessible, from any thread. Its {@link #address()} is set to zero. * * @param charArray the primitive array backing the heap segment. @@ -1198,7 +1196,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a heap segment backed by the on-heap region of memory that holds the given short array. - * The scope of the returned segment is a fresh scope that is always alive, and keeps the given array reachable. + * The scope of the returned segment is an automatic scope that keeps the given array reachable. * The returned segment is always accessible, from any thread. Its {@link #address()} is set to zero. * * @param shortArray the primitive array backing the heap segment. @@ -1210,7 +1208,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a heap segment backed by the on-heap region of memory that holds the given int array. - * The scope of the returned segment is a fresh scope that is always alive, and keeps the given array reachable. + * The scope of the returned segment is an automatic scope that keeps the given array reachable. * The returned segment is always accessible, from any thread. Its {@link #address()} is set to zero. * * @param intArray the primitive array backing the heap segment. @@ -1222,7 +1220,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a heap segment backed by the on-heap region of memory that holds the given float array. - * The scope of the returned segment is a fresh scope that is always alive, and keeps the given array reachable. + * The scope of the returned segment is an automatic scope that keeps the given array reachable. * The returned segment is always accessible, from any thread. Its {@link #address()} is set to zero. * * @param floatArray the primitive array backing the heap segment. @@ -1234,7 +1232,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a heap segment backed by the on-heap region of memory that holds the given long array. - * The scope of the returned segment is a fresh scope that is always alive, and keeps the given array reachable. + * The scope of the returned segment is an automatic scope that keeps the given array reachable. * The returned segment is always accessible, from any thread. Its {@link #address()} is set to zero. * * @param longArray the primitive array backing the heap segment. @@ -1246,7 +1244,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a heap segment backed by the on-heap region of memory that holds the given double array. - * The scope of the returned segment is a fresh scope that is always alive, and keeps the given array reachable. + * The scope of the returned segment is an automatic scope that keeps the given array reachable. * The returned segment is always accessible, from any thread. Its {@link #address()} is set to zero. * * @param doubleArray the primitive array backing the heap segment. @@ -1263,7 +1261,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Creates a zero-length native segment from the given {@linkplain #address() address value}. - * The returned segment is associated with a scope that is always alive, and is accessible from any thread. + * The returned segment is associated with the global scope, and is accessible from any thread. *
* On 32-bit platforms, the given address value will be normalized such that the * highest-order ("leftmost") 32 bits of the {@link MemorySegment#address() address} @@ -1642,7 +1640,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Reads an address from this segment at the given offset, with the given layout. The read address is wrapped in - * a native segment, associated with a fresh scope that is always alive. Under normal conditions, + * a native segment, associated with the global scope. Under normal conditions, * the size of the returned segment is {@code 0}. However, if the provided address layout has a * {@linkplain AddressLayout#targetLayout() target layout} {@code T}, then the size of the returned segment * is set to {@code T.byteSize()}. @@ -1994,7 +1992,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * Reads an address from this segment at the given at the given index, scaled by the given layout size. The read address is wrapped in - * a native segment, associated with a fresh scope that is always alive. Under normal conditions, + * a native segment, associated with the global scope. Under normal conditions, * the size of the returned segment is {@code 0}. However, if the provided address layout has a * {@linkplain AddressLayout#targetLayout() target layout} {@code T}, then the size of the returned segment * is set to {@code T.byteSize()}. @@ -2192,11 +2190,38 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { /** * A scope models the lifetime of all the memory segments associated with it. That is, a memory segment - * cannot be accessed if its associated scope is not {@linkplain #isAlive() alive}. A new scope is typically - * obtained indirectly, by creating a new {@linkplain Arena arena}. + * cannot be accessed if its associated scope is not {@linkplain #isAlive() alive}. Scope instances can be compared + * for equality. That is, two scopes are considered {@linkplain #equals(Object) equal} if they denote the same lifetime. *
- * Scope instances can be compared for equality. That is, two scopes - * are considered {@linkplain #equals(Object)} if they denote the same lifetime. + * The lifetime of a memory segment can be either unbounded or bounded. An unbounded lifetime + * is modelled with the global scope. The global scope is always {@link #isAlive() alive}. As such, a segment + * associated with the global scope features trivial temporal bounds, and is always accessible. + * Segments associated with the global scope are: + *
+ * Conversely, a bounded lifetime is modelled with a segment scope that can be invalidated, either {@link Arena#close() explicitly}, + * or automatically, by the garbage collector. A segment scope that is invalidated automatically is an automatic scope. + * An automatic scope is always {@link #isAlive() alive} as long as it is reachable. + * Segments associated with an automatic scope are: + *
* Libraries associated with a class loader are unloaded when the class loader becomes * unreachable. The symbol lookup - * returned by this method is associated with a fresh {@linkplain MemorySegment.Scope scope} which keeps the caller's + * returned by this method is associated with an automatic {@linkplain MemorySegment.Scope scope} which keeps the caller's * class loader reachable. Therefore, libraries associated with the caller's class loader are kept loaded * (and their symbols available) as long as a loader lookup for that class loader, or any of the segments * obtained by it, is reachable. @@ -189,7 +189,7 @@ public interface SymbolLookup { if ((loader == null || loader instanceof BuiltinClassLoader)) { loaderArena = Arena.global(); } else { - MemorySessionImpl session = MemorySessionImpl.heapSession(loader); + MemorySessionImpl session = MemorySessionImpl.createHeap(loader); loaderArena = session.asArena(); } return name -> { diff --git a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java index 8b674a90c33..bf8b1e79819 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java @@ -55,6 +55,7 @@ import jdk.internal.reflect.Reflection; import jdk.internal.util.ArraysSupport; import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.ForceInline; +import sun.nio.ch.DirectBuffer; import static java.lang.foreign.ValueLayout.JAVA_BYTE; @@ -556,7 +557,7 @@ public abstract sealed class AbstractMemorySegmentImpl if (bufferSegment != null) { bufferScope = bufferSegment.scope; } else { - bufferScope = MemorySessionImpl.heapSession(bb); + bufferScope = MemorySessionImpl.createHeap(bufferRef(bb)); } if (base != null) { if (base instanceof byte[]) { @@ -584,6 +585,17 @@ public abstract sealed class AbstractMemorySegmentImpl } } + private static Object bufferRef(Buffer buffer) { + if (buffer instanceof DirectBuffer directBuffer) { + // direct buffer, return either the buffer attachment (for slices and views), or the buffer itself + return directBuffer.attachment() != null ? + directBuffer.attachment() : directBuffer; + } else { + // heap buffer, return the underlying array + return NIO_ACCESS.getBufferBase(buffer); + } + } + private static int getScaleFactor(Buffer buffer) { if (buffer instanceof ByteBuffer) { return 0; diff --git a/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java b/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java index 2d78a66eb6f..c075778b5ee 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java +++ b/src/java.base/share/classes/jdk/internal/foreign/GlobalSession.java @@ -25,20 +25,23 @@ package jdk.internal.foreign; +import jdk.internal.access.JavaNioAccess; +import jdk.internal.access.SharedSecrets; import jdk.internal.vm.annotation.ForceInline; +import sun.nio.ch.DirectBuffer; + +import java.nio.Buffer; +import java.util.Objects; /** * The global, non-closeable, shared session. Similar to a shared session, but its {@link #close()} method throws unconditionally. * Adding new resources to the global session, does nothing: as the session can never become not-alive, there is nothing to track. * Acquiring and or releasing a memory session similarly does nothing. */ -final class GlobalSession extends MemorySessionImpl { +non-sealed class GlobalSession extends MemorySessionImpl { - final Object ref; - - public GlobalSession(Object ref) { + public GlobalSession() { super(null, null); - this.ref = ref; } @Override @@ -67,4 +70,32 @@ final class GlobalSession extends MemorySessionImpl { public void justClose() { throw nonCloseable(); } + + /** + * This is a global session that wraps a heap object. Possible objects are: Java arrays, buffers and + * class loaders. Objects of two heap sessions are compared by identity. That is, if the wrapped object is the same, + * then the resulting heap sessions are also considered equals. We do not compare the objects using + * {@link Object#equals(Object)}, as that would be problematic when comparing buffers, whose equality and + * hash codes are content-dependent. + */ + static class HeapSession extends GlobalSession { + + final Object ref; + + public HeapSession(Object ref) { + super(); + this.ref = Objects.requireNonNull(ref); + } + + @Override + public boolean equals(Object obj) { + return obj instanceof HeapSession session && + ref == session.ref; + } + + @Override + public int hashCode() { + return System.identityHashCode(ref); + } + } } diff --git a/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java index dce223c4683..e313780a348 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/MappedMemorySegmentImpl.java @@ -48,8 +48,7 @@ final class MappedMemorySegmentImpl extends NativeMemorySegmentImpl { @Override ByteBuffer makeByteBuffer() { - return NIO_ACCESS.newMappedByteBuffer(unmapper, min, (int)length, null, - scope == MemorySessionImpl.GLOBAL ? null : this); + return NIO_ACCESS.newMappedByteBuffer(unmapper, min, (int)length, null, this); } @Override diff --git a/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java b/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java index c8c7dd2e543..33bac79999f 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/MemorySessionImpl.java @@ -33,6 +33,8 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.ref.Cleaner; import java.util.Objects; + +import jdk.internal.foreign.GlobalSession.HeapSession; import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.vm.annotation.ForceInline; @@ -59,10 +61,10 @@ public abstract sealed class MemorySessionImpl static final VarHandle STATE; static final int MAX_FORKS = Integer.MAX_VALUE; - public static final MemorySessionImpl GLOBAL = new GlobalSession(null); - static final ScopedMemoryAccess.ScopedAccessError ALREADY_CLOSED = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::alreadyClosed); static final ScopedMemoryAccess.ScopedAccessError WRONG_THREAD = new ScopedMemoryAccess.ScopedAccessError(MemorySessionImpl::wrongThread); + // This is the session of all zero-length memory segments + public static final MemorySessionImpl GLOBAL_SESSION = new GlobalSession(); final ResourceList resourceList; final Thread owner; @@ -143,6 +145,10 @@ public abstract sealed class MemorySessionImpl return new ImplicitSession(cleaner); } + public static MemorySessionImpl createHeap(Object ref) { + return new HeapSession(ref); + } + public abstract void release0(); public abstract void acquire0(); @@ -230,10 +236,6 @@ public abstract sealed class MemorySessionImpl abstract void justClose(); - public static MemorySessionImpl heapSession(Object ref) { - return new GlobalSession(ref); - } - /** * A list of all cleanup actions associated with a memory session. Cleanup actions are modelled as instances * of the {@link ResourceCleanup} class, and, together, form a linked list. Depending on whether a session diff --git a/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java index 5339d3fb8b7..5427c9f810f 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/NativeMemorySegmentImpl.java @@ -69,8 +69,7 @@ sealed class NativeMemorySegmentImpl extends AbstractMemorySegmentImpl permits M @Override ByteBuffer makeByteBuffer() { - return NIO_ACCESS.newDirectByteBuffer(min, (int) this.length, null, - scope == MemorySessionImpl.GLOBAL ? null : this); + return NIO_ACCESS.newDirectByteBuffer(min, (int) this.length, null, this); } @Override diff --git a/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java b/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java index c4f1d5a8a0f..9e9d60ecf89 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java +++ b/src/java.base/share/classes/jdk/internal/foreign/SegmentFactories.java @@ -77,7 +77,7 @@ public class SegmentFactories { @ForceInline public static MemorySegment makeNativeSegmentUnchecked(long min, long byteSize) { ensureInitialized(); - return new NativeMemorySegmentImpl(min, byteSize, false, new GlobalSession(null)); + return new NativeMemorySegmentImpl(min, byteSize, false, MemorySessionImpl.GLOBAL_SESSION); } public static MemorySegment fromArray(byte[] arr) { @@ -85,7 +85,7 @@ public class SegmentFactories { Objects.requireNonNull(arr); long byteSize = (long)arr.length * Unsafe.ARRAY_BYTE_INDEX_SCALE; return new OfByte(Unsafe.ARRAY_BYTE_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + MemorySessionImpl.createHeap(arr)); } public static MemorySegment fromArray(short[] arr) { @@ -93,7 +93,7 @@ public class SegmentFactories { Objects.requireNonNull(arr); long byteSize = (long)arr.length * Unsafe.ARRAY_SHORT_INDEX_SCALE; return new OfShort(Unsafe.ARRAY_SHORT_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + MemorySessionImpl.createHeap(arr)); } public static MemorySegment fromArray(int[] arr) { @@ -101,7 +101,7 @@ public class SegmentFactories { Objects.requireNonNull(arr); long byteSize = (long)arr.length * Unsafe.ARRAY_INT_INDEX_SCALE; return new OfInt(Unsafe.ARRAY_INT_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + MemorySessionImpl.createHeap(arr)); } public static MemorySegment fromArray(char[] arr) { @@ -109,7 +109,7 @@ public class SegmentFactories { Objects.requireNonNull(arr); long byteSize = (long)arr.length * Unsafe.ARRAY_CHAR_INDEX_SCALE; return new OfChar(Unsafe.ARRAY_CHAR_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + MemorySessionImpl.createHeap(arr)); } public static MemorySegment fromArray(float[] arr) { @@ -117,7 +117,7 @@ public class SegmentFactories { Objects.requireNonNull(arr); long byteSize = (long)arr.length * Unsafe.ARRAY_FLOAT_INDEX_SCALE; return new OfFloat(Unsafe.ARRAY_FLOAT_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + MemorySessionImpl.createHeap(arr)); } public static MemorySegment fromArray(double[] arr) { @@ -125,7 +125,7 @@ public class SegmentFactories { Objects.requireNonNull(arr); long byteSize = (long)arr.length * Unsafe.ARRAY_DOUBLE_INDEX_SCALE; return new OfDouble(Unsafe.ARRAY_DOUBLE_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + MemorySessionImpl.createHeap(arr)); } public static MemorySegment fromArray(long[] arr) { @@ -133,7 +133,7 @@ public class SegmentFactories { Objects.requireNonNull(arr); long byteSize = (long)arr.length * Unsafe.ARRAY_LONG_INDEX_SCALE; return new OfLong(Unsafe.ARRAY_LONG_BASE_OFFSET, arr, byteSize, false, - MemorySessionImpl.heapSession(arr)); + MemorySessionImpl.createHeap(arr)); } public static MemorySegment allocateSegment(long byteSize, long byteAlignment, MemorySessionImpl sessionImpl, diff --git a/test/jdk/java/foreign/TestScope.java b/test/jdk/java/foreign/TestScope.java new file mode 100644 index 00000000000..337dce7d0ca --- /dev/null +++ b/test/jdk/java/foreign/TestScope.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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. + */ + +/* + * @test + * @run testng/othervm --enable-native-access=ALL-UNNAMED TestScope + */ + +import org.testng.annotations.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; + +import static org.testng.Assert.*; + +public class TestScope { + + static { + System.loadLibrary("LookupTest"); + } + + @Test + public void testDifferentArrayScope() { + MemorySegment.Scope scope1 = MemorySegment.ofArray(new byte[10]).scope(); + MemorySegment.Scope scope2 = MemorySegment.ofArray(new byte[10]).scope(); + assertNotEquals(scope1, scope2); + } + + @Test + public void testDifferentBufferScope() { + MemorySegment.Scope scope1 = MemorySegment.ofBuffer(ByteBuffer.allocateDirect(10)).scope(); + MemorySegment.Scope scope2 = MemorySegment.ofBuffer(ByteBuffer.allocateDirect(10)).scope(); + assertNotEquals(scope1, scope2); + } + + @Test + public void testDifferentArenaScope() { + MemorySegment.Scope scope1 = Arena.ofAuto().allocate(10).scope(); + MemorySegment.Scope scope2 = Arena.ofAuto().allocate(10).scope(); + assertNotEquals(scope1, scope2); + } + + @Test + public void testSameArrayScope() { + byte[] arr = new byte[10]; + assertEquals(MemorySegment.ofArray(arr).scope(), MemorySegment.ofArray(arr).scope()); + ByteBuffer buf = ByteBuffer.wrap(arr); + assertEquals(MemorySegment.ofArray(arr).scope(), MemorySegment.ofBuffer(buf).scope()); + testDerivedBufferScope(MemorySegment.ofArray(arr)); + } + + @Test + public void testSameBufferScope() { + ByteBuffer buf = ByteBuffer.allocateDirect(10); + assertEquals(MemorySegment.ofBuffer(buf).scope(), MemorySegment.ofBuffer(buf).scope()); + testDerivedBufferScope(MemorySegment.ofBuffer(buf)); + } + + @Test + public void testSameArenaScope() { + try (Arena arena = Arena.ofConfined()) { + MemorySegment segment1 = arena.allocate(10); + MemorySegment segment2 = arena.allocate(10); + assertEquals(segment1.scope(), segment2.scope()); + testDerivedBufferScope(segment1); + } + } + + @Test + public void testSameNativeScope() { + MemorySegment segment1 = MemorySegment.ofAddress(42); + MemorySegment segment2 = MemorySegment.ofAddress(43); + assertEquals(segment1.scope(), segment2.scope()); + assertEquals(segment1.scope(), segment2.reinterpret(10).scope()); + assertEquals(segment1.scope(), Arena.global().scope()); + testDerivedBufferScope(segment1.reinterpret(10)); + } + + @Test + public void testSameLookupScope() { + SymbolLookup loaderLookup = SymbolLookup.loaderLookup(); + MemorySegment segment1 = loaderLookup.find("f").get(); + MemorySegment segment2 = loaderLookup.find("c").get(); + assertEquals(segment1.scope(), segment2.scope()); + testDerivedBufferScope(segment1.reinterpret(10)); + } + + void testDerivedBufferScope(MemorySegment segment) { + ByteBuffer buffer = segment.asByteBuffer(); + MemorySegment.Scope expectedScope = segment.scope(); + assertEquals(MemorySegment.ofBuffer(buffer).scope(), expectedScope); + // buffer slices should have same scope + ByteBuffer slice = buffer.slice(0, 2); + assertEquals(expectedScope, MemorySegment.ofBuffer(slice).scope()); + // buffer views should have same scope + IntBuffer view = buffer.asIntBuffer(); + assertEquals(expectedScope, MemorySegment.ofBuffer(view).scope()); + } +}