mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8347408: Create an internal method handle adapter for system calls with errno
Reviewed-by: mcimadamore
This commit is contained in:
parent
de801fea76
commit
45cf32bd2c
@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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 jdk.internal.foreign;
|
||||
|
||||
import jdk.internal.invoke.MhUtil;
|
||||
import jdk.internal.vm.annotation.ForceInline;
|
||||
|
||||
import java.lang.foreign.Arena;
|
||||
import java.lang.foreign.Linker;
|
||||
import java.lang.foreign.MemoryLayout;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.foreign.StructLayout;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.IntFunction;
|
||||
|
||||
/**
|
||||
* An internal utility class that can be used to adapt system-call-styled method handles
|
||||
* for efficient and easy use.
|
||||
*/
|
||||
public final class CaptureStateUtil {
|
||||
|
||||
private static final StructLayout CAPTURE_LAYOUT = Linker.Option.captureStateLayout();
|
||||
private static final BufferStack POOL = BufferStack.of(CAPTURE_LAYOUT);
|
||||
|
||||
// The `BASIC_HANDLE_CACHE` contains the common "basic handles" that can be reused for
|
||||
// all adapted method handles. Keeping as much as possible reusable reduces the number
|
||||
// of combinators needed to form an adapted method handle.
|
||||
// The function is lazily computed.
|
||||
//
|
||||
private static final Function<SegmentExtractorKey, MethodHandle> SEGMENT_EXTRACTION_HANDLE_CACHE;
|
||||
|
||||
static {
|
||||
final Set<SegmentExtractorKey> inputs = new HashSet<>();
|
||||
// The Cartesian product : (int.class, long.class) x ("errno", ...)
|
||||
// Do not use Streams in order to enable "early" use in the init sequence.
|
||||
for (Class<?> c : new Class<?>[]{int.class, long.class}) {
|
||||
for (MemoryLayout layout : CAPTURE_LAYOUT.memberLayouts()) {
|
||||
inputs.add(new SegmentExtractorKey(c, layout.name().orElseThrow()));
|
||||
}
|
||||
}
|
||||
|
||||
// Do not use a lambda in order to allow early use in the init sequence
|
||||
final Function<SegmentExtractorKey, MethodHandle> segmentExtractionHandle = new Function<>() {
|
||||
@Override
|
||||
public MethodHandle apply(SegmentExtractorKey basicKey) {
|
||||
return makeSegmentExtractionHandle(basicKey);
|
||||
}
|
||||
};
|
||||
|
||||
SEGMENT_EXTRACTION_HANDLE_CACHE = StableValue.function(inputs, segmentExtractionHandle);
|
||||
}
|
||||
|
||||
// A key that holds both the `returnType` and the `stateName` needed to look up a
|
||||
// specific "basic handle" in the `BASIC_HANDLE_CACHE`.
|
||||
// returnType in {int.class | long.class}
|
||||
// stateName can be anything non-null but should be in {"GetLastError" | "WSAGetLastError" | "errno"}
|
||||
private record SegmentExtractorKey(Class<?> returnType, String stateName) {
|
||||
|
||||
SegmentExtractorKey(MethodHandle target, String stateName) {
|
||||
this(returnType(target), Objects.requireNonNull(stateName));
|
||||
}
|
||||
|
||||
static Class<?> returnType(MethodHandle target) {
|
||||
// Implicit null check
|
||||
final MethodType type = target.type();
|
||||
final Class<?> returnType = type.returnType();
|
||||
|
||||
if (!(returnType.equals(int.class) || returnType.equals(long.class))) {
|
||||
throw illegalArgDoesNot(target, "return an int or a long");
|
||||
}
|
||||
if (type.parameterCount() == 0 || type.parameterType(0) != MemorySegment.class) {
|
||||
throw illegalArgDoesNot(target, "have a MemorySegment as the first parameter");
|
||||
}
|
||||
return returnType;
|
||||
}
|
||||
|
||||
private static IllegalArgumentException illegalArgDoesNot(MethodHandle target, String info) {
|
||||
return new IllegalArgumentException("The provided target " + target
|
||||
+ " does not " + info);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private CaptureStateUtil() {}
|
||||
|
||||
/**
|
||||
* {@return a new MethodHandle that adapts the provided {@code target} so that it
|
||||
* directly returns the same value as the {@code target} if it is non-negative,
|
||||
* otherwise returns the negated captured state defined by the provided
|
||||
* {@code stateName}}
|
||||
* <p>
|
||||
* This method is suitable for adapting system-call method handles(e.g.
|
||||
* {@code open()}, {@code read()}, and {@code close()}). Clients can check the return
|
||||
* value as shown in this example:
|
||||
* {@snippet lang = java:
|
||||
* // (MemorySegment capture, MemorySegment pathname, int flags)int
|
||||
* static final MethodHandle CAPTURING_OPEN = ...
|
||||
*
|
||||
* // (MemorySegment pathname, int flags)int
|
||||
* static final MethodHandle OPEN = CaptureStateUtil
|
||||
* .adaptSystemCall(CAPTURING_OPEN, "errno");
|
||||
*
|
||||
* try {
|
||||
* int fh = (int)OPEN.invokeExact(pathName, flags);
|
||||
* if (fh < 0) {
|
||||
* throw new IOException("Error opening file: errno = " + (-fh));
|
||||
* }
|
||||
* processFile(fh);
|
||||
* } catch (Throwable t) {
|
||||
* throw new RuntimeException(t);
|
||||
* }
|
||||
*
|
||||
*}
|
||||
*
|
||||
* For a {@code target} method handle that takes a {@code MemorySegment} and two
|
||||
* {@code int} parameters and returns an {@code int} value, the method returns a new
|
||||
* method handle that is doing the equivalent of:
|
||||
* <p>
|
||||
* {@snippet lang = java:
|
||||
* private static final MemoryLayout CAPTURE_LAYOUT =
|
||||
* Linker.Option.captureStateLayout();
|
||||
* private static final BufferStack POOL =
|
||||
* BufferStack.of(CAPTURE_LAYOUT);
|
||||
*
|
||||
* public int invoke(MethodHandle target,
|
||||
* String stateName,
|
||||
* int a, int b) {
|
||||
* try (var arena = POOL.pushFrame(CAPTURE_LAYOUT)) {
|
||||
* final MemorySegment segment = arena.allocate(CAPTURE_LAYOUT);
|
||||
* final int result = (int) handle.invoke(segment, a, b);
|
||||
* if (result >= 0) {
|
||||
* return result;
|
||||
* }
|
||||
* return -(int) CAPTURE_LAYOUT
|
||||
* .varHandle(MemoryLayout.PathElement.groupElement(stateName))
|
||||
* .get(segment, 0);
|
||||
* }
|
||||
* }
|
||||
*}
|
||||
* except it is more performant. In the above {@code stateName} is the name of the
|
||||
* captured state (e.g. {@code errno}). The static {@code CAPTURE_LAYOUT} is shared
|
||||
* across all target method handles adapted by this method.
|
||||
*
|
||||
* @param target method handle that returns an {@code int} or a {@code long} and
|
||||
* has a capturing state MemorySegment as its first parameter
|
||||
* @param stateName the name of the capturing state member layout (i.e. "errno",
|
||||
* "GetLastError", or "WSAGetLastError")
|
||||
* @throws IllegalArgumentException if the provided {@code target}'s return type is
|
||||
* not {@code int} or {@code long}
|
||||
* @throws IllegalArgumentException if the provided {@code target}'s first parameter
|
||||
* type is not {@linkplain MemorySegment}
|
||||
* @throws IllegalArgumentException if the provided {@code stateName} is unknown on
|
||||
* the current platform
|
||||
*/
|
||||
public static MethodHandle adaptSystemCall(MethodHandle target,
|
||||
String stateName) {
|
||||
// Invariants checked in the BasicKey record
|
||||
final SegmentExtractorKey key = new SegmentExtractorKey(target, stateName);
|
||||
|
||||
// ((int | long), MemorySegment)(int | long)
|
||||
final MethodHandle segmentExtractor = SEGMENT_EXTRACTION_HANDLE_CACHE.apply(key);
|
||||
|
||||
// Make `target` specific adaptations of the basic handle
|
||||
|
||||
// Pre-pend all the parameters from the `target` MH.
|
||||
// (C0=MemorySegment, C1-Cn, MemorySegment)(int|long)
|
||||
MethodHandle innerAdapted = MethodHandles.collectArguments(segmentExtractor, 0, target);
|
||||
|
||||
final int[] perm = new int[target.type().parameterCount() + 1];
|
||||
for (int i = 0; i < target.type().parameterCount(); i++) {
|
||||
perm[i] = i;
|
||||
}
|
||||
// Last takes first
|
||||
perm[perm.length - 1] = 0;
|
||||
// Deduplicate the first and last coordinate and only use the first one.
|
||||
// (C0=MemorySegment, C1-Cn)(int|long)
|
||||
innerAdapted = MethodHandles.permuteArguments(innerAdapted, target.type(), perm);
|
||||
|
||||
// Use an `Arena` for the first argument instead and extract a segment from it.
|
||||
// (C0=Arena, C1-Cn)(int|long)
|
||||
innerAdapted = MethodHandles.collectArguments(innerAdapted, 0, HANDLES_CACHE.apply(ALLOCATE));
|
||||
|
||||
// Add an identity function for the result of the cleanup action.
|
||||
// ((int|long))(int|long)
|
||||
MethodHandle cleanup = MethodHandles.identity(key.returnType());
|
||||
// Add a dummy `Throwable` argument for the cleanup action.
|
||||
// This means, anything thrown will just be propagated.
|
||||
// (Throwable, (int|long))(int|long)
|
||||
cleanup = MethodHandles.dropArguments(cleanup, 0, Throwable.class);
|
||||
// Add the first `Arena` parameter of the `innerAdapted` method handle to the
|
||||
// cleanup action and invoke `Arena::close` when it is run. The `cleanup` handle
|
||||
// does not have to have all parameters. It can have zero or more.
|
||||
// (Throwable, (int|long), Arena)(int|long)
|
||||
cleanup = MethodHandles.collectArguments(cleanup, 2, HANDLES_CACHE.apply(ARENA_CLOSE));
|
||||
|
||||
// Combine the `innerAdapted` and `cleanup` action into a try/finally block.
|
||||
// (Arena, C1-Cn)(int|long)
|
||||
final MethodHandle tryFinally = MethodHandles.tryFinally(innerAdapted, cleanup);
|
||||
|
||||
// Acquire the arena from the global pool.
|
||||
// With this, we finally arrive at the intended method handle:
|
||||
// (C1-Cn)(int|long)
|
||||
return MethodHandles.collectArguments(tryFinally, 0, HANDLES_CACHE.apply(ACQUIRE_ARENA));
|
||||
}
|
||||
|
||||
private static MethodHandle makeSegmentExtractionHandle(SegmentExtractorKey segmentExtractorKey) {
|
||||
final VarHandle vh = CAPTURE_LAYOUT.varHandle(
|
||||
MemoryLayout.PathElement.groupElement(segmentExtractorKey.stateName()));
|
||||
// This MH is used to extract the named captured state
|
||||
// from the capturing `MemorySegment`.
|
||||
// (MemorySegment, long)int
|
||||
MethodHandle intExtractor = vh.toMethodHandle(VarHandle.AccessMode.GET);
|
||||
// As the MH is already adapted to use the appropriate
|
||||
// offset, we just insert `0L` for the offset.
|
||||
// (MemorySegment)int
|
||||
intExtractor = MethodHandles.insertArguments(intExtractor, 1, 0L);
|
||||
|
||||
// If X is the `returnType` (either `int` or `long`) then
|
||||
// the code below is equivalent to:
|
||||
//
|
||||
// X handle(X returnValue, MemorySegment segment)
|
||||
// if (returnValue >= 0) {
|
||||
// // Ignore the segment
|
||||
// return returnValue;
|
||||
// } else {
|
||||
// // ignore the returnValue
|
||||
// return -(X)intExtractor.invokeExact(segment);
|
||||
// }
|
||||
// }
|
||||
if (segmentExtractorKey.returnType().equals(int.class)) {
|
||||
// (int, MemorySegment)int
|
||||
return MethodHandles.guardWithTest(
|
||||
HANDLES_CACHE.apply(NON_NEGATIVE_INT),
|
||||
HANDLES_CACHE.apply(SUCCESS_INT),
|
||||
HANDLES_CACHE.apply(ERROR_INT).bindTo(intExtractor));
|
||||
} else {
|
||||
// (long, MemorySegment)long
|
||||
return MethodHandles.guardWithTest(
|
||||
HANDLES_CACHE.apply(NON_NEGATIVE_LONG),
|
||||
HANDLES_CACHE.apply(SUCCESS_LONG),
|
||||
HANDLES_CACHE.apply(ERROR_LONG).bindTo(intExtractor));
|
||||
}
|
||||
}
|
||||
|
||||
// The methods below are reflective used via static MethodHandles
|
||||
|
||||
@ForceInline
|
||||
private static Arena acquireArena() {
|
||||
return POOL.pushFrame(CAPTURE_LAYOUT);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static MemorySegment allocate(Arena arena) {
|
||||
return arena.allocate(CAPTURE_LAYOUT.byteSize(), CAPTURE_LAYOUT.byteAlignment());
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static boolean nonNegative(int value) {
|
||||
return value >= 0;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static int success(int value,
|
||||
MemorySegment segment) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static int error(MethodHandle errorHandle,
|
||||
int value,
|
||||
MemorySegment segment) throws Throwable {
|
||||
return -(int) errorHandle.invokeExact(segment);
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static boolean nonNegative(long value) {
|
||||
return value >= 0L;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static long success(long value,
|
||||
MemorySegment segment) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@ForceInline
|
||||
private static long error(MethodHandle errorHandle,
|
||||
long value,
|
||||
MemorySegment segment) throws Throwable {
|
||||
return -(int) errorHandle.invokeExact(segment);
|
||||
}
|
||||
|
||||
// The method handles below are bound to static methods residing in this class
|
||||
|
||||
private static final int
|
||||
NON_NEGATIVE_INT = 0,
|
||||
SUCCESS_INT = 1,
|
||||
ERROR_INT = 2,
|
||||
NON_NEGATIVE_LONG = 3,
|
||||
SUCCESS_LONG = 4,
|
||||
ERROR_LONG = 5,
|
||||
ACQUIRE_ARENA = 6,
|
||||
ALLOCATE = 7,
|
||||
ARENA_CLOSE = 8;
|
||||
|
||||
// Do not use a lambda in order to allow early use in the init sequence
|
||||
private static final IntFunction<MethodHandle> UNDERLYING_MAKE_HANDLE = new IntFunction<MethodHandle>() {
|
||||
@Override
|
||||
public MethodHandle apply(int value) {
|
||||
return makeHandle(value);
|
||||
}
|
||||
};
|
||||
|
||||
private static final IntFunction<MethodHandle> HANDLES_CACHE =
|
||||
StableValue.intFunction(ARENA_CLOSE + 1, UNDERLYING_MAKE_HANDLE);
|
||||
|
||||
private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
|
||||
|
||||
private static MethodHandle makeHandle(int index) {
|
||||
return switch (index) {
|
||||
case NON_NEGATIVE_INT -> MhUtil.findStatic(LOOKUP, "nonNegative",
|
||||
MethodType.methodType(boolean.class, int.class));
|
||||
case SUCCESS_INT -> MhUtil.findStatic(LOOKUP, "success",
|
||||
MethodType.methodType(int.class, int.class, MemorySegment.class));
|
||||
case ERROR_INT -> MhUtil.findStatic(LOOKUP, "error",
|
||||
MethodType.methodType(int.class, MethodHandle.class, int.class, MemorySegment.class));
|
||||
case NON_NEGATIVE_LONG -> MhUtil.findStatic(LOOKUP, "nonNegative",
|
||||
MethodType.methodType(boolean.class, long.class));
|
||||
case SUCCESS_LONG -> MhUtil.findStatic(LOOKUP, "success",
|
||||
MethodType.methodType(long.class, long.class, MemorySegment.class));
|
||||
case ERROR_LONG -> MhUtil.findStatic(LOOKUP, "error",
|
||||
MethodType.methodType(long.class, MethodHandle.class, long.class, MemorySegment.class));
|
||||
case ACQUIRE_ARENA -> MhUtil.findStatic(LOOKUP, "acquireArena",
|
||||
MethodType.methodType(Arena.class));
|
||||
case ALLOCATE -> MhUtil.findStatic(LOOKUP, "allocate",
|
||||
MethodType.methodType(MemorySegment.class, Arena.class));
|
||||
case ARENA_CLOSE -> MhUtil.findVirtual(LOOKUP, Arena.class, "close",
|
||||
MethodType.methodType(void.class));
|
||||
default -> throw new InternalError("Unknown index: " + index);
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@ -51,7 +51,7 @@ 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;
|
||||
private static final long SMALL_ALLOC_SIZE = JAVA_LONG.byteSize();
|
||||
|
||||
@Test
|
||||
void invariants() {
|
||||
@ -267,6 +267,14 @@ final class TestBufferStack extends NativeTestHelper {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void noInit() {
|
||||
BufferStack stack = newBufferStack();
|
||||
try (var arena = stack.pushFrame(SMALL_ALLOC_SIZE, 1)) {
|
||||
assertDoesNotThrow(() -> arena.allocateFrom(JAVA_INT, 1));
|
||||
}
|
||||
}
|
||||
|
||||
static void doInTwoStackedArenas(BufferStack pool,
|
||||
Consumer<Arena> firstAction,
|
||||
Consumer<Arena> secondAction) {
|
||||
|
||||
154
test/jdk/java/foreign/TestCaptureStateUtil.java
Normal file
154
test/jdk/java/foreign/TestCaptureStateUtil.java
Normal file
@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright (c) 2024, 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
|
||||
* @summary Test CaptureStateUtil
|
||||
* @modules java.base/jdk.internal.foreign
|
||||
* @run junit TestCaptureStateUtil
|
||||
*/
|
||||
|
||||
import jdk.internal.foreign.CaptureStateUtil;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.foreign.Linker;
|
||||
import java.lang.foreign.MemoryLayout;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.VarHandle;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
final class TestCaptureStateUtil {
|
||||
|
||||
private static final String ERRNO_NAME = "errno";
|
||||
|
||||
private static final VarHandle ERRNO_HANDLE = Linker.Option.captureStateLayout()
|
||||
.varHandle(MemoryLayout.PathElement.groupElement(ERRNO_NAME));
|
||||
|
||||
private static final MethodHandle INT_DUMMY_HANDLE;
|
||||
private static final MethodHandle LONG_DUMMY_HANDLE;
|
||||
|
||||
static {
|
||||
try {
|
||||
MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||||
INT_DUMMY_HANDLE = lookup
|
||||
.findStatic(TestCaptureStateUtil.class, "dummy",
|
||||
MethodType.methodType(int.class, MemorySegment.class, int.class, int.class));
|
||||
LONG_DUMMY_HANDLE = lookup
|
||||
.findStatic(TestCaptureStateUtil.class, "dummy",
|
||||
MethodType.methodType(long.class, MemorySegment.class, long.class, int.class));
|
||||
} catch (ReflectiveOperationException e) {
|
||||
throw new InternalError(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static final MethodHandle ADAPTED_INT = CaptureStateUtil
|
||||
.adaptSystemCall(INT_DUMMY_HANDLE, ERRNO_NAME);
|
||||
private static final MethodHandle ADAPTED_LONG = CaptureStateUtil
|
||||
.adaptSystemCall(LONG_DUMMY_HANDLE, ERRNO_NAME);
|
||||
|
||||
@Test
|
||||
void successfulInt() throws Throwable {
|
||||
int r = (int) ADAPTED_INT.invokeExact(1, 0);
|
||||
assertEquals(1, r);
|
||||
}
|
||||
|
||||
private static final int EACCES = 13; /* Permission denied */
|
||||
|
||||
@Test
|
||||
void errorInt() throws Throwable {
|
||||
int r = (int) ADAPTED_INT.invokeExact(-1, EACCES);
|
||||
assertEquals(-EACCES, r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void successfulLong() throws Throwable {
|
||||
long r = (long) ADAPTED_LONG.invokeExact(1L, 0);
|
||||
assertEquals(1, r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void errorLong() throws Throwable {
|
||||
long r = (long) ADAPTED_LONG.invokeExact(-1L, EACCES);
|
||||
assertEquals(-EACCES, r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void successfulIntPerHandle() throws Throwable {
|
||||
MethodHandle handle = CaptureStateUtil
|
||||
.adaptSystemCall(INT_DUMMY_HANDLE, ERRNO_NAME);
|
||||
int r = (int) handle.invokeExact(1, 0);
|
||||
assertEquals(1, r);
|
||||
}
|
||||
|
||||
@Test
|
||||
void invariants() throws Throwable {
|
||||
MethodHandle noSegment = MethodHandles.lookup()
|
||||
.findStatic(TestCaptureStateUtil.class, "wrongType",
|
||||
MethodType.methodType(long.class, long.class, int.class));
|
||||
|
||||
var noSegEx = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(noSegment, ERRNO_NAME));
|
||||
assertTrue(noSegEx.getMessage().contains("does not have a MemorySegment as the first parameter"));
|
||||
|
||||
MethodHandle noArgMH = MethodHandles.empty(MethodType.methodType(int.class));
|
||||
var emptyEx = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(noArgMH, ERRNO_NAME));
|
||||
assertTrue(emptyEx.getMessage().contains("does not have a MemorySegment as the first parameter"));
|
||||
|
||||
MethodHandle wrongReturnType = MethodHandles.lookup()
|
||||
.findStatic(TestCaptureStateUtil.class, "wrongType",
|
||||
MethodType.methodType(short.class, MemorySegment.class, long.class, int.class));
|
||||
|
||||
var wrongRetEx = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(wrongReturnType, ERRNO_NAME));
|
||||
assertTrue(wrongRetEx.getMessage().contains("does not return an int or a long"));
|
||||
|
||||
var wrongCaptureName = assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(LONG_DUMMY_HANDLE, "foo"));
|
||||
assertEquals("Input not allowed: SegmentExtractorKey[returnType=long, stateName=foo]", wrongCaptureName.getMessage());
|
||||
|
||||
assertThrows(NullPointerException.class, () -> CaptureStateUtil.adaptSystemCall(null, ERRNO_NAME));
|
||||
assertThrows(IllegalArgumentException.class, () -> CaptureStateUtil.adaptSystemCall(noSegment, null));
|
||||
}
|
||||
|
||||
// Dummy method that is just returning the provided parameters
|
||||
private static int dummy(MemorySegment segment, int result, int errno) {
|
||||
ERRNO_HANDLE.set(segment, 0, errno);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Dummy method that is just returning the provided parameters
|
||||
private static long dummy(MemorySegment segment, long result, int errno) {
|
||||
ERRNO_HANDLE.set(segment, 0, errno);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static long wrongType(long result, int errno) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static short wrongType(MemorySegment segment, long result, int errno) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.CaptureStateUtil;
|
||||
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.Scope;
|
||||
import org.openjdk.jmh.annotations.State;
|
||||
import org.openjdk.jmh.annotations.Warmup;
|
||||
|
||||
import java.lang.foreign.Arena;
|
||||
import java.lang.foreign.Linker;
|
||||
import java.lang.foreign.MemoryLayout;
|
||||
import java.lang.foreign.MemorySegment;
|
||||
import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.invoke.MethodType;
|
||||
import java.lang.invoke.VarHandle;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@BenchmarkMode(Mode.AverageTime)
|
||||
@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
|
||||
@State(Scope.Benchmark)
|
||||
@OutputTimeUnit(TimeUnit.NANOSECONDS)
|
||||
@Fork(value = 3, jvmArgs = {"--add-exports=java.base/jdk.internal.foreign=ALL-UNNAMED",
|
||||
"--enable-native-access=ALL-UNNAMED"})
|
||||
public class CaptureStateUtilBench {
|
||||
|
||||
private static final String ERRNO_NAME = "errno";
|
||||
|
||||
private static final VarHandle ERRNO_HANDLE = Linker.Option.captureStateLayout()
|
||||
.varHandle(MemoryLayout.PathElement.groupElement(ERRNO_NAME));
|
||||
|
||||
private static final long SIZE = Linker.Option.captureStateLayout().byteSize();
|
||||
|
||||
private static final MethodHandle DUMMY_EXPLICIT_ALLOC = dummyExplicitAlloc();
|
||||
private static final MethodHandle DUMMY_TL_ALLOC = dummyTlAlloc();
|
||||
|
||||
@Benchmark
|
||||
public int explicitAllocationSuccess() throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
return (int) DUMMY_EXPLICIT_ALLOC.invokeExact(arena.allocate(SIZE), 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int explicitAllocationFail() throws Throwable {
|
||||
try (var arena = Arena.ofConfined()) {
|
||||
return (int) DUMMY_EXPLICIT_ALLOC.invokeExact(arena.allocate(SIZE), -1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int adaptedSysCallSuccess() throws Throwable {
|
||||
return (int) DUMMY_TL_ALLOC.invokeExact(0, 0);
|
||||
}
|
||||
|
||||
@Benchmark
|
||||
public int adaptedSysCallFail() throws Throwable {
|
||||
return (int) DUMMY_TL_ALLOC.invokeExact( -1, 1);
|
||||
}
|
||||
|
||||
private static MethodHandle dummyExplicitAlloc() {
|
||||
try {
|
||||
return MethodHandles.lookup().findStatic(CaptureStateUtilBench.class,
|
||||
"dummy", MethodType.methodType(int.class, MemorySegment.class, int.class, int.class));
|
||||
} catch (ReflectiveOperationException roe) {
|
||||
throw new RuntimeException(roe);
|
||||
}
|
||||
}
|
||||
|
||||
private static MethodHandle dummyTlAlloc() {
|
||||
final MethodHandle handle = dummyExplicitAlloc();
|
||||
return CaptureStateUtil.adaptSystemCall(handle, ERRNO_NAME);
|
||||
}
|
||||
|
||||
// Dummy method that is just returning the provided parameters
|
||||
private static int dummy(MemorySegment segment, int result, int errno) {
|
||||
if (errno != 0) {
|
||||
// Assuming the capture state is only modified upon detecting an error.
|
||||
ERRNO_HANDLE.set(segment, 0, errno);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Fork(value = 3, jvmArgsAppend = "-Djmh.executor=VIRTUAL")
|
||||
public static class OfVirtual extends CaptureStateUtilBench {}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user