diff --git a/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json b/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json index d947d1391591..42cdd780214b 100644 --- a/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json +++ b/docs/reference-manual/native-image/assets/foreign-config-schema-v0.1.0.json @@ -6,6 +6,10 @@ "default": [], "items": { "properties": { + "condition": { + "$ref": "config-condition-schema-v1.0.0.json", + "title": "Condition under which the downcall stub should be registered" + }, "returnType": { "type": "string", "title": "A memory layout definition (allows canonical layouts; see 'java.lang.foreign.Linker')" @@ -56,6 +60,10 @@ "default": [], "items": { "properties": { + "condition": { + "$ref": "config-condition-schema-v1.0.0.json", + "title": "Condition under which the upcall stub should be registered" + }, "returnType": { "type": "string", "title": "A memory layout definition (allows canonical layouts; see 'java.lang.foreign.Linker')" @@ -88,6 +96,10 @@ "default": [], "items": { "properties": { + "condition": { + "$ref": "config-condition-schema-v1.0.0.json", + "title": "Condition under which the direct upcall stub should be registered" + }, "class": { "type": "string", "title": "Full-qualified class name (e.g. 'org.package.OuterClass$InnerClass')" diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ForeignConfigurationParser.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ForeignConfigurationParser.java index 65147491f835..c6f007cf077a 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ForeignConfigurationParser.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ForeignConfigurationParser.java @@ -29,7 +29,6 @@ import java.util.List; import org.graalvm.collections.EconomicMap; -import org.graalvm.nativeimage.impl.ConfigurationCondition; /** * A base class for parsing FFM API configurations. @@ -37,7 +36,7 @@ * @param the type of the function descriptor * @param the type of the linker options */ -public abstract class ForeignConfigurationParser extends ConfigurationParser { +public abstract class ForeignConfigurationParser extends ConditionalConfigurationParser { private static final String PARAMETER_TYPES = "parameterTypes"; private static final String RETURN_TYPE = "returnType"; @@ -68,20 +67,21 @@ public void parseAndRegister(Object json, URI origin) { private void parseAndRegisterForeignCall(Object call, boolean forUpcall) { var map = asMap(call, "a foreign call must be a map"); - checkAttributes(map, "foreign call", List.of(RETURN_TYPE, PARAMETER_TYPES), List.of("options")); + checkAttributes(map, "foreign call", List.of(RETURN_TYPE, PARAMETER_TYPES), List.of(CONDITIONAL_KEY, "options")); + var condition = parseCondition(map, true); var descriptor = createFunctionDescriptor(map); var optionsMap = asMap(map.get("options", EconomicMap.emptyMap()), "options must be a map"); if (forUpcall) { LO upcallOptions = createUpcallOptions(optionsMap, descriptor); try { - registerUpcall(ConfigurationCondition.alwaysTrue(), descriptor, upcallOptions); + registerUpcall(condition, descriptor, upcallOptions); } catch (Exception e) { handleRegistrationError(e, map); } } else { LO downcallOptions = createDowncallOptions(optionsMap, descriptor); try { - registerDowncall(ConfigurationCondition.alwaysTrue(), descriptor, downcallOptions); + registerDowncall(condition, descriptor, downcallOptions); } catch (Exception e) { handleRegistrationError(e, map); } @@ -90,8 +90,9 @@ private void parseAndRegisterForeignCall(Object call, boolean forUpcall) { private void parseAndRegisterDirectUpcall(Object call) { var map = asMap(call, "a foreign call must be a map"); - checkAttributes(map, "foreign call", List.of("class", "method"), List.of(RETURN_TYPE, PARAMETER_TYPES, "options")); + checkAttributes(map, "foreign call", List.of("class", "method"), List.of(CONDITIONAL_KEY, RETURN_TYPE, PARAMETER_TYPES, "options")); + var condition = parseCondition(map, true); String className = asString(map.get("class"), "class"); String methodName = asString(map.get("method"), "method"); Object returnTypeInput = map.get(RETURN_TYPE); @@ -102,13 +103,13 @@ private void parseAndRegisterDirectUpcall(Object call) { FD descriptor = createFunctionDescriptor(map); LO upcallOptions = createUpcallOptions(optionsMap, descriptor); try { - registerDirectUpcallWithDescriptor(className, methodName, descriptor, upcallOptions); + registerDirectUpcallWithDescriptor(condition, className, methodName, descriptor, upcallOptions); } catch (Exception e) { handleRegistrationError(e, map); } } else { try { - registerDirectUpcallWithoutDescriptor(className, methodName, optionsMap); + registerDirectUpcallWithoutDescriptor(condition, className, methodName, optionsMap); } catch (Exception e) { handleRegistrationError(e, map); } @@ -141,13 +142,13 @@ private FD createFunctionDescriptor(EconomicMap map) { /** Parses the options allowed for upcalls. */ protected abstract LO createUpcallOptions(EconomicMap map, FD desc); - protected abstract void registerDowncall(ConfigurationCondition configurationCondition, FD descriptor, LO options); + protected abstract void registerDowncall(UnresolvedConfigurationCondition configurationCondition, FD descriptor, LO options); - protected abstract void registerUpcall(ConfigurationCondition configurationCondition, FD descriptor, LO options); + protected abstract void registerUpcall(UnresolvedConfigurationCondition configurationCondition, FD descriptor, LO options); - protected abstract void registerDirectUpcallWithoutDescriptor(String className, String methodName, EconomicMap optionsMap); + protected abstract void registerDirectUpcallWithoutDescriptor(UnresolvedConfigurationCondition configurationCondition, String className, String methodName, EconomicMap optionsMap); - protected abstract void registerDirectUpcallWithDescriptor(String className, String methodName, FD descriptor, LO options); + protected abstract void registerDirectUpcallWithDescriptor(UnresolvedConfigurationCondition configurationCondition, String className, String methodName, FD descriptor, LO options); protected abstract void handleRegistrationError(Exception e, EconomicMap map); } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java index b38e2d234504..a5fa946ec308 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ForeignConfiguration.java @@ -37,7 +37,6 @@ import org.graalvm.collections.EconomicMap; import org.graalvm.collections.MapCursor; -import org.graalvm.nativeimage.impl.ConfigurationCondition; import com.oracle.svm.configure.ConfigurationBase; import com.oracle.svm.configure.ConfigurationParser; @@ -57,7 +56,7 @@ public void printJson(JsonWriter writer) throws IOException { } } - private record StubDesc(ConfigurationFunctionDescriptor desc, Map linkerOptions) implements JsonPrintable { + private record StubDesc(UnresolvedConfigurationCondition condition, ConfigurationFunctionDescriptor desc, Map linkerOptions) implements JsonPrintable { @Override public void printJson(JsonWriter writer) throws IOException { writer.appendObjectStart(); @@ -69,7 +68,8 @@ public void printJson(JsonWriter writer) throws IOException { } } - private record DirectStubDesc(String clazz, String method, ConfigurationFunctionDescriptor desc, Map linkerOptions) implements JsonPrintable { + private record DirectStubDesc(UnresolvedConfigurationCondition condition, String clazz, String method, ConfigurationFunctionDescriptor desc, + Map linkerOptions) implements JsonPrintable { @Override public void printJson(JsonWriter writer) throws IOException { writer.appendObjectStart() @@ -88,7 +88,7 @@ public DirectStubDesc withoutFD() { if (desc == null) { return this; } - return new DirectStubDesc(clazz, method, null, linkerOptions); + return new DirectStubDesc(condition, clazz, method, null, linkerOptions); } } @@ -216,13 +216,13 @@ public void mergeConditional(UnresolvedConfigurationCondition condition, Foreign public void addDowncall(String returnType, List parameterTypes, Map linkerOptions) { Objects.requireNonNull(returnType); Objects.requireNonNull(parameterTypes); - addDowncall(new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions)); + addDowncall(UnresolvedConfigurationCondition.alwaysTrue(), new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions)); } public void addUpcall(String returnType, List parameterTypes, Map linkerOptions) { Objects.requireNonNull(returnType); Objects.requireNonNull(parameterTypes); - addUpcall(new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions)); + addUpcall(UnresolvedConfigurationCondition.alwaysTrue(), new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions)); } public void addDirectUpcall(String returnType, List parameterTypes, Map linkerOptions, String clazz, String method) { @@ -230,34 +230,34 @@ public void addDirectUpcall(String returnType, List parameterTypes, Map< Objects.requireNonNull(parameterTypes); Objects.requireNonNull(clazz); Objects.requireNonNull(method); - addDirectUpcall(new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions), clazz, method); + addDirectUpcall(UnresolvedConfigurationCondition.alwaysTrue(), new ConfigurationFunctionDescriptor(returnType, parameterTypes), Map.copyOf(linkerOptions), clazz, method); } - public void addDowncall(ConfigurationFunctionDescriptor desc, Map linkerOptions) { + public void addDowncall(UnresolvedConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor desc, Map linkerOptions) { Objects.requireNonNull(desc); - downcallStubs.add(new StubDesc(desc, Map.copyOf(linkerOptions))); + downcallStubs.add(new StubDesc(configurationCondition, desc, Map.copyOf(linkerOptions))); } - public void addUpcall(ConfigurationFunctionDescriptor desc, Map linkerOptions) { + public void addUpcall(UnresolvedConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor desc, Map linkerOptions) { Objects.requireNonNull(desc); - upcallStubs.add(new StubDesc(desc, Map.copyOf(linkerOptions))); + upcallStubs.add(new StubDesc(configurationCondition, desc, Map.copyOf(linkerOptions))); } - public void addDirectUpcall(ConfigurationFunctionDescriptor desc, Map linkerOptions, String clazz, String method) { + public void addDirectUpcall(UnresolvedConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor desc, Map linkerOptions, String clazz, String method) { Objects.requireNonNull(desc); Objects.requireNonNull(clazz); Objects.requireNonNull(method); - DirectStubDesc candidate = new DirectStubDesc(clazz, method, desc, Map.copyOf(linkerOptions)); + DirectStubDesc candidate = new DirectStubDesc(configurationCondition, clazz, method, desc, Map.copyOf(linkerOptions)); // only add the new descriptor if it is not subsumed by an existing one if (!directUpcallStubs.contains(candidate.withoutFD())) { directUpcallStubs.add(candidate); } } - public void addDirectUpcall(Map linkerOptions, String clazz, String method) { + public void addDirectUpcall(UnresolvedConfigurationCondition configurationCondition, Map linkerOptions, String clazz, String method) { Objects.requireNonNull(clazz); Objects.requireNonNull(method); - DirectStubDesc directStubDesc = new DirectStubDesc(clazz, method, null, Map.copyOf(linkerOptions)); + DirectStubDesc directStubDesc = new DirectStubDesc(configurationCondition, clazz, method, null, Map.copyOf(linkerOptions)); // remove all existing descriptors if they are subsumed by the new descriptor directUpcallStubs.removeIf(existing -> directStubDesc.equals(existing.withoutFD())); directUpcallStubs.add(directStubDesc); @@ -337,24 +337,25 @@ private UnresolvedForeignConfigurationParser(EnumSet } @Override - protected void registerDowncall(ConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor descriptor, Map options) { - ForeignConfiguration.this.addDowncall(descriptor, options); + protected void registerDowncall(UnresolvedConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor descriptor, Map options) { + ForeignConfiguration.this.addDowncall(configurationCondition, descriptor, options); } @Override - protected void registerUpcall(ConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor descriptor, Map options) { - ForeignConfiguration.this.addUpcall(descriptor, options); + protected void registerUpcall(UnresolvedConfigurationCondition configurationCondition, ConfigurationFunctionDescriptor descriptor, Map options) { + ForeignConfiguration.this.addUpcall(configurationCondition, descriptor, options); } @Override - protected void registerDirectUpcallWithoutDescriptor(String className, String methodName, EconomicMap optionsMap) { - ForeignConfiguration.this.addDirectUpcall(economicMapToJavaMap(optionsMap), className, methodName); + protected void registerDirectUpcallWithoutDescriptor(UnresolvedConfigurationCondition configurationCondition, String className, String methodName, EconomicMap optionsMap) { + ForeignConfiguration.this.addDirectUpcall(configurationCondition, economicMapToJavaMap(optionsMap), className, methodName); } @Override - protected void registerDirectUpcallWithDescriptor(String className, String methodName, ConfigurationFunctionDescriptor descriptor, Map options) { - ForeignConfiguration.this.addDirectUpcall(descriptor, options, className, methodName); + protected void registerDirectUpcallWithDescriptor(UnresolvedConfigurationCondition configurationCondition, String className, String methodName, ConfigurationFunctionDescriptor descriptor, + Map options) { + ForeignConfiguration.this.addDirectUpcall(configurationCondition, descriptor, options, className, methodName); } @Override 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 6327d6cb4311..6ed9f84d0b7c 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 @@ -36,6 +36,7 @@ import java.util.Map; import java.util.function.BiConsumer; +import com.oracle.svm.core.util.ImageHeapMap; import org.graalvm.collections.EconomicMap; import org.graalvm.collections.Pair; import org.graalvm.nativeimage.ImageSingletons; @@ -75,9 +76,10 @@ public static ForeignFunctionsRuntime singleton() { } private final AbiUtils.TrampolineTemplate trampolineTemplate; - private final EconomicMap downcallStubs = EconomicMap.create(); - private final EconomicMap, FunctionPointerHolder> directUpcallStubs = EconomicMap.create(); - private final EconomicMap upcallStubs = EconomicMap.create(); + + private final EconomicMap downcallStubs = ImageHeapMap.create("downcallStubs"); + private final EconomicMap, FunctionPointerHolder> directUpcallStubs = ImageHeapMap.create("directUpcallStubs"); + private final EconomicMap upcallStubs = ImageHeapMap.create("upcallStubs"); private final Map trampolines = new HashMap<>(); private TrampolineSet currentTrampolineSet; @@ -104,22 +106,49 @@ public static RuntimeException functionCallsUnsupported() { } @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); - VMError.guarantee(downcallStubs.put(nep, new FunctionPointerHolder(ptr)) == null); + public boolean downcallStubExists(NativeEntryPointInfo nep) { + return downcallStubs.containsKey(nep); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public int getDowncallStubsCount() { + return downcallStubs.size(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean upcallStubExists(JavaEntryPointInfo jep) { + return upcallStubs.containsKey(jep); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public int getUpcallStubsCount() { + return upcallStubs.size(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean directUpcallStubExists(DirectMethodHandleDesc desc, JavaEntryPointInfo jep) { + return directUpcallStubs.containsKey(Pair.create(desc, jep)); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public int getDirectUpcallStubsCount() { + return directUpcallStubs.size(); + } + + @Platforms(Platform.HOSTED_ONLY.class) + public boolean addDowncallStubPointer(NativeEntryPointInfo nep, CFunctionPointer ptr) { + return downcallStubs.putIfAbsent(nep, new FunctionPointerHolder(ptr)) == null; } @Platforms(Platform.HOSTED_ONLY.class) - public void addUpcallStubPointer(JavaEntryPointInfo jep, CFunctionPointer ptr) { - VMError.guarantee(!upcallStubs.containsKey(jep), "Seems like multiple stubs were generated for %s", jep); - VMError.guarantee(upcallStubs.put(jep, new FunctionPointerHolder(ptr)) == null); + public boolean addUpcallStubPointer(JavaEntryPointInfo jep, CFunctionPointer ptr) { + return upcallStubs.putIfAbsent(jep, new FunctionPointerHolder(ptr)) == null; } @Platforms(Platform.HOSTED_ONLY.class) - public void addDirectUpcallStubPointer(DirectMethodHandleDesc desc, JavaEntryPointInfo jep, CFunctionPointer ptr) { + public boolean addDirectUpcallStubPointer(DirectMethodHandleDesc desc, JavaEntryPointInfo jep, CFunctionPointer ptr) { var key = Pair.create(desc, jep); - VMError.guarantee(!directUpcallStubs.containsKey(key), "Seems like multiple stubs were generated for %s", desc); - VMError.guarantee(directUpcallStubs.put(key, new FunctionPointerHolder(ptr)) == null); + return directUpcallStubs.putIfAbsent(key, new FunctionPointerHolder(ptr)) == null; } /** 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 ddb2c2119a5c..35ad7ec9c4b3 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 @@ -55,11 +55,15 @@ import com.oracle.svm.configure.ConfigurationParserOption; import com.oracle.svm.configure.ForeignConfigurationParser; +import com.oracle.svm.configure.UnresolvedConfigurationCondition; import com.oracle.svm.core.util.BasedOnJDKFile; import com.oracle.svm.hosted.ImageClassLoader; +import com.oracle.svm.hosted.classinitialization.ClassInitializationSupport; import com.oracle.svm.hosted.foreign.MemoryLayoutParser.MemoryLayoutParserException; +import com.oracle.svm.hosted.reflect.NativeImageConditionResolver; import com.oracle.svm.util.LogUtils; import com.oracle.svm.util.ReflectionUtil; +import com.oracle.svm.util.TypeResult; import jdk.graal.compiler.util.json.JsonFormatter; import jdk.graal.compiler.util.json.JsonParserException; @@ -76,6 +80,7 @@ public class ForeignFunctionsConfigurationParser extends ForeignConfigurationPar private static final Linker.Option[] EMPTY_OPTIONS = new Linker.Option[0]; private final ImageClassLoader imageClassLoader; + private final NativeImageConditionResolver conditionResolver; private final RuntimeForeignAccessSupport accessSupport; private final Map canonicalLayouts; @@ -84,6 +89,7 @@ public class ForeignFunctionsConfigurationParser extends ForeignConfigurationPar public ForeignFunctionsConfigurationParser(ImageClassLoader imageClassLoader, RuntimeForeignAccessSupport access, Map canonicalLayouts) { super(EnumSet.of(ConfigurationParserOption.STRICT_CONFIGURATION)); this.imageClassLoader = imageClassLoader; + this.conditionResolver = new NativeImageConditionResolver(imageClassLoader, ClassInitializationSupport.singleton()); this.accessSupport = access; this.canonicalLayouts = canonicalLayouts; } @@ -96,17 +102,30 @@ protected EnumSet supportedOptions() { } @Override - protected void registerDowncall(ConfigurationCondition configurationCondition, FunctionDescriptor descriptor, Option[] options) { - accessSupport.registerForDowncall(ConfigurationCondition.alwaysTrue(), descriptor, (Object[]) options); + protected void registerDowncall(UnresolvedConfigurationCondition configurationCondition, FunctionDescriptor descriptor, Option[] options) { + TypeResult typeResult = conditionResolver.resolveCondition(configurationCondition); + if (!typeResult.isPresent()) { + return; + } + accessSupport.registerForDowncall(typeResult.get(), descriptor, (Object[]) options); } @Override - protected void registerUpcall(ConfigurationCondition configurationCondition, FunctionDescriptor descriptor, Option[] options) { - accessSupport.registerForUpcall(ConfigurationCondition.alwaysTrue(), descriptor, (Object[]) options); + protected void registerUpcall(UnresolvedConfigurationCondition configurationCondition, FunctionDescriptor descriptor, Option[] options) { + TypeResult typeResult = conditionResolver.resolveCondition(configurationCondition); + if (!typeResult.isPresent()) { + return; + } + accessSupport.registerForUpcall(typeResult.get(), descriptor, (Object[]) options); } @Override - protected void registerDirectUpcallWithDescriptor(String className, String methodName, FunctionDescriptor descriptor, Option[] options) { + protected void registerDirectUpcallWithDescriptor(UnresolvedConfigurationCondition configurationCondition, String className, String methodName, FunctionDescriptor descriptor, Option[] options) { + TypeResult typeResult = conditionResolver.resolveCondition(configurationCondition); + if (!typeResult.isPresent()) { + return; + } + Class aClass; try { aClass = imageClassLoader.forName(className); @@ -129,11 +148,16 @@ protected void registerDirectUpcallWithDescriptor(String className, String metho className, methodName, methodType); return; } - accessSupport.registerForDirectUpcall(ConfigurationCondition.alwaysTrue(), target, descriptor, (Object[]) options); + accessSupport.registerForDirectUpcall(typeResult.get(), target, descriptor, (Object[]) options); } @Override - protected void registerDirectUpcallWithoutDescriptor(String className, String methodName, EconomicMap optionsMap) { + protected void registerDirectUpcallWithoutDescriptor(UnresolvedConfigurationCondition configurationCondition, String className, String methodName, EconomicMap optionsMap) { + TypeResult typeResult = conditionResolver.resolveCondition(configurationCondition); + if (!typeResult.isPresent()) { + return; + } + Class aClass; try { aClass = imageClassLoader.forName(className); @@ -167,7 +191,7 @@ protected void registerDirectUpcallWithoutDescriptor(String className, String me for (Pair pair : descriptors) { var options = createUpcallOptions(optionsMap, pair.getLeft()); try { - accessSupport.registerForDirectUpcall(ConfigurationCondition.alwaysTrue(), pair.getRight(), pair.getLeft(), (Object[]) options); + accessSupport.registerForDirectUpcall(typeResult.get(), pair.getRight(), pair.getLeft(), (Object[]) options); } catch (IllegalArgumentException e) { handleMissingElement(e, "Could not register direct upcall stub '%s.%s%s'", className, methodName, pair.getLeft().toMethodType()); } 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 7d92f9ae9ba2..93364b3ec3ca 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 @@ -40,13 +40,10 @@ import java.lang.reflect.Executable; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -140,22 +137,20 @@ public class ForeignFunctionsFeature implements InternalFeature { "jdk.internal.foreign.abi.x64.windows", "jdk.internal.foreign.layout"}); - private boolean sealed = false; - private final RuntimeForeignAccessSupportImpl accessSupport = new RuntimeForeignAccessSupportImpl(); + /** Indicates if the registration of stubs is no longer allowed. */ + private boolean sealed; + private RuntimeForeignAccessSupportImpl accessSupport; - private final Set registeredDowncalls = ConcurrentHashMap.newKeySet(); - private int downcallCount = -1; - - private final Set registeredUpcalls = ConcurrentHashMap.newKeySet(); - private int upcallCount = -1; - - private final Set registeredDirectUpcalls = ConcurrentHashMap.newKeySet(); - private int directUpcallCount = -1; + /** Indicates if at least one stub was registered. */ + private boolean stubsRegistered; private final EconomicSet neverAccessesSharedArena = EconomicSet.create(); private final EconomicSet neverAccessesSharedArenaMethods = EconomicSet.create(); + private AbiUtils abiUtils; + private ForeignFunctionsRuntime foreignFunctionsRuntime; + @Fold public static ForeignFunctionsFeature singleton() { return ImageSingletons.lookup(ForeignFunctionsFeature.class); @@ -195,12 +190,21 @@ private final class RuntimeForeignAccessSupportImpl extends ConditionalConfigura private final Lookup implLookup = ReflectionUtil.readStaticField(MethodHandles.Lookup.class, "IMPL_LOOKUP"); + private final AnalysisMetaAccess analysisMetaAccess; + private final AnalysisUniverse universe; + + RuntimeForeignAccessSupportImpl(AnalysisMetaAccess analysisMetaAccess, AnalysisUniverse analysisUniverse) { + this.analysisMetaAccess = analysisMetaAccess; + this.universe = analysisUniverse; + } + @Override public void registerForDowncall(ConfigurationCondition condition, FunctionDescriptor desc, Linker.Option... options) { checkNotSealed(); try { LinkerOptions linkerOptions = LinkerOptions.forDowncall(desc, options); - registerConditionalConfiguration(condition, _ -> registeredDowncalls.add(new SharedDesc(desc, linkerOptions))); + SharedDesc sharedDesc = new SharedDesc(desc, linkerOptions); + registerConditionalConfiguration(condition, _ -> universe.getBigbang().postTask(_ -> createStub(DowncallStubFactory.INSTANCE, sharedDesc))); } catch (IllegalArgumentException e) { throw UserError.abort(e, "Could not register downcall"); } @@ -211,7 +215,8 @@ public void registerForUpcall(ConfigurationCondition condition, FunctionDescript checkNotSealed(); try { LinkerOptions linkerOptions = LinkerOptions.forUpcall(desc, options); - registerConditionalConfiguration(condition, _ -> registeredUpcalls.add(new SharedDesc(desc, linkerOptions))); + SharedDesc sharedDesc = new SharedDesc(desc, linkerOptions); + registerConditionalConfiguration(condition, _ -> universe.getBigbang().postTask(_ -> createStub(UpcallStubFactory.INSTANCE, sharedDesc))); } catch (IllegalArgumentException e) { throw UserError.abort(e, "Could not register upcall"); } @@ -234,14 +239,65 @@ public void registerForDirectUpcall(ConfigurationCondition condition, MethodHand Executable method = implLookup.revealDirect(Objects.requireNonNull(target)).reflectAs(Executable.class, implLookup); try { LinkerOptions linkerOptions = LinkerOptions.forUpcall(desc, options); - registerConditionalConfiguration(condition, _ -> { + DirectUpcallDesc directUpcallDesc = new DirectUpcallDesc(target, directMethodHandleDesc, desc, linkerOptions); + registerConditionalConfiguration(condition, _ -> universe.getBigbang().postTask(_ -> { RuntimeReflection.register(method); - registeredDirectUpcalls.add(new DirectUpcallDesc(target, directMethodHandleDesc, desc, linkerOptions)); - }); + createStub(UpcallStubFactory.INSTANCE, directUpcallDesc.toSharedDesc()); + createStub(DirectUpcallStubFactory.INSTANCE, directUpcallDesc); + })); } catch (IllegalArgumentException e) { throw UserError.abort(e, "Could not register direct upcall"); } } + + /** + * Generic routine for creating a single stub. This method must be thread-safe because it is + * called during analysis. + * + * @param The descriptor type which instances uniquely identify the stubs at run time + * (e.g. {@link NativeEntryPointInfo}). + * @param The stub descriptor type (e.g. {@link SharedDesc}). + * @param The stub type (e.g. {@link DowncallStub}). + */ + private void createStub(StubFactory factory, T descriptor) { + + /* + * If foreign function calls are generally not supported on this platform, we just + * remember (for reporting) that there was an attempt to create a stub. + */ + if (!ForeignFunctionsRuntime.areFunctionCallsSupported()) { + stubsRegistered = true; + return; + } + + S key = factory.createKey(abiUtils, descriptor); + + /* + * Early test if there is already a stub for 'key'. We do this just to save some + * unnecessary work. However, since an equivalent stub may be created concurrently, + * there is no guarantee that this condition holds until the end of this method + * execution. + */ + if (factory.stubExists(foreignFunctionsRuntime, key)) { + return; + } + + U stub = factory.generateStub(analysisMetaAccess.getWrapped(), universe, key); + AnalysisMethod analysisStub = universe.lookup(stub); + + /* + * If 'factory.registerStub' returns 'true', then the stub created in this method + * execution was actually "consumed" and we need to register it as root method as well. + * If the return value is 'false', the stub was not consumed since a concurrent method + * execution created an equal stub and this execution lost the race. + */ + if (factory.registerStub(foreignFunctionsRuntime, key, new MethodPointer(analysisStub))) { + universe.getBigbang().addRootMethod(analysisStub, false, "Foreign stub, registered in " + ForeignFunctionsFeature.class); + if (factory.registerAsEntryPoint()) { + analysisStub.registerAsNativeEntryPoint(CEntryPointData.createCustomUnpublished()); + } + } + } } private final class SharedArenaSupportImpl implements SharedArenaSupport { @@ -291,8 +347,8 @@ public boolean isInConfiguration(IsInConfigurationAccess access) { @Override public void afterRegistration(AfterRegistrationAccess access) { - AbiUtils abiUtils = AbiUtils.create(); - ForeignFunctionsRuntime foreignFunctionsRuntime = new ForeignFunctionsRuntime(abiUtils); + abiUtils = AbiUtils.create(); + foreignFunctionsRuntime = new ForeignFunctionsRuntime(abiUtils); ImageSingletons.add(AbiUtils.class, abiUtils); ImageSingletons.add(ForeignSupport.class, foreignFunctionsRuntime); @@ -302,6 +358,7 @@ public void afterRegistration(AfterRegistrationAccess access) { @Override public void duringSetup(DuringSetupAccess a) { var access = (FeatureImpl.DuringSetupAccessImpl) a; + accessSupport = new RuntimeForeignAccessSupportImpl(access.getMetaAccess(), access.getUniverse()); ImageSingletons.add(RuntimeForeignAccessSupport.class, accessSupport); if (SubstrateOptions.SharedArenaSupport.getValue()) { assert ForeignAPIPredicates.SharedArenasEnabled.getValue(); @@ -326,37 +383,44 @@ private ConfigurationParser getConfigurationParser(ImageClassLoader imageClassLo } private interface StubFactory { - S createKey(T registeredDescriptor); + S createKey(AbiUtils abiUtils, T registeredDescriptor); + + U generateStub(MetaAccessProvider metaAccessProvider, AnalysisUniverse universe, S stubDescriptor); - U generateStub(S stubDescriptor); + boolean registerStub(ForeignFunctionsRuntime runtime, S stubDescriptor, CFunctionPointer stubPointer); - void registerStub(S stubDescriptor, CFunctionPointer stubPointer); + boolean stubExists(ForeignFunctionsRuntime runtime, S key); + + boolean registerAsEntryPoint(); } - private record DowncallStubFactory(MetaAccessProvider metaAccessProvider) implements StubFactory { + private record DowncallStubFactory() implements StubFactory { + private static final DowncallStubFactory INSTANCE = new DowncallStubFactory(); @Override - public NativeEntryPointInfo createKey(SharedDesc registeredDescriptor) { - return AbiUtils.singleton().makeNativeEntrypoint(registeredDescriptor.fd, registeredDescriptor.options); + public NativeEntryPointInfo createKey(AbiUtils abiUtils, SharedDesc registeredDescriptor) { + return abiUtils.makeNativeEntrypoint(registeredDescriptor.fd, registeredDescriptor.options); } @Override - public DowncallStub generateStub(NativeEntryPointInfo stubDescriptor) { + public DowncallStub generateStub(MetaAccessProvider metaAccessProvider, AnalysisUniverse universe, NativeEntryPointInfo stubDescriptor) { return new DowncallStub(stubDescriptor, metaAccessProvider); } @Override - public void registerStub(NativeEntryPointInfo stubDescriptor, CFunctionPointer stubPointer) { - ForeignFunctionsRuntime.singleton().addDowncallStubPointer(stubDescriptor, stubPointer); + public boolean registerStub(ForeignFunctionsRuntime runtime, NativeEntryPointInfo stubDescriptor, CFunctionPointer stubPointer) { + return runtime.addDowncallStubPointer(stubDescriptor, stubPointer); + } + + @Override + public boolean stubExists(ForeignFunctionsRuntime runtime, NativeEntryPointInfo key) { + return runtime.downcallStubExists(key); } - } - private void createDowncallStubs(FeatureImpl.BeforeAnalysisAccessImpl access) { - this.downcallCount = createStubs( - registeredDowncalls, - access, - false, - new DowncallStubFactory(access.getMetaAccess().getWrapped())).size(); + @Override + public boolean registerAsEntryPoint() { + return false; + } } private record DirectUpcall(DirectMethodHandleDesc targetDesc, MethodHandle bindings, JavaEntryPointInfo jep) { @@ -374,21 +438,32 @@ public int hashCode() { } } - private record UpcallStubFactory(AnalysisUniverse universe, MetaAccessProvider metaAccessProvider) implements StubFactory { + private record UpcallStubFactory() implements StubFactory { + private static final UpcallStubFactory INSTANCE = new UpcallStubFactory(); @Override - public JavaEntryPointInfo createKey(SharedDesc registeredDescriptor) { - return AbiUtils.singleton().makeJavaEntryPoint(registeredDescriptor.fd, registeredDescriptor.options); + public JavaEntryPointInfo createKey(AbiUtils abiUtils, SharedDesc registeredDescriptor) { + return abiUtils.makeJavaEntryPoint(registeredDescriptor.fd, registeredDescriptor.options); } @Override - public UpcallStub generateStub(JavaEntryPointInfo stubDescriptor) { + public UpcallStub generateStub(MetaAccessProvider metaAccessProvider, AnalysisUniverse universe, JavaEntryPointInfo stubDescriptor) { return LowLevelUpcallStub.make(stubDescriptor, universe, metaAccessProvider); } @Override - public void registerStub(JavaEntryPointInfo stubDescriptor, CFunctionPointer stubPointer) { - ForeignFunctionsRuntime.singleton().addUpcallStubPointer(stubDescriptor, stubPointer); + public boolean registerStub(ForeignFunctionsRuntime runtime, JavaEntryPointInfo stubDescriptor, CFunctionPointer stubPointer) { + return runtime.addUpcallStubPointer(stubDescriptor, stubPointer); + } + + @Override + public boolean stubExists(ForeignFunctionsRuntime runtime, JavaEntryPointInfo key) { + return runtime.upcallStubExists(key); + } + + @Override + public boolean registerAsEntryPoint() { + return true; } } @@ -402,18 +477,13 @@ public void registerStub(JavaEntryPointInfo stubDescriptor, CFunctionPointer stu * intrinsification. */ private static final class DirectUpcallStubFactory implements StubFactory { + private static final DirectUpcallStubFactory INSTANCE = new DirectUpcallStubFactory(); private static final String COULD_NOT_EXTRACT_METHOD_HANDLE_FOR_UPCALL = "Could not extract method handle for upcall."; - private final AnalysisUniverse universe; - private final MetaAccessProvider metaAccessProvider; private final Method arrangeUpcallMethod; private final Method adaptUpcallForIMRMethod; - private final Set registeredUpcalls; - DirectUpcallStubFactory(AnalysisUniverse universe, MetaAccessProvider metaAccessProvider, Set registeredUpcalls) { - this.universe = universe; - this.metaAccessProvider = metaAccessProvider; - this.registeredUpcalls = registeredUpcalls; + DirectUpcallStubFactory() { this.arrangeUpcallMethod = ReflectionUtil.lookupMethod(LINKER.getClass(), "arrangeUpcall", MethodType.class, FunctionDescriptor.class, LinkerOptions.class); this.adaptUpcallForIMRMethod = ReflectionUtil.lookupMethod(SharedUtils.class, "adaptUpcallForIMR", MethodHandle.class, boolean.class); } @@ -421,8 +491,7 @@ private static final class DirectUpcallStubFactory implements StubFactory expectedFieldType, Field[] d } } - private void createUpcallStubs(FeatureImpl.BeforeAnalysisAccessImpl access) { - Map directUpcallStubs = createStubs(registeredDirectUpcalls, access, true, - new DirectUpcallStubFactory(access.getUniverse(), access.getMetaAccess().getWrapped(), registeredUpcalls)); - this.directUpcallCount = directUpcallStubs.size(); - registeredDirectUpcalls.clear(); - - Map upcallStubs = createStubs(registeredUpcalls, access, true, - new UpcallStubFactory(access.getUniverse(), access.getMetaAccess().getWrapped())); - this.upcallCount = upcallStubs.size(); - registeredUpcalls.clear(); - } - private static final Linker LINKER = Linker.nativeLinker(); - private static Map createStubs( - Iterable sources, - FeatureImpl.BeforeAnalysisAccessImpl access, - boolean registerAsEntryPoints, - StubFactory factory) { - - Map created = new HashMap<>(); - - for (T source : sources) { - S key = factory.createKey(source); - - if (!created.containsKey(key)) { - U stub = factory.generateStub(key); - AnalysisMethod analysisStub = access.getUniverse().lookup(stub); - access.getBigBang().addRootMethod(analysisStub, false, "Foreign stub, registered in " + ForeignFunctionsFeature.class); - if (registerAsEntryPoints) { - analysisStub.registerAsNativeEntryPoint(CEntryPointData.createCustomUnpublished()); - } - created.put(key, stub); - factory.registerStub(key, new MethodPointer(analysisStub)); - } - } - return created; - } - private static final String JLI_PACKAGE = "java.lang.invoke"; /** @@ -584,10 +625,11 @@ private static String platform() { @Override public void beforeAnalysis(BeforeAnalysisAccess a) { var access = (FeatureImpl.BeforeAnalysisAccessImpl) a; - sealed = true; AbiUtils.singleton().checkLibrarySupport(); + accessSupport.setAnalysisAccess(a); + for (String simpleName : VAR_HANDLE_SEGMENT_ACCESSORS) { Class varHandleSegmentAsXClass = ReflectionUtil.lookupClass(JLI_PACKAGE + '.' + simpleName); access.registerSubtypeReachabilityHandler(ForeignFunctionsFeature::registerVarHandleMethodsForReflection, varHandleSegmentAsXClass); @@ -614,23 +656,6 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { access.registerAsRoot(ReflectionUtil.lookupMethod(ForeignFunctionsRuntime.class, "captureCallState", int.class, CIntPointer.class), false, "Runtime support, registered in " + ForeignFunctionsFeature.class); - 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()); - /* * Even if there is no instance of MemorySessionImpl, we will kill the field location of * 'MemorySessionImpl.state' which may trigger registration of the declaring type after the @@ -644,6 +669,18 @@ public void beforeAnalysis(BeforeAnalysisAccess a) { } } + @Override + public void afterAnalysis(AfterAnalysisAccess access) { + sealed = true; + if (!ForeignFunctionsRuntime.areFunctionCallsSupported() && stubsRegistered) { + assert getCreatedDowncallStubsCount() == 0; + assert getCreatedUpcallStubsCount() == 0; + assert getCreatedDirectUpcallStubsCount() == 0; + LogUtils.warning("Registered down- and upcall stubs will be ignored because calling foreign functions is currently not supported on platform: %s", platform()); + } + ProgressReporter.singleton().setForeignFunctionsInfo(getCreatedDowncallStubsCount(), getCreatedUpcallStubsCount()); + } + /** * Remember a set of known methods that frequently appear in scoped memory access methods as * callees. Not all of those callees have to be inlined because some of them are SVM specific @@ -716,23 +753,20 @@ public void registerForeignCalls(SubstrateForeignCallsProvider foreignCalls) { foreignCalls.register(ForeignFunctionsRuntime.CAPTURE_CALL_STATE); } - /* Testing interface */ + /* Testing and reporting interface */ public int getCreatedDowncallStubsCount() { assert sealed; - assert downcallCount >= 0; - return downcallCount; + return foreignFunctionsRuntime.getDowncallStubsCount(); } public int getCreatedUpcallStubsCount() { assert sealed; - assert upcallCount >= 0; - return upcallCount; + return foreignFunctionsRuntime.getUpcallStubsCount(); } public int getCreatedDirectUpcallStubsCount() { assert sealed; - assert directUpcallCount >= 0; - return directUpcallCount; + return foreignFunctionsRuntime.getDirectUpcallStubsCount(); } }