diff --git a/docs/reference-manual/native-image/ForeignInterface.md b/docs/reference-manual/native-image/ForeignInterface.md index e4cbdd514943..98b09e73c975 100644 --- a/docs/reference-manual/native-image/ForeignInterface.md +++ b/docs/reference-manual/native-image/ForeignInterface.md @@ -30,14 +30,18 @@ These two kinds of calls are referred to as "downcalls" and "upcalls" respective ### Looking Up Native Functions The FFM API provides the `SymbolLookup` interface to find functions in native libraries by name. -`SymbolLookup.loaderLookup()` is currently the only supported kind of `SymbolLookup`. +Native image supports all available symbol lookup methods, i.e., `SymbolLookup.loaderLookup()`, `SymbolLookup.libraryLookup()`, and `Linker.defaultLookup()`. ### Registering Foreign Calls In order to perform calls to native code at run time, supporting code must be generated at image build time. -Therefore, the `native-image` tool must be provided with descriptors that characterize the functions to which downcalls may be performed at run time. +Therefore, the `native-image` tool must be provided with descriptors that characterize the functions with which downcalls or upcalls can be performed at runtime. -These descriptors can be registered using a custom `Feature`, for example: +For upcalls, it is recommended to register a specific static method as an upcall target by providing its declaring class and the method name. +This allows `native-image` to create specialized upcall code that can be orders of magnitude faster than a upcall registered only by function descriptor. +Whenever possible, this should be the preferred way to register upcalls. + +Descriptors and target methods can be registered using a custom `Feature`, for example: ```java import static java.lang.foreign.ValueLayout.*; @@ -50,6 +54,9 @@ class ForeignRegistrationFeature implements Feature { RuntimeForeignAccess.registerForUpcall(FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT)); RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.of(ADDRESS, JAVA_INT, JAVA_INT), Linker.Option.firstVariadicArg(1)); RuntimeForeignAccess.registerForDowncall(FunctionDescriptor.ofVoid(JAVA_INT), Linker.Option.captureCallState("errno")); + + MethodHandle target = MethodHandles.lookup().findStatic(UserClass.class, "aStaticMethod", MethodType.of(int.class, int.class, int.class)); + RuntimeForeignAccess.registerForUpcall(target, FunctionDescriptor.of(JAVA_INT, JAVA_INT, JAVA_INT)); } } ``` diff --git a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest index ef23e446d560..41ebca3c11ac 100644 --- a/sdk/src/org.graalvm.nativeimage/snapshot.sigtest +++ b/sdk/src/org.graalvm.nativeimage/snapshot.sigtest @@ -1088,6 +1088,7 @@ meth public !varargs static void initializeAtRunTime(java.lang.String[]) supr java.lang.Object CLSS public final org.graalvm.nativeimage.hosted.RuntimeForeignAccess +meth public !varargs static void registerForDirectUpcall(java.lang.invoke.MethodHandle,java.lang.Object,java.lang.Object[]) meth public !varargs static void registerForDowncall(java.lang.Object,java.lang.Object[]) meth public !varargs static void registerForUpcall(java.lang.Object,java.lang.Object[]) supr java.lang.Object diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java index 9d3586017b23..2b5aa887f6ce 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/hosted/RuntimeForeignAccess.java @@ -40,6 +40,9 @@ */ package org.graalvm.nativeimage.hosted; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; + import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; @@ -86,6 +89,34 @@ public static void registerForUpcall(Object desc, Object... options) { ImageSingletons.lookup(RuntimeForeignAccessSupport.class).registerForUpcall(ConfigurationCondition.alwaysTrue(), desc, options); } + /** + * Registers a specific static method (denoted by a method handle) as a fast upcall target. This + * will create a specialized upcall stub that will invoke only the specified method, which is + * much faster than using {@link #registerForUpcall(Object, Object...)}). + *

+ * The provided method handle must be a direct method handle. Those are most commonly created + * using {@link java.lang.invoke.MethodHandles.Lookup#findStatic(Class, String, MethodType)}. + * However, a strict requirement is that it must be possible to create a non-empty descriptor + * for the method handle using {@link MethodHandle#describeConstable()}. The denoted static + * method will also be registered for reflective access since run-time code will also create a + * method handle to denoted static method. + *

+ *

+ * Even though this method is weakly typed for compatibility reasons, runtime checks will be + * performed to ensure that the arguments have the expected type. It will be deprecated in favor + * of strongly typed variant as soon as possible. + *

+ * + * @param target A direct method handle denoting a static method. + * @param desc A {@link java.lang.foreign.FunctionDescriptor} to register for upcalls. + * @param options An array of {@link java.lang.foreign.Linker.Option} used for the upcalls. + * + * @since 24.2 + */ + public static void registerForDirectUpcall(MethodHandle target, Object desc, Object... options) { + ImageSingletons.lookup(RuntimeForeignAccessSupport.class).registerForDirectUpcall(ConfigurationCondition.alwaysTrue(), target, desc, options); + } + private RuntimeForeignAccess() { } } diff --git a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java index 96db98ab24be..dd0537c496fc 100644 --- a/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java +++ b/sdk/src/org.graalvm.nativeimage/src/org/graalvm/nativeimage/impl/RuntimeForeignAccessSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * The Universal Permissive License (UPL), Version 1.0 @@ -40,8 +40,12 @@ */ package org.graalvm.nativeimage.impl; +import java.lang.invoke.MethodHandle; + public interface RuntimeForeignAccessSupport { void registerForDowncall(ConfigurationCondition condition, Object desc, Object... options); void registerForUpcall(ConfigurationCondition condition, Object desc, Object... options); + + void registerForDirectUpcall(ConfigurationCondition condition, MethodHandle target, Object desc, Object... options); } diff --git a/substratevm/CHANGELOG.md b/substratevm/CHANGELOG.md index dc702c150655..1f747b21cf83 100644 --- a/substratevm/CHANGELOG.md +++ b/substratevm/CHANGELOG.md @@ -10,6 +10,7 @@ At runtime, premain runtime options are set along with main class' arguments in The warning is planned to be replaced by an error in GraalVM for JDK 25. * (GR-48384) Added a GDB Python script (`gdb-debughelpers.py`) to improve the Native Image debugging experience. * (GR-49517) Add support for emitting Windows x64 unwind info. This enables stack walking in native tooling such as debuggers and profilers. +* (GR-52576) Optimize FFM API upcalls for specifiable static upcall target methods. * (GR-56599) Update native image debuginfo from DWARF4 to DWARF5 and store type information for debugging in DWARF type units. * (GR-56601) Together with Red Hat, we added experimental support for `jcmd` on Linux and macOS. Add `--enable-monitoring=jcmd` to your build arguments to try it out. * (GR-57384) Preserve the origin of a resource included in a native image. The information is included in the report produced by -H:+GenerateEmbeddedResourcesFile. diff --git a/substratevm/mx.substratevm/suite.py b/substratevm/mx.substratevm/suite.py index 18623201ce2f..0b2b476975d8 100644 --- a/substratevm/mx.substratevm/suite.py +++ b/substratevm/mx.substratevm/suite.py @@ -719,13 +719,13 @@ ], "requiresConcealed": { "java.base": [ - "jdk.internal.loader", - "jdk.internal.reflect", "jdk.internal.foreign", "jdk.internal.foreign.abi", "jdk.internal.foreign.abi.x64", "jdk.internal.foreign.abi.x64.sysv", "jdk.internal.foreign.abi.x64.windows", + "jdk.internal.loader", + "jdk.internal.reflect", ], "jdk.internal.vm.ci" : [ "jdk.vm.ci.amd64", @@ -758,6 +758,9 @@ "java.base": [ "jdk.internal.foreign", "jdk.internal.foreign.abi", + "jdk.internal.foreign.abi.x64.windows", + "jdk.internal.foreign.abi.x64.sysv", + "jdk.internal.foreign.layout", ], "jdk.internal.vm.ci" : [ "jdk.vm.ci.code", diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java index abf07ec02d15..fca87f363cd7 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsRuntime.java @@ -26,9 +26,11 @@ import static jdk.graal.compiler.core.common.spi.ForeignCallDescriptor.CallSideEffect.HAS_SIDE_EFFECT; +import java.lang.constant.DirectMethodHandleDesc; import java.lang.invoke.MethodHandle; import java.util.HashMap; import java.util.Map; +import java.util.function.BiConsumer; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; @@ -61,11 +63,15 @@ public static ForeignFunctionsRuntime singleton() { private final AbiUtils.TrampolineTemplate trampolineTemplate = AbiUtils.singleton().generateTrampolineTemplate(); private final EconomicMap downcallStubs = EconomicMap.create(); + private final EconomicMap directUpcallStubs = EconomicMap.create(); private final EconomicMap upcallStubs = EconomicMap.create(); private final Map trampolines = new HashMap<>(); private TrampolineSet currentTrampolineSet; + // for testing: callback if direct upcall lookup succeeded + private BiConsumer usingSpecializedUpcallListener; + @Platforms(Platform.HOSTED_ONLY.class) public ForeignFunctionsRuntime() { } @@ -82,6 +88,12 @@ public void addUpcallStubPointer(JavaEntryPointInfo jep, CFunctionPointer ptr) { VMError.guarantee(upcallStubs.put(jep, new FunctionPointerHolder(ptr)) == null); } + @Platforms(Platform.HOSTED_ONLY.class) + public void addDirectUpcallStubPointer(DirectMethodHandleDesc desc, CFunctionPointer ptr) { + VMError.guarantee(!directUpcallStubs.containsKey(desc), "Seems like multiple stubs were generated for " + desc); + VMError.guarantee(directUpcallStubs.put(desc, new FunctionPointerHolder(ptr)) == null); + } + /** * We'd rather report the function descriptor than the native method type, but we don't have it * available here. One could intercept this exception in @@ -105,15 +117,61 @@ CFunctionPointer getUpcallStubPointer(JavaEntryPointInfo jep) { } Pointer registerForUpcall(MethodHandle methodHandle, JavaEntryPointInfo jep) { + /* + * Look up the upcall stub pointer first to avoid unnecessary allocation and synchronization + * if it doesn't exist. + */ + CFunctionPointer upcallStubPointer = getUpcallStubPointer(jep); synchronized (trampolines) { if (currentTrampolineSet == null || !currentTrampolineSet.hasFreeTrampolines()) { currentTrampolineSet = new TrampolineSet(trampolineTemplate); trampolines.put(currentTrampolineSet.base().rawValue(), currentTrampolineSet); } - return currentTrampolineSet.assignTrampoline(methodHandle, getUpcallStubPointer(jep)); + return currentTrampolineSet.assignTrampoline(methodHandle, upcallStubPointer); } } + /** + * Updates the stub address in the upcall trampoline with the address of a direct upcall stub. + * The trampoline is identified by the given native address and the direct upcall stub is + * identified by the method handle descriptor. + * + * @param trampolineAddress The address of the upcall trampoline. + * @param desc A direct method handle descriptor used to lookup the direct upcall stub. + */ + void patchForDirectUpcall(long trampolineAddress, DirectMethodHandleDesc desc) { + FunctionPointerHolder functionPointerHolder = directUpcallStubs.get(desc); + if (functionPointerHolder == null) { + return; + } + + Pointer trampolinePointer = WordFactory.pointer(trampolineAddress); + Pointer trampolineSetBase = TrampolineSet.getAllocationBase(trampolinePointer); + TrampolineSet trampolineSet = trampolines.get(trampolineSetBase.rawValue()); + if (trampolineSet == null) { + return; + } + /* + * Synchronizing on 'trampolineSet' is not necessary at this point since we are still in the + * call context of 'Linker.upcallStub' and the allocated trampoline is owned by the + * allocating thread until it returns from the call. Also, the trampoline cannot be free'd + * between allocation and patching because the associated arena is still on the stack. + */ + trampolineSet.patchTrampolineForDirectUpcall(trampolinePointer, functionPointerHolder.functionPointer); + /* + * If we reach this point, everything went fine and the trampoline was patched with the + * specialized upcall stub's address. For testing, now report that the lookup and patching + * succeeded. + */ + if (usingSpecializedUpcallListener != null) { + usingSpecializedUpcallListener.accept(trampolineAddress, desc); + } + } + + public void setUsingSpecializedUpcallListener(BiConsumer listener) { + usingSpecializedUpcallListener = listener; + } + void freeTrampoline(long addr) { synchronized (trampolines) { long base = TrampolineSet.getAllocationBase(WordFactory.pointer(addr)).rawValue(); diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java index 6d60e1a41344..4871809310e8 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_AbstractLinker.java @@ -24,11 +24,25 @@ */ package com.oracle.svm.core.foreign; +import java.lang.constant.DirectMethodHandleDesc; +import java.lang.constant.MethodHandleDesc; +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodType; +import java.util.Optional; + import com.oracle.svm.core.annotate.Alias; import com.oracle.svm.core.annotate.RecomputeFieldValue; +import com.oracle.svm.core.annotate.Substitute; import com.oracle.svm.core.annotate.TargetClass; import jdk.internal.foreign.abi.AbstractLinker; +import jdk.internal.foreign.abi.AbstractLinker.UpcallStubFactory; +import jdk.internal.foreign.abi.LinkerOptions; +import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker; +import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker; @TargetClass(AbstractLinker.class) public final class Target_jdk_internal_foreign_abi_AbstractLinker { @@ -46,3 +60,55 @@ public final class Target_jdk_internal_foreign_abi_AbstractLinker { @TargetClass(className = "jdk.internal.foreign.abi.SoftReferenceCache") final class Target_jdk_internal_foreign_abi_SoftReferenceCache { } + +/** + * A decorator for jdk.internal.foreign.abi.UpcallStubFactory which intercepts the call to method + * 'makeStub'. It will (1) call the original factory to create the upcall, and (2) then use the + * method handle's descriptor to lookup if a specialized (direct) upcall stub is available. If so, + * the trampoline will be updated with the specialized stub's address. + * + * @param delegate The original upcall stub factory as created by JDK's call arranger. + */ +record UpcallStubFactoryDecorator(UpcallStubFactory delegate) implements UpcallStubFactory { + + @Override + public MemorySegment makeStub(MethodHandle target, Arena arena) { + MemorySegment segment = delegate.makeStub(target, arena); + + /* + * We cannot do this in 'UpcallLinker.makeUpcallStub' because that one already gets a + * different method handle that will handle parameter/return value bindings. Further, method + * handles cannot be compared. If the provided method handle is a DirectMethodHandle, we use + * the MH descriptor to check if there is a registered direct upcall stub. Then, we will + * patch the already allocated trampoline with a different upcall stub pointer. + */ + Optional methodHandleDesc = target.describeConstable(); + if (methodHandleDesc.isPresent() && methodHandleDesc.get() instanceof DirectMethodHandleDesc desc) { + ForeignFunctionsRuntime.singleton().patchForDirectUpcall(segment.address(), desc); + } + return segment; + } +} + +@TargetClass(value = SysVx64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +final class Target_jdk_internal_foreign_abi_x64_sysv_SysVx64Linker { + + @Substitute + UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return new UpcallStubFactoryDecorator(jdk.internal.foreign.abi.x64.sysv.CallArranger.arrangeUpcall(targetType, function, options)); + } +} + +@TargetClass(value = Windowsx64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +final class Target_jdk_internal_foreign_abi_x64_windows_Windowsx64Linker { + + @Substitute + UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor function, LinkerOptions options) { + return new UpcallStubFactoryDecorator(jdk.internal.foreign.abi.x64.windows.CallArranger.arrangeUpcall(targetType, function, options)); + } +} + +/* + * GR-58659, GR-58660: add substitutions for LinuxAArch64Linker and MacOsAArch64Linker here once we + * support them. + */ \ No newline at end of file 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 61974e0518b6..29ec06ea0cf3 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 @@ -26,6 +26,7 @@ import java.lang.invoke.MethodHandle; import java.util.ArrayList; +import java.util.BitSet; import java.util.List; import org.graalvm.nativeimage.CurrentIsolate; @@ -37,6 +38,7 @@ import org.graalvm.word.WordFactory; import com.oracle.svm.core.os.VirtualMemoryProvider; +import com.oracle.svm.core.util.UnsignedUtils; import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.core.common.NumUtil; @@ -73,8 +75,22 @@ public static Pointer getAllocationBase(Pointer ptr) { private final int trampolineCount = maxTrampolineCount(); private final PointerBase[] methodHandles = new PointerBase[trampolineCount]; private final CFunctionPointer[] stubs = new CFunctionPointer[trampolineCount]; + private final BitSet patchedStubs; private final Pointer trampolines; + private static BitSet initializedPatchedStubs(int nbits) { + BitSet patchedStubs = null; + assert (patchedStubs = new BitSet(nbits)).isEmpty(); + return patchedStubs; + } + + private boolean getAndSetPatchedStub(int id) { + assert patchedStubs != null; + boolean res = patchedStubs.get(id); + patchedStubs.set(id); + return res; + } + private PinnedObject pin(Object object) { PinnedObject pinned = PinnedObject.create(object); pins.add(pinned); @@ -86,6 +102,7 @@ private PinnedObject pin(Object object) { assert trampolineCount <= maxTrampolineCount(); trampolines = prepareTrampolines(pin(methodHandles), pin(stubs), template); + this.patchedStubs = initializedPatchedStubs(stubs.length); } Pointer base() { @@ -103,10 +120,19 @@ Pointer assignTrampoline(MethodHandle methodHandle, CFunctionPointer upcallStubP methodHandles[id] = pinned.addressOfObject(); stubs[id] = upcallStubPointer; + assert !patchedStubs.get(id); return trampolines.add(id * AbiUtils.singleton().trampolineSize()); } + void patchTrampolineForDirectUpcall(Pointer trampolinePointer, CFunctionPointer directUpcallStubPointer) { + VMError.guarantee(trampolinePointer.aboveOrEqual(trampolines), "invalid trampoline pointer"); + int id = UnsignedUtils.safeToInt(trampolinePointer.subtract(trampolines).unsignedDivide(AbiUtils.singleton().trampolineSize())); + VMError.guarantee(id >= 0 && id < stubs.length, "invalid trampoline id"); + assert !getAndSetPatchedStub(id) : "attempt to patch trampoline twice"; + stubs[id] = directUpcallStubPointer; + } + private Pointer prepareTrampolines(PinnedObject mhsArray, PinnedObject stubsArray, AbiUtils.TrampolineTemplate template) { VirtualMemoryProvider memoryProvider = VirtualMemoryProvider.get(); UnsignedWord pageSize = allocationSize(); @@ -142,6 +168,9 @@ boolean tryFree() { } VirtualMemoryProvider.get().free(trampolines, allocationSize()); assigned = FREED; + if (patchedStubs != null) { + patchedStubs.clear(); + } return true; } } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java index 1a71459d279e..e76dfe455bfe 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/UpcallStubsHolder.java @@ -54,10 +54,14 @@ public static ConstantPool getConstantPool(MetaAccessProvider metaAccess) { * */ @Platforms(Platform.HOSTED_ONLY.class) - public static String stubName(JavaEntryPointInfo jep, boolean highLevel) { + public static String stubName(JavaEntryPointInfo jep, boolean highLevel, boolean direct) { MethodType type = jep.handleType(); - StringBuilder builder = new StringBuilder("upcall"); + StringBuilder builder = new StringBuilder(); + if (direct) { + builder.append("direct_"); + } + builder.append("upcall"); builder.append(highLevel ? "High" : "Low"); builder.append("_"); for (var param : type.parameterArray()) { 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 cd3b2b260faa..877a86482c07 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 @@ -27,6 +27,9 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.Linker.Option; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; import java.net.URI; import java.util.ArrayList; import java.util.List; @@ -41,6 +44,8 @@ import com.oracle.svm.core.configure.ConfigurationParser; import com.oracle.svm.core.util.BasedOnJDKFile; +import com.oracle.svm.core.util.UserError; +import com.oracle.svm.hosted.ImageClassLoader; import jdk.graal.compiler.util.json.JsonParserException; @@ -52,38 +57,68 @@ public class ForeignFunctionsConfigurationParser extends ConfigurationParser { private static final String DOWNCALL_OPTION_CRITICAL = "critical"; private static final String DOWNCALL_OPTION_ALLOW_HEAP_ACCESS = "allowHeapAccess"; + private final ImageClassLoader imageClassLoader; private final RuntimeForeignAccessSupport accessSupport; - public ForeignFunctionsConfigurationParser(RuntimeForeignAccessSupport access) { + public ForeignFunctionsConfigurationParser(ImageClassLoader imageClassLoader, RuntimeForeignAccessSupport access) { super(true); + this.imageClassLoader = imageClassLoader; this.accessSupport = access; } @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")); + 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 method signatures"); + 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, this::parseDowncallOptions, (descriptor, options) -> accessSupport.registerForDowncall(ConfigurationCondition.alwaysTrue(), descriptor, options)); } - var upcalls = asList(topLevel.get("upcalls", List.of()), "upcalls must be an array of method signatures"); + 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, this::parseUpcallOptions, (descriptor, options) -> accessSupport.registerForUpcall(ConfigurationCondition.alwaysTrue(), descriptor, 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); + } } private void parseAndRegisterForeignCall(Object call, Function, List