Skip to content
Closed
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 188 additions & 0 deletions src/java.base/share/classes/jdk/internal/foreign/BufferStack.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* 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.Objects;
import java.util.concurrent.locks.ReentrantLock;

import static java.lang.foreign.ValueLayout.JAVA_LONG;

/**
* A buffer stack that allows efficient reuse of memory segments. This is useful in cases
* where temporary memory is needed.
* <p>
* Use the factory {@link #of(long)} to create new instances of this class.
* <p>
* Note: The reused segments are neither zeroed out before nor after re-use.
*/
public record BufferStack(long size, CarrierThreadLocal<PerThread> tl) {

/**
* {@return a new Arena that tries to provide {@code size} and {@code byteAlignment}
* allocations by recycling the BufferStacks internal memory}
*
* @param size to be reserved from this BufferStacks internal memory
* @param byteAlignment to be used for reservation
*/
@ForceInline
public Arena pushFrame(long size, long byteAlignment) {
return tl.get().pushFrame(size, byteAlignment);
}

/**
* {@return a new Arena that tries to provide {@code layout}
* allocations by recycling the BufferStacks 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[" + size + "]";
}

@Override
public int hashCode() {
return System.identityHashCode(this);
}

@Override
public boolean equals(Object obj) {
return this == obj;
}

private record PerThread(ReentrantLock lock, SlicingAllocator stack, Arena arena) {

@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 size) {
final Arena arena = Arena.ofAuto();
final SlicingAllocator stack = new SlicingAllocator(arena.allocate(size));
return new PerThread(new ReentrantLock(), stack, arena);
}

private final class Frame implements Arena {
private final boolean locked;
private final long parentOffset;
private final long topOfStack;
private final Arena confinedArena = Arena.ofConfined();
private final SegmentAllocator frame;

@SuppressWarnings("restricted")
@ForceInline
public Frame(boolean locked, long byteSize, long byteAlignment) {
this.locked = locked;
parentOffset = stack.currentOffset();
MemorySegment frameSegment = stack.allocate(byteSize, byteAlignment);
topOfStack = stack.currentOffset();
frame = new SlicingAllocator(frameSegment.reinterpret(confinedArena, null));
}

@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
if (((MemorySessionImpl) scope()).ownerThread() != Thread.currentThread()) {
throw MemorySessionImpl.wrongThread();
}
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();
}
Reference.reachabilityFence(arena);
}
}
}

public static BufferStack of(long size) {
if (size < 0) {
throw new IllegalArgumentException("Size is negative: " + size);
}
return new BufferStack(size, new CarrierThreadLocal<>() {
@Override
protected BufferStack.PerThread initialValue() {
return BufferStack.PerThread.of(size);
}
});
}

public static BufferStack of(MemoryLayout layout) {
Objects.requireNonNull(layout);
// Allocations are always at least aligned with the largest Java type (e.g., long)
long size = layout.byteAlignment() > JAVA_LONG.byteSize()
? Utils.alignUp(layout.byteSize(), layout.byteAlignment())
: layout.byteSize();
return of(size);
}
}
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

@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() {
Expand Down
2 changes: 2 additions & 0 deletions test/jdk/ProblemList.txt
Original file line number Diff line number Diff line change
Expand Up @@ -784,6 +784,8 @@ jdk/jfr/jvm/TestWaste.java 8282427 generic-

# jdk_foreign

java/foreign/TestBufferStackStress.java 8350455 macosx-all

############################################################################
# Client manual tests

Expand Down
Loading