mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 03:58:21 +00:00
8349146: [REDO] Implement a better allocator for downcalls
Reviewed-by: mcimadamore, jvernee, liach
This commit is contained in:
parent
995d54161f
commit
9f9e73d5f9
@ -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.
|
||||
* <p>
|
||||
* Use the factories {@code BufferStack.of(...)} to create new instances of this class.
|
||||
* <p>
|
||||
* 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<PerThread> 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<MemorySegment> {
|
||||
@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());
|
||||
}
|
||||
}
|
||||
@ -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 final class SlicingAllocator implements SegmentAllocator {
|
||||
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;
|
||||
|
||||
@ -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 @@ public final class SharedUtils {
|
||||
: chunkOffset;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@ForceInline
|
||||
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();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
arena.close();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MemorySegment allocate(long byteSize, long byteAlignment) {
|
||||
return slicingAllocator.allocate(byteSize, byteAlignment);
|
||||
}
|
||||
};
|
||||
return LINKER_STACK.pushFrame(size, 8);
|
||||
}
|
||||
|
||||
public static Arena newEmptyArena() {
|
||||
|
||||
@ -784,6 +784,8 @@ jdk/jfr/jvm/TestWaste.java 8282427 generic-
|
||||
|
||||
# jdk_foreign
|
||||
|
||||
java/foreign/TestBufferStackStress.java 8350455 macosx-all
|
||||
|
||||
############################################################################
|
||||
# Client manual tests
|
||||
|
||||
|
||||
286
test/jdk/java/foreign/TestBufferStack.java
Normal file
286
test/jdk/java/foreign/TestBufferStack.java
Normal file
@ -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<Arena> allocateAction = arena ->
|
||||
assertThrows(WrongThreadException.class, () -> {
|
||||
CompletableFuture<Arena> 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<Arena> closeAction = arena -> {
|
||||
// Do not use CompletableFuture here as it might accidentally run on the
|
||||
// same carrier thread as a virtual thread.
|
||||
AtomicReference<Arena> 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<Arena> firstAction,
|
||||
Consumer<Arena> 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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
72
test/jdk/java/foreign/TestBufferStackStress.java
Normal file
72
test/jdk/java/foreign/TestBufferStackStress.java
Normal file
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
136
test/jdk/java/foreign/TestBufferStackStress2.java
Normal file
136
test/jdk/java/foreign/TestBufferStackStress2.java
Normal file
@ -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.
|
||||
* <p>
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
39
test/jdk/java/foreign/libTestBufferStack.c
Normal file
39
test/jdk/java/foreign/libTestBufferStack.c
Normal file
@ -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;
|
||||
}
|
||||
@ -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 {}
|
||||
|
||||
}
|
||||
|
||||
@ -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 {}
|
||||
|
||||
}
|
||||
@ -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();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user