blob: 2f3ba7d9e05b1769cf5a2a46fcdad6176fe49b40 [file]
/*
* 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. 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.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);
};
}
}