diff --git a/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json b/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json index 92de32ddbab9..d947d1391591 100644 --- a/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json +++ b/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json @@ -28,7 +28,7 @@ "title": "Specifies if a call state should be captured. Which states to capture is determined at run time. See also: 'java.lang.foreign.Linker.Option.captureCallState'" }, "critical": { - "type": ["boolean", "object"], + "type": "object", "title": "see 'java.lang.foreign.Linker.Option.critical'", "properties": { "allowHeapAccess": { diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index a25d8d923e54..9832fcafe621 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -23,6 +23,7 @@ This changelog summarizes major changes to GraalVM Native Image. * (GR-53985) Add experimental option `ClassForNameRespectsClassLoader` that makes `Class.forName(...)` respect the class loader hierarchy. * (GR-59869) Implemented initial optimization of Java Vector API (JEP 338) operations in native images. See the compiler changelog for more details. * (GR-63268) Reflection and JNI queries do not require metadata entries to throw the expected JDK exception when querying a class that doesn't exist under `--exact-reachability-metadata` if the query cannot possibly be a valid class name +* (GR-60208) Adds the Tracing Agent support for applications using the Foreign Function & Memory (FFM) API. The agent generates FFM configuration in _foreign-config.json_. Additionally, support for FFM configurations has been added to the `native-image-configure` tool. * (GR-47881) Remove the total number of loaded types, fields, and methods from the build output, deprecated these metrics in the build output schema, and removed already deprecated build output metrics. ## GraalVM for JDK 24 (Internal Version 24.2.0) diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 81532c5f988c..3c0e11c89e0a 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -185,6 +185,10 @@ private static void traceSerializeBreakpoint(JNIEnvironment env, String function traceBreakpoint(env, "serialization", nullHandle(), nullHandle(), nullHandle(), function, result, stackTrace, args); } + private static void traceForeignBreakpoint(JNIEnvironment env, String function, Object result, JNIMethodId[] stackTrace, Object... args) { + traceBreakpoint(env, "foreign", nullHandle(), nullHandle(), nullHandle(), function, result, stackTrace, args); + } + private static void traceBreakpoint(JNIEnvironment env, String context, JNIObjectHandle clazz, JNIObjectHandle declaringClass, JNIObjectHandle callerClass, String function, Object result, JNIMethodId[] stackTrace, Object[] args) { if (tracer != null) { @@ -350,6 +354,54 @@ private static boolean handleGetField(JNIEnvironment jni, JNIObjectHandle thread return true; } + private static boolean downcallHandle0(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + JNIObjectHandle receiver = getReceiver(thread); + JNIObjectHandle function = getObjectArgument(thread, 1); + JNIObjectHandle options = getObjectArgument(thread, 2); + + JNIObjectHandle result = Support.callObjectMethodLL(jni, receiver, bp.method, function, options); + boolean isValidResult = !clearException(jni) && nullHandle().notEqual(result); + + String returnLayoutString = Tracer.UNKNOWN_VALUE; + Object argumentLayoutStrings = Tracer.UNKNOWN_VALUE; + Object optionsStrings = Tracer.UNKNOWN_VALUE; + if (isValidResult) { + NativeImageAgentJNIHandleSet handles = agent.handles(); + returnLayoutString = ForeignUtil.getReturnLayoutString(jni, handles, function); + argumentLayoutStrings = ForeignUtil.getArgumentLayoutStrings(jni, handles, function); + optionsStrings = ForeignUtil.getOptionsStrings(jni, handles, options); + } + + traceForeignBreakpoint(jni, bp.specification.methodName, isValidResult, state.getFullStackTraceOrNull(), returnLayoutString, argumentLayoutStrings, optionsStrings); + return true; + } + + private static boolean upcallStub(JNIEnvironment jni, JNIObjectHandle thread, Breakpoint bp, InterceptedState state) { + JNIObjectHandle receiver = getReceiver(thread); + JNIObjectHandle target = getObjectArgument(thread, 1); + JNIObjectHandle function = getObjectArgument(thread, 2); + JNIObjectHandle arena = getObjectArgument(thread, 3); + JNIObjectHandle options = getObjectArgument(thread, 4); + + JNIObjectHandle result = Support.callObjectMethodLLLL(jni, receiver, bp.method, target, function, arena, options); + boolean isValidResult = !clearException(jni) && nullHandle().notEqual(result); + + String returnLayoutString = Tracer.UNKNOWN_VALUE; + Object argumentLayoutStrings = Tracer.UNKNOWN_VALUE; + Object optionsStrings = Tracer.UNKNOWN_VALUE; + Object targetString = Tracer.UNKNOWN_VALUE; + if (isValidResult) { + NativeImageAgentJNIHandleSet handles = agent.handles(); + returnLayoutString = ForeignUtil.getReturnLayoutString(jni, handles, function); + argumentLayoutStrings = ForeignUtil.getArgumentLayoutStrings(jni, handles, function); + optionsStrings = ForeignUtil.getOptionsStrings(jni, handles, options); + targetString = ForeignUtil.getTargetString(jni, handles, target); + } + + traceForeignBreakpoint(jni, bp.specification.methodName, isValidResult, state.getFullStackTraceOrNull(), returnLayoutString, argumentLayoutStrings, optionsStrings, targetString); + return true; + } + private static final CEntryPointLiteral nativeAllocateInstance = CEntryPointLiteral.create( BreakpointInterceptor.class, "nativeAllocateInstance", JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class); @@ -1713,7 +1765,15 @@ private interface BreakpointHandler { optionalBrk("java/lang/Class", "getNestMembers", "()[Ljava/lang/Class;", BreakpointInterceptor::getNestMembers), optionalBrk("java/lang/Class", "getSigners", "()[Ljava/lang/Object;", - BreakpointInterceptor::getSigners) + BreakpointInterceptor::getSigners), + + /* FFM API was introduced in Java 22 */ + brk("jdk/internal/foreign/abi/AbstractLinker", "downcallHandle0", + "(Ljava/lang/foreign/FunctionDescriptor;[Ljava/lang/foreign/Linker$Option;)Ljava/lang/invoke/MethodHandle;", + BreakpointInterceptor::downcallHandle0), + brk("jdk/internal/foreign/abi/AbstractLinker", "upcallStub", + "(Ljava/lang/invoke/MethodHandle;Ljava/lang/foreign/FunctionDescriptor;Ljava/lang/foreign/Arena;[Ljava/lang/foreign/Linker$Option;)Ljava/lang/foreign/MemorySegment;", + BreakpointInterceptor::upcallStub) }; private static boolean allocateInstance(JNIEnvironment jni, JNIObjectHandle thread, @SuppressWarnings("unused") Breakpoint bp, InterceptedState state) { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ForeignUtil.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ForeignUtil.java new file mode 100644 index 000000000000..cdca3266e627 --- /dev/null +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/ForeignUtil.java @@ -0,0 +1,385 @@ +/* + * Copyright (c) 2025, 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 com.oracle.svm.agent; + +import static com.oracle.svm.core.jni.JNIObjectHandles.nullHandle; +import static com.oracle.svm.jvmtiagentbase.Support.clearException; +import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions; + +import java.util.ArrayList; +import java.util.List; + +import com.oracle.svm.agent.tracing.core.Tracer; +import com.oracle.svm.core.jni.headers.JNIEnvironment; +import com.oracle.svm.core.jni.headers.JNIFieldId; +import com.oracle.svm.core.jni.headers.JNIObjectHandle; +import com.oracle.svm.jvmtiagentbase.JNIHandleSet; +import com.oracle.svm.jvmtiagentbase.Support; + +/** + * A utility class that contains helper methods for FFM API tracing. + */ +public final class ForeignUtil extends JNIHandleSet { + + public ForeignUtil(JNIEnvironment env) { + super(env); + } + + /** + * Takes an object of type {@code Optional} and returns the appropriate memory + * layout string. Return type {@code void} is represented by {@code Optional.empty()} (because + * there is no MemoryLayout for it) and will result in string {@code "void"}. Otherwise, + * {@link #getLayoutString} will be applied on the element. + */ + public static String getOptionalReturnLayoutString(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle optionalReturnLayout) { + // Optional + assert isOptional(env, handles, optionalReturnLayout); + boolean isEmpty = callOptionalIsEmpty(env, handles, optionalReturnLayout); + if (clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + if (isEmpty) { + return "void"; + } + // optionalReturnLayout.get() + JNIObjectHandle returnLayout = Support.callObjectMethod(env, optionalReturnLayout, handles.getJavaUtilOptionalGet(env)); + if (clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + return getLayoutString(env, handles, returnLayout); + } + + /** + * Recursively visits the provided memory layout and generates a layout descriptor string + * parsable by {@code com.oracle.svm.hosted.foreign.MemoryLayoutParser}. + */ + public static String getLayoutString(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) { + try { + return visitMemoryLayout(env, handles, layout); + } catch (MemoryLayoutTraverseException e) { + return Tracer.UNKNOWN_VALUE; + } + } + + private static String visitMemoryLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + String layoutString; + if (isValueLayout(env, handles, layout)) { + layoutString = visitValueLayout(env, handles, layout); + } else if (isStructLayout(env, handles, layout)) { + layoutString = visitStructLayout(env, handles, layout); + } else if (isUnionLayout(env, handles, layout)) { + layoutString = visitUnionLayout(env, handles, layout); + } else if (isPaddingLayout(env, handles, layout)) { + layoutString = visitPaddingLayout(env, handles, layout); + } else if (isSequenceLayout(env, handles, layout)) { + layoutString = visitSequenceLayout(env, handles, layout); + } else { + throw MemoryLayoutTraverseException.INSTANCE; + } + return decorateWithAlignmentIfNecessary(env, handles, layout, layoutString); + } + + private static String visitStructLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + String[] members = getMemberLayouts(env, handles, layout); + return "struct(" + String.join(",", members) + ")"; + } + + private static String visitUnionLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + String[] members = getMemberLayouts(env, handles, layout); + return "union(" + String.join(",", members) + ")"; + } + + private static String[] getMemberLayouts(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + // returns java.util.List + JNIObjectHandle memberLayoutsList = Support.callObjectMethod(env, layout, handles.getJavaLangForeignGroupLayoutMemberLayouts(env)); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + // convert list to Object[] + JNIObjectHandle memberLayoutsArray = callListToArray(env, handles, memberLayoutsList); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + // visit each element of the array + List list = mapArrayElements(env, handles, memberLayoutsArray, MEMORY_LAYOUT_TO_STRING); + if (list == null) { + throw MemoryLayoutTraverseException.INSTANCE; + } + return list.toArray(new String[0]); + } + + private static final ArrayElementFunction MEMORY_LAYOUT_TO_STRING = new ArrayElementFunction<>() { + @Override + public String apply(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle element) throws MemoryLayoutTraverseException { + return visitMemoryLayout(env, handles, element); + } + + @Override + public String handleException(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle arrayHandle, int index) throws MemoryLayoutTraverseException { + throw MemoryLayoutTraverseException.INSTANCE; + } + }; + + private static String visitSequenceLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + long elementCount = Support.callLongMethod(env, layout, handles.getJavaLangForeignSequenceLayoutElementCount(env)); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + JNIObjectHandle elementLayout = Support.callObjectMethod(env, layout, handles.getJavaLangForeignSequenceLayoutElementLayout(env)); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + return "sequence(" + elementCount + ", " + visitMemoryLayout(env, handles, elementLayout) + ")"; + } + + private static String visitValueLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + JNIObjectHandle carrier = Support.callObjectMethod(env, layout, handles.getJavaLangForeignValueLayoutCarrier(env)); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + if (jniFunctions().getIsSameObject().invoke(env, handles.getJavaLangForeignMemorySegment(env), carrier)) { + return "void*"; + } + String classNameOrNull = Support.getClassNameOrNull(env, carrier); + if (classNameOrNull == null) { + throw MemoryLayoutTraverseException.INSTANCE; + } + assert isValidCarrierClass(classNameOrNull); + return "j" + classNameOrNull; + } + + private static boolean isValidCarrierClass(String carrierClassName) { + return switch (carrierClassName) { + case "boolean", "byte", "short", "char", "int", "long", "float", "double" -> true; + default -> false; + }; + } + + private static String visitPaddingLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + long byteSize = Support.callLongMethod(env, layout, handles.getJavaLangForeignMemoryLayoutByteSize(env)); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + return "padding(" + byteSize + ")"; + } + + private static String decorateWithAlignmentIfNecessary(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout, String layoutString) throws MemoryLayoutTraverseException { + if (!hasNaturalAlignment(env, handles, layout)) { + long byteAlignment = Support.callLongMethod(env, layout, handles.getJdkInternalForeignLayoutAbstractLayoutByteAlignment(env)); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + return "align(" + byteAlignment + ", " + layoutString + ")"; + } + return layoutString; + } + + private static boolean isPaddingLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) { + return jniFunctions().getIsInstanceOf().invoke(env, layout, handles.getJavaLangForeignPaddingLayout(env)); + } + + private static boolean isUnionLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) { + return jniFunctions().getIsInstanceOf().invoke(env, layout, handles.getJavaLangForeignUnionLayout(env)); + } + + private static boolean isStructLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) { + return jniFunctions().getIsInstanceOf().invoke(env, layout, handles.getJavaLangForeignStructLayout(env)); + } + + private static boolean isSequenceLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) { + return jniFunctions().getIsInstanceOf().invoke(env, layout, handles.getJavaLangForeignSequenceLayout(env)); + } + + private static boolean isValueLayout(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) { + return jniFunctions().getIsInstanceOf().invoke(env, layout, handles.getJavaLangForeignValueLayout(env)); + } + + private static boolean isOptional(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) { + return jniFunctions().getIsInstanceOf().invoke(env, layout, handles.getJavaUtilOptional(env)); + } + + private static boolean callOptionalIsEmpty(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle optionalReturnLayout) { + return Support.callBooleanMethod(env, optionalReturnLayout, handles.getJavaUtilOptionalIsEmpty(env)); + } + + private static boolean hasNaturalAlignment(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle layout) throws MemoryLayoutTraverseException { + boolean hasNaturalAlignment = Support.callBooleanMethod(env, layout, handles.getJdkInternalForeignLayoutAbstractLayoutHasNaturalAlignment(env)); + if (clearException(env)) { + throw MemoryLayoutTraverseException.INSTANCE; + } + return hasNaturalAlignment; + } + + static JNIObjectHandle callListToArray(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle listHandle) { + return Support.callObjectMethod(env, listHandle, handles.getJavaUtilListToArray(env)); + } + + interface ArrayElementFunction { + String apply(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle element) throws E; + + @SuppressWarnings("unused") + default String handleException(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle arrayHandle, int index) throws E { + return Tracer.UNKNOWN_VALUE; + } + } + + static List mapArrayElements(JNIEnvironment jni, NativeImageAgentJNIHandleSet handles, JNIObjectHandle arrayHandle, ArrayElementFunction op) throws E { + int length = jniFunctions().getGetArrayLength().invoke(jni, arrayHandle); + if (!clearException(jni) && length >= 0) { + List result = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + String elementString; + JNIObjectHandle element = jniFunctions().getGetObjectArrayElement().invoke(jni, arrayHandle, i); + if (clearException(jni)) { + elementString = op.handleException(jni, handles, arrayHandle, i); + } else { + elementString = op.apply(jni, handles, element); + } + result.add(elementString); + } + return result; + } + return null; + } + + private static final ArrayElementFunction ARRAY_ELEMENT_TO_STRING = (env, handles, option) -> { + JNIObjectHandle optionString = Support.callObjectMethod(env, option, handles.javaLangObjectToString); + if (!clearException(env)) { + return Support.fromJniString(env, optionString); + } + return Tracer.UNKNOWN_VALUE; + }; + + static Object getOptionsStrings(JNIEnvironment jni, NativeImageAgentJNIHandleSet handles, JNIObjectHandle options) { + Object optionStrings = Tracer.EXPLICIT_NULL; + if (options.notEqual(nullHandle())) { + optionStrings = mapArrayElements(jni, handles, options, ARRAY_ELEMENT_TO_STRING); + } + return optionStrings; + } + + static Object getArgumentLayoutStrings(JNIEnvironment jni, NativeImageAgentJNIHandleSet handles, JNIObjectHandle function) { + // desc.argumentLayouts() -> List + JNIObjectHandle argumentLayoutsList = Support.callObjectMethod(jni, function, handles.getJavaLangForeignFunctionDescriptorArgumentLayouts(jni)); + if (clearException(jni)) { + return Tracer.EXPLICIT_NULL; + } + JNIObjectHandle argumentLayoutsArray = callListToArray(jni, handles, argumentLayoutsList); + if (clearException(jni)) { + return Tracer.EXPLICIT_NULL; + } + return mapArrayElements(jni, handles, argumentLayoutsArray, ForeignUtil::getLayoutString); + } + + static String getReturnLayoutString(JNIEnvironment jni, NativeImageAgentJNIHandleSet handles, JNIObjectHandle function) { + // function.returnLayout() -> Optional + JNIObjectHandle returnLayout = Support.callObjectMethod(jni, function, handles.getJavaLangForeignFunctionDescriptorReturnLayout(jni)); + String returnLayoutString = Tracer.UNKNOWN_VALUE; + if (!clearException(jni)) { + returnLayoutString = getOptionalReturnLayoutString(jni, handles, returnLayout); + } + return returnLayoutString; + } + + private static int javaLangInvokeMethodHandleInfoREFInvokeStatic = -1; + + private static int getJavaLangInvokeMethodHandleInfoREFInvokeStatic(JNIEnvironment env, NativeImageAgentJNIHandleSet handles) { + if (javaLangInvokeMethodHandleInfoREFInvokeStatic == -1) { + JNIObjectHandle javaLangInvokeMethodHandleInfo = handles.findClass(env, "java/lang/invoke/MethodHandleInfo"); + JNIFieldId fieldId = handles.getFieldId(env, javaLangInvokeMethodHandleInfo, "REF_invokeStatic", "I", true); + javaLangInvokeMethodHandleInfoREFInvokeStatic = Support.jniFunctions().getGetStaticIntField().invoke(env, javaLangInvokeMethodHandleInfo, fieldId); + } + return javaLangInvokeMethodHandleInfoREFInvokeStatic; + } + + /** + * Extracts the qualified class name and method name from a crackable method handle and returns + * a string in form of {@code className::methodName} (e.g. + * {@code "Lorg/my/MyClass$InnerClass::foo"}). If extraction fails, {@link Tracer#UNKNOWN_VALUE} + * is returned. + * + * Unfortunately, we cannot just call 'toString()' on the method handle descriptor because the + * output will only contain the simple class name. Therefore, we need to do: + * + *
{@code
+     * Optional methodHandleDescOptional = target.describeConstable();
+     * if (methodHandleDescOptional.isEmpty())
+     *     return Tracer.UNKNOWN_VALUE;
+     * MethodHandleDesc methodHandleDesc = methodHandleDescOptional.get();
+     * if (!(methodHandleDesc instanceof DirectMethodHandleDesc directMethodHandleDesc))
+     *     return Tracer.UNKNOWN_VALUE;
+     * if (directMethodHandleDesc.refKind() != MethodHandleInfo.REF_invokeStatic)
+     *     return Tracer.UNKNOWN_VALUE;
+     * return directMethodHandleDesc.owner().descriptorString() + "::" + methodHandleDesc.methodName();
+     * }
+     * 
+ */ + public static String getTargetString(JNIEnvironment env, NativeImageAgentJNIHandleSet handles, JNIObjectHandle target) { + // Optional describeConstable() + JNIObjectHandle methodHandleDescOptional = Support.callObjectMethod(env, target, handles.getJavaLangInvokeMethodHandleDescribeConstable(env)); + if (clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + boolean isEmpty = callOptionalIsEmpty(env, handles, methodHandleDescOptional); + if (clearException(env) || isEmpty) { + return Tracer.UNKNOWN_VALUE; + } + // methodHandleDescOptional.get() + JNIObjectHandle methodHandleDesc = Support.callObjectMethod(env, methodHandleDescOptional, handles.getJavaUtilOptionalGet(env)); + if (clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + // methodHandleDesc instance DirectMethodHandleDesc + if (!jniFunctions().getIsInstanceOf().invoke(env, methodHandleDesc, handles.getJavaLangConstantDirectMethodHandleDesc(env))) { + return Tracer.UNKNOWN_VALUE; + } + // methodHandleDesc.refKind() != MethodHandleInfo.REF_invokeStatic + int refKind = Support.callIntMethod(env, methodHandleDesc, handles.getJavaLangConstantDirectMethodHandleDescRefKind(env)); + if (clearException(env) || refKind != getJavaLangInvokeMethodHandleInfoREFInvokeStatic(env, handles)) { + return Tracer.UNKNOWN_VALUE; + } + // methodHandleDesc.owner() -> ClassDesc + JNIObjectHandle owner = Support.callObjectMethod(env, methodHandleDesc, handles.getJavaLangConstantDirectMethodHandleDescOwner(env)); + if (clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + // methodHandleDesc.owner().descriptorString() + JNIObjectHandle descriptorString = Support.callObjectMethod(env, owner, handles.getJavaLangConstantClassDescDescriptorString(env)); + if (clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + // methodHandleDesc.methodName() + JNIObjectHandle methodName = Support.callObjectMethod(env, methodHandleDesc, handles.getJavaLangConstantDirectMethodHandleDescMethodName(env)); + if (clearException(env)) { + return Tracer.UNKNOWN_VALUE; + } + return Support.fromJniString(env, descriptorString) + "::" + Support.fromJniString(env, methodName); + } + + @SuppressWarnings("serial") + private static final class MemoryLayoutTraverseException extends Exception { + private static final MemoryLayoutTraverseException INSTANCE = new MemoryLayoutTraverseException(); + } +} diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java index 3f42a8c452ac..62d0d68db7cb 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgentJNIHandleSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -54,6 +54,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIObjectHandle jdkInternalReflectDelegatingClassLoader; final JNIMethodId javaLangObjectGetClass; + final JNIMethodId javaLangObjectToString; final JNIObjectHandle javaLangStackOverflowError; @@ -67,7 +68,6 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { private JNIMethodId javaUtilResourceBundleGetBundleImplSLCC; private boolean queriedJavaUtilResourceBundleGetBundleImplSLCC; - private JNIObjectHandle javaIoObjectStreamClass; private JNIMethodId javaIoObjectStreamClassForClass; private JNIMethodId javaIoObjectStreamClassGetName; private JNIMethodId javaIoObjectStreamClassGetClassDataLayout0; @@ -89,6 +89,12 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { final JNIMethodId javaUtilLocaleToLanguageTag; final JNIMethodId javaUtilLocaleEquals; + private JNIObjectHandle javaUtilOptional; + private JNIMethodId javaUtilOptionalIsEmpty; + private JNIMethodId javaUtilOptionalGet; + + private JNIMethodId javaUtilListToArray; + final JNIFieldId javaLangInvokeSerializedLambdaCapturingClass; final JNIMethodId javaLangModuleGetName; @@ -97,6 +103,30 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { private JNIMethodId javaLangInvokeMethodHandleNativesLinkCallSiteImpl = WordFactory.nullPointer(); private JNIMethodId javaLangInvokeMethodHandleNativesLinkCallSite = WordFactory.nullPointer(); + private JNIMethodId javaLangForeignFunctionDescriptorReturnLayout = WordFactory.nullPointer(); + private JNIMethodId javaLangForeignFunctionDescriptorArgumentLayouts = WordFactory.nullPointer(); + + private JNIObjectHandle javaLangForeignMemorySegment = WordFactory.nullPointer(); + private JNIObjectHandle javaLangForeignStructLayout = WordFactory.nullPointer(); + private JNIObjectHandle javaLangForeignUnionLayout = WordFactory.nullPointer(); + private JNIObjectHandle javaLangForeignSequenceLayout = WordFactory.nullPointer(); + private JNIObjectHandle javaLangForeignValueLayout = WordFactory.nullPointer(); + private JNIObjectHandle javaLangForeignPaddingLayout = WordFactory.nullPointer(); + private JNIMethodId javaLangForeignGroupLayoutMemberLayouts = WordFactory.nullPointer(); + private JNIMethodId javaLangForeignMemoryLayoutByteSize = WordFactory.nullPointer(); + private JNIMethodId javaLangForeignValueLayoutCarrier = WordFactory.nullPointer(); + private JNIMethodId javaLangForeignSequenceLayoutElementCount = WordFactory.nullPointer(); + private JNIMethodId javaLangForeignSequenceLayoutElementLayout = WordFactory.nullPointer(); + private JNIMethodId jdkInternalForeignLayoutAbstractLayoutHasNaturalAlignment = WordFactory.nullPointer(); + private JNIMethodId jdkInternalForeignLayoutAbstractLayoutByteAlignment = WordFactory.nullPointer(); + + private JNIObjectHandle javaLangConstantDirectMethodHandleDesc = WordFactory.nullPointer(); + private JNIMethodId javaLangInvokeMethodHandleDescribeConstable = WordFactory.nullPointer(); + private JNIMethodId javaLangConstantDirectMethodHandleDescRefKind = WordFactory.nullPointer(); + private JNIMethodId javaLangConstantDirectMethodHandleDescOwner = WordFactory.nullPointer(); + private JNIMethodId javaLangConstantDirectMethodHandleDescMethodName = WordFactory.nullPointer(); + private JNIMethodId javaLangConstantClassDescDescriptorString = WordFactory.nullPointer(); + NativeImageAgentJNIHandleSet(JNIEnvironment env) { super(env); javaLangClass = newClassGlobalRef(env, "java/lang/Class"); @@ -120,6 +150,7 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { JNIObjectHandle javaLangObject = findClass(env, "java/lang/Object"); javaLangObjectGetClass = getMethodId(env, javaLangObject, "getClass", "()Ljava/lang/Class;", false); + javaLangObjectToString = getMethodId(env, javaLangObject, "toString", "()Ljava/lang/String;", false); javaLangStackOverflowError = newClassGlobalRef(env, "java/lang/StackOverflowError"); @@ -143,6 +174,41 @@ public class NativeImageAgentJNIHandleSet extends JNIHandleSet { javaUtilLocaleToLanguageTag = getMethodId(env, javaUtilLocale, "toLanguageTag", "()Ljava/lang/String;", false); } + private void initializeForeignHandles(JNIEnvironment env) { + javaLangForeignMemorySegment = newClassGlobalRef(env, "java/lang/foreign/MemorySegment"); + javaLangForeignStructLayout = newClassGlobalRef(env, "java/lang/foreign/StructLayout"); + javaLangForeignUnionLayout = newClassGlobalRef(env, "java/lang/foreign/UnionLayout"); + javaLangForeignSequenceLayout = newClassGlobalRef(env, "java/lang/foreign/SequenceLayout"); + javaLangForeignValueLayout = newClassGlobalRef(env, "java/lang/foreign/ValueLayout"); + javaLangForeignPaddingLayout = newClassGlobalRef(env, "java/lang/foreign/PaddingLayout"); + + JNIObjectHandle groupLayout = findClass(env, "java/lang/foreign/GroupLayout"); + javaLangForeignGroupLayoutMemberLayouts = getMethodId(env, groupLayout, "memberLayouts", "()Ljava/util/List;", false); + + JNIObjectHandle memoryLayout = findClass(env, "java/lang/foreign/MemoryLayout"); + javaLangForeignMemoryLayoutByteSize = getMethodId(env, memoryLayout, "byteSize", "()J", false); + + javaLangForeignValueLayoutCarrier = getMethodId(env, javaLangForeignValueLayout, "carrier", "()Ljava/lang/Class;", false); + + javaLangForeignSequenceLayoutElementCount = getMethodId(env, javaLangForeignSequenceLayout, "elementCount", "()J", false); + javaLangForeignSequenceLayoutElementLayout = getMethodId(env, javaLangForeignSequenceLayout, "elementLayout", "()Ljava/lang/foreign/MemoryLayout;", false); + + JNIObjectHandle javaLangInvokeAbstractLayout = findClass(env, "jdk/internal/foreign/layout/AbstractLayout"); + jdkInternalForeignLayoutAbstractLayoutHasNaturalAlignment = getMethodId(env, javaLangInvokeAbstractLayout, "hasNaturalAlignment", "()Z", false); + jdkInternalForeignLayoutAbstractLayoutByteAlignment = getMethodId(env, javaLangInvokeAbstractLayout, "byteAlignment", "()J", false); + + JNIObjectHandle javaLangInvokeMethodHandle = findClass(env, "java/lang/invoke/MethodHandle"); + javaLangInvokeMethodHandleDescribeConstable = getMethodId(env, javaLangInvokeMethodHandle, "describeConstable", "()Ljava/util/Optional;", false); + + javaLangConstantDirectMethodHandleDesc = newClassGlobalRef(env, "java/lang/constant/DirectMethodHandleDesc"); + javaLangConstantDirectMethodHandleDescRefKind = getMethodId(env, javaLangConstantDirectMethodHandleDesc, "refKind", "()I", false); + javaLangConstantDirectMethodHandleDescOwner = getMethodId(env, javaLangConstantDirectMethodHandleDesc, "owner", "()Ljava/lang/constant/ClassDesc;", false); + javaLangConstantDirectMethodHandleDescMethodName = getMethodId(env, javaLangConstantDirectMethodHandleDesc, "methodName", "()Ljava/lang/String;", false); + + JNIObjectHandle javaLangConstantClassDesc = findClass(env, "java/lang/constant/ClassDesc"); + javaLangConstantClassDescDescriptorString = getMethodId(env, javaLangConstantClassDesc, "descriptorString", "()Ljava/lang/String;", false); + } + JNIMethodId getJavaLangReflectExecutableGetParameterTypes(JNIEnvironment env) { if (javaLangReflectExecutableGetParameterTypes.isNull()) { JNIObjectHandle javaLangReflectExecutable = findClass(env, "java/lang/reflect/Executable"); @@ -177,13 +243,6 @@ JNIMethodId tryGetJavaUtilResourceBundleGetBundleImplSLCC(JNIEnvironment env) { return javaUtilResourceBundleGetBundleImplSLCC; } - JNIObjectHandle getJavaIOObjectStreamClass(JNIEnvironment env) { - if (javaIoObjectStreamClass.equal(nullHandle())) { - javaIoObjectStreamClass = findClass(env, "java/io/ObjectStreamClass"); - } - return javaIoObjectStreamClass; - } - JNIMethodId getJavaIoObjectStreamClassForClass(JNIEnvironment env, JNIObjectHandle javaIoObjectStreamClassParam) { if (javaIoObjectStreamClassForClass.equal(nullHandle())) { javaIoObjectStreamClassForClass = getMethodId(env, javaIoObjectStreamClassParam, "forClass", "()Ljava/lang/Class;", false); @@ -193,7 +252,8 @@ JNIMethodId getJavaIoObjectStreamClassForClass(JNIEnvironment env, JNIObjectHand JNIMethodId getJavaIoObjectStreamClassGetName(JNIEnvironment env) { if (javaIoObjectStreamClassGetName.equal(nullHandle())) { - javaIoObjectStreamClassGetName = getMethodId(env, getJavaIOObjectStreamClass(env), "getName", "()Ljava/lang/String;", false); + JNIObjectHandle javaIoObjectStreamClass = findClass(env, "java/io/ObjectStreamClass"); + javaIoObjectStreamClassGetName = getMethodId(env, javaIoObjectStreamClass, "getName", "()Ljava/lang/String;", false); } return javaIoObjectStreamClassGetName; } @@ -282,4 +342,184 @@ public JNIMethodId getJavaLangInvokeMethodHandleNativesLinkCallSite(JNIEnvironme } return javaLangInvokeMethodHandleNativesLinkCallSite; } + + JNIObjectHandle getJavaUtilOptional(JNIEnvironment env) { + if (javaUtilOptional.equal(nullHandle())) { + javaUtilOptional = newClassGlobalRef(env, "java/util/Optional"); + } + return javaUtilOptional; + } + + JNIMethodId getJavaUtilOptionalIsEmpty(JNIEnvironment env) { + if (javaUtilOptionalIsEmpty.isNull()) { + javaUtilOptionalIsEmpty = getMethodId(env, getJavaUtilOptional(env), "isEmpty", "()Z", false); + } + return javaUtilOptionalIsEmpty; + } + + JNIMethodId getJavaUtilOptionalGet(JNIEnvironment env) { + if (javaUtilOptionalGet.isNull()) { + javaUtilOptionalGet = getMethodId(env, getJavaUtilOptional(env), "get", "()Ljava/lang/Object;", false); + } + return javaUtilOptionalGet; + } + + JNIMethodId getJavaUtilListToArray(JNIEnvironment env) { + if (javaUtilListToArray.isNull()) { + JNIObjectHandle javaUtilList = findClass(env, "java/util/List"); + javaUtilListToArray = getMethodId(env, javaUtilList, "toArray", "()[Ljava/lang/Object;", false); + } + return javaUtilListToArray; + } + + JNIMethodId getJavaLangForeignFunctionDescriptorReturnLayout(JNIEnvironment env) { + if (javaLangForeignFunctionDescriptorReturnLayout.isNull()) { + JNIObjectHandle javaLangForeignFunctionDescriptor = findClass(env, "java/lang/foreign/FunctionDescriptor"); + javaLangForeignFunctionDescriptorReturnLayout = getMethodIdOptional(env, javaLangForeignFunctionDescriptor, "returnLayout", + "()Ljava/util/Optional;", false); + } + return javaLangForeignFunctionDescriptorReturnLayout; + } + + JNIMethodId getJavaLangForeignFunctionDescriptorArgumentLayouts(JNIEnvironment env) { + if (javaLangForeignFunctionDescriptorArgumentLayouts.isNull()) { + JNIObjectHandle javaLangForeignFunctionDescriptor = findClass(env, "java/lang/foreign/FunctionDescriptor"); + javaLangForeignFunctionDescriptorArgumentLayouts = getMethodIdOptional(env, javaLangForeignFunctionDescriptor, "argumentLayouts", + "()Ljava/util/List;", false); + } + return javaLangForeignFunctionDescriptorArgumentLayouts; + } + + JNIMethodId getJavaLangForeignGroupLayoutMemberLayouts(JNIEnvironment env) { + if (javaLangForeignGroupLayoutMemberLayouts.isNull()) { + initializeForeignHandles(env); + } + return javaLangForeignGroupLayoutMemberLayouts; + } + + JNIMethodId getJavaLangForeignSequenceLayoutElementCount(JNIEnvironment env) { + if (javaLangForeignSequenceLayoutElementCount.isNull()) { + initializeForeignHandles(env); + } + return javaLangForeignSequenceLayoutElementCount; + } + + JNIMethodId getJavaLangForeignSequenceLayoutElementLayout(JNIEnvironment env) { + if (javaLangForeignSequenceLayoutElementLayout.isNull()) { + initializeForeignHandles(env); + } + return javaLangForeignSequenceLayoutElementLayout; + } + + JNIMethodId getJavaLangForeignValueLayoutCarrier(JNIEnvironment env) { + if (javaLangForeignValueLayoutCarrier.isNull()) { + initializeForeignHandles(env); + } + return javaLangForeignValueLayoutCarrier; + } + + JNIMethodId getJavaLangForeignMemoryLayoutByteSize(JNIEnvironment env) { + if (javaLangForeignMemoryLayoutByteSize.isNull()) { + initializeForeignHandles(env); + } + return javaLangForeignMemoryLayoutByteSize; + } + + JNIObjectHandle getJavaLangForeignPaddingLayout(JNIEnvironment env) { + if (javaLangForeignPaddingLayout.equal(nullHandle())) { + initializeForeignHandles(env); + } + return javaLangForeignPaddingLayout; + } + + JNIObjectHandle getJavaLangForeignUnionLayout(JNIEnvironment env) { + if (javaLangForeignUnionLayout.equal(nullHandle())) { + initializeForeignHandles(env); + } + return javaLangForeignUnionLayout; + } + + JNIObjectHandle getJavaLangForeignStructLayout(JNIEnvironment env) { + if (javaLangForeignStructLayout.equal(nullHandle())) { + initializeForeignHandles(env); + } + return javaLangForeignStructLayout; + } + + JNIObjectHandle getJavaLangForeignSequenceLayout(JNIEnvironment env) { + if (javaLangForeignSequenceLayout.equal(nullHandle())) { + initializeForeignHandles(env); + } + return javaLangForeignSequenceLayout; + } + + JNIObjectHandle getJavaLangForeignValueLayout(JNIEnvironment env) { + if (javaLangForeignValueLayout.equal(nullHandle())) { + initializeForeignHandles(env); + } + return javaLangForeignValueLayout; + } + + JNIObjectHandle getJavaLangForeignMemorySegment(JNIEnvironment env) { + if (javaLangForeignMemorySegment.equal(nullHandle())) { + initializeForeignHandles(env); + } + return javaLangForeignMemorySegment; + } + + public JNIMethodId getJdkInternalForeignLayoutAbstractLayoutHasNaturalAlignment(JNIEnvironment env) { + if (jdkInternalForeignLayoutAbstractLayoutHasNaturalAlignment.isNull()) { + initializeForeignHandles(env); + } + return jdkInternalForeignLayoutAbstractLayoutHasNaturalAlignment; + } + + public JNIMethodId getJdkInternalForeignLayoutAbstractLayoutByteAlignment(JNIEnvironment env) { + if (jdkInternalForeignLayoutAbstractLayoutByteAlignment.isNull()) { + initializeForeignHandles(env); + } + return jdkInternalForeignLayoutAbstractLayoutByteAlignment; + } + + public JNIMethodId getJavaLangInvokeMethodHandleDescribeConstable(JNIEnvironment env) { + if (javaLangInvokeMethodHandleDescribeConstable.isNull()) { + initializeForeignHandles(env); + } + return javaLangInvokeMethodHandleDescribeConstable; + } + + public JNIObjectHandle getJavaLangConstantDirectMethodHandleDesc(JNIEnvironment env) { + if (javaLangConstantDirectMethodHandleDesc.equal(nullHandle())) { + initializeForeignHandles(env); + } + return javaLangConstantDirectMethodHandleDesc; + } + + public JNIMethodId getJavaLangConstantDirectMethodHandleDescRefKind(JNIEnvironment env) { + if (javaLangConstantDirectMethodHandleDescRefKind.isNull()) { + initializeForeignHandles(env); + } + return javaLangConstantDirectMethodHandleDescRefKind; + } + + public JNIMethodId getJavaLangConstantDirectMethodHandleDescOwner(JNIEnvironment env) { + if (javaLangConstantDirectMethodHandleDescOwner.isNull()) { + initializeForeignHandles(env); + } + return javaLangConstantDirectMethodHandleDescOwner; + } + + public JNIMethodId getJavaLangConstantDirectMethodHandleDescMethodName(JNIEnvironment env) { + if (javaLangConstantDirectMethodHandleDescMethodName.isNull()) { + initializeForeignHandles(env); + } + return javaLangConstantDirectMethodHandleDescMethodName; + } + + public JNIMethodId getJavaLangConstantClassDescDescriptorString(JNIEnvironment env) { + if (javaLangConstantClassDescDescriptorString.isNull()) { + initializeForeignHandles(env); + } + return javaLangConstantClassDescDescriptorString; + } } diff --git a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java index f65d8d273616..7f4027220745 100644 --- a/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java +++ b/substratevm/src/com.oracle.svm.configure.test/src/com/oracle/svm/configure/test/config/OmitPreviousConfigTests.java @@ -102,6 +102,7 @@ public void testSameConfig() { assertTrue(config.getResourceConfiguration().isEmpty()); assertTrue(config.getSerializationConfiguration().isEmpty()); assertTrue(config.getPredefinedClassesConfiguration().isEmpty()); + assertTrue(config.getForeignConfiguration().isEmpty()); } @Test diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationFile.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationFile.java index f17695a7817d..3627e532f546 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationFile.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2021, 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 @@ -24,6 +24,7 @@ */ package com.oracle.svm.configure; +import static com.oracle.svm.configure.ConfigurationParser.FOREIGN_KEY; import static com.oracle.svm.configure.ConfigurationParser.JNI_KEY; import static com.oracle.svm.configure.ConfigurationParser.REFLECTION_KEY; import static com.oracle.svm.configure.ConfigurationParser.RESOURCES_KEY; @@ -43,7 +44,7 @@ public enum ConfigurationFile { DYNAMIC_PROXY("proxy", null, true, false), PREDEFINED_CLASSES_NAME("predefined-classes", null, true, false), /* Non-metadata categories */ - FOREIGN("foreign", null, false, false), + FOREIGN("foreign", FOREIGN_KEY, true, false), SERIALIZATION_DENY("serialization-deny", null, false, false); public static final String LEGACY_FILE_NAME_SUFFIX = "-config.json"; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java index 79cd8f4fd1b2..a677f0d21e7e 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationParser.java @@ -65,6 +65,7 @@ public static InputStream openStream(URI uri) throws IOException { public static final String PROXY_KEY = "proxy"; public static final String REFLECTION_KEY = "reflection"; public static final String JNI_KEY = "jni"; + public static final String FOREIGN_KEY = "foreign"; public static final String SERIALIZATION_KEY = "serialization"; public static final String RESOURCES_KEY = "resources"; public static final String BUNDLES_KEY = "bundles"; diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ForeignConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ForeignConfigurationParser.java new file mode 100644 index 000000000000..65147491f835 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ForeignConfigurationParser.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2025, 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 com.oracle.svm.configure; + +import java.net.URI; +import java.util.EnumSet; +import java.util.List; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +/** + * A base class for parsing FFM API configurations. + * + * @param the type of the function descriptor + * @param the type of the linker options + */ +public abstract class ForeignConfigurationParser extends ConfigurationParser { + private static final String PARAMETER_TYPES = "parameterTypes"; + private static final String RETURN_TYPE = "returnType"; + + public ForeignConfigurationParser(EnumSet parserOptions) { + super(parserOptions); + } + + @Override + public void parseAndRegister(Object json, URI origin) { + var topLevel = asMap(json, "first level of document must be a map"); + checkAttributes(topLevel, "foreign methods categories", List.of(), List.of("downcalls", "upcalls", "directUpcalls")); + + var downcalls = asList(topLevel.get("downcalls", List.of()), "downcalls must be an array of function descriptor and linker options"); + for (Object downcall : downcalls) { + parseAndRegisterForeignCall(downcall, false); + } + + var upcalls = asList(topLevel.get("upcalls", List.of()), "upcalls must be an array of function descriptor and linker options"); + for (Object upcall : upcalls) { + parseAndRegisterForeignCall(upcall, true); + } + + var directUpcalls = asList(topLevel.get("directUpcalls", List.of()), "direct upcalls must be an array of method references, function descriptors, and linker options"); + for (Object upcall : directUpcalls) { + parseAndRegisterDirectUpcall(upcall); + } + } + + private void parseAndRegisterForeignCall(Object call, boolean forUpcall) { + var map = asMap(call, "a foreign call must be a map"); + checkAttributes(map, "foreign call", List.of(RETURN_TYPE, PARAMETER_TYPES), List.of("options")); + var descriptor = createFunctionDescriptor(map); + var optionsMap = asMap(map.get("options", EconomicMap.emptyMap()), "options must be a map"); + if (forUpcall) { + LO upcallOptions = createUpcallOptions(optionsMap, descriptor); + try { + registerUpcall(ConfigurationCondition.alwaysTrue(), descriptor, upcallOptions); + } catch (Exception e) { + handleRegistrationError(e, map); + } + } else { + LO downcallOptions = createDowncallOptions(optionsMap, descriptor); + try { + registerDowncall(ConfigurationCondition.alwaysTrue(), descriptor, downcallOptions); + } catch (Exception e) { + handleRegistrationError(e, map); + } + } + } + + private void parseAndRegisterDirectUpcall(Object call) { + var map = asMap(call, "a foreign call must be a map"); + checkAttributes(map, "foreign call", List.of("class", "method"), List.of(RETURN_TYPE, PARAMETER_TYPES, "options")); + + String className = asString(map.get("class"), "class"); + String methodName = asString(map.get("method"), "method"); + Object returnTypeInput = map.get(RETURN_TYPE); + Object parameterTypesInput = map.get(PARAMETER_TYPES); + var optionsMap = asMap(map.get("options", EconomicMap.emptyMap()), "options must be a map"); + + if (returnTypeInput != null || parameterTypesInput != null) { + FD descriptor = createFunctionDescriptor(map); + LO upcallOptions = createUpcallOptions(optionsMap, descriptor); + try { + registerDirectUpcallWithDescriptor(className, methodName, descriptor, upcallOptions); + } catch (Exception e) { + handleRegistrationError(e, map); + } + } else { + try { + registerDirectUpcallWithoutDescriptor(className, methodName, optionsMap); + } catch (Exception e) { + handleRegistrationError(e, map); + } + } + } + + private FD createFunctionDescriptor(EconomicMap map) { + String returnTypeInput = asString(map.get(RETURN_TYPE), RETURN_TYPE); + List tmpParameterTypes = asList(map.get(PARAMETER_TYPES), "Element '" + PARAMETER_TYPES + "' must be a list"); + + String[] parameterTypes = new String[tmpParameterTypes.size()]; + for (int i = 0; i < tmpParameterTypes.size(); i++) { + parameterTypes[i] = asString(tmpParameterTypes.get(i), String.format("%s[%d]", PARAMETER_TYPES, i)); + } + return createFunctionDescriptor(returnTypeInput, List.of(parameterTypes)); + } + + /** + * Parses the descriptor based on the provided return type input and parameter types. + * + * @param returnType the return type of the descriptor + * @param parameterTypes the parameter types of the descriptor + * @return the parsed descriptor + */ + protected abstract FD createFunctionDescriptor(String returnType, List parameterTypes); + + /** Parses the options allowed for downcalls. */ + protected abstract LO createDowncallOptions(EconomicMap map, FD desc); + + /** Parses the options allowed for upcalls. */ + protected abstract LO createUpcallOptions(EconomicMap map, FD desc); + + protected abstract void registerDowncall(ConfigurationCondition configurationCondition, FD descriptor, LO options); + + protected abstract void registerUpcall(ConfigurationCondition configurationCondition, FD descriptor, LO options); + + protected abstract void registerDirectUpcallWithoutDescriptor(String className, String methodName, EconomicMap optionsMap); + + protected abstract void registerDirectUpcallWithDescriptor(String className, String methodName, FD descriptor, LO options); + + protected abstract void handleRegistrationError(Exception e, EconomicMap map); +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java index 615e24a482c6..48af0c4f4898 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationFileCollection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 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 @@ -66,6 +66,7 @@ public ConfigurationFileCollection() { private final Set resourceConfigPaths = new LinkedHashSet<>(); private final Set serializationConfigPaths = new LinkedHashSet<>(); private final Set predefinedClassesConfigPaths = new LinkedHashSet<>(); + private final Set foreignConfigPaths = new LinkedHashSet<>(); private Set lockFilePaths; public void addDirectory(Path path) { @@ -76,6 +77,7 @@ public void addDirectory(Path path) { resourceConfigPaths.add(path.resolve(ConfigurationFile.RESOURCES.getFileName()).toUri()); serializationConfigPaths.add(path.resolve(ConfigurationFile.SERIALIZATION.getFileName()).toUri()); predefinedClassesConfigPaths.add(path.resolve(ConfigurationFile.PREDEFINED_CLASSES_NAME.getFileName()).toUri()); + foreignConfigPaths.add(path.resolve(ConfigurationFile.FOREIGN.getFileName()).toUri()); detectAgentLock(path.resolve(ConfigurationFile.LOCK_FILE_NAME), Files::exists, Path::toUri); } @@ -96,6 +98,7 @@ public void addDirectory(Function fileResolver) { addFile(resourceConfigPaths, fileResolver, ConfigurationFile.RESOURCES); addFile(serializationConfigPaths, fileResolver, ConfigurationFile.SERIALIZATION); addFile(predefinedClassesConfigPaths, fileResolver, ConfigurationFile.PREDEFINED_CLASSES_NAME); + addFile(foreignConfigPaths, fileResolver, ConfigurationFile.FOREIGN); detectAgentLock(fileResolver.apply(ConfigurationFile.LOCK_FILE_NAME), Objects::nonNull, Function.identity()); } @@ -125,6 +128,7 @@ public Set getPaths(ConfigurationFile configurationFile) { case REFLECTION -> uris = getReflectConfigPaths(); case SERIALIZATION -> uris = getSerializationConfigPaths(); case PREDEFINED_CLASSES_NAME -> uris = getPredefinedClassesConfigPaths(); + case FOREIGN -> uris = getForeignConfigPaths(); default -> throw new IllegalArgumentException("Cannot get paths for configuration file " + configurationFile); } return uris.stream().map(Paths::get).collect(Collectors.toSet()); @@ -158,6 +162,10 @@ public Set getPredefinedClassesConfigPaths() { return predefinedClassesConfigPaths; } + public Set getForeignConfigPaths() { + return foreignConfigPaths; + } + public TypeConfiguration loadReflectConfig(Function exceptionHandler) throws Exception { TypeConfiguration reflectConfig = loadTypeConfig(ConfigurationFile.REFLECTION, reflectConfigPaths, exceptionHandler); TypeConfiguration jniConfig = loadTypeConfig(ConfigurationFile.JNI, jniConfigPaths, exceptionHandler); @@ -177,6 +185,12 @@ public PredefinedClassesConfiguration loadPredefinedClassesConfig(List exceptionHandler) throws Exception { + ForeignConfiguration foreignConfiguration = new ForeignConfiguration(); + loadConfig(foreignConfigPaths, foreignConfiguration.createParser(false, parserOptions), exceptionHandler); + return foreignConfiguration; + } + public ResourceConfiguration loadResourceConfig(Function exceptionHandler) throws Exception { ResourceConfiguration resourceConfiguration = new ResourceConfiguration(); loadConfig(reachabilityMetadataPaths, resourceConfiguration.createParser(true, parserOptions), exceptionHandler); @@ -195,7 +209,7 @@ public ConfigurationSet loadConfigurationSet(Function ex Predicate predefinedConfigClassWithHashExclusionPredicate) throws Exception { return new ConfigurationSet(loadReflectConfig(exceptionHandler), loadResourceConfig(exceptionHandler), loadProxyConfig(exceptionHandler), loadSerializationConfig(exceptionHandler), - loadPredefinedClassesConfig(predefinedConfigClassDestinationDirs, predefinedConfigClassWithHashExclusionPredicate, exceptionHandler)); + loadPredefinedClassesConfig(predefinedConfigClassDestinationDirs, predefinedConfigClassWithHashExclusionPredicate, exceptionHandler), loadForeignConfig(exceptionHandler)); } private TypeConfiguration loadTypeConfig(ConfigurationFile configurationKind, Collection uris, Function exceptionHandler) throws Exception { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java index bbaaef82e35d..acafec14115f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationSet.java @@ -51,25 +51,27 @@ private interface Mutator { private final ProxyConfiguration proxyConfiguration; private final SerializationConfiguration serializationConfiguration; private final PredefinedClassesConfiguration predefinedClassesConfiguration; + private final ForeignConfiguration foreignConfiguration; public ConfigurationSet(TypeConfiguration reflectionConfiguration, ResourceConfiguration resourceConfiguration, ProxyConfiguration proxyConfiguration, - SerializationConfiguration serializationConfiguration, PredefinedClassesConfiguration predefinedClassesConfiguration) { + SerializationConfiguration serializationConfiguration, PredefinedClassesConfiguration predefinedClassesConfiguration, ForeignConfiguration foreignConfiguration) { this.reflectionConfiguration = reflectionConfiguration; this.resourceConfiguration = resourceConfiguration; this.proxyConfiguration = proxyConfiguration; this.serializationConfiguration = serializationConfiguration; this.predefinedClassesConfiguration = predefinedClassesConfiguration; + this.foreignConfiguration = foreignConfiguration; } public ConfigurationSet(ConfigurationSet other) { this(other.reflectionConfiguration.copy(), other.resourceConfiguration.copy(), other.proxyConfiguration.copy(), other.serializationConfiguration.copy(), - other.predefinedClassesConfiguration.copy()); + other.predefinedClassesConfiguration.copy(), other.foreignConfiguration.copy()); } @SuppressWarnings("unchecked") public ConfigurationSet() { this(new TypeConfiguration(), new ResourceConfiguration(), new ProxyConfiguration(), new SerializationConfiguration(), - new PredefinedClassesConfiguration(Collections.emptyList(), hash -> false)); + new PredefinedClassesConfiguration(Collections.emptyList(), hash -> false), new ForeignConfiguration()); } private ConfigurationSet mutate(ConfigurationSet other, Mutator mutator) { @@ -78,7 +80,8 @@ private ConfigurationSet mutate(ConfigurationSet other, Mutator mutator) { ProxyConfiguration proxyConfig = mutator.apply(this.proxyConfiguration, other.proxyConfiguration); SerializationConfiguration serializationConfig = mutator.apply(this.serializationConfiguration, other.serializationConfiguration); PredefinedClassesConfiguration predefinedClassesConfig = mutator.apply(this.predefinedClassesConfiguration, other.predefinedClassesConfiguration); - return new ConfigurationSet(reflectionConfig, resourceConfig, proxyConfig, serializationConfig, predefinedClassesConfig); + ForeignConfiguration foreignConfig = mutator.apply(this.foreignConfiguration, other.foreignConfiguration); + return new ConfigurationSet(reflectionConfig, resourceConfig, proxyConfig, serializationConfig, predefinedClassesConfig, foreignConfig); } public ConfigurationSet copyAndMerge(ConfigurationSet other) { @@ -99,7 +102,8 @@ public ConfigurationSet filter(ConditionalConfigurationPredicate filter) { ProxyConfiguration proxyConfig = this.proxyConfiguration.copyAndFilter(filter); SerializationConfiguration serializationConfig = this.serializationConfiguration.copyAndFilter(filter); PredefinedClassesConfiguration predefinedClassesConfig = this.predefinedClassesConfiguration.copyAndFilter(filter); - return new ConfigurationSet(reflectionConfig, resourceConfig, proxyConfig, serializationConfig, predefinedClassesConfig); + ForeignConfiguration foreignConfig = this.foreignConfiguration.copyAndFilter(filter); + return new ConfigurationSet(reflectionConfig, resourceConfig, proxyConfig, serializationConfig, predefinedClassesConfig, foreignConfig); } public TypeConfiguration getReflectionConfiguration() { @@ -122,6 +126,10 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { return predefinedClassesConfiguration; } + public ForeignConfiguration getForeignConfiguration() { + return foreignConfiguration; + } + @SuppressWarnings("unchecked") public > T getConfiguration(ConfigurationFile configurationFile) { switch (configurationFile) { @@ -136,6 +144,8 @@ public PredefinedClassesConfiguration getPredefinedClassesConfiguration() { return (T) serializationConfiguration; case PREDEFINED_CLASSES_NAME: return (T) predefinedClassesConfiguration; + case FOREIGN: + return (T) foreignConfiguration; default: throw new IllegalArgumentException("Unsupported configuration in configuration container: " + configurationFile); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java new file mode 100644 index 000000000000..b38e2d234504 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java @@ -0,0 +1,394 @@ +/* + * Copyright (c) 2025, 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 com.oracle.svm.configure.config; + +import java.io.IOException; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.graalvm.collections.EconomicMap; +import org.graalvm.collections.MapCursor; +import org.graalvm.nativeimage.impl.ConfigurationCondition; + +import com.oracle.svm.configure.ConfigurationBase; +import com.oracle.svm.configure.ConfigurationParser; +import com.oracle.svm.configure.ConfigurationParserOption; +import com.oracle.svm.configure.ForeignConfigurationParser; +import com.oracle.svm.configure.UnresolvedConfigurationCondition; + +import jdk.graal.compiler.util.json.JsonPrintable; +import jdk.graal.compiler.util.json.JsonWriter; + +public final class ForeignConfiguration extends ConfigurationBase { + public record ConfigurationFunctionDescriptor(String returnType, List parameterTypes) implements JsonPrintable { + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.appendKeyValue("returnType", returnType).appendSeparator() + .quote("parameterTypes").appendFieldSeparator().print(parameterTypes); + } + } + + private record StubDesc(ConfigurationFunctionDescriptor desc, Map linkerOptions) implements JsonPrintable { + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.appendObjectStart(); + desc.printJson(writer); + if (!linkerOptions.isEmpty()) { + writer.appendSeparator().quote("options").appendFieldSeparator().print(linkerOptions); + } + writer.appendObjectEnd(); + } + } + + private record DirectStubDesc(String clazz, String method, ConfigurationFunctionDescriptor desc, Map linkerOptions) implements JsonPrintable { + @Override + public void printJson(JsonWriter writer) throws IOException { + writer.appendObjectStart() + .appendKeyValue("class", clazz).appendSeparator() + .appendKeyValue("method", method).appendSeparator(); + if (desc != null) { + desc.printJson(writer); + } + if (!linkerOptions.isEmpty()) { + writer.appendSeparator().quote("options").appendFieldSeparator().print(linkerOptions); + } + writer.appendObjectEnd(); + } + + public DirectStubDesc withoutFD() { + if (desc == null) { + return this; + } + return new DirectStubDesc(clazz, method, null, linkerOptions); + } + } + + private final Set downcallStubs = ConcurrentHashMap.newKeySet(); + private final Set upcallStubs = ConcurrentHashMap.newKeySet(); + private final Set directUpcallStubs = ConcurrentHashMap.newKeySet(); + + public ForeignConfiguration() { + } + + public ForeignConfiguration(ForeignConfiguration other) { + downcallStubs.addAll(other.downcallStubs); + upcallStubs.addAll(other.upcallStubs); + directUpcallStubs.addAll(other.directUpcallStubs); + } + + @Override + public ForeignConfiguration copy() { + return new ForeignConfiguration(this); + } + + @Override + protected void merge(ForeignConfiguration other) { + downcallStubs.addAll(other.downcallStubs); + upcallStubs.addAll(other.upcallStubs); + + /*- + * First, add all direct upcall stubs from 'other'. Second, remove all stub descs that are + * subsumed by any other stub desc. A stub desc Desc0 is subsumed by a stub desc Desc1 if + * both denote the same method and linker options but Desc0 has a function descriptor and + * Desc1 doesn't. In this case, Desc1 already denotes all possible overloads of the method. + * + * Example: + * directUpcallStubs={ + * DirectStubDesc(class="A", methodName="foo", desc=null, linkerOptions={}), + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["long"]), linkerOptions={}) + * } + * + * other.directUpcallStubs={ + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={"captureCallState": true}) + * DirectStubDesc(class="A", methodName="bar", desc=null, linkerOptions={}), + * } + * result_step_1={ + * DirectStubDesc(class="A", methodName="foo", desc=null, linkerOptions={}), + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={"captureCallState": true}) + * DirectStubDesc(class="A", methodName="bar", desc=null, linkerOptions={}), + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["long"]), linkerOptions={}) + * } + * result_step_2={ + * DirectStubDesc(class="A", methodName="foo", desc=null, linkerOptions={}), + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={"captureCallState": true}) + * DirectStubDesc(class="A", methodName="bar", desc=null, linkerOptions={}), + * } + */ + directUpcallStubs.addAll(other.directUpcallStubs); + directUpcallStubs.removeIf(e -> e.desc != null && directUpcallStubs.contains(e.withoutFD())); + } + + @Override + protected void intersect(ForeignConfiguration other) { + downcallStubs.retainAll(other.downcallStubs); + upcallStubs.retainAll(other.upcallStubs); + Set tmp = new HashSet<>(); + /*- + * Example: directUpcallStubs={ + * DirectStubDesc(class="A", methodName="foo", desc=null, linkerOptions={}), + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["long"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="baz", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * } + * + * other.directUpcallStubs={ + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={"captureCallState": true}) + * DirectStubDesc(class="A", methodName="bar", desc=null, linkerOptions={}), + * } + * tmp_step_1={ + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["long"]), linkerOptions={}) + * } + * tmp_step_2={ + * DirectStubDesc(class="A", methodName="foo", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["int"]), linkerOptions={}) + * DirectStubDesc(class="A", methodName="bar", desc=ConfigurationFunctionDescriptor(returnType="void", parameterTypes=["long"]), linkerOptions={}) + * } + */ + for (DirectStubDesc e : directUpcallStubs) { + if (other.directUpcallStubs.contains(e) || other.directUpcallStubs.contains(e.withoutFD())) { + tmp.add(e); + } + } + for (DirectStubDesc e : other.directUpcallStubs) { + if (directUpcallStubs.contains(e) || directUpcallStubs.contains(e.withoutFD())) { + tmp.add(e); + } + } + directUpcallStubs.clear(); + directUpcallStubs.addAll(tmp); + } + + @Override + protected void removeIf(Predicate predicate) { + downcallStubs.removeIf(element -> predicate.testDowncall(element.desc, element.linkerOptions)); + upcallStubs.removeIf(element -> predicate.testUpcall(element.desc, element.linkerOptions)); + directUpcallStubs.removeIf(element -> predicate.testDirectUpcall(element.clazz, element.method, element.desc, element.linkerOptions)); + } + + @Override + public void subtract(ForeignConfiguration other) { + downcallStubs.removeAll(other.downcallStubs); + upcallStubs.removeAll(other.upcallStubs); + directUpcallStubs.removeAll(other.directUpcallStubs); + } + + @Override + public void mergeConditional(UnresolvedConfigurationCondition condition, ForeignConfiguration other) { + // GR-64144: Not implemented with conditions yet + merge(other); + } + + public void addDowncall(String returnType, List parameterTypes, Map linkerOptions) { + Objects.requireNonNull(returnType); + Objects.requireNonNull(parameterTypes); + addDowncall(new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions)); + } + + public void addUpcall(String returnType, List parameterTypes, Map linkerOptions) { + Objects.requireNonNull(returnType); + Objects.requireNonNull(parameterTypes); + addUpcall(new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions)); + } + + public void addDirectUpcall(String returnType, List parameterTypes, Map linkerOptions, String clazz, String method) { + Objects.requireNonNull(returnType); + Objects.requireNonNull(parameterTypes); + Objects.requireNonNull(clazz); + Objects.requireNonNull(method); + addDirectUpcall(new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions), clazz, method); + } + + public void addDowncall(ConfigurationFunctionDescriptor desc, Map linkerOptions) { + Objects.requireNonNull(desc); + downcallStubs.add(new StubDesc(desc, Map.copyOf(linkerOptions))); + } + + public void addUpcall(ConfigurationFunctionDescriptor desc, Map linkerOptions) { + Objects.requireNonNull(desc); + upcallStubs.add(new StubDesc(desc, Map.copyOf(linkerOptions))); + } + + public void addDirectUpcall(ConfigurationFunctionDescriptor desc, Map linkerOptions, String clazz, String method) { + Objects.requireNonNull(desc); + Objects.requireNonNull(clazz); + Objects.requireNonNull(method); + DirectStubDesc candidate = new DirectStubDesc(clazz, method, desc, Map.copyOf(linkerOptions)); + // only add the new descriptor if it is not subsumed by an existing one + if (!directUpcallStubs.contains(candidate.withoutFD())) { + directUpcallStubs.add(candidate); + } + } + + public void addDirectUpcall(Map linkerOptions, String clazz, String method) { + Objects.requireNonNull(clazz); + Objects.requireNonNull(method); + DirectStubDesc directStubDesc = new DirectStubDesc(clazz, method, null, Map.copyOf(linkerOptions)); + // remove all existing descriptors if they are subsumed by the new descriptor + directUpcallStubs.removeIf(existing -> directStubDesc.equals(existing.withoutFD())); + directUpcallStubs.add(directStubDesc); + } + + @Override + public void printJson(JsonWriter writer) throws IOException { + Map> stubSets = Map.of( + "downcalls", downcallStubs, + "upcalls", upcallStubs, + "directUpcalls", directUpcallStubs); + + writer.appendObjectStart().indent(); + boolean first = true; + for (String sectionName : stubSets.keySet()) { + Collection stubs = stubSets.get(sectionName); + if (!stubs.isEmpty()) { + if (!first) { + writer.appendSeparator(); + } + writer.newline().quote(sectionName).appendFieldSeparator().appendArrayStart().indent().newline(); + printStubs(writer, stubs); + writer.unindent().newline().appendArrayEnd(); + first = false; + } + } + writer.unindent().newline().appendObjectEnd(); + } + + private static void printStubs(JsonWriter writer, Collection stubs) throws IOException { + boolean first = true; + for (var stubDesc : stubs) { + if (first) { + first = false; + } else { + writer.appendSeparator().newline(); + } + stubDesc.printJson(writer); + } + } + + @Override + public ConfigurationParser createParser(boolean combinedFileSchema, EnumSet parserOptions) { + if (combinedFileSchema) { + throw new IllegalArgumentException("Foreign configuration is only supported with the legacy metadata schema"); + } + return new UnresolvedForeignConfigurationParser(parserOptions); + } + + @Override + public boolean isEmpty() { + return downcallStubs.isEmpty() && upcallStubs.isEmpty() && directUpcallStubs.isEmpty(); + } + + @Override + public boolean supportsCombinedFile() { + return false; + } + + public interface Predicate { + boolean testDowncall(ConfigurationFunctionDescriptor desc, Map linkerOptions); + + boolean testUpcall(ConfigurationFunctionDescriptor desc, Map linkerOptions); + + boolean testDirectUpcall(String clazz, String method, ConfigurationFunctionDescriptor desc, Map linkerOptions); + } + + /** + * A simple implementation of the {@link ForeignConfigurationParser} which does not + * resolve/validate any descriptors/handles but just collects the configuration into + * {@link ForeignConfiguration} in order to be able to do the common operations (e.g. merge, + * diff, ...) on configuration files. + */ + private final class UnresolvedForeignConfigurationParser extends ForeignConfigurationParser> { + private UnresolvedForeignConfigurationParser(EnumSet parserOptions) { + super(parserOptions); + } + + @Override + protected void registerDowncall(ConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor descriptor, Map options) { + ForeignConfiguration.this.addDowncall(descriptor, options); + + } + + @Override + protected void registerUpcall(ConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor descriptor, Map options) { + ForeignConfiguration.this.addUpcall(descriptor, options); + } + + @Override + protected void registerDirectUpcallWithoutDescriptor(String className, String methodName, EconomicMap optionsMap) { + ForeignConfiguration.this.addDirectUpcall(economicMapToJavaMap(optionsMap), className, methodName); + } + + @Override + protected void registerDirectUpcallWithDescriptor(String className, String methodName, ConfigurationFunctionDescriptor descriptor, Map options) { + ForeignConfiguration.this.addDirectUpcall(descriptor, options, className, methodName); + } + + @Override + protected void handleRegistrationError(Exception e, EconomicMap map) { + /* + * We should never reach here because this handler is only called if one of the + * register(Donwcall|Upcall|DirectUpcallWithDescriptor|DirectUpcallWithoutDescriptor) + * methods throws an exception. + */ + throw new RuntimeException("Should not be reached", e); + } + + @Override + protected ConfigurationFunctionDescriptor createFunctionDescriptor(String returnType, List parameterTypes) { + return new ConfigurationFunctionDescriptor(returnType, parameterTypes); + } + + @Override + protected Map createDowncallOptions(EconomicMap map, @SuppressWarnings("unused") ConfigurationFunctionDescriptor desc) { + return economicMapToJavaMap(map); + } + + @Override + protected Map createUpcallOptions(EconomicMap map, @SuppressWarnings("unused") ConfigurationFunctionDescriptor desc) { + return economicMapToJavaMap(map); + } + + private static Map economicMapToJavaMap(EconomicMap map) { + Map result = new HashMap<>(); + MapCursor cursor = map.getEntries(); + while (cursor.advance()) { + result.put(cursor.getKey(), cursor.getValue()); + } + return result; + } + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java index 6dc34c1071cc..cf3094966743 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/conditional/ConditionalConfigurationPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 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 @@ -25,12 +25,15 @@ package com.oracle.svm.configure.config.conditional; import java.util.List; +import java.util.Map; import com.oracle.svm.configure.ConditionalElement; import com.oracle.svm.configure.ConfigurationTypeDescriptor; import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.configure.config.ConfigurationPredefinedClass; import com.oracle.svm.configure.config.ConfigurationType; +import com.oracle.svm.configure.config.ForeignConfiguration; +import com.oracle.svm.configure.config.ForeignConfiguration.ConfigurationFunctionDescriptor; import com.oracle.svm.configure.config.PredefinedClassesConfiguration; import com.oracle.svm.configure.config.ProxyConfiguration; import com.oracle.svm.configure.config.ResourceConfiguration; @@ -41,7 +44,7 @@ import com.oracle.svm.configure.filters.ComplexFilter; public class ConditionalConfigurationPredicate implements TypeConfiguration.Predicate, ProxyConfiguration.Predicate, - ResourceConfiguration.Predicate, SerializationConfiguration.Predicate, PredefinedClassesConfiguration.Predicate { + ResourceConfiguration.Predicate, SerializationConfiguration.Predicate, PredefinedClassesConfiguration.Predicate, ForeignConfiguration.Predicate { private final ComplexFilter filter; @@ -100,4 +103,22 @@ private boolean testTypeDescriptor(UnresolvedConfigurationCondition condition, C } return false; } + + @Override + public boolean testDowncall(ConfigurationFunctionDescriptor desc, Map linkerOptions) { + // GR-64144: Not implemented with conditions yet + return true; + } + + @Override + public boolean testUpcall(ConfigurationFunctionDescriptor desc, Map linkerOptions) { + // GR-64144: Not implemented with conditions yet + return true; + } + + @Override + public boolean testDirectUpcall(String clazz, String method, ConfigurationFunctionDescriptor desc, Map linkerOptions) { + // GR-64144: Not implemented with conditions yet + return true; + } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ForeignProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ForeignProcessor.java new file mode 100644 index 000000000000..d054d56d5747 --- /dev/null +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ForeignProcessor.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025, 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 com.oracle.svm.configure.trace; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.graalvm.collections.EconomicMap; + +import com.oracle.svm.configure.config.ConfigurationSet; +import com.oracle.svm.configure.config.ForeignConfiguration; +import com.oracle.svm.util.LogUtils; + +import jdk.vm.ci.meta.MetaUtil; + +class ForeignProcessor extends AbstractProcessor { + + @Override + @SuppressWarnings("unchecked") + public void processEntry(EconomicMap entry, ConfigurationSet configurationSet) { + boolean invalidResult = Boolean.FALSE.equals(entry.get("result")); + if (invalidResult) { + return; + } + + String function = (String) entry.get("function"); + List args = (List) entry.get("args"); + + ForeignConfiguration foreignConfiguration = configurationSet.getForeignConfiguration(); + switch (function) { + case "downcallHandle0" -> { + // returnType, parameterTypes, linkerOptions + expectSize(args, 3); + String returnType = (String) args.get(0); + List parameterTypes = (List) args.get(1); + List linkerOptions = (List) args.get(2); + foreignConfiguration.addDowncall(returnType, parameterTypes, processLinkerOptions(linkerOptions)); + } + case "upcallStub" -> { + // returnType, parameterTypes, linkerOptions, target + expectSize(args, 4); + String returnType = (String) args.get(0); + List parameterTypes = (List) args.get(1); + List linkerOptions = (List) args.get(2); + Object targetDesc = args.get(3); + ClassAndMethodName classAndMethodName = targetDesc instanceof String targetDescString ? getClassAndMethodName(targetDescString) : null; + if (classAndMethodName != null) { + foreignConfiguration.addDirectUpcall(returnType, parameterTypes, processLinkerOptions(linkerOptions), classAndMethodName.className, classAndMethodName.methodName); + } else { + foreignConfiguration.addUpcall(returnType, parameterTypes, processLinkerOptions(linkerOptions)); + } + } + } + } + + private static Map processLinkerOptions(List linkerOptions) { + Map result = new HashMap<>(); + for (String linkerOption : linkerOptions) { + Map.Entry processed = processLinkerOption(linkerOption); + if (processed != null) { + result.put(processed.getKey(), processed.getValue()); + } + } + return result; + } + + private static Map.Entry processLinkerOption(String linkerOption) { + if (linkerOption == null || linkerOption.isEmpty()) { + return null; + } + // since jdk-25+21: 'critical' became an Enum + switch (linkerOption) { + case "ALLOW_HEAP": + return critical(true); + case "DONT_ALLOW_HEAP": + return critical(false); + } + + // e.g. "FirstVariadicArg[index=123]" + int argStart = linkerOption.indexOf('['); + int argEnd = linkerOption.lastIndexOf(']'); + if (argStart == -1 || argEnd == -1 || argEnd != linkerOption.length() - 1) { + LogUtils.warning("Ignoring invalid Linker.Option: " + linkerOption); + return null; + } + + return switch (linkerOption.substring(0, argStart)) { + /* + * Special case: 'captureCallState' is just a Boolean in the configuration file. Also, + * even if no state was specified to capture (i.e. no parameters were given to + * `Linker.Option.captureCallState()`), we need to set the options otherwise the stub + * won't be found. + */ + case "CaptureCallState" -> Map.entry("captureCallState", true); + case "FirstVariadicArg" -> { + // e.g. "FirstVariadicArg[index=123]" + try { + int eqIndex = linkerOption.indexOf('=', argStart); + int index = Integer.parseInt(linkerOption.substring(eqIndex + 1, argEnd)); + yield Map.entry("firstVariadicArg", index); + } catch (NumberFormatException e) { + LogUtils.warning("Invalid parameter in Linker.Option: " + linkerOption); + yield null; + } + } + case "Critical" -> { + // e.g. "Critical[allowHeapAccess=true]" + try { + int eqIndex = linkerOption.indexOf('=', argStart); + boolean allowHeapAccess = Boolean.parseBoolean(linkerOption.substring(eqIndex + 1, argEnd)); + yield critical(allowHeapAccess); + } catch (NumberFormatException e) { + LogUtils.warning("Invalid parameter in Linker.Option: " + linkerOption); + yield null; + } + } + default -> null; + }; + } + + private static Map.Entry critical(boolean allowHeapAccess) { + return Map.entry("critical", Map.of("allowHeapAccess", allowHeapAccess)); + } + + private static ClassAndMethodName getClassAndMethodName(String targetDesc) { + + // Example: Method "String foo(long)" of class "org.my.MyClass$InnerClass" + // Lorg/my/MyClass$InnerClass::foo + int sep; + if (targetDesc == null || (sep = targetDesc.indexOf("::")) == -1) { + // don't output a warning; it is valid if upcalls do not bind a DirectMethodHandle + return null; + } + + /* + * @formatter:off + * Lorg/my/MyClass$InnerClass::foo + * ^ + * sep + * @formatter:on + */ + String className = MetaUtil.internalNameToJava(targetDesc.substring(0, sep), true, false); + String methodName = targetDesc.substring(sep + 2); + return new ClassAndMethodName(className, methodName); + } + + private record ClassAndMethodName(String className, String methodName) { + } +} diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java index 34a88777291a..d769b5223aa8 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/TraceProcessor.java @@ -43,6 +43,7 @@ public class TraceProcessor extends AbstractProcessor { private final ReflectionProcessor reflectionProcessor; private final SerializationProcessor serializationProcessor; private final ClassLoadingProcessor classLoadingProcessor; + private final ForeignProcessor foreignProcessor; public TraceProcessor(AccessAdvisor accessAdvisor) { advisor = accessAdvisor; @@ -50,6 +51,7 @@ public TraceProcessor(AccessAdvisor accessAdvisor) { reflectionProcessor = new ReflectionProcessor(this.advisor); serializationProcessor = new SerializationProcessor(this.advisor); classLoadingProcessor = new ClassLoadingProcessor(); + foreignProcessor = new ForeignProcessor(); } @SuppressWarnings("unchecked") @@ -96,6 +98,9 @@ public void processEntry(EconomicMap entry, ConfigurationSet con case "classloading": classLoadingProcessor.processEntry(entry, configurationSet); break; + case "foreign": + foreignProcessor.processEntry(entry, configurationSet); + break; default: LogUtils.warning("Unknown tracer, ignoring: " + tracer); break; diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java index 34645616ad6c..081662562be3 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/TrampolineSet.java @@ -36,8 +36,10 @@ import org.graalvm.word.PointerBase; import org.graalvm.word.UnsignedWord; +import com.oracle.svm.core.SubstrateOptions; import com.oracle.svm.core.code.AbstractRuntimeCodeInstaller.RuntimeCodeInstallerPlatformHelper; import com.oracle.svm.core.heap.VMOperationInfos; +import com.oracle.svm.core.os.CommittedMemoryProvider; import com.oracle.svm.core.os.VirtualMemoryProvider; import com.oracle.svm.core.thread.JavaVMOperation; import com.oracle.svm.core.util.UnsignedUtils; @@ -54,6 +56,10 @@ private static UnsignedWord allocationSize() { return VirtualMemoryProvider.get().getGranularity(); } + private static UnsignedWord alignment() { + return Word.unsigned(SubstrateOptions.codeAlignment()); + } + private static int maxTrampolineCount() { long result = allocationSize().rawValue() / AbiUtils.singleton().trampolineSize(); return NumUtil.safeToInt(result); @@ -137,10 +143,9 @@ void patchTrampolineForDirectUpcall(Pointer trampolinePointer, CFunctionPointer } private Pointer prepareTrampolines(PinnedObject mhsArray, PinnedObject stubsArray, AbiUtils.TrampolineTemplate template) { - VirtualMemoryProvider memoryProvider = VirtualMemoryProvider.get(); UnsignedWord pageSize = allocationSize(); /* We request a specific alignment to guarantee correctness of getAllocationBase */ - Pointer page = memoryProvider.commit(Word.nullPointer(), pageSize, VirtualMemoryProvider.Access.WRITE | VirtualMemoryProvider.Access.FUTURE_EXECUTE); + Pointer page = CommittedMemoryProvider.get().allocateExecutableMemory(pageSize, Word.unsigned(SubstrateOptions.codeAlignment())); if (page.isNull()) { throw new OutOfMemoryError("Could not allocate memory for trampolines."); } @@ -154,7 +159,7 @@ private Pointer prepareTrampolines(PinnedObject mhsArray, PinnedObject stubsArra VMError.guarantee(it.belowOrEqual(end), "Not enough memory was allocated to hold trampolines"); } - VMError.guarantee(memoryProvider.protect(page, pageSize, VirtualMemoryProvider.Access.EXECUTE) == 0, + VMError.guarantee(VirtualMemoryProvider.get().protect(page, pageSize, VirtualMemoryProvider.Access.EXECUTE) == 0, "Error when making the trampoline allocation executable"); /* @@ -178,7 +183,7 @@ boolean tryFree() { for (PinnedObject pinned : pins) { pinned.close(); } - VirtualMemoryProvider.get().free(trampolines, allocationSize()); + CommittedMemoryProvider.get().freeExecutableMemory(trampolines, allocationSize(), alignment()); assigned = FREED; if (patchedStubs != null) { patchedStubs.clear(); diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java index 02485bfc7be6..ddb2c2119a5c 100644 --- a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsConfigurationParser.java @@ -37,7 +37,6 @@ import java.lang.invoke.MethodType; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.net.URI; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; @@ -54,8 +53,8 @@ import org.graalvm.nativeimage.impl.ConfigurationCondition; import org.graalvm.nativeimage.impl.RuntimeForeignAccessSupport; -import com.oracle.svm.configure.ConfigurationParser; import com.oracle.svm.configure.ConfigurationParserOption; +import com.oracle.svm.configure.ForeignConfigurationParser; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.foreign.MemoryLayoutParser.MemoryLayoutParserException; @@ -68,13 +67,13 @@ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+22/src/java.base/share/classes/jdk/internal/foreign/abi/LinkerOptions.java") @Platforms(Platform.HOSTED_ONLY.class) -public class ForeignFunctionsConfigurationParser extends ConfigurationParser { +public class ForeignFunctionsConfigurationParser extends ForeignConfigurationParser { private static final String DOWNCALL_OPTION_CAPTURE_CALL_STATE = "captureCallState"; private static final String DOWNCALL_OPTION_FIRST_VARIADIC_ARG = "firstVariadicArg"; private static final String DOWNCALL_OPTION_CRITICAL = "critical"; private static final String DOWNCALL_OPTION_ALLOW_HEAP_ACCESS = "allowHeapAccess"; - private static final String PARAMETER_TYPES = "parameterTypes"; - private static final String RETURN_TYPE = "returnType"; + + private static final Linker.Option[] EMPTY_OPTIONS = new Linker.Option[0]; private final ImageClassLoader imageClassLoader; private final RuntimeForeignAccessSupport accessSupport; @@ -97,106 +96,80 @@ protected EnumSet supportedOptions() { } @Override - public void parseAndRegister(Object json, URI origin) { - var topLevel = asMap(json, "first level of document must be a map"); - checkAttributes(topLevel, "foreign methods categories", List.of(), List.of("downcalls", "upcalls", "directUpcalls")); - - var downcalls = asList(topLevel.get("downcalls", List.of()), "downcalls must be an array of function descriptor and linker options"); - for (Object downcall : downcalls) { - parseAndRegisterForeignCall(downcall, false); - } + protected void registerDowncall(ConfigurationCondition configurationCondition, FunctionDescriptor descriptor, Option[] options) { + accessSupport.registerForDowncall(ConfigurationCondition.alwaysTrue(), descriptor, (Object[]) options); + } - var upcalls = asList(topLevel.get("upcalls", List.of()), "upcalls must be an array of function descriptor and linker options"); - for (Object upcall : upcalls) { - parseAndRegisterForeignCall(upcall, true); - } + @Override + protected void registerUpcall(ConfigurationCondition configurationCondition, FunctionDescriptor descriptor, Option[] options) { + accessSupport.registerForUpcall(ConfigurationCondition.alwaysTrue(), descriptor, (Object[]) options); + } - var directUpcalls = asList(topLevel.get("directUpcalls", List.of()), "direct upcalls must be an array of method references, function descriptors, and linker options"); - for (Object upcall : directUpcalls) { - parseAndRegisterDirectUpcall(upcall); + @Override + protected void registerDirectUpcallWithDescriptor(String className, String methodName, FunctionDescriptor descriptor, Option[] options) { + Class aClass; + try { + aClass = imageClassLoader.forName(className); + } catch (ClassNotFoundException e) { + handleMissingElement(e, "Cannot find class '%s' used to register method(s) '%s' for a direct upcall(s). ", className, methodName); + return; } - } - private void parseAndRegisterForeignCall(Object call, boolean forUpcall) { - var map = asMap(call, "a foreign call must be a map"); - checkAttributes(map, "foreign call", List.of(RETURN_TYPE, PARAMETER_TYPES), List.of("options")); - var descriptor = parseDescriptor(map); - var optionsMap = asMap(map.get("options", EconomicMap.emptyMap()), "options must be a map"); + /* + * A FunctionDescriptor was provided, so we use it to create the MethodType and to lookup + * the method. Since we have a MethodType, there should be exactly one method. + */ + MethodType methodType = descriptor.toMethodType(); + MethodHandle target; try { - if (forUpcall) { - var options = parseUpcallOptions(optionsMap); - accessSupport.registerForUpcall(ConfigurationCondition.alwaysTrue(), descriptor, options.toArray()); - } else { - var options = parseDowncallOptions(optionsMap, descriptor); - accessSupport.registerForDowncall(ConfigurationCondition.alwaysTrue(), descriptor, options.toArray()); - } - } catch (IllegalArgumentException e) { - handleRegistrationError(e, map); + target = getImplLookup().findStatic(aClass, methodName, methodType); + } catch (NoSuchMethodException | IllegalAccessException e) { + handleMissingElement(e, "Method '%s.%s(%s)' could not be registered as an upcall target method. " + + "Please verify that the method is static and that the parameter types match.", + className, methodName, methodType); + return; } + accessSupport.registerForDirectUpcall(ConfigurationCondition.alwaysTrue(), target, descriptor, (Object[]) options); } - private void parseAndRegisterDirectUpcall(Object call) { - var map = asMap(call, "a foreign call must be a map"); - checkAttributes(map, "foreign call", List.of("class", "method"), List.of(RETURN_TYPE, PARAMETER_TYPES, "options")); - - String className = asString(map.get("class"), "class"); - String methodName = asString(map.get("method"), "method"); + @Override + protected void registerDirectUpcallWithoutDescriptor(String className, String methodName, EconomicMap optionsMap) { Class aClass; try { aClass = imageClassLoader.forName(className); } catch (ClassNotFoundException e) { - handleMissingElement(e, "Cannot find class '%s' used to register method(s) '%s' for a direct upcall(s). ", className, methodName); + handleMissingElement(e, "Cannot find class '%s' used to register method(s) '%s' for a direct upcall. ", className, methodName); return; } List> descriptors; - Object returnTypeInput = map.get(RETURN_TYPE); - Object parameterTypesInput = map.get(PARAMETER_TYPES); - if (returnTypeInput != null || parameterTypesInput != null) { - /* - * A FunctionDescriptor was provided, so we use it to create the MethodType and to - * lookup the method. Since we have a MethodType, there should be exactly one method. - */ - FunctionDescriptor descriptor = parseDescriptor(map); - MethodType methodType = descriptor.toMethodType(); - try { - descriptors = List.of(Pair.create(descriptor, getImplLookup().findStatic(aClass, methodName, methodType))); - } catch (NoSuchMethodException | IllegalAccessException e) { - handleMissingElement(e, "Method '%s.%s(%s)' could not be registered as an upcall target method. " + - "Please verify that the method is static and that the parameter types match.", - className, methodName, methodType); - return; - } - } else { - // FunctionDescriptor was not provided; derive from method signature(s) - try { - descriptors = new LinkedList<>(); - for (Method method : findStaticMethods(aClass, methodName)) { - try { - descriptors.add(Pair.create(deriveFunctionDescriptor(method), getImplLookup().unreflect(method))); - } catch (AmbiguousParameterType | InvalidCarrierType e) { - handleMissingElement(e); - } catch (IllegalAccessException e) { - handleMissingElement(e, "Method '%s.%s' and all its possible overloads could not be registered as upcall target methods. " + - "Please verify that all overloads of the method are static.", - className, methodName); - } + // FunctionDescriptor was not provided; derive from method signature(s) + try { + descriptors = new LinkedList<>(); + for (Method method : findStaticMethods(aClass, methodName)) { + try { + descriptors.add(Pair.create(deriveFunctionDescriptor(method), getImplLookup().unreflect(method))); + } catch (AmbiguousParameterType | InvalidCarrierType e) { + handleMissingElement(e); + } catch (IllegalAccessException e) { + handleMissingElement(e, "Method '%s.%s' and all its possible overloads could not be registered as upcall target methods. " + + "Please verify that all overloads of the method are static.", + className, methodName); } - } catch (NoSuchMethodException e) { - handleMissingElement(e, "Method '%s.%s' and all its possible overloads could not be registered as upcall target methods. " + - "Please verify that all overloads of the method are static.", - className, methodName); - return; } + } catch (NoSuchMethodException e) { + handleMissingElement(e, "Method '%s.%s' and all its possible overloads could not be registered as upcall target methods. " + + "Please verify that all overloads of the method are static.", + className, methodName); + return; } for (Pair pair : descriptors) { - var optionsMap = asMap(map.get("options", EconomicMap.emptyMap()), "options must be a map"); + var options = createUpcallOptions(optionsMap, pair.getLeft()); try { - var options = parseUpcallOptions(optionsMap); - accessSupport.registerForDirectUpcall(ConfigurationCondition.alwaysTrue(), pair.getRight(), pair.getLeft(), options.toArray()); + accessSupport.registerForDirectUpcall(ConfigurationCondition.alwaysTrue(), pair.getRight(), pair.getLeft(), (Object[]) options); } catch (IllegalArgumentException e) { - handleRegistrationError(e, map); + handleMissingElement(e, "Could not register direct upcall stub '%s.%s%s'", className, methodName, pair.getLeft().toMethodType()); } } } @@ -249,40 +222,31 @@ private static String describe(String parameterName) { return parameterName != null ? "Parameter \"" + parameterName + "\" with" : "Return"; } - private Optional parseReturnType(Object signature) throws MemoryLayoutParserException { - String input = asString(signature, RETURN_TYPE); - return MemoryLayoutParser.parseAllowVoid(input, canonicalLayouts); - } - - private MemoryLayout[] parseParameterTypes(Object parameterTypesObject) throws MemoryLayoutParserException { - List parameterTypesList = asList(parameterTypesObject, "Element '" + PARAMETER_TYPES + "' must be a list"); - MemoryLayout[] parameterTypes = new MemoryLayout[parameterTypesList.size()]; - for (int i = 0; i < parameterTypes.length; i++) { - String parameterTypeString = asString(parameterTypesList.get(i), String.format("%s[%d]", PARAMETER_TYPES, i)); - parameterTypes[i] = MemoryLayoutParser.parse(parameterTypeString, canonicalLayouts); - } - return parameterTypes; - } - - private FunctionDescriptor parseDescriptor(EconomicMap map) { - return parseDescriptor(map.get(RETURN_TYPE), map.get(PARAMETER_TYPES)); - } - - private FunctionDescriptor parseDescriptor(Object returnTypeInput, Object parameterTypeInput) { + @Override + protected FunctionDescriptor createFunctionDescriptor(String returnType, List parameterTypes) { try { - Optional returnType = parseReturnType(returnTypeInput); - MemoryLayout[] parameterTypes = parseParameterTypes(parameterTypeInput); - return returnType.map(memoryLayout -> FunctionDescriptor.of(memoryLayout, parameterTypes)).orElseGet(() -> FunctionDescriptor.ofVoid(parameterTypes)); + Optional returnLayout = MemoryLayoutParser.parseAllowVoid(returnType, canonicalLayouts); + MemoryLayout[] parameterLayouts = parseParameterTypes(parameterTypes); + return returnLayout.map(memoryLayout -> FunctionDescriptor.of(memoryLayout, parameterLayouts)).orElseGet(() -> FunctionDescriptor.ofVoid(parameterLayouts)); } catch (MemoryLayoutParserException e) { throw new JsonParserException(e.getMessage()); } } + private MemoryLayout[] parseParameterTypes(List parameterTypes) throws MemoryLayoutParserException { + MemoryLayout[] parameterLayouts = new MemoryLayout[parameterTypes.size()]; + for (int i = 0; i < parameterLayouts.length; i++) { + parameterLayouts[i] = MemoryLayoutParser.parse(parameterTypes.get(i), canonicalLayouts); + } + return parameterLayouts; + } + /** * Parses the options allowed for downcalls. This needs to be consistent with - * 'jdk.internal.foreign.abi.LinkerOptions.forDowncall'. + * {@link jdk.internal.foreign.abi.LinkerOptions#forDowncall}. */ - private List parseDowncallOptions(EconomicMap map, FunctionDescriptor desc) { + @Override + protected Option[] createDowncallOptions(EconomicMap map, FunctionDescriptor desc) { checkAttributes(map, "options", List.of(), List.of(DOWNCALL_OPTION_FIRST_VARIADIC_ARG, DOWNCALL_OPTION_CAPTURE_CALL_STATE, DOWNCALL_OPTION_CRITICAL)); ArrayList