From 53b646d78e5eb43070fa6a67ac63b47101020d56 Mon Sep 17 00:00:00 2001 From: Florian Angerer Date: Fri, 17 Jan 2025 14:32:35 +0100 Subject: [PATCH] Allow usage of foreign memory API on all architectures --- .../com/oracle/svm/core/foreign/AbiUtils.java | 66 ++--------------- ...Enabled.java => ForeignAPIPredicates.java} | 32 ++++++-- .../core/foreign/ForeignFunctionsRuntime.java | 24 +++++- ...Target_java_lang_foreign_SymbolLookup.java | 4 +- ...get_jdk_internal_foreign_SystemLookup.java | 6 +- ...k_internal_foreign_Utils_BaseAndScale.java | 8 +- ...k_internal_foreign_abi_AbstractLinker.java | 12 +-- ...internal_foreign_abi_NativeEntryPoint.java | 4 +- ...jdk_internal_foreign_abi_UpcallLinker.java | 6 +- ..._jdk_internal_foreign_abi_UpcallStubs.java | 4 +- ..._jdk_internal_misc_ScopedMemoryAccess.java | 8 +- ...k_internal_foreign_abi_AbstractLinker.java | 73 +++++++++++++++++++ .../ForeignFunctionsConfigurationParser.java | 8 +- .../foreign/ForeignFunctionsFeature.java | 47 +++++++++--- .../foreign/FunctionDescriptorParser.java | 15 ++-- 15 files changed, 204 insertions(+), 113 deletions(-) rename substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/{ForeignFunctionsEnabled.java => ForeignAPIPredicates.java} (51%) create mode 100644 substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/unsupported/Target_jdk_internal_foreign_abi_AbstractLinker.java diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java index c0beb958c974..c7b9157a3263 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/AbiUtils.java @@ -79,6 +79,7 @@ import jdk.internal.foreign.abi.CallingSequence; import jdk.internal.foreign.abi.LinkerOptions; import jdk.internal.foreign.abi.NativeEntryPoint; +import jdk.internal.foreign.abi.SharedUtils; import jdk.internal.foreign.abi.VMStorage; import jdk.internal.foreign.abi.aarch64.AArch64Architecture; import jdk.internal.foreign.abi.x64.X86_64Architecture; @@ -696,7 +697,6 @@ protected List generateAdaptations(NativeEntryPointInfo nep) @Override public void checkLibrarySupport() { - fail(); } @Override @@ -716,7 +716,7 @@ public int trampolineSize() { @Override public TrampolineTemplate generateTrampolineTemplate() { - return fail(); + return null; } } @@ -763,33 +763,7 @@ public AssignedLocation[] toMemoryAssignment(VMStorage[] argMoves, boolean forRe @Override public Map canonicalLayouts() { - return Map.ofEntries( - // specified canonical layouts - Map.entry("bool", ValueLayout.JAVA_BOOLEAN), - Map.entry("char", ValueLayout.JAVA_BYTE), - Map.entry("short", ValueLayout.JAVA_SHORT), - Map.entry("int", ValueLayout.JAVA_INT), - Map.entry("float", ValueLayout.JAVA_FLOAT), - Map.entry("long", (ValueLayout) ValueLayout.JAVA_LONG), - Map.entry("long long", ValueLayout.JAVA_LONG), - Map.entry("double", ValueLayout.JAVA_DOUBLE), - Map.entry("void*", ValueLayout.ADDRESS), - Map.entry("size_t", (ValueLayout) ValueLayout.JAVA_LONG), - Map.entry("wchar_t", (ValueLayout) ValueLayout.JAVA_INT), - // unspecified size-dependent layouts - Map.entry("int8_t", ValueLayout.JAVA_BYTE), - Map.entry("int16_t", ValueLayout.JAVA_SHORT), - Map.entry("int32_t", ValueLayout.JAVA_INT), - Map.entry("int64_t", ValueLayout.JAVA_LONG), - // unspecified JNI layouts - Map.entry("jboolean", ValueLayout.JAVA_BOOLEAN), - Map.entry("jchar", ValueLayout.JAVA_CHAR), - Map.entry("jbyte", ValueLayout.JAVA_BYTE), - Map.entry("jshort", ValueLayout.JAVA_SHORT), - Map.entry("jint", ValueLayout.JAVA_INT), - Map.entry("jlong", ValueLayout.JAVA_LONG), - Map.entry("jfloat", ValueLayout.JAVA_FLOAT), - Map.entry("jdouble", ValueLayout.JAVA_DOUBLE)); + return SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); } @Override @@ -937,36 +911,6 @@ public AssignedLocation[] toMemoryAssignment(VMStorage[] argMoves, boolean forRe return storages; } - protected static Map canonicalLayouts(ValueLayout longLayout, ValueLayout sizetLayout, ValueLayout wchartLayout) { - return Map.ofEntries( - // specified canonical layouts - Map.entry("bool", ValueLayout.JAVA_BOOLEAN), - Map.entry("char", ValueLayout.JAVA_BYTE), - Map.entry("short", ValueLayout.JAVA_SHORT), - Map.entry("int", ValueLayout.JAVA_INT), - Map.entry("float", ValueLayout.JAVA_FLOAT), - Map.entry("long", longLayout), - Map.entry("long long", ValueLayout.JAVA_LONG), - Map.entry("double", ValueLayout.JAVA_DOUBLE), - Map.entry("void*", ValueLayout.ADDRESS), - Map.entry("size_t", sizetLayout), - Map.entry("wchar_t", wchartLayout), - // unspecified size-dependent layouts - Map.entry("int8_t", ValueLayout.JAVA_BYTE), - Map.entry("int16_t", ValueLayout.JAVA_SHORT), - Map.entry("int32_t", ValueLayout.JAVA_INT), - Map.entry("int64_t", ValueLayout.JAVA_LONG), - // unspecified JNI layouts - Map.entry("jboolean", ValueLayout.JAVA_BOOLEAN), - Map.entry("jchar", ValueLayout.JAVA_CHAR), - Map.entry("jbyte", ValueLayout.JAVA_BYTE), - Map.entry("jshort", ValueLayout.JAVA_SHORT), - Map.entry("jint", ValueLayout.JAVA_INT), - Map.entry("jlong", ValueLayout.JAVA_LONG), - Map.entry("jfloat", ValueLayout.JAVA_FLOAT), - Map.entry("jdouble", ValueLayout.JAVA_DOUBLE)); - } - @Override public Registers upcallSpecialArgumentsRegisters() { return new Registers(AMD64.r10, AMD64.r11); @@ -1058,7 +1002,7 @@ public void checkLibrarySupport() { @Override public Map canonicalLayouts() { - return canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); + return SharedUtils.canonicalLayouts(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT); } } @@ -1114,7 +1058,7 @@ public void checkLibrarySupport() { @Override public Map canonicalLayouts() { - return canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR); + return SharedUtils.canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR); } } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsEnabled.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java similarity index 51% rename from substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsEnabled.java rename to substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java index 1b937c22a20d..6ffccbd12d4d 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignFunctionsEnabled.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/ForeignAPIPredicates.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * 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 @@ -28,9 +28,31 @@ import com.oracle.svm.core.SubstrateOptions; -final class ForeignFunctionsEnabled implements BooleanSupplier { - @Override - public boolean getAsBoolean() { - return SubstrateOptions.ForeignAPISupport.getValue(); +/** + * Set of predicates used to control activation of substitutions (depending on method + * {@link ForeignFunctionsRuntime#areFunctionCallsSupported()}) if FFM API support is enabled. In + * case of the FFM API support is disabled entirely, substitutions in + * {@link com.oracle.svm.core.jdk.ForeignDisabledSubstitutions} will be used. + */ +public final class ForeignAPIPredicates { + public static final class Enabled implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return SubstrateOptions.ForeignAPISupport.getValue(); + } + } + + public static final class FunctionCallsSupported implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return SubstrateOptions.ForeignAPISupport.getValue() && ForeignFunctionsRuntime.areFunctionCallsSupported(); + } + } + + public static final class FunctionCallsUnsupported implements BooleanSupplier { + @Override + public boolean getAsBoolean() { + return SubstrateOptions.ForeignAPISupport.getValue() && !ForeignFunctionsRuntime.areFunctionCallsSupported(); + } } } 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 f3caffc8a081..ec4bf1e8adf4 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 @@ -29,10 +29,10 @@ import java.lang.constant.DirectMethodHandleDesc; import java.lang.invoke.MethodHandle; import java.util.HashMap; +import java.util.Locale; import java.util.Map; import java.util.function.BiConsumer; -import jdk.graal.compiler.word.Word; import org.graalvm.collections.EconomicMap; import org.graalvm.nativeimage.ImageSingletons; import org.graalvm.nativeimage.Platform; @@ -44,6 +44,8 @@ import com.oracle.svm.core.FunctionPointerHolder; import com.oracle.svm.core.OS; +import com.oracle.svm.core.SubstrateOptions; +import com.oracle.svm.core.SubstrateUtil; import com.oracle.svm.core.Uninterruptible; import com.oracle.svm.core.headers.LibC; import com.oracle.svm.core.headers.WindowsAPIs; @@ -53,6 +55,8 @@ import com.oracle.svm.core.util.VMError; import jdk.graal.compiler.api.replacements.Fold; +import jdk.graal.compiler.word.Word; +import jdk.internal.foreign.CABI; import jdk.internal.foreign.abi.CapturableState; public class ForeignFunctionsRuntime { @@ -76,6 +80,21 @@ public static ForeignFunctionsRuntime singleton() { public ForeignFunctionsRuntime() { } + public static boolean areFunctionCallsSupported() { + return switch (CABI.current()) { + case CABI.SYS_V -> !OS.DARWIN.isCurrent(); // GR-63074: code emit failures on + // darwin-amd64 + case CABI.WIN_64, CABI.MAC_OS_AARCH_64, CABI.LINUX_AARCH_64 -> true; + default -> false; + }; + } + + public static RuntimeException functionCallsUnsupported() { + assert SubstrateOptions.ForeignAPISupport.getValue(); + throw VMError.unsupportedFeature("Calling foreign functions is currently not supported on platform: " + + (OS.getCurrent().className + "-" + SubstrateUtil.getArchitectureName()).toLowerCase(Locale.ROOT)); + } + @Platforms(Platform.HOSTED_ONLY.class) public void addDowncallStubPointer(NativeEntryPointInfo nep, CFunctionPointer ptr) { VMError.guarantee(!downcallStubs.containsKey(nep), "Seems like multiple stubs were generated for %s", nep); @@ -117,6 +136,9 @@ CFunctionPointer getUpcallStubPointer(JavaEntryPointInfo jep) { } Pointer registerForUpcall(MethodHandle methodHandle, JavaEntryPointInfo jep) { + if (!areFunctionCallsSupported()) { + throw functionCallsUnsupported(); + } /* * Look up the upcall stub pointer first to avoid unnecessary allocation and synchronization * if it doesn't exist. diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_java_lang_foreign_SymbolLookup.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_java_lang_foreign_SymbolLookup.java index 39f91daf37fe..b1b77f4832f3 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_java_lang_foreign_SymbolLookup.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_java_lang_foreign_SymbolLookup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -67,7 +67,7 @@ * succeed. See * {@link com.oracle.svm.core.jdk.Target_java_lang_ClassLoader#loadLibrary(java.lang.Class, java.lang.String)} */ -@TargetClass(className = "java.lang.foreign.SymbolLookup", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "java.lang.foreign.SymbolLookup", onlyWith = ForeignAPIPredicates.Enabled.class) public final class Target_java_lang_foreign_SymbolLookup { @Substitute diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_SystemLookup.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_SystemLookup.java index 46bb52aa4bc7..a7e24d2adeca 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_SystemLookup.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_SystemLookup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -35,7 +35,7 @@ * libraries. The provided libraries are not really defined in the documentation, so the best we can * do is load the exact same libraries as HotSpot. */ -@TargetClass(className = "jdk.internal.foreign.SystemLookup", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "jdk.internal.foreign.SystemLookup", onlyWith = ForeignAPIPredicates.Enabled.class) public final class Target_jdk_internal_foreign_SystemLookup { @Substitute public Optional find(String name) { @@ -48,6 +48,6 @@ public Optional find(String name) { * 'jdk.internal.foreign.SystemLookup$WindowsFallbackSymbols') changes, ensure that the enum values * are still in sync with 'com.oracle.svm.native.libchelper/src/syslookup.c'. */ -@TargetClass(className = "jdk.internal.foreign.SystemLookup", innerClass = "WindowsFallbackSymbols", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "jdk.internal.foreign.SystemLookup", innerClass = "WindowsFallbackSymbols", onlyWith = ForeignAPIPredicates.Enabled.class) final class Target_jdk_internal_foreign_SystemLookup_WindowsFallbackSymbols { } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java index 991bd636980d..e2f30836421b 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_Utils_BaseAndScale.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,6 @@ */ package com.oracle.svm.core.foreign; -import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import org.graalvm.nativeimage.hosted.FieldValueTransformer; import com.oracle.svm.core.annotate.Alias; @@ -35,17 +34,18 @@ import com.oracle.svm.core.jdk.JDKLatest; import com.oracle.svm.core.util.VMError; +import jdk.graal.compiler.serviceprovider.JavaVersionUtil; import jdk.internal.foreign.Utils; import jdk.vm.ci.meta.JavaKind; -@TargetClass(className = "jdk.internal.foreign.Utils", innerClass = "BaseAndScale", onlyWith = {ForeignFunctionsEnabled.class, JDKLatest.class}) +@TargetClass(className = "jdk.internal.foreign.Utils", innerClass = "BaseAndScale", onlyWith = {ForeignAPIPredicates.Enabled.class, JDKLatest.class}) final class Target_jdk_internal_foreign_Utils_BaseAndScale { @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = BaseFieldRecomputer.class) // long base; } -@TargetClass(className = "jdk.internal.foreign.Utils", innerClass = "BaseAndScale", onlyWith = {ForeignFunctionsEnabled.class, JDK21OrEarlier.class}) +@TargetClass(className = "jdk.internal.foreign.Utils", innerClass = "BaseAndScale", onlyWith = {ForeignAPIPredicates.Enabled.class, JDK21OrEarlier.class}) final class Target_jdk_internal_foreign_Utils_BaseAndScale_JDK21 { @Alias // @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.Custom, declClass = BaseFieldRecomputer.class) // 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 a9314e915c54..9c9e3112b4e8 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 @@ -46,7 +46,7 @@ import jdk.internal.foreign.abi.x64.sysv.SysVx64Linker; import jdk.internal.foreign.abi.x64.windows.Windowsx64Linker; -@TargetClass(AbstractLinker.class) +@TargetClass(value = AbstractLinker.class, onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) public final class Target_jdk_internal_foreign_abi_AbstractLinker { // Checkstyle: stop @Alias // @@ -59,7 +59,7 @@ public final class Target_jdk_internal_foreign_abi_AbstractLinker { // Checkstyle: resume } -@TargetClass(className = "jdk.internal.foreign.abi.SoftReferenceCache") +@TargetClass(className = "jdk.internal.foreign.abi.SoftReferenceCache", onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_SoftReferenceCache { } @@ -92,7 +92,7 @@ public MemorySegment makeStub(MethodHandle target, Arena arena) { } } -@TargetClass(value = SysVx64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(value = SysVx64Linker.class, onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_x64_sysv_SysVx64Linker { @Substitute @@ -101,7 +101,7 @@ UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor functi } } -@TargetClass(value = Windowsx64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(value = Windowsx64Linker.class, onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_x64_windows_Windowsx64Linker { @Substitute @@ -110,7 +110,7 @@ UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor functi } } -@TargetClass(value = MacOsAArch64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(value = MacOsAArch64Linker.class, onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_aarch64_macos_MacOsAArch64Linker { @Substitute @@ -119,7 +119,7 @@ UpcallStubFactory arrangeUpcall(MethodType targetType, FunctionDescriptor functi } } -@TargetClass(value = LinuxAArch64Linker.class, onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(value = LinuxAArch64Linker.class, onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_aarch64_linux_LinuxAArch64Linker { @Substitute diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java index 89a67468d7b6..3214bee3a8a2 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_NativeEntryPoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -38,7 +38,7 @@ * Packs the address of a {@link com.oracle.svm.hosted.foreign.DowncallStub} with some extra * information. */ -@TargetClass(className = "jdk.internal.foreign.abi.NativeEntryPoint", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "jdk.internal.foreign.abi.NativeEntryPoint", onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) @Substitute public final class Target_jdk_internal_foreign_abi_NativeEntryPoint { diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallLinker.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallLinker.java index 90364478caa8..de821bb3b5d4 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallLinker.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallLinker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -33,7 +33,7 @@ import jdk.internal.foreign.abi.ABIDescriptor; import jdk.internal.foreign.abi.VMStorage; -@TargetClass(className = "jdk.internal.foreign.abi.UpcallLinker", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "jdk.internal.foreign.abi.UpcallLinker", onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_UpcallLinker { @Substitute @@ -48,7 +48,7 @@ private static void registerNatives() { } } -@TargetClass(className = "jdk.internal.foreign.abi.UpcallLinker", innerClass = "CallRegs", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "jdk.internal.foreign.abi.UpcallLinker", innerClass = "CallRegs", onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_UpcallLinker_CallRegs { @Alias private VMStorage[] argRegs; @Alias private VMStorage[] retRegs; diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallStubs.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallStubs.java index 9e3483389d6f..d3ca04891f76 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallStubs.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_foreign_abi_UpcallStubs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -32,7 +32,7 @@ import jdk.internal.foreign.MemorySessionImpl; -@TargetClass(className = "jdk.internal.foreign.abi.UpcallStubs", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "jdk.internal.foreign.abi.UpcallStubs", onlyWith = ForeignAPIPredicates.FunctionCallsSupported.class) final class Target_jdk_internal_foreign_abi_UpcallStubs { @Substitute @SuppressWarnings("restricted") diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java index 1216c70fd97c..f2e30655d7e7 100644 --- a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/Target_jdk_internal_misc_ScopedMemoryAccess.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -37,12 +37,10 @@ /** * Gracefully handle unsupported features. - *

- * It seems like this could be easily supported once thread-local handshakes are supported. */ @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-24+15/src/java.base/share/classes/jdk/internal/misc/X-ScopedMemoryAccess-bin.java.template") @BasedOnJDKFile("https://github.com/openjdk/jdk/blob/jdk-25+13/src/java.base/share/classes/jdk/internal/misc/X-ScopedMemoryAccess.java.template") -@TargetClass(className = "jdk.internal.misc.ScopedMemoryAccess", onlyWith = ForeignFunctionsEnabled.class) +@TargetClass(className = "jdk.internal.misc.ScopedMemoryAccess", onlyWith = ForeignAPIPredicates.Enabled.class) public final class Target_jdk_internal_misc_ScopedMemoryAccess { @Substitute static void registerNatives() { @@ -83,6 +81,6 @@ boolean closeScope0(MemorySessionImpl session) { } } -@TargetClass(className = "jdk.internal.misc.ScopedMemoryAccess$ScopedAccessError", onlyWith = {JDKLatest.class, ForeignFunctionsEnabled.class}) +@TargetClass(className = "jdk.internal.misc.ScopedMemoryAccess$ScopedAccessError", onlyWith = {JDKLatest.class, ForeignAPIPredicates.Enabled.class}) final class Target_jdk_internal_misc_ScopedMemoryAccess_ScopedAccessError { } diff --git a/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/unsupported/Target_jdk_internal_foreign_abi_AbstractLinker.java b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/unsupported/Target_jdk_internal_foreign_abi_AbstractLinker.java new file mode 100644 index 000000000000..216a07feb7a8 --- /dev/null +++ b/substratevm/src/com.oracle.svm.core.foreign/src/com/oracle/svm/core/foreign/unsupported/Target_jdk_internal_foreign_abi_AbstractLinker.java @@ -0,0 +1,73 @@ +/* + * 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.core.foreign.unsupported; + +import java.lang.foreign.Arena; +import java.lang.foreign.FunctionDescriptor; +import java.lang.foreign.Linker; +import java.lang.foreign.Linker.Option; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.MethodHandle; + +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 com.oracle.svm.core.foreign.ForeignAPIPredicates; +import com.oracle.svm.core.foreign.ForeignFunctionsRuntime; + +/* + * Substitutions for when Foreign Function and Memory (FFM) API support is enabled but not fully supported on the current architecture. + * In this case, the Memory API usually works but the Foreign Function API and the symbol lookups don't. + */ + +@TargetClass(className = "jdk.internal.foreign.abi.SoftReferenceCache", onlyWith = ForeignAPIPredicates.FunctionCallsUnsupported.class) +final class Target_jdk_internal_foreign_abi_SoftReferenceCache { +} + +@TargetClass(className = "jdk.internal.foreign.abi.AbstractLinker", onlyWith = ForeignAPIPredicates.FunctionCallsUnsupported.class) +final class Target_jdk_internal_foreign_abi_AbstractLinker { + // Checkstyle: stop + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "jdk.internal.foreign.abi.SoftReferenceCache") // + private Target_jdk_internal_foreign_abi_SoftReferenceCache DOWNCALL_CACHE; + + @Alias // + @RecomputeFieldValue(kind = RecomputeFieldValue.Kind.NewInstance, declClassName = "jdk.internal.foreign.abi.SoftReferenceCache") // + private Target_jdk_internal_foreign_abi_SoftReferenceCache UPCALL_CACHE; + + // Checkstyle: resume + @Substitute + @SuppressWarnings({"unused", "static-method"}) + MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options) { + throw ForeignFunctionsRuntime.functionCallsUnsupported(); + } + + @Substitute + @SuppressWarnings({"unused", "static-method"}) + private MethodHandle downcallHandle0(FunctionDescriptor function, Option... options) { + throw ForeignFunctionsRuntime.functionCallsUnsupported(); + } +} 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 7aa1b11a03c5..2e54be64d97f 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,12 +27,14 @@ import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; import java.lang.foreign.Linker.Option; +import java.lang.foreign.MemoryLayout; 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; +import java.util.Map; import java.util.function.BiConsumer; import java.util.function.Function; @@ -59,11 +61,13 @@ public class ForeignFunctionsConfigurationParser extends ConfigurationParser { private final ImageClassLoader imageClassLoader; private final RuntimeForeignAccessSupport accessSupport; + private final Map canonicalLayouts; - public ForeignFunctionsConfigurationParser(ImageClassLoader imageClassLoader, RuntimeForeignAccessSupport access) { + public ForeignFunctionsConfigurationParser(ImageClassLoader imageClassLoader, RuntimeForeignAccessSupport access, Map canonicalLayouts) { super(true); this.imageClassLoader = imageClassLoader; this.accessSupport = access; + this.canonicalLayouts = canonicalLayouts; } @Override @@ -121,7 +125,7 @@ private void parseAndRegisterDirectUpcall(Object call) { private FunctionDescriptor parseDescriptor(Object signature) { String input = asString(signature, "a function descriptor must be a string"); - return FunctionDescriptorParser.parse(input); + return FunctionDescriptorParser.parse(input, canonicalLayouts); } /** diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java index 442e971d5e4e..876d70268fba 100644 --- a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/ForeignFunctionsFeature.java @@ -31,6 +31,7 @@ import java.lang.constant.DirectMethodHandleDesc.Kind; import java.lang.foreign.FunctionDescriptor; import java.lang.foreign.Linker; +import java.lang.foreign.MemoryLayout; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; @@ -41,6 +42,7 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -81,9 +83,11 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ConditionalConfigurationRegistry; import com.oracle.svm.hosted.FeatureImpl; +import com.oracle.svm.hosted.ImageClassLoader; import com.oracle.svm.hosted.ProgressReporter; import com.oracle.svm.hosted.code.CEntryPointData; import com.oracle.svm.hosted.config.ConfigurationParserUtils; +import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ModuleSupport; import com.oracle.svm.util.ReflectionUtil; @@ -222,12 +226,6 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { return false; } UserError.guarantee(JavaVersionUtil.JAVA_SPEC >= 22, "Support for the Foreign Function and Memory API is available only with JDK 22 and later."); - boolean isLinuxAmd64 = OS.LINUX.isCurrent() && SubstrateUtil.getArchitectureName().contains("amd64"); - boolean isWindowsAmd64 = OS.WINDOWS.isCurrent() && SubstrateUtil.getArchitectureName().contains("amd64"); - boolean isDarwinAArch64 = OS.DARWIN.isCurrent() && SubstrateUtil.getArchitectureName().contains("aarch64"); - boolean isLinuxAArch64 = OS.LINUX.isCurrent() && SubstrateUtil.getArchitectureName().contains("aarch64"); - UserError.guarantee(isLinuxAmd64 || isWindowsAmd64 || isDarwinAArch64 || isLinuxAArch64, - "Support for the Foreign Function and Memory API is currently available on Linux AMD64, Windows AMD64, Darwin AArch64 or Linux AArch64."); UserError.guarantee(!SubstrateOptions.useLLVMBackend(), "Support for the Foreign Function and Memory API is not available with the LLVM backend."); return true; } @@ -240,11 +238,23 @@ public void duringSetup(DuringSetupAccess a) { ImageSingletons.add(RuntimeForeignAccessSupport.class, accessSupport); ImageSingletons.add(LinkToNativeSupport.class, new LinkToNativeSupportImpl()); - ConfigurationParser parser = new ForeignFunctionsConfigurationParser(access.getImageClassLoader(), accessSupport); - ConfigurationParserUtils.parseAndRegisterConfigurations(parser, access.getImageClassLoader(), "panama foreign", + ImageClassLoader imageClassLoader = access.getImageClassLoader(); + ConfigurationParserUtils.parseAndRegisterConfigurations(getConfigurationParser(imageClassLoader), imageClassLoader, "panama foreign", ConfigurationFiles.Options.ForeignConfigurationFiles, ConfigurationFiles.Options.ForeignResources, ConfigurationFile.FOREIGN.getFileName()); } + private ConfigurationParser getConfigurationParser(ImageClassLoader imageClassLoader) { + /* + * If foreign function calls are not supported on this platform, we still want to parse the + * configuration files such that their syntax is validated. In this case, + * 'AbiUtils.singleton()' would return the 'Unsupported' ABI and calling method + * 'canonicalLayouts' would cause an exception. However, since the layouts won't be + * consumed, it doesn't matter much which ones we use and so we just use the hosted ones. + */ + Map canonicalLayouts = ForeignFunctionsRuntime.areFunctionCallsSupported() ? AbiUtils.singleton().canonicalLayouts() : Linker.nativeLinker().canonicalLayouts(); + return new ForeignFunctionsConfigurationParser(imageClassLoader, accessSupport, canonicalLayouts); + } + private interface StubFactory { S createKey(T registeredDescriptor); @@ -457,6 +467,10 @@ private static void registerVarHandleMethodsForReflection(FeatureAccess access, RuntimeReflection.register(subtype.getDeclaredMethods()); } + private static String platform() { + return (OS.getCurrent().className + "-" + SubstrateUtil.getArchitectureName()).toLowerCase(Locale.ROOT); + } + @Override public void beforeAnalysis(BeforeAnalysisAccess a) { var access = (FeatureImpl.BeforeAnalysisAccessImpl) a; @@ -490,8 +504,21 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.registerAsRoot(ReflectionUtil.lookupMethod(ForeignFunctionsRuntime.class, "captureCallState", int.class, CIntPointer.class), false, "Runtime support, registered in " + ForeignFunctionsFeature.class); - createDowncallStubs(access); - createUpcallStubs(access); + if (ForeignFunctionsRuntime.areFunctionCallsSupported()) { + createDowncallStubs(access); + createUpcallStubs(access); + } else { + if (!registeredDowncalls.isEmpty() || !registeredUpcalls.isEmpty() || !registeredDirectUpcalls.isEmpty()) { + registeredDowncalls.clear(); + registeredUpcalls.clear(); + registeredDirectUpcalls.clear(); + + LogUtils.warning("Registered down- and upcall stubs will be ignored because calling foreign functions is currently not supported on platform: %s", platform()); + } + downcallCount = 0; + upcallCount = 0; + directUpcallCount = 0; + } ProgressReporter.singleton().setForeignFunctionsInfo(getCreatedDowncallStubsCount(), getCreatedUpcallStubsCount()); } diff --git a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/FunctionDescriptorParser.java b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/FunctionDescriptorParser.java index 4de2067da47f..7103c3735410 100644 --- a/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/FunctionDescriptorParser.java +++ b/substratevm/src/com.oracle.svm.hosted.foreign/src/com/oracle/svm/hosted/foreign/FunctionDescriptorParser.java @@ -28,13 +28,12 @@ import java.lang.foreign.MemoryLayout; import java.util.ArrayList; import java.util.List; +import java.util.Map; import java.util.function.Supplier; import org.graalvm.nativeimage.Platform; import org.graalvm.nativeimage.Platforms; -import com.oracle.svm.core.foreign.AbiUtils; - /** * Parses a string into a {@link java.lang.foreign.FunctionDescriptor}. The syntax is as follows * @@ -65,10 +64,12 @@ private FunctionDescriptorParser() { private static final class Impl { private final String layout; private int at; + private final Map canonicalLayouts; - private Impl(String input) { + private Impl(String input, Map canonicalLayouts) { this.layout = input; this.at = 0; + this.canonicalLayouts = canonicalLayouts; } private char peek() { @@ -209,10 +210,10 @@ private MemoryLayout parseSequenceLayout() { private MemoryLayout parseValueLayout() { String name = consumeName(); - if (!AbiUtils.singleton().canonicalLayouts().containsKey(name)) { + if (!canonicalLayouts.containsKey(name)) { handleError("Unknown value layout: " + name + " at " + this.at + " in " + this.layout); } - return AbiUtils.singleton().canonicalLayouts().get(name); + return canonicalLayouts.get(name); } private void checkDone() { @@ -222,9 +223,9 @@ private void checkDone() { } } - public static FunctionDescriptor parse(String input) { + public static FunctionDescriptor parse(String input, Map canonicalLayouts) { try { - Impl parser = new Impl(input); + Impl parser = new Impl(input, canonicalLayouts); FunctionDescriptor res = parser.parseDescriptor(); parser.checkDone(); return res;