diff --git a/src/java.base/share/classes/jdk/internal/foreign/BufferStack.java b/src/java.base/share/classes/jdk/internal/foreign/BufferStack.java
new file mode 100644
index 0000000000000..dbf21601c53ac
--- /dev/null
+++ b/src/java.base/share/classes/jdk/internal/foreign/BufferStack.java
@@ -0,0 +1,214 @@
+/*
+ * Copyright (c) 2025, 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.CarrierThreadLocal;
+import jdk.internal.vm.annotation.ForceInline;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
+import java.lang.ref.Reference;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.function.Consumer;
+
+/**
+ * A buffer stack that allows efficient reuse of memory segments. This is useful in cases
+ * where temporary memory is needed.
+ *
+ * Use the factories {@code BufferStack.of(...)} to create new instances of this class.
+ *
+ * Note: The reused segments are neither zeroed out before nor after re-use.
+ */
+public final class BufferStack {
+
+ private final long byteSize;
+ private final long byteAlignment;
+ private final CarrierThreadLocal tl;
+
+ private BufferStack(long byteSize, long byteAlignment) {
+ this.byteSize = byteSize;
+ this.byteAlignment = byteAlignment;
+ this.tl = new CarrierThreadLocal<>() {
+ @Override
+ protected BufferStack.PerThread initialValue() {
+ return BufferStack.PerThread.of(byteSize, byteAlignment);
+ }
+ };
+ }
+
+ /**
+ * {@return a new Arena that tries to provide {@code byteSize} and {@code byteAlignment}
+ * allocations by recycling the BufferStack's internal memory}
+ *
+ * @param byteSize to be reserved from this BufferStack's internal memory
+ * @param byteAlignment to be used for reservation
+ */
+ @ForceInline
+ public Arena pushFrame(long byteSize, long byteAlignment) {
+ return tl.get().pushFrame(byteSize, byteAlignment);
+ }
+
+ /**
+ * {@return a new Arena that tries to provide {@code byteSize}
+ * allocations by recycling the BufferStack's internal memory}
+ *
+ * @param byteSize to be reserved from this BufferStack's internal memory
+ */
+ @ForceInline
+ public Arena pushFrame(long byteSize) {
+ return pushFrame(byteSize, 1);
+ }
+
+ /**
+ * {@return a new Arena that tries to provide {@code layout}
+ * allocations by recycling the BufferStack's internal memory}
+ *
+ * @param layout for which to reserve internal memory
+ */
+ @ForceInline
+ public Arena pushFrame(MemoryLayout layout) {
+ return pushFrame(layout.byteSize(), layout.byteAlignment());
+ }
+
+ @Override
+ public String toString() {
+ return "BufferStack[byteSize=" + byteSize + ", byteAlignment=" + byteAlignment + "]";
+ }
+
+ private record PerThread(ReentrantLock lock,
+ Arena arena,
+ SlicingAllocator stack,
+ CleanupAction cleanupAction) {
+
+ @ForceInline
+ public Arena pushFrame(long size, long byteAlignment) {
+ boolean needsLock = Thread.currentThread().isVirtual() && !lock.isHeldByCurrentThread();
+ if (needsLock && !lock.tryLock()) {
+ // Rare: another virtual thread on the same carrier competed for acquisition.
+ return Arena.ofConfined();
+ }
+ if (!stack.canAllocate(size, byteAlignment)) {
+ if (needsLock) lock.unlock();
+ return Arena.ofConfined();
+ }
+ return new Frame(needsLock, size, byteAlignment);
+ }
+
+ static PerThread of(long byteSize, long byteAlignment) {
+ final Arena arena = Arena.ofAuto();
+ return new PerThread(new ReentrantLock(),
+ arena,
+ new SlicingAllocator(arena.allocate(byteSize, byteAlignment)),
+ new CleanupAction(arena));
+ }
+
+ private record CleanupAction(Arena arena) implements Consumer {
+ @Override
+ public void accept(MemorySegment memorySegment) {
+ Reference.reachabilityFence(arena);
+ }
+ }
+
+ private final class Frame implements Arena {
+
+ private final boolean locked;
+ private final long parentOffset;
+ private final long topOfStack;
+ private final Arena confinedArena;
+ private final SegmentAllocator frame;
+
+ @SuppressWarnings("restricted")
+ @ForceInline
+ public Frame(boolean locked, long byteSize, long byteAlignment) {
+ this.locked = locked;
+ this.parentOffset = stack.currentOffset();
+ final MemorySegment frameSegment = stack.allocate(byteSize, byteAlignment);
+ this.topOfStack = stack.currentOffset();
+ this.confinedArena = Arena.ofConfined();
+ // The cleanup action will keep the original automatic `arena` (from which
+ // the reusable segment is first allocated) alive even if this Frame
+ // becomes unreachable but there are reachable segments still alive.
+ this.frame = new SlicingAllocator(frameSegment.reinterpret(confinedArena, cleanupAction));
+ }
+
+ @ForceInline
+ private void assertOrder() {
+ if (topOfStack != stack.currentOffset())
+ throw new IllegalStateException("Out of order access: frame not top-of-stack");
+ }
+
+ @ForceInline
+ @Override
+ @SuppressWarnings("restricted")
+ public MemorySegment allocate(long byteSize, long byteAlignment) {
+ // Make sure we are on the right thread and not closed
+ MemorySessionImpl.toMemorySession(confinedArena).checkValidState();
+ return frame.allocate(byteSize, byteAlignment);
+ }
+
+ @ForceInline
+ @Override
+ public MemorySegment.Scope scope() {
+ return confinedArena.scope();
+ }
+
+ @ForceInline
+ @Override
+ public void close() {
+ assertOrder();
+ // the Arena::close method is called "early" as it checks thread
+ // confinement and crucially before any mutation of the internal
+ // state takes place.
+ confinedArena.close();
+ stack.resetTo(parentOffset);
+ if (locked) {
+ lock.unlock();
+ }
+ }
+ }
+ }
+
+ public static BufferStack of(long byteSize, long byteAlignment) {
+ if (byteSize < 0) {
+ throw new IllegalArgumentException("Negative byteSize: " + byteSize);
+ }
+ if (byteAlignment < 0) {
+ throw new IllegalArgumentException("Negative byteAlignment: " + byteAlignment);
+ }
+ return new BufferStack(byteSize, byteAlignment);
+ }
+
+ public static BufferStack of(long byteSize) {
+ return new BufferStack(byteSize, 1);
+ }
+
+ public static BufferStack of(MemoryLayout layout) {
+ // Implicit null check
+ return of(layout.byteSize(), layout.byteAlignment());
+ }
+}
diff --git a/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java b/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java
index db7d476053e54..6b1a071c2af07 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/SlicingAllocator.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2021, 2025, 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
@@ -38,6 +38,22 @@ public SlicingAllocator(MemorySegment segment) {
this.segment = segment;
}
+ public long currentOffset() {
+ return sp;
+ }
+
+ public void resetTo(long offset) {
+ if (offset < 0 || offset > sp)
+ throw new IllegalArgumentException(String.format("offset %d should be in [0, %d] ", offset, sp));
+ this.sp = offset;
+ }
+
+ public boolean canAllocate(long byteSize, long byteAlignment) {
+ long min = segment.address();
+ long start = Utils.alignUp(min + sp, byteAlignment) - min;
+ return start + byteSize <= segment.byteSize();
+ }
+
MemorySegment trySlice(long byteSize, long byteAlignment) {
long min = segment.address();
long start = Utils.alignUp(min + sp, byteAlignment) - min;
diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java
index 125730560a2e6..37200598d5b07 100644
--- a/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java
+++ b/src/java.base/share/classes/jdk/internal/foreign/abi/SharedUtils.java
@@ -24,9 +24,9 @@
*/
package jdk.internal.foreign.abi;
-import jdk.internal.access.JavaLangAccess;
import jdk.internal.access.JavaLangInvokeAccess;
import jdk.internal.access.SharedSecrets;
+import jdk.internal.foreign.BufferStack;
import jdk.internal.foreign.CABI;
import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory;
import jdk.internal.foreign.abi.aarch64.linux.LinuxAArch64Linker;
@@ -390,26 +390,12 @@ static long pickChunkOffset(long chunkOffset, long byteWidth, int chunkWidth) {
: chunkOffset;
}
- public static Arena newBoundedArena(long size) {
- return new Arena() {
- final Arena arena = Arena.ofConfined();
- final SegmentAllocator slicingAllocator = SegmentAllocator.slicingAllocator(arena.allocate(size));
-
- @Override
- public Scope scope() {
- return arena.scope();
- }
+ private static final int LINKER_STACK_SIZE = Integer.getInteger("jdk.internal.foreign.LINKER_STACK_SIZE", 256);
+ private static final BufferStack LINKER_STACK = BufferStack.of(LINKER_STACK_SIZE, 1);
- @Override
- public void close() {
- arena.close();
- }
-
- @Override
- public MemorySegment allocate(long byteSize, long byteAlignment) {
- return slicingAllocator.allocate(byteSize, byteAlignment);
- }
- };
+ @ForceInline
+ public static Arena newBoundedArena(long size) {
+ return LINKER_STACK.pushFrame(size, 8);
}
public static Arena newEmptyArena() {
diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt
index c17d8dbe559ec..3edf4a9d18e9a 100644
--- a/test/jdk/ProblemList.txt
+++ b/test/jdk/ProblemList.txt
@@ -784,6 +784,8 @@ jdk/jfr/jvm/TestWaste.java 8282427 generic-
# jdk_foreign
+java/foreign/TestBufferStackStress.java 8350455 macosx-all
+
############################################################################
# Client manual tests
diff --git a/test/jdk/java/foreign/TestBufferStack.java b/test/jdk/java/foreign/TestBufferStack.java
new file mode 100644
index 0000000000000..f7bf67bfb50f0
--- /dev/null
+++ b/test/jdk/java/foreign/TestBufferStack.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2025, 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
+ * @library /test/lib
+ * @modules java.base/jdk.internal.foreign
+ * @build NativeTestHelper TestBufferStack
+ * @run junit/othervm --enable-native-access=ALL-UNNAMED TestBufferStack
+ */
+
+import jdk.internal.foreign.BufferStack;
+import org.junit.jupiter.api.Test;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
+import java.lang.invoke.MethodHandle;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import jdk.test.lib.thread.VThreadRunner;
+
+import static java.lang.foreign.MemoryLayout.structLayout;
+import static java.lang.foreign.ValueLayout.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+final class TestBufferStack extends NativeTestHelper {
+
+ private static final long POOL_SIZE = 64;
+ private static final long SMALL_ALLOC_SIZE = 8;
+
+ @Test
+ void invariants() {
+ var exBS = assertThrows(IllegalArgumentException.class, () -> BufferStack.of(-1, 1));
+ assertEquals("Negative byteSize: -1", exBS.getMessage());
+ var exBA = assertThrows(IllegalArgumentException.class, () -> BufferStack.of(1, -1));
+ assertEquals("Negative byteAlignment: -1", exBA.getMessage());
+ assertThrows(NullPointerException.class, () -> BufferStack.of(null));
+
+ BufferStack stack = newBufferStack();
+ assertThrows(IllegalArgumentException.class, () -> stack.pushFrame(-1, 8));
+ assertThrows(IllegalArgumentException.class, () -> stack.pushFrame(SMALL_ALLOC_SIZE, -1));
+
+ try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) {
+ assertThrows(IllegalArgumentException.class, () -> arena.allocate(-1));
+ assertThrows(IllegalArgumentException.class, () -> arena.allocate(4, -1));
+ }
+ }
+
+ @Test
+ void invariantsVt() {
+ VThreadRunner.run(this::invariants);
+ }
+
+ @Test
+ void testScopedAllocation() {
+ int stackSize = 128;
+ BufferStack stack = newBufferStack();
+ try (Arena frame1 = stack.pushFrame(3 * JAVA_INT.byteSize(), JAVA_INT.byteAlignment())) {
+ // Segments have expected sizes and are accessible and allocated consecutively in the same scope.
+ MemorySegment segment11 = frame1.allocate(JAVA_INT);
+ assertEquals(frame1.scope(), segment11.scope());
+ assertEquals(JAVA_INT.byteSize(), segment11.byteSize());
+ segment11.set(JAVA_INT, 0, 1);
+
+ MemorySegment segment12 = frame1.allocate(JAVA_INT);
+ assertEquals(segment11.address() + JAVA_INT.byteSize(), segment12.address());
+ assertEquals(JAVA_INT.byteSize(), segment12.byteSize());
+ assertEquals(frame1.scope(), segment12.scope());
+ segment12.set(JAVA_INT, 0, 1);
+
+ MemorySegment segment2;
+ try (Arena frame2 = stack.pushFrame(JAVA_LONG.byteSize(), JAVA_LONG.byteAlignment())) {
+ assertNotEquals(frame1.scope(), frame2.scope());
+ // same here, but a new scope.
+ segment2 = frame2.allocate(JAVA_LONG);
+ assertEquals( segment12.address() + /*segment12 size + frame 1 spare + alignment constraint*/ 3 * JAVA_INT.byteSize(), segment2.address());
+ assertEquals(JAVA_LONG.byteSize(), segment2.byteSize());
+ assertEquals(frame2.scope(), segment2.scope());
+ segment2.set(JAVA_LONG, 0, 1);
+
+ // Frames must be closed in stack order.
+ assertThrows(IllegalStateException.class, frame1::close);
+ }
+ // Scope is closed here, inner segments throw.
+ assertThrows(IllegalStateException.class, () -> segment2.get(JAVA_INT, 0));
+ // A new stack frame allocates at the same location (but different scope) as the previous did.
+ try (Arena frame3 = stack.pushFrame(2 * JAVA_INT.byteSize(), JAVA_INT.byteAlignment())) {
+ MemorySegment segment3 = frame3.allocate(JAVA_INT);
+ assertEquals(frame3.scope(), segment3.scope());
+ assertEquals(segment12.address() + 2 * JAVA_INT.byteSize(), segment3.address());
+ }
+
+ // Fallback arena behaves like regular stack frame.
+ MemorySegment outOfStack;
+ try (Arena hugeFrame = stack.pushFrame(1024, 4)) {
+ outOfStack = hugeFrame.allocate(4);
+ assertEquals(hugeFrame.scope(), outOfStack.scope());
+ assertTrue(outOfStack.asOverlappingSlice(segment11).isEmpty());
+ }
+ assertThrows(IllegalStateException.class, () -> outOfStack.get(JAVA_INT, 0));
+
+ // Outer segments are still accessible.
+ segment11.get(JAVA_INT, 0);
+ segment12.get(JAVA_INT, 0);
+ }
+ }
+
+ @Test
+ void testScopedAllocationVt() {
+ VThreadRunner.run(this::testScopedAllocation);
+ }
+
+ static {
+ System.loadLibrary("TestBufferStack");
+ }
+
+ private static final MemoryLayout HVAPoint3D = structLayout(NativeTestHelper.C_DOUBLE, C_DOUBLE, C_DOUBLE);
+ private static final MemorySegment UPCALL_MH = upcallStub(TestBufferStack.class, "recurse", FunctionDescriptor.of(HVAPoint3D, C_INT));
+ private static final MethodHandle DOWNCALL_MH = downcallHandle("recurse", FunctionDescriptor.of(HVAPoint3D, C_INT, ADDRESS));
+
+ public static MemorySegment recurse(int depth) {
+ try {
+ return (MemorySegment) DOWNCALL_MH.invokeExact((SegmentAllocator) Arena.ofAuto(), depth, UPCALL_MH);
+ } catch (Throwable e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ void testDeepStack() {
+ // Each downcall and upcall require 48 bytes of stack.
+ // After five allocations we start falling back.
+ MemorySegment point = recurse(10);
+ assertEquals( 12.0, point.getAtIndex(C_DOUBLE, 0));
+ assertEquals(11.0, point.getAtIndex(C_DOUBLE, 1));
+ assertEquals( 10.0, point.getAtIndex(C_DOUBLE, 2));
+ }
+
+ @Test
+ void testDeepStackVt() {
+ VThreadRunner.run(this::testDeepStack);
+ }
+
+ @Test
+ void equals() {
+ var first = newBufferStack();
+ var second = newBufferStack();
+ assertNotEquals(first, second);
+ assertEquals(first, first);
+ }
+
+ @Test
+ void allocationSameAsPoolSize() {
+ MemoryLayout twoInts = MemoryLayout.sequenceLayout(2, JAVA_INT);
+ var pool = newBufferStack();
+ long firstAddress;
+ try (var arena = pool.pushFrame(JAVA_INT)) {
+ var segment = arena.allocate(JAVA_INT);
+ firstAddress = segment.address();
+ }
+ for (int i = 0; i < 10; i++) {
+ try (var arena = pool.pushFrame(twoInts)) {
+ var segment = arena.allocate(JAVA_INT);
+ assertEquals(firstAddress, segment.address());
+ var segmentTwo = arena.allocate(JAVA_INT);
+ assertEquals(firstAddress + JAVA_INT.byteSize(), segmentTwo.address());
+ // Questionable exception type
+ assertThrows(IndexOutOfBoundsException.class, () -> arena.allocate(JAVA_INT));
+ }
+ }
+ }
+ @Test
+ void allocationSameAsPoolSizeVt() {
+ VThreadRunner.run(this::allocationSameAsPoolSize);
+ }
+
+ @Test
+ void allocateConfinement() {
+ var pool = newBufferStack();
+ Consumer allocateAction = arena ->
+ assertThrows(WrongThreadException.class, () -> {
+ CompletableFuture future = CompletableFuture.supplyAsync(() -> pool.pushFrame(SMALL_ALLOC_SIZE, 1));
+ var otherThreadArena = future.get();
+ otherThreadArena.allocate(SMALL_ALLOC_SIZE);
+ // Intentionally do not close the otherThreadArena here.
+ });
+ doInTwoStackedArenas(pool, allocateAction, allocateAction);
+ }
+
+ @Test
+ void allocateConfinementVt() {
+ VThreadRunner.run(this::allocateConfinement);
+ }
+
+ @Test
+ void closeConfinement() {
+ var pool = newBufferStack();
+ Consumer closeAction = arena -> {
+ // Do not use CompletableFuture here as it might accidentally run on the
+ // same carrier thread as a virtual thread.
+ AtomicReference otherThreadArena = new AtomicReference<>();
+ var thread = Thread.ofPlatform().start(() -> {
+ otherThreadArena.set(pool.pushFrame(SMALL_ALLOC_SIZE, 1));
+ });
+ try {
+ thread.join();
+ } catch (InterruptedException ie) {
+ fail(ie);
+ }
+ assertThrows(WrongThreadException.class, otherThreadArena.get()::close);
+ };
+ doInTwoStackedArenas(pool, closeAction, closeAction);
+ }
+
+ @Test
+ void closeConfinementVt() {
+ VThreadRunner.run(this::closeConfinement);
+ }
+
+ @Test
+ void toStringTest() {
+ BufferStack stack = newBufferStack();
+ assertEquals("BufferStack[byteSize=" + POOL_SIZE + ", byteAlignment=1]", stack.toString());
+ }
+
+ @Test
+ void allocBounds() {
+ BufferStack stack = newBufferStack();
+ try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) {
+ assertThrows(IllegalArgumentException.class, () -> arena.allocate(-1));
+ assertDoesNotThrow(() -> arena.allocate(SMALL_ALLOC_SIZE));
+ assertThrows(IndexOutOfBoundsException.class, () -> arena.allocate(SMALL_ALLOC_SIZE + 1));
+ }
+ }
+
+ @Test
+ void accessBounds() {
+ BufferStack stack = newBufferStack();
+ try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) {
+ var segment = arena.allocate(SMALL_ALLOC_SIZE);
+ assertThrows(IndexOutOfBoundsException.class, () -> segment.get(JAVA_BYTE, SMALL_ALLOC_SIZE));
+ }
+ }
+
+ static void doInTwoStackedArenas(BufferStack pool,
+ Consumer firstAction,
+ Consumer secondAction) {
+ try (var firstArena = pool.pushFrame(SMALL_ALLOC_SIZE, 1)) {
+ firstAction.accept(firstArena);
+ try (var secondArena = pool.pushFrame(SMALL_ALLOC_SIZE, 1)) {
+ secondAction.accept(secondArena);
+ }
+ }
+ }
+
+ private static BufferStack newBufferStack() {
+ return BufferStack.of(POOL_SIZE, 1);
+ }
+
+
+}
diff --git a/test/jdk/java/foreign/TestBufferStackStress.java b/test/jdk/java/foreign/TestBufferStackStress.java
new file mode 100644
index 0000000000000..0ec46311a6f17
--- /dev/null
+++ b/test/jdk/java/foreign/TestBufferStackStress.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2025, 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
+ * @modules java.base/jdk.internal.foreign
+ * @build NativeTestHelper TestBufferStackStress
+ * @run junit TestBufferStackStress
+ */
+
+import jdk.internal.foreign.BufferStack;
+import org.junit.jupiter.api.Test;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.time.Duration;
+import java.util.Arrays;
+import java.util.stream.IntStream;
+
+import static java.lang.foreign.ValueLayout.*;
+import static java.time.temporal.ChronoUnit.SECONDS;
+import static org.junit.jupiter.api.Assertions.*;
+
+public class TestBufferStackStress {
+
+ @Test
+ public void stress() throws InterruptedException {
+ BufferStack stack = BufferStack.of(256, 1);
+ Thread[] vThreads = IntStream.range(0, 1024).mapToObj(_ ->
+ Thread.ofVirtual().start(() -> {
+ long threadId = Thread.currentThread().threadId();
+ while (!Thread.interrupted()) {
+ for (int i = 0; i < 1_000_000; i++) {
+ try (Arena arena = stack.pushFrame(JAVA_LONG.byteSize(), JAVA_LONG.byteAlignment())) {
+ // Try to assert no two vThreads get allocated the same stack space.
+ MemorySegment segment = arena.allocate(JAVA_LONG);
+ JAVA_LONG.varHandle().setVolatile(segment, 0L, threadId);
+ assertEquals(threadId, (long) JAVA_LONG.varHandle().getVolatile(segment, 0L));
+ }
+ }
+ Thread.yield(); // make sure the driver thread gets a chance.
+ }
+ })).toArray(Thread[]::new);
+ Thread.sleep(Duration.of(10, SECONDS));
+ Arrays.stream(vThreads).forEach(
+ thread -> {
+ assertTrue(thread.isAlive());
+ thread.interrupt();
+ });
+ }
+
+}
diff --git a/test/jdk/java/foreign/TestBufferStackStress2.java b/test/jdk/java/foreign/TestBufferStackStress2.java
new file mode 100644
index 0000000000000..4b02f4691fb8a
--- /dev/null
+++ b/test/jdk/java/foreign/TestBufferStackStress2.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2025, 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
+ * @modules java.base/jdk.internal.foreign
+ * @build NativeTestHelper TestBufferStackStress2
+ * @run junit TestBufferStackStress2
+ */
+
+import jdk.internal.foreign.BufferStack;
+import org.junit.jupiter.api.Test;
+
+import java.io.FileDescriptor;
+import java.lang.foreign.Arena;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.time.Duration;
+import java.time.temporal.ChronoUnit;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ForkJoinWorkerThread;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+final class TestBufferStackStress2 {
+
+ private static final long POOL_SIZE = 64;
+ private static final long SMALL_ALLOC_SIZE = 8;
+
+ /**
+ * The objective with this test is to test the case when a virtual thread VT0 is
+ * mounted on a carrier thread CT0; VT0 is then suspended; The pool of carrier threads
+ * are then contracted; VT0 is then remounted on another carrier thread C1. VT0 runs
+ * for a while when there is a lot of GC activity.
+ * In other words, we are trying to establish that there is no use-after-free and that
+ * the original arena, from which reusable segments are initially allocated from, is
+ * not closed underneath.
+ *
+ * Unfortunately, this test takes about 30 seconds as that is the time it takes for
+ * the pool of carrier threads to be contracted.
+ */
+ @Test
+ void movingVirtualThreadWithGc() throws InterruptedException {
+ final long begin = System.nanoTime();
+ var pool = BufferStack.of(POOL_SIZE, 1);
+
+ System.setProperty("jdk.virtualThreadScheduler.parallelism", "1");
+
+ var done = new AtomicBoolean();
+ var completed = new AtomicBoolean();
+ var quiescent = new Object();
+ var executor = Executors.newVirtualThreadPerTaskExecutor();
+
+ executor.submit(() -> {
+ while (!done.get()) {
+ FileDescriptor.out.sync();
+ }
+ return null;
+ });
+
+ executor.submit(() -> {
+ System.out.println(duration(begin) + "ALLOCATING = " + Thread.currentThread());
+ try (Arena arena = pool.pushFrame(SMALL_ALLOC_SIZE, 1)) {
+ MemorySegment segment = arena.allocate(SMALL_ALLOC_SIZE);
+ done.set(true);
+ synchronized (quiescent) {
+ try {
+ quiescent.wait();
+ } catch (Throwable ex) {
+ throw new AssertionError(ex);
+ }
+ }
+ System.out.println(duration(begin) + "ACCESSING SEGMENT");
+
+ for (int i = 0; i < 100_000; i++) {
+ if (i % 100 == 0) {
+ System.gc();
+ }
+ segment.get(ValueLayout.JAVA_BYTE, i % SMALL_ALLOC_SIZE);
+ }
+ System.out.println(duration(begin) + "DONE ACCESSING SEGMENT");
+ }
+ System.out.println(duration(begin) + "VT DONE");
+ completed.set(true);
+ });
+
+ long count;
+ do {
+ Thread.sleep(1000);
+ count = Thread.getAllStackTraces().keySet().stream()
+ .filter(t -> t instanceof ForkJoinWorkerThread)
+ .count();
+ } while (count > 0);
+
+ System.out.println(duration(begin) + "FJP HAS CONTRACTED");
+
+ synchronized (quiescent) {
+ quiescent.notify();
+ }
+
+ System.out.println(duration(begin) + "CLOSING EXECUTOR");
+ executor.close();
+ System.out.println(duration(begin) + "EXECUTOR CLOSED");
+ assertTrue(completed.get(), "The VT did not complete properly");
+ }
+
+ private static String duration(Long begin) {
+ var duration = Duration.of(System.nanoTime() - begin, ChronoUnit.NANOS);
+ long seconds = duration.toSeconds();
+ int nanos = duration.toNanosPart();
+ return (Thread.currentThread().isVirtual() ? "VT: " : "PT: ") +
+ String.format("%3d:%09d ", seconds, nanos);
+ }
+
+}
diff --git a/test/jdk/java/foreign/libTestBufferStack.c b/test/jdk/java/foreign/libTestBufferStack.c
new file mode 100644
index 0000000000000..79eb32bf9334c
--- /dev/null
+++ b/test/jdk/java/foreign/libTestBufferStack.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2025, 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.
+ */
+
+#include "export.h"
+
+typedef struct { double x, y, z; } HVAPoint3D;
+
+EXPORT HVAPoint3D recurse(int depth, HVAPoint3D (*cb)(int)) {
+ if (depth == 0) {
+ HVAPoint3D result = { 2, 1, 0};
+ return result;
+ }
+
+ HVAPoint3D result = cb(depth - 1);
+ result.x += 1;
+ result.y += 1;
+ result.z += 1;
+ return result;
+}
diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/BufferStackBench.java b/test/micro/org/openjdk/bench/java/lang/foreign/BufferStackBench.java
new file mode 100644
index 0000000000000..06f5a3522093b
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/lang/foreign/BufferStackBench.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2025, 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.
+ *
+ */
+
+package org.openjdk.bench.java.lang.foreign;
+
+import jdk.internal.foreign.BufferStack;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.lang.foreign.Arena;
+import java.util.concurrent.TimeUnit;
+
+@BenchmarkMode(Mode.AverageTime)
+@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@State(Scope.Thread)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Fork(value = 3, jvmArgs = {"--add-exports=java.base/jdk.internal.foreign=ALL-UNNAMED"})
+public class BufferStackBench {
+
+ @Param({"8", "16", "32"})
+ public int ELEM_SIZE;
+
+ private BufferStack bufferStack;
+
+ @Setup
+ public void setup() {
+ bufferStack = BufferStack.of(128);
+ }
+
+ @Benchmark
+ public long confined() {
+ try (Arena arena = Arena.ofConfined()) {
+ return arena.allocate(ELEM_SIZE).address();
+ }
+ }
+
+ @Benchmark
+ public long buffer() {
+ try (Arena arena = bufferStack.pushFrame(64, 1)) {
+ return arena.allocate(ELEM_SIZE).address();
+ }
+ }
+
+ @Fork(value = 3, jvmArgsAppend = "-Djmh.executor=VIRTUAL")
+ public static class OfVirtual extends BufferStackBench {}
+
+}
+
diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadByValue.java b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadByValue.java
new file mode 100644
index 0000000000000..18ddcda495a8c
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/lang/foreign/CallOverheadByValue.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (c) 2025, 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.
+ */
+package org.openjdk.bench.java.lang.foreign;
+
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.lang.foreign.Arena;
+import java.lang.foreign.FunctionDescriptor;
+import java.lang.foreign.Linker;
+import java.lang.foreign.MemoryLayout;
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.SegmentAllocator;
+import java.lang.foreign.SymbolLookup;
+import java.lang.foreign.ValueLayout;
+import java.lang.invoke.MethodHandle;
+import java.util.concurrent.TimeUnit;
+
+import static org.openjdk.bench.java.lang.foreign.CLayouts.C_DOUBLE;
+
+@BenchmarkMode(Mode.AverageTime)
+@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@Measurement(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
+@State(org.openjdk.jmh.annotations.Scope.Thread)
+@OutputTimeUnit(TimeUnit.NANOSECONDS)
+@Fork(value = 3, jvmArgs = { "--enable-native-access=ALL-UNNAMED", "-Djava.library.path=micro/native" })
+public class CallOverheadByValue {
+
+ public static final MemoryLayout POINT_LAYOUT = MemoryLayout.structLayout(
+ C_DOUBLE, C_DOUBLE
+ );
+ private static final MethodHandle MH_UNIT_BY_VALUE;
+ private static final MethodHandle MH_UNIT_BY_PTR;
+
+ static {
+ Linker abi = Linker.nativeLinker();
+ System.loadLibrary("CallOverheadByValue");
+ SymbolLookup loaderLibs = SymbolLookup.loaderLookup();
+ MH_UNIT_BY_VALUE = abi.downcallHandle(
+ loaderLibs.findOrThrow("unit"),
+ FunctionDescriptor.of(POINT_LAYOUT)
+ );
+ MH_UNIT_BY_PTR = abi.downcallHandle(
+ loaderLibs.findOrThrow("unit_ptr"),
+ FunctionDescriptor.ofVoid(ValueLayout.ADDRESS)
+ );
+ }
+
+ private static final Arena arena = Arena.ofConfined();
+ private static final MemorySegment point = arena.allocate(POINT_LAYOUT);
+ private static final SegmentAllocator BY_VALUE_ALLOCATOR = (SegmentAllocator) (_, _) -> point;
+
+ @TearDown
+ public void tearDown() {
+ arena.close();
+ }
+
+ @Benchmark
+ public void byValue() throws Throwable {
+ // point = unit();
+ MemorySegment unused = (MemorySegment) MH_UNIT_BY_VALUE.invokeExact(BY_VALUE_ALLOCATOR);
+ }
+
+ @Benchmark
+ public void byPtr() throws Throwable {
+ // unit_ptr(&point);
+ MH_UNIT_BY_PTR.invokeExact(point);
+ }
+
+ @Fork(value = 3, jvmArgsAppend = "-Djmh.executor=VIRTUAL")
+ public static class OfVirtual extends CallOverheadByValue {}
+
+}
diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/libCallOverheadByValue.c b/test/micro/org/openjdk/bench/java/lang/foreign/libCallOverheadByValue.c
new file mode 100644
index 0000000000000..2eb80f537d8c8
--- /dev/null
+++ b/test/micro/org/openjdk/bench/java/lang/foreign/libCallOverheadByValue.c
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2025, 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.
+ */
+
+#include "export.h"
+
+typedef struct {
+ double x;
+ double y;
+} DoublePoint;
+
+EXPORT DoublePoint unit() {
+ DoublePoint result = { 1, 0 };
+ return result;
+}
+
+EXPORT void unit_ptr(DoublePoint* out) {
+ *out = unit();
+}