From 45cf32bd2c9e44e2fcbb4b8d0714b87e078cdb7c Mon Sep 17 00:00:00 2001 From: Per Minborg Date: Mon, 12 May 2025 06:59:41 +0000 Subject: [PATCH] 8347408: Create an internal method handle adapter for system calls with errno Reviewed-by: mcimadamore --- .../internal/foreign/CaptureStateUtil.java | 371 ++++++++++++++++++ test/jdk/java/foreign/TestBufferStack.java | 10 +- .../java/foreign/TestCaptureStateUtil.java | 154 ++++++++ .../lang/foreign/CaptureStateUtilBench.java | 116 ++++++ 4 files changed, 650 insertions(+), 1 deletion(-) create mode 100644 src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java create mode 100644 test/jdk/java/foreign/TestCaptureStateUtil.java create mode 100644 test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java diff --git a/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java b/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java new file mode 100644 index 00000000000..a98db6173fd --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java @@ -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 SEGMENT_EXTRACTION_HANDLE_CACHE; + + static { + final Set 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 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}} + *

+ * 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: + *

+ * {@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 UNDERLYING_MAKE_HANDLE = new IntFunction() { + @Override + public MethodHandle apply(int value) { + return makeHandle(value); + } + }; + + private static final IntFunction 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); + }; + } + +} diff --git a/test/jdk/java/foreign/TestBufferStack.java b/test/jdk/java/foreign/TestBufferStack.java index f7bf67bfb50..6873cb6415e 100644 --- a/test/jdk/java/foreign/TestBufferStack.java +++ b/test/jdk/java/foreign/TestBufferStack.java @@ -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 firstAction, Consumer secondAction) { diff --git a/test/jdk/java/foreign/TestCaptureStateUtil.java b/test/jdk/java/foreign/TestCaptureStateUtil.java new file mode 100644 index 00000000000..c4f0e038f09 --- /dev/null +++ b/test/jdk/java/foreign/TestCaptureStateUtil.java @@ -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; + } + +} diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java b/test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java new file mode 100644 index 00000000000..c833444d713 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/CaptureStateUtilBench.java @@ -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 {} + +}