Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GR-52573] Allow usage of foreign memory API on all platforms. #10890

Merged
merged 1 commit into from
Mar 20, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<Adapter.Adaptation> 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<String, MemoryLayout> 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<String, MemoryLayout> 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<String, MemoryLayout> 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<String, MemoryLayout> canonicalLayouts() {
return canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR);
return SharedUtils.canonicalLayouts(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_CHAR);
}
}

Original file line number Diff line number Diff line change
@@ -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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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<MemorySegment> find(String name) {
@@ -48,6 +48,6 @@ public Optional<MemorySegment> 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 {
}
Original file line number Diff line number Diff line change
@@ -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) //
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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 {

Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -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")
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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 {
}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<String, MemoryLayout> canonicalLayouts;

public ForeignFunctionsConfigurationParser(ImageClassLoader imageClassLoader, RuntimeForeignAccessSupport access) {
public ForeignFunctionsConfigurationParser(ImageClassLoader imageClassLoader, RuntimeForeignAccessSupport access, Map<String, MemoryLayout> 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);
}

/**
Original file line number Diff line number Diff line change
@@ -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<String, MemoryLayout> canonicalLayouts = ForeignFunctionsRuntime.areFunctionCallsSupported() ? AbiUtils.singleton().canonicalLayouts() : Linker.nativeLinker().canonicalLayouts();
return new ForeignFunctionsConfigurationParser(imageClassLoader, accessSupport, canonicalLayouts);
}

private interface StubFactory<S, T, U extends ResolvedJavaMethod> {
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());
}

Original file line number Diff line number Diff line change
@@ -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<String, MemoryLayout> canonicalLayouts;

private Impl(String input) {
private Impl(String input, Map<String, MemoryLayout> 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<String, MemoryLayout> canonicalLayouts) {
try {
Impl parser = new Impl(input);
Impl parser = new Impl(input, canonicalLayouts);
FunctionDescriptor res = parser.parseDescriptor();
parser.checkDone();
return res;