/
githubmirror
/
galahad
Обзор
Документация
Войти
/
githubmirror
/
galahad
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/java.base/share/classes/jdk/internal/foreign/CaptureStateUtil.java
378 строк
17 KB
Per Minborg
8366178: Implement JEP 526: Lazy Constants (Second Preview)
18 ноя 2025, 15:20
18 ноя 2025, 15:20
f946449
Код
Авторство
О чём код?
/* * 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.List; import java.util.Map; 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 Map<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 = Map.ofLazy(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.get(key); if (segmentExtractor == null) { throw new IllegalArgumentException("Input not allowed: " + 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.get(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.get(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.get(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.get(NON_NEGATIVE_INT), HANDLES_CACHE.get(SUCCESS_INT), HANDLES_CACHE.get(ERROR_INT).bindTo(intExtractor)); } else { // (long, MemorySegment)long return MethodHandles.guardWithTest( HANDLES_CACHE.get(NON_NEGATIVE_LONG), HANDLES_CACHE.get(SUCCESS_LONG), HANDLES_CACHE.get(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 List<MethodHandle> HANDLES_CACHE = List.ofLazy(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); }; } }