diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodConfiguration.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodConfiguration.java deleted file mode 100644 index 6bdaf4aa00b6..000000000000 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodConfiguration.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2023, 2023, 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.bootstrap; - -import java.lang.invoke.LambdaMetafactory; -import java.lang.invoke.MethodHandle; -import java.lang.invoke.MethodHandles; -import java.lang.invoke.MethodType; -import java.lang.invoke.StringConcatFactory; -import java.lang.invoke.TypeDescriptor; -import java.lang.reflect.Executable; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.lang.runtime.ObjectMethods; -import java.lang.runtime.SwitchBootstraps; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -import org.graalvm.nativeimage.ImageSingletons; - -import com.oracle.svm.core.feature.AutomaticallyRegisteredFeature; -import com.oracle.svm.core.feature.InternalFeature; -import com.oracle.svm.core.traits.BuiltinTraits.BuildtimeAccessOnly; -import com.oracle.svm.core.traits.BuiltinTraits.NoLayeredCallbacks; -import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent; -import com.oracle.svm.core.traits.SingletonTraits; -import com.oracle.svm.util.ReflectionUtil; - -import jdk.vm.ci.meta.ResolvedJavaMethod; - -/** - * Class storing a list of bootstrap methods that are allowed to be executed at build time. Those - * methods are trusted methods from the JDK. Additionally used to register methods used by some - * bootstrap methods for runtime reflection. - */ -@SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = NoLayeredCallbacks.class, layeredInstallationKind = Independent.class) -@AutomaticallyRegisteredFeature -public class BootstrapMethodConfiguration implements InternalFeature { - - public record BootstrapMethodRecord(int bci, int cpi, ResolvedJavaMethod method) { - } - - /* - * Map used to cache the BootstrapMethodInfo and reuse it for duplicated bytecode, avoiding - * execution of the bootstrap method for the same bci and method pair. This can happen during - * bytecode parsing as some blocks are duplicated, or for methods that are parsed multiple times - * (see MultiMethod). - */ - private final ConcurrentMap bootstrapMethodInfoCache = new ConcurrentHashMap<>(); - private final Set indyBuildTimeAllowList; - private final Set condyBuildTimeAllowList; - private final Method metafactory; - private final Method altMetafactory; - - public static BootstrapMethodConfiguration singleton() { - return ImageSingletons.lookup(BootstrapMethodConfiguration.class); - } - - public BootstrapMethodConfiguration() { - /* - * Bootstrap method used for Lambdas. Executing this method at run time implies defining - * hidden class at run time, which is unsupported. - */ - metafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "metafactory", MethodHandles.Lookup.class, String.class, MethodType.class, MethodType.class, MethodHandle.class, - MethodType.class); - /* Alternate version of LambdaMetafactory.metafactory. */ - altMetafactory = ReflectionUtil.lookupMethod(LambdaMetafactory.class, "altMetafactory", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class); - - /* - * Bootstrap method used to optimize String concatenation. Executing it at run time - * currently causes a StackOverFlow error as it infinitely calls itself. - */ - Method makeConcat = ReflectionUtil.lookupMethod(StringConcatFactory.class, "makeConcat", MethodHandles.Lookup.class, String.class, MethodType.class); - /* Alternate version of StringConcatFactory.makeConcat with constant arguments. */ - Method makeConcatWithConstants = ReflectionUtil.lookupMethod(StringConcatFactory.class, "makeConcatWithConstants", MethodHandles.Lookup.class, String.class, MethodType.class, String.class, - Object[].class); - - /* Causes deadlock in Permission feature. */ - Method bootstrap = ReflectionUtil.lookupMethod(ObjectMethods.class, "bootstrap", MethodHandles.Lookup.class, String.class, TypeDescriptor.class, Class.class, String.class, - MethodHandle[].class); - - /* - * Bootstrap methods used for switch statements. Executing these methods at run time implies - * defining hidden classes at run time, which is unsupported. - */ - Method typeSwitch = ReflectionUtil.lookupMethod(SwitchBootstraps.class, "typeSwitch", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class); - Method enumSwitch = ReflectionUtil.lookupMethod(SwitchBootstraps.class, "enumSwitch", MethodHandles.Lookup.class, String.class, MethodType.class, Object[].class); - - /* Bootstrap method used for retrieving the value of static final processors. */ - indyBuildTimeAllowList = Set.of(metafactory, altMetafactory, makeConcat, makeConcatWithConstants, bootstrap, typeSwitch, enumSwitch); - - /* Set of bootstrap methods for constant dynamic allowed at build time is empty for now */ - condyBuildTimeAllowList = Set.of(); - } - - /** - * Check if the provided method is allowed to be executed at build time. - */ - public boolean isIndyAllowedAtBuildTime(Executable method) { - return method != null && indyBuildTimeAllowList.contains(method); - } - - public boolean isMetafactory(Executable method) { - return method != null && (method.equals(metafactory) || method.equals(altMetafactory)); - } - - /** - * Check if the provided method is allowed to be executed at build time. - */ - public boolean isCondyAllowedAtBuildTime(Executable method) { - return method != null && (condyBuildTimeAllowList.contains(method) || isProxyCondy(method)); - } - - /** - * Every {@link Proxy} class has its own bootstrap method that is used for a constant dynamic. - */ - private static boolean isProxyCondy(Executable method) { - return Proxy.isProxyClass(method.getDeclaringClass()) && method.getName().equals("$getMethod"); - } - - public ConcurrentMap getBootstrapMethodInfoCache() { - return bootstrapMethodInfoCache; - } -} diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodInfoCache.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodInfoCache.java new file mode 100644 index 000000000000..98c1711afa8a --- /dev/null +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/bootstrap/BootstrapMethodInfoCache.java @@ -0,0 +1,69 @@ +/* + * 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.bootstrap; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.graalvm.nativeimage.ImageSingletons; + +import com.oracle.svm.core.feature.AutomaticallyRegisteredImageSingleton; +import com.oracle.svm.core.traits.BuiltinTraits; +import com.oracle.svm.core.traits.BuiltinTraits.BuildtimeAccessOnly; +import com.oracle.svm.core.traits.SingletonLayeredInstallationKind.Independent; +import com.oracle.svm.core.traits.SingletonTraits; + +import jdk.vm.ci.meta.ResolvedJavaMethod; + +/** + * This image singleton is used to cache the {@link BootstrapMethodInfo} computed at run time to + * make sure they are only computed once. + */ +@SingletonTraits(access = BuildtimeAccessOnly.class, layeredCallbacks = BuiltinTraits.NoLayeredCallbacks.class, layeredInstallationKind = Independent.class) +@AutomaticallyRegisteredImageSingleton +public class BootstrapMethodInfoCache { + + /** + * Map used to cache the {@link BootstrapMethodInfo} and reuse it for duplicated bytecode, + * avoiding execution of the bootstrap method for the same bci and method pair. This can happen + * during bytecode parsing as some blocks are duplicated, or for methods that are parsed + * multiple times (see MultiMethod). + */ + private final ConcurrentMap bootstrapMethodInfoCache = new ConcurrentHashMap<>(); + + /** + * The key of the cache. + */ + public record BootstrapMethodRecord(int bci, int cpi, ResolvedJavaMethod method) { + } + + public static BootstrapMethodInfoCache singleton() { + return ImageSingletons.lookup(BootstrapMethodInfoCache.class); + } + + public ConcurrentMap getBootstrapMethodInfoCache() { + return bootstrapMethodInfoCache; + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java index 7fa6ebfe27d3..b45ae59865e9 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/FeatureImpl.java @@ -88,6 +88,7 @@ import com.oracle.svm.core.util.VMError; import com.oracle.svm.hosted.ameta.FieldValueInterceptionSupport; import com.oracle.svm.hosted.analysis.Inflation; +import com.oracle.svm.hosted.bootstrap.BootstrapMethodConfiguration; import com.oracle.svm.hosted.c.NativeLibraries; import com.oracle.svm.hosted.code.CEntryPointData; import com.oracle.svm.hosted.code.CompileQueue.CompileTask; @@ -438,6 +439,30 @@ public void registerClassReachabilityListener(BiConsumer buildTimeIndy = new HashSet<>(); + private final Set buildTimeCondy = new HashSet<>(); + + public static BootstrapMethodConfiguration singleton() { + return ImageSingletons.lookup(BootstrapMethodConfiguration.class); + } + + @Override + public void duringSetup(DuringSetupAccess access) { + MetaAccessProvider metaAccess = GraalAccess.getOriginalProviders().getMetaAccess(); + /* + * Bootstrap method used for Lambdas. Executing this method at run time implies defining + * hidden class at run time, which is unsupported. + */ + ResolvedJavaType lambdaMetaFactory = metaAccess.lookupJavaType(LambdaMetafactory.class); + ResolvedJavaMethod metafactory = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, lambdaMetaFactory, "metafactory", MethodHandles.Lookup.class, String.class, MethodType.class, + MethodType.class, MethodHandle.class, MethodType.class); + /* Alternate version of LambdaMetafactory.metafactory. */ + ResolvedJavaMethod altMetafactory = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, lambdaMetaFactory, "altMetafactory", MethodHandles.Lookup.class, String.class, MethodType.class, + Object[].class); + buildTimeIndy.add(metafactory); + buildTimeIndy.add(altMetafactory); + + /* + * Bootstrap method used to optimize String concatenation. Executing it at run time + * currently causes a StackOverFlow error as it infinitely calls itself. + */ + ResolvedJavaType stringConcatFactory = metaAccess.lookupJavaType(StringConcatFactory.class); + ResolvedJavaMethod makeConcat = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, stringConcatFactory, "makeConcat", MethodHandles.Lookup.class, String.class, MethodType.class); + /* Alternate version of StringConcatFactory.makeConcat with constant arguments. */ + ResolvedJavaMethod makeConcatWithConstants = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, stringConcatFactory, "makeConcatWithConstants", MethodHandles.Lookup.class, String.class, + MethodType.class, String.class, Object[].class); + buildTimeIndy.add(makeConcat); + buildTimeIndy.add(makeConcatWithConstants); + + /* Causes deadlock in Permission feature. */ + ResolvedJavaType objectMethods = metaAccess.lookupJavaType(ObjectMethods.class); + ResolvedJavaMethod bootstrap = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, objectMethods, "bootstrap", MethodHandles.Lookup.class, String.class, TypeDescriptor.class, Class.class, + String.class, MethodHandle[].class); + buildTimeIndy.add(bootstrap); + + /* + * Bootstrap methods used for switch statements. Executing these methods at run time implies + * defining hidden classes at run time, which is unsupported. + */ + ResolvedJavaType switchBootstraps = metaAccess.lookupJavaType(SwitchBootstraps.class); + ResolvedJavaMethod typeSwitch = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, switchBootstraps, "typeSwitch", MethodHandles.Lookup.class, String.class, MethodType.class, + Object[].class); + ResolvedJavaMethod enumSwitch = JVMCIReflectionUtil.getUniqueDeclaredMethod(metaAccess, switchBootstraps, "enumSwitch", MethodHandles.Lookup.class, String.class, MethodType.class, + Object[].class); + buildTimeIndy.add(typeSwitch); + buildTimeIndy.add(enumSwitch); + } + + public void addBuildTimeIndy(ResolvedJavaMethod method) { + buildTimeIndy.add(method); + } + + public void addBuildTimeCondy(ResolvedJavaMethod method) { + buildTimeCondy.add(method); + } + + /** + * Check if the provided method is allowed to be executed at build time. + */ + public boolean isIndyAllowedAtBuildTime(ResolvedJavaMethod method) { + ResolvedJavaMethod m = getWrapped(method); + return m != null && buildTimeIndy.contains(m); + } + + /** + * Check if the provided method is allowed to be executed at build time. + */ + public boolean isCondyAllowedAtBuildTime(ResolvedJavaMethod method) { + ResolvedJavaMethod m = getWrapped(method); + return m != null && (buildTimeCondy.contains(m) || isProxyCondy(m)); + } + + private static ResolvedJavaMethod getWrapped(ResolvedJavaMethod method) { + if (method instanceof AnalysisMethod analysisMethod) { + return analysisMethod.getWrapped(); + } else { + return method; + } + } + + /** + * Every {@link Proxy} class has its own bootstrap method that is used for a constant dynamic. + */ + private static boolean isProxyCondy(ResolvedJavaMethod method) { + return ProxyRenamingSubstitutionProcessor.isProxyType(method.getDeclaringClass()) && method.getName().equals("$getMethod"); + } +} diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java index 0bf58f4da66f..6e8788548801 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/AnalysisGraphBuilderPhase.java @@ -30,10 +30,9 @@ import java.lang.invoke.WrongMethodTypeException; import java.util.List; -import com.oracle.svm.util.OriginalMethodProvider; import com.oracle.graal.pointsto.meta.AnalysisMethod; -import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration; import com.oracle.svm.hosted.SVMHost; +import com.oracle.svm.hosted.bootstrap.BootstrapMethodConfiguration; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import com.oracle.svm.hosted.dynamicaccessinference.ConstantExpressionRegistry; import com.oracle.svm.util.ModuleSupport; @@ -169,8 +168,7 @@ protected void genInvokeDynamic(int cpi, int opcode) { } JavaMethod calleeMethod = lookupMethodInPool(cpi, opcode); - if (bootstrap == null || calleeMethod instanceof ResolvedJavaMethod || - BootstrapMethodConfiguration.singleton().isIndyAllowedAtBuildTime(OriginalMethodProvider.getJavaMethod(bootstrap.getMethod()))) { + if (bootstrap == null || calleeMethod instanceof ResolvedJavaMethod || BootstrapMethodConfiguration.singleton().isIndyAllowedAtBuildTime(bootstrap.getMethod())) { super.genInvokeDynamic(cpi, opcode); return; } diff --git a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java index d2bf8b63d738..d92fc9820c42 100644 --- a/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java +++ b/substratevm/src/com.oracle.svm.hosted/src/com/oracle/svm/hosted/phases/SharedGraphBuilderPhase.java @@ -52,10 +52,10 @@ import com.oracle.graal.pointsto.meta.AnalysisType; import com.oracle.svm.common.meta.MultiMethod; import com.oracle.svm.core.ForeignSupport; -import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration; -import com.oracle.svm.core.bootstrap.BootstrapMethodConfiguration.BootstrapMethodRecord; import com.oracle.svm.core.bootstrap.BootstrapMethodInfo; import com.oracle.svm.core.bootstrap.BootstrapMethodInfo.ExceptionWrapper; +import com.oracle.svm.core.bootstrap.BootstrapMethodInfoCache; +import com.oracle.svm.core.bootstrap.BootstrapMethodInfoCache.BootstrapMethodRecord; import com.oracle.svm.core.deopt.DeoptimizationSupport; import com.oracle.svm.core.graal.nodes.DeoptEntryBeginNode; import com.oracle.svm.core.graal.nodes.DeoptEntryNode; @@ -72,6 +72,7 @@ import com.oracle.svm.hosted.ExceptionSynthesizer; import com.oracle.svm.hosted.LinkAtBuildTimeSupport; import com.oracle.svm.hosted.SharedArenaSupport; +import com.oracle.svm.hosted.bootstrap.BootstrapMethodConfiguration; import com.oracle.svm.hosted.code.FactoryMethodSupport; import com.oracle.svm.hosted.code.SubstrateCompilationDirectives; import com.oracle.svm.hosted.nodes.DeoptProxyNode; @@ -1442,7 +1443,7 @@ private Object loadConstantDynamic(int cpi, int opcode) { } } - if (!BootstrapMethodConfiguration.singleton().isCondyAllowedAtBuildTime(bootstrapMethod)) { + if (!BootstrapMethodConfiguration.singleton().isCondyAllowedAtBuildTime(bootstrap.getMethod())) { int parameterLength = bootstrap.getMethod().getParameters().length; List staticArguments = bootstrap.getStaticArguments(); boolean isVarargs = bootstrap.getMethod().isVarArgs(); @@ -1513,7 +1514,7 @@ protected Object resolveLinkedObject(int bci, int cpi, int opcode, BootstrapMeth /* Step 1: Initialize the BootstrapMethodInfo. */ BootstrapMethodRecord bootstrapMethodRecord = new BootstrapMethodRecord(bci, cpi, ((AnalysisMethod) method).getMultiMethod(MultiMethod.ORIGINAL_METHOD)); - BootstrapMethodInfo bootstrapMethodInfo = BootstrapMethodConfiguration.singleton().getBootstrapMethodInfoCache().computeIfAbsent(bootstrapMethodRecord, + BootstrapMethodInfo bootstrapMethodInfo = BootstrapMethodInfoCache.singleton().getBootstrapMethodInfoCache().computeIfAbsent(bootstrapMethodRecord, _ -> new BootstrapMethodInfo()); ConstantNode bootstrapMethodInfoNode = ConstantNode.forConstant(getSnippetReflection().forObject(bootstrapMethodInfo), getMetaAccess(), getGraph());